@openclaw/ai 2026.10.1 → 2026.10.5-beta.1
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/README.md +6 -0
- package/dist/{anthropic-BDfXKgF1.mjs → anthropic-BAHKgWam.mjs} +30 -59
- package/dist/{anthropic-payload-policy-BZUAchqb.d.mts → anthropic-payload-policy-DGblkHL5.d.mts} +19 -16
- package/dist/{anthropic-stream-reducer-DHt9uFWX.mjs → anthropic-stream-reducer-g6w1YVii.mjs} +195 -283
- package/dist/{api-registry-DcEP_o4h.d.mts → api-registry-BhXXYdRA.d.mts} +2 -2
- package/dist/assistant-message-events-CHMJgbhs.mjs +18 -0
- package/dist/{assistant-output-mJ9cL4Qg.mjs → assistant-output-CUEeSotf.mjs} +2 -2
- package/dist/{assistant-text-phase-DAv1BdTm.mjs → assistant-text-phase-BrV8KdFO.mjs} +7 -6
- package/dist/{azure-openai-responses-gn9hMf8x.mjs → azure-openai-responses-C7qBuR23.mjs} +8 -9
- package/dist/{credential-redaction-BKv49aiv.d.mts → credential-redaction-BHxruEH6.d.mts} +3 -1
- package/dist/{diagnostics-Caj-4RBA.mjs → diagnostics-tQasYct1.mjs} +10 -3
- package/dist/diagnostics.d.mts +7 -7
- package/dist/diagnostics.mjs +3 -3
- package/dist/{env-api-keys-CyX4VKGO.mjs → env-api-keys-CrxwdylR.mjs} +24 -35
- package/dist/event-stream-6UNs-AhN.d.mts +1 -0
- package/dist/{event-stream-DrRM_90c.d.mts → event-stream-BcqWLo_u.d.mts} +1 -1
- package/dist/{event-stream-C5AbCAaZ.mjs → event-stream-X2gmsUWv.mjs} +5 -8
- package/dist/event-stream.d.mts +2 -2
- package/dist/event-stream.mjs +1 -1
- package/dist/google-Boz2QcpK.mjs +20 -0
- package/dist/{google-interactions-DXu4IYGb.mjs → google-interactions-tjAb1mc5.mjs} +52 -84
- package/dist/{google-messages-Bmheetdg.mjs → google-messages-C-RZzloH.mjs} +25 -35
- package/dist/google-provider-stream-CMImyxbX.mjs +58 -0
- package/dist/{google-shared-DE_ddfsC.mjs → google-shared-OyAtMTZv.mjs} +6 -15
- package/dist/google-vertex-hCxTJea7.mjs +40 -0
- package/dist/{host-BjIYEjwT.d.mts → host-CTDz7LXD.d.mts} +6 -3
- package/dist/{host-JpNulOOs.mjs → host-JWXZWa-c.mjs} +3 -2
- package/dist/{host-policy-CC5H0iRZ.mjs → host-policy-q4Azym9F.mjs} +1 -1
- package/dist/{index-DJrNQnKw.d.mts → index-BYDvopUB.d.mts} +8 -5
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +7 -6
- package/dist/internal/anthropic.d.mts +5 -5
- package/dist/internal/anthropic.mjs +4 -4
- package/dist/internal/openai-completions-compat.d.mts +1 -1
- package/dist/internal/openai-completions-compat.mjs +1 -1
- package/dist/internal/openai-responses-payload-policy.d.mts +8 -1
- package/dist/internal/openai-responses-payload-policy.mjs +3 -3
- package/dist/internal/openai.d.mts +9 -9
- package/dist/internal/openai.mjs +9 -11
- package/dist/internal/runtime.d.mts +31 -13
- package/dist/internal/runtime.mjs +10 -13
- package/dist/internal/shared.d.mts +5 -5
- package/dist/internal/shared.mjs +6 -7
- package/dist/internal/tool-schema.mjs +2 -2
- package/dist/{llm-request-activity-D8f_oxHj.mjs → llm-request-activity-CfMuh5cC.mjs} +2 -2
- package/dist/{mistral-1Bnbneg1.mjs → mistral-DUveP4hF.mjs} +71 -146
- package/dist/{model-transport-url-C5f59L3Y.d.mts → model-transport-url-BrsjjdjR.d.mts} +5 -1
- package/dist/{number-coercion-V8-BhME4.mjs → number-coercion-C3hztWoY.mjs} +5 -4
- package/dist/{openai-chatgpt-responses-CB1JwNFF.mjs → openai-chatgpt-responses-OCwckGhY.mjs} +164 -234
- package/dist/{openai-completions-BPadHHKe.mjs → openai-completions-DOltBUe9.mjs} +7 -6
- package/dist/{openai-completions-compat-CJ0ktmGy.d.mts → openai-completions-compat-i-nLZxFy.d.mts} +10 -11
- package/dist/{openai-completions-compat-D7hWYB0G.mjs → openai-completions-compat-pg1q4uNG.mjs} +10 -6
- package/dist/{openai-completions-transport-DITBEALX.mjs → openai-completions-transport-OMrn7gnZ.mjs} +1429 -294
- package/dist/{openai-prompt-cache-Dj8qeOHT.mjs → openai-prompt-cache-Cr2xKVrS.mjs} +1 -1
- package/dist/{openai-prompt-cache-C-Q1Ksfs.d.mts → openai-prompt-cache-DIPMrMFy.d.mts} +2 -2
- package/dist/{openai-responses-1_TvcWI3.mjs → openai-responses-BSBJUA19.mjs} +9 -10
- package/dist/{openai-responses-compaction-replay-BB2SWdsx.d.mts → openai-responses-compaction-replay-B2FE4Nw8.d.mts} +4 -4
- package/dist/{openai-responses-contracts-sAvdqNjS.d.mts → openai-responses-contracts-DKqMJ1Ct.d.mts} +9 -4
- package/dist/{openai-responses-contracts-BKu8O7l4.mjs → openai-responses-contracts-DlKMMTxK.mjs} +26 -6
- package/dist/{openai-responses-compaction-window-m878aVpU.mjs → openai-responses-endpoint-CinGh4VZ.mjs} +14 -4
- package/dist/{openai-responses-prompt-observer-internal-CbH84xIS.mjs → openai-responses-request-lifecycle-D2IFiI0-.mjs} +39 -4
- package/dist/{openai-responses-shared-TCJRAoMW.mjs → openai-responses-shared-DVGhq5xF.mjs} +1036 -327
- package/dist/{openai-responses-terminal-usage-BVvyXZ8u.d.mts → openai-responses-terminal-usage-BygjiAdi.d.mts} +2 -2
- package/dist/{openai-tool-schema-DYNePiF8.mjs → openai-tool-schema-DDPXsvJP.mjs} +236 -399
- package/dist/{positive-integer-CwLi8LUL.mjs → positive-integer-CqWjqSi9.mjs} +1 -1
- package/dist/{provider-error-DQBE1bUQ.mjs → provider-error-CF9GWCs2.mjs} +5 -3
- package/dist/{provider-options-Dk4PeQV3.d.mts → provider-options-CVrXZBtA.d.mts} +3 -2
- package/dist/{provider-transport-turn-state-8c_Nj2NS.mjs → provider-transport-turn-state-LuCaTsSO.mjs} +2 -2
- package/dist/{provider-types-De8PRdR0.d.mts → provider-types-CHtRx0dI.d.mts} +3 -3
- package/dist/provider-types.d.mts +6 -6
- package/dist/providers.d.mts +2 -2
- package/dist/providers.mjs +12 -12
- package/dist/{reasoning-tag-text-partitioner-Ju9VpUGl.mjs → reasoning-tag-text-partitioner-0-Rsezog.mjs} +13 -12
- package/dist/{required-api-key-D4kn3252.mjs → required-api-key-CFJvcFy_.mjs} +1 -1
- package/dist/{simple-options-wW3117Fu.mjs → simple-options-BARFAzk9.mjs} +9 -11
- package/dist/{src-DWJq14pn.mjs → src-C_wfIUmV.mjs} +19 -4
- package/dist/{stream-CElngO8h.mjs → stream-DwrETW0_.mjs} +12 -21
- package/dist/{stream-first-event-timeout-D9ZmL_s9.mjs → stream-first-event-timeout-Dc42hKv6.mjs} +8 -21
- package/dist/{streaming-byte-guard-CC-HMn_u.mjs → streaming-byte-guard-DBXr1XYn.mjs} +16 -1
- package/dist/{string-normalization-PN10hf6d.mjs → string-normalization-I3ux50s9.mjs} +1 -1
- package/dist/{text-signature-rOn31nxc.mjs → text-signature-B_MB6U_9.mjs} +32 -5
- package/dist/{tool-schema-json-projection-Ct8SeBeJ.mjs → tool-schema-json-projection-B8sG1I9v.mjs} +29 -23
- package/dist/{transport-stream-shared-CAxkgOSj.d.mts → transport-stream-shared-BmpzDtdQ.d.mts} +4 -4
- package/dist/{transport-stream-shared-C5TAc3U_.mjs → transport-stream-shared-DH08b7Il.mjs} +8 -16
- package/dist/transports.d.mts +33 -22
- package/dist/transports.mjs +73 -1234
- package/dist/{types-DvzmWW8J.d.mts → types-Bd2T86VJ.d.mts} +5 -23
- package/dist/types-Kdfw_04V.d.mts +1 -0
- package/dist/types.d.mts +6 -6
- package/dist/types.mjs +5 -4
- package/dist/{validation-B3lQt_VE.d.mts → validation-DdGB1yy3.d.mts} +1 -1
- package/dist/{validation-DbqlIfDn.mjs → validation-opC2pjk9.mjs} +2 -3
- package/dist/validation.d.mts +1 -1
- package/dist/validation.mjs +1 -1
- package/package.json +4 -9
- package/dist/env-api-keys-BR0MUYYD.d.mts +0 -17
- package/dist/event-stream-DqDQWqiy.d.mts +0 -1
- package/dist/google-CyMz2jF_.mjs +0 -54
- package/dist/google-vertex-BEW0OQIA.mjs +0 -88
- package/dist/internal/env-api-keys.d.mts +0 -2
- package/dist/internal/env-api-keys.mjs +0 -2
- package/dist/openai-responses-request-lifecycle-Fsag-IGZ.mjs +0 -36
- package/dist/openai-transport-params-ildrm49s.mjs +0 -735
- package/dist/openai-web-search-tools-Cfu0uLyN.mjs +0 -14
- package/dist/provider-replay-context-ywPKKLjL.mjs +0 -25
- package/dist/types-Dlz-8qcy.d.mts +0 -1
package/dist/{openai-completions-transport-DITBEALX.mjs → openai-completions-transport-OMrn7gnZ.mjs}
RENAMED
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
import { _ as runtimeContextContentToText, f as hasRuntimeContextMarker, p as isRuntimeContextMessage } from "./types-CQc6JNG4.mjs";
|
|
2
|
-
import { r as appendAssistantThinking } from "./event-stream-
|
|
3
|
-
import { o as isRecord } from "./record-coerce-CNDovvft.mjs";
|
|
2
|
+
import { r as appendAssistantThinking } from "./event-stream-X2gmsUWv.mjs";
|
|
3
|
+
import { i as asOptionalRecord, o as isRecord, r as asOptionalObjectRecord } from "./record-coerce-CNDovvft.mjs";
|
|
4
4
|
import { s as readNonEmptyStringPreservingWhitespace, t as hasNonEmptyString } from "./string-coerce-CtgOVOSv.mjs";
|
|
5
|
-
import { n as asNonNegativeFiniteNumber } from "./number-coercion-
|
|
6
|
-
import { N as isImageWithMediaPayload, r as getAiTransportHost } from "./host-
|
|
7
|
-
import { _ as
|
|
8
|
-
import { r as uniqueStrings } from "./string-normalization-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { r as
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { i as
|
|
5
|
+
import { n as asNonNegativeFiniteNumber, r as asPositiveFiniteNumber } from "./number-coercion-C3hztWoY.mjs";
|
|
6
|
+
import { N as isImageWithMediaPayload, a as resolveAiTransportHeaderSentinels, r as getAiTransportHost } from "./host-JWXZWa-c.mjs";
|
|
7
|
+
import { M as resolveOpenAIThinkingApi, S as isOpenAIGpt6Model, _ as isResponsesServiceTierRejection, b as isOpenAIGpt55Model, d as OpenAIResponsesWebSocketSafeRetryError, g as responsesServiceTierObserver, k as supportsOpenAITemperature, l as OpenAIResponsesWebSocketPostDispatchError, m as parseOpenAIResponsesWebSocketServerError, r as OPENAI_CODEX_RESPONSES_DEFAULT_INSTRUCTIONS, t as AZURE_RESPONSES_FIRST_EVENT_TIMEOUT_MS, u as OpenAIResponsesWebSocketPreDispatchError, v as nextResponsesServiceTier, x as isOpenAIGpt56Model, y as isOpenAIGpt54MiniModel } from "./openai-responses-contracts-DlKMMTxK.mjs";
|
|
8
|
+
import { r as uniqueStrings } from "./string-normalization-I3ux50s9.mjs";
|
|
9
|
+
import { n as getSessionResourceOwnerId, r as registerSessionResourceCleanup } from "./session-resources-D1wgDQ8C.mjs";
|
|
10
|
+
import { c as stableStringify, s as truncateUtf16Safe } from "./provider-error-CF9GWCs2.mjs";
|
|
11
|
+
import { r as resolveOpenAIPromptCacheParams } from "./openai-prompt-cache-Cr2xKVrS.mjs";
|
|
12
|
+
import { d as hasOnlyResponsesFunctionTools, i as emitModelTransportError, r as emitModelTransportDebug, t as formatModelTransportDebugBaseUrl } from "./diagnostics-tQasYct1.mjs";
|
|
13
|
+
import { f as supportsModelTools, i as providerReplayContextMatches, n as buildProviderReplayContext, o as estimateStringChars, r as isProviderReplayContext } from "./text-signature-B_MB6U_9.mjs";
|
|
14
|
+
import { c as resolveOpenAIResponsesTextFormat, i as resolveOpenAICompletionsCompat, l as shouldOmitOllamaCompatResponseFormat, n as isNativeOpenAIEndpoint, o as usesNativeOpenAICodexResponsesBackend, r as isOpenAICodexResponsesModel, s as resolveOpenAICompletionsResponseFormat, t as detectOpenAICompletionsCompat } from "./openai-completions-compat-pg1q4uNG.mjs";
|
|
15
|
+
import { i as resolveProviderEndpoint, t as buildGuardedModelFetch } from "./host-policy-q4Azym9F.mjs";
|
|
15
16
|
import { i as resolveCacheRetention, r as buildCopilotDynamicHeaders } from "./cloudflare-5qSGvIUH.mjs";
|
|
16
|
-
import { E as createToolArgumentPreviewSchedule, O as parseStreamingJson, P as sanitizeSurrogates, S as withProviderResponseHook,
|
|
17
|
-
import { C as stripSystemPromptCacheBoundary, D as sortPromptCacheToolsByName, S as splitSystemPromptRelocatableBoundary, d as describeToolResultMediaPlaceholder, l as codeModeToolSurfaceObserver, m as extractToolResultText, s as prepareModelRequestBody, u as reasoningTagTextPolicy, w as stripSystemPromptRelocatableBoundary, x as splitSystemPromptCacheBoundary } from "./simple-options-
|
|
18
|
-
import { C as
|
|
19
|
-
import { n as getEnvApiKey } from "./env-api-keys-
|
|
20
|
-
import { n as transformProviderMessages, t as createAssistantOutput } from "./assistant-output-
|
|
21
|
-
import { a as tagUnresolvedTextAsCommentary, i as tagPendingCommentaryText, n as rememberPendingCommentaryTags, r as tagInterruptedTextPhases, t as clearPendingCommentaryText } from "./assistant-text-phase-
|
|
22
|
-
import { t as notifyLlmRequestActivity } from "./llm-request-activity-
|
|
17
|
+
import { E as createToolArgumentPreviewSchedule, M as racePromiseWithAbortSignal, O as parseStreamingJson, P as sanitizeSurrogates, S as withProviderResponseHook, b as transportAbortError, c as failTransportStream, d as iterateModelStream, h as notifyProviderStreamOpened, l as finalizeTerminalToolCallArguments, s as createWritableTransportEventStream, u as finalizeTransportStream, v as sanitizeTransportPayloadText, y as throwIfModelStreamAborted } from "./transport-stream-shared-DH08b7Il.mjs";
|
|
18
|
+
import { C as stripSystemPromptCacheBoundary, D as sortPromptCacheToolsByName, S as splitSystemPromptRelocatableBoundary, d as describeToolResultMediaPlaceholder, l as codeModeToolSurfaceObserver, m as extractToolResultText, s as prepareModelRequestBody, u as reasoningTagTextPolicy, w as stripSystemPromptRelocatableBoundary, x as splitSystemPromptCacheBoundary } from "./simple-options-BARFAzk9.mjs";
|
|
19
|
+
import { $ as replayResponsesReasoningUpdates, A as summarizeResponsesPayload, At as measureUtf8AppendBytes, B as responsesInputFingerprint, C as readResponsesRecoveryEvent, Ct as toOpenAIResponsesToolChoice, D as logResponsesFailedNoDetails, Dt as createResponseModelTracker, E as ResponsesStreamFailure, Ft as resolvePromptCacheKey, G as responsesContinuationRequestFingerprint, H as recordResponsesContinuationState, I as convertResponsesMessages, It as redactIdentifier, Lt as sha256Hex, Mt as readOpenAICompletionsContentDeltas, Nt as readOpenAICompletionsReasoningBatch, O as safeDebugValue, Ot as isOpenAICompletionsThinkingEnabled, P as buildResponsesInputMessage, Pt as resolveOpenAIClientBaseUrl, Q as isConfigurationUpdate, R as readResponsesInputReplayState, S as isInvalidEncryptedContentError, St as reconcileOpenAIResponsesToolChoice, T as resolveNextResponsesEncryptedContentAttempt, Tt as createOpenAIProviderAcceptanceHook, U as resolveResponsesContinuationRequest, V as claimOpenAIResponsesHttpContinuation, W as responsesContinuationPrefixFingerprint, _t as resolveOpenAISimpleReasoningEffort, a as prepareResponsesTools, at as suppressOpenAIResponsesCompaction, bt as projectOpenAITools, c as observeResponsesStream, ct as buildOpenAISdkClientOptions, et as supportsResponsesReasoningUpdate, ft as getCompat, gt as resolveOpenAIRequestReasoning, ht as resolveOpenAIStrictToolFlagWithDiagnostics, j as recordResponsesContextUsage, jt as parseOpenAICompletionsUsage, k as summarizeOpenAITransportError, kt as log, lt as buildOpenAISdkRequestOptions, mt as resolveCodeModeResponsesVisibleToolNames, n as applyResponsesServiceTierPricing, nt as buildOpenAIResponsesReasoningReplayMetadata, ot as assertCodeModeResponsesToolSurface, s as processResponsesStream, st as buildOpenAIClientHeaders, ut as enforceCodeModeResponsesToolSurface, w as resolveAzureOpenAIApiVersion, wt as GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, x as createResponsesStreamWithRecovery, xt as reconcileOpenAICompletionsToolChoice, yt as prepareOpenAITools, z as recordResponsesInputReplay } from "./openai-responses-shared-DVGhq5xF.mjs";
|
|
20
|
+
import { n as getEnvApiKey } from "./env-api-keys-CrxwdylR.mjs";
|
|
21
|
+
import { n as transformProviderMessages, t as createAssistantOutput } from "./assistant-output-CUEeSotf.mjs";
|
|
22
|
+
import { a as tagUnresolvedTextAsCommentary, i as tagPendingCommentaryText, n as rememberPendingCommentaryTags, r as tagInterruptedTextPhases, t as clearPendingCommentaryText } from "./assistant-text-phase-BrV8KdFO.mjs";
|
|
23
|
+
import { t as notifyLlmRequestActivity } from "./llm-request-activity-CfMuh5cC.mjs";
|
|
23
24
|
import { n as resolveOpencodeSessionHeaders } from "./session-affinity-DA4WHv1h.mjs";
|
|
24
25
|
import "./types.mjs";
|
|
25
|
-
import { i as createOpenAIProviderClient, n as resolveProviderSimpleCompletionHeaders, r as resolveProviderTransportTurnState, t as filterProviderTurnHeadersForExplicitOpencodeSession } from "./provider-transport-turn-state-
|
|
26
|
-
import { a as withFirstStreamEventTimeout, i as getFirstStreamEventTimeoutMs, r as getFirstStreamEventTimeoutHandler, t as createFirstStreamEventAbortController } from "./stream-first-event-timeout-
|
|
27
|
-
import {
|
|
26
|
+
import { i as createOpenAIProviderClient, n as resolveProviderSimpleCompletionHeaders, r as resolveProviderTransportTurnState, t as filterProviderTurnHeadersForExplicitOpencodeSession } from "./provider-transport-turn-state-LuCaTsSO.mjs";
|
|
27
|
+
import { a as withFirstStreamEventTimeout, i as getFirstStreamEventTimeoutMs, r as getFirstStreamEventTimeoutHandler, t as createFirstStreamEventAbortController } from "./stream-first-event-timeout-Dc42hKv6.mjs";
|
|
28
|
+
import { t as boundResponseBody } from "./streaming-byte-guard-DBXr1XYn.mjs";
|
|
29
|
+
import { f as withPreparedToolSchemaNormalization, r as normalizeOpenAIStrictToolParameters } from "./openai-tool-schema-DDPXsvJP.mjs";
|
|
28
30
|
import { isGoogleGemini3ThinkingLevelModel } from "./internal/google-model-family.mjs";
|
|
29
31
|
import { t as mapOpenAIStopReason } from "./openai-stop-reason-8s70vDXj.mjs";
|
|
30
|
-
import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-
|
|
32
|
+
import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-0-Rsezog.mjs";
|
|
33
|
+
import { i as resolveAzureDeploymentNameFromMap, t as isOpenAICompatibleAzureResponsesBaseUrl } from "./azure-openai-responses-client-compat-D0dvJMbZ.mjs";
|
|
34
|
+
import { i as isOpenAIResponsesCompactionOutput, l as resolveOpenAIResponsesPayloadPolicy, n as supportsNativeOpenAIResponsesEndpoint, o as sanitizeResponsesImagePayload, r as createBoundedOpenAIResponsesCompactionFetch, s as applyOpenAIResponsesPayloadPolicy, t as isOfficialOpenAIResponsesBaseUrl } from "./openai-responses-endpoint-CinGh4VZ.mjs";
|
|
35
|
+
import { n as withResponsesRequestAcceptance, r as createResponsesPromptEgressObserver, t as responsesRequestLifecycle } from "./openai-responses-request-lifecycle-D2IFiI0-.mjs";
|
|
31
36
|
import { randomUUID } from "node:crypto";
|
|
32
|
-
import OpenAI from "openai";
|
|
37
|
+
import OpenAI, { AzureOpenAI } from "openai";
|
|
38
|
+
import { ResponsesWS } from "openai/resources/responses/ws.js";
|
|
33
39
|
//#region packages/ai/src/transports/deepseek-dsml-grammar.ts
|
|
34
40
|
const DEEPSEEK_DSML_MARKERS = [
|
|
35
41
|
"|",
|
|
@@ -247,37 +253,33 @@ function applyCompletionsAnthropicCacheControl(payload, cacheControl = { type: "
|
|
|
247
253
|
* OpenAI Chat Completions compatibility helpers. Some providers only accept
|
|
248
254
|
* role/content messages with plain string content instead of text block arrays.
|
|
249
255
|
*/
|
|
250
|
-
function flattenStringOnlyCompletionContent(content) {
|
|
251
|
-
if (!Array.isArray(content)) return content;
|
|
252
|
-
const textParts = [];
|
|
253
|
-
for (const item of content) {
|
|
254
|
-
if (!item || typeof item !== "object" || item.type !== "text" || typeof item.text !== "string") return content;
|
|
255
|
-
textParts.push(item.text);
|
|
256
|
-
}
|
|
257
|
-
return textParts.join("\n");
|
|
258
|
-
}
|
|
259
256
|
/** Flatten string-only text block content arrays into newline-joined strings. */
|
|
260
257
|
function flattenCompletionMessagesToStringContent(messages) {
|
|
261
258
|
return messages.map((message) => {
|
|
262
|
-
|
|
263
|
-
const content =
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
const record = asOptionalObjectRecord(message);
|
|
260
|
+
const content = record?.content;
|
|
261
|
+
if (!Array.isArray(content)) return message;
|
|
262
|
+
const textParts = [];
|
|
263
|
+
for (const item of content) {
|
|
264
|
+
const part = asOptionalObjectRecord(item);
|
|
265
|
+
if (part?.type !== "text" || typeof part.text !== "string") return message;
|
|
266
|
+
textParts.push(part.text);
|
|
267
|
+
}
|
|
266
268
|
return {
|
|
267
|
-
...
|
|
268
|
-
content:
|
|
269
|
+
...record,
|
|
270
|
+
content: textParts.join("\n")
|
|
269
271
|
};
|
|
270
272
|
});
|
|
271
273
|
}
|
|
272
274
|
/** Strip completion messages to role/content fields for strict providers. */
|
|
273
275
|
function stripCompletionMessagesToRoleContent(messages) {
|
|
274
276
|
return messages.map((message) => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
277
|
+
const record = asOptionalRecord(message);
|
|
278
|
+
if (!record) return message;
|
|
279
|
+
return {
|
|
280
|
+
...Object.hasOwn(record, "role") ? { role: record.role } : {},
|
|
281
|
+
...Object.hasOwn(record, "content") ? { content: record.content } : {}
|
|
282
|
+
};
|
|
281
283
|
});
|
|
282
284
|
}
|
|
283
285
|
//#endregion
|
|
@@ -321,6 +323,15 @@ function createOpenAIEncryptedToolCallReasoningTracker() {
|
|
|
321
323
|
}
|
|
322
324
|
};
|
|
323
325
|
}
|
|
326
|
+
function hasObservableContent(value) {
|
|
327
|
+
if (typeof value === "string") return value.length > 0;
|
|
328
|
+
if (Array.isArray(value)) return value.some(hasObservableContent);
|
|
329
|
+
if (isRecord(value)) return Object.entries(value).some(([field, nestedValue]) => field !== "type" && field !== "id" && field !== "index" && hasObservableContent(nestedValue));
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
function hasOpenAICompletionsDeltaContent(delta) {
|
|
333
|
+
return Object.entries(delta).some(([field, value]) => field !== "role" && hasObservableContent(value));
|
|
334
|
+
}
|
|
324
335
|
/** Normalize the SDK's legacy single-function lane into its modern tool-call shape. */
|
|
325
336
|
function createOpenAICompletionsToolCallDeltaNormalizer() {
|
|
326
337
|
let sawModernToolCall = false;
|
|
@@ -342,12 +353,6 @@ function createOpenAICompletionsToolCallDeltaNormalizer() {
|
|
|
342
353
|
pendingFollowingDeltaBytes += nextDeltaBytes;
|
|
343
354
|
pendingFollowingDeltas.push(delta);
|
|
344
355
|
};
|
|
345
|
-
const hasObservableContent = (value) => {
|
|
346
|
-
if (typeof value === "string") return value.length > 0;
|
|
347
|
-
if (Array.isArray(value)) return value.some(hasObservableContent);
|
|
348
|
-
if (isRecord(value)) return Object.entries(value).some(([field, nestedValue]) => field !== "type" && field !== "id" && field !== "index" && hasObservableContent(nestedValue));
|
|
349
|
-
return false;
|
|
350
|
-
};
|
|
351
356
|
return (delta, finishReason) => {
|
|
352
357
|
const ordinaryDelta = { ...delta };
|
|
353
358
|
delete ordinaryDelta.function_call;
|
|
@@ -383,7 +388,7 @@ function createOpenAICompletionsToolCallDeltaNormalizer() {
|
|
|
383
388
|
}];
|
|
384
389
|
const hadPendingLegacyCall = pendingLegacyToolCall !== void 0;
|
|
385
390
|
const leadingDeltas = [];
|
|
386
|
-
if (
|
|
391
|
+
if (hasOpenAICompletionsDeltaContent(ordinaryDelta)) {
|
|
387
392
|
if (hadPendingLegacyCall) bufferFollowingDelta(ordinaryDelta);
|
|
388
393
|
else leadingDeltas.push({
|
|
389
394
|
delta: ordinaryDelta,
|
|
@@ -435,10 +440,7 @@ function finalizeOpenAICompletionsToolCalls(output, options = {}) {
|
|
|
435
440
|
const isToolCall = (block) => block.type === "toolCall";
|
|
436
441
|
const hasToolCalls = output.content.some(isToolCall);
|
|
437
442
|
if (output.stopReason === "toolUse" && !hasToolCalls) output.stopReason = "stop";
|
|
438
|
-
if (output.stopReason === "stop" && hasToolCalls && options.allowSilentToolCallPromotion !== false && !output.content.some((block) =>
|
|
439
|
-
const candidate = block;
|
|
440
|
-
return candidate.type === "text" && typeof candidate.text === "string" && candidate.text.trim().length > 0;
|
|
441
|
-
})) output.stopReason = "toolUse";
|
|
443
|
+
if (output.stopReason === "stop" && hasToolCalls && options.allowSilentToolCallPromotion !== false && !output.content.some((block) => block.type === "text" && block.text.trim().length > 0)) output.stopReason = "toolUse";
|
|
442
444
|
if (output.stopReason !== "toolUse") {
|
|
443
445
|
if (hasToolCalls) output.content = output.content.filter((block) => !isToolCall(block));
|
|
444
446
|
return;
|
|
@@ -519,7 +521,7 @@ function convertMessages(model, context, compat, options = {}) {
|
|
|
519
521
|
});
|
|
520
522
|
if (isRuntimeContextMessage(msg)) {
|
|
521
523
|
params.push({
|
|
522
|
-
role:
|
|
524
|
+
role: compat.supportsDeveloperRole ? "developer" : "user",
|
|
523
525
|
content: sanitizeSurrogates(runtimeContextContentToText(msg.content))
|
|
524
526
|
});
|
|
525
527
|
options.cacheOptOutIndexes?.add(params.length - 1);
|
|
@@ -617,7 +619,7 @@ function convertMessages(model, context, compat, options = {}) {
|
|
|
617
619
|
const images = toolMsg.content.filter(isImageWithMediaPayload);
|
|
618
620
|
const toolResultMsg = {
|
|
619
621
|
role: "tool",
|
|
620
|
-
content:
|
|
622
|
+
content: textResult.trim() ? textResult : mediaPlaceholder ?? "(no output)",
|
|
621
623
|
tool_call_id: toolMsg.toolCallId
|
|
622
624
|
};
|
|
623
625
|
if (compat.requiresToolResultName && toolMsg.toolName) toolResultMsg.name = toolMsg.toolName;
|
|
@@ -678,24 +680,36 @@ function relocateNonBehavioralRegion(args) {
|
|
|
678
680
|
}
|
|
679
681
|
}
|
|
680
682
|
//#endregion
|
|
681
|
-
//#region packages/ai/src/transports/openai-completions-
|
|
682
|
-
function
|
|
683
|
+
//#region packages/ai/src/transports/openai-completions-policy.ts
|
|
684
|
+
function applyCompletionsReasoningAndRouting(params, model, reasoning, compat, mode) {
|
|
685
|
+
const direct = mode === "direct";
|
|
683
686
|
const nativeEffort = compat.thinkingFormat === "openrouter" ? void 0 : reasoning.effort;
|
|
684
|
-
const
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
else
|
|
697
|
-
|
|
698
|
-
|
|
687
|
+
const enabled = reasoning.thinkingEnabled ?? false;
|
|
688
|
+
if (model.reasoning) {
|
|
689
|
+
let allowScalarEffort = true;
|
|
690
|
+
if (direct && compat.thinkingFormat === "zai") {
|
|
691
|
+
params.thinking = enabled ? {
|
|
692
|
+
type: "enabled",
|
|
693
|
+
clear_thinking: false
|
|
694
|
+
} : { type: "disabled" };
|
|
695
|
+
allowScalarEffort = false;
|
|
696
|
+
} else if (compat.thinkingFormat === "qwen") {
|
|
697
|
+
params.enable_thinking = enabled;
|
|
698
|
+
allowScalarEffort = false;
|
|
699
|
+
} else if (compat.thinkingFormat === "qwen-chat-template") {
|
|
700
|
+
params.chat_template_kwargs = {
|
|
701
|
+
enable_thinking: enabled,
|
|
702
|
+
...direct ? { preserve_thinking: true } : {}
|
|
703
|
+
};
|
|
704
|
+
allowScalarEffort = false;
|
|
705
|
+
} else if (compat.thinkingFormat === "together" || direct && compat.thinkingFormat === "deepseek") {
|
|
706
|
+
if (compat.thinkingFormat === "deepseek") params.thinking = { type: enabled ? "enabled" : "disabled" };
|
|
707
|
+
else params.reasoning = { enabled };
|
|
708
|
+
allowScalarEffort = enabled;
|
|
709
|
+
}
|
|
710
|
+
if (allowScalarEffort && compat.supportsReasoningEffort && (direct ? nativeEffort !== void 0 : nativeEffort)) params.reasoning_effort = nativeEffort;
|
|
711
|
+
}
|
|
712
|
+
if (!direct) return;
|
|
699
713
|
if (compat.openRouterRouting) params.provider = compat.openRouterRouting;
|
|
700
714
|
if (model.baseUrl.includes("ai-gateway.vercel.sh") && model.compat?.vercelGatewayRouting) {
|
|
701
715
|
const routing = model.compat.vercelGatewayRouting;
|
|
@@ -738,21 +752,15 @@ function injectToolCallThoughtSignatures(outgoingMessages, context, model) {
|
|
|
738
752
|
}
|
|
739
753
|
if (sigById.size === 0 && !fallbackSig) return;
|
|
740
754
|
for (const message of outgoingMessages) {
|
|
741
|
-
const toolCalls = message.tool_calls;
|
|
755
|
+
const toolCalls = "tool_calls" in message ? message.tool_calls : void 0;
|
|
742
756
|
if (!Array.isArray(toolCalls)) continue;
|
|
743
757
|
for (const toolCall of toolCalls) {
|
|
744
|
-
|
|
745
|
-
if (
|
|
746
|
-
let sig = sigById.get(id) ?? fallbackSig;
|
|
747
|
-
if (typeof sig === "string" && sig.length > 0) {
|
|
758
|
+
let sig = sigById.get(toolCall.id) ?? fallbackSig;
|
|
759
|
+
if (sig) {
|
|
748
760
|
if (hasGoogleCompatThoughtSignatureTruncationFootprint(sig.trim())) sig = fallbackSig;
|
|
749
761
|
}
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
toolCall.extra_content = extra;
|
|
753
|
-
const google = extra.google && typeof extra.google === "object" ? extra.google : {};
|
|
754
|
-
extra.google = google;
|
|
755
|
-
google.thought_signature = sig;
|
|
762
|
+
if (!sig) continue;
|
|
763
|
+
Object.assign(toolCall, { extra_content: { google: { thought_signature: sig } } });
|
|
756
764
|
}
|
|
757
765
|
}
|
|
758
766
|
}
|
|
@@ -762,9 +770,6 @@ const COMPLETIONS_REASONING_REPLAY_FIELDS = [
|
|
|
762
770
|
"reasoning",
|
|
763
771
|
"reasoning_text"
|
|
764
772
|
];
|
|
765
|
-
function stripCompletionsReasoningReplayFields(record) {
|
|
766
|
-
for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) delete record[field];
|
|
767
|
-
}
|
|
768
773
|
function sanitizeOpenRouterReasoningReplayFields(record) {
|
|
769
774
|
const reasoningDetails = record.reasoning_details;
|
|
770
775
|
if (typeof reasoningDetails === "string") {
|
|
@@ -776,12 +781,6 @@ function sanitizeOpenRouterReasoningReplayFields(record) {
|
|
|
776
781
|
if (typeof reasoningText === "string" && reasoningText.length > 0 && typeof record.reasoning !== "string" && typeof record.reasoning_content !== "string") record.reasoning = reasoningText;
|
|
777
782
|
delete record.reasoning_text;
|
|
778
783
|
}
|
|
779
|
-
function sanitizeReasoningContentReplayFields(record) {
|
|
780
|
-
if ("reasoning_content" in record && typeof record.reasoning_content !== "string") delete record.reasoning_content;
|
|
781
|
-
delete record.reasoning_details;
|
|
782
|
-
delete record.reasoning;
|
|
783
|
-
delete record.reasoning_text;
|
|
784
|
-
}
|
|
785
784
|
const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
786
785
|
"deepseek-v4-flash",
|
|
787
786
|
"deepseek-v4-pro",
|
|
@@ -839,23 +838,21 @@ function shouldTrustReasoningContentReplayMetadata(model) {
|
|
|
839
838
|
if (model.provider.trim().toLowerCase() === "openai") return false;
|
|
840
839
|
return shouldPreserveOpenRouterReasoningReplay(model);
|
|
841
840
|
}
|
|
842
|
-
function sanitizeCompletionsReasoningReplayFields(messages, options) {
|
|
843
|
-
if (!Array.isArray(messages)) return;
|
|
844
|
-
for (const msg of messages) {
|
|
845
|
-
if (!msg || typeof msg !== "object") continue;
|
|
846
|
-
const record = msg;
|
|
847
|
-
if (record.role !== "assistant") continue;
|
|
848
|
-
if (options.preserveOpenRouterReasoning) sanitizeOpenRouterReasoningReplayFields(record);
|
|
849
|
-
else if (options.preserveReasoningContent) sanitizeReasoningContentReplayFields(record);
|
|
850
|
-
else stripCompletionsReasoningReplayFields(record);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
841
|
function applyCompletionsReplay(outgoingMessages, context, model, compat) {
|
|
854
842
|
injectToolCallThoughtSignatures(outgoingMessages, context, model);
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
843
|
+
const preserveOpenRouterReasoning = compat.thinkingFormat === "openrouter" && shouldPreserveOpenRouterReasoningReplay(model);
|
|
844
|
+
const preserveReasoningContent = shouldPreserveReasoningContentReplay(model, compat);
|
|
845
|
+
for (const message of outgoingMessages) {
|
|
846
|
+
if (message.role !== "assistant") continue;
|
|
847
|
+
if (preserveOpenRouterReasoning) sanitizeOpenRouterReasoningReplayFields(message);
|
|
848
|
+
else {
|
|
849
|
+
const record = message;
|
|
850
|
+
for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) {
|
|
851
|
+
if (preserveReasoningContent && field === "reasoning_content" && typeof record[field] === "string") continue;
|
|
852
|
+
delete record[field];
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
859
856
|
}
|
|
860
857
|
//#endregion
|
|
861
858
|
//#region packages/ai/src/transports/openai-completions-params.ts
|
|
@@ -882,7 +879,8 @@ function resolveOpenAICompletionsMaxTokens(model, options) {
|
|
|
882
879
|
};
|
|
883
880
|
}
|
|
884
881
|
function resolveOpenAICompletionsModelMaxTokens(model) {
|
|
885
|
-
|
|
882
|
+
const maxTokens = asPositiveFiniteNumber(model.maxTokens);
|
|
883
|
+
return maxTokens === void 0 ? void 0 : Math.floor(maxTokens);
|
|
886
884
|
}
|
|
887
885
|
const OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN = 1.25;
|
|
888
886
|
const OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE = 8e3;
|
|
@@ -897,12 +895,11 @@ function estimateJsonChars(value, fallback) {
|
|
|
897
895
|
function estimateOpenAICompletionsInputTokens(payload) {
|
|
898
896
|
let adjustedChars = 0;
|
|
899
897
|
adjustedChars += estimateOpenAICompletionsMessagesChars(payload.messages);
|
|
900
|
-
if (
|
|
898
|
+
if (payload.tools?.length) adjustedChars += estimateJsonChars(payload.tools, 1024);
|
|
901
899
|
if (payload.response_format !== void 0) adjustedChars += estimateJsonChars(payload.response_format, 256);
|
|
902
900
|
return Math.ceil(adjustedChars / 4 * OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN);
|
|
903
901
|
}
|
|
904
902
|
function estimateOpenAICompletionsMessagesChars(messages) {
|
|
905
|
-
if (!Array.isArray(messages)) return 0;
|
|
906
903
|
let adjustedChars = 0;
|
|
907
904
|
for (const message of messages) {
|
|
908
905
|
if (!message || typeof message !== "object") continue;
|
|
@@ -933,35 +930,6 @@ function estimateOpenAICompletionsContentChars(value) {
|
|
|
933
930
|
}
|
|
934
931
|
return adjustedChars;
|
|
935
932
|
}
|
|
936
|
-
function resolveOpenAICompletionsEffectiveContextTokens(model) {
|
|
937
|
-
const contextTokens = model.contextTokens;
|
|
938
|
-
if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens > 0) return contextTokens;
|
|
939
|
-
return typeof model.contextWindow === "number" && Number.isFinite(model.contextWindow) && model.contextWindow > 0 ? model.contextWindow : void 0;
|
|
940
|
-
}
|
|
941
|
-
function setQwenChatTemplateThinking(params, enabled) {
|
|
942
|
-
const existing = params.chat_template_kwargs;
|
|
943
|
-
params.chat_template_kwargs = existing && typeof existing === "object" && !Array.isArray(existing) ? {
|
|
944
|
-
...existing,
|
|
945
|
-
enable_thinking: enabled
|
|
946
|
-
} : { enable_thinking: enabled };
|
|
947
|
-
}
|
|
948
|
-
/** Return whether the binary control replaces scalar reasoning effort. */
|
|
949
|
-
function applyBinaryCompletionsThinkingParams(params) {
|
|
950
|
-
if (!params.modelReasoning) return false;
|
|
951
|
-
const enabled = params.thinkingEnabled;
|
|
952
|
-
switch (params.compatThinkingFormat) {
|
|
953
|
-
case "qwen-chat-template":
|
|
954
|
-
setQwenChatTemplateThinking(params.payload, enabled);
|
|
955
|
-
return true;
|
|
956
|
-
case "qwen":
|
|
957
|
-
params.payload.enable_thinking = enabled;
|
|
958
|
-
return true;
|
|
959
|
-
case "together":
|
|
960
|
-
params.payload.reasoning = { enabled };
|
|
961
|
-
return !enabled;
|
|
962
|
-
default: return false;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
933
|
function convertTools(tools, compat, model, mode) {
|
|
966
934
|
const prepared = mode === "managed" ? prepareOpenAITools(tools) : void 0;
|
|
967
935
|
const projection = prepared?.projection ?? projectOpenAITools(tools);
|
|
@@ -1012,12 +980,13 @@ function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
|
1012
980
|
"qwen"
|
|
1013
981
|
].includes(model.provider));
|
|
1014
982
|
const cacheOptOutIndexes = /* @__PURE__ */ new Set();
|
|
1015
|
-
|
|
983
|
+
const convertedMessages = convertMessages(model, context, compat, {
|
|
1016
984
|
cacheOptOutIndexes,
|
|
1017
985
|
preserveSystemPromptCacheBoundary: cacheControl !== void 0 && !managedCompat?.requiresStringContent
|
|
1018
986
|
});
|
|
987
|
+
let messages = convertedMessages;
|
|
1019
988
|
if (managedCompat) {
|
|
1020
|
-
applyCompletionsReplay(
|
|
989
|
+
applyCompletionsReplay(convertedMessages, context, model, managedCompat);
|
|
1021
990
|
if (managedCompat.strictMessageKeys) messages = stripCompletionMessagesToRoleContent(messages);
|
|
1022
991
|
if (managedCompat.requiresStringContent) messages = flattenCompletionMessagesToStringContent(messages);
|
|
1023
992
|
}
|
|
@@ -1025,10 +994,10 @@ function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
|
1025
994
|
const params = {
|
|
1026
995
|
model: model.id,
|
|
1027
996
|
messages,
|
|
1028
|
-
stream:
|
|
997
|
+
stream: (options?.streaming ?? model.params?.streaming) !== false,
|
|
1029
998
|
...resolveOpenAIPromptCacheParams(model, cacheRetention, compat)
|
|
1030
999
|
};
|
|
1031
|
-
if (compat.supportsUsageInStreaming) params.stream_options = { include_usage: true };
|
|
1000
|
+
if (params.stream && compat.supportsUsageInStreaming) params.stream_options = { include_usage: true };
|
|
1032
1001
|
if (compat.supportsStore) params.store = false;
|
|
1033
1002
|
if (policy.mode === "direct" || compat.supportsPromptCacheKey && promptCacheKey) params.prompt_cache_key = compat.supportsPromptCacheKey ? promptCacheKey : void 0;
|
|
1034
1003
|
if (options?.temperature !== void 0) params.temperature = options.temperature;
|
|
@@ -1051,13 +1020,13 @@ function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
|
1051
1020
|
if (policy.mode === "direct") directToolProjection = converted.projection;
|
|
1052
1021
|
if (converted.tools.length > 0 || policy.mode === "managed" && converted.projection.inputToolCount === 0 && converted.projection.diagnostics.length === 0) params.tools = converted.tools;
|
|
1053
1022
|
else if (hasToolCallHistory(context.messages)) params.tools = [];
|
|
1054
|
-
if (policy.mode === "direct" && compat.zaiToolStream && converted.tools.length > 0) params.tool_stream = true;
|
|
1023
|
+
if (policy.mode === "direct" && params.stream && compat.zaiToolStream && converted.tools.length > 0) params.tool_stream = true;
|
|
1055
1024
|
if (policy.mode === "managed" && options?.toolChoice) {
|
|
1056
1025
|
const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, converted.projection);
|
|
1057
1026
|
if (toolChoice !== void 0) params.tool_choice = toolChoice;
|
|
1058
|
-
} else if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint &&
|
|
1027
|
+
} else if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && params.tools?.length) params.tool_choice = "auto";
|
|
1059
1028
|
} else if (hasToolCallHistory(context.messages)) params.tools = [];
|
|
1060
|
-
if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint &&
|
|
1029
|
+
if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && params.tools?.length === 0) {
|
|
1061
1030
|
delete params.tools;
|
|
1062
1031
|
delete params.tool_choice;
|
|
1063
1032
|
}
|
|
@@ -1074,7 +1043,7 @@ function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
|
1074
1043
|
...model,
|
|
1075
1044
|
compat: model.compat ?? void 0
|
|
1076
1045
|
}, simpleReasoning)) ?? (usesBinaryOpenRouterThinking ? void 0 : "high");
|
|
1077
|
-
const reasoning = resolveOpenAIRequestReasoning(model, requestedEffort);
|
|
1046
|
+
const reasoning = resolveOpenAIRequestReasoning(model, requestedEffort, compat.reasoningEffortForOff);
|
|
1078
1047
|
const { effort, thinkingEnabled } = reasoning;
|
|
1079
1048
|
{
|
|
1080
1049
|
const maxTokenBudget = policy.mode === "direct" ? {
|
|
@@ -1082,7 +1051,7 @@ function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
|
1082
1051
|
clampToModelMaxTokens: true
|
|
1083
1052
|
} : resolveOpenAICompletionsMaxTokens(model, options);
|
|
1084
1053
|
const effectiveMaxTokens = maxTokenBudget.maxTokens;
|
|
1085
|
-
const effectiveContextTokens =
|
|
1054
|
+
const effectiveContextTokens = asPositiveFiniteNumber(model.contextTokens) ?? asPositiveFiniteNumber(model.contextWindow);
|
|
1086
1055
|
let clampedMaxTokens = effectiveMaxTokens;
|
|
1087
1056
|
const modelMaxTokens = resolveOpenAICompletionsModelMaxTokens(model);
|
|
1088
1057
|
if (maxTokenBudget.clampToModelMaxTokens && clampedMaxTokens !== void 0 && modelMaxTokens !== void 0 && clampedMaxTokens > modelMaxTokens) {
|
|
@@ -1107,17 +1076,8 @@ function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
|
1107
1076
|
if (usesBinaryOpenRouterThinking && thinkingEnabled !== void 0) params.reasoning = { enabled: thinkingEnabled };
|
|
1108
1077
|
else if (effort !== void 0) params.reasoning = { effort };
|
|
1109
1078
|
}
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
const suppressScalarEffort = applyBinaryCompletionsThinkingParams({
|
|
1113
|
-
compatThinkingFormat: compat.thinkingFormat,
|
|
1114
|
-
modelReasoning: model.reasoning,
|
|
1115
|
-
payload: params,
|
|
1116
|
-
thinkingEnabled: thinkingEnabled ?? false
|
|
1117
|
-
});
|
|
1118
|
-
if (!isOpenRouter && effort && model.reasoning && compat.supportsReasoningEffort && !suppressScalarEffort) params.reasoning_effort = effort;
|
|
1119
|
-
if (compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools, !managedCompat?.requiresStringContent);
|
|
1120
|
-
}
|
|
1079
|
+
applyCompletionsReasoningAndRouting(params, model, reasoning, compat, policy.mode);
|
|
1080
|
+
if (policy.mode === "managed" && compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools, !managedCompat?.requiresStringContent);
|
|
1121
1081
|
if (params.tools?.length && isKnownOpenAICompletionsEndpoint(model)) {
|
|
1122
1082
|
if (isOpenAIGpt56Model(model)) params.reasoning_effort = "none";
|
|
1123
1083
|
else if (isOpenAIGpt54MiniModel(model) || isOpenAIGpt55Model(model)) delete params.reasoning_effort;
|
|
@@ -1487,7 +1447,8 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1487
1447
|
const MAX_POST_TOOL_CALL_BUFFER_BYTES = 256e3;
|
|
1488
1448
|
const directMode = options?.mode === "direct";
|
|
1489
1449
|
const emitReasoning = options?.emitReasoning ?? true;
|
|
1490
|
-
const
|
|
1450
|
+
const openAIModel = model;
|
|
1451
|
+
const compat = getCompat(openAIModel);
|
|
1491
1452
|
const visibleReasoningDetailTypes = new Set(compat.visibleReasoningDetailTypes);
|
|
1492
1453
|
const shouldFilterDeepSeekDsmlText = !directMode && compat.thinkingFormat === "deepseek";
|
|
1493
1454
|
const deepSeekTextFilter = shouldFilterDeepSeekDsmlText ? createDeepSeekTextFilter() : null;
|
|
@@ -1496,31 +1457,37 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1496
1457
|
const reasoningTagTextPartitioner = createReasoningTagTextPartitioner();
|
|
1497
1458
|
if (options?.strictReasoningTags) reasoningTagTextPartitioner.markStrict();
|
|
1498
1459
|
let currentBlock = null;
|
|
1499
|
-
|
|
1500
|
-
let directThinkingBlock = null;
|
|
1460
|
+
const directContent = { block: null };
|
|
1501
1461
|
let currentTextSource;
|
|
1502
1462
|
let pendingInterruptedTextBlock = null;
|
|
1503
1463
|
let confirmedInterruptedTextBlock = null;
|
|
1504
1464
|
let pendingPostToolCallDeltas = [];
|
|
1505
1465
|
let pendingPostToolCallBytes = 0;
|
|
1506
|
-
let isFlushingPendingPostToolCallDeltas = false;
|
|
1507
1466
|
const toolCallBlocksByIndex = /* @__PURE__ */ new Map();
|
|
1508
1467
|
const toolCallBlocksById = /* @__PURE__ */ new Map();
|
|
1509
1468
|
const encryptedReasoning = createOpenAIEncryptedToolCallReasoningTracker();
|
|
1510
1469
|
const toolArgumentPreviewSchedules = /* @__PURE__ */ new WeakMap();
|
|
1511
1470
|
const provisionalCommentaryTags = directMode ? options.provisionalCommentaryTags : /* @__PURE__ */ new Map();
|
|
1512
|
-
const
|
|
1513
|
-
const toolCallBlockIndices = /* @__PURE__ */ new WeakMap();
|
|
1471
|
+
const blockIndices = /* @__PURE__ */ new WeakMap();
|
|
1514
1472
|
let explicitVisibleTextBlocks;
|
|
1515
1473
|
const normalizeToolCallDeltas = createOpenAICompletionsToolCallDeltaNormalizer();
|
|
1516
1474
|
let finishReason;
|
|
1517
1475
|
let sawNativeToolCallDelta = false;
|
|
1518
|
-
const blockIndex = () => directMode && currentBlock && currentBlock.type !== "toolCall" ?
|
|
1476
|
+
const blockIndex = () => directMode && currentBlock && currentBlock.type !== "toolCall" ? blockIndices.get(currentBlock) ?? output.content.length - 1 : output.content.length - 1;
|
|
1519
1477
|
let chunkPushedEvent = false;
|
|
1520
1478
|
const pushStreamEvent = (event) => {
|
|
1521
1479
|
chunkPushedEvent = true;
|
|
1522
1480
|
stream.push(event);
|
|
1523
1481
|
};
|
|
1482
|
+
const appendToolCallBlock = (block) => {
|
|
1483
|
+
output.content.push(block);
|
|
1484
|
+
blockIndices.set(block, output.content.length - 1);
|
|
1485
|
+
pushStreamEvent({
|
|
1486
|
+
type: "toolcall_start",
|
|
1487
|
+
contentIndex: blockIndices.get(block) ?? -1,
|
|
1488
|
+
partial: output
|
|
1489
|
+
});
|
|
1490
|
+
};
|
|
1524
1491
|
const queuePostToolCallDelta = (next) => {
|
|
1525
1492
|
const nextBytes = Buffer.byteLength(next.text, "utf8");
|
|
1526
1493
|
if (pendingPostToolCallBytes + nextBytes > MAX_POST_TOOL_CALL_BUFFER_BYTES) throw new Error("Exceeded post-tool-call delta buffer limit");
|
|
@@ -1532,88 +1499,62 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1532
1499
|
}
|
|
1533
1500
|
previous.text += next.text;
|
|
1534
1501
|
};
|
|
1535
|
-
const
|
|
1536
|
-
|
|
1537
|
-
if (
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1502
|
+
const appendContentDelta = (delta) => {
|
|
1503
|
+
flushPendingPostToolCallDeltas();
|
|
1504
|
+
if (directMode && directContent.block?.type === delta.kind) currentBlock = directContent.block;
|
|
1505
|
+
if (delta.kind === "text" && currentBlock?.type === "text" && currentTextSource !== delta.source) currentBlock = null;
|
|
1506
|
+
if (!currentBlock || currentBlock.type !== delta.kind) {
|
|
1507
|
+
options?.beforeContentBlock?.(delta.kind);
|
|
1508
|
+
if (delta.kind === "text") {
|
|
1509
|
+
currentBlock = {
|
|
1510
|
+
type: "text",
|
|
1511
|
+
text: ""
|
|
1512
|
+
};
|
|
1513
|
+
currentTextSource = delta.source;
|
|
1514
|
+
if (delta.source === "reasoning_detail") (explicitVisibleTextBlocks ??= /* @__PURE__ */ new Set()).add(currentBlock);
|
|
1515
|
+
} else currentBlock = {
|
|
1541
1516
|
type: "thinking",
|
|
1542
1517
|
thinking: "",
|
|
1543
|
-
...
|
|
1518
|
+
...delta.signature ? { thinkingSignature: delta.signature } : {}
|
|
1544
1519
|
};
|
|
1545
|
-
if (directMode)
|
|
1546
|
-
directTextBlock = null;
|
|
1547
|
-
directThinkingBlock = currentBlock;
|
|
1548
|
-
}
|
|
1520
|
+
if (directMode) directContent.block = currentBlock;
|
|
1549
1521
|
output.content.push(currentBlock);
|
|
1550
|
-
|
|
1522
|
+
blockIndices.set(currentBlock, output.content.length - 1);
|
|
1551
1523
|
pushStreamEvent({
|
|
1552
|
-
type:
|
|
1524
|
+
type: `${delta.kind}_start`,
|
|
1553
1525
|
contentIndex: blockIndex(),
|
|
1554
1526
|
partial: output
|
|
1555
1527
|
});
|
|
1556
1528
|
}
|
|
1557
|
-
appendAssistantThinking(currentBlock,
|
|
1558
|
-
|
|
1559
|
-
|
|
1529
|
+
if (currentBlock.type === "thinking") appendAssistantThinking(currentBlock, delta.text);
|
|
1530
|
+
else {
|
|
1531
|
+
currentBlock.text += delta.text;
|
|
1532
|
+
if (pendingInterruptedTextBlock && delta.text.trim()) {
|
|
1533
|
+
confirmedInterruptedTextBlock = pendingInterruptedTextBlock;
|
|
1534
|
+
pendingInterruptedTextBlock = null;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
const event = {
|
|
1560
1538
|
contentIndex: blockIndex(),
|
|
1561
|
-
delta:
|
|
1539
|
+
delta: delta.text
|
|
1540
|
+
};
|
|
1541
|
+
if (delta.kind === "thinking") pushStreamEvent({
|
|
1542
|
+
type: "thinking_delta",
|
|
1543
|
+
...event,
|
|
1562
1544
|
partial: output
|
|
1563
1545
|
});
|
|
1564
|
-
|
|
1565
|
-
const appendTextDeltaInternal = (text, source) => {
|
|
1566
|
-
if (directMode && directTextBlock) currentBlock = directTextBlock;
|
|
1567
|
-
if (currentBlock?.type === "text" && currentTextSource !== source) currentBlock = null;
|
|
1568
|
-
if (!currentBlock || currentBlock.type !== "text") {
|
|
1569
|
-
options?.beforeContentBlock?.("text");
|
|
1570
|
-
currentBlock = {
|
|
1571
|
-
type: "text",
|
|
1572
|
-
text: ""
|
|
1573
|
-
};
|
|
1574
|
-
currentTextSource = source;
|
|
1575
|
-
if (directMode) {
|
|
1576
|
-
directTextBlock = currentBlock;
|
|
1577
|
-
directThinkingBlock = null;
|
|
1578
|
-
}
|
|
1579
|
-
if (source === "reasoning_detail") (explicitVisibleTextBlocks ??= /* @__PURE__ */ new Set()).add(currentBlock);
|
|
1580
|
-
output.content.push(currentBlock);
|
|
1581
|
-
contentBlockIndices.set(currentBlock, output.content.length - 1);
|
|
1582
|
-
pushStreamEvent({
|
|
1583
|
-
type: "text_start",
|
|
1584
|
-
contentIndex: blockIndex(),
|
|
1585
|
-
partial: output
|
|
1586
|
-
});
|
|
1587
|
-
}
|
|
1588
|
-
currentBlock.text += text;
|
|
1589
|
-
if (pendingInterruptedTextBlock && text.trim()) {
|
|
1590
|
-
confirmedInterruptedTextBlock = pendingInterruptedTextBlock;
|
|
1591
|
-
pendingInterruptedTextBlock = null;
|
|
1592
|
-
}
|
|
1593
|
-
pushStreamEvent({
|
|
1546
|
+
else pushStreamEvent({
|
|
1594
1547
|
type: "text_delta",
|
|
1595
|
-
|
|
1596
|
-
delta: text,
|
|
1548
|
+
...event,
|
|
1597
1549
|
...directMode ? { partial: output } : {}
|
|
1598
1550
|
});
|
|
1599
1551
|
};
|
|
1600
1552
|
const flushPendingPostToolCallDeltas = () => {
|
|
1601
|
-
if (
|
|
1602
|
-
isFlushingPendingPostToolCallDeltas = true;
|
|
1553
|
+
if (currentBlock?.type === "toolCall" || pendingPostToolCallDeltas.length === 0) return;
|
|
1603
1554
|
const bufferedDeltas = pendingPostToolCallDeltas;
|
|
1604
1555
|
pendingPostToolCallDeltas = [];
|
|
1605
1556
|
pendingPostToolCallBytes = 0;
|
|
1606
|
-
for (const delta of bufferedDeltas) if (delta.kind === "text")
|
|
1607
|
-
else if (emitReasoning) appendThinkingDeltaInternal(delta);
|
|
1608
|
-
isFlushingPendingPostToolCallDeltas = false;
|
|
1609
|
-
};
|
|
1610
|
-
const appendThinkingDelta = (reasoningDelta) => {
|
|
1611
|
-
flushPendingPostToolCallDeltas();
|
|
1612
|
-
appendThinkingDeltaInternal(reasoningDelta);
|
|
1613
|
-
};
|
|
1614
|
-
const appendTextDelta = (text, source) => {
|
|
1615
|
-
flushPendingPostToolCallDeltas();
|
|
1616
|
-
appendTextDeltaInternal(text, source);
|
|
1557
|
+
for (const delta of bufferedDeltas) if (delta.kind === "text" || emitReasoning) appendContentDelta(delta);
|
|
1617
1558
|
};
|
|
1618
1559
|
const appendVisibleTextDelta = (text) => {
|
|
1619
1560
|
if (!text) return;
|
|
@@ -1621,20 +1562,22 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1621
1562
|
kind: "text",
|
|
1622
1563
|
text
|
|
1623
1564
|
});
|
|
1624
|
-
else
|
|
1565
|
+
else appendContentDelta({
|
|
1566
|
+
kind: "text",
|
|
1567
|
+
text
|
|
1568
|
+
});
|
|
1625
1569
|
};
|
|
1626
1570
|
const appendReasoningDeltas = (reasoningDeltas) => {
|
|
1627
|
-
for (const
|
|
1628
|
-
if (
|
|
1571
|
+
for (const delta of reasoningDeltas) {
|
|
1572
|
+
if (delta.kind === "thinking" && !emitReasoning) continue;
|
|
1629
1573
|
if (currentBlock?.type === "toolCall" && !directMode) {
|
|
1630
|
-
queuePostToolCallDelta({ ...
|
|
1574
|
+
queuePostToolCallDelta({ ...delta });
|
|
1631
1575
|
continue;
|
|
1632
1576
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
...reasoningDelta,
|
|
1577
|
+
appendContentDelta(delta.kind === "thinking" && directMode && model.provider === "opencode-go" && delta.signature === "reasoning" ? {
|
|
1578
|
+
...delta,
|
|
1636
1579
|
signature: "reasoning_content"
|
|
1637
|
-
} :
|
|
1580
|
+
} : delta);
|
|
1638
1581
|
}
|
|
1639
1582
|
};
|
|
1640
1583
|
const appendRecoveredToolCall = (toolCall) => {
|
|
@@ -1650,18 +1593,11 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1650
1593
|
arguments: toolCall.arguments,
|
|
1651
1594
|
partialArgs: toolCall.partialArgs
|
|
1652
1595
|
};
|
|
1653
|
-
toolArgumentPreviewSchedules.set(block, createToolArgumentPreviewSchedule());
|
|
1654
1596
|
currentBlock = block;
|
|
1655
|
-
|
|
1656
|
-
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
1657
|
-
pushStreamEvent({
|
|
1658
|
-
type: "toolcall_start",
|
|
1659
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
1660
|
-
partial: output
|
|
1661
|
-
});
|
|
1597
|
+
appendToolCallBlock(block);
|
|
1662
1598
|
pushStreamEvent({
|
|
1663
1599
|
type: "toolcall_delta",
|
|
1664
|
-
contentIndex:
|
|
1600
|
+
contentIndex: blockIndices.get(block) ?? -1,
|
|
1665
1601
|
delta: toolCall.partialArgs,
|
|
1666
1602
|
partial: output
|
|
1667
1603
|
});
|
|
@@ -1676,24 +1612,25 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1676
1612
|
for (const part of parts) appendVisibleTextDelta(part);
|
|
1677
1613
|
}
|
|
1678
1614
|
};
|
|
1679
|
-
const
|
|
1680
|
-
appendRecoveredParts(deepSeekToolCallRecoverer?.push(text) ?? [{
|
|
1615
|
+
const appendPartitionedVisibleDelta = (delta) => {
|
|
1616
|
+
if (delta.kind === "text") appendRecoveredParts(deepSeekToolCallRecoverer?.push(delta.text) ?? [{
|
|
1681
1617
|
kind: "text",
|
|
1682
|
-
text
|
|
1618
|
+
text: delta.text
|
|
1683
1619
|
}]);
|
|
1684
1620
|
};
|
|
1685
|
-
const appendPartitionedVisibleDelta = (delta) => {
|
|
1686
|
-
if (delta.kind === "text") appendFilteredVisibleTextDelta(delta.text);
|
|
1687
|
-
};
|
|
1688
1621
|
const emitReasoningUsageActivity = (hasReasoningUsageActivity) => {
|
|
1689
1622
|
if (directMode || !hasReasoningUsageActivity || chunkPushedEvent || !emitReasoning) return;
|
|
1690
1623
|
const latestBlock = output.content[output.content.length - 1];
|
|
1691
1624
|
if (currentBlock?.type === "text" || currentBlock?.type === "toolCall") return;
|
|
1692
1625
|
if (latestBlock?.type === "text" || latestBlock?.type === "toolCall") return;
|
|
1693
|
-
|
|
1626
|
+
appendContentDelta({
|
|
1627
|
+
kind: "thinking",
|
|
1628
|
+
text: ""
|
|
1629
|
+
});
|
|
1694
1630
|
};
|
|
1695
1631
|
const flushReasoningTagTextPartitioner = () => {
|
|
1696
|
-
|
|
1632
|
+
const recoverUnclosed = output.stopReason !== "length" && output.stopReason !== "error" && output.stopReason !== "aborted";
|
|
1633
|
+
for (const delta of reasoningTagTextPartitioner.flush({ recoverUnclosed })) appendPartitionedVisibleDelta(delta);
|
|
1697
1634
|
};
|
|
1698
1635
|
const flushGemmaToolCallRecoverer = (allowRecovery = true) => {
|
|
1699
1636
|
for (const part of gemmaToolCallRecoverer?.flush(allowRecovery) ?? []) if (part.kind === "toolCall") appendRecoveredToolCall(part);
|
|
@@ -1705,7 +1642,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1705
1642
|
if (currentBlock?.type !== "text") return;
|
|
1706
1643
|
if (currentTextSource !== "reasoning_detail" && currentBlock.text.trim()) pendingInterruptedTextBlock = currentBlock;
|
|
1707
1644
|
currentBlock = null;
|
|
1708
|
-
if (directMode)
|
|
1645
|
+
if (directMode) directContent.block = null;
|
|
1709
1646
|
currentTextSource = void 0;
|
|
1710
1647
|
};
|
|
1711
1648
|
const beginReasoning = (hasFollowingVisibleText, forceStrict = false) => {
|
|
@@ -1731,32 +1668,25 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1731
1668
|
throwIfModelStreamAborted(options?.signal);
|
|
1732
1669
|
chunkPushedEvent = false;
|
|
1733
1670
|
if (!rawChunk || typeof rawChunk !== "object") continue;
|
|
1734
|
-
notifyLlmRequestActivity(options?.signal);
|
|
1735
1671
|
const chunk = rawChunk;
|
|
1736
1672
|
output.responseId ||= chunk.id;
|
|
1737
1673
|
if (typeof chunk.model === "string" && chunk.model.length > 0 && chunk.model !== model.id) output.responseModel ||= chunk.model;
|
|
1738
|
-
let hasReasoningUsageActivity = false;
|
|
1739
|
-
if (chunk.usage) {
|
|
1740
|
-
output.usage = parseOpenAICompletionsUsage(chunk.usage, model, { includeReasoningTokens: !directMode });
|
|
1741
|
-
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(chunk.usage);
|
|
1742
|
-
}
|
|
1743
1674
|
const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : void 0;
|
|
1675
|
+
const usage = chunk.usage || choice?.usage;
|
|
1676
|
+
const hasReasoningUsageActivity = Boolean(asPositiveFiniteNumber(usage?.completion_tokens_details?.reasoning_tokens));
|
|
1677
|
+
if (usage) output.usage = parseOpenAICompletionsUsage(usage, model, { includeReasoningTokens: !directMode });
|
|
1678
|
+
const rawChoiceDelta = choice?.delta ?? choice?.message;
|
|
1679
|
+
notifyLlmRequestActivity(options?.signal, Boolean(usage || choice?.finish_reason || rawChoiceDelta && (rawChoiceDelta.tool_calls?.length || hasOpenAICompletionsDeltaContent(rawChoiceDelta))));
|
|
1744
1680
|
if (!choice) {
|
|
1745
1681
|
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
1746
1682
|
continue;
|
|
1747
1683
|
}
|
|
1748
|
-
const choiceUsage = choice.usage;
|
|
1749
|
-
if (!chunk.usage && choiceUsage) {
|
|
1750
|
-
output.usage = parseOpenAICompletionsUsage(choiceUsage, model, { includeReasoningTokens: !directMode });
|
|
1751
|
-
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(choiceUsage);
|
|
1752
|
-
}
|
|
1753
1684
|
if (choice.finish_reason) {
|
|
1754
1685
|
const finishReasonResult = mapOpenAIStopReason(choice.finish_reason, { allowSingularToolCall: true });
|
|
1755
1686
|
output.stopReason = finishReasonResult.stopReason;
|
|
1756
1687
|
finishReason = finishReasonResult.stopReason;
|
|
1757
1688
|
if (finishReasonResult.errorMessage) output.errorMessage = finishReasonResult.errorMessage;
|
|
1758
1689
|
}
|
|
1759
|
-
const rawChoiceDelta = choice.delta ?? choice.message;
|
|
1760
1690
|
if (!rawChoiceDelta) {
|
|
1761
1691
|
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
1762
1692
|
continue;
|
|
@@ -1784,7 +1714,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1784
1714
|
beginReasoning(contentDeltaIndex < lastVisibleTextIndex);
|
|
1785
1715
|
if (emitReasoning) {
|
|
1786
1716
|
if (currentBlock?.type === "toolCall" && !directMode) queuePostToolCallDelta(contentDelta);
|
|
1787
|
-
else
|
|
1717
|
+
else appendContentDelta(contentDelta);
|
|
1788
1718
|
}
|
|
1789
1719
|
}
|
|
1790
1720
|
if (!hasReasoningThinking) appendReasoningDeltas(reasoningDeltas);
|
|
@@ -1805,7 +1735,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1805
1735
|
}
|
|
1806
1736
|
const initialSig = directMode ? void 0 : extractToolCallThoughtSignature(toolCall);
|
|
1807
1737
|
options?.beforeContentBlock?.("toolCall");
|
|
1808
|
-
if (directMode)
|
|
1738
|
+
if (directMode && directContent.block?.type === "thinking") directContent.block = null;
|
|
1809
1739
|
block = {
|
|
1810
1740
|
type: "toolCall",
|
|
1811
1741
|
id: toolCall.id || "",
|
|
@@ -1816,13 +1746,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1816
1746
|
};
|
|
1817
1747
|
encryptedReasoning.rememberToolCall(block.id, block);
|
|
1818
1748
|
toolArgumentPreviewSchedules.set(block, createToolArgumentPreviewSchedule());
|
|
1819
|
-
|
|
1820
|
-
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
1821
|
-
pushStreamEvent({
|
|
1822
|
-
type: "toolcall_start",
|
|
1823
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
1824
|
-
partial: output
|
|
1825
|
-
});
|
|
1749
|
+
appendToolCallBlock(block);
|
|
1826
1750
|
}
|
|
1827
1751
|
if (streamIndex !== void 0 && !toolCallBlocksByIndex.has(streamIndex)) toolCallBlocksByIndex.set(streamIndex, block);
|
|
1828
1752
|
if (toolCall.id) {
|
|
@@ -1843,7 +1767,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1843
1767
|
}
|
|
1844
1768
|
if (toolArgumentsDelta || directMode) pushStreamEvent({
|
|
1845
1769
|
type: "toolcall_delta",
|
|
1846
|
-
contentIndex:
|
|
1770
|
+
contentIndex: blockIndices.get(block) ?? -1,
|
|
1847
1771
|
delta: toolArgumentsDelta ?? "",
|
|
1848
1772
|
partial: output
|
|
1849
1773
|
});
|
|
@@ -1865,7 +1789,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1865
1789
|
finalizeOpenAICompletionsToolCalls(output, {
|
|
1866
1790
|
allowSilentToolCallPromotion: finishReason === "stop" || sawNativeToolCallDelta && (options?.sawStreamDONE?.() ?? false),
|
|
1867
1791
|
onConfirmedToolCall(block, contentIndex) {
|
|
1868
|
-
if (directMode
|
|
1792
|
+
if (directMode) return;
|
|
1869
1793
|
pushStreamEvent({
|
|
1870
1794
|
type: "toolcall_end",
|
|
1871
1795
|
contentIndex,
|
|
@@ -1878,15 +1802,1203 @@ async function processCompletionsStream(responseStream, output, model, stream, o
|
|
|
1878
1802
|
if (output.stopReason !== "toolUse") clearPendingCommentaryText(provisionalCommentaryTags);
|
|
1879
1803
|
if (output.stopReason === "error" || output.stopReason === "aborted") tagUnresolvedTextAsCommentary(output);
|
|
1880
1804
|
if (output.stopReason === "toolUse") tagPendingCommentaryText(output.content);
|
|
1805
|
+
if (!output.usage.contextUsage && !options?.signal?.aborted && output.stopReason !== "error" && output.stopReason !== "aborted") {
|
|
1806
|
+
output.usage.contextUsage = { state: "unavailable" };
|
|
1807
|
+
if (!compat.supportsUsageInStreaming) warnMissingStreamingUsage(openAIModel);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const MAX_MISSING_USAGE_HINT_KEYS = 256;
|
|
1811
|
+
const missingUsageHintKeys = /* @__PURE__ */ new Set();
|
|
1812
|
+
function warnMissingStreamingUsage(model) {
|
|
1813
|
+
if (detectOpenAICompletionsCompat(model).capabilities.endpointClass !== "custom") return;
|
|
1814
|
+
const key = `${model.provider}/${model.id}`;
|
|
1815
|
+
if (missingUsageHintKeys.has(key) || missingUsageHintKeys.size >= MAX_MISSING_USAGE_HINT_KEYS) return;
|
|
1816
|
+
missingUsageHintKeys.add(key);
|
|
1817
|
+
log.warn(`${key} returned no token usage; context size is estimated and token accounting is unavailable. If this endpoint supports stream_options.include_usage, set compat.supportsUsageInStreaming: true on the model in models.providers.${model.provider}.models.`, {
|
|
1818
|
+
provider: model.provider,
|
|
1819
|
+
model: model.id
|
|
1820
|
+
});
|
|
1881
1821
|
}
|
|
1882
1822
|
function shouldEmitOpenAICompletionsReasoning(model, options) {
|
|
1883
1823
|
if (!model.reasoning) return false;
|
|
1884
1824
|
const effort = options?.reasoningEffort ?? options?.reasoning ?? "high";
|
|
1885
1825
|
return Boolean(effort) && isOpenAICompletionsThinkingEnabled(effort);
|
|
1886
1826
|
}
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1827
|
+
//#endregion
|
|
1828
|
+
//#region packages/ai/src/providers/openai-web-search-tools.ts
|
|
1829
|
+
function hasResponsesWebSearchTool(tools) {
|
|
1830
|
+
if (!Array.isArray(tools)) return false;
|
|
1831
|
+
return tools.some((tool) => {
|
|
1832
|
+
if (!isRecord(tool)) return false;
|
|
1833
|
+
if (tool.type === "web_search") return true;
|
|
1834
|
+
if (tool.type === "function" && tool.name === "web_search") return true;
|
|
1835
|
+
const fn = tool.function;
|
|
1836
|
+
return isRecord(fn) && fn.name === "web_search";
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
//#endregion
|
|
1840
|
+
//#region packages/ai/src/transports/openai-responses-params-internal.ts
|
|
1841
|
+
const OPENAI_RESPONSES_TOOL_CALL_PROVIDERS = /* @__PURE__ */ new Set([
|
|
1842
|
+
"openai",
|
|
1843
|
+
"opencode",
|
|
1844
|
+
"azure-openai-responses",
|
|
1845
|
+
"github-copilot"
|
|
1846
|
+
]);
|
|
1847
|
+
function raiseMinimalReasoningForResponsesWebSearch(params) {
|
|
1848
|
+
if (params.effort !== "minimal" || !hasResponsesWebSearchTool(params.tools)) return params.effort;
|
|
1849
|
+
for (const effort of [
|
|
1850
|
+
"low",
|
|
1851
|
+
"medium",
|
|
1852
|
+
"high"
|
|
1853
|
+
]) {
|
|
1854
|
+
const resolved = resolveOpenAIRequestReasoning(params.model, effort).effort;
|
|
1855
|
+
if (resolved && resolved !== "none" && resolved !== "minimal") return resolved;
|
|
1856
|
+
}
|
|
1857
|
+
return params.effort;
|
|
1858
|
+
}
|
|
1859
|
+
const OPENAI_CODEX_RESPONSES_UNSUPPORTED_PARAMS = [
|
|
1860
|
+
"max_output_tokens",
|
|
1861
|
+
"metadata",
|
|
1862
|
+
"prompt_cache_retention",
|
|
1863
|
+
"prompt_cache_options",
|
|
1864
|
+
"service_tier",
|
|
1865
|
+
"temperature",
|
|
1866
|
+
"top_p"
|
|
1867
|
+
];
|
|
1868
|
+
function stripOpenAICodexResponsesUnsupportedTextFields(params) {
|
|
1869
|
+
const text = params.text;
|
|
1870
|
+
if (!text || typeof text !== "object" || Array.isArray(text)) return;
|
|
1871
|
+
const sanitizedText = { ...text };
|
|
1872
|
+
delete sanitizedText.format;
|
|
1873
|
+
if (Object.keys(sanitizedText).length > 0) params.text = sanitizedText;
|
|
1874
|
+
else delete params.text;
|
|
1875
|
+
}
|
|
1876
|
+
function sanitizeOpenAICodexResponsesParams(model, params) {
|
|
1877
|
+
if (!usesNativeOpenAICodexResponsesBackend(model)) return params;
|
|
1878
|
+
for (const key of OPENAI_CODEX_RESPONSES_UNSUPPORTED_PARAMS) delete params[key];
|
|
1879
|
+
Object.assign(params, { store: false });
|
|
1880
|
+
stripOpenAICodexResponsesUnsupportedTextFields(params);
|
|
1881
|
+
return params;
|
|
1882
|
+
}
|
|
1883
|
+
function buildOpenAIResponsesInstructionsText(context) {
|
|
1884
|
+
if (!context.systemPrompt) return;
|
|
1885
|
+
return sanitizeTransportPayloadText(stripSystemPromptCacheBoundary(context.systemPrompt));
|
|
1886
|
+
}
|
|
1887
|
+
function resolveOpenAIResponsesInstructions(model, context, usesInstructionsField) {
|
|
1888
|
+
if (!usesInstructionsField) return;
|
|
1889
|
+
const instructions = buildOpenAIResponsesInstructionsText(context);
|
|
1890
|
+
if (instructions && instructions.trim().length > 0) return instructions;
|
|
1891
|
+
return usesNativeOpenAICodexResponsesBackend(model) ? OPENAI_CODEX_RESPONSES_DEFAULT_INSTRUCTIONS : void 0;
|
|
1892
|
+
}
|
|
1893
|
+
function buildOpenAIResponsesCompactSystemMessage(model, instructions) {
|
|
1894
|
+
const compat = getCompat(model);
|
|
1895
|
+
const role = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
|
|
1896
|
+
return buildResponsesInputMessage(role, [{
|
|
1897
|
+
type: "input_text",
|
|
1898
|
+
text: instructions
|
|
1899
|
+
}]);
|
|
1900
|
+
}
|
|
1901
|
+
function ensureOpenAIResponsesNonEmptyInput(messages, context) {
|
|
1902
|
+
if (messages.length > 0 || !context.systemPrompt) return;
|
|
1903
|
+
if (!buildOpenAIResponsesInstructionsText(context)) throw new Error("OpenAI Responses requires non-empty input when only systemPrompt is provided.");
|
|
1904
|
+
messages.push(buildResponsesInputMessage("user", [{
|
|
1905
|
+
type: "input_text",
|
|
1906
|
+
text: " "
|
|
1907
|
+
}]));
|
|
1908
|
+
}
|
|
1909
|
+
function buildOpenAIResponsesParams(model, context, options, metadata, replayMode = "checkpoint") {
|
|
1910
|
+
const payloadPolicy = resolveOpenAIResponsesPayloadPolicy(model, { storeMode: "transport-default" });
|
|
1911
|
+
const policyAllowsReplayIds = payloadPolicy.explicitStore !== false && !payloadPolicy.shouldStripStore;
|
|
1912
|
+
const replayResponsesItemIds = !usesNativeOpenAICodexResponsesBackend(model) && (options?.replayResponsesItemIds ?? policyAllowsReplayIds);
|
|
1913
|
+
const messages = convertResponsesMessages(model, context, OPENAI_RESPONSES_TOOL_CALL_PROVIDERS, {
|
|
1914
|
+
includeSystemPrompt: !payloadPolicy.usesInstructionsField,
|
|
1915
|
+
replayReasoningItems: true,
|
|
1916
|
+
replayResponsesItemIds,
|
|
1917
|
+
authProfileId: options?.authProfileId,
|
|
1918
|
+
sessionId: options?.sessionId,
|
|
1919
|
+
replayMode
|
|
1920
|
+
});
|
|
1921
|
+
ensureOpenAIResponsesNonEmptyInput(messages, context);
|
|
1922
|
+
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
1923
|
+
const compat = getCompat(model);
|
|
1924
|
+
const promptCacheKey = compat.supportsPromptCacheKey ? resolvePromptCacheKey(options, cacheRetention) : void 0;
|
|
1925
|
+
const instructions = resolveOpenAIResponsesInstructions(model, context, payloadPolicy.usesInstructionsField);
|
|
1926
|
+
const params = {
|
|
1927
|
+
model: model.id,
|
|
1928
|
+
input: messages,
|
|
1929
|
+
stream: true,
|
|
1930
|
+
prompt_cache_key: promptCacheKey,
|
|
1931
|
+
...resolveOpenAIPromptCacheParams(model, cacheRetention, compat),
|
|
1932
|
+
...instructions ? { instructions } : {},
|
|
1933
|
+
...metadata ? { metadata } : {}
|
|
1934
|
+
};
|
|
1935
|
+
const effectiveMaxTokens = options?.maxTokens || model.maxTokens;
|
|
1936
|
+
if (effectiveMaxTokens) params.max_output_tokens = Math.max(effectiveMaxTokens, 16);
|
|
1937
|
+
if (options?.temperature !== void 0 && supportsOpenAITemperature(model)) params.temperature = options.temperature;
|
|
1938
|
+
if (options?.topP !== void 0 && (!isOpenAIGpt6Model(model) || resolveOpenAIThinkingApi(model.api) === "azure-openai-responses")) params.top_p = options.topP;
|
|
1939
|
+
if (options?.responseFormat !== void 0) params.text = {
|
|
1940
|
+
...params.text,
|
|
1941
|
+
format: resolveOpenAIResponsesTextFormat(options.responseFormat)
|
|
1942
|
+
};
|
|
1943
|
+
if (options?.serviceTier !== void 0 && payloadPolicy.allowsServiceTier) params.service_tier = options.serviceTier;
|
|
1944
|
+
if (context.tools) {
|
|
1945
|
+
const tools = context.tools;
|
|
1946
|
+
const strict = getAiTransportHost().resolveOpenAIStrictToolSetting(model, {
|
|
1947
|
+
transport: "stream",
|
|
1948
|
+
supportsStrictMode: compat.supportsStrictMode
|
|
1949
|
+
});
|
|
1950
|
+
const { projection, tools: converted } = prepareResponsesTools(tools, strict, model);
|
|
1951
|
+
if (converted.length > 0 || projection.inputToolCount === 0 && projection.diagnostics.length === 0) params.tools = converted;
|
|
1952
|
+
if (options?.toolChoice) {
|
|
1953
|
+
const toolChoice = reconcileOpenAIResponsesToolChoice(options.toolChoice, projection);
|
|
1954
|
+
if (toolChoice !== void 0) params.tool_choice = toolChoice;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
if (model.reasoning) {
|
|
1958
|
+
const reasoning = options?.reasoning;
|
|
1959
|
+
const requestedEffort = options?.reasoningEffort ?? (reasoning === "none" ? "none" : resolveOpenAISimpleReasoningEffort(model, reasoning)) ?? (options?.reasoningSummary ? "high" : payloadPolicy.defaultManagedReasoningEffort);
|
|
1960
|
+
const resolvedEffort = requestedEffort === void 0 ? void 0 : resolveOpenAIRequestReasoning(model, requestedEffort).effort;
|
|
1961
|
+
if (resolvedEffort !== void 0) {
|
|
1962
|
+
const effort = raiseMinimalReasoningForResponsesWebSearch({
|
|
1963
|
+
model,
|
|
1964
|
+
effort: resolvedEffort,
|
|
1965
|
+
tools: params.tools
|
|
1966
|
+
});
|
|
1967
|
+
const summary = effort !== "none" && (options?.reasoningEffort || options?.reasoning || options?.reasoningSummary) ? options.reasoningSummary || "auto" : void 0;
|
|
1968
|
+
params.reasoning = {
|
|
1969
|
+
effort,
|
|
1970
|
+
...summary ? { summary } : {}
|
|
1971
|
+
};
|
|
1972
|
+
if (summary) params.include = ["reasoning.encrypted_content"];
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
applyOpenAIResponsesPayloadPolicy(params, payloadPolicy);
|
|
1976
|
+
return sanitizeOpenAICodexResponsesParams(model, params);
|
|
1977
|
+
}
|
|
1978
|
+
//#endregion
|
|
1979
|
+
//#region packages/ai/src/transports/openai-responses-compact-client.ts
|
|
1980
|
+
async function postOpenAIResponsesCompaction(params) {
|
|
1981
|
+
const compactInput = typeof params.request.instructions === "string" && params.request.instructions.length > 0 ? [buildOpenAIResponsesCompactSystemMessage(params.model, params.request.instructions), ...params.request.input ?? []] : params.request.input;
|
|
1982
|
+
const response = await params.client.post("/responses/compact", {
|
|
1983
|
+
...buildOpenAISdkRequestOptions(params.model, params.options?.signal, { timeoutMs: params.options?.timeoutMs }),
|
|
1984
|
+
body: {
|
|
1985
|
+
model: params.request.model,
|
|
1986
|
+
input: compactInput
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
const output = isRecord(response) && Array.isArray(response.output) ? response.output : [];
|
|
1990
|
+
const item = output.at(-1);
|
|
1991
|
+
const retainedItems = output.slice(0, -1);
|
|
1992
|
+
const retainedUserMessageCount = retainedItems.filter((candidate) => isRecord(candidate) && candidate.type === "message" && candidate.role === "user" && Array.isArray(candidate.content)).length;
|
|
1993
|
+
const inputUserMessageCount = Array.isArray(params.request.input) ? params.request.input.filter((candidate) => isRecord(candidate) && candidate.type === "message" && candidate.role === "user").length : 0;
|
|
1994
|
+
const retainedMessagePrefixSupported = supportsNativeOpenAIResponsesEndpoint(params.model);
|
|
1995
|
+
const usage = isRecord(response) && isRecord(response.usage) ? response.usage : void 0;
|
|
1996
|
+
if (!isRecord(response) || response.object !== "response.compaction" || !isOpenAIResponsesCompactionOutput(output, params.model) || retainedItems.length > 0 && (!retainedMessagePrefixSupported || retainedUserMessageCount !== inputUserMessageCount) || !isRecord(item) || item.type !== "compaction" || typeof item.encrypted_content !== "string" || item.encrypted_content.length === 0 || !usage || typeof usage.input_tokens !== "number" || typeof usage.output_tokens !== "number") throw new Error("Responses compact endpoint did not return one trailing compaction item");
|
|
1997
|
+
return {
|
|
1998
|
+
output,
|
|
1999
|
+
item,
|
|
2000
|
+
historyMode: retainedUserMessageCount > 0 ? "retained-users" : "compacted-prefix",
|
|
2001
|
+
usage,
|
|
2002
|
+
model: params.model,
|
|
2003
|
+
replayMetadata: buildOpenAIResponsesReasoningReplayMetadata(params.model, {
|
|
2004
|
+
authProfileId: params.options?.authProfileId,
|
|
2005
|
+
sessionId: params.options?.sessionId
|
|
2006
|
+
})
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
//#endregion
|
|
2010
|
+
//#region packages/ai/src/transports/openai-responses-compact-request.ts
|
|
2011
|
+
const COMPACT_REQUEST = Symbol("openaiResponsesCompactRequest");
|
|
2012
|
+
function claimResponsesCompactRequest(options) {
|
|
2013
|
+
const controller = options ? Reflect.get(options, COMPACT_REQUEST) : void 0;
|
|
2014
|
+
if (controller?.claimed === false) {
|
|
2015
|
+
controller.claimed = true;
|
|
2016
|
+
return controller;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
/** Run a compact-endpoint request through the session's prepared stream stack. */
|
|
2020
|
+
async function requestPreparedOpenAIResponsesCompaction(streamFn, model, context, options) {
|
|
2021
|
+
const preparedOptions = { ...options };
|
|
2022
|
+
let resolveResult;
|
|
2023
|
+
let rejectResult;
|
|
2024
|
+
const result = new Promise((resolve, reject) => {
|
|
2025
|
+
resolveResult = resolve;
|
|
2026
|
+
rejectResult = reject;
|
|
2027
|
+
});
|
|
2028
|
+
const controller = {
|
|
2029
|
+
claimed: false,
|
|
2030
|
+
resolve: resolveResult,
|
|
2031
|
+
reject: rejectResult
|
|
2032
|
+
};
|
|
2033
|
+
Reflect.set(preparedOptions, COMPACT_REQUEST, controller);
|
|
2034
|
+
const stream = await Promise.resolve(streamFn(model, context, preparedOptions));
|
|
2035
|
+
if (!controller.claimed) throw new Error("Prepared stream did not reach an OpenAI Responses transport");
|
|
2036
|
+
try {
|
|
2037
|
+
return await result;
|
|
2038
|
+
} finally {
|
|
2039
|
+
await stream.result().catch(() => void 0);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
//#endregion
|
|
2043
|
+
//#region packages/ai/src/transports/openai-responses-reasoning-state.ts
|
|
2044
|
+
/** Save only admitted settings and hashes, never another copy of the conversation. */
|
|
2045
|
+
function recordResponsesReasoningState(message, model, identity, request, output) {
|
|
2046
|
+
if (!supportsResponsesReasoningUpdate(request) || !isRecord(request.reasoning) || !request.input || request.previous_response_id || message.providerReplay || output.some((item) => isRecord(item) && item.type === "compaction")) return;
|
|
2047
|
+
const reasoning = {
|
|
2048
|
+
...buildProviderReplayContext(model, identity),
|
|
2049
|
+
effort: request.reasoning.effort,
|
|
2050
|
+
controls: request.input.flatMap((item, index) => isConfigurationUpdate(item) ? [{
|
|
2051
|
+
index,
|
|
2052
|
+
item
|
|
2053
|
+
}] : []),
|
|
2054
|
+
inputLength: request.input.length,
|
|
2055
|
+
outputLength: output.length,
|
|
2056
|
+
prefixHash: responsesContinuationPrefixFingerprint(request.input, output),
|
|
2057
|
+
requestHash: responsesContinuationRequestFingerprint(request)
|
|
2058
|
+
};
|
|
2059
|
+
Object.assign(message, { openclawResponsesInputReplay: {
|
|
2060
|
+
...readResponsesInputReplayState(message),
|
|
2061
|
+
reasoning
|
|
2062
|
+
} });
|
|
2063
|
+
}
|
|
2064
|
+
/** A cold transport can replay controls, but cannot resurrect a server response handle. */
|
|
2065
|
+
function restoreResponsesReasoningState(context, model, identity, request) {
|
|
2066
|
+
const latest = context.messages.findLast((message) => message.role === "assistant");
|
|
2067
|
+
const state = latest ? readResponsesInputReplayState(latest)?.reasoning : void 0;
|
|
2068
|
+
if (!isRecord(state)) return request;
|
|
2069
|
+
const { effort, inputLength, outputLength, controls, prefixHash, requestHash } = state;
|
|
2070
|
+
if (!isProviderReplayContext(state) || !providerReplayContextMatches(state, buildProviderReplayContext(model, identity)) || latest?.providerReplay || !supportsResponsesReasoningUpdate(request) || !isRecord(request.reasoning) || !request.input || request.previous_response_id || request.input.some(isConfigurationUpdate) || typeof effort !== "string" || typeof inputLength !== "number" || !Number.isSafeInteger(inputLength) || inputLength < 0 || typeof outputLength !== "number" || !Number.isSafeInteger(outputLength) || outputLength < 0 || !Array.isArray(controls) || controls.length > inputLength || inputLength + outputLength > request.input.length + controls.length) return request;
|
|
2071
|
+
const previousInput = request.input.slice(0, inputLength - controls.length);
|
|
2072
|
+
let lastIndex = -1;
|
|
2073
|
+
for (const control of controls) {
|
|
2074
|
+
if (!isRecord(control) || typeof control.index !== "number" || !Number.isSafeInteger(control.index) || control.index <= lastIndex || control.index > previousInput.length || !isConfigurationUpdate(control.item)) return request;
|
|
2075
|
+
previousInput.splice(control.index, 0, control.item);
|
|
2076
|
+
lastIndex = control.index;
|
|
2077
|
+
}
|
|
2078
|
+
const previous = {
|
|
2079
|
+
...request,
|
|
2080
|
+
reasoning: {
|
|
2081
|
+
...request.reasoning,
|
|
2082
|
+
effort
|
|
2083
|
+
},
|
|
2084
|
+
input: previousInput
|
|
2085
|
+
};
|
|
2086
|
+
if (responsesContinuationRequestFingerprint(previous) !== requestHash) return request;
|
|
2087
|
+
const prepared = replayResponsesReasoningUpdates(previous, request, outputLength);
|
|
2088
|
+
if (responsesContinuationPrefixFingerprint((prepared.input ?? []).slice(0, inputLength + outputLength)) !== prefixHash) return request;
|
|
2089
|
+
return prepared;
|
|
2090
|
+
}
|
|
2091
|
+
//#endregion
|
|
2092
|
+
//#region packages/ai/src/transports/openai-responses-request-fetch.ts
|
|
2093
|
+
function withDefaultResponsesStreamEncoding(fetch) {
|
|
2094
|
+
return (input, init) => {
|
|
2095
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
2096
|
+
if (headers.has("accept-encoding")) return fetch(input, init);
|
|
2097
|
+
headers.set("accept-encoding", "identity");
|
|
2098
|
+
return fetch(input, {
|
|
2099
|
+
...init,
|
|
2100
|
+
headers
|
|
2101
|
+
});
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
/** Preserve the existing fetch policy while binding explicit continuation dispatch to its signal. */
|
|
2105
|
+
function createResponsesRequestFetch(model, options) {
|
|
2106
|
+
const lifecycle = options.lifecycle;
|
|
2107
|
+
if (lifecycle && options.compact) throw new Error("Provider review continuation cannot replace its reviewed input with compaction");
|
|
2108
|
+
let fetchOverride = options.compact ? createBoundedOpenAIResponsesCompactionFetch(buildGuardedModelFetch(model)) : options.stream ? withDefaultResponsesStreamEncoding(buildGuardedModelFetch(model, void 0, { onSseComment: options.onSseComment })) : void 0;
|
|
2109
|
+
if (lifecycle) {
|
|
2110
|
+
const dispatchFetch = fetchOverride ?? buildGuardedModelFetch(model);
|
|
2111
|
+
fetchOverride = async (input, init) => {
|
|
2112
|
+
const signal = init?.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
2113
|
+
await lifecycle.beforeDispatch(signal ?? void 0);
|
|
2114
|
+
signal?.throwIfAborted();
|
|
2115
|
+
lifecycle.assertCurrent();
|
|
2116
|
+
return dispatchFetch(input, init);
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
return fetchOverride;
|
|
2120
|
+
}
|
|
2121
|
+
//#endregion
|
|
2122
|
+
//#region packages/ai/src/transports/openai-responses-steering.ts
|
|
2123
|
+
function cloneWireRequest(request) {
|
|
2124
|
+
const serialized = JSON.stringify(request);
|
|
2125
|
+
return JSON.parse(serialized);
|
|
2126
|
+
}
|
|
2127
|
+
async function projectResponsesSteeringInput(request, project) {
|
|
2128
|
+
const { input: activeInput, ...activeSettings } = cloneWireRequest(request);
|
|
2129
|
+
if (!Array.isArray(activeInput)) return [];
|
|
2130
|
+
const { input, ...settings } = cloneWireRequest(await project());
|
|
2131
|
+
if (!Array.isArray(input) || stableStringify(settings) !== stableStringify(activeSettings) || stableStringify(input.slice(0, activeInput.length)) !== stableStringify(activeInput)) return [];
|
|
2132
|
+
const projected = input.slice(activeInput.length);
|
|
2133
|
+
return projected.every((item) => isRecord(item) && item.role === "user") ? projected : [];
|
|
2134
|
+
}
|
|
2135
|
+
/** One response owns admission; accepted input stays on this connection until continuation. */
|
|
2136
|
+
function createResponsesSteering(params) {
|
|
2137
|
+
let responseId;
|
|
2138
|
+
let sealed = false;
|
|
2139
|
+
let unsubscribe;
|
|
2140
|
+
const pending = [];
|
|
2141
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
2142
|
+
const acknowledged = /* @__PURE__ */ new Set();
|
|
2143
|
+
const seal = () => {
|
|
2144
|
+
sealed = true;
|
|
2145
|
+
const cleanup = unsubscribe;
|
|
2146
|
+
unsubscribe = void 0;
|
|
2147
|
+
cleanup?.();
|
|
2148
|
+
};
|
|
2149
|
+
return {
|
|
2150
|
+
get responseId() {
|
|
2151
|
+
return responseId;
|
|
2152
|
+
},
|
|
2153
|
+
get pending() {
|
|
2154
|
+
return pending.length > 0;
|
|
2155
|
+
},
|
|
2156
|
+
get acceptedInput() {
|
|
2157
|
+
return [...accepted.values()].flat();
|
|
2158
|
+
},
|
|
2159
|
+
seal,
|
|
2160
|
+
close(error) {
|
|
2161
|
+
seal();
|
|
2162
|
+
for (const submission of pending.splice(0)) submission.reject(error);
|
|
2163
|
+
},
|
|
2164
|
+
handle(event) {
|
|
2165
|
+
if (!isRecord(event)) return false;
|
|
2166
|
+
if (event.type === "response.created" && !responseId && !sealed) {
|
|
2167
|
+
const response = isRecord(event.response) ? event.response : void 0;
|
|
2168
|
+
if (typeof response?.id !== "string" || !response.id.trim()) throw new Error("Responses steering requires a response identity");
|
|
2169
|
+
const activeResponseId = response.id;
|
|
2170
|
+
responseId = activeResponseId;
|
|
2171
|
+
const cleanup = params.onActiveResponse({
|
|
2172
|
+
needsContinuation: params.needsContinuation,
|
|
2173
|
+
steer(messages) {
|
|
2174
|
+
if (sealed || messages.length === 0) return Promise.resolve(false);
|
|
2175
|
+
params.assertActive();
|
|
2176
|
+
const converted = params.toInput(messages);
|
|
2177
|
+
const submit = (input) => {
|
|
2178
|
+
if (sealed || input.length === 0) return Promise.resolve(false);
|
|
2179
|
+
params.assertActive();
|
|
2180
|
+
return new Promise((resolve, reject) => {
|
|
2181
|
+
pending.push({
|
|
2182
|
+
input,
|
|
2183
|
+
resolve,
|
|
2184
|
+
reject
|
|
2185
|
+
});
|
|
2186
|
+
params.send({
|
|
2187
|
+
type: "response.steer",
|
|
2188
|
+
previous_response_id: activeResponseId,
|
|
2189
|
+
input
|
|
2190
|
+
});
|
|
2191
|
+
});
|
|
2192
|
+
};
|
|
2193
|
+
return Array.isArray(converted) ? submit(converted) : converted.then(submit);
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
if (sealed) cleanup?.();
|
|
2197
|
+
else unsubscribe = cleanup;
|
|
2198
|
+
return false;
|
|
2199
|
+
}
|
|
2200
|
+
if (event.type !== "response.steer.accepted" && event.type !== "response.steer.failed" && event.type !== "response.steer.pending") return false;
|
|
2201
|
+
const steer = isRecord(event.steer) ? event.steer : void 0;
|
|
2202
|
+
if (!responseId || steer?.previous_response_id !== responseId) throw new Error("Responses steering acknowledgement has an unexpected identity");
|
|
2203
|
+
if (event.type === "response.steer.failed" && steer.id === void 0) {
|
|
2204
|
+
const index = pending.findIndex((submission) => stableStringify(submission.input) === stableStringify(steer.input));
|
|
2205
|
+
const submission = pending[index];
|
|
2206
|
+
if (!submission) throw new Error("Responses steering acknowledgement has no pending submission");
|
|
2207
|
+
pending.splice(index, 1);
|
|
2208
|
+
submission.resolve(false);
|
|
2209
|
+
return true;
|
|
2210
|
+
}
|
|
2211
|
+
if (typeof steer.id !== "string" || !steer.id.trim()) throw new Error("Responses steering acknowledgement has an unexpected identity");
|
|
2212
|
+
if (event.type === "response.steer.pending") {
|
|
2213
|
+
if (!accepted.has(steer.id)) throw new Error("Responses steering pending event has no accepted submission");
|
|
2214
|
+
return true;
|
|
2215
|
+
}
|
|
2216
|
+
if (event.type === "response.steer.failed" && accepted.has(steer.id)) throw new Error("OpenAI could not apply accepted steering; the queued message remains in the conversation");
|
|
2217
|
+
if (acknowledged.has(steer.id)) throw new Error("Responses steering acknowledgement was already applied");
|
|
2218
|
+
const submission = pending.shift();
|
|
2219
|
+
if (!submission) throw new Error("Responses steering acknowledgement has no pending submission");
|
|
2220
|
+
acknowledged.add(steer.id);
|
|
2221
|
+
if (event.type === "response.steer.accepted") {
|
|
2222
|
+
accepted.set(steer.id, submission.input);
|
|
2223
|
+
submission.resolve(true);
|
|
2224
|
+
} else submission.resolve(false);
|
|
2225
|
+
return true;
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
/** Accepted steering is prepended by the server, so never repeat it in response.create. */
|
|
2230
|
+
function omitAcceptedSteering(input, accepted) {
|
|
2231
|
+
const remaining = [...input];
|
|
2232
|
+
for (const message of accepted) {
|
|
2233
|
+
const index = remaining.findIndex((item) => stableStringify(item) === stableStringify(message));
|
|
2234
|
+
if (index < 0) throw new Error("Responses steering continuation no longer contains the accepted user input");
|
|
2235
|
+
remaining.splice(index, 1);
|
|
2236
|
+
}
|
|
2237
|
+
return remaining;
|
|
2238
|
+
}
|
|
2239
|
+
//#endregion
|
|
2240
|
+
//#region packages/ai/src/transports/openai-responses-websocket-recovery.ts
|
|
2241
|
+
/** Owns safe WebSocket rejection recovery; transport selection and session leases stay with the caller. */
|
|
2242
|
+
function createRecoverableResponsesWebSocketStream(params) {
|
|
2243
|
+
const { trackedWebSocketStream, websocket, websocketSignal, options, output, buildRequest, createSseStream, closeWebSocketForFallback, logWebSocketFallback, startStream } = params;
|
|
2244
|
+
return { async *[Symbol.asyncIterator]() {
|
|
2245
|
+
let acceptance = "pending";
|
|
2246
|
+
let outputObserved = false;
|
|
2247
|
+
try {
|
|
2248
|
+
for await (const event of trackedWebSocketStream) {
|
|
2249
|
+
const { failure, hasResponseOutput, isPrelude } = readResponsesRecoveryEvent(event);
|
|
2250
|
+
outputObserved ||= hasResponseOutput;
|
|
2251
|
+
if (isResponsesServiceTierRejection(failure) && !websocket.hasActiveResponse) throw new OpenAIResponsesWebSocketSafeRetryError("invalid_request_error", void 0, "service_tier", "Invalid service_tier argument", failure);
|
|
2252
|
+
outputObserved ||= !isPrelude;
|
|
2253
|
+
if (acceptance === "pending") {
|
|
2254
|
+
acceptance = "observing";
|
|
2255
|
+
await notifyProviderStreamOpened({
|
|
2256
|
+
options,
|
|
2257
|
+
cancelStream: () => websocket.finish({ keep: false })
|
|
2258
|
+
});
|
|
2259
|
+
acceptance = "accepted";
|
|
2260
|
+
}
|
|
2261
|
+
startStream();
|
|
2262
|
+
yield event;
|
|
2263
|
+
}
|
|
2264
|
+
} catch (error) {
|
|
2265
|
+
if (error instanceof OpenAIResponsesWebSocketSafeRetryError) {
|
|
2266
|
+
if (isResponsesServiceTierRejection(error)) {
|
|
2267
|
+
const nextTier = nextResponsesServiceTier(websocket.request.service_tier, error);
|
|
2268
|
+
if (!nextTier || outputObserved || acceptance === "observing" || output.content.length || websocketSignal.aborted || websocket.hasActiveResponse) throw error;
|
|
2269
|
+
closeWebSocketForFallback("service_tier_rejected");
|
|
2270
|
+
const rejectedTier = websocket.request.service_tier;
|
|
2271
|
+
if (rejectedTier === "ultrafast" || rejectedTier === "priority") responsesServiceTierObserver.reject(options, rejectedTier);
|
|
2272
|
+
yield* await createSseStream({
|
|
2273
|
+
...await buildRequest("full-history"),
|
|
2274
|
+
service_tier: nextTier
|
|
2275
|
+
});
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
const encryptedContentRejected = isInvalidEncryptedContentError(error);
|
|
2279
|
+
const recovery = encryptedContentRejected ? await resolveNextResponsesEncryptedContentAttempt({
|
|
2280
|
+
kind: "initial",
|
|
2281
|
+
request: {
|
|
2282
|
+
...websocket.request,
|
|
2283
|
+
stream: true
|
|
2284
|
+
}
|
|
2285
|
+
}, error, { buildFullHistoryRequest: () => buildRequest("full-history") }) : void 0;
|
|
2286
|
+
if (encryptedContentRejected && !recovery) throw error;
|
|
2287
|
+
closeWebSocketForFallback(`safe_server_error code=${error.code} status=${safeDebugValue(error.status)} param=${safeDebugValue(error.param)}`);
|
|
2288
|
+
yield* await createSseStream(recovery?.request ?? await buildRequest("full-history"), recovery?.kind ?? "continuation-rejected", recovery?.rejectedCompaction);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
if (websocketSignal.aborted || !(error instanceof OpenAIResponsesWebSocketPreDispatchError)) throw error;
|
|
2292
|
+
params.setTransportToSse();
|
|
2293
|
+
logWebSocketFallback("before_first_event");
|
|
2294
|
+
yield* await createSseStream();
|
|
2295
|
+
}
|
|
2296
|
+
} };
|
|
2297
|
+
}
|
|
2298
|
+
//#endregion
|
|
2299
|
+
//#region packages/ai/src/transports/openai-responses-websocket.ts
|
|
2300
|
+
const SESSION_WEBSOCKET_CACHE_TTL_MS = 3e5;
|
|
2301
|
+
const SESSION_WEBSOCKET_MAX_AGE_MS = 33e5;
|
|
2302
|
+
const WEBSOCKET_OPEN_STATE = 1;
|
|
2303
|
+
const websocketSessionCache = /* @__PURE__ */ new Map();
|
|
2304
|
+
const degradedWebSocketConnections = /* @__PURE__ */ new Map();
|
|
2305
|
+
function closeWebSocketSilently(socket, reason = "done") {
|
|
2306
|
+
try {
|
|
2307
|
+
socket.close({
|
|
2308
|
+
code: 1e3,
|
|
2309
|
+
reason
|
|
2310
|
+
});
|
|
2311
|
+
} catch {}
|
|
2312
|
+
}
|
|
2313
|
+
function invalidateOwnedWebSocketSession(cacheKey, entry, reason = "done") {
|
|
2314
|
+
if (entry.idleTimer) {
|
|
2315
|
+
clearTimeout(entry.idleTimer);
|
|
2316
|
+
entry.idleTimer = void 0;
|
|
2317
|
+
}
|
|
2318
|
+
closeWebSocketSilently(entry.socket, reason);
|
|
2319
|
+
entry.steeringContinuation?.steering.close(/* @__PURE__ */ new Error("Responses steering connection closed"));
|
|
2320
|
+
entry.steeringContinuation?.iterator.return?.().catch(() => void 0);
|
|
2321
|
+
entry.steeringContinuation = void 0;
|
|
2322
|
+
if (websocketSessionCache.get(cacheKey) === entry) websocketSessionCache.delete(cacheKey);
|
|
2323
|
+
}
|
|
2324
|
+
function scheduleSessionWebSocketExpiry(cacheKey, entry) {
|
|
2325
|
+
if (entry.idleTimer) clearTimeout(entry.idleTimer);
|
|
2326
|
+
entry.idleTimer = setTimeout(() => {
|
|
2327
|
+
if (entry.busy) return;
|
|
2328
|
+
invalidateOwnedWebSocketSession(cacheKey, entry, "idle_timeout");
|
|
2329
|
+
}, SESSION_WEBSOCKET_CACHE_TTL_MS);
|
|
2330
|
+
entry.idleTimer.unref?.();
|
|
2331
|
+
}
|
|
2332
|
+
function prepareWebSocketConnection(client, headers) {
|
|
2333
|
+
if (!isOfficialOpenAIResponsesBaseUrl(client.baseURL)) throw new Error("OpenAI Responses WebSocket requires the official API endpoint");
|
|
2334
|
+
if (typeof client.apiKey !== "string" || client.apiKey.length === 0) throw new Error("OpenAI Responses WebSocket requires an API key");
|
|
2335
|
+
const owner = getAiTransportHost();
|
|
2336
|
+
const resolvedApiKey = owner.resolveSecretSentinel(client.apiKey);
|
|
2337
|
+
const resolvedHeaders = { ...resolveAiTransportHeaderSentinels(headers) };
|
|
2338
|
+
for (const key of Object.keys(resolvedHeaders)) {
|
|
2339
|
+
const normalizedKey = key.toLowerCase();
|
|
2340
|
+
if (normalizedKey === "authorization" || normalizedKey === "traceparent") delete resolvedHeaders[key];
|
|
2341
|
+
}
|
|
2342
|
+
if (!resolvedApiKey) throw new Error("OpenAI Responses WebSocket requires a resolved API key");
|
|
2343
|
+
return {
|
|
2344
|
+
client: client.withOptions({ apiKey: resolvedApiKey }),
|
|
2345
|
+
headers: resolvedHeaders,
|
|
2346
|
+
owner,
|
|
2347
|
+
ownerId: getSessionResourceOwnerId(owner),
|
|
2348
|
+
identity: sha256Hex(JSON.stringify([
|
|
2349
|
+
resolvedApiKey,
|
|
2350
|
+
client.baseURL,
|
|
2351
|
+
Object.entries(resolvedHeaders).toSorted(([a], [b]) => a.localeCompare(b))
|
|
2352
|
+
]))
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
function createWebSocket(connection, onError) {
|
|
2356
|
+
const socket = new ResponsesWS(connection.client, {
|
|
2357
|
+
headers: connection.headers,
|
|
2358
|
+
maxQueueSize: 1
|
|
2359
|
+
});
|
|
2360
|
+
socket.on("error", () => onError(socket));
|
|
2361
|
+
return socket;
|
|
2362
|
+
}
|
|
2363
|
+
function createTransientWebSocketLease(connection) {
|
|
2364
|
+
const socket = createWebSocket(connection, (failedSocket) => closeWebSocketSilently(failedSocket, "transport_error"));
|
|
2365
|
+
return {
|
|
2366
|
+
socket,
|
|
2367
|
+
iterator: socket.stream(),
|
|
2368
|
+
reusedConnection: false,
|
|
2369
|
+
release: () => closeWebSocketSilently(socket)
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
function createCachedWebSocketLease(cacheKey, entry, reusedConnection) {
|
|
2373
|
+
entry.busy = true;
|
|
2374
|
+
const steeringContinuation = entry.steeringContinuation;
|
|
2375
|
+
entry.steeringContinuation = void 0;
|
|
2376
|
+
return {
|
|
2377
|
+
socket: entry.socket,
|
|
2378
|
+
iterator: steeringContinuation?.iterator ?? entry.socket.stream(),
|
|
2379
|
+
entry,
|
|
2380
|
+
reusedConnection,
|
|
2381
|
+
steeringContinuation,
|
|
2382
|
+
release: ({ keep } = {}) => {
|
|
2383
|
+
if (!keep || entry.socket.socket.readyState !== WEBSOCKET_OPEN_STATE) {
|
|
2384
|
+
invalidateOwnedWebSocketSession(cacheKey, entry);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
entry.busy = false;
|
|
2388
|
+
scheduleSessionWebSocketExpiry(cacheKey, entry);
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
function acquireWebSocket(params, connection) {
|
|
2393
|
+
if (params.mode === "websocket" || !params.sessionId) return createTransientWebSocketLease(connection);
|
|
2394
|
+
const cacheKey = `${connection.ownerId}\0${params.sessionId}\0${connection.identity}`;
|
|
2395
|
+
const cached = websocketSessionCache.get(cacheKey);
|
|
2396
|
+
if (cached) {
|
|
2397
|
+
if (cached.idleTimer) {
|
|
2398
|
+
clearTimeout(cached.idleTimer);
|
|
2399
|
+
cached.idleTimer = void 0;
|
|
2400
|
+
}
|
|
2401
|
+
if (cached.busy) return createTransientWebSocketLease(connection);
|
|
2402
|
+
const expired = Date.now() - cached.createdAt >= SESSION_WEBSOCKET_MAX_AGE_MS;
|
|
2403
|
+
if (!expired && cached.socket.socket.readyState === WEBSOCKET_OPEN_STATE) return createCachedWebSocketLease(cacheKey, cached, true);
|
|
2404
|
+
invalidateOwnedWebSocketSession(cacheKey, cached, expired ? "connection_age_limit" : "done");
|
|
2405
|
+
}
|
|
2406
|
+
const entry = {
|
|
2407
|
+
socket: createWebSocket(connection, (failedSocket) => {
|
|
2408
|
+
const failedEntry = websocketSessionCache.get(cacheKey);
|
|
2409
|
+
if (failedEntry?.socket === failedSocket) invalidateOwnedWebSocketSession(cacheKey, failedEntry, "transport_error");
|
|
2410
|
+
else closeWebSocketSilently(failedSocket, "transport_error");
|
|
2411
|
+
}),
|
|
2412
|
+
sessionId: params.sessionId,
|
|
2413
|
+
owner: connection.owner,
|
|
2414
|
+
busy: true,
|
|
2415
|
+
createdAt: Date.now()
|
|
2416
|
+
};
|
|
2417
|
+
websocketSessionCache.set(cacheKey, entry);
|
|
2418
|
+
return createCachedWebSocketLease(cacheKey, entry, false);
|
|
2419
|
+
}
|
|
2420
|
+
function sanitizeWebSocketRequest(request) {
|
|
2421
|
+
const { stream: _stream, background: _background, ...websocketRequest } = request;
|
|
2422
|
+
return websocketRequest;
|
|
2423
|
+
}
|
|
2424
|
+
async function nextWebSocketMessage(iterator, signal) {
|
|
2425
|
+
if (signal?.aborted) throw transportAbortError(signal);
|
|
2426
|
+
return await racePromiseWithAbortSignal(iterator.next(), signal, transportAbortError);
|
|
2427
|
+
}
|
|
2428
|
+
function readServerEvent(message) {
|
|
2429
|
+
if (message.type === "message") return message.message;
|
|
2430
|
+
if (message.type === "error") throw parseOpenAIResponsesWebSocketServerError(message.error) ?? new Error("OpenAI Responses WebSocket transport failed", { cause: message.error });
|
|
2431
|
+
if (message.type === "close") throw new Error(`OpenAI Responses WebSocket closed before completion (code ${message.code})`);
|
|
2432
|
+
}
|
|
2433
|
+
function createOpenAIResponsesWebSocketStream(params) {
|
|
2434
|
+
const connection = prepareWebSocketConnection(params.client, params.headers);
|
|
2435
|
+
let fullRequest = sanitizeWebSocketRequest(params.request);
|
|
2436
|
+
const requestModel = typeof fullRequest.model === "string" ? fullRequest.model : "";
|
|
2437
|
+
const degradationKey = `${connection.ownerId}\0${params.sessionId ?? ""}\0${connection.identity}\0${requestModel}`;
|
|
2438
|
+
const degraded = degradedWebSocketConnections.get(degradationKey);
|
|
2439
|
+
if (degraded && degraded.retryAt > Date.now()) throw new OpenAIResponsesWebSocketPreDispatchError(/* @__PURE__ */ new Error("OpenAI Responses WebSocket is cooling down after a transport failure"));
|
|
2440
|
+
if (degraded) clearTimeout(degraded.expiryTimer);
|
|
2441
|
+
degradedWebSocketConnections.delete(degradationKey);
|
|
2442
|
+
const markDegraded = () => {
|
|
2443
|
+
const cooldownMs = params.degradeCooldownMs;
|
|
2444
|
+
if (cooldownMs === void 0 || !Number.isFinite(cooldownMs) || cooldownMs <= 0) return;
|
|
2445
|
+
const entry = {
|
|
2446
|
+
sessionId: params.sessionId,
|
|
2447
|
+
owner: connection.owner,
|
|
2448
|
+
retryAt: Date.now() + cooldownMs,
|
|
2449
|
+
expiryTimer: setTimeout(() => {
|
|
2450
|
+
if (degradedWebSocketConnections.get(degradationKey) === entry) degradedWebSocketConnections.delete(degradationKey);
|
|
2451
|
+
}, cooldownMs)
|
|
2452
|
+
};
|
|
2453
|
+
entry.expiryTimer.unref?.();
|
|
2454
|
+
degradedWebSocketConnections.set(degradationKey, entry);
|
|
2455
|
+
};
|
|
2456
|
+
let lease;
|
|
2457
|
+
try {
|
|
2458
|
+
lease = acquireWebSocket(params, connection);
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
markDegraded();
|
|
2461
|
+
throw new OpenAIResponsesWebSocketPreDispatchError(error);
|
|
2462
|
+
}
|
|
2463
|
+
let prepared;
|
|
2464
|
+
let previousContinuation;
|
|
2465
|
+
const resumedSteering = lease.steeringContinuation;
|
|
2466
|
+
const steeringMode = resumedSteering ? resumedSteering.requiresInput ? "required-input" : "automatic" : void 0;
|
|
2467
|
+
try {
|
|
2468
|
+
const continuation = previousContinuation = lease.entry?.continuation;
|
|
2469
|
+
if (continuation && lease.entry) {
|
|
2470
|
+
lease.entry.continuation = void 0;
|
|
2471
|
+
prepared = resolveResponsesContinuationRequest(continuation, fullRequest, steeringMode);
|
|
2472
|
+
} else {
|
|
2473
|
+
fullRequest = params.restoreRequest?.(fullRequest) ?? fullRequest;
|
|
2474
|
+
prepared = {
|
|
2475
|
+
request: fullRequest,
|
|
2476
|
+
continuationStatus: lease.entry ? "no_previous_response" : "socket_not_cached"
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
} catch (error) {
|
|
2480
|
+
lease.iterator.return?.().catch(() => void 0);
|
|
2481
|
+
lease.release({ keep: false });
|
|
2482
|
+
throw error;
|
|
2483
|
+
}
|
|
2484
|
+
let streamStarted = false;
|
|
2485
|
+
let terminalResponse;
|
|
2486
|
+
let terminalReceived = false;
|
|
2487
|
+
let released = false;
|
|
2488
|
+
let retainIterator = false;
|
|
2489
|
+
let deferredInput = false;
|
|
2490
|
+
let inputReplay;
|
|
2491
|
+
if (resumedSteering) try {
|
|
2492
|
+
if (prepared.continuationStatus !== "continued" || !prepared.request.previous_response_id) throw new Error("Responses steering continuation changed its request or history");
|
|
2493
|
+
const input = omitAcceptedSteering(prepared.request.input ?? [], resumedSteering.acceptedInput);
|
|
2494
|
+
if (!resumedSteering.requiresInput && (stableStringify(fullRequest.instructions) !== stableStringify(resumedSteering.instructions) || stableStringify(fullRequest.tools) !== stableStringify(resumedSteering.tools))) throw new Error("Responses automatic steering continuation cannot change instructions or tools");
|
|
2495
|
+
const baseline = prepared.fullRequest ?? fullRequest;
|
|
2496
|
+
const priorLength = (baseline.input?.length ?? 0) - (prepared.request.input?.length ?? 0);
|
|
2497
|
+
const fingerprints = input.map(responsesInputFingerprint);
|
|
2498
|
+
if (fingerprints.length > 0) inputReplay = {
|
|
2499
|
+
afterResponseId: prepared.request.previous_response_id,
|
|
2500
|
+
before: resumedSteering.requiresInput ? fingerprints : [],
|
|
2501
|
+
after: resumedSteering.requiresInput ? [] : fingerprints
|
|
2502
|
+
};
|
|
2503
|
+
const delivered = resumedSteering.requiresInput ? input : [];
|
|
2504
|
+
prepared.fullRequest = {
|
|
2505
|
+
...baseline,
|
|
2506
|
+
input: [
|
|
2507
|
+
...(baseline.input ?? []).slice(0, priorLength),
|
|
2508
|
+
...resumedSteering.acceptedInput,
|
|
2509
|
+
...delivered
|
|
2510
|
+
]
|
|
2511
|
+
};
|
|
2512
|
+
prepared.request = {
|
|
2513
|
+
...prepared.request,
|
|
2514
|
+
input: delivered
|
|
2515
|
+
};
|
|
2516
|
+
deferredInput = !resumedSteering.requiresInput && input.length > 0;
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
lease.iterator.return?.().catch(() => void 0);
|
|
2519
|
+
lease.release({ keep: false });
|
|
2520
|
+
throw new OpenAIResponsesWebSocketPostDispatchError(error);
|
|
2521
|
+
}
|
|
2522
|
+
const steering = lease.entry && params.onActiveResponse && params.steeringInput ? createResponsesSteering({
|
|
2523
|
+
onActiveResponse: params.onActiveResponse,
|
|
2524
|
+
toInput: params.steeringInput,
|
|
2525
|
+
send: (event) => lease.socket.sendRaw(JSON.stringify(event)),
|
|
2526
|
+
needsContinuation: () => deferredInput,
|
|
2527
|
+
assertActive: () => {
|
|
2528
|
+
if (released || terminalReceived || params.signal?.aborted) throw new Error("Responses steering is no longer active");
|
|
2529
|
+
}
|
|
2530
|
+
}) : void 0;
|
|
2531
|
+
const finish = ({ keep = true } = {}) => {
|
|
2532
|
+
if (released) return;
|
|
2533
|
+
released = true;
|
|
2534
|
+
if (keep && lease.entry && terminalResponse) lease.entry.continuation = recordResponsesContinuationState(previousContinuation, prepared.fullRequest ?? fullRequest, terminalResponse, previousContinuation?.lastResponseId === prepared.request.previous_response_id);
|
|
2535
|
+
lease.release({ keep });
|
|
2536
|
+
};
|
|
2537
|
+
return {
|
|
2538
|
+
stream: { async *[Symbol.asyncIterator]() {
|
|
2539
|
+
if (streamStarted) throw new Error("OpenAI Responses WebSocket stream can only be consumed once");
|
|
2540
|
+
streamStarted = true;
|
|
2541
|
+
const iterator = lease.iterator;
|
|
2542
|
+
let requestDispatched = false;
|
|
2543
|
+
try {
|
|
2544
|
+
if (params.signal?.aborted) throw transportAbortError(params.signal);
|
|
2545
|
+
if (resumedSteering) {
|
|
2546
|
+
requestDispatched = true;
|
|
2547
|
+
if (resumedSteering.requiresInput) lease.socket.send({
|
|
2548
|
+
...prepared.request,
|
|
2549
|
+
type: "response.create"
|
|
2550
|
+
});
|
|
2551
|
+
}
|
|
2552
|
+
for (;;) {
|
|
2553
|
+
const buffered = resumedSteering?.buffered.shift();
|
|
2554
|
+
const next = buffered ? {
|
|
2555
|
+
value: buffered,
|
|
2556
|
+
done: false
|
|
2557
|
+
} : await nextWebSocketMessage(iterator, params.signal);
|
|
2558
|
+
if (next.done) throw new Error("OpenAI Responses WebSocket closed before a terminal response event");
|
|
2559
|
+
if (next.value.type === "open") {
|
|
2560
|
+
if (!requestDispatched) {
|
|
2561
|
+
requestDispatched = true;
|
|
2562
|
+
lease.socket.send({
|
|
2563
|
+
...prepared.request,
|
|
2564
|
+
type: "response.create"
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
const event = readServerEvent(next.value);
|
|
2570
|
+
if (!event) continue;
|
|
2571
|
+
if (isRecord(event) && typeof event.type === "string" && event.type.startsWith("response.steer.")) {
|
|
2572
|
+
if ((isRecord(event.steer) && event.steer.previous_response_id === steering?.responseId ? steering : resumedSteering?.steering ?? steering)?.handle(event)) continue;
|
|
2573
|
+
}
|
|
2574
|
+
steering?.handle(event);
|
|
2575
|
+
if (event.type === "response.completed") terminalResponse = event.response;
|
|
2576
|
+
terminalReceived = event.type === "response.completed" || event.type === "response.incomplete" || event.type === "response.failed";
|
|
2577
|
+
if (terminalReceived) {
|
|
2578
|
+
steering?.seal();
|
|
2579
|
+
if (event.type === "response.failed") steering?.close(/* @__PURE__ */ new Error("Responses failed before steering could be applied"));
|
|
2580
|
+
const continuationBuffer = [];
|
|
2581
|
+
for (;;) {
|
|
2582
|
+
if (!steering?.pending) break;
|
|
2583
|
+
const acknowledgement = await nextWebSocketMessage(iterator, params.signal);
|
|
2584
|
+
if (acknowledgement.done) throw new Error("Responses closed before acknowledging steering");
|
|
2585
|
+
const acknowledgedEvent = readServerEvent(acknowledgement.value);
|
|
2586
|
+
if (!steering.handle(acknowledgedEvent)) continuationBuffer.push(acknowledgement.value);
|
|
2587
|
+
}
|
|
2588
|
+
const acceptedInput = steering?.acceptedInput ?? [];
|
|
2589
|
+
const isSteered = event.type === "response.incomplete" && isRecord(event.response.incomplete_details) && event.response.incomplete_details.reason === "steered";
|
|
2590
|
+
if (lease.entry && steering && acceptedInput.length > 0 && (event.type === "response.completed" || isSteered)) {
|
|
2591
|
+
if (event.type === "response.incomplete") terminalResponse = event.response;
|
|
2592
|
+
lease.entry.steeringContinuation = {
|
|
2593
|
+
iterator,
|
|
2594
|
+
buffered: continuationBuffer,
|
|
2595
|
+
acceptedInput,
|
|
2596
|
+
requiresInput: (terminalResponse?.output ?? []).some((item) => (item.type === "function_call" || item.type === "custom_tool_call") && !(isRecord(item) && item.async === true) || item.type === "mcp_approval_request"),
|
|
2597
|
+
steering,
|
|
2598
|
+
instructions: fullRequest.instructions,
|
|
2599
|
+
tools: fullRequest.tools
|
|
2600
|
+
};
|
|
2601
|
+
retainIterator = true;
|
|
2602
|
+
if (isSteered) {
|
|
2603
|
+
yield {
|
|
2604
|
+
...event,
|
|
2605
|
+
type: "response.completed",
|
|
2606
|
+
response: {
|
|
2607
|
+
...event.response,
|
|
2608
|
+
status: "completed",
|
|
2609
|
+
incomplete_details: null
|
|
2610
|
+
}
|
|
2611
|
+
};
|
|
2612
|
+
return;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
yield event;
|
|
2617
|
+
if (terminalReceived) {
|
|
2618
|
+
degradedWebSocketConnections.delete(degradationKey);
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
const steeringDispatched = Boolean(steering?.pending || steering?.acceptedInput.length || resumedSteering);
|
|
2624
|
+
steering?.close(error instanceof Error ? error : /* @__PURE__ */ new Error("Responses steering failed"));
|
|
2625
|
+
if (lease.entry) lease.entry.continuation = void 0;
|
|
2626
|
+
const safeRetry = error instanceof OpenAIResponsesWebSocketSafeRetryError && !steeringDispatched;
|
|
2627
|
+
if (!params.callerSignal?.aborted && !safeRetry) markDegraded();
|
|
2628
|
+
if (!requestDispatched && !params.signal?.aborted) throw new OpenAIResponsesWebSocketPreDispatchError(error);
|
|
2629
|
+
if (!requestDispatched || params.callerSignal?.aborted || safeRetry) throw error;
|
|
2630
|
+
throw new OpenAIResponsesWebSocketPostDispatchError(error);
|
|
2631
|
+
} finally {
|
|
2632
|
+
steering?.seal();
|
|
2633
|
+
if (!retainIterator) {
|
|
2634
|
+
steering?.close(/* @__PURE__ */ new Error("Responses stream ended before steering was confirmed"));
|
|
2635
|
+
await iterator.return?.().catch(() => void 0);
|
|
2636
|
+
}
|
|
2637
|
+
if (!terminalReceived) finish({ keep: false });
|
|
2638
|
+
}
|
|
2639
|
+
} },
|
|
2640
|
+
request: prepared.request,
|
|
2641
|
+
fullRequest: prepared.fullRequest ?? fullRequest,
|
|
2642
|
+
reusedConnection: lease.reusedConnection,
|
|
2643
|
+
continuationStatus: prepared.continuationStatus,
|
|
2644
|
+
inputReplay,
|
|
2645
|
+
get hasActiveResponse() {
|
|
2646
|
+
return Boolean(steering?.responseId || resumedSteering);
|
|
2647
|
+
},
|
|
2648
|
+
finish
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
function closeOpenAIResponsesWebSocketSessions(sessionId, owner) {
|
|
2652
|
+
for (const [cacheKey, entry] of websocketSessionCache) {
|
|
2653
|
+
if (owner && entry.owner !== owner || sessionId && entry.sessionId !== sessionId) continue;
|
|
2654
|
+
invalidateOwnedWebSocketSession(cacheKey, entry, "session_cleanup");
|
|
2655
|
+
}
|
|
2656
|
+
for (const [key, entry] of degradedWebSocketConnections) if ((!owner || entry.owner === owner) && (!sessionId || entry.sessionId === sessionId)) {
|
|
2657
|
+
clearTimeout(entry.expiryTimer);
|
|
2658
|
+
degradedWebSocketConnections.delete(key);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
registerSessionResourceCleanup(closeOpenAIResponsesWebSocketSessions);
|
|
2662
|
+
//#endregion
|
|
2663
|
+
//#region packages/ai/src/transports/openai-responses-client.ts
|
|
2664
|
+
function resolveNativeOpenAIResponsesWebSocketMode(model, transport) {
|
|
2665
|
+
if (transport !== "websocket" && transport !== "websocket-cached" && transport !== "auto") return;
|
|
2666
|
+
if (getAiTransportHost().requiresManagedTransport(model)) return;
|
|
2667
|
+
return supportsNativeOpenAIResponsesEndpoint(model) ? transport : void 0;
|
|
2668
|
+
}
|
|
2669
|
+
function combineWebSocketTimeoutSignal(signal, model, timeoutMs) {
|
|
2670
|
+
const resolvedTimeoutMs = timeoutMs !== void 0 && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : getAiTransportHost().resolveModelRequestTimeoutMs(model);
|
|
2671
|
+
if (resolvedTimeoutMs === void 0 || !Number.isFinite(resolvedTimeoutMs)) return signal;
|
|
2672
|
+
return AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, resolvedTimeoutMs))]);
|
|
2673
|
+
}
|
|
2674
|
+
function createOpenAIResponsesClient(model, apiKey, defaultHeaders, fetchOverride) {
|
|
2675
|
+
return new OpenAI({
|
|
2676
|
+
apiKey,
|
|
2677
|
+
baseURL: resolveOpenAIClientBaseUrl(model),
|
|
2678
|
+
dangerouslyAllowBrowser: true,
|
|
2679
|
+
defaultHeaders,
|
|
2680
|
+
fetch: fetchOverride ?? buildGuardedModelFetch(model),
|
|
2681
|
+
...buildOpenAISdkClientOptions(model)
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
function createResponsesTransportExecutor(config) {
|
|
2685
|
+
return (model, context, options) => {
|
|
2686
|
+
const responsesOptions = options;
|
|
2687
|
+
const compactRequest = claimResponsesCompactRequest(responsesOptions);
|
|
2688
|
+
const { eventStream, stream } = createWritableTransportEventStream();
|
|
2689
|
+
(async () => {
|
|
2690
|
+
const output = createAssistantOutput(model, config.outputApi);
|
|
2691
|
+
let firstEventAbort;
|
|
2692
|
+
let continuationClaim;
|
|
2693
|
+
const requestLifecycle = responsesRequestLifecycle.get(options);
|
|
2694
|
+
try {
|
|
2695
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
2696
|
+
const websocketMode = resolveNativeOpenAIResponsesWebSocketMode(model, responsesOptions?.transport);
|
|
2697
|
+
const encodeBody = prepareModelRequestBody(websocketMode || compactRequest ? void 0 : options);
|
|
2698
|
+
const turnState = resolveProviderTransportTurnState(model, {
|
|
2699
|
+
sessionId: options?.sessionId,
|
|
2700
|
+
turnId: randomUUID(),
|
|
2701
|
+
attempt: 1,
|
|
2702
|
+
transport: websocketMode ? "websocket" : "stream"
|
|
2703
|
+
});
|
|
2704
|
+
const websocketSessionPolicy = websocketMode ? turnState?.websocket : void 0;
|
|
2705
|
+
const httpTurnHeaders = filterProviderTurnHeadersForExplicitOpencodeSession(model, options, turnState?.headers);
|
|
2706
|
+
const websocketTurnHeaders = filterProviderTurnHeadersForExplicitOpencodeSession(model, options, websocketSessionPolicy?.headers);
|
|
2707
|
+
const websocketHeaders = websocketMode ? buildOpenAIClientHeaders(model, context, options?.headers, websocketTurnHeaders, options?.sessionId, options?.cacheRetention) : void 0;
|
|
2708
|
+
const httpHeaders = buildOpenAIClientHeaders(model, context, options?.headers, httpTurnHeaders, options?.sessionId, options?.cacheRetention);
|
|
2709
|
+
const fetchOverride = createResponsesRequestFetch(model, {
|
|
2710
|
+
compact: Boolean(compactRequest),
|
|
2711
|
+
stream: config.streamRequest,
|
|
2712
|
+
lifecycle: requestLifecycle,
|
|
2713
|
+
onSseComment: () => notifyLlmRequestActivity(options?.signal, false)
|
|
2714
|
+
});
|
|
2715
|
+
const client = config.createClient(model, apiKey, httpHeaders, fetchOverride);
|
|
2716
|
+
const nativeAstra = model.id === "gpt-6-astra" && supportsNativeOpenAIResponsesEndpoint(model);
|
|
2717
|
+
const asyncToolExecutionEligible = nativeAstra && options?.asyncToolExecution === true && !responsesOptions?.openclawCodeModeToolSurface;
|
|
2718
|
+
const prepareRequest = async (request) => {
|
|
2719
|
+
let params = request;
|
|
2720
|
+
const nextParams = await options?.onPayload?.(params, model);
|
|
2721
|
+
if (nextParams !== void 0) params = nextParams;
|
|
2722
|
+
params = sanitizeOpenAICodexResponsesParams(model, params);
|
|
2723
|
+
params = sanitizeResponsesImagePayload(params);
|
|
2724
|
+
if (responsesOptions?.openclawCodeModeToolSurface === true) {
|
|
2725
|
+
const visibleToolNames = resolveCodeModeResponsesVisibleToolNames(context);
|
|
2726
|
+
const allowedHostedToolTypes = responsesOptions?.openclawCodeModeAllowedHostedToolTypes;
|
|
2727
|
+
enforceCodeModeResponsesToolSurface(params, visibleToolNames, allowedHostedToolTypes, codeModeToolSurfaceObserver.get(options));
|
|
2728
|
+
assertCodeModeResponsesToolSurface(params, visibleToolNames, allowedHostedToolTypes);
|
|
2729
|
+
}
|
|
2730
|
+
if (asyncToolExecutionEligible && params.model === "gpt-6-astra" && params.multi_agent?.enabled !== true && params.tools) {
|
|
2731
|
+
const synchronousTools = new Set(context.tools?.flatMap((tool) => tool.async === false ? [tool.name] : []));
|
|
2732
|
+
params.tools = params.tools.map((tool) => tool.type === "function" && !synchronousTools.has(tool.name) ? {
|
|
2733
|
+
...tool,
|
|
2734
|
+
async: true
|
|
2735
|
+
} : tool);
|
|
2736
|
+
}
|
|
2737
|
+
return params;
|
|
2738
|
+
};
|
|
2739
|
+
const buildRequest = (replayMode, requestContext = context) => prepareRequest(config.buildRequest(model, requestContext, responsesOptions, turnState?.metadata, replayMode));
|
|
2740
|
+
let params = await buildRequest("checkpoint");
|
|
2741
|
+
const asyncTools = asyncToolExecutionEligible && params.model === "gpt-6-astra" && params.multi_agent?.enabled !== true;
|
|
2742
|
+
if (compactRequest) {
|
|
2743
|
+
const compacted = await postOpenAIResponsesCompaction({
|
|
2744
|
+
client,
|
|
2745
|
+
model,
|
|
2746
|
+
request: params,
|
|
2747
|
+
options: responsesOptions
|
|
2748
|
+
});
|
|
2749
|
+
output.usage.input = compacted.usage.input_tokens;
|
|
2750
|
+
output.usage.output = compacted.usage.output_tokens;
|
|
2751
|
+
output.usage.totalTokens = compacted.usage.input_tokens + compacted.usage.output_tokens;
|
|
2752
|
+
compactRequest.resolve(compacted);
|
|
2753
|
+
finalizeTransportStream({
|
|
2754
|
+
stream,
|
|
2755
|
+
output,
|
|
2756
|
+
signal: options?.signal
|
|
2757
|
+
});
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const sessionId = options?.sessionId;
|
|
2761
|
+
if (config.httpContinuation && !websocketMode && !getAiTransportHost().requiresManagedTransport(model) && (supportsNativeOpenAIResponsesEndpoint(model) || resolveOpenAIResponsesPayloadPolicy(model).explicitContinuationOptIn) && sessionId && (params.store === true || supportsResponsesReasoningUpdate(params)) && !params.previous_response_id) {
|
|
2762
|
+
continuationClaim = claimOpenAIResponsesHttpContinuation({
|
|
2763
|
+
sessionId,
|
|
2764
|
+
apiKey,
|
|
2765
|
+
baseUrl: model.baseUrl,
|
|
2766
|
+
headers: httpHeaders,
|
|
2767
|
+
request: params,
|
|
2768
|
+
restoreRequest: () => restoreResponsesReasoningState(context, model, responsesOptions, params)
|
|
2769
|
+
});
|
|
2770
|
+
if (continuationClaim) params = continuationClaim.fullRequest;
|
|
2771
|
+
}
|
|
2772
|
+
const observePrompt = createResponsesPromptEgressObserver(responsesOptions, context.systemPrompt);
|
|
2773
|
+
const requestStartedAt = Date.now();
|
|
2774
|
+
let started = false;
|
|
2775
|
+
const startStream = () => {
|
|
2776
|
+
if (!started) {
|
|
2777
|
+
started = true;
|
|
2778
|
+
stream.push({
|
|
2779
|
+
type: "start",
|
|
2780
|
+
partial: output
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
};
|
|
2784
|
+
const firstEvent = createFirstStreamEventAbortController(options?.signal);
|
|
2785
|
+
firstEventAbort = firstEvent;
|
|
2786
|
+
const requestOptions = buildOpenAISdkRequestOptions(model, firstEvent.signal, {
|
|
2787
|
+
stream: config.streamRequest,
|
|
2788
|
+
timeoutMs: options?.timeoutMs
|
|
2789
|
+
});
|
|
2790
|
+
const websocketSignal = combineWebSocketTimeoutSignal(firstEvent.signal, model, requestOptions?.timeout);
|
|
2791
|
+
emitModelTransportDebug(log, `[responses] start provider=${model.provider} api=${model.api} model=${model.id} requestIdHash=${redactIdentifier(options?.requestId, { len: 64 })} baseUrl=${formatModelTransportDebugBaseUrl(model.baseUrl)} timeoutMs=${safeDebugValue(requestOptions?.timeout)} apiKey=${apiKey ? "present" : "missing"} ${summarizeResponsesPayload(params)}`);
|
|
2792
|
+
const responseModelTracker = createResponseModelTracker(isOpenAICodexResponsesModel(model));
|
|
2793
|
+
let continuationBaseline;
|
|
2794
|
+
let dispatchedPreviousResponseId;
|
|
2795
|
+
let contextUsageEligible = true;
|
|
2796
|
+
let requestedTier;
|
|
2797
|
+
const createSseStream = async (initialRequest = continuationClaim?.request ?? params, initialAttemptKind = "initial", initialRejectedCompaction) => {
|
|
2798
|
+
const { stream: responseStream } = await createResponsesStreamWithRecovery({
|
|
2799
|
+
client,
|
|
2800
|
+
request: initialRequest,
|
|
2801
|
+
requestOptions,
|
|
2802
|
+
model,
|
|
2803
|
+
encodeBody,
|
|
2804
|
+
observePrompt,
|
|
2805
|
+
initialAttemptKind,
|
|
2806
|
+
initialRejectedCompaction,
|
|
2807
|
+
buildFullHistoryRequest: () => buildRequest("full-history"),
|
|
2808
|
+
onCompactionRejected: (checkpoint) => suppressOpenAIResponsesCompaction(output, model, responsesOptions, checkpoint),
|
|
2809
|
+
canRetryStream: () => output.content.length === 0,
|
|
2810
|
+
onServiceTierRejected: (tier) => responsesServiceTierObserver.reject(options, tier),
|
|
2811
|
+
wrapStream: ({ stream: rawResponseStream, response, attempt }) => {
|
|
2812
|
+
requestedTier = attempt.request.service_tier;
|
|
2813
|
+
contextUsageEligible &&= attempt.kind === "initial";
|
|
2814
|
+
dispatchedPreviousResponseId = attempt.request.previous_response_id;
|
|
2815
|
+
continuationBaseline = attempt.request.previous_response_id ? params : attempt.request;
|
|
2816
|
+
const trackedResponseStream = responseModelTracker.track(response, rawResponseStream);
|
|
2817
|
+
return withProviderResponseHook({
|
|
2818
|
+
stream: observeResponsesStream(trackedResponseStream, model, requestStartedAt),
|
|
2819
|
+
signal: firstEvent.signal,
|
|
2820
|
+
abort: firstEvent.abort,
|
|
2821
|
+
hook: createOpenAIProviderAcceptanceHook(options, response, model),
|
|
2822
|
+
onReady: () => {
|
|
2823
|
+
emitModelTransportDebug(log, `[responses] headers provider=${model.provider} api=${model.api} model=${model.id} transport=sse elapsedMs=${Date.now() - requestStartedAt}`);
|
|
2824
|
+
startStream();
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
return responseStream;
|
|
2830
|
+
};
|
|
2831
|
+
let responseStream;
|
|
2832
|
+
let websocketBaseline;
|
|
2833
|
+
let finishWebSocket;
|
|
2834
|
+
let transport = "sse";
|
|
2835
|
+
const logWebSocketFallback = (reason) => emitModelTransportDebug(log, `[responses] websocket_fallback provider=${model.provider} api=${model.api} model=${model.id} reason=${reason}`);
|
|
2836
|
+
const closeWebSocketForFallback = (reason) => {
|
|
2837
|
+
finishWebSocket?.({ keep: false });
|
|
2838
|
+
finishWebSocket = void 0;
|
|
2839
|
+
transport = "sse";
|
|
2840
|
+
logWebSocketFallback(reason);
|
|
2841
|
+
};
|
|
2842
|
+
if (websocketMode) try {
|
|
2843
|
+
const websocket = createOpenAIResponsesWebSocketStream({
|
|
2844
|
+
client,
|
|
2845
|
+
request: params,
|
|
2846
|
+
restoreRequest: (request) => restoreResponsesReasoningState(context, model, responsesOptions, request),
|
|
2847
|
+
mode: websocketMode,
|
|
2848
|
+
sessionId: options?.sessionId,
|
|
2849
|
+
headers: websocketHeaders,
|
|
2850
|
+
signal: websocketSignal,
|
|
2851
|
+
callerSignal: options?.signal,
|
|
2852
|
+
degradeCooldownMs: websocketSessionPolicy?.degradeCooldownMs,
|
|
2853
|
+
onActiveResponse: nativeAstra && params.model === "gpt-6-astra" ? options?.onActiveResponse : void 0,
|
|
2854
|
+
steeringInput: (messages) => {
|
|
2855
|
+
contextUsageEligible = false;
|
|
2856
|
+
return projectResponsesSteeringInput(params, () => buildRequest("checkpoint", {
|
|
2857
|
+
...context,
|
|
2858
|
+
messages: [...context.messages, ...messages]
|
|
2859
|
+
}));
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2862
|
+
finishWebSocket = websocket.finish;
|
|
2863
|
+
requestedTier = websocket.request.service_tier;
|
|
2864
|
+
websocketBaseline = websocket.fullRequest;
|
|
2865
|
+
recordResponsesInputReplay(output, websocket.inputReplay);
|
|
2866
|
+
contextUsageEligible &&= websocket.inputReplay === void 0;
|
|
2867
|
+
observePrompt?.(websocket.request, {
|
|
2868
|
+
egress: "responses-websocket",
|
|
2869
|
+
payloadVariant: "initial"
|
|
2870
|
+
});
|
|
2871
|
+
transport = "websocket";
|
|
2872
|
+
emitModelTransportDebug(log, `[responses] websocket_selected provider=${model.provider} api=${model.api} model=${model.id} mode=${websocketMode} reused=${websocket.reusedConnection} continuation=${websocket.continuationStatus === "continued"} continuationStatus=${websocket.continuationStatus} sessionIdHash=${redactIdentifier(options?.sessionId)} headersHash=${redactIdentifier(JSON.stringify(Object.entries(websocketHeaders ?? {}).toSorted(([a], [b]) => a.localeCompare(b))))}`);
|
|
2873
|
+
responseStream = createRecoverableResponsesWebSocketStream({
|
|
2874
|
+
trackedWebSocketStream: responseModelTracker.track(void 0, websocket.stream),
|
|
2875
|
+
websocket,
|
|
2876
|
+
websocketSignal,
|
|
2877
|
+
options,
|
|
2878
|
+
output,
|
|
2879
|
+
buildRequest,
|
|
2880
|
+
createSseStream,
|
|
2881
|
+
closeWebSocketForFallback,
|
|
2882
|
+
logWebSocketFallback,
|
|
2883
|
+
startStream,
|
|
2884
|
+
setTransportToSse: () => {
|
|
2885
|
+
transport = "sse";
|
|
2886
|
+
}
|
|
2887
|
+
});
|
|
2888
|
+
} catch (error) {
|
|
2889
|
+
if (error instanceof OpenAIResponsesWebSocketPostDispatchError) throw error;
|
|
2890
|
+
closeWebSocketForFallback("setup_failure");
|
|
2891
|
+
responseStream = await createSseStream();
|
|
2892
|
+
}
|
|
2893
|
+
else responseStream = await createSseStream();
|
|
2894
|
+
try {
|
|
2895
|
+
const acceptedResponseStream = withResponsesRequestAcceptance(responseStream, requestLifecycle, transport === "websocket" ? websocketSignal : firstEvent.signal);
|
|
2896
|
+
const terminal = await processResponsesStream(acceptedResponseStream, output, stream, model, {
|
|
2897
|
+
canRetryIdentityConflict: () => hasOnlyResponsesFunctionTools(transport === "websocket" ? websocketBaseline : continuationBaseline),
|
|
2898
|
+
...config.pricingOptions?.(responsesOptions, model),
|
|
2899
|
+
resolveServiceTier: (responseTier, originalTier) => responseTier ?? (requestedTier === "priority" || requestedTier === "default" ? requestedTier : originalTier),
|
|
2900
|
+
onServiceTier: (responseTier) => responsesServiceTierObserver.observe(options, requestedTier, responseTier),
|
|
2901
|
+
firstEventTimeoutMs: getFirstStreamEventTimeoutMs(options) ?? config.firstEventTimeoutMs,
|
|
2902
|
+
abortFirstEventStream: firstEvent.abort,
|
|
2903
|
+
onFirstEventTimeout: getFirstStreamEventTimeoutHandler(options),
|
|
2904
|
+
signal: options?.signal,
|
|
2905
|
+
reasoningReplayMetadata: buildOpenAIResponsesReasoningReplayMetadata(model, {
|
|
2906
|
+
authProfileId: responsesOptions?.authProfileId,
|
|
2907
|
+
sessionId: options?.sessionId
|
|
2908
|
+
}),
|
|
2909
|
+
asyncToolExecution: asyncTools,
|
|
2910
|
+
...responseModelTracker.terminalOptions
|
|
2911
|
+
});
|
|
2912
|
+
finishWebSocket?.();
|
|
2913
|
+
if (options?.signal?.aborted) throw transportAbortError(options.signal);
|
|
2914
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error(output.errorMessage ?? "An unknown error occurred");
|
|
2915
|
+
const admitted = transport === "websocket" ? websocketBaseline : continuationBaseline;
|
|
2916
|
+
if (terminal && admitted && supportsNativeOpenAIResponsesEndpoint(model)) recordResponsesReasoningState(output, model, responsesOptions, admitted, terminal.output);
|
|
2917
|
+
if (continuationClaim && continuationBaseline && terminal) continuationClaim.commit(continuationBaseline, terminal, dispatchedPreviousResponseId);
|
|
2918
|
+
if (terminal && admitted && contextUsageEligible) recordResponsesContextUsage(output, model, responsesOptions, admitted, terminal.output, "transport");
|
|
2919
|
+
} catch (error) {
|
|
2920
|
+
finishWebSocket?.({ keep: false });
|
|
2921
|
+
throw error;
|
|
2922
|
+
}
|
|
2923
|
+
emitModelTransportDebug(log, `[responses] completed provider=${model.provider} api=${model.api} model=${model.id} transport=${transport} elapsedMs=${Date.now() - requestStartedAt}`);
|
|
2924
|
+
finalizeTransportStream({
|
|
2925
|
+
stream,
|
|
2926
|
+
output,
|
|
2927
|
+
signal: options?.signal
|
|
2928
|
+
});
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
if (requestLifecycle) await requestLifecycle.settle().catch(() => void 0);
|
|
2931
|
+
if (compactRequest) {
|
|
2932
|
+
compactRequest.reject(error);
|
|
2933
|
+
failTransportStream({
|
|
2934
|
+
stream,
|
|
2935
|
+
output,
|
|
2936
|
+
signal: options?.signal,
|
|
2937
|
+
error
|
|
2938
|
+
});
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
if (error instanceof ResponsesStreamFailure && error.observation) logResponsesFailedNoDetails(error.observation);
|
|
2942
|
+
const incompleteReason = output.diagnostics?.find(({ type }) => type === "openai_responses_terminal")?.details?.incompleteReason;
|
|
2943
|
+
emitModelTransportError(log, "responses", `provider=${model.provider} api=${model.api} model=${model.id} ` + summarizeOpenAITransportError(error) + (typeof incompleteReason === "string" ? ` incompleteReason=${incompleteReason}` : ""), options?.signal);
|
|
2944
|
+
failTransportStream({
|
|
2945
|
+
stream,
|
|
2946
|
+
output,
|
|
2947
|
+
signal: options?.signal,
|
|
2948
|
+
error
|
|
2949
|
+
});
|
|
2950
|
+
} finally {
|
|
2951
|
+
continuationClaim?.release();
|
|
2952
|
+
firstEventAbort?.dispose();
|
|
2953
|
+
}
|
|
2954
|
+
})();
|
|
2955
|
+
return eventStream;
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
function createOpenAIResponsesTransportStreamFn() {
|
|
2959
|
+
return createResponsesTransportExecutor({
|
|
2960
|
+
streamRequest: true,
|
|
2961
|
+
httpContinuation: true,
|
|
2962
|
+
createClient: createOpenAIResponsesClient,
|
|
2963
|
+
buildRequest: buildOpenAIResponsesParams,
|
|
2964
|
+
pricingOptions: (options, model) => ({
|
|
2965
|
+
serviceTier: options?.serviceTier,
|
|
2966
|
+
applyServiceTierPricing: (usage, serviceTier) => applyResponsesServiceTierPricing(usage, serviceTier, model)
|
|
2967
|
+
})
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
function createAzureOpenAIResponsesTransportStreamFn() {
|
|
2971
|
+
return createResponsesTransportExecutor({
|
|
2972
|
+
outputApi: "azure-openai-responses",
|
|
2973
|
+
firstEventTimeoutMs: AZURE_RESPONSES_FIRST_EVENT_TIMEOUT_MS,
|
|
2974
|
+
createClient: createAzureOpenAIClient,
|
|
2975
|
+
buildRequest: (model, context, options, metadata, replayMode) => {
|
|
2976
|
+
const deploymentName = resolveAzureDeploymentNameFromMap({
|
|
2977
|
+
modelId: model.id,
|
|
2978
|
+
deploymentMap: process.env.AZURE_OPENAI_DEPLOYMENT_NAME_MAP
|
|
2979
|
+
});
|
|
2980
|
+
const params = buildOpenAIResponsesParams(model, context, options, metadata, replayMode);
|
|
2981
|
+
params.model = deploymentName;
|
|
2982
|
+
delete params.store;
|
|
2983
|
+
return params;
|
|
2984
|
+
}
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
function createAzureOpenAIClient(model, apiKey, defaultHeaders, fetchOverride) {
|
|
2988
|
+
const baseURL = model.baseUrl.replace(/\/+$/, "");
|
|
2989
|
+
const clientOptions = {
|
|
2990
|
+
apiKey,
|
|
2991
|
+
dangerouslyAllowBrowser: true,
|
|
2992
|
+
defaultHeaders,
|
|
2993
|
+
baseURL,
|
|
2994
|
+
fetch: fetchOverride ?? buildGuardedModelFetch(model),
|
|
2995
|
+
...buildOpenAISdkClientOptions(model)
|
|
2996
|
+
};
|
|
2997
|
+
if (isOpenAICompatibleAzureResponsesBaseUrl(baseURL)) return new OpenAI(clientOptions);
|
|
2998
|
+
return new AzureOpenAI({
|
|
2999
|
+
...clientOptions,
|
|
3000
|
+
apiVersion: resolveAzureOpenAIApiVersion()
|
|
3001
|
+
});
|
|
1890
3002
|
}
|
|
1891
3003
|
//#endregion
|
|
1892
3004
|
//#region packages/ai/src/transports/openai-completions-transport.ts
|
|
@@ -2010,7 +3122,23 @@ function createDirectCompletionsEventStream(output, stream) {
|
|
|
2010
3122
|
};
|
|
2011
3123
|
}
|
|
2012
3124
|
function createOpenAICompletionsTransportStreamFn() {
|
|
2013
|
-
|
|
3125
|
+
const streamResponses = createOpenAIResponsesTransportStreamFn();
|
|
3126
|
+
return (model, context, options) => {
|
|
3127
|
+
const completionsOptions = options;
|
|
3128
|
+
if (model.reasoning && context.tools?.length && supportsModelTools(model) && isNativeOpenAIEndpoint(model)) {
|
|
3129
|
+
const toolChoice = completionsOptions?.toolChoice;
|
|
3130
|
+
const responsesOptions = {
|
|
3131
|
+
...options,
|
|
3132
|
+
...completionsOptions?.reasoning === void 0 && completionsOptions?.reasoningEffort === void 0 ? { reasoningEffort: "high" } : {},
|
|
3133
|
+
...toolChoice && typeof toolChoice === "object" ? { toolChoice: toOpenAIResponsesToolChoice(toolChoice) } : {}
|
|
3134
|
+
};
|
|
3135
|
+
return streamResponses(getAiTransportHost().inheritManagedTransport(model, {
|
|
3136
|
+
...model,
|
|
3137
|
+
api: "openai-responses"
|
|
3138
|
+
}), context, responsesOptions);
|
|
3139
|
+
}
|
|
3140
|
+
return streamOpenAICompletionsRequest(model, context, completionsOptions, "managed");
|
|
3141
|
+
};
|
|
2014
3142
|
}
|
|
2015
3143
|
function streamOpenAICompletionsRequest(model, context, options, mode) {
|
|
2016
3144
|
const { eventStream, stream } = createWritableTransportEventStream();
|
|
@@ -2054,9 +3182,21 @@ function streamOpenAICompletionsRequest(model, context, options, mode) {
|
|
|
2054
3182
|
...buildOpenAISdkRequestOptions(model, firstEventAbort.signal, { timeoutMs: options?.timeoutMs }),
|
|
2055
3183
|
...await encodeBody(params)
|
|
2056
3184
|
};
|
|
2057
|
-
const
|
|
3185
|
+
const request = client.chat.completions.create(params, requestOptions);
|
|
3186
|
+
const response = await request.asResponse();
|
|
2058
3187
|
const hookedResponseStream = withProviderResponseHook({
|
|
2059
|
-
stream:
|
|
3188
|
+
stream: { async *[Symbol.asyncIterator]() {
|
|
3189
|
+
if (!params.stream) {
|
|
3190
|
+
yield await boundResponseBody(response, {
|
|
3191
|
+
maxBytes: 16777216,
|
|
3192
|
+
onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`Chat Completions JSON response exceeds ${maxBytes} bytes`)
|
|
3193
|
+
}).json();
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
const data = await request;
|
|
3197
|
+
if (Symbol.asyncIterator in data) yield* data;
|
|
3198
|
+
else yield data;
|
|
3199
|
+
} },
|
|
2060
3200
|
signal: firstEventAbort.signal,
|
|
2061
3201
|
abort: firstEventAbort.abort,
|
|
2062
3202
|
hook: createOpenAIProviderAcceptanceHook(options, response, model),
|
|
@@ -2102,7 +3242,6 @@ function streamOpenAICompletionsRequest(model, context, options, mode) {
|
|
|
2102
3242
|
signal: options?.signal,
|
|
2103
3243
|
error,
|
|
2104
3244
|
cleanup: () => {
|
|
2105
|
-
if (mode === "managed") output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
2106
3245
|
finalizeOpenAICompletionsToolCalls(output, { allowSilentToolCallPromotion: false });
|
|
2107
3246
|
clearPendingCommentaryText(provisionalCommentaryTags);
|
|
2108
3247
|
tagUnresolvedTextAsCommentary(output);
|
|
@@ -2143,7 +3282,7 @@ function createManagedCompletionsClient(model, context, options, apiKey, cacheRe
|
|
|
2143
3282
|
const optionHeaders = resolveOpencodeSessionHeaders(model, options);
|
|
2144
3283
|
const turnHeaders = filterProviderTurnHeadersForExplicitOpencodeSession(model, options, turnState?.headers);
|
|
2145
3284
|
const doneDetector = createSseDoneDetector();
|
|
2146
|
-
const baseFetch = buildGuardedModelFetch(model);
|
|
3285
|
+
const baseFetch = buildGuardedModelFetch(model, void 0, { onSseComment: () => notifyLlmRequestActivity(options?.signal, false) });
|
|
2147
3286
|
const doneDetectingFetch = async (url, init) => {
|
|
2148
3287
|
const response = await baseFetch(url, init);
|
|
2149
3288
|
if (!response.body || !response.ok) return response;
|
|
@@ -2157,11 +3296,7 @@ function createManagedCompletionsClient(model, context, options, apiKey, cacheRe
|
|
|
2157
3296
|
doneDetector.finish();
|
|
2158
3297
|
}
|
|
2159
3298
|
}));
|
|
2160
|
-
return new Response(transformed,
|
|
2161
|
-
headers: response.headers,
|
|
2162
|
-
status: response.status,
|
|
2163
|
-
statusText: response.statusText
|
|
2164
|
-
});
|
|
3299
|
+
return new Response(transformed, response);
|
|
2165
3300
|
};
|
|
2166
3301
|
const clientConfig = buildOpenAICompletionsClientConfig(model, buildOpenAIClientHeaders(model, context, {
|
|
2167
3302
|
...turnHeaders,
|
|
@@ -2181,4 +3316,4 @@ function createManagedCompletionsClient(model, context, options, apiKey, cacheRe
|
|
|
2181
3316
|
};
|
|
2182
3317
|
}
|
|
2183
3318
|
//#endregion
|
|
2184
|
-
export {
|
|
3319
|
+
export { requestPreparedOpenAIResponsesCompaction as a, convertMessages as c, applyCompletionsAnthropicCacheControl as d, hasOpenAICompatibleConversationTurn as f, createDeepSeekTextFilter as h, createOpenAIResponsesTransportStreamFn as i, flattenCompletionMessagesToStringContent as l, resolveMaxTokensParam as m, streamOpenAICompletionsRequest as n, hasResponsesWebSearchTool as o, canonicalizeMaxTokensParam as p, createAzureOpenAIResponsesTransportStreamFn as r, buildOpenAICompletionsParams as s, createOpenAICompletionsTransportStreamFn as t, stripCompletionMessagesToRoleContent as u };
|