@tanstack/openai-base 0.10.1 → 0.10.3

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 (29) hide show
  1. package/README.md +2 -2
  2. package/dist/esm/adapters/chat-completions-text.d.ts +15 -7
  3. package/dist/esm/adapters/chat-completions-text.js +23 -7
  4. package/dist/esm/adapters/chat-completions-text.js.map +1 -1
  5. package/dist/esm/adapters/chat-completions-tool-converter.test.d.ts +1 -0
  6. package/dist/esm/adapters/responses-text.d.ts +15 -7
  7. package/dist/esm/adapters/responses-text.js +141 -105
  8. package/dist/esm/adapters/responses-text.js.map +1 -1
  9. package/dist/esm/adapters/responses-tool-converter.test.d.ts +1 -0
  10. package/dist/esm/index.d.ts +1 -1
  11. package/dist/esm/index.js +2 -2
  12. package/dist/esm/tools/function-tool.test.d.ts +1 -0
  13. package/dist/esm/utils/schema-converter.d.ts +14 -0
  14. package/dist/esm/utils/schema-converter.js +106 -18
  15. package/dist/esm/utils/schema-converter.js.map +1 -1
  16. package/dist/esm/utils/tool-input-normalizer.d.ts +12 -0
  17. package/dist/esm/utils/tool-input-normalizer.js +36 -0
  18. package/dist/esm/utils/tool-input-normalizer.js.map +1 -0
  19. package/dist/esm/utils/tool-input-normalizer.test.d.ts +1 -0
  20. package/package.json +3 -3
  21. package/src/adapters/chat-completions-text.ts +51 -18
  22. package/src/adapters/chat-completions-tool-converter.test.ts +64 -0
  23. package/src/adapters/responses-text.ts +198 -130
  24. package/src/adapters/responses-tool-converter.test.ts +69 -0
  25. package/src/index.ts +4 -1
  26. package/src/tools/function-tool.test.ts +62 -0
  27. package/src/utils/schema-converter.ts +165 -20
  28. package/src/utils/tool-input-normalizer.test.ts +146 -0
  29. package/src/utils/tool-input-normalizer.ts +55 -0
package/README.md CHANGED
@@ -71,7 +71,7 @@ Per-provider quirks are handled via protected hooks:
71
71
  differences (extra fields, omitted fields, alternative encodings).
72
72
  - `extractReasoning` — surface a provider's reasoning channel into the
73
73
  shared `REASONING_*` AG-UI lifecycle.
74
- - `transformStructuredOutput`, `makeStructuredOutputCompatible` —
74
+ - `transformStructuredOutput`, `makeStructuredOutputCompatibleWithMap` —
75
75
  adjust structured-output handling for provider quirks (e.g. Groq's
76
76
  schema-shape requirements).
77
77
  - `processStreamChunks` — wrap the shared chunk processor for last-mile
@@ -117,5 +117,5 @@ self-hosted gateway, …), import the abstract adapters from this package
117
117
  and subclass them. The existing providers are worked examples —
118
118
  `@tanstack/ai-grok` is the simplest (xAI's API is a near-direct OpenAI
119
119
  Chat Completions clone), `@tanstack/ai-groq` shows the
120
- `processStreamChunks` and `makeStructuredOutputCompatible` override
120
+ `processStreamChunks` and `makeStructuredOutputCompatibleWithMap` override
121
121
  pattern.
@@ -1,7 +1,8 @@
1
1
  import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
2
+ import { StructuredOutputCompatibility } from '../utils/schema-converter.js';
2
3
  import { default as OpenAI } from 'openai';
3
4
  import { ChatCompletionChunk, ChatCompletionContentPart, ChatCompletionCreateParamsStreaming, ChatCompletionMessageParam } from 'openai/resources/chat/completions/completions';
4
- import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
5
+ import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, AdapterYieldChunk, TextOptions } from '@tanstack/ai';
5
6
  type ChatStreamState = {
6
7
  runId: string;
7
8
  threadId: string;
@@ -19,7 +20,7 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
19
20
  readonly name: string;
20
21
  protected client: OpenAI;
21
22
  constructor(model: TModel, name: string, client: OpenAI);
22
- chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
23
+ chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
23
24
  private handleChatStreamError;
24
25
  /**
25
26
  * Extracts a rejected tool call from a provider error. Returned calls are
@@ -53,7 +54,7 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
53
54
  * structured-output-transform hooks as `chatStream` / `structuredOutput` —
54
55
  * no per-subclass override should be needed.
55
56
  */
56
- structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
57
+ structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
57
58
  /**
58
59
  * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types
59
60
  * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,
@@ -61,9 +62,16 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
61
62
  * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.
62
63
  */
63
64
  protected isAbortError(error: unknown): boolean;
65
+ /**
66
+ * Strict conversion plus the inverse null-widening map for this request.
67
+ * Override this when schema conversion changes, so tool-input undo matches
68
+ * the wire schema.
69
+ */
70
+ protected makeStructuredOutputCompatibleWithMap(schema: Record<string, any>, originalRequired?: Array<string>): StructuredOutputCompatibility;
64
71
  /**
65
72
  * Applies provider-specific transformations for structured output compatibility.
66
- * Override this in subclasses to handle provider-specific quirks.
73
+ * Override `makeStructuredOutputCompatibleWithMap` when you need the inverse map
74
+ * to match the wire schema.
67
75
  */
68
76
  protected makeStructuredOutputCompatible(schema: Record<string, any>, originalRequired?: Array<string>): Record<string, any>;
69
77
  /**
@@ -92,7 +100,7 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
92
100
  * Processes streamed chunks from the Chat Completions API and yields AG-UI events.
93
101
  * Override this in subclasses to handle provider-specific stream behavior.
94
102
  */
95
- protected processStreamChunks(stream: AsyncIterable<ChatCompletionChunk>, options: TextOptions, aguiState: ChatStreamState): AsyncIterable<StreamChunk>;
103
+ protected processStreamChunks(stream: AsyncIterable<ChatCompletionChunk>, options: TextOptions, aguiState: ChatStreamState): AsyncIterable<AdapterYieldChunk>;
96
104
  /**
97
105
  * Maps common TextOptions to Chat Completions API request format.
98
106
  * Override this in subclasses to add provider-specific options.
@@ -120,10 +128,10 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
120
128
  * Normalizes message content to an array of ContentPart.
121
129
  * Handles backward compatibility with string content.
122
130
  */
123
- protected normalizeContent(content: string | null | Array<ContentPart>): Array<ContentPart>;
131
+ protected normalizeContent(content: string | null | undefined | Array<ContentPart>): Array<ContentPart>;
124
132
  /**
125
133
  * Extracts text content from a content value that may be string, null, or ContentPart array.
126
134
  */
127
- protected extractTextContent(content: string | null | Array<ContentPart>): string;
135
+ protected extractTextContent(content: string | null | undefined | Array<ContentPart>): string;
128
136
  }
129
137
  export {};
@@ -1,6 +1,7 @@
1
- import { makeStructuredOutputCompatible } from "../utils/schema-converter.js";
1
+ import { makeStructuredOutputCompatibleWithMap } from "../utils/schema-converter.js";
2
2
  import { buildChatCompletionsUsage } from "../usage.js";
3
3
  import { extractRequestOptions } from "../utils/request-options.js";
4
+ import { createToolInputNormalizer } from "../utils/tool-input-normalizer.js";
4
5
  import { convertToolsToChatCompletionsFormat } from "./chat-completions-tool-converter.js";
5
6
  import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
6
7
  import { toRunErrorPayload, toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
@@ -106,6 +107,8 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
106
107
  });
107
108
  yield {
108
109
  type: EventType.RUN_ERROR,
110
+ runId: aguiState.runId,
111
+ threadId: aguiState.threadId,
109
112
  model: options.model,
110
113
  timestamp: Date.now(),
111
114
  message: errorPayload.message,
@@ -229,6 +232,9 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
229
232
  timestamp: Date.now(),
230
233
  content: accumulatedReasoning
231
234
  };
235
+ reasoningMessageId = void 0;
236
+ stepId = void 0;
237
+ hasClosedReasoning = false;
232
238
  }
233
239
  }.bind(this);
234
240
  try {
@@ -440,11 +446,20 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
440
446
  return e.name === "APIUserAbortError" || e.name === "AbortError" || e.code === "ERR_CANCELED";
441
447
  }
442
448
  /**
449
+ * Strict conversion plus the inverse null-widening map for this request.
450
+ * Override this when schema conversion changes, so tool-input undo matches
451
+ * the wire schema.
452
+ */
453
+ makeStructuredOutputCompatibleWithMap(schema, originalRequired) {
454
+ return makeStructuredOutputCompatibleWithMap(schema, originalRequired);
455
+ }
456
+ /**
443
457
  * Applies provider-specific transformations for structured output compatibility.
444
- * Override this in subclasses to handle provider-specific quirks.
458
+ * Override `makeStructuredOutputCompatibleWithMap` when you need the inverse map
459
+ * to match the wire schema.
445
460
  */
446
461
  makeStructuredOutputCompatible(schema, originalRequired) {
447
- return makeStructuredOutputCompatible(schema, originalRequired);
462
+ return this.makeStructuredOutputCompatibleWithMap(schema, originalRequired).schema;
448
463
  }
449
464
  /**
450
465
  * Extract reasoning content from a stream chunk. Default returns
@@ -473,6 +488,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
473
488
  * Override this in subclasses to handle provider-specific stream behavior.
474
489
  */
