@leanandmean/ai 0.74.1-scramjet.29 → 0.74.1-scramjet.30
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.
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +40 -8
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts +69 -0
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +513 -7
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +2 -0
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +41 -8
- package/dist/providers/openai-responses.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azure-openai-responses.d.ts","sourceRoot":"","sources":["../../src/providers/azure-openai-responses.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAKX,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,MAAM,aAAa,CAAC;AA+BrB,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IACjE,eAAe,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IAC1E,gBAAgB,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,CAyE5G,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAkB1G,CAAC","sourcesContent":["import { AzureOpenAI } from \"openai\";\nimport type { ResponseCreateParamsStreaming } from \"openai/resources/responses/responses.js\";\nimport { getEnvApiKey } from \"../env-api-keys.js\";\nimport { clampThinkingLevel } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n} from \"../types.js\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { headersToRecord } from \"../utils/headers.js\";\nimport { convertResponsesMessages, convertResponsesTools, processResponsesStream } from \"./openai-responses-shared.js\";\nimport { buildBaseOptions } from \"./simple-options.js\";\n\nconst DEFAULT_AZURE_API_VERSION = \"v1\";\nconst AZURE_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\", \"azure-openai-responses\"]);\n\nfunction parseDeploymentNameMap(value: string | undefined): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tif (!value) return map;\n\tfor (const entry of value.split(\",\")) {\n\t\tconst trimmed = entry.trim();\n\t\tif (!trimmed) continue;\n\t\tconst [modelId, deploymentName] = trimmed.split(\"=\", 2);\n\t\tif (!modelId || !deploymentName) continue;\n\t\tmap.set(modelId.trim(), deploymentName.trim());\n\t}\n\treturn map;\n}\n\nfunction resolveDeploymentName(model: Model<\"azure-openai-responses\">, options?: AzureOpenAIResponsesOptions): string {\n\tif (options?.azureDeploymentName) {\n\t\treturn options.azureDeploymentName;\n\t}\n\tconst mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(model.id);\n\treturn mappedDeployment || model.id;\n}\n\n// Azure OpenAI Responses-specific options\nexport interface AzureOpenAIResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\treasoningSummary?: \"auto\" | \"detailed\" | \"concise\" | null;\n\tazureApiVersion?: string;\n\tazureResourceName?: string;\n\tazureBaseUrl?: string;\n\tazureDeploymentName?: string;\n}\n\n/**\n * Generate function for Azure OpenAI Responses API\n */\nexport const streamAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", AzureOpenAIResponsesOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: AzureOpenAIResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t// Start async processing\n\t(async () => {\n\t\tconst deploymentName = resolveDeploymentName(model, options);\n\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"azure-openai-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\t// Create Azure OpenAI client\n\t\t\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider) || \"\";\n\t\t\tconst client = createClient(model, apiKey, options);\n\t\t\tlet params = buildParams(model, context, options, deploymentName);\n\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\tif (nextParams !== undefined) {\n\t\t\t\tparams = nextParams as ResponseCreateParamsStreaming;\n\t\t\t}\n\t\t\tconst requestOptions = {\n\t\t\t\t...(options?.signal ? { signal: options.signal } : {}),\n\t\t\t\t...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),\n\t\t\t\t...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\n\t\t\t};\n\t\t\tconst { data: openaiStream, response } = await client.responses.create(params, requestOptions).withResponse();\n\t\t\tawait options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\tawait processResponsesStream(openaiStream, output, stream, model);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(\"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimpleAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, options, apiKey);\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn streamAzureOpenAIResponses(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies AzureOpenAIResponsesOptions);\n};\n\nfunction normalizeAzureBaseUrl(baseUrl: string): string {\n\tconst trimmed = baseUrl.trim().replace(/\\/+$/, \"\");\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(`Invalid Azure OpenAI base URL: ${baseUrl}`);\n\t}\n\n\tconst isAzureHost =\n\t\turl.hostname.endsWith(\".openai.azure.com\") || url.hostname.endsWith(\".cognitiveservices.azure.com\");\n\tconst normalizedPath = url.pathname.replace(/\\/+$/, \"\");\n\n\t// Ensure Azure hosts have /openai/v1 as base path so the AzureOpenAI SDK\n\t// can append /deployments/<model>/... and ?api-version=v1 correctly.\n\tif (isAzureHost && (normalizedPath === \"\" || normalizedPath === \"/\" || normalizedPath === \"/openai\")) {\n\t\turl.pathname = \"/openai/v1\";\n\t\turl.search = \"\";\n\t}\n\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nfunction buildDefaultBaseUrl(resourceName: string): string {\n\treturn `https://${resourceName}.openai.azure.com/openai/v1`;\n}\n\nfunction resolveAzureConfig(\n\tmodel: Model<\"azure-openai-responses\">,\n\toptions?: AzureOpenAIResponsesOptions,\n): { baseUrl: string; apiVersion: string } {\n\tconst apiVersion = options?.azureApiVersion || process.env.AZURE_OPENAI_API_VERSION || DEFAULT_AZURE_API_VERSION;\n\n\tconst baseUrl = options?.azureBaseUrl?.trim() || process.env.AZURE_OPENAI_BASE_URL?.trim() || undefined;\n\tconst resourceName = options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME;\n\n\tlet resolvedBaseUrl = baseUrl;\n\n\tif (!resolvedBaseUrl && resourceName) {\n\t\tresolvedBaseUrl = buildDefaultBaseUrl(resourceName);\n\t}\n\n\tif (!resolvedBaseUrl && model.baseUrl) {\n\t\tresolvedBaseUrl = model.baseUrl;\n\t}\n\n\tif (!resolvedBaseUrl) {\n\t\tthrow new Error(\n\t\t\t\"Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_RESOURCE_NAME, or pass azureBaseUrl, azureResourceName, or model.baseUrl.\",\n\t\t);\n\t}\n\n\treturn {\n\t\tbaseUrl: normalizeAzureBaseUrl(resolvedBaseUrl),\n\t\tapiVersion,\n\t};\n}\n\nfunction createClient(model: Model<\"azure-openai-responses\">, apiKey: string, options?: AzureOpenAIResponsesOptions) {\n\tif (!apiKey) {\n\t\tif (!process.env.AZURE_OPENAI_API_KEY) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass it as an argument.\",\n\t\t\t);\n\t\t}\n\t\tapiKey = process.env.AZURE_OPENAI_API_KEY;\n\t}\n\n\tconst headers = { ...model.headers };\n\n\tif (options?.headers) {\n\t\tObject.assign(headers, options.headers);\n\t}\n\n\tconst { baseUrl, apiVersion } = resolveAzureConfig(model, options);\n\n\treturn new AzureOpenAI({\n\t\tapiKey,\n\t\tapiVersion,\n\t\tdangerouslyAllowBrowser: true,\n\t\tdefaultHeaders: headers,\n\t\tbaseURL: baseUrl,\n\t});\n}\n\nfunction buildParams(\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions: AzureOpenAIResponsesOptions | undefined,\n\tdeploymentName: string,\n) {\n\tconst messages = convertResponsesMessages(model, context, AZURE_TOOL_CALL_PROVIDERS);\n\n\tconst params: ResponseCreateParamsStreaming = {\n\t\tmodel: deploymentName,\n\t\tinput: messages,\n\t\tstream: true,\n\t\tprompt_cache_key: options?.sessionId,\n\t};\n\n\tif (options?.maxTokens) {\n\t\tparams.max_output_tokens = options?.maxTokens;\n\t}\n\n\tif (options?.temperature !== undefined) {\n\t\tparams.temperature = options?.temperature;\n\t}\n\n\tif (context.tools && context.tools.length > 0) {\n\t\tparams.tools = convertResponsesTools(context.tools);\n\t}\n\n\tif (model.reasoning) {\n\t\tif (options?.reasoningEffort || options?.reasoningSummary) {\n\t\t\tconst effort = options?.reasoningEffort\n\t\t\t\t? (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort)\n\t\t\t\t: \"medium\";\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: effort as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t\tsummary: options?.reasoningSummary || \"auto\",\n\t\t\t};\n\t\t\tparams.include = [\"reasoning.encrypted_content\"];\n\t\t} else if (model.thinkingLevelMap?.off !== null) {\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: (model.thinkingLevelMap?.off ?? \"none\") as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t};\n\t\t}\n\t}\n\n\treturn params;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"azure-openai-responses.d.ts","sourceRoot":"","sources":["../../src/providers/azure-openai-responses.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAKX,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,MAAM,aAAa,CAAC;AAwCrB,MAAM,WAAW,2BAA4B,SAAQ,aAAa;IACjE,eAAe,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IAC1E,gBAAgB,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,GAAG,IAAI,CAAC;IAC1D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,eAAO,MAAM,0BAA0B,EAAE,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,CAuG5G,CAAC;AAEF,eAAO,MAAM,gCAAgC,EAAE,cAAc,CAAC,wBAAwB,EAAE,mBAAmB,CAkB1G,CAAC","sourcesContent":["import { AzureOpenAI } from \"openai\";\nimport type { ResponseCreateParamsStreaming } from \"openai/resources/responses/responses.js\";\nimport { getEnvApiKey } from \"../env-api-keys.js\";\nimport { clampThinkingLevel } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n} from \"../types.js\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { headersToRecord } from \"../utils/headers.js\";\nimport {\n\tabortedResponsesFailureMessage,\n\tappendResponsesFailureDiagnostics,\n\tappendResponsesSdkRetryDiagnostic,\n\tconvertResponsesMessages,\n\tconvertResponsesTools,\n\tcreateResponsesSdkRequestObserver,\n\tnormalizeResponsesFailure,\n\tprocessResponsesStream,\n} from \"./openai-responses-shared.js\";\nimport { buildBaseOptions } from \"./simple-options.js\";\n\nconst DEFAULT_AZURE_API_VERSION = \"v1\";\nconst AZURE_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\", \"azure-openai-responses\"]);\n\nfunction parseDeploymentNameMap(value: string | undefined): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tif (!value) return map;\n\tfor (const entry of value.split(\",\")) {\n\t\tconst trimmed = entry.trim();\n\t\tif (!trimmed) continue;\n\t\tconst [modelId, deploymentName] = trimmed.split(\"=\", 2);\n\t\tif (!modelId || !deploymentName) continue;\n\t\tmap.set(modelId.trim(), deploymentName.trim());\n\t}\n\treturn map;\n}\n\nfunction resolveDeploymentName(model: Model<\"azure-openai-responses\">, options?: AzureOpenAIResponsesOptions): string {\n\tif (options?.azureDeploymentName) {\n\t\treturn options.azureDeploymentName;\n\t}\n\tconst mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(model.id);\n\treturn mappedDeployment || model.id;\n}\n\n// Azure OpenAI Responses-specific options\nexport interface AzureOpenAIResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\treasoningSummary?: \"auto\" | \"detailed\" | \"concise\" | null;\n\tazureApiVersion?: string;\n\tazureResourceName?: string;\n\tazureBaseUrl?: string;\n\tazureDeploymentName?: string;\n}\n\n/**\n * Generate function for Azure OpenAI Responses API\n */\nexport const streamAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", AzureOpenAIResponsesOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: AzureOpenAIResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t// Start async processing\n\t(async () => {\n\t\tconst deploymentName = resolveDeploymentName(model, options);\n\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"azure-openai-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\t// SCRAMJET-DIVERGENCE: classify failures and observe SDK attempts at the request/stream boundary (#553).\n\t\tlet failurePhase: \"request\" | \"stream\" = \"request\";\n\t\tlet payloadCallbackFailed = false;\n\t\tlet responseCallbackFailed = false;\n\t\tconst sdkRequestObserver = createResponsesSdkRequestObserver(fetch);\n\t\ttry {\n\t\t\t// Create Azure OpenAI client\n\t\t\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider) || \"\";\n\t\t\tconst client = createClient(model, apiKey, options, sdkRequestObserver.fetch);\n\t\t\tlet params = buildParams(model, context, options, deploymentName);\n\t\t\ttry {\n\t\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\t\tif (nextParams !== undefined) {\n\t\t\t\t\tparams = nextParams as ResponseCreateParamsStreaming;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tpayloadCallbackFailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst requestOptions = {\n\t\t\t\t...(options?.signal ? { signal: options.signal } : {}),\n\t\t\t\t...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),\n\t\t\t\t...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\n\t\t\t};\n\t\t\tconst { data: openaiStream, response } = await client.responses.create(params, requestOptions).withResponse();\n\t\t\tsdkRequestObserver.markAccepted();\n\t\t\ttry {\n\t\t\t\tawait options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\t\t} catch (error) {\n\t\t\t\tresponseCallbackFailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\tfailurePhase = \"stream\";\n\t\t\tawait processResponsesStream(openaiStream, output, stream, model);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(\"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tappendResponsesSdkRetryDiagnostic(output, sdkRequestObserver.diagnosticForSuccess());\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\tif (payloadCallbackFailed) {\n\t\t\t\toutput.errorMessage = \"OpenAI Responses payload callback failed.\";\n\t\t\t} else if (responseCallbackFailed) {\n\t\t\t\toutput.errorMessage = \"OpenAI Responses response callback failed.\";\n\t\t\t} else if (output.stopReason === \"aborted\") {\n\t\t\t\toutput.errorMessage = abortedResponsesFailureMessage(error);\n\t\t\t} else {\n\t\t\t\tappendResponsesFailureDiagnostics(\n\t\t\t\t\toutput,\n\t\t\t\t\tnormalizeResponsesFailure(error, failurePhase),\n\t\t\t\t\tsdkRequestObserver.diagnosticForFailure(options?.maxRetries),\n\t\t\t\t);\n\t\t\t}\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimpleAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, options, apiKey);\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn streamAzureOpenAIResponses(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies AzureOpenAIResponsesOptions);\n};\n\nfunction normalizeAzureBaseUrl(baseUrl: string): string {\n\tconst trimmed = baseUrl.trim().replace(/\\/+$/, \"\");\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(`Invalid Azure OpenAI base URL: ${baseUrl}`);\n\t}\n\n\tconst isAzureHost =\n\t\turl.hostname.endsWith(\".openai.azure.com\") || url.hostname.endsWith(\".cognitiveservices.azure.com\");\n\tconst normalizedPath = url.pathname.replace(/\\/+$/, \"\");\n\n\t// Ensure Azure hosts have /openai/v1 as base path so the AzureOpenAI SDK\n\t// can append /deployments/<model>/... and ?api-version=v1 correctly.\n\tif (isAzureHost && (normalizedPath === \"\" || normalizedPath === \"/\" || normalizedPath === \"/openai\")) {\n\t\turl.pathname = \"/openai/v1\";\n\t\turl.search = \"\";\n\t}\n\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nfunction buildDefaultBaseUrl(resourceName: string): string {\n\treturn `https://${resourceName}.openai.azure.com/openai/v1`;\n}\n\nfunction resolveAzureConfig(\n\tmodel: Model<\"azure-openai-responses\">,\n\toptions?: AzureOpenAIResponsesOptions,\n): { baseUrl: string; apiVersion: string } {\n\tconst apiVersion = options?.azureApiVersion || process.env.AZURE_OPENAI_API_VERSION || DEFAULT_AZURE_API_VERSION;\n\n\tconst baseUrl = options?.azureBaseUrl?.trim() || process.env.AZURE_OPENAI_BASE_URL?.trim() || undefined;\n\tconst resourceName = options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME;\n\n\tlet resolvedBaseUrl = baseUrl;\n\n\tif (!resolvedBaseUrl && resourceName) {\n\t\tresolvedBaseUrl = buildDefaultBaseUrl(resourceName);\n\t}\n\n\tif (!resolvedBaseUrl && model.baseUrl) {\n\t\tresolvedBaseUrl = model.baseUrl;\n\t}\n\n\tif (!resolvedBaseUrl) {\n\t\tthrow new Error(\n\t\t\t\"Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_RESOURCE_NAME, or pass azureBaseUrl, azureResourceName, or model.baseUrl.\",\n\t\t);\n\t}\n\n\treturn {\n\t\tbaseUrl: normalizeAzureBaseUrl(resolvedBaseUrl),\n\t\tapiVersion,\n\t};\n}\n\nfunction createClient(\n\tmodel: Model<\"azure-openai-responses\">,\n\tapiKey: string,\n\toptions: AzureOpenAIResponsesOptions | undefined,\n\tfetchImplementation: typeof fetch,\n) {\n\tif (!apiKey) {\n\t\tif (!process.env.AZURE_OPENAI_API_KEY) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass it as an argument.\",\n\t\t\t);\n\t\t}\n\t\tapiKey = process.env.AZURE_OPENAI_API_KEY;\n\t}\n\n\tconst headers = { ...model.headers };\n\n\tif (options?.headers) {\n\t\tObject.assign(headers, options.headers);\n\t}\n\n\tconst { baseUrl, apiVersion } = resolveAzureConfig(model, options);\n\n\treturn new AzureOpenAI({\n\t\tapiKey,\n\t\tapiVersion,\n\t\tdangerouslyAllowBrowser: true,\n\t\tdefaultHeaders: headers,\n\t\tbaseURL: baseUrl,\n\t\tfetch: fetchImplementation,\n\t});\n}\n\nfunction buildParams(\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions: AzureOpenAIResponsesOptions | undefined,\n\tdeploymentName: string,\n) {\n\tconst messages = convertResponsesMessages(model, context, AZURE_TOOL_CALL_PROVIDERS);\n\n\tconst params: ResponseCreateParamsStreaming = {\n\t\tmodel: deploymentName,\n\t\tinput: messages,\n\t\tstream: true,\n\t\tprompt_cache_key: options?.sessionId,\n\t};\n\n\tif (options?.maxTokens) {\n\t\tparams.max_output_tokens = options?.maxTokens;\n\t}\n\n\tif (options?.temperature !== undefined) {\n\t\tparams.temperature = options?.temperature;\n\t}\n\n\tif (context.tools && context.tools.length > 0) {\n\t\tparams.tools = convertResponsesTools(context.tools);\n\t}\n\n\tif (model.reasoning) {\n\t\tif (options?.reasoningEffort || options?.reasoningSummary) {\n\t\t\tconst effort = options?.reasoningEffort\n\t\t\t\t? (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort)\n\t\t\t\t: \"medium\";\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: effort as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t\tsummary: options?.reasoningSummary || \"auto\",\n\t\t\t};\n\t\t\tparams.include = [\"reasoning.encrypted_content\"];\n\t\t} else if (model.thinkingLevelMap?.off !== null) {\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: (model.thinkingLevelMap?.off ?? \"none\") as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t};\n\t\t}\n\t}\n\n\treturn params;\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { getEnvApiKey } from "../env-api-keys.js";
|
|
|
3
3
|
import { clampThinkingLevel } from "../models.js";
|
|
4
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
5
|
import { headersToRecord } from "../utils/headers.js";
|
|
6
|
-
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
|
|
6
|
+
import { abortedResponsesFailureMessage, appendResponsesFailureDiagnostics, appendResponsesSdkRetryDiagnostic, convertResponsesMessages, convertResponsesTools, createResponsesSdkRequestObserver, normalizeResponsesFailure, processResponsesStream, } from "./openai-responses-shared.js";
|
|
7
7
|
import { buildBaseOptions } from "./simple-options.js";
|
|
8
8
|
const DEFAULT_AZURE_API_VERSION = "v1";
|
|
9
9
|
const AZURE_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode", "azure-openai-responses"]);
|
|
@@ -54,14 +54,25 @@ export const streamAzureOpenAIResponses = (model, context, options) => {
|
|
|
54
54
|
stopReason: "stop",
|
|
55
55
|
timestamp: Date.now(),
|
|
56
56
|
};
|
|
57
|
+
// SCRAMJET-DIVERGENCE: classify failures and observe SDK attempts at the request/stream boundary (#553).
|
|
58
|
+
let failurePhase = "request";
|
|
59
|
+
let payloadCallbackFailed = false;
|
|
60
|
+
let responseCallbackFailed = false;
|
|
61
|
+
const sdkRequestObserver = createResponsesSdkRequestObserver(fetch);
|
|
57
62
|
try {
|
|
58
63
|
// Create Azure OpenAI client
|
|
59
64
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
60
|
-
const client = createClient(model, apiKey, options);
|
|
65
|
+
const client = createClient(model, apiKey, options, sdkRequestObserver.fetch);
|
|
61
66
|
let params = buildParams(model, context, options, deploymentName);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
try {
|
|
68
|
+
const nextParams = await options?.onPayload?.(params, model);
|
|
69
|
+
if (nextParams !== undefined) {
|
|
70
|
+
params = nextParams;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
payloadCallbackFailed = true;
|
|
75
|
+
throw error;
|
|
65
76
|
}
|
|
66
77
|
const requestOptions = {
|
|
67
78
|
...(options?.signal ? { signal: options.signal } : {}),
|
|
@@ -69,8 +80,16 @@ export const streamAzureOpenAIResponses = (model, context, options) => {
|
|
|
69
80
|
...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
|
|
70
81
|
};
|
|
71
82
|
const { data: openaiStream, response } = await client.responses.create(params, requestOptions).withResponse();
|
|
72
|
-
|
|
83
|
+
sdkRequestObserver.markAccepted();
|
|
84
|
+
try {
|
|
85
|
+
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
responseCallbackFailed = true;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
73
91
|
stream.push({ type: "start", partial: output });
|
|
92
|
+
failurePhase = "stream";
|
|
74
93
|
await processResponsesStream(openaiStream, output, stream, model);
|
|
75
94
|
if (options?.signal?.aborted) {
|
|
76
95
|
throw new Error("Request was aborted");
|
|
@@ -78,6 +97,7 @@ export const streamAzureOpenAIResponses = (model, context, options) => {
|
|
|
78
97
|
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
79
98
|
throw new Error("An unknown error occurred");
|
|
80
99
|
}
|
|
100
|
+
appendResponsesSdkRetryDiagnostic(output, sdkRequestObserver.diagnosticForSuccess());
|
|
81
101
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
82
102
|
stream.end();
|
|
83
103
|
}
|
|
@@ -88,7 +108,18 @@ export const streamAzureOpenAIResponses = (model, context, options) => {
|
|
|
88
108
|
delete block.partialJson;
|
|
89
109
|
}
|
|
90
110
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
91
|
-
|
|
111
|
+
if (payloadCallbackFailed) {
|
|
112
|
+
output.errorMessage = "OpenAI Responses payload callback failed.";
|
|
113
|
+
}
|
|
114
|
+
else if (responseCallbackFailed) {
|
|
115
|
+
output.errorMessage = "OpenAI Responses response callback failed.";
|
|
116
|
+
}
|
|
117
|
+
else if (output.stopReason === "aborted") {
|
|
118
|
+
output.errorMessage = abortedResponsesFailureMessage(error);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
appendResponsesFailureDiagnostics(output, normalizeResponsesFailure(error, failurePhase), sdkRequestObserver.diagnosticForFailure(options?.maxRetries));
|
|
122
|
+
}
|
|
92
123
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
93
124
|
stream.end();
|
|
94
125
|
}
|
|
@@ -149,7 +180,7 @@ function resolveAzureConfig(model, options) {
|
|
|
149
180
|
apiVersion,
|
|
150
181
|
};
|
|
151
182
|
}
|
|
152
|
-
function createClient(model, apiKey, options) {
|
|
183
|
+
function createClient(model, apiKey, options, fetchImplementation) {
|
|
153
184
|
if (!apiKey) {
|
|
154
185
|
if (!process.env.AZURE_OPENAI_API_KEY) {
|
|
155
186
|
throw new Error("Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass it as an argument.");
|
|
@@ -167,6 +198,7 @@ function createClient(model, apiKey, options) {
|
|
|
167
198
|
dangerouslyAllowBrowser: true,
|
|
168
199
|
defaultHeaders: headers,
|
|
169
200
|
baseURL: baseUrl,
|
|
201
|
+
fetch: fetchImplementation,
|
|
170
202
|
});
|
|
171
203
|
}
|
|
172
204
|
function buildParams(model, context, options, deploymentName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azure-openai-responses.js","sourceRoot":"","sources":["../../src/providers/azure-openai-responses.ts"],"names":[],"mappings":"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","sourcesContent":["import { AzureOpenAI } from \"openai\";\nimport type { ResponseCreateParamsStreaming } from \"openai/resources/responses/responses.js\";\nimport { getEnvApiKey } from \"../env-api-keys.js\";\nimport { clampThinkingLevel } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n} from \"../types.js\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { headersToRecord } from \"../utils/headers.js\";\nimport { convertResponsesMessages, convertResponsesTools, processResponsesStream } from \"./openai-responses-shared.js\";\nimport { buildBaseOptions } from \"./simple-options.js\";\n\nconst DEFAULT_AZURE_API_VERSION = \"v1\";\nconst AZURE_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\", \"azure-openai-responses\"]);\n\nfunction parseDeploymentNameMap(value: string | undefined): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tif (!value) return map;\n\tfor (const entry of value.split(\",\")) {\n\t\tconst trimmed = entry.trim();\n\t\tif (!trimmed) continue;\n\t\tconst [modelId, deploymentName] = trimmed.split(\"=\", 2);\n\t\tif (!modelId || !deploymentName) continue;\n\t\tmap.set(modelId.trim(), deploymentName.trim());\n\t}\n\treturn map;\n}\n\nfunction resolveDeploymentName(model: Model<\"azure-openai-responses\">, options?: AzureOpenAIResponsesOptions): string {\n\tif (options?.azureDeploymentName) {\n\t\treturn options.azureDeploymentName;\n\t}\n\tconst mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(model.id);\n\treturn mappedDeployment || model.id;\n}\n\n// Azure OpenAI Responses-specific options\nexport interface AzureOpenAIResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\treasoningSummary?: \"auto\" | \"detailed\" | \"concise\" | null;\n\tazureApiVersion?: string;\n\tazureResourceName?: string;\n\tazureBaseUrl?: string;\n\tazureDeploymentName?: string;\n}\n\n/**\n * Generate function for Azure OpenAI Responses API\n */\nexport const streamAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", AzureOpenAIResponsesOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: AzureOpenAIResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t// Start async processing\n\t(async () => {\n\t\tconst deploymentName = resolveDeploymentName(model, options);\n\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"azure-openai-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\t// Create Azure OpenAI client\n\t\t\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider) || \"\";\n\t\t\tconst client = createClient(model, apiKey, options);\n\t\t\tlet params = buildParams(model, context, options, deploymentName);\n\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\tif (nextParams !== undefined) {\n\t\t\t\tparams = nextParams as ResponseCreateParamsStreaming;\n\t\t\t}\n\t\t\tconst requestOptions = {\n\t\t\t\t...(options?.signal ? { signal: options.signal } : {}),\n\t\t\t\t...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),\n\t\t\t\t...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\n\t\t\t};\n\t\t\tconst { data: openaiStream, response } = await client.responses.create(params, requestOptions).withResponse();\n\t\t\tawait options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\tawait processResponsesStream(openaiStream, output, stream, model);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(\"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimpleAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, options, apiKey);\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn streamAzureOpenAIResponses(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies AzureOpenAIResponsesOptions);\n};\n\nfunction normalizeAzureBaseUrl(baseUrl: string): string {\n\tconst trimmed = baseUrl.trim().replace(/\\/+$/, \"\");\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(`Invalid Azure OpenAI base URL: ${baseUrl}`);\n\t}\n\n\tconst isAzureHost =\n\t\turl.hostname.endsWith(\".openai.azure.com\") || url.hostname.endsWith(\".cognitiveservices.azure.com\");\n\tconst normalizedPath = url.pathname.replace(/\\/+$/, \"\");\n\n\t// Ensure Azure hosts have /openai/v1 as base path so the AzureOpenAI SDK\n\t// can append /deployments/<model>/... and ?api-version=v1 correctly.\n\tif (isAzureHost && (normalizedPath === \"\" || normalizedPath === \"/\" || normalizedPath === \"/openai\")) {\n\t\turl.pathname = \"/openai/v1\";\n\t\turl.search = \"\";\n\t}\n\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nfunction buildDefaultBaseUrl(resourceName: string): string {\n\treturn `https://${resourceName}.openai.azure.com/openai/v1`;\n}\n\nfunction resolveAzureConfig(\n\tmodel: Model<\"azure-openai-responses\">,\n\toptions?: AzureOpenAIResponsesOptions,\n): { baseUrl: string; apiVersion: string } {\n\tconst apiVersion = options?.azureApiVersion || process.env.AZURE_OPENAI_API_VERSION || DEFAULT_AZURE_API_VERSION;\n\n\tconst baseUrl = options?.azureBaseUrl?.trim() || process.env.AZURE_OPENAI_BASE_URL?.trim() || undefined;\n\tconst resourceName = options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME;\n\n\tlet resolvedBaseUrl = baseUrl;\n\n\tif (!resolvedBaseUrl && resourceName) {\n\t\tresolvedBaseUrl = buildDefaultBaseUrl(resourceName);\n\t}\n\n\tif (!resolvedBaseUrl && model.baseUrl) {\n\t\tresolvedBaseUrl = model.baseUrl;\n\t}\n\n\tif (!resolvedBaseUrl) {\n\t\tthrow new Error(\n\t\t\t\"Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_RESOURCE_NAME, or pass azureBaseUrl, azureResourceName, or model.baseUrl.\",\n\t\t);\n\t}\n\n\treturn {\n\t\tbaseUrl: normalizeAzureBaseUrl(resolvedBaseUrl),\n\t\tapiVersion,\n\t};\n}\n\nfunction createClient(model: Model<\"azure-openai-responses\">, apiKey: string, options?: AzureOpenAIResponsesOptions) {\n\tif (!apiKey) {\n\t\tif (!process.env.AZURE_OPENAI_API_KEY) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass it as an argument.\",\n\t\t\t);\n\t\t}\n\t\tapiKey = process.env.AZURE_OPENAI_API_KEY;\n\t}\n\n\tconst headers = { ...model.headers };\n\n\tif (options?.headers) {\n\t\tObject.assign(headers, options.headers);\n\t}\n\n\tconst { baseUrl, apiVersion } = resolveAzureConfig(model, options);\n\n\treturn new AzureOpenAI({\n\t\tapiKey,\n\t\tapiVersion,\n\t\tdangerouslyAllowBrowser: true,\n\t\tdefaultHeaders: headers,\n\t\tbaseURL: baseUrl,\n\t});\n}\n\nfunction buildParams(\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions: AzureOpenAIResponsesOptions | undefined,\n\tdeploymentName: string,\n) {\n\tconst messages = convertResponsesMessages(model, context, AZURE_TOOL_CALL_PROVIDERS);\n\n\tconst params: ResponseCreateParamsStreaming = {\n\t\tmodel: deploymentName,\n\t\tinput: messages,\n\t\tstream: true,\n\t\tprompt_cache_key: options?.sessionId,\n\t};\n\n\tif (options?.maxTokens) {\n\t\tparams.max_output_tokens = options?.maxTokens;\n\t}\n\n\tif (options?.temperature !== undefined) {\n\t\tparams.temperature = options?.temperature;\n\t}\n\n\tif (context.tools && context.tools.length > 0) {\n\t\tparams.tools = convertResponsesTools(context.tools);\n\t}\n\n\tif (model.reasoning) {\n\t\tif (options?.reasoningEffort || options?.reasoningSummary) {\n\t\t\tconst effort = options?.reasoningEffort\n\t\t\t\t? (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort)\n\t\t\t\t: \"medium\";\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: effort as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t\tsummary: options?.reasoningSummary || \"auto\",\n\t\t\t};\n\t\t\tparams.include = [\"reasoning.encrypted_content\"];\n\t\t} else if (model.thinkingLevelMap?.off !== null) {\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: (model.thinkingLevelMap?.off ?? \"none\") as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t};\n\t\t}\n\t}\n\n\treturn params;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"azure-openai-responses.js","sourceRoot":"","sources":["../../src/providers/azure-openai-responses.ts"],"names":[],"mappings":"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","sourcesContent":["import { AzureOpenAI } from \"openai\";\nimport type { ResponseCreateParamsStreaming } from \"openai/resources/responses/responses.js\";\nimport { getEnvApiKey } from \"../env-api-keys.js\";\nimport { clampThinkingLevel } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamFunction,\n\tStreamOptions,\n} from \"../types.js\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { headersToRecord } from \"../utils/headers.js\";\nimport {\n\tabortedResponsesFailureMessage,\n\tappendResponsesFailureDiagnostics,\n\tappendResponsesSdkRetryDiagnostic,\n\tconvertResponsesMessages,\n\tconvertResponsesTools,\n\tcreateResponsesSdkRequestObserver,\n\tnormalizeResponsesFailure,\n\tprocessResponsesStream,\n} from \"./openai-responses-shared.js\";\nimport { buildBaseOptions } from \"./simple-options.js\";\n\nconst DEFAULT_AZURE_API_VERSION = \"v1\";\nconst AZURE_TOOL_CALL_PROVIDERS = new Set([\"openai\", \"openai-codex\", \"opencode\", \"azure-openai-responses\"]);\n\nfunction parseDeploymentNameMap(value: string | undefined): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tif (!value) return map;\n\tfor (const entry of value.split(\",\")) {\n\t\tconst trimmed = entry.trim();\n\t\tif (!trimmed) continue;\n\t\tconst [modelId, deploymentName] = trimmed.split(\"=\", 2);\n\t\tif (!modelId || !deploymentName) continue;\n\t\tmap.set(modelId.trim(), deploymentName.trim());\n\t}\n\treturn map;\n}\n\nfunction resolveDeploymentName(model: Model<\"azure-openai-responses\">, options?: AzureOpenAIResponsesOptions): string {\n\tif (options?.azureDeploymentName) {\n\t\treturn options.azureDeploymentName;\n\t}\n\tconst mappedDeployment = parseDeploymentNameMap(process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP).get(model.id);\n\treturn mappedDeployment || model.id;\n}\n\n// Azure OpenAI Responses-specific options\nexport interface AzureOpenAIResponsesOptions extends StreamOptions {\n\treasoningEffort?: \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\treasoningSummary?: \"auto\" | \"detailed\" | \"concise\" | null;\n\tazureApiVersion?: string;\n\tazureResourceName?: string;\n\tazureBaseUrl?: string;\n\tazureDeploymentName?: string;\n}\n\n/**\n * Generate function for Azure OpenAI Responses API\n */\nexport const streamAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", AzureOpenAIResponsesOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: AzureOpenAIResponsesOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t// Start async processing\n\t(async () => {\n\t\tconst deploymentName = resolveDeploymentName(model, options);\n\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: \"azure-openai-responses\" as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\t// SCRAMJET-DIVERGENCE: classify failures and observe SDK attempts at the request/stream boundary (#553).\n\t\tlet failurePhase: \"request\" | \"stream\" = \"request\";\n\t\tlet payloadCallbackFailed = false;\n\t\tlet responseCallbackFailed = false;\n\t\tconst sdkRequestObserver = createResponsesSdkRequestObserver(fetch);\n\t\ttry {\n\t\t\t// Create Azure OpenAI client\n\t\t\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider) || \"\";\n\t\t\tconst client = createClient(model, apiKey, options, sdkRequestObserver.fetch);\n\t\t\tlet params = buildParams(model, context, options, deploymentName);\n\t\t\ttry {\n\t\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\t\tif (nextParams !== undefined) {\n\t\t\t\t\tparams = nextParams as ResponseCreateParamsStreaming;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tpayloadCallbackFailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst requestOptions = {\n\t\t\t\t...(options?.signal ? { signal: options.signal } : {}),\n\t\t\t\t...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),\n\t\t\t\t...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\n\t\t\t};\n\t\t\tconst { data: openaiStream, response } = await client.responses.create(params, requestOptions).withResponse();\n\t\t\tsdkRequestObserver.markAccepted();\n\t\t\ttry {\n\t\t\t\tawait options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);\n\t\t\t} catch (error) {\n\t\t\t\tresponseCallbackFailed = true;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\tfailurePhase = \"stream\";\n\t\t\tawait processResponsesStream(openaiStream, output, stream, model);\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(\"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tappendResponsesSdkRetryDiagnostic(output, sdkRequestObserver.diagnosticForSuccess());\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) {\n\t\t\t\tdelete (block as { index?: number }).index;\n\t\t\t\t// partialJson is only a streaming scratch buffer; never persist it.\n\t\t\t\tdelete (block as { partialJson?: string }).partialJson;\n\t\t\t}\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\tif (payloadCallbackFailed) {\n\t\t\t\toutput.errorMessage = \"OpenAI Responses payload callback failed.\";\n\t\t\t} else if (responseCallbackFailed) {\n\t\t\t\toutput.errorMessage = \"OpenAI Responses response callback failed.\";\n\t\t\t} else if (output.stopReason === \"aborted\") {\n\t\t\t\toutput.errorMessage = abortedResponsesFailureMessage(error);\n\t\t\t} else {\n\t\t\t\tappendResponsesFailureDiagnostics(\n\t\t\t\t\toutput,\n\t\t\t\t\tnormalizeResponsesFailure(error, failurePhase),\n\t\t\t\t\tsdkRequestObserver.diagnosticForFailure(options?.maxRetries),\n\t\t\t\t);\n\t\t\t}\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\nexport const streamSimpleAzureOpenAIResponses: StreamFunction<\"azure-openai-responses\", SimpleStreamOptions> = (\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, options, apiKey);\n\tconst clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;\n\tconst reasoningEffort = clampedReasoning === \"off\" ? undefined : clampedReasoning;\n\n\treturn streamAzureOpenAIResponses(model, context, {\n\t\t...base,\n\t\treasoningEffort,\n\t} satisfies AzureOpenAIResponsesOptions);\n};\n\nfunction normalizeAzureBaseUrl(baseUrl: string): string {\n\tconst trimmed = baseUrl.trim().replace(/\\/+$/, \"\");\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(trimmed);\n\t} catch {\n\t\tthrow new Error(`Invalid Azure OpenAI base URL: ${baseUrl}`);\n\t}\n\n\tconst isAzureHost =\n\t\turl.hostname.endsWith(\".openai.azure.com\") || url.hostname.endsWith(\".cognitiveservices.azure.com\");\n\tconst normalizedPath = url.pathname.replace(/\\/+$/, \"\");\n\n\t// Ensure Azure hosts have /openai/v1 as base path so the AzureOpenAI SDK\n\t// can append /deployments/<model>/... and ?api-version=v1 correctly.\n\tif (isAzureHost && (normalizedPath === \"\" || normalizedPath === \"/\" || normalizedPath === \"/openai\")) {\n\t\turl.pathname = \"/openai/v1\";\n\t\turl.search = \"\";\n\t}\n\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nfunction buildDefaultBaseUrl(resourceName: string): string {\n\treturn `https://${resourceName}.openai.azure.com/openai/v1`;\n}\n\nfunction resolveAzureConfig(\n\tmodel: Model<\"azure-openai-responses\">,\n\toptions?: AzureOpenAIResponsesOptions,\n): { baseUrl: string; apiVersion: string } {\n\tconst apiVersion = options?.azureApiVersion || process.env.AZURE_OPENAI_API_VERSION || DEFAULT_AZURE_API_VERSION;\n\n\tconst baseUrl = options?.azureBaseUrl?.trim() || process.env.AZURE_OPENAI_BASE_URL?.trim() || undefined;\n\tconst resourceName = options?.azureResourceName || process.env.AZURE_OPENAI_RESOURCE_NAME;\n\n\tlet resolvedBaseUrl = baseUrl;\n\n\tif (!resolvedBaseUrl && resourceName) {\n\t\tresolvedBaseUrl = buildDefaultBaseUrl(resourceName);\n\t}\n\n\tif (!resolvedBaseUrl && model.baseUrl) {\n\t\tresolvedBaseUrl = model.baseUrl;\n\t}\n\n\tif (!resolvedBaseUrl) {\n\t\tthrow new Error(\n\t\t\t\"Azure OpenAI base URL is required. Set AZURE_OPENAI_BASE_URL or AZURE_OPENAI_RESOURCE_NAME, or pass azureBaseUrl, azureResourceName, or model.baseUrl.\",\n\t\t);\n\t}\n\n\treturn {\n\t\tbaseUrl: normalizeAzureBaseUrl(resolvedBaseUrl),\n\t\tapiVersion,\n\t};\n}\n\nfunction createClient(\n\tmodel: Model<\"azure-openai-responses\">,\n\tapiKey: string,\n\toptions: AzureOpenAIResponsesOptions | undefined,\n\tfetchImplementation: typeof fetch,\n) {\n\tif (!apiKey) {\n\t\tif (!process.env.AZURE_OPENAI_API_KEY) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Azure OpenAI API key is required. Set AZURE_OPENAI_API_KEY environment variable or pass it as an argument.\",\n\t\t\t);\n\t\t}\n\t\tapiKey = process.env.AZURE_OPENAI_API_KEY;\n\t}\n\n\tconst headers = { ...model.headers };\n\n\tif (options?.headers) {\n\t\tObject.assign(headers, options.headers);\n\t}\n\n\tconst { baseUrl, apiVersion } = resolveAzureConfig(model, options);\n\n\treturn new AzureOpenAI({\n\t\tapiKey,\n\t\tapiVersion,\n\t\tdangerouslyAllowBrowser: true,\n\t\tdefaultHeaders: headers,\n\t\tbaseURL: baseUrl,\n\t\tfetch: fetchImplementation,\n\t});\n}\n\nfunction buildParams(\n\tmodel: Model<\"azure-openai-responses\">,\n\tcontext: Context,\n\toptions: AzureOpenAIResponsesOptions | undefined,\n\tdeploymentName: string,\n) {\n\tconst messages = convertResponsesMessages(model, context, AZURE_TOOL_CALL_PROVIDERS);\n\n\tconst params: ResponseCreateParamsStreaming = {\n\t\tmodel: deploymentName,\n\t\tinput: messages,\n\t\tstream: true,\n\t\tprompt_cache_key: options?.sessionId,\n\t};\n\n\tif (options?.maxTokens) {\n\t\tparams.max_output_tokens = options?.maxTokens;\n\t}\n\n\tif (options?.temperature !== undefined) {\n\t\tparams.temperature = options?.temperature;\n\t}\n\n\tif (context.tools && context.tools.length > 0) {\n\t\tparams.tools = convertResponsesTools(context.tools);\n\t}\n\n\tif (model.reasoning) {\n\t\tif (options?.reasoningEffort || options?.reasoningSummary) {\n\t\t\tconst effort = options?.reasoningEffort\n\t\t\t\t? (model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort)\n\t\t\t\t: \"medium\";\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: effort as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t\tsummary: options?.reasoningSummary || \"auto\",\n\t\t\t};\n\t\t\tparams.include = [\"reasoning.encrypted_content\"];\n\t\t} else if (model.thinkingLevelMap?.off !== null) {\n\t\t\tparams.reasoning = {\n\t\t\t\teffort: (model.thinkingLevelMap?.off ?? \"none\") as NonNullable<typeof params.reasoning>[\"effort\"],\n\t\t\t};\n\t\t}\n\t}\n\n\treturn params;\n}\n"]}
|
|
@@ -12,7 +12,76 @@ export interface ConvertResponsesMessagesOptions {
|
|
|
12
12
|
export interface ConvertResponsesToolsOptions {
|
|
13
13
|
strict?: boolean | null;
|
|
14
14
|
}
|
|
15
|
+
export type ResponsesFailureCategory = "rate_limit" | "quota_exhausted" | "overloaded" | "server" | "timeout" | "transport" | "context_overflow" | "authentication" | "permission" | "invalid_request" | "not_found" | "conflict" | "content_rejection" | "provider_error" | "malformed_event" | "unknown";
|
|
16
|
+
export type ResponsesRetryDisposition = "transient" | "non_transient" | "unknown";
|
|
17
|
+
type ResponsesFailureKind = "http" | "provider_event" | "transport" | "malformed_event";
|
|
18
|
+
type ResponsesFailureDetailSource = "provider_code" | "provider_type" | "http_status" | "message_category" | "none";
|
|
19
|
+
type ResponsesProviderCode = "rate_limit_exceeded" | "insufficient_quota" | "billing_hard_limit_reached" | "overloaded_error" | "server_error" | "timeout" | "context_length_exceeded" | "authentication_error" | "permission_denied" | "invalid_request_error" | "not_found" | "conflict" | "content_filter" | "content_policy_violation";
|
|
20
|
+
export interface ResponsesProviderFailureV1 {
|
|
21
|
+
schemaVersion: 1;
|
|
22
|
+
layer: "openai_responses";
|
|
23
|
+
phase: "request" | "stream";
|
|
24
|
+
kind: ResponsesFailureKind;
|
|
25
|
+
category: ResponsesFailureCategory;
|
|
26
|
+
retryDisposition: ResponsesRetryDisposition;
|
|
27
|
+
detailSource: ResponsesFailureDetailSource;
|
|
28
|
+
httpStatus?: number;
|
|
29
|
+
providerCode?: ResponsesProviderCode;
|
|
30
|
+
}
|
|
31
|
+
export type ResponsesProviderFailureValidation = {
|
|
32
|
+
status: "absent";
|
|
33
|
+
} | {
|
|
34
|
+
status: "valid";
|
|
35
|
+
category: ResponsesFailureCategory;
|
|
36
|
+
retryDisposition: ResponsesRetryDisposition;
|
|
37
|
+
} | {
|
|
38
|
+
status: "malformed";
|
|
39
|
+
} | {
|
|
40
|
+
status: "duplicate";
|
|
41
|
+
};
|
|
42
|
+
export interface SafeResponsesFailure {
|
|
43
|
+
message: string;
|
|
44
|
+
diagnostic: ResponsesProviderFailureV1;
|
|
45
|
+
}
|
|
46
|
+
type ResponsesSdkRetryAttempt = {
|
|
47
|
+
ordinal: number;
|
|
48
|
+
result: "response";
|
|
49
|
+
status?: number;
|
|
50
|
+
} | {
|
|
51
|
+
ordinal: number;
|
|
52
|
+
result: "transport";
|
|
53
|
+
category: "timeout" | "connection" | "other";
|
|
54
|
+
};
|
|
55
|
+
type ResponsesSdkRetryReason = "accepted_after_retry" | "configured_limit_reached" | "terminal_after_retry" | "configured_zero" | "non_retryable_request" | "stream_already_accepted" | "insufficient_evidence";
|
|
56
|
+
export interface GatewayObservabilityV1 {
|
|
57
|
+
schemaVersion: 1;
|
|
58
|
+
layer: "gateway_service_internal";
|
|
59
|
+
outcome: "unobservable";
|
|
60
|
+
reason: "no_structured_evidence";
|
|
61
|
+
}
|
|
62
|
+
export interface ResponsesSdkRetryV1 {
|
|
63
|
+
schemaVersion: 1;
|
|
64
|
+
layer: "openai_sdk_request";
|
|
65
|
+
outcome: "recovered" | "exhausted" | "not_attempted";
|
|
66
|
+
reason: ResponsesSdkRetryReason;
|
|
67
|
+
observedAttemptCount: number;
|
|
68
|
+
attempts: ResponsesSdkRetryAttempt[];
|
|
69
|
+
truncated: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface ResponsesSdkRequestObserver {
|
|
72
|
+
fetch: typeof fetch;
|
|
73
|
+
markAccepted(): void;
|
|
74
|
+
diagnosticForSuccess(): ResponsesSdkRetryV1 | undefined;
|
|
75
|
+
diagnosticForFailure(maxRetries: number | undefined): ResponsesSdkRetryV1;
|
|
76
|
+
}
|
|
77
|
+
export declare function abortedResponsesFailureMessage(error: unknown): string;
|
|
78
|
+
export declare function normalizeResponsesFailure(value: unknown, phase: "request" | "stream"): SafeResponsesFailure;
|
|
79
|
+
export declare function createResponsesSdkRequestObserver(fetchImplementation: typeof fetch): ResponsesSdkRequestObserver;
|
|
80
|
+
export declare function appendResponsesSdkRetryDiagnostic(output: AssistantMessage, diagnostic: ResponsesSdkRetryV1 | undefined): void;
|
|
81
|
+
export declare function appendResponsesFailureDiagnostics(output: AssistantMessage, failure: SafeResponsesFailure, sdkRetry?: ResponsesSdkRetryV1): void;
|
|
82
|
+
export declare function validateResponsesProviderFailure(diagnostics: unknown): ResponsesProviderFailureValidation;
|
|
15
83
|
export declare function convertResponsesMessages<TApi extends Api>(model: Model<TApi>, context: Context, allowedToolCallProviders: ReadonlySet<string>, options?: ConvertResponsesMessagesOptions): ResponseInput;
|
|
16
84
|
export declare function convertResponsesTools(tools: Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[];
|
|
17
85
|
export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: OpenAIResponsesStreamOptions): Promise<void>;
|
|
86
|
+
export {};
|
|
18
87
|
//# sourceMappingURL=openai-responses-shared.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-responses-shared.d.ts","sourceRoot":"","sources":["../../src/providers/openai-responses-shared.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,IAAI,IAAI,UAAU,EAClB,6BAA6B,EAG7B,aAAa,EAMb,mBAAmB,EACnB,MAAM,yCAAyC,CAAC;AAEjD,OAAO,KAAK,EACX,GAAG,EACH,gBAAgB,EAChB,OAAO,EAEP,KAAK,EAKL,IAAI,EAEJ,KAAK,EACL,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAqC5E,MAAM,WAAW,4BAA4B;IAC5C,WAAW,CAAC,EAAE,6BAA6B,CAAC,cAAc,CAAC,CAAC;IAC5D,kBAAkB,CAAC,EAAE,CACpB,mBAAmB,EAAE,6BAA6B,CAAC,cAAc,CAAC,GAAG,SAAS,EAC9E,kBAAkB,EAAE,6BAA6B,CAAC,cAAc,CAAC,GAAG,SAAS,KACzE,6BAA6B,CAAC,cAAc,CAAC,GAAG,SAAS,CAAC;IAC/D,uBAAuB,CAAC,EAAE,CACzB,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,6BAA6B,CAAC,cAAc,CAAC,GAAG,SAAS,KAClE,IAAI,CAAC;CACV;AAED,MAAM,WAAW,+BAA+B;IAC/C,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,4BAA4B;IAC5C,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CACxB;AAMD,wBAAgB,wBAAwB,CAAC,IAAI,SAAS,GAAG,EACxD,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,OAAO,EAAE,OAAO,EAChB,wBAAwB,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7C,OAAO,CAAC,EAAE,+BAA+B,GACvC,aAAa,CAwKf;AAMD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,EAAE,4BAA4B,GAAG,UAAU,EAAE,CASzG;AAMD,wBAAsB,sBAAsB,CAAC,IAAI,SAAS,GAAG,EAC5D,YAAY,EAAE,aAAa,CAAC,mBAAmB,CAAC,EAChD,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,2BAA2B,EACnC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,OAAO,CAAC,EAAE,4BAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAiPf","sourcesContent":["import type OpenAI from \"openai\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseFunctionCallOutputItemList,\n\tResponseFunctionToolCall,\n\tResponseInput,\n\tResponseInputContent,\n\tResponseInputImage,\n\tResponseInputText,\n\tResponseOutputMessage,\n\tResponseReasoningItem,\n\tResponseStreamEvent,\n} from \"openai/resources/responses/responses.js\";\nimport { calculateCost } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tImageContent,\n\tModel,\n\tStopReason,\n\tTextContent,\n\tTextSignatureV1,\n\tThinkingContent,\n\tTool,\n\tToolCall,\n\tUsage,\n} from \"../types.js\";\nimport type { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { shortHash } from \"../utils/hash.js\";\nimport { parseStreamingJson } from \"../utils/json-parse.js\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.js\";\nimport { flattenSystemPrompt } from \"../utils/system-prompt.js\";\nimport { transformMessages } from \"./transform-messages.js\";\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\nfunction encodeTextSignatureV1(id: string, phase?: TextSignatureV1[\"phase\"]): string {\n\tconst payload: TextSignatureV1 = { v: 1, id };\n\tif (phase) payload.phase = phase;\n\treturn JSON.stringify(payload);\n}\n\nfunction parseTextSignature(\n\tsignature: string | undefined,\n): { id: string; phase?: TextSignatureV1[\"phase\"] } | undefined {\n\tif (!signature) return undefined;\n\tif (signature.startsWith(\"{\")) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(signature) as Partial<TextSignatureV1>;\n\t\t\tif (parsed.v === 1 && typeof parsed.id === \"string\") {\n\t\t\t\tif (parsed.phase === \"commentary\" || parsed.phase === \"final_answer\") {\n\t\t\t\t\treturn { id: parsed.id, phase: parsed.phase };\n\t\t\t\t}\n\t\t\t\treturn { id: parsed.id };\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to legacy plain-string handling.\n\t\t}\n\t}\n\treturn { id: signature };\n}\n\nexport interface OpenAIResponsesStreamOptions {\n\tserviceTier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\tresolveServiceTier?: (\n\t\tresponseServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\t\trequestServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\t) => ResponseCreateParamsStreaming[\"service_tier\"] | undefined;\n\tapplyServiceTierPricing?: (\n\t\tusage: Usage,\n\t\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\t) => void;\n}\n\nexport interface ConvertResponsesMessagesOptions {\n\tincludeSystemPrompt?: boolean;\n}\n\nexport interface ConvertResponsesToolsOptions {\n\tstrict?: boolean | null;\n}\n\n// =============================================================================\n// Message conversion\n// =============================================================================\n\nexport function convertResponsesMessages<TApi extends Api>(\n\tmodel: Model<TApi>,\n\tcontext: Context,\n\tallowedToolCallProviders: ReadonlySet<string>,\n\toptions?: ConvertResponsesMessagesOptions,\n): ResponseInput {\n\tconst messages: ResponseInput = [];\n\n\tconst normalizeIdPart = (part: string): string => {\n\t\tconst sanitized = part.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\t\tconst normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;\n\t\treturn normalized.replace(/_+$/, \"\");\n\t};\n\n\tconst buildForeignResponsesItemId = (itemId: string): string => {\n\t\tconst normalized = `fc_${shortHash(itemId)}`;\n\t\treturn normalized.length > 64 ? normalized.slice(0, 64) : normalized;\n\t};\n\n\tconst normalizeToolCallId = (id: string, _targetModel: Model<TApi>, source: AssistantMessage): string => {\n\t\tif (!allowedToolCallProviders.has(model.provider)) return normalizeIdPart(id);\n\t\tif (!id.includes(\"|\")) return normalizeIdPart(id);\n\t\tconst [callId, itemId] = id.split(\"|\");\n\t\tconst normalizedCallId = normalizeIdPart(callId);\n\t\tconst isForeignToolCall = source.provider !== model.provider || source.api !== model.api;\n\t\tlet normalizedItemId = isForeignToolCall ? buildForeignResponsesItemId(itemId) : normalizeIdPart(itemId);\n\t\t// OpenAI Responses API requires item id to start with \"fc\"\n\t\tif (!normalizedItemId.startsWith(\"fc_\")) {\n\t\t\tnormalizedItemId = normalizeIdPart(`fc_${normalizedItemId}`);\n\t\t}\n\t\treturn `${normalizedCallId}|${normalizedItemId}`;\n\t};\n\n\tconst transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);\n\n\tconst includeSystemPrompt = options?.includeSystemPrompt ?? true;\n\tconst systemPrompt = flattenSystemPrompt(context.systemPrompt);\n\tif (includeSystemPrompt && systemPrompt) {\n\t\tconst role = model.reasoning ? \"developer\" : \"system\";\n\t\tmessages.push({\n\t\t\trole,\n\t\t\tcontent: sanitizeSurrogates(systemPrompt),\n\t\t});\n\t}\n\n\tlet msgIndex = 0;\n\tfor (const msg of transformedMessages) {\n\t\tif (msg.role === \"user\") {\n\t\t\tif (typeof msg.content === \"string\") {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"input_text\", text: sanitizeSurrogates(msg.content) }],\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst content: ResponseInputContent[] = msg.content.map((item): ResponseInputContent => {\n\t\t\t\t\tif (item.type === \"text\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"input_text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(item.text),\n\t\t\t\t\t\t} satisfies ResponseInputText;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"input_image\",\n\t\t\t\t\t\tdetail: \"auto\",\n\t\t\t\t\t\timage_url: `data:${item.mimeType};base64,${item.data}`,\n\t\t\t\t\t} satisfies ResponseInputImage;\n\t\t\t\t});\n\t\t\t\tif (content.length === 0) continue;\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst output: ResponseInput = [];\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isDifferentModel =\n\t\t\t\tassistantMsg.model !== model.id &&\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api;\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\tif (block.thinkingSignature) {\n\t\t\t\t\t\tconst reasoningItem = JSON.parse(block.thinkingSignature) as ResponseReasoningItem;\n\t\t\t\t\t\toutput.push(reasoningItem);\n\t\t\t\t\t}\n\t\t\t\t} else if (block.type === \"text\") {\n\t\t\t\t\tconst textBlock = block as TextContent;\n\t\t\t\t\tconst parsedSignature = parseTextSignature(textBlock.textSignature);\n\t\t\t\t\t// OpenAI requires id to be max 64 characters\n\t\t\t\t\tlet msgId = parsedSignature?.id;\n\t\t\t\t\tif (!msgId) {\n\t\t\t\t\t\tmsgId = `msg_${msgIndex}`;\n\t\t\t\t\t} else if (msgId.length > 64) {\n\t\t\t\t\t\tmsgId = `msg_${shortHash(msgId)}`;\n\t\t\t\t\t}\n\t\t\t\t\toutput.push({\n\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\tcontent: [{ type: \"output_text\", text: sanitizeSurrogates(textBlock.text), annotations: [] }],\n\t\t\t\t\t\tstatus: \"completed\",\n\t\t\t\t\t\tid: msgId,\n\t\t\t\t\t\tphase: parsedSignature?.phase,\n\t\t\t\t\t} satisfies ResponseOutputMessage);\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tconst [callId, itemIdRaw] = toolCall.id.split(\"|\");\n\t\t\t\t\tlet itemId: string | undefined = itemIdRaw;\n\n\t\t\t\t\t// For different-model messages, set id to undefined to avoid pairing validation.\n\t\t\t\t\t// OpenAI tracks which fc_xxx IDs were paired with rs_xxx reasoning items.\n\t\t\t\t\t// By omitting the id, we avoid triggering that validation (like cross-provider does).\n\t\t\t\t\tif (isDifferentModel && itemId?.startsWith(\"fc_\")) {\n\t\t\t\t\t\titemId = undefined;\n\t\t\t\t\t}\n\n\t\t\t\t\toutput.push({\n\t\t\t\t\t\ttype: \"function_call\",\n\t\t\t\t\t\tid: itemId,\n\t\t\t\t\t\tcall_id: callId,\n\t\t\t\t\t\tname: toolCall.name,\n\t\t\t\t\t\targuments: JSON.stringify(toolCall.arguments),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (output.length === 0) continue;\n\t\t\tmessages.push(...output);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\tconst textResult = msg.content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst hasImages = msg.content.some((c): c is ImageContent => c.type === \"image\");\n\t\t\tconst hasText = textResult.length > 0;\n\t\t\tconst [callId] = msg.toolCallId.split(\"|\");\n\n\t\t\tlet output: string | ResponseFunctionCallOutputItemList;\n\t\t\tif (hasImages && model.input.includes(\"image\")) {\n\t\t\t\tconst contentParts: ResponseFunctionCallOutputItemList = [];\n\n\t\t\t\tif (hasText) {\n\t\t\t\t\tcontentParts.push({\n\t\t\t\t\t\ttype: \"input_text\",\n\t\t\t\t\t\ttext: sanitizeSurrogates(textResult),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tfor (const block of msg.content) {\n\t\t\t\t\tif (block.type === \"image\") {\n\t\t\t\t\t\tcontentParts.push({\n\t\t\t\t\t\t\ttype: \"input_image\",\n\t\t\t\t\t\t\tdetail: \"auto\",\n\t\t\t\t\t\t\timage_url: `data:${block.mimeType};base64,${block.data}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\toutput = contentParts;\n\t\t\t} else {\n\t\t\t\toutput = sanitizeSurrogates(hasText ? textResult : \"(see attached image)\");\n\t\t\t}\n\n\t\t\tmessages.push({\n\t\t\t\ttype: \"function_call_output\",\n\t\t\t\tcall_id: callId,\n\t\t\t\toutput,\n\t\t\t});\n\t\t}\n\t\tmsgIndex++;\n\t}\n\n\treturn messages;\n}\n\n// =============================================================================\n// Tool conversion\n// =============================================================================\n\nexport function convertResponsesTools(tools: Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[] {\n\tconst strict = options?.strict === undefined ? false : options.strict;\n\treturn tools.map((tool) => ({\n\t\ttype: \"function\",\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tparameters: tool.parameters as any, // TypeBox already generates JSON Schema\n\t\tstrict,\n\t}));\n}\n\n// =============================================================================\n// Stream processing\n// =============================================================================\n\nexport async function processResponsesStream<TApi extends Api>(\n\topenaiStream: AsyncIterable<ResponseStreamEvent>,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<TApi>,\n\toptions?: OpenAIResponsesStreamOptions,\n): Promise<void> {\n\tlet currentItem: ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | null = null;\n\tlet currentBlock: ThinkingContent | TextContent | (ToolCall & { partialJson: string }) | null = null;\n\tconst blocks = output.content;\n\tconst blockIndex = () => blocks.length - 1;\n\n\tfor await (const event of openaiStream) {\n\t\tif (event.type === \"response.created\") {\n\t\t\toutput.responseId = event.response.id;\n\t\t} else if (event.type === \"response.output_item.added\") {\n\t\t\tconst item = event.item;\n\t\t\tif (item.type === \"reasoning\") {\n\t\t\t\tcurrentItem = item;\n\t\t\t\tcurrentBlock = { type: \"thinking\", thinking: \"\" };\n\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t} else if (item.type === \"message\") {\n\t\t\t\tcurrentItem = item;\n\t\t\t\tcurrentBlock = { type: \"text\", text: \"\" };\n\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\tstream.push({ type: \"text_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t} else if (item.type === \"function_call\") {\n\t\t\t\tcurrentItem = item;\n\t\t\t\tcurrentBlock = {\n\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\tid: `${item.call_id}|${item.id}`,\n\t\t\t\t\tname: item.name,\n\t\t\t\t\targuments: {},\n\t\t\t\t\tpartialJson: item.arguments || \"\",\n\t\t\t\t};\n\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_summary_part.added\") {\n\t\t\tif (currentItem && currentItem.type === \"reasoning\") {\n\t\t\t\tcurrentItem.summary = currentItem.summary || [];\n\t\t\t\tcurrentItem.summary.push(event.part);\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_summary_text.delta\") {\n\t\t\tif (currentItem?.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tcurrentItem.summary = currentItem.summary || [];\n\t\t\t\tconst lastPart = currentItem.summary[currentItem.summary.length - 1];\n\t\t\t\tif (lastPart) {\n\t\t\t\t\tcurrentBlock.thinking += event.delta;\n\t\t\t\t\tlastPart.text += event.delta;\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_summary_part.done\") {\n\t\t\tif (currentItem?.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tcurrentItem.summary = currentItem.summary || [];\n\t\t\t\tconst lastPart = currentItem.summary[currentItem.summary.length - 1];\n\t\t\t\tif (lastPart) {\n\t\t\t\t\tcurrentBlock.thinking += \"\\n\\n\";\n\t\t\t\t\tlastPart.text += \"\\n\\n\";\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: \"\\n\\n\",\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_text.delta\") {\n\t\t\tif (currentItem?.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tcurrentBlock.thinking += event.delta;\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (event.type === \"response.content_part.added\") {\n\t\t\tif (currentItem?.type === \"message\") {\n\t\t\t\tcurrentItem.content = currentItem.content || [];\n\t\t\t\t// Filter out ReasoningText, only accept output_text and refusal\n\t\t\t\tif (event.part.type === \"output_text\" || event.part.type === \"refusal\") {\n\t\t\t\t\tcurrentItem.content.push(event.part);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.output_text.delta\") {\n\t\t\tif (currentItem?.type === \"message\" && currentBlock?.type === \"text\") {\n\t\t\t\tif (!currentItem.content || currentItem.content.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst lastPart = currentItem.content[currentItem.content.length - 1];\n\t\t\t\tif (lastPart?.type === \"output_text\") {\n\t\t\t\t\tcurrentBlock.text += event.delta;\n\t\t\t\t\tlastPart.text += event.delta;\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.refusal.delta\") {\n\t\t\tif (currentItem?.type === \"message\" && currentBlock?.type === \"text\") {\n\t\t\t\tif (!currentItem.content || currentItem.content.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst lastPart = currentItem.content[currentItem.content.length - 1];\n\t\t\t\tif (lastPart?.type === \"refusal\") {\n\t\t\t\t\tcurrentBlock.text += event.delta;\n\t\t\t\t\tlastPart.refusal += event.delta;\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.function_call_arguments.delta\") {\n\t\t\tif (currentItem?.type === \"function_call\" && currentBlock?.type === \"toolCall\") {\n\t\t\t\tcurrentBlock.partialJson += event.delta;\n\t\t\t\tcurrentBlock.arguments = parseStreamingJson(currentBlock.partialJson);\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (event.type === \"response.function_call_arguments.done\") {\n\t\t\tif (currentItem?.type === \"function_call\" && currentBlock?.type === \"toolCall\") {\n\t\t\t\tconst previousPartialJson = currentBlock.partialJson;\n\t\t\t\tcurrentBlock.partialJson = event.arguments;\n\t\t\t\tcurrentBlock.arguments = parseStreamingJson(currentBlock.partialJson);\n\n\t\t\t\tif (event.arguments.startsWith(previousPartialJson)) {\n\t\t\t\t\tconst delta = event.arguments.slice(previousPartialJson.length);\n\t\t\t\t\tif (delta.length > 0) {\n\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\tdelta,\n\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.output_item.done\") {\n\t\t\tconst item = event.item;\n\n\t\t\tif (item.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tconst summaryText = item.summary?.map((s) => s.text).join(\"\\n\\n\") || \"\";\n\t\t\t\tconst contentText = item.content?.map((c) => c.text).join(\"\\n\\n\") || \"\";\n\t\t\t\tcurrentBlock.thinking = summaryText || contentText || currentBlock.thinking;\n\t\t\t\tcurrentBlock.thinkingSignature = JSON.stringify(item);\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tcontent: currentBlock.thinking,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t\tcurrentBlock = null;\n\t\t\t} else if (item.type === \"message\" && currentBlock?.type === \"text\") {\n\t\t\t\tcurrentBlock.text = item.content.map((c) => (c.type === \"output_text\" ? c.text : c.refusal)).join(\"\");\n\t\t\t\tcurrentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tcontent: currentBlock.text,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t\tcurrentBlock = null;\n\t\t\t} else if (item.type === \"function_call\") {\n\t\t\t\tconst args =\n\t\t\t\t\tcurrentBlock?.type === \"toolCall\" && currentBlock.partialJson\n\t\t\t\t\t\t? parseStreamingJson(currentBlock.partialJson)\n\t\t\t\t\t\t: parseStreamingJson(item.arguments || \"{}\");\n\n\t\t\t\tlet toolCall: ToolCall;\n\t\t\t\tif (currentBlock?.type === \"toolCall\") {\n\t\t\t\t\t// Finalize in-place and strip the scratch buffer so replay only\n\t\t\t\t\t// carries parsed arguments.\n\t\t\t\t\tcurrentBlock.arguments = args;\n\t\t\t\t\tdelete (currentBlock as { partialJson?: string }).partialJson;\n\t\t\t\t\ttoolCall = currentBlock;\n\t\t\t\t} else {\n\t\t\t\t\ttoolCall = {\n\t\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\t\tid: `${item.call_id}|${item.id}`,\n\t\t\t\t\t\tname: item.name,\n\t\t\t\t\t\targuments: args,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tcurrentBlock = null;\n\t\t\t\tstream.push({ type: \"toolcall_end\", contentIndex: blockIndex(), toolCall, partial: output });\n\t\t\t}\n\t\t} else if (event.type === \"response.completed\") {\n\t\t\tconst response = event.response;\n\t\t\tif (response?.id) {\n\t\t\t\toutput.responseId = response.id;\n\t\t\t}\n\t\t\tif (response?.usage) {\n\t\t\t\tconst cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;\n\t\t\t\toutput.usage = {\n\t\t\t\t\t// OpenAI includes cached tokens in input_tokens, so subtract to get non-cached input\n\t\t\t\t\tinput: (response.usage.input_tokens || 0) - cachedTokens,\n\t\t\t\t\toutput: response.usage.output_tokens || 0,\n\t\t\t\t\tcacheRead: cachedTokens,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: response.usage.total_tokens || 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t};\n\t\t\t}\n\t\t\tcalculateCost(model, output.usage);\n\t\t\tif (options?.applyServiceTierPricing) {\n\t\t\t\tconst serviceTier = options.resolveServiceTier\n\t\t\t\t\t? options.resolveServiceTier(response?.service_tier, options.serviceTier)\n\t\t\t\t\t: (response?.service_tier ?? options.serviceTier);\n\t\t\t\toptions.applyServiceTierPricing(output.usage, serviceTier);\n\t\t\t}\n\t\t\t// Map status to stop reason\n\t\t\toutput.stopReason = mapStopReason(response?.status);\n\t\t\tif (output.content.some((b) => b.type === \"toolCall\") && output.stopReason === \"stop\") {\n\t\t\t\toutput.stopReason = \"toolUse\";\n\t\t\t}\n\t\t} else if (event.type === \"error\") {\n\t\t\tthrow new Error(`Error Code ${event.code}: ${event.message}` || \"Unknown error\");\n\t\t} else if (event.type === \"response.failed\") {\n\t\t\tconst error = event.response?.error;\n\t\t\tconst details = event.response?.incomplete_details;\n\t\t\tconst msg = error\n\t\t\t\t? `${error.code || \"unknown\"}: ${error.message || \"no message\"}`\n\t\t\t\t: details?.reason\n\t\t\t\t\t? `incomplete: ${details.reason}`\n\t\t\t\t\t: \"Unknown error (no error details in response)\";\n\t\t\tthrow new Error(msg);\n\t\t}\n\t}\n}\n\nfunction mapStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason {\n\tif (!status) return \"stop\";\n\tswitch (status) {\n\t\tcase \"completed\":\n\t\t\treturn \"stop\";\n\t\tcase \"incomplete\":\n\t\t\treturn \"length\";\n\t\tcase \"failed\":\n\t\tcase \"cancelled\":\n\t\t\treturn \"error\";\n\t\t// These two are wonky ...\n\t\tcase \"in_progress\":\n\t\tcase \"queued\":\n\t\t\treturn \"stop\";\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tthrow new Error(`Unhandled stop reason: ${_exhaustive}`);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"openai-responses-shared.d.ts","sourceRoot":"","sources":["../../src/providers/openai-responses-shared.ts"],"names":[],"mappings":"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","sourcesContent":["import type OpenAI from \"openai\";\nimport type {\n\tTool as OpenAITool,\n\tResponseCreateParamsStreaming,\n\tResponseFunctionCallOutputItemList,\n\tResponseFunctionToolCall,\n\tResponseInput,\n\tResponseInputContent,\n\tResponseInputImage,\n\tResponseInputText,\n\tResponseOutputMessage,\n\tResponseReasoningItem,\n\tResponseStreamEvent,\n} from \"openai/resources/responses/responses.js\";\nimport { calculateCost } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tImageContent,\n\tModel,\n\tStopReason,\n\tTextContent,\n\tTextSignatureV1,\n\tThinkingContent,\n\tTool,\n\tToolCall,\n\tUsage,\n} from \"../types.js\";\nimport type { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { shortHash } from \"../utils/hash.js\";\nimport { parseStreamingJson } from \"../utils/json-parse.js\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.js\";\nimport { flattenSystemPrompt } from \"../utils/system-prompt.js\";\nimport { transformMessages } from \"./transform-messages.js\";\n\n// =============================================================================\n// Utilities\n// =============================================================================\n\nfunction encodeTextSignatureV1(id: string, phase?: TextSignatureV1[\"phase\"]): string {\n\tconst payload: TextSignatureV1 = { v: 1, id };\n\tif (phase) payload.phase = phase;\n\treturn JSON.stringify(payload);\n}\n\nfunction parseTextSignature(\n\tsignature: string | undefined,\n): { id: string; phase?: TextSignatureV1[\"phase\"] } | undefined {\n\tif (!signature) return undefined;\n\tif (signature.startsWith(\"{\")) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(signature) as Partial<TextSignatureV1>;\n\t\t\tif (parsed.v === 1 && typeof parsed.id === \"string\") {\n\t\t\t\tif (parsed.phase === \"commentary\" || parsed.phase === \"final_answer\") {\n\t\t\t\t\treturn { id: parsed.id, phase: parsed.phase };\n\t\t\t\t}\n\t\t\t\treturn { id: parsed.id };\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fall through to legacy plain-string handling.\n\t\t}\n\t}\n\treturn { id: signature };\n}\n\nexport interface OpenAIResponsesStreamOptions {\n\tserviceTier?: ResponseCreateParamsStreaming[\"service_tier\"];\n\tresolveServiceTier?: (\n\t\tresponseServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\t\trequestServiceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\t) => ResponseCreateParamsStreaming[\"service_tier\"] | undefined;\n\tapplyServiceTierPricing?: (\n\t\tusage: Usage,\n\t\tserviceTier: ResponseCreateParamsStreaming[\"service_tier\"] | undefined,\n\t) => void;\n}\n\nexport interface ConvertResponsesMessagesOptions {\n\tincludeSystemPrompt?: boolean;\n}\n\nexport interface ConvertResponsesToolsOptions {\n\tstrict?: boolean | null;\n}\n\n// SCRAMJET-DIVERGENCE: Shared Responses failures use closed privacy-safe diagnostics and fixed text (#553).\nexport type ResponsesFailureCategory =\n\t| \"rate_limit\"\n\t| \"quota_exhausted\"\n\t| \"overloaded\"\n\t| \"server\"\n\t| \"timeout\"\n\t| \"transport\"\n\t| \"context_overflow\"\n\t| \"authentication\"\n\t| \"permission\"\n\t| \"invalid_request\"\n\t| \"not_found\"\n\t| \"conflict\"\n\t| \"content_rejection\"\n\t| \"provider_error\"\n\t| \"malformed_event\"\n\t| \"unknown\";\n\nexport type ResponsesRetryDisposition = \"transient\" | \"non_transient\" | \"unknown\";\n\ntype ResponsesFailureKind = \"http\" | \"provider_event\" | \"transport\" | \"malformed_event\";\ntype ResponsesFailureDetailSource = \"provider_code\" | \"provider_type\" | \"http_status\" | \"message_category\" | \"none\";\n\ntype ResponsesProviderCode =\n\t| \"rate_limit_exceeded\"\n\t| \"insufficient_quota\"\n\t| \"billing_hard_limit_reached\"\n\t| \"overloaded_error\"\n\t| \"server_error\"\n\t| \"timeout\"\n\t| \"context_length_exceeded\"\n\t| \"authentication_error\"\n\t| \"permission_denied\"\n\t| \"invalid_request_error\"\n\t| \"not_found\"\n\t| \"conflict\"\n\t| \"content_filter\"\n\t| \"content_policy_violation\";\n\nexport interface ResponsesProviderFailureV1 {\n\tschemaVersion: 1;\n\tlayer: \"openai_responses\";\n\tphase: \"request\" | \"stream\";\n\tkind: ResponsesFailureKind;\n\tcategory: ResponsesFailureCategory;\n\tretryDisposition: ResponsesRetryDisposition;\n\tdetailSource: ResponsesFailureDetailSource;\n\thttpStatus?: number;\n\tproviderCode?: ResponsesProviderCode;\n}\n\nexport type ResponsesProviderFailureValidation =\n\t| { status: \"absent\" }\n\t| {\n\t\t\tstatus: \"valid\";\n\t\t\tcategory: ResponsesFailureCategory;\n\t\t\tretryDisposition: ResponsesRetryDisposition;\n\t }\n\t| { status: \"malformed\" }\n\t| { status: \"duplicate\" };\n\nconst PROVIDER_CODE_CATEGORIES = {\n\trate_limit_exceeded: \"rate_limit\",\n\tinsufficient_quota: \"quota_exhausted\",\n\tbilling_hard_limit_reached: \"quota_exhausted\",\n\toverloaded_error: \"overloaded\",\n\tserver_error: \"server\",\n\ttimeout: \"timeout\",\n\tcontext_length_exceeded: \"context_overflow\",\n\tauthentication_error: \"authentication\",\n\tpermission_denied: \"permission\",\n\tinvalid_request_error: \"invalid_request\",\n\tnot_found: \"not_found\",\n\tconflict: \"conflict\",\n\tcontent_filter: \"content_rejection\",\n\tcontent_policy_violation: \"content_rejection\",\n} as const satisfies Record<ResponsesProviderCode, ResponsesFailureCategory>;\n\nconst CATEGORY_DISPOSITIONS: Record<ResponsesFailureCategory, ResponsesRetryDisposition> = {\n\trate_limit: \"transient\",\n\tquota_exhausted: \"non_transient\",\n\toverloaded: \"transient\",\n\tserver: \"transient\",\n\ttimeout: \"transient\",\n\ttransport: \"transient\",\n\tcontext_overflow: \"non_transient\",\n\tauthentication: \"non_transient\",\n\tpermission: \"non_transient\",\n\tinvalid_request: \"non_transient\",\n\tnot_found: \"non_transient\",\n\tconflict: \"non_transient\",\n\tcontent_rejection: \"non_transient\",\n\tprovider_error: \"unknown\",\n\tmalformed_event: \"unknown\",\n\tunknown: \"unknown\",\n};\n\nconst CATEGORY_MESSAGES: Record<ResponsesFailureCategory, string> = {\n\trate_limit: \"OpenAI Responses request was rate limited.\",\n\tquota_exhausted: \"OpenAI Responses quota was exhausted.\",\n\toverloaded: \"OpenAI Responses service was overloaded.\",\n\tserver: \"OpenAI Responses service returned a server error.\",\n\ttimeout: \"OpenAI Responses request timed out.\",\n\ttransport: \"OpenAI Responses request failed during transport.\",\n\tcontext_overflow: \"OpenAI Responses input exceeds the context window.\",\n\tauthentication: \"OpenAI Responses authentication failed.\",\n\tpermission: \"OpenAI Responses request was not permitted.\",\n\tinvalid_request: \"OpenAI Responses request was invalid.\",\n\tnot_found: \"OpenAI Responses resource was not found.\",\n\tconflict: \"OpenAI Responses request conflicted with the current resource state.\",\n\tcontent_rejection: \"OpenAI Responses rejected the content.\",\n\tprovider_error: \"OpenAI Responses returned a provider error.\",\n\tmalformed_event: \"OpenAI Responses returned a malformed error event.\",\n\tunknown: \"OpenAI Responses request failed without recognized details.\",\n};\n\nconst PROVIDER_MESSAGE_MAX_LENGTH = 4096;\nconst PROVIDER_DETAIL_MAX_LENGTH = 200;\nconst PROVIDER_DETAIL_REDACTIONS: RegExp[] = [\n\t/\\b[a-z][a-z0-9+.-]*:\\/\\/[^\\s'\"`<>]+/gi,\n\t/[^\\s@'\"`<>]+@[^\\s@'\"`<>]+\\.[a-z]{2,}/gi,\n\t/\\bbearer\\s+[^\\s'\"`]+/gi,\n\t/\\bsk-[a-z0-9_-]{8,}/gi,\n\t/\\b(?:resp|req|chatcmpl|msg|rs|fc|call|sess|proj|org)[_-](?=[a-z0-9_-]*\\d)[a-z0-9_-]{6,}\\b/gi,\n\t/\\b\\d{1,3}(?:\\.\\d{1,3}){3}(?::\\d{1,5})?\\b/g,\n\t// All-digit labels are dotted paths (`input.0.content`) or versions, not hostnames; IPv4 has its own rule.\n\t/\\b(?:(?!\\d+\\.)[a-z0-9-]+\\.){2,}(?!\\d+\\b)[a-z0-9-]+(?::\\d{1,5})?\\b/gi,\n\t/(?<=^|[\\s'\"`(])(?:~|\\/)[^\\s'\"`)]+/g,\n\t/\\b[A-Za-z0-9_-]{32,}\\b/g,\n];\n\nconst GATEWAY_OBSERVABILITY: GatewayObservabilityV1 = {\n\tschemaVersion: 1,\n\tlayer: \"gateway_service_internal\",\n\toutcome: \"unobservable\",\n\treason: \"no_structured_evidence\",\n};\n\nconst PROVIDER_CODES = new Set<string>(Object.keys(PROVIDER_CODE_CATEGORIES));\nconst FAILURE_DETAIL_KEYS = new Set([\n\t\"schemaVersion\",\n\t\"layer\",\n\t\"phase\",\n\t\"kind\",\n\t\"category\",\n\t\"retryDisposition\",\n\t\"detailSource\",\n\t\"httpStatus\",\n\t\"providerCode\",\n]);\n\ninterface FailureScalars {\n\tcode?: string;\n\ttype?: string;\n\tmessage?: string;\n\tstatus?: number;\n}\n\nexport interface SafeResponsesFailure {\n\tmessage: string;\n\tdiagnostic: ResponsesProviderFailureV1;\n}\n\ntype ResponsesSdkRetryAttempt =\n\t| { ordinal: number; result: \"response\"; status?: number }\n\t| { ordinal: number; result: \"transport\"; category: \"timeout\" | \"connection\" | \"other\" };\n\ntype ResponsesSdkRetryReason =\n\t| \"accepted_after_retry\"\n\t| \"configured_limit_reached\"\n\t| \"terminal_after_retry\"\n\t| \"configured_zero\"\n\t| \"non_retryable_request\"\n\t| \"stream_already_accepted\"\n\t| \"insufficient_evidence\";\n\nexport interface GatewayObservabilityV1 {\n\tschemaVersion: 1;\n\tlayer: \"gateway_service_internal\";\n\toutcome: \"unobservable\";\n\treason: \"no_structured_evidence\";\n}\n\nexport interface ResponsesSdkRetryV1 {\n\tschemaVersion: 1;\n\tlayer: \"openai_sdk_request\";\n\toutcome: \"recovered\" | \"exhausted\" | \"not_attempted\";\n\treason: ResponsesSdkRetryReason;\n\tobservedAttemptCount: number;\n\tattempts: ResponsesSdkRetryAttempt[];\n\ttruncated: boolean;\n}\n\nexport interface ResponsesSdkRequestObserver {\n\tfetch: typeof fetch;\n\tmarkAccepted(): void;\n\tdiagnosticForSuccess(): ResponsesSdkRetryV1 | undefined;\n\tdiagnosticForFailure(maxRetries: number | undefined): ResponsesSdkRetryV1;\n}\n\nclass SafeResponsesFailureError extends Error {\n\tconstructor(readonly failure: SafeResponsesFailure) {\n\t\tsuper(failure.message);\n\t}\n}\n\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction finiteString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.length > 0 && value.length <= 256 ? value : undefined;\n}\n\nfunction boundedMessage(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.length > 0 ? value.slice(0, PROVIDER_MESSAGE_MAX_LENGTH) : undefined;\n}\n\nfunction finiteStatus(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;\n}\n\nfunction readFailureScalars(value: unknown): { top: FailureScalars; nested: FailureScalars } {\n\tconst topRecord = recordOf(value);\n\tconst nestedRecord = recordOf(topRecord?.error);\n\tconst read = (record: Record<string, unknown> | undefined): FailureScalars => ({\n\t\tcode: finiteString(record?.code),\n\t\ttype: finiteString(record?.type),\n\t\tmessage: boundedMessage(record?.message),\n\t\tstatus: finiteStatus(record?.status),\n\t});\n\treturn { top: read(topRecord), nested: read(nestedRecord) };\n}\n\nfunction sanitizeProviderDetail(message: string | undefined): string | undefined {\n\tif (!message) return undefined;\n\tlet detail = message;\n\tfor (const pattern of PROVIDER_DETAIL_REDACTIONS) detail = detail.replace(pattern, \"[redacted]\");\n\tdetail = detail\n\t\t.replace(/[\\u0000-\\u001f\\u007f]+/g, \" \")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\tif (detail.length === 0 || /^(?:undefined|null)$/i.test(detail)) return undefined;\n\tif (detail.length > PROVIDER_DETAIL_MAX_LENGTH) detail = `${detail.slice(0, PROVIDER_DETAIL_MAX_LENGTH - 1)}…`;\n\treturn detail;\n}\n\nfunction composeFailureMessage(category: ResponsesFailureCategory, detail: string | undefined): string {\n\tconst base = CATEGORY_MESSAGES[category];\n\tif (!detail) return base;\n\treturn `${base.slice(0, -1)}: ${detail}${/[.!?…]$/.test(detail) ? \"\" : \".\"}`;\n}\n\n// SCRAMJET-DIVERGENCE: a user abort keeps the abort reason, stripped and capped like provider detail (#553).\nexport function abortedResponsesFailureMessage(error: unknown): string {\n\treturn (\n\t\tsanitizeProviderDetail(error instanceof Error ? error.message : undefined) ??\n\t\t\"OpenAI Responses request was aborted.\"\n\t);\n}\n\nfunction categoryFromMessage(message: string | undefined): ResponsesFailureCategory | undefined {\n\tif (!message) return undefined;\n\tconst normalized = message.toLowerCase();\n\tif (/context (length|window)|maximum context|too many tokens/.test(normalized)) return \"context_overflow\";\n\tif (/insufficient.quota|quota.*(exhaust|exceed)|billing.*limit/.test(normalized)) return \"quota_exhausted\";\n\tif (/rate.?limit|too many requests/.test(normalized)) return \"rate_limit\";\n\tif (/overload|capacity/.test(normalized)) return \"overloaded\";\n\tif (/timed? ?out|timeout/.test(normalized)) return \"timeout\";\n\tif (/connection error|network error/.test(normalized)) return \"transport\";\n\tif (/authenticat|invalid api key/.test(normalized)) return \"authentication\";\n\tif (/permission|forbidden|not authorized/.test(normalized)) return \"permission\";\n\tif (/not found/.test(normalized)) return \"not_found\";\n\tif (/conflict/.test(normalized)) return \"conflict\";\n\tif (/content.filter|content.policy|safety policy/.test(normalized)) return \"content_rejection\";\n\tif (/invalid request|bad request/.test(normalized)) return \"invalid_request\";\n\tif (/server error|internal error|internal server/.test(normalized)) return \"server\";\n\tif (\n\t\t/\\bupstream (?:error|unavailable|connect(?:ion)?|request failed)\\b|(?:status|http|error|code|returned|received|upstream)\\s*:?\\s*50[0234]\\b|service unavailable|bad gateway|gateway time-?out/.test(\n\t\t\tnormalized,\n\t\t)\n\t) {\n\t\treturn \"server\";\n\t}\n\tif (/fetch failed|socket hang up|econnreset|stream ended/.test(normalized)) return \"transport\";\n\treturn undefined;\n}\n\nfunction categoryFromStatus(status: number | undefined): ResponsesFailureCategory | undefined {\n\tif (status === 400 || status === 422) return \"invalid_request\";\n\tif (status === 401) return \"authentication\";\n\tif (status === 403) return \"permission\";\n\tif (status === 404) return \"not_found\";\n\tif (status === 409) return \"conflict\";\n\tif (status === 408 || status === 504) return \"timeout\";\n\tif (status === 429) return \"rate_limit\";\n\tif (status !== undefined && status >= 500) return \"server\";\n\treturn undefined;\n}\n\nfunction allowlistedProviderCode(value: string | undefined): ResponsesProviderCode | undefined {\n\treturn value && PROVIDER_CODES.has(value) ? (value as ResponsesProviderCode) : undefined;\n}\n\nfunction makeFailure(\n\tvalue: unknown,\n\tphase: \"request\" | \"stream\",\n\tkindHint?: ResponsesFailureKind,\n): SafeResponsesFailure {\n\tif (value instanceof SafeResponsesFailureError) return value.failure;\n\tconst { top, nested } = readFailureScalars(value);\n\tconst codeCandidates = [\n\t\t[top.code, \"provider_code\"],\n\t\t[top.type, \"provider_type\"],\n\t\t[nested.code, \"provider_code\"],\n\t\t[nested.type, \"provider_type\"],\n\t] as const;\n\tconst matchedCode = codeCandidates.find(([value]) => allowlistedProviderCode(value) !== undefined);\n\tconst providerCode = allowlistedProviderCode(matchedCode?.[0]);\n\tconst contextOverflow = [top.code, top.type, nested.code, nested.type, top.message, nested.message].some(\n\t\t(field) => field === \"context_length_exceeded\" || categoryFromMessage(field) === \"context_overflow\",\n\t);\n\tconst status = top.status ?? nested.status;\n\tconst statusCategory = categoryFromStatus(status);\n\tconst messageCategory = categoryFromMessage(top.message) ?? categoryFromMessage(nested.message);\n\tlet category: ResponsesFailureCategory;\n\tlet detailSource: ResponsesFailureDetailSource;\n\tif (contextOverflow) {\n\t\tcategory = \"context_overflow\";\n\t\tdetailSource = providerCode === \"context_length_exceeded\" ? \"provider_code\" : \"message_category\";\n\t} else if (providerCode) {\n\t\tcategory = PROVIDER_CODE_CATEGORIES[providerCode];\n\t\tdetailSource = matchedCode?.[1] ?? \"provider_code\";\n\t} else if (statusCategory) {\n\t\tcategory = statusCategory;\n\t\tdetailSource = \"http_status\";\n\t} else {\n\t\tcategory = messageCategory ?? (kindHint === \"malformed_event\" ? \"malformed_event\" : \"unknown\");\n\t\tdetailSource = messageCategory ? \"message_category\" : \"none\";\n\t}\n\tconst hasEvidence = Boolean(\n\t\ttop.code ||\n\t\t\t(top.type !== \"error\" && top.type !== \"response.failed\" && top.type) ||\n\t\t\ttop.message ||\n\t\t\ttop.status ||\n\t\t\tnested.code ||\n\t\t\tnested.type ||\n\t\t\tnested.message ||\n\t\t\tnested.status,\n\t);\n\tconst errorName = value instanceof Error ? value.name.toLowerCase() : \"\";\n\tconst inferredTransport =\n\t\tphase === \"request\" &&\n\t\tstatus === undefined &&\n\t\tproviderCode === undefined &&\n\t\tmessageCategory === undefined &&\n\t\t(errorName.includes(\"connection\") || errorName.includes(\"timeout\") || errorName === \"typeerror\");\n\tif (inferredTransport && !contextOverflow) {\n\t\tcategory = errorName.includes(\"timeout\") ? \"timeout\" : \"transport\";\n\t\tdetailSource = \"none\";\n\t}\n\tconst kind =\n\t\tkindHint ??\n\t\t(status\n\t\t\t? \"http\"\n\t\t\t: (inferredTransport && !contextOverflow) || (phase === \"request\" && category === \"transport\")\n\t\t\t\t? \"transport\"\n\t\t\t\t: hasEvidence\n\t\t\t\t\t? \"provider_event\"\n\t\t\t\t\t: \"malformed_event\");\n\tif (kind === \"malformed_event\") {\n\t\tcategory = \"malformed_event\";\n\t\tdetailSource = \"none\";\n\t}\n\tconst diagnostic: ResponsesProviderFailureV1 = {\n\t\tschemaVersion: 1,\n\t\tlayer: \"openai_responses\",\n\t\tphase,\n\t\tkind,\n\t\tcategory,\n\t\tretryDisposition: CATEGORY_DISPOSITIONS[category],\n\t\tdetailSource,\n\t};\n\tif (status !== undefined) diagnostic.httpStatus = status;\n\tif (providerCode !== undefined) diagnostic.providerCode = providerCode;\n\t// Overflow text stays exact so `isContextOverflow` keys on it deterministically.\n\tconst detail =\n\t\tkind === \"malformed_event\" || category === \"context_overflow\"\n\t\t\t? undefined\n\t\t\t: sanitizeProviderDetail(top.message ?? nested.message);\n\treturn { message: composeFailureMessage(category, detail), diagnostic };\n}\n\nexport function normalizeResponsesFailure(value: unknown, phase: \"request\" | \"stream\"): SafeResponsesFailure {\n\treturn makeFailure(value, phase);\n}\n\nfunction sdkRetryOrdinal(input: string | URL | Request, init: RequestInit | undefined, fallback: number): number {\n\ttry {\n\t\tconst headers = init?.headers ? new Headers(init.headers) : input instanceof Request ? input.headers : undefined;\n\t\tconst value = headers?.get(\"x-stainless-retry-count\");\n\t\tif (value && /^\\d+$/.test(value)) {\n\t\t\tconst ordinal = Number(value);\n\t\t\tif (Number.isSafeInteger(ordinal) && ordinal <= 65_535) return ordinal;\n\t\t}\n\t} catch {\n\t\t// Observation must not affect the request.\n\t}\n\treturn Math.min(fallback, 65_535);\n}\n\nfunction transportCategory(value: unknown): \"timeout\" | \"connection\" | \"other\" {\n\tif (!(value instanceof Error)) return \"other\";\n\tconst name = value.name.toLowerCase();\n\tif (name.includes(\"timeout\") || name.includes(\"abort\")) return \"timeout\";\n\tif (name === \"typeerror\" || name.includes(\"connection\")) return \"connection\";\n\treturn \"other\";\n}\n\nfunction isSdkRetryableStatus(status: number | undefined): boolean {\n\treturn status === 408 || status === 409 || status === 429 || (status !== undefined && status >= 500);\n}\n\nconst OPENAI_SDK_DEFAULT_MAX_RETRIES = 2;\n\n// SCRAMJET-DIVERGENCE: Observe bounded SDK request attempts without changing fetch behavior or retry policy (#553).\nexport function createResponsesSdkRequestObserver(fetchImplementation: typeof fetch): ResponsesSdkRequestObserver {\n\tlet observedAttemptCount = 0;\n\tlet accepted = false;\n\tconst firstAttempts: ResponsesSdkRetryAttempt[] = [];\n\tconst latestAttempts: ResponsesSdkRetryAttempt[] = [];\n\n\tconst record = (attempt: ResponsesSdkRetryAttempt): void => {\n\t\tobservedAttemptCount = Math.min(observedAttemptCount + 1, 65_535);\n\t\tif (firstAttempts.length < 4) {\n\t\t\tfirstAttempts.push(attempt);\n\t\t\treturn;\n\t\t}\n\t\tlatestAttempts.push(attempt);\n\t\tif (latestAttempts.length > 4) latestAttempts.shift();\n\t};\n\tconst attempts = (): ResponsesSdkRetryAttempt[] => [...firstAttempts, ...latestAttempts];\n\tconst diagnostic = (\n\t\toutcome: ResponsesSdkRetryV1[\"outcome\"],\n\t\treason: ResponsesSdkRetryReason,\n\t): ResponsesSdkRetryV1 => ({\n\t\tschemaVersion: 1,\n\t\tlayer: \"openai_sdk_request\",\n\t\toutcome,\n\t\treason,\n\t\tobservedAttemptCount,\n\t\tattempts: attempts(),\n\t\ttruncated: observedAttemptCount > 8,\n\t});\n\n\treturn {\n\t\tfetch: async (input, init) => {\n\t\t\tconst fallbackOrdinal = observedAttemptCount;\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await fetchImplementation(input, init);\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\trecord({\n\t\t\t\t\t\tordinal: sdkRetryOrdinal(input, init, fallbackOrdinal),\n\t\t\t\t\t\tresult: \"transport\",\n\t\t\t\t\t\tcategory: transportCategory(error),\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Observation must not replace the transport failure.\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst attempt: ResponsesSdkRetryAttempt = {\n\t\t\t\t\tordinal: sdkRetryOrdinal(input, init, fallbackOrdinal),\n\t\t\t\t\tresult: \"response\",\n\t\t\t\t};\n\t\t\t\tif (finiteStatus(response.status) !== undefined) attempt.status = response.status;\n\t\t\t\trecord(attempt);\n\t\t\t} catch {\n\t\t\t\t// Observation must not replace or alter the response.\n\t\t\t}\n\t\t\treturn response;\n\t\t},\n\t\tmarkAccepted: () => {\n\t\t\taccepted = true;\n\t\t},\n\t\tdiagnosticForSuccess: () =>\n\t\t\tobservedAttemptCount > 1 ? diagnostic(\"recovered\", \"accepted_after_retry\") : undefined,\n\t\tdiagnosticForFailure: (maxRetries) => {\n\t\t\tif (accepted) {\n\t\t\t\treturn observedAttemptCount > 1\n\t\t\t\t\t? diagnostic(\"recovered\", \"accepted_after_retry\")\n\t\t\t\t\t: diagnostic(\"not_attempted\", \"stream_already_accepted\");\n\t\t\t}\n\t\t\tif (observedAttemptCount > 1) {\n\t\t\t\tconst effectiveMaxRetries = maxRetries ?? OPENAI_SDK_DEFAULT_MAX_RETRIES;\n\t\t\t\tconst reachedConfiguredLimit = observedAttemptCount >= Math.min(effectiveMaxRetries + 1, 65_535);\n\t\t\t\treturn diagnostic(\n\t\t\t\t\t\"exhausted\",\n\t\t\t\t\treachedConfiguredLimit ? \"configured_limit_reached\" : \"terminal_after_retry\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (observedAttemptCount === 1) {\n\t\t\t\tif (maxRetries === 0) return diagnostic(\"not_attempted\", \"configured_zero\");\n\t\t\t\tconst [attempt] = attempts();\n\t\t\t\tif (attempt?.result === \"response\" && !isSdkRetryableStatus(attempt.status)) {\n\t\t\t\t\treturn diagnostic(\"not_attempted\", \"non_retryable_request\");\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn diagnostic(\"not_attempted\", \"insufficient_evidence\");\n\t\t},\n\t};\n}\n\nexport function appendResponsesSdkRetryDiagnostic(\n\toutput: AssistantMessage,\n\tdiagnostic: ResponsesSdkRetryV1 | undefined,\n): void {\n\tif (!diagnostic) return;\n\toutput.diagnostics = [\n\t\t...(output.diagnostics ?? []),\n\t\t{ type: \"sdk_request_retry\", timestamp: Date.now(), details: { ...diagnostic } },\n\t];\n}\n\nexport function appendResponsesFailureDiagnostics(\n\toutput: AssistantMessage,\n\tfailure: SafeResponsesFailure,\n\tsdkRetry?: ResponsesSdkRetryV1,\n): void {\n\toutput.errorMessage = failure.message;\n\toutput.diagnostics = [\n\t\t...(output.diagnostics ?? []),\n\t\t{ type: \"provider_failure\", timestamp: Date.now(), details: { ...failure.diagnostic } },\n\t\t...(sdkRetry ? [{ type: \"sdk_request_retry\", timestamp: Date.now(), details: { ...sdkRetry } }] : []),\n\t\t{ type: \"gateway_observability\", timestamp: Date.now(), details: { ...GATEWAY_OBSERVABILITY } },\n\t];\n}\n\nfunction isProviderFailureDetails(value: unknown): value is ResponsesProviderFailureV1 {\n\tconst details = recordOf(value);\n\tif (!details || Object.keys(details).some((key) => !FAILURE_DETAIL_KEYS.has(key))) return false;\n\tconst category = details.category;\n\tconst kind = details.kind;\n\tconst source = details.detailSource;\n\tconst status = details.httpStatus;\n\tconst providerCode = details.providerCode;\n\tif (\n\t\tdetails.schemaVersion !== 1 ||\n\t\tdetails.layer !== \"openai_responses\" ||\n\t\t(details.phase !== \"request\" && details.phase !== \"stream\") ||\n\t\ttypeof kind !== \"string\" ||\n\t\t![\"http\", \"provider_event\", \"transport\", \"malformed_event\"].includes(kind) ||\n\t\ttypeof category !== \"string\" ||\n\t\t!(category in CATEGORY_DISPOSITIONS) ||\n\t\tdetails.retryDisposition !== CATEGORY_DISPOSITIONS[category as ResponsesFailureCategory] ||\n\t\ttypeof source !== \"string\" ||\n\t\t![\"provider_code\", \"provider_type\", \"http_status\", \"message_category\", \"none\"].includes(source) ||\n\t\t(status !== undefined && finiteStatus(status) === undefined) ||\n\t\t(providerCode !== undefined &&\n\t\t\t(typeof providerCode !== \"string\" || allowlistedProviderCode(providerCode) === undefined))\n\t) {\n\t\treturn false;\n\t}\n\tif (kind === \"malformed_event\") {\n\t\treturn category === \"malformed_event\" && source === \"none\" && status === undefined && providerCode === undefined;\n\t}\n\tif (kind === \"transport\") {\n\t\tif (details.phase !== \"request\" || status !== undefined || providerCode !== undefined) return false;\n\t\tif (source === \"none\") return category === \"transport\" || category === \"timeout\";\n\t\treturn source === \"message_category\" && category === \"transport\";\n\t}\n\tif (kind === \"http\" && status === undefined) return false;\n\tif (kind === \"provider_event\" && details.phase === \"request\" && status !== undefined) return false;\n\tif (kind === \"provider_event\" && details.phase === \"request\" && category === \"transport\") return false;\n\tif (source === \"provider_code\" || source === \"provider_type\") {\n\t\treturn (\n\t\t\ttypeof providerCode === \"string\" &&\n\t\t\tPROVIDER_CODE_CATEGORIES[providerCode as ResponsesProviderCode] === category\n\t\t);\n\t}\n\tif (source === \"http_status\") {\n\t\treturn providerCode === undefined && categoryFromStatus(status as number | undefined) === category;\n\t}\n\tif (source === \"message_category\") {\n\t\tif (category === \"provider_error\" || category === \"malformed_event\" || category === \"unknown\") return false;\n\t\tif (\n\t\t\tproviderCode !== undefined &&\n\t\t\t(category !== \"context_overflow\" || providerCode === \"context_length_exceeded\")\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\t\tconst statusCategory = categoryFromStatus(status as number | undefined);\n\t\treturn statusCategory === undefined || category === \"context_overflow\";\n\t}\n\tif (providerCode !== undefined) return false;\n\tif (kind === \"http\") return category === \"unknown\" && categoryFromStatus(status as number | undefined) === undefined;\n\tif (status !== undefined) return false;\n\treturn kind === \"provider_event\" && (category === \"provider_error\" || category === \"unknown\");\n}\n\nexport function validateResponsesProviderFailure(diagnostics: unknown): ResponsesProviderFailureValidation {\n\tif (diagnostics === undefined) return { status: \"absent\" };\n\tif (!Array.isArray(diagnostics)) return { status: \"malformed\" };\n\tconst matches: Record<string, unknown>[] = [];\n\tfor (const diagnostic of diagnostics) {\n\t\tconst candidate = recordOf(diagnostic);\n\t\tif (candidate?.type === \"provider_failure\") matches.push(candidate);\n\t}\n\tif (matches.length === 0) return { status: \"absent\" };\n\tif (matches.length > 1) return { status: \"duplicate\" };\n\tconst details = matches[0].details;\n\tif (!isProviderFailureDetails(details)) return { status: \"malformed\" };\n\treturn { status: \"valid\", category: details.category, retryDisposition: details.retryDisposition };\n}\n\nfunction providerEventFailure(value: unknown, kindHint?: ResponsesFailureKind): SafeResponsesFailureError {\n\treturn new SafeResponsesFailureError(makeFailure(value, \"stream\", kindHint));\n}\n\n// =============================================================================\n// Message conversion\n// =============================================================================\n\nexport function convertResponsesMessages<TApi extends Api>(\n\tmodel: Model<TApi>,\n\tcontext: Context,\n\tallowedToolCallProviders: ReadonlySet<string>,\n\toptions?: ConvertResponsesMessagesOptions,\n): ResponseInput {\n\tconst messages: ResponseInput = [];\n\n\tconst normalizeIdPart = (part: string): string => {\n\t\tconst sanitized = part.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n\t\tconst normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;\n\t\treturn normalized.replace(/_+$/, \"\");\n\t};\n\n\tconst buildForeignResponsesItemId = (itemId: string): string => {\n\t\tconst normalized = `fc_${shortHash(itemId)}`;\n\t\treturn normalized.length > 64 ? normalized.slice(0, 64) : normalized;\n\t};\n\n\tconst normalizeToolCallId = (id: string, _targetModel: Model<TApi>, source: AssistantMessage): string => {\n\t\tif (!allowedToolCallProviders.has(model.provider)) return normalizeIdPart(id);\n\t\tif (!id.includes(\"|\")) return normalizeIdPart(id);\n\t\tconst [callId, itemId] = id.split(\"|\");\n\t\tconst normalizedCallId = normalizeIdPart(callId);\n\t\tconst isForeignToolCall = source.provider !== model.provider || source.api !== model.api;\n\t\tlet normalizedItemId = isForeignToolCall ? buildForeignResponsesItemId(itemId) : normalizeIdPart(itemId);\n\t\t// OpenAI Responses API requires item id to start with \"fc\"\n\t\tif (!normalizedItemId.startsWith(\"fc_\")) {\n\t\t\tnormalizedItemId = normalizeIdPart(`fc_${normalizedItemId}`);\n\t\t}\n\t\treturn `${normalizedCallId}|${normalizedItemId}`;\n\t};\n\n\tconst transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);\n\n\tconst includeSystemPrompt = options?.includeSystemPrompt ?? true;\n\tconst systemPrompt = flattenSystemPrompt(context.systemPrompt);\n\tif (includeSystemPrompt && systemPrompt) {\n\t\tconst role = model.reasoning ? \"developer\" : \"system\";\n\t\tmessages.push({\n\t\t\trole,\n\t\t\tcontent: sanitizeSurrogates(systemPrompt),\n\t\t});\n\t}\n\n\tlet msgIndex = 0;\n\tfor (const msg of transformedMessages) {\n\t\tif (msg.role === \"user\") {\n\t\t\tif (typeof msg.content === \"string\") {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"input_text\", text: sanitizeSurrogates(msg.content) }],\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tconst content: ResponseInputContent[] = msg.content.map((item): ResponseInputContent => {\n\t\t\t\t\tif (item.type === \"text\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"input_text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(item.text),\n\t\t\t\t\t\t} satisfies ResponseInputText;\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"input_image\",\n\t\t\t\t\t\tdetail: \"auto\",\n\t\t\t\t\t\timage_url: `data:${item.mimeType};base64,${item.data}`,\n\t\t\t\t\t} satisfies ResponseInputImage;\n\t\t\t\t});\n\t\t\t\tif (content.length === 0) continue;\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst output: ResponseInput = [];\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isDifferentModel =\n\t\t\t\tassistantMsg.model !== model.id &&\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api;\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\tif (block.thinkingSignature) {\n\t\t\t\t\t\tconst reasoningItem = JSON.parse(block.thinkingSignature) as ResponseReasoningItem;\n\t\t\t\t\t\toutput.push(reasoningItem);\n\t\t\t\t\t}\n\t\t\t\t} else if (block.type === \"text\") {\n\t\t\t\t\tconst textBlock = block as TextContent;\n\t\t\t\t\tconst parsedSignature = parseTextSignature(textBlock.textSignature);\n\t\t\t\t\t// OpenAI requires id to be max 64 characters\n\t\t\t\t\tlet msgId = parsedSignature?.id;\n\t\t\t\t\tif (!msgId) {\n\t\t\t\t\t\tmsgId = `msg_${msgIndex}`;\n\t\t\t\t\t} else if (msgId.length > 64) {\n\t\t\t\t\t\tmsgId = `msg_${shortHash(msgId)}`;\n\t\t\t\t\t}\n\t\t\t\t\toutput.push({\n\t\t\t\t\t\ttype: \"message\",\n\t\t\t\t\t\trole: \"assistant\",\n\t\t\t\t\t\tcontent: [{ type: \"output_text\", text: sanitizeSurrogates(textBlock.text), annotations: [] }],\n\t\t\t\t\t\tstatus: \"completed\",\n\t\t\t\t\t\tid: msgId,\n\t\t\t\t\t\tphase: parsedSignature?.phase,\n\t\t\t\t\t} satisfies ResponseOutputMessage);\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tconst [callId, itemIdRaw] = toolCall.id.split(\"|\");\n\t\t\t\t\tlet itemId: string | undefined = itemIdRaw;\n\n\t\t\t\t\t// For different-model messages, set id to undefined to avoid pairing validation.\n\t\t\t\t\t// OpenAI tracks which fc_xxx IDs were paired with rs_xxx reasoning items.\n\t\t\t\t\t// By omitting the id, we avoid triggering that validation (like cross-provider does).\n\t\t\t\t\tif (isDifferentModel && itemId?.startsWith(\"fc_\")) {\n\t\t\t\t\t\titemId = undefined;\n\t\t\t\t\t}\n\n\t\t\t\t\toutput.push({\n\t\t\t\t\t\ttype: \"function_call\",\n\t\t\t\t\t\tid: itemId,\n\t\t\t\t\t\tcall_id: callId,\n\t\t\t\t\t\tname: toolCall.name,\n\t\t\t\t\t\targuments: JSON.stringify(toolCall.arguments),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (output.length === 0) continue;\n\t\t\tmessages.push(...output);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\tconst textResult = msg.content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\\n\");\n\t\t\tconst hasImages = msg.content.some((c): c is ImageContent => c.type === \"image\");\n\t\t\tconst hasText = textResult.length > 0;\n\t\t\tconst [callId] = msg.toolCallId.split(\"|\");\n\n\t\t\tlet output: string | ResponseFunctionCallOutputItemList;\n\t\t\tif (hasImages && model.input.includes(\"image\")) {\n\t\t\t\tconst contentParts: ResponseFunctionCallOutputItemList = [];\n\n\t\t\t\tif (hasText) {\n\t\t\t\t\tcontentParts.push({\n\t\t\t\t\t\ttype: \"input_text\",\n\t\t\t\t\t\ttext: sanitizeSurrogates(textResult),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tfor (const block of msg.content) {\n\t\t\t\t\tif (block.type === \"image\") {\n\t\t\t\t\t\tcontentParts.push({\n\t\t\t\t\t\t\ttype: \"input_image\",\n\t\t\t\t\t\t\tdetail: \"auto\",\n\t\t\t\t\t\t\timage_url: `data:${block.mimeType};base64,${block.data}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\toutput = contentParts;\n\t\t\t} else {\n\t\t\t\toutput = sanitizeSurrogates(hasText ? textResult : \"(see attached image)\");\n\t\t\t}\n\n\t\t\tmessages.push({\n\t\t\t\ttype: \"function_call_output\",\n\t\t\t\tcall_id: callId,\n\t\t\t\toutput,\n\t\t\t});\n\t\t}\n\t\tmsgIndex++;\n\t}\n\n\treturn messages;\n}\n\n// =============================================================================\n// Tool conversion\n// =============================================================================\n\nexport function convertResponsesTools(tools: Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[] {\n\tconst strict = options?.strict === undefined ? false : options.strict;\n\treturn tools.map((tool) => ({\n\t\ttype: \"function\",\n\t\tname: tool.name,\n\t\tdescription: tool.description,\n\t\tparameters: tool.parameters as any, // TypeBox already generates JSON Schema\n\t\tstrict,\n\t}));\n}\n\n// =============================================================================\n// Stream processing\n// =============================================================================\n\nexport async function processResponsesStream<TApi extends Api>(\n\topenaiStream: AsyncIterable<ResponseStreamEvent>,\n\toutput: AssistantMessage,\n\tstream: AssistantMessageEventStream,\n\tmodel: Model<TApi>,\n\toptions?: OpenAIResponsesStreamOptions,\n): Promise<void> {\n\tlet currentItem: ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | null = null;\n\tlet currentBlock: ThinkingContent | TextContent | (ToolCall & { partialJson: string }) | null = null;\n\tconst blocks = output.content;\n\tconst blockIndex = () => blocks.length - 1;\n\n\tfor await (const event of openaiStream) {\n\t\tif (event.type === \"response.created\") {\n\t\t\toutput.responseId = event.response.id;\n\t\t} else if (event.type === \"response.output_item.added\") {\n\t\t\tconst item = event.item;\n\t\t\tif (item.type === \"reasoning\") {\n\t\t\t\tcurrentItem = item;\n\t\t\t\tcurrentBlock = { type: \"thinking\", thinking: \"\" };\n\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t} else if (item.type === \"message\") {\n\t\t\t\tcurrentItem = item;\n\t\t\t\tcurrentBlock = { type: \"text\", text: \"\" };\n\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\tstream.push({ type: \"text_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t} else if (item.type === \"function_call\") {\n\t\t\t\tcurrentItem = item;\n\t\t\t\tcurrentBlock = {\n\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\tid: `${item.call_id}|${item.id}`,\n\t\t\t\t\tname: item.name,\n\t\t\t\t\targuments: {},\n\t\t\t\t\tpartialJson: item.arguments || \"\",\n\t\t\t\t};\n\t\t\t\toutput.content.push(currentBlock);\n\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: blockIndex(), partial: output });\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_summary_part.added\") {\n\t\t\tif (currentItem && currentItem.type === \"reasoning\") {\n\t\t\t\tcurrentItem.summary = currentItem.summary || [];\n\t\t\t\tcurrentItem.summary.push(event.part);\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_summary_text.delta\") {\n\t\t\tif (currentItem?.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tcurrentItem.summary = currentItem.summary || [];\n\t\t\t\tconst lastPart = currentItem.summary[currentItem.summary.length - 1];\n\t\t\t\tif (lastPart) {\n\t\t\t\t\tcurrentBlock.thinking += event.delta;\n\t\t\t\t\tlastPart.text += event.delta;\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_summary_part.done\") {\n\t\t\tif (currentItem?.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tcurrentItem.summary = currentItem.summary || [];\n\t\t\t\tconst lastPart = currentItem.summary[currentItem.summary.length - 1];\n\t\t\t\tif (lastPart) {\n\t\t\t\t\tcurrentBlock.thinking += \"\\n\\n\";\n\t\t\t\t\tlastPart.text += \"\\n\\n\";\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: \"\\n\\n\",\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.reasoning_text.delta\") {\n\t\t\tif (currentItem?.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tcurrentBlock.thinking += event.delta;\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (event.type === \"response.content_part.added\") {\n\t\t\tif (currentItem?.type === \"message\") {\n\t\t\t\tcurrentItem.content = currentItem.content || [];\n\t\t\t\t// Filter out ReasoningText, only accept output_text and refusal\n\t\t\t\tif (event.part.type === \"output_text\" || event.part.type === \"refusal\") {\n\t\t\t\t\tcurrentItem.content.push(event.part);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.output_text.delta\") {\n\t\t\tif (currentItem?.type === \"message\" && currentBlock?.type === \"text\") {\n\t\t\t\tif (!currentItem.content || currentItem.content.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst lastPart = currentItem.content[currentItem.content.length - 1];\n\t\t\t\tif (lastPart?.type === \"output_text\") {\n\t\t\t\t\tcurrentBlock.text += event.delta;\n\t\t\t\t\tlastPart.text += event.delta;\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.refusal.delta\") {\n\t\t\tif (currentItem?.type === \"message\" && currentBlock?.type === \"text\") {\n\t\t\t\tif (!currentItem.content || currentItem.content.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst lastPart = currentItem.content[currentItem.content.length - 1];\n\t\t\t\tif (lastPart?.type === \"refusal\") {\n\t\t\t\t\tcurrentBlock.text += event.delta;\n\t\t\t\t\tlastPart.refusal += event.delta;\n\t\t\t\t\tstream.push({\n\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.function_call_arguments.delta\") {\n\t\t\tif (currentItem?.type === \"function_call\" && currentBlock?.type === \"toolCall\") {\n\t\t\t\tcurrentBlock.partialJson += event.delta;\n\t\t\t\tcurrentBlock.arguments = parseStreamingJson(currentBlock.partialJson);\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tdelta: event.delta,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (event.type === \"response.function_call_arguments.done\") {\n\t\t\tif (currentItem?.type === \"function_call\" && currentBlock?.type === \"toolCall\") {\n\t\t\t\tconst previousPartialJson = currentBlock.partialJson;\n\t\t\t\tcurrentBlock.partialJson = event.arguments;\n\t\t\t\tcurrentBlock.arguments = parseStreamingJson(currentBlock.partialJson);\n\n\t\t\t\tif (event.arguments.startsWith(previousPartialJson)) {\n\t\t\t\t\tconst delta = event.arguments.slice(previousPartialJson.length);\n\t\t\t\t\tif (delta.length > 0) {\n\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\t\t\tdelta,\n\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"response.output_item.done\") {\n\t\t\tconst item = event.item;\n\n\t\t\tif (item.type === \"reasoning\" && currentBlock?.type === \"thinking\") {\n\t\t\t\tconst summaryText = item.summary?.map((s) => s.text).join(\"\\n\\n\") || \"\";\n\t\t\t\tconst contentText = item.content?.map((c) => c.text).join(\"\\n\\n\") || \"\";\n\t\t\t\tcurrentBlock.thinking = summaryText || contentText || currentBlock.thinking;\n\t\t\t\tcurrentBlock.thinkingSignature = JSON.stringify(item);\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tcontent: currentBlock.thinking,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t\tcurrentBlock = null;\n\t\t\t} else if (item.type === \"message\" && currentBlock?.type === \"text\") {\n\t\t\t\tcurrentBlock.text = item.content.map((c) => (c.type === \"output_text\" ? c.text : c.refusal)).join(\"\");\n\t\t\t\tcurrentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);\n\t\t\t\tstream.push({\n\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\tcontentIndex: blockIndex(),\n\t\t\t\t\tcontent: currentBlock.text,\n\t\t\t\t\tpartial: output,\n\t\t\t\t});\n\t\t\t\tcurrentBlock = null;\n\t\t\t} else if (item.type === \"function_call\") {\n\t\t\t\tconst args =\n\t\t\t\t\tcurrentBlock?.type === \"toolCall\" && currentBlock.partialJson\n\t\t\t\t\t\t? parseStreamingJson(currentBlock.partialJson)\n\t\t\t\t\t\t: parseStreamingJson(item.arguments || \"{}\");\n\n\t\t\t\tlet toolCall: ToolCall;\n\t\t\t\tif (currentBlock?.type === \"toolCall\") {\n\t\t\t\t\t// Finalize in-place and strip the scratch buffer so replay only\n\t\t\t\t\t// carries parsed arguments.\n\t\t\t\t\tcurrentBlock.arguments = args;\n\t\t\t\t\tdelete (currentBlock as { partialJson?: string }).partialJson;\n\t\t\t\t\ttoolCall = currentBlock;\n\t\t\t\t} else {\n\t\t\t\t\ttoolCall = {\n\t\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\t\tid: `${item.call_id}|${item.id}`,\n\t\t\t\t\t\tname: item.name,\n\t\t\t\t\t\targuments: args,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tcurrentBlock = null;\n\t\t\t\tstream.push({ type: \"toolcall_end\", contentIndex: blockIndex(), toolCall, partial: output });\n\t\t\t}\n\t\t} else if (event.type === \"response.completed\") {\n\t\t\tconst response = event.response;\n\t\t\tif (response?.id) {\n\t\t\t\toutput.responseId = response.id;\n\t\t\t}\n\t\t\tif (response?.usage) {\n\t\t\t\tconst cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;\n\t\t\t\toutput.usage = {\n\t\t\t\t\t// OpenAI includes cached tokens in input_tokens, so subtract to get non-cached input\n\t\t\t\t\tinput: (response.usage.input_tokens || 0) - cachedTokens,\n\t\t\t\t\toutput: response.usage.output_tokens || 0,\n\t\t\t\t\tcacheRead: cachedTokens,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: response.usage.total_tokens || 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t};\n\t\t\t}\n\t\t\tcalculateCost(model, output.usage);\n\t\t\tif (options?.applyServiceTierPricing) {\n\t\t\t\tconst serviceTier = options.resolveServiceTier\n\t\t\t\t\t? options.resolveServiceTier(response?.service_tier, options.serviceTier)\n\t\t\t\t\t: (response?.service_tier ?? options.serviceTier);\n\t\t\t\toptions.applyServiceTierPricing(output.usage, serviceTier);\n\t\t\t}\n\t\t\t// Map status to stop reason\n\t\t\toutput.stopReason = mapStopReason(response?.status);\n\t\t\tif (output.content.some((b) => b.type === \"toolCall\") && output.stopReason === \"stop\") {\n\t\t\t\toutput.stopReason = \"toolUse\";\n\t\t\t}\n\t\t} else if (event.type === \"error\") {\n\t\t\tthrow providerEventFailure(event);\n\t\t} else if (event.type === \"response.failed\") {\n\t\t\tconst error = event.response?.error;\n\t\t\tconst details = event.response?.incomplete_details;\n\t\t\tif (error) throw providerEventFailure(error, \"provider_event\");\n\t\t\tif (details?.reason) throw providerEventFailure({ message: details.reason }, \"provider_event\");\n\t\t\tthrow providerEventFailure(event, \"malformed_event\");\n\t\t}\n\t}\n}\n\nfunction mapStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason {\n\tif (!status) return \"stop\";\n\tswitch (status) {\n\t\tcase \"completed\":\n\t\t\treturn \"stop\";\n\t\tcase \"incomplete\":\n\t\t\treturn \"length\";\n\t\tcase \"failed\":\n\t\tcase \"cancelled\":\n\t\t\treturn \"error\";\n\t\t// These two are wonky ...\n\t\tcase \"in_progress\":\n\t\tcase \"queued\":\n\t\t\treturn \"stop\";\n\t\tdefault: {\n\t\t\tconst _exhaustive: never = status;\n\t\t\tthrow new Error(`Unhandled stop reason: ${_exhaustive}`);\n\t\t}\n\t}\n}\n"]}
|