@openclaw/ai 2026.10.1-beta.2 → 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-CZv6XFER.mjs → anthropic-BAHKgWam.mjs} +30 -59
- package/dist/{anthropic-payload-policy-BhCcgNdm.d.mts → anthropic-payload-policy-DGblkHL5.d.mts} +19 -16
- package/dist/{anthropic-stream-reducer-BQ1tjd7x.mjs → anthropic-stream-reducer-g6w1YVii.mjs} +197 -285
- package/dist/{api-registry-DpU0Rj-I.d.mts → api-registry-BhXXYdRA.d.mts} +2 -2
- package/dist/assistant-message-events-CHMJgbhs.mjs +18 -0
- package/dist/{assistant-output-OLm-Oln0.mjs → assistant-output-CUEeSotf.mjs} +2 -2
- package/dist/{assistant-text-phase-Bl1aDCB7.mjs → assistant-text-phase-BrV8KdFO.mjs} +7 -6
- package/dist/{azure-openai-responses-DH7bd3QT.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-wrnE6MLw.mjs → google-interactions-tjAb1mc5.mjs} +52 -84
- package/dist/{google-messages-BwPPycw6.mjs → google-messages-C-RZzloH.mjs} +25 -35
- package/dist/google-provider-stream-CMImyxbX.mjs +58 -0
- package/dist/{google-shared-Eql1OMPU.mjs → google-shared-OyAtMTZv.mjs} +6 -15
- package/dist/google-vertex-hCxTJea7.mjs +40 -0
- package/dist/{host-BinBeGcQ.d.mts → host-CTDz7LXD.d.mts} +6 -3
- package/dist/{host-DCW52iHe.mjs → host-JWXZWa-c.mjs} +12 -8
- package/dist/{host-policy-B3jc4Rpn.mjs → host-policy-q4Azym9F.mjs} +1 -1
- package/dist/{index-a8e_oKnu.d.mts → index-BYDvopUB.d.mts} +27 -6
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +7 -6
- package/dist/internal/anthropic.d.mts +6 -6
- package/dist/internal/anthropic.mjs +5 -5
- 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 +14 -11
- 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-DSt4vQAO.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-BC2x0Oqx.mjs → openai-chatgpt-responses-OCwckGhY.mjs} +173 -242
- package/dist/{openai-completions-DNyHnXKg.mjs → openai-completions-DOltBUe9.mjs} +7 -6
- package/dist/{openai-completions-compat-BYdYpXhU.d.mts → openai-completions-compat-i-nLZxFy.d.mts} +10 -11
- package/dist/{openai-completions-compat-BdmlhD4u.mjs → openai-completions-compat-pg1q4uNG.mjs} +10 -6
- package/dist/openai-completions-transport-OMrn7gnZ.mjs +3319 -0
- package/dist/{openai-prompt-cache-I8YQTzf8.mjs → openai-prompt-cache-Cr2xKVrS.mjs} +1 -1
- package/dist/{openai-prompt-cache-45-AH_xv.d.mts → openai-prompt-cache-DIPMrMFy.d.mts} +2 -2
- package/dist/{openai-responses-uZ5oWSp7.mjs → openai-responses-BSBJUA19.mjs} +9 -10
- package/dist/{openai-responses-compaction-replay-BClApUIf.d.mts → openai-responses-compaction-replay-B2FE4Nw8.d.mts} +4 -4
- package/dist/{openai-responses-contracts-Br5IIw_h.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-CCg7B6l4.mjs → openai-responses-request-lifecycle-D2IFiI0-.mjs} +39 -4
- package/dist/{openai-responses-shared-C7oVyjED.mjs → openai-responses-shared-DVGhq5xF.mjs} +1036 -327
- package/dist/{openai-responses-terminal-usage-DgImcyJu.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-D5ABCV_l.d.mts → provider-options-CVrXZBtA.d.mts} +3 -2
- package/dist/{provider-transport-turn-state-_M2kLdUE.mjs → provider-transport-turn-state-LuCaTsSO.mjs} +2 -2
- package/dist/{provider-types-F8Mqc9Ma.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-BkKTXxhp.mjs → simple-options-BARFAzk9.mjs} +9 -11
- package/dist/{src-B0NIt2UK.mjs → src-C_wfIUmV.mjs} +35 -10
- package/dist/{stream-DSdzKsDK.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-MLwfi6fq.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-BcAKh2XH.d.mts → transport-stream-shared-BmpzDtdQ.d.mts} +4 -4
- package/dist/{transport-stream-shared-CxXXz_Dd.mjs → transport-stream-shared-DH08b7Il.mjs} +31 -20
- 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 -4
- package/dist/event-stream-DqDQWqiy.d.mts +0 -1
- package/dist/google-1WLOtb8w.mjs +0 -54
- package/dist/google-vertex-0LrlRoO3.mjs +0 -88
- package/dist/openai-completions-transport-DWM2mQDB.mjs +0 -2081
- package/dist/openai-responses-request-lifecycle-Fsag-IGZ.mjs +0 -36
- package/dist/openai-transport-params-JFsnIyto.mjs +0 -735
- package/dist/openai-web-search-tools-Cfu0uLyN.mjs +0 -14
- package/dist/provider-replay-context-C7Bmi84a.mjs +0 -25
- package/dist/types-BuiEBM3u.d.mts +0 -1
|
@@ -1,2081 +0,0 @@
|
|
|
1
|
-
import { _ as runtimeContextContentToText, f as hasRuntimeContextMarker, p as isRuntimeContextMessage } from "./types-CQc6JNG4.mjs";
|
|
2
|
-
import { r as appendAssistantThinking } from "./event-stream-C5AbCAaZ.mjs";
|
|
3
|
-
import { o as isRecord } from "./record-coerce-CNDovvft.mjs";
|
|
4
|
-
import { s as readNonEmptyStringPreservingWhitespace, t as hasNonEmptyString } from "./string-coerce-CtgOVOSv.mjs";
|
|
5
|
-
import { n as asNonNegativeFiniteNumber } from "./number-coercion-V8-BhME4.mjs";
|
|
6
|
-
import { N as isImageWithMediaPayload, r as getAiTransportHost } from "./host-DCW52iHe.mjs";
|
|
7
|
-
import { _ as isOpenAIGpt54MiniModel, v as isOpenAIGpt55Model, y as isOpenAIGpt56Model } from "./openai-responses-contracts-BKu8O7l4.mjs";
|
|
8
|
-
import { r as uniqueStrings } from "./string-normalization-PN10hf6d.mjs";
|
|
9
|
-
import { o as truncateUtf16Safe } from "./provider-error-DQBE1bUQ.mjs";
|
|
10
|
-
import { r as resolveOpenAIPromptCacheParams } from "./openai-prompt-cache-I8YQTzf8.mjs";
|
|
11
|
-
import { r as emitModelTransportDebug } from "./diagnostics-Caj-4RBA.mjs";
|
|
12
|
-
import { c as supportsModelTools, r as estimateStringChars } from "./text-signature-MLwfi6fq.mjs";
|
|
13
|
-
import { i as resolveOpenAICompletionsCompat, l as shouldOmitOllamaCompatResponseFormat, n as isNativeOpenAIEndpoint, s as resolveOpenAICompletionsResponseFormat, t as detectOpenAICompletionsCompat } from "./openai-completions-compat-BdmlhD4u.mjs";
|
|
14
|
-
import { i as resolveProviderEndpoint, t as buildGuardedModelFetch } from "./host-policy-B3jc4Rpn.mjs";
|
|
15
|
-
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, _ as sanitizeNonEmptyTransportPayloadText, b as transportAbortError, c as failTransportStream, d as iterateModelStream, l as finalizeTerminalToolCallArguments, s as createWritableTransportEventStream, u as finalizeTransportStream, y as throwIfModelStreamAborted } from "./transport-stream-shared-CxXXz_Dd.mjs";
|
|
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-BkKTXxhp.mjs";
|
|
18
|
-
import { C as log, D as readOpenAICompletionsReasoningBatch, E as readOpenAICompletionsContentDeltas, O as resolveOpenAIClientBaseUrl, S as isOpenAICompletionsThinkingEnabled, T as parseOpenAICompletionsUsage, a as enforceCodeModeResponsesToolSurface, d as prepareOpenAITools, f as projectOpenAITools, g as resolveOpenAISimpleReasoningEffort, h as resolveOpenAIRequestReasoning, i as buildOpenAISdkRequestOptions, k as resolvePromptCacheKey, l as resolveCodeModeResponsesVisibleToolNames, n as buildOpenAIClientHeaders, p as reconcileOpenAICompletionsToolChoice, r as buildOpenAISdkClientOptions, s as getCompat, t as assertCodeModeResponsesToolSurface, u as resolveOpenAIStrictToolFlagWithDiagnostics, v as GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, w as measureUtf8AppendBytes, y as createOpenAIProviderAcceptanceHook } from "./openai-transport-params-JFsnIyto.mjs";
|
|
19
|
-
import { n as getEnvApiKey } from "./env-api-keys-CyX4VKGO.mjs";
|
|
20
|
-
import { n as transformProviderMessages, t as createAssistantOutput } from "./assistant-output-OLm-Oln0.mjs";
|
|
21
|
-
import { a as tagUnresolvedTextAsCommentary, i as tagPendingCommentaryText, n as rememberPendingCommentaryTags, r as tagInterruptedTextPhases, t as clearPendingCommentaryText } from "./assistant-text-phase-Bl1aDCB7.mjs";
|
|
22
|
-
import { t as notifyLlmRequestActivity } from "./llm-request-activity-D8f_oxHj.mjs";
|
|
23
|
-
import { n as resolveOpencodeSessionHeaders } from "./session-affinity-DA4WHv1h.mjs";
|
|
24
|
-
import "./types.mjs";
|
|
25
|
-
import { i as createOpenAIProviderClient, n as resolveProviderSimpleCompletionHeaders, r as resolveProviderTransportTurnState, t as filterProviderTurnHeadersForExplicitOpencodeSession } from "./provider-transport-turn-state-_M2kLdUE.mjs";
|
|
26
|
-
import { a as withFirstStreamEventTimeout, i as getFirstStreamEventTimeoutMs, r as getFirstStreamEventTimeoutHandler, t as createFirstStreamEventAbortController } from "./stream-first-event-timeout-D9ZmL_s9.mjs";
|
|
27
|
-
import { f as withPreparedToolSchemaNormalization, r as normalizeOpenAIStrictToolParameters } from "./openai-tool-schema-DYNePiF8.mjs";
|
|
28
|
-
import { isGoogleGemini3ThinkingLevelModel } from "./internal/google-model-family.mjs";
|
|
29
|
-
import { t as mapOpenAIStopReason } from "./openai-stop-reason-8s70vDXj.mjs";
|
|
30
|
-
import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-Ju9VpUGl.mjs";
|
|
31
|
-
import { randomUUID } from "node:crypto";
|
|
32
|
-
import OpenAI from "openai";
|
|
33
|
-
//#region packages/ai/src/transports/deepseek-dsml-grammar.ts
|
|
34
|
-
const DEEPSEEK_DSML_MARKERS = [
|
|
35
|
-
"|",
|
|
36
|
-
"|",
|
|
37
|
-
"||"
|
|
38
|
-
].map((bar) => `${bar}DSML${bar}`);
|
|
39
|
-
const DEEPSEEK_DSML_MARKER_PATTERN = `(${DEEPSEEK_DSML_MARKERS.map((marker) => marker.replaceAll("|", "\\|")).join("|")})`;
|
|
40
|
-
function findEarliestDsmlToken(text, tokens, fromIndex = 0) {
|
|
41
|
-
let best = null;
|
|
42
|
-
for (const token of tokens) {
|
|
43
|
-
const index = text.indexOf(token, fromIndex);
|
|
44
|
-
if (index !== -1 && (!best || index < best.index)) best = {
|
|
45
|
-
index,
|
|
46
|
-
token
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return best;
|
|
50
|
-
}
|
|
51
|
-
function longestDsmlTokenPrefixSuffixLength(text, tokens, maxTokenLength) {
|
|
52
|
-
const maxLength = Math.min(text.length, maxTokenLength - 1);
|
|
53
|
-
for (let length = maxLength; length > 0; length--) {
|
|
54
|
-
const suffix = text.slice(text.length - length);
|
|
55
|
-
if (tokens.some((token) => token.startsWith(suffix))) return length;
|
|
56
|
-
}
|
|
57
|
-
return 0;
|
|
58
|
-
}
|
|
59
|
-
//#endregion
|
|
60
|
-
//#region packages/ai/src/transports/deepseek-text-filter.ts
|
|
61
|
-
/**
|
|
62
|
-
* DeepSeek DSML streaming text filter.
|
|
63
|
-
* Removes provider-emitted DSML tool markup while buffering split tag prefixes
|
|
64
|
-
* across streamed chunks.
|
|
65
|
-
*/
|
|
66
|
-
const DSML_KINDS = [
|
|
67
|
-
"tool_use_error",
|
|
68
|
-
"tool_calls",
|
|
69
|
-
"tool_call",
|
|
70
|
-
"function_calls"
|
|
71
|
-
];
|
|
72
|
-
const DSML_OPEN_TOKENS = DEEPSEEK_DSML_MARKERS.flatMap((marker) => DSML_KINDS.map((kind) => `<${marker}${kind}>`));
|
|
73
|
-
const MAX_OPEN_TOKEN_LEN = Math.max(...DSML_OPEN_TOKENS.map((token) => token.length));
|
|
74
|
-
/** Create an incremental text filter that strips DeepSeek DSML tool blocks. */
|
|
75
|
-
function createDeepSeekTextFilter() {
|
|
76
|
-
let buffer = "";
|
|
77
|
-
let closeToken;
|
|
78
|
-
const consume = (final) => {
|
|
79
|
-
const output = [];
|
|
80
|
-
const emit = (text) => {
|
|
81
|
-
if (text) output.push(text);
|
|
82
|
-
};
|
|
83
|
-
while (buffer) {
|
|
84
|
-
if (closeToken) {
|
|
85
|
-
const closeIndex = buffer.indexOf(closeToken);
|
|
86
|
-
if (closeIndex !== -1) {
|
|
87
|
-
buffer = buffer.slice(closeIndex + closeToken.length);
|
|
88
|
-
closeToken = void 0;
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const keep = final ? 0 : Math.min(buffer.length, closeToken.length - 1);
|
|
92
|
-
buffer = buffer.slice(buffer.length - keep);
|
|
93
|
-
if (final) closeToken = void 0;
|
|
94
|
-
return output;
|
|
95
|
-
}
|
|
96
|
-
const open = findEarliestDsmlToken(buffer, DSML_OPEN_TOKENS);
|
|
97
|
-
if (open) {
|
|
98
|
-
emit(buffer.slice(0, open.index));
|
|
99
|
-
buffer = buffer.slice(open.index + open.token.length);
|
|
100
|
-
closeToken = open.token.replace("<", "</");
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
if (final) {
|
|
104
|
-
emit(buffer);
|
|
105
|
-
buffer = "";
|
|
106
|
-
return output;
|
|
107
|
-
}
|
|
108
|
-
const keep = longestDsmlTokenPrefixSuffixLength(buffer, DSML_OPEN_TOKENS, MAX_OPEN_TOKEN_LEN);
|
|
109
|
-
const emitLength = buffer.length - keep;
|
|
110
|
-
if (emitLength <= 0) return output;
|
|
111
|
-
emit(buffer.slice(0, emitLength));
|
|
112
|
-
buffer = buffer.slice(emitLength);
|
|
113
|
-
return output;
|
|
114
|
-
}
|
|
115
|
-
return output;
|
|
116
|
-
};
|
|
117
|
-
return {
|
|
118
|
-
push(chunk) {
|
|
119
|
-
buffer += chunk;
|
|
120
|
-
return consume(false);
|
|
121
|
-
},
|
|
122
|
-
flush() {
|
|
123
|
-
return consume(true);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
//#endregion
|
|
128
|
-
//#region packages/ai/src/transports/model-max-tokens-params.ts
|
|
129
|
-
/**
|
|
130
|
-
* Max-token parameter normalization across provider/native naming variants.
|
|
131
|
-
* Callers canonicalize aliases before dispatch so payloads cannot carry
|
|
132
|
-
* conflicting limits.
|
|
133
|
-
*/
|
|
134
|
-
const MAX_TOKENS_PARAM_KEYS = [
|
|
135
|
-
"maxTokens",
|
|
136
|
-
"max_completion_tokens",
|
|
137
|
-
"max_tokens"
|
|
138
|
-
];
|
|
139
|
-
/** Resolve the first supported max-token parameter present in a params object. */
|
|
140
|
-
function resolveMaxTokensParam(params) {
|
|
141
|
-
if (!params) return;
|
|
142
|
-
for (const key of MAX_TOKENS_PARAM_KEYS) {
|
|
143
|
-
const resolved = asNonNegativeFiniteNumber(params[key]);
|
|
144
|
-
if (resolved !== void 0) return resolved;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Canonicalize merged params to `maxTokens`, preserving source precedence from
|
|
149
|
-
* left to right across the provided source objects.
|
|
150
|
-
*/
|
|
151
|
-
function canonicalizeMaxTokensParam(params) {
|
|
152
|
-
let resolved;
|
|
153
|
-
for (const source of params.sources) {
|
|
154
|
-
const sourceValue = resolveMaxTokensParam(source);
|
|
155
|
-
if (sourceValue !== void 0) resolved = sourceValue;
|
|
156
|
-
}
|
|
157
|
-
if (resolved === void 0) return;
|
|
158
|
-
for (const key of MAX_TOKENS_PARAM_KEYS) delete params.merged[key];
|
|
159
|
-
params.merged.maxTokens = resolved;
|
|
160
|
-
}
|
|
161
|
-
//#endregion
|
|
162
|
-
//#region packages/ai/src/transports/openai-compatible-conversation-turn.ts
|
|
163
|
-
function hasNonEmptyContentPart(part) {
|
|
164
|
-
if (!part || typeof part !== "object") return false;
|
|
165
|
-
const record = part;
|
|
166
|
-
if (record.type === "text") return hasNonEmptyString(record.text);
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
function hasNonEmptyMessageContent(content) {
|
|
170
|
-
if (hasNonEmptyString(content)) return true;
|
|
171
|
-
if (!Array.isArray(content)) return false;
|
|
172
|
-
return content.some(hasNonEmptyContentPart);
|
|
173
|
-
}
|
|
174
|
-
/** Returns whether an OpenAI-compatible messages payload contains a usable turn. */
|
|
175
|
-
function hasOpenAICompatibleConversationTurn(messages) {
|
|
176
|
-
if (!Array.isArray(messages)) return false;
|
|
177
|
-
return messages.some((message) => {
|
|
178
|
-
if (!message || typeof message !== "object") return false;
|
|
179
|
-
const record = message;
|
|
180
|
-
if (record.role === "user") return hasNonEmptyMessageContent(record.content);
|
|
181
|
-
if (record.role === "assistant") {
|
|
182
|
-
if (hasNonEmptyMessageContent(record.content)) return true;
|
|
183
|
-
const toolCalls = record.tool_calls;
|
|
184
|
-
return Array.isArray(toolCalls) && toolCalls.some((toolCall) => toolCall && typeof toolCall === "object");
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region packages/ai/src/transports/openai-completions-cache-control.ts
|
|
191
|
-
const shapedPayloads = /* @__PURE__ */ new WeakSet();
|
|
192
|
-
function resolveCompletionsCacheControl(compat, retention, openRouterRoute) {
|
|
193
|
-
if (compat.cacheControlFormat !== "anthropic" || retention === "none") return;
|
|
194
|
-
return {
|
|
195
|
-
type: "ephemeral",
|
|
196
|
-
...retention === "long" && compat.supportsLongCacheRetention && (openRouterRoute || compat.configuredSupportsLongCacheRetention === true) ? { ttl: "1h" } : {}
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/** Shared Chat Completions policy; repeated wrapper application preserves existing checkpoints. */
|
|
200
|
-
function applyCompletionsAnthropicCacheControl(payload, cacheControl = { type: "ephemeral" }, cacheOptOutIndexes = /* @__PURE__ */ new Set(), markTools = true, markMessages = true) {
|
|
201
|
-
if (shapedPayloads.has(payload)) return;
|
|
202
|
-
shapedPayloads.add(payload);
|
|
203
|
-
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
204
|
-
const tools = Array.isArray(payload.tools) ? payload.tools.filter(isRecord) : [];
|
|
205
|
-
const blocks = messages.filter(isRecord).flatMap((message) => Array.isArray(message.content) ? message.content.filter(isRecord) : []);
|
|
206
|
-
for (const block of [...tools, ...blocks]) delete block.cache_control;
|
|
207
|
-
if (!cacheControl) return;
|
|
208
|
-
const markText = (message, splitBoundary) => {
|
|
209
|
-
if (typeof message.content === "string" && message.content) message.content = [{
|
|
210
|
-
type: "text",
|
|
211
|
-
text: message.content
|
|
212
|
-
}];
|
|
213
|
-
const content = message.content;
|
|
214
|
-
if (!Array.isArray(content)) return false;
|
|
215
|
-
for (let i = content.length - 1; i >= 0; i--) {
|
|
216
|
-
const block = content[i];
|
|
217
|
-
if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string" || !block.text) continue;
|
|
218
|
-
const split = splitBoundary ? splitSystemPromptCacheBoundary(block.text) : void 0;
|
|
219
|
-
if (split) {
|
|
220
|
-
content.splice(i, 1, ...split.stablePrefix ? [{
|
|
221
|
-
type: "text",
|
|
222
|
-
text: split.stablePrefix,
|
|
223
|
-
cache_control: cacheControl
|
|
224
|
-
}] : [], ...split.dynamicSuffix ? [{
|
|
225
|
-
type: "text",
|
|
226
|
-
text: split.dynamicSuffix
|
|
227
|
-
}] : []);
|
|
228
|
-
if (!split.stablePrefix) return false;
|
|
229
|
-
} else block.cache_control = cacheControl;
|
|
230
|
-
return true;
|
|
231
|
-
}
|
|
232
|
-
return false;
|
|
233
|
-
};
|
|
234
|
-
const lastTool = tools.at(-1);
|
|
235
|
-
if (markTools && lastTool) lastTool.cache_control = cacheControl;
|
|
236
|
-
if (!markMessages) return;
|
|
237
|
-
const system = messages.find((message) => isRecord(message) && (message.role === "system" || message.role === "developer"));
|
|
238
|
-
if (isRecord(system)) markText(system, true);
|
|
239
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
240
|
-
const message = messages[i];
|
|
241
|
-
if (!cacheOptOutIndexes.has(i) && isRecord(message) && (message.role === "user" || message.role === "tool") && markText(message, false)) return;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
//#endregion
|
|
245
|
-
//#region packages/ai/src/transports/openai-completions-string-content.ts
|
|
246
|
-
/**
|
|
247
|
-
* OpenAI Chat Completions compatibility helpers. Some providers only accept
|
|
248
|
-
* role/content messages with plain string content instead of text block arrays.
|
|
249
|
-
*/
|
|
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
|
-
/** Flatten string-only text block content arrays into newline-joined strings. */
|
|
260
|
-
function flattenCompletionMessagesToStringContent(messages) {
|
|
261
|
-
return messages.map((message) => {
|
|
262
|
-
if (!message || typeof message !== "object") return message;
|
|
263
|
-
const content = message.content;
|
|
264
|
-
const flattenedContent = flattenStringOnlyCompletionContent(content);
|
|
265
|
-
if (flattenedContent === content) return message;
|
|
266
|
-
return {
|
|
267
|
-
...message,
|
|
268
|
-
content: flattenedContent
|
|
269
|
-
};
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
/** Strip completion messages to role/content fields for strict providers. */
|
|
273
|
-
function stripCompletionMessagesToRoleContent(messages) {
|
|
274
|
-
return messages.map((message) => {
|
|
275
|
-
if (!message || typeof message !== "object" || Array.isArray(message)) return message;
|
|
276
|
-
const record = message;
|
|
277
|
-
const stripped = {};
|
|
278
|
-
if (Object.hasOwn(record, "role")) stripped.role = record.role;
|
|
279
|
-
if (Object.hasOwn(record, "content")) stripped.content = record.content;
|
|
280
|
-
return stripped;
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
//#endregion
|
|
284
|
-
//#region packages/ai/src/providers/openai-completions-tool-calls.ts
|
|
285
|
-
const MAX_BUFFERED_TOOL_CALL_ARGUMENT_BYTES = 256e3;
|
|
286
|
-
const MAX_BUFFERED_LEGACY_FOLLOWING_DELTA_BYTES = 256e3;
|
|
287
|
-
const MAX_BUFFERED_LEGACY_FOLLOWING_DELTAS = 1024;
|
|
288
|
-
/** Keep encrypted provider reasoning attached to the first matching tool call. */
|
|
289
|
-
function createOpenAIEncryptedToolCallReasoningTracker() {
|
|
290
|
-
const firstBlocks = /* @__PURE__ */ new Map();
|
|
291
|
-
const pendingDetails = /* @__PURE__ */ new Map();
|
|
292
|
-
return {
|
|
293
|
-
rememberToolCall(id, block, previousId = id) {
|
|
294
|
-
const previous = firstBlocks.get(previousId);
|
|
295
|
-
if (previousId !== id && previous?.block === block) {
|
|
296
|
-
firstBlocks.delete(previousId);
|
|
297
|
-
if (previous.signature && block.thoughtSignature === previous.signature) delete block.thoughtSignature;
|
|
298
|
-
}
|
|
299
|
-
if (!id || firstBlocks.has(id)) return;
|
|
300
|
-
const pendingDetail = pendingDetails.get(id);
|
|
301
|
-
firstBlocks.set(id, {
|
|
302
|
-
block,
|
|
303
|
-
signature: pendingDetail
|
|
304
|
-
});
|
|
305
|
-
if (pendingDetail) {
|
|
306
|
-
block.thoughtSignature = pendingDetail;
|
|
307
|
-
pendingDetails.delete(id);
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
consumeDetails(details) {
|
|
311
|
-
if (!Array.isArray(details)) return;
|
|
312
|
-
for (const detail of details) {
|
|
313
|
-
if (!isRecord(detail) || detail.type !== "reasoning.encrypted" || typeof detail.id !== "string" || detail.id.length === 0 || typeof detail.data !== "string" || detail.data.length === 0) continue;
|
|
314
|
-
const serializedDetail = JSON.stringify(detail);
|
|
315
|
-
const matchingBlock = firstBlocks.get(detail.id);
|
|
316
|
-
if (matchingBlock) {
|
|
317
|
-
matchingBlock.signature = serializedDetail;
|
|
318
|
-
matchingBlock.block.thoughtSignature = serializedDetail;
|
|
319
|
-
} else pendingDetails.set(detail.id, serializedDetail);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
/** Normalize the SDK's legacy single-function lane into its modern tool-call shape. */
|
|
325
|
-
function createOpenAICompletionsToolCallDeltaNormalizer() {
|
|
326
|
-
let sawModernToolCall = false;
|
|
327
|
-
let pendingLegacyArgumentBytes = 0;
|
|
328
|
-
let pendingFollowingDeltaBytes = 0;
|
|
329
|
-
let pendingLegacyToolCall;
|
|
330
|
-
const modernArguments = /* @__PURE__ */ new Map();
|
|
331
|
-
const pendingFollowingDeltas = [];
|
|
332
|
-
const takePendingFollowingDeltas = () => {
|
|
333
|
-
pendingFollowingDeltaBytes = 0;
|
|
334
|
-
return pendingFollowingDeltas.splice(0).map((delta) => ({
|
|
335
|
-
delta,
|
|
336
|
-
toolCalls: []
|
|
337
|
-
}));
|
|
338
|
-
};
|
|
339
|
-
const bufferFollowingDelta = (delta) => {
|
|
340
|
-
const nextDeltaBytes = Buffer.byteLength(JSON.stringify(delta), "utf8");
|
|
341
|
-
if (pendingFollowingDeltaBytes + nextDeltaBytes > MAX_BUFFERED_LEGACY_FOLLOWING_DELTA_BYTES || pendingFollowingDeltas.length >= MAX_BUFFERED_LEGACY_FOLLOWING_DELTAS) throw new Error("Exceeded legacy tool-call content buffer limit");
|
|
342
|
-
pendingFollowingDeltaBytes += nextDeltaBytes;
|
|
343
|
-
pendingFollowingDeltas.push(delta);
|
|
344
|
-
};
|
|
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
|
-
return (delta, finishReason) => {
|
|
352
|
-
const ordinaryDelta = { ...delta };
|
|
353
|
-
delete ordinaryDelta.function_call;
|
|
354
|
-
delete ordinaryDelta.tool_calls;
|
|
355
|
-
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
|
356
|
-
for (const toolCall of delta.tool_calls) {
|
|
357
|
-
const index = typeof toolCall.index === "number" ? toolCall.index : void 0;
|
|
358
|
-
const state = (index === void 0 ? void 0 : modernArguments.get(index)) ?? (toolCall.id ? modernArguments.get(toolCall.id) : void 0) ?? {
|
|
359
|
-
bytes: 0,
|
|
360
|
-
pendingHighSurrogate: false
|
|
361
|
-
};
|
|
362
|
-
if (index !== void 0) modernArguments.set(index, state);
|
|
363
|
-
if (toolCall.id) modernArguments.set(toolCall.id, state);
|
|
364
|
-
const argumentDelta = toolCall.function?.arguments ?? "";
|
|
365
|
-
const append = measureUtf8AppendBytes(state.pendingHighSurrogate, argumentDelta);
|
|
366
|
-
state.bytes += append.bytes;
|
|
367
|
-
if (state.bytes > MAX_BUFFERED_TOOL_CALL_ARGUMENT_BYTES) throw new Error("Exceeded tool-call argument buffer limit");
|
|
368
|
-
state.pendingHighSurrogate = append.endsWithHighSurrogate;
|
|
369
|
-
}
|
|
370
|
-
const precedingDeltas = takePendingFollowingDeltas();
|
|
371
|
-
sawModernToolCall = true;
|
|
372
|
-
pendingLegacyArgumentBytes = 0;
|
|
373
|
-
pendingLegacyToolCall = void 0;
|
|
374
|
-
return [...precedingDeltas, {
|
|
375
|
-
delta: ordinaryDelta,
|
|
376
|
-
toolCalls: delta.tool_calls
|
|
377
|
-
}];
|
|
378
|
-
}
|
|
379
|
-
const functionCall = delta.function_call;
|
|
380
|
-
if (sawModernToolCall || !functionCall && !pendingLegacyToolCall) return [{
|
|
381
|
-
delta: ordinaryDelta,
|
|
382
|
-
toolCalls: []
|
|
383
|
-
}];
|
|
384
|
-
const hadPendingLegacyCall = pendingLegacyToolCall !== void 0;
|
|
385
|
-
const leadingDeltas = [];
|
|
386
|
-
if (Object.entries(ordinaryDelta).some(([field, value]) => field !== "role" && hasObservableContent(value))) {
|
|
387
|
-
if (hadPendingLegacyCall) bufferFollowingDelta(ordinaryDelta);
|
|
388
|
-
else leadingDeltas.push({
|
|
389
|
-
delta: ordinaryDelta,
|
|
390
|
-
toolCalls: []
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
if (functionCall && (functionCall.name || functionCall.arguments)) {
|
|
394
|
-
const nextFunctionName = pendingLegacyToolCall?.function?.name ? void 0 : functionCall.name;
|
|
395
|
-
const nextArgumentBytes = Buffer.byteLength(functionCall.arguments ?? "", "utf8") + Buffer.byteLength(nextFunctionName ?? "", "utf8");
|
|
396
|
-
if (pendingLegacyArgumentBytes + nextArgumentBytes > MAX_BUFFERED_TOOL_CALL_ARGUMENT_BYTES) throw new Error("Exceeded tool-call argument buffer limit");
|
|
397
|
-
pendingLegacyArgumentBytes += nextArgumentBytes;
|
|
398
|
-
pendingLegacyToolCall ??= {
|
|
399
|
-
index: 0,
|
|
400
|
-
id: `call_${randomUUID().replaceAll("-", "").slice(0, 24)}`,
|
|
401
|
-
type: "function",
|
|
402
|
-
function: {}
|
|
403
|
-
};
|
|
404
|
-
const pendingFunction = pendingLegacyToolCall.function ??= {};
|
|
405
|
-
if (nextFunctionName) pendingFunction.name = nextFunctionName;
|
|
406
|
-
if (functionCall.arguments) pendingFunction.arguments = (pendingFunction.arguments ?? "") + functionCall.arguments;
|
|
407
|
-
}
|
|
408
|
-
if (!pendingLegacyToolCall) return leadingDeltas.length > 0 ? leadingDeltas : [{
|
|
409
|
-
delta: ordinaryDelta,
|
|
410
|
-
toolCalls: []
|
|
411
|
-
}];
|
|
412
|
-
if (finishReason === "function_call") {
|
|
413
|
-
const confirmedLegacyToolCall = pendingLegacyToolCall;
|
|
414
|
-
pendingLegacyToolCall = void 0;
|
|
415
|
-
pendingLegacyArgumentBytes = 0;
|
|
416
|
-
return [
|
|
417
|
-
...leadingDeltas,
|
|
418
|
-
{
|
|
419
|
-
delta: {},
|
|
420
|
-
toolCalls: [confirmedLegacyToolCall]
|
|
421
|
-
},
|
|
422
|
-
...takePendingFollowingDeltas()
|
|
423
|
-
];
|
|
424
|
-
}
|
|
425
|
-
if (finishReason) {
|
|
426
|
-
pendingLegacyArgumentBytes = 0;
|
|
427
|
-
pendingLegacyToolCall = void 0;
|
|
428
|
-
return [...leadingDeltas, ...takePendingFollowingDeltas()];
|
|
429
|
-
}
|
|
430
|
-
return leadingDeltas;
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
/** Publish only executable calls; streaming scratch state never belongs in replay. */
|
|
434
|
-
function finalizeOpenAICompletionsToolCalls(output, options = {}) {
|
|
435
|
-
const isToolCall = (block) => block.type === "toolCall";
|
|
436
|
-
const hasToolCalls = output.content.some(isToolCall);
|
|
437
|
-
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";
|
|
442
|
-
if (output.stopReason !== "toolUse") {
|
|
443
|
-
if (hasToolCalls) output.content = output.content.filter((block) => !isToolCall(block));
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
const toolCalls = output.content.filter(isToolCall);
|
|
447
|
-
const rejectToolCalls = () => {
|
|
448
|
-
output.stopReason = "error";
|
|
449
|
-
output.errorMessage = "Provider returned an incomplete or malformed tool call";
|
|
450
|
-
output.content = output.content.filter((candidate) => !isToolCall(candidate));
|
|
451
|
-
};
|
|
452
|
-
if (toolCalls.some((call) => typeof call.name !== "string" || call.name.trim().length === 0 || typeof call.partialArgs !== "string" || call.partialArgs.trim().length === 0)) {
|
|
453
|
-
rejectToolCalls();
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
try {
|
|
457
|
-
finalizeTerminalToolCallArguments(toolCalls, (call) => call.partialArgs);
|
|
458
|
-
} catch {
|
|
459
|
-
rejectToolCalls();
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
for (let contentIndex = 0; contentIndex < output.content.length; contentIndex += 1) {
|
|
463
|
-
const block = output.content[contentIndex];
|
|
464
|
-
if (!block || !isToolCall(block)) continue;
|
|
465
|
-
delete block.partialArgs;
|
|
466
|
-
delete block.streamIndex;
|
|
467
|
-
options.onConfirmedToolCall?.(block, contentIndex);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
//#endregion
|
|
471
|
-
//#region packages/ai/src/transports/openai-completions-host.ts
|
|
472
|
-
/**
|
|
473
|
-
* Chat Completions accepts Azure AI Foundry hosts in addition to traditional
|
|
474
|
-
* Azure OpenAI hosts. Do not replace this with isTraditionalAzureOpenAIHost,
|
|
475
|
-
* which intentionally excludes the .services.ai.azure.com Foundry suffix.
|
|
476
|
-
*/
|
|
477
|
-
function isAzureOpenAICompatibleHost(hostname) {
|
|
478
|
-
return hostname.endsWith(".openai.azure.com") || hostname.endsWith(".services.ai.azure.com") || hostname.endsWith(".cognitiveservices.azure.com");
|
|
479
|
-
}
|
|
480
|
-
//#endregion
|
|
481
|
-
//#region packages/ai/src/openai-completions-messages.ts
|
|
482
|
-
/** Whether replayed messages require a tools marker for proxy compatibility. */
|
|
483
|
-
function hasToolCallHistory(messages) {
|
|
484
|
-
return messages.some((message) => message.role === "toolResult" || message.role === "assistant" && Array.isArray(message.content) && message.content.some((block) => block.type === "toolCall"));
|
|
485
|
-
}
|
|
486
|
-
/** Convert a normalized transcript to OpenAI Chat Completions messages. */
|
|
487
|
-
function convertMessages(model, context, compat, options = {}) {
|
|
488
|
-
const params = [];
|
|
489
|
-
const normalizeToolCallId = (id) => {
|
|
490
|
-
if (id.includes("|")) return id.slice(0, id.indexOf("|")).replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
|
|
491
|
-
if (model.provider === "openai") return id.length > 40 ? truncateUtf16Safe(id, 40) : id;
|
|
492
|
-
return id;
|
|
493
|
-
};
|
|
494
|
-
const transformedMessages = transformProviderMessages(context.messages, model, normalizeToolCallId);
|
|
495
|
-
let relocatableSplit;
|
|
496
|
-
let systemParamIndex;
|
|
497
|
-
if (context.systemPrompt) {
|
|
498
|
-
const role = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
|
|
499
|
-
let systemPrompt;
|
|
500
|
-
if (options.preserveSystemPromptCacheBoundary) systemPrompt = stripSystemPromptRelocatableBoundary(context.systemPrompt);
|
|
501
|
-
else {
|
|
502
|
-
const split = splitSystemPromptRelocatableBoundary(context.systemPrompt);
|
|
503
|
-
if (split && split.relocatable.length > 0) relocatableSplit = split;
|
|
504
|
-
systemPrompt = stripSystemPromptCacheBoundary(context.systemPrompt);
|
|
505
|
-
}
|
|
506
|
-
systemParamIndex = params.length;
|
|
507
|
-
params.push({
|
|
508
|
-
role,
|
|
509
|
-
content: sanitizeSurrogates(systemPrompt)
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
let lastRole = null;
|
|
513
|
-
for (let i = 0; i < transformedMessages.length; i++) {
|
|
514
|
-
const msg = transformedMessages[i];
|
|
515
|
-
if (!msg) continue;
|
|
516
|
-
if (compat.requiresAssistantAfterToolResult && lastRole === "toolResult" && msg.role === "user") params.push({
|
|
517
|
-
role: "assistant",
|
|
518
|
-
content: "I have processed the tool results."
|
|
519
|
-
});
|
|
520
|
-
if (isRuntimeContextMessage(msg)) {
|
|
521
|
-
params.push({
|
|
522
|
-
role: model.reasoning && compat.supportsDeveloperRole ? "developer" : "system",
|
|
523
|
-
content: sanitizeSurrogates(runtimeContextContentToText(msg.content))
|
|
524
|
-
});
|
|
525
|
-
options.cacheOptOutIndexes?.add(params.length - 1);
|
|
526
|
-
} else if (msg.role === "user") {
|
|
527
|
-
let userParam;
|
|
528
|
-
if (typeof msg.content === "string") userParam = {
|
|
529
|
-
role: "user",
|
|
530
|
-
content: sanitizeSurrogates(msg.content)
|
|
531
|
-
};
|
|
532
|
-
else {
|
|
533
|
-
const content = msg.content.map((item) => {
|
|
534
|
-
if (item.type === "text") return {
|
|
535
|
-
type: "text",
|
|
536
|
-
text: sanitizeSurrogates(item.text)
|
|
537
|
-
};
|
|
538
|
-
if (item.type === "video") return {
|
|
539
|
-
type: "video_url",
|
|
540
|
-
video_url: { url: `data:${item.mimeType};base64,${item.data}` }
|
|
541
|
-
};
|
|
542
|
-
return {
|
|
543
|
-
type: "image_url",
|
|
544
|
-
image_url: { url: `data:${item.mimeType};base64,${item.data}` }
|
|
545
|
-
};
|
|
546
|
-
});
|
|
547
|
-
if (content.length === 0) continue;
|
|
548
|
-
userParam = {
|
|
549
|
-
role: "user",
|
|
550
|
-
content
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
if (hasRuntimeContextMarker(msg)) options.cacheOptOutIndexes?.add(params.length);
|
|
554
|
-
params.push(userParam);
|
|
555
|
-
} else if (msg.role === "assistant") {
|
|
556
|
-
const assistantMsg = {
|
|
557
|
-
role: "assistant",
|
|
558
|
-
content: compat.requiresAssistantAfterToolResult ? "" : null
|
|
559
|
-
};
|
|
560
|
-
const assistantTexts = [];
|
|
561
|
-
const nonEmptyThinkingBlocks = [];
|
|
562
|
-
const toolCalls = [];
|
|
563
|
-
msg.content.forEach((block) => {
|
|
564
|
-
if (block.type === "text" && block.text.trim().length > 0) assistantTexts.push(sanitizeSurrogates(block.text));
|
|
565
|
-
else if (block.type === "thinking" && block.thinking.trim().length > 0) nonEmptyThinkingBlocks.push(block);
|
|
566
|
-
else if (block.type === "toolCall") toolCalls.push(block);
|
|
567
|
-
});
|
|
568
|
-
if (nonEmptyThinkingBlocks.length > 0 && compat.requiresThinkingAsText) assistantMsg.content = [{
|
|
569
|
-
type: "text",
|
|
570
|
-
text: nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n\n")
|
|
571
|
-
}, ...assistantTexts.map((text) => ({
|
|
572
|
-
type: "text",
|
|
573
|
-
text
|
|
574
|
-
}))];
|
|
575
|
-
else {
|
|
576
|
-
const assistantText = assistantTexts.join("\n");
|
|
577
|
-
if (assistantText.length > 0) assistantMsg.content = assistantText;
|
|
578
|
-
if (nonEmptyThinkingBlocks.length > 0) {
|
|
579
|
-
let signature = nonEmptyThinkingBlocks.at(0)?.thinkingSignature;
|
|
580
|
-
if (model.provider === "opencode-go" && signature === "reasoning") signature = "reasoning_content";
|
|
581
|
-
if (signature && signature.length > 0) assistantMsg[signature] = nonEmptyThinkingBlocks.map((block) => block.thinking).join("\n");
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (toolCalls.length > 0) {
|
|
585
|
-
assistantMsg.tool_calls = toolCalls.map((toolCall) => ({
|
|
586
|
-
id: toolCall.id,
|
|
587
|
-
type: "function",
|
|
588
|
-
function: {
|
|
589
|
-
name: toolCall.name,
|
|
590
|
-
arguments: JSON.stringify(toolCall.arguments)
|
|
591
|
-
}
|
|
592
|
-
}));
|
|
593
|
-
const reasoningDetails = toolCalls.flatMap((toolCall) => {
|
|
594
|
-
const signature = toolCall.thoughtSignature;
|
|
595
|
-
if (!signature) return [];
|
|
596
|
-
try {
|
|
597
|
-
const parsed = JSON.parse(signature);
|
|
598
|
-
return parsed ? [parsed] : [];
|
|
599
|
-
} catch {
|
|
600
|
-
return [];
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
if (reasoningDetails.length > 0) assistantMsg.reasoning_details = reasoningDetails;
|
|
604
|
-
}
|
|
605
|
-
if (compat.requiresReasoningContentOnAssistantMessages && model.reasoning && assistantMsg.reasoning_content === void 0) assistantMsg.reasoning_content = "";
|
|
606
|
-
const content = assistantMsg.content;
|
|
607
|
-
if (!(content !== null && content !== void 0 && content.length > 0) && !assistantMsg.tool_calls) continue;
|
|
608
|
-
params.push(assistantMsg);
|
|
609
|
-
} else if (msg.role === "toolResult") {
|
|
610
|
-
const imageContentParts = [];
|
|
611
|
-
let j = i;
|
|
612
|
-
while (j < transformedMessages.length) {
|
|
613
|
-
const toolMsg = transformedMessages.at(j);
|
|
614
|
-
if (toolMsg?.role !== "toolResult") break;
|
|
615
|
-
const textResult = extractToolResultText(toolMsg.content);
|
|
616
|
-
const mediaPlaceholder = describeToolResultMediaPlaceholder(toolMsg.content);
|
|
617
|
-
const images = toolMsg.content.filter(isImageWithMediaPayload);
|
|
618
|
-
const toolResultMsg = {
|
|
619
|
-
role: "tool",
|
|
620
|
-
content: sanitizeNonEmptyTransportPayloadText(textResult, mediaPlaceholder),
|
|
621
|
-
tool_call_id: toolMsg.toolCallId
|
|
622
|
-
};
|
|
623
|
-
if (compat.requiresToolResultName && toolMsg.toolName) toolResultMsg.name = toolMsg.toolName;
|
|
624
|
-
params.push(toolResultMsg);
|
|
625
|
-
if (images.length > 0 && model.input.includes("image")) {
|
|
626
|
-
const boundedToolName = sanitizeSurrogates(truncateUtf16Safe(toolMsg.toolName ?? "", 64));
|
|
627
|
-
imageContentParts.push({
|
|
628
|
-
type: "text",
|
|
629
|
-
text: `Image(s) from tool result #${j - i + 1}${boundedToolName ? ` (${boundedToolName})` : ""}:`
|
|
630
|
-
});
|
|
631
|
-
for (const block of images) imageContentParts.push({
|
|
632
|
-
type: "image_url",
|
|
633
|
-
image_url: { url: `data:${block.mimeType};base64,${block.data}` }
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
j += 1;
|
|
637
|
-
}
|
|
638
|
-
i = j - 1;
|
|
639
|
-
if (imageContentParts.length > 0) {
|
|
640
|
-
if (compat.requiresAssistantAfterToolResult) params.push({
|
|
641
|
-
role: "assistant",
|
|
642
|
-
content: "I have processed the tool results."
|
|
643
|
-
});
|
|
644
|
-
params.push({
|
|
645
|
-
role: "user",
|
|
646
|
-
content: imageContentParts
|
|
647
|
-
});
|
|
648
|
-
lastRole = "user";
|
|
649
|
-
} else lastRole = "toolResult";
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
lastRole = msg.role;
|
|
653
|
-
}
|
|
654
|
-
if (relocatableSplit !== void 0 && systemParamIndex !== void 0) relocateNonBehavioralRegion({
|
|
655
|
-
params,
|
|
656
|
-
systemParamIndex,
|
|
657
|
-
split: relocatableSplit,
|
|
658
|
-
cacheOptOutIndexes: options.cacheOptOutIndexes
|
|
659
|
-
});
|
|
660
|
-
return params;
|
|
661
|
-
}
|
|
662
|
-
/** Commit relocation only after finding an emitted carrier. */
|
|
663
|
-
function relocateNonBehavioralRegion(args) {
|
|
664
|
-
const text = sanitizeSurrogates(stripSystemPromptCacheBoundary(args.split.relocatable));
|
|
665
|
-
for (let index = args.systemParamIndex + 1; index < args.params.length; index++) {
|
|
666
|
-
const param = args.params[index];
|
|
667
|
-
if (!param || param.role !== "user") continue;
|
|
668
|
-
if (typeof param.content === "string") param.content = `${param.content}\n\n${text}`;
|
|
669
|
-
else if (Array.isArray(param.content)) param.content = [...param.content, {
|
|
670
|
-
type: "text",
|
|
671
|
-
text
|
|
672
|
-
}];
|
|
673
|
-
else continue;
|
|
674
|
-
const systemParam = args.params[args.systemParamIndex];
|
|
675
|
-
if (systemParam) systemParam.content = sanitizeSurrogates(stripSystemPromptCacheBoundary(args.split.remainingPrompt));
|
|
676
|
-
args.cacheOptOutIndexes?.add(index);
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
//#endregion
|
|
681
|
-
//#region packages/ai/src/transports/openai-completions-direct-policy.ts
|
|
682
|
-
function applyDirectCompletionsReasoningAndRouting(params, model, reasoning, compat) {
|
|
683
|
-
const nativeEffort = compat.thinkingFormat === "openrouter" ? void 0 : reasoning.effort;
|
|
684
|
-
const reasoningEnabled = reasoning.thinkingEnabled ?? false;
|
|
685
|
-
if (compat.thinkingFormat === "zai" && model.reasoning) params.thinking = reasoningEnabled ? {
|
|
686
|
-
type: "enabled",
|
|
687
|
-
clear_thinking: false
|
|
688
|
-
} : { type: "disabled" };
|
|
689
|
-
else if (compat.thinkingFormat === "qwen" && model.reasoning) params.enable_thinking = reasoningEnabled;
|
|
690
|
-
else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) params.chat_template_kwargs = {
|
|
691
|
-
enable_thinking: reasoningEnabled,
|
|
692
|
-
preserve_thinking: true
|
|
693
|
-
};
|
|
694
|
-
else if ((compat.thinkingFormat === "deepseek" || compat.thinkingFormat === "together") && model.reasoning) {
|
|
695
|
-
if (compat.thinkingFormat === "deepseek") params.thinking = { type: reasoningEnabled ? "enabled" : "disabled" };
|
|
696
|
-
else params.reasoning = { enabled: reasoningEnabled };
|
|
697
|
-
if (reasoningEnabled && compat.supportsReasoningEffort && nativeEffort !== void 0) params.reasoning_effort = nativeEffort;
|
|
698
|
-
} else if (model.reasoning && compat.supportsReasoningEffort && nativeEffort !== void 0) params.reasoning_effort = nativeEffort;
|
|
699
|
-
if (compat.openRouterRouting) params.provider = compat.openRouterRouting;
|
|
700
|
-
if (model.baseUrl.includes("ai-gateway.vercel.sh") && model.compat?.vercelGatewayRouting) {
|
|
701
|
-
const routing = model.compat.vercelGatewayRouting;
|
|
702
|
-
if (routing.only || routing.order) {
|
|
703
|
-
const gatewayOptions = {};
|
|
704
|
-
if (routing.only) gatewayOptions.only = routing.only;
|
|
705
|
-
if (routing.order) gatewayOptions.order = routing.order;
|
|
706
|
-
params.providerOptions = { gateway: gatewayOptions };
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
//#endregion
|
|
711
|
-
//#region packages/ai/src/transports/openai-completions-replay.ts
|
|
712
|
-
function isGoogleOpenAICompatModel(model) {
|
|
713
|
-
const endpointClass = detectOpenAICompletionsCompat(model).capabilities.endpointClass;
|
|
714
|
-
return model.provider === "google" || endpointClass === "google-generative-ai" || endpointClass === "google-vertex";
|
|
715
|
-
}
|
|
716
|
-
const GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE = /[\u2026]|\.\.\./;
|
|
717
|
-
const GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE = /^[A-Za-z0-9+/=]+$/;
|
|
718
|
-
function hasGoogleCompatThoughtSignatureTruncationFootprint(value) {
|
|
719
|
-
return GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE.test(value) || GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE.test(value) && value.length % 4 !== 0;
|
|
720
|
-
}
|
|
721
|
-
function injectToolCallThoughtSignatures(outgoingMessages, context, model) {
|
|
722
|
-
if (!isGoogleOpenAICompatModel(model)) return;
|
|
723
|
-
const sigById = /* @__PURE__ */ new Map();
|
|
724
|
-
const fallbackSig = isGoogleGemini3ThinkingLevelModel(model.id) ? GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP : void 0;
|
|
725
|
-
for (const msg of context.messages ?? []) {
|
|
726
|
-
if (msg.role !== "assistant") continue;
|
|
727
|
-
if (!Array.isArray(msg.content)) continue;
|
|
728
|
-
for (const block of msg.content) {
|
|
729
|
-
if (block.type !== "toolCall") continue;
|
|
730
|
-
const id = block.id;
|
|
731
|
-
const sig = block.thoughtSignature;
|
|
732
|
-
if (typeof id === "string" && typeof sig === "string" && sig.length > 0) {
|
|
733
|
-
const isSameRoute = msg.api === model.api && msg.provider === model.provider && msg.model === model.id;
|
|
734
|
-
if (!isSameRoute && !fallbackSig) continue;
|
|
735
|
-
sigById.set(id, isSameRoute ? sig : fallbackSig ?? sig);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
if (sigById.size === 0 && !fallbackSig) return;
|
|
740
|
-
for (const message of outgoingMessages) {
|
|
741
|
-
const toolCalls = message.tool_calls;
|
|
742
|
-
if (!Array.isArray(toolCalls)) continue;
|
|
743
|
-
for (const toolCall of toolCalls) {
|
|
744
|
-
const id = toolCall.id;
|
|
745
|
-
if (typeof id !== "string") continue;
|
|
746
|
-
let sig = sigById.get(id) ?? fallbackSig;
|
|
747
|
-
if (typeof sig === "string" && sig.length > 0) {
|
|
748
|
-
if (hasGoogleCompatThoughtSignatureTruncationFootprint(sig.trim())) sig = fallbackSig;
|
|
749
|
-
}
|
|
750
|
-
if (typeof sig !== "string" || sig.length === 0) continue;
|
|
751
|
-
const extra = toolCall.extra_content && typeof toolCall.extra_content === "object" ? toolCall.extra_content : {};
|
|
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;
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
const COMPLETIONS_REASONING_REPLAY_FIELDS = [
|
|
760
|
-
"reasoning_details",
|
|
761
|
-
"reasoning_content",
|
|
762
|
-
"reasoning",
|
|
763
|
-
"reasoning_text"
|
|
764
|
-
];
|
|
765
|
-
function stripCompletionsReasoningReplayFields(record) {
|
|
766
|
-
for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) delete record[field];
|
|
767
|
-
}
|
|
768
|
-
function sanitizeOpenRouterReasoningReplayFields(record) {
|
|
769
|
-
const reasoningDetails = record.reasoning_details;
|
|
770
|
-
if (typeof reasoningDetails === "string") {
|
|
771
|
-
if (reasoningDetails.length > 0 && typeof record.reasoning !== "string") record.reasoning = reasoningDetails;
|
|
772
|
-
delete record.reasoning_details;
|
|
773
|
-
} else if (reasoningDetails !== void 0 && !Array.isArray(reasoningDetails)) delete record.reasoning_details;
|
|
774
|
-
for (const field of ["reasoning", "reasoning_content"]) if (field in record && (typeof record[field] !== "string" || record[field] === "")) delete record[field];
|
|
775
|
-
const reasoningText = record.reasoning_text;
|
|
776
|
-
if (typeof reasoningText === "string" && reasoningText.length > 0 && typeof record.reasoning !== "string" && typeof record.reasoning_content !== "string") record.reasoning = reasoningText;
|
|
777
|
-
delete record.reasoning_text;
|
|
778
|
-
}
|
|
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
|
-
const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
786
|
-
"deepseek-v4-flash",
|
|
787
|
-
"deepseek-v4-pro",
|
|
788
|
-
"kimi-for-coding",
|
|
789
|
-
"kimi-k2.5",
|
|
790
|
-
"kimi-k2.6",
|
|
791
|
-
"kimi-k2.7-code",
|
|
792
|
-
"kimi-k2.7-code-highspeed",
|
|
793
|
-
"kimi-k3",
|
|
794
|
-
"kimi-k2-thinking",
|
|
795
|
-
"kimi-k2-thinking-turbo",
|
|
796
|
-
"mimo-v2-pro",
|
|
797
|
-
"mimo-v2-omni",
|
|
798
|
-
"mimo-v2.5",
|
|
799
|
-
"mimo-v2.5-pro",
|
|
800
|
-
"mimo-v2.6-flash",
|
|
801
|
-
"mimo-v2.6-pro",
|
|
802
|
-
"mimo-v2.6-pro-ultraspeed"
|
|
803
|
-
]);
|
|
804
|
-
const REASONING_CONTENT_REPLAY_TIER_SUFFIXES = [
|
|
805
|
-
"-free",
|
|
806
|
-
"-paid",
|
|
807
|
-
"-trial"
|
|
808
|
-
];
|
|
809
|
-
function stripReasoningContentReplayTierSuffix(modelId) {
|
|
810
|
-
for (const suffix of REASONING_CONTENT_REPLAY_TIER_SUFFIXES) if (modelId.length > suffix.length && modelId.endsWith(suffix)) return modelId.slice(0, -suffix.length);
|
|
811
|
-
return modelId;
|
|
812
|
-
}
|
|
813
|
-
function getReasoningContentReplayModelIdCandidates(modelId) {
|
|
814
|
-
if (typeof modelId !== "string") return [];
|
|
815
|
-
const normalized = modelId.trim().toLowerCase();
|
|
816
|
-
if (!normalized) return [];
|
|
817
|
-
const parts = normalized.split("/").filter(Boolean);
|
|
818
|
-
const finalPart = parts[parts.length - 1] ?? normalized;
|
|
819
|
-
const candidates = [finalPart];
|
|
820
|
-
const colonParts = finalPart.split(":").filter(Boolean);
|
|
821
|
-
if (colonParts.length > 1) candidates.push(colonParts[0] ?? "", colonParts[colonParts.length - 1] ?? "");
|
|
822
|
-
for (const candidate of candidates.slice()) {
|
|
823
|
-
const stripped = stripReasoningContentReplayTierSuffix(candidate);
|
|
824
|
-
if (stripped !== candidate) candidates.push(stripped);
|
|
825
|
-
}
|
|
826
|
-
return uniqueStrings(candidates.filter(Boolean));
|
|
827
|
-
}
|
|
828
|
-
function shouldPreserveReasoningContentReplay(model, compat) {
|
|
829
|
-
if (compat.requiresReasoningContentOnAssistantMessages || compat.thinkingFormat === "deepseek" || compat.thinkingFormat === "zai" || shouldTrustReasoningContentReplayMetadata(model)) return true;
|
|
830
|
-
return getReasoningContentReplayModelIdCandidates(model.id).some((modelId) => REASONING_CONTENT_REPLAY_MODEL_IDS.has(modelId));
|
|
831
|
-
}
|
|
832
|
-
function shouldPreserveOpenRouterReasoningReplay(model) {
|
|
833
|
-
if (model.provider !== "openrouter") return true;
|
|
834
|
-
const normalizedModelId = model.id.trim().toLowerCase();
|
|
835
|
-
return !(normalizedModelId.startsWith("anthropic/") || normalizedModelId.startsWith("x-ai/"));
|
|
836
|
-
}
|
|
837
|
-
function shouldTrustReasoningContentReplayMetadata(model) {
|
|
838
|
-
if (!model.reasoning) return false;
|
|
839
|
-
if (model.provider.trim().toLowerCase() === "openai") return false;
|
|
840
|
-
return shouldPreserveOpenRouterReasoningReplay(model);
|
|
841
|
-
}
|
|
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
|
-
function applyCompletionsReplay(outgoingMessages, context, model, compat) {
|
|
854
|
-
injectToolCallThoughtSignatures(outgoingMessages, context, model);
|
|
855
|
-
sanitizeCompletionsReasoningReplayFields(outgoingMessages, {
|
|
856
|
-
preserveOpenRouterReasoning: compat.thinkingFormat === "openrouter" && shouldPreserveOpenRouterReasoningReplay(model),
|
|
857
|
-
preserveReasoningContent: shouldPreserveReasoningContentReplay(model, compat)
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
//#endregion
|
|
861
|
-
//#region packages/ai/src/transports/openai-completions-params.ts
|
|
862
|
-
function isKnownOpenAICompletionsEndpoint(model) {
|
|
863
|
-
if (!model.baseUrl.trim() || isNativeOpenAIEndpoint(model)) return true;
|
|
864
|
-
const endpointClass = resolveProviderEndpoint(model).endpointClass;
|
|
865
|
-
if (endpointClass === "openai-public" || endpointClass === "azure-openai") return true;
|
|
866
|
-
const endpoint = URL.parse(model.baseUrl);
|
|
867
|
-
return endpoint !== null && isAzureOpenAICompatibleHost(endpoint.hostname.toLowerCase());
|
|
868
|
-
}
|
|
869
|
-
function resolveOpenAICompletionsMaxTokens(model, options) {
|
|
870
|
-
if (options?.maxTokens) return {
|
|
871
|
-
maxTokens: options.maxTokens,
|
|
872
|
-
clampToModelMaxTokens: true
|
|
873
|
-
};
|
|
874
|
-
const paramsMaxTokens = resolveMaxTokensParam(model.params);
|
|
875
|
-
if (paramsMaxTokens) return {
|
|
876
|
-
maxTokens: paramsMaxTokens,
|
|
877
|
-
clampToModelMaxTokens: false
|
|
878
|
-
};
|
|
879
|
-
return {
|
|
880
|
-
maxTokens: model.maxTokens,
|
|
881
|
-
clampToModelMaxTokens: false
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
function resolveOpenAICompletionsModelMaxTokens(model) {
|
|
885
|
-
return typeof model.maxTokens === "number" && Number.isFinite(model.maxTokens) && model.maxTokens > 0 ? Math.floor(model.maxTokens) : void 0;
|
|
886
|
-
}
|
|
887
|
-
const OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN = 1.25;
|
|
888
|
-
const OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE = 8e3;
|
|
889
|
-
const MIN_USEFUL_OUTPUT_TOKENS = 16;
|
|
890
|
-
function estimateJsonChars(value, fallback) {
|
|
891
|
-
try {
|
|
892
|
-
return estimateStringChars(JSON.stringify(value));
|
|
893
|
-
} catch {
|
|
894
|
-
return fallback;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
function estimateOpenAICompletionsInputTokens(payload) {
|
|
898
|
-
let adjustedChars = 0;
|
|
899
|
-
adjustedChars += estimateOpenAICompletionsMessagesChars(payload.messages);
|
|
900
|
-
if (Array.isArray(payload.tools) && payload.tools.length > 0) adjustedChars += estimateJsonChars(payload.tools, 1024);
|
|
901
|
-
if (payload.response_format !== void 0) adjustedChars += estimateJsonChars(payload.response_format, 256);
|
|
902
|
-
return Math.ceil(adjustedChars / 4 * OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN);
|
|
903
|
-
}
|
|
904
|
-
function estimateOpenAICompletionsMessagesChars(messages) {
|
|
905
|
-
if (!Array.isArray(messages)) return 0;
|
|
906
|
-
let adjustedChars = 0;
|
|
907
|
-
for (const message of messages) {
|
|
908
|
-
if (!message || typeof message !== "object") continue;
|
|
909
|
-
const record = message;
|
|
910
|
-
adjustedChars += estimateOpenAICompletionsContentChars(record.content);
|
|
911
|
-
for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) adjustedChars += estimateOpenAICompletionsContentChars(record[field]);
|
|
912
|
-
if (record.tool_calls !== void 0) adjustedChars += estimateJsonChars(record.tool_calls, 256);
|
|
913
|
-
}
|
|
914
|
-
return adjustedChars;
|
|
915
|
-
}
|
|
916
|
-
function estimateOpenAICompletionsContentChars(value) {
|
|
917
|
-
if (typeof value === "string") return estimateStringChars(value);
|
|
918
|
-
if (!Array.isArray(value)) return 0;
|
|
919
|
-
let adjustedChars = 0;
|
|
920
|
-
for (const block of value) {
|
|
921
|
-
if (!block || typeof block !== "object") continue;
|
|
922
|
-
const record = block;
|
|
923
|
-
if (record.type === "image_url" || record.type === "input_image") {
|
|
924
|
-
adjustedChars += OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE;
|
|
925
|
-
continue;
|
|
926
|
-
}
|
|
927
|
-
const text = record.text;
|
|
928
|
-
if (typeof text === "string") {
|
|
929
|
-
adjustedChars += estimateStringChars(text);
|
|
930
|
-
continue;
|
|
931
|
-
}
|
|
932
|
-
adjustedChars += estimateJsonChars(block, 256);
|
|
933
|
-
}
|
|
934
|
-
return adjustedChars;
|
|
935
|
-
}
|
|
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
|
-
function convertTools(tools, compat, model, mode) {
|
|
966
|
-
const prepared = mode === "managed" ? prepareOpenAITools(tools) : void 0;
|
|
967
|
-
const projection = prepared?.projection ?? projectOpenAITools(tools);
|
|
968
|
-
const convert = () => {
|
|
969
|
-
const strict = mode === "direct" ? compat.supportsStrictMode ? false : void 0 : resolveOpenAIStrictToolFlagWithDiagnostics(projection, getAiTransportHost().resolveOpenAIStrictToolSetting(model, {
|
|
970
|
-
transport: "stream",
|
|
971
|
-
supportsStrictMode: compat?.supportsStrictMode
|
|
972
|
-
}), {
|
|
973
|
-
transport: "completions",
|
|
974
|
-
model
|
|
975
|
-
});
|
|
976
|
-
return {
|
|
977
|
-
projection,
|
|
978
|
-
tools: sortPromptCacheToolsByName(projection.tools).map((tool) => {
|
|
979
|
-
const functionTool = {
|
|
980
|
-
name: tool.name,
|
|
981
|
-
description: tool.description,
|
|
982
|
-
parameters: mode === "direct" ? tool.parameters : normalizeOpenAIStrictToolParameters(tool.parameters, strict === true, model.compat)
|
|
983
|
-
};
|
|
984
|
-
if (strict !== void 0) functionTool.strict = strict;
|
|
985
|
-
return {
|
|
986
|
-
type: "function",
|
|
987
|
-
function: functionTool
|
|
988
|
-
};
|
|
989
|
-
})
|
|
990
|
-
};
|
|
991
|
-
};
|
|
992
|
-
return prepared ? withPreparedToolSchemaNormalization(prepared.schemas, convert) : convert();
|
|
993
|
-
}
|
|
994
|
-
function buildOpenAICompletionsParams(model, context, options) {
|
|
995
|
-
return buildOpenAICompletionsRequest(model, context, options, { mode: "managed" });
|
|
996
|
-
}
|
|
997
|
-
function buildOpenAICompletionsRequest(model, context, options, policy) {
|
|
998
|
-
const resolvedPolicy = policy.mode === "direct" ? policy : {
|
|
999
|
-
...policy,
|
|
1000
|
-
compat: getCompat(model)
|
|
1001
|
-
};
|
|
1002
|
-
const compat = resolvedPolicy.compat;
|
|
1003
|
-
const managedCompat = resolvedPolicy.mode === "managed" ? resolvedPolicy.compat : void 0;
|
|
1004
|
-
const endpointDetection = detectOpenAICompletionsCompat(model);
|
|
1005
|
-
const compatDetection = policy.mode === "managed" ? endpointDetection : void 0;
|
|
1006
|
-
const { endpointClass } = endpointDetection.capabilities;
|
|
1007
|
-
const cacheRetention = policy.mode === "direct" ? policy.cacheRetention : resolveCacheRetention(options?.cacheRetention);
|
|
1008
|
-
const cacheControl = resolveCompletionsCacheControl(compat, cacheRetention, endpointClass === "openrouter" || endpointClass === "default" && model.provider === "openrouter");
|
|
1009
|
-
const markTools = endpointClass !== "modelstudio-native" && !(endpointClass === "default" && [
|
|
1010
|
-
"modelstudio",
|
|
1011
|
-
"dashscope",
|
|
1012
|
-
"qwen"
|
|
1013
|
-
].includes(model.provider));
|
|
1014
|
-
const cacheOptOutIndexes = /* @__PURE__ */ new Set();
|
|
1015
|
-
let messages = convertMessages(model, context, compat, {
|
|
1016
|
-
cacheOptOutIndexes,
|
|
1017
|
-
preserveSystemPromptCacheBoundary: cacheControl !== void 0 && !managedCompat?.requiresStringContent
|
|
1018
|
-
});
|
|
1019
|
-
if (managedCompat) {
|
|
1020
|
-
applyCompletionsReplay(messages, context, model, managedCompat);
|
|
1021
|
-
if (managedCompat.strictMessageKeys) messages = stripCompletionMessagesToRoleContent(messages);
|
|
1022
|
-
if (managedCompat.requiresStringContent) messages = flattenCompletionMessagesToStringContent(messages);
|
|
1023
|
-
}
|
|
1024
|
-
const promptCacheKey = resolvePromptCacheKey(options, cacheRetention);
|
|
1025
|
-
const params = {
|
|
1026
|
-
model: model.id,
|
|
1027
|
-
messages,
|
|
1028
|
-
stream: true,
|
|
1029
|
-
...resolveOpenAIPromptCacheParams(model, cacheRetention, compat)
|
|
1030
|
-
};
|
|
1031
|
-
if (compat.supportsUsageInStreaming) params.stream_options = { include_usage: true };
|
|
1032
|
-
if (compat.supportsStore) params.store = false;
|
|
1033
|
-
if (policy.mode === "direct" || compat.supportsPromptCacheKey && promptCacheKey) params.prompt_cache_key = compat.supportsPromptCacheKey ? promptCacheKey : void 0;
|
|
1034
|
-
if (options?.temperature !== void 0) params.temperature = options.temperature;
|
|
1035
|
-
if (policy.mode === "managed" && options?.topP !== void 0) params.top_p = options.topP;
|
|
1036
|
-
const requestedResponseFormat = options?.responseFormat;
|
|
1037
|
-
const responseFormat = policy.mode === "direct" && requestedResponseFormat === void 0 ? void 0 : resolveOpenAICompletionsResponseFormat(shouldOmitOllamaCompatResponseFormat({
|
|
1038
|
-
provider: model.provider,
|
|
1039
|
-
baseUrl: model.baseUrl,
|
|
1040
|
-
hasTools: () => Boolean(context.tools?.length)
|
|
1041
|
-
}) ? void 0 : requestedResponseFormat, compat.supportsJsonSchemaResponseFormat);
|
|
1042
|
-
if (responseFormat !== void 0) params.response_format = responseFormat;
|
|
1043
|
-
if (policy.mode === "managed" && options?.frequencyPenalty !== void 0) params.frequency_penalty = options.frequencyPenalty;
|
|
1044
|
-
if (policy.mode === "managed" && options?.presencePenalty !== void 0) params.presence_penalty = options.presencePenalty;
|
|
1045
|
-
if (policy.mode === "managed" && options?.seed !== void 0) params.seed = options.seed;
|
|
1046
|
-
if (options?.stop !== void 0 && options.stop.length > 0) params.stop = options.stop;
|
|
1047
|
-
let directToolProjection;
|
|
1048
|
-
if (policy.mode === "direct" || supportsModelTools(model)) {
|
|
1049
|
-
if (context.tools) {
|
|
1050
|
-
const converted = convertTools(context.tools, compat, model, policy.mode);
|
|
1051
|
-
if (policy.mode === "direct") directToolProjection = converted.projection;
|
|
1052
|
-
if (converted.tools.length > 0 || policy.mode === "managed" && converted.projection.inputToolCount === 0 && converted.projection.diagnostics.length === 0) params.tools = converted.tools;
|
|
1053
|
-
else if (hasToolCallHistory(context.messages)) params.tools = [];
|
|
1054
|
-
if (policy.mode === "direct" && compat.zaiToolStream && converted.tools.length > 0) params.tool_stream = true;
|
|
1055
|
-
if (policy.mode === "managed" && options?.toolChoice) {
|
|
1056
|
-
const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, converted.projection);
|
|
1057
|
-
if (toolChoice !== void 0) params.tool_choice = toolChoice;
|
|
1058
|
-
} else if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length > 0) params.tool_choice = "auto";
|
|
1059
|
-
} else if (hasToolCallHistory(context.messages)) params.tools = [];
|
|
1060
|
-
if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length === 0) {
|
|
1061
|
-
delete params.tools;
|
|
1062
|
-
delete params.tool_choice;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
if (policy.mode === "direct" && compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools);
|
|
1066
|
-
if (policy.mode === "direct" && options?.toolChoice) {
|
|
1067
|
-
const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, directToolProjection ?? projectOpenAITools([]));
|
|
1068
|
-
if (toolChoice !== void 0) params.tool_choice = toolChoice;
|
|
1069
|
-
}
|
|
1070
|
-
const isOpenRouter = compat.thinkingFormat === "openrouter";
|
|
1071
|
-
const usesBinaryOpenRouterThinking = isOpenRouter && (model.compat?.supportsReasoningEffort === false || model.compat?.supportedReasoningEfforts?.length === 0);
|
|
1072
|
-
const simpleReasoning = options?.reasoning;
|
|
1073
|
-
const requestedEffort = policy.mode === "direct" ? options?.reasoningEffort : options?.reasoningEffort ?? (simpleReasoning === "none" ? "none" : resolveOpenAISimpleReasoningEffort({
|
|
1074
|
-
...model,
|
|
1075
|
-
compat: model.compat ?? void 0
|
|
1076
|
-
}, simpleReasoning)) ?? (usesBinaryOpenRouterThinking ? void 0 : "high");
|
|
1077
|
-
const reasoning = resolveOpenAIRequestReasoning(model, requestedEffort);
|
|
1078
|
-
const { effort, thinkingEnabled } = reasoning;
|
|
1079
|
-
{
|
|
1080
|
-
const maxTokenBudget = policy.mode === "direct" ? {
|
|
1081
|
-
maxTokens: options?.maxTokens,
|
|
1082
|
-
clampToModelMaxTokens: true
|
|
1083
|
-
} : resolveOpenAICompletionsMaxTokens(model, options);
|
|
1084
|
-
const effectiveMaxTokens = maxTokenBudget.maxTokens;
|
|
1085
|
-
const effectiveContextTokens = resolveOpenAICompletionsEffectiveContextTokens(model);
|
|
1086
|
-
let clampedMaxTokens = effectiveMaxTokens;
|
|
1087
|
-
const modelMaxTokens = resolveOpenAICompletionsModelMaxTokens(model);
|
|
1088
|
-
if (maxTokenBudget.clampToModelMaxTokens && clampedMaxTokens !== void 0 && modelMaxTokens !== void 0 && clampedMaxTokens > modelMaxTokens) {
|
|
1089
|
-
clampedMaxTokens = modelMaxTokens;
|
|
1090
|
-
if (policy.mode === "managed") emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} modelMaxTokens=${modelMaxTokens}`);
|
|
1091
|
-
}
|
|
1092
|
-
if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && clampedMaxTokens !== void 0 && effectiveContextTokens !== void 0) {
|
|
1093
|
-
const estimatedInputTokens = estimateOpenAICompletionsInputTokens(params);
|
|
1094
|
-
const remainingBudget = Math.max(1, effectiveContextTokens - estimatedInputTokens - 1);
|
|
1095
|
-
if (clampedMaxTokens > remainingBudget) {
|
|
1096
|
-
clampedMaxTokens = remainingBudget;
|
|
1097
|
-
emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} effectiveContext=${effectiveContextTokens} estimatedInput=${estimatedInputTokens}`);
|
|
1098
|
-
if (remainingBudget < MIN_USEFUL_OUTPUT_TOKENS) {
|
|
1099
|
-
if (model.reasoning && thinkingEnabled !== false) throw Object.assign(/* @__PURE__ */ new Error(`Context window exceeded: estimated input ${estimatedInputTokens} leaves only ${remainingBudget} output tokens within the ${effectiveContextTokens}-token context.`), { code: "context_length_exceeded" });
|
|
1100
|
-
log.warn(`[completions] insufficient_output_budget provider=${model.provider} api=${model.api} model=${model.id} output=${clampedMaxTokens} effectiveContext=${effectiveContextTokens} estimatedInput=${estimatedInputTokens}`);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
if (policy.mode === "direct" ? options?.maxTokens : clampedMaxTokens) {
|
|
1105
|
-
if (compat.maxTokensField === "max_tokens") params.max_tokens = clampedMaxTokens;
|
|
1106
|
-
else params.max_completion_tokens = clampedMaxTokens;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
if (isOpenRouter && model.reasoning) {
|
|
1110
|
-
if (usesBinaryOpenRouterThinking && thinkingEnabled !== void 0) params.reasoning = { enabled: thinkingEnabled };
|
|
1111
|
-
else if (effort !== void 0) params.reasoning = { effort };
|
|
1112
|
-
}
|
|
1113
|
-
if (policy.mode === "direct") applyDirectCompletionsReasoningAndRouting(params, model, reasoning, compat);
|
|
1114
|
-
else {
|
|
1115
|
-
const suppressScalarEffort = applyBinaryCompletionsThinkingParams({
|
|
1116
|
-
compatThinkingFormat: compat.thinkingFormat,
|
|
1117
|
-
modelReasoning: model.reasoning,
|
|
1118
|
-
payload: params,
|
|
1119
|
-
thinkingEnabled: thinkingEnabled ?? false
|
|
1120
|
-
});
|
|
1121
|
-
if (!isOpenRouter && effort && model.reasoning && compat.supportsReasoningEffort && !suppressScalarEffort) params.reasoning_effort = effort;
|
|
1122
|
-
if (compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools, !managedCompat?.requiresStringContent);
|
|
1123
|
-
}
|
|
1124
|
-
if (params.tools?.length && isKnownOpenAICompletionsEndpoint(model)) {
|
|
1125
|
-
if (isOpenAIGpt56Model(model)) params.reasoning_effort = "none";
|
|
1126
|
-
else if (isOpenAIGpt54MiniModel(model) || isOpenAIGpt55Model(model)) delete params.reasoning_effort;
|
|
1127
|
-
}
|
|
1128
|
-
return params;
|
|
1129
|
-
}
|
|
1130
|
-
//#endregion
|
|
1131
|
-
//#region packages/ai/src/transports/openai-completions-dsml.ts
|
|
1132
|
-
const DEEPSEEK_DSML_TOOL_KINDS = [
|
|
1133
|
-
"tool_calls",
|
|
1134
|
-
"tool_call",
|
|
1135
|
-
"function_calls"
|
|
1136
|
-
];
|
|
1137
|
-
const DEEPSEEK_DSML_TOOL_OPEN_TOKENS = DEEPSEEK_DSML_MARKERS.flatMap((marker) => DEEPSEEK_DSML_TOOL_KINDS.map((kind) => `<${marker}${kind}>`));
|
|
1138
|
-
const DEEPSEEK_DSML_INVOKE_OPEN_PREFIXES = DEEPSEEK_DSML_MARKERS.map((marker) => `<${marker}invoke`);
|
|
1139
|
-
const DEEPSEEK_DSML_INVOKE_OPEN_TAG = new RegExp(`^<${DEEPSEEK_DSML_MARKER_PATTERN}invoke\\b[^<>]*>$`);
|
|
1140
|
-
const DEEPSEEK_DSML_TOOL_MAX_OPEN_TOKEN_LEN = Math.max(...DEEPSEEK_DSML_TOOL_OPEN_TOKENS.map((token) => token.length));
|
|
1141
|
-
const DEEPSEEK_DSML_RECOVERY_MAX_BOUNDARY_LEN = Math.max(...DEEPSEEK_DSML_TOOL_OPEN_TOKENS.map((token) => token.length + 1), ...DEEPSEEK_DSML_INVOKE_OPEN_PREFIXES.map((token) => token.length + 2));
|
|
1142
|
-
const MAX_DSML_RECOVERY_BUFFER_BYTES = 256e3;
|
|
1143
|
-
const DEEPSEEK_DSML_SCAN_BATCH_CHARS = 65536;
|
|
1144
|
-
const DSML_NAME_ATTRIBUTE_RE = /\bname=("([^"]*)"|'([^']*)'|([^\s>]+))/;
|
|
1145
|
-
function createDsmlRecoverer() {
|
|
1146
|
-
let buffer = "";
|
|
1147
|
-
let bufferBytes = 0;
|
|
1148
|
-
let bufferEndsWithHighSurrogate = false;
|
|
1149
|
-
let pendingScanChars = 0;
|
|
1150
|
-
let activeOpenToken = null;
|
|
1151
|
-
let blockScanState = { offset: 0 };
|
|
1152
|
-
const resetBlockScan = () => {
|
|
1153
|
-
activeOpenToken = null;
|
|
1154
|
-
pendingScanChars = 0;
|
|
1155
|
-
blockScanState = { offset: 0 };
|
|
1156
|
-
};
|
|
1157
|
-
const consume = (final) => {
|
|
1158
|
-
const output = [];
|
|
1159
|
-
while (buffer) {
|
|
1160
|
-
const open = activeOpenToken ? {
|
|
1161
|
-
index: 0,
|
|
1162
|
-
token: activeOpenToken
|
|
1163
|
-
} : findEarliestDsmlToken(buffer, DEEPSEEK_DSML_TOOL_OPEN_TOKENS);
|
|
1164
|
-
if (!open) {
|
|
1165
|
-
resetBlockScan();
|
|
1166
|
-
if (final) {
|
|
1167
|
-
output.push({
|
|
1168
|
-
kind: "text",
|
|
1169
|
-
text: buffer
|
|
1170
|
-
});
|
|
1171
|
-
buffer = "";
|
|
1172
|
-
bufferBytes = 0;
|
|
1173
|
-
bufferEndsWithHighSurrogate = false;
|
|
1174
|
-
return output;
|
|
1175
|
-
}
|
|
1176
|
-
const keep = longestDsmlTokenPrefixSuffixLength(buffer, DEEPSEEK_DSML_TOOL_OPEN_TOKENS, DEEPSEEK_DSML_TOOL_MAX_OPEN_TOKEN_LEN);
|
|
1177
|
-
const emitLength = buffer.length - keep;
|
|
1178
|
-
if (emitLength > 0) {
|
|
1179
|
-
const emitted = buffer.slice(0, emitLength);
|
|
1180
|
-
output.push({
|
|
1181
|
-
kind: "text",
|
|
1182
|
-
text: emitted
|
|
1183
|
-
});
|
|
1184
|
-
bufferBytes -= Buffer.byteLength(emitted, "utf8");
|
|
1185
|
-
buffer = buffer.slice(emitted.length);
|
|
1186
|
-
if (!buffer) bufferEndsWithHighSurrogate = false;
|
|
1187
|
-
}
|
|
1188
|
-
return output;
|
|
1189
|
-
}
|
|
1190
|
-
if (open.index > 0) {
|
|
1191
|
-
const prefix = buffer.slice(0, open.index);
|
|
1192
|
-
output.push({
|
|
1193
|
-
kind: "text",
|
|
1194
|
-
text: prefix
|
|
1195
|
-
});
|
|
1196
|
-
bufferBytes -= Buffer.byteLength(prefix, "utf8");
|
|
1197
|
-
buffer = buffer.slice(prefix.length);
|
|
1198
|
-
resetBlockScan();
|
|
1199
|
-
}
|
|
1200
|
-
activeOpenToken = open.token;
|
|
1201
|
-
if (blockScanState.offset === 0) blockScanState.offset = open.token.length;
|
|
1202
|
-
const blockScan = scanDeepSeekDsmlToolBlock(buffer, open.token.replace("<", "</"), open.token.length, blockScanState);
|
|
1203
|
-
if (blockScan.kind === "nested-open") throw new Error("Nested DeepSeek DSML recovery wrappers are not supported");
|
|
1204
|
-
const close = blockScan.kind === "close" ? blockScan : null;
|
|
1205
|
-
if (!close) {
|
|
1206
|
-
if (final) {
|
|
1207
|
-
output.push({
|
|
1208
|
-
kind: "text",
|
|
1209
|
-
text: buffer
|
|
1210
|
-
});
|
|
1211
|
-
buffer = "";
|
|
1212
|
-
bufferBytes = 0;
|
|
1213
|
-
bufferEndsWithHighSurrogate = false;
|
|
1214
|
-
return output;
|
|
1215
|
-
}
|
|
1216
|
-
if (bufferBytes > MAX_DSML_RECOVERY_BUFFER_BYTES) throw new Error("Exceeded DeepSeek DSML recovery buffer limit");
|
|
1217
|
-
return output;
|
|
1218
|
-
}
|
|
1219
|
-
resetBlockScan();
|
|
1220
|
-
const body = buffer.slice(open.token.length, close.index);
|
|
1221
|
-
const blockText = buffer.slice(0, close.index + close.token.length);
|
|
1222
|
-
if (Buffer.byteLength(blockText, "utf8") > MAX_DSML_RECOVERY_BUFFER_BYTES) throw new Error("Exceeded DeepSeek DSML recovery buffer limit");
|
|
1223
|
-
const recoveredToolCalls = parseDeepSeekDsmlToolCallBlock(body);
|
|
1224
|
-
if (recoveredToolCalls.length > 0) output.push(...recoveredToolCalls);
|
|
1225
|
-
else output.push({
|
|
1226
|
-
kind: "text",
|
|
1227
|
-
text: blockText
|
|
1228
|
-
});
|
|
1229
|
-
bufferBytes -= Buffer.byteLength(blockText, "utf8");
|
|
1230
|
-
buffer = buffer.slice(blockText.length);
|
|
1231
|
-
if (!buffer) bufferEndsWithHighSurrogate = false;
|
|
1232
|
-
}
|
|
1233
|
-
return output;
|
|
1234
|
-
};
|
|
1235
|
-
return {
|
|
1236
|
-
push(chunk) {
|
|
1237
|
-
const append = measureUtf8AppendBytes(bufferEndsWithHighSurrogate, chunk);
|
|
1238
|
-
bufferBytes += append.bytes;
|
|
1239
|
-
bufferEndsWithHighSurrogate = append.endsWithHighSurrogate;
|
|
1240
|
-
buffer += chunk;
|
|
1241
|
-
pendingScanChars += chunk.length;
|
|
1242
|
-
if (activeOpenToken && pendingScanChars < DEEPSEEK_DSML_SCAN_BATCH_CHARS && !chunk.includes("<") && !chunk.includes(">") && bufferBytes <= MAX_DSML_RECOVERY_BUFFER_BYTES) return [];
|
|
1243
|
-
pendingScanChars = 0;
|
|
1244
|
-
return consume(false);
|
|
1245
|
-
},
|
|
1246
|
-
flush() {
|
|
1247
|
-
return consume(true);
|
|
1248
|
-
}
|
|
1249
|
-
};
|
|
1250
|
-
}
|
|
1251
|
-
function parseDeepSeekDsmlToolCallBlock(body) {
|
|
1252
|
-
const toolCalls = [];
|
|
1253
|
-
const invokeOpenRegex = new RegExp(`<${DEEPSEEK_DSML_MARKER_PATTERN}invoke\\b([^<>]*)>`, "g");
|
|
1254
|
-
let openMatch;
|
|
1255
|
-
while ((openMatch = invokeOpenRegex.exec(body)) !== null) {
|
|
1256
|
-
const invokeBodyStart = openMatch.index + openMatch[0].length;
|
|
1257
|
-
const invokeCloseToken = `</${openMatch[1]}invoke>`;
|
|
1258
|
-
const invokeCloseIndex = body.indexOf(invokeCloseToken, invokeBodyStart);
|
|
1259
|
-
if (invokeCloseIndex === -1) break;
|
|
1260
|
-
const invokeBody = body.slice(invokeBodyStart, invokeCloseIndex);
|
|
1261
|
-
invokeOpenRegex.lastIndex = invokeCloseIndex + invokeCloseToken.length;
|
|
1262
|
-
const invokeName = parseDsmlNameAttribute(openMatch[2] ?? "");
|
|
1263
|
-
if (!invokeName) continue;
|
|
1264
|
-
const parsedArguments = parseDeepSeekDsmlInvokeArguments(invokeBody);
|
|
1265
|
-
if (!parsedArguments) continue;
|
|
1266
|
-
toolCalls.push({
|
|
1267
|
-
kind: "toolCall",
|
|
1268
|
-
name: invokeName,
|
|
1269
|
-
arguments: parsedArguments,
|
|
1270
|
-
partialArgs: JSON.stringify(parsedArguments)
|
|
1271
|
-
});
|
|
1272
|
-
}
|
|
1273
|
-
return toolCalls;
|
|
1274
|
-
}
|
|
1275
|
-
function parseDeepSeekDsmlInvokeArguments(body) {
|
|
1276
|
-
const args = {};
|
|
1277
|
-
const parameterRegex = new RegExp(`<${DEEPSEEK_DSML_MARKER_PATTERN}parameter\\b([^>]*)>([\\s\\S]*?)</\\1parameter>`, "g");
|
|
1278
|
-
let parameterMatch;
|
|
1279
|
-
while ((parameterMatch = parameterRegex.exec(body)) !== null) {
|
|
1280
|
-
const name = parseDsmlNameAttribute(parameterMatch[2] ?? "");
|
|
1281
|
-
if (!name) continue;
|
|
1282
|
-
const rawValue = parameterMatch[3] ?? "";
|
|
1283
|
-
if (rawValue.length === 0) {
|
|
1284
|
-
const stringAttribute = Array.from((parameterMatch[2] ?? "").matchAll(/(?:^|\s)([^\s=]+)=(?:"([^"]*)"|'([^']*)'|([^\s"']+))(?=\s|$)/g)).find((attribute) => attribute[1] === "string");
|
|
1285
|
-
if ((stringAttribute?.[2] ?? stringAttribute?.[3] ?? stringAttribute?.[4]) !== "true") continue;
|
|
1286
|
-
}
|
|
1287
|
-
Object.defineProperty(args, name, {
|
|
1288
|
-
value: decodeDeepSeekDsmlText(rawValue),
|
|
1289
|
-
enumerable: true,
|
|
1290
|
-
configurable: true,
|
|
1291
|
-
writable: true
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
if (Object.keys(args).length > 0) return args;
|
|
1295
|
-
const trimmed = body.trim();
|
|
1296
|
-
if (!trimmed.startsWith("{")) return null;
|
|
1297
|
-
try {
|
|
1298
|
-
const parsed = JSON.parse(trimmed);
|
|
1299
|
-
if (isRecord(parsed) && Object.keys(parsed).length > 0) return parsed;
|
|
1300
|
-
} catch {
|
|
1301
|
-
return null;
|
|
1302
|
-
}
|
|
1303
|
-
return null;
|
|
1304
|
-
}
|
|
1305
|
-
function parseDsmlNameAttribute(attributes) {
|
|
1306
|
-
const match = DSML_NAME_ATTRIBUTE_RE.exec(attributes);
|
|
1307
|
-
const value = match?.[2] ?? match?.[3] ?? match?.[4];
|
|
1308
|
-
return value ? decodeDeepSeekDsmlText(value) : null;
|
|
1309
|
-
}
|
|
1310
|
-
function decodeDeepSeekDsmlText(value) {
|
|
1311
|
-
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
1312
|
-
}
|
|
1313
|
-
function scanDeepSeekDsmlToolBlock(text, closeToken, contentStartIndex, state) {
|
|
1314
|
-
while (state.offset < text.length) {
|
|
1315
|
-
if (state.invoke?.kind === "open") {
|
|
1316
|
-
const nextOpen = text.indexOf("<", state.offset);
|
|
1317
|
-
const nextClose = text.indexOf(">", state.offset);
|
|
1318
|
-
if (nextClose === -1 && nextOpen === -1) {
|
|
1319
|
-
state.offset = text.length;
|
|
1320
|
-
return { kind: "incomplete" };
|
|
1321
|
-
}
|
|
1322
|
-
if (nextOpen !== -1 && (nextClose === -1 || nextOpen < nextClose)) {
|
|
1323
|
-
state.invoke = void 0;
|
|
1324
|
-
state.offset = nextOpen;
|
|
1325
|
-
continue;
|
|
1326
|
-
}
|
|
1327
|
-
const invokeOpenTag = text.slice(state.invoke.start, nextClose + 1);
|
|
1328
|
-
const open = DEEPSEEK_DSML_INVOKE_OPEN_TAG.exec(invokeOpenTag);
|
|
1329
|
-
if (!open) {
|
|
1330
|
-
state.offset = state.invoke.start + 1;
|
|
1331
|
-
state.invoke = void 0;
|
|
1332
|
-
continue;
|
|
1333
|
-
}
|
|
1334
|
-
state.invoke = {
|
|
1335
|
-
kind: "body",
|
|
1336
|
-
closeToken: `</${open[1]}invoke>`
|
|
1337
|
-
};
|
|
1338
|
-
state.offset = nextClose + 1;
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
if (state.invoke?.kind === "body") {
|
|
1342
|
-
const invokeCloseIndex = text.indexOf(state.invoke.closeToken, state.offset);
|
|
1343
|
-
if (invokeCloseIndex === -1) {
|
|
1344
|
-
state.offset = Math.max(0, text.length - DEEPSEEK_DSML_RECOVERY_MAX_BOUNDARY_LEN + 1);
|
|
1345
|
-
return { kind: "incomplete" };
|
|
1346
|
-
}
|
|
1347
|
-
state.offset = invokeCloseIndex + state.invoke.closeToken.length;
|
|
1348
|
-
state.invoke = void 0;
|
|
1349
|
-
continue;
|
|
1350
|
-
}
|
|
1351
|
-
const toolOpen = findEarliestDsmlToken(text, DEEPSEEK_DSML_TOOL_OPEN_TOKENS, state.offset);
|
|
1352
|
-
const toolCloseIndex = text.indexOf(closeToken, state.offset);
|
|
1353
|
-
const invokeOpen = findEarliestDsmlToken(text, DEEPSEEK_DSML_INVOKE_OPEN_PREFIXES, state.offset);
|
|
1354
|
-
const next = [
|
|
1355
|
-
toolOpen ? {
|
|
1356
|
-
kind: "nested-open",
|
|
1357
|
-
...toolOpen
|
|
1358
|
-
} : null,
|
|
1359
|
-
toolCloseIndex === -1 ? null : {
|
|
1360
|
-
kind: "close",
|
|
1361
|
-
index: toolCloseIndex,
|
|
1362
|
-
token: closeToken
|
|
1363
|
-
},
|
|
1364
|
-
invokeOpen ? {
|
|
1365
|
-
kind: "invoke-open",
|
|
1366
|
-
...invokeOpen
|
|
1367
|
-
} : null
|
|
1368
|
-
].filter((candidate) => candidate !== null).toSorted((left, right) => left.index - right.index)[0];
|
|
1369
|
-
if (!next) {
|
|
1370
|
-
state.offset = Math.max(contentStartIndex, text.length - DEEPSEEK_DSML_RECOVERY_MAX_BOUNDARY_LEN + 1);
|
|
1371
|
-
return { kind: "incomplete" };
|
|
1372
|
-
}
|
|
1373
|
-
if (next.kind === "invoke-open") {
|
|
1374
|
-
state.invoke = {
|
|
1375
|
-
kind: "open",
|
|
1376
|
-
start: next.index
|
|
1377
|
-
};
|
|
1378
|
-
state.offset = next.index + next.token.length;
|
|
1379
|
-
continue;
|
|
1380
|
-
}
|
|
1381
|
-
return next;
|
|
1382
|
-
}
|
|
1383
|
-
return { kind: "incomplete" };
|
|
1384
|
-
}
|
|
1385
|
-
//#endregion
|
|
1386
|
-
//#region packages/ai/src/transports/openai-completions-stream.ts
|
|
1387
|
-
function extractToolCallThoughtSignature(toolCall) {
|
|
1388
|
-
const tc = toolCall;
|
|
1389
|
-
if (!tc) return;
|
|
1390
|
-
const extra = tc.extra_content?.google;
|
|
1391
|
-
return readNonEmptyStringPreservingWhitespace(extra?.thought_signature) ?? readNonEmptyStringPreservingWhitespace(tc.function?.thought_signature) ?? readNonEmptyStringPreservingWhitespace(tc.thought_signature);
|
|
1392
|
-
}
|
|
1393
|
-
async function processCompletionsStream(responseStream, output, model, stream, options) {
|
|
1394
|
-
const MAX_POST_TOOL_CALL_BUFFER_BYTES = 256e3;
|
|
1395
|
-
const directMode = options?.mode === "direct";
|
|
1396
|
-
const emitReasoning = options?.emitReasoning ?? true;
|
|
1397
|
-
const compat = getCompat(model);
|
|
1398
|
-
const visibleReasoningDetailTypes = new Set(compat.visibleReasoningDetailTypes);
|
|
1399
|
-
const shouldFilterDeepSeekDsmlText = !directMode && compat.thinkingFormat === "deepseek";
|
|
1400
|
-
const deepSeekTextFilter = shouldFilterDeepSeekDsmlText ? createDeepSeekTextFilter() : null;
|
|
1401
|
-
const deepSeekToolCallRecoverer = shouldFilterDeepSeekDsmlText ? createDsmlRecoverer() : null;
|
|
1402
|
-
const reasoningTagTextPartitioner = createReasoningTagTextPartitioner();
|
|
1403
|
-
if (options?.strictReasoningTags) reasoningTagTextPartitioner.markStrict();
|
|
1404
|
-
let currentBlock = null;
|
|
1405
|
-
let directTextBlock = null;
|
|
1406
|
-
let directThinkingBlock = null;
|
|
1407
|
-
let currentTextSource;
|
|
1408
|
-
let pendingInterruptedTextBlock = null;
|
|
1409
|
-
let confirmedInterruptedTextBlock = null;
|
|
1410
|
-
let pendingPostToolCallDeltas = [];
|
|
1411
|
-
let pendingPostToolCallBytes = 0;
|
|
1412
|
-
let isFlushingPendingPostToolCallDeltas = false;
|
|
1413
|
-
const toolCallBlocksByIndex = /* @__PURE__ */ new Map();
|
|
1414
|
-
const toolCallBlocksById = /* @__PURE__ */ new Map();
|
|
1415
|
-
const encryptedReasoning = createOpenAIEncryptedToolCallReasoningTracker();
|
|
1416
|
-
const toolArgumentPreviewSchedules = /* @__PURE__ */ new WeakMap();
|
|
1417
|
-
const provisionalCommentaryTags = directMode ? options.provisionalCommentaryTags : /* @__PURE__ */ new Map();
|
|
1418
|
-
const contentBlockIndices = /* @__PURE__ */ new WeakMap();
|
|
1419
|
-
const toolCallBlockIndices = /* @__PURE__ */ new WeakMap();
|
|
1420
|
-
let explicitVisibleTextBlocks;
|
|
1421
|
-
const normalizeToolCallDeltas = createOpenAICompletionsToolCallDeltaNormalizer();
|
|
1422
|
-
let finishReason;
|
|
1423
|
-
let sawNativeToolCallDelta = false;
|
|
1424
|
-
const blockIndex = () => directMode && currentBlock && currentBlock.type !== "toolCall" ? contentBlockIndices.get(currentBlock) ?? output.content.length - 1 : output.content.length - 1;
|
|
1425
|
-
let chunkPushedEvent = false;
|
|
1426
|
-
const pushStreamEvent = (event) => {
|
|
1427
|
-
chunkPushedEvent = true;
|
|
1428
|
-
stream.push(event);
|
|
1429
|
-
};
|
|
1430
|
-
const queuePostToolCallDelta = (next) => {
|
|
1431
|
-
const nextBytes = Buffer.byteLength(next.text, "utf8");
|
|
1432
|
-
if (pendingPostToolCallBytes + nextBytes > MAX_POST_TOOL_CALL_BUFFER_BYTES) throw new Error("Exceeded post-tool-call delta buffer limit");
|
|
1433
|
-
pendingPostToolCallBytes += nextBytes;
|
|
1434
|
-
const previous = pendingPostToolCallDeltas[pendingPostToolCallDeltas.length - 1];
|
|
1435
|
-
if (!previous || previous.kind !== next.kind || previous.kind === "text" && next.kind === "text" && previous.source !== next.source || previous.kind === "thinking" && next.kind === "thinking" && previous.signature !== next.signature) {
|
|
1436
|
-
pendingPostToolCallDeltas.push(next);
|
|
1437
|
-
return;
|
|
1438
|
-
}
|
|
1439
|
-
previous.text += next.text;
|
|
1440
|
-
};
|
|
1441
|
-
const appendThinkingDeltaInternal = (reasoningDelta) => {
|
|
1442
|
-
if (directMode && directThinkingBlock) currentBlock = directThinkingBlock;
|
|
1443
|
-
if (!currentBlock || currentBlock.type !== "thinking") {
|
|
1444
|
-
options?.beforeContentBlock?.("thinking");
|
|
1445
|
-
const thinkingSignature = reasoningDelta.signature;
|
|
1446
|
-
currentBlock = {
|
|
1447
|
-
type: "thinking",
|
|
1448
|
-
thinking: "",
|
|
1449
|
-
...thinkingSignature ? { thinkingSignature } : {}
|
|
1450
|
-
};
|
|
1451
|
-
if (directMode) {
|
|
1452
|
-
directTextBlock = null;
|
|
1453
|
-
directThinkingBlock = currentBlock;
|
|
1454
|
-
}
|
|
1455
|
-
output.content.push(currentBlock);
|
|
1456
|
-
contentBlockIndices.set(currentBlock, output.content.length - 1);
|
|
1457
|
-
pushStreamEvent({
|
|
1458
|
-
type: "thinking_start",
|
|
1459
|
-
contentIndex: blockIndex(),
|
|
1460
|
-
partial: output
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
appendAssistantThinking(currentBlock, reasoningDelta.text);
|
|
1464
|
-
pushStreamEvent({
|
|
1465
|
-
type: "thinking_delta",
|
|
1466
|
-
contentIndex: blockIndex(),
|
|
1467
|
-
delta: reasoningDelta.text,
|
|
1468
|
-
partial: output
|
|
1469
|
-
});
|
|
1470
|
-
};
|
|
1471
|
-
const appendTextDeltaInternal = (text, source) => {
|
|
1472
|
-
if (directMode && directTextBlock) currentBlock = directTextBlock;
|
|
1473
|
-
if (currentBlock?.type === "text" && currentTextSource !== source) currentBlock = null;
|
|
1474
|
-
if (!currentBlock || currentBlock.type !== "text") {
|
|
1475
|
-
options?.beforeContentBlock?.("text");
|
|
1476
|
-
currentBlock = {
|
|
1477
|
-
type: "text",
|
|
1478
|
-
text: ""
|
|
1479
|
-
};
|
|
1480
|
-
currentTextSource = source;
|
|
1481
|
-
if (directMode) {
|
|
1482
|
-
directTextBlock = currentBlock;
|
|
1483
|
-
directThinkingBlock = null;
|
|
1484
|
-
}
|
|
1485
|
-
if (source === "reasoning_detail") (explicitVisibleTextBlocks ??= /* @__PURE__ */ new Set()).add(currentBlock);
|
|
1486
|
-
output.content.push(currentBlock);
|
|
1487
|
-
contentBlockIndices.set(currentBlock, output.content.length - 1);
|
|
1488
|
-
pushStreamEvent({
|
|
1489
|
-
type: "text_start",
|
|
1490
|
-
contentIndex: blockIndex(),
|
|
1491
|
-
partial: output
|
|
1492
|
-
});
|
|
1493
|
-
}
|
|
1494
|
-
currentBlock.text += text;
|
|
1495
|
-
if (pendingInterruptedTextBlock && text.trim()) {
|
|
1496
|
-
confirmedInterruptedTextBlock = pendingInterruptedTextBlock;
|
|
1497
|
-
pendingInterruptedTextBlock = null;
|
|
1498
|
-
}
|
|
1499
|
-
pushStreamEvent({
|
|
1500
|
-
type: "text_delta",
|
|
1501
|
-
contentIndex: blockIndex(),
|
|
1502
|
-
delta: text,
|
|
1503
|
-
...directMode ? { partial: output } : {}
|
|
1504
|
-
});
|
|
1505
|
-
};
|
|
1506
|
-
const flushPendingPostToolCallDeltas = () => {
|
|
1507
|
-
if (isFlushingPendingPostToolCallDeltas || currentBlock?.type === "toolCall" || pendingPostToolCallDeltas.length === 0) return;
|
|
1508
|
-
isFlushingPendingPostToolCallDeltas = true;
|
|
1509
|
-
const bufferedDeltas = pendingPostToolCallDeltas;
|
|
1510
|
-
pendingPostToolCallDeltas = [];
|
|
1511
|
-
pendingPostToolCallBytes = 0;
|
|
1512
|
-
for (const delta of bufferedDeltas) if (delta.kind === "text") appendTextDeltaInternal(delta.text, delta.source);
|
|
1513
|
-
else if (emitReasoning) appendThinkingDeltaInternal(delta);
|
|
1514
|
-
isFlushingPendingPostToolCallDeltas = false;
|
|
1515
|
-
};
|
|
1516
|
-
const appendThinkingDelta = (reasoningDelta) => {
|
|
1517
|
-
flushPendingPostToolCallDeltas();
|
|
1518
|
-
appendThinkingDeltaInternal(reasoningDelta);
|
|
1519
|
-
};
|
|
1520
|
-
const appendTextDelta = (text, source) => {
|
|
1521
|
-
flushPendingPostToolCallDeltas();
|
|
1522
|
-
appendTextDeltaInternal(text, source);
|
|
1523
|
-
};
|
|
1524
|
-
const appendVisibleTextDelta = (text) => {
|
|
1525
|
-
if (!text) return;
|
|
1526
|
-
if (currentBlock?.type === "toolCall" && !directMode) queuePostToolCallDelta({
|
|
1527
|
-
kind: "text",
|
|
1528
|
-
text
|
|
1529
|
-
});
|
|
1530
|
-
else appendTextDelta(text);
|
|
1531
|
-
};
|
|
1532
|
-
const appendReasoningDeltas = (reasoningDeltas) => {
|
|
1533
|
-
for (const reasoningDelta of reasoningDeltas) {
|
|
1534
|
-
if (reasoningDelta.kind === "thinking" && !emitReasoning) continue;
|
|
1535
|
-
if (currentBlock?.type === "toolCall" && !directMode) {
|
|
1536
|
-
queuePostToolCallDelta({ ...reasoningDelta });
|
|
1537
|
-
continue;
|
|
1538
|
-
}
|
|
1539
|
-
if (reasoningDelta.kind === "text") appendTextDelta(reasoningDelta.text, reasoningDelta.source);
|
|
1540
|
-
else if (emitReasoning) appendThinkingDelta(directMode && model.provider === "opencode-go" && reasoningDelta.signature === "reasoning" ? {
|
|
1541
|
-
...reasoningDelta,
|
|
1542
|
-
signature: "reasoning_content"
|
|
1543
|
-
} : reasoningDelta);
|
|
1544
|
-
}
|
|
1545
|
-
};
|
|
1546
|
-
const appendRecoveredToolCall = (toolCall) => {
|
|
1547
|
-
if (currentBlock?.type === "toolCall") {
|
|
1548
|
-
currentBlock = null;
|
|
1549
|
-
flushPendingPostToolCallDeltas();
|
|
1550
|
-
}
|
|
1551
|
-
rememberPendingCommentaryTags(provisionalCommentaryTags, tagPendingCommentaryText(output.content));
|
|
1552
|
-
const block = {
|
|
1553
|
-
type: "toolCall",
|
|
1554
|
-
id: `call_${randomUUID().replaceAll("-", "").slice(0, 24)}`,
|
|
1555
|
-
name: toolCall.name,
|
|
1556
|
-
arguments: toolCall.arguments,
|
|
1557
|
-
partialArgs: toolCall.partialArgs
|
|
1558
|
-
};
|
|
1559
|
-
toolArgumentPreviewSchedules.set(block, createToolArgumentPreviewSchedule());
|
|
1560
|
-
currentBlock = block;
|
|
1561
|
-
output.content.push(block);
|
|
1562
|
-
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
1563
|
-
pushStreamEvent({
|
|
1564
|
-
type: "toolcall_start",
|
|
1565
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
1566
|
-
partial: output
|
|
1567
|
-
});
|
|
1568
|
-
pushStreamEvent({
|
|
1569
|
-
type: "toolcall_delta",
|
|
1570
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
1571
|
-
delta: toolCall.partialArgs,
|
|
1572
|
-
partial: output
|
|
1573
|
-
});
|
|
1574
|
-
};
|
|
1575
|
-
const appendRecoveredParts = (recoveredParts) => {
|
|
1576
|
-
for (const recoveredPart of recoveredParts) {
|
|
1577
|
-
if (recoveredPart.kind === "toolCall") {
|
|
1578
|
-
appendRecoveredToolCall(recoveredPart);
|
|
1579
|
-
continue;
|
|
1580
|
-
}
|
|
1581
|
-
const parts = deepSeekTextFilter?.push(recoveredPart.text) ?? [recoveredPart.text];
|
|
1582
|
-
for (const part of parts) appendVisibleTextDelta(part);
|
|
1583
|
-
}
|
|
1584
|
-
};
|
|
1585
|
-
const appendFilteredVisibleTextDelta = (text) => {
|
|
1586
|
-
appendRecoveredParts(deepSeekToolCallRecoverer?.push(text) ?? [{
|
|
1587
|
-
kind: "text",
|
|
1588
|
-
text
|
|
1589
|
-
}]);
|
|
1590
|
-
};
|
|
1591
|
-
const appendPartitionedVisibleDelta = (delta) => {
|
|
1592
|
-
if (delta.kind === "text") appendFilteredVisibleTextDelta(delta.text);
|
|
1593
|
-
};
|
|
1594
|
-
const emitReasoningUsageActivity = (hasReasoningUsageActivity) => {
|
|
1595
|
-
if (directMode || !hasReasoningUsageActivity || chunkPushedEvent || !emitReasoning) return;
|
|
1596
|
-
const latestBlock = output.content[output.content.length - 1];
|
|
1597
|
-
if (currentBlock?.type === "text" || currentBlock?.type === "toolCall") return;
|
|
1598
|
-
if (latestBlock?.type === "text" || latestBlock?.type === "toolCall") return;
|
|
1599
|
-
appendThinkingDelta({ text: "" });
|
|
1600
|
-
};
|
|
1601
|
-
const flushReasoningTagTextPartitioner = () => {
|
|
1602
|
-
for (const delta of reasoningTagTextPartitioner.flush()) appendPartitionedVisibleDelta(delta);
|
|
1603
|
-
};
|
|
1604
|
-
const sealTextBeforeReasoning = () => {
|
|
1605
|
-
if (currentBlock?.type !== "text" && !reasoningTagTextPartitioner.hasPending()) return;
|
|
1606
|
-
flushReasoningTagTextPartitioner();
|
|
1607
|
-
if (currentBlock?.type !== "text") return;
|
|
1608
|
-
if (currentTextSource !== "reasoning_detail" && currentBlock.text.trim()) pendingInterruptedTextBlock = currentBlock;
|
|
1609
|
-
currentBlock = null;
|
|
1610
|
-
if (directMode) directTextBlock = null;
|
|
1611
|
-
currentTextSource = void 0;
|
|
1612
|
-
};
|
|
1613
|
-
const beginReasoning = (hasFollowingVisibleText, forceStrict = false) => {
|
|
1614
|
-
if (!output.openclawDelivery?.textPhaseRequiresTerminal) output.openclawDelivery = {
|
|
1615
|
-
...output.openclawDelivery,
|
|
1616
|
-
textPhaseRequiresTerminal: true
|
|
1617
|
-
};
|
|
1618
|
-
if (forceStrict || reasoningTagTextPartitioner.hasPending()) reasoningTagTextPartitioner.markStrict();
|
|
1619
|
-
if (!hasFollowingVisibleText || !reasoningTagTextPartitioner.hasPendingSyntax()) sealTextBeforeReasoning();
|
|
1620
|
-
};
|
|
1621
|
-
const guardedStream = withFirstStreamEventTimeout(responseStream, {
|
|
1622
|
-
provider: model.provider,
|
|
1623
|
-
api: model.api,
|
|
1624
|
-
model: model.id,
|
|
1625
|
-
timeoutMs: options?.firstEventTimeoutMs ?? 0,
|
|
1626
|
-
stage: "completions",
|
|
1627
|
-
abort: options?.abortFirstEventStream,
|
|
1628
|
-
onTimeout: options?.onFirstEventTimeout,
|
|
1629
|
-
hint: "The provider may be stalled while parsing the tool payload; retry with a smaller tool surface or enable OPENCLAW_DEBUG_MODEL_PAYLOAD=tools to inspect exposed tools."
|
|
1630
|
-
});
|
|
1631
|
-
const events = directMode ? guardedStream : iterateModelStream(guardedStream, options?.signal);
|
|
1632
|
-
for await (const rawChunk of events) {
|
|
1633
|
-
throwIfModelStreamAborted(options?.signal);
|
|
1634
|
-
chunkPushedEvent = false;
|
|
1635
|
-
if (!rawChunk || typeof rawChunk !== "object") continue;
|
|
1636
|
-
notifyLlmRequestActivity(options?.signal);
|
|
1637
|
-
const chunk = rawChunk;
|
|
1638
|
-
output.responseId ||= chunk.id;
|
|
1639
|
-
if (typeof chunk.model === "string" && chunk.model.length > 0 && chunk.model !== model.id) output.responseModel ||= chunk.model;
|
|
1640
|
-
let hasReasoningUsageActivity = false;
|
|
1641
|
-
if (chunk.usage) {
|
|
1642
|
-
output.usage = parseOpenAICompletionsUsage(chunk.usage, model, { includeReasoningTokens: !directMode });
|
|
1643
|
-
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(chunk.usage);
|
|
1644
|
-
}
|
|
1645
|
-
const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : void 0;
|
|
1646
|
-
if (!choice) {
|
|
1647
|
-
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
1648
|
-
continue;
|
|
1649
|
-
}
|
|
1650
|
-
const choiceUsage = choice.usage;
|
|
1651
|
-
if (!chunk.usage && choiceUsage) {
|
|
1652
|
-
output.usage = parseOpenAICompletionsUsage(choiceUsage, model, { includeReasoningTokens: !directMode });
|
|
1653
|
-
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(choiceUsage);
|
|
1654
|
-
}
|
|
1655
|
-
if (choice.finish_reason) {
|
|
1656
|
-
const finishReasonResult = mapOpenAIStopReason(choice.finish_reason, { allowSingularToolCall: true });
|
|
1657
|
-
output.stopReason = finishReasonResult.stopReason;
|
|
1658
|
-
finishReason = finishReasonResult.stopReason;
|
|
1659
|
-
if (finishReasonResult.errorMessage) output.errorMessage = finishReasonResult.errorMessage;
|
|
1660
|
-
}
|
|
1661
|
-
const rawChoiceDelta = choice.delta ?? choice.message;
|
|
1662
|
-
if (!rawChoiceDelta) {
|
|
1663
|
-
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
1664
|
-
continue;
|
|
1665
|
-
}
|
|
1666
|
-
for (const normalizedDelta of normalizeToolCallDeltas(rawChoiceDelta, choice.finish_reason)) {
|
|
1667
|
-
const choiceDelta = normalizedDelta.delta;
|
|
1668
|
-
const deltaFields = choiceDelta;
|
|
1669
|
-
const reasoningBatch = readOpenAICompletionsReasoningBatch(deltaFields, visibleReasoningDetailTypes);
|
|
1670
|
-
const reasoningDeltas = reasoningBatch.deltas;
|
|
1671
|
-
const hasReasoningThinking = reasoningBatch.hasThinking;
|
|
1672
|
-
const contentDeltas = readOpenAICompletionsContentDeltas(choiceDelta.content, choiceDelta.refusal, reasoningBatch.mirroredThinking);
|
|
1673
|
-
const lastVisibleTextIndex = contentDeltas.findLastIndex((delta) => delta.kind === "text");
|
|
1674
|
-
const hasSameChunkVisibleText = reasoningBatch.hasVisibleText || lastVisibleTextIndex !== -1;
|
|
1675
|
-
if (hasReasoningThinking) {
|
|
1676
|
-
beginReasoning(hasSameChunkVisibleText, true);
|
|
1677
|
-
appendReasoningDeltas(reasoningDeltas);
|
|
1678
|
-
}
|
|
1679
|
-
for (const [contentDeltaIndex, contentDelta] of contentDeltas.entries()) if (contentDelta.kind === "text") {
|
|
1680
|
-
const routedDeltas = hasReasoningThinking ? reasoningTagTextPartitioner.push(contentDelta.text) : reasoningTagTextPartitioner.pushVisible(contentDelta.text);
|
|
1681
|
-
for (const routedDelta of routedDeltas) appendPartitionedVisibleDelta(routedDelta);
|
|
1682
|
-
} else {
|
|
1683
|
-
beginReasoning(contentDeltaIndex < lastVisibleTextIndex);
|
|
1684
|
-
if (emitReasoning) {
|
|
1685
|
-
if (currentBlock?.type === "toolCall" && !directMode) queuePostToolCallDelta(contentDelta);
|
|
1686
|
-
else appendThinkingDelta(contentDelta);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
if (!hasReasoningThinking) appendReasoningDeltas(reasoningDeltas);
|
|
1690
|
-
const toolCallDeltas = normalizedDelta.toolCalls;
|
|
1691
|
-
if (toolCallDeltas.length > 0) {
|
|
1692
|
-
sawNativeToolCallDelta = true;
|
|
1693
|
-
flushReasoningTagTextPartitioner();
|
|
1694
|
-
rememberPendingCommentaryTags(provisionalCommentaryTags, tagPendingCommentaryText(output.content));
|
|
1695
|
-
for (const toolCall of toolCallDeltas) {
|
|
1696
|
-
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : void 0;
|
|
1697
|
-
let block = streamIndex !== void 0 ? toolCallBlocksByIndex.get(streamIndex) : void 0;
|
|
1698
|
-
if (!block && toolCall.id) block = toolCallBlocksById.get(toolCall.id);
|
|
1699
|
-
if (!block) {
|
|
1700
|
-
if (currentBlock?.type === "toolCall") {
|
|
1701
|
-
currentBlock = null;
|
|
1702
|
-
flushPendingPostToolCallDeltas();
|
|
1703
|
-
}
|
|
1704
|
-
const initialSig = directMode ? void 0 : extractToolCallThoughtSignature(toolCall);
|
|
1705
|
-
options?.beforeContentBlock?.("toolCall");
|
|
1706
|
-
if (directMode) directThinkingBlock = null;
|
|
1707
|
-
block = {
|
|
1708
|
-
type: "toolCall",
|
|
1709
|
-
id: toolCall.id || "",
|
|
1710
|
-
name: toolCall.function?.name || "",
|
|
1711
|
-
arguments: {},
|
|
1712
|
-
partialArgs: "",
|
|
1713
|
-
...initialSig ? { thoughtSignature: initialSig } : {}
|
|
1714
|
-
};
|
|
1715
|
-
encryptedReasoning.rememberToolCall(block.id, block);
|
|
1716
|
-
toolArgumentPreviewSchedules.set(block, createToolArgumentPreviewSchedule());
|
|
1717
|
-
output.content.push(block);
|
|
1718
|
-
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
1719
|
-
pushStreamEvent({
|
|
1720
|
-
type: "toolcall_start",
|
|
1721
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
1722
|
-
partial: output
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
if (streamIndex !== void 0 && !toolCallBlocksByIndex.has(streamIndex)) toolCallBlocksByIndex.set(streamIndex, block);
|
|
1726
|
-
if (toolCall.id) {
|
|
1727
|
-
const previousId = block.id;
|
|
1728
|
-
if (!directMode || !block.id) block.id = toolCall.id;
|
|
1729
|
-
toolCallBlocksById.set(toolCall.id, block);
|
|
1730
|
-
if (block.id === toolCall.id) encryptedReasoning.rememberToolCall(toolCall.id, block, previousId);
|
|
1731
|
-
}
|
|
1732
|
-
currentBlock = block;
|
|
1733
|
-
const conflictingId = directMode && block.id && toolCall.id && block.id !== toolCall.id;
|
|
1734
|
-
if (toolCall.function?.name && !conflictingId) block.name = toolCall.function.name;
|
|
1735
|
-
const deltaSig = directMode ? void 0 : extractToolCallThoughtSignature(toolCall);
|
|
1736
|
-
if (deltaSig) block.thoughtSignature = deltaSig;
|
|
1737
|
-
const toolArgumentsDelta = toolCall.function?.arguments;
|
|
1738
|
-
if (toolArgumentsDelta) {
|
|
1739
|
-
block.partialArgs += toolArgumentsDelta;
|
|
1740
|
-
if (toolArgumentPreviewSchedules.get(block)?.(block.partialArgs.length)) block.arguments = parseStreamingJson(block.partialArgs);
|
|
1741
|
-
}
|
|
1742
|
-
if (toolArgumentsDelta || directMode) pushStreamEvent({
|
|
1743
|
-
type: "toolcall_delta",
|
|
1744
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
1745
|
-
delta: toolArgumentsDelta ?? "",
|
|
1746
|
-
partial: output
|
|
1747
|
-
});
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
encryptedReasoning.consumeDetails(deltaFields.reasoning_details);
|
|
1751
|
-
}
|
|
1752
|
-
flushPendingPostToolCallDeltas();
|
|
1753
|
-
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
1754
|
-
}
|
|
1755
|
-
throwIfModelStreamAborted(options?.signal);
|
|
1756
|
-
if (!finishReason && (directMode || options?.sawStreamDONE?.() === false)) throw new Error("Stream ended without finish_reason");
|
|
1757
|
-
flushReasoningTagTextPartitioner();
|
|
1758
|
-
appendRecoveredParts(deepSeekToolCallRecoverer?.flush() ?? []);
|
|
1759
|
-
for (const part of deepSeekTextFilter?.flush() ?? []) appendVisibleTextDelta(part);
|
|
1760
|
-
currentBlock = null;
|
|
1761
|
-
flushPendingPostToolCallDeltas();
|
|
1762
|
-
finalizeOpenAICompletionsToolCalls(output, {
|
|
1763
|
-
allowSilentToolCallPromotion: finishReason === "stop" || sawNativeToolCallDelta && (options?.sawStreamDONE?.() ?? false),
|
|
1764
|
-
onConfirmedToolCall(block, contentIndex) {
|
|
1765
|
-
if (directMode || block.type !== "toolCall") return;
|
|
1766
|
-
pushStreamEvent({
|
|
1767
|
-
type: "toolcall_end",
|
|
1768
|
-
contentIndex,
|
|
1769
|
-
toolCall: block,
|
|
1770
|
-
partial: output
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
});
|
|
1774
|
-
if (confirmedInterruptedTextBlock && output.stopReason !== "toolUse" && output.stopReason !== "error" && output.stopReason !== "aborted") tagInterruptedTextPhases(output.content, confirmedInterruptedTextBlock, explicitVisibleTextBlocks);
|
|
1775
|
-
if (output.stopReason !== "toolUse") clearPendingCommentaryText(provisionalCommentaryTags);
|
|
1776
|
-
if (output.stopReason === "error" || output.stopReason === "aborted") tagUnresolvedTextAsCommentary(output);
|
|
1777
|
-
if (output.stopReason === "toolUse") tagPendingCommentaryText(output.content);
|
|
1778
|
-
}
|
|
1779
|
-
function shouldEmitOpenAICompletionsReasoning(model, options) {
|
|
1780
|
-
if (!model.reasoning) return false;
|
|
1781
|
-
const effort = options?.reasoningEffort ?? options?.reasoning ?? "high";
|
|
1782
|
-
return Boolean(effort) && isOpenAICompletionsThinkingEnabled(effort);
|
|
1783
|
-
}
|
|
1784
|
-
function hasOpenAICompletionsReasoningUsageActivity(rawUsage) {
|
|
1785
|
-
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens;
|
|
1786
|
-
return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) && reasoningTokens > 0;
|
|
1787
|
-
}
|
|
1788
|
-
//#endregion
|
|
1789
|
-
//#region packages/ai/src/transports/openai-completions-transport.ts
|
|
1790
|
-
function assertOpenAICompletionsPayloadHasConversationTurn(params, model) {
|
|
1791
|
-
const messages = params.messages;
|
|
1792
|
-
if (!Array.isArray(messages) || hasOpenAICompatibleConversationTurn(messages)) return;
|
|
1793
|
-
throw new Error(`OpenAI-compatible chat payload for ${model.provider}/${model.id} contains no non-empty user or assistant messages after compaction and transport transforms; refusing to send a system/tool-only request. Start a new user turn or repair the compacted session history.`);
|
|
1794
|
-
}
|
|
1795
|
-
const SSE_DONE_LINE_RE = /^data:[ \t]*\[DONE\][ \t]*$/i;
|
|
1796
|
-
const SSE_DONE_MAX_LINE_CHARS = 1024;
|
|
1797
|
-
function createSseDoneDetector() {
|
|
1798
|
-
const decoder = new TextDecoder();
|
|
1799
|
-
let line = "";
|
|
1800
|
-
let lineOverflowed = false;
|
|
1801
|
-
let sawDone = false;
|
|
1802
|
-
const finishLine = () => {
|
|
1803
|
-
if (!lineOverflowed && SSE_DONE_LINE_RE.test(line)) sawDone = true;
|
|
1804
|
-
line = "";
|
|
1805
|
-
lineOverflowed = false;
|
|
1806
|
-
};
|
|
1807
|
-
const observeText = (text) => {
|
|
1808
|
-
for (const char of text) {
|
|
1809
|
-
if (char === "\n" || char === "\r") {
|
|
1810
|
-
finishLine();
|
|
1811
|
-
continue;
|
|
1812
|
-
}
|
|
1813
|
-
if (!lineOverflowed && line.length < SSE_DONE_MAX_LINE_CHARS) line += char;
|
|
1814
|
-
else lineOverflowed = true;
|
|
1815
|
-
}
|
|
1816
|
-
};
|
|
1817
|
-
return {
|
|
1818
|
-
observe(chunk) {
|
|
1819
|
-
if (!sawDone) observeText(decoder.decode(chunk, { stream: true }));
|
|
1820
|
-
},
|
|
1821
|
-
finish() {
|
|
1822
|
-
if (sawDone) return;
|
|
1823
|
-
observeText(decoder.decode());
|
|
1824
|
-
if (line || lineOverflowed) finishLine();
|
|
1825
|
-
},
|
|
1826
|
-
sawDone: () => sawDone
|
|
1827
|
-
};
|
|
1828
|
-
}
|
|
1829
|
-
function buildOpenAICompletionsClientConfig(model, headers) {
|
|
1830
|
-
const defaultQuery = {};
|
|
1831
|
-
let baseURL = model.baseUrl;
|
|
1832
|
-
let isAzureHost = false;
|
|
1833
|
-
const parsed = URL.parse(model.baseUrl);
|
|
1834
|
-
if (parsed) {
|
|
1835
|
-
isAzureHost = isAzureOpenAICompatibleHost(parsed.hostname.toLowerCase());
|
|
1836
|
-
parsed.searchParams.forEach((value, key) => {
|
|
1837
|
-
if (value) defaultQuery[key] = value;
|
|
1838
|
-
});
|
|
1839
|
-
parsed.search = "";
|
|
1840
|
-
baseURL = parsed.toString().replace(/\/$/, "");
|
|
1841
|
-
}
|
|
1842
|
-
if (isAzureHost) {
|
|
1843
|
-
const apiVersionHeader = Object.keys(headers).find((key) => key.toLowerCase() === "api-version");
|
|
1844
|
-
if (apiVersionHeader) {
|
|
1845
|
-
const apiVersion = headers[apiVersionHeader]?.trim();
|
|
1846
|
-
delete headers[apiVersionHeader];
|
|
1847
|
-
if (apiVersion && !defaultQuery["api-version"]) defaultQuery["api-version"] = apiVersion;
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
return {
|
|
1851
|
-
baseURL: resolveOpenAIClientBaseUrl(model, baseURL),
|
|
1852
|
-
defaultHeaders: headers,
|
|
1853
|
-
defaultQuery: Object.keys(defaultQuery).length > 0 ? defaultQuery : void 0
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
function createDirectCompletionsEventStream(output, stream) {
|
|
1857
|
-
const finishedBlocks = /* @__PURE__ */ new Set();
|
|
1858
|
-
const contentIndices = /* @__PURE__ */ new WeakMap();
|
|
1859
|
-
let openTextBlock;
|
|
1860
|
-
let openThinkingBlock;
|
|
1861
|
-
const finishBlock = (block) => {
|
|
1862
|
-
const contentIndex = contentIndices.get(block);
|
|
1863
|
-
if (contentIndex === void 0 || finishedBlocks.has(block)) return;
|
|
1864
|
-
finishedBlocks.add(block);
|
|
1865
|
-
if (block.type === "text") {
|
|
1866
|
-
openTextBlock = void 0;
|
|
1867
|
-
stream.push({
|
|
1868
|
-
type: "text_end",
|
|
1869
|
-
contentIndex,
|
|
1870
|
-
content: block.text,
|
|
1871
|
-
partial: output
|
|
1872
|
-
});
|
|
1873
|
-
} else if (block.type === "thinking") {
|
|
1874
|
-
openThinkingBlock = void 0;
|
|
1875
|
-
stream.push({
|
|
1876
|
-
type: "thinking_end",
|
|
1877
|
-
contentIndex,
|
|
1878
|
-
content: block.thinking,
|
|
1879
|
-
partial: output
|
|
1880
|
-
});
|
|
1881
|
-
} else if (block.type === "toolCall") stream.push({
|
|
1882
|
-
type: "toolcall_end",
|
|
1883
|
-
contentIndex,
|
|
1884
|
-
toolCall: block,
|
|
1885
|
-
partial: output
|
|
1886
|
-
});
|
|
1887
|
-
};
|
|
1888
|
-
return {
|
|
1889
|
-
stream: { push(event) {
|
|
1890
|
-
if (event.type === "text_start" || event.type === "thinking_start" || event.type === "toolcall_start") {
|
|
1891
|
-
const block = output.content[event.contentIndex];
|
|
1892
|
-
if (block) {
|
|
1893
|
-
contentIndices.set(block, event.contentIndex);
|
|
1894
|
-
if (block.type === "text") openTextBlock = block;
|
|
1895
|
-
else if (block.type === "thinking") openThinkingBlock = block;
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
stream.push(event);
|
|
1899
|
-
} },
|
|
1900
|
-
beforeContentBlock: (nextType) => {
|
|
1901
|
-
if (openThinkingBlock) finishBlock(openThinkingBlock);
|
|
1902
|
-
if (openTextBlock && nextType !== "toolCall") finishBlock(openTextBlock);
|
|
1903
|
-
},
|
|
1904
|
-
finish(includeToolCalls) {
|
|
1905
|
-
for (const block of output.content) if (block.type !== "toolCall" || includeToolCalls) finishBlock(block);
|
|
1906
|
-
}
|
|
1907
|
-
};
|
|
1908
|
-
}
|
|
1909
|
-
function createOpenAICompletionsTransportStreamFn() {
|
|
1910
|
-
return (model, context, options) => streamOpenAICompletionsRequest(model, context, options, "managed");
|
|
1911
|
-
}
|
|
1912
|
-
function streamOpenAICompletionsRequest(model, context, options, mode) {
|
|
1913
|
-
const { eventStream, stream } = createWritableTransportEventStream();
|
|
1914
|
-
(async () => {
|
|
1915
|
-
const output = createAssistantOutput(model);
|
|
1916
|
-
const provisionalCommentaryTags = /* @__PURE__ */ new Map();
|
|
1917
|
-
let firstEventAbort;
|
|
1918
|
-
try {
|
|
1919
|
-
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
1920
|
-
const directEmitReasoning = Boolean(mode === "direct" && model.reasoning && options?.reasoningEffort && isOpenAICompletionsThinkingEnabled(options.reasoningEffort));
|
|
1921
|
-
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
1922
|
-
const policy = mode === "direct" ? {
|
|
1923
|
-
mode,
|
|
1924
|
-
compat: resolveOpenAICompletionsCompat(model),
|
|
1925
|
-
cacheRetention
|
|
1926
|
-
} : { mode };
|
|
1927
|
-
const { client, sawStreamDONE } = policy.mode === "direct" ? {
|
|
1928
|
-
client: createDirectCompletionsClient(model, context, apiKey, resolveProviderSimpleCompletionHeaders(model, options), cacheRetention === "none" ? void 0 : options?.sessionId, policy.compat),
|
|
1929
|
-
sawStreamDONE: void 0
|
|
1930
|
-
} : createManagedCompletionsClient(model, context, options, apiKey, cacheRetention);
|
|
1931
|
-
let params = buildOpenAICompletionsRequest(model, context, options, policy);
|
|
1932
|
-
const encodeBody = prepareModelRequestBody(options);
|
|
1933
|
-
const nextParams = await options?.onPayload?.(params, model);
|
|
1934
|
-
if (nextParams !== void 0) params = nextParams;
|
|
1935
|
-
if (mode === "managed") {
|
|
1936
|
-
if (options?.openclawCodeModeToolSurface === true) {
|
|
1937
|
-
const visibleToolNames = resolveCodeModeResponsesVisibleToolNames(context);
|
|
1938
|
-
enforceCodeModeResponsesToolSurface(params, visibleToolNames, void 0, codeModeToolSurfaceObserver.get(options));
|
|
1939
|
-
assertCodeModeResponsesToolSurface(params, visibleToolNames);
|
|
1940
|
-
}
|
|
1941
|
-
if (getCompat(model).requiresNonEmptyUserOrAssistantMessage) assertOpenAICompletionsPayloadHasConversationTurn(params, model);
|
|
1942
|
-
}
|
|
1943
|
-
const emitReasoning = mode === "direct" ? directEmitReasoning : shouldEmitOpenAICompletionsReasoning(model, options);
|
|
1944
|
-
firstEventAbort = createFirstStreamEventAbortController(options?.signal);
|
|
1945
|
-
const requestOptions = mode === "direct" ? {
|
|
1946
|
-
...await encodeBody(params),
|
|
1947
|
-
signal: firstEventAbort.signal,
|
|
1948
|
-
...options?.timeoutMs !== void 0 ? { timeout: options.timeoutMs } : {},
|
|
1949
|
-
maxRetries: 0
|
|
1950
|
-
} : {
|
|
1951
|
-
...buildOpenAISdkRequestOptions(model, firstEventAbort.signal, { timeoutMs: options?.timeoutMs }),
|
|
1952
|
-
...await encodeBody(params)
|
|
1953
|
-
};
|
|
1954
|
-
const { data: responseStream, response } = await client.chat.completions.create(params, requestOptions).withResponse();
|
|
1955
|
-
const hookedResponseStream = withProviderResponseHook({
|
|
1956
|
-
stream: responseStream,
|
|
1957
|
-
signal: firstEventAbort.signal,
|
|
1958
|
-
abort: firstEventAbort.abort,
|
|
1959
|
-
hook: createOpenAIProviderAcceptanceHook(options, response, model),
|
|
1960
|
-
onReady: () => stream.push({
|
|
1961
|
-
type: "start",
|
|
1962
|
-
partial: output
|
|
1963
|
-
})
|
|
1964
|
-
});
|
|
1965
|
-
const directEvents = mode === "direct" ? createDirectCompletionsEventStream(output, stream) : void 0;
|
|
1966
|
-
try {
|
|
1967
|
-
await processCompletionsStream(hookedResponseStream, output, model, directEvents?.stream ?? stream, {
|
|
1968
|
-
...directEvents ? {
|
|
1969
|
-
mode: "direct",
|
|
1970
|
-
beforeContentBlock: directEvents.beforeContentBlock,
|
|
1971
|
-
provisionalCommentaryTags
|
|
1972
|
-
} : { mode: "managed" },
|
|
1973
|
-
signal: options?.signal,
|
|
1974
|
-
emitReasoning,
|
|
1975
|
-
strictReasoningTags: reasoningTagTextPolicy.isStrict(options),
|
|
1976
|
-
firstEventTimeoutMs: getFirstStreamEventTimeoutMs(options),
|
|
1977
|
-
abortFirstEventStream: firstEventAbort.abort,
|
|
1978
|
-
onFirstEventTimeout: getFirstStreamEventTimeoutHandler(options),
|
|
1979
|
-
sawStreamDONE
|
|
1980
|
-
});
|
|
1981
|
-
if (directEvents) {
|
|
1982
|
-
if (options?.signal?.aborted) throw transportAbortError(options.signal);
|
|
1983
|
-
if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error(output.errorMessage || (output.stopReason === "aborted" ? "Request was aborted" : "Provider returned an invalid tool call"));
|
|
1984
|
-
}
|
|
1985
|
-
} catch (error) {
|
|
1986
|
-
directEvents?.finish(false);
|
|
1987
|
-
throw error;
|
|
1988
|
-
}
|
|
1989
|
-
directEvents?.finish(output.stopReason === "toolUse");
|
|
1990
|
-
finalizeTransportStream({
|
|
1991
|
-
stream,
|
|
1992
|
-
output,
|
|
1993
|
-
signal: options?.signal
|
|
1994
|
-
});
|
|
1995
|
-
} catch (error) {
|
|
1996
|
-
failTransportStream({
|
|
1997
|
-
stream,
|
|
1998
|
-
output,
|
|
1999
|
-
signal: options?.signal,
|
|
2000
|
-
error,
|
|
2001
|
-
cleanup: () => {
|
|
2002
|
-
if (mode === "managed") output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
2003
|
-
finalizeOpenAICompletionsToolCalls(output, { allowSilentToolCallPromotion: false });
|
|
2004
|
-
clearPendingCommentaryText(provisionalCommentaryTags);
|
|
2005
|
-
tagUnresolvedTextAsCommentary(output);
|
|
2006
|
-
if (mode === "direct") for (const block of output.content) {
|
|
2007
|
-
delete block.index;
|
|
2008
|
-
delete block.partialArgs;
|
|
2009
|
-
delete block.streamIndex;
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
});
|
|
2013
|
-
} finally {
|
|
2014
|
-
firstEventAbort?.dispose();
|
|
2015
|
-
}
|
|
2016
|
-
})();
|
|
2017
|
-
return eventStream;
|
|
2018
|
-
}
|
|
2019
|
-
function createDirectCompletionsClient(model, context, apiKey, optionsHeaders, sessionId, compat) {
|
|
2020
|
-
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
2021
|
-
const headers = { ...model.headers };
|
|
2022
|
-
if (model.provider === "github-copilot") Object.assign(headers, buildCopilotDynamicHeaders(context.messages));
|
|
2023
|
-
if (sessionId && compat.sessionAffinity !== "none") {
|
|
2024
|
-
if (compat.sessionAffinity === "openrouter") headers["x-session-id"] = sessionId;
|
|
2025
|
-
else {
|
|
2026
|
-
headers.session_id = sessionId;
|
|
2027
|
-
headers["x-client-request-id"] = sessionId;
|
|
2028
|
-
headers["x-session-affinity"] = sessionId;
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
return createOpenAIProviderClient(model, apiKey, headers, optionsHeaders);
|
|
2032
|
-
}
|
|
2033
|
-
function createManagedCompletionsClient(model, context, options, apiKey, cacheRetention) {
|
|
2034
|
-
const turnState = resolveProviderTransportTurnState(model, {
|
|
2035
|
-
sessionId: options?.sessionId,
|
|
2036
|
-
turnId: randomUUID(),
|
|
2037
|
-
attempt: 1,
|
|
2038
|
-
transport: "stream"
|
|
2039
|
-
});
|
|
2040
|
-
const optionHeaders = resolveOpencodeSessionHeaders(model, options);
|
|
2041
|
-
const turnHeaders = filterProviderTurnHeadersForExplicitOpencodeSession(model, options, turnState?.headers);
|
|
2042
|
-
const doneDetector = createSseDoneDetector();
|
|
2043
|
-
const baseFetch = buildGuardedModelFetch(model);
|
|
2044
|
-
const doneDetectingFetch = async (url, init) => {
|
|
2045
|
-
const response = await baseFetch(url, init);
|
|
2046
|
-
if (!response.body || !response.ok) return response;
|
|
2047
|
-
if (typeof TransformStream === "undefined" || !response.body.pipeThrough) return response;
|
|
2048
|
-
const transformed = response.body.pipeThrough(new TransformStream({
|
|
2049
|
-
transform(chunk, controller) {
|
|
2050
|
-
doneDetector.observe(chunk);
|
|
2051
|
-
controller.enqueue(chunk);
|
|
2052
|
-
},
|
|
2053
|
-
flush() {
|
|
2054
|
-
doneDetector.finish();
|
|
2055
|
-
}
|
|
2056
|
-
}));
|
|
2057
|
-
return new Response(transformed, {
|
|
2058
|
-
headers: response.headers,
|
|
2059
|
-
status: response.status,
|
|
2060
|
-
statusText: response.statusText
|
|
2061
|
-
});
|
|
2062
|
-
};
|
|
2063
|
-
const clientConfig = buildOpenAICompletionsClientConfig(model, buildOpenAIClientHeaders(model, context, {
|
|
2064
|
-
...turnHeaders,
|
|
2065
|
-
...optionHeaders
|
|
2066
|
-
}, void 0, resolvePromptCacheKey(options, cacheRetention), cacheRetention));
|
|
2067
|
-
return {
|
|
2068
|
-
client: new OpenAI({
|
|
2069
|
-
apiKey,
|
|
2070
|
-
baseURL: clientConfig.baseURL,
|
|
2071
|
-
dangerouslyAllowBrowser: true,
|
|
2072
|
-
defaultHeaders: clientConfig.defaultHeaders,
|
|
2073
|
-
defaultQuery: clientConfig.defaultQuery,
|
|
2074
|
-
fetch: doneDetectingFetch,
|
|
2075
|
-
...buildOpenAISdkClientOptions(model)
|
|
2076
|
-
}),
|
|
2077
|
-
sawStreamDONE: doneDetector.sawDone
|
|
2078
|
-
};
|
|
2079
|
-
}
|
|
2080
|
-
//#endregion
|
|
2081
|
-
export { flattenCompletionMessagesToStringContent as a, hasOpenAICompatibleConversationTurn as c, createDeepSeekTextFilter as d, convertMessages as i, canonicalizeMaxTokensParam as l, streamOpenAICompletionsRequest as n, stripCompletionMessagesToRoleContent as o, buildOpenAICompletionsParams as r, applyCompletionsAnthropicCacheControl as s, createOpenAICompletionsTransportStreamFn as t, resolveMaxTokensParam as u };
|