workers-ai-provider 0.7.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { type EmbeddingModelV1, TooManyEmbeddingValuesForCallError } from "@ai-sdk/provider";
1
+ import { type EmbeddingModelV2, TooManyEmbeddingValuesForCallError } from "@ai-sdk/provider";
2
2
  import type { StringLike } from "./utils";
3
3
  import type { EmbeddingModels } from "./workersai-models";
4
4
 
@@ -19,12 +19,12 @@ export type WorkersAIEmbeddingSettings = {
19
19
  [key: string]: StringLike;
20
20
  };
21
21
 
22
- export class WorkersAIEmbeddingModel implements EmbeddingModelV1<string> {
22
+ export class WorkersAIEmbeddingModel implements EmbeddingModelV2<string> {
23
23
  /**
24
24
  * Semantic version of the {@link EmbeddingModelV1} specification implemented
25
25
  * by this class. It never changes.
26
26
  */
27
- readonly specificationVersion = "v1";
27
+ readonly specificationVersion = "v2";
28
28
  readonly modelId: EmbeddingModels;
29
29
  private readonly config: WorkersAIEmbeddingConfig;
30
30
  private readonly settings: WorkersAIEmbeddingSettings;
@@ -38,7 +38,7 @@ export class WorkersAIEmbeddingModel implements EmbeddingModelV1<string> {
38
38
 
39
39
  get maxEmbeddingsPerCall(): number {
40
40
  // https://developers.cloudflare.com/workers-ai/platform/limits/#text-embeddings
41
- const maxEmbeddingsPerCall = this.modelId === "@cf/baai/bge-large-en-v1.5" ? 1500 : 3000;
41
+ const maxEmbeddingsPerCall = 3000;
42
42
  return this.settings.maxEmbeddingsPerCall ?? maxEmbeddingsPerCall;
43
43
  }
44
44
 
@@ -58,8 +58,8 @@ export class WorkersAIEmbeddingModel implements EmbeddingModelV1<string> {
58
58
 
59
59
  async doEmbed({
60
60
  values,
61
- }: Parameters<EmbeddingModelV1<string>["doEmbed"]>[0]): Promise<
62
- Awaited<ReturnType<EmbeddingModelV1<string>["doEmbed"]>>
61
+ }: Parameters<EmbeddingModelV2<string>["doEmbed"]>[0]): Promise<
62
+ Awaited<ReturnType<EmbeddingModelV2<string>["doEmbed"]>>
63
63
  > {
64
64
  if (values.length > this.maxEmbeddingsPerCall) {
65
65
  throw new TooManyEmbeddingValuesForCallError({
@@ -74,15 +74,17 @@ export class WorkersAIEmbeddingModel implements EmbeddingModelV1<string> {
74
74
 
75
75
  const response = await this.config.binding.run(
76
76
  this.modelId,
77
- // @ts-ignore: Error introduced with "@cloudflare/workers-types": "^4.20250617.0"
78
77
  {
79
78
  text: values,
80
79
  },
81
- { gateway: this.config.gateway ?? gateway, ...passthroughOptions },
80
+ {
81
+ gateway: this.config.gateway ?? gateway,
82
+ ...passthroughOptions,
83
+ tags: [],
84
+ },
82
85
  );
83
86
 
84
87
  return {
85
- // @ts-ignore: Error introduced with "@cloudflare/workers-types": "^4.20250617.0"
86
88
  embeddings: response.data,
87
89
  };
88
90
  }
@@ -1,9 +1,9 @@
1
- import {
2
- type LanguageModelV1,
3
- type LanguageModelV1CallWarning,
4
- type LanguageModelV1StreamPart,
5
- UnsupportedFunctionalityError,
1
+ import type {
2
+ LanguageModelV2,
3
+ LanguageModelV2CallWarning,
4
+ LanguageModelV2StreamPart,
6
5
  } from "@ai-sdk/provider";
6
+ import { generateId } from "ai";
7
7
  import { convertToWorkersAIChatMessages } from "./convert-to-workersai-chat-messages";
8
8
  import { mapWorkersAIFinishReason } from "./map-workersai-finish-reason";
9
9
  import { mapWorkersAIUsage } from "./map-workersai-usage";
@@ -23,10 +23,14 @@ type WorkersAIChatConfig = {
23
23
  gateway?: GatewayOptions;
24
24
  };
25
25
 
26
- export class WorkersAIChatLanguageModel implements LanguageModelV1 {
27
- readonly specificationVersion = "v1";
26
+ export class WorkersAIChatLanguageModel implements LanguageModelV2 {
27
+ readonly specificationVersion = "v2";
28
28
  readonly defaultObjectGenerationMode = "json";
29
29
 
30
+ readonly supportedUrls: Record<string, RegExp[]> | PromiseLike<Record<string, RegExp[]>> = {
31
+ // Empty
32
+ };
33
+
30
34
  readonly modelId: TextGenerationModels;
31
35
  readonly settings: WorkersAIChatSettings;
32
36
 
@@ -47,17 +51,19 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
47
51
  }
48
52
 
49
53
  private getArgs({
50
- mode,
51
- maxTokens,
54
+ responseFormat,
55
+ tools,
56
+ toolChoice,
57
+ maxOutputTokens,
52
58
  temperature,
53
59
  topP,
54
60
  frequencyPenalty,
55
61
  presencePenalty,
56
62
  seed,
57
- }: Parameters<LanguageModelV1["doGenerate"]>[0]) {
58
- const type = mode.type;
63
+ }: Parameters<LanguageModelV2["doGenerate"]>[0]) {
64
+ const type = responseFormat?.type ?? "text";
59
65
 
60
- const warnings: LanguageModelV1CallWarning[] = [];
66
+ const warnings: LanguageModelV2CallWarning[] = [];
61
67
 
62
68
  if (frequencyPenalty != null) {
63
69
  warnings.push({
@@ -75,7 +81,7 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
75
81
 
76
82
  const baseArgs = {
77
83
  // standardized settings:
78
- max_tokens: maxTokens,
84
+ max_tokens: maxOutputTokens,
79
85
  // model id:
80
86
  model: this.modelId,
81
87
  random_seed: seed,
@@ -87,19 +93,22 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
87
93
  };
88
94
 
89
95
  switch (type) {
90
- case "regular": {
96
+ case "text": {
91
97
  return {
92
- args: { ...baseArgs, ...prepareToolsAndToolChoice(mode) },
98
+ args: {
99
+ ...baseArgs,
100
+ ...prepareToolsAndToolChoice(tools, toolChoice),
101
+ },
93
102
  warnings,
94
103
  };
95
104
  }
96
105
 
97
- case "object-json": {
106
+ case "json": {
98
107
  return {
99
108
  args: {
100
109
  ...baseArgs,
101
110
  response_format: {
102
- json_schema: mode.schema,
111
+ json_schema: responseFormat?.type === "json" && responseFormat.schema,
103
112
  type: "json_schema",
104
113
  },
105
114
  tools: undefined,
@@ -108,25 +117,6 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
108
117
  };
109
118
  }
110
119
 
111
- case "object-tool": {
112
- return {
113
- args: {
114
- ...baseArgs,
115
- tool_choice: "any",
116
- tools: [{ function: mode.tool, type: "function" }],
117
- },
118
- warnings,
119
- };
120
- }
121
-
122
- // @ts-expect-error - this is unreachable code
123
- // TODO: fixme
124
- case "object-grammar": {
125
- throw new UnsupportedFunctionalityError({
126
- functionality: "object-grammar mode",
127
- });
128
- }
129
-
130
120
  default: {
131
121
  const exhaustiveCheck = type satisfies never;
132
122
  throw new Error(`Unsupported type: ${exhaustiveCheck}`);
@@ -135,8 +125,8 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
135
125
  }
136
126
 
137
127
  async doGenerate(
138
- options: Parameters<LanguageModelV1["doGenerate"]>[0],
139
- ): Promise<Awaited<ReturnType<LanguageModelV1["doGenerate"]>>> {
128
+ options: Parameters<LanguageModelV2["doGenerate"]>[0],
129
+ ): Promise<Awaited<ReturnType<LanguageModelV2["doGenerate"]>>> {
140
130
  const { args, warnings } = this.getArgs(options);
141
131
 
142
132
  // biome-ignore lint/correctness/noUnusedVariables: this needs to be destructured
@@ -166,29 +156,51 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
166
156
  // @ts-expect-error response_format not yet added to types
167
157
  response_format: args.response_format,
168
158
  },
169
- { gateway: this.config.gateway ?? gateway, ...passthroughOptions },
159
+ {
160
+ gateway: this.config.gateway ?? gateway,
161
+ ...passthroughOptions,
162
+ tags: [],
163
+ },
170
164
  );
171
165
 
172
166
  if (output instanceof ReadableStream) {
173
167
  throw new Error("This shouldn't happen");
174
168
  }
175
169
 
170
+ const reasoningContent = (output as any)?.choices?.[0]?.message?.reasoning_content;
171
+
176
172
  return {
177
173
  finishReason: mapWorkersAIFinishReason(output),
178
- rawCall: { rawPrompt: messages, rawSettings: args },
179
- rawResponse: { body: output },
180
- text: processText(output),
181
- toolCalls: processToolCalls(output),
182
- // @ts-ignore: Missing types
183
- reasoning: output?.choices?.[0]?.message?.reasoning_content,
174
+ // TODO: rawCall and rawResponse- not sure
175
+ // rawCall: { rawPrompt: messages, rawSettings: args },
176
+ // rawResponse: { body: output },
177
+ // maybe this?
178
+ // providerMetadata: {
179
+ // prompt: messages,
180
+ // settings: args,
181
+ // response: output,
182
+ // },
183
+ content: [
184
+ ...(reasoningContent
185
+ ? [{ type: "reasoning" as const, text: reasoningContent }]
186
+ : []),
187
+ {
188
+ type: "text",
189
+ text: processText(output) ?? "",
190
+ },
191
+ ...processToolCalls(output),
192
+ ],
193
+
194
+ // @ts-expect-error: Missing types
195
+ reasoningText: reasoningContent,
184
196
  usage: mapWorkersAIUsage(output),
185
197
  warnings,
186
198
  };
187
199
  }
188
200
 
189
201
  async doStream(
190
- options: Parameters<LanguageModelV1["doStream"]>[0],
191
- ): Promise<Awaited<ReturnType<LanguageModelV1["doStream"]>>> {
202
+ options: Parameters<LanguageModelV2["doStream"]>[0],
203
+ ): Promise<Awaited<ReturnType<LanguageModelV2["doStream"]>>> {
192
204
  const { args, warnings } = this.getArgs(options);
193
205
 
194
206
  // Extract image from messages if present
@@ -204,29 +216,57 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
204
216
  throw new Error("This shouldn't happen");
205
217
  }
206
218
 
219
+ // Track start/delta/end IDs per v5 streaming protocol
220
+ let textId: string | null = null;
221
+ let reasoningId: string | null = null;
222
+
207
223
  return {
208
- rawCall: { rawPrompt: messages, rawSettings: args },
209
- stream: new ReadableStream<LanguageModelV1StreamPart>({
224
+ // rawCall: { rawPrompt: messages, rawSettings: args },
225
+ stream: new ReadableStream<LanguageModelV2StreamPart>({
210
226
  async start(controller) {
211
- if (response.text) {
212
- controller.enqueue({
213
- textDelta: response.text,
214
- type: "text-delta",
215
- });
216
- }
217
- if (response.toolCalls) {
218
- for (const toolCall of response.toolCalls) {
227
+ // Emit the stream-start part with warnings
228
+ controller.enqueue({
229
+ type: "stream-start",
230
+ warnings: warnings as LanguageModelV2CallWarning[],
231
+ });
232
+
233
+ for (const contentPart of response.content) {
234
+ if (contentPart.type === "text") {
235
+ if (!textId) {
236
+ textId = generateId();
237
+ controller.enqueue({ type: "text-start", id: textId });
238
+ }
219
239
  controller.enqueue({
220
- type: "tool-call",
221
- ...toolCall,
240
+ delta: contentPart.text,
241
+ type: "text-delta",
242
+ id: textId,
222
243
  });
223
244
  }
245
+ if (contentPart.type === "tool-call") {
246
+ controller.enqueue(contentPart);
247
+ }
248
+ if (contentPart.type === "reasoning") {
249
+ if (!reasoningId) {
250
+ reasoningId = generateId();
251
+ controller.enqueue({
252
+ type: "reasoning-start",
253
+ id: reasoningId,
254
+ });
255
+ }
256
+ controller.enqueue({
257
+ type: "reasoning-delta",
258
+ delta: contentPart.text,
259
+ id: generateId(),
260
+ });
261
+ }
262
+ }
263
+ if (reasoningId) {
264
+ controller.enqueue({ type: "reasoning-end", id: reasoningId });
265
+ reasoningId = null;
224
266
  }
225
- if (response.reasoning && typeof response.reasoning === "string") {
226
- controller.enqueue({
227
- type: "reasoning",
228
- textDelta: response.reasoning,
229
- });
267
+ if (textId) {
268
+ controller.enqueue({ type: "text-end", id: textId });
269
+ textId = null;
230
270
  }
231
271
  controller.enqueue({
232
272
  finishReason: mapWorkersAIFinishReason(response),
@@ -236,7 +276,6 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
236
276
  controller.close();
237
277
  },
238
278
  }),
239
- warnings,
240
279
  };
241
280
  }
242
281
 
@@ -265,17 +304,48 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
265
304
  // @ts-expect-error response_format not yet added to types
266
305
  response_format: args.response_format,
267
306
  },
268
- { gateway: this.config.gateway ?? gateway, ...passthroughOptions },
307
+ {
308
+ gateway: this.config.gateway ?? gateway,
309
+ ...passthroughOptions,
310
+ tags: [],
311
+ },
269
312
  );
270
313
 
271
314
  if (!(response instanceof ReadableStream)) {
272
315
  throw new Error("This shouldn't happen");
273
316
  }
274
317
 
318
+ // Create a new stream that first emits the stream-start part with warnings,
319
+ // then pipes through the rest of the response stream
320
+ const stream = new ReadableStream<LanguageModelV2StreamPart>({
321
+ start(controller) {
322
+ // Emit the stream-start part with warnings
323
+ controller.enqueue({
324
+ type: "stream-start",
325
+ warnings: warnings as LanguageModelV2CallWarning[],
326
+ });
327
+
328
+ // Pipe the rest of the response stream
329
+ const reader = getMappedStream(new Response(response)).getReader();
330
+
331
+ function push() {
332
+ reader.read().then(({ done, value }) => {
333
+ if (done) {
334
+ controller.close();
335
+ return;
336
+ }
337
+ controller.enqueue(value);
338
+ push();
339
+ });
340
+ }
341
+ push();
342
+ },
343
+ });
344
+
275
345
  return {
276
- rawCall: { rawPrompt: messages, rawSettings: args },
277
- stream: getMappedStream(new Response(response)),
278
- warnings,
346
+ stream,
347
+ // TODO: not sure about rawCalls
348
+ // rawCall: { rawPrompt: messages, rawSettings: args },
279
349
  };
280
350
  }
281
351
  }
@@ -1,5 +1,5 @@
1
1
  import { createJsonErrorResponseHandler } from "@ai-sdk/provider-utils";
2
- import { z } from "zod";
2
+ import { z } from "zod/v4";
3
3
 
4
4
  const workersAIErrorDataSchema = z.object({
5
5
  code: z.string().nullable(),
@@ -1,10 +1,10 @@
1
- import type { ImageModelV1, ImageModelV1CallWarning } from "@ai-sdk/provider";
1
+ import type { ImageModelV2, ImageModelV2CallWarning } from "@ai-sdk/provider";
2
2
  import type { WorkersAIImageConfig } from "./workersai-image-config";
3
3
  import type { WorkersAIImageSettings } from "./workersai-image-settings";
4
4
  import type { ImageGenerationModels } from "./workersai-models";
5
5
 
6
- export class WorkersAIImageModel implements ImageModelV1 {
7
- readonly specificationVersion = "v1";
6
+ export class WorkersAIImageModel implements ImageModelV2 {
7
+ readonly specificationVersion = "v2";
8
8
 
9
9
  get maxImagesPerCall(): number {
10
10
  return this.settings.maxImagesPerCall ?? 1;
@@ -27,12 +27,12 @@ export class WorkersAIImageModel implements ImageModelV1 {
27
27
  seed,
28
28
  // headers,
29
29
  // abortSignal,
30
- }: Parameters<ImageModelV1["doGenerate"]>[0]): Promise<
31
- Awaited<ReturnType<ImageModelV1["doGenerate"]>>
30
+ }: Parameters<ImageModelV2["doGenerate"]>[0]): Promise<
31
+ Awaited<ReturnType<ImageModelV2["doGenerate"]>>
32
32
  > {
33
33
  const { width, height } = getDimensionsFromSizeString(size);
34
34
 
35
- const warnings: Array<ImageModelV1CallWarning> = [];
35
+ const warnings: Array<ImageModelV2CallWarning> = [];
36
36
 
37
37
  if (aspectRatio != null) {
38
38
  warnings.push({