Skip to content
File

Blob: src/cloudflare/internal/test/ai/ai-mock.js

javascript170 lines
1// Copyright (c) 2024 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5export default {
6 async fetch(request, env, ctx) {
7 const url = new URL(request.url);
8 
9 if (url.pathname === '/ai-api/models/search') {
10 return Response.json({
11 success: true,
12 result: [
13 {
14 id: 'f8703a00-ed54-4f98-bdc3-cd9a813286f3',
15 source: 1,
16 name: '@cf/qwen/qwen1.5-0.5b-chat',
17 description:
18 'Qwen1.5 is the improved version of Qwen, the large language model series developed by Alibaba Cloud.',
19 task: {
20 id: 'c329a1f9-323d-4e91-b2aa-582dd4188d34',
21 name: 'Text Generation',
22 description:
23 'Family of generative text models, such as large language models (LLM), that can be adapted for a variety of natural language tasks.',
24 },
25 tags: [],
26 properties: [
27 {
28 property_id: 'debug',
29 value: request.url,
30 },
31 ],
32 },
33 ],
34 });
35 }
36 
37 const reqContentType = request.headers.get('content-type');
38 
39 let data = {};
40 if (reqContentType === 'application/json') {
41 data = await request.json();
42 } else {
43 data = {
44 inputs: request.body,
45 options: Object.fromEntries(url.searchParams),
46 };
47 }
48 
49 const modelName = request.headers.get('cf-consn-model-id');
50 const isWebsocket = request.headers.get('Upgrade') === 'websocket';
51 
52 const respHeaders = {
53 'cf-ai-req-id': '3a1983d7-1ddd-453a-ab75-c4358c91b582',
54 };
55 
56 if (modelName === 'blobResponseModel') {
57 let utf8Encode = new TextEncoder();
58 utf8Encode.encode('hello world');
59 
60 return new Response(utf8Encode, {
61 headers: respHeaders,
62 });
63 }
64 
65 if (modelName === 'rawInputs') {
66 return Response.json(
67 {
68 ...data,
69 requestUrl: request.url,
70 },
71 {
72 headers: respHeaders,
73 }
74 );
75 }
76 
77 if (modelName === 'readableStreamIputs') {
78 return Response.json(
79 {
80 inputs: {},
81 options: { ...data.options },
82 requestUrl: request.url,
83 },
84 {
85 headers: respHeaders,
86 }
87 );
88 }
89 
90 if (modelName === 'formDataInputs') {
91 return Response.json(
92 {
93 inputs: {},
94 options: { ...data.options },
95 requestUrl: request.url,
96 },
97 {
98 headers: respHeaders,
99 }
100 );
101 }
102 
103 if (modelName === 'hangingModel') {
104 // Never resolve — used to test abort signal on an in-flight request.
105 // We can't just return a hanging promise because the runtime actually detects that and
106 // errors. We use an excessively long timeout instead so that the runtime thinks that it's
107 // actually waiting for something.
108 await scheduler.wait(1e8);
109 }
110 
111 if (modelName === 'inputErrorModel') {
112 return Response.json(
113 {
114 internalCode: 1001,
115 message: 'InvalidInput: prompt and messages are mutually exclusive',
116 name: 'InvalidInput',
117 description: 'prompt and messages are mutually exclusive',
118 },
119 {
120 status: 400,
121 headers: {
122 'content-type': 'application/json',
123 ...respHeaders,
124 },
125 }
126 );
127 }
128 
129 // Handle websocket requests
130 if (isWebsocket && modelName === '@cf/test/websocket') {
131 // For websocket requests, extract data from URL 'body' parameter
132 const bodyParam = url.searchParams.get('body');
133 let websocketData = {};
134 if (bodyParam) {
135 try {
136 // The AI API doesn't URL-encode the body parameter, so parse directly
137 websocketData = JSON.parse(bodyParam);
138 } catch (_e) {
139 websocketData = { inputs: {}, options: {} };
140 }
141 }
142 
143 return Response.json(
144 {
145 ...websocketData,
146 requestUrl: request.url,
147 headers: {
148 'cf-consn-sdk-version': '2.0.0',
149 'cf-consn-model-id': '@cf/test/websocket',
150 upgrade: 'websocket',
151 },
152 },
153 {
154 headers: respHeaders,
155 }
156 );
157 }
158 
159 return Response.json(
160 { response: 'model response' },
161 {
162 headers: {
163 'content-type': 'application/json',
164 ...respHeaders,
165 },
166 }
167 );
168 },
169};