475
490
  async *processStreamChunks(stream, options, aguiState) {
491
+ const normalizeToolInput = createToolInputNormalizer(options.tools, (schema, required) => this.makeStructuredOutputCompatibleWithMap(schema, required));
476
492
  let accumulatedContent = "";
477
493
  let hasEmittedTextMessageStart = false;
478
494
  let lastModel;
@@ -632,7 +648,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
632
648
  let parsedInput = {};
633
649
  if (toolCall.arguments) try {
634
650
  const parsed = JSON.parse(toolCall.arguments);
635
- parsedInput = parsed && typeof parsed === "object" ? parsed : {};
651
+ parsedInput = normalizeToolInput(toolCall.name, parsed && typeof parsed === "object" ? parsed : {});
636
652
  } catch (parseError) {
637
653
  options.logger.errors(`${this.name}.processStreamChunks tool-args JSON parse failed`, {
638
654
  error: toRunErrorPayload(parseError, `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`),
@@ -675,7 +691,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
675
691
  let parsedInput = {};
676
692
  if (toolCall.arguments) try {
677
693
  const parsed = JSON.parse(toolCall.arguments);
678
- parsedInput = parsed && typeof parsed === "object" ? parsed : {};
694
+ parsedInput = normalizeToolInput(toolCall.name, parsed && typeof parsed === "object" ? parsed : {});
679
695
  } catch (parseError) {
680
696
  options.logger.errors(`${this.name}.processStreamChunks tool-args JSON parse failed (drain)`, {
681
697
  error: toRunErrorPayload(parseError, `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`),
@@ -862,7 +878,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
862
878
  * Handles backward compatibility with string content.
863
879
  */
864
880
  normalizeContent(content) {
865
- if (content === null) return [];
881
+ if (content === null || content === void 0) return [];
866
882
  if (typeof content === "string") return [{
867
883
  type: "text",
868
884
  content
@@ -873,7 +889,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
873
889
  * Extracts text content from a content value that may be string, null, or ContentPart array.
874
890
  */
875
891
  extractTextContent(content) {
876
- if (content === null) return "";
892
+ if (content === null || content === void 0) return "";
877
893
  if (typeof content === "string") return content;
878
894
  return content.filter((p) => p.type === "text").map((p) => p.content).join("");
879
895
  }
@@ -1 +1 @@
1
- {"version":3,"file":"chat-completions-text.js","names":[],"sources":["../../../src/adapters/chat-completions-text.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n toRunErrorPayload,\n toRunErrorRawEvent,\n} from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport { extractRequestOptions } from '../utils/request-options'\nimport { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport { buildChatCompletionsUsage } from '../usage'\nimport { convertToolsToChatCompletionsFormat } from './chat-completions-tool-converter'\nimport type OpenAI from 'openai'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n ChatCompletionChunk,\n ChatCompletionContentPart,\n ChatCompletionCreateParamsStreaming,\n ChatCompletionMessageParam,\n} from 'openai/resources/chat/completions/completions'\nimport type {\n ContentPart,\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n RunFinishedEvent,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\n\ntype ChatStreamState = {\n runId: string\n threadId: string\n messageId: string\n hasEmittedRunStarted: boolean\n}\n\n/**\n * Shared implementation of the OpenAI Chat Completions API. Holds the\n * stream-accumulator + AG-UI lifecycle logic and calls the OpenAI SDK\n * directly. Subclasses (ai-openai, ai-grok, ai-groq) construct an OpenAI\n * client with their provider-specific `baseURL` / headers and pass it in.\n */\nexport abstract class OpenAIBaseChatCompletionsTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, unknown> = Record<string, unknown>,\n TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TMessageMetadata extends DefaultMessageMetadataByModality =\n DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadata,\n TToolCapabilities\n> {\n override readonly kind = 'text' as const\n readonly name: string\n protected client: OpenAI\n\n constructor(model: TModel, name: string, client: OpenAI) {\n super({}, model)\n this.name = name\n this.client = client\n }\n\n async *chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n // AG-UI lifecycle tracking (mutable state object for ESLint compatibility)\n const aguiState = {\n runId: generateId(this.name),\n threadId: options.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n hasEmittedRunStarted: false,\n }\n\n try {\n // mapOptionsToRequest can throw (e.g. fail-loud guards in convertMessage\n // for empty content or unsupported parts). Keep it inside the try so\n // those failures surface as a single RUN_ERROR event, matching every\n // other failure mode here — callers iterating chatStream then only need\n // one error-handling path instead of both a try/catch around iteration\n // and a RUN_ERROR handler.\n const requestParams = this.mapOptionsToRequest(options)\n options.logger.request(\n `activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: this.name, model: this.model },\n )\n const stream = await this.client.chat.completions.create(\n {\n ...requestParams,\n stream: true,\n stream_options: { include_usage: true },\n },\n extractRequestOptions(options.request),\n )\n\n yield* this.processStreamChunks(stream, options, aguiState)\n } catch (error: unknown) {\n yield* this.handleChatStreamError(error, options, aguiState, 'chatStream')\n }\n }\n\n private async *handleChatStreamError(\n error: unknown,\n options: TextOptions,\n aguiState: ChatStreamState,\n source: 'chatStream' | 'processStreamChunks',\n ): AsyncIterable<StreamChunk> {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.${source} failed`,\n )\n const rawEvent = toRunErrorRawEvent(error)\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n const rejectedToolCall = this.extractRejectedToolCall(\n rawEvent,\n errorPayload.message,\n )\n if (rejectedToolCall) {\n const toolCallId = generateId(this.name)\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: rejectedToolCall.toolName,\n toolName: rejectedToolCall.toolName,\n parentMessageId: aguiState.messageId,\n model: options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n delta: rejectedToolCall.arguments,\n args: rejectedToolCall.arguments,\n model: options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolCallName: rejectedToolCall.toolName,\n toolName: rejectedToolCall.toolName,\n ...(rejectedToolCall.input !== undefined && {\n input: rejectedToolCall.input,\n }),\n result: JSON.stringify({ error: rejectedToolCall.error }),\n state: 'output-error',\n model: options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp: Date.now(),\n finishReason: 'tool_calls',\n }\n return\n }\n\n options.logger.errors(`${this.name}.${source} fatal`, {\n error: errorPayload,\n source: `${this.name}.${source}`,\n })\n\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n },\n }\n }\n\n /**\n * Extracts a rejected tool call from a provider error. Returned calls are\n * emitted as non-executable `output-error` results so the model can repair them.\n */\n protected extractRejectedToolCall(\n _rawEvent: unknown,\n _fallbackMessage: string,\n ):\n | {\n toolName: string\n arguments: string\n input?: unknown\n error: string\n }\n | undefined {\n return undefined\n }\n\n /**\n * Generate structured output using the provider's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * OpenAI-compatible APIs have strict requirements for structured output:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply provider-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapOptionsToRequest(chatOptions)\n\n const jsonSchema = this.makeStructuredOutputCompatible(\n outputSchema,\n outputSchema.required,\n )\n\n try {\n // Strip stream_options which is only valid for streaming calls\n const {\n stream_options: _,\n stream: __,\n ...cleanParams\n } = requestParams as any\n chatOptions.logger.request(\n `activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n const response = await this.client.chat.completions.create(\n {\n ...cleanParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n },\n extractRequestOptions(chatOptions.request),\n )\n\n // Extract text content from the response. Fail loud on empty content\n // rather than letting it cascade into a JSON-parse error on '' — the\n // root cause (the model returned no content for the structured request)\n // is then visible in logs.\n const rawText = response.choices[0]?.message.content\n if (typeof rawText !== 'string' || rawText.length === 0) {\n throw new Error(\n `${this.name}.structuredOutput: response contained no content`,\n )\n }\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n // Final provider-specific shaping pass (default passthrough). Null-widening\n // from strict mode is undone by the engine, not here.\n const transformed = this.transformStructuredOutput(parsed)\n\n // Surface usage so non-stream structured paths (and\n // fallbackStructuredOutputStream) can forward tokens to middleware.\n const usage = buildChatCompletionsUsage(response.usage)\n return {\n data: transformed,\n rawText,\n ...(usage && { usage }),\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {\n error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),\n source: `${this.name}.structuredOutput`,\n })\n throw error\n }\n }\n\n /**\n * Stream structured output. Single Chat Completions request with\n * `response_format: json_schema` + `stream: true`. Emits the standard\n * AG-UI lifecycle (`RUN_STARTED` → `REASONING_*?` → `TEXT_MESSAGE_*`\n * carrying raw JSON deltas → terminal `CUSTOM 'structured-output.complete'`\n * → `RUN_FINISHED`). Subclasses use the same SDK-call / reasoning /\n * structured-output-transform hooks as `chatStream` / `structuredOutput` —\n * no per-subclass override should be needed.\n */\n async *structuredOutputStream(\n options: StructuredOutputOptions<TProviderOptions>,\n ): AsyncIterable<StreamChunk> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapOptionsToRequest(chatOptions)\n\n const jsonSchema = this.makeStructuredOutputCompatible(\n outputSchema,\n outputSchema.required,\n )\n\n const aguiState = {\n runId: generateId(this.name),\n threadId: chatOptions.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n hasEmittedRunStarted: false,\n }\n\n let accumulatedContent = ''\n let accumulatedReasoning = ''\n let hasEmittedTextMessageStart = false\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n let stepId: string | undefined\n let lastModel: string | undefined\n let lastUsage:\n | OpenAI.Chat.Completions.ChatCompletionChunk['usage']\n | undefined\n\n const closeReasoningLifecycle = function* (this: {\n name: string\n }): Generator<StreamChunk> {\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n }.bind(this)\n\n try {\n // Strip stream_options + tools from the base request. Structured output\n // sends `response_format: json_schema` and doesn't carry tools — keeping\n // them in the request can confuse strict-mode validation upstream.\n const {\n stream_options: _so,\n stream: _s,\n tools: _t,\n ...cleanParams\n } = requestParams\n\n chatOptions.logger.request(\n `activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n\n const stream = await this.client.chat.completions.create(\n {\n ...cleanParams,\n stream: true,\n stream_options: { include_usage: true },\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n },\n extractRequestOptions(chatOptions.request),\n )\n\n for await (const chunk of stream) {\n const choiceForLog = chunk.choices[0]\n chatOptions.logger.provider(\n `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? 'none'} hasContent=${!!choiceForLog?.delta.content} hasUsage=${!!chunk.usage}`,\n { provider: this.name, model: chunk.model },\n )\n\n if (chunk.model) lastModel = chunk.model\n\n // Usage may arrive on a chunk with empty `choices` (OpenAI's\n // include_usage terminal chunk) or piggybacked on a finish chunk\n // (`x_groq.usage` on Groq). Capture from either independent of\n // choices[0].\n const usage =\n chunk.usage ??\n (chunk as { x_groq?: { usage?: typeof chunk.usage } }).x_groq?.usage\n if (usage) lastUsage = usage\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n // Reasoning (via the extractReasoning hook — same hook as chatStream).\n const reasoning = this.extractReasoning(chunk)\n if (reasoning && reasoning.text) {\n if (!reasoningMessageId) {\n reasoningMessageId = generateId(this.name)\n stepId = generateId(this.name)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n accumulatedReasoning += reasoning.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: reasoning.text,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n }\n }\n\n const choice = chunk.choices[0]\n if (!choice) continue\n\n const deltaContent = choice.delta.content\n if (deltaContent) {\n yield* closeReasoningLifecycle()\n\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: aguiState.messageId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: aguiState.messageId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n }\n\n // Finalisation: close any open lifecycle, parse + validate, emit\n // terminal events. This block always runs unless the loop threw — abort\n // and SDK errors land in the catch block below.\n yield* closeReasoningLifecycle()\n\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n }\n\n if (accumulatedContent.length === 0) {\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n error: {\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n },\n }\n return\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(accumulatedContent)\n } catch {\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n message: `Failed to parse structured output as JSON. Content: ${accumulatedContent.slice(0, 200)}${accumulatedContent.length > 200 ? '...' : ''}`,\n code: 'parse-error',\n error: {\n message: 'Failed to parse structured output as JSON',\n code: 'parse-error',\n },\n }\n return\n }\n\n const transformed = this.transformStructuredOutput(parsed)\n\n yield {\n type: EventType.CUSTOM,\n name: 'structured-output.complete',\n value: {\n object: transformed,\n raw: accumulatedContent,\n ...(accumulatedReasoning ? { reasoning: accumulatedReasoning } : {}),\n },\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n finishReason: 'stop',\n ...(lastUsage && {\n usage: buildChatCompletionsUsage(lastUsage),\n }),\n }\n } catch (error: unknown) {\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n const isAbort = this.isAbortError(error)\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.structuredOutputStream failed`,\n )\n\n // Conditional `code` spread keeps the wire shape spec-compliant under\n // `exactOptionalPropertyTypes`: AG-UI's `RunErrorEvent.code` is `string?`\n // (absent vs explicit `undefined` matter).\n const resolvedCode = isAbort ? 'aborted' : errorPayload.code\n const rawEvent = isAbort ? undefined : toRunErrorRawEvent(error)\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n },\n }\n\n chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {\n error: errorPayload,\n source: `${this.name}.structuredOutputStream`,\n })\n }\n }\n\n /**\n * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types\n * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,\n * and standard `AbortError`s. Subclasses with proprietary error types (e.g.\n * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.\n */\n protected isAbortError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false\n const e = error as { name?: unknown; code?: unknown }\n return (\n e.name === 'APIUserAbortError' ||\n e.name === 'AbortError' ||\n e.code === 'ERR_CANCELED'\n )\n }\n\n /**\n * Applies provider-specific transformations for structured output compatibility.\n * Override this in subclasses to handle provider-specific quirks.\n */\n protected makeStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired?: Array<string>,\n ): Record<string, any> {\n return makeStructuredOutputCompatible(schema, originalRequired)\n }\n\n /**\n * Extract reasoning content from a stream chunk. Default returns\n * `undefined` because the OpenAI Chat Completions chunk shape doesn't\n * carry reasoning. The chunk param is typed `unknown` so an override can\n * narrow to its own SDK chunk type without an `as` dance — the base only\n * passes through `processStreamChunks`'s structurally-iterated chunk.\n */\n protected extractReasoning(_chunk: unknown): { text: string } | undefined {\n return undefined\n }\n\n /**\n * Final shaping pass applied to parsed structured-output JSON before it is\n * returned to the caller. Default is a passthrough.\n *\n * Provider `null`s are no longer stripped here: strict-mode null-widening is\n * now undone precisely by the engine (`undoNullWidening`, driven by the\n * schema's null-widening map) the moment the result is captured, so a blind\n * `transformNullsToUndefined` at the adapter would only destroy genuine\n * `.nullable()` nulls. Subclasses may still override to remap or reshape the\n * provider's structured output.\n */\n protected transformStructuredOutput(parsed: unknown): unknown {\n return parsed\n }\n\n /**\n * Processes streamed chunks from the Chat Completions API and yields AG-UI events.\n * Override this in subclasses to handle provider-specific stream behavior.\n */\n protected async *processStreamChunks(\n stream: AsyncIterable<ChatCompletionChunk>,\n options: TextOptions,\n aguiState: ChatStreamState,\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n let hasEmittedTextMessageStart = false\n let lastModel: string | undefined\n // Track usage from any chunk that carries it. With\n // `stream_options: { include_usage: true }` OpenAI emits a terminal chunk\n // whose `choices` is `[]` and only the `usage` field is populated; the\n // earlier `finish_reason` chunk does NOT include token counts. We must\n // therefore defer RUN_FINISHED until the iterator is exhausted so we can\n // pick up usage from the trailing chunk regardless of arrival order.\n let lastUsage: ChatCompletionChunk['usage'] | undefined\n let pendingFinishReason:\n | ChatCompletionChunk['choices'][number]['finish_reason']\n | undefined\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean // Track if TOOL_CALL_START has been emitted\n }\n >()\n\n // Reasoning lifecycle (driven by extractReasoning() hook — see method\n // docs). The base wire format (OpenAI Chat Completions) has no reasoning,\n // so these stay unused for openai/grok/groq. OpenRouter etc. opt in.\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n // Legacy STEP_STARTED/STEP_FINISHED pair emitted alongside REASONING_*\n // for back-compat with consumers (UI, devtools) that haven't migrated\n // to the spec REASONING_* events yet.\n let stepId: string | undefined\n let accumulatedReasoning = ''\n // Track whether ANY tool call lifecycle was actually completed across the\n // entire stream. Lets us downgrade a `tool_calls` finish_reason to `stop`\n // when the upstream signalled tool calls but never produced a complete\n // start/end pair — emitting RUN_FINISHED { finishReason: 'tool_calls' }\n // with no matching TOOL_CALL_END would leave consumers waiting for tool\n // results that never arrive.\n let emittedAnyToolCallEnd = false\n\n try {\n for await (const chunk of stream) {\n const choiceForLog = chunk.choices[0]\n options.logger.provider(\n `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? 'none'} hasContent=${!!choiceForLog?.delta.content} hasToolCalls=${!!choiceForLog?.delta.tool_calls} hasUsage=${!!chunk.usage}`,\n { provider: this.name, model: chunk.model },\n )\n\n // Capture usage from any chunk (including the terminal usage-only\n // chunk emitted when `stream_options.include_usage` is on).\n if (chunk.usage) {\n lastUsage = chunk.usage\n }\n if (chunk.model) {\n lastModel = chunk.model\n }\n\n // Emit RUN_STARTED on the first chunk of any kind so callers see a\n // run lifecycle even on streams that arrive entirely as usage-only\n // (no choices). Without this, a usage-first stream would skip\n // RUN_STARTED via `if (!choice) continue` below and the post-loop\n // synthetic block would also skip RUN_FINISHED (it gates on\n // `hasEmittedRunStarted`).\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n // Reasoning content (extractReasoning() hook). Run before reading\n // choice/delta so reasoning-only chunks (no `choices`) still drive\n // the REASONING_* lifecycle on providers that send reasoning out of\n // band. The base default returns undefined.\n const reasoning = this.extractReasoning(chunk)\n if (reasoning && reasoning.text) {\n if (!reasoningMessageId) {\n reasoningMessageId = generateId(this.name)\n stepId = generateId(this.name)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n // Legacy STEP_STARTED (single emission, paired with the\n // STEP_FINISHED below when reasoning closes).\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n accumulatedReasoning += reasoning.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: reasoning.text,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n }\n\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n // Close reasoning before text starts so consumers see a clean\n // REASONING_END before any TEXT_MESSAGE_START.\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n // Emit AG-UI TEXT_MESSAGE_CONTENT\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n let toolCall = toolCallsInProgress.get(index)\n if (!toolCall) {\n toolCall = {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n }\n toolCallsInProgress.set(index, toolCall)\n }\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n // Emit TOOL_CALL_START when we have id and name\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n parentMessageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n index,\n }\n }\n\n // Emit TOOL_CALL_ARGS for argument deltas\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: toolCallDelta.function.arguments,\n }\n }\n }\n }\n\n // Handle finish reason. We DO emit TOOL_CALL_END and TEXT_MESSAGE_END\n // here because the corresponding _START events have already fired,\n // and tool execution downstream wants to begin as soon as possible.\n // RUN_FINISHED is deferred until the iterator is fully exhausted so\n // we can capture the trailing usage chunk that arrives AFTER this\n // chunk when stream_options.include_usage is on.\n if (choice.finish_reason) {\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n // Skip tool calls that never emitted TOOL_CALL_START — emitting\n // a stray TOOL_CALL_END here would violate AG-UI lifecycle\n // (END without matching START) for partial deltas where the\n // upstream never sent both id and name.\n if (!toolCall.started) continue\n\n // Parse arguments for TOOL_CALL_END. Surface parse failures via\n // the logger so a model emitting malformed JSON for tool args\n // is debuggable instead of silently invoking the tool with {}.\n // Non-object JSON (e.g. a bare string or number) is also coerced\n // to {} so downstream tool execution doesn't receive a primitive\n // input, mirroring the Responses adapter's guard.\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput =\n parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n\n // Emit AG-UI TOOL_CALL_END\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n emittedAnyToolCallEnd = true\n }\n // Clear tool-call state after emission so a subsequent\n // `finish_reason: 'stop'` chunk (or the post-loop synthetic\n // block) doesn't see lingering entries and misreport the finish.\n toolCallsInProgress.clear()\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n hasEmittedTextMessageStart = false\n }\n\n // Remember the upstream finish_reason; RUN_FINISHED is emitted at\n // end-of-stream so we pick up the trailing usage-only chunk too.\n pendingFinishReason = choice.finish_reason\n }\n }\n\n // Emit a single terminal RUN_FINISHED after the iterator is exhausted.\n // This both delivers accurate token counts (the trailing usage chunk\n // may arrive AFTER the finish_reason chunk) and gives consumers a\n // guaranteed terminal event even when the upstream cuts off mid-stream\n // (no finish_reason chunk ever arrives).\n if (aguiState.hasEmittedRunStarted) {\n // Close any started tool calls that never got finish_reason. A\n // truncated stream that emitted TOOL_CALL_START but never reached\n // finish_reason would otherwise leave consumers with an unbalanced\n // start. Skip non-started entries (no matching START to close).\n let pendingToolCount = 0\n for (const [, toolCall] of toolCallsInProgress) {\n if (!toolCall.started) continue\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n // Mirror the finish_reason path's logger call — a truncated\n // stream emitting malformed tool-call JSON would otherwise\n // silently invoke the tool with `{}`, the exact failure the\n // finish_reason logger was added to prevent.\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed (drain)`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: lastModel || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n pendingToolCount += 1\n emittedAnyToolCallEnd = true\n }\n toolCallsInProgress.clear()\n\n // Make sure the text message lifecycle is closed even on early\n // termination paths where finish_reason never arrives.\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n }\n\n // Close any reasoning lifecycle that text never closed (no text\n // content arrived, or the stream cut off before text started).\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Map upstream finish_reason to AG-UI's narrower vocabulary.\n // Collapsing length / content_filter to 'stop' would hide why the\n // run terminated — surface it instead. Use `tool_calls` only when\n // a TOOL_CALL_END was actually emitted: an upstream that signalled\n // `tool_calls` but never produced a started/ended pair must NOT\n // surface `tool_calls` here, since downstream consumers wait for\n // tool results that would never arrive. OpenAI's legacy\n // `function_call` value (from the v1 function-calling API) is\n // normalized to `tool_calls` — semantically the same termination.\n const finishReason: NonNullable<RunFinishedEvent['finishReason']> =\n emittedAnyToolCallEnd\n ? 'tool_calls'\n : pendingFinishReason === 'tool_calls'\n ? 'stop'\n : pendingFinishReason === 'function_call'\n ? 'tool_calls'\n : (pendingFinishReason ?? 'stop')\n\n // Conditional `usage` spread: AG-UI's `RunFinishedEvent.usage` is\n // optional with no `| undefined`; omit the key entirely when no usage\n // arrived rather than emitting `usage: undefined`.\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n ...(lastUsage && {\n usage: buildChatCompletionsUsage(lastUsage),\n }),\n finishReason,\n }\n }\n } catch (error: unknown) {\n yield* this.handleChatStreamError(\n error,\n options,\n aguiState,\n 'processStreamChunks',\n )\n }\n }\n\n /**\n * Maps common TextOptions to Chat Completions API request format.\n * Override this in subclasses to add provider-specific options.\n */\n protected mapOptionsToRequest(\n options: TextOptions,\n ): ChatCompletionCreateParamsStreaming {\n const tools = options.tools\n ? convertToolsToChatCompletionsFormat(\n options.tools,\n this.makeStructuredOutputCompatible.bind(this),\n )\n : undefined\n\n // Build messages array with system prompts\n const messages: Array<ChatCompletionMessageParam> = []\n\n // Add system prompts first\n const systemPrompts = normalizeSystemPrompts(options.systemPrompts)\n if (systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: systemPrompts.map((p) => p.content).join('\\n'),\n })\n }\n\n // Convert messages\n for (const message of options.messages) {\n messages.push(this.convertMessage(message))\n }\n\n const modelOptions = options.modelOptions\n\n // Native combined mode (issue #605): when the engine threads\n // `outputSchema` through TextOptions, the adapter declared\n // `supportsCombinedToolsAndSchema` and the schema is already JSON Schema\n // (pre-converted at the activity boundary). Wire it into\n // `response_format` alongside any `tools`. Modern OpenAI-compatible\n // Chat Completions accepts both together and emits the schema-\n // constrained text on the natural final turn.\n const combinedSchema = options.outputSchema as\n | Record<string, unknown>\n | undefined\n const responseFormat = combinedSchema\n ? {\n response_format: {\n type: 'json_schema' as const,\n json_schema: {\n name: 'structured_output',\n schema: this.makeStructuredOutputCompatible(\n combinedSchema,\n Array.isArray(combinedSchema.required)\n ? (combinedSchema.required as Array<string>)\n : undefined,\n ),\n strict: true,\n },\n },\n }\n : undefined\n\n // `modelOptions` is the sole sampling surface: callers set provider-native\n // wire names (`temperature`, `top_p`, `max_tokens`/`max_completion_tokens`)\n // there and they flow through the spread below. The root\n // `temperature`/`topP`/`maxTokens` fields are intentionally NOT read here.\n return {\n ...modelOptions,\n model: options.model,\n messages,\n // Conditional spread: `tools: undefined` would clobber any\n // modelOptions.tools the caller set above.\n ...(tools &&\n tools.length > 0 && {\n tools,\n }),\n ...(responseFormat ?? {}),\n stream: true,\n }\n }\n\n /**\n * Modern OpenAI-compatible Chat Completions APIs support `tools` and\n * `response_format: json_schema` together in a single streaming request\n * (per issue #605). Subclasses can override — Groq, for instance, must\n * return `false` because its API rejects schema + tools + stream with a\n * 400.\n */\n supportsCombinedToolsAndSchema(): boolean {\n return true\n }\n\n /**\n * Converts a single ModelMessage to the Chat Completions API message format.\n * Override this in subclasses to handle provider-specific message formats.\n */\n protected convertMessage(message: ModelMessage): ChatCompletionMessageParam {\n // Handle tool messages\n if (message.role === 'tool') {\n // The Chat Completions API has no multimodal `tool` message support\n // (unlike the Responses API's `function_call_output`). A tool that\n // returns an `Array<ContentPart>` is therefore stringified here — the\n // documented fallback for providers on the chat-completions path\n // (Groq, Ollama, Grok, OpenRouter chat). Multimodal tool results are\n // only delivered structurally via the Responses adapter.\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n // Handle assistant messages\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n const hasToolCalls = !!toolCalls && toolCalls.length > 0\n const textContent = this.extractTextContent(message.content)\n\n // Per the OpenAI Chat Completions contract, an assistant message that\n // only carries tool_calls should have `content: null` (or omit content)\n // rather than `content: ''`. Empty-string content interacts oddly with\n // tokenization on some backends; null is the documented shape.\n return {\n role: 'assistant',\n content: hasToolCalls && !textContent ? null : textContent,\n ...(hasToolCalls ? { tool_calls: toolCalls } : {}),\n }\n }\n\n // Handle user messages - support multimodal content\n const contentParts = this.normalizeContent(message.content)\n\n // If only text, use simple string format\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n const text = contentParts[0].content\n if (text.length === 0) {\n // Single empty text part is the same fail-loud condition as below —\n // an empty paid request mask a real intent (caller passed `null`/'',\n // or an upstream step normalised everything to an empty string).\n throw new Error(\n `User message for ${this.name} has empty text content. ` +\n `Empty user messages would produce a paid request with no input; ` +\n `provide non-empty content or omit the message.`,\n )\n }\n return {\n role: 'user',\n content: text,\n }\n }\n\n // Otherwise, use array format for multimodal. Fail fast on unsupported\n // content parts rather than silently dropping them — a message of all\n // unsupported parts would otherwise turn into an empty user prompt and\n // mask a real capability mismatch.\n const parts: Array<ChatCompletionContentPart> = []\n for (const part of contentParts) {\n const converted = this.convertContentPart(part)\n if (!converted) {\n throw new Error(\n `Unsupported content part type for ${this.name}: ${part.type}. ` +\n `Override convertContentPart() in a subclass to handle this type, ` +\n `or remove it from the message.`,\n )\n }\n parts.push(converted)\n }\n\n if (parts.length === 0) {\n // The original message had no content parts at all (e.g. content was\n // explicitly null or []). Sending an empty user message to OpenAI\n // produces a paid request with no signal — fail loud instead.\n throw new Error(\n `User message for ${this.name} has no content parts. ` +\n `Empty user messages would produce a paid request with no input; ` +\n `provide at least one text/image/audio part or omit the message.`,\n )\n }\n\n return {\n role: 'user',\n content: parts,\n }\n }\n\n /**\n * Converts a single ContentPart to the Chat Completions API content part format.\n * Override this in subclasses to handle additional content types or provider-specific metadata.\n */\n protected convertContentPart(\n part: ContentPart,\n ): ChatCompletionContentPart | null {\n if (part.type === 'text') {\n return { type: 'text', text: part.content }\n }\n\n if (part.type === 'image') {\n const imageMetadata = part.metadata as\n | { detail?: 'auto' | 'low' | 'high' }\n | undefined\n\n // For base64 data, construct a data URI using the mimeType from source.\n // Default to a generic octet-stream MIME if the source didn't provide\n // one — interpolating `undefined` into the URI (\"data:undefined;base64,\n // ...\") would produce an invalid URI the API rejects.\n const imageValue = part.source.value\n const imageMime = part.source.mimeType || 'application/octet-stream'\n const imageUrl =\n part.source.type === 'data' && !imageValue.startsWith('data:')\n ? `data:${imageMime};base64,${imageValue}`\n : imageValue\n\n return {\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n },\n }\n }\n\n if (part.type === 'document') {\n // Documents (PDF) are implemented on the Responses adapter, which maps\n // them to `input_file`. Model modality arrays are not endpoint-scoped,\n // so a document part can type-check for a model this adapter serves —\n // point callers at the supported path instead of a generic error.\n throw new Error(\n `${this.name} does not support document parts on the Chat Completions ` +\n `API; use the Responses adapter, which sends them as input_file.`,\n )\n }\n\n // Unsupported content type — subclasses can override to handle more types\n return null\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n protected normalizeContent(\n content: string | null | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n protected extractTextContent(\n content: string | null | Array<ContentPart>,\n ): string {\n if (content === null) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // It's an array of ContentPart\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"chat-completions-text.js","names":[],"sources":["../../../src/adapters/chat-completions-text.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n toRunErrorPayload,\n toRunErrorRawEvent,\n} from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport { extractRequestOptions } from '../utils/request-options'\nimport { makeStructuredOutputCompatibleWithMap } from '../utils/schema-converter'\nimport { createToolInputNormalizer } from '../utils/tool-input-normalizer'\nimport type { StructuredOutputCompatibility } from '../utils/schema-converter'\nimport { buildChatCompletionsUsage } from '../usage'\nimport { convertToolsToChatCompletionsFormat } from './chat-completions-tool-converter'\nimport type OpenAI from 'openai'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n ChatCompletionChunk,\n ChatCompletionContentPart,\n ChatCompletionCreateParamsStreaming,\n ChatCompletionMessageParam,\n} from 'openai/resources/chat/completions/completions'\nimport type {\n ContentPart,\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n AdapterYieldChunk,\n TextOptions,\n} from '@tanstack/ai'\n\ntype ChatStreamState = {\n runId: string\n threadId: string\n messageId: string\n hasEmittedRunStarted: boolean\n}\n\n/**\n * Shared implementation of the OpenAI Chat Completions API. Holds the\n * stream-accumulator + AG-UI lifecycle logic and calls the OpenAI SDK\n * directly. Subclasses (ai-openai, ai-grok, ai-groq) construct an OpenAI\n * client with their provider-specific `baseURL` / headers and pass it in.\n */\nexport abstract class OpenAIBaseChatCompletionsTextAdapter<\n TModel extends string,\n TProviderOptions extends Record<string, unknown> = Record<string, unknown>,\n TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>,\n TMessageMetadata extends DefaultMessageMetadataByModality =\n DefaultMessageMetadataByModality,\n TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>,\n> extends BaseTextAdapter<\n TModel,\n TProviderOptions,\n TInputModalities,\n TMessageMetadata,\n TToolCapabilities\n> {\n override readonly kind = 'text' as const\n readonly name: string\n protected client: OpenAI\n\n constructor(model: TModel, name: string, client: OpenAI) {\n super({}, model)\n this.name = name\n this.client = client\n }\n\n async *chatStream(\n options: TextOptions<TProviderOptions>,\n ): AsyncIterable<AdapterYieldChunk> {\n // AG-UI lifecycle tracking (mutable state object for ESLint compatibility)\n const aguiState = {\n runId: generateId(this.name),\n threadId: options.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n hasEmittedRunStarted: false,\n }\n\n try {\n // mapOptionsToRequest can throw (e.g. fail-loud guards in convertMessage\n // for empty content or unsupported parts). Keep it inside the try so\n // those failures surface as a single RUN_ERROR event, matching every\n // other failure mode here — callers iterating chatStream then only need\n // one error-handling path instead of both a try/catch around iteration\n // and a RUN_ERROR handler.\n const requestParams = this.mapOptionsToRequest(options)\n options.logger.request(\n `activity=chat provider=${this.name} model=${this.model} messages=${options.messages.length} tools=${options.tools?.length ?? 0} stream=true`,\n { provider: this.name, model: this.model },\n )\n const stream = await this.client.chat.completions.create(\n {\n ...requestParams,\n stream: true,\n stream_options: { include_usage: true },\n },\n extractRequestOptions(options.request),\n )\n\n yield* this.processStreamChunks(stream, options, aguiState)\n } catch (error: unknown) {\n yield* this.handleChatStreamError(error, options, aguiState, 'chatStream')\n }\n }\n\n private async *handleChatStreamError(\n error: unknown,\n options: TextOptions,\n aguiState: ChatStreamState,\n source: 'chatStream' | 'processStreamChunks',\n ): AsyncIterable<AdapterYieldChunk> {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.${source} failed`,\n )\n const rawEvent = toRunErrorRawEvent(error)\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n const rejectedToolCall = this.extractRejectedToolCall(\n rawEvent,\n errorPayload.message,\n )\n if (rejectedToolCall) {\n const toolCallId = generateId(this.name)\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId,\n toolCallName: rejectedToolCall.toolName,\n toolName: rejectedToolCall.toolName,\n parentMessageId: aguiState.messageId,\n model: options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId,\n delta: rejectedToolCall.arguments,\n args: rejectedToolCall.arguments,\n model: options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId,\n toolCallName: rejectedToolCall.toolName,\n toolName: rejectedToolCall.toolName,\n ...(rejectedToolCall.input !== undefined && {\n input: rejectedToolCall.input,\n }),\n result: JSON.stringify({ error: rejectedToolCall.error }),\n state: 'output-error',\n model: options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp: Date.now(),\n finishReason: 'tool_calls',\n }\n return\n }\n\n options.logger.errors(`${this.name}.${source} fatal`, {\n error: errorPayload,\n source: `${this.name}.${source}`,\n })\n\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: options.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n ...(errorPayload.code !== undefined && { code: errorPayload.code }),\n },\n }\n }\n\n /**\n * Extracts a rejected tool call from a provider error. Returned calls are\n * emitted as non-executable `output-error` results so the model can repair them.\n */\n protected extractRejectedToolCall(\n _rawEvent: unknown,\n _fallbackMessage: string,\n ):\n | {\n toolName: string\n arguments: string\n input?: unknown\n error: string\n }\n | undefined {\n return undefined\n }\n\n /**\n * Generate structured output using the provider's JSON Schema response format.\n * Uses stream: false to get the complete response in one call.\n *\n * OpenAI-compatible APIs have strict requirements for structured output:\n * - All properties must be in the `required` array\n * - Optional fields should have null added to their type union\n * - additionalProperties must be false for all objects\n *\n * The outputSchema is already JSON Schema (converted in the ai layer).\n * We apply provider-specific transformations for structured output compatibility.\n */\n async structuredOutput(\n options: StructuredOutputOptions<TProviderOptions>,\n ): Promise<StructuredOutputResult<unknown>> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapOptionsToRequest(chatOptions)\n\n const jsonSchema = this.makeStructuredOutputCompatible(\n outputSchema,\n outputSchema.required,\n )\n\n try {\n // Strip stream_options which is only valid for streaming calls\n const {\n stream_options: _,\n stream: __,\n ...cleanParams\n } = requestParams as any\n chatOptions.logger.request(\n `activity=structuredOutput provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n const response = await this.client.chat.completions.create(\n {\n ...cleanParams,\n stream: false,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n },\n extractRequestOptions(chatOptions.request),\n )\n\n // Extract text content from the response. Fail loud on empty content\n // rather than letting it cascade into a JSON-parse error on '' — the\n // root cause (the model returned no content for the structured request)\n // is then visible in logs.\n const rawText = response.choices[0]?.message.content\n if (typeof rawText !== 'string' || rawText.length === 0) {\n throw new Error(\n `${this.name}.structuredOutput: response contained no content`,\n )\n }\n\n // Parse the JSON response\n let parsed: unknown\n try {\n parsed = JSON.parse(rawText)\n } catch {\n throw new Error(\n `Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? '...' : ''}`,\n )\n }\n\n // Final provider-specific shaping pass (default passthrough). Null-widening\n // from strict mode is undone by the engine, not here.\n const transformed = this.transformStructuredOutput(parsed)\n\n // Surface usage so non-stream structured paths (and\n // fallbackStructuredOutputStream) can forward tokens to middleware.\n const usage = buildChatCompletionsUsage(response.usage)\n return {\n data: transformed,\n rawText,\n ...(usage && { usage }),\n }\n } catch (error: unknown) {\n // Narrow before logging: raw SDK errors can carry request metadata\n // (including auth headers) which we must never surface to user loggers.\n chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {\n error: toRunErrorPayload(error, `${this.name}.structuredOutput failed`),\n source: `${this.name}.structuredOutput`,\n })\n throw error\n }\n }\n\n /**\n * Stream structured output. Single Chat Completions request with\n * `response_format: json_schema` + `stream: true`. Emits the standard\n * AG-UI lifecycle (`RUN_STARTED` → `REASONING_*?` → `TEXT_MESSAGE_*`\n * carrying raw JSON deltas → terminal `CUSTOM 'structured-output.complete'`\n * → `RUN_FINISHED`). Subclasses use the same SDK-call / reasoning /\n * structured-output-transform hooks as `chatStream` / `structuredOutput` —\n * no per-subclass override should be needed.\n */\n async *structuredOutputStream(\n options: StructuredOutputOptions<TProviderOptions>,\n ): AsyncIterable<AdapterYieldChunk> {\n const { chatOptions, outputSchema } = options\n const requestParams = this.mapOptionsToRequest(chatOptions)\n\n const jsonSchema = this.makeStructuredOutputCompatible(\n outputSchema,\n outputSchema.required,\n )\n\n const aguiState = {\n runId: generateId(this.name),\n threadId: chatOptions.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n hasEmittedRunStarted: false,\n }\n\n let accumulatedContent = ''\n let accumulatedReasoning = ''\n let hasEmittedTextMessageStart = false\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n let stepId: string | undefined\n let lastModel: string | undefined\n let lastUsage:\n | OpenAI.Chat.Completions.ChatCompletionChunk['usage']\n | undefined\n\n const closeReasoningLifecycle = function* (this: {\n name: string\n }): Generator<AdapterYieldChunk> {\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n reasoningMessageId = undefined\n stepId = undefined\n hasClosedReasoning = false\n }\n }.bind(this)\n\n try {\n // Strip stream_options + tools from the base request. Structured output\n // sends `response_format: json_schema` and doesn't carry tools — keeping\n // them in the request can confuse strict-mode validation upstream.\n const {\n stream_options: _so,\n stream: _s,\n tools: _t,\n ...cleanParams\n } = requestParams\n\n chatOptions.logger.request(\n `activity=structuredOutputStream provider=${this.name} model=${this.model} messages=${chatOptions.messages.length}`,\n { provider: this.name, model: this.model },\n )\n\n const stream = await this.client.chat.completions.create(\n {\n ...cleanParams,\n stream: true,\n stream_options: { include_usage: true },\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'structured_output',\n schema: jsonSchema,\n strict: true,\n },\n },\n },\n extractRequestOptions(chatOptions.request),\n )\n\n for await (const chunk of stream) {\n const choiceForLog = chunk.choices[0]\n chatOptions.logger.provider(\n `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? 'none'} hasContent=${!!choiceForLog?.delta.content} hasUsage=${!!chunk.usage}`,\n { provider: this.name, model: chunk.model },\n )\n\n if (chunk.model) lastModel = chunk.model\n\n // Usage may arrive on a chunk with empty `choices` (OpenAI's\n // include_usage terminal chunk) or piggybacked on a finish chunk\n // (`x_groq.usage` on Groq). Capture from either independent of\n // choices[0].\n const usage =\n chunk.usage ??\n (chunk as { x_groq?: { usage?: typeof chunk.usage } }).x_groq?.usage\n if (usage) lastUsage = usage\n\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n // Reasoning (via the extractReasoning hook — same hook as chatStream).\n const reasoning = this.extractReasoning(chunk)\n if (reasoning && reasoning.text) {\n if (!reasoningMessageId) {\n reasoningMessageId = generateId(this.name)\n stepId = generateId(this.name)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n accumulatedReasoning += reasoning.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: reasoning.text,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n }\n }\n\n const choice = chunk.choices[0]\n if (!choice) continue\n\n const deltaContent = choice.delta.content\n if (deltaContent) {\n yield* closeReasoningLifecycle()\n\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: aguiState.messageId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: aguiState.messageId,\n model: chunk.model || chatOptions.model,\n timestamp: Date.now(),\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n }\n\n // Finalisation: close any open lifecycle, parse + validate, emit\n // terminal events. This block always runs unless the loop threw — abort\n // and SDK errors land in the catch block below.\n yield* closeReasoningLifecycle()\n\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n }\n\n if (accumulatedContent.length === 0) {\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n error: {\n message: `${this.name}.structuredOutputStream: response contained no content`,\n code: 'empty-response',\n },\n }\n return\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(accumulatedContent)\n } catch {\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n message: `Failed to parse structured output as JSON. Content: ${accumulatedContent.slice(0, 200)}${accumulatedContent.length > 200 ? '...' : ''}`,\n code: 'parse-error',\n error: {\n message: 'Failed to parse structured output as JSON',\n code: 'parse-error',\n },\n }\n return\n }\n\n const transformed = this.transformStructuredOutput(parsed)\n\n yield {\n type: EventType.CUSTOM,\n name: 'structured-output.complete',\n value: {\n object: transformed,\n raw: accumulatedContent,\n ...(accumulatedReasoning ? { reasoning: accumulatedReasoning } : {}),\n },\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n finishReason: 'stop',\n ...(lastUsage && {\n usage: buildChatCompletionsUsage(lastUsage),\n }),\n }\n } catch (error: unknown) {\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chatOptions.model,\n timestamp: Date.now(),\n parentRunId: chatOptions.parentRunId,\n }\n }\n\n const isAbort = this.isAbortError(error)\n const errorPayload = toRunErrorPayload(\n error,\n `${this.name}.structuredOutputStream failed`,\n )\n\n // Conditional `code` spread keeps the wire shape spec-compliant under\n // `exactOptionalPropertyTypes`: AG-UI's `RunErrorEvent.code` is `string?`\n // (absent vs explicit `undefined` matter).\n const resolvedCode = isAbort ? 'aborted' : errorPayload.code\n const rawEvent = isAbort ? undefined : toRunErrorRawEvent(error)\n yield {\n type: EventType.RUN_ERROR,\n runId: aguiState.runId,\n model: lastModel || chatOptions.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n },\n }\n\n chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {\n error: errorPayload,\n source: `${this.name}.structuredOutputStream`,\n })\n }\n }\n\n /**\n * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types\n * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,\n * and standard `AbortError`s. Subclasses with proprietary error types (e.g.\n * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.\n */\n protected isAbortError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false\n const e = error as { name?: unknown; code?: unknown }\n return (\n e.name === 'APIUserAbortError' ||\n e.name === 'AbortError' ||\n e.code === 'ERR_CANCELED'\n )\n }\n\n /**\n * Strict conversion plus the inverse null-widening map for this request.\n * Override this when schema conversion changes, so tool-input undo matches\n * the wire schema.\n */\n protected makeStructuredOutputCompatibleWithMap(\n schema: Record<string, any>,\n originalRequired?: Array<string>,\n ): StructuredOutputCompatibility {\n return makeStructuredOutputCompatibleWithMap(schema, originalRequired)\n }\n\n /**\n * Applies provider-specific transformations for structured output compatibility.\n * Override `makeStructuredOutputCompatibleWithMap` when you need the inverse map\n * to match the wire schema.\n */\n protected makeStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired?: Array<string>,\n ): Record<string, any> {\n return this.makeStructuredOutputCompatibleWithMap(schema, originalRequired)\n .schema\n }\n\n /**\n * Extract reasoning content from a stream chunk. Default returns\n * `undefined` because the OpenAI Chat Completions chunk shape doesn't\n * carry reasoning. The chunk param is typed `unknown` so an override can\n * narrow to its own SDK chunk type without an `as` dance — the base only\n * passes through `processStreamChunks`'s structurally-iterated chunk.\n */\n protected extractReasoning(_chunk: unknown): { text: string } | undefined {\n return undefined\n }\n\n /**\n * Final shaping pass applied to parsed structured-output JSON before it is\n * returned to the caller. Default is a passthrough.\n *\n * Provider `null`s are no longer stripped here: strict-mode null-widening is\n * now undone precisely by the engine (`undoNullWidening`, driven by the\n * schema's null-widening map) the moment the result is captured, so a blind\n * `transformNullsToUndefined` at the adapter would only destroy genuine\n * `.nullable()` nulls. Subclasses may still override to remap or reshape the\n * provider's structured output.\n */\n protected transformStructuredOutput(parsed: unknown): unknown {\n return parsed\n }\n\n /**\n * Processes streamed chunks from the Chat Completions API and yields AG-UI events.\n * Override this in subclasses to handle provider-specific stream behavior.\n */\n protected async *processStreamChunks(\n stream: AsyncIterable<ChatCompletionChunk>,\n options: TextOptions,\n aguiState: ChatStreamState,\n ): AsyncIterable<AdapterYieldChunk> {\n const normalizeToolInput = createToolInputNormalizer(\n options.tools,\n (schema, required) =>\n this.makeStructuredOutputCompatibleWithMap(schema, required),\n )\n let accumulatedContent = ''\n let hasEmittedTextMessageStart = false\n let lastModel: string | undefined\n // Track usage from any chunk that carries it. With\n // `stream_options: { include_usage: true }` OpenAI emits a terminal chunk\n // whose `choices` is `[]` and only the `usage` field is populated; the\n // earlier `finish_reason` chunk does NOT include token counts. We must\n // therefore defer RUN_FINISHED until the iterator is exhausted so we can\n // pick up usage from the trailing chunk regardless of arrival order.\n let lastUsage: ChatCompletionChunk['usage'] | undefined\n let pendingFinishReason:\n | ChatCompletionChunk['choices'][number]['finish_reason']\n | undefined\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean // Track if TOOL_CALL_START has been emitted\n }\n >()\n\n // Reasoning lifecycle (driven by extractReasoning() hook — see method\n // docs). The base wire format (OpenAI Chat Completions) has no reasoning,\n // so these stay unused for openai/grok/groq. OpenRouter etc. opt in.\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n // Legacy STEP_STARTED/STEP_FINISHED pair emitted alongside REASONING_*\n // for back-compat with consumers (UI, devtools) that haven't migrated\n // to the spec REASONING_* events yet.\n let stepId: string | undefined\n let accumulatedReasoning = ''\n // Track whether ANY tool call lifecycle was actually completed across the\n // entire stream. Lets us downgrade a `tool_calls` finish_reason to `stop`\n // when the upstream signalled tool calls but never produced a complete\n // start/end pair — emitting RUN_FINISHED { finishReason: 'tool_calls' }\n // with no matching TOOL_CALL_END would leave consumers waiting for tool\n // results that never arrive.\n let emittedAnyToolCallEnd = false\n\n try {\n for await (const chunk of stream) {\n const choiceForLog = chunk.choices[0]\n options.logger.provider(\n `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? 'none'} hasContent=${!!choiceForLog?.delta.content} hasToolCalls=${!!choiceForLog?.delta.tool_calls} hasUsage=${!!chunk.usage}`,\n { provider: this.name, model: chunk.model },\n )\n\n // Capture usage from any chunk (including the terminal usage-only\n // chunk emitted when `stream_options.include_usage` is on).\n if (chunk.usage) {\n lastUsage = chunk.usage\n }\n if (chunk.model) {\n lastModel = chunk.model\n }\n\n // Emit RUN_STARTED on the first chunk of any kind so callers see a\n // run lifecycle even on streams that arrive entirely as usage-only\n // (no choices). Without this, a usage-first stream would skip\n // RUN_STARTED via `if (!choice) continue` below and the post-loop\n // synthetic block would also skip RUN_FINISHED (it gates on\n // `hasEmittedRunStarted`).\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n // Reasoning content (extractReasoning() hook). Run before reading\n // choice/delta so reasoning-only chunks (no `choices`) still drive\n // the REASONING_* lifecycle on providers that send reasoning out of\n // band. The base default returns undefined.\n const reasoning = this.extractReasoning(chunk)\n if (reasoning && reasoning.text) {\n if (!reasoningMessageId) {\n reasoningMessageId = generateId(this.name)\n stepId = generateId(this.name)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n // Legacy STEP_STARTED (single emission, paired with the\n // STEP_FINISHED below when reasoning closes).\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n accumulatedReasoning += reasoning.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: reasoning.text,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n }\n\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n // Close reasoning before text starts so consumers see a clean\n // REASONING_END before any TEXT_MESSAGE_START.\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n // Emit AG-UI TEXT_MESSAGE_CONTENT\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n let toolCall = toolCallsInProgress.get(index)\n if (!toolCall) {\n toolCall = {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n }\n toolCallsInProgress.set(index, toolCall)\n }\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n // Emit TOOL_CALL_START when we have id and name\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n parentMessageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n index,\n }\n }\n\n // Emit TOOL_CALL_ARGS for argument deltas\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: toolCallDelta.function.arguments,\n }\n }\n }\n }\n\n // Handle finish reason. We DO emit TOOL_CALL_END and TEXT_MESSAGE_END\n // here because the corresponding _START events have already fired,\n // and tool execution downstream wants to begin as soon as possible.\n // RUN_FINISHED is deferred until the iterator is fully exhausted so\n // we can capture the trailing usage chunk that arrives AFTER this\n // chunk when stream_options.include_usage is on.\n if (choice.finish_reason) {\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n // Skip tool calls that never emitted TOOL_CALL_START — emitting\n // a stray TOOL_CALL_END here would violate AG-UI lifecycle\n // (END without matching START) for partial deltas where the\n // upstream never sent both id and name.\n if (!toolCall.started) continue\n\n // Parse arguments for TOOL_CALL_END. Surface parse failures via\n // the logger so a model emitting malformed JSON for tool args\n // is debuggable instead of silently invoking the tool with {}.\n // Non-object JSON (e.g. a bare string or number) is also coerced\n // to {} so downstream tool execution doesn't receive a primitive\n // input, mirroring the Responses adapter's guard.\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput = normalizeToolInput(\n toolCall.name,\n parsed && typeof parsed === 'object' ? parsed : {},\n )\n } catch (parseError) {\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n\n // Emit AG-UI TOOL_CALL_END\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n emittedAnyToolCallEnd = true\n }\n // Clear tool-call state after emission so a subsequent\n // `finish_reason: 'stop'` chunk (or the post-loop synthetic\n // block) doesn't see lingering entries and misreport the finish.\n toolCallsInProgress.clear()\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n hasEmittedTextMessageStart = false\n }\n\n // Remember the upstream finish_reason; RUN_FINISHED is emitted at\n // end-of-stream so we pick up the trailing usage-only chunk too.\n pendingFinishReason = choice.finish_reason\n }\n }\n\n // Emit a single terminal RUN_FINISHED after the iterator is exhausted.\n // This both delivers accurate token counts (the trailing usage chunk\n // may arrive AFTER the finish_reason chunk) and gives consumers a\n // guaranteed terminal event even when the upstream cuts off mid-stream\n // (no finish_reason chunk ever arrives).\n if (aguiState.hasEmittedRunStarted) {\n // Close any started tool calls that never got finish_reason. A\n // truncated stream that emitted TOOL_CALL_START but never reached\n // finish_reason would otherwise leave consumers with an unbalanced\n // start. Skip non-started entries (no matching START to close).\n let pendingToolCount = 0\n for (const [, toolCall] of toolCallsInProgress) {\n if (!toolCall.started) continue\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput = normalizeToolInput(\n toolCall.name,\n parsed && typeof parsed === 'object' ? parsed : {},\n )\n } catch (parseError) {\n // Mirror the finish_reason path's logger call — a truncated\n // stream emitting malformed tool-call JSON would otherwise\n // silently invoke the tool with `{}`, the exact failure the\n // finish_reason logger was added to prevent.\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed (drain)`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: lastModel || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n pendingToolCount += 1\n emittedAnyToolCallEnd = true\n }\n toolCallsInProgress.clear()\n\n // Make sure the text message lifecycle is closed even on early\n // termination paths where finish_reason never arrives.\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n }\n\n // Close any reasoning lifecycle that text never closed (no text\n // content arrived, or the stream cut off before text started).\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Map upstream finish_reason to AG-UI's narrower vocabulary.\n // Collapsing length / content_filter to 'stop' would hide why the\n // run terminated — surface it instead. Use `tool_calls` only when\n // a TOOL_CALL_END was actually emitted: an upstream that signalled\n // `tool_calls` but never produced a started/ended pair must NOT\n // surface `tool_calls` here, since downstream consumers wait for\n // tool results that would never arrive. OpenAI's legacy\n // `function_call` value (from the v1 function-calling API) is\n // normalized to `tool_calls` — semantically the same termination.\n const finishReason: NonNullable<AdapterYieldChunk['finishReason']> =\n emittedAnyToolCallEnd\n ? 'tool_calls'\n : pendingFinishReason === 'tool_calls'\n ? 'stop'\n : pendingFinishReason === 'function_call'\n ? 'tool_calls'\n : (pendingFinishReason ?? 'stop')\n\n // Conditional `usage` spread: AG-UI's `RunFinishedEvent.usage` is\n // optional with no `| undefined`; omit the key entirely when no usage\n // arrived rather than emitting `usage: undefined`.\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n ...(lastUsage && {\n usage: buildChatCompletionsUsage(lastUsage),\n }),\n finishReason,\n }\n }\n } catch (error: unknown) {\n yield* this.handleChatStreamError(\n error,\n options,\n aguiState,\n 'processStreamChunks',\n )\n }\n }\n\n /**\n * Maps common TextOptions to Chat Completions API request format.\n * Override this in subclasses to add provider-specific options.\n */\n protected mapOptionsToRequest(\n options: TextOptions,\n ): ChatCompletionCreateParamsStreaming {\n const tools = options.tools\n ? convertToolsToChatCompletionsFormat(\n options.tools,\n this.makeStructuredOutputCompatible.bind(this),\n )\n : undefined\n\n // Build messages array with system prompts\n const messages: Array<ChatCompletionMessageParam> = []\n\n // Add system prompts first\n const systemPrompts = normalizeSystemPrompts(options.systemPrompts)\n if (systemPrompts.length > 0) {\n messages.push({\n role: 'system',\n content: systemPrompts.map((p) => p.content).join('\\n'),\n })\n }\n\n // Convert messages\n for (const message of options.messages) {\n messages.push(this.convertMessage(message))\n }\n\n const modelOptions = options.modelOptions\n\n // Native combined mode (issue #605): when the engine threads\n // `outputSchema` through TextOptions, the adapter declared\n // `supportsCombinedToolsAndSchema` and the schema is already JSON Schema\n // (pre-converted at the activity boundary). Wire it into\n // `response_format` alongside any `tools`. Modern OpenAI-compatible\n // Chat Completions accepts both together and emits the schema-\n // constrained text on the natural final turn.\n const combinedSchema = options.outputSchema as\n | Record<string, unknown>\n | undefined\n const responseFormat = combinedSchema\n ? {\n response_format: {\n type: 'json_schema' as const,\n json_schema: {\n name: 'structured_output',\n schema: this.makeStructuredOutputCompatible(\n combinedSchema,\n Array.isArray(combinedSchema.required)\n ? (combinedSchema.required as Array<string>)\n : undefined,\n ),\n strict: true,\n },\n },\n }\n : undefined\n\n // `modelOptions` is the sole sampling surface: callers set provider-native\n // wire names (`temperature`, `top_p`, `max_tokens`/`max_completion_tokens`)\n // there and they flow through the spread below. The root\n // `temperature`/`topP`/`maxTokens` fields are intentionally NOT read here.\n return {\n ...modelOptions,\n model: options.model,\n messages,\n // Conditional spread: `tools: undefined` would clobber any\n // modelOptions.tools the caller set above.\n ...(tools &&\n tools.length > 0 && {\n tools,\n }),\n ...(responseFormat ?? {}),\n stream: true,\n }\n }\n\n /**\n * Modern OpenAI-compatible Chat Completions APIs support `tools` and\n * `response_format: json_schema` together in a single streaming request\n * (per issue #605). Subclasses can override — Groq, for instance, must\n * return `false` because its API rejects schema + tools + stream with a\n * 400.\n */\n supportsCombinedToolsAndSchema(): boolean {\n return true\n }\n\n /**\n * Converts a single ModelMessage to the Chat Completions API message format.\n * Override this in subclasses to handle provider-specific message formats.\n */\n protected convertMessage(message: ModelMessage): ChatCompletionMessageParam {\n // Handle tool messages\n if (message.role === 'tool') {\n // The Chat Completions API has no multimodal `tool` message support\n // (unlike the Responses API's `function_call_output`). A tool that\n // returns an `Array<ContentPart>` is therefore stringified here — the\n // documented fallback for providers on the chat-completions path\n // (Groq, Ollama, Grok, OpenRouter chat). Multimodal tool results are\n // only delivered structurally via the Responses adapter.\n return {\n role: 'tool',\n tool_call_id: message.toolCallId || '',\n content:\n typeof message.content === 'string'\n ? message.content\n : JSON.stringify(message.content),\n }\n }\n\n // Handle assistant messages\n if (message.role === 'assistant') {\n const toolCalls = message.toolCalls?.map((tc) => ({\n id: tc.id,\n type: 'function' as const,\n function: {\n name: tc.function.name,\n arguments:\n typeof tc.function.arguments === 'string'\n ? tc.function.arguments\n : JSON.stringify(tc.function.arguments),\n },\n }))\n const hasToolCalls = !!toolCalls && toolCalls.length > 0\n const textContent = this.extractTextContent(message.content)\n\n // Per the OpenAI Chat Completions contract, an assistant message that\n // only carries tool_calls should have `content: null` (or omit content)\n // rather than `content: ''`. Empty-string content interacts oddly with\n // tokenization on some backends; null is the documented shape.\n return {\n role: 'assistant',\n content: hasToolCalls && !textContent ? null : textContent,\n ...(hasToolCalls ? { tool_calls: toolCalls } : {}),\n }\n }\n\n // Handle user messages - support multimodal content\n const contentParts = this.normalizeContent(message.content)\n\n // If only text, use simple string format\n if (contentParts.length === 1 && contentParts[0]?.type === 'text') {\n const text = contentParts[0].content\n if (text.length === 0) {\n // Single empty text part is the same fail-loud condition as below —\n // an empty paid request mask a real intent (caller passed `null`/'',\n // or an upstream step normalised everything to an empty string).\n throw new Error(\n `User message for ${this.name} has empty text content. ` +\n `Empty user messages would produce a paid request with no input; ` +\n `provide non-empty content or omit the message.`,\n )\n }\n return {\n role: 'user',\n content: text,\n }\n }\n\n // Otherwise, use array format for multimodal. Fail fast on unsupported\n // content parts rather than silently dropping them — a message of all\n // unsupported parts would otherwise turn into an empty user prompt and\n // mask a real capability mismatch.\n const parts: Array<ChatCompletionContentPart> = []\n for (const part of contentParts) {\n const converted = this.convertContentPart(part)\n if (!converted) {\n throw new Error(\n `Unsupported content part type for ${this.name}: ${part.type}. ` +\n `Override convertContentPart() in a subclass to handle this type, ` +\n `or remove it from the message.`,\n )\n }\n parts.push(converted)\n }\n\n if (parts.length === 0) {\n // The original message had no content parts at all (e.g. content was\n // explicitly null or []). Sending an empty user message to OpenAI\n // produces a paid request with no signal — fail loud instead.\n throw new Error(\n `User message for ${this.name} has no content parts. ` +\n `Empty user messages would produce a paid request with no input; ` +\n `provide at least one text/image/audio part or omit the message.`,\n )\n }\n\n return {\n role: 'user',\n content: parts,\n }\n }\n\n /**\n * Converts a single ContentPart to the Chat Completions API content part format.\n * Override this in subclasses to handle additional content types or provider-specific metadata.\n */\n protected convertContentPart(\n part: ContentPart,\n ): ChatCompletionContentPart | null {\n if (part.type === 'text') {\n return { type: 'text', text: part.content }\n }\n\n if (part.type === 'image') {\n const imageMetadata = part.metadata as\n | { detail?: 'auto' | 'low' | 'high' }\n | undefined\n\n // For base64 data, construct a data URI using the mimeType from source.\n // Default to a generic octet-stream MIME if the source didn't provide\n // one — interpolating `undefined` into the URI (\"data:undefined;base64,\n // ...\") would produce an invalid URI the API rejects.\n const imageValue = part.source.value\n const imageMime = part.source.mimeType || 'application/octet-stream'\n const imageUrl =\n part.source.type === 'data' && !imageValue.startsWith('data:')\n ? `data:${imageMime};base64,${imageValue}`\n : imageValue\n\n return {\n type: 'image_url',\n image_url: {\n url: imageUrl,\n detail: imageMetadata?.detail || 'auto',\n },\n }\n }\n\n if (part.type === 'document') {\n // Documents (PDF) are implemented on the Responses adapter, which maps\n // them to `input_file`. Model modality arrays are not endpoint-scoped,\n // so a document part can type-check for a model this adapter serves —\n // point callers at the supported path instead of a generic error.\n throw new Error(\n `${this.name} does not support document parts on the Chat Completions ` +\n `API; use the Responses adapter, which sends them as input_file.`,\n )\n }\n\n // Unsupported content type — subclasses can override to handle more types\n return null\n }\n\n /**\n * Normalizes message content to an array of ContentPart.\n * Handles backward compatibility with string content.\n */\n protected normalizeContent(\n content: string | null | undefined | Array<ContentPart>,\n ): Array<ContentPart> {\n if (content === null || content === undefined) {\n return []\n }\n if (typeof content === 'string') {\n return [{ type: 'text', content: content }]\n }\n return content\n }\n\n /**\n * Extracts text content from a content value that may be string, null, or ContentPart array.\n */\n protected extractTextContent(\n content: string | null | undefined | Array<ContentPart>,\n ): string {\n // Tool-call-only assistant turns (e.g. an approval resume replaying the\n // pending call) carry no text and arrive as `null` or `undefined`; both\n // must collapse to '' rather than crash on `.filter`.\n if (content === null || content === undefined) {\n return ''\n }\n if (typeof content === 'string') {\n return content\n }\n // It's an array of ContentPart\n return content\n .filter((p) => p.type === 'text')\n .map((p) => p.content)\n .join('')\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from '@tanstack/ai/adapters';
2
+ import { StructuredOutputCompatibility } from '../utils/schema-converter.js';
2
3
  import { default as OpenAI } from 'openai';
3
4
  import { Response, ResponseCreateParams, ResponseInput, ResponseInputContent, ResponseStreamEvent } from 'openai/resources/responses/responses';
4
- import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
5
+ import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, AdapterYieldChunk, TextOptions } from '@tanstack/ai';
5
6
  /**
6
7
  * Provider-specific metadata that preserves the Responses API output item ID.
7
8
  *
@@ -32,7 +33,7 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
32
33
  readonly name: string;
33
34
  protected client: OpenAI;
34
35
  constructor(model: TModel, name: string, client: OpenAI);
35
- chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
36
+ chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
36
37
  /**
37
38
  * Generate structured output using the provider's native JSON Schema response format.
38
39
  * Uses stream: false to get the complete response in one call.
@@ -59,15 +60,22 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
59
60
  * REASONING_* lifecycle events during the stream and on the terminal
60
61
  * CUSTOM event's `value.reasoning`.
61
62
  */
62
- structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
63
+ structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
63
64
  /**
64
65
  * Cross-SDK abort detection for `structuredOutputStream`. Mirrors the
65
66
  * Chat Completions base; subclasses with proprietary error types override.
66
67
  */
67
68
  protected isAbortError(error: unknown): boolean;
69
+ /**
70
+ * Strict conversion plus the inverse null-widening map for this request.
71
+ * Override this when schema conversion changes, so tool-input undo matches
72
+ * the wire schema.
73
+ */
74
+ protected makeStructuredOutputCompatibleWithMap(schema: Record<string, any>, originalRequired?: Array<string>): StructuredOutputCompatibility;
68
75
  /**
69
76
  * Applies provider-specific transformations for structured output compatibility.
70
- * Override this in subclasses to handle provider-specific quirks.
77
+ * Override `makeStructuredOutputCompatibleWithMap` when you need the inverse map
78
+ * to match the wire schema.
71
79
  */
72
80
  protected makeStructuredOutputCompatible(schema: Record<string, any>, originalRequired?: Array<string>): Record<string, any>;
73
81
  /**
@@ -107,7 +115,7 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
107
115
  threadId: string;
108
116
  messageId: string;
109
117
  hasEmittedRunStarted: boolean;
110
- }): AsyncIterable<StreamChunk>;
118
+ }): AsyncIterable<AdapterYieldChunk>;
111
119
  /**
112
120
  * Maps common TextOptions to Responses API request format.
113
121
  * Override this in subclasses to add provider-specific options.
@@ -140,10 +148,10 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
140
148
  * Normalizes message content to an array of ContentPart.
141
149
  * Handles backward compatibility with string content.
142
150
  */
143
- protected normalizeContent(content: string | null | Array<ContentPart>): Array<ContentPart>;
151
+ protected normalizeContent(content: string | null | undefined | Array<ContentPart>): Array<ContentPart>;
144
152
  /**
145
153
  * Extracts text content from a content value that may be string, null, or ContentPart array.
146
154
  */
147
- protected extractTextContent(content: string | null | Array<ContentPart>): string;
155
+ protected extractTextContent(content: string | null | undefined | Array<ContentPart>): string;
148
156
  }
149
157
  export {};