@cloudflare/tanstack-ai 0.1.9 → 0.1.10

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.
Files changed (84) hide show
  1. package/README.md +5 -5
  2. package/dist/adapters/anthropic.cjs +2 -3
  3. package/dist/adapters/anthropic.cjs.map +1 -1
  4. package/dist/adapters/anthropic.d.cts +4 -3
  5. package/dist/adapters/anthropic.d.mts +4 -3
  6. package/dist/adapters/anthropic.mjs +3 -3
  7. package/dist/adapters/anthropic.mjs.map +1 -1
  8. package/dist/adapters/gemini.cjs +2 -3
  9. package/dist/adapters/gemini.cjs.map +1 -1
  10. package/dist/adapters/gemini.d.cts +6 -6
  11. package/dist/adapters/gemini.d.mts +6 -6
  12. package/dist/adapters/gemini.mjs +4 -3
  13. package/dist/adapters/gemini.mjs.map +1 -1
  14. package/dist/adapters/grok.cjs +2 -6
  15. package/dist/adapters/grok.cjs.map +1 -1
  16. package/dist/adapters/grok.d.cts +5 -4
  17. package/dist/adapters/grok.d.mts +5 -4
  18. package/dist/adapters/grok.mjs +3 -6
  19. package/dist/adapters/grok.mjs.map +1 -1
  20. package/dist/adapters/openai.cjs +2 -3
  21. package/dist/adapters/openai.cjs.map +1 -1
  22. package/dist/adapters/openai.d.cts +6 -5
  23. package/dist/adapters/openai.d.mts +6 -5
  24. package/dist/adapters/openai.mjs +3 -3
  25. package/dist/adapters/openai.mjs.map +1 -1
  26. package/dist/adapters/openrouter.cjs +3 -17
  27. package/dist/adapters/openrouter.cjs.map +1 -1
  28. package/dist/adapters/openrouter.d.cts +4 -4
  29. package/dist/adapters/openrouter.d.mts +4 -4
  30. package/dist/adapters/openrouter.mjs +4 -17
  31. package/dist/adapters/openrouter.mjs.map +1 -1
  32. package/dist/adapters/workers-ai-image.cjs +5 -6
  33. package/dist/adapters/workers-ai-image.cjs.map +1 -1
  34. package/dist/adapters/workers-ai-image.d.cts +1 -1
  35. package/dist/adapters/workers-ai-image.d.mts +2 -2
  36. package/dist/adapters/workers-ai-image.mjs +5 -5
  37. package/dist/adapters/workers-ai-image.mjs.map +1 -1
  38. package/dist/adapters/workers-ai-summarize.cjs +3 -4
  39. package/dist/adapters/workers-ai-summarize.cjs.map +1 -1
  40. package/dist/adapters/workers-ai-summarize.d.cts +1 -1
  41. package/dist/adapters/workers-ai-summarize.d.mts +2 -2
  42. package/dist/adapters/workers-ai-summarize.mjs +3 -3
  43. package/dist/adapters/workers-ai-summarize.mjs.map +1 -1
  44. package/dist/adapters/workers-ai-transcription.cjs +5 -6
  45. package/dist/adapters/workers-ai-transcription.cjs.map +1 -1
  46. package/dist/adapters/workers-ai-transcription.d.cts +1 -1
  47. package/dist/adapters/workers-ai-transcription.d.mts +2 -2
  48. package/dist/adapters/workers-ai-transcription.mjs +5 -5
  49. package/dist/adapters/workers-ai-transcription.mjs.map +1 -1
  50. package/dist/adapters/workers-ai-tts.cjs +5 -6
  51. package/dist/adapters/workers-ai-tts.cjs.map +1 -1
  52. package/dist/adapters/workers-ai-tts.d.cts +1 -1
  53. package/dist/adapters/workers-ai-tts.d.mts +2 -2
  54. package/dist/adapters/workers-ai-tts.mjs +5 -5
  55. package/dist/adapters/workers-ai-tts.mjs.map +1 -1
  56. package/dist/adapters/workers-ai.cjs +520 -3
  57. package/dist/adapters/workers-ai.cjs.map +1 -0
  58. package/dist/adapters/workers-ai.d.cts +2 -2
  59. package/dist/adapters/workers-ai.d.mts +3 -3
  60. package/dist/adapters/workers-ai.mjs +56 -41
  61. package/dist/adapters/workers-ai.mjs.map +1 -1
  62. package/dist/{binary-p4H_N_3M.mjs → binary-B5YCVsro.mjs} +1 -1
  63. package/dist/{binary-C9FAYwZj.cjs.map → binary-B5YCVsro.mjs.map} +1 -1
  64. package/dist/{binary-C9FAYwZj.cjs → binary-CZhr1_2n.cjs} +1 -1
  65. package/dist/{binary-p4H_N_3M.mjs.map → binary-CZhr1_2n.cjs.map} +1 -1
  66. package/dist/{create-fetcher-CeUOJgrh.mjs → create-fetcher-BnSnCaHf.mjs} +1 -1
  67. package/dist/{create-fetcher-By-hTiT9.cjs.map → create-fetcher-BnSnCaHf.mjs.map} +1 -1
  68. package/dist/{create-fetcher-6p6heb85.d.mts → create-fetcher-Bp5yCNaO.d.cts} +1 -1
  69. package/dist/{create-fetcher-vAQ8WW-p.d.cts → create-fetcher-Bp5yCNaO.d.mts} +1 -1
  70. package/dist/{create-fetcher-By-hTiT9.cjs → create-fetcher-YVxWCTVL.cjs} +1 -1
  71. package/dist/{create-fetcher-CeUOJgrh.mjs.map → create-fetcher-YVxWCTVL.cjs.map} +1 -1
  72. package/dist/{defineProperty-CbyrzcbA.mjs → defineProperty-BC3bEcwq.mjs} +4 -4
  73. package/dist/{defineProperty-DQoAg20E.cjs → defineProperty-In8g9yAD.cjs} +4 -4
  74. package/dist/index.cjs +2 -3
  75. package/dist/index.d.cts +3 -3
  76. package/dist/index.d.mts +3 -3
  77. package/dist/index.mjs +2 -2
  78. package/dist/{workers-ai-rest-GKy2r7eG.mjs → workers-ai-rest-B6_yU35Q.mjs} +1 -1
  79. package/dist/{workers-ai-rest-CkNCtBwv.cjs.map → workers-ai-rest-B6_yU35Q.mjs.map} +1 -1
  80. package/dist/{workers-ai-rest-CkNCtBwv.cjs → workers-ai-rest-_MXOmyDs.cjs} +1 -1
  81. package/dist/{workers-ai-rest-GKy2r7eG.mjs.map → workers-ai-rest-_MXOmyDs.cjs.map} +1 -1
  82. package/package.json +9 -9
  83. package/dist/workers-ai-BOZ5iyhY.cjs +0 -521
  84. package/dist/workers-ai-BOZ5iyhY.cjs.map +0 -1
@@ -1,5 +1,6 @@
1
- import { a as validateWorkersAiConfig, i as isDirectCredentialsConfig, n as createWorkersAiBindingFetch, r as isDirectBindingConfig, t as createGatewayFetch } from "../create-fetcher-CeUOJgrh.mjs";
2
- import { t as _defineProperty } from "../defineProperty-CbyrzcbA.mjs";
1
+ import { a as validateWorkersAiConfig, i as isDirectCredentialsConfig, n as createWorkersAiBindingFetch, r as isDirectBindingConfig, t as createGatewayFetch } from "../create-fetcher-BnSnCaHf.mjs";
2
+ import { t as _defineProperty } from "../defineProperty-BC3bEcwq.mjs";
3
+ import { EventType } from "@tanstack/ai";
3
4
  import { BaseTextAdapter } from "@tanstack/ai/adapters";
4
5
  import OpenAI from "openai";
5
6
  //#region src/adapters/workers-ai.ts
@@ -77,7 +78,7 @@ function buildOpenAIMessages(systemPrompts, messages, options) {
77
78
  const openAIMessages = [];
78
79
  if (systemPrompts && systemPrompts.length > 0) openAIMessages.push({
79
80
  role: "system",
80
- content: systemPrompts.join("\n")
81
+ content: systemPrompts.map((prompt) => typeof prompt === "string" ? prompt : prompt.content).join("\n")
81
82
  });
82
83
  for (const message of messages) if (message.role === "user") openAIMessages.push({
83
84
  role: "user",
@@ -142,12 +143,13 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
142
143
  this.client = buildWorkersAiClient(config);
143
144
  }
144
145
  async *chatStream(options) {
145
- const { systemPrompts, messages, tools, temperature, maxTokens, model, modelOptions } = options;
146
+ const { systemPrompts, messages, tools, model, modelOptions } = options;
146
147
  const extraBody = normalizeModelOptions(modelOptions);
147
148
  const openAIMessages = buildOpenAIMessages(systemPrompts, messages);
148
149
  const openAITools = buildOpenAITools(tools);
149
150
  const timestamp = Date.now();
150
- const runId = generateId();
151
+ const runId = options.runId ?? generateId();
152
+ const threadId = options.threadId ?? generateId("thread");
151
153
  const messageId = generateId();
152
154
  let hasEmittedRunStarted = false;
153
155
  let hasEmittedTextMessageStart = false;
@@ -165,8 +167,6 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
165
167
  model: model ?? this.model,
166
168
  messages: openAIMessages,
167
169
  tools: openAITools,
168
- temperature,
169
- max_tokens: maxTokens,
170
170
  stream: true,
171
171
  stream_options: { include_usage: true }
172
172
  });
@@ -176,27 +176,26 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
176
176
  ...extraBody,
177
177
  model: model ?? this.model,
178
178
  messages: openAIMessages,
179
- tools: openAITools,
180
- temperature,
181
- max_tokens: maxTokens
179
+ tools: openAITools
182
180
  });
183
181
  yield {
184
- type: "RUN_STARTED",
182
+ type: EventType.RUN_STARTED,
185
183
  runId,
184
+ threadId,
186
185
  model: nonStreamResult.model || model || this.model,
187
186
  timestamp
188
187
  };
189
188
  const msg = nonStreamResult.choices[0]?.message;
