@tanstack/openai-base 0.10.2 → 0.10.5

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.
@@ -2,7 +2,7 @@ import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from
2
2
  import { StructuredOutputCompatibility } from '../utils/schema-converter.js';
3
3
  import { default as OpenAI } from 'openai';
4
4
  import { ChatCompletionChunk, ChatCompletionContentPart, ChatCompletionCreateParamsStreaming, ChatCompletionMessageParam } from 'openai/resources/chat/completions/completions';
5
- import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
5
+ import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, AdapterYieldChunk, TextOptions } from '@tanstack/ai';
6
6
  type ChatStreamState = {
7
7
  runId: string;
8
8
  threadId: string;
@@ -20,7 +20,7 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
20
20
  readonly name: string;
21
21
  protected client: OpenAI;
22
22
  constructor(model: TModel, name: string, client: OpenAI);
23
- chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
23
+ chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
24
24
  private handleChatStreamError;
25
25
  /**
26
26
  * Extracts a rejected tool call from a provider error. Returned calls are
@@ -54,7 +54,7 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
54
54
  * structured-output-transform hooks as `chatStream` / `structuredOutput` —
55
55
  * no per-subclass override should be needed.
56
56
  */
57
- structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
57
+ structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
58
58
  /**
59
59
  * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types
60
60
  * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,
@@ -100,7 +100,7 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
100
100
  * Processes streamed chunks from the Chat Completions API and yields AG-UI events.
101
101
  * Override this in subclasses to handle provider-specific stream behavior.
102
102
  */
103
- protected processStreamChunks(stream: AsyncIterable<ChatCompletionChunk>, options: TextOptions, aguiState: ChatStreamState): AsyncIterable<StreamChunk>;
103
+ protected processStreamChunks(stream: AsyncIterable<ChatCompletionChunk>, options: TextOptions, aguiState: ChatStreamState): AsyncIterable<AdapterYieldChunk>;
104
104
  /**
105
105
  * Maps common TextOptions to Chat Completions API request format.
106
106
  * Override this in subclasses to add provider-specific options.
@@ -128,10 +128,10 @@ export declare abstract class OpenAIBaseChatCompletionsTextAdapter<TModel extend
128
128
  * Normalizes message content to an array of ContentPart.
129
129
  * Handles backward compatibility with string content.
130
130
  */
131
- protected normalizeContent(content: string | null | Array<ContentPart>): Array<ContentPart>;
131
+ protected normalizeContent(content: string | null | undefined | Array<ContentPart>): Array<ContentPart>;
132
132
  /**
133
133
  * Extracts text content from a content value that may be string, null, or ContentPart array.
134
134
  */
135
- protected extractTextContent(content: string | null | Array<ContentPart>): string;
135
+ protected extractTextContent(content: string | null | undefined | Array<ContentPart>): string;
136
136
  }
137
137
  export {};
@@ -107,6 +107,8 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
107
107
  });
108
108
  yield {
109
109
  type: EventType.RUN_ERROR,
110
+ runId: aguiState.runId,
111
+ threadId: aguiState.threadId,
110
112
  model: options.model,
111
113
  timestamp: Date.now(),
112
114
  message: errorPayload.message,
@@ -230,6 +232,9 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
230
232
  timestamp: Date.now(),
231
233
  content: accumulatedReasoning
232
234
  };
235
+ reasoningMessageId = void 0;
236
+ stepId = void 0;
237
+ hasClosedReasoning = false;
233
238
  }
234
239
  }.bind(this);
