workers-ai-provider 0.4.0 → 0.4.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.
@@ -8,9 +8,9 @@ import { convertToWorkersAIChatMessages } from "./convert-to-workersai-chat-mess
8
8
  import type { WorkersAIChatSettings } from "./workersai-chat-settings";
9
9
  import type { TextGenerationModels } from "./workersai-models";
10
10
 
11
- import { events } from "fetch-event-stream";
12
11
  import { mapWorkersAIUsage } from "./map-workersai-usage";
13
- import type { WorkersAIChatPrompt } from "./workersai-chat-prompt";
12
+ import { getMappedStream } from "./streaming";
13
+ import { lastMessageWasUser, prepareToolsAndToolChoice, processToolCalls } from "./utils";
14
14
 
15
15
  type WorkersAIChatConfig = {
16
16
  provider: string;
@@ -138,9 +138,7 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
138
138
  const { gateway, safePrompt, ...passthroughOptions } = this.settings;
139
139
 
140
140
  // Extract image from messages if present
141
- const { messages, images } = convertToWorkersAIChatMessages(
142
- options.prompt,
143
- );
141
+ const { messages, images } = convertToWorkersAIChatMessages(options.prompt);
144
142
 
145
143
  // TODO: support for multiple images
146
144
  if (images.length !== 0 && images.length !== 1) {
@@ -189,9 +187,7 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
189
187
  const { args, warnings } = this.getArgs(options);
190
188
 
191
189
  // Extract image from messages if present
192
- const { messages, images } = convertToWorkersAIChatMessages(
193
- options.prompt,
194
- );
190
+ const { messages, images } = convertToWorkersAIChatMessages(options.prompt);
195
191
 
196
192
  // [1] When the latest message is not a tool response, we use the regular generate function
197
193
  // and simulate it as a streamed response in order to satisfy the AI SDK's interface for
@@ -265,129 +261,10 @@ export class WorkersAIChatLanguageModel implements LanguageModelV1 {
265
261
  throw new Error("This shouldn't happen");
266
262
  }
267
263
 
268
- const chunkEvent = events(new Response(response));
269
- let usage = { promptTokens: 0, completionTokens: 0 };
270
-
271
264
  return {
272
- stream: new ReadableStream<LanguageModelV1StreamPart>({
273
- async start(controller) {
274
- for await (const event of chunkEvent) {
275
- if (!event.data) {
276
- continue;
277
- }
278
- if (event.data === "[DONE]") {
279
- break;
280
- }
281
- const chunk = JSON.parse(event.data);
282
- if (chunk.usage) {
283
- usage = mapWorkersAIUsage(chunk);
284
- }
285
- chunk.response?.length &&
286
- controller.enqueue({
287
- type: "text-delta",
288
- textDelta: chunk.response,
289
- });
290
- }
291
- controller.enqueue({
292
- type: "finish",
293
- finishReason: "stop",
294
- usage: usage,
295
- });
296
- controller.close();
297
- },
298
- }),
265
+ stream: getMappedStream(new Response(response)),
299
266
  rawCall: { rawPrompt: messages, rawSettings: args },
300
267
  warnings,
301
268
  };
302
269
  }
303
270
  }
304
-
305
- function processToolCalls(output: any) {
306
- // Check for OpenAI format tool calls first
307
- if (output.tool_calls && Array.isArray(output.tool_calls)) {
308
- return output.tool_calls.map((toolCall: any) => {
309
- // Handle new format
310
- if (toolCall.function && toolCall.id) {
311
- return {
312
- toolCallType: "function",
313
- toolCallId: toolCall.id,
314
- toolName: toolCall.function.name,
315
- args:
316
- typeof toolCall.function.arguments === "string"
317
- ? toolCall.function.arguments
318
- : JSON.stringify(toolCall.function.arguments || {}),
319
- };
320
- }
321
- return {
322
- toolCallType: "function",
323
- toolCallId: toolCall.name,
324
- toolName: toolCall.name,
325
- args:
326
- typeof toolCall.arguments === "string"
327
- ? toolCall.arguments
328
- : JSON.stringify(toolCall.arguments || {}),
329
- };
330
- });
331
- }
332
-
333
- return [];
334
- }
335
-
336
- function prepareToolsAndToolChoice(
337
- mode: Parameters<LanguageModelV1["doGenerate"]>[0]["mode"] & {
338
- type: "regular";
339
- },
340
- ) {
341
- // when the tools array is empty, change it to undefined to prevent errors:
342
- const tools = mode.tools?.length ? mode.tools : undefined;
343
-
344
- if (tools == null) {
345
- return { tools: undefined, tool_choice: undefined };
346
- }
347
-
348
- const mappedTools = tools.map((tool) => ({
349
- type: "function",
350
- function: {
351
- name: tool.name,
352
- // @ts-expect-error - description is not a property of tool
353
- description: tool.description,
354
- // @ts-expect-error - parameters is not a property of tool
355
- parameters: tool.parameters,
356
- },
357
- }));
358
-
359
- const toolChoice = mode.toolChoice;
360
-
361
- if (toolChoice == null) {
362
- return { tools: mappedTools, tool_choice: undefined };
363
- }
364
-
365
- const type = toolChoice.type;
366
-
367
- switch (type) {
368
- case "auto":
369
- return { tools: mappedTools, tool_choice: type };
370
- case "none":
371
- return { tools: mappedTools, tool_choice: type };
372
- case "required":
373
- return { tools: mappedTools, tool_choice: "any" };
374
-
375
- // workersAI does not support tool mode directly,
376
- // so we filter the tools and force the tool choice through 'any'
377
- case "tool":
378
- return {
379
- tools: mappedTools.filter(
380
- (tool) => tool.function.name === toolChoice.toolName,
381
- ),
382
- tool_choice: "any",
383
- };
384
- default: {
385
- const exhaustiveCheck = type satisfies never;
386
- throw new Error(`Unsupported tool choice type: ${exhaustiveCheck}`);
387
- }
388
- }
389
- }
390
-
391
- function lastMessageWasUser(messages: WorkersAIChatPrompt) {
392
- return messages.length > 0 && messages[messages.length - 1].role === "user";
393
- }