190
189
  if (msg?.content) {
191
190
  yield {
192
- type: "TEXT_MESSAGE_START",
191
+ type: EventType.TEXT_MESSAGE_START,
193
192
  messageId,
194
193
  model: nonStreamResult.model || model || this.model,
195
194
  timestamp,
196
195
  role: "assistant"
197
196
  };
198
197
  yield {
199
- type: "TEXT_MESSAGE_CONTENT",
198
+ type: EventType.TEXT_MESSAGE_CONTENT,
200
199
  messageId,
201
200
  model: nonStreamResult.model || model || this.model,
202
201
  timestamp,
@@ -204,7 +203,7 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
204
203
  content: msg.content
205
204
  };
206
205
  yield {
207
- type: "TEXT_MESSAGE_END",
206
+ type: EventType.TEXT_MESSAGE_END,
208
207
  messageId,
209
208
  model: nonStreamResult.model || model || this.model,
210
209
  timestamp
@@ -220,15 +219,16 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
220
219
  parsedInput = {};
221
220
  }
222
221
  yield {
223
- type: "TOOL_CALL_START",
222
+ type: EventType.TOOL_CALL_START,
224
223
  toolCallId: tc.id,
224
+ toolCallName: fn.name,
225
225
  toolName: fn.name,
226
226
  model: nonStreamResult.model || model || this.model,
227
227
  timestamp,
228
228
  index: 0
229
229
  };
230
230
  yield {
231
- type: "TOOL_CALL_END",
231
+ type: EventType.TOOL_CALL_END,
232
232
  toolCallId: tc.id,
233
233
  toolName: fn.name,
234
234
  model: nonStreamResult.model || model || this.model,
@@ -238,8 +238,9 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
238
238
  }
239
239
  const finishReason = nonStreamResult.choices[0]?.finish_reason;
240
240
  yield {
241
- type: "RUN_FINISHED",
241
+ type: EventType.RUN_FINISHED,
242
242
  runId,
243
+ threadId,
243
244
  model: nonStreamResult.model || model || this.model,
244
245
  timestamp,
245
246
  usage: nonStreamResult.usage ? {
@@ -258,8 +259,9 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
258
259
  if (!hasEmittedRunStarted) {
259
260
  hasEmittedRunStarted = true;
260
261
  yield {
261
- type: "RUN_STARTED",
262
+ type: EventType.RUN_STARTED,
262
263
  runId,
264
+ threadId,
263
265
  model: chunk.model || model || this.model,
264
266
  timestamp
265
267
  };
@@ -270,7 +272,8 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
270
272
  if (!hasEmittedStepStarted) {
271
273
  hasEmittedStepStarted = true;
272
274
  yield {
273
- type: "STEP_STARTED",
275
+ type: EventType.STEP_STARTED,
276
+ stepName: "thinking",
274
277
  stepId,
275
278
  stepType: "thinking",
276
279
  model: chunk.model || model || this.model,
@@ -279,7 +282,8 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
279
282
  }
280
283
  accumulatedReasoning += reasoningContent;
281
284
  yield {
282
- type: "STEP_FINISHED",
285
+ type: EventType.STEP_FINISHED,
286
+ stepName: "thinking",
283
287
  stepId,
284
288
  delta: reasoningContent,
285
289
  content: accumulatedReasoning,
@@ -291,7 +295,7 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
291
295
  if (!hasEmittedTextMessageStart) {
292
296
  hasEmittedTextMessageStart = true;
293
297
  yield {
294
- type: "TEXT_MESSAGE_START",
298
+ type: EventType.TEXT_MESSAGE_START,
295
299
  messageId,
296
300
  model: chunk.model || model || this.model,
297
301
  timestamp,
@@ -300,7 +304,7 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
300
304
  }
301
305
  accumulatedContent += delta.content;
302
306
  yield {
303
- type: "TEXT_MESSAGE_CONTENT",
307
+ type: EventType.TEXT_MESSAGE_CONTENT,
304
308
  messageId,
305
309
  model: chunk.model || model || this.model,
306
310
  timestamp,
@@ -314,7 +318,8 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
314
318
  id: generateId("chatcmpl-tool"),
315
319
  name: toolCallDelta.function?.name || "",
316
320
  arguments: "",
317
- started: false
321
+ started: false,
322
+ emittedArgsLength: 0
318
323
  });
319
324
  const toolCall = toolCallsInProgress.get(index);
320
325
  if (toolCallDelta.function?.name) toolCall.name = toolCallDelta.function.name;
@@ -322,21 +327,26 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
322
327
  if (toolCall.id && toolCall.name && !toolCall.started) {
323
328
  toolCall.started = true;
324
329
  yield {
325
- type: "TOOL_CALL_START",
330
+ type: EventType.TOOL_CALL_START,
326
331
  toolCallId: toolCall.id,
332
+ toolCallName: toolCall.name,
327
333
  toolName: toolCall.name,
328
334
  model: chunk.model || model || this.model,
329
335
  timestamp,
330
336
  index
331
337
  };
332
338
  }
333
- if (toolCallDelta.function?.arguments && toolCall.started) yield {
334
- type: "TOOL_CALL_ARGS",
335
- toolCallId: toolCall.id,
336
- model: chunk.model || model || this.model,
337
- timestamp,
338
- delta: toolCallDelta.function.arguments
339
- };
339
+ if (toolCall.started && toolCall.arguments.length > toolCall.emittedArgsLength) {
340
+ const argsDelta = toolCall.arguments.slice(toolCall.emittedArgsLength);
341
+ toolCall.emittedArgsLength = toolCall.arguments.length;
342
+ yield {
343
+ type: EventType.TOOL_CALL_ARGS,
344
+ toolCallId: toolCall.id,
345
+ model: chunk.model || model || this.model,
346
+ timestamp,
347
+ delta: argsDelta
348
+ };
349
+ }
340
350
  }
341
351
  if (choice.finish_reason) {
342
352
  hasReceivedFinishReason = true;
@@ -348,7 +358,7 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
348
358
  parsedInput = {};
349
359
  }
350
360
  yield {
351
- type: "TOOL_CALL_END",
361
+ type: EventType.TOOL_CALL_END,
352
362
  toolCallId: toolCall.id,
353
363
  toolName: toolCall.name,
354
364
  model: chunk.model || model || this.model,
@@ -358,14 +368,15 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
358
368
  }
359
369
  const computedFinishReason = choice.finish_reason === "tool_calls" || choice.finish_reason === "function_call" || toolCallsInProgress.size > 0 ? "tool_calls" : choice.finish_reason;
360
370
  if (hasEmittedTextMessageStart) yield {
361
- type: "TEXT_MESSAGE_END",
371
+ type: EventType.TEXT_MESSAGE_END,
362
372
  messageId,
363
373
  model: chunk.model || model || this.model,
364
374
  timestamp
365
375
  };
366
376
  yield {
367
- type: "RUN_FINISHED",
377
+ type: EventType.RUN_FINISHED,
368
378
  runId,
379
+ threadId,
369
380
  model: chunk.model || model || this.model,
370
381
  timestamp,
371
382
  usage: chunk.usage ? {
@@ -387,7 +398,7 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
387
398
  parsedInput = {};
388
399
  }
389
400
  yield {
390
- type: "TOOL_CALL_END",
401
+ type: EventType.TOOL_CALL_END,
391
402
  toolCallId: toolCall.id,
392
403
  toolName: toolCall.name,
393
404
  model: model ?? this.model,
@@ -396,14 +407,15 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
396
407
  };
397
408
  }
398
409
  if (hasEmittedTextMessageStart) yield {
399
- type: "TEXT_MESSAGE_END",
410
+ type: EventType.TEXT_MESSAGE_END,
400
411
  messageId,
401
412
  model: model ?? this.model,
402
413
  timestamp
403
414
  };
404
415
  yield {
405
- type: "RUN_FINISHED",
416
+ type: EventType.RUN_FINISHED,
406
417
  runId,
418
+ threadId,
407
419
  model: model ?? this.model,
408
420
  timestamp,
409
421
  finishReason: "stop"
@@ -413,16 +425,20 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
413
425
  const message = error instanceof Error ? error.message : String(error);
414
426
  const code = error instanceof Error ? error.code : void 0;
415
427
  if (!hasEmittedRunStarted) yield {
416
- type: "RUN_STARTED",
428
+ type: EventType.RUN_STARTED,
417
429
  runId,
430
+ threadId,
418
431
  model: model ?? this.model,
419
432
  timestamp
420
433
  };
421
434
  yield {
422
- type: "RUN_ERROR",
435
+ type: EventType.RUN_ERROR,
423
436
  runId,
437
+ threadId,
424
438
  model: model ?? this.model,
425
439
  timestamp,
440
+ message: message || "Unknown error",
441
+ code,
426
442
  error: {
427
443
  message: message || "Unknown error",
428
444
  code
@@ -432,14 +448,13 @@ var WorkersAiTextAdapter = class extends BaseTextAdapter {
432
448
  }
433
449
  async structuredOutput(options) {
434
450
  const { outputSchema, chatOptions } = options;
435
- const { systemPrompts, messages, temperature, model, modelOptions } = chatOptions;
451
+ const { systemPrompts, messages, model, modelOptions } = chatOptions;
436
452
  const extraBody = normalizeModelOptions(modelOptions);
437
453
  const openAIMessages = buildOpenAIMessages(systemPrompts, messages, { includeToolMessages: false });
438
454
  const response = await this.client.chat.completions.create({
439
455
  ...extraBody,
440
456
  model: model ?? this.model,
441
457
  messages: openAIMessages,
442
- temperature,
443
458
  stream: false,
444
459
  response_format: {
445
460
  type: "json_schema",
@@ -1 +1 @@
1
- {"version":3,"file":"workers-ai.mjs","names":[],"sources":["../../src/adapters/workers-ai.ts"],"sourcesContent":["import type { AiModels, BaseAiTextGeneration } from \"@cloudflare/workers-types\";\nimport type { ContentPart, ModelMessage, StreamChunk, TextOptions } from \"@tanstack/ai\";\nimport {\n\tBaseTextAdapter,\n\ttype StructuredOutputOptions,\n\ttype StructuredOutputResult,\n} from \"@tanstack/ai/adapters\";\nimport OpenAI from \"openai\";\nimport {\n\ttype WorkersAiAdapterConfig,\n\ttype AiGatewayAdapterConfig,\n\tcreateGatewayFetch,\n\tcreateWorkersAiBindingFetch,\n\tisDirectBindingConfig,\n\tisDirectCredentialsConfig,\n\tvalidateWorkersAiConfig,\n} from \"../utils/create-fetcher\";\n\n// ---------------------------------------------------------------------------\n// Model types derived from @cloudflare/workers-types\n// ---------------------------------------------------------------------------\n\nexport type WorkersAiTextModel =\n\t| {\n\t\t\t[K in keyof AiModels]: AiModels[K] extends BaseAiTextGeneration ? K : never;\n\t }[keyof AiModels]\n\t| (string & {});\n\n// ---------------------------------------------------------------------------\n// Provider-specific options forwarded to Workers AI's chat completions inputs.\n//\n// These correspond to fields on `ChatCompletionsCommonOptions` in\n// `@cloudflare/workers-types`. They are passed verbatim into the request body\n// sent to the Workers AI binding / REST endpoint / AI Gateway, and ultimately\n// land on the `inputs` object of `binding.run(model, inputs)`.\n//\n// Pass via `modelOptions` on a per-call basis:\n//\n// await adapter.chatStream({\n// model, messages,\n// modelOptions: {\n// reasoning_effort: \"low\",\n// chat_template_kwargs: { enable_thinking: false },\n// },\n// });\n// ---------------------------------------------------------------------------\n\nexport interface WorkersAiTextModelOptions {\n\t/**\n\t * Controls the reasoning budget for reasoning-capable models\n\t * (e.g. `@cf/zai-org/glm-4.7-flash`, `@cf/moonshotai/kimi-k2.5`,\n\t * `@cf/openai/gpt-oss-120b`).\n\t *\n\t * `null` is a valid value and disables reasoning for models that support it.\n\t */\n\treasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n\t/**\n\t * Chat-template overrides for reasoning-capable models that expose\n\t * thinking toggles (e.g. GLM, Kimi).\n\t */\n\tchat_template_kwargs?: {\n\t\t/** Whether to enable reasoning. Enabled by default on reasoning models. */\n\t\tenable_thinking?: boolean;\n\t\t/** If false, preserves reasoning context between turns. */\n\t\tclear_thinking?: boolean;\n\t};\n\t/**\n\t * Escape hatch for other Workers AI inputs-level parameters.\n\t *\n\t * Keys placed here are merged into the outbound request body and forwarded\n\t * to the underlying transport (binding / REST / gateway). Only fields that\n\t * the binding shim knows about are extracted for direct `env.AI` bindings;\n\t * everything is passed through on REST and gateway paths.\n\t */\n\t[key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers: build the right OpenAI client depending on config mode\n// ---------------------------------------------------------------------------\n\nfunction buildWorkersAiClient(config: WorkersAiAdapterConfig): OpenAI {\n\tvalidateWorkersAiConfig(config);\n\n\tconst sessionHeaders: Record<string, string> | undefined = config.sessionAffinity\n\t\t? { \"x-session-affinity\": config.sessionAffinity }\n\t\t: undefined;\n\n\tif (isDirectBindingConfig(config)) {\n\t\t// Plain binding mode: shim translates OpenAI fetch calls to env.AI.run()\n\t\treturn new OpenAI({\n\t\t\tapiKey: \"unused\",\n\t\t\tfetch: createWorkersAiBindingFetch(\n\t\t\t\tconfig.binding,\n\t\t\t\tsessionHeaders ? { extraHeaders: sessionHeaders } : undefined,\n\t\t\t),\n\t\t});\n\t}\n\n\tif (isDirectCredentialsConfig(config)) {\n\t\t// Plain REST mode: point OpenAI SDK at Workers AI's OpenAI-compatible endpoint\n\t\treturn new OpenAI({\n\t\t\tbaseURL: `https://api.cloudflare.com/client/v4/accounts/${config.accountId}/ai/v1`,\n\t\t\tapiKey: config.apiKey,\n\t\t\tdefaultHeaders: sessionHeaders,\n\t\t});\n\t}\n\n\t// Gateway mode (existing): use createGatewayFetch\n\tconst gatewayConfig = config as AiGatewayAdapterConfig;\n\treturn new OpenAI({\n\t\tfetch: createGatewayFetch(\"workers-ai\", gatewayConfig, sessionHeaders),\n\t\tapiKey: gatewayConfig.apiKey ?? \"unused\",\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Shared message-building helpers\n// ---------------------------------------------------------------------------\n\nfunction extractTextContent(content: ModelMessage[\"content\"]): string {\n\tif (content === null) return \"\";\n\tif (typeof content === \"string\") return content;\n\treturn content.flatMap((p) => (p.type === \"text\" ? [p.content] : [])).join(\"\");\n}\n\n/**\n * Convert a single TanStack AI {@link ContentPart} to the OpenAI Chat\n * Completions multi-modal format.\n *\n * TODO: handle other content types (audio, video, document)\n */\nfunction convertContentPart(part: ContentPart): OpenAI.Chat.ChatCompletionContentPart | undefined {\n\tswitch (part.type) {\n\t\tcase \"text\":\n\t\t\tif (part.content) {\n\t\t\t\treturn { type: \"text\", text: part.content };\n\t\t\t}\n\t\t\treturn undefined;\n\t\tcase \"image\": {\n\t\t\tlet url: string;\n\t\t\tif (part.source.type === \"data\") {\n\t\t\t\turl = part.source.value.startsWith(\"data:\")\n\t\t\t\t\t? part.source.value\n\t\t\t\t\t: `data:${part.source.mimeType};base64,${part.source.value}`;\n\t\t\t} else {\n\t\t\t\turl = part.source.value;\n\t\t\t}\n\t\t\treturn { type: \"image_url\", image_url: { url } };\n\t\t}\n\t\tdefault:\n\t\t\t// audio, video, document — not supported for now\n\t\t\tconsole.warn(\n\t\t\t\t`[@cloudflare/tanstack-ai] Unsupported content part type \"${part.type}\" — skipping`,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Build OpenAI-compatible user message content from TanStack AI content.\n *\n * If the content has only text parts, returns a plain string.\n * If it includes image parts, returns an array of content parts in\n * OpenAI's multi-modal format (text + image_url).\n */\nfunction buildUserContent(\n\tcontent: ModelMessage[\"content\"],\n): string | OpenAI.Chat.ChatCompletionContentPart[] {\n\tif (content === null) return \"\";\n\tif (typeof content === \"string\") return content;\n\n\tconst hasImages = content.some((p) => p.type === \"image\");\n\tif (!hasImages) {\n\t\treturn content.flatMap((p) => (p.type === \"text\" ? [p.content] : [])).join(\"\");\n\t}\n\n\tconst parts: OpenAI.Chat.ChatCompletionContentPart[] = [];\n\tfor (const part of content) {\n\t\tconst converted = convertContentPart(part);\n\t\tif (converted) {\n\t\t\tparts.push(converted);\n\t\t}\n\t}\n\treturn parts;\n}\n\nfunction buildOpenAIMessages(\n\tsystemPrompts: string[] | undefined,\n\tmessages: ModelMessage[],\n\toptions?: { includeToolMessages?: boolean },\n): OpenAI.Chat.ChatCompletionMessageParam[] {\n\tconst includeTools = options?.includeToolMessages ?? true;\n\tconst openAIMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [];\n\n\tif (systemPrompts && systemPrompts.length > 0) {\n\t\topenAIMessages.push({\n\t\t\trole: \"system\",\n\t\t\tcontent: systemPrompts.join(\"\\n\"),\n\t\t});\n\t}\n\n\tfor (const message of messages) {\n\t\tif (message.role === \"user\") {\n\t\t\topenAIMessages.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: buildUserContent(message.content),\n\t\t\t});\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst assistantMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: extractTextContent(message.content),\n\t\t\t};\n\t\t\tif (includeTools && message.toolCalls && message.toolCalls.length > 0) {\n\t\t\t\tassistantMessage.tool_calls = message.toolCalls.map((tc) => ({\n\t\t\t\t\tid: tc.id,\n\t\t\t\t\ttype: \"function\" as const,\n\t\t\t\t\tfunction: {\n\t\t\t\t\t\tname: tc.function.name,\n\t\t\t\t\t\targuments: tc.function.arguments,\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t}\n\t\t\topenAIMessages.push(assistantMessage);\n\t\t} else if (includeTools && message.role === \"tool\") {\n\t\t\tlet toolContent: string;\n\t\t\tif (typeof message.content === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tJSON.parse(message.content);\n\t\t\t\t\ttoolContent = message.content;\n\t\t\t\t} catch {\n\t\t\t\t\ttoolContent = JSON.stringify(message.content);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttoolContent = JSON.stringify(message.content);\n\t\t\t}\n\t\t\topenAIMessages.push({\n\t\t\t\trole: \"tool\",\n\t\t\t\ttool_call_id: message.toolCallId || `tool_${crypto.randomUUID().slice(0, 8)}`,\n\t\t\t\tcontent: toolContent,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn openAIMessages;\n}\n\nfunction buildOpenAITools(\n\ttools: Array<{ name: string; description: string; inputSchema?: unknown }> | undefined,\n): OpenAI.Chat.ChatCompletionTool[] | undefined {\n\tif (!tools) return undefined;\n\treturn tools.map((tool) => ({\n\t\ttype: \"function\" as const,\n\t\tfunction: {\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: tool.inputSchema as Record<string, unknown>,\n\t\t},\n\t}));\n}\n\n// ---------------------------------------------------------------------------\n// ID generation\n// ---------------------------------------------------------------------------\n\nfunction generateId(prefix = \"chatcmpl\"): string {\n\treturn `${prefix}-${crypto.randomUUID().replace(/-/g, \"\").slice(0, 16)}`;\n}\n\n// ---------------------------------------------------------------------------\n// modelOptions normalization\n//\n// Users pass Workers AI-specific chat params (e.g. `reasoning_effort`,\n// `chat_template_kwargs`) via `modelOptions`. We merge these into the outbound\n// request body so they reach the binding / REST / gateway transports.\n//\n// Spread order matters: these go FIRST in the request body so that TanStack-AI\n// managed fields (`model`, `messages`, `temperature`, `max_tokens`, `stream`,\n// `tools`, `response_format`, ...) always win if a user accidentally sets\n// them both at the top level and inside `modelOptions`.\n//\n// `undefined` values are stripped so that JSON.stringify (and our binding shim\n// which does `!== undefined` checks) see them as absent. `null` values are\n// preserved — they're meaningful for fields like `reasoning_effort: null`\n// which explicitly disables reasoning on some models.\n// ---------------------------------------------------------------------------\nfunction normalizeModelOptions(\n\tmodelOptions: WorkersAiTextModelOptions | undefined,\n): Record<string, unknown> {\n\t// Guard against runtime misuse. TanStack AI types this as an object, but\n\t// users can always bypass with `as any`. `Object.entries` on a string\n\t// surprisingly returns per-character tuples (e.g. `Object.entries(\"ab\") →\n\t// [[\"0\",\"a\"],[\"1\",\"b\"]]`), which would leak spurious keys into the body.\n\t// Arrays similarly become index-keyed. Only accept plain objects.\n\tif (modelOptions === null || typeof modelOptions !== \"object\" || Array.isArray(modelOptions)) {\n\t\treturn {};\n\t}\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(modelOptions)) {\n\t\tif (value !== undefined) out[key] = value;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// WorkersAiTextAdapter: chat / structured output via OpenAI Chat Completions\n// ---------------------------------------------------------------------------\n\nexport class WorkersAiTextAdapter<TModel extends WorkersAiTextModel> extends BaseTextAdapter<\n\tTModel,\n\tWorkersAiTextModelOptions,\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- remaining BaseTextAdapter generics are opaque\n\tany,\n\tany\n> {\n\tname = \"workers-ai\" as const;\n\n\tprivate client: OpenAI;\n\n\tconstructor(model: TModel, config: WorkersAiAdapterConfig) {\n\t\tsuper({ apiKey: \"unused\" }, model);\n\t\tthis.client = buildWorkersAiClient(config);\n\t}\n\n\tasync *chatStream(options: TextOptions<WorkersAiTextModelOptions>): AsyncIterable<StreamChunk> {\n\t\tconst { systemPrompts, messages, tools, temperature, maxTokens, model, modelOptions } =\n\t\t\toptions;\n\t\tconst extraBody = normalizeModelOptions(modelOptions);\n\n\t\tconst openAIMessages = buildOpenAIMessages(systemPrompts, messages);\n\t\tconst openAITools = buildOpenAITools(tools);\n\n\t\tconst timestamp = Date.now();\n\t\tconst runId = generateId();\n\t\tconst messageId = generateId();\n\t\tlet hasEmittedRunStarted = false;\n\t\tlet hasEmittedTextMessageStart = false;\n\t\tlet accumulatedContent = \"\";\n\t\tlet hasEmittedStepStarted = false;\n\t\tlet accumulatedReasoning = \"\";\n\t\tconst stepId = generateId();\n\t\tlet hasReceivedFinishReason = false;\n\t\tconst toolCallsInProgress = new Map<\n\t\t\tnumber,\n\t\t\t{ id: string; name: string; arguments: string; started: boolean }\n\t\t>();\n\n\t\ttry {\n\t\t\tlet stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>;\n\t\t\ttry {\n\t\t\t\tstream = await this.client.chat.completions.create({\n\t\t\t\t\t...extraBody,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\tmessages: openAIMessages,\n\t\t\t\t\ttools: openAITools,\n\t\t\t\t\ttemperature,\n\t\t\t\t\tmax_tokens: maxTokens,\n\t\t\t\t\tstream: true,\n\t\t\t\t\tstream_options: { include_usage: true },\n\t\t\t\t} as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming);\n\t\t\t} catch (streamError: unknown) {\n\t\t\t\t// Some models (e.g. GPT-OSS) don't support streaming via the REST API.\n\t\t\t\t// Fall back to a non-streaming call and yield the result as a single chunk.\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[tanstack-ai] Streaming failed, falling back to non-streaming:\",\n\t\t\t\t\tstreamError instanceof Error ? streamError.message : streamError,\n\t\t\t\t);\n\t\t\t\tconst nonStreamResult = await this.client.chat.completions.create({\n\t\t\t\t\t...extraBody,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\tmessages: openAIMessages,\n\t\t\t\t\ttools: openAITools,\n\t\t\t\t\ttemperature,\n\t\t\t\t\tmax_tokens: maxTokens,\n\t\t\t\t} as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming);\n\n\t\t\t\tyield {\n\t\t\t\t\ttype: \"RUN_STARTED\",\n\t\t\t\t\trunId,\n\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t} satisfies StreamChunk;\n\n\t\t\t\tconst msg = nonStreamResult.choices[0]?.message;\n\t\t\t\tif (msg?.content) {\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"TEXT_MESSAGE_START\",\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"TEXT_MESSAGE_CONTENT\",\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\tdelta: msg.content,\n\t\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"TEXT_MESSAGE_END\",\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\tif (msg?.tool_calls) {\n\t\t\t\t\tfor (const tc of msg.tool_calls) {\n\t\t\t\t\t\tif (tc.type !== \"function\") continue;\n\t\t\t\t\t\tconst fn = tc.function;\n\t\t\t\t\t\tlet parsedInput: unknown = {};\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tparsedInput = fn.arguments ? JSON.parse(fn.arguments) : {};\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tparsedInput = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: \"TOOL_CALL_START\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: fn.name,\n\t\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\tindex: 0,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: \"TOOL_CALL_END\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: fn.name,\n\t\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\tinput: parsedInput,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst finishReason = nonStreamResult.choices[0]?.finish_reason;\n\t\t\t\tyield {\n\t\t\t\t\ttype: \"RUN_FINISHED\",\n\t\t\t\t\trunId,\n\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t\tusage: nonStreamResult.usage\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tpromptTokens: nonStreamResult.usage.prompt_tokens,\n\t\t\t\t\t\t\t\tcompletionTokens: nonStreamResult.usage.completion_tokens,\n\t\t\t\t\t\t\t\ttotalTokens: nonStreamResult.usage.total_tokens,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\tfinishReason:\n\t\t\t\t\t\tfinishReason === \"tool_calls\" || finishReason === \"function_call\"\n\t\t\t\t\t\t\t? \"tool_calls\"\n\t\t\t\t\t\t\t: ((finishReason as \"stop\" | \"length\" | \"content_filter\") ?? \"stop\"),\n\t\t\t\t} satisfies StreamChunk;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor await (const chunk of stream) {\n\t\t\t\tif (!chunk.choices || chunk.choices.length === 0) continue;\n\t\t\t\tconst choice = chunk.choices[0];\n\t\t\t\tif (!choice) continue;\n\n\t\t\t\t// Emit RUN_STARTED on first chunk\n\t\t\t\tif (!hasEmittedRunStarted) {\n\t\t\t\t\thasEmittedRunStarted = true;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"RUN_STARTED\",\n\t\t\t\t\t\trunId,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\tconst delta = choice.delta;\n\n\t\t\t\t// Reasoning content (used by models like QwQ, DeepSeek R1, Kimi K2.5)\n\t\t\t\t// The OpenAI SDK doesn't type this field, but models send it as an extension.\n\t\t\t\tconst reasoningContent = ((delta as Record<string, unknown>).reasoning_content ??\n\t\t\t\t\t(delta as Record<string, unknown>).reasoning) as string | undefined;\n\t\t\t\tif (reasoningContent) {\n\t\t\t\t\t// RUN_STARTED is already guaranteed by the guard above\n\t\t\t\t\tif (!hasEmittedStepStarted) {\n\t\t\t\t\t\thasEmittedStepStarted = true;\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: \"STEP_STARTED\",\n\t\t\t\t\t\t\tstepId,\n\t\t\t\t\t\t\tstepType: \"thinking\",\n\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\t\t\t\t\taccumulatedReasoning += reasoningContent;\n\t\t\t\t\t// TODO: TanStack AI's StreamProcessor currently treats STEP_FINISHED as an\n\t\t\t\t\t// incremental reasoning event (with `delta` + accumulated `content`), so we\n\t\t\t\t\t// emit one per token. If TanStack AI adds a dedicated STEP_CONTENT event\n\t\t\t\t\t// type, this should be updated to emit STEP_CONTENT per token and a single\n\t\t\t\t\t// STEP_FINISHED when reasoning ends (i.e. when the first non-reasoning\n\t\t\t\t\t// content or finish_reason arrives).\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"STEP_FINISHED\",\n\t\t\t\t\t\tstepId,\n\t\t\t\t\t\tdelta: reasoningContent,\n\t\t\t\t\t\tcontent: accumulatedReasoning,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\t// Text content\n\t\t\t\tif (delta.content) {\n\t\t\t\t\tif (!hasEmittedTextMessageStart) {\n\t\t\t\t\t\thasEmittedTextMessageStart = true;\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: \"TEXT_MESSAGE_START\",\n\t\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\n\t\t\t\t\taccumulatedContent += delta.content;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"TEXT_MESSAGE_CONTENT\",\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\tdelta: delta.content,\n\t\t\t\t\t\tcontent: accumulatedContent,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\t// Tool calls\n\t\t\t\tif (delta.tool_calls) {\n\t\t\t\t\tfor (const toolCallDelta of delta.tool_calls) {\n\t\t\t\t\t\tconst index = toolCallDelta.index;\n\n\t\t\t\t\t\tif (!toolCallsInProgress.has(index)) {\n\t\t\t\t\t\t\t// Always generate a unique ID per tool call index.\n\t\t\t\t\t\t\t// The backend may send the same ID for multiple tool calls,\n\t\t\t\t\t\t\t// so we cannot trust toolCallDelta.id to be unique.\n\t\t\t\t\t\t\ttoolCallsInProgress.set(index, {\n\t\t\t\t\t\t\t\tid: generateId(\"chatcmpl-tool\"),\n\t\t\t\t\t\t\t\tname: toolCallDelta.function?.name || \"\",\n\t\t\t\t\t\t\t\targuments: \"\",\n\t\t\t\t\t\t\t\tstarted: false,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst toolCall = toolCallsInProgress.get(index)!;\n\n\t\t\t\t\t\t// Only update name if provided (ID is already set at creation time\n\t\t\t\t\t\t// and should not be overwritten by subsequent chunks that may have\n\t\t\t\t\t\t// duplicate/shared IDs from the backend)\n\t\t\t\t\t\tif (toolCallDelta.function?.name) {\n\t\t\t\t\t\t\ttoolCall.name = toolCallDelta.function.name;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (toolCallDelta.function?.arguments) {\n\t\t\t\t\t\t\ttoolCall.arguments += toolCallDelta.function.arguments;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Emit TOOL_CALL_START once we have id and name\n\t\t\t\t\t\tif (toolCall.id && toolCall.name && !toolCall.started) {\n\t\t\t\t\t\t\ttoolCall.started = true;\n\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\ttype: \"TOOL_CALL_START\",\n\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Stream tool call arguments\n\t\t\t\t\t\tif (toolCallDelta.function?.arguments && toolCall.started) {\n\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\ttype: \"TOOL_CALL_ARGS\",\n\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\tdelta: toolCallDelta.function.arguments,\n\t\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Finish\n\t\t\t\tif (choice.finish_reason) {\n\t\t\t\t\thasReceivedFinishReason = true;\n\n\t\t\t\t\t// End tool calls\n\t\t\t\t\tif (choice.finish_reason === \"tool_calls\" || toolCallsInProgress.size > 0) {\n\t\t\t\t\t\tfor (const [, toolCall] of toolCallsInProgress) {\n\t\t\t\t\t\t\tlet parsedInput: unknown = {};\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tparsedInput = toolCall.arguments\n\t\t\t\t\t\t\t\t\t? JSON.parse(toolCall.arguments)\n\t\t\t\t\t\t\t\t\t: {};\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tparsedInput = {};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\ttype: \"TOOL_CALL_END\",\n\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\tinput: parsedInput,\n\t\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst computedFinishReason =\n\t\t\t\t\t\tchoice.finish_reason === \"tool_calls\" ||\n\t\t\t\t\t\tchoice.finish_reason === \"function_call\" ||\n\t\t\t\t\t\ttoolCallsInProgress.size > 0\n\t\t\t\t\t\t\t? \"tool_calls\"\n\t\t\t\t\t\t\t: (choice.finish_reason as \"stop\" | \"length\" | \"content_filter\");\n\n\t\t\t\t\t// End text message if started\n\t\t\t\t\tif (hasEmittedTextMessageStart) {\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: \"TEXT_MESSAGE_END\",\n\t\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Emit RUN_FINISHED\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"RUN_FINISHED\",\n\t\t\t\t\t\trunId,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\tusage: chunk.usage\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tpromptTokens: chunk.usage.prompt_tokens,\n\t\t\t\t\t\t\t\t\tcompletionTokens: chunk.usage.completion_tokens,\n\t\t\t\t\t\t\t\t\ttotalTokens: chunk.usage.total_tokens,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tfinishReason: computedFinishReason,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Premature stream termination: the stream ended without a finish_reason.\n\t\t\t// This can happen when Workers AI truncates a response or the connection drops.\n\t\t\t// Emit proper closing events so the consumer doesn't hang.\n\t\t\tif (hasEmittedRunStarted && !hasReceivedFinishReason) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[tanstack-ai] Stream ended without finish_reason — possible truncation or connection drop\",\n\t\t\t\t);\n\n\t\t\t\t// Close any open tool calls\n\t\t\t\tfor (const [, toolCall] of toolCallsInProgress) {\n\t\t\t\t\tif (toolCall.started) {\n\t\t\t\t\t\tlet parsedInput: unknown = {};\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tparsedInput = toolCall.arguments ? JSON.parse(toolCall.arguments) : {};\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tparsedInput = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: \"TOOL_CALL_END\",\n\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\tinput: parsedInput,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Close text message if open\n\t\t\t\tif (hasEmittedTextMessageStart) {\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: \"TEXT_MESSAGE_END\",\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\tyield {\n\t\t\t\t\ttype: \"RUN_FINISHED\",\n\t\t\t\t\trunId,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t\tfinishReason: \"stop\",\n\t\t\t\t} satisfies StreamChunk;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconst code =\n\t\t\t\terror instanceof Error ? (error as Error & { code?: string }).code : undefined;\n\t\t\tif (!hasEmittedRunStarted) {\n\t\t\t\tyield {\n\t\t\t\t\ttype: \"RUN_STARTED\",\n\t\t\t\t\trunId,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t} satisfies StreamChunk;\n\t\t\t}\n\t\t\tyield {\n\t\t\t\ttype: \"RUN_ERROR\",\n\t\t\t\trunId,\n\t\t\t\tmodel: model ?? this.model,\n\t\t\t\ttimestamp,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: message || \"Unknown error\",\n\t\t\t\t\tcode,\n\t\t\t\t},\n\t\t\t} satisfies StreamChunk;\n\t\t}\n\t}\n\n\tasync structuredOutput(\n\t\toptions: StructuredOutputOptions<WorkersAiTextModelOptions>,\n\t): Promise<StructuredOutputResult<unknown>> {\n\t\tconst { outputSchema, chatOptions } = options;\n\t\tconst { systemPrompts, messages, temperature, model, modelOptions } = chatOptions;\n\t\tconst extraBody = normalizeModelOptions(modelOptions);\n\n\t\tconst openAIMessages = buildOpenAIMessages(systemPrompts, messages, {\n\t\t\tincludeToolMessages: false,\n\t\t});\n\n\t\tconst response = await this.client.chat.completions.create({\n\t\t\t...extraBody,\n\t\t\tmodel: model ?? this.model,\n\t\t\tmessages: openAIMessages,\n\t\t\ttemperature,\n\t\t\tstream: false,\n\t\t\tresponse_format: {\n\t\t\t\ttype: \"json_schema\",\n\t\t\t\tjson_schema: {\n\t\t\t\t\tname: \"structured_output\",\n\t\t\t\t\tstrict: true,\n\t\t\t\t\tschema: outputSchema,\n\t\t\t\t},\n\t\t\t},\n\t\t} as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming);\n\n\t\tconst choice = response.choices?.[0];\n\n\t\tif (!choice) {\n\t\t\tthrow new Error(\n\t\t\t\t`Workers AI structured output returned no choices: ${JSON.stringify(response)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst rawContent = choice.message?.content ?? \"\";\n\n\t\t// Workers AI REST endpoint may return `content` as an already-parsed object\n\t\t// when using json_schema response format, so normalise both cases.\n\t\tlet data: unknown;\n\t\tlet rawText: string;\n\n\t\tif (typeof rawContent === \"string\") {\n\t\t\trawText = rawContent;\n\t\t\ttry {\n\t\t\t\tdata = JSON.parse(rawText);\n\t\t\t} catch {\n\t\t\t\tdata = rawText;\n\t\t\t}\n\t\t} else {\n\t\t\t// Already an object — stringify for rawText, use directly for data\n\t\t\tdata = rawContent;\n\t\t\trawText = JSON.stringify(rawContent);\n\t\t}\n\n\t\treturn { data, rawText };\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\nexport function createWorkersAiChat(model: WorkersAiTextModel, config: WorkersAiAdapterConfig) {\n\treturn new WorkersAiTextAdapter(model, config);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"workers-ai.mjs","names":[],"sources":["../../src/adapters/workers-ai.ts"],"sourcesContent":["import type { AiModels, BaseAiTextGeneration } from \"@cloudflare/workers-types\";\nimport type {\n\tContentPart,\n\tModelMessage,\n\tStreamChunk,\n\tSystemPrompt,\n\tTextOptions,\n} from \"@tanstack/ai\";\nimport { EventType } from \"@tanstack/ai\";\nimport {\n\tBaseTextAdapter,\n\ttype StructuredOutputOptions,\n\ttype StructuredOutputResult,\n} from \"@tanstack/ai/adapters\";\nimport OpenAI from \"openai\";\nimport {\n\ttype WorkersAiAdapterConfig,\n\ttype AiGatewayAdapterConfig,\n\tcreateGatewayFetch,\n\tcreateWorkersAiBindingFetch,\n\tisDirectBindingConfig,\n\tisDirectCredentialsConfig,\n\tvalidateWorkersAiConfig,\n} from \"../utils/create-fetcher\";\n\n// ---------------------------------------------------------------------------\n// Model types derived from @cloudflare/workers-types\n// ---------------------------------------------------------------------------\n\nexport type WorkersAiTextModel =\n\t| {\n\t\t\t[K in keyof AiModels]: AiModels[K] extends BaseAiTextGeneration ? K : never;\n\t }[keyof AiModels]\n\t| (string & {});\n\n// ---------------------------------------------------------------------------\n// Provider-specific options forwarded to Workers AI's chat completions inputs.\n//\n// These correspond to fields on `ChatCompletionsCommonOptions` in\n// `@cloudflare/workers-types`. They are passed verbatim into the request body\n// sent to the Workers AI binding / REST endpoint / AI Gateway, and ultimately\n// land on the `inputs` object of `binding.run(model, inputs)`.\n//\n// Pass via `modelOptions` on a per-call basis:\n//\n// await adapter.chatStream({\n// model, messages,\n// modelOptions: {\n// reasoning_effort: \"low\",\n// chat_template_kwargs: { enable_thinking: false },\n// },\n// });\n// ---------------------------------------------------------------------------\n\nexport interface WorkersAiTextModelOptions {\n\t/**\n\t * Controls the reasoning budget for reasoning-capable models\n\t * (e.g. `@cf/zai-org/glm-4.7-flash`, `@cf/moonshotai/kimi-k2.7-code`,\n\t * `@cf/openai/gpt-oss-120b`).\n\t *\n\t * `null` is a valid value and disables reasoning for models that support it.\n\t */\n\treasoning_effort?: \"low\" | \"medium\" | \"high\" | null;\n\t/**\n\t * Chat-template overrides for reasoning-capable models that expose\n\t * thinking toggles (e.g. GLM, Kimi).\n\t */\n\tchat_template_kwargs?: {\n\t\t/** Whether to enable reasoning. Enabled by default on reasoning models. */\n\t\tenable_thinking?: boolean;\n\t\t/** If false, preserves reasoning context between turns. */\n\t\tclear_thinking?: boolean;\n\t};\n\t/**\n\t * Escape hatch for other Workers AI inputs-level parameters.\n\t *\n\t * Keys placed here are merged into the outbound request body and forwarded\n\t * to the underlying transport (binding / REST / gateway). Only fields that\n\t * the binding shim knows about are extracted for direct `env.AI` bindings;\n\t * everything is passed through on REST and gateway paths.\n\t */\n\t[key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers: build the right OpenAI client depending on config mode\n// ---------------------------------------------------------------------------\n\nfunction buildWorkersAiClient(config: WorkersAiAdapterConfig): OpenAI {\n\tvalidateWorkersAiConfig(config);\n\n\tconst sessionHeaders: Record<string, string> | undefined = config.sessionAffinity\n\t\t? { \"x-session-affinity\": config.sessionAffinity }\n\t\t: undefined;\n\n\tif (isDirectBindingConfig(config)) {\n\t\t// Plain binding mode: shim translates OpenAI fetch calls to env.AI.run()\n\t\treturn new OpenAI({\n\t\t\tapiKey: \"unused\",\n\t\t\tfetch: createWorkersAiBindingFetch(\n\t\t\t\tconfig.binding,\n\t\t\t\tsessionHeaders ? { extraHeaders: sessionHeaders } : undefined,\n\t\t\t),\n\t\t});\n\t}\n\n\tif (isDirectCredentialsConfig(config)) {\n\t\t// Plain REST mode: point OpenAI SDK at Workers AI's OpenAI-compatible endpoint\n\t\treturn new OpenAI({\n\t\t\tbaseURL: `https://api.cloudflare.com/client/v4/accounts/${config.accountId}/ai/v1`,\n\t\t\tapiKey: config.apiKey,\n\t\t\tdefaultHeaders: sessionHeaders,\n\t\t});\n\t}\n\n\t// Gateway mode (existing): use createGatewayFetch\n\tconst gatewayConfig = config as AiGatewayAdapterConfig;\n\treturn new OpenAI({\n\t\tfetch: createGatewayFetch(\"workers-ai\", gatewayConfig, sessionHeaders),\n\t\tapiKey: gatewayConfig.apiKey ?? \"unused\",\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Shared message-building helpers\n// ---------------------------------------------------------------------------\n\nfunction extractTextContent(content: ModelMessage[\"content\"]): string {\n\tif (content === null) return \"\";\n\tif (typeof content === \"string\") return content;\n\treturn content.flatMap((p) => (p.type === \"text\" ? [p.content] : [])).join(\"\");\n}\n\n/**\n * Convert a single TanStack AI {@link ContentPart} to the OpenAI Chat\n * Completions multi-modal format.\n *\n * TODO: handle other content types (audio, video, document)\n */\nfunction convertContentPart(part: ContentPart): OpenAI.Chat.ChatCompletionContentPart | undefined {\n\tswitch (part.type) {\n\t\tcase \"text\":\n\t\t\tif (part.content) {\n\t\t\t\treturn { type: \"text\", text: part.content };\n\t\t\t}\n\t\t\treturn undefined;\n\t\tcase \"image\": {\n\t\t\tlet url: string;\n\t\t\tif (part.source.type === \"data\") {\n\t\t\t\turl = part.source.value.startsWith(\"data:\")\n\t\t\t\t\t? part.source.value\n\t\t\t\t\t: `data:${part.source.mimeType};base64,${part.source.value}`;\n\t\t\t} else {\n\t\t\t\turl = part.source.value;\n\t\t\t}\n\t\t\treturn { type: \"image_url\", image_url: { url } };\n\t\t}\n\t\tdefault:\n\t\t\t// audio, video, document — not supported for now\n\t\t\tconsole.warn(\n\t\t\t\t`[@cloudflare/tanstack-ai] Unsupported content part type \"${part.type}\" — skipping`,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Build OpenAI-compatible user message content from TanStack AI content.\n *\n * If the content has only text parts, returns a plain string.\n * If it includes image parts, returns an array of content parts in\n * OpenAI's multi-modal format (text + image_url).\n */\nfunction buildUserContent(\n\tcontent: ModelMessage[\"content\"],\n): string | OpenAI.Chat.ChatCompletionContentPart[] {\n\tif (content === null) return \"\";\n\tif (typeof content === \"string\") return content;\n\n\tconst hasImages = content.some((p) => p.type === \"image\");\n\tif (!hasImages) {\n\t\treturn content.flatMap((p) => (p.type === \"text\" ? [p.content] : [])).join(\"\");\n\t}\n\n\tconst parts: OpenAI.Chat.ChatCompletionContentPart[] = [];\n\tfor (const part of content) {\n\t\tconst converted = convertContentPart(part);\n\t\tif (converted) {\n\t\t\tparts.push(converted);\n\t\t}\n\t}\n\treturn parts;\n}\n\nfunction buildOpenAIMessages(\n\tsystemPrompts: SystemPrompt[] | undefined,\n\tmessages: ModelMessage[],\n\toptions?: { includeToolMessages?: boolean },\n): OpenAI.Chat.ChatCompletionMessageParam[] {\n\tconst includeTools = options?.includeToolMessages ?? true;\n\tconst openAIMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [];\n\n\tif (systemPrompts && systemPrompts.length > 0) {\n\t\topenAIMessages.push({\n\t\t\trole: \"system\",\n\t\t\tcontent: systemPrompts\n\t\t\t\t.map((prompt) => (typeof prompt === \"string\" ? prompt : prompt.content))\n\t\t\t\t.join(\"\\n\"),\n\t\t});\n\t}\n\n\tfor (const message of messages) {\n\t\tif (message.role === \"user\") {\n\t\t\topenAIMessages.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: buildUserContent(message.content),\n\t\t\t});\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst assistantMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: extractTextContent(message.content),\n\t\t\t};\n\t\t\tif (includeTools && message.toolCalls && message.toolCalls.length > 0) {\n\t\t\t\tassistantMessage.tool_calls = message.toolCalls.map((tc) => ({\n\t\t\t\t\tid: tc.id,\n\t\t\t\t\ttype: \"function\" as const,\n\t\t\t\t\tfunction: {\n\t\t\t\t\t\tname: tc.function.name,\n\t\t\t\t\t\targuments: tc.function.arguments,\n\t\t\t\t\t},\n\t\t\t\t}));\n\t\t\t}\n\t\t\topenAIMessages.push(assistantMessage);\n\t\t} else if (includeTools && message.role === \"tool\") {\n\t\t\tlet toolContent: string;\n\t\t\tif (typeof message.content === \"string\") {\n\t\t\t\ttry {\n\t\t\t\t\tJSON.parse(message.content);\n\t\t\t\t\ttoolContent = message.content;\n\t\t\t\t} catch {\n\t\t\t\t\ttoolContent = JSON.stringify(message.content);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttoolContent = JSON.stringify(message.content);\n\t\t\t}\n\t\t\topenAIMessages.push({\n\t\t\t\trole: \"tool\",\n\t\t\t\ttool_call_id: message.toolCallId || `tool_${crypto.randomUUID().slice(0, 8)}`,\n\t\t\t\tcontent: toolContent,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn openAIMessages;\n}\n\nfunction buildOpenAITools(\n\ttools: Array<{ name: string; description: string; inputSchema?: unknown }> | undefined,\n): OpenAI.Chat.ChatCompletionTool[] | undefined {\n\tif (!tools) return undefined;\n\treturn tools.map((tool) => ({\n\t\ttype: \"function\" as const,\n\t\tfunction: {\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: tool.inputSchema as Record<string, unknown>,\n\t\t},\n\t}));\n}\n\n// ---------------------------------------------------------------------------\n// ID generation\n// ---------------------------------------------------------------------------\n\nfunction generateId(prefix = \"chatcmpl\"): string {\n\treturn `${prefix}-${crypto.randomUUID().replace(/-/g, \"\").slice(0, 16)}`;\n}\n\n// ---------------------------------------------------------------------------\n// modelOptions normalization\n//\n// Users pass Workers AI-specific chat params (e.g. `reasoning_effort`,\n// `chat_template_kwargs`) via `modelOptions`. We merge these into the outbound\n// request body so they reach the binding / REST / gateway transports.\n//\n// Spread order matters: these go FIRST in the request body so that TanStack-AI\n// managed fields (`model`, `messages`, `temperature`, `max_tokens`, `stream`,\n// `tools`, `response_format`, ...) always win if a user accidentally sets\n// them both at the top level and inside `modelOptions`.\n//\n// `undefined` values are stripped so that JSON.stringify (and our binding shim\n// which does `!== undefined` checks) see them as absent. `null` values are\n// preserved — they're meaningful for fields like `reasoning_effort: null`\n// which explicitly disables reasoning on some models.\n// ---------------------------------------------------------------------------\nfunction normalizeModelOptions(\n\tmodelOptions: WorkersAiTextModelOptions | undefined,\n): Record<string, unknown> {\n\t// Guard against runtime misuse. TanStack AI types this as an object, but\n\t// users can always bypass with `as any`. `Object.entries` on a string\n\t// surprisingly returns per-character tuples (e.g. `Object.entries(\"ab\") →\n\t// [[\"0\",\"a\"],[\"1\",\"b\"]]`), which would leak spurious keys into the body.\n\t// Arrays similarly become index-keyed. Only accept plain objects.\n\tif (modelOptions === null || typeof modelOptions !== \"object\" || Array.isArray(modelOptions)) {\n\t\treturn {};\n\t}\n\tconst out: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(modelOptions)) {\n\t\tif (value !== undefined) out[key] = value;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// WorkersAiTextAdapter: chat / structured output via OpenAI Chat Completions\n// ---------------------------------------------------------------------------\n\nexport class WorkersAiTextAdapter<TModel extends WorkersAiTextModel> extends BaseTextAdapter<\n\tTModel,\n\tWorkersAiTextModelOptions,\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- remaining BaseTextAdapter generics are opaque\n\tany,\n\tany\n> {\n\tname = \"workers-ai\" as const;\n\n\tprivate client: OpenAI;\n\n\tconstructor(model: TModel, config: WorkersAiAdapterConfig) {\n\t\tsuper({ apiKey: \"unused\" }, model);\n\t\tthis.client = buildWorkersAiClient(config);\n\t}\n\n\tasync *chatStream(options: TextOptions<WorkersAiTextModelOptions>): AsyncIterable<StreamChunk> {\n\t\tconst { systemPrompts, messages, tools, model, modelOptions } = options;\n\t\tconst extraBody = normalizeModelOptions(modelOptions);\n\n\t\tconst openAIMessages = buildOpenAIMessages(systemPrompts, messages);\n\t\tconst openAITools = buildOpenAITools(tools);\n\n\t\tconst timestamp = Date.now();\n\t\tconst runId = options.runId ?? generateId();\n\t\tconst threadId = options.threadId ?? generateId(\"thread\");\n\t\tconst messageId = generateId();\n\t\tlet hasEmittedRunStarted = false;\n\t\tlet hasEmittedTextMessageStart = false;\n\t\tlet accumulatedContent = \"\";\n\t\tlet hasEmittedStepStarted = false;\n\t\tlet accumulatedReasoning = \"\";\n\t\tconst stepId = generateId();\n\t\tlet hasReceivedFinishReason = false;\n\t\tconst toolCallsInProgress = new Map<\n\t\t\tnumber,\n\t\t\t{\n\t\t\t\tid: string;\n\t\t\t\tname: string;\n\t\t\t\targuments: string;\n\t\t\t\tstarted: boolean;\n\t\t\t\t/** Number of `arguments` chars already forwarded via TOOL_CALL_ARGS. */\n\t\t\t\temittedArgsLength: number;\n\t\t\t}\n\t\t>();\n\n\t\ttry {\n\t\t\tlet stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>;\n\t\t\ttry {\n\t\t\t\tstream = await this.client.chat.completions.create({\n\t\t\t\t\t...extraBody,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\tmessages: openAIMessages,\n\t\t\t\t\ttools: openAITools,\n\t\t\t\t\tstream: true,\n\t\t\t\t\tstream_options: { include_usage: true },\n\t\t\t\t} as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming);\n\t\t\t} catch (streamError: unknown) {\n\t\t\t\t// Some models (e.g. GPT-OSS) don't support streaming via the REST API.\n\t\t\t\t// Fall back to a non-streaming call and yield the result as a single chunk.\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[tanstack-ai] Streaming failed, falling back to non-streaming:\",\n\t\t\t\t\tstreamError instanceof Error ? streamError.message : streamError,\n\t\t\t\t);\n\t\t\t\tconst nonStreamResult = await this.client.chat.completions.create({\n\t\t\t\t\t...extraBody,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\tmessages: openAIMessages,\n\t\t\t\t\ttools: openAITools,\n\t\t\t\t} as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming);\n\n\t\t\t\tyield {\n\t\t\t\t\ttype: EventType.RUN_STARTED,\n\t\t\t\t\trunId,\n\t\t\t\t\tthreadId,\n\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t} satisfies StreamChunk;\n\n\t\t\t\tconst msg = nonStreamResult.choices[0]?.message;\n\t\t\t\tif (msg?.content) {\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_START,\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_CONTENT,\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\tdelta: msg.content,\n\t\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_END,\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\tif (msg?.tool_calls) {\n\t\t\t\t\tfor (const tc of msg.tool_calls) {\n\t\t\t\t\t\tif (tc.type !== \"function\") continue;\n\t\t\t\t\t\tconst fn = tc.function;\n\t\t\t\t\t\tlet parsedInput: unknown = {};\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tparsedInput = fn.arguments ? JSON.parse(fn.arguments) : {};\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tparsedInput = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: EventType.TOOL_CALL_START,\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolCallName: fn.name,\n\t\t\t\t\t\t\ttoolName: fn.name,\n\t\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\tindex: 0,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: EventType.TOOL_CALL_END,\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: fn.name,\n\t\t\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\tinput: parsedInput,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst finishReason = nonStreamResult.choices[0]?.finish_reason;\n\t\t\t\tyield {\n\t\t\t\t\ttype: EventType.RUN_FINISHED,\n\t\t\t\t\trunId,\n\t\t\t\t\tthreadId,\n\t\t\t\t\tmodel: nonStreamResult.model || model || this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t\tusage: nonStreamResult.usage\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tpromptTokens: nonStreamResult.usage.prompt_tokens,\n\t\t\t\t\t\t\t\tcompletionTokens: nonStreamResult.usage.completion_tokens,\n\t\t\t\t\t\t\t\ttotalTokens: nonStreamResult.usage.total_tokens,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\tfinishReason:\n\t\t\t\t\t\tfinishReason === \"tool_calls\" || finishReason === \"function_call\"\n\t\t\t\t\t\t\t? \"tool_calls\"\n\t\t\t\t\t\t\t: ((finishReason as \"stop\" | \"length\" | \"content_filter\") ?? \"stop\"),\n\t\t\t\t} satisfies StreamChunk;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor await (const chunk of stream) {\n\t\t\t\tif (!chunk.choices || chunk.choices.length === 0) continue;\n\t\t\t\tconst choice = chunk.choices[0];\n\t\t\t\tif (!choice) continue;\n\n\t\t\t\t// Emit RUN_STARTED on first chunk\n\t\t\t\tif (!hasEmittedRunStarted) {\n\t\t\t\t\thasEmittedRunStarted = true;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.RUN_STARTED,\n\t\t\t\t\t\trunId,\n\t\t\t\t\t\tthreadId,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\tconst delta = choice.delta;\n\n\t\t\t\t// Reasoning content (used by models like QwQ, DeepSeek R1, Kimi K2.5)\n\t\t\t\t// The OpenAI SDK doesn't type this field, but models send it as an extension.\n\t\t\t\tconst reasoningContent = ((delta as Record<string, unknown>).reasoning_content ??\n\t\t\t\t\t(delta as Record<string, unknown>).reasoning) as string | undefined;\n\t\t\t\tif (reasoningContent) {\n\t\t\t\t\t// RUN_STARTED is already guaranteed by the guard above\n\t\t\t\t\tif (!hasEmittedStepStarted) {\n\t\t\t\t\t\thasEmittedStepStarted = true;\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: EventType.STEP_STARTED,\n\t\t\t\t\t\t\tstepName: \"thinking\",\n\t\t\t\t\t\t\tstepId,\n\t\t\t\t\t\t\tstepType: \"thinking\",\n\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\t\t\t\t\taccumulatedReasoning += reasoningContent;\n\t\t\t\t\t// TODO: TanStack AI's StreamProcessor currently treats STEP_FINISHED as an\n\t\t\t\t\t// incremental reasoning event (with `delta` + accumulated `content`), so we\n\t\t\t\t\t// emit one per token. If TanStack AI adds a dedicated STEP_CONTENT event\n\t\t\t\t\t// type, this should be updated to emit STEP_CONTENT per token and a single\n\t\t\t\t\t// STEP_FINISHED when reasoning ends (i.e. when the first non-reasoning\n\t\t\t\t\t// content or finish_reason arrives).\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.STEP_FINISHED,\n\t\t\t\t\t\tstepName: \"thinking\",\n\t\t\t\t\t\tstepId,\n\t\t\t\t\t\tdelta: reasoningContent,\n\t\t\t\t\t\tcontent: accumulatedReasoning,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\t// Text content\n\t\t\t\tif (delta.content) {\n\t\t\t\t\tif (!hasEmittedTextMessageStart) {\n\t\t\t\t\t\thasEmittedTextMessageStart = true;\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_START,\n\t\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\n\t\t\t\t\taccumulatedContent += delta.content;\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_CONTENT,\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\tdelta: delta.content,\n\t\t\t\t\t\tcontent: accumulatedContent,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\t// Tool calls\n\t\t\t\tif (delta.tool_calls) {\n\t\t\t\t\tfor (const toolCallDelta of delta.tool_calls) {\n\t\t\t\t\t\tconst index = toolCallDelta.index;\n\n\t\t\t\t\t\tif (!toolCallsInProgress.has(index)) {\n\t\t\t\t\t\t\t// Always generate a unique ID per tool call index.\n\t\t\t\t\t\t\t// The backend may send the same ID for multiple tool calls,\n\t\t\t\t\t\t\t// so we cannot trust toolCallDelta.id to be unique.\n\t\t\t\t\t\t\ttoolCallsInProgress.set(index, {\n\t\t\t\t\t\t\t\tid: generateId(\"chatcmpl-tool\"),\n\t\t\t\t\t\t\t\tname: toolCallDelta.function?.name || \"\",\n\t\t\t\t\t\t\t\targuments: \"\",\n\t\t\t\t\t\t\t\tstarted: false,\n\t\t\t\t\t\t\t\temittedArgsLength: 0,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst toolCall = toolCallsInProgress.get(index)!;\n\n\t\t\t\t\t\t// Only update name if provided (ID is already set at creation time\n\t\t\t\t\t\t// and should not be overwritten by subsequent chunks that may have\n\t\t\t\t\t\t// duplicate/shared IDs from the backend)\n\t\t\t\t\t\tif (toolCallDelta.function?.name) {\n\t\t\t\t\t\t\ttoolCall.name = toolCallDelta.function.name;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (toolCallDelta.function?.arguments) {\n\t\t\t\t\t\t\ttoolCall.arguments += toolCallDelta.function.arguments;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Emit TOOL_CALL_START once we have id and name. We must wait\n\t\t\t\t\t\t// for the name: TanStack AI's StreamProcessor reads the tool\n\t\t\t\t\t\t// name ONLY from TOOL_CALL_START (it is never updated by later\n\t\t\t\t\t\t// events), so emitting early with an empty name produces a\n\t\t\t\t\t\t// tool-call part with no `name`, breaking dispatch (issue #523).\n\t\t\t\t\t\tif (toolCall.id && toolCall.name && !toolCall.started) {\n\t\t\t\t\t\t\ttoolCall.started = true;\n\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\ttype: EventType.TOOL_CALL_START,\n\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\ttoolCallName: toolCall.name,\n\t\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\tindex,\n\t\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Stream any argument fragments that haven't been forwarded\n\t\t\t\t\t\t// yet. We track the emitted length rather than forwarding the\n\t\t\t\t\t\t// raw per-chunk delta because some models stream argument\n\t\t\t\t\t\t// fragments BEFORE the name arrives; those are buffered in\n\t\t\t\t\t\t// `arguments` while we wait for the name, and must be flushed\n\t\t\t\t\t\t// once START is emitted so the full argument string reaches\n\t\t\t\t\t\t// the consumer (issue #523).\n\t\t\t\t\t\tif (toolCall.started && toolCall.arguments.length > toolCall.emittedArgsLength) {\n\t\t\t\t\t\t\tconst argsDelta = toolCall.arguments.slice(toolCall.emittedArgsLength);\n\t\t\t\t\t\t\ttoolCall.emittedArgsLength = toolCall.arguments.length;\n\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\ttype: EventType.TOOL_CALL_ARGS,\n\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\tdelta: argsDelta,\n\t\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Finish\n\t\t\t\tif (choice.finish_reason) {\n\t\t\t\t\thasReceivedFinishReason = true;\n\n\t\t\t\t\t// End tool calls\n\t\t\t\t\tif (choice.finish_reason === \"tool_calls\" || toolCallsInProgress.size > 0) {\n\t\t\t\t\t\tfor (const [, toolCall] of toolCallsInProgress) {\n\t\t\t\t\t\t\tlet parsedInput: unknown = {};\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tparsedInput = toolCall.arguments\n\t\t\t\t\t\t\t\t\t? JSON.parse(toolCall.arguments)\n\t\t\t\t\t\t\t\t\t: {};\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tparsedInput = {};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\ttype: EventType.TOOL_CALL_END,\n\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\t\tinput: parsedInput,\n\t\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst computedFinishReason =\n\t\t\t\t\t\tchoice.finish_reason === \"tool_calls\" ||\n\t\t\t\t\t\tchoice.finish_reason === \"function_call\" ||\n\t\t\t\t\t\ttoolCallsInProgress.size > 0\n\t\t\t\t\t\t\t? \"tool_calls\"\n\t\t\t\t\t\t\t: (choice.finish_reason as \"stop\" | \"length\" | \"content_filter\");\n\n\t\t\t\t\t// End text message if started\n\t\t\t\t\tif (hasEmittedTextMessageStart) {\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_END,\n\t\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Emit RUN_FINISHED\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.RUN_FINISHED,\n\t\t\t\t\t\trunId,\n\t\t\t\t\t\tthreadId,\n\t\t\t\t\t\tmodel: chunk.model || model || this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\tusage: chunk.usage\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tpromptTokens: chunk.usage.prompt_tokens,\n\t\t\t\t\t\t\t\t\tcompletionTokens: chunk.usage.completion_tokens,\n\t\t\t\t\t\t\t\t\ttotalTokens: chunk.usage.total_tokens,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tfinishReason: computedFinishReason,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Premature stream termination: the stream ended without a finish_reason.\n\t\t\t// This can happen when Workers AI truncates a response or the connection drops.\n\t\t\t// Emit proper closing events so the consumer doesn't hang.\n\t\t\tif (hasEmittedRunStarted && !hasReceivedFinishReason) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[tanstack-ai] Stream ended without finish_reason — possible truncation or connection drop\",\n\t\t\t\t);\n\n\t\t\t\t// Close any open tool calls\n\t\t\t\tfor (const [, toolCall] of toolCallsInProgress) {\n\t\t\t\t\tif (toolCall.started) {\n\t\t\t\t\t\tlet parsedInput: unknown = {};\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tparsedInput = toolCall.arguments ? JSON.parse(toolCall.arguments) : {};\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tparsedInput = {};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\ttype: EventType.TOOL_CALL_END,\n\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t\t\tinput: parsedInput,\n\t\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Close text message if open\n\t\t\t\tif (hasEmittedTextMessageStart) {\n\t\t\t\t\tyield {\n\t\t\t\t\t\ttype: EventType.TEXT_MESSAGE_END,\n\t\t\t\t\t\tmessageId,\n\t\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\t\ttimestamp,\n\t\t\t\t\t} satisfies StreamChunk;\n\t\t\t\t}\n\n\t\t\t\tyield {\n\t\t\t\t\ttype: EventType.RUN_FINISHED,\n\t\t\t\t\trunId,\n\t\t\t\t\tthreadId,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t\tfinishReason: \"stop\",\n\t\t\t\t} satisfies StreamChunk;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tconst code =\n\t\t\t\terror instanceof Error ? (error as Error & { code?: string }).code : undefined;\n\t\t\tif (!hasEmittedRunStarted) {\n\t\t\t\tyield {\n\t\t\t\t\ttype: EventType.RUN_STARTED,\n\t\t\t\t\trunId,\n\t\t\t\t\tthreadId,\n\t\t\t\t\tmodel: model ?? this.model,\n\t\t\t\t\ttimestamp,\n\t\t\t\t} satisfies StreamChunk;\n\t\t\t}\n\t\t\tyield {\n\t\t\t\ttype: EventType.RUN_ERROR,\n\t\t\t\trunId,\n\t\t\t\tthreadId,\n\t\t\t\tmodel: model ?? this.model,\n\t\t\t\ttimestamp,\n\t\t\t\tmessage: message || \"Unknown error\",\n\t\t\t\tcode,\n\t\t\t\terror: {\n\t\t\t\t\tmessage: message || \"Unknown error\",\n\t\t\t\t\tcode,\n\t\t\t\t},\n\t\t\t} satisfies StreamChunk;\n\t\t}\n\t}\n\n\tasync structuredOutput(\n\t\toptions: StructuredOutputOptions<WorkersAiTextModelOptions>,\n\t): Promise<StructuredOutputResult<unknown>> {\n\t\tconst { outputSchema, chatOptions } = options;\n\t\tconst { systemPrompts, messages, model, modelOptions } = chatOptions;\n\t\tconst extraBody = normalizeModelOptions(modelOptions);\n\n\t\tconst openAIMessages = buildOpenAIMessages(systemPrompts, messages, {\n\t\t\tincludeToolMessages: false,\n\t\t});\n\n\t\tconst response = await this.client.chat.completions.create({\n\t\t\t...extraBody,\n\t\t\tmodel: model ?? this.model,\n\t\t\tmessages: openAIMessages,\n\t\t\tstream: false,\n\t\t\tresponse_format: {\n\t\t\t\ttype: \"json_schema\",\n\t\t\t\tjson_schema: {\n\t\t\t\t\tname: \"structured_output\",\n\t\t\t\t\tstrict: true,\n\t\t\t\t\tschema: outputSchema,\n\t\t\t\t},\n\t\t\t},\n\t\t} as OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming);\n\n\t\tconst choice = response.choices?.[0];\n\n\t\tif (!choice) {\n\t\t\tthrow new Error(\n\t\t\t\t`Workers AI structured output returned no choices: ${JSON.stringify(response)}`,\n\t\t\t);\n\t\t}\n\n\t\tconst rawContent = choice.message?.content ?? \"\";\n\n\t\t// Workers AI REST endpoint may return `content` as an already-parsed object\n\t\t// when using json_schema response format, so normalise both cases.\n\t\tlet data: unknown;\n\t\tlet rawText: string;\n\n\t\tif (typeof rawContent === \"string\") {\n\t\t\trawText = rawContent;\n\t\t\ttry {\n\t\t\t\tdata = JSON.parse(rawText);\n\t\t\t} catch {\n\t\t\t\tdata = rawText;\n\t\t\t}\n\t\t} else {\n\t\t\t// Already an object — stringify for rawText, use directly for data\n\t\t\tdata = rawContent;\n\t\t\trawText = JSON.stringify(rawContent);\n\t\t}\n\n\t\treturn { data, rawText };\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Factory functions\n// ---------------------------------------------------------------------------\n\nexport function createWorkersAiChat(model: WorkersAiTextModel, config: WorkersAiAdapterConfig) {\n\treturn new WorkersAiTextAdapter(model, config);\n}\n"],"mappings":"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"}
@@ -56,4 +56,4 @@ async function binaryToBase64(result, binaryKey = "image") {
56
56
  //#endregion
57
57
  export { uint8ArrayToBase64 as n, binaryToBase64 as t };
58
58
 
59
- //# sourceMappingURL=binary-p4H_N_3M.mjs.map
59
+ //# sourceMappingURL=binary-B5YCVsro.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"binary-C9FAYwZj.cjs","names":[],"sources":["../src/utils/binary.ts"],"sourcesContent":["/**\n * Shared binary-data utilities for Workers AI adapters.\n *\n * Workers AI returns binary data (images, audio) in various formats —\n * Uint8Array, ArrayBuffer, ReadableStream, or JSON objects with a base64 field.\n * These helpers normalise everything into base64 strings for the TanStack AI\n * result types.\n */\n\n/**\n * Convert a Uint8Array to a base64 string.\n */\nexport function uint8ArrayToBase64(bytes: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]!);\n\t}\n\treturn btoa(binary);\n}\n\n/**\n * Collect all chunks from a ReadableStream<Uint8Array> into a single Uint8Array.\n */\nexport async function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {\n\tconst reader = stream.getReader();\n\tconst chunks: Uint8Array[] = [];\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\tchunks.push(value);\n\t}\n\tconst totalLength = chunks.reduce((acc, c) => acc + c.length, 0);\n\tconst combined = new Uint8Array(totalLength);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tcombined.set(chunk, offset);\n\t\toffset += chunk.length;\n\t}\n\treturn combined;\n}\n\n/**\n * Normalise a Workers AI binary response to a base64 string.\n *\n * Handles:\n * - `Uint8Array` / `ArrayBuffer` — raw bytes\n * - `ReadableStream<Uint8Array>` — streamed bytes\n * - `{ [binaryKey]: \"base64...\" }` — JSON wrapper (e.g. `{ image: \"...\" }`)\n *\n * @param result The raw value returned from Workers AI\n * @param binaryKey The JSON field to look for when the result is an object\n * (defaults to `\"image\"`; pass `\"audio\"` for TTS responses)\n */\nexport async function binaryToBase64(\n\tresult: unknown,\n\tbinaryKey: string = \"image\",\n): Promise<string> {\n\tif (result instanceof ReadableStream) {\n\t\treturn uint8ArrayToBase64(await collectStream(result));\n\t}\n\n\tif (result instanceof Uint8Array || result instanceof ArrayBuffer) {\n\t\tconst bytes = result instanceof ArrayBuffer ? new Uint8Array(result) : result;\n\t\treturn uint8ArrayToBase64(bytes);\n\t}\n\n\t// Some models return { image: \"base64...\" } or { audio: \"base64...\" }\n\tif (typeof result === \"object\" && result !== null && binaryKey in result) {\n\t\treturn (result as Record<string, string>)[binaryKey]!;\n\t}\n\n\tthrow new Error(\n\t\t`Unexpected binary response format from Workers AI (expected Uint8Array, ArrayBuffer, ReadableStream, or { ${binaryKey}: string })`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,mBAAmB,OAA2B;CAC7D,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IACjC,WAAU,OAAO,aAAa,MAAM,GAAI;AAEzC,QAAO,KAAK,OAAO;;;;;AAMpB,eAAsB,cAAc,QAAyD;CAC5F,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,SAAuB,EAAE;AAC/B,QAAO,MAAM;EACZ,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM;AACV,SAAO,KAAK,MAAM;;CAEnB,MAAM,cAAc,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,EAAE;CAChE,MAAM,WAAW,IAAI,WAAW,YAAY;CAC5C,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ;AAC3B,WAAS,IAAI,OAAO,OAAO;AAC3B,YAAU,MAAM;;AAEjB,QAAO;;;;;;;;;;;;;;AAeR,eAAsB,eACrB,QACA,YAAoB,SACF;AAClB,KAAI,kBAAkB,eACrB,QAAO,mBAAmB,MAAM,cAAc,OAAO,CAAC;AAGvD,KAAI,kBAAkB,cAAc,kBAAkB,YAErD,QAAO,mBADO,kBAAkB,cAAc,IAAI,WAAW,OAAO,GAAG,OACvC;AAIjC,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,OACjE,QAAQ,OAAkC;AAG3C,OAAM,IAAI,MACT,6GAA6G,UAAU,aACvH"}
1
+ {"version":3,"file":"binary-B5YCVsro.mjs","names":[],"sources":["../src/utils/binary.ts"],"sourcesContent":["/**\n * Shared binary-data utilities for Workers AI adapters.\n *\n * Workers AI returns binary data (images, audio) in various formats —\n * Uint8Array, ArrayBuffer, ReadableStream, or JSON objects with a base64 field.\n * These helpers normalise everything into base64 strings for the TanStack AI\n * result types.\n */\n\n/**\n * Convert a Uint8Array to a base64 string.\n */\nexport function uint8ArrayToBase64(bytes: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]!);\n\t}\n\treturn btoa(binary);\n}\n\n/**\n * Collect all chunks from a ReadableStream<Uint8Array> into a single Uint8Array.\n */\nexport async function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {\n\tconst reader = stream.getReader();\n\tconst chunks: Uint8Array[] = [];\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\tchunks.push(value);\n\t}\n\tconst totalLength = chunks.reduce((acc, c) => acc + c.length, 0);\n\tconst combined = new Uint8Array(totalLength);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tcombined.set(chunk, offset);\n\t\toffset += chunk.length;\n\t}\n\treturn combined;\n}\n\n/**\n * Normalise a Workers AI binary response to a base64 string.\n *\n * Handles:\n * - `Uint8Array` / `ArrayBuffer` — raw bytes\n * - `ReadableStream<Uint8Array>` — streamed bytes\n * - `{ [binaryKey]: \"base64...\" }` — JSON wrapper (e.g. `{ image: \"...\" }`)\n *\n * @param result The raw value returned from Workers AI\n * @param binaryKey The JSON field to look for when the result is an object\n * (defaults to `\"image\"`; pass `\"audio\"` for TTS responses)\n */\nexport async function binaryToBase64(\n\tresult: unknown,\n\tbinaryKey: string = \"image\",\n): Promise<string> {\n\tif (result instanceof ReadableStream) {\n\t\treturn uint8ArrayToBase64(await collectStream(result));\n\t}\n\n\tif (result instanceof Uint8Array || result instanceof ArrayBuffer) {\n\t\tconst bytes = result instanceof ArrayBuffer ? new Uint8Array(result) : result;\n\t\treturn uint8ArrayToBase64(bytes);\n\t}\n\n\t// Some models return { image: \"base64...\" } or { audio: \"base64...\" }\n\tif (typeof result === \"object\" && result !== null && binaryKey in result) {\n\t\treturn (result as Record<string, string>)[binaryKey]!;\n\t}\n\n\tthrow new Error(\n\t\t`Unexpected binary response format from Workers AI (expected Uint8Array, ArrayBuffer, ReadableStream, or { ${binaryKey}: string })`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,mBAAmB,OAA2B;CAC7D,IAAI,SAAS;CACb,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KACjC,UAAU,OAAO,aAAa,MAAM,EAAG;CAExC,OAAO,KAAK,MAAM;AACnB;;;;AAKA,eAAsB,cAAc,QAAyD;CAC5F,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,SAAuB,CAAC;CAC9B,OAAO,MAAM;EACZ,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;EAC1C,IAAI,MAAM;EACV,OAAO,KAAK,KAAK;CAClB;CACA,MAAM,cAAc,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;CAC/D,MAAM,WAAW,IAAI,WAAW,WAAW;CAC3C,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC3B,SAAS,IAAI,OAAO,MAAM;EAC1B,UAAU,MAAM;CACjB;CACA,OAAO;AACR;;;;;;;;;;;;;AAcA,eAAsB,eACrB,QACA,YAAoB,SACF;CAClB,IAAI,kBAAkB,gBACrB,OAAO,mBAAmB,MAAM,cAAc,MAAM,CAAC;CAGtD,IAAI,kBAAkB,cAAc,kBAAkB,aAErD,OAAO,mBADO,kBAAkB,cAAc,IAAI,WAAW,MAAM,IAAI,MACxC;CAIhC,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,QACjE,OAAQ,OAAkC;CAG3C,MAAM,IAAI,MACT,6GAA6G,UAAU,YACxH;AACD"}
@@ -67,4 +67,4 @@ Object.defineProperty(exports, "uint8ArrayToBase64", {
67
67
  }
68
68
  });
69
69
 
70
- //# sourceMappingURL=binary-C9FAYwZj.cjs.map
70
+ //# sourceMappingURL=binary-CZhr1_2n.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"binary-p4H_N_3M.mjs","names":[],"sources":["../src/utils/binary.ts"],"sourcesContent":["/**\n * Shared binary-data utilities for Workers AI adapters.\n *\n * Workers AI returns binary data (images, audio) in various formats —\n * Uint8Array, ArrayBuffer, ReadableStream, or JSON objects with a base64 field.\n * These helpers normalise everything into base64 strings for the TanStack AI\n * result types.\n */\n\n/**\n * Convert a Uint8Array to a base64 string.\n */\nexport function uint8ArrayToBase64(bytes: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]!);\n\t}\n\treturn btoa(binary);\n}\n\n/**\n * Collect all chunks from a ReadableStream<Uint8Array> into a single Uint8Array.\n */\nexport async function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {\n\tconst reader = stream.getReader();\n\tconst chunks: Uint8Array[] = [];\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\tchunks.push(value);\n\t}\n\tconst totalLength = chunks.reduce((acc, c) => acc + c.length, 0);\n\tconst combined = new Uint8Array(totalLength);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tcombined.set(chunk, offset);\n\t\toffset += chunk.length;\n\t}\n\treturn combined;\n}\n\n/**\n * Normalise a Workers AI binary response to a base64 string.\n *\n * Handles:\n * - `Uint8Array` / `ArrayBuffer` — raw bytes\n * - `ReadableStream<Uint8Array>` — streamed bytes\n * - `{ [binaryKey]: \"base64...\" }` — JSON wrapper (e.g. `{ image: \"...\" }`)\n *\n * @param result The raw value returned from Workers AI\n * @param binaryKey The JSON field to look for when the result is an object\n * (defaults to `\"image\"`; pass `\"audio\"` for TTS responses)\n */\nexport async function binaryToBase64(\n\tresult: unknown,\n\tbinaryKey: string = \"image\",\n): Promise<string> {\n\tif (result instanceof ReadableStream) {\n\t\treturn uint8ArrayToBase64(await collectStream(result));\n\t}\n\n\tif (result instanceof Uint8Array || result instanceof ArrayBuffer) {\n\t\tconst bytes = result instanceof ArrayBuffer ? new Uint8Array(result) : result;\n\t\treturn uint8ArrayToBase64(bytes);\n\t}\n\n\t// Some models return { image: \"base64...\" } or { audio: \"base64...\" }\n\tif (typeof result === \"object\" && result !== null && binaryKey in result) {\n\t\treturn (result as Record<string, string>)[binaryKey]!;\n\t}\n\n\tthrow new Error(\n\t\t`Unexpected binary response format from Workers AI (expected Uint8Array, ArrayBuffer, ReadableStream, or { ${binaryKey}: string })`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,mBAAmB,OAA2B;CAC7D,IAAI,SAAS;AACb,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,IACjC,WAAU,OAAO,aAAa,MAAM,GAAI;AAEzC,QAAO,KAAK,OAAO;;;;;AAMpB,eAAsB,cAAc,QAAyD;CAC5F,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,SAAuB,EAAE;AAC/B,QAAO,MAAM;EACZ,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,MAAI,KAAM;AACV,SAAO,KAAK,MAAM;;CAEnB,MAAM,cAAc,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,EAAE;CAChE,MAAM,WAAW,IAAI,WAAW,YAAY;CAC5C,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ;AAC3B,WAAS,IAAI,OAAO,OAAO;AAC3B,YAAU,MAAM;;AAEjB,QAAO;;;;;;;;;;;;;;AAeR,eAAsB,eACrB,QACA,YAAoB,SACF;AAClB,KAAI,kBAAkB,eACrB,QAAO,mBAAmB,MAAM,cAAc,OAAO,CAAC;AAGvD,KAAI,kBAAkB,cAAc,kBAAkB,YAErD,QAAO,mBADO,kBAAkB,cAAc,IAAI,WAAW,OAAO,GAAG,OACvC;AAIjC,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,OACjE,QAAQ,OAAkC;AAG3C,OAAM,IAAI,MACT,6GAA6G,UAAU,aACvH"}
1
+ {"version":3,"file":"binary-CZhr1_2n.cjs","names":[],"sources":["../src/utils/binary.ts"],"sourcesContent":["/**\n * Shared binary-data utilities for Workers AI adapters.\n *\n * Workers AI returns binary data (images, audio) in various formats —\n * Uint8Array, ArrayBuffer, ReadableStream, or JSON objects with a base64 field.\n * These helpers normalise everything into base64 strings for the TanStack AI\n * result types.\n */\n\n/**\n * Convert a Uint8Array to a base64 string.\n */\nexport function uint8ArrayToBase64(bytes: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbinary += String.fromCharCode(bytes[i]!);\n\t}\n\treturn btoa(binary);\n}\n\n/**\n * Collect all chunks from a ReadableStream<Uint8Array> into a single Uint8Array.\n */\nexport async function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {\n\tconst reader = stream.getReader();\n\tconst chunks: Uint8Array[] = [];\n\twhile (true) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\tchunks.push(value);\n\t}\n\tconst totalLength = chunks.reduce((acc, c) => acc + c.length, 0);\n\tconst combined = new Uint8Array(totalLength);\n\tlet offset = 0;\n\tfor (const chunk of chunks) {\n\t\tcombined.set(chunk, offset);\n\t\toffset += chunk.length;\n\t}\n\treturn combined;\n}\n\n/**\n * Normalise a Workers AI binary response to a base64 string.\n *\n * Handles:\n * - `Uint8Array` / `ArrayBuffer` — raw bytes\n * - `ReadableStream<Uint8Array>` — streamed bytes\n * - `{ [binaryKey]: \"base64...\" }` — JSON wrapper (e.g. `{ image: \"...\" }`)\n *\n * @param result The raw value returned from Workers AI\n * @param binaryKey The JSON field to look for when the result is an object\n * (defaults to `\"image\"`; pass `\"audio\"` for TTS responses)\n */\nexport async function binaryToBase64(\n\tresult: unknown,\n\tbinaryKey: string = \"image\",\n): Promise<string> {\n\tif (result instanceof ReadableStream) {\n\t\treturn uint8ArrayToBase64(await collectStream(result));\n\t}\n\n\tif (result instanceof Uint8Array || result instanceof ArrayBuffer) {\n\t\tconst bytes = result instanceof ArrayBuffer ? new Uint8Array(result) : result;\n\t\treturn uint8ArrayToBase64(bytes);\n\t}\n\n\t// Some models return { image: \"base64...\" } or { audio: \"base64...\" }\n\tif (typeof result === \"object\" && result !== null && binaryKey in result) {\n\t\treturn (result as Record<string, string>)[binaryKey]!;\n\t}\n\n\tthrow new Error(\n\t\t`Unexpected binary response format from Workers AI (expected Uint8Array, ArrayBuffer, ReadableStream, or { ${binaryKey}: string })`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,mBAAmB,OAA2B;CAC7D,IAAI,SAAS;CACb,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KACjC,UAAU,OAAO,aAAa,MAAM,EAAG;CAExC,OAAO,KAAK,MAAM;AACnB;;;;AAKA,eAAsB,cAAc,QAAyD;CAC5F,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,SAAuB,CAAC;CAC9B,OAAO,MAAM;EACZ,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;EAC1C,IAAI,MAAM;EACV,OAAO,KAAK,KAAK;CAClB;CACA,MAAM,cAAc,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;CAC/D,MAAM,WAAW,IAAI,WAAW,WAAW;CAC3C,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC3B,SAAS,IAAI,OAAO,MAAM;EAC1B,UAAU,MAAM;CACjB;CACA,OAAO;AACR;;;;;;;;;;;;;AAcA,eAAsB,eACrB,QACA,YAAoB,SACF;CAClB,IAAI,kBAAkB,gBACrB,OAAO,mBAAmB,MAAM,cAAc,MAAM,CAAC;CAGtD,IAAI,kBAAkB,cAAc,kBAAkB,aAErD,OAAO,mBADO,kBAAkB,cAAc,IAAI,WAAW,MAAM,IAAI,MACxC;CAIhC,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,QACjE,OAAQ,OAAkC;CAG3C,MAAM,IAAI,MACT,6GAA6G,UAAU,YACxH;AACD"}
@@ -280,4 +280,4 @@ function transformWorkersAiStream(source, model) {
280
280
  //#endregion
281
281
  export { validateWorkersAiConfig as a, isDirectCredentialsConfig as i, createWorkersAiBindingFetch as n, isDirectBindingConfig as r, createGatewayFetch as t };
282
282
 
283
- //# sourceMappingURL=create-fetcher-CeUOJgrh.mjs.map
283
+ //# sourceMappingURL=create-fetcher-BnSnCaHf.mjs.map