235
240
  try {
@@ -873,7 +878,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
873
878
  * Handles backward compatibility with string content.
874
879
  */
875
880
  normalizeContent(content) {
876
- if (content === null) return [];
881
+ if (content === null || content === void 0) return [];
877
882
  if (typeof content === "string") return [{
878
883
  type: "text",
879
884
  content
@@ -884,7 +889,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
884
889
  * Extracts text content from a content value that may be string, null, or ContentPart array.
885
890
  */
886
891
  extractTextContent(content) {
887
- if (content === null) return "";
892
+ if (content === null || content === void 0) return "";
888
893
  if (typeof content === "string") return content;
889
894
  return content.filter((p) => p.type === "text").map((p) => p.content).join("");
890
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 { 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 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 * 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<StreamChunk> {\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<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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"}
@@ -2,7 +2,7 @@ import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from
2
2
  import { StructuredOutputCompatibility } from '../utils/schema-converter.js';
3
3
  import { default as OpenAI } from 'openai';
4
4
  import { Response, ResponseCreateParams, ResponseInput, ResponseInputContent, ResponseStreamEvent } from 'openai/resources/responses/responses';
5
- import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
5
+ import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, AdapterYieldChunk, TextOptions } from '@tanstack/ai';
6
6
  /**
7
7
  * Provider-specific metadata that preserves the Responses API output item ID.
8
8
  *
@@ -22,6 +22,15 @@ interface StreamedFunctionCallMetadata {
22
22
  ended?: boolean;
23
23
  pendingArguments?: string | undefined;
24
24
  }
25
+ /**
26
+ * The pre-2025-07 spec name for `response.reasoning_text.delta`. Removed from
27
+ * the openai SDK, but OpenAI-compatible providers frozen on the older spec
28
+ * (e.g. Amazon Bedrock's Mantle endpoint serving Gemma) still emit it.
29
+ */
30
+ interface LegacyReasoningDeltaEvent {
31
+ type: 'response.reasoning.delta';
32
+ delta?: unknown;
33
+ }
25
34
  /**
26
35
  * Shared implementation of the OpenAI Responses API. Holds the stream-event
27
36
  * accumulator + AG-UI lifecycle and calls the OpenAI SDK directly. Subclasses
@@ -33,7 +42,7 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
33
42
  readonly name: string;
34
43
  protected client: OpenAI;
35
44
  constructor(model: TModel, name: string, client: OpenAI);
36
- chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
45
+ chatStream(options: TextOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
37
46
  /**
38
47
  * Generate structured output using the provider's native JSON Schema response format.
39
48
  * Uses stream: false to get the complete response in one call.
@@ -51,7 +60,8 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
51
60
  * Stream structured output via the Responses API: single request with
52
61
  * `text.format: json_schema` + `stream: true`. Consumes Responses-API
53
62
  * events (`response.output_text.delta`, `response.reasoning_text.delta`,
54
- * `response.reasoning_summary_text.delta`, `response.refusal.delta`,
63
+ * `response.reasoning_summary_text.delta`, the legacy
64
+ * `response.reasoning.delta`, `response.refusal.delta`,
55
65
  * `response.completed`, `response.failed`) and re-emits the standard AG-UI
56
66
  * lifecycle ending with `CUSTOM 'structured-output.complete'`.
57
67
  *
@@ -60,7 +70,7 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
60
70
  * REASONING_* lifecycle events during the stream and on the terminal
61
71
  * CUSTOM event's `value.reasoning`.
62
72
  */
63
- structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<StreamChunk>;
73
+ structuredOutputStream(options: StructuredOutputOptions<TProviderOptions>): AsyncIterable<AdapterYieldChunk>;
64
74
  /**
65
75
  * Cross-SDK abort detection for `structuredOutputStream`. Mirrors the
66
76
  * Chat Completions base; subclasses with proprietary error types override.
@@ -103,6 +113,7 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
103
113
  * - response.created / response.incomplete / response.failed
104
114
  * - response.output_text.delta
105
115
  * - response.reasoning_text.delta
116
+ * - response.reasoning.delta (the legacy type used before response.reasoning_text.delta)
106
117
  * - response.reasoning_summary_text.delta
107
118
  * - response.content_part.added / response.content_part.done
108
119
  * - response.output_item.added
@@ -110,12 +121,12 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
110
121
  * - response.completed
111
122
  * - error
112
123
  */
113
- protected processStreamChunks(stream: AsyncIterable<ResponseStreamEvent>, toolCallMetadata: Map<string, StreamedFunctionCallMetadata>, options: TextOptions<TProviderOptions>, aguiState: {
124
+ protected processStreamChunks(stream: AsyncIterable<ResponseStreamEvent | LegacyReasoningDeltaEvent>, toolCallMetadata: Map<string, StreamedFunctionCallMetadata>, options: TextOptions<TProviderOptions>, aguiState: {
114
125
  runId: string;
115
126
  threadId: string;
116
127
  messageId: string;
117
128
  hasEmittedRunStarted: boolean;
118
- }): AsyncIterable<StreamChunk>;
129
+ }): AsyncIterable<AdapterYieldChunk>;
119
130
  /**
120
131
  * Maps common TextOptions to Responses API request format.
121
132
  * Override this in subclasses to add provider-specific options.
@@ -148,10 +159,10 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
148
159
  * Normalizes message content to an array of ContentPart.
149
160
  * Handles backward compatibility with string content.
150
161
  */
151
- protected normalizeContent(content: string | null | Array<ContentPart>): Array<ContentPart>;
162
+ protected normalizeContent(content: string | null | undefined | Array<ContentPart>): Array<ContentPart>;
152
163
  /**
153
164
  * Extracts text content from a content value that may be string, null, or ContentPart array.
154
165
  */
155
- protected extractTextContent(content: string | null | Array<ContentPart>): string;
166
+ protected extractTextContent(content: string | null | undefined | Array<ContentPart>): string;
156
167
  }
157
168
  export {};