@tanstack/openai-base 0.9.10 → 0.9.12

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.
@@ -95,7 +95,7 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
95
95
  provider: this.name,
96
96
  model: this.model
97
97
  });
98
- const rawText = (await this.client.chat.completions.create({
98
+ const response = await this.client.chat.completions.create({
99
99
  ...cleanParams,
100
100
  stream: false,
101
101
  response_format: {
@@ -106,7 +106,8 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
106
106
  strict: true
107
107
  }
108
108
  }
109
- }, extractRequestOptions(chatOptions.request))).choices[0]?.message.content;
109
+ }, extractRequestOptions(chatOptions.request));
110
+ const rawText = response.choices[0]?.message.content;
110
111
  if (typeof rawText !== "string" || rawText.length === 0) throw new Error(`${this.name}.structuredOutput: response contained no content`);
111
112
  let parsed;
112
113
  try {
@@ -114,9 +115,12 @@ var OpenAIBaseChatCompletionsTextAdapter = class extends BaseTextAdapter {
114
115
  } catch {
115
116
  throw new Error(`Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`);
116
117
  }
118
+ const transformed = this.transformStructuredOutput(parsed);
119
+ const usage = buildChatCompletionsUsage(response.usage);
117
120
  return {
118
- data: this.transformStructuredOutput(parsed),
119
- rawText
121
+ data: transformed,
122
+ rawText,
123
+ ...usage && { usage }
120
124
  };
121
125
  } catch (error) {
122
126
  chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
@@ -1 +1 @@
1
- {"version":3,"file":"chat-completions-text.js","names":[],"sources":["../../../src/adapters/chat-completions-text.ts"],"sourcesContent":["import { EventType, normalizeSystemPrompts } from '@tanstack/ai'\nimport { BaseTextAdapter } from '@tanstack/ai/adapters'\nimport {\n toRunErrorPayload,\n toRunErrorRawEvent,\n} from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport { extractRequestOptions } from '../utils/request-options'\nimport { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport { buildChatCompletionsUsage } from '../usage'\nimport { convertToolsToChatCompletionsFormat } from './chat-completions-tool-converter'\nimport type OpenAI from 'openai'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n ChatCompletionChunk,\n ChatCompletionContentPart,\n ChatCompletionCreateParamsStreaming,\n ChatCompletionMessageParam,\n} from 'openai/resources/chat/completions/completions'\nimport type {\n ContentPart,\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n RunFinishedEvent,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\n\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 // 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}.chatStream failed`,\n )\n const rawEvent = toRunErrorRawEvent(error)\n\n // Emit RUN_STARTED if not yet emitted\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 // Emit AG-UI RUN_ERROR. Conditional `code` spread keeps the wire\n // shape spec-compliant under `exactOptionalPropertyTypes`: AG-UI's\n // `RunErrorEvent.code` is `string?` (absent vs explicit `undefined`\n // matter), so we omit the key when there's no code.\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n code: errorPayload.code,\n // Forward the provider's structured error body so consumers can recover\n // the upstream detail the `{ message, code }` payload drops. Omitted\n // when the error carried no provider body (see toRunErrorRawEvent).\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n code: errorPayload.code,\n },\n }\n\n options.logger.errors(`${this.name}.chatStream fatal`, {\n error: errorPayload,\n source: `${this.name}.chatStream`,\n })\n }\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 return {\n data: transformed,\n rawText,\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 timestamp = Date.now()\n const aguiState = {\n runId: generateId(this.name),\n threadId: chatOptions.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\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,\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || chatOptions.model,\n timestamp,\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || chatOptions.model,\n timestamp,\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,\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,\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,\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || chatOptions.model,\n timestamp,\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,\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,\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,\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,\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,\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,\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,\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || chatOptions.model,\n timestamp,\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,\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,\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n },\n }\n\n chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {\n error: errorPayload,\n source: `${this.name}.structuredOutputStream`,\n })\n }\n }\n\n /**\n * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types\n * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,\n * and standard `AbortError`s. Subclasses with proprietary error types (e.g.\n * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.\n */\n protected isAbortError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false\n const e = error as { name?: unknown; code?: unknown }\n return (\n e.name === 'APIUserAbortError' ||\n e.name === 'AbortError' ||\n e.code === 'ERR_CANCELED'\n )\n }\n\n /**\n * Applies provider-specific transformations for structured output compatibility.\n * Override this in subclasses to handle provider-specific quirks.\n */\n protected makeStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired?: Array<string>,\n ): Record<string, any> {\n return makeStructuredOutputCompatible(schema, originalRequired)\n }\n\n /**\n * Extract reasoning content from a stream chunk. Default returns\n * `undefined` because the OpenAI Chat Completions chunk shape doesn't\n * carry reasoning. The chunk param is typed `unknown` so an override can\n * narrow to its own SDK chunk type without an `as` dance — the base only\n * passes through `processStreamChunks`'s structurally-iterated chunk.\n */\n protected extractReasoning(_chunk: unknown): { text: string } | undefined {\n return undefined\n }\n\n /**\n * Final shaping pass applied to parsed structured-output JSON before it is\n * returned to the caller. Default is a passthrough.\n *\n * Provider `null`s are no longer stripped here: strict-mode null-widening is\n * now undone precisely by the engine (`undoNullWidening`, driven by the\n * schema's null-widening map) the moment the result is captured, so a blind\n * `transformNullsToUndefined` at the adapter would only destroy genuine\n * `.nullable()` nulls. Subclasses may still override to remap or reshape the\n * provider's structured output.\n */\n protected transformStructuredOutput(parsed: unknown): unknown {\n return parsed\n }\n\n /**\n * Processes streamed chunks from the Chat Completions API and yields AG-UI events.\n * Override this in subclasses to handle provider-specific stream behavior.\n */\n protected async *processStreamChunks(\n stream: AsyncIterable<ChatCompletionChunk>,\n options: TextOptions,\n aguiState: {\n runId: string\n threadId: string\n messageId: string\n hasEmittedRunStarted: boolean\n },\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n let hasEmittedTextMessageStart = false\n let lastModel: string | undefined\n // Track usage from any chunk that carries it. With\n // `stream_options: { include_usage: true }` OpenAI emits a terminal chunk\n // whose `choices` is `[]` and only the `usage` field is populated; the\n // earlier `finish_reason` chunk does NOT include token counts. We must\n // therefore defer RUN_FINISHED until the iterator is exhausted so we can\n // pick up usage from the trailing chunk regardless of arrival order.\n let lastUsage: ChatCompletionChunk['usage'] | undefined\n let pendingFinishReason:\n | ChatCompletionChunk['choices'][number]['finish_reason']\n | undefined\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean // Track if TOOL_CALL_START has been emitted\n }\n >()\n\n // Reasoning lifecycle (driven by extractReasoning() hook — see method\n // docs). The base wire format (OpenAI Chat Completions) has no reasoning,\n // so these stay unused for openai/grok/groq. OpenRouter etc. opt in.\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n // Legacy STEP_STARTED/STEP_FINISHED pair emitted alongside REASONING_*\n // for back-compat with consumers (UI, devtools) that haven't migrated\n // to the spec REASONING_* events yet.\n let stepId: string | undefined\n let accumulatedReasoning = ''\n // Track whether ANY tool call lifecycle was actually completed across the\n // entire stream. Lets us downgrade a `tool_calls` finish_reason to `stop`\n // when the upstream signalled tool calls but never produced a complete\n // start/end pair — emitting RUN_FINISHED { finishReason: 'tool_calls' }\n // with no matching TOOL_CALL_END would leave consumers waiting for tool\n // results that never arrive.\n let emittedAnyToolCallEnd = false\n\n try {\n for await (const chunk of stream) {\n const choiceForLog = chunk.choices[0]\n options.logger.provider(\n `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? 'none'} hasContent=${!!choiceForLog?.delta.content} hasToolCalls=${!!choiceForLog?.delta.tool_calls} hasUsage=${!!chunk.usage}`,\n { provider: this.name, model: chunk.model },\n )\n\n // Capture usage from any chunk (including the terminal usage-only\n // chunk emitted when `stream_options.include_usage` is on).\n if (chunk.usage) {\n lastUsage = chunk.usage\n }\n if (chunk.model) {\n lastModel = chunk.model\n }\n\n // Emit RUN_STARTED on the first chunk of any kind so callers see a\n // run lifecycle even on streams that arrive entirely as usage-only\n // (no choices). Without this, a usage-first stream would skip\n // RUN_STARTED via `if (!choice) continue` below and the post-loop\n // synthetic block would also skip RUN_FINISHED (it gates on\n // `hasEmittedRunStarted`).\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n // Reasoning content (extractReasoning() hook). Run before reading\n // choice/delta so reasoning-only chunks (no `choices`) still drive\n // the REASONING_* lifecycle on providers that send reasoning out of\n // band. The base default returns undefined.\n const reasoning = this.extractReasoning(chunk)\n if (reasoning && reasoning.text) {\n if (!reasoningMessageId) {\n reasoningMessageId = generateId(this.name)\n stepId = generateId(this.name)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n // Legacy STEP_STARTED (single emission, paired with the\n // STEP_FINISHED below when reasoning closes).\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n accumulatedReasoning += reasoning.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: reasoning.text,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n }\n\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n // Close reasoning before text starts so consumers see a clean\n // REASONING_END before any TEXT_MESSAGE_START.\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n // Emit AG-UI TEXT_MESSAGE_CONTENT\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n let toolCall = toolCallsInProgress.get(index)\n if (!toolCall) {\n toolCall = {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n }\n toolCallsInProgress.set(index, toolCall)\n }\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n // Emit TOOL_CALL_START when we have id and name\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n parentMessageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n index,\n }\n }\n\n // Emit TOOL_CALL_ARGS for argument deltas\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: toolCallDelta.function.arguments,\n }\n }\n }\n }\n\n // Handle finish reason. We DO emit TOOL_CALL_END and TEXT_MESSAGE_END\n // here because the corresponding _START events have already fired,\n // and tool execution downstream wants to begin as soon as possible.\n // RUN_FINISHED is deferred until the iterator is fully exhausted so\n // we can capture the trailing usage chunk that arrives AFTER this\n // chunk when stream_options.include_usage is on.\n if (choice.finish_reason) {\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n // Skip tool calls that never emitted TOOL_CALL_START — emitting\n // a stray TOOL_CALL_END here would violate AG-UI lifecycle\n // (END without matching START) for partial deltas where the\n // upstream never sent both id and name.\n if (!toolCall.started) continue\n\n // Parse arguments for TOOL_CALL_END. Surface parse failures via\n // the logger so a model emitting malformed JSON for tool args\n // is debuggable instead of silently invoking the tool with {}.\n // Non-object JSON (e.g. a bare string or number) is also coerced\n // to {} so downstream tool execution doesn't receive a primitive\n // input, mirroring the Responses adapter's guard.\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput =\n parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n\n // Emit AG-UI TOOL_CALL_END\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n emittedAnyToolCallEnd = true\n }\n // Clear tool-call state after emission so a subsequent\n // `finish_reason: 'stop'` chunk (or the post-loop synthetic\n // block) doesn't see lingering entries and misreport the finish.\n toolCallsInProgress.clear()\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n hasEmittedTextMessageStart = false\n }\n\n // Remember the upstream finish_reason; RUN_FINISHED is emitted at\n // end-of-stream so we pick up the trailing usage-only chunk too.\n pendingFinishReason = choice.finish_reason\n }\n }\n\n // Emit a single terminal RUN_FINISHED after the iterator is exhausted.\n // This both delivers accurate token counts (the trailing usage chunk\n // may arrive AFTER the finish_reason chunk) and gives consumers a\n // guaranteed terminal event even when the upstream cuts off mid-stream\n // (no finish_reason chunk ever arrives).\n if (aguiState.hasEmittedRunStarted) {\n // Close any started tool calls that never got finish_reason. A\n // truncated stream that emitted TOOL_CALL_START but never reached\n // finish_reason would otherwise leave consumers with an unbalanced\n // start. Skip non-started entries (no matching START to close).\n let pendingToolCount = 0\n for (const [, toolCall] of toolCallsInProgress) {\n if (!toolCall.started) continue\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n // Mirror the finish_reason path's logger call — a truncated\n // stream emitting malformed tool-call JSON would otherwise\n // silently invoke the tool with `{}`, the exact failure the\n // finish_reason logger was added to prevent.\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed (drain)`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: lastModel || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n pendingToolCount += 1\n emittedAnyToolCallEnd = true\n }\n toolCallsInProgress.clear()\n\n // Make sure the text message lifecycle is closed even on early\n // termination paths where finish_reason never arrives.\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n }\n\n // Close any reasoning lifecycle that text never closed (no text\n // content arrived, or the stream cut off before text started).\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Map upstream finish_reason to AG-UI's narrower vocabulary.\n // Collapsing length / content_filter to 'stop' would hide why the\n // run terminated — surface it instead. Use `tool_calls` only when\n // a TOOL_CALL_END was actually emitted: an upstream that signalled\n // `tool_calls` but never produced a started/ended pair must NOT\n // surface `tool_calls` here, since downstream consumers wait for\n // tool results that would never arrive. OpenAI's legacy\n // `function_call` value (from the v1 function-calling API) is\n // normalized to `tool_calls` — semantically the same termination.\n const finishReason: NonNullable<RunFinishedEvent['finishReason']> =\n emittedAnyToolCallEnd\n ? 'tool_calls'\n : pendingFinishReason === 'tool_calls'\n ? 'stop'\n : pendingFinishReason === 'function_call'\n ? 'tool_calls'\n : (pendingFinishReason ?? 'stop')\n\n // Conditional `usage` spread: AG-UI's `RunFinishedEvent.usage` is\n // optional with no `| undefined`; omit the key entirely when no usage\n // arrived rather than emitting `usage: undefined`.\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n ...(lastUsage && {\n usage: buildChatCompletionsUsage(lastUsage),\n }),\n finishReason,\n }\n }\n } catch (error: unknown) {\n // 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}.processStreamChunks failed`,\n )\n const rawEvent = toRunErrorRawEvent(error)\n options.logger.errors(`${this.name}.processStreamChunks fatal`, {\n error: errorPayload,\n source: `${this.name}.processStreamChunks`,\n })\n\n // Emit AG-UI RUN_ERROR with conditional `code` spread (see chatStream's\n // catch block for the rationale). `rawEvent` carries the provider's\n // structured error body when present.\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 /**\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 // 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 { makeStructuredOutputCompatible } from '../utils/schema-converter'\nimport { buildChatCompletionsUsage } from '../usage'\nimport { convertToolsToChatCompletionsFormat } from './chat-completions-tool-converter'\nimport type OpenAI from 'openai'\nimport type {\n StructuredOutputOptions,\n StructuredOutputResult,\n} from '@tanstack/ai/adapters'\nimport type {\n ChatCompletionChunk,\n ChatCompletionContentPart,\n ChatCompletionCreateParamsStreaming,\n ChatCompletionMessageParam,\n} from 'openai/resources/chat/completions/completions'\nimport type {\n ContentPart,\n DefaultMessageMetadataByModality,\n Modality,\n ModelMessage,\n RunFinishedEvent,\n StreamChunk,\n TextOptions,\n} from '@tanstack/ai'\n\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 // 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}.chatStream failed`,\n )\n const rawEvent = toRunErrorRawEvent(error)\n\n // Emit RUN_STARTED if not yet emitted\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 // Emit AG-UI RUN_ERROR. Conditional `code` spread keeps the wire\n // shape spec-compliant under `exactOptionalPropertyTypes`: AG-UI's\n // `RunErrorEvent.code` is `string?` (absent vs explicit `undefined`\n // matter), so we omit the key when there's no code.\n yield {\n type: EventType.RUN_ERROR,\n model: options.model,\n timestamp: Date.now(),\n message: errorPayload.message,\n code: errorPayload.code,\n // Forward the provider's structured error body so consumers can recover\n // the upstream detail the `{ message, code }` payload drops. Omitted\n // when the error carried no provider body (see toRunErrorRawEvent).\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n code: errorPayload.code,\n },\n }\n\n options.logger.errors(`${this.name}.chatStream fatal`, {\n error: errorPayload,\n source: `${this.name}.chatStream`,\n })\n }\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 timestamp = Date.now()\n const aguiState = {\n runId: generateId(this.name),\n threadId: chatOptions.threadId ?? generateId(this.name),\n messageId: generateId(this.name),\n timestamp,\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,\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || chatOptions.model,\n timestamp,\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || chatOptions.model,\n timestamp,\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,\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,\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,\n }\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || chatOptions.model,\n timestamp,\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,\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,\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,\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,\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,\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,\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,\n }\n\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || chatOptions.model,\n timestamp,\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,\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,\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n ...(rawEvent !== undefined && { rawEvent }),\n error: {\n message: errorPayload.message,\n ...(resolvedCode !== undefined && { code: resolvedCode }),\n },\n }\n\n chatOptions.logger.errors(`${this.name}.structuredOutputStream fatal`, {\n error: errorPayload,\n source: `${this.name}.structuredOutputStream`,\n })\n }\n }\n\n /**\n * Cross-SDK abort detection for `structuredOutputStream`. Default duck-types\n * on `name === 'APIUserAbortError'` (OpenAI SDK), `code === 'ERR_CANCELED'`,\n * and standard `AbortError`s. Subclasses with proprietary error types (e.g.\n * `@openrouter/sdk`'s `RequestAbortedError`) override to extend the check.\n */\n protected isAbortError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false\n const e = error as { name?: unknown; code?: unknown }\n return (\n e.name === 'APIUserAbortError' ||\n e.name === 'AbortError' ||\n e.code === 'ERR_CANCELED'\n )\n }\n\n /**\n * Applies provider-specific transformations for structured output compatibility.\n * Override this in subclasses to handle provider-specific quirks.\n */\n protected makeStructuredOutputCompatible(\n schema: Record<string, any>,\n originalRequired?: Array<string>,\n ): Record<string, any> {\n return makeStructuredOutputCompatible(schema, originalRequired)\n }\n\n /**\n * Extract reasoning content from a stream chunk. Default returns\n * `undefined` because the OpenAI Chat Completions chunk shape doesn't\n * carry reasoning. The chunk param is typed `unknown` so an override can\n * narrow to its own SDK chunk type without an `as` dance — the base only\n * passes through `processStreamChunks`'s structurally-iterated chunk.\n */\n protected extractReasoning(_chunk: unknown): { text: string } | undefined {\n return undefined\n }\n\n /**\n * Final shaping pass applied to parsed structured-output JSON before it is\n * returned to the caller. Default is a passthrough.\n *\n * Provider `null`s are no longer stripped here: strict-mode null-widening is\n * now undone precisely by the engine (`undoNullWidening`, driven by the\n * schema's null-widening map) the moment the result is captured, so a blind\n * `transformNullsToUndefined` at the adapter would only destroy genuine\n * `.nullable()` nulls. Subclasses may still override to remap or reshape the\n * provider's structured output.\n */\n protected transformStructuredOutput(parsed: unknown): unknown {\n return parsed\n }\n\n /**\n * Processes streamed chunks from the Chat Completions API and yields AG-UI events.\n * Override this in subclasses to handle provider-specific stream behavior.\n */\n protected async *processStreamChunks(\n stream: AsyncIterable<ChatCompletionChunk>,\n options: TextOptions,\n aguiState: {\n runId: string\n threadId: string\n messageId: string\n hasEmittedRunStarted: boolean\n },\n ): AsyncIterable<StreamChunk> {\n let accumulatedContent = ''\n let hasEmittedTextMessageStart = false\n let lastModel: string | undefined\n // Track usage from any chunk that carries it. With\n // `stream_options: { include_usage: true }` OpenAI emits a terminal chunk\n // whose `choices` is `[]` and only the `usage` field is populated; the\n // earlier `finish_reason` chunk does NOT include token counts. We must\n // therefore defer RUN_FINISHED until the iterator is exhausted so we can\n // pick up usage from the trailing chunk regardless of arrival order.\n let lastUsage: ChatCompletionChunk['usage'] | undefined\n let pendingFinishReason:\n | ChatCompletionChunk['choices'][number]['finish_reason']\n | undefined\n\n // Track tool calls being streamed (arguments come in chunks)\n const toolCallsInProgress = new Map<\n number,\n {\n id: string\n name: string\n arguments: string\n started: boolean // Track if TOOL_CALL_START has been emitted\n }\n >()\n\n // Reasoning lifecycle (driven by extractReasoning() hook — see method\n // docs). The base wire format (OpenAI Chat Completions) has no reasoning,\n // so these stay unused for openai/grok/groq. OpenRouter etc. opt in.\n let reasoningMessageId: string | undefined\n let hasClosedReasoning = false\n // Legacy STEP_STARTED/STEP_FINISHED pair emitted alongside REASONING_*\n // for back-compat with consumers (UI, devtools) that haven't migrated\n // to the spec REASONING_* events yet.\n let stepId: string | undefined\n let accumulatedReasoning = ''\n // Track whether ANY tool call lifecycle was actually completed across the\n // entire stream. Lets us downgrade a `tool_calls` finish_reason to `stop`\n // when the upstream signalled tool calls but never produced a complete\n // start/end pair — emitting RUN_FINISHED { finishReason: 'tool_calls' }\n // with no matching TOOL_CALL_END would leave consumers waiting for tool\n // results that never arrive.\n let emittedAnyToolCallEnd = false\n\n try {\n for await (const chunk of stream) {\n const choiceForLog = chunk.choices[0]\n options.logger.provider(\n `provider=${this.name} finish_reason=${choiceForLog?.finish_reason ?? 'none'} hasContent=${!!choiceForLog?.delta.content} hasToolCalls=${!!choiceForLog?.delta.tool_calls} hasUsage=${!!chunk.usage}`,\n { provider: this.name, model: chunk.model },\n )\n\n // Capture usage from any chunk (including the terminal usage-only\n // chunk emitted when `stream_options.include_usage` is on).\n if (chunk.usage) {\n lastUsage = chunk.usage\n }\n if (chunk.model) {\n lastModel = chunk.model\n }\n\n // Emit RUN_STARTED on the first chunk of any kind so callers see a\n // run lifecycle even on streams that arrive entirely as usage-only\n // (no choices). Without this, a usage-first stream would skip\n // RUN_STARTED via `if (!choice) continue` below and the post-loop\n // synthetic block would also skip RUN_FINISHED (it gates on\n // `hasEmittedRunStarted`).\n if (!aguiState.hasEmittedRunStarted) {\n aguiState.hasEmittedRunStarted = true\n yield {\n type: EventType.RUN_STARTED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n parentRunId: options.parentRunId,\n }\n }\n\n // Reasoning content (extractReasoning() hook). Run before reading\n // choice/delta so reasoning-only chunks (no `choices`) still drive\n // the REASONING_* lifecycle on providers that send reasoning out of\n // band. The base default returns undefined.\n const reasoning = this.extractReasoning(chunk)\n if (reasoning && reasoning.text) {\n if (!reasoningMessageId) {\n reasoningMessageId = generateId(this.name)\n stepId = generateId(this.name)\n yield {\n type: EventType.REASONING_START,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_MESSAGE_START,\n messageId: reasoningMessageId,\n role: 'reasoning' as const,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n // Legacy STEP_STARTED (single emission, paired with the\n // STEP_FINISHED below when reasoning closes).\n yield {\n type: EventType.STEP_STARTED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n stepType: 'thinking',\n }\n }\n accumulatedReasoning += reasoning.text\n yield {\n type: EventType.REASONING_MESSAGE_CONTENT,\n messageId: reasoningMessageId,\n delta: reasoning.text,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n }\n\n const choice = chunk.choices[0]\n\n if (!choice) continue\n\n const delta = choice.delta\n const deltaContent = delta.content\n const deltaToolCalls = delta.tool_calls\n\n // Handle content delta\n if (deltaContent) {\n // Close reasoning before text starts so consumers see a clean\n // REASONING_END before any TEXT_MESSAGE_START.\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Emit TEXT_MESSAGE_START on first text content\n if (!hasEmittedTextMessageStart) {\n hasEmittedTextMessageStart = true\n yield {\n type: EventType.TEXT_MESSAGE_START,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n role: 'assistant',\n }\n }\n\n accumulatedContent += deltaContent\n\n // Emit AG-UI TEXT_MESSAGE_CONTENT\n yield {\n type: EventType.TEXT_MESSAGE_CONTENT,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: deltaContent,\n content: accumulatedContent,\n }\n }\n\n // Handle tool calls - they come in as deltas\n if (deltaToolCalls) {\n for (const toolCallDelta of deltaToolCalls) {\n const index = toolCallDelta.index\n\n // Initialize or update the tool call in progress\n let toolCall = toolCallsInProgress.get(index)\n if (!toolCall) {\n toolCall = {\n id: toolCallDelta.id || '',\n name: toolCallDelta.function?.name || '',\n arguments: '',\n started: false,\n }\n toolCallsInProgress.set(index, toolCall)\n }\n\n // Update with any new data from the delta\n if (toolCallDelta.id) {\n toolCall.id = toolCallDelta.id\n }\n if (toolCallDelta.function?.name) {\n toolCall.name = toolCallDelta.function.name\n }\n if (toolCallDelta.function?.arguments) {\n toolCall.arguments += toolCallDelta.function.arguments\n }\n\n // Emit TOOL_CALL_START when we have id and name\n if (toolCall.id && toolCall.name && !toolCall.started) {\n toolCall.started = true\n yield {\n type: EventType.TOOL_CALL_START,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n parentMessageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n index,\n }\n }\n\n // Emit TOOL_CALL_ARGS for argument deltas\n if (toolCallDelta.function?.arguments && toolCall.started) {\n yield {\n type: EventType.TOOL_CALL_ARGS,\n toolCallId: toolCall.id,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n delta: toolCallDelta.function.arguments,\n }\n }\n }\n }\n\n // Handle finish reason. We DO emit TOOL_CALL_END and TEXT_MESSAGE_END\n // here because the corresponding _START events have already fired,\n // and tool execution downstream wants to begin as soon as possible.\n // RUN_FINISHED is deferred until the iterator is fully exhausted so\n // we can capture the trailing usage chunk that arrives AFTER this\n // chunk when stream_options.include_usage is on.\n if (choice.finish_reason) {\n if (\n choice.finish_reason === 'tool_calls' ||\n toolCallsInProgress.size > 0\n ) {\n for (const [, toolCall] of toolCallsInProgress) {\n // Skip tool calls that never emitted TOOL_CALL_START — emitting\n // a stray TOOL_CALL_END here would violate AG-UI lifecycle\n // (END without matching START) for partial deltas where the\n // upstream never sent both id and name.\n if (!toolCall.started) continue\n\n // Parse arguments for TOOL_CALL_END. Surface parse failures via\n // the logger so a model emitting malformed JSON for tool args\n // is debuggable instead of silently invoking the tool with {}.\n // Non-object JSON (e.g. a bare string or number) is also coerced\n // to {} so downstream tool execution doesn't receive a primitive\n // input, mirroring the Responses adapter's guard.\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput =\n parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n\n // Emit AG-UI TOOL_CALL_END\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n emittedAnyToolCallEnd = true\n }\n // Clear tool-call state after emission so a subsequent\n // `finish_reason: 'stop'` chunk (or the post-loop synthetic\n // block) doesn't see lingering entries and misreport the finish.\n toolCallsInProgress.clear()\n }\n\n // Emit TEXT_MESSAGE_END if we had text content\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: chunk.model || options.model,\n timestamp: Date.now(),\n }\n hasEmittedTextMessageStart = false\n }\n\n // Remember the upstream finish_reason; RUN_FINISHED is emitted at\n // end-of-stream so we pick up the trailing usage-only chunk too.\n pendingFinishReason = choice.finish_reason\n }\n }\n\n // Emit a single terminal RUN_FINISHED after the iterator is exhausted.\n // This both delivers accurate token counts (the trailing usage chunk\n // may arrive AFTER the finish_reason chunk) and gives consumers a\n // guaranteed terminal event even when the upstream cuts off mid-stream\n // (no finish_reason chunk ever arrives).\n if (aguiState.hasEmittedRunStarted) {\n // Close any started tool calls that never got finish_reason. A\n // truncated stream that emitted TOOL_CALL_START but never reached\n // finish_reason would otherwise leave consumers with an unbalanced\n // start. Skip non-started entries (no matching START to close).\n let pendingToolCount = 0\n for (const [, toolCall] of toolCallsInProgress) {\n if (!toolCall.started) continue\n let parsedInput: unknown = {}\n if (toolCall.arguments) {\n try {\n const parsed: unknown = JSON.parse(toolCall.arguments)\n parsedInput = parsed && typeof parsed === 'object' ? parsed : {}\n } catch (parseError) {\n // Mirror the finish_reason path's logger call — a truncated\n // stream emitting malformed tool-call JSON would otherwise\n // silently invoke the tool with `{}`, the exact failure the\n // finish_reason logger was added to prevent.\n options.logger.errors(\n `${this.name}.processStreamChunks tool-args JSON parse failed (drain)`,\n {\n error: toRunErrorPayload(\n parseError,\n `tool ${toolCall.name} (${toolCall.id}) returned malformed JSON arguments`,\n ),\n source: `${this.name}.processStreamChunks`,\n toolCallId: toolCall.id,\n toolName: toolCall.name,\n rawArguments: toolCall.arguments,\n },\n )\n parsedInput = {}\n }\n }\n yield {\n type: EventType.TOOL_CALL_END,\n toolCallId: toolCall.id,\n toolCallName: toolCall.name,\n toolName: toolCall.name,\n model: lastModel || options.model,\n timestamp: Date.now(),\n input: parsedInput,\n }\n pendingToolCount += 1\n emittedAnyToolCallEnd = true\n }\n toolCallsInProgress.clear()\n\n // Make sure the text message lifecycle is closed even on early\n // termination paths where finish_reason never arrives.\n if (hasEmittedTextMessageStart) {\n yield {\n type: EventType.TEXT_MESSAGE_END,\n messageId: aguiState.messageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n }\n\n // Close any reasoning lifecycle that text never closed (no text\n // content arrived, or the stream cut off before text started).\n if (reasoningMessageId && !hasClosedReasoning) {\n hasClosedReasoning = true\n yield {\n type: EventType.REASONING_MESSAGE_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n yield {\n type: EventType.REASONING_END,\n messageId: reasoningMessageId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n }\n if (stepId) {\n yield {\n type: EventType.STEP_FINISHED,\n stepName: stepId,\n stepId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n content: accumulatedReasoning,\n }\n }\n }\n\n // Map upstream finish_reason to AG-UI's narrower vocabulary.\n // Collapsing length / content_filter to 'stop' would hide why the\n // run terminated — surface it instead. Use `tool_calls` only when\n // a TOOL_CALL_END was actually emitted: an upstream that signalled\n // `tool_calls` but never produced a started/ended pair must NOT\n // surface `tool_calls` here, since downstream consumers wait for\n // tool results that would never arrive. OpenAI's legacy\n // `function_call` value (from the v1 function-calling API) is\n // normalized to `tool_calls` — semantically the same termination.\n const finishReason: NonNullable<RunFinishedEvent['finishReason']> =\n emittedAnyToolCallEnd\n ? 'tool_calls'\n : pendingFinishReason === 'tool_calls'\n ? 'stop'\n : pendingFinishReason === 'function_call'\n ? 'tool_calls'\n : (pendingFinishReason ?? 'stop')\n\n // Conditional `usage` spread: AG-UI's `RunFinishedEvent.usage` is\n // optional with no `| undefined`; omit the key entirely when no usage\n // arrived rather than emitting `usage: undefined`.\n yield {\n type: EventType.RUN_FINISHED,\n runId: aguiState.runId,\n threadId: aguiState.threadId,\n model: lastModel || options.model,\n timestamp: Date.now(),\n ...(lastUsage && {\n usage: buildChatCompletionsUsage(lastUsage),\n }),\n finishReason,\n }\n }\n } catch (error: unknown) {\n // 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}.processStreamChunks failed`,\n )\n const rawEvent = toRunErrorRawEvent(error)\n options.logger.errors(`${this.name}.processStreamChunks fatal`, {\n error: errorPayload,\n source: `${this.name}.processStreamChunks`,\n })\n\n // Emit AG-UI RUN_ERROR with conditional `code` spread (see chatStream's\n // catch block for the rationale). `rawEvent` carries the provider's\n // structured error body when present.\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 /**\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 // 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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"}
@@ -2,13 +2,32 @@ import { BaseTextAdapter, StructuredOutputOptions, StructuredOutputResult } from
2
2
  import { default as OpenAI } from 'openai';
3
3
  import { Response, ResponseCreateParams, ResponseInput, ResponseInputContent, ResponseStreamEvent } from 'openai/resources/responses/responses';
4
4
  import { ContentPart, DefaultMessageMetadataByModality, Modality, ModelMessage, StreamChunk, TextOptions } from '@tanstack/ai';
5
+ /**
6
+ * Provider-specific metadata that preserves the Responses API output item ID.
7
+ *
8
+ * Responses function calls have two identifiers: `call_id` correlates the
9
+ * function output with the call, while `id` identifies the output item itself.
10
+ * TanStack AI uses `call_id` as the canonical tool-call ID and carries the
11
+ * item ID here so stateless follow-up requests can replay both values.
12
+ */
13
+ export interface OpenAIResponsesToolCallMetadata {
14
+ itemId: string;
15
+ }
16
+ interface StreamedFunctionCallMetadata {
17
+ callId: string;
18
+ index: number;
19
+ name: string;
20
+ started: boolean;
21
+ ended?: boolean;
22
+ pendingArguments?: string | undefined;
23
+ }
5
24
  /**
6
25
  * Shared implementation of the OpenAI Responses API. Holds the stream-event
7
26
  * accumulator + AG-UI lifecycle and calls the OpenAI SDK directly. Subclasses
8
27
  * (today: ai-openai) construct an OpenAI client with their provider-specific
9
28
  * `baseURL` / headers and pass it in.
10
29
  */
11
- export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends string, TProviderOptions extends Record<string, unknown> = Record<string, unknown>, TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TMessageMetadata extends DefaultMessageMetadataByModality = DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadata, TToolCapabilities> {
30
+ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends string, TProviderOptions extends Record<string, unknown> = Record<string, unknown>, TInputModalities extends ReadonlyArray<Modality> = ReadonlyArray<Modality>, TMessageMetadata extends DefaultMessageMetadataByModality = DefaultMessageMetadataByModality, TToolCapabilities extends ReadonlyArray<string> = ReadonlyArray<string>> extends BaseTextAdapter<TModel, TProviderOptions, TInputModalities, TMessageMetadata, TToolCapabilities, OpenAIResponsesToolCallMetadata> {
12
31
  readonly kind: "text";
13
32
  readonly name: string;
14
33
  protected client: OpenAI;
@@ -83,13 +102,7 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
83
102
  * - response.completed
84
103
  * - error
85
104
  */
86
- protected processStreamChunks(stream: AsyncIterable<ResponseStreamEvent>, toolCallMetadata: Map<string, {
87
- index: number;
88
- name: string;
89
- started: boolean;
90
- ended?: boolean;
91
- pendingArguments?: string | undefined;
92
- }>, options: TextOptions<TProviderOptions>, aguiState: {
105
+ protected processStreamChunks(stream: AsyncIterable<ResponseStreamEvent>, toolCallMetadata: Map<string, StreamedFunctionCallMetadata>, options: TextOptions<TProviderOptions>, aguiState: {
93
106
  runId: string;
94
107
  threadId: string;
95
108
  messageId: string;
@@ -133,3 +146,4 @@ export declare abstract class OpenAIBaseResponsesTextAdapter<TModel extends stri
133
146
  */
134
147
  protected extractTextContent(content: string | null | Array<ContentPart>): string;
135
148
  }
149
+ export {};
@@ -113,9 +113,12 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
113
113
  } catch {
114
114
  throw new Error(`Failed to parse structured output as JSON. Content: ${rawText.slice(0, 200)}${rawText.length > 200 ? "..." : ""}`);
115
115
  }
116
+ const transformed = this.transformStructuredOutput(parsed);
117
+ const usage = buildResponsesUsage(response.usage);
116
118
  return {
117
- data: this.transformStructuredOutput(parsed),
118
- rawText
119
+ data: transformed,
120
+ rawText,
121
+ ...usage && { usage }
119
122
  };
120
123
  } catch (error) {
121
124
  chatOptions.logger.errors(`${this.name}.structuredOutput fatal`, {
@@ -755,39 +758,45 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
755
758
  let metadata = toolCallMetadata.get(item.id);
756
759
  if (!metadata) {
757
760
  metadata = {
761
+ callId: item.call_id || item.id,
758
762
  index: chunk.output_index,
759
763
  name: item.name || "",
760
764
  started: false
761
765
  };
762
766
  toolCallMetadata.set(item.id, metadata);
763
- } else if (!metadata.name && item.name) metadata.name = item.name;
767
+ } else {
768
+ if (item.call_id) metadata.callId = item.call_id;
769
+ if (!metadata.name && item.name) metadata.name = item.name;
770
+ }
764
771
  if (!metadata.started && metadata.name) {
765
772
  yield {
766
773
  type: EventType.TOOL_CALL_START,
767
- toolCallId: item.id,
774
+ toolCallId: metadata.callId,
768
775
  toolCallName: metadata.name,
769
776
  toolName: metadata.name,
770
777
  parentMessageId: aguiState.messageId,
771
778
  model: model || options.model,
772
779
  timestamp: Date.now(),
773
- index: chunk.output_index
780
+ index: chunk.output_index,
781
+ metadata: { itemId: item.id }
774
782
  };
775
783
  metadata.started = true;
776
784
  }
777
785
  }
778
786
  }
779
787
  if (chunk.type === "response.function_call_arguments.delta" && chunk.delta) {
780
- if (!toolCallMetadata.get(chunk.item_id)?.started) {
788
+ const metadata = toolCallMetadata.get(chunk.item_id);
789
+ if (!metadata?.started) {
781
790
  options.logger.errors(`${this.name}.processStreamChunks orphan function_call_arguments.delta`, {
782
791
  source: `${this.name}.processStreamChunks`,
783
- toolCallId: chunk.item_id,
792
+ itemId: chunk.item_id,
784
793
  rawDelta: chunk.delta
785
794
  });
786
795
  continue;
787
796
  }
788
797
  yield {
789
798
  type: EventType.TOOL_CALL_ARGS,
790
- toolCallId: chunk.item_id,
799
+ toolCallId: metadata.callId,
791
800
  model: model || options.model,
792
801
  timestamp: Date.now(),
793
802
  delta: chunk.delta
@@ -800,7 +809,8 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
800
809
  if (metadata) metadata.pendingArguments = chunk.arguments;
801
810
  options.logger.errors(`${this.name}.processStreamChunks deferring function_call_arguments.done — TOOL_CALL_START not yet emitted (waiting for name)`, {
802
811
  source: `${this.name}.processStreamChunks`,
803
- toolCallId: item_id,
812
+ ...metadata && { toolCallId: metadata.callId },
813
+ itemId: item_id,
804
814
  rawArguments: chunk.arguments
805
815
  });
806
816
  continue;
@@ -814,9 +824,10 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
814
824
  parsedInput = parsed && typeof parsed === "object" ? parsed : {};
815
825
  } catch (parseError) {
816
826
  options.logger.errors(`${this.name}.processStreamChunks tool-args JSON parse failed`, {
817
- error: toRunErrorPayload(parseError, `tool ${name} (${item_id}) returned malformed JSON arguments`),
827
+ error: toRunErrorPayload(parseError, `tool ${name} (${metadata.callId}) returned malformed JSON arguments`),
818
828
  source: `${this.name}.processStreamChunks`,
819
- toolCallId: item_id,
829
+ toolCallId: metadata.callId,
830
+ itemId: item_id,
820
831
  toolName: name,
821
832
  rawArguments: chunk.arguments
822
833
  });
@@ -824,7 +835,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
824
835
  }
825
836
  yield {
826
837
  type: EventType.TOOL_CALL_END,
827
- toolCallId: item_id,
838
+ toolCallId: metadata.callId,
828
839
  toolCallName: name,
829
840
  toolName: name,
830
841
  model: model || options.model,
@@ -836,22 +847,27 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
836
847
  const item = chunk.item;
837
848
  if (item.type === "function_call" && item.id) {
838
849
  const metadata = toolCallMetadata.get(item.id) ?? {
850
+ callId: item.call_id || item.id,
839
851
  index: chunk.output_index,
840
852
  name: item.name || "",
841
853
  started: false
842
854
  };
843
855
  if (!toolCallMetadata.has(item.id)) toolCallMetadata.set(item.id, metadata);
844
- else if (!metadata.name && item.name) metadata.name = item.name;
856
+ else {
857
+ if (item.call_id) metadata.callId = item.call_id;
858
+ if (!metadata.name && item.name) metadata.name = item.name;
859
+ }
845
860
  if (!metadata.started && metadata.name) {
846
861
  yield {
847
862
  type: EventType.TOOL_CALL_START,
848
- toolCallId: item.id,
863
+ toolCallId: metadata.callId,
849
864
  toolCallName: metadata.name,
850
865
  toolName: metadata.name,
851
866
  parentMessageId: aguiState.messageId,
852
867
  model: model || options.model,
853
868
  timestamp: Date.now(),
854
- index: metadata.index
869
+ index: metadata.index,
870
+ metadata: { itemId: item.id }
855
871
  };
856
872
  metadata.started = true;
857
873
  }
@@ -864,9 +880,10 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
864
880
  parsedInput = parsed && typeof parsed === "object" ? parsed : {};
865
881
  } catch (parseError) {
866
882
  options.logger.errors(`${this.name}.processStreamChunks tool-args JSON parse failed (output_item.done backfill)`, {
867
- error: toRunErrorPayload(parseError, `tool ${name} (${item.id}) returned malformed JSON arguments`),
883
+ error: toRunErrorPayload(parseError, `tool ${name} (${metadata.callId}) returned malformed JSON arguments`),
868
884
  source: `${this.name}.processStreamChunks`,
869
- toolCallId: item.id,
885
+ toolCallId: metadata.callId,
886
+ itemId: item.id,
870
887
  toolName: name,
871
888
  rawArguments: rawArgs
872
889
  });
@@ -874,7 +891,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
874
891
  }
875
892
  yield {
876
893
  type: EventType.TOOL_CALL_END,
877
- toolCallId: item.id,
894
+ toolCallId: metadata.callId,
878
895
  toolCallName: name,
879
896
  toolName: name,
880
897
  model: model || options.model,
@@ -890,22 +907,27 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
890
907
  for (const item of chunk.response.output) {
891
908
  if (item.type !== "function_call" || !item.id) continue;
892
909
  const metadata = toolCallMetadata.get(item.id) ?? {
910
+ callId: item.call_id || item.id,
893
911
  index: 0,
894
912
  name: item.name || "",
895
913
  started: false
896
914
  };
897
915
  if (!toolCallMetadata.has(item.id)) toolCallMetadata.set(item.id, metadata);
898
- else if (!metadata.name && item.name) metadata.name = item.name;
916
+ else {
917
+ if (item.call_id) metadata.callId = item.call_id;
918
+ if (!metadata.name && item.name) metadata.name = item.name;
919
+ }
899
920
  if (!metadata.started && metadata.name) {
900
921
  yield {
901
922
  type: EventType.TOOL_CALL_START,
902
- toolCallId: item.id,
923
+ toolCallId: metadata.callId,
903
924
  toolCallName: metadata.name,
904
925
  toolName: metadata.name,
905
926
  parentMessageId: aguiState.messageId,
906
927
  model: model || options.model,
907
928
  timestamp: Date.now(),
908
- index: metadata.index
929
+ index: metadata.index,
930
+ metadata: { itemId: item.id }
909
931
  };
910
932
  metadata.started = true;
911
933
  }
@@ -918,9 +940,10 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
918
940
  parsedInput = parsed && typeof parsed === "object" ? parsed : {};
919
941
  } catch (parseError) {
920
942
  options.logger.errors(`${this.name}.processStreamChunks tool-args JSON parse failed (response.completed backfill)`, {
921
- error: toRunErrorPayload(parseError, `tool ${name} (${item.id}) returned malformed JSON arguments`),
943
+ error: toRunErrorPayload(parseError, `tool ${name} (${metadata.callId}) returned malformed JSON arguments`),
922
944
  source: `${this.name}.processStreamChunks`,
923
- toolCallId: item.id,
945
+ toolCallId: metadata.callId,
946
+ itemId: item.id,
924
947
  toolName: name,
925
948
  rawArguments: rawArgs
926
949
  });
@@ -928,7 +951,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
928
951
  }
929
952
  yield {
930
953
  type: EventType.TOOL_CALL_END,
931
- toolCallId: item.id,
954
+ toolCallId: metadata.callId,
932
955
  toolCallName: name,
933
956
  toolName: name,
934
957
  model: model || options.model,
@@ -1079,9 +1102,11 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1079
1102
  if (message.role === "assistant") {
1080
1103
  if (message.toolCalls && message.toolCalls.length > 0) for (const toolCall of message.toolCalls) {
1081
1104
  const argumentsString = typeof toolCall.function.arguments === "string" ? toolCall.function.arguments : JSON.stringify(toolCall.function.arguments);
1105
+ const itemId = toolCall.metadata?.itemId;
1082
1106
  result.push({
1083
1107
  type: "function_call",
1084
1108
  call_id: toolCall.id,
1109
+ ...itemId && { id: itemId },
1085
1110
  name: toolCall.function.name,
1086
1111
  arguments: argumentsString
1087
1112
  });