@openclaw/ai 2026.7.2-beta.7 → 2026.8.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{anthropic-CH4UUnZr.mjs → anthropic-B6dLpq5L.mjs} +55 -203
- package/dist/{src-QkygScBs.mjs → anthropic-JsNA5KCu.mjs} +0 -1
- package/dist/anthropic-compaction-replay-8lJNKXOE.mjs +840 -0
- package/dist/anthropic-payload-policy-CiEuQS72.d.mts +49 -0
- package/dist/{api-registry-DlMgPR39.d.mts → api-registry-k3zTz0cV.d.mts} +1 -1
- package/dist/{azure-openai-responses-CImcwB83.mjs → azure-openai-responses-mxIOtUnn.mjs} +23 -29
- package/dist/diagnostics.d.mts +24 -1
- package/dist/diagnostics.mjs +2 -1
- package/dist/{event-stream-YjaPW20U.d.mts → event-stream-BP6AWT8j.d.mts} +4 -1
- package/dist/{event-stream-D8n2uFee.mjs → event-stream-uSMZJ3FA.mjs} +26 -5
- package/dist/event-stream.d.mts +1 -1
- package/dist/event-stream.mjs +1 -1
- package/dist/{google-CtSg0iTS.mjs → google-C7h2QzDX.mjs} +5 -5
- package/dist/{google-shared-DNBz5rcD.mjs → google-shared-B0Qr9OR7.mjs} +146 -89
- package/dist/google-thinking-level-C-V3tecN.mjs +9 -0
- package/dist/{google-vertex-31f1uS9L.mjs → google-vertex-Bhc3TjDl.mjs} +11 -7
- package/dist/{llm-request-activity-BjtkplhG.mjs → headers-DdOQtGuU.mjs} +9 -1
- package/dist/host-DTqNc7ad.mjs +466 -0
- package/dist/{host-B9GUmcra.d.mts → host-vWgMMhiJ.d.mts} +9 -4
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +7 -5
- package/dist/internal/anthropic.d.mts +29 -5
- package/dist/internal/anthropic.mjs +5 -5
- package/dist/internal/openai-responses-payload-policy.d.mts +3 -0
- package/dist/internal/openai-responses-payload-policy.mjs +3 -0
- package/dist/internal/openai.d.mts +6 -6
- package/dist/internal/openai.mjs +8 -7
- package/dist/internal/runtime.d.mts +17 -5
- package/dist/internal/runtime.mjs +85 -73
- package/dist/internal/shared.d.mts +1 -6
- package/dist/internal/shared.mjs +3 -5
- package/dist/{json-parse-BvXNt1-7.mjs → json-parse-CDnesDM_.mjs} +4 -6
- package/dist/{mistral-CWmpvWYh.mjs → mistral-CEWoQI_g.mjs} +45 -48
- package/dist/number-coercion-H9qHik3g.mjs +71 -0
- package/dist/openai-chatgpt-jwt-KWcgd0d_.mjs +19 -0
- package/dist/{openai-chatgpt-responses-B84Ibtrd.mjs → openai-chatgpt-responses-CrPmqERt.mjs} +284 -240
- package/dist/{openai-completions-DsOxhOD1.mjs → openai-completions-BPnt4Sml.mjs} +65 -99
- package/dist/{openai-completions-compat-DBWjXoMZ.d.mts → openai-completions-compat-Dt3dcawL.d.mts} +2 -2
- package/dist/{openai-responses-BT7A3sLu.mjs → openai-responses-DhIKtOup.mjs} +18 -41
- package/dist/openai-responses-contracts-CyfIkQi5.mjs +253 -0
- package/dist/openai-responses-contracts-XpZJxrRG.d.mts +68 -0
- package/dist/openai-responses-payload-policy-BDxV-W0c.mjs +206 -0
- package/dist/openai-responses-payload-policy-BSs371VM.d.mts +40 -0
- package/dist/openai-responses-prompt-observer-internal-DgNTYnRY.mjs +46 -0
- package/dist/{openai-responses-stream-internal-Cw5txaGW.mjs → openai-responses-shared-DXIt3iY5.mjs} +1406 -940
- package/dist/{openai-reasoning-compat-YgeLncHw.mjs → openai-stop-reason-BkFkqqK0.mjs} +204 -37
- package/dist/openai-tool-projection-CY04OcvQ.mjs +338 -0
- package/dist/provider-error-BUwEnjXq.mjs +429 -0
- package/dist/provider-error-CzNw4BWX.d.mts +12 -0
- package/dist/{provider-options-D8bB3z9b.d.mts → provider-options-B96RdNpH.d.mts} +9 -3
- package/dist/provider-transcript-transform-ePx-Bbfr.mjs +155 -0
- package/dist/provider-types.d.mts +31 -0
- package/dist/provider-types.mjs +8 -0
- package/dist/providers.d.mts +1 -1
- package/dist/providers.mjs +17 -19
- package/dist/{reasoning-tag-text-partitioner-CGDyLWUR.mjs → reasoning-tag-text-partitioner-rnPwX2pg.mjs} +14 -8
- package/dist/record-coerce-DdXsgUd_.mjs +23 -0
- package/dist/sanitize-unicode-BYqrYtC_.mjs +90 -0
- package/dist/session-resources-CkR4WWy1.mjs +21 -0
- package/dist/simple-options-D58D5Kvw.mjs +117 -0
- package/dist/src-D2H6yKkH.mjs +2 -0
- package/dist/{stream-first-event-timeout-BBys9hSb.mjs → stream-first-event-timeout-MK28puvq.mjs} +2 -2
- package/dist/string-coerce-fsri9iCu.mjs +34 -0
- package/dist/{tool-schema-json-projection-BwNu3nDi.mjs → tool-schema-json-projection-q5d7QX5c.mjs} +32 -7
- package/dist/transport-utils-DJqkxbhC.mjs +138 -0
- package/dist/transports.d.mts +166 -241
- package/dist/transports.mjs +1979 -1825
- package/dist/types-BDdaOVi2.mjs +6 -0
- package/dist/{types-bzp5k29J.d.mts → types-BHNrPS1l.d.mts} +23 -1
- package/dist/types.d.mts +4 -4
- package/dist/types.mjs +6 -4
- package/dist/utf16-slice-CvGodqok.mjs +29 -0
- package/dist/{validation-DAa_yFOM.mjs → validation-B61OhAio.mjs} +6 -6
- package/dist/{validation-B-j7cOYp.d.mts → validation-DT9SrFn3.d.mts} +1 -1
- package/dist/validation.d.mts +1 -1
- package/dist/validation.mjs +1 -1
- package/package.json +15 -1
- package/dist/anthropic-usage-DWU-x8MI.mjs +0 -459
- package/dist/error-coercion-DgxlWC0n.mjs +0 -15
- package/dist/headers-B_e4-1J0.mjs +0 -9
- package/dist/host-Dog2WQiR.mjs +0 -369
- package/dist/number-coercion-DvG7SNMg.mjs +0 -129
- package/dist/openai-chatgpt-jwt-DhAAzLkj.mjs +0 -39
- package/dist/openai-responses-shared-pXl6Wd8S.mjs +0 -392
- package/dist/openai-tool-projection-OhX64DoP.mjs +0 -215
- package/dist/provider-error-CAEvRjry.mjs +0 -47
- package/dist/sanitize-unicode-DT5o51ur.mjs +0 -26
- package/dist/simple-options-9lhRrN73.mjs +0 -50
- package/dist/tool-result-text-CTpIRbYd.mjs +0 -225
- package/dist/transform-messages-C8mBqZxF.mjs +0 -2
- package/dist/transport-stream-shared-D81p90xq.mjs +0 -297
package/dist/transports.mjs
CHANGED
|
@@ -1,364 +1,53 @@
|
|
|
1
1
|
import { n as getEnvApiKey } from "./env-api-keys-DrgeBuva.mjs";
|
|
2
|
-
import { d as resolveClaudeSonnet5ModelIdentity, g as supportsClaudeNativeXhighEffort,
|
|
3
|
-
import { r as createAssistantMessageEventStream } from "./event-stream-
|
|
4
|
-
import {
|
|
2
|
+
import { d as resolveClaudeSonnet5ModelIdentity, g as supportsClaudeNativeXhighEffort, p as supportsClaudeAdaptiveThinking, u as resolveClaudeOpus5ModelIdentity } from "./anthropic-JsNA5KCu.mjs";
|
|
3
|
+
import { r as createAssistantMessageEventStream } from "./event-stream-uSMZJ3FA.mjs";
|
|
4
|
+
import { n as normalizeLowercaseStringOrEmpty, t as hasNonEmptyString } from "./string-coerce-fsri9iCu.mjs";
|
|
5
|
+
import { r as calculateCost } from "./sanitize-unicode-BYqrYtC_.mjs";
|
|
6
|
+
import { _ as resolveAnthropicThinkingEffort, a as describeToolResultMediaPlaceholder, b as usesClaudeStreamingRefusalContract, d as ANTHROPIC_CLAUDE_CODE_VERSION, f as applyClaudeRequestContract, g as requiresClaudeAdaptiveThinking, h as prepareClaudeNoPrefillRequestContext, l as isImageWithMediaPayload, m as mapAnthropicStopReason, n as getAiTransportHost, o as extractToolResultBlockText, p as defaultsClaudeAdaptiveThinking, r as resolveAiTransportHeaderSentinels, s as extractToolResultText, u as ANTHROPIC_CLAUDE_CODE_BILLING_SYSTEM_BLOCK, y as usesClaudeFable5MessagesContract } from "./host-DTqNc7ad.mjs";
|
|
7
|
+
import { a as isRecord } from "./record-coerce-DdXsgUd_.mjs";
|
|
8
|
+
import { i as canonicalizeBase64, n as projectProviderError, o as stableStringify } from "./provider-error-BUwEnjXq.mjs";
|
|
9
|
+
import { n as toErrorObject, t as createResponsesPromptEgressObserver } from "./openai-responses-prompt-observer-internal-DgNTYnRY.mjs";
|
|
10
|
+
import { r as asNonNegativeFiniteNumber } from "./number-coercion-H9qHik3g.mjs";
|
|
11
|
+
import { _ as isOpenAIGpt56Model, c as OpenAIResponsesWebSocketPostDispatchError, d as OpenAIResponsesWebSocketSafeRetryError, g as isOpenAIGpt55Model, h as isOpenAIGpt54MiniModel, i as OPENAI_RESPONSES_APIS, l as OpenAIResponsesWebSocketPreDispatchError, p as parseOpenAIResponsesWebSocketServerError, r as OPENAI_CODEX_RESPONSES_DEFAULT_INSTRUCTIONS, t as AZURE_RESPONSES_FIRST_EVENT_TIMEOUT_MS, u as OpenAIResponsesWebSocketResponseFailedError, v as normalizeOpenAIReasoningEffort, w as uniqueStrings, y as resolveOpenAIReasoningEffortForModel } from "./openai-responses-contracts-CyfIkQi5.mjs";
|
|
5
12
|
import { n as clampOpenAIPromptCacheKey } from "./openai-prompt-cache-mZTCdRPo.mjs";
|
|
6
13
|
import { t as resolveCacheRetention } from "./cache-retention-0x979a5V.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { a as
|
|
9
|
-
import { t as
|
|
10
|
-
import {
|
|
11
|
-
import { a as parseStrictPositiveInteger, c as calculateCost, l as clampThinkingLevel, s as applyProviderReportedUsageCost } from "./number-coercion-DvG7SNMg.mjs";
|
|
12
|
-
import { a as resolveOpenAICompletionsCompat, c as clearPendingCommentaryText, i as detectOpenAICompletionsCompat, l as rememberPendingCommentaryTags, n as mapOpenAIStopReason, o as resolveOpenAICompletionsResponseFormat, r as convertMessages, s as shouldOmitOllamaCompatResponseFormat, t as resolveOpenAIReasoningEffortMap, u as tagPendingCommentaryText } from "./openai-reasoning-compat-YgeLncHw.mjs";
|
|
14
|
+
import { f as sortPromptCacheToolsByName, l as stripSystemPromptCacheBoundary, t as adjustMaxTokensForThinking } from "./simple-options-D58D5Kvw.mjs";
|
|
15
|
+
import { a as resolveProviderEndpoint, c as transformTransportMessages, i as resolveOpenAIStrictToolSetting, n as buildGuardedModelFetch, r as resolveModelRequestTimeoutMs, s as resolveProviderRequestPolicyConfig } from "./tool-schema-json-projection-q5d7QX5c.mjs";
|
|
16
|
+
import { A as resolveAnthropicImageMediaType, C as readAnthropicFallbackBoundary, D as usesFoundryBearerAuth, E as omitFoundryBearerCredentialHeaders, F as resolveAnthropicPayloadPolicy, I as resolveAnthropicServerCompactionPlan, M as applyAnthropicEphemeralCacheControlMarkers, N as applyAnthropicPayloadPolicyToParams, O as createAnthropicInlineImageBudget, P as resolveAnthropicEphemeralCacheControl, S as applyAnthropicFallbackBoundary, T as applyAnthropicRefusal, _ as ANTHROPIC_OMITTED_REASONING_TEXT, a as applyAnthropicMessageDeltaUsage, b as ANTHROPIC_SERVER_SIDE_FALLBACK_BETA, d as normalizeAnthropicToolCallId, f as normalizeAnthropicToolChoice, g as toClaudeCodeToolName, h as resolveOriginalAnthropicToolName, i as suppressAnthropicCompaction, j as applyAnthropicCacheControlToMessages, k as normalizeAnthropicInlineContent, m as reconcileAnthropicToolChoice, n as createCompactionCapture, o as applyAnthropicMessageStartUsage, p as projectAnthropicTools, r as isAnthropicReplayRejection, t as buildAnthropicReplayPlan, v as findActiveAnthropicToolTurnAssistantIndex, w as resolveAnthropicFallbackServingModelCost, y as ANTHROPIC_SERVER_SIDE_FALLBACKS } from "./anthropic-compaction-replay-8lJNKXOE.mjs";
|
|
17
|
+
import { a as createOpenAICompletionsToolCallDeltaNormalizer, c as resolveOpenAICompletionsCompat, d as clearPendingCommentaryText, f as rememberPendingCommentaryTags, i as hasToolCallHistory, l as resolveOpenAICompletionsResponseFormat, n as resolveOpenAIReasoningEffortMap, o as finalizeOpenAICompletionsToolCalls, p as tagPendingCommentaryText, r as convertMessages, s as detectOpenAICompletionsCompat, t as mapOpenAIStopReason, u as shouldOmitOllamaCompatResponseFormat } from "./openai-stop-reason-BkFkqqK0.mjs";
|
|
13
18
|
import { t as createDeferredEventBuffer } from "./deferred-event-buffer-DAvyP7qA.mjs";
|
|
14
|
-
import { n as parseStreamingJson } from "./json-parse-
|
|
15
|
-
import {
|
|
16
|
-
import { C as
|
|
17
|
-
import {
|
|
18
|
-
import { t as
|
|
19
|
-
import {
|
|
19
|
+
import { n as parseStreamingJson } from "./json-parse-CDnesDM_.mjs";
|
|
20
|
+
import { n as notifyLlmRequestActivity } from "./headers-DdOQtGuU.mjs";
|
|
21
|
+
import { B as buildResponsesInputMessage, C as summarizeResponsesTools, G as suppressOpenAIResponsesCompaction, H as createOpenAIResponsesAssistantOutput, I as createResponsesStreamWithEncryptedContentRetry, K as resolveReplayableResponsesMessageId, L as isInvalidEncryptedContentError, R as resolveAzureOpenAIApiVersion, S as summarizeResponsesPayload, U as buildOpenAIResponsesReasoningReplayMetadata, V as convertResponsesMessages, W as captureOpenAIResponsesCompaction, Y as normalizeOpenAIStrictToolParameters, Z as resolveOpenAIProjectedToolsStrictToolFlag, _ as safeDebugValue, b as summarizeOpenAITransportError, c as processResponsesStream, dt as resolveModelPayloadDebugMode, f as observeResponsesStream, ft as resolveModelSseDebugMode, g as normalizeResponsesFailedEvent, h as logResponsesFailedNoDetails, ht as quoteUnsafeIntegerLiterals, m as buildResponsesFailedNoDetailsObservation, mt as parseJsonPreservingUnsafeIntegers, n as applyResponsesServiceTierPricing, p as ResponsesStreamFailure, pt as parseJsonObjectPreservingUnsafeIntegers, q as findOpenAIStrictToolProjectionDiagnostics, ut as emitModelTransportDebug, v as stringifyRedactedEvent, x as summarizeResponsesFailedNoDetailsObservation, y as stringifyRedactedPayload, z as resolveNextResponsesEncryptedContentAttempt } from "./openai-responses-shared-DXIt3iY5.mjs";
|
|
22
|
+
import { a as createWritableTransportEventStream, c as mergeTransportHeaders, d as sanitizeTransportPayloadText, f as transportAbortError, i as createEmptyTransportUsage, l as mergeTransportMetadata, n as assignTransportErrorDetails, o as failTransportStream, p as withProviderResponseHook, r as coerceTransportToolCallArguments, s as finalizeTransportStream, u as sanitizeNonEmptyTransportPayloadText } from "./provider-transcript-transform-ePx-Bbfr.mjs";
|
|
23
|
+
import { a as isGoogleGemini3FlashModel, c as readResponseTextSnippet, d as resolveModelHeaderSentinels$1, f as resolveSecretSentinel, i as isCodeModeModelVisibleToolName, l as redactIdentifier, m as supportsModelTools, n as createAbortError$1, o as isGoogleGemini3ProModel, p as sha256Hex, r as estimateStringChars, s as parseRetryAfterSeconds, t as MALFORMED_STREAMING_FRAGMENT_ERROR_MESSAGE } from "./transport-utils-DJqkxbhC.mjs";
|
|
24
|
+
import { t as googleFlashSupportsMinimalThinking } from "./google-thinking-level-C-V3tecN.mjs";
|
|
25
|
+
import { a as createModelStreamCooperativeScheduler, c as log, d as readOpenAICompletionsContentDeltas, f as resolvePromptCacheKey, i as GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, l as measureUtf8AppendBytes, n as reconcileOpenAICompletionsToolChoice, o as createOpenAIResponseHook, p as throwIfModelStreamAborted, r as reconcileOpenAIResponsesToolChoice, s as isOpenAICompletionsThinkingEnabled, t as projectOpenAITools, u as parseOpenAICompletionsUsage } from "./openai-tool-projection-CY04OcvQ.mjs";
|
|
26
|
+
import { a as withFirstStreamEventTimeout, i as getFirstStreamEventTimeoutMs, r as getFirstStreamEventTimeoutHandler, t as createFirstStreamEventAbortController } from "./stream-first-event-timeout-MK28puvq.mjs";
|
|
27
|
+
import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-rnPwX2pg.mjs";
|
|
28
|
+
import { i as resolveOpenAIResponsesServerCompactionPlan, n as resolveOpenAIResponsesCompactEndpointPlan, r as resolveOpenAIResponsesPayloadPolicy, t as applyOpenAIResponsesPayloadPolicy } from "./openai-responses-payload-policy-BDxV-W0c.mjs";
|
|
20
29
|
import { i as resolveAzureDeploymentNameFromMap, t as isOpenAICompatibleAzureResponsesBaseUrl } from "./azure-openai-responses-client-compat-C7K7QfUE.mjs";
|
|
30
|
+
import { n as registerSessionResourceCleanup } from "./session-resources-CkR4WWy1.mjs";
|
|
21
31
|
import { randomUUID } from "node:crypto";
|
|
22
32
|
import OpenAI, { AzureOpenAI } from "openai";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Anthropic-family request payload policy helpers.
|
|
26
|
-
* Applies service-tier and cache-control markers only when provider endpoint
|
|
27
|
-
* capabilities allow them.
|
|
28
|
-
*/
|
|
29
|
-
const ANTHROPIC_CACHE_CONTROL_LIMIT = 4;
|
|
30
|
-
function resolveBaseUrlHostname(baseUrl) {
|
|
31
|
-
try {
|
|
32
|
-
return new URL(baseUrl).hostname;
|
|
33
|
-
} catch {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function isLongTtlEligibleEndpoint(baseUrl) {
|
|
38
|
-
if (typeof baseUrl !== "string") return false;
|
|
39
|
-
const hostname = resolveBaseUrlHostname(baseUrl);
|
|
40
|
-
if (!hostname) return false;
|
|
41
|
-
return hostname === "api.anthropic.com" || hostname === "aiplatform.googleapis.com" || hostname.endsWith("-aiplatform.googleapis.com");
|
|
42
|
-
}
|
|
43
|
-
/** Resolve Anthropic cache-control marker retention for a request endpoint. */
|
|
44
|
-
function resolveAnthropicEphemeralCacheControl(baseUrl, cacheRetention) {
|
|
45
|
-
const retention = resolveCacheRetention(cacheRetention);
|
|
46
|
-
if (retention === "none") return;
|
|
47
|
-
const ttl = retention === "long" && (cacheRetention === "long" || isLongTtlEligibleEndpoint(baseUrl)) ? "1h" : void 0;
|
|
48
|
-
return {
|
|
49
|
-
type: "ephemeral",
|
|
50
|
-
...ttl ? { ttl } : {}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
function applyAnthropicCacheControlToSystem(system, cacheControl) {
|
|
54
|
-
if (!Array.isArray(system)) return;
|
|
55
|
-
const normalizedBlocks = [];
|
|
56
|
-
for (const block of system) {
|
|
57
|
-
if (!block || typeof block !== "object") {
|
|
58
|
-
normalizedBlocks.push(block);
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const record = block;
|
|
62
|
-
if (record.type !== "text" || typeof record.text !== "string") {
|
|
63
|
-
normalizedBlocks.push(block);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const split = splitSystemPromptCacheBoundary(record.text);
|
|
67
|
-
if (!split) {
|
|
68
|
-
if (record.cache_control === void 0) record.cache_control = cacheControl;
|
|
69
|
-
normalizedBlocks.push(record);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const { cache_control: existingCacheControl, ...rest } = record;
|
|
73
|
-
if (split.stablePrefix) normalizedBlocks.push({
|
|
74
|
-
...rest,
|
|
75
|
-
text: split.stablePrefix,
|
|
76
|
-
cache_control: existingCacheControl ?? cacheControl
|
|
77
|
-
});
|
|
78
|
-
if (split.dynamicSuffix) normalizedBlocks.push({
|
|
79
|
-
...rest,
|
|
80
|
-
text: split.dynamicSuffix
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
system.splice(0, system.length, ...normalizedBlocks);
|
|
84
|
-
}
|
|
85
|
-
function stripAnthropicSystemPromptBoundary(system) {
|
|
86
|
-
if (!Array.isArray(system)) return;
|
|
87
|
-
for (const block of system) {
|
|
88
|
-
if (!block || typeof block !== "object") continue;
|
|
89
|
-
const record = block;
|
|
90
|
-
if (record.type === "text" && typeof record.text === "string") record.text = stripSystemPromptCacheBoundary(record.text);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function applyAnthropicCacheControlToMessages(messages, cacheControl, markerLimit, cacheBreakpointOptOutMessageIndexes) {
|
|
94
|
-
if (!Array.isArray(messages) || messages.length === 0 || markerLimit <= 0) return;
|
|
95
|
-
let fallbackToolResult;
|
|
96
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
97
|
-
const message = messages[i];
|
|
98
|
-
if (!message || typeof message !== "object") continue;
|
|
99
|
-
const record = message;
|
|
100
|
-
if (record.role !== "user" || cacheBreakpointOptOutMessageIndexes.has(i)) continue;
|
|
101
|
-
const content = record.content;
|
|
102
|
-
if (typeof content === "string") {
|
|
103
|
-
if (fallbackToolResult && markerLimit === 1) {
|
|
104
|
-
fallbackToolResult.cache_control = cacheControl;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
record.content = [{
|
|
108
|
-
type: "text",
|
|
109
|
-
text: content,
|
|
110
|
-
cache_control: cacheControl
|
|
111
|
-
}];
|
|
112
|
-
if (fallbackToolResult && markerLimit > 1) fallbackToolResult.cache_control = cacheControl;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (!Array.isArray(content)) continue;
|
|
116
|
-
for (let j = content.length - 1; j >= 0; j--) {
|
|
117
|
-
const block = content[j];
|
|
118
|
-
if (!block || typeof block !== "object") continue;
|
|
119
|
-
const blockRecord = block;
|
|
120
|
-
if (blockRecord.type === "text" || blockRecord.type === "image") {
|
|
121
|
-
if (fallbackToolResult && markerLimit === 1) {
|
|
122
|
-
fallbackToolResult.cache_control = cacheControl;
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
blockRecord.cache_control = cacheControl;
|
|
126
|
-
if (fallbackToolResult && markerLimit > 1) fallbackToolResult.cache_control = cacheControl;
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (blockRecord.type === "tool_result" && fallbackToolResult === void 0) fallbackToolResult = blockRecord;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (fallbackToolResult) fallbackToolResult.cache_control = cacheControl;
|
|
133
|
-
}
|
|
134
|
-
function countAnthropicCacheControlMarkers(blocks) {
|
|
135
|
-
if (!Array.isArray(blocks)) return 0;
|
|
136
|
-
let count = 0;
|
|
137
|
-
for (const block of blocks) if (block && typeof block === "object" && "cache_control" in block) count += 1;
|
|
138
|
-
return count;
|
|
139
|
-
}
|
|
140
|
-
/** @deprecated Anthropic-family provider payload helper; do not use from third-party plugins. */
|
|
141
|
-
function resolveAnthropicPayloadPolicy(input) {
|
|
142
|
-
return {
|
|
143
|
-
allowsServiceTier: resolveProviderRequestCapabilities({
|
|
144
|
-
provider: input.provider,
|
|
145
|
-
api: input.api,
|
|
146
|
-
baseUrl: input.baseUrl,
|
|
147
|
-
capability: "llm",
|
|
148
|
-
transport: "stream"
|
|
149
|
-
}).allowsAnthropicServiceTier,
|
|
150
|
-
cacheControl: input.enableCacheControl === true ? resolveAnthropicEphemeralCacheControl(input.baseUrl, input.cacheRetention) : void 0,
|
|
151
|
-
serviceTier: input.serviceTier
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
/** @deprecated Anthropic-family provider payload helper; do not use from third-party plugins. */
|
|
155
|
-
function applyAnthropicPayloadPolicyToParams(payloadObj, policy, cacheBreakpointOptOutMessageIndexes) {
|
|
156
|
-
if (policy.allowsServiceTier && policy.serviceTier !== void 0 && payloadObj.service_tier === void 0) payloadObj.service_tier = policy.serviceTier;
|
|
157
|
-
if (policy.cacheControl) applyAnthropicCacheControlToSystem(payloadObj.system, policy.cacheControl);
|
|
158
|
-
else stripAnthropicSystemPromptBoundary(payloadObj.system);
|
|
159
|
-
if (!policy.cacheControl) return;
|
|
160
|
-
const usedMarkers = countAnthropicCacheControlMarkers(payloadObj.system) + countAnthropicCacheControlMarkers(payloadObj.tools);
|
|
161
|
-
applyAnthropicCacheControlToMessages(payloadObj.messages, policy.cacheControl, ANTHROPIC_CACHE_CONTROL_LIMIT - usedMarkers, cacheBreakpointOptOutMessageIndexes);
|
|
162
|
-
}
|
|
163
|
-
/** @deprecated Anthropic-family provider payload helper; do not use from third-party plugins. */
|
|
164
|
-
function applyAnthropicEphemeralCacheControlMarkers(payloadObj, cacheControl = { type: "ephemeral" }) {
|
|
165
|
-
const messages = payloadObj.messages;
|
|
166
|
-
if (!Array.isArray(messages)) return;
|
|
167
|
-
for (const message of messages) {
|
|
168
|
-
if (message.role === "system" || message.role === "developer") {
|
|
169
|
-
if (!cacheControl) continue;
|
|
170
|
-
if (typeof message.content === "string") {
|
|
171
|
-
message.content = [{
|
|
172
|
-
type: "text",
|
|
173
|
-
text: message.content,
|
|
174
|
-
cache_control: cacheControl
|
|
175
|
-
}];
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (Array.isArray(message.content) && message.content.length > 0) {
|
|
179
|
-
const last = message.content[message.content.length - 1];
|
|
180
|
-
if (last && typeof last === "object") {
|
|
181
|
-
const record = last;
|
|
182
|
-
if (record.type !== "thinking" && record.type !== "redacted_thinking") record.cache_control = cacheControl;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
if (message.role === "assistant" && Array.isArray(message.content)) for (const block of message.content) {
|
|
188
|
-
if (!block || typeof block !== "object") continue;
|
|
189
|
-
const record = block;
|
|
190
|
-
if (record.type === "thinking" || record.type === "redacted_thinking") delete record.cache_control;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region packages/ai/src/transports/json-unsafe-integers.ts
|
|
196
|
-
/**
|
|
197
|
-
* JSON parsing helpers that preserve integer literals larger than
|
|
198
|
-
* Number.MAX_SAFE_INTEGER as strings before JSON.parse can round them.
|
|
199
|
-
*/
|
|
200
|
-
const MAX_SAFE_INTEGER_ABS_STR = String(Number.MAX_SAFE_INTEGER);
|
|
201
|
-
function isAsciiDigit(ch) {
|
|
202
|
-
return ch !== void 0 && ch >= "0" && ch <= "9";
|
|
203
|
-
}
|
|
204
|
-
function parseJsonNumberToken(input, start) {
|
|
205
|
-
let idx = start;
|
|
206
|
-
if (input[idx] === "-") idx += 1;
|
|
207
|
-
if (idx >= input.length) return null;
|
|
208
|
-
if (input[idx] === "0") idx += 1;
|
|
209
|
-
else if (isAsciiDigit(input[idx]) && input[idx] !== "0") while (isAsciiDigit(input[idx])) idx += 1;
|
|
210
|
-
else return null;
|
|
211
|
-
let isInteger = true;
|
|
212
|
-
if (input[idx] === ".") {
|
|
213
|
-
isInteger = false;
|
|
214
|
-
idx += 1;
|
|
215
|
-
if (!isAsciiDigit(input[idx])) return null;
|
|
216
|
-
while (isAsciiDigit(input[idx])) idx += 1;
|
|
217
|
-
}
|
|
218
|
-
if (input[idx] === "e" || input[idx] === "E") {
|
|
219
|
-
isInteger = false;
|
|
220
|
-
idx += 1;
|
|
221
|
-
if (input[idx] === "+" || input[idx] === "-") idx += 1;
|
|
222
|
-
if (!isAsciiDigit(input[idx])) return null;
|
|
223
|
-
while (isAsciiDigit(input[idx])) idx += 1;
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
token: input.slice(start, idx),
|
|
227
|
-
end: idx,
|
|
228
|
-
isInteger
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function isUnsafeIntegerLiteral(token) {
|
|
232
|
-
const digits = token[0] === "-" ? token.slice(1) : token;
|
|
233
|
-
if (digits.length < MAX_SAFE_INTEGER_ABS_STR.length) return false;
|
|
234
|
-
if (digits.length > MAX_SAFE_INTEGER_ABS_STR.length) return true;
|
|
235
|
-
return digits > MAX_SAFE_INTEGER_ABS_STR;
|
|
236
|
-
}
|
|
237
|
-
/** Quotes integer literals above Number.MAX_SAFE_INTEGER before JSON.parse. */
|
|
238
|
-
function quoteUnsafeIntegerLiterals(input) {
|
|
239
|
-
let out = "";
|
|
240
|
-
let inString = false;
|
|
241
|
-
let escaped = false;
|
|
242
|
-
let idx = 0;
|
|
243
|
-
while (idx < input.length) {
|
|
244
|
-
const ch = input[idx] ?? "";
|
|
245
|
-
if (inString) {
|
|
246
|
-
out += ch;
|
|
247
|
-
if (escaped) escaped = false;
|
|
248
|
-
else if (ch === "\\") escaped = true;
|
|
249
|
-
else if (ch === "\"") inString = false;
|
|
250
|
-
idx += 1;
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
if (ch === "\"") {
|
|
254
|
-
inString = true;
|
|
255
|
-
out += ch;
|
|
256
|
-
idx += 1;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (ch === "-" || isAsciiDigit(ch)) {
|
|
260
|
-
const parsed = parseJsonNumberToken(input, idx);
|
|
261
|
-
if (parsed) {
|
|
262
|
-
if (parsed.isInteger && isUnsafeIntegerLiteral(parsed.token)) out += `"${parsed.token}"`;
|
|
263
|
-
else out += parsed.token;
|
|
264
|
-
idx = parsed.end;
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
out += ch;
|
|
269
|
-
idx += 1;
|
|
270
|
-
}
|
|
271
|
-
return out;
|
|
272
|
-
}
|
|
273
|
-
/** Parses JSON while preserving unsafe integer literals as strings. */
|
|
274
|
-
function parseJsonPreservingUnsafeIntegers(input) {
|
|
275
|
-
return JSON.parse(quoteUnsafeIntegerLiterals(input));
|
|
276
|
-
}
|
|
277
|
-
/** Parses or accepts an object while preserving unsafe integer literals in string input. */
|
|
278
|
-
function parseJsonObjectPreservingUnsafeIntegers(value) {
|
|
279
|
-
if (typeof value === "string") {
|
|
280
|
-
try {
|
|
281
|
-
const parsed = parseJsonPreservingUnsafeIntegers(value);
|
|
282
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
283
|
-
} catch {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
//#endregion
|
|
33
|
+
import { ResponsesWS } from "openai/resources/responses/ws.js";
|
|
292
34
|
//#region packages/ai/src/transports/anthropic-transport-stream.ts
|
|
293
35
|
/**
|
|
294
36
|
* Native Anthropic Messages streaming transport.
|
|
295
37
|
* Converts OpenClaw contexts/tools into Anthropic payloads, streams SSE events
|
|
296
38
|
* back into runtime output blocks, and applies provider request policy.
|
|
297
39
|
*/
|
|
298
|
-
const CLAUDE_CODE_VERSION = "2.1.75";
|
|
299
|
-
const CLAUDE_CODE_BILLING_SYSTEM_BLOCK = `x-anthropic-billing-header: cc_version=${CLAUDE_CODE_VERSION}; cc_entrypoint=sdk-cli;`;
|
|
300
40
|
const ANTHROPIC_MESSAGES_ERROR_BODY_MAX_BYTES = 8 * 1024;
|
|
301
41
|
const ANTHROPIC_MESSAGES_ERROR_BODY_MAX_CHARS = 400;
|
|
302
42
|
const ANTHROPIC_MESSAGES_ERROR_BODY_READ_IDLE_TIMEOUT_MS = 1e4;
|
|
303
43
|
const ANTHROPIC_MESSAGES_DEFAULT_MAX_TOKENS = 4096;
|
|
304
44
|
const ANTHROPIC_MESSAGES_FALLBACK_CONTEXT_DIVISOR = 4;
|
|
305
45
|
const ANTHROPIC_MESSAGES_SSE_PENDING_BUFFER_MAX_CHARS = 16 * 1024 * 1024;
|
|
306
|
-
const CLAUDE_CODE_TOOL_LOOKUP = new Map([
|
|
307
|
-
"Read",
|
|
308
|
-
"Write",
|
|
309
|
-
"Edit",
|
|
310
|
-
"Bash",
|
|
311
|
-
"Grep",
|
|
312
|
-
"Glob",
|
|
313
|
-
"AskUserQuestion",
|
|
314
|
-
"EnterPlanMode",
|
|
315
|
-
"ExitPlanMode",
|
|
316
|
-
"KillShell",
|
|
317
|
-
"NotebookEdit",
|
|
318
|
-
"Skill",
|
|
319
|
-
"Task",
|
|
320
|
-
"TaskOutput",
|
|
321
|
-
"TodoWrite",
|
|
322
|
-
"WebFetch",
|
|
323
|
-
"WebSearch"
|
|
324
|
-
].map((tool) => [normalizeLowercaseStringOrEmpty(tool), tool]));
|
|
325
46
|
function resolveAnthropicRequestModelId(model) {
|
|
326
47
|
if (isDirectAnthropicModel(model) && /^anthropic\//i.test(model.id)) return model.id.replace(/^anthropic\//i, "");
|
|
327
48
|
return model.id;
|
|
328
49
|
}
|
|
329
50
|
const EMPTY_ANTHROPIC_MESSAGES_FALLBACK_TEXT = ".";
|
|
330
|
-
function normalizeAnthropicToolChoice(thinkingEnabled, toolChoice) {
|
|
331
|
-
if (thinkingEnabled && (toolChoice === "any" || typeof toolChoice === "object" && toolChoice.type === "tool")) return { type: "auto" };
|
|
332
|
-
return typeof toolChoice === "string" ? { type: toolChoice } : toolChoice;
|
|
333
|
-
}
|
|
334
|
-
function supportsNativeXhighEffort(model) {
|
|
335
|
-
return supportsClaudeNativeXhighEffort(model);
|
|
336
|
-
}
|
|
337
|
-
function supportsAdaptiveThinking(model) {
|
|
338
|
-
return supportsClaudeAdaptiveThinking(model);
|
|
339
|
-
}
|
|
340
|
-
function mapThinkingLevelToEffort(level, model) {
|
|
341
|
-
const thinkingLevelMap = resolveClaudeNativeThinkingLevelMap(model);
|
|
342
|
-
const resolvedLevel = clampThinkingLevel({
|
|
343
|
-
...model,
|
|
344
|
-
...typeof model.params?.canonicalModelId === "string" ? { reasoning: true } : {},
|
|
345
|
-
...thinkingLevelMap ? { thinkingLevelMap } : {}
|
|
346
|
-
}, level);
|
|
347
|
-
const mapped = thinkingLevelMap?.[resolvedLevel];
|
|
348
|
-
if (typeof mapped === "string") return mapped;
|
|
349
|
-
switch (resolvedLevel) {
|
|
350
|
-
case "off":
|
|
351
|
-
case "minimal":
|
|
352
|
-
case "low": return "low";
|
|
353
|
-
case "medium": return "medium";
|
|
354
|
-
case "xhigh": return supportsNativeXhighEffort(model) ? "xhigh" : "high";
|
|
355
|
-
case "max": return supportsClaudeNativeMaxEffort(model) ? "max" : "high";
|
|
356
|
-
default: return "high";
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
function clampReasoningLevel(level) {
|
|
360
|
-
return level === "xhigh" || level === "max" ? "high" : level;
|
|
361
|
-
}
|
|
362
51
|
function resolvePositiveAnthropicTokenLimit(value) {
|
|
363
52
|
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
|
364
53
|
const floored = Math.floor(value);
|
|
@@ -373,23 +62,6 @@ function resolveAnthropicMessagesMaxTokens(params) {
|
|
|
373
62
|
const contextWindow = resolvePositiveAnthropicTokenLimit(params.modelContextWindow);
|
|
374
63
|
return contextWindow === void 0 ? ANTHROPIC_MESSAGES_DEFAULT_MAX_TOKENS : Math.max(1, Math.min(ANTHROPIC_MESSAGES_DEFAULT_MAX_TOKENS, Math.floor(contextWindow / ANTHROPIC_MESSAGES_FALLBACK_CONTEXT_DIVISOR)));
|
|
375
64
|
}
|
|
376
|
-
function adjustMaxTokensForThinking(params) {
|
|
377
|
-
const budgets = {
|
|
378
|
-
minimal: 1024,
|
|
379
|
-
low: 2048,
|
|
380
|
-
medium: 8192,
|
|
381
|
-
high: 16384,
|
|
382
|
-
...params.customBudgets
|
|
383
|
-
};
|
|
384
|
-
const minOutputTokens = 1024;
|
|
385
|
-
let thinkingBudget = budgets[clampReasoningLevel(params.reasoningLevel)];
|
|
386
|
-
const maxTokens = Math.min(params.baseMaxTokens + thinkingBudget, params.modelMaxTokens);
|
|
387
|
-
if (maxTokens <= thinkingBudget) thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
|
|
388
|
-
return {
|
|
389
|
-
maxTokens,
|
|
390
|
-
thinkingBudget
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
65
|
function isAnthropicOAuthToken(apiKey) {
|
|
394
66
|
return (resolveSecretSentinel(apiKey) ?? apiKey).includes("sk-ant-oat");
|
|
395
67
|
}
|
|
@@ -416,9 +88,6 @@ function buildAnthropicBetaHeader(model, betaFeatures, params) {
|
|
|
416
88
|
if (!isDirectAnthropicModel(model)) return;
|
|
417
89
|
return params.oauth ? `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}` : betaFeatures.join(",");
|
|
418
90
|
}
|
|
419
|
-
function toClaudeCodeName(name) {
|
|
420
|
-
return CLAUDE_CODE_TOOL_LOOKUP.get(normalizeLowercaseStringOrEmpty(name)) ?? name;
|
|
421
|
-
}
|
|
422
91
|
const NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
423
92
|
async function convertContentBlocks(content, model, imageBudget) {
|
|
424
93
|
const text = extractToolResultText(content);
|
|
@@ -459,15 +128,12 @@ async function convertContentBlocks(content, model, imageBudget) {
|
|
|
459
128
|
});
|
|
460
129
|
return blocks;
|
|
461
130
|
}
|
|
462
|
-
function normalizeToolCallId(id) {
|
|
463
|
-
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
464
|
-
}
|
|
465
131
|
async function convertAnthropicMessages(messages, model, isOAuthToken, options) {
|
|
466
132
|
const params = [];
|
|
467
133
|
const imageBudget = createAnthropicInlineImageBudget();
|
|
468
134
|
const allowReasoningContentReplay = options.allowReasoningContentReplay === true;
|
|
469
135
|
const replayThinkingEnabled = options.replayThinkingEnabled !== false;
|
|
470
|
-
const transformedMessages = transformTransportMessages(messages, model,
|
|
136
|
+
const transformedMessages = transformTransportMessages(messages, model, normalizeAnthropicToolCallId);
|
|
471
137
|
const activeToolTurnAssistantIndex = replayThinkingEnabled ? -1 : findActiveAnthropicToolTurnAssistantIndex(transformedMessages);
|
|
472
138
|
for (let i = 0; i < transformedMessages.length; i += 1) {
|
|
473
139
|
const msg = transformedMessages[i];
|
|
@@ -511,7 +177,7 @@ async function convertAnthropicMessages(messages, model, isOAuthToken, options)
|
|
|
511
177
|
continue;
|
|
512
178
|
}
|
|
513
179
|
if (msg.role === "assistant") {
|
|
514
|
-
const blocks = [];
|
|
180
|
+
const blocks = i === 0 && options.compaction ? [options.compaction] : [];
|
|
515
181
|
const reasoningContent = [];
|
|
516
182
|
let omittedThinking = false;
|
|
517
183
|
for (const block of msg.content) {
|
|
@@ -566,7 +232,7 @@ async function convertAnthropicMessages(messages, model, isOAuthToken, options)
|
|
|
566
232
|
if (block.type === "toolCall") blocks.push({
|
|
567
233
|
type: "tool_use",
|
|
568
234
|
id: block.id,
|
|
569
|
-
name: isOAuthToken ?
|
|
235
|
+
name: isOAuthToken ? toClaudeCodeToolName(block.name) : block.name,
|
|
570
236
|
input: coerceTransportToolCallArguments(block.arguments)
|
|
571
237
|
});
|
|
572
238
|
}
|
|
@@ -625,7 +291,7 @@ function ensureNonEmptyAnthropicMessages(messages) {
|
|
|
625
291
|
}];
|
|
626
292
|
}
|
|
627
293
|
function convertAnthropicTools(tools, isOAuthToken) {
|
|
628
|
-
const projection = projectAnthropicTools(tools ?? [], (name) => isOAuthToken ?
|
|
294
|
+
const projection = projectAnthropicTools(tools ?? [], (name) => isOAuthToken ? toClaudeCodeToolName(name) : name);
|
|
629
295
|
const converted = [];
|
|
630
296
|
for (const tool of projection.tools) converted.push({
|
|
631
297
|
name: tool.wireName,
|
|
@@ -640,18 +306,6 @@ function convertAnthropicTools(tools, isOAuthToken) {
|
|
|
640
306
|
function parseAnthropicToolCallArguments(inputJson) {
|
|
641
307
|
return parseJsonObjectPreservingUnsafeIntegers(inputJson) ?? parseStreamingJson(inputJson);
|
|
642
308
|
}
|
|
643
|
-
function mapStopReason(reason) {
|
|
644
|
-
switch (reason) {
|
|
645
|
-
case "end_turn": return "stop";
|
|
646
|
-
case "max_tokens": return "length";
|
|
647
|
-
case "tool_use": return "toolUse";
|
|
648
|
-
case "pause_turn": return "stop";
|
|
649
|
-
case "refusal":
|
|
650
|
-
case "sensitive": return "error";
|
|
651
|
-
case "stop_sequence": return "stop";
|
|
652
|
-
default: throw new Error(`Unhandled stop reason: ${String(reason)}`);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
309
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com";
|
|
656
310
|
/** Resolve the effective Anthropic API base URL from model or environment. */
|
|
657
311
|
function resolveAnthropicBaseUrl(baseUrl) {
|
|
@@ -794,7 +448,7 @@ async function readAnthropicMessagesErrorBodySnippet(response) {
|
|
|
794
448
|
}
|
|
795
449
|
function createAnthropicTransportClient(params) {
|
|
796
450
|
const { model, context, apiKey, options } = params;
|
|
797
|
-
const needsInterleavedBeta = (options?.interleavedThinking ?? true) && !
|
|
451
|
+
const needsInterleavedBeta = (options?.interleavedThinking ?? true) && !supportsClaudeAdaptiveThinking(model);
|
|
798
452
|
const fetch = isKimiAnthropicProvider(model.provider) && options?.thinkingEnabled === true ? buildGuardedModelFetch(model, void 0, { sanitizeSse: false }) : buildGuardedModelFetch(model);
|
|
799
453
|
if (model.provider === "github-copilot") {
|
|
800
454
|
const betaFeatures = needsInterleavedBeta ? ["interleaved-thinking-2025-05-14"] : [];
|
|
@@ -843,7 +497,7 @@ function createAnthropicTransportClient(params) {
|
|
|
843
497
|
accept: "application/json",
|
|
844
498
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
845
499
|
...betaHeader ? { "anthropic-beta": betaHeader } : {},
|
|
846
|
-
"user-agent": `claude-cli/${
|
|
500
|
+
"user-agent": `claude-cli/${ANTHROPIC_CLAUDE_CODE_VERSION}`,
|
|
847
501
|
"x-app": "cli"
|
|
848
502
|
}, model.headers, options?.headers),
|
|
849
503
|
fetch
|
|
@@ -884,9 +538,15 @@ async function buildAnthropicParams(model, context, isOAuthToken, options) {
|
|
|
884
538
|
enableCacheControl: true
|
|
885
539
|
});
|
|
886
540
|
const cacheBreakpointOptOutMessageIndexes = /* @__PURE__ */ new Set();
|
|
887
|
-
const
|
|
541
|
+
const replayPlan = buildAnthropicReplayPlan(context.messages, model, {
|
|
542
|
+
enabled: !isOAuthToken && options?.anthropicServerCompaction === true,
|
|
543
|
+
authProfileId: options?.authProfileId,
|
|
544
|
+
sessionId: options?.sessionId
|
|
545
|
+
});
|
|
546
|
+
const messages = await convertAnthropicMessages(replayPlan.messages, model, isOAuthToken, {
|
|
888
547
|
allowReasoningContentReplay: supportsReasoningContentReplay(model),
|
|
889
548
|
cacheBreakpointOptOutMessageIndexes,
|
|
549
|
+
compaction: replayPlan.compaction,
|
|
890
550
|
replayThinkingEnabled
|
|
891
551
|
});
|
|
892
552
|
const params = {
|
|
@@ -899,7 +559,7 @@ async function buildAnthropicParams(model, context, isOAuthToken, options) {
|
|
|
899
559
|
if (isOAuthToken) params.system = [
|
|
900
560
|
{
|
|
901
561
|
type: "text",
|
|
902
|
-
text:
|
|
562
|
+
text: ANTHROPIC_CLAUDE_CODE_BILLING_SYSTEM_BLOCK
|
|
903
563
|
},
|
|
904
564
|
{
|
|
905
565
|
type: "text",
|
|
@@ -914,7 +574,7 @@ async function buildAnthropicParams(model, context, isOAuthToken, options) {
|
|
|
914
574
|
type: "text",
|
|
915
575
|
text: sanitizeTransportPayloadText(context.systemPrompt)
|
|
916
576
|
}];
|
|
917
|
-
if (options?.temperature !== void 0 && !options.thinkingEnabled && !
|
|
577
|
+
if (options?.temperature !== void 0 && !options.thinkingEnabled && !supportsClaudeNativeXhighEffort(model)) params.temperature = options.temperature;
|
|
918
578
|
if (options?.stop !== void 0 && options.stop.length > 0) params.stop_sequences = options.stop;
|
|
919
579
|
let toolProjection;
|
|
920
580
|
if (context.tools) {
|
|
@@ -922,8 +582,8 @@ async function buildAnthropicParams(model, context, isOAuthToken, options) {
|
|
|
922
582
|
toolProjection = convertedTools.projection;
|
|
923
583
|
if (convertedTools.tools.length > 0) params.tools = convertedTools.tools;
|
|
924
584
|
}
|
|
925
|
-
if (mandatoryAdaptiveThinking || model.reasoning ||
|
|
926
|
-
if (mandatoryAdaptiveThinking || options?.thinkingEnabled) if (
|
|
585
|
+
if (mandatoryAdaptiveThinking || model.reasoning || supportsClaudeAdaptiveThinking(model)) {
|
|
586
|
+
if (mandatoryAdaptiveThinking || options?.thinkingEnabled) if (supportsClaudeAdaptiveThinking(model)) {
|
|
927
587
|
params.thinking = {
|
|
928
588
|
type: "adaptive",
|
|
929
589
|
display: options?.thinkingDisplay ?? "summarized"
|
|
@@ -945,7 +605,8 @@ async function buildAnthropicParams(model, context, isOAuthToken, options) {
|
|
|
945
605
|
applyAnthropicPayloadPolicyToParams(params, payloadPolicy, cacheBreakpointOptOutMessageIndexes);
|
|
946
606
|
return {
|
|
947
607
|
params,
|
|
948
|
-
toolProjection
|
|
608
|
+
toolProjection,
|
|
609
|
+
usedCompactionReplay: replayPlan.compaction !== void 0
|
|
949
610
|
};
|
|
950
611
|
}
|
|
951
612
|
function resolveAnthropicTransportOptions(model, options, apiKey) {
|
|
@@ -974,7 +635,9 @@ function resolveAnthropicTransportOptions(model, options, apiKey) {
|
|
|
974
635
|
interleavedThinking: options?.interleavedThinking,
|
|
975
636
|
toolChoice: options?.toolChoice,
|
|
976
637
|
thinkingBudgets: options?.thinkingBudgets,
|
|
977
|
-
reasoning
|
|
638
|
+
reasoning,
|
|
639
|
+
...options?.anthropicServerCompaction === true ? { anthropicServerCompaction: true } : {},
|
|
640
|
+
...options?.authProfileId ? { authProfileId: options.authProfileId } : {}
|
|
978
641
|
};
|
|
979
642
|
if (reasoning === "off") {
|
|
980
643
|
resolved.thinkingEnabled = false;
|
|
@@ -985,17 +648,12 @@ function resolveAnthropicTransportOptions(model, options, apiKey) {
|
|
|
985
648
|
if (resolved.thinkingEnabled) resolved.effort = "high";
|
|
986
649
|
return resolved;
|
|
987
650
|
}
|
|
988
|
-
if (
|
|
651
|
+
if (supportsClaudeAdaptiveThinking(model)) {
|
|
989
652
|
resolved.thinkingEnabled = true;
|
|
990
|
-
resolved.effort =
|
|
653
|
+
resolved.effort = resolveAnthropicThinkingEffort(model, reasoning);
|
|
991
654
|
return resolved;
|
|
992
655
|
}
|
|
993
|
-
const adjusted = adjustMaxTokensForThinking(
|
|
994
|
-
baseMaxTokens,
|
|
995
|
-
modelMaxTokens: reasoningModelMaxTokens,
|
|
996
|
-
reasoningLevel: reasoning,
|
|
997
|
-
customBudgets: options?.thinkingBudgets
|
|
998
|
-
});
|
|
656
|
+
const adjusted = adjustMaxTokensForThinking(baseMaxTokens, reasoningModelMaxTokens, reasoning === "max" ? "high" : reasoning, options?.thinkingBudgets);
|
|
999
657
|
const thinkingEnabled = adjusted.thinkingBudget >= 1024;
|
|
1000
658
|
resolved.maxTokens = adjusted.maxTokens;
|
|
1001
659
|
resolved.thinkingEnabled = thinkingEnabled;
|
|
@@ -1023,6 +681,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1023
681
|
const eventSink = refusalBuffer ?? stream;
|
|
1024
682
|
let costModel = model;
|
|
1025
683
|
let messageStartPromptUsage;
|
|
684
|
+
let usedCompactionReplay = false;
|
|
1026
685
|
try {
|
|
1027
686
|
const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
|
1028
687
|
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
@@ -1035,6 +694,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1035
694
|
options: transportOptions
|
|
1036
695
|
});
|
|
1037
696
|
const builtParams = await buildAnthropicParams(model, requestContext, isOAuthToken, transportOptions);
|
|
697
|
+
usedCompactionReplay = builtParams.usedCompactionReplay;
|
|
1038
698
|
let params = builtParams.params;
|
|
1039
699
|
const toolProjection = builtParams.toolProjection;
|
|
1040
700
|
const nextParams = await transportOptions.onPayload?.(params, model);
|
|
@@ -1046,6 +706,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1046
706
|
}, transportOptions.signal ? { signal: transportOptions.signal } : void 0);
|
|
1047
707
|
const blocks = output.content;
|
|
1048
708
|
const blockIndexes = /* @__PURE__ */ new Map();
|
|
709
|
+
const compactionCapture = createCompactionCapture(output, model, transportOptions);
|
|
1049
710
|
const pendingThinkingSignatures = /* @__PURE__ */ new Map();
|
|
1050
711
|
const allowReasoningContentReplay = supportsReasoningContentReplay(model);
|
|
1051
712
|
const reasoningContentThinkingBlocks = /* @__PURE__ */ new Map();
|
|
@@ -1153,23 +814,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1153
814
|
const usage = message?.usage ?? {};
|
|
1154
815
|
output.responseId = typeof message?.id === "string" ? message.id : void 0;
|
|
1155
816
|
output.responseModel = typeof message?.model === "string" ? message.model : void 0;
|
|
1156
|
-
|
|
1157
|
-
const messageStartPromptTokens = promptUsage ? promptUsage.input + promptUsage.cacheRead + promptUsage.cacheWrite : 0;
|
|
1158
|
-
messageStartPromptUsage = messageStartPromptTokens > 0 ? promptUsage : void 0;
|
|
1159
|
-
const inputTokens = readAnthropicUsageTokenCount(usage.input_tokens);
|
|
1160
|
-
if (inputTokens !== void 0) output.usage.input = inputTokens;
|
|
1161
|
-
const outputTokens = readAnthropicUsageTokenCount(usage.output_tokens);
|
|
1162
|
-
if (outputTokens !== void 0) output.usage.output = outputTokens;
|
|
1163
|
-
const cacheReadTokens = usage.cache_read_input_tokens == null ? 0 : readAnthropicUsageTokenCount(usage.cache_read_input_tokens);
|
|
1164
|
-
if (cacheReadTokens !== void 0) output.usage.cacheRead = cacheReadTokens;
|
|
1165
|
-
const cacheWriteTokens = usage.cache_creation_input_tokens == null ? 0 : readAnthropicUsageTokenCount(usage.cache_creation_input_tokens);
|
|
1166
|
-
if (cacheWriteTokens !== void 0) output.usage.cacheWrite = cacheWriteTokens;
|
|
1167
|
-
output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
1168
|
-
if (messageStartPromptUsage && outputTokens !== void 0) output.usage.contextUsage = {
|
|
1169
|
-
state: "available",
|
|
1170
|
-
promptTokens: messageStartPromptTokens,
|
|
1171
|
-
totalTokens: messageStartPromptTokens + output.usage.output
|
|
1172
|
-
};
|
|
817
|
+
messageStartPromptUsage = applyAnthropicMessageStartUsage(output.usage, usage);
|
|
1173
818
|
calculateCost(costModel, output.usage);
|
|
1174
819
|
eventSink.push({
|
|
1175
820
|
type: "start",
|
|
@@ -1184,6 +829,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1184
829
|
if (event.type === "content_block_start") {
|
|
1185
830
|
const contentBlock = event.content_block;
|
|
1186
831
|
const index = typeof event.index === "number" ? event.index : -1;
|
|
832
|
+
if (transportOptions.anthropicServerCompaction === true && compactionCapture.begin(index, contentBlock, output.content.length)) continue;
|
|
1187
833
|
const fallbackBoundary = refusalBuffer ? readAnthropicFallbackBoundary(contentBlock) : null;
|
|
1188
834
|
if (fallbackBoundary) {
|
|
1189
835
|
refusalBuffer?.discard();
|
|
@@ -1320,6 +966,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1320
966
|
if (event.type === "content_block_delta") {
|
|
1321
967
|
const delta = event.delta;
|
|
1322
968
|
const eventIndex = typeof event.index === "number" ? event.index : void 0;
|
|
969
|
+
if (eventIndex !== void 0 && compactionCapture.delta(eventIndex, delta)) continue;
|
|
1323
970
|
let index = eventIndex === void 0 ? void 0 : blockIndexes.get(eventIndex);
|
|
1324
971
|
let block = index === void 0 ? void 0 : blocks[index];
|
|
1325
972
|
if (allowReasoningContentReplay) {
|
|
@@ -1400,6 +1047,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1400
1047
|
}
|
|
1401
1048
|
if (event.type === "content_block_stop") {
|
|
1402
1049
|
const eventIndex = typeof event.index === "number" ? event.index : void 0;
|
|
1050
|
+
if (eventIndex !== void 0 && compactionCapture.complete(eventIndex)) continue;
|
|
1403
1051
|
const pendingSignature = eventIndex === void 0 ? void 0 : pendingThinkingSignatures.get(eventIndex);
|
|
1404
1052
|
if (eventIndex !== void 0) pendingThinkingSignatures.delete(eventIndex);
|
|
1405
1053
|
const index = eventIndex === void 0 ? void 0 : blockIndexes.get(eventIndex);
|
|
@@ -1447,37 +1095,14 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1447
1095
|
const delta = event.delta;
|
|
1448
1096
|
const usage = event.usage;
|
|
1449
1097
|
if (delta?.stop_reason) if (delta.stop_reason === "refusal") applyAnthropicRefusal(output, delta.stop_details, model.provider);
|
|
1450
|
-
else output.stopReason =
|
|
1451
|
-
|
|
1452
|
-
if (inputTokens !== void 0) output.usage.input = inputTokens;
|
|
1453
|
-
const outputTokens = readAnthropicUsageTokenCount(usage?.output_tokens);
|
|
1454
|
-
if (outputTokens !== void 0) output.usage.output = outputTokens;
|
|
1455
|
-
const cacheReadTokens = readAnthropicUsageTokenCount(usage?.cache_read_input_tokens);
|
|
1456
|
-
if (cacheReadTokens !== void 0) output.usage.cacheRead = cacheReadTokens;
|
|
1457
|
-
const cacheWriteTokens = readAnthropicUsageTokenCount(usage?.cache_creation_input_tokens);
|
|
1458
|
-
if (cacheWriteTokens !== void 0) output.usage.cacheWrite = cacheWriteTokens;
|
|
1459
|
-
output.usage.totalTokens = output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
1460
|
-
const iterationUsage = readLastAnthropicIterationUsage(usage ?? {});
|
|
1461
|
-
if (iterationUsage.state === "valid") output.usage.contextUsage = {
|
|
1462
|
-
state: "available",
|
|
1463
|
-
promptTokens: iterationUsage.usage.contextPromptTokens,
|
|
1464
|
-
totalTokens: iterationUsage.usage.totalTokens
|
|
1465
|
-
};
|
|
1466
|
-
else if (iterationUsage.state === "invalid") output.usage.contextUsage = { state: "unavailable" };
|
|
1467
|
-
else if (outputTokens !== void 0 && (messageStartPromptUsage !== void 0 || inputTokens !== void 0 && cacheReadTokens !== void 0 && cacheWriteTokens !== void 0)) {
|
|
1468
|
-
const promptTokens = output.usage.input + output.usage.cacheRead + output.usage.cacheWrite;
|
|
1469
|
-
output.usage.contextUsage = {
|
|
1470
|
-
state: "available",
|
|
1471
|
-
promptTokens,
|
|
1472
|
-
totalTokens: promptTokens + output.usage.output
|
|
1473
|
-
};
|
|
1474
|
-
} else output.usage.contextUsage = { state: "unavailable" };
|
|
1098
|
+
else output.stopReason = mapAnthropicStopReason(delta.stop_reason);
|
|
1099
|
+
applyAnthropicMessageDeltaUsage(output.usage, usage, messageStartPromptUsage);
|
|
1475
1100
|
calculateCost(costModel, output.usage);
|
|
1476
1101
|
if (output.stopReason === "toolUse" || output.content.some((block) => block.type === "toolCall")) tagPendingCommentaryText(output.content);
|
|
1477
1102
|
flushPendingTextEnds();
|
|
1478
1103
|
}
|
|
1479
1104
|
}
|
|
1480
|
-
if (
|
|
1105
|
+
if (isDirectAnthropicModel(model) && !sawMessageStop) throw new Error("Anthropic stream ended before message_stop");
|
|
1481
1106
|
if (transportOptions.signal?.aborted) throw transportAbortError(transportOptions.signal);
|
|
1482
1107
|
if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error(output.errorMessage ?? "An unknown error occurred");
|
|
1483
1108
|
refusalBuffer?.flush();
|
|
@@ -1492,6 +1117,7 @@ function createAnthropicMessagesTransportStreamFn() {
|
|
|
1492
1117
|
refusalBuffer.discard();
|
|
1493
1118
|
output.content = [];
|
|
1494
1119
|
}
|
|
1120
|
+
if (usedCompactionReplay && isAnthropicReplayRejection(error)) suppressAnthropicCompaction(output, model, options);
|
|
1495
1121
|
failTransportStream({
|
|
1496
1122
|
stream,
|
|
1497
1123
|
output,
|
|
@@ -1608,15 +1234,11 @@ const MAX_TOKENS_PARAM_KEYS = [
|
|
|
1608
1234
|
"max_completion_tokens",
|
|
1609
1235
|
"max_tokens"
|
|
1610
1236
|
];
|
|
1611
|
-
/** Return a finite non-negative max-token value, or undefined for invalid input. */
|
|
1612
|
-
function resolveNonNegativeMaxTokensParam(value) {
|
|
1613
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1614
|
-
}
|
|
1615
1237
|
/** Resolve the first supported max-token parameter present in a params object. */
|
|
1616
1238
|
function resolveMaxTokensParam(params) {
|
|
1617
1239
|
if (!params) return;
|
|
1618
1240
|
for (const key of MAX_TOKENS_PARAM_KEYS) {
|
|
1619
|
-
const resolved =
|
|
1241
|
+
const resolved = asNonNegativeFiniteNumber(params[key]);
|
|
1620
1242
|
if (resolved !== void 0) return resolved;
|
|
1621
1243
|
}
|
|
1622
1244
|
}
|
|
@@ -1733,100 +1355,279 @@ function stripCompletionMessagesToRoleContent(messages) {
|
|
|
1733
1355
|
});
|
|
1734
1356
|
}
|
|
1735
1357
|
//#endregion
|
|
1736
|
-
//#region packages/ai/src/transports/openai-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
return;
|
|
1745
|
-
}
|
|
1358
|
+
//#region packages/ai/src/transports/openai-completions-host.ts
|
|
1359
|
+
/**
|
|
1360
|
+
* Chat Completions accepts Azure AI Foundry hosts in addition to traditional
|
|
1361
|
+
* Azure OpenAI hosts. Do not replace this with isTraditionalAzureOpenAIHost,
|
|
1362
|
+
* which intentionally excludes the .services.ai.azure.com Foundry suffix.
|
|
1363
|
+
*/
|
|
1364
|
+
function isAzureOpenAICompatibleHost(hostname) {
|
|
1365
|
+
return hostname.endsWith(".openai.azure.com") || hostname.endsWith(".services.ai.azure.com") || hostname.endsWith(".cognitiveservices.azure.com");
|
|
1746
1366
|
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
if (!isRecord(fn)) return;
|
|
1753
|
-
const fnName = readToolPayloadField(fn, "name");
|
|
1754
|
-
return typeof fnName === "string" ? fnName : void 0;
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region packages/ai/src/transports/openai-completions-replay.ts
|
|
1369
|
+
function isGoogleOpenAICompatModel(model) {
|
|
1370
|
+
const endpointClass = detectOpenAICompletionsCompat(model).capabilities.endpointClass;
|
|
1371
|
+
return model.provider === "google" || endpointClass === "google-generative-ai" || endpointClass === "google-vertex";
|
|
1755
1372
|
}
|
|
1756
|
-
function
|
|
1757
|
-
|
|
1758
|
-
const tools = readToolPayloadField(payload, "tools");
|
|
1759
|
-
if (!Array.isArray(tools)) return;
|
|
1760
|
-
payload.tools = tools.flatMap((tool) => {
|
|
1761
|
-
const name = readCodeModePayloadToolName(tool);
|
|
1762
|
-
if (typeof name === "string" && isCodeModeModelVisibleToolName(name, visibleToolNames)) return [tool];
|
|
1763
|
-
if (!isRecord(tool)) return [];
|
|
1764
|
-
const filteredGroups = {};
|
|
1765
|
-
for (const key of ["functionDeclarations", "function_declarations"]) {
|
|
1766
|
-
const declarations = readToolPayloadField(tool, key);
|
|
1767
|
-
if (!Array.isArray(declarations)) continue;
|
|
1768
|
-
const filtered = declarations.filter((declaration) => {
|
|
1769
|
-
const declarationName = readCodeModePayloadToolName(declaration);
|
|
1770
|
-
return typeof declarationName === "string" && isCodeModeModelVisibleToolName(declarationName, visibleToolNames);
|
|
1771
|
-
});
|
|
1772
|
-
if (filtered.length > 0) filteredGroups[key] = filtered;
|
|
1773
|
-
}
|
|
1774
|
-
return Object.keys(filteredGroups).length > 0 ? [filteredGroups] : [];
|
|
1775
|
-
});
|
|
1373
|
+
function requiresGoogleCompatToolCallThoughtSignature(model) {
|
|
1374
|
+
return isGoogleGemini3ProModel(model.id) || isGoogleGemini3FlashModel(model.id);
|
|
1776
1375
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1376
|
+
const GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE = /[\u2026]|\.\.\./;
|
|
1377
|
+
const GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE = /^[A-Za-z0-9+/=]+$/;
|
|
1378
|
+
function hasGoogleCompatThoughtSignatureTruncationFootprint(value) {
|
|
1379
|
+
return GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE.test(value) || GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE.test(value) && value.length % 4 !== 0;
|
|
1779
1380
|
}
|
|
1780
|
-
function
|
|
1781
|
-
if (!
|
|
1782
|
-
const
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1381
|
+
function injectToolCallThoughtSignatures(outgoingMessages, context, model) {
|
|
1382
|
+
if (!isGoogleOpenAICompatModel(model)) return;
|
|
1383
|
+
const sigById = /* @__PURE__ */ new Map();
|
|
1384
|
+
const fallbackSig = requiresGoogleCompatToolCallThoughtSignature(model) ? GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP : void 0;
|
|
1385
|
+
for (const msg of context.messages ?? []) {
|
|
1386
|
+
if (msg.role !== "assistant") continue;
|
|
1387
|
+
const source = msg;
|
|
1388
|
+
if (!Array.isArray(source.content)) continue;
|
|
1389
|
+
for (const block of source.content) {
|
|
1390
|
+
if (block.type !== "toolCall") continue;
|
|
1391
|
+
const id = block.id;
|
|
1392
|
+
const sig = block.thoughtSignature;
|
|
1393
|
+
if (typeof id === "string" && typeof sig === "string" && sig.length > 0) {
|
|
1394
|
+
const isSameRoute = source.api === model.api && source.provider === model.provider && source.model === model.id;
|
|
1395
|
+
if (!isSameRoute && !fallbackSig) continue;
|
|
1396
|
+
sigById.set(id, isSameRoute ? sig : fallbackSig ?? sig);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
if (sigById.size === 0 && !fallbackSig) return;
|
|
1401
|
+
for (const message of outgoingMessages) {
|
|
1402
|
+
const toolCalls = message.tool_calls;
|
|
1403
|
+
if (!Array.isArray(toolCalls)) continue;
|
|
1404
|
+
for (const toolCall of toolCalls) {
|
|
1405
|
+
const id = toolCall.id;
|
|
1406
|
+
if (typeof id !== "string") continue;
|
|
1407
|
+
let sig = sigById.get(id) ?? fallbackSig;
|
|
1408
|
+
if (typeof sig === "string" && sig.length > 0) {
|
|
1409
|
+
if (hasGoogleCompatThoughtSignatureTruncationFootprint(sig.trim())) sig = fallbackSig;
|
|
1410
|
+
}
|
|
1411
|
+
if (typeof sig !== "string" || sig.length === 0) continue;
|
|
1412
|
+
const extra = toolCall.extra_content && typeof toolCall.extra_content === "object" ? toolCall.extra_content : {};
|
|
1413
|
+
toolCall.extra_content = extra;
|
|
1414
|
+
const google = extra.google && typeof extra.google === "object" ? extra.google : {};
|
|
1415
|
+
extra.google = google;
|
|
1416
|
+
google.thought_signature = sig;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1788
1419
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1420
|
+
const COMPLETIONS_REASONING_REPLAY_FIELDS = [
|
|
1421
|
+
"reasoning_details",
|
|
1422
|
+
"reasoning_content",
|
|
1423
|
+
"reasoning",
|
|
1424
|
+
"reasoning_text"
|
|
1425
|
+
];
|
|
1426
|
+
function stripCompletionsReasoningReplayFields(record) {
|
|
1427
|
+
for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) if (field in record) delete record[field];
|
|
1795
1428
|
}
|
|
1796
|
-
function
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1429
|
+
function sanitizeOpenRouterReasoningReplayFields(record) {
|
|
1430
|
+
const reasoningDetails = record.reasoning_details;
|
|
1431
|
+
if (typeof reasoningDetails === "string") {
|
|
1432
|
+
if (reasoningDetails.length > 0 && typeof record.reasoning !== "string") record.reasoning = reasoningDetails;
|
|
1433
|
+
delete record.reasoning_details;
|
|
1434
|
+
} else if (reasoningDetails !== void 0 && !Array.isArray(reasoningDetails)) delete record.reasoning_details;
|
|
1435
|
+
if ("reasoning" in record && (typeof record.reasoning !== "string" || record.reasoning === "")) delete record.reasoning;
|
|
1436
|
+
if ("reasoning_content" in record && (typeof record.reasoning_content !== "string" || record.reasoning_content === "")) delete record.reasoning_content;
|
|
1437
|
+
const reasoningText = record.reasoning_text;
|
|
1438
|
+
if (typeof reasoningText === "string" && reasoningText.length > 0 && typeof record.reasoning !== "string" && typeof record.reasoning_content !== "string") record.reasoning = reasoningText;
|
|
1439
|
+
if ("reasoning_text" in record) delete record.reasoning_text;
|
|
1807
1440
|
}
|
|
1808
|
-
function
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
return true;
|
|
1441
|
+
function sanitizeReasoningContentReplayFields(record) {
|
|
1442
|
+
if ("reasoning_content" in record && typeof record.reasoning_content !== "string") delete record.reasoning_content;
|
|
1443
|
+
delete record.reasoning_details;
|
|
1444
|
+
delete record.reasoning;
|
|
1445
|
+
delete record.reasoning_text;
|
|
1814
1446
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1447
|
+
const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
1448
|
+
"deepseek-v4-flash",
|
|
1449
|
+
"deepseek-v4-pro",
|
|
1450
|
+
"kimi-for-coding",
|
|
1451
|
+
"kimi-k2.5",
|
|
1452
|
+
"kimi-k2.6",
|
|
1453
|
+
"kimi-k2.7-code",
|
|
1454
|
+
"kimi-k2.7-code-highspeed",
|
|
1455
|
+
"kimi-k3",
|
|
1456
|
+
"kimi-k2-thinking",
|
|
1457
|
+
"kimi-k2-thinking-turbo",
|
|
1458
|
+
"mimo-v2-pro",
|
|
1459
|
+
"mimo-v2-omni",
|
|
1460
|
+
"mimo-v2.5",
|
|
1461
|
+
"mimo-v2.5-pro",
|
|
1462
|
+
"mimo-v2.6-pro"
|
|
1463
|
+
]);
|
|
1464
|
+
const REASONING_CONTENT_REPLAY_TIER_SUFFIXES = [
|
|
1465
|
+
"-free",
|
|
1466
|
+
"-paid",
|
|
1467
|
+
"-trial"
|
|
1468
|
+
];
|
|
1469
|
+
function stripReasoningContentReplayTierSuffix(modelId) {
|
|
1470
|
+
for (const suffix of REASONING_CONTENT_REPLAY_TIER_SUFFIXES) if (modelId.length > suffix.length && modelId.endsWith(suffix)) return modelId.slice(0, -suffix.length);
|
|
1471
|
+
return modelId;
|
|
1472
|
+
}
|
|
1473
|
+
function getReasoningContentReplayModelIdCandidates(modelId) {
|
|
1474
|
+
if (typeof modelId !== "string") return [];
|
|
1475
|
+
const normalized = modelId.trim().toLowerCase();
|
|
1476
|
+
if (!normalized) return [];
|
|
1477
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
1478
|
+
const finalPart = parts[parts.length - 1] ?? normalized;
|
|
1479
|
+
const candidates = [finalPart];
|
|
1480
|
+
const colonParts = finalPart.split(":").filter(Boolean);
|
|
1481
|
+
if (colonParts.length > 1) candidates.push(colonParts[0] ?? "", colonParts[colonParts.length - 1] ?? "");
|
|
1482
|
+
const baseCount = candidates.length;
|
|
1483
|
+
for (let index = 0; index < baseCount; index += 1) {
|
|
1484
|
+
const candidate = candidates[index];
|
|
1485
|
+
if (typeof candidate !== "string") continue;
|
|
1486
|
+
const stripped = stripReasoningContentReplayTierSuffix(candidate);
|
|
1487
|
+
if (stripped !== candidate) candidates.push(stripped);
|
|
1488
|
+
}
|
|
1489
|
+
return uniqueStrings(candidates.filter(Boolean));
|
|
1490
|
+
}
|
|
1491
|
+
function shouldPreserveReasoningContentReplay(model, compat) {
|
|
1492
|
+
if (compat.requiresReasoningContentOnAssistantMessages || compat.thinkingFormat === "deepseek" || compat.thinkingFormat === "zai" || shouldTrustReasoningContentReplayMetadata(model)) return true;
|
|
1493
|
+
return getReasoningContentReplayModelIdCandidates(model.id).some((modelId) => REASONING_CONTENT_REPLAY_MODEL_IDS.has(modelId));
|
|
1494
|
+
}
|
|
1495
|
+
function shouldPreserveOpenRouterReasoningReplay(model) {
|
|
1496
|
+
if (model.provider !== "openrouter") return true;
|
|
1497
|
+
const normalizedModelId = model.id.trim().toLowerCase();
|
|
1498
|
+
return !(normalizedModelId.startsWith("anthropic/") || normalizedModelId.startsWith("x-ai/"));
|
|
1499
|
+
}
|
|
1500
|
+
function shouldTrustReasoningContentReplayMetadata(model) {
|
|
1501
|
+
if (!model.reasoning) return false;
|
|
1502
|
+
if (model.provider.trim().toLowerCase() === "openai") return false;
|
|
1503
|
+
return shouldPreserveOpenRouterReasoningReplay(model);
|
|
1504
|
+
}
|
|
1505
|
+
function sanitizeCompletionsReasoningReplayFields(messages, options) {
|
|
1506
|
+
if (!Array.isArray(messages)) return;
|
|
1507
|
+
for (const msg of messages) {
|
|
1508
|
+
if (!msg || typeof msg !== "object") continue;
|
|
1509
|
+
const record = msg;
|
|
1510
|
+
if (record.role !== "assistant") continue;
|
|
1511
|
+
if (options.preserveOpenRouterReasoning) sanitizeOpenRouterReasoningReplayFields(record);
|
|
1512
|
+
else if (options.preserveReasoningContent) sanitizeReasoningContentReplayFields(record);
|
|
1513
|
+
else stripCompletionsReasoningReplayFields(record);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function applyCompletionsReplay(outgoingMessages, context, model, compat) {
|
|
1517
|
+
injectToolCallThoughtSignatures(outgoingMessages, context, model);
|
|
1518
|
+
sanitizeCompletionsReasoningReplayFields(outgoingMessages, {
|
|
1519
|
+
preserveOpenRouterReasoning: compat.thinkingFormat === "openrouter" && shouldPreserveOpenRouterReasoningReplay(model),
|
|
1520
|
+
preserveReasoningContent: shouldPreserveReasoningContentReplay(model, compat)
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
//#endregion
|
|
1524
|
+
//#region packages/ai/src/transports/openai-transport-params.ts
|
|
1525
|
+
const MAX_OPENAI_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS = 256;
|
|
1526
|
+
const OPENAI_CODEX_RESPONSES_PROVIDERS = /* @__PURE__ */ new Set(["openai"]);
|
|
1527
|
+
const loggedOpenAIStrictToolDowngradeDiagnosticKeys = /* @__PURE__ */ new Set();
|
|
1528
|
+
function readToolPayloadField(record, field) {
|
|
1529
|
+
try {
|
|
1530
|
+
return Object.hasOwn(record, field) ? record[field] : void 0;
|
|
1531
|
+
} catch {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function readCodeModePayloadToolName(tool) {
|
|
1536
|
+
if (!isRecord(tool)) return;
|
|
1537
|
+
const name = readToolPayloadField(tool, "name");
|
|
1538
|
+
if (typeof name === "string") return name;
|
|
1539
|
+
const fn = readToolPayloadField(tool, "function");
|
|
1540
|
+
if (!isRecord(fn)) return;
|
|
1541
|
+
const fnName = readToolPayloadField(fn, "name");
|
|
1542
|
+
return typeof fnName === "string" ? fnName : void 0;
|
|
1543
|
+
}
|
|
1544
|
+
function readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes) {
|
|
1545
|
+
if (!isRecord(tool)) return;
|
|
1546
|
+
const type = readToolPayloadField(tool, "type");
|
|
1547
|
+
if (typeof type === "string" && allowedHostedToolTypes?.has(type)) {
|
|
1548
|
+
try {
|
|
1549
|
+
if (Object.hasOwn(tool, "name") || Object.hasOwn(tool, "function") || Object.hasOwn(tool, "functionDeclarations") || Object.hasOwn(tool, "function_declarations")) return false;
|
|
1550
|
+
} catch {
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
return `hosted:${type}`;
|
|
1554
|
+
}
|
|
1555
|
+
const name = readCodeModePayloadToolName(tool);
|
|
1556
|
+
return typeof name === "string" && isCodeModeModelVisibleToolName(name, visibleToolNames) ? `client:${name}` : void 0;
|
|
1557
|
+
}
|
|
1558
|
+
function filterCodeModePayloadTools(payload, visibleToolNames, allowedHostedToolTypes) {
|
|
1559
|
+
if (!isRecord(payload)) return;
|
|
1560
|
+
const tools = readToolPayloadField(payload, "tools");
|
|
1561
|
+
if (!Array.isArray(tools)) return;
|
|
1562
|
+
payload.tools = tools.flatMap((tool) => {
|
|
1563
|
+
const identity = readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes);
|
|
1564
|
+
if (identity) return [tool];
|
|
1565
|
+
if (identity === false) return [];
|
|
1566
|
+
if (!isRecord(tool)) return [];
|
|
1567
|
+
const filteredGroups = {};
|
|
1568
|
+
for (const key of ["functionDeclarations", "function_declarations"]) {
|
|
1569
|
+
const declarations = readToolPayloadField(tool, key);
|
|
1570
|
+
if (!Array.isArray(declarations)) continue;
|
|
1571
|
+
const filtered = declarations.filter((declaration) => {
|
|
1572
|
+
const declarationName = readCodeModePayloadToolName(declaration);
|
|
1573
|
+
return typeof declarationName === "string" && isCodeModeModelVisibleToolName(declarationName, visibleToolNames);
|
|
1574
|
+
});
|
|
1575
|
+
if (filtered.length > 0) filteredGroups[key] = filtered;
|
|
1576
|
+
}
|
|
1577
|
+
return Object.keys(filteredGroups).length > 0 ? [filteredGroups] : [];
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
function resolveCodeModeResponsesVisibleToolNames(context) {
|
|
1581
|
+
return new Set((context.tools ?? []).map(readCodeModePayloadToolName).filter((name) => typeof name === "string"));
|
|
1582
|
+
}
|
|
1583
|
+
function enforceCodeModeResponsesToolSurface(payload, visibleToolNames, allowedHostedToolTypes) {
|
|
1584
|
+
if (!isRecord(payload)) return;
|
|
1585
|
+
const tools = readToolPayloadField(payload, "tools");
|
|
1586
|
+
if (!Array.isArray(tools)) return;
|
|
1587
|
+
payload.tools = tools.filter((tool) => Boolean(readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes)));
|
|
1588
|
+
}
|
|
1589
|
+
function assertCodeModeResponsesToolSurface(payload, visibleToolNames, allowedHostedToolTypes) {
|
|
1590
|
+
const tools = isRecord(payload) ? readToolPayloadField(payload, "tools") : void 0;
|
|
1591
|
+
if (!Array.isArray(tools)) throw new Error("Code mode payload tool surface violation: expected exec,wait; got no tools");
|
|
1592
|
+
const identities = tools.map((tool) => readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes));
|
|
1593
|
+
const names = identities.flatMap((identity) => typeof identity === "string" && identity.startsWith("client:") ? [identity.slice(7)] : []).toSorted((left, right) => left.localeCompare(right));
|
|
1594
|
+
if (names.length >= 2 && identities.every((identity) => typeof identity === "string") && new Set(identities).size === identities.length && names.includes("exec") && names.includes("wait")) return;
|
|
1595
|
+
throw new Error(`Code mode payload tool surface violation: expected exec,wait plus direct-only tools; got ${names.length > 0 ? names.join(",") : "none"}`);
|
|
1596
|
+
}
|
|
1597
|
+
function buildOpenAIStrictToolDowngradeDiagnosticKey(diagnostics, context) {
|
|
1598
|
+
return sha256Hex(JSON.stringify({
|
|
1599
|
+
transport: context.transport,
|
|
1600
|
+
provider: context.model.provider ?? null,
|
|
1601
|
+
model: context.model.id ?? null,
|
|
1602
|
+
diagnostics: diagnostics.map((entry) => ({
|
|
1603
|
+
toolIndex: entry.toolIndex,
|
|
1604
|
+
toolName: entry.toolName ?? null,
|
|
1605
|
+
violations: entry.violations
|
|
1606
|
+
}))
|
|
1607
|
+
}));
|
|
1608
|
+
}
|
|
1609
|
+
function shouldLogOpenAIStrictToolDowngradeDiagnostic(diagnostics, context) {
|
|
1610
|
+
const key = buildOpenAIStrictToolDowngradeDiagnosticKey(diagnostics, context);
|
|
1611
|
+
if (loggedOpenAIStrictToolDowngradeDiagnosticKeys.has(key)) return false;
|
|
1612
|
+
if (loggedOpenAIStrictToolDowngradeDiagnosticKeys.size >= MAX_OPENAI_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS) loggedOpenAIStrictToolDowngradeDiagnosticKeys.clear();
|
|
1613
|
+
loggedOpenAIStrictToolDowngradeDiagnosticKeys.add(key);
|
|
1614
|
+
return true;
|
|
1615
|
+
}
|
|
1616
|
+
function resolveOpenAIStrictToolFlagWithDiagnostics(projection, strictSetting, context) {
|
|
1617
|
+
const strict = resolveOpenAIProjectedToolsStrictToolFlag(projection, strictSetting);
|
|
1618
|
+
if (strictSetting === true && strict === false) {
|
|
1619
|
+
const diagnostics = findOpenAIStrictToolProjectionDiagnostics(projection);
|
|
1620
|
+
getAiTransportHost().logDebug("openai-transport", () => {
|
|
1621
|
+
if (!shouldLogOpenAIStrictToolDowngradeDiagnostic(diagnostics, context)) return null;
|
|
1622
|
+
const sample = diagnostics.slice(0, 5).map((entry) => ({
|
|
1623
|
+
tool: entry.toolName ?? `tool[${entry.toolIndex}]`,
|
|
1624
|
+
violations: entry.violations.slice(0, 8)
|
|
1625
|
+
}));
|
|
1626
|
+
return {
|
|
1627
|
+
message: `OpenAI ${context.transport} tool schema strict mode downgraded to strict=false for ${context.model.provider ?? "unknown"}/${context.model.id ?? "unknown"} because ${diagnostics.length} tool schema(s) are not strict-compatible`,
|
|
1628
|
+
data: {
|
|
1629
|
+
transport: context.transport,
|
|
1630
|
+
provider: context.model.provider,
|
|
1830
1631
|
model: context.model.id,
|
|
1831
1632
|
incompatibleToolCount: diagnostics.length,
|
|
1832
1633
|
sample
|
|
@@ -1912,64 +1713,7 @@ function getCompat(model) {
|
|
|
1912
1713
|
};
|
|
1913
1714
|
}
|
|
1914
1715
|
//#endregion
|
|
1915
|
-
//#region packages/ai/src/transports/openai-completions-
|
|
1916
|
-
function hasToolHistory(messages) {
|
|
1917
|
-
return messages.some((message) => message.role === "toolResult" || message.role === "assistant" && Array.isArray(message.content) && message.content.some((block) => block.type === "toolCall"));
|
|
1918
|
-
}
|
|
1919
|
-
function assertOpenAICompletionsPayloadHasConversationTurn(params, model) {
|
|
1920
|
-
const messages = params.messages;
|
|
1921
|
-
if (!Array.isArray(messages) || hasOpenAICompatibleConversationTurn(messages)) return;
|
|
1922
|
-
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.`);
|
|
1923
|
-
}
|
|
1924
|
-
const SSE_DONE_LINE_RE = /^data:[ \t]*\[DONE\][ \t]*$/i;
|
|
1925
|
-
const SSE_DONE_MAX_LINE_CHARS = 1024;
|
|
1926
|
-
function createSseDoneDetector() {
|
|
1927
|
-
const decoder = new TextDecoder();
|
|
1928
|
-
let line = "";
|
|
1929
|
-
let lineOverflowed = false;
|
|
1930
|
-
let sawDone = false;
|
|
1931
|
-
const finishLine = () => {
|
|
1932
|
-
if (!lineOverflowed && SSE_DONE_LINE_RE.test(line)) sawDone = true;
|
|
1933
|
-
line = "";
|
|
1934
|
-
lineOverflowed = false;
|
|
1935
|
-
};
|
|
1936
|
-
const observeText = (text) => {
|
|
1937
|
-
for (const char of text) {
|
|
1938
|
-
if (char === "\n" || char === "\r") {
|
|
1939
|
-
finishLine();
|
|
1940
|
-
continue;
|
|
1941
|
-
}
|
|
1942
|
-
if (!lineOverflowed && line.length < SSE_DONE_MAX_LINE_CHARS) line += char;
|
|
1943
|
-
else lineOverflowed = true;
|
|
1944
|
-
}
|
|
1945
|
-
};
|
|
1946
|
-
return {
|
|
1947
|
-
observe(chunk) {
|
|
1948
|
-
if (!sawDone) observeText(decoder.decode(chunk, { stream: true }));
|
|
1949
|
-
},
|
|
1950
|
-
finish() {
|
|
1951
|
-
if (sawDone) return;
|
|
1952
|
-
observeText(decoder.decode());
|
|
1953
|
-
if (line || lineOverflowed) finishLine();
|
|
1954
|
-
},
|
|
1955
|
-
sawDone: () => sawDone
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
function createOpenAICompletionsClient(model, context, apiKey, optionHeaders, opts) {
|
|
1959
|
-
const clientConfig = buildOpenAICompletionsClientConfig(model, context, optionHeaders);
|
|
1960
|
-
return new OpenAI({
|
|
1961
|
-
apiKey,
|
|
1962
|
-
baseURL: clientConfig.baseURL,
|
|
1963
|
-
dangerouslyAllowBrowser: true,
|
|
1964
|
-
defaultHeaders: clientConfig.defaultHeaders,
|
|
1965
|
-
defaultQuery: clientConfig.defaultQuery,
|
|
1966
|
-
fetch: opts?.fetch ?? buildGuardedModelFetch(model),
|
|
1967
|
-
...buildOpenAISdkClientOptions(model)
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
function isAzureOpenAICompatibleHost(hostname) {
|
|
1971
|
-
return hostname.endsWith(".openai.azure.com") || hostname.endsWith(".services.ai.azure.com") || hostname.endsWith(".cognitiveservices.azure.com");
|
|
1972
|
-
}
|
|
1716
|
+
//#region packages/ai/src/transports/openai-completions-params.ts
|
|
1973
1717
|
function isKnownOpenAICompletionsEndpoint(model) {
|
|
1974
1718
|
if (!model.baseUrl.trim()) return true;
|
|
1975
1719
|
const endpointClass = resolveProviderEndpoint(model.baseUrl).endpointClass;
|
|
@@ -1980,481 +1724,232 @@ function isKnownOpenAICompletionsEndpoint(model) {
|
|
|
1980
1724
|
return false;
|
|
1981
1725
|
}
|
|
1982
1726
|
}
|
|
1983
|
-
function
|
|
1984
|
-
|
|
1985
|
-
const defaultQuery = {};
|
|
1986
|
-
let baseURL = model.baseUrl;
|
|
1987
|
-
let isAzureHost = false;
|
|
1988
|
-
try {
|
|
1989
|
-
const parsed = new URL(model.baseUrl);
|
|
1990
|
-
isAzureHost = isAzureOpenAICompatibleHost(parsed.hostname.toLowerCase());
|
|
1991
|
-
parsed.searchParams.forEach((value, key) => {
|
|
1992
|
-
if (value) defaultQuery[key] = value;
|
|
1993
|
-
});
|
|
1994
|
-
parsed.search = "";
|
|
1995
|
-
baseURL = parsed.toString().replace(/\/$/, "");
|
|
1996
|
-
} catch {}
|
|
1997
|
-
if (isAzureHost) {
|
|
1998
|
-
const apiVersionHeader = Object.keys(headers).find((key) => key.toLowerCase() === "api-version");
|
|
1999
|
-
if (apiVersionHeader) {
|
|
2000
|
-
const apiVersion = headers[apiVersionHeader]?.trim();
|
|
2001
|
-
delete headers[apiVersionHeader];
|
|
2002
|
-
if (apiVersion && !defaultQuery["api-version"]) defaultQuery["api-version"] = apiVersion;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
return {
|
|
2006
|
-
baseURL,
|
|
2007
|
-
defaultHeaders: headers,
|
|
2008
|
-
defaultQuery: Object.keys(defaultQuery).length > 0 ? defaultQuery : void 0
|
|
2009
|
-
};
|
|
1727
|
+
function resolveOpenAICompletionsReasoningEffort$1(options) {
|
|
1728
|
+
return options?.reasoningEffort ?? options?.reasoning ?? "high";
|
|
2010
1729
|
}
|
|
2011
|
-
function
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
(async () => {
|
|
2016
|
-
const output = {
|
|
2017
|
-
role: "assistant",
|
|
2018
|
-
content: [],
|
|
2019
|
-
api: model.api,
|
|
2020
|
-
provider: model.provider,
|
|
2021
|
-
model: model.id,
|
|
2022
|
-
usage: {
|
|
2023
|
-
input: 0,
|
|
2024
|
-
output: 0,
|
|
2025
|
-
cacheRead: 0,
|
|
2026
|
-
cacheWrite: 0,
|
|
2027
|
-
totalTokens: 0,
|
|
2028
|
-
cost: {
|
|
2029
|
-
input: 0,
|
|
2030
|
-
output: 0,
|
|
2031
|
-
cacheRead: 0,
|
|
2032
|
-
cacheWrite: 0,
|
|
2033
|
-
total: 0
|
|
2034
|
-
}
|
|
2035
|
-
},
|
|
2036
|
-
stopReason: "stop",
|
|
2037
|
-
timestamp: Date.now()
|
|
2038
|
-
};
|
|
2039
|
-
let firstEventAbort;
|
|
2040
|
-
try {
|
|
2041
|
-
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
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 client = createOpenAICompletionsClient(model, context, apiKey, options?.headers, { fetch: doneDetectingFetch });
|
|
2064
|
-
let params = buildOpenAICompletionsParams(model, context, options);
|
|
2065
|
-
const nextParams = await options?.onPayload?.(params, model);
|
|
2066
|
-
if (nextParams !== void 0) params = nextParams;
|
|
2067
|
-
if (options?.openclawCodeModeToolSurface === true) {
|
|
2068
|
-
const visibleToolNames = resolveCodeModeResponsesVisibleToolNames(context);
|
|
2069
|
-
enforceCodeModeResponsesToolSurface(params, visibleToolNames);
|
|
2070
|
-
assertCodeModeResponsesToolSurface(params, visibleToolNames);
|
|
2071
|
-
}
|
|
2072
|
-
if (getCompat(model).requiresNonEmptyUserOrAssistantMessage) assertOpenAICompletionsPayloadHasConversationTurn(params, model);
|
|
2073
|
-
const emitReasoning = shouldEmitOpenAICompletionsReasoning(model, options);
|
|
2074
|
-
firstEventAbort = createFirstStreamEventAbortController(options?.signal);
|
|
2075
|
-
const responseStream = await client.chat.completions.create(params, buildOpenAISdkRequestOptions(model, firstEventAbort.signal, {
|
|
2076
|
-
timeoutMs: options?.timeoutMs,
|
|
2077
|
-
maxRetries: options?.maxRetries
|
|
2078
|
-
}));
|
|
2079
|
-
stream.push({
|
|
2080
|
-
type: "start",
|
|
2081
|
-
partial: output
|
|
2082
|
-
});
|
|
2083
|
-
await processOpenAICompletionsStream(responseStream, output, model, stream, {
|
|
2084
|
-
signal: options?.signal,
|
|
2085
|
-
emitReasoning,
|
|
2086
|
-
firstEventTimeoutMs: getFirstStreamEventTimeoutMs(options),
|
|
2087
|
-
abortFirstEventStream: firstEventAbort.abort,
|
|
2088
|
-
onFirstEventTimeout: getFirstStreamEventTimeoutHandler(options),
|
|
2089
|
-
sawStreamDONE: doneDetector.sawDone
|
|
2090
|
-
});
|
|
2091
|
-
finalizeTransportStream({
|
|
2092
|
-
stream,
|
|
2093
|
-
output,
|
|
2094
|
-
signal: options?.signal
|
|
2095
|
-
});
|
|
2096
|
-
} catch (error) {
|
|
2097
|
-
failTransportStream({
|
|
2098
|
-
stream,
|
|
2099
|
-
output,
|
|
2100
|
-
signal: options?.signal,
|
|
2101
|
-
error
|
|
2102
|
-
});
|
|
2103
|
-
} finally {
|
|
2104
|
-
firstEventAbort?.dispose();
|
|
2105
|
-
}
|
|
2106
|
-
})();
|
|
2107
|
-
return eventStream;
|
|
2108
|
-
};
|
|
2109
|
-
}
|
|
2110
|
-
async function processOpenAICompletionsStream(responseStream, output, model, stream, options) {
|
|
2111
|
-
const MAX_POST_TOOL_CALL_BUFFER_BYTES = 256e3;
|
|
2112
|
-
const MAX_TOOL_CALL_ARGUMENT_BUFFER_BYTES = 256e3;
|
|
2113
|
-
const emitReasoning = options?.emitReasoning ?? true;
|
|
2114
|
-
const compat = getCompat(model);
|
|
2115
|
-
const deepSeekTextFilter = shouldFilterDeepSeekDsmlText(compat) ? createDeepSeekTextFilter() : null;
|
|
2116
|
-
const deepSeekToolCallRecoverer = shouldFilterDeepSeekDsmlText(compat) ? createDeepSeekDsmlToolCallRecoverer() : null;
|
|
2117
|
-
const reasoningTagTextPartitioner = createReasoningTagTextPartitioner();
|
|
2118
|
-
let currentBlock = null;
|
|
2119
|
-
let pendingPostToolCallDeltas = [];
|
|
2120
|
-
let pendingPostToolCallBytes = 0;
|
|
2121
|
-
let isFlushingPendingPostToolCallDeltas = false;
|
|
2122
|
-
const toolCallBlocksByIndex = /* @__PURE__ */ new Map();
|
|
2123
|
-
const toolCallBlocksById = /* @__PURE__ */ new Map();
|
|
2124
|
-
const provisionalCommentaryTags = /* @__PURE__ */ new Map();
|
|
2125
|
-
const toolCallBlockBytes = /* @__PURE__ */ new WeakMap();
|
|
2126
|
-
const toolCallBlockIndices = /* @__PURE__ */ new WeakMap();
|
|
2127
|
-
let sawStopFinishReason = false;
|
|
2128
|
-
let sawNativeToolCallDelta = false;
|
|
2129
|
-
const blockIndex = () => output.content.length - 1;
|
|
2130
|
-
const measureUtf8Bytes = (text) => Buffer.byteLength(text, "utf8");
|
|
2131
|
-
let chunkPushedEvent = false;
|
|
2132
|
-
const pushStreamEvent = (event) => {
|
|
2133
|
-
chunkPushedEvent = true;
|
|
2134
|
-
stream.push(event);
|
|
2135
|
-
};
|
|
2136
|
-
const queuePostToolCallDelta = (next) => {
|
|
2137
|
-
const nextBytes = measureUtf8Bytes(next.text);
|
|
2138
|
-
if (pendingPostToolCallBytes + nextBytes > MAX_POST_TOOL_CALL_BUFFER_BYTES) throw new Error("Exceeded post-tool-call delta buffer limit");
|
|
2139
|
-
pendingPostToolCallBytes += nextBytes;
|
|
2140
|
-
const previous = pendingPostToolCallDeltas[pendingPostToolCallDeltas.length - 1];
|
|
2141
|
-
if (!previous || previous.kind !== next.kind) {
|
|
2142
|
-
pendingPostToolCallDeltas.push(next);
|
|
2143
|
-
return;
|
|
2144
|
-
}
|
|
2145
|
-
if (next.kind === "thinking" && previous.kind === "thinking") {
|
|
2146
|
-
if (previous.signature !== next.signature) {
|
|
2147
|
-
pendingPostToolCallDeltas.push(next);
|
|
2148
|
-
return;
|
|
2149
|
-
}
|
|
2150
|
-
previous.text += next.text;
|
|
2151
|
-
return;
|
|
2152
|
-
}
|
|
2153
|
-
previous.text += next.text;
|
|
2154
|
-
};
|
|
2155
|
-
const appendThinkingDeltaInternal = (reasoningDelta) => {
|
|
2156
|
-
if (!currentBlock || currentBlock.type !== "thinking") {
|
|
2157
|
-
currentBlock = {
|
|
2158
|
-
type: "thinking",
|
|
2159
|
-
thinking: "",
|
|
2160
|
-
...reasoningDelta.signature ? { thinkingSignature: reasoningDelta.signature } : {}
|
|
2161
|
-
};
|
|
2162
|
-
output.content.push(currentBlock);
|
|
2163
|
-
pushStreamEvent({
|
|
2164
|
-
type: "thinking_start",
|
|
2165
|
-
contentIndex: blockIndex(),
|
|
2166
|
-
partial: output
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
currentBlock.thinking += reasoningDelta.text;
|
|
2170
|
-
pushStreamEvent({
|
|
2171
|
-
type: "thinking_delta",
|
|
2172
|
-
contentIndex: blockIndex(),
|
|
2173
|
-
delta: reasoningDelta.text,
|
|
2174
|
-
partial: output
|
|
2175
|
-
});
|
|
2176
|
-
};
|
|
2177
|
-
const appendTextDeltaInternal = (text) => {
|
|
2178
|
-
if (!currentBlock || currentBlock.type !== "text") {
|
|
2179
|
-
currentBlock = {
|
|
2180
|
-
type: "text",
|
|
2181
|
-
text: ""
|
|
2182
|
-
};
|
|
2183
|
-
output.content.push(currentBlock);
|
|
2184
|
-
pushStreamEvent({
|
|
2185
|
-
type: "text_start",
|
|
2186
|
-
contentIndex: blockIndex(),
|
|
2187
|
-
partial: output
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
currentBlock.text += text;
|
|
2191
|
-
pushStreamEvent({
|
|
2192
|
-
type: "text_delta",
|
|
2193
|
-
contentIndex: blockIndex(),
|
|
2194
|
-
delta: text
|
|
2195
|
-
});
|
|
2196
|
-
};
|
|
2197
|
-
const flushPendingPostToolCallDeltas = () => {
|
|
2198
|
-
if (isFlushingPendingPostToolCallDeltas || currentBlock?.type === "toolCall" || pendingPostToolCallDeltas.length === 0) return;
|
|
2199
|
-
isFlushingPendingPostToolCallDeltas = true;
|
|
2200
|
-
const bufferedDeltas = pendingPostToolCallDeltas;
|
|
2201
|
-
pendingPostToolCallDeltas = [];
|
|
2202
|
-
pendingPostToolCallBytes = 0;
|
|
2203
|
-
for (const delta of bufferedDeltas) if (delta.kind === "text") appendTextDeltaInternal(delta.text);
|
|
2204
|
-
else if (emitReasoning) appendThinkingDeltaInternal(delta);
|
|
2205
|
-
isFlushingPendingPostToolCallDeltas = false;
|
|
2206
|
-
};
|
|
2207
|
-
const appendThinkingDelta = (reasoningDelta) => {
|
|
2208
|
-
flushPendingPostToolCallDeltas();
|
|
2209
|
-
appendThinkingDeltaInternal(reasoningDelta);
|
|
2210
|
-
};
|
|
2211
|
-
const appendTextDelta = (text) => {
|
|
2212
|
-
flushPendingPostToolCallDeltas();
|
|
2213
|
-
appendTextDeltaInternal(text);
|
|
2214
|
-
};
|
|
2215
|
-
const appendVisibleTextDelta = (text) => {
|
|
2216
|
-
if (!text) return;
|
|
2217
|
-
if (currentBlock?.type === "toolCall") queuePostToolCallDelta({
|
|
2218
|
-
kind: "text",
|
|
2219
|
-
text
|
|
2220
|
-
});
|
|
2221
|
-
else appendTextDelta(text);
|
|
2222
|
-
};
|
|
2223
|
-
const appendRecoveredToolCall = (toolCall) => {
|
|
2224
|
-
if (currentBlock?.type === "toolCall") {
|
|
2225
|
-
currentBlock = null;
|
|
2226
|
-
flushPendingPostToolCallDeltas();
|
|
2227
|
-
}
|
|
2228
|
-
rememberPendingCommentaryTags(provisionalCommentaryTags, tagPendingCommentaryText(output.content));
|
|
2229
|
-
const block = {
|
|
2230
|
-
type: "toolCall",
|
|
2231
|
-
id: `call_${randomUUID().replaceAll("-", "").slice(0, 24)}`,
|
|
2232
|
-
name: toolCall.name,
|
|
2233
|
-
arguments: toolCall.arguments,
|
|
2234
|
-
partialArgs: toolCall.partialArgs
|
|
2235
|
-
};
|
|
2236
|
-
currentBlock = block;
|
|
2237
|
-
output.content.push(block);
|
|
2238
|
-
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
2239
|
-
pushStreamEvent({
|
|
2240
|
-
type: "toolcall_start",
|
|
2241
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2242
|
-
partial: output
|
|
2243
|
-
});
|
|
2244
|
-
pushStreamEvent({
|
|
2245
|
-
type: "toolcall_delta",
|
|
2246
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2247
|
-
delta: toolCall.partialArgs,
|
|
2248
|
-
partial: output
|
|
2249
|
-
});
|
|
2250
|
-
};
|
|
2251
|
-
const appendFilteredVisibleTextDelta = (text) => {
|
|
2252
|
-
const recoveredParts = deepSeekToolCallRecoverer?.push(text) ?? [{
|
|
2253
|
-
kind: "text",
|
|
2254
|
-
text
|
|
2255
|
-
}];
|
|
2256
|
-
for (const recoveredPart of recoveredParts) {
|
|
2257
|
-
if (recoveredPart.kind === "toolCall") {
|
|
2258
|
-
appendRecoveredToolCall(recoveredPart);
|
|
2259
|
-
continue;
|
|
2260
|
-
}
|
|
2261
|
-
const parts = deepSeekTextFilter?.push(recoveredPart.text) ?? [recoveredPart.text];
|
|
2262
|
-
for (const part of parts) appendVisibleTextDelta(part);
|
|
2263
|
-
}
|
|
2264
|
-
};
|
|
2265
|
-
const flushDeepSeekToolCallRecovererAtEnd = () => {
|
|
2266
|
-
const recoveredParts = deepSeekToolCallRecoverer?.flush();
|
|
2267
|
-
if (!recoveredParts) return;
|
|
2268
|
-
for (const recoveredPart of recoveredParts) {
|
|
2269
|
-
if (recoveredPart.kind === "toolCall") {
|
|
2270
|
-
appendRecoveredToolCall(recoveredPart);
|
|
2271
|
-
continue;
|
|
2272
|
-
}
|
|
2273
|
-
const parts = deepSeekTextFilter?.push(recoveredPart.text) ?? [recoveredPart.text];
|
|
2274
|
-
for (const part of parts) appendVisibleTextDelta(part);
|
|
2275
|
-
}
|
|
2276
|
-
};
|
|
2277
|
-
const flushDeepSeekTextFilterAtEnd = () => {
|
|
2278
|
-
const parts = deepSeekTextFilter?.flush();
|
|
2279
|
-
if (!parts) return;
|
|
2280
|
-
for (const part of parts) appendVisibleTextDelta(part);
|
|
2281
|
-
};
|
|
2282
|
-
const appendRoutedContentDelta = (delta) => {
|
|
2283
|
-
if (delta.kind === "text") {
|
|
2284
|
-
appendFilteredVisibleTextDelta(delta.text);
|
|
2285
|
-
return;
|
|
2286
|
-
}
|
|
2287
|
-
if (!emitReasoning) return;
|
|
2288
|
-
if (currentBlock?.type === "toolCall") queuePostToolCallDelta(delta);
|
|
2289
|
-
else appendThinkingDelta(delta);
|
|
2290
|
-
};
|
|
2291
|
-
const appendPartitionedVisibleDelta = (delta) => {
|
|
2292
|
-
if (delta.kind === "text") appendFilteredVisibleTextDelta(delta.text);
|
|
1730
|
+
function resolveOpenAICompletionsMaxTokens(model, options) {
|
|
1731
|
+
if (options?.maxTokens) return {
|
|
1732
|
+
maxTokens: options.maxTokens,
|
|
1733
|
+
clampToModelMaxTokens: true
|
|
2293
1734
|
};
|
|
2294
|
-
const
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
if (latestBlock?.type === "text" || latestBlock?.type === "toolCall") return;
|
|
2299
|
-
appendThinkingDelta({
|
|
2300
|
-
signature: "",
|
|
2301
|
-
text: ""
|
|
2302
|
-
});
|
|
1735
|
+
const paramsMaxTokens = resolveMaxTokensParam(model.params);
|
|
1736
|
+
if (paramsMaxTokens) return {
|
|
1737
|
+
maxTokens: paramsMaxTokens,
|
|
1738
|
+
clampToModelMaxTokens: false
|
|
2303
1739
|
};
|
|
2304
|
-
|
|
2305
|
-
|
|
1740
|
+
return {
|
|
1741
|
+
maxTokens: model.maxTokens,
|
|
1742
|
+
clampToModelMaxTokens: false
|
|
2306
1743
|
};
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
1744
|
+
}
|
|
1745
|
+
function resolveOpenAICompletionsModelMaxTokens(model) {
|
|
1746
|
+
return typeof model.maxTokens === "number" && Number.isFinite(model.maxTokens) && model.maxTokens > 0 ? Math.floor(model.maxTokens) : void 0;
|
|
1747
|
+
}
|
|
1748
|
+
const OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN = 1.25;
|
|
1749
|
+
const OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE = 8e3;
|
|
1750
|
+
function estimateOpenAICompletionsInputTokens(payload) {
|
|
1751
|
+
let adjustedChars = 0;
|
|
1752
|
+
adjustedChars += estimateOpenAICompletionsMessagesChars(payload.messages);
|
|
1753
|
+
if (Array.isArray(payload.tools) && payload.tools.length > 0) try {
|
|
1754
|
+
adjustedChars += estimateStringChars(JSON.stringify(payload.tools));
|
|
1755
|
+
} catch {
|
|
1756
|
+
adjustedChars += 1024;
|
|
1757
|
+
}
|
|
1758
|
+
if (payload.response_format !== void 0) try {
|
|
1759
|
+
adjustedChars += estimateStringChars(JSON.stringify(payload.response_format));
|
|
1760
|
+
} catch {
|
|
1761
|
+
adjustedChars += 256;
|
|
1762
|
+
}
|
|
1763
|
+
return Math.ceil(adjustedChars / 4 * OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN);
|
|
1764
|
+
}
|
|
1765
|
+
function estimateOpenAICompletionsMessagesChars(messages) {
|
|
1766
|
+
if (!Array.isArray(messages)) return 0;
|
|
1767
|
+
let adjustedChars = 0;
|
|
1768
|
+
for (const message of messages) {
|
|
1769
|
+
if (!message || typeof message !== "object") continue;
|
|
1770
|
+
const record = message;
|
|
1771
|
+
adjustedChars += estimateOpenAICompletionsContentChars(record.content);
|
|
1772
|
+
for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) adjustedChars += estimateOpenAICompletionsContentChars(record[field]);
|
|
1773
|
+
if (record.tool_calls !== void 0) try {
|
|
1774
|
+
adjustedChars += estimateStringChars(JSON.stringify(record.tool_calls));
|
|
1775
|
+
} catch {
|
|
1776
|
+
adjustedChars += 256;
|
|
2332
1777
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
1778
|
+
}
|
|
1779
|
+
return adjustedChars;
|
|
1780
|
+
}
|
|
1781
|
+
function estimateOpenAICompletionsContentChars(value) {
|
|
1782
|
+
if (typeof value === "string") return estimateStringChars(value);
|
|
1783
|
+
if (!Array.isArray(value)) return 0;
|
|
1784
|
+
let adjustedChars = 0;
|
|
1785
|
+
for (const block of value) {
|
|
1786
|
+
if (!block || typeof block !== "object") continue;
|
|
1787
|
+
const record = block;
|
|
1788
|
+
if (record.type === "image_url" || record.type === "input_image") {
|
|
1789
|
+
adjustedChars += OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE;
|
|
2337
1790
|
continue;
|
|
2338
1791
|
}
|
|
2339
|
-
const
|
|
2340
|
-
if (
|
|
2341
|
-
|
|
2342
|
-
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(choiceUsage);
|
|
2343
|
-
}
|
|
2344
|
-
if (choice.finish_reason) {
|
|
2345
|
-
const finishReasonResult = mapOpenAIStopReason(choice.finish_reason, { allowSingularToolCall: true });
|
|
2346
|
-
output.stopReason = finishReasonResult.stopReason;
|
|
2347
|
-
if (finishReasonResult.stopReason === "stop") sawStopFinishReason = true;
|
|
2348
|
-
if (finishReasonResult.errorMessage) output.errorMessage = finishReasonResult.errorMessage;
|
|
2349
|
-
}
|
|
2350
|
-
const choiceDelta = choice.delta ?? choice.message;
|
|
2351
|
-
if (!choiceDelta) {
|
|
2352
|
-
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
2353
|
-
await cooperativeScheduler.afterEvent();
|
|
1792
|
+
const text = record.text;
|
|
1793
|
+
if (typeof text === "string") {
|
|
1794
|
+
adjustedChars += estimateStringChars(text);
|
|
2354
1795
|
continue;
|
|
2355
1796
|
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
const contentDeltas = getCompletionsContentDeltas(choiceDelta.content);
|
|
2361
|
-
for (const contentDelta of contentDeltas) if (contentDelta.kind === "text") {
|
|
2362
|
-
const routedDeltas = hasMirroredReasoning ? reasoningTagTextPartitioner.push(contentDelta.text) : reasoningTagTextPartitioner.pushVisible(contentDelta.text);
|
|
2363
|
-
for (const routedDelta of routedDeltas) appendPartitionedVisibleDelta(routedDelta);
|
|
2364
|
-
} else {
|
|
2365
|
-
reasoningTagTextPartitioner.markStrict();
|
|
2366
|
-
appendRoutedContentDelta(contentDelta);
|
|
2367
|
-
}
|
|
1797
|
+
try {
|
|
1798
|
+
adjustedChars += estimateStringChars(JSON.stringify(block));
|
|
1799
|
+
} catch {
|
|
1800
|
+
adjustedChars += 256;
|
|
2368
1801
|
}
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
1802
|
+
}
|
|
1803
|
+
return adjustedChars;
|
|
1804
|
+
}
|
|
1805
|
+
function resolveOpenAICompletionsEffectiveContextTokens(model) {
|
|
1806
|
+
const contextTokens = model.contextTokens;
|
|
1807
|
+
if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens > 0) return contextTokens;
|
|
1808
|
+
return typeof model.contextWindow === "number" && Number.isFinite(model.contextWindow) && model.contextWindow > 0 ? model.contextWindow : void 0;
|
|
1809
|
+
}
|
|
1810
|
+
function isQwenOpenAICompletionsThinkingFormat(format) {
|
|
1811
|
+
return format === "qwen" || format === "qwen-chat-template";
|
|
1812
|
+
}
|
|
1813
|
+
function setQwenChatTemplateThinking(params, enabled) {
|
|
1814
|
+
const existing = params.chat_template_kwargs;
|
|
1815
|
+
params.chat_template_kwargs = existing && typeof existing === "object" && !Array.isArray(existing) ? {
|
|
1816
|
+
...existing,
|
|
1817
|
+
enable_thinking: enabled
|
|
1818
|
+
} : { enable_thinking: enabled };
|
|
1819
|
+
}
|
|
1820
|
+
function applyQwenOpenAICompletionsThinkingParams(params) {
|
|
1821
|
+
if (!params.modelReasoning || !isQwenOpenAICompletionsThinkingFormat(params.compatThinkingFormat)) return false;
|
|
1822
|
+
const enabled = isOpenAICompletionsThinkingEnabled(params.requestedEffort);
|
|
1823
|
+
if (params.compatThinkingFormat === "qwen-chat-template") setQwenChatTemplateThinking(params.payload, enabled);
|
|
1824
|
+
else params.payload.enable_thinking = enabled;
|
|
1825
|
+
return true;
|
|
1826
|
+
}
|
|
1827
|
+
function applyTogetherOpenAICompletionsThinkingParams(params) {
|
|
1828
|
+
if (!params.modelReasoning || params.compatThinkingFormat !== "together") return;
|
|
1829
|
+
params.payload.reasoning = { enabled: isOpenAICompletionsThinkingEnabled(params.requestedEffort) };
|
|
1830
|
+
}
|
|
1831
|
+
function convertTools(tools, compat, model) {
|
|
1832
|
+
const projection = projectOpenAITools(tools);
|
|
1833
|
+
const strict = resolveOpenAIStrictToolFlagWithDiagnostics(projection, resolveOpenAIStrictToolSetting(model, {
|
|
1834
|
+
transport: "stream",
|
|
1835
|
+
supportsStrictMode: compat?.supportsStrictMode
|
|
1836
|
+
}), {
|
|
1837
|
+
transport: "completions",
|
|
1838
|
+
model
|
|
1839
|
+
});
|
|
1840
|
+
return {
|
|
1841
|
+
projection,
|
|
1842
|
+
tools: sortPromptCacheToolsByName(projection.tools).map((tool) => {
|
|
1843
|
+
const functionTool = {
|
|
1844
|
+
name: tool.name,
|
|
1845
|
+
description: tool.description,
|
|
1846
|
+
parameters: normalizeOpenAIStrictToolParameters(tool.parameters, strict === true, model.compat)
|
|
1847
|
+
};
|
|
1848
|
+
if (strict !== void 0) functionTool.strict = strict;
|
|
1849
|
+
return {
|
|
1850
|
+
type: "function",
|
|
1851
|
+
function: functionTool
|
|
1852
|
+
};
|
|
1853
|
+
})
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
function buildOpenAICompletionsParams(model, context, options) {
|
|
1857
|
+
const compat = getCompat(model);
|
|
1858
|
+
const compatDetection = detectOpenAICompletionsCompat(model);
|
|
1859
|
+
let messages = convertMessages(model, context.systemPrompt ? {
|
|
1860
|
+
...context,
|
|
1861
|
+
systemPrompt: stripSystemPromptCacheBoundary(context.systemPrompt)
|
|
1862
|
+
} : context, compat);
|
|
1863
|
+
applyCompletionsReplay(messages, context, model, compat);
|
|
1864
|
+
if (compat.strictMessageKeys) messages = stripCompletionMessagesToRoleContent(messages);
|
|
1865
|
+
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
1866
|
+
const promptCacheKey = resolvePromptCacheKey(options, cacheRetention);
|
|
1867
|
+
const params = {
|
|
1868
|
+
model: model.id,
|
|
1869
|
+
messages: compat.requiresStringContent ? flattenCompletionMessagesToStringContent(messages) : messages,
|
|
1870
|
+
stream: true
|
|
1871
|
+
};
|
|
1872
|
+
if (compat.supportsUsageInStreaming) params.stream_options = { include_usage: true };
|
|
1873
|
+
if (compat.supportsStore) params.store = false;
|
|
1874
|
+
if (compat.supportsPromptCacheKey && promptCacheKey) {
|
|
1875
|
+
params.prompt_cache_key = promptCacheKey;
|
|
1876
|
+
if (cacheRetention === "long" && compat.supportsLongCacheRetention) params.prompt_cache_retention = "24h";
|
|
1877
|
+
}
|
|
1878
|
+
if (options?.temperature !== void 0) params.temperature = options.temperature;
|
|
1879
|
+
if (options?.topP !== void 0) params.top_p = options.topP;
|
|
1880
|
+
const responseFormat = resolveOpenAICompletionsResponseFormat(shouldOmitOllamaCompatResponseFormat({
|
|
1881
|
+
provider: model.provider,
|
|
1882
|
+
baseUrl: model.baseUrl,
|
|
1883
|
+
hasTools: () => Boolean(context.tools?.length)
|
|
1884
|
+
}) ? void 0 : options?.responseFormat, compat.supportsJsonSchemaResponseFormat);
|
|
1885
|
+
if (responseFormat !== void 0) params.response_format = responseFormat;
|
|
1886
|
+
if (options?.frequencyPenalty !== void 0) params.frequency_penalty = options.frequencyPenalty;
|
|
1887
|
+
if (options?.presencePenalty !== void 0) params.presence_penalty = options.presencePenalty;
|
|
1888
|
+
if (options?.seed !== void 0) params.seed = options.seed;
|
|
1889
|
+
if (options?.stop !== void 0 && options.stop.length > 0) params.stop = options.stop;
|
|
1890
|
+
if (supportsModelTools(model)) {
|
|
1891
|
+
if (context.tools) {
|
|
1892
|
+
const converted = convertTools(context.tools, compat, model);
|
|
1893
|
+
if (converted.tools.length > 0 || converted.projection.inputToolCount === 0 && converted.projection.diagnostics.length === 0) params.tools = converted.tools;
|
|
1894
|
+
else if (hasToolCallHistory(context.messages)) params.tools = [];
|
|
1895
|
+
if (options?.toolChoice) {
|
|
1896
|
+
const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, converted.projection);
|
|
1897
|
+
if (toolChoice !== void 0) params.tool_choice = toolChoice;
|
|
1898
|
+
} else if (compatDetection.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length > 0) params.tool_choice = "auto";
|
|
1899
|
+
} else if (hasToolCallHistory(context.messages)) params.tools = [];
|
|
1900
|
+
if (compatDetection.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length === 0) {
|
|
1901
|
+
delete params.tools;
|
|
1902
|
+
delete params.tool_choice;
|
|
2373
1903
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
1904
|
+
}
|
|
1905
|
+
{
|
|
1906
|
+
const maxTokenBudget = resolveOpenAICompletionsMaxTokens(model, options);
|
|
1907
|
+
const effectiveMaxTokens = maxTokenBudget.maxTokens;
|
|
1908
|
+
const effectiveContextTokens = resolveOpenAICompletionsEffectiveContextTokens(model);
|
|
1909
|
+
let clampedMaxTokens = effectiveMaxTokens;
|
|
1910
|
+
const modelMaxTokens = resolveOpenAICompletionsModelMaxTokens(model);
|
|
1911
|
+
if (maxTokenBudget.clampToModelMaxTokens && clampedMaxTokens !== void 0 && modelMaxTokens !== void 0 && clampedMaxTokens > modelMaxTokens) {
|
|
1912
|
+
clampedMaxTokens = modelMaxTokens;
|
|
1913
|
+
emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} modelMaxTokens=${modelMaxTokens}`);
|
|
2382
1914
|
}
|
|
2383
|
-
if (
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
let block = streamIndex !== void 0 ? toolCallBlocksByIndex.get(streamIndex) : void 0;
|
|
2390
|
-
if (!block && toolCall.id) block = toolCallBlocksById.get(toolCall.id);
|
|
2391
|
-
if (!block) {
|
|
2392
|
-
if (currentBlock?.type === "toolCall") {
|
|
2393
|
-
currentBlock = null;
|
|
2394
|
-
flushPendingPostToolCallDeltas();
|
|
2395
|
-
}
|
|
2396
|
-
const initialSig = extractGoogleThoughtSignature(toolCall);
|
|
2397
|
-
block = {
|
|
2398
|
-
type: "toolCall",
|
|
2399
|
-
id: toolCall.id || "",
|
|
2400
|
-
name: toolCall.function?.name || "",
|
|
2401
|
-
arguments: {},
|
|
2402
|
-
partialArgs: "",
|
|
2403
|
-
...initialSig ? { thoughtSignature: initialSig } : {}
|
|
2404
|
-
};
|
|
2405
|
-
output.content.push(block);
|
|
2406
|
-
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
2407
|
-
pushStreamEvent({
|
|
2408
|
-
type: "toolcall_start",
|
|
2409
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2410
|
-
partial: output
|
|
2411
|
-
});
|
|
2412
|
-
}
|
|
2413
|
-
if (streamIndex !== void 0 && !toolCallBlocksByIndex.has(streamIndex)) toolCallBlocksByIndex.set(streamIndex, block);
|
|
2414
|
-
if (toolCall.id) {
|
|
2415
|
-
block.id = toolCall.id;
|
|
2416
|
-
toolCallBlocksById.set(toolCall.id, block);
|
|
2417
|
-
}
|
|
2418
|
-
currentBlock = block;
|
|
2419
|
-
if (toolCall.function?.name) block.name = toolCall.function.name;
|
|
2420
|
-
const deltaSig = extractGoogleThoughtSignature(toolCall);
|
|
2421
|
-
if (deltaSig) block.thoughtSignature = deltaSig;
|
|
2422
|
-
if (toolCall.function?.arguments) {
|
|
2423
|
-
const nextArgumentBytes = measureUtf8Bytes(toolCall.function.arguments);
|
|
2424
|
-
const currentBlockArgBytes = toolCallBlockBytes.get(block) ?? 0;
|
|
2425
|
-
if (currentBlockArgBytes + nextArgumentBytes > MAX_TOOL_CALL_ARGUMENT_BUFFER_BYTES) throw new Error("Exceeded tool-call argument buffer limit");
|
|
2426
|
-
toolCallBlockBytes.set(block, currentBlockArgBytes + nextArgumentBytes);
|
|
2427
|
-
block.partialArgs += toolCall.function.arguments;
|
|
2428
|
-
block.arguments = parseStreamingJson(block.partialArgs);
|
|
2429
|
-
pushStreamEvent({
|
|
2430
|
-
type: "toolcall_delta",
|
|
2431
|
-
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2432
|
-
delta: toolCall.function.arguments,
|
|
2433
|
-
partial: output
|
|
2434
|
-
});
|
|
2435
|
-
}
|
|
1915
|
+
if (compatDetection.capabilities.usesExplicitProxyLikeEndpoint && clampedMaxTokens !== void 0 && effectiveContextTokens !== void 0) {
|
|
1916
|
+
const estimatedInputTokens = estimateOpenAICompletionsInputTokens(params);
|
|
1917
|
+
const remainingBudget = Math.max(1, effectiveContextTokens - estimatedInputTokens - 1);
|
|
1918
|
+
if (clampedMaxTokens > remainingBudget) {
|
|
1919
|
+
clampedMaxTokens = remainingBudget;
|
|
1920
|
+
emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} effectiveContext=${effectiveContextTokens} estimatedInput=${estimatedInputTokens}`);
|
|
2436
1921
|
}
|
|
2437
1922
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
await cooperativeScheduler.afterEvent();
|
|
1923
|
+
if (clampedMaxTokens) if (compat.maxTokensField === "max_tokens") params.max_tokens = clampedMaxTokens;
|
|
1924
|
+
else params.max_completion_tokens = clampedMaxTokens;
|
|
2441
1925
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
const
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
1926
|
+
const completionsReasoningEffort = resolveOpenAICompletionsReasoningEffort$1(options);
|
|
1927
|
+
const resolvedCompletionsReasoningEffort = completionsReasoningEffort ? resolveOpenAIReasoningEffortForModel({
|
|
1928
|
+
model,
|
|
1929
|
+
effort: completionsReasoningEffort,
|
|
1930
|
+
fallbackMap: compat.reasoningEffortMap
|
|
1931
|
+
}) : void 0;
|
|
1932
|
+
const omitChatCompletionsToolReasoningEffort = Array.isArray(params.tools) && params.tools.length > 0 && (isOpenAIGpt54MiniModel(model) || isOpenAIGpt55Model(model) && isKnownOpenAICompletionsEndpoint(model));
|
|
1933
|
+
const disableChatCompletionsToolReasoning = Array.isArray(params.tools) && params.tools.length > 0 && isOpenAIGpt56Model(model) && isKnownOpenAICompletionsEndpoint(model);
|
|
1934
|
+
const handledQwenThinkingFormat = applyQwenOpenAICompletionsThinkingParams({
|
|
1935
|
+
compatThinkingFormat: compat.thinkingFormat,
|
|
1936
|
+
modelReasoning: model.reasoning,
|
|
1937
|
+
payload: params,
|
|
1938
|
+
requestedEffort: completionsReasoningEffort
|
|
1939
|
+
});
|
|
1940
|
+
applyTogetherOpenAICompletionsThinkingParams({
|
|
1941
|
+
compatThinkingFormat: compat.thinkingFormat,
|
|
1942
|
+
modelReasoning: model.reasoning,
|
|
1943
|
+
payload: params,
|
|
1944
|
+
requestedEffort: completionsReasoningEffort
|
|
1945
|
+
});
|
|
1946
|
+
if (disableChatCompletionsToolReasoning) params.reasoning_effort = "none";
|
|
1947
|
+
else if (compat.thinkingFormat === "openrouter" && model.reasoning && resolvedCompletionsReasoningEffort) params.reasoning = { effort: resolvedCompletionsReasoningEffort };
|
|
1948
|
+
else if (resolvedCompletionsReasoningEffort && model.reasoning && compat.supportsReasoningEffort && !handledQwenThinkingFormat && !omitChatCompletionsToolReasoningEffort) params.reasoning_effort = resolvedCompletionsReasoningEffort;
|
|
1949
|
+
return params;
|
|
2457
1950
|
}
|
|
1951
|
+
//#endregion
|
|
1952
|
+
//#region packages/ai/src/transports/openai-completions-dsml.ts
|
|
2458
1953
|
const DEEPSEEK_DSML_BARS = ["|", "|"];
|
|
2459
1954
|
const DEEPSEEK_DSML_TOOL_KINDS = [
|
|
2460
1955
|
"tool_calls",
|
|
@@ -2463,42 +1958,80 @@ const DEEPSEEK_DSML_TOOL_KINDS = [
|
|
|
2463
1958
|
];
|
|
2464
1959
|
const DEEPSEEK_DSML_TOOL_OPEN_TOKENS = DEEPSEEK_DSML_BARS.flatMap((bar) => DEEPSEEK_DSML_TOOL_KINDS.map((kind) => `<${bar}DSML${bar}${kind}>`));
|
|
2465
1960
|
const DEEPSEEK_DSML_TOOL_CLOSE_TOKENS = DEEPSEEK_DSML_BARS.flatMap((bar) => DEEPSEEK_DSML_TOOL_KINDS.map((kind) => `</${bar}DSML${bar}${kind}>`));
|
|
1961
|
+
const DEEPSEEK_DSML_INVOKE_OPEN_PREFIXES = DEEPSEEK_DSML_BARS.map((bar) => `<${bar}DSML${bar}invoke`);
|
|
1962
|
+
const DEEPSEEK_DSML_INVOKE_CLOSE_TOKENS = DEEPSEEK_DSML_BARS.map((bar) => `</${bar}DSML${bar}invoke>`);
|
|
2466
1963
|
const DEEPSEEK_DSML_TOOL_MAX_OPEN_TOKEN_LEN = Math.max(...DEEPSEEK_DSML_TOOL_OPEN_TOKENS.map((token) => token.length));
|
|
2467
|
-
|
|
1964
|
+
const DEEPSEEK_DSML_RECOVERY_MAX_BOUNDARY_LEN = Math.max(...DEEPSEEK_DSML_TOOL_OPEN_TOKENS.map((token) => token.length), ...DEEPSEEK_DSML_TOOL_CLOSE_TOKENS.map((token) => token.length), ...DEEPSEEK_DSML_INVOKE_OPEN_PREFIXES.map((token) => token.length), ...DEEPSEEK_DSML_INVOKE_CLOSE_TOKENS.map((token) => token.length));
|
|
1965
|
+
const MAX_DSML_RECOVERY_BUFFER_BYTES = 256e3;
|
|
1966
|
+
const DEEPSEEK_DSML_SCAN_BATCH_CHARS = 64 * 1024;
|
|
1967
|
+
function createDsmlRecoverer() {
|
|
2468
1968
|
let buffer = "";
|
|
1969
|
+
let bufferBytes = 0;
|
|
1970
|
+
let bufferEndsWithHighSurrogate = false;
|
|
1971
|
+
let pendingScanChars = 0;
|
|
1972
|
+
let activeOpenToken = null;
|
|
1973
|
+
let blockScanState = {
|
|
1974
|
+
offset: 0,
|
|
1975
|
+
mode: "outer",
|
|
1976
|
+
invokeOpenStart: -1
|
|
1977
|
+
};
|
|
1978
|
+
const resetBlockScan = () => {
|
|
1979
|
+
activeOpenToken = null;
|
|
1980
|
+
pendingScanChars = 0;
|
|
1981
|
+
blockScanState = {
|
|
1982
|
+
offset: 0,
|
|
1983
|
+
mode: "outer",
|
|
1984
|
+
invokeOpenStart: -1
|
|
1985
|
+
};
|
|
1986
|
+
};
|
|
2469
1987
|
const consume = (final) => {
|
|
2470
1988
|
const output = [];
|
|
2471
1989
|
while (buffer) {
|
|
2472
|
-
const open =
|
|
1990
|
+
const open = activeOpenToken ? {
|
|
1991
|
+
index: 0,
|
|
1992
|
+
token: activeOpenToken
|
|
1993
|
+
} : findEarliestStringToken(buffer, DEEPSEEK_DSML_TOOL_OPEN_TOKENS);
|
|
2473
1994
|
if (!open) {
|
|
1995
|
+
resetBlockScan();
|
|
2474
1996
|
if (final) {
|
|
2475
1997
|
output.push({
|
|
2476
1998
|
kind: "text",
|
|
2477
1999
|
text: buffer
|
|
2478
2000
|
});
|
|
2479
2001
|
buffer = "";
|
|
2002
|
+
bufferBytes = 0;
|
|
2003
|
+
bufferEndsWithHighSurrogate = false;
|
|
2480
2004
|
return output;
|
|
2481
2005
|
}
|
|
2482
2006
|
const keep = longestDeepSeekDsmlToolOpenPrefixSuffixLength(buffer);
|
|
2483
2007
|
const emitLength = buffer.length - keep;
|
|
2484
2008
|
if (emitLength > 0) {
|
|
2009
|
+
const emitted = buffer.slice(0, emitLength);
|
|
2485
2010
|
output.push({
|
|
2486
2011
|
kind: "text",
|
|
2487
|
-
text:
|
|
2012
|
+
text: emitted
|
|
2488
2013
|
});
|
|
2489
|
-
|
|
2014
|
+
bufferBytes -= Buffer.byteLength(emitted, "utf8");
|
|
2015
|
+
buffer = buffer.slice(emitted.length);
|
|
2016
|
+
if (!buffer) bufferEndsWithHighSurrogate = false;
|
|
2490
2017
|
}
|
|
2491
2018
|
return output;
|
|
2492
2019
|
}
|
|
2493
2020
|
if (open.index > 0) {
|
|
2021
|
+
const prefix = buffer.slice(0, open.index);
|
|
2494
2022
|
output.push({
|
|
2495
2023
|
kind: "text",
|
|
2496
|
-
text:
|
|
2024
|
+
text: prefix
|
|
2497
2025
|
});
|
|
2498
|
-
|
|
2026
|
+
bufferBytes -= Buffer.byteLength(prefix, "utf8");
|
|
2027
|
+
buffer = buffer.slice(prefix.length);
|
|
2028
|
+
resetBlockScan();
|
|
2499
2029
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2030
|
+
activeOpenToken = open.token;
|
|
2031
|
+
if (blockScanState.offset === 0) blockScanState.offset = open.token.length;
|
|
2032
|
+
const blockScan = scanDeepSeekDsmlToolBlock(buffer, open.token.replace("<", "</"), open.token.length, blockScanState);
|
|
2033
|
+
if (blockScan.kind === "nested-open") throw new Error("Nested DeepSeek DSML recovery wrappers are not supported");
|
|
2034
|
+
const close = blockScan.kind === "close" ? blockScan : null;
|
|
2502
2035
|
if (!close) {
|
|
2503
2036
|
if (final) {
|
|
2504
2037
|
output.push({
|
|
@@ -2506,24 +2039,38 @@ function createDeepSeekDsmlToolCallRecoverer() {
|
|
|
2506
2039
|
text: buffer
|
|
2507
2040
|
});
|
|
2508
2041
|
buffer = "";
|
|
2042
|
+
bufferBytes = 0;
|
|
2043
|
+
bufferEndsWithHighSurrogate = false;
|
|
2044
|
+
return output;
|
|
2509
2045
|
}
|
|
2046
|
+
if (bufferBytes > MAX_DSML_RECOVERY_BUFFER_BYTES) throw new Error("Exceeded DeepSeek DSML recovery buffer limit");
|
|
2510
2047
|
return output;
|
|
2511
2048
|
}
|
|
2512
|
-
|
|
2513
|
-
const
|
|
2049
|
+
resetBlockScan();
|
|
2050
|
+
const body = buffer.slice(open.token.length, close.index);
|
|
2051
|
+
const blockText = buffer.slice(0, close.index + close.token.length);
|
|
2052
|
+
if (Buffer.byteLength(blockText, "utf8") > MAX_DSML_RECOVERY_BUFFER_BYTES) throw new Error("Exceeded DeepSeek DSML recovery buffer limit");
|
|
2514
2053
|
const recoveredToolCalls = parseDeepSeekDsmlToolCallBlock(body);
|
|
2515
2054
|
if (recoveredToolCalls.length > 0) output.push(...recoveredToolCalls);
|
|
2516
2055
|
else output.push({
|
|
2517
2056
|
kind: "text",
|
|
2518
|
-
text:
|
|
2057
|
+
text: blockText
|
|
2519
2058
|
});
|
|
2520
|
-
|
|
2059
|
+
bufferBytes -= Buffer.byteLength(blockText, "utf8");
|
|
2060
|
+
buffer = buffer.slice(blockText.length);
|
|
2061
|
+
if (!buffer) bufferEndsWithHighSurrogate = false;
|
|
2521
2062
|
}
|
|
2522
2063
|
return output;
|
|
2523
2064
|
};
|
|
2524
2065
|
return {
|
|
2525
2066
|
push(chunk) {
|
|
2067
|
+
const append = measureUtf8AppendBytes(bufferEndsWithHighSurrogate, chunk);
|
|
2068
|
+
bufferBytes += append.bytes;
|
|
2069
|
+
bufferEndsWithHighSurrogate = append.endsWithHighSurrogate;
|
|
2526
2070
|
buffer += chunk;
|
|
2071
|
+
pendingScanChars += chunk.length;
|
|
2072
|
+
if (activeOpenToken && pendingScanChars < DEEPSEEK_DSML_SCAN_BATCH_CHARS && !chunk.includes("<") && !chunk.includes(">") && bufferBytes <= MAX_DSML_RECOVERY_BUFFER_BYTES) return [];
|
|
2073
|
+
pendingScanChars = 0;
|
|
2527
2074
|
return consume(false);
|
|
2528
2075
|
},
|
|
2529
2076
|
flush() {
|
|
@@ -2533,16 +2080,16 @@ function createDeepSeekDsmlToolCallRecoverer() {
|
|
|
2533
2080
|
}
|
|
2534
2081
|
function parseDeepSeekDsmlToolCallBlock(body) {
|
|
2535
2082
|
const toolCalls = [];
|
|
2536
|
-
const invokeOpenRegex = /<[||]DSML[||]invoke\b([
|
|
2083
|
+
const invokeOpenRegex = /<[||]DSML[||]invoke\b([^<>]*)>/g;
|
|
2537
2084
|
let openMatch;
|
|
2538
2085
|
while ((openMatch = invokeOpenRegex.exec(body)) !== null) {
|
|
2539
|
-
const invokeName = parseXmlAttribute(openMatch[1] ?? "", "name");
|
|
2540
|
-
if (!invokeName) continue;
|
|
2541
2086
|
const invokeBodyStart = openMatch.index + openMatch[0].length;
|
|
2542
2087
|
const invokeClose = findEarliestStringToken(body.slice(invokeBodyStart), ["</|DSML|invoke>", "</|DSML|invoke>"]);
|
|
2543
|
-
if (!invokeClose)
|
|
2088
|
+
if (!invokeClose) break;
|
|
2544
2089
|
const invokeBody = body.slice(invokeBodyStart, invokeBodyStart + invokeClose.index);
|
|
2545
2090
|
invokeOpenRegex.lastIndex = invokeBodyStart + invokeClose.index + invokeClose.token.length;
|
|
2091
|
+
const invokeName = parseXmlAttribute(openMatch[1] ?? "", "name");
|
|
2092
|
+
if (!invokeName) continue;
|
|
2546
2093
|
const parsedArguments = parseDeepSeekDsmlInvokeArguments(invokeBody);
|
|
2547
2094
|
if (!parsedArguments) continue;
|
|
2548
2095
|
toolCalls.push({
|
|
@@ -2593,10 +2140,10 @@ function parseXmlAttribute(attributes, name) {
|
|
|
2593
2140
|
function decodeDeepSeekDsmlText(value) {
|
|
2594
2141
|
return value.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&");
|
|
2595
2142
|
}
|
|
2596
|
-
function findEarliestStringToken(text, tokens) {
|
|
2143
|
+
function findEarliestStringToken(text, tokens, fromIndex = 0) {
|
|
2597
2144
|
let best = null;
|
|
2598
2145
|
for (const token of tokens) {
|
|
2599
|
-
const index = text.indexOf(token);
|
|
2146
|
+
const index = text.indexOf(token, fromIndex);
|
|
2600
2147
|
if (index !== -1 && (!best || index < best.index)) best = {
|
|
2601
2148
|
index,
|
|
2602
2149
|
token
|
|
@@ -2604,6 +2151,75 @@ function findEarliestStringToken(text, tokens) {
|
|
|
2604
2151
|
}
|
|
2605
2152
|
return best;
|
|
2606
2153
|
}
|
|
2154
|
+
function scanDeepSeekDsmlToolBlock(text, closeToken, contentStartIndex, state) {
|
|
2155
|
+
while (state.offset < text.length) {
|
|
2156
|
+
if (state.mode === "invoke-open") {
|
|
2157
|
+
const nextOpen = text.indexOf("<", state.offset);
|
|
2158
|
+
const nextClose = text.indexOf(">", state.offset);
|
|
2159
|
+
if (nextClose === -1 && nextOpen === -1) {
|
|
2160
|
+
state.offset = text.length;
|
|
2161
|
+
return { kind: "incomplete" };
|
|
2162
|
+
}
|
|
2163
|
+
if (nextOpen !== -1 && (nextClose === -1 || nextOpen < nextClose)) {
|
|
2164
|
+
state.mode = "outer";
|
|
2165
|
+
state.offset = nextOpen;
|
|
2166
|
+
state.invokeOpenStart = -1;
|
|
2167
|
+
continue;
|
|
2168
|
+
}
|
|
2169
|
+
const invokeOpenTag = text.slice(state.invokeOpenStart, nextClose + 1);
|
|
2170
|
+
if (!/^<[||]DSML[||]invoke\b[^<>]*>$/.test(invokeOpenTag)) {
|
|
2171
|
+
state.mode = "outer";
|
|
2172
|
+
state.offset = state.invokeOpenStart + 1;
|
|
2173
|
+
state.invokeOpenStart = -1;
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
2176
|
+
state.mode = "invoke-body";
|
|
2177
|
+
state.offset = nextClose + 1;
|
|
2178
|
+
state.invokeOpenStart = -1;
|
|
2179
|
+
continue;
|
|
2180
|
+
}
|
|
2181
|
+
if (state.mode === "invoke-body") {
|
|
2182
|
+
const invokeClose = findEarliestStringToken(text, DEEPSEEK_DSML_INVOKE_CLOSE_TOKENS, state.offset);
|
|
2183
|
+
if (!invokeClose) {
|
|
2184
|
+
state.offset = Math.max(0, text.length - DEEPSEEK_DSML_RECOVERY_MAX_BOUNDARY_LEN + 1);
|
|
2185
|
+
return { kind: "incomplete" };
|
|
2186
|
+
}
|
|
2187
|
+
state.mode = "outer";
|
|
2188
|
+
state.offset = invokeClose.index + invokeClose.token.length;
|
|
2189
|
+
continue;
|
|
2190
|
+
}
|
|
2191
|
+
const toolOpen = findEarliestStringToken(text, DEEPSEEK_DSML_TOOL_OPEN_TOKENS, state.offset);
|
|
2192
|
+
const toolCloseIndex = text.indexOf(closeToken, state.offset);
|
|
2193
|
+
const invokeOpen = findEarliestStringToken(text, DEEPSEEK_DSML_INVOKE_OPEN_PREFIXES, state.offset);
|
|
2194
|
+
const next = [
|
|
2195
|
+
toolOpen ? {
|
|
2196
|
+
kind: "nested-open",
|
|
2197
|
+
...toolOpen
|
|
2198
|
+
} : null,
|
|
2199
|
+
toolCloseIndex === -1 ? null : {
|
|
2200
|
+
kind: "close",
|
|
2201
|
+
index: toolCloseIndex,
|
|
2202
|
+
token: closeToken
|
|
2203
|
+
},
|
|
2204
|
+
invokeOpen ? {
|
|
2205
|
+
kind: "invoke-open",
|
|
2206
|
+
...invokeOpen
|
|
2207
|
+
} : null
|
|
2208
|
+
].filter((candidate) => candidate !== null).toSorted((left, right) => left.index - right.index)[0];
|
|
2209
|
+
if (!next) {
|
|
2210
|
+
state.offset = Math.max(contentStartIndex, text.length - DEEPSEEK_DSML_RECOVERY_MAX_BOUNDARY_LEN + 1);
|
|
2211
|
+
return { kind: "incomplete" };
|
|
2212
|
+
}
|
|
2213
|
+
if (next.kind === "invoke-open") {
|
|
2214
|
+
state.mode = "invoke-open";
|
|
2215
|
+
state.invokeOpenStart = next.index;
|
|
2216
|
+
state.offset = next.index + next.token.length;
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
return next;
|
|
2220
|
+
}
|
|
2221
|
+
return { kind: "incomplete" };
|
|
2222
|
+
}
|
|
2607
2223
|
function longestDeepSeekDsmlToolOpenPrefixSuffixLength(text) {
|
|
2608
2224
|
const maxLength = Math.min(text.length, DEEPSEEK_DSML_TOOL_MAX_OPEN_TOKEN_LEN - 1);
|
|
2609
2225
|
for (let length = maxLength; length > 0; length -= 1) {
|
|
@@ -2612,725 +2228,784 @@ function longestDeepSeekDsmlToolOpenPrefixSuffixLength(text) {
|
|
|
2612
2228
|
}
|
|
2613
2229
|
return 0;
|
|
2614
2230
|
}
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
if (
|
|
2622
|
-
const
|
|
2623
|
-
|
|
2624
|
-
const
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2231
|
+
//#endregion
|
|
2232
|
+
//#region packages/ai/src/transports/openai-completions-stream.ts
|
|
2233
|
+
function extractToolCallThoughtSignature(toolCall) {
|
|
2234
|
+
const tc = toolCall;
|
|
2235
|
+
if (!tc) return;
|
|
2236
|
+
const fromExtra = (tc.extra_content?.google)?.thought_signature;
|
|
2237
|
+
if (typeof fromExtra === "string" && fromExtra.length > 0) return fromExtra;
|
|
2238
|
+
const fromFunction = tc.function?.thought_signature;
|
|
2239
|
+
if (typeof fromFunction === "string" && fromFunction.length > 0) return fromFunction;
|
|
2240
|
+
const fromToolCall = tc.thought_signature;
|
|
2241
|
+
return typeof fromToolCall === "string" && fromToolCall.length > 0 ? fromToolCall : void 0;
|
|
2242
|
+
}
|
|
2243
|
+
async function processCompletionsStream(responseStream, output, model, stream, options) {
|
|
2244
|
+
const MAX_POST_TOOL_CALL_BUFFER_BYTES = 256e3;
|
|
2245
|
+
const emitReasoning = options?.emitReasoning ?? true;
|
|
2246
|
+
const compat = getCompat(model);
|
|
2247
|
+
const shouldFilterDeepSeekDsmlText = compat.thinkingFormat === "deepseek";
|
|
2248
|
+
const deepSeekTextFilter = shouldFilterDeepSeekDsmlText ? createDeepSeekTextFilter() : null;
|
|
2249
|
+
const deepSeekToolCallRecoverer = shouldFilterDeepSeekDsmlText ? createDsmlRecoverer() : null;
|
|
2250
|
+
const reasoningTagTextPartitioner = createReasoningTagTextPartitioner();
|
|
2251
|
+
let currentBlock = null;
|
|
2252
|
+
let pendingPostToolCallDeltas = [];
|
|
2253
|
+
let pendingPostToolCallBytes = 0;
|
|
2254
|
+
let isFlushingPendingPostToolCallDeltas = false;
|
|
2255
|
+
const toolCallBlocksByIndex = /* @__PURE__ */ new Map();
|
|
2256
|
+
const toolCallBlocksById = /* @__PURE__ */ new Map();
|
|
2257
|
+
const provisionalCommentaryTags = /* @__PURE__ */ new Map();
|
|
2258
|
+
const toolCallBlockIndices = /* @__PURE__ */ new WeakMap();
|
|
2259
|
+
const normalizeToolCallDeltas = createOpenAICompletionsToolCallDeltaNormalizer();
|
|
2260
|
+
let sawStopFinishReason = false;
|
|
2261
|
+
let sawNativeToolCallDelta = false;
|
|
2262
|
+
const blockIndex = () => output.content.length - 1;
|
|
2263
|
+
const measureUtf8Bytes = (text) => Buffer.byteLength(text, "utf8");
|
|
2264
|
+
let chunkPushedEvent = false;
|
|
2265
|
+
const pushStreamEvent = (event) => {
|
|
2266
|
+
chunkPushedEvent = true;
|
|
2267
|
+
stream.push(event);
|
|
2268
|
+
};
|
|
2269
|
+
const queuePostToolCallDelta = (next) => {
|
|
2270
|
+
const nextBytes = measureUtf8Bytes(next.text);
|
|
2271
|
+
if (pendingPostToolCallBytes + nextBytes > MAX_POST_TOOL_CALL_BUFFER_BYTES) throw new Error("Exceeded post-tool-call delta buffer limit");
|
|
2272
|
+
pendingPostToolCallBytes += nextBytes;
|
|
2273
|
+
const previous = pendingPostToolCallDeltas[pendingPostToolCallDeltas.length - 1];
|
|
2274
|
+
if (!previous || previous.kind !== next.kind) {
|
|
2275
|
+
pendingPostToolCallDeltas.push(next);
|
|
2276
|
+
return;
|
|
2630
2277
|
}
|
|
2631
|
-
|
|
2278
|
+
if (next.kind === "thinking" && previous.kind === "thinking") {
|
|
2279
|
+
if (previous.signature !== next.signature) {
|
|
2280
|
+
pendingPostToolCallDeltas.push(next);
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
previous.text += next.text;
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
previous.text += next.text;
|
|
2287
|
+
};
|
|
2288
|
+
const appendThinkingDeltaInternal = (reasoningDelta) => {
|
|
2289
|
+
if (!currentBlock || currentBlock.type !== "thinking") {
|
|
2290
|
+
currentBlock = {
|
|
2291
|
+
type: "thinking",
|
|
2292
|
+
thinking: "",
|
|
2293
|
+
...reasoningDelta.signature ? { thinkingSignature: reasoningDelta.signature } : {}
|
|
2294
|
+
};
|
|
2295
|
+
output.content.push(currentBlock);
|
|
2296
|
+
pushStreamEvent({
|
|
2297
|
+
type: "thinking_start",
|
|
2298
|
+
contentIndex: blockIndex(),
|
|
2299
|
+
partial: output
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
currentBlock.thinking += reasoningDelta.text;
|
|
2303
|
+
pushStreamEvent({
|
|
2304
|
+
type: "thinking_delta",
|
|
2305
|
+
contentIndex: blockIndex(),
|
|
2306
|
+
delta: reasoningDelta.text,
|
|
2307
|
+
partial: output
|
|
2308
|
+
});
|
|
2309
|
+
};
|
|
2310
|
+
const appendTextDeltaInternal = (text) => {
|
|
2311
|
+
if (!currentBlock || currentBlock.type !== "text") {
|
|
2312
|
+
currentBlock = {
|
|
2313
|
+
type: "text",
|
|
2314
|
+
text: ""
|
|
2315
|
+
};
|
|
2316
|
+
output.content.push(currentBlock);
|
|
2317
|
+
pushStreamEvent({
|
|
2318
|
+
type: "text_start",
|
|
2319
|
+
contentIndex: blockIndex(),
|
|
2320
|
+
partial: output
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
currentBlock.text += text;
|
|
2324
|
+
pushStreamEvent({
|
|
2325
|
+
type: "text_delta",
|
|
2326
|
+
contentIndex: blockIndex(),
|
|
2327
|
+
delta: text
|
|
2328
|
+
});
|
|
2329
|
+
};
|
|
2330
|
+
const flushPendingPostToolCallDeltas = () => {
|
|
2331
|
+
if (isFlushingPendingPostToolCallDeltas || currentBlock?.type === "toolCall" || pendingPostToolCallDeltas.length === 0) return;
|
|
2332
|
+
isFlushingPendingPostToolCallDeltas = true;
|
|
2333
|
+
const bufferedDeltas = pendingPostToolCallDeltas;
|
|
2334
|
+
pendingPostToolCallDeltas = [];
|
|
2335
|
+
pendingPostToolCallBytes = 0;
|
|
2336
|
+
for (const delta of bufferedDeltas) if (delta.kind === "text") appendTextDeltaInternal(delta.text);
|
|
2337
|
+
else if (emitReasoning) appendThinkingDeltaInternal(delta);
|
|
2338
|
+
isFlushingPendingPostToolCallDeltas = false;
|
|
2339
|
+
};
|
|
2340
|
+
const appendThinkingDelta = (reasoningDelta) => {
|
|
2341
|
+
flushPendingPostToolCallDeltas();
|
|
2342
|
+
appendThinkingDeltaInternal(reasoningDelta);
|
|
2632
2343
|
};
|
|
2633
|
-
const
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
text
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
const previous = output[output.length - 1];
|
|
2650
|
-
if (!previous || previous.kind !== next.kind) {
|
|
2651
|
-
output.push(next);
|
|
2652
|
-
return;
|
|
2344
|
+
const appendTextDelta = (text) => {
|
|
2345
|
+
flushPendingPostToolCallDeltas();
|
|
2346
|
+
appendTextDeltaInternal(text);
|
|
2347
|
+
};
|
|
2348
|
+
const appendVisibleTextDelta = (text) => {
|
|
2349
|
+
if (!text) return;
|
|
2350
|
+
if (currentBlock?.type === "toolCall") queuePostToolCallDelta({
|
|
2351
|
+
kind: "text",
|
|
2352
|
+
text
|
|
2353
|
+
});
|
|
2354
|
+
else appendTextDelta(text);
|
|
2355
|
+
};
|
|
2356
|
+
const appendRecoveredToolCall = (toolCall) => {
|
|
2357
|
+
if (currentBlock?.type === "toolCall") {
|
|
2358
|
+
currentBlock = null;
|
|
2359
|
+
flushPendingPostToolCallDeltas();
|
|
2653
2360
|
}
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2361
|
+
rememberPendingCommentaryTags(provisionalCommentaryTags, tagPendingCommentaryText(output.content));
|
|
2362
|
+
const block = {
|
|
2363
|
+
type: "toolCall",
|
|
2364
|
+
id: `call_${randomUUID().replaceAll("-", "").slice(0, 24)}`,
|
|
2365
|
+
name: toolCall.name,
|
|
2366
|
+
arguments: toolCall.arguments,
|
|
2367
|
+
partialArgs: toolCall.partialArgs
|
|
2368
|
+
};
|
|
2369
|
+
currentBlock = block;
|
|
2370
|
+
output.content.push(block);
|
|
2371
|
+
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
2372
|
+
pushStreamEvent({
|
|
2373
|
+
type: "toolcall_start",
|
|
2374
|
+
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2375
|
+
partial: output
|
|
2376
|
+
});
|
|
2377
|
+
pushStreamEvent({
|
|
2378
|
+
type: "toolcall_delta",
|
|
2379
|
+
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2380
|
+
delta: toolCall.partialArgs,
|
|
2381
|
+
partial: output
|
|
2382
|
+
});
|
|
2383
|
+
};
|
|
2384
|
+
const appendFilteredVisibleTextDelta = (text) => {
|
|
2385
|
+
const recoveredParts = deepSeekToolCallRecoverer?.push(text) ?? [{
|
|
2386
|
+
kind: "text",
|
|
2387
|
+
text
|
|
2388
|
+
}];
|
|
2389
|
+
for (const recoveredPart of recoveredParts) {
|
|
2390
|
+
if (recoveredPart.kind === "toolCall") {
|
|
2391
|
+
appendRecoveredToolCall(recoveredPart);
|
|
2392
|
+
continue;
|
|
2658
2393
|
}
|
|
2659
|
-
|
|
2660
|
-
|
|
2394
|
+
const parts = deepSeekTextFilter?.push(recoveredPart.text) ?? [recoveredPart.text];
|
|
2395
|
+
for (const part of parts) appendVisibleTextDelta(part);
|
|
2661
2396
|
}
|
|
2662
|
-
previous.text += next.text;
|
|
2663
2397
|
};
|
|
2664
|
-
const
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
if (typeof detail.text !== "string" || !detail.text) continue;
|
|
2671
|
-
if (detail.type === "reasoning.text") {
|
|
2672
|
-
usedReasoningThinkingDetails = true;
|
|
2673
|
-
pushDelta({
|
|
2674
|
-
kind: "thinking",
|
|
2675
|
-
signature: "reasoning_details",
|
|
2676
|
-
text: detail.text
|
|
2677
|
-
});
|
|
2398
|
+
const flushDeepSeekToolCallRecovererAtEnd = () => {
|
|
2399
|
+
const recoveredParts = deepSeekToolCallRecoverer?.flush();
|
|
2400
|
+
if (!recoveredParts) return;
|
|
2401
|
+
for (const recoveredPart of recoveredParts) {
|
|
2402
|
+
if (recoveredPart.kind === "toolCall") {
|
|
2403
|
+
appendRecoveredToolCall(recoveredPart);
|
|
2678
2404
|
continue;
|
|
2679
2405
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
text: detail.text
|
|
2683
|
-
});
|
|
2406
|
+
const parts = deepSeekTextFilter?.push(recoveredPart.text) ?? [recoveredPart.text];
|
|
2407
|
+
for (const part of parts) appendVisibleTextDelta(part);
|
|
2684
2408
|
}
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
if (
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
signature: field,
|
|
2696
|
-
text: value
|
|
2697
|
-
});
|
|
2698
|
-
break;
|
|
2409
|
+
};
|
|
2410
|
+
const flushDeepSeekTextFilterAtEnd = () => {
|
|
2411
|
+
const parts = deepSeekTextFilter?.flush();
|
|
2412
|
+
if (!parts) return;
|
|
2413
|
+
for (const part of parts) appendVisibleTextDelta(part);
|
|
2414
|
+
};
|
|
2415
|
+
const appendRoutedContentDelta = (delta) => {
|
|
2416
|
+
if (delta.kind === "text") {
|
|
2417
|
+
appendFilteredVisibleTextDelta(delta.text);
|
|
2418
|
+
return;
|
|
2699
2419
|
}
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
function resolveOpenAICompletionsReasoningEffort(options) {
|
|
2704
|
-
return options?.reasoningEffort ?? options?.reasoning ?? "high";
|
|
2705
|
-
}
|
|
2706
|
-
function shouldEmitOpenAICompletionsReasoning(model, options) {
|
|
2707
|
-
if (!model.reasoning) return false;
|
|
2708
|
-
const effort = resolveOpenAICompletionsReasoningEffort(options);
|
|
2709
|
-
if (!effort || !isOpenAICompletionsThinkingEnabled(effort)) return false;
|
|
2710
|
-
return true;
|
|
2711
|
-
}
|
|
2712
|
-
function shouldEmitOpenAICompletionsReasoningForModel(model, options) {
|
|
2713
|
-
return shouldEmitOpenAICompletionsReasoning(model, options);
|
|
2714
|
-
}
|
|
2715
|
-
function resolveOpenAICompletionsMaxTokens(model, options) {
|
|
2716
|
-
if (options?.maxTokens) return {
|
|
2717
|
-
maxTokens: options.maxTokens,
|
|
2718
|
-
clampToModelMaxTokens: true
|
|
2420
|
+
if (!emitReasoning) return;
|
|
2421
|
+
if (currentBlock?.type === "toolCall") queuePostToolCallDelta(delta);
|
|
2422
|
+
else appendThinkingDelta(delta);
|
|
2719
2423
|
};
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
maxTokens: paramsMaxTokens,
|
|
2723
|
-
clampToModelMaxTokens: false
|
|
2424
|
+
const appendPartitionedVisibleDelta = (delta) => {
|
|
2425
|
+
if (delta.kind === "text") appendFilteredVisibleTextDelta(delta.text);
|
|
2724
2426
|
};
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2427
|
+
const emitReasoningUsageActivity = (hasReasoningUsageActivity) => {
|
|
2428
|
+
if (!hasReasoningUsageActivity || chunkPushedEvent || !emitReasoning) return;
|
|
2429
|
+
const latestBlock = output.content[output.content.length - 1];
|
|
2430
|
+
if (currentBlock?.type === "text" || currentBlock?.type === "toolCall") return;
|
|
2431
|
+
if (latestBlock?.type === "text" || latestBlock?.type === "toolCall") return;
|
|
2432
|
+
appendThinkingDelta({ text: "" });
|
|
2728
2433
|
};
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
const
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
}
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
if (
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2434
|
+
const flushReasoningTagTextPartitionerAtEnd = () => {
|
|
2435
|
+
for (const delta of reasoningTagTextPartitioner.flush()) appendPartitionedVisibleDelta(delta);
|
|
2436
|
+
};
|
|
2437
|
+
const cooperativeScheduler = createModelStreamCooperativeScheduler(options?.signal);
|
|
2438
|
+
const guardedStream = withFirstStreamEventTimeout(responseStream, {
|
|
2439
|
+
provider: model.provider,
|
|
2440
|
+
api: model.api,
|
|
2441
|
+
model: model.id,
|
|
2442
|
+
timeoutMs: options?.firstEventTimeoutMs ?? 0,
|
|
2443
|
+
stage: "completions",
|
|
2444
|
+
abort: options?.abortFirstEventStream,
|
|
2445
|
+
onTimeout: options?.onFirstEventTimeout,
|
|
2446
|
+
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."
|
|
2447
|
+
});
|
|
2448
|
+
for await (const rawChunk of guardedStream) {
|
|
2449
|
+
throwIfModelStreamAborted(options?.signal);
|
|
2450
|
+
chunkPushedEvent = false;
|
|
2451
|
+
if (!rawChunk || typeof rawChunk !== "object") {
|
|
2452
|
+
await cooperativeScheduler.afterEvent();
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
notifyLlmRequestActivity(options?.signal);
|
|
2456
|
+
const chunk = rawChunk;
|
|
2457
|
+
output.responseId ||= chunk.id;
|
|
2458
|
+
let hasReasoningUsageActivity = false;
|
|
2459
|
+
if (chunk.usage) {
|
|
2460
|
+
output.usage = parseOpenAICompletionsUsage(chunk.usage, model);
|
|
2461
|
+
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(chunk.usage);
|
|
2462
|
+
}
|
|
2463
|
+
const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : void 0;
|
|
2464
|
+
if (!choice) {
|
|
2465
|
+
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
2466
|
+
await cooperativeScheduler.afterEvent();
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2469
|
+
const choiceUsage = choice.usage;
|
|
2470
|
+
if (!chunk.usage && choiceUsage) {
|
|
2471
|
+
output.usage = parseOpenAICompletionsUsage(choiceUsage, model);
|
|
2472
|
+
hasReasoningUsageActivity = hasOpenAICompletionsReasoningUsageActivity(choiceUsage);
|
|
2473
|
+
}
|
|
2474
|
+
if (choice.finish_reason) {
|
|
2475
|
+
const finishReasonResult = mapOpenAIStopReason(choice.finish_reason, { allowSingularToolCall: true });
|
|
2476
|
+
output.stopReason = finishReasonResult.stopReason;
|
|
2477
|
+
if (finishReasonResult.stopReason === "stop") sawStopFinishReason = true;
|
|
2478
|
+
if (finishReasonResult.errorMessage) output.errorMessage = finishReasonResult.errorMessage;
|
|
2479
|
+
}
|
|
2480
|
+
const rawChoiceDelta = choice.delta ?? choice.message;
|
|
2481
|
+
if (!rawChoiceDelta) {
|
|
2482
|
+
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
2483
|
+
await cooperativeScheduler.afterEvent();
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
for (const normalizedDelta of normalizeToolCallDeltas(rawChoiceDelta, choice.finish_reason)) {
|
|
2487
|
+
const choiceDelta = normalizedDelta.delta;
|
|
2488
|
+
const reasoningDeltas = getCompletionsReasoningDeltas(choiceDelta, compat.visibleReasoningDetailTypes);
|
|
2489
|
+
const hasMirroredReasoning = reasoningDeltas.some((delta) => delta.kind === "thinking");
|
|
2490
|
+
if (hasMirroredReasoning) reasoningTagTextPartitioner.markStrict();
|
|
2491
|
+
const contentDeltas = readOpenAICompletionsContentDeltas(choiceDelta.content, choiceDelta.refusal, reasoningDeltas.filter((reasoningDelta) => reasoningDelta.kind === "thinking").map((reasoningDelta) => reasoningDelta.text));
|
|
2492
|
+
const appendReasoningDeltas = () => {
|
|
2493
|
+
for (const reasoningDelta of reasoningDeltas) {
|
|
2494
|
+
if (reasoningDelta.kind === "thinking" && !emitReasoning) continue;
|
|
2495
|
+
if (currentBlock?.type === "toolCall") {
|
|
2496
|
+
queuePostToolCallDelta({ ...reasoningDelta });
|
|
2497
|
+
continue;
|
|
2498
|
+
}
|
|
2499
|
+
if (reasoningDelta.kind === "text") appendTextDelta(reasoningDelta.text);
|
|
2500
|
+
else if (emitReasoning) appendThinkingDelta(reasoningDelta);
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
if (hasMirroredReasoning) appendReasoningDeltas();
|
|
2504
|
+
for (const contentDelta of contentDeltas) if (contentDelta.kind === "text") {
|
|
2505
|
+
const routedDeltas = hasMirroredReasoning ? reasoningTagTextPartitioner.push(contentDelta.text) : reasoningTagTextPartitioner.pushVisible(contentDelta.text);
|
|
2506
|
+
for (const routedDelta of routedDeltas) appendPartitionedVisibleDelta(routedDelta);
|
|
2507
|
+
} else {
|
|
2508
|
+
if (reasoningTagTextPartitioner.hasPending()) reasoningTagTextPartitioner.markStrict();
|
|
2509
|
+
appendRoutedContentDelta(contentDelta);
|
|
2510
|
+
}
|
|
2511
|
+
if (!hasMirroredReasoning) appendReasoningDeltas();
|
|
2512
|
+
const toolCallDeltas = normalizedDelta.toolCalls;
|
|
2513
|
+
if (toolCallDeltas.length > 0) {
|
|
2514
|
+
sawNativeToolCallDelta = true;
|
|
2515
|
+
flushReasoningTagTextPartitionerAtEnd();
|
|
2516
|
+
rememberPendingCommentaryTags(provisionalCommentaryTags, tagPendingCommentaryText(output.content));
|
|
2517
|
+
for (const toolCall of toolCallDeltas) {
|
|
2518
|
+
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : void 0;
|
|
2519
|
+
let block = streamIndex !== void 0 ? toolCallBlocksByIndex.get(streamIndex) : void 0;
|
|
2520
|
+
if (!block && toolCall.id) block = toolCallBlocksById.get(toolCall.id);
|
|
2521
|
+
if (!block) {
|
|
2522
|
+
if (currentBlock?.type === "toolCall") {
|
|
2523
|
+
currentBlock = null;
|
|
2524
|
+
flushPendingPostToolCallDeltas();
|
|
2525
|
+
}
|
|
2526
|
+
const initialSig = extractToolCallThoughtSignature(toolCall);
|
|
2527
|
+
block = {
|
|
2528
|
+
type: "toolCall",
|
|
2529
|
+
id: toolCall.id || "",
|
|
2530
|
+
name: toolCall.function?.name || "",
|
|
2531
|
+
arguments: {},
|
|
2532
|
+
partialArgs: "",
|
|
2533
|
+
...initialSig ? { thoughtSignature: initialSig } : {}
|
|
2534
|
+
};
|
|
2535
|
+
output.content.push(block);
|
|
2536
|
+
toolCallBlockIndices.set(block, output.content.length - 1);
|
|
2537
|
+
pushStreamEvent({
|
|
2538
|
+
type: "toolcall_start",
|
|
2539
|
+
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2540
|
+
partial: output
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
if (streamIndex !== void 0 && !toolCallBlocksByIndex.has(streamIndex)) toolCallBlocksByIndex.set(streamIndex, block);
|
|
2544
|
+
if (toolCall.id) {
|
|
2545
|
+
block.id = toolCall.id;
|
|
2546
|
+
toolCallBlocksById.set(toolCall.id, block);
|
|
2547
|
+
}
|
|
2548
|
+
currentBlock = block;
|
|
2549
|
+
if (toolCall.function?.name) block.name = toolCall.function.name;
|
|
2550
|
+
const deltaSig = extractToolCallThoughtSignature(toolCall);
|
|
2551
|
+
if (deltaSig) block.thoughtSignature = deltaSig;
|
|
2552
|
+
if (toolCall.function?.arguments) {
|
|
2553
|
+
block.partialArgs += toolCall.function.arguments;
|
|
2554
|
+
block.arguments = parseStreamingJson(block.partialArgs);
|
|
2555
|
+
pushStreamEvent({
|
|
2556
|
+
type: "toolcall_delta",
|
|
2557
|
+
contentIndex: toolCallBlockIndices.get(block) ?? -1,
|
|
2558
|
+
delta: toolCall.function.arguments,
|
|
2559
|
+
partial: output
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2762
2564
|
}
|
|
2565
|
+
flushPendingPostToolCallDeltas();
|
|
2566
|
+
emitReasoningUsageActivity(hasReasoningUsageActivity);
|
|
2567
|
+
await cooperativeScheduler.afterEvent();
|
|
2763
2568
|
}
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
if (typeof text === "string") {
|
|
2779
|
-
adjustedChars += estimateStringChars(text);
|
|
2780
|
-
continue;
|
|
2781
|
-
}
|
|
2782
|
-
try {
|
|
2783
|
-
adjustedChars += estimateStringChars(JSON.stringify(block));
|
|
2784
|
-
} catch {
|
|
2785
|
-
adjustedChars += 256;
|
|
2569
|
+
flushReasoningTagTextPartitionerAtEnd();
|
|
2570
|
+
flushDeepSeekToolCallRecovererAtEnd();
|
|
2571
|
+
flushDeepSeekTextFilterAtEnd();
|
|
2572
|
+
currentBlock = null;
|
|
2573
|
+
flushPendingPostToolCallDeltas();
|
|
2574
|
+
finalizeOpenAICompletionsToolCalls(output, {
|
|
2575
|
+
allowSilentToolCallPromotion: sawStopFinishReason || sawNativeToolCallDelta && (options?.sawStreamDONE?.() ?? false),
|
|
2576
|
+
onConfirmedToolCall(block, contentIndex) {
|
|
2577
|
+
pushStreamEvent({
|
|
2578
|
+
type: "toolcall_end",
|
|
2579
|
+
contentIndex,
|
|
2580
|
+
toolCall: block,
|
|
2581
|
+
partial: output
|
|
2582
|
+
});
|
|
2786
2583
|
}
|
|
2787
|
-
}
|
|
2788
|
-
return adjustedChars;
|
|
2789
|
-
}
|
|
2790
|
-
function resolveOpenAICompletionsEffectiveContextTokens(model) {
|
|
2791
|
-
const contextTokens = model.contextTokens;
|
|
2792
|
-
if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens > 0) return contextTokens;
|
|
2793
|
-
return typeof model.contextWindow === "number" && Number.isFinite(model.contextWindow) && model.contextWindow > 0 ? model.contextWindow : void 0;
|
|
2794
|
-
}
|
|
2795
|
-
function isQwenOpenAICompletionsThinkingFormat(format) {
|
|
2796
|
-
return format === "qwen" || format === "qwen-chat-template";
|
|
2797
|
-
}
|
|
2798
|
-
function isOpenAICompletionsThinkingEnabled(effort) {
|
|
2799
|
-
const normalized = effort.trim().toLowerCase();
|
|
2800
|
-
return normalized !== "off" && normalized !== "none";
|
|
2801
|
-
}
|
|
2802
|
-
function setQwenChatTemplateThinking(params, enabled) {
|
|
2803
|
-
const existing = params.chat_template_kwargs;
|
|
2804
|
-
params.chat_template_kwargs = existing && typeof existing === "object" && !Array.isArray(existing) ? {
|
|
2805
|
-
...existing,
|
|
2806
|
-
enable_thinking: enabled
|
|
2807
|
-
} : { enable_thinking: enabled };
|
|
2808
|
-
}
|
|
2809
|
-
function applyQwenOpenAICompletionsThinkingParams(params) {
|
|
2810
|
-
if (!params.modelReasoning || !isQwenOpenAICompletionsThinkingFormat(params.compatThinkingFormat)) return false;
|
|
2811
|
-
const enabled = isOpenAICompletionsThinkingEnabled(params.requestedEffort);
|
|
2812
|
-
if (params.compatThinkingFormat === "qwen-chat-template") setQwenChatTemplateThinking(params.payload, enabled);
|
|
2813
|
-
else params.payload.enable_thinking = enabled;
|
|
2814
|
-
return true;
|
|
2815
|
-
}
|
|
2816
|
-
function applyTogetherOpenAICompletionsThinkingParams(params) {
|
|
2817
|
-
if (!params.modelReasoning || params.compatThinkingFormat !== "together") return false;
|
|
2818
|
-
params.payload.reasoning = { enabled: isOpenAICompletionsThinkingEnabled(params.requestedEffort) };
|
|
2819
|
-
return true;
|
|
2820
|
-
}
|
|
2821
|
-
function convertTools(tools, compat, model) {
|
|
2822
|
-
const projection = projectOpenAITools(tools);
|
|
2823
|
-
const strict = resolveOpenAIStrictToolFlagWithDiagnostics(projection, resolveOpenAIStrictToolSetting(model, {
|
|
2824
|
-
transport: "stream",
|
|
2825
|
-
supportsStrictMode: compat?.supportsStrictMode
|
|
2826
|
-
}), {
|
|
2827
|
-
transport: "completions",
|
|
2828
|
-
model
|
|
2829
2584
|
});
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
tools: sortPromptCacheToolsByName(projection.tools).map((tool) => {
|
|
2833
|
-
const functionTool = {
|
|
2834
|
-
name: tool.name,
|
|
2835
|
-
description: tool.description,
|
|
2836
|
-
parameters: normalizeOpenAIStrictToolParameters(tool.parameters, strict === true, model.compat)
|
|
2837
|
-
};
|
|
2838
|
-
if (strict !== void 0) functionTool.strict = strict;
|
|
2839
|
-
return {
|
|
2840
|
-
type: "function",
|
|
2841
|
-
function: functionTool
|
|
2842
|
-
};
|
|
2843
|
-
})
|
|
2844
|
-
};
|
|
2845
|
-
}
|
|
2846
|
-
function extractGoogleThoughtSignature(toolCall) {
|
|
2847
|
-
const tc = toolCall;
|
|
2848
|
-
if (!tc) return;
|
|
2849
|
-
const fromExtra = (tc.extra_content?.google)?.thought_signature;
|
|
2850
|
-
if (typeof fromExtra === "string" && fromExtra.length > 0) return fromExtra;
|
|
2851
|
-
const fromFunction = tc.function?.thought_signature;
|
|
2852
|
-
return typeof fromFunction === "string" && fromFunction.length > 0 ? fromFunction : void 0;
|
|
2853
|
-
}
|
|
2854
|
-
function isGoogleOpenAICompatModel(model) {
|
|
2855
|
-
const endpointClass = detectOpenAICompletionsCompat(model).capabilities.endpointClass;
|
|
2856
|
-
return model.provider === "google" || endpointClass === "google-generative-ai" || endpointClass === "google-vertex";
|
|
2857
|
-
}
|
|
2858
|
-
function requiresGoogleCompatToolCallThoughtSignature(model) {
|
|
2859
|
-
return isGoogleGemini3ProModel(model.id) || isGoogleGemini3FlashModel(model.id);
|
|
2860
|
-
}
|
|
2861
|
-
const GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE = /[\u2026]|\.\.\./;
|
|
2862
|
-
const GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE = /^[A-Za-z0-9+/=]+$/;
|
|
2863
|
-
function hasGoogleCompatThoughtSignatureTruncationFootprint(value) {
|
|
2864
|
-
return GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE.test(value) || GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE.test(value) && value.length % 4 !== 0;
|
|
2585
|
+
if (output.stopReason !== "toolUse") clearPendingCommentaryText(provisionalCommentaryTags);
|
|
2586
|
+
if (output.stopReason === "toolUse") tagPendingCommentaryText(output.content);
|
|
2865
2587
|
}
|
|
2866
|
-
function
|
|
2867
|
-
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
if (
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
if (typeof id === "string" && typeof sig === "string" && sig.length > 0) {
|
|
2879
|
-
const isSameRoute = source.api === model.api && source.provider === model.provider && source.model === model.id;
|
|
2880
|
-
if (!isSameRoute && !fallbackSig) continue;
|
|
2881
|
-
sigById.set(id, isSameRoute ? sig : fallbackSig ?? sig);
|
|
2588
|
+
function getCompletionsReasoningDeltas(delta, visibleReasoningDetailTypes) {
|
|
2589
|
+
const output = [];
|
|
2590
|
+
const pushDelta = (next) => {
|
|
2591
|
+
const previous = output[output.length - 1];
|
|
2592
|
+
if (!previous || previous.kind !== next.kind) {
|
|
2593
|
+
output.push(next);
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
if (next.kind === "thinking" && previous.kind === "thinking") {
|
|
2597
|
+
if (previous.signature !== next.signature) {
|
|
2598
|
+
output.push(next);
|
|
2599
|
+
return;
|
|
2882
2600
|
}
|
|
2601
|
+
previous.text += next.text;
|
|
2602
|
+
return;
|
|
2883
2603
|
}
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
if (
|
|
2894
|
-
|
|
2604
|
+
previous.text += next.text;
|
|
2605
|
+
};
|
|
2606
|
+
const reasoningDetails = delta.reasoning_details;
|
|
2607
|
+
let usedReasoningThinkingDetails = false;
|
|
2608
|
+
if (Array.isArray(reasoningDetails)) {
|
|
2609
|
+
const visibleTypes = new Set(visibleReasoningDetailTypes);
|
|
2610
|
+
for (const item of reasoningDetails) {
|
|
2611
|
+
const detail = item;
|
|
2612
|
+
if (typeof detail.text !== "string" || !detail.text) continue;
|
|
2613
|
+
if (detail.type === "reasoning.text") {
|
|
2614
|
+
usedReasoningThinkingDetails = true;
|
|
2615
|
+
pushDelta({
|
|
2616
|
+
kind: "thinking",
|
|
2617
|
+
signature: "reasoning_details",
|
|
2618
|
+
text: detail.text
|
|
2619
|
+
});
|
|
2620
|
+
continue;
|
|
2895
2621
|
}
|
|
2896
|
-
if (typeof
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
extra.google = google;
|
|
2901
|
-
google.thought_signature = sig;
|
|
2622
|
+
if (typeof detail.type === "string" && visibleTypes.has(detail.type)) pushDelta({
|
|
2623
|
+
kind: "text",
|
|
2624
|
+
text: detail.text
|
|
2625
|
+
});
|
|
2902
2626
|
}
|
|
2903
2627
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
delete record.reasoning_details;
|
|
2919
|
-
} else if (reasoningDetails !== void 0 && !Array.isArray(reasoningDetails)) delete record.reasoning_details;
|
|
2920
|
-
if ("reasoning" in record && (typeof record.reasoning !== "string" || record.reasoning === "")) delete record.reasoning;
|
|
2921
|
-
if ("reasoning_content" in record && (typeof record.reasoning_content !== "string" || record.reasoning_content === "")) delete record.reasoning_content;
|
|
2922
|
-
const reasoningText = record.reasoning_text;
|
|
2923
|
-
if (typeof reasoningText === "string" && reasoningText.length > 0 && typeof record.reasoning !== "string" && typeof record.reasoning_content !== "string") record.reasoning = reasoningText;
|
|
2924
|
-
if ("reasoning_text" in record) delete record.reasoning_text;
|
|
2925
|
-
}
|
|
2926
|
-
function sanitizeReasoningContentReplayFields(record) {
|
|
2927
|
-
if ("reasoning_content" in record && typeof record.reasoning_content !== "string") delete record.reasoning_content;
|
|
2928
|
-
delete record.reasoning_details;
|
|
2929
|
-
delete record.reasoning;
|
|
2930
|
-
delete record.reasoning_text;
|
|
2931
|
-
}
|
|
2932
|
-
const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([
|
|
2933
|
-
"deepseek-v4-flash",
|
|
2934
|
-
"deepseek-v4-pro",
|
|
2935
|
-
"kimi-for-coding",
|
|
2936
|
-
"kimi-k2.5",
|
|
2937
|
-
"kimi-k2.6",
|
|
2938
|
-
"kimi-k2.7-code",
|
|
2939
|
-
"kimi-k2.7-code-highspeed",
|
|
2940
|
-
"kimi-k3",
|
|
2941
|
-
"kimi-k2-thinking",
|
|
2942
|
-
"kimi-k2-thinking-turbo",
|
|
2943
|
-
"mimo-v2-pro",
|
|
2944
|
-
"mimo-v2-omni",
|
|
2945
|
-
"mimo-v2.5",
|
|
2946
|
-
"mimo-v2.5-pro",
|
|
2947
|
-
"mimo-v2.6-pro"
|
|
2948
|
-
]);
|
|
2949
|
-
const REASONING_CONTENT_REPLAY_TIER_SUFFIXES = [
|
|
2950
|
-
"-free",
|
|
2951
|
-
"-paid",
|
|
2952
|
-
"-trial"
|
|
2953
|
-
];
|
|
2954
|
-
function stripReasoningContentReplayTierSuffix(modelId) {
|
|
2955
|
-
for (const suffix of REASONING_CONTENT_REPLAY_TIER_SUFFIXES) if (modelId.length > suffix.length && modelId.endsWith(suffix)) return modelId.slice(0, -suffix.length);
|
|
2956
|
-
return modelId;
|
|
2957
|
-
}
|
|
2958
|
-
function getReasoningContentReplayModelIdCandidates(modelId) {
|
|
2959
|
-
if (typeof modelId !== "string") return [];
|
|
2960
|
-
const normalized = modelId.trim().toLowerCase();
|
|
2961
|
-
if (!normalized) return [];
|
|
2962
|
-
const parts = normalized.split("/").filter(Boolean);
|
|
2963
|
-
const finalPart = parts[parts.length - 1] ?? normalized;
|
|
2964
|
-
const candidates = [finalPart];
|
|
2965
|
-
const colonParts = finalPart.split(":").filter(Boolean);
|
|
2966
|
-
if (colonParts.length > 1) candidates.push(colonParts[0] ?? "", colonParts[colonParts.length - 1] ?? "");
|
|
2967
|
-
const baseCount = candidates.length;
|
|
2968
|
-
for (let index = 0; index < baseCount; index += 1) {
|
|
2969
|
-
const candidate = candidates[index];
|
|
2970
|
-
if (typeof candidate !== "string") continue;
|
|
2971
|
-
const stripped = stripReasoningContentReplayTierSuffix(candidate);
|
|
2972
|
-
if (stripped !== candidate) candidates.push(stripped);
|
|
2628
|
+
if (!usedReasoningThinkingDetails) for (const field of [
|
|
2629
|
+
"reasoning_content",
|
|
2630
|
+
"reasoning",
|
|
2631
|
+
"reasoning_text"
|
|
2632
|
+
]) {
|
|
2633
|
+
const value = delta[field];
|
|
2634
|
+
if (typeof value === "string" && value.length > 0) {
|
|
2635
|
+
pushDelta({
|
|
2636
|
+
kind: "thinking",
|
|
2637
|
+
signature: field,
|
|
2638
|
+
text: value
|
|
2639
|
+
});
|
|
2640
|
+
break;
|
|
2641
|
+
}
|
|
2973
2642
|
}
|
|
2974
|
-
return
|
|
2975
|
-
}
|
|
2976
|
-
function shouldPreserveReasoningContentReplay(model, compat) {
|
|
2977
|
-
if (compat.requiresReasoningContentOnAssistantMessages || compat.thinkingFormat === "deepseek" || compat.thinkingFormat === "zai" || shouldTrustReasoningContentReplayMetadata(model)) return true;
|
|
2978
|
-
return getReasoningContentReplayModelIdCandidates(model.id).some((modelId) => REASONING_CONTENT_REPLAY_MODEL_IDS.has(modelId));
|
|
2643
|
+
return output;
|
|
2979
2644
|
}
|
|
2980
|
-
function
|
|
2981
|
-
|
|
2982
|
-
const normalizedModelId = model.id.trim().toLowerCase();
|
|
2983
|
-
return !(normalizedModelId.startsWith("anthropic/") || normalizedModelId.startsWith("x-ai/"));
|
|
2645
|
+
function resolveOpenAICompletionsReasoningEffort(options) {
|
|
2646
|
+
return options?.reasoningEffort ?? options?.reasoning ?? "high";
|
|
2984
2647
|
}
|
|
2985
|
-
function
|
|
2648
|
+
function shouldEmitOpenAICompletionsReasoning(model, options) {
|
|
2986
2649
|
if (!model.reasoning) return false;
|
|
2987
|
-
|
|
2988
|
-
|
|
2650
|
+
const effort = resolveOpenAICompletionsReasoningEffort(options);
|
|
2651
|
+
if (!effort || !isOpenAICompletionsThinkingEnabled(effort)) return false;
|
|
2652
|
+
return true;
|
|
2989
2653
|
}
|
|
2990
|
-
function
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
if (!msg || typeof msg !== "object") continue;
|
|
2994
|
-
const record = msg;
|
|
2995
|
-
if (record.role !== "assistant") continue;
|
|
2996
|
-
if (options.preserveOpenRouterReasoning) sanitizeOpenRouterReasoningReplayFields(record);
|
|
2997
|
-
else if (options.preserveReasoningContent) sanitizeReasoningContentReplayFields(record);
|
|
2998
|
-
else stripCompletionsReasoningReplayFields(record);
|
|
2999
|
-
}
|
|
2654
|
+
function hasOpenAICompletionsReasoningUsageActivity(rawUsage) {
|
|
2655
|
+
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens;
|
|
2656
|
+
return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) && reasoningTokens > 0;
|
|
3000
2657
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
const
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
stream: true
|
|
2658
|
+
//#endregion
|
|
2659
|
+
//#region packages/ai/src/transports/openai-completions-transport.ts
|
|
2660
|
+
function assertOpenAICompletionsPayloadHasConversationTurn(params, model) {
|
|
2661
|
+
const messages = params.messages;
|
|
2662
|
+
if (!Array.isArray(messages) || hasOpenAICompatibleConversationTurn(messages)) return;
|
|
2663
|
+
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.`);
|
|
2664
|
+
}
|
|
2665
|
+
const SSE_DONE_LINE_RE = /^data:[ \t]*\[DONE\][ \t]*$/i;
|
|
2666
|
+
const SSE_DONE_MAX_LINE_CHARS = 1024;
|
|
2667
|
+
function createSseDoneDetector() {
|
|
2668
|
+
const decoder = new TextDecoder();
|
|
2669
|
+
let line = "";
|
|
2670
|
+
let lineOverflowed = false;
|
|
2671
|
+
let sawDone = false;
|
|
2672
|
+
const finishLine = () => {
|
|
2673
|
+
if (!lineOverflowed && SSE_DONE_LINE_RE.test(line)) sawDone = true;
|
|
2674
|
+
line = "";
|
|
2675
|
+
lineOverflowed = false;
|
|
3020
2676
|
};
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
const responseFormat = resolveOpenAICompletionsResponseFormat(shouldOmitOllamaCompatResponseFormat({
|
|
3030
|
-
provider: model.provider,
|
|
3031
|
-
baseUrl: model.baseUrl,
|
|
3032
|
-
hasTools: () => Boolean(context.tools?.length)
|
|
3033
|
-
}) ? void 0 : options?.responseFormat, compat.supportsJsonSchemaResponseFormat);
|
|
3034
|
-
if (responseFormat !== void 0) params.response_format = responseFormat;
|
|
3035
|
-
if (options?.frequencyPenalty !== void 0) params.frequency_penalty = options.frequencyPenalty;
|
|
3036
|
-
if (options?.presencePenalty !== void 0) params.presence_penalty = options.presencePenalty;
|
|
3037
|
-
if (options?.seed !== void 0) params.seed = options.seed;
|
|
3038
|
-
if (options?.stop !== void 0 && options.stop.length > 0) params.stop = options.stop;
|
|
3039
|
-
if (supportsModelTools(model)) {
|
|
3040
|
-
if (context.tools) {
|
|
3041
|
-
const converted = convertTools(context.tools, compat, model);
|
|
3042
|
-
if (converted.tools.length > 0 || converted.projection.inputToolCount === 0 && converted.projection.diagnostics.length === 0) params.tools = converted.tools;
|
|
3043
|
-
else if (hasToolHistory(context.messages)) params.tools = [];
|
|
3044
|
-
if (options?.toolChoice) {
|
|
3045
|
-
const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, converted.projection);
|
|
3046
|
-
if (toolChoice !== void 0) params.tool_choice = toolChoice;
|
|
3047
|
-
} else if (compatDetection.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length > 0) params.tool_choice = "auto";
|
|
3048
|
-
} else if (hasToolHistory(context.messages)) params.tools = [];
|
|
3049
|
-
if (compatDetection.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length === 0) {
|
|
3050
|
-
delete params.tools;
|
|
3051
|
-
delete params.tool_choice;
|
|
2677
|
+
const observeText = (text) => {
|
|
2678
|
+
for (const char of text) {
|
|
2679
|
+
if (char === "\n" || char === "\r") {
|
|
2680
|
+
finishLine();
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
if (!lineOverflowed && line.length < SSE_DONE_MAX_LINE_CHARS) line += char;
|
|
2684
|
+
else lineOverflowed = true;
|
|
3052
2685
|
}
|
|
3053
|
-
}
|
|
3054
|
-
{
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
2686
|
+
};
|
|
2687
|
+
return {
|
|
2688
|
+
observe(chunk) {
|
|
2689
|
+
if (!sawDone) observeText(decoder.decode(chunk, { stream: true }));
|
|
2690
|
+
},
|
|
2691
|
+
finish() {
|
|
2692
|
+
if (sawDone) return;
|
|
2693
|
+
observeText(decoder.decode());
|
|
2694
|
+
if (line || lineOverflowed) finishLine();
|
|
2695
|
+
},
|
|
2696
|
+
sawDone: () => sawDone
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
function createOpenAICompletionsClient(model, context, apiKey, optionHeaders, opts) {
|
|
2700
|
+
const clientConfig = buildOpenAICompletionsClientConfig(model, context, optionHeaders);
|
|
2701
|
+
return new OpenAI({
|
|
2702
|
+
apiKey,
|
|
2703
|
+
baseURL: clientConfig.baseURL,
|
|
2704
|
+
dangerouslyAllowBrowser: true,
|
|
2705
|
+
defaultHeaders: clientConfig.defaultHeaders,
|
|
2706
|
+
defaultQuery: clientConfig.defaultQuery,
|
|
2707
|
+
fetch: opts?.fetch ?? buildGuardedModelFetch(model),
|
|
2708
|
+
...buildOpenAISdkClientOptions(model)
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
function buildOpenAICompletionsClientConfig(model, context, optionHeaders) {
|
|
2712
|
+
const headers = buildOpenAIClientHeaders(model, context, optionHeaders);
|
|
2713
|
+
const defaultQuery = {};
|
|
2714
|
+
let baseURL = model.baseUrl;
|
|
2715
|
+
let isAzureHost = false;
|
|
2716
|
+
try {
|
|
2717
|
+
const parsed = new URL(model.baseUrl);
|
|
2718
|
+
isAzureHost = isAzureOpenAICompatibleHost(parsed.hostname.toLowerCase());
|
|
2719
|
+
parsed.searchParams.forEach((value, key) => {
|
|
2720
|
+
if (value) defaultQuery[key] = value;
|
|
2721
|
+
});
|
|
2722
|
+
parsed.search = "";
|
|
2723
|
+
baseURL = parsed.toString().replace(/\/$/, "");
|
|
2724
|
+
} catch {}
|
|
2725
|
+
if (isAzureHost) {
|
|
2726
|
+
const apiVersionHeader = Object.keys(headers).find((key) => key.toLowerCase() === "api-version");
|
|
2727
|
+
if (apiVersionHeader) {
|
|
2728
|
+
const apiVersion = headers[apiVersionHeader]?.trim();
|
|
2729
|
+
delete headers[apiVersionHeader];
|
|
2730
|
+
if (apiVersion && !defaultQuery["api-version"]) defaultQuery["api-version"] = apiVersion;
|
|
3063
2731
|
}
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
2732
|
+
}
|
|
2733
|
+
return {
|
|
2734
|
+
baseURL,
|
|
2735
|
+
defaultHeaders: headers,
|
|
2736
|
+
defaultQuery: Object.keys(defaultQuery).length > 0 ? defaultQuery : void 0
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
function createOpenAICompletionsTransportStreamFn() {
|
|
2740
|
+
return (model, context, options) => {
|
|
2741
|
+
const eventStream = createAssistantMessageEventStream();
|
|
2742
|
+
const stream = eventStream;
|
|
2743
|
+
(async () => {
|
|
2744
|
+
const output = {
|
|
2745
|
+
role: "assistant",
|
|
2746
|
+
content: [],
|
|
2747
|
+
api: model.api,
|
|
2748
|
+
provider: model.provider,
|
|
2749
|
+
model: model.id,
|
|
2750
|
+
usage: {
|
|
2751
|
+
input: 0,
|
|
2752
|
+
output: 0,
|
|
2753
|
+
cacheRead: 0,
|
|
2754
|
+
cacheWrite: 0,
|
|
2755
|
+
totalTokens: 0,
|
|
2756
|
+
cost: {
|
|
2757
|
+
input: 0,
|
|
2758
|
+
output: 0,
|
|
2759
|
+
cacheRead: 0,
|
|
2760
|
+
cacheWrite: 0,
|
|
2761
|
+
total: 0
|
|
2762
|
+
}
|
|
2763
|
+
},
|
|
2764
|
+
stopReason: "stop",
|
|
2765
|
+
timestamp: Date.now()
|
|
2766
|
+
};
|
|
2767
|
+
let firstEventAbort;
|
|
2768
|
+
try {
|
|
2769
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
2770
|
+
const doneDetector = createSseDoneDetector();
|
|
2771
|
+
const baseFetch = buildGuardedModelFetch(model);
|
|
2772
|
+
const doneDetectingFetch = async (url, init) => {
|
|
2773
|
+
const response = await baseFetch(url, init);
|
|
2774
|
+
if (!response.body || !response.ok) return response;
|
|
2775
|
+
if (typeof TransformStream === "undefined" || !response.body.pipeThrough) return response;
|
|
2776
|
+
const transformed = response.body.pipeThrough(new TransformStream({
|
|
2777
|
+
transform(chunk, controller) {
|
|
2778
|
+
doneDetector.observe(chunk);
|
|
2779
|
+
controller.enqueue(chunk);
|
|
2780
|
+
},
|
|
2781
|
+
flush() {
|
|
2782
|
+
doneDetector.finish();
|
|
2783
|
+
}
|
|
2784
|
+
}));
|
|
2785
|
+
return new Response(transformed, {
|
|
2786
|
+
headers: response.headers,
|
|
2787
|
+
status: response.status,
|
|
2788
|
+
statusText: response.statusText
|
|
2789
|
+
});
|
|
2790
|
+
};
|
|
2791
|
+
const client = createOpenAICompletionsClient(model, context, apiKey, options?.headers, { fetch: doneDetectingFetch });
|
|
2792
|
+
let params = buildOpenAICompletionsParams(model, context, options);
|
|
2793
|
+
const nextParams = await options?.onPayload?.(params, model);
|
|
2794
|
+
if (nextParams !== void 0) params = nextParams;
|
|
2795
|
+
if (options?.openclawCodeModeToolSurface === true) {
|
|
2796
|
+
const visibleToolNames = resolveCodeModeResponsesVisibleToolNames(context);
|
|
2797
|
+
enforceCodeModeResponsesToolSurface(params, visibleToolNames);
|
|
2798
|
+
assertCodeModeResponsesToolSurface(params, visibleToolNames);
|
|
2799
|
+
}
|
|
2800
|
+
if (getCompat(model).requiresNonEmptyUserOrAssistantMessage) assertOpenAICompletionsPayloadHasConversationTurn(params, model);
|
|
2801
|
+
const emitReasoning = shouldEmitOpenAICompletionsReasoning(model, options);
|
|
2802
|
+
firstEventAbort = createFirstStreamEventAbortController(options?.signal);
|
|
2803
|
+
const { data: responseStream, response } = await client.chat.completions.create(params, buildOpenAISdkRequestOptions(model, firstEventAbort.signal, {
|
|
2804
|
+
timeoutMs: options?.timeoutMs,
|
|
2805
|
+
maxRetries: options?.maxRetries
|
|
2806
|
+
})).withResponse();
|
|
2807
|
+
await processCompletionsStream(withProviderResponseHook({
|
|
2808
|
+
stream: responseStream,
|
|
2809
|
+
signal: firstEventAbort.signal,
|
|
2810
|
+
abort: firstEventAbort.abort,
|
|
2811
|
+
hook: createOpenAIResponseHook(options?.onResponse, response, model),
|
|
2812
|
+
onReady: () => stream.push({
|
|
2813
|
+
type: "start",
|
|
2814
|
+
partial: output
|
|
2815
|
+
})
|
|
2816
|
+
}), output, model, stream, {
|
|
2817
|
+
signal: options?.signal,
|
|
2818
|
+
emitReasoning,
|
|
2819
|
+
firstEventTimeoutMs: getFirstStreamEventTimeoutMs(options),
|
|
2820
|
+
abortFirstEventStream: firstEventAbort.abort,
|
|
2821
|
+
onFirstEventTimeout: getFirstStreamEventTimeoutHandler(options),
|
|
2822
|
+
sawStreamDONE: doneDetector.sawDone
|
|
2823
|
+
});
|
|
2824
|
+
finalizeTransportStream({
|
|
2825
|
+
stream,
|
|
2826
|
+
output,
|
|
2827
|
+
signal: options?.signal
|
|
2828
|
+
});
|
|
2829
|
+
} catch (error) {
|
|
2830
|
+
failTransportStream({
|
|
2831
|
+
stream,
|
|
2832
|
+
output,
|
|
2833
|
+
signal: options?.signal,
|
|
2834
|
+
error,
|
|
2835
|
+
cleanup: () => {
|
|
2836
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
2837
|
+
finalizeOpenAICompletionsToolCalls(output, { allowSilentToolCallPromotion: false });
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
} finally {
|
|
2841
|
+
firstEventAbort?.dispose();
|
|
3070
2842
|
}
|
|
3071
|
-
}
|
|
3072
|
-
|
|
3073
|
-
else params.max_completion_tokens = clampedMaxTokens;
|
|
3074
|
-
}
|
|
3075
|
-
const completionsReasoningEffort = resolveOpenAICompletionsReasoningEffort(options);
|
|
3076
|
-
const resolvedCompletionsReasoningEffort = completionsReasoningEffort ? resolveOpenAIReasoningEffortForModel({
|
|
3077
|
-
model,
|
|
3078
|
-
effort: completionsReasoningEffort,
|
|
3079
|
-
fallbackMap: compat.reasoningEffortMap
|
|
3080
|
-
}) : void 0;
|
|
3081
|
-
const omitChatCompletionsToolReasoningEffort = Array.isArray(params.tools) && params.tools.length > 0 && (isOpenAIGpt54MiniModel(model) || isOpenAIGpt55Model(model) && isKnownOpenAICompletionsEndpoint(model));
|
|
3082
|
-
const disableChatCompletionsToolReasoning = Array.isArray(params.tools) && params.tools.length > 0 && isOpenAIGpt56Model(model) && isKnownOpenAICompletionsEndpoint(model);
|
|
3083
|
-
const handledQwenThinkingFormat = applyQwenOpenAICompletionsThinkingParams({
|
|
3084
|
-
compatThinkingFormat: compat.thinkingFormat,
|
|
3085
|
-
modelReasoning: model.reasoning,
|
|
3086
|
-
payload: params,
|
|
3087
|
-
requestedEffort: completionsReasoningEffort
|
|
3088
|
-
});
|
|
3089
|
-
applyTogetherOpenAICompletionsThinkingParams({
|
|
3090
|
-
compatThinkingFormat: compat.thinkingFormat,
|
|
3091
|
-
modelReasoning: model.reasoning,
|
|
3092
|
-
payload: params,
|
|
3093
|
-
requestedEffort: completionsReasoningEffort
|
|
3094
|
-
});
|
|
3095
|
-
if (disableChatCompletionsToolReasoning) params.reasoning_effort = "none";
|
|
3096
|
-
else if (compat.thinkingFormat === "openrouter" && model.reasoning && resolvedCompletionsReasoningEffort) params.reasoning = { effort: resolvedCompletionsReasoningEffort };
|
|
3097
|
-
else if (resolvedCompletionsReasoningEffort && model.reasoning && compat.supportsReasoningEffort && !handledQwenThinkingFormat && !omitChatCompletionsToolReasoningEffort) params.reasoning_effort = resolvedCompletionsReasoningEffort;
|
|
3098
|
-
return params;
|
|
3099
|
-
}
|
|
3100
|
-
function parseTransportChunkUsage(rawUsage, model) {
|
|
3101
|
-
const cacheWriteTokens = rawUsage.prompt_tokens_details?.cache_write_tokens || 0;
|
|
3102
|
-
const cachedTokens = rawUsage.prompt_tokens_details?.cached_tokens || 0;
|
|
3103
|
-
const promptTokens = rawUsage.prompt_tokens || 0;
|
|
3104
|
-
const input = Math.max(0, promptTokens - cachedTokens - cacheWriteTokens);
|
|
3105
|
-
const outputTokens = rawUsage.completion_tokens || 0;
|
|
3106
|
-
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens;
|
|
3107
|
-
const usage = {
|
|
3108
|
-
input,
|
|
3109
|
-
output: outputTokens,
|
|
3110
|
-
cacheRead: cachedTokens,
|
|
3111
|
-
cacheWrite: cacheWriteTokens,
|
|
3112
|
-
...typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) ? { reasoningTokens } : {},
|
|
3113
|
-
totalTokens: input + outputTokens + cachedTokens + cacheWriteTokens,
|
|
3114
|
-
cost: {
|
|
3115
|
-
input: 0,
|
|
3116
|
-
output: 0,
|
|
3117
|
-
cacheRead: 0,
|
|
3118
|
-
cacheWrite: 0,
|
|
3119
|
-
total: 0
|
|
3120
|
-
}
|
|
2843
|
+
})();
|
|
2844
|
+
return eventStream;
|
|
3121
2845
|
};
|
|
3122
|
-
calculateCost(model, usage);
|
|
3123
|
-
applyProviderReportedUsageCost(usage, rawUsage.cost);
|
|
3124
|
-
return usage;
|
|
3125
|
-
}
|
|
3126
|
-
function hasOpenAICompletionsReasoningUsageActivity(rawUsage) {
|
|
3127
|
-
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens;
|
|
3128
|
-
return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) && reasoningTokens > 0;
|
|
3129
2846
|
}
|
|
3130
|
-
const completionsTesting = {
|
|
3131
|
-
getCompat,
|
|
3132
|
-
createSseDoneDetector,
|
|
3133
|
-
createOpenAICompletionsClient,
|
|
3134
|
-
buildOpenAICompletionsClientConfig,
|
|
3135
|
-
parseTransportChunkUsage,
|
|
3136
|
-
processOpenAICompletionsStream,
|
|
3137
|
-
shouldEmitOpenAICompletionsReasoningForModel
|
|
3138
|
-
};
|
|
3139
|
-
if (process.env.VITEST || false) globalThis.openclawOpenAICompletionsTransportTestApi = completionsTesting;
|
|
3140
2847
|
//#endregion
|
|
3141
|
-
//#region packages/ai/src/transports/openai-responses-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
"openai-responses",
|
|
3149
|
-
"azure-openai-responses",
|
|
3150
|
-
"openai-chatgpt-responses",
|
|
3151
|
-
"openclaw-openai-responses-transport",
|
|
3152
|
-
"openclaw-openai-chatgpt-responses-transport"
|
|
3153
|
-
]);
|
|
3154
|
-
const OPENAI_RESPONSES_PROVIDERS = /* @__PURE__ */ new Set([
|
|
3155
|
-
"openai",
|
|
3156
|
-
"azure-openai",
|
|
3157
|
-
"azure-openai-responses"
|
|
3158
|
-
]);
|
|
3159
|
-
const LOCAL_ENDPOINT_HOSTS = /* @__PURE__ */ new Set([
|
|
3160
|
-
"localhost",
|
|
3161
|
-
"127.0.0.1",
|
|
3162
|
-
"::1",
|
|
3163
|
-
"[::1]"
|
|
3164
|
-
]);
|
|
3165
|
-
const MODELSTUDIO_NATIVE_BASE_URLS = /* @__PURE__ */ new Set([
|
|
3166
|
-
"https://coding-intl.dashscope.aliyuncs.com/v1",
|
|
3167
|
-
"https://coding.dashscope.aliyuncs.com/v1",
|
|
3168
|
-
"https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
3169
|
-
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
|
3170
|
-
]);
|
|
3171
|
-
const MOONSHOT_NATIVE_BASE_URLS = /* @__PURE__ */ new Set(["https://api.moonshot.ai/v1", "https://api.moonshot.cn/v1"]);
|
|
3172
|
-
function normalizeLowercaseString(value) {
|
|
3173
|
-
const stringValue = readStringValue(value)?.trim().toLowerCase();
|
|
3174
|
-
return stringValue ? stringValue : void 0;
|
|
3175
|
-
}
|
|
3176
|
-
function normalizeComparableBaseUrl(value) {
|
|
3177
|
-
const trimmed = readStringValue(value)?.trim();
|
|
3178
|
-
if (!trimmed) return;
|
|
3179
|
-
const parsedValue = /^[a-z0-9.[\]-]+(?::\d+)?(?:[/?#].*)?$/i.test(trimmed) ? `https://${trimmed}` : trimmed;
|
|
3180
|
-
try {
|
|
3181
|
-
const url = new URL(parsedValue);
|
|
3182
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return;
|
|
3183
|
-
url.hash = "";
|
|
3184
|
-
url.search = "";
|
|
3185
|
-
return url.toString().replace(/\/+$/, "").toLowerCase();
|
|
3186
|
-
} catch {
|
|
3187
|
-
return;
|
|
2848
|
+
//#region packages/ai/src/transports/openai-responses-compact-request.ts
|
|
2849
|
+
const COMPACT_REQUEST = Symbol("openaiResponsesCompactRequest");
|
|
2850
|
+
function claimResponsesCompactRequest(options) {
|
|
2851
|
+
const controller = options ? Reflect.get(options, COMPACT_REQUEST) : void 0;
|
|
2852
|
+
if (controller?.claimed === false) {
|
|
2853
|
+
controller.claimed = true;
|
|
2854
|
+
return controller;
|
|
3188
2855
|
}
|
|
3189
2856
|
}
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
2857
|
+
/** Run a compact-endpoint request through the session's prepared stream stack. */
|
|
2858
|
+
async function requestPreparedOpenAIResponsesCompaction(streamFn, model, context, options) {
|
|
2859
|
+
const preparedOptions = { ...options };
|
|
2860
|
+
let resolveResult;
|
|
2861
|
+
let rejectResult;
|
|
2862
|
+
const result = new Promise((resolve, reject) => {
|
|
2863
|
+
resolveResult = resolve;
|
|
2864
|
+
rejectResult = reject;
|
|
2865
|
+
});
|
|
2866
|
+
const controller = {
|
|
2867
|
+
claimed: false,
|
|
2868
|
+
resolve: resolveResult,
|
|
2869
|
+
reject: rejectResult
|
|
2870
|
+
};
|
|
2871
|
+
Reflect.set(preparedOptions, COMPACT_REQUEST, controller);
|
|
2872
|
+
const stream = await Promise.resolve(streamFn(model, context, preparedOptions));
|
|
2873
|
+
if (!controller.claimed) throw new Error("Prepared stream did not reach an OpenAI Responses transport");
|
|
3193
2874
|
try {
|
|
3194
|
-
return
|
|
3195
|
-
}
|
|
3196
|
-
|
|
3197
|
-
return new URL(`https://${trimmed}`).hostname.toLowerCase();
|
|
3198
|
-
} catch {
|
|
3199
|
-
return;
|
|
3200
|
-
}
|
|
2875
|
+
return await result;
|
|
2876
|
+
} finally {
|
|
2877
|
+
await stream.result().catch(() => void 0);
|
|
3201
2878
|
}
|
|
3202
2879
|
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
case "llm.chutes.ai": return "chutes-native";
|
|
3219
|
-
case "api.deepseek.com": return "deepseek-native";
|
|
3220
|
-
case "api.groq.com": return "groq-native";
|
|
3221
|
-
case "api.mistral.ai": return "mistral-public";
|
|
3222
|
-
case "api.openai.com": return "openai-public";
|
|
3223
|
-
case "chatgpt.com": return "openai";
|
|
3224
|
-
case "generativelanguage.googleapis.com": return "google-generative-ai";
|
|
3225
|
-
case "aiplatform.googleapis.com": return "google-vertex";
|
|
3226
|
-
case "api.x.ai": return "xai-native";
|
|
3227
|
-
case "api.z.ai": return "zai-native";
|
|
3228
|
-
}
|
|
3229
|
-
if (hostMatchesSuffix(host, ".githubcopilot.com")) return "github-copilot-native";
|
|
3230
|
-
if (hostMatchesSuffix(host, ".openai.azure.com")) return "azure-openai";
|
|
3231
|
-
if (hostMatchesSuffix(host, "openrouter.ai")) return "openrouter";
|
|
3232
|
-
if (hostMatchesSuffix(host, "opencode.ai")) return "opencode-native";
|
|
3233
|
-
if (hostMatchesSuffix(host, "-aiplatform.googleapis.com")) return "google-vertex";
|
|
3234
|
-
if (comparableBaseUrl && MOONSHOT_NATIVE_BASE_URLS.has(comparableBaseUrl)) return "moonshot-native";
|
|
3235
|
-
if (comparableBaseUrl && MODELSTUDIO_NATIVE_BASE_URLS.has(comparableBaseUrl)) return "modelstudio-native";
|
|
3236
|
-
if (isLocalEndpointHost(host)) return "local";
|
|
3237
|
-
return "custom";
|
|
3238
|
-
}
|
|
3239
|
-
function isOpenAIResponsesApi(api) {
|
|
3240
|
-
return api !== void 0 && OPENAI_RESPONSES_APIS.has(api);
|
|
3241
|
-
}
|
|
3242
|
-
function readCompatPayloadBoolean(compat, key) {
|
|
3243
|
-
if (!compat || typeof compat !== "object") return;
|
|
3244
|
-
const value = compat[key];
|
|
3245
|
-
return typeof value === "boolean" ? value : void 0;
|
|
3246
|
-
}
|
|
3247
|
-
function resolveOpenAIResponsesPayloadCapabilities(model) {
|
|
3248
|
-
const provider = normalizeLowercaseString(model.provider);
|
|
3249
|
-
const api = normalizeLowercaseString(model.api);
|
|
3250
|
-
const isOpenAIProvider = provider === "openai";
|
|
3251
|
-
const endpointClass = resolveBundledOpenAIResponsesEndpointClass(model.baseUrl);
|
|
3252
|
-
const isResponsesApi = isOpenAIResponsesApi(api);
|
|
3253
|
-
const usesConfiguredBaseUrl = endpointClass !== "default";
|
|
3254
|
-
const usesKnownNativeOpenAIEndpoint = endpointClass === "openai-public" || endpointClass === "openai" || endpointClass === "azure-openai";
|
|
3255
|
-
const usesKnownNativeOpenAIRoute = endpointClass === "default" ? provider === "openai" : usesKnownNativeOpenAIEndpoint;
|
|
3256
|
-
const usesExplicitProxyLikeEndpoint = usesConfiguredBaseUrl && !usesKnownNativeOpenAIEndpoint;
|
|
3257
|
-
const promptCacheKeySupport = readCompatPayloadBoolean(model.compat, "supportsPromptCacheKey");
|
|
3258
|
-
const shouldStripResponsesPromptCache = promptCacheKeySupport === true ? false : promptCacheKeySupport === false ? isResponsesApi : isResponsesApi && usesExplicitProxyLikeEndpoint;
|
|
3259
|
-
const supportsResponsesStoreField = readCompatPayloadBoolean(model.compat, "supportsStore") !== false && isResponsesApi;
|
|
2880
|
+
//#endregion
|
|
2881
|
+
//#region packages/ai/src/transports/openai-responses-continuation.ts
|
|
2882
|
+
const HTTP_CONTINUATION_IDLE_TTL_MS = 300 * 1e3;
|
|
2883
|
+
const TURN_HEADERS = /* @__PURE__ */ new Set([
|
|
2884
|
+
"traceparent",
|
|
2885
|
+
"x-openclaw-turn-id",
|
|
2886
|
+
"x-openclaw-turn-attempt"
|
|
2887
|
+
]);
|
|
2888
|
+
function jsonValuesEqual(left, right) {
|
|
2889
|
+
return stableStringify(JSON.parse(JSON.stringify(left))) === stableStringify(JSON.parse(JSON.stringify(right)));
|
|
2890
|
+
}
|
|
2891
|
+
function requestWithoutInput(request) {
|
|
2892
|
+
const { input: _input, previous_response_id: _previousResponseId, ...rest } = request;
|
|
2893
|
+
if (!isRecord(rest.metadata)) return rest;
|
|
2894
|
+
const metadata = Object.fromEntries(Object.entries(rest.metadata).filter(([key]) => key !== "openclaw_turn_id" && key !== "openclaw_turn_attempt"));
|
|
3260
2895
|
return {
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
shouldStripResponsesPromptCache,
|
|
3264
|
-
supportsResponsesStoreField,
|
|
3265
|
-
usesKnownNativeOpenAIRoute
|
|
2896
|
+
...rest,
|
|
2897
|
+
metadata
|
|
3266
2898
|
};
|
|
3267
2899
|
}
|
|
3268
|
-
function
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
if (
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
2900
|
+
function normalizeAssistantReplayInput(input) {
|
|
2901
|
+
return input.map((item) => {
|
|
2902
|
+
if (!isRecord(item)) return item;
|
|
2903
|
+
if (item.type === "reasoning") return { type: "reasoning" };
|
|
2904
|
+
if (item.type !== "function_call" && !(item.type === "message" && item.role === "assistant")) return item;
|
|
2905
|
+
const { id: _id, status: _status, ...stableItem } = item;
|
|
2906
|
+
if (item.type === "message" && Array.isArray(stableItem.content)) stableItem.content = stableItem.content.map((part) => {
|
|
2907
|
+
if (!isRecord(part) || part.type !== "output_text") return part;
|
|
2908
|
+
const { annotations: _annotations, logprobs: _logprobs, ...stablePart } = part;
|
|
2909
|
+
return stablePart;
|
|
2910
|
+
});
|
|
2911
|
+
return stableItem;
|
|
2912
|
+
});
|
|
2913
|
+
}
|
|
2914
|
+
function resolveResponsesContinuationRequest(continuation, request) {
|
|
2915
|
+
if (!continuation) return {
|
|
2916
|
+
request,
|
|
2917
|
+
continuationStatus: "no_previous_response"
|
|
2918
|
+
};
|
|
2919
|
+
if (request.previous_response_id) return {
|
|
2920
|
+
request,
|
|
2921
|
+
continuationStatus: "explicit_previous_response_id"
|
|
2922
|
+
};
|
|
2923
|
+
if (!jsonValuesEqual(requestWithoutInput(request), requestWithoutInput(continuation.lastRequest))) return {
|
|
2924
|
+
request,
|
|
2925
|
+
continuationStatus: "request_changed"
|
|
2926
|
+
};
|
|
2927
|
+
const currentInput = request.input ?? [];
|
|
2928
|
+
const previousInput = continuation.lastRequest.input ?? [];
|
|
2929
|
+
const baselineLength = previousInput.length + continuation.lastResponseItems.length;
|
|
2930
|
+
if (currentInput.length < baselineLength) return {
|
|
2931
|
+
request,
|
|
2932
|
+
continuationStatus: "history_shorter"
|
|
2933
|
+
};
|
|
2934
|
+
if (!jsonValuesEqual(normalizeAssistantReplayInput(currentInput.slice(0, previousInput.length)), normalizeAssistantReplayInput(previousInput)) || !jsonValuesEqual(normalizeAssistantReplayInput(currentInput.slice(previousInput.length, baselineLength)), normalizeAssistantReplayInput(continuation.lastResponseItems))) return {
|
|
2935
|
+
request,
|
|
2936
|
+
continuationStatus: "history_changed"
|
|
2937
|
+
};
|
|
3306
2938
|
return {
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
shouldStripStore: explicitStore !== true && readCompatPayloadBoolean(model.compat, "supportsStore") === false && isResponsesApi,
|
|
3314
|
-
useServerCompaction: options.enableServerCompaction === true && shouldEnableOpenAIResponsesServerCompaction(explicitStore, model.provider, options.extraParams)
|
|
2939
|
+
request: {
|
|
2940
|
+
...request,
|
|
2941
|
+
previous_response_id: continuation.lastResponseId,
|
|
2942
|
+
input: currentInput.slice(baselineLength)
|
|
2943
|
+
},
|
|
2944
|
+
continuationStatus: "continued"
|
|
3315
2945
|
};
|
|
3316
2946
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
|
-
if (
|
|
2947
|
+
const httpContinuationEntries = /* @__PURE__ */ new Map();
|
|
2948
|
+
let nextHttpContinuationGeneration = 1;
|
|
2949
|
+
function connectionIdentity(params) {
|
|
2950
|
+
const headers = Object.entries(resolveAiTransportHeaderSentinels(params.headers) ?? {}).map(([name, value]) => [name.toLowerCase(), value]).filter(([name]) => !TURN_HEADERS.has(name)).toSorted(([a], [b]) => a.localeCompare(b));
|
|
2951
|
+
return sha256Hex(JSON.stringify([
|
|
2952
|
+
getAiTransportHost().resolveSecretSentinel(params.apiKey),
|
|
2953
|
+
params.baseUrl,
|
|
2954
|
+
headers
|
|
2955
|
+
]));
|
|
2956
|
+
}
|
|
2957
|
+
function claimOpenAIResponsesHttpContinuation(params) {
|
|
2958
|
+
const key = `${params.sessionId}\0${connectionIdentity(params)}`;
|
|
2959
|
+
const previous = httpContinuationEntries.get(key);
|
|
2960
|
+
if (previous?.kind === "claimed") return;
|
|
2961
|
+
if (previous?.kind === "ready") clearTimeout(previous.idleTimer);
|
|
2962
|
+
const generation = nextHttpContinuationGeneration++;
|
|
2963
|
+
const claimed = {
|
|
2964
|
+
kind: "claimed",
|
|
2965
|
+
sessionId: params.sessionId,
|
|
2966
|
+
generation
|
|
2967
|
+
};
|
|
2968
|
+
httpContinuationEntries.set(key, claimed);
|
|
2969
|
+
return {
|
|
2970
|
+
request: resolveResponsesContinuationRequest(previous?.kind === "ready" ? previous.state : void 0, params.request).request,
|
|
2971
|
+
commit: (effectiveRequest, response) => {
|
|
2972
|
+
if (httpContinuationEntries.get(key) !== claimed) return;
|
|
2973
|
+
const idleTimer = setTimeout(() => {
|
|
2974
|
+
const current = httpContinuationEntries.get(key);
|
|
2975
|
+
if (current?.kind === "ready" && current.generation === generation) httpContinuationEntries.delete(key);
|
|
2976
|
+
}, HTTP_CONTINUATION_IDLE_TTL_MS);
|
|
2977
|
+
idleTimer.unref?.();
|
|
2978
|
+
const ready = {
|
|
2979
|
+
...claimed,
|
|
2980
|
+
kind: "ready",
|
|
2981
|
+
state: {
|
|
2982
|
+
lastRequest: effectiveRequest,
|
|
2983
|
+
lastResponseId: response.id,
|
|
2984
|
+
lastResponseItems: response.output
|
|
2985
|
+
},
|
|
2986
|
+
idleTimer
|
|
2987
|
+
};
|
|
2988
|
+
httpContinuationEntries.set(key, ready);
|
|
2989
|
+
},
|
|
2990
|
+
release: () => {
|
|
2991
|
+
if (httpContinuationEntries.get(key) === claimed) httpContinuationEntries.delete(key);
|
|
2992
|
+
}
|
|
2993
|
+
};
|
|
3331
2994
|
}
|
|
2995
|
+
registerSessionResourceCleanup((sessionId) => {
|
|
2996
|
+
for (const [key, entry] of httpContinuationEntries) if (!sessionId || entry.sessionId === sessionId) {
|
|
2997
|
+
if (entry.kind === "ready") clearTimeout(entry.idleTimer);
|
|
2998
|
+
httpContinuationEntries.delete(key);
|
|
2999
|
+
}
|
|
3000
|
+
});
|
|
3332
3001
|
//#endregion
|
|
3333
3002
|
//#region packages/ai/src/transports/openai-responses-params-internal.ts
|
|
3003
|
+
const OPENAI_RESPONSES_TOOL_CALL_PROVIDERS = /* @__PURE__ */ new Set([
|
|
3004
|
+
"openai",
|
|
3005
|
+
"opencode",
|
|
3006
|
+
"azure-openai-responses",
|
|
3007
|
+
"github-copilot"
|
|
3008
|
+
]);
|
|
3334
3009
|
function convertResponsesTools(tools, model, options) {
|
|
3335
3010
|
const projection = projectOpenAITools(tools);
|
|
3336
3011
|
const strict = resolveOpenAIStrictToolFlagWithDiagnostics(projection, options?.strict, {
|
|
@@ -3429,7 +3104,7 @@ function resolveOpenAIResponsesTextFormat(responseFormat) {
|
|
|
3429
3104
|
};
|
|
3430
3105
|
return responseFormat;
|
|
3431
3106
|
}
|
|
3432
|
-
function
|
|
3107
|
+
function convertOpenAIResponsesMessagesForRequest(model, context, options, replayMode) {
|
|
3433
3108
|
const isCodexResponses = isOpenAICodexResponsesModel(model);
|
|
3434
3109
|
const isNativeCodexResponses = usesNativeOpenAICodexResponsesBackend(model);
|
|
3435
3110
|
const compat = getCompat(model);
|
|
@@ -3437,19 +3112,20 @@ function buildOpenAIResponsesParams(model, context, options, metadata) {
|
|
|
3437
3112
|
const payloadPolicy = resolveOpenAIResponsesPayloadPolicy(model, { storeMode: "disable" });
|
|
3438
3113
|
const policyAllowsReplayIds = payloadPolicy.explicitStore !== false && !payloadPolicy.shouldStripStore;
|
|
3439
3114
|
const replayResponsesItemIds = !isNativeCodexResponses && (options?.replayResponsesItemIds ?? policyAllowsReplayIds);
|
|
3440
|
-
|
|
3441
|
-
"openai",
|
|
3442
|
-
"opencode",
|
|
3443
|
-
"azure-openai-responses",
|
|
3444
|
-
"github-copilot"
|
|
3445
|
-
]), {
|
|
3115
|
+
return convertResponsesMessages(model, context, OPENAI_RESPONSES_TOOL_CALL_PROVIDERS, {
|
|
3446
3116
|
includeSystemPrompt: !isCodexResponses,
|
|
3447
3117
|
supportsDeveloperRole,
|
|
3448
3118
|
replayReasoningItems: true,
|
|
3449
3119
|
replayResponsesItemIds,
|
|
3450
3120
|
authProfileId: options?.authProfileId,
|
|
3451
|
-
sessionId: options?.sessionId
|
|
3121
|
+
sessionId: options?.sessionId,
|
|
3122
|
+
replayMode
|
|
3452
3123
|
});
|
|
3124
|
+
}
|
|
3125
|
+
function buildOpenAIResponsesParams(model, context, options, metadata, replayMode = "checkpoint") {
|
|
3126
|
+
const isCodexResponses = isOpenAICodexResponsesModel(model);
|
|
3127
|
+
const payloadPolicy = resolveOpenAIResponsesPayloadPolicy(model, { storeMode: "disable" });
|
|
3128
|
+
const messages = convertOpenAIResponsesMessagesForRequest(model, context, options, replayMode);
|
|
3453
3129
|
if (isCodexResponses) ensureOpenAICodexResponsesInput(messages, context);
|
|
3454
3130
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
3455
3131
|
const promptCacheKey = resolvePromptCacheKey(options, cacheRetention);
|
|
@@ -3509,6 +3185,262 @@ function buildOpenAIResponsesParams(model, context, options, metadata) {
|
|
|
3509
3185
|
return sanitizeOpenAICodexResponsesParams(model, params);
|
|
3510
3186
|
}
|
|
3511
3187
|
//#endregion
|
|
3188
|
+
//#region packages/ai/src/transports/openai-responses-websocket.ts
|
|
3189
|
+
const SESSION_WEBSOCKET_CACHE_TTL_MS = 300 * 1e3;
|
|
3190
|
+
const SESSION_WEBSOCKET_MAX_AGE_MS = 3300 * 1e3;
|
|
3191
|
+
const WEBSOCKET_OPEN_STATE = 1;
|
|
3192
|
+
const websocketSessionCache = /* @__PURE__ */ new Map();
|
|
3193
|
+
const degradedWebSocketConnections = /* @__PURE__ */ new Map();
|
|
3194
|
+
function isOfficialOpenAIResponsesBaseUrl(baseUrl) {
|
|
3195
|
+
if (!baseUrl) return false;
|
|
3196
|
+
try {
|
|
3197
|
+
const url = new URL(baseUrl);
|
|
3198
|
+
return url.origin === "https://api.openai.com" && url.username === "" && url.password === "" && url.search === "" && url.hash === "" && url.pathname.replace(/\/+$/, "") === "/v1";
|
|
3199
|
+
} catch {
|
|
3200
|
+
return false;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
function supportsNativeOpenAIResponsesEndpoint(params) {
|
|
3204
|
+
return params.provider.trim().toLowerCase() === "openai" && params.api === "openai-responses" && isOfficialOpenAIResponsesBaseUrl(params.baseUrl);
|
|
3205
|
+
}
|
|
3206
|
+
function closeWebSocketSilently(socket, reason = "done") {
|
|
3207
|
+
try {
|
|
3208
|
+
socket.close({
|
|
3209
|
+
code: 1e3,
|
|
3210
|
+
reason
|
|
3211
|
+
});
|
|
3212
|
+
} catch {}
|
|
3213
|
+
}
|
|
3214
|
+
function invalidateOwnedWebSocketSession(cacheKey, entry, reason = "done") {
|
|
3215
|
+
if (entry.idleTimer) {
|
|
3216
|
+
clearTimeout(entry.idleTimer);
|
|
3217
|
+
entry.idleTimer = void 0;
|
|
3218
|
+
}
|
|
3219
|
+
closeWebSocketSilently(entry.socket, reason);
|
|
3220
|
+
if (websocketSessionCache.get(cacheKey) === entry) websocketSessionCache.delete(cacheKey);
|
|
3221
|
+
}
|
|
3222
|
+
function scheduleSessionWebSocketExpiry(cacheKey, entry) {
|
|
3223
|
+
if (entry.idleTimer) clearTimeout(entry.idleTimer);
|
|
3224
|
+
entry.idleTimer = setTimeout(() => {
|
|
3225
|
+
if (entry.busy) return;
|
|
3226
|
+
invalidateOwnedWebSocketSession(cacheKey, entry, "idle_timeout");
|
|
3227
|
+
}, SESSION_WEBSOCKET_CACHE_TTL_MS);
|
|
3228
|
+
entry.idleTimer.unref?.();
|
|
3229
|
+
}
|
|
3230
|
+
function prepareWebSocketConnection(client, headers) {
|
|
3231
|
+
if (!isOfficialOpenAIResponsesBaseUrl(client.baseURL)) throw new Error("OpenAI Responses WebSocket requires the official API endpoint");
|
|
3232
|
+
if (typeof client.apiKey !== "string" || client.apiKey.length === 0) throw new Error("OpenAI Responses WebSocket requires an API key");
|
|
3233
|
+
const resolvedApiKey = getAiTransportHost().resolveSecretSentinel(client.apiKey);
|
|
3234
|
+
const resolvedHeaders = { ...resolveAiTransportHeaderSentinels(headers) };
|
|
3235
|
+
for (const key of Object.keys(resolvedHeaders)) {
|
|
3236
|
+
const normalizedKey = key.toLowerCase();
|
|
3237
|
+
if (normalizedKey === "authorization" || normalizedKey === "traceparent") delete resolvedHeaders[key];
|
|
3238
|
+
}
|
|
3239
|
+
if (!resolvedApiKey) throw new Error("OpenAI Responses WebSocket requires a resolved API key");
|
|
3240
|
+
return {
|
|
3241
|
+
client: client.withOptions({ apiKey: resolvedApiKey }),
|
|
3242
|
+
headers: resolvedHeaders,
|
|
3243
|
+
identity: sha256Hex(JSON.stringify([
|
|
3244
|
+
resolvedApiKey,
|
|
3245
|
+
client.baseURL,
|
|
3246
|
+
Object.entries(resolvedHeaders).toSorted(([a], [b]) => a.localeCompare(b))
|
|
3247
|
+
]))
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
function createWebSocket(connection, onError) {
|
|
3251
|
+
const socket = new ResponsesWS(connection.client, {
|
|
3252
|
+
headers: connection.headers,
|
|
3253
|
+
maxQueueSize: 1
|
|
3254
|
+
});
|
|
3255
|
+
socket.on("error", () => onError(socket));
|
|
3256
|
+
return socket;
|
|
3257
|
+
}
|
|
3258
|
+
function createTransientWebSocketLease(connection) {
|
|
3259
|
+
const socket = createWebSocket(connection, (failedSocket) => closeWebSocketSilently(failedSocket, "transport_error"));
|
|
3260
|
+
return {
|
|
3261
|
+
socket,
|
|
3262
|
+
iterator: socket.stream(),
|
|
3263
|
+
reusedConnection: false,
|
|
3264
|
+
release: () => closeWebSocketSilently(socket)
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
function createCachedWebSocketLease(cacheKey, entry, reusedConnection) {
|
|
3268
|
+
entry.busy = true;
|
|
3269
|
+
return {
|
|
3270
|
+
socket: entry.socket,
|
|
3271
|
+
iterator: entry.socket.stream(),
|
|
3272
|
+
entry,
|
|
3273
|
+
reusedConnection,
|
|
3274
|
+
release: ({ keep } = {}) => {
|
|
3275
|
+
if (!keep || entry.socket.socket.readyState !== WEBSOCKET_OPEN_STATE) {
|
|
3276
|
+
invalidateOwnedWebSocketSession(cacheKey, entry);
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
entry.busy = false;
|
|
3280
|
+
scheduleSessionWebSocketExpiry(cacheKey, entry);
|
|
3281
|
+
}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
function acquireWebSocket(params, connection) {
|
|
3285
|
+
if (!(params.mode !== "websocket" && Boolean(params.sessionId)) || !params.sessionId) return createTransientWebSocketLease(connection);
|
|
3286
|
+
const cacheKey = `${params.sessionId}\0${connection.identity}`;
|
|
3287
|
+
const cached = websocketSessionCache.get(cacheKey);
|
|
3288
|
+
if (cached) {
|
|
3289
|
+
if (cached.idleTimer) {
|
|
3290
|
+
clearTimeout(cached.idleTimer);
|
|
3291
|
+
cached.idleTimer = void 0;
|
|
3292
|
+
}
|
|
3293
|
+
if (cached.busy) return createTransientWebSocketLease(connection);
|
|
3294
|
+
const expired = Date.now() - cached.createdAt >= SESSION_WEBSOCKET_MAX_AGE_MS;
|
|
3295
|
+
if (!expired && cached.socket.socket.readyState === WEBSOCKET_OPEN_STATE) return createCachedWebSocketLease(cacheKey, cached, true);
|
|
3296
|
+
invalidateOwnedWebSocketSession(cacheKey, cached, expired ? "connection_age_limit" : "done");
|
|
3297
|
+
}
|
|
3298
|
+
const entry = {
|
|
3299
|
+
socket: createWebSocket(connection, (failedSocket) => {
|
|
3300
|
+
const failedEntry = websocketSessionCache.get(cacheKey);
|
|
3301
|
+
if (failedEntry?.socket === failedSocket) invalidateOwnedWebSocketSession(cacheKey, failedEntry, "transport_error");
|
|
3302
|
+
else closeWebSocketSilently(failedSocket, "transport_error");
|
|
3303
|
+
}),
|
|
3304
|
+
sessionId: params.sessionId,
|
|
3305
|
+
busy: true,
|
|
3306
|
+
createdAt: Date.now()
|
|
3307
|
+
};
|
|
3308
|
+
websocketSessionCache.set(cacheKey, entry);
|
|
3309
|
+
return createCachedWebSocketLease(cacheKey, entry, false);
|
|
3310
|
+
}
|
|
3311
|
+
function sanitizeWebSocketRequest(request) {
|
|
3312
|
+
const { stream: _stream, background: _background, ...websocketRequest } = request;
|
|
3313
|
+
return websocketRequest;
|
|
3314
|
+
}
|
|
3315
|
+
async function nextWebSocketMessage(iterator, signal) {
|
|
3316
|
+
if (!signal) return iterator.next();
|
|
3317
|
+
if (signal.aborted) throw transportAbortError(signal);
|
|
3318
|
+
let onAbort;
|
|
3319
|
+
try {
|
|
3320
|
+
return await Promise.race([iterator.next(), new Promise((_resolve, reject) => {
|
|
3321
|
+
onAbort = () => reject(transportAbortError(signal));
|
|
3322
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3323
|
+
})]);
|
|
3324
|
+
} finally {
|
|
3325
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
function readServerEvent(message) {
|
|
3329
|
+
if (message.type === "message") return message.message;
|
|
3330
|
+
if (message.type === "error") throw parseOpenAIResponsesWebSocketServerError(message.error) ?? new Error("OpenAI Responses WebSocket transport failed", { cause: message.error });
|
|
3331
|
+
if (message.type === "close") throw new Error(`OpenAI Responses WebSocket closed before completion (code ${message.code})`);
|
|
3332
|
+
}
|
|
3333
|
+
function createOpenAIResponsesWebSocketStream(params) {
|
|
3334
|
+
const connection = prepareWebSocketConnection(params.client, params.headers);
|
|
3335
|
+
const fullRequest = sanitizeWebSocketRequest(params.request);
|
|
3336
|
+
const requestModel = typeof fullRequest.model === "string" ? fullRequest.model : "";
|
|
3337
|
+
const degradationKey = `${params.sessionId ?? ""}\0${connection.identity}\0${requestModel}`;
|
|
3338
|
+
const degraded = degradedWebSocketConnections.get(degradationKey);
|
|
3339
|
+
if (degraded && degraded.retryAt > Date.now()) throw new OpenAIResponsesWebSocketPreDispatchError(/* @__PURE__ */ new Error("OpenAI Responses WebSocket is cooling down after a transport failure"));
|
|
3340
|
+
degradedWebSocketConnections.delete(degradationKey);
|
|
3341
|
+
const markDegraded = () => {
|
|
3342
|
+
const cooldownMs = params.degradeCooldownMs;
|
|
3343
|
+
if (cooldownMs === void 0 || !Number.isFinite(cooldownMs) || cooldownMs <= 0) return;
|
|
3344
|
+
degradedWebSocketConnections.set(degradationKey, {
|
|
3345
|
+
sessionId: params.sessionId,
|
|
3346
|
+
retryAt: Date.now() + cooldownMs
|
|
3347
|
+
});
|
|
3348
|
+
};
|
|
3349
|
+
let lease;
|
|
3350
|
+
try {
|
|
3351
|
+
lease = acquireWebSocket(params, connection);
|
|
3352
|
+
} catch (error) {
|
|
3353
|
+
markDegraded();
|
|
3354
|
+
throw new OpenAIResponsesWebSocketPreDispatchError(error);
|
|
3355
|
+
}
|
|
3356
|
+
let prepared;
|
|
3357
|
+
try {
|
|
3358
|
+
const continuation = lease.entry?.continuation;
|
|
3359
|
+
if (continuation && lease.entry) {
|
|
3360
|
+
lease.entry.continuation = void 0;
|
|
3361
|
+
prepared = resolveResponsesContinuationRequest(continuation, fullRequest);
|
|
3362
|
+
} else prepared = {
|
|
3363
|
+
request: fullRequest,
|
|
3364
|
+
continuationStatus: lease.entry ? "no_previous_response" : "socket_not_cached"
|
|
3365
|
+
};
|
|
3366
|
+
} catch (error) {
|
|
3367
|
+
lease.iterator.return?.().catch(() => void 0);
|
|
3368
|
+
lease.release({ keep: false });
|
|
3369
|
+
throw error;
|
|
3370
|
+
}
|
|
3371
|
+
let streamStarted = false;
|
|
3372
|
+
let terminalResponse;
|
|
3373
|
+
let terminalReceived = false;
|
|
3374
|
+
let released = false;
|
|
3375
|
+
const finish = ({ keep = true } = {}) => {
|
|
3376
|
+
if (released) return;
|
|
3377
|
+
released = true;
|
|
3378
|
+
if (keep && lease.entry && terminalResponse) lease.entry.continuation = {
|
|
3379
|
+
lastRequest: fullRequest,
|
|
3380
|
+
lastResponseId: terminalResponse.id,
|
|
3381
|
+
lastResponseItems: terminalResponse.output
|
|
3382
|
+
};
|
|
3383
|
+
lease.release({ keep });
|
|
3384
|
+
};
|
|
3385
|
+
return {
|
|
3386
|
+
stream: { async *[Symbol.asyncIterator]() {
|
|
3387
|
+
if (streamStarted) throw new Error("OpenAI Responses WebSocket stream can only be consumed once");
|
|
3388
|
+
streamStarted = true;
|
|
3389
|
+
const iterator = lease.iterator;
|
|
3390
|
+
let requestDispatched = false;
|
|
3391
|
+
try {
|
|
3392
|
+
if (params.signal?.aborted) throw transportAbortError(params.signal);
|
|
3393
|
+
for (;;) {
|
|
3394
|
+
const next = await nextWebSocketMessage(iterator, params.signal);
|
|
3395
|
+
if (next.done) throw new Error("OpenAI Responses WebSocket closed before a terminal response event");
|
|
3396
|
+
if (next.value.type === "open") {
|
|
3397
|
+
if (!requestDispatched) {
|
|
3398
|
+
requestDispatched = true;
|
|
3399
|
+
lease.socket.send({
|
|
3400
|
+
...prepared.request,
|
|
3401
|
+
type: "response.create"
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
continue;
|
|
3405
|
+
}
|
|
3406
|
+
const event = readServerEvent(next.value);
|
|
3407
|
+
if (!event) continue;
|
|
3408
|
+
if (event.type === "response.failed") throw new OpenAIResponsesWebSocketResponseFailedError(event.response.output.length > 0);
|
|
3409
|
+
if (event.type === "response.completed") terminalResponse = event.response;
|
|
3410
|
+
terminalReceived = event.type === "response.completed" || event.type === "response.incomplete";
|
|
3411
|
+
yield event;
|
|
3412
|
+
if (terminalReceived) {
|
|
3413
|
+
degradedWebSocketConnections.delete(degradationKey);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
} catch (error) {
|
|
3418
|
+
if (lease.entry) lease.entry.continuation = void 0;
|
|
3419
|
+
const safeRetry = error instanceof OpenAIResponsesWebSocketSafeRetryError;
|
|
3420
|
+
if (!params.callerSignal?.aborted && !safeRetry) markDegraded();
|
|
3421
|
+
if (!requestDispatched && !params.signal?.aborted) throw new OpenAIResponsesWebSocketPreDispatchError(error);
|
|
3422
|
+
if (!requestDispatched || params.callerSignal?.aborted || error instanceof OpenAIResponsesWebSocketResponseFailedError || safeRetry) throw error;
|
|
3423
|
+
throw new OpenAIResponsesWebSocketPostDispatchError(error);
|
|
3424
|
+
} finally {
|
|
3425
|
+
await iterator.return?.().catch(() => void 0);
|
|
3426
|
+
if (!terminalReceived) finish({ keep: false });
|
|
3427
|
+
}
|
|
3428
|
+
} },
|
|
3429
|
+
request: prepared.request,
|
|
3430
|
+
reusedConnection: lease.reusedConnection,
|
|
3431
|
+
continuationStatus: prepared.continuationStatus,
|
|
3432
|
+
finish
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
function closeOpenAIResponsesWebSocketSessions(sessionId) {
|
|
3436
|
+
for (const [cacheKey, entry] of websocketSessionCache) {
|
|
3437
|
+
if (sessionId && entry.sessionId !== sessionId) continue;
|
|
3438
|
+
invalidateOwnedWebSocketSession(cacheKey, entry, "session_cleanup");
|
|
3439
|
+
}
|
|
3440
|
+
for (const [key, entry] of degradedWebSocketConnections) if (!sessionId || entry.sessionId === sessionId) degradedWebSocketConnections.delete(key);
|
|
3441
|
+
}
|
|
3442
|
+
registerSessionResourceCleanup(closeOpenAIResponsesWebSocketSessions);
|
|
3443
|
+
//#endregion
|
|
3512
3444
|
//#region packages/media-core/src/inline-image-data-url.ts
|
|
3513
3445
|
/** Prefix used to distinguish inline data URLs from remote/local image references. */
|
|
3514
3446
|
const INLINE_IMAGE_DATA_URL_PREFIX = "data:";
|
|
@@ -3653,6 +3585,20 @@ function sanitizeResponsesImagePayload(params) {
|
|
|
3653
3585
|
}
|
|
3654
3586
|
//#endregion
|
|
3655
3587
|
//#region packages/ai/src/transports/openai-responses-client.ts
|
|
3588
|
+
function resolveNativeOpenAIResponsesWebSocketMode(model, transport) {
|
|
3589
|
+
if (transport !== "websocket" && transport !== "websocket-cached" && transport !== "auto") return;
|
|
3590
|
+
if (getAiTransportHost().requiresManagedTransport(model)) return;
|
|
3591
|
+
return supportsNativeOpenAIResponsesEndpoint({
|
|
3592
|
+
provider: model.provider,
|
|
3593
|
+
api: model.api,
|
|
3594
|
+
baseUrl: model.baseUrl
|
|
3595
|
+
}) ? transport : void 0;
|
|
3596
|
+
}
|
|
3597
|
+
function combineWebSocketTimeoutSignal(signal, model, timeoutMs) {
|
|
3598
|
+
const resolvedTimeoutMs = timeoutMs !== void 0 && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : getAiTransportHost().resolveModelRequestTimeoutMs(model);
|
|
3599
|
+
if (resolvedTimeoutMs === void 0 || !Number.isFinite(resolvedTimeoutMs)) return signal;
|
|
3600
|
+
return AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, resolvedTimeoutMs))]);
|
|
3601
|
+
}
|
|
3656
3602
|
function resolveProviderTransportTurnState(model, params) {
|
|
3657
3603
|
const normalizedProvider = model.provider.trim().toLowerCase();
|
|
3658
3604
|
const allowRuntimePluginLoad = normalizedProvider === "openai" || normalizedProvider === "azure-openai" || normalizedProvider === "azure-openai-responses";
|
|
@@ -3681,88 +3627,227 @@ function createOpenAIResponsesClient(model, context, apiKey, optionHeaders, turn
|
|
|
3681
3627
|
...buildOpenAISdkClientOptions(model)
|
|
3682
3628
|
});
|
|
3683
3629
|
}
|
|
3630
|
+
async function postOpenAIResponsesCompaction(params) {
|
|
3631
|
+
const response = await params.client.post("/responses/compact", {
|
|
3632
|
+
...buildOpenAISdkRequestOptions(params.model, params.options?.signal, {
|
|
3633
|
+
timeoutMs: params.options?.timeoutMs,
|
|
3634
|
+
maxRetries: params.options?.maxRetries
|
|
3635
|
+
}),
|
|
3636
|
+
body: {
|
|
3637
|
+
model: params.request.model,
|
|
3638
|
+
input: params.request.input
|
|
3639
|
+
}
|
|
3640
|
+
});
|
|
3641
|
+
const output = isRecord(response) && Array.isArray(response.output) ? response.output : [];
|
|
3642
|
+
const item = output[0];
|
|
3643
|
+
const usage = isRecord(response) && isRecord(response.usage) ? response.usage : void 0;
|
|
3644
|
+
if (!isRecord(response) || response.object !== "response.compaction" || output.length !== 1 || !isRecord(item) || item.type !== "compaction" || typeof item.encrypted_content !== "string" || item.encrypted_content.length === 0 || !usage || typeof usage.input_tokens !== "number" || typeof usage.output_tokens !== "number") throw new Error("Responses compact endpoint did not return exactly one compaction item");
|
|
3645
|
+
return {
|
|
3646
|
+
item,
|
|
3647
|
+
usage,
|
|
3648
|
+
model: params.model,
|
|
3649
|
+
replayMetadata: buildOpenAIResponsesReasoningReplayMetadata(params.model, {
|
|
3650
|
+
authProfileId: params.options?.authProfileId,
|
|
3651
|
+
sessionId: params.options?.sessionId
|
|
3652
|
+
})
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3684
3655
|
function createResponsesTransportExecutor(config) {
|
|
3685
3656
|
return (model, context, options) => {
|
|
3686
3657
|
const responsesOptions = options;
|
|
3658
|
+
const compactRequest = claimResponsesCompactRequest(responsesOptions);
|
|
3687
3659
|
const eventStream = createAssistantMessageEventStream();
|
|
3688
3660
|
const stream = eventStream;
|
|
3689
3661
|
(async () => {
|
|
3690
|
-
const output =
|
|
3691
|
-
role: "assistant",
|
|
3692
|
-
content: [],
|
|
3693
|
-
api: config.outputApi ?? model.api,
|
|
3694
|
-
provider: model.provider,
|
|
3695
|
-
model: model.id,
|
|
3696
|
-
usage: {
|
|
3697
|
-
input: 0,
|
|
3698
|
-
output: 0,
|
|
3699
|
-
cacheRead: 0,
|
|
3700
|
-
cacheWrite: 0,
|
|
3701
|
-
totalTokens: 0,
|
|
3702
|
-
cost: {
|
|
3703
|
-
input: 0,
|
|
3704
|
-
output: 0,
|
|
3705
|
-
cacheRead: 0,
|
|
3706
|
-
cacheWrite: 0,
|
|
3707
|
-
total: 0
|
|
3708
|
-
}
|
|
3709
|
-
},
|
|
3710
|
-
stopReason: "stop",
|
|
3711
|
-
timestamp: Date.now()
|
|
3712
|
-
};
|
|
3662
|
+
const output = createOpenAIResponsesAssistantOutput(model, config.outputApi);
|
|
3713
3663
|
let firstEventAbort;
|
|
3664
|
+
let continuationClaim;
|
|
3714
3665
|
try {
|
|
3715
3666
|
const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
|
|
3667
|
+
const websocketMode = resolveNativeOpenAIResponsesWebSocketMode(model, responsesOptions?.transport);
|
|
3716
3668
|
const turnState = resolveProviderTransportTurnState(model, {
|
|
3717
3669
|
sessionId: options?.sessionId,
|
|
3718
3670
|
turnId: randomUUID(),
|
|
3719
3671
|
attempt: 1,
|
|
3720
|
-
transport: "stream"
|
|
3672
|
+
transport: websocketMode ? "websocket" : "stream"
|
|
3721
3673
|
});
|
|
3674
|
+
const websocketSessionPolicy = websocketMode ? turnState?.websocket : void 0;
|
|
3675
|
+
const websocketHeaders = websocketMode ? buildOpenAIClientHeaders(model, context, options?.headers, websocketSessionPolicy?.headers, options?.sessionId) : void 0;
|
|
3722
3676
|
const client = config.createClient(model, context, apiKey, options?.headers, turnState?.headers, options?.sessionId);
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3677
|
+
const buildRequest = async (replayMode) => {
|
|
3678
|
+
let params = config.buildRequest(model, context, responsesOptions, turnState?.metadata, replayMode);
|
|
3679
|
+
const nextParams = await options?.onPayload?.(params, model);
|
|
3680
|
+
if (nextParams !== void 0) params = nextParams;
|
|
3681
|
+
if (!isOpenAICodexResponsesModel(model)) params = mergeTransportMetadata(params, turnState?.metadata);
|
|
3682
|
+
params = sanitizeOpenAICodexResponsesParams(model, params);
|
|
3683
|
+
params = sanitizeResponsesImagePayload(params);
|
|
3684
|
+
if (options?.openclawCodeModeToolSurface === true) {
|
|
3685
|
+
const visibleToolNames = resolveCodeModeResponsesVisibleToolNames(context);
|
|
3686
|
+
const allowedHostedToolTypes = responsesOptions?.openclawCodeModeAllowedHostedToolTypes;
|
|
3687
|
+
enforceCodeModeResponsesToolSurface(params, visibleToolNames, allowedHostedToolTypes);
|
|
3688
|
+
assertCodeModeResponsesToolSurface(params, visibleToolNames, allowedHostedToolTypes);
|
|
3689
|
+
}
|
|
3690
|
+
return params;
|
|
3691
|
+
};
|
|
3692
|
+
const params = await buildRequest("checkpoint");
|
|
3693
|
+
if (compactRequest) {
|
|
3694
|
+
const compacted = await postOpenAIResponsesCompaction({
|
|
3695
|
+
client,
|
|
3696
|
+
model,
|
|
3697
|
+
request: params,
|
|
3698
|
+
options: responsesOptions
|
|
3699
|
+
});
|
|
3700
|
+
output.usage.input = compacted.usage.input_tokens;
|
|
3701
|
+
output.usage.output = compacted.usage.output_tokens;
|
|
3702
|
+
output.usage.totalTokens = compacted.usage.input_tokens + compacted.usage.output_tokens;
|
|
3703
|
+
compactRequest.resolve(compacted);
|
|
3704
|
+
stream.push({
|
|
3705
|
+
type: "done",
|
|
3706
|
+
reason: output.stopReason,
|
|
3707
|
+
message: output
|
|
3708
|
+
});
|
|
3709
|
+
stream.end();
|
|
3710
|
+
return;
|
|
3733
3711
|
}
|
|
3712
|
+
const sessionId = options?.sessionId;
|
|
3713
|
+
if (config.httpContinuation && !websocketMode && !getAiTransportHost().requiresManagedTransport(model) && supportsNativeOpenAIResponsesEndpoint({
|
|
3714
|
+
provider: model.provider,
|
|
3715
|
+
api: model.api,
|
|
3716
|
+
baseUrl: model.baseUrl
|
|
3717
|
+
}) && sessionId && params.store === true && !params.previous_response_id) continuationClaim = claimOpenAIResponsesHttpContinuation({
|
|
3718
|
+
sessionId,
|
|
3719
|
+
apiKey,
|
|
3720
|
+
baseUrl: model.baseUrl,
|
|
3721
|
+
headers: buildOpenAIClientHeaders(model, context, options?.headers, turnState?.headers, sessionId),
|
|
3722
|
+
request: params
|
|
3723
|
+
});
|
|
3724
|
+
const observePrompt = createResponsesPromptEgressObserver(responsesOptions, context.systemPrompt);
|
|
3734
3725
|
const requestStartedAt = Date.now();
|
|
3735
|
-
|
|
3736
|
-
const
|
|
3726
|
+
let started = false;
|
|
3727
|
+
const startStream = () => {
|
|
3728
|
+
if (!started) {
|
|
3729
|
+
started = true;
|
|
3730
|
+
stream.push({
|
|
3731
|
+
type: "start",
|
|
3732
|
+
partial: output
|
|
3733
|
+
});
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
const firstEvent = createFirstStreamEventAbortController(options?.signal);
|
|
3737
|
+
firstEventAbort = firstEvent;
|
|
3738
|
+
const requestOptions = buildOpenAISdkRequestOptions(model, firstEvent.signal, {
|
|
3737
3739
|
stream: config.streamRequest,
|
|
3738
3740
|
timeoutMs: options?.timeoutMs,
|
|
3739
3741
|
maxRetries: options?.maxRetries
|
|
3740
3742
|
});
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3743
|
+
const websocketSignal = combineWebSocketTimeoutSignal(firstEvent.signal, model, requestOptions?.timeout);
|
|
3744
|
+
emitModelTransportDebug(log, `[responses] start provider=${model.provider} api=${model.api} model=${model.id} requestIdHash=${redactIdentifier(options?.requestId, { len: 64 })} baseUrl=${formatModelTransportDebugBaseUrl(model.baseUrl)} timeoutMs=${safeDebugValue(requestOptions?.timeout)} apiKey=${apiKey ? "present" : "missing"} ${summarizeResponsesPayload(params)}`);
|
|
3745
|
+
let continuationBaseline;
|
|
3746
|
+
const createSseStream = async (initialRequest = continuationClaim?.request ?? params, initialAttemptKind = "initial", initialRejectedCompaction) => {
|
|
3747
|
+
const { stream: rawResponseStream, response, attempt } = await config.createResponseStream({
|
|
3748
|
+
client,
|
|
3749
|
+
request: initialRequest,
|
|
3750
|
+
requestOptions,
|
|
3751
|
+
model,
|
|
3752
|
+
observePrompt,
|
|
3753
|
+
initialAttemptKind,
|
|
3754
|
+
initialRejectedCompaction,
|
|
3755
|
+
buildFullHistoryRequest: () => buildRequest("full-history"),
|
|
3756
|
+
onCompactionRejected: (checkpoint) => suppressOpenAIResponsesCompaction(output, model, responsesOptions, checkpoint)
|
|
3757
|
+
});
|
|
3758
|
+
if (continuationClaim) continuationBaseline = attempt.request.previous_response_id ? params : attempt.request;
|
|
3759
|
+
return withProviderResponseHook({
|
|
3760
|
+
stream: observeResponsesStream(rawResponseStream, model, requestStartedAt),
|
|
3761
|
+
signal: firstEvent.signal,
|
|
3762
|
+
abort: firstEvent.abort,
|
|
3763
|
+
hook: createOpenAIResponseHook(options?.onResponse, response, model),
|
|
3764
|
+
onReady: () => {
|
|
3765
|
+
emitModelTransportDebug(log, `[responses] headers provider=${model.provider} api=${model.api} model=${model.id} transport=sse elapsedMs=${Date.now() - requestStartedAt}`);
|
|
3766
|
+
startStream();
|
|
3767
|
+
}
|
|
3768
|
+
});
|
|
3769
|
+
};
|
|
3770
|
+
let responseStream;
|
|
3771
|
+
let finishWebSocket;
|
|
3772
|
+
let transport = "sse";
|
|
3773
|
+
const logWebSocketFallback = (reason) => emitModelTransportDebug(log, `[responses] websocket_fallback provider=${model.provider} api=${model.api} model=${model.id} reason=${reason}`);
|
|
3774
|
+
const closeWebSocketForFallback = (reason) => {
|
|
3775
|
+
finishWebSocket?.({ keep: false });
|
|
3776
|
+
finishWebSocket = void 0;
|
|
3777
|
+
transport = "sse";
|
|
3778
|
+
logWebSocketFallback(reason);
|
|
3779
|
+
};
|
|
3780
|
+
if (websocketMode) try {
|
|
3781
|
+
const websocket = createOpenAIResponsesWebSocketStream({
|
|
3782
|
+
client,
|
|
3783
|
+
request: params,
|
|
3784
|
+
mode: websocketMode,
|
|
3785
|
+
sessionId: options?.sessionId,
|
|
3786
|
+
headers: websocketHeaders,
|
|
3787
|
+
signal: websocketSignal,
|
|
3788
|
+
callerSignal: options?.signal,
|
|
3789
|
+
degradeCooldownMs: websocketSessionPolicy?.degradeCooldownMs
|
|
3790
|
+
});
|
|
3791
|
+
finishWebSocket = websocket.finish;
|
|
3792
|
+
observePrompt?.(websocket.request, {
|
|
3793
|
+
egress: "responses-websocket",
|
|
3794
|
+
payloadVariant: "initial"
|
|
3795
|
+
});
|
|
3796
|
+
transport = "websocket";
|
|
3797
|
+
emitModelTransportDebug(log, `[responses] websocket_selected provider=${model.provider} api=${model.api} model=${model.id} mode=${websocketMode} reused=${websocket.reusedConnection} continuation=${websocket.continuationStatus === "continued"} continuationStatus=${websocket.continuationStatus} sessionIdHash=${redactIdentifier(options?.sessionId)} headersHash=${redactIdentifier(JSON.stringify(Object.entries(websocketHeaders ?? {}).toSorted(([a], [b]) => a.localeCompare(b))))}`);
|
|
3798
|
+
responseStream = { async *[Symbol.asyncIterator]() {
|
|
3799
|
+
try {
|
|
3800
|
+
for await (const event of websocket.stream) {
|
|
3801
|
+
startStream();
|
|
3802
|
+
yield event;
|
|
3803
|
+
}
|
|
3804
|
+
} catch (error) {
|
|
3805
|
+
if (error instanceof OpenAIResponsesWebSocketSafeRetryError) {
|
|
3806
|
+
const encryptedContentRejected = isInvalidEncryptedContentError(error);
|
|
3807
|
+
const recovery = encryptedContentRejected ? await resolveNextResponsesEncryptedContentAttempt({
|
|
3808
|
+
kind: "initial",
|
|
3809
|
+
request: {
|
|
3810
|
+
...websocket.request,
|
|
3811
|
+
stream: true
|
|
3812
|
+
}
|
|
3813
|
+
}, error, { buildFullHistoryRequest: () => buildRequest("full-history") }) : void 0;
|
|
3814
|
+
if (encryptedContentRejected && !recovery) throw error;
|
|
3815
|
+
closeWebSocketForFallback(`safe_server_error code=${error.code} status=${safeDebugValue(error.status)} param=${safeDebugValue(error.param)}`);
|
|
3816
|
+
yield* await createSseStream(recovery?.request ?? await buildRequest("full-history"), recovery?.kind ?? "continuation-rejected", recovery?.rejectedCompaction);
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3819
|
+
if (websocketSignal.aborted || !(error instanceof OpenAIResponsesWebSocketPreDispatchError)) throw error;
|
|
3820
|
+
transport = "sse";
|
|
3821
|
+
logWebSocketFallback("before_first_event");
|
|
3822
|
+
yield* await createSseStream();
|
|
3823
|
+
}
|
|
3824
|
+
} };
|
|
3825
|
+
} catch {
|
|
3826
|
+
closeWebSocketForFallback("setup_failure");
|
|
3827
|
+
responseStream = await createSseStream();
|
|
3828
|
+
}
|
|
3829
|
+
else responseStream = await createSseStream();
|
|
3830
|
+
try {
|
|
3831
|
+
const terminal = await processResponsesStream(responseStream, output, stream, model, {
|
|
3832
|
+
...config.pricingOptions?.(responsesOptions, model),
|
|
3833
|
+
firstEventTimeoutMs: getFirstStreamEventTimeoutMs(options) ?? config.firstEventTimeoutMs,
|
|
3834
|
+
abortFirstEventStream: firstEvent.abort,
|
|
3835
|
+
onFirstEventTimeout: getFirstStreamEventTimeoutHandler(options),
|
|
3836
|
+
signal: options?.signal,
|
|
3837
|
+
reasoningReplayMetadata: buildOpenAIResponsesReasoningReplayMetadata(model, {
|
|
3838
|
+
authProfileId: responsesOptions?.authProfileId,
|
|
3839
|
+
sessionId: options?.sessionId
|
|
3840
|
+
})
|
|
3841
|
+
});
|
|
3842
|
+
finishWebSocket?.();
|
|
3843
|
+
if (options?.signal?.aborted) throw transportAbortError(options.signal);
|
|
3844
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") throw new Error(output.errorMessage ?? "An unknown error occurred");
|
|
3845
|
+
if (continuationClaim && continuationBaseline && terminal) continuationClaim.commit(continuationBaseline, terminal);
|
|
3846
|
+
} catch (error) {
|
|
3847
|
+
finishWebSocket?.({ keep: false });
|
|
3848
|
+
throw error;
|
|
3849
|
+
}
|
|
3850
|
+
emitModelTransportDebug(log, `[responses] completed provider=${model.provider} api=${model.api} model=${model.id} transport=${transport} elapsedMs=${Date.now() - requestStartedAt}`);
|
|
3766
3851
|
stream.push({
|
|
3767
3852
|
type: "done",
|
|
3768
3853
|
reason: output.stopReason,
|
|
@@ -3770,9 +3855,20 @@ function createResponsesTransportExecutor(config) {
|
|
|
3770
3855
|
});
|
|
3771
3856
|
stream.end();
|
|
3772
3857
|
} catch (error) {
|
|
3858
|
+
if (compactRequest) {
|
|
3859
|
+
compactRequest.reject(error);
|
|
3860
|
+
Object.assign(output, projectProviderError(error, options?.signal));
|
|
3861
|
+
stream.push({
|
|
3862
|
+
type: "error",
|
|
3863
|
+
reason: output.stopReason,
|
|
3864
|
+
error: output
|
|
3865
|
+
});
|
|
3866
|
+
stream.end();
|
|
3867
|
+
return;
|
|
3868
|
+
}
|
|
3773
3869
|
if (error instanceof ResponsesStreamFailure && error.observation) logResponsesFailedNoDetails(error.observation);
|
|
3774
3870
|
log.warn(`[responses] error provider=${model.provider} api=${model.api} model=${model.id} ` + summarizeOpenAITransportError(error));
|
|
3775
|
-
|
|
3871
|
+
Object.assign(output, projectProviderError(error, options?.signal));
|
|
3776
3872
|
stream.push({
|
|
3777
3873
|
type: "error",
|
|
3778
3874
|
reason: output.stopReason,
|
|
@@ -3780,6 +3876,7 @@ function createResponsesTransportExecutor(config) {
|
|
|
3780
3876
|
});
|
|
3781
3877
|
stream.end();
|
|
3782
3878
|
} finally {
|
|
3879
|
+
continuationClaim?.release();
|
|
3783
3880
|
firstEventAbort?.dispose();
|
|
3784
3881
|
}
|
|
3785
3882
|
})();
|
|
@@ -3789,12 +3886,13 @@ function createResponsesTransportExecutor(config) {
|
|
|
3789
3886
|
function createOpenAIResponsesTransportStreamFn() {
|
|
3790
3887
|
return createResponsesTransportExecutor({
|
|
3791
3888
|
streamRequest: true,
|
|
3889
|
+
httpContinuation: true,
|
|
3792
3890
|
createClient: createOpenAIResponsesClient,
|
|
3793
3891
|
buildRequest: buildOpenAIResponsesParams,
|
|
3794
3892
|
createResponseStream: createResponsesStreamWithEncryptedContentRetry,
|
|
3795
|
-
pricingOptions: (options) => ({
|
|
3893
|
+
pricingOptions: (options, model) => ({
|
|
3796
3894
|
serviceTier: options?.serviceTier,
|
|
3797
|
-
applyServiceTierPricing
|
|
3895
|
+
applyServiceTierPricing: (usage, serviceTier) => applyResponsesServiceTierPricing(usage, serviceTier, model)
|
|
3798
3896
|
})
|
|
3799
3897
|
});
|
|
3800
3898
|
}
|
|
@@ -3803,8 +3901,8 @@ function createAzureOpenAIResponsesTransportStreamFn() {
|
|
|
3803
3901
|
outputApi: "azure-openai-responses",
|
|
3804
3902
|
firstEventTimeoutMs: AZURE_RESPONSES_FIRST_EVENT_TIMEOUT_MS,
|
|
3805
3903
|
createClient: createAzureOpenAIClient,
|
|
3806
|
-
buildRequest: (model, context, options, metadata) => buildAzureOpenAIResponsesParams(model, context, options, resolveAzureDeploymentName(model), metadata),
|
|
3807
|
-
createResponseStream:
|
|
3904
|
+
buildRequest: (model, context, options, metadata, replayMode) => buildAzureOpenAIResponsesParams(model, context, options, resolveAzureDeploymentName(model), metadata, replayMode),
|
|
3905
|
+
createResponseStream: createResponsesStreamWithEncryptedContentRetry
|
|
3808
3906
|
});
|
|
3809
3907
|
}
|
|
3810
3908
|
function normalizeAzureBaseUrl(baseUrl) {
|
|
@@ -3832,8 +3930,8 @@ function createAzureOpenAIClient(model, context, apiKey, optionHeaders, turnHead
|
|
|
3832
3930
|
apiVersion: resolveAzureOpenAIApiVersion()
|
|
3833
3931
|
});
|
|
3834
3932
|
}
|
|
3835
|
-
function buildAzureOpenAIResponsesParams(model, context, options, deploymentName, metadata) {
|
|
3836
|
-
const params = buildOpenAIResponsesParams(model, context, options, metadata);
|
|
3933
|
+
function buildAzureOpenAIResponsesParams(model, context, options, deploymentName, metadata, replayMode = "checkpoint") {
|
|
3934
|
+
const params = buildOpenAIResponsesParams(model, context, options, metadata, replayMode);
|
|
3837
3935
|
params.model = deploymentName;
|
|
3838
3936
|
delete params.store;
|
|
3839
3937
|
return params;
|
|
@@ -3858,11 +3956,8 @@ const responsesTesting = {
|
|
|
3858
3956
|
buildOpenAIResponsesReasoningReplayMetadata,
|
|
3859
3957
|
isInvalidEncryptedContentError,
|
|
3860
3958
|
normalizeResponsesFailedEvent,
|
|
3861
|
-
prepareOpenAIResponsesReasoningItemForReplay,
|
|
3862
3959
|
createResponsesStreamWithEncryptedContentRetry,
|
|
3863
3960
|
resolveAzureOpenAIApiVersion,
|
|
3864
|
-
stripResponsesRequestEncryptedContent,
|
|
3865
|
-
tagOpenAIResponsesReasoningReplayItem,
|
|
3866
3961
|
summarizeResponsesFailedNoDetailsObservation,
|
|
3867
3962
|
summarizeResponsesPayload,
|
|
3868
3963
|
summarizeResponsesTools,
|
|
@@ -3871,6 +3966,50 @@ const responsesTesting = {
|
|
|
3871
3966
|
};
|
|
3872
3967
|
if (process.env.VITEST || false) globalThis.openclawOpenAIResponsesTransportTestApi = responsesTesting;
|
|
3873
3968
|
//#endregion
|
|
3969
|
+
//#region packages/ai/src/transports/provider-compaction-replay.ts
|
|
3970
|
+
/** Whether provider replay state is a prefix-bound server compaction checkpoint. */
|
|
3971
|
+
function isCompactionReplayCheckpoint(replay) {
|
|
3972
|
+
const type = replay && typeof replay === "object" ? replay.type : void 0;
|
|
3973
|
+
return type === "anthropic-compaction" || type === "openai-responses-compaction";
|
|
3974
|
+
}
|
|
3975
|
+
/** Strip prefix-bound checkpoints after local history rewrites. */
|
|
3976
|
+
function stripCompactionReplayCheckpoint(message) {
|
|
3977
|
+
if (!isCompactionReplayCheckpoint(message.providerReplay)) return message;
|
|
3978
|
+
const replaySafeMessage = { ...message };
|
|
3979
|
+
delete replaySafeMessage.providerReplay;
|
|
3980
|
+
return replaySafeMessage;
|
|
3981
|
+
}
|
|
3982
|
+
/** Strip prefix-bound checkpoint state from an in-place message rewrite. */
|
|
3983
|
+
function stripCompactionReplayCheckpointInPlace(message) {
|
|
3984
|
+
if (isCompactionReplayCheckpoint(message.providerReplay)) delete message.providerReplay;
|
|
3985
|
+
}
|
|
3986
|
+
/** Reindex a prefix-bound checkpoint after known content removals. */
|
|
3987
|
+
function replaceCompactionReplayOwnerContent(message, content) {
|
|
3988
|
+
const next = {
|
|
3989
|
+
...message,
|
|
3990
|
+
content
|
|
3991
|
+
};
|
|
3992
|
+
const replay = message.providerReplay;
|
|
3993
|
+
if (!isCompactionReplayCheckpoint(replay)) return next;
|
|
3994
|
+
const replayIndex = replay.replayIndex ?? 0;
|
|
3995
|
+
if (content.length === 0 || replayIndex > message.content.length) return stripCompactionReplayCheckpoint(next);
|
|
3996
|
+
let sourceIndex = 0;
|
|
3997
|
+
let nextReplayIndex = 0;
|
|
3998
|
+
if (!content.every((block) => {
|
|
3999
|
+
const index = message.content.indexOf(block, sourceIndex);
|
|
4000
|
+
sourceIndex = index + 1;
|
|
4001
|
+
nextReplayIndex += index >= 0 && index < replayIndex ? 1 : 0;
|
|
4002
|
+
return index >= 0;
|
|
4003
|
+
})) return stripCompactionReplayCheckpoint(next);
|
|
4004
|
+
return nextReplayIndex === replayIndex ? next : {
|
|
4005
|
+
...next,
|
|
4006
|
+
providerReplay: {
|
|
4007
|
+
...replay,
|
|
4008
|
+
replayIndex: nextReplayIndex
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
}
|
|
4012
|
+
//#endregion
|
|
3874
4013
|
//#region packages/ai/src/transports/provider-transport-stream.ts
|
|
3875
4014
|
const SUPPORTED_TRANSPORT_APIS = /* @__PURE__ */ new Set([
|
|
3876
4015
|
"openai-responses",
|
|
@@ -3881,10 +4020,7 @@ const SUPPORTED_TRANSPORT_APIS = /* @__PURE__ */ new Set([
|
|
|
3881
4020
|
"google-generative-ai"
|
|
3882
4021
|
]);
|
|
3883
4022
|
const SIMPLE_TRANSPORT_API_ALIAS = {
|
|
3884
|
-
"openai-responses": "openclaw-openai-responses-transport",
|
|
3885
|
-
"openai-chatgpt-responses": "openclaw-openai-chatgpt-responses-transport",
|
|
3886
4023
|
"openai-completions": "openclaw-openai-completions-transport",
|
|
3887
|
-
"azure-openai-responses": "openclaw-azure-openai-responses-transport",
|
|
3888
4024
|
"anthropic-messages": "openclaw-anthropic-messages-transport",
|
|
3889
4025
|
"google-generative-ai": "openclaw-google-generative-ai-transport"
|
|
3890
4026
|
};
|
|
@@ -3937,6 +4073,10 @@ function isTransportAwareApiSupported(api) {
|
|
|
3937
4073
|
}
|
|
3938
4074
|
/** Maps public model APIs to the internal transport API id used by simple runtime dispatch. */
|
|
3939
4075
|
function resolveTransportAwareSimpleApi(api) {
|
|
4076
|
+
if (OPENAI_RESPONSES_APIS.has(api)) {
|
|
4077
|
+
const alias = `openclaw-${api}-transport`;
|
|
4078
|
+
return OPENAI_RESPONSES_APIS.has(alias) ? alias : void 0;
|
|
4079
|
+
}
|
|
3940
4080
|
return SIMPLE_TRANSPORT_API_ALIAS[api];
|
|
3941
4081
|
}
|
|
3942
4082
|
/** Creates a managed transport stream only when request overrides require it. */
|
|
@@ -3971,6 +4111,7 @@ function buildTransportAwareSimpleStreamFn(model, ctx) {
|
|
|
3971
4111
|
//#endregion
|
|
3972
4112
|
//#region packages/ai/src/transports/simple-completion-transport.ts
|
|
3973
4113
|
const PROVIDER_SIMPLE_COMPLETION_API_PREFIX = "openclaw-provider-simple:";
|
|
4114
|
+
const PROVIDER_STREAM_API_PREFIX = "openclaw-provider-stream:";
|
|
3974
4115
|
const INVALID_CODEX_BASE_URL_MESSAGE = "OpenAI Codex Responses baseUrl must not include query parameters or fragments";
|
|
3975
4116
|
function registerCustomApi(registry, api, streamFn) {
|
|
3976
4117
|
getAiTransportHost().registerCustomApi(registry, api, streamFn);
|
|
@@ -4023,12 +4164,21 @@ function resolveProviderSimpleCompletionApi(model) {
|
|
|
4023
4164
|
];
|
|
4024
4165
|
return `${PROVIDER_SIMPLE_COMPLETION_API_PREFIX}${parts.map((part) => encodeURIComponent(part)).join(":")}`;
|
|
4025
4166
|
}
|
|
4026
|
-
function
|
|
4167
|
+
function resolveProviderStreamApi(model) {
|
|
4168
|
+
const parts = [
|
|
4169
|
+
model.provider,
|
|
4170
|
+
model.id,
|
|
4171
|
+
model.api,
|
|
4172
|
+
model.baseUrl || "default"
|
|
4173
|
+
];
|
|
4174
|
+
return `${PROVIDER_STREAM_API_PREFIX}${parts.map((part) => encodeURIComponent(part)).join(":")}`;
|
|
4175
|
+
}
|
|
4176
|
+
function applyProviderSimpleCompletionWrapper(registry, model, cfg, hookSourceApi = model.api) {
|
|
4027
4177
|
if (model.api.startsWith(PROVIDER_SIMPLE_COMPLETION_API_PREFIX)) return model;
|
|
4028
4178
|
const sourceProvider = registry.getApiProvider(model.api);
|
|
4029
4179
|
if (!sourceProvider) return model;
|
|
4030
|
-
const
|
|
4031
|
-
const sourceStreamFn = (runtimeModel, context, options) => sourceProvider.streamSimple(projectModel(runtimeModel, { api:
|
|
4180
|
+
const dispatchApi = model.api;
|
|
4181
|
+
const sourceStreamFn = (runtimeModel, context, options) => sourceProvider.streamSimple(projectModel(runtimeModel, { api: dispatchApi }), context, options);
|
|
4032
4182
|
const streamFn = getAiTransportHost().plugin.wrapSimpleCompletionStream({
|
|
4033
4183
|
provider: model.provider,
|
|
4034
4184
|
config: cfg,
|
|
@@ -4037,6 +4187,7 @@ function applyProviderSimpleCompletionWrapper(registry, model, cfg) {
|
|
|
4037
4187
|
provider: model.provider,
|
|
4038
4188
|
modelId: model.id,
|
|
4039
4189
|
model,
|
|
4190
|
+
sourceApi: hookSourceApi,
|
|
4040
4191
|
streamFn: sourceStreamFn
|
|
4041
4192
|
}
|
|
4042
4193
|
});
|
|
@@ -4069,7 +4220,7 @@ function wrapPluginProviderStream(streamFn) {
|
|
|
4069
4220
|
});
|
|
4070
4221
|
};
|
|
4071
4222
|
}
|
|
4072
|
-
function
|
|
4223
|
+
function prepareProviderStreamModel(params) {
|
|
4073
4224
|
const pluginModel = resolveModelHeaderSentinels(params.model);
|
|
4074
4225
|
const providerStreamFn = getAiTransportHost().plugin.resolveProviderStream({
|
|
4075
4226
|
provider: params.model.provider,
|
|
@@ -4083,28 +4234,31 @@ function registerProviderStreamForModel(params) {
|
|
|
4083
4234
|
});
|
|
4084
4235
|
const transportFallback = providerStreamFn ? void 0 : createTransportAwareStreamFnForModel(params.model.api === "google-generative-ai" ? pluginModel : params.model, { cfg: params.cfg });
|
|
4085
4236
|
const streamFn = providerStreamFn ? wrapPluginProviderStream(providerStreamFn) : transportFallback && params.model.api === "google-generative-ai" ? wrapPluginProviderStream(transportFallback) : transportFallback;
|
|
4086
|
-
|
|
4237
|
+
if (!streamFn) return;
|
|
4238
|
+
const api = params.apiRegistry.getApiProvider(params.model.api) ? resolveProviderStreamApi(params.model) : params.model.api;
|
|
4239
|
+
if (!registerCustomApi(params.apiRegistry, api, streamFn)) return;
|
|
4240
|
+
return api === params.model.api ? params.model : projectModel(params.model, { api });
|
|
4087
4241
|
}
|
|
4088
4242
|
function prepareModelForSimpleCompletion(params) {
|
|
4089
4243
|
const { apiRegistry, model, cfg } = params;
|
|
4090
|
-
|
|
4244
|
+
const providerStreamModel = prepareProviderStreamModel({
|
|
4091
4245
|
model,
|
|
4092
4246
|
cfg,
|
|
4093
4247
|
apiRegistry
|
|
4094
|
-
})
|
|
4248
|
+
});
|
|
4249
|
+
if (providerStreamModel) return applyProviderSimpleCompletionWrapper(apiRegistry, providerStreamModel, cfg, model.api);
|
|
4095
4250
|
const codexTransportModel = prepareCodexSimpleTransportModel(apiRegistry, model, cfg);
|
|
4096
|
-
if (codexTransportModel) return applyProviderSimpleCompletionWrapper(apiRegistry, codexTransportModel, cfg);
|
|
4251
|
+
if (codexTransportModel) return applyProviderSimpleCompletionWrapper(apiRegistry, codexTransportModel, cfg, model.api);
|
|
4097
4252
|
const transportAwareModel = prepareTransportAwareSimpleModel(model, { cfg });
|
|
4098
4253
|
if (transportAwareModel !== model) {
|
|
4099
4254
|
const streamFn = buildTransportAwareSimpleStreamFn(model, { cfg });
|
|
4100
|
-
if (streamFn && registerCustomApi(apiRegistry, transportAwareModel.api, streamFn)) return applyProviderSimpleCompletionWrapper(apiRegistry, transportAwareModel, cfg);
|
|
4255
|
+
if (streamFn && registerCustomApi(apiRegistry, transportAwareModel.api, streamFn)) return applyProviderSimpleCompletionWrapper(apiRegistry, transportAwareModel, cfg, model.api);
|
|
4101
4256
|
}
|
|
4102
|
-
if (model.api === "google-generative-ai") return applyProviderSimpleCompletionWrapper(apiRegistry, getAiTransportHost().prepareGoogleSimpleCompletionModel(apiRegistry, model), cfg);
|
|
4103
4257
|
if (model.provider === "anthropic-vertex") {
|
|
4104
4258
|
const api = resolveAnthropicVertexSimpleApi(model.baseUrl);
|
|
4105
|
-
if (registerCustomApi(apiRegistry, api, getAiTransportHost().plugin.createAnthropicVertexStream(model))) return applyProviderSimpleCompletionWrapper(apiRegistry, projectModel(model, { api }), cfg);
|
|
4259
|
+
if (registerCustomApi(apiRegistry, api, getAiTransportHost().plugin.createAnthropicVertexStream(model))) return applyProviderSimpleCompletionWrapper(apiRegistry, projectModel(model, { api }), cfg, model.api);
|
|
4106
4260
|
}
|
|
4107
4261
|
return applyProviderSimpleCompletionWrapper(apiRegistry, model, cfg);
|
|
4108
4262
|
}
|
|
4109
4263
|
//#endregion
|
|
4110
|
-
export { GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, applyAnthropicEphemeralCacheControlMarkers, applyAnthropicPayloadPolicyToParams, applyOpenAIResponsesPayloadPolicy, assertCodeModeResponsesToolSurface, assignTransportErrorDetails, buildOpenAIClientHeaders, buildOpenAICompletionsParams, buildOpenAISdkClientOptions, buildOpenAISdkRequestOptions, buildTransportAwareSimpleStreamFn, canonicalizeMaxTokensParam, coerceTransportToolCallArguments, createAnthropicMessagesTransportStreamFn, createAzureOpenAIResponsesTransportStreamFn, createBoundaryAwareStreamFnForModel, createDeepSeekTextFilter, createEmptyTransportUsage, createModelStreamCooperativeScheduler, createOpenAICompletionsTransportStreamFn, createOpenAIResponsesTransportStreamFn, createOpenClawTransportStreamFnForModel, createTransportAwareStreamFnForModel, createWritableTransportEventStream, detectOpenAICompletionsCompat, emitModelTransportDebug, enforceCodeModeResponsesToolSurface, failTransportStream, filterCodeModePayloadTools, finalizeTransportStream, flattenCompletionMessagesToStringContent, formatModelTransportDebugBaseUrl, formatModelTransportDebugUrl, getCompat, hasOpenAICompatibleConversationTurn, isCodeModeModelVisibleToolName, isOpenAICodexResponsesModel, log, mergeTransportHeaders, mergeTransportMetadata, normalizeCodexResponsesBaseUrlForOpenAISdk, parseJsonObjectPreservingUnsafeIntegers, parseJsonPreservingUnsafeIntegers, prepareModelForSimpleCompletion, prepareTransportAwareSimpleModel, quoteUnsafeIntegerLiterals, readCodeModePayloadToolName, resolveAnthropicEphemeralCacheControl, resolveAnthropicMessagesUrl, resolveAnthropicPayloadPolicy, resolveCodeModeResponsesVisibleToolNames, resolveMaxTokensParam, resolveModelPayloadDebugMode, resolveModelSseDebugMode, resolveOpenAICompletionsCompat, resolveOpenAIReasoningEffortMap, resolveOpenAIResponsesPayloadPolicy, resolveOpenAIStrictToolFlagWithDiagnostics, resolvePromptCacheKey, resolveReplayableResponsesMessageId, resolveTransportAwareSimpleApi, sanitizeNonEmptyTransportPayloadText, sanitizeResponsesImagePayload, sanitizeTransportPayloadText, sortPromptCacheToolsByName as sortTransportToolsByName, stripCompletionMessagesToRoleContent, throwIfModelStreamAborted, transportAbortError, usesNativeOpenAICodexResponsesBackend };
|
|
4264
|
+
export { GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, applyAnthropicCacheControlToMessages, applyAnthropicEphemeralCacheControlMarkers, applyAnthropicPayloadPolicyToParams, applyOpenAIResponsesPayloadPolicy, assertCodeModeResponsesToolSurface, assignTransportErrorDetails, buildOpenAIClientHeaders, buildOpenAICompletionsParams, buildOpenAISdkClientOptions, buildOpenAISdkRequestOptions, buildTransportAwareSimpleStreamFn, canonicalizeMaxTokensParam, captureOpenAIResponsesCompaction, coerceTransportToolCallArguments, createAnthropicMessagesTransportStreamFn, createAzureOpenAIResponsesTransportStreamFn, createBoundaryAwareStreamFnForModel, createDeepSeekTextFilter, createEmptyTransportUsage, createModelStreamCooperativeScheduler, createOpenAICompletionsTransportStreamFn, createOpenAIResponseHook, createOpenAIResponsesTransportStreamFn, createOpenClawTransportStreamFnForModel, createTransportAwareStreamFnForModel, createWritableTransportEventStream, detectOpenAICompletionsCompat, emitModelTransportDebug, enforceCodeModeResponsesToolSurface, failTransportStream, filterCodeModePayloadTools, finalizeTransportStream, flattenCompletionMessagesToStringContent, formatModelTransportDebugBaseUrl, formatModelTransportDebugUrl, getCompat, googleFlashSupportsMinimalThinking, hasOpenAICompatibleConversationTurn, isCodeModeModelVisibleToolName, isCompactionReplayCheckpoint, isOpenAICodexResponsesModel, isOpenAICompletionsThinkingEnabled, log, measureUtf8AppendBytes, mergeTransportHeaders, mergeTransportMetadata, normalizeCodexResponsesBaseUrlForOpenAISdk, parseJsonObjectPreservingUnsafeIntegers, parseJsonPreservingUnsafeIntegers, parseOpenAICompletionsUsage, prepareModelForSimpleCompletion, prepareTransportAwareSimpleModel, quoteUnsafeIntegerLiterals, readCodeModePayloadToolName, readOpenAICompletionsContentDeltas, replaceCompactionReplayOwnerContent, requestPreparedOpenAIResponsesCompaction, resolveAnthropicEphemeralCacheControl, resolveAnthropicMessagesUrl, resolveAnthropicPayloadPolicy, resolveAnthropicServerCompactionPlan, resolveCodeModeResponsesVisibleToolNames, resolveMaxTokensParam, resolveModelPayloadDebugMode, resolveModelSseDebugMode, resolveOpenAICompletionsCompat, resolveOpenAIReasoningEffortMap, resolveOpenAIResponsesCompactEndpointPlan, resolveOpenAIResponsesPayloadPolicy, resolveOpenAIResponsesServerCompactionPlan, resolveOpenAIStrictToolFlagWithDiagnostics, resolvePromptCacheKey, resolveReplayableResponsesMessageId, resolveTransportAwareSimpleApi, sanitizeNonEmptyTransportPayloadText, sanitizeResponsesImagePayload, sanitizeTransportPayloadText, sortPromptCacheToolsByName as sortTransportToolsByName, stripCompactionReplayCheckpoint, stripCompactionReplayCheckpointInPlace, stripCompletionMessagesToRoleContent, throwIfModelStreamAborted, transportAbortError, usesNativeOpenAICodexResponsesBackend, withProviderResponseHook };
|