@openclaw/ai 2026.9.2 → 2026.9.4

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.
Files changed (115) hide show
  1. package/README.md +3 -2
  2. package/dist/anthropic-C4Qu4H0Z.mjs +377 -0
  3. package/dist/{anthropic-payload-policy-d46X2pR0.d.mts → anthropic-payload-policy-BZ8umAbk.d.mts} +16 -6
  4. package/dist/{anthropic-compaction-replay-DB2FGLQc.mjs → anthropic-stream-reducer-CILWF7JD.mjs} +931 -184
  5. package/dist/{api-registry-DWtPjzyn.d.mts → api-registry-ByUwIR0e.d.mts} +2 -2
  6. package/dist/assistant-output-tLt4H-iQ.mjs +16 -0
  7. package/dist/assistant-text-phase-C20rxWwP.mjs +55 -0
  8. package/dist/{azure-openai-responses-C5bZRfAP.mjs → azure-openai-responses-BAlqlKKc.mjs} +5 -5
  9. package/dist/{base64-CEFBpSkN.mjs → base64-D-su8YVo.mjs} +1 -27
  10. package/dist/{diagnostics-DfFyKeX_.mjs → diagnostics-QuErwCIl.mjs} +96 -21
  11. package/dist/diagnostics.d.mts +3 -1
  12. package/dist/diagnostics.mjs +3 -3
  13. package/dist/{event-stream-BhT5T1Ay.d.mts → event-stream-Bt5Y4Pav.d.mts} +11 -3
  14. package/dist/event-stream-C3WGFsum.d.mts +1 -0
  15. package/dist/{event-stream-BgDvQeum.mjs → event-stream-D8PARQfL.mjs} +45 -1
  16. package/dist/event-stream.d.mts +2 -2
  17. package/dist/event-stream.mjs +1 -1
  18. package/dist/{github-copilot-headers-NCJtz9i0.mjs → github-copilot-headers-B37TB3rB.mjs} +3 -10
  19. package/dist/github-copilot-request-facts-BTEBMeOv.mjs +16 -0
  20. package/dist/{google-4qeuE8iX.mjs → google-DPBAOaOW.mjs} +10 -10
  21. package/dist/google-messages-6JkpHrhJ.mjs +448 -0
  22. package/dist/google-shared-BvBeW9aq.mjs +185 -0
  23. package/dist/{google-vertex-O9BcnB6X.mjs → google-vertex-k-TMCAYD.mjs} +7 -6
  24. package/dist/{host-DjzGmdZ2.d.mts → host-4atIX-2V.d.mts} +5 -3
  25. package/dist/{host-CEvLw30U.mjs → host-B8YfDGd4.mjs} +181 -41
  26. package/dist/host-policy-Zcg_cNz8.mjs +37 -0
  27. package/dist/{index-FnHM2FcI.d.mts → index-DdD3qerf.d.mts} +4 -4
  28. package/dist/index.d.mts +7 -7
  29. package/dist/index.mjs +7 -6
  30. package/dist/internal/anthropic.d.mts +7 -7
  31. package/dist/internal/anthropic.mjs +4 -4
  32. package/dist/internal/google-model-family.d.mts +5 -0
  33. package/dist/internal/google-model-family.mjs +15 -0
  34. package/dist/internal/openai-responses-payload-policy.d.mts +3 -3
  35. package/dist/internal/openai-responses-payload-policy.mjs +3 -2
  36. package/dist/internal/openai.d.mts +8 -103
  37. package/dist/internal/openai.mjs +9 -10
  38. package/dist/internal/retry-after.d.mts +2 -4
  39. package/dist/internal/retry-after.mjs +57 -8
  40. package/dist/internal/runtime.d.mts +43 -7
  41. package/dist/internal/runtime.mjs +6 -7
  42. package/dist/internal/shared.d.mts +43 -3
  43. package/dist/internal/shared.mjs +5 -4
  44. package/dist/internal/tool-schema.d.mts +63 -0
  45. package/dist/internal/tool-schema.mjs +3 -0
  46. package/dist/{mistral-Tb6oalqH.mjs → mistral-CxUZ1jUb.mjs} +10 -37
  47. package/dist/model-transport-url-DKocrEsb.d.mts +28 -0
  48. package/dist/{openai-chatgpt-responses-yUXjPNfu.mjs → openai-chatgpt-responses-CgO6kZfo.mjs} +50 -41
  49. package/dist/{openai-completions-compat-CpjYUArk.d.mts → openai-completions-compat-eHgh5UPE.d.mts} +26 -4
  50. package/dist/{openai-completions-stream-BsrBe3Gg.mjs → openai-completions-stream-Da2vvl-S.mjs} +654 -453
  51. package/dist/openai-completions-yJuk7eis.mjs +187 -0
  52. package/dist/openai-prompt-cache-B4eYo2-I.d.mts +16 -0
  53. package/dist/openai-prompt-cache-Bds-n_9Q.mjs +220 -0
  54. package/dist/openai-provider-client-S2gCrM2Z.mjs +24 -0
  55. package/dist/{openai-responses-CCL41ALM.mjs → openai-responses-DaYwH05E.mjs} +14 -29
  56. package/dist/{openai-responses-compaction-window-BMVHFOtq.mjs → openai-responses-compaction-window-CIhBAkkq.mjs} +6 -246
  57. package/dist/{openai-responses-contracts-rF5DDRNS.d.mts → openai-responses-contracts-BjBAqAg_.d.mts} +5 -38
  58. package/dist/openai-responses-contracts-DDOHA62Y.mjs +245 -0
  59. package/dist/{openai-responses-prompt-observer-internal-D0bhBfgL.mjs → openai-responses-prompt-observer-internal-f8J7wpsk.mjs} +3 -3
  60. package/dist/{openai-responses-shared-Bmma_3Qc.mjs → openai-responses-shared-B8RdBPCv.mjs} +47 -118
  61. package/dist/openai-tool-projection-793cE3QX.d.mts +37 -0
  62. package/dist/{openai-tool-schema-_pTAJqKF.mjs → openai-tool-schema-CzjyYXun.mjs} +38 -556
  63. package/dist/openai-tool-schema-ynZBgqW-.d.mts +38 -0
  64. package/dist/openai-transport-params-9aPuV5YY.mjs +675 -0
  65. package/dist/positive-integer-41zhOdcV.mjs +8 -0
  66. package/dist/{provider-error-9TraxGvt.mjs → provider-error-BA-v_tKd.mjs} +70 -11
  67. package/dist/{provider-options-Bcc_p-TU.d.mts → provider-options-Ceqv1OKk.d.mts} +4 -39
  68. package/dist/{provider-replay-context-BuSUaAk5.mjs → provider-replay-context-CJ_YvcEW.mjs} +1 -1
  69. package/dist/{provider-transcript-transform-BaMbI1hr.mjs → provider-transcript-transform-V5YzU9zh.mjs} +1 -1
  70. package/dist/provider-transport-turn-state-D5EXOFL2.mjs +42 -0
  71. package/dist/provider-types-CVjKjsuq.d.mts +29 -0
  72. package/dist/provider-types.d.mts +6 -31
  73. package/dist/providers.d.mts +2 -2
  74. package/dist/providers.mjs +11 -11
  75. package/dist/{reasoning-tag-text-partitioner-DLCNXki6.mjs → reasoning-tag-text-partitioner-BcR5pztD.mjs} +19 -19
  76. package/dist/record-coerce-DwRYMj3t.mjs +32 -0
  77. package/dist/retry-after-CdCURCVg.d.mts +15 -0
  78. package/dist/session-affinity-Bcunsn4I.mjs +23 -0
  79. package/dist/{simple-options-BjHCCh4v.mjs → simple-options-BQbb4yQL.mjs} +24 -5
  80. package/dist/src-DDmEryvj.mjs +2 -0
  81. package/dist/{stream-first-event-timeout-DcNjoFQE.mjs → stream-first-event-timeout-2hfrquuz.mjs} +1 -1
  82. package/dist/string-coerce-fsri9iCu.mjs +34 -0
  83. package/dist/{string-normalization--fwJ4S2q.mjs → string-normalization-CmLIasuf.mjs} +1 -1
  84. package/dist/{tool-schema-json-projection-mJhXDcyz.mjs → tool-schema-json-projection-ClptDdAO.mjs} +2 -38
  85. package/dist/{transport-stream-shared-xnaqxmbP.d.mts → transport-stream-shared-DNvmoWnv.d.mts} +20 -36
  86. package/dist/{transport-stream-shared-CZqMhfIw.mjs → transport-stream-shared-zHll9BxO.mjs} +225 -13
  87. package/dist/transport-utils-zrYjICLZ.mjs +118 -0
  88. package/dist/transports.d.mts +137 -193
  89. package/dist/transports.mjs +204 -1334
  90. package/dist/types-DlfwzH3T.d.mts +1 -0
  91. package/dist/types-LFWwv0cF.mjs +8 -0
  92. package/dist/{types-CJ1-Ht7A.d.mts → types-Ntv5z2g2.d.mts} +74 -57
  93. package/dist/types.d.mts +6 -6
  94. package/dist/types.mjs +6 -5
  95. package/dist/{src-2qBGKg8O.mjs → usage-cost-BNWbbXav.mjs} +0 -1
  96. package/dist/utf16-slice-CvGodqok.mjs +29 -0
  97. package/dist/{validation-AKZBDGQd.d.mts → validation-B0t_G2H6.d.mts} +1 -1
  98. package/dist/{validation-CJZtym2g.mjs → validation-BDzVDnTs.mjs} +9 -1
  99. package/dist/validation.d.mts +1 -1
  100. package/dist/validation.mjs +1 -1
  101. package/package.json +13 -3
  102. package/dist/anthropic-BDdqdVLK.mjs +0 -886
  103. package/dist/event-stream-zctLx0yr.d.mts +0 -1
  104. package/dist/google-shared-CWeG8RIl.mjs +0 -636
  105. package/dist/google-thinking-level-C-V3tecN.mjs +0 -9
  106. package/dist/hash-CHgqbJmD.mjs +0 -16
  107. package/dist/json-parse-BuAJEbdW.mjs +0 -146
  108. package/dist/openai-completions-BIUV3RDT.mjs +0 -403
  109. package/dist/openai-prompt-cache-CGnVB74a.mjs +0 -21
  110. package/dist/openai-prompt-cache-CNoIfHYC.d.mts +0 -16
  111. package/dist/sanitize-unicode-D6xUvZaS.mjs +0 -83
  112. package/dist/transport-utils-7il795_9.mjs +0 -138
  113. package/dist/types-3Lnm-QSJ.d.mts +0 -1
  114. package/dist/types-BDdaOVi2.mjs +0 -6
  115. package/dist/utf16-slice-qz3nsy87.mjs +0 -84
@@ -1,72 +1,27 @@
1
- import { c as normalizeLowercaseStringOrEmpty, i as asOptionalObjectRecord, o as isRecord, t as truncateUtf16Safe } from "./utf16-slice-qz3nsy87.mjs";
2
- import { t as sanitizeSurrogates } from "./sanitize-unicode-D6xUvZaS.mjs";
3
- import { a as describeToolResultMediaPlaceholder, c as extractToolResultText, d as isImageWithMediaPayload, n as getAiTransportHost } from "./host-CEvLw30U.mjs";
4
- import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-DLCNXki6.mjs";
5
- import { n as clampOpenAIPromptCacheKey } from "./openai-prompt-cache-CGnVB74a.mjs";
6
- import { d as stripSystemPromptCacheBoundary } from "./simple-options-BjHCCh4v.mjs";
7
- import { o as resolveProviderRequestCapabilities, r as resolveModelRequestTimeoutMs, s as resolveProviderRequestPolicyConfig } from "./tool-schema-json-projection-mJhXDcyz.mjs";
8
- import { i as isCodeModeModelVisibleToolName, p as sha256Hex } from "./transport-utils-7il795_9.mjs";
9
- import { s as finalizeTerminalToolCallArguments } from "./transport-stream-shared-CZqMhfIw.mjs";
10
- import { r as parseStreamingJson, t as createToolArgumentPreviewSchedule } from "./json-parse-BuAJEbdW.mjs";
1
+ import { r as appendAssistantThinking } from "./event-stream-D8PARQfL.mjs";
2
+ import { _ as isImageWithMediaPayload, d as describeToolResultMediaPlaceholder, m as extractToolResultText, v as sanitizeSurrogates } from "./host-B8YfDGd4.mjs";
3
+ import { o as isRecord } from "./record-coerce-DwRYMj3t.mjs";
4
+ import { t as truncateUtf16Safe } from "./utf16-slice-CvGodqok.mjs";
5
+ import { d as shouldOmitOllamaCompatResponseFormat, i as detectOpenAICompletionsCompat, r as resolveOpenAIPromptCacheParams, u as resolveOpenAICompletionsResponseFormat } from "./openai-prompt-cache-Bds-n_9Q.mjs";
6
+ import { r as emitModelTransportDebug } from "./diagnostics-QuErwCIl.mjs";
7
+ import { i as asNonNegativeFiniteNumber } from "./base64-D-su8YVo.mjs";
8
+ import { c as supportsModelTools, r as estimateStringChars } from "./transport-utils-zrYjICLZ.mjs";
9
+ import { n as uniqueStrings } from "./string-normalization-CmLIasuf.mjs";
10
+ import { i as resolveProviderEndpoint, r as resolveOpenAIStrictToolSetting } from "./host-policy-Zcg_cNz8.mjs";
11
+ import { t as resolveCacheRetention } from "./cache-retention-0x979a5V.mjs";
12
+ import { f as splitSystemPromptCacheBoundary, h as stripSystemPromptRelocatableBoundary, m as stripSystemPromptCacheBoundary, p as splitSystemPromptRelocatableBoundary, v as sortPromptCacheToolsByName } from "./simple-options-BQbb4yQL.mjs";
13
+ import { C as readOpenAICompletionsContentDeltas, D as throwIfModelStreamAborted, E as resolvePromptCacheKey, S as parseOpenAICompletionsUsage, b as log, d as resolveOpenAIReasoningEffortMap, f as projectOpenAITools, g as createModelStreamCooperativeScheduler, h as GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP, p as reconcileOpenAICompletionsToolChoice, s as getCompat, u as resolveOpenAIStrictToolFlagWithDiagnostics, w as readOpenAICompletionsReasoningBatch, x as measureUtf8AppendBytes, y as isOpenAICompletionsThinkingEnabled } from "./openai-transport-params-9aPuV5YY.mjs";
14
+ import { E as parseStreamingJson, c as finalizeTerminalToolCallArguments, w as createToolArgumentPreviewSchedule } from "./transport-stream-shared-zHll9BxO.mjs";
15
+ import { a as tagUnresolvedTextAsCommentary, i as tagPendingCommentaryText, n as rememberPendingCommentaryTags, r as tagInterruptedTextPhases, t as clearPendingCommentaryText } from "./assistant-text-phase-C20rxWwP.mjs";
11
16
  import { t as notifyLlmRequestActivity } from "./llm-request-activity-BjtkplhG.mjs";
12
- import { A as readOpenAICompletionsReasoningBatch, D as measureUtf8AppendBytes, N as throwIfModelStreamAborted, O as parseOpenAICompletionsUsage, S as createModelStreamCooperativeScheduler, T as isOpenAICompletionsThinkingEnabled, a as resolveOpenAIProjectedToolsStrictToolFlag, k as readOpenAICompletionsContentDeltas, t as findOpenAIStrictToolProjectionDiagnostics } from "./openai-tool-schema-_pTAJqKF.mjs";
13
- import { a as withFirstStreamEventTimeout } from "./stream-first-event-timeout-DcNjoFQE.mjs";
14
- import { t as transformProviderMessages } from "./provider-transcript-transform-BaMbI1hr.mjs";
17
+ import { a as withFirstStreamEventTimeout } from "./stream-first-event-timeout-2hfrquuz.mjs";
18
+ import { t as transformProviderMessages } from "./provider-transcript-transform-V5YzU9zh.mjs";
19
+ import { _ as isOpenAIGpt56Model, b as resolveOpenAIReasoningEffortForModel, g as isOpenAIGpt55Model, h as isOpenAIGpt54MiniModel } from "./openai-responses-contracts-DDOHA62Y.mjs";
20
+ import { r as normalizeOpenAIStrictToolParameters } from "./openai-tool-schema-CzjyYXun.mjs";
21
+ import { isGoogleGemini3FlashModel, isGoogleGemini3ProModel } from "./internal/google-model-family.mjs";
15
22
  import { t as mapOpenAIStopReason } from "./openai-stop-reason-Drnn_6Qj.mjs";
23
+ import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-BcR5pztD.mjs";
16
24
  import { randomUUID } from "node:crypto";
17
- //#region packages/ai/src/utils/assistant-text-phase.ts
18
- const EMPTY_ASSISTANT_TEXT_BLOCK_SET = /* @__PURE__ */ new Set();
19
- function isAssistantTextPhaseBlock(block) {
20
- if (!block || typeof block !== "object") return false;
21
- const record = block;
22
- return record.type === "text" && typeof record.text === "string";
23
- }
24
- function encodeAssistantTextSignatureV1(id, phase) {
25
- return JSON.stringify({
26
- v: 1,
27
- id,
28
- ...phase ? { phase } : {}
29
- });
30
- }
31
- function tagUnphasedText(content, phase, idPrefix) {
32
- const textBlocks = content.filter(isAssistantTextPhaseBlock);
33
- let phaseIndex = textBlocks.filter((block) => block.textSignature !== void 0).length;
34
- const tagged = /* @__PURE__ */ new Map();
35
- for (const block of textBlocks) {
36
- if (block.text.trim().length === 0 || block.textSignature !== void 0) continue;
37
- const signature = encodeAssistantTextSignatureV1(`${idPrefix}-${phaseIndex}-${randomUUID().replaceAll("-", "").slice(0, 24)}`, phase);
38
- block.textSignature = signature;
39
- tagged.set(block, signature);
40
- phaseIndex += 1;
41
- }
42
- return tagged;
43
- }
44
- /** Tags unphased narration before a tool-call event becomes consumer-visible. */
45
- function tagPendingCommentaryText(content) {
46
- return tagUnphasedText(content, "commentary", "commentary");
47
- }
48
- /** Records the confirmed final-answer boundary after reasoning resumes. */
49
- function tagInterruptedTextPhases(content, interruptedText, preservedVisibleText = EMPTY_ASSISTANT_TEXT_BLOCK_SET) {
50
- const interruptedTextIndex = content.indexOf(interruptedText);
51
- if (interruptedTextIndex === -1) return;
52
- const finalAnswerIndex = content.findIndex((block, index) => index > interruptedTextIndex && isAssistantTextPhaseBlock(block) && block.text.trim().length > 0);
53
- if (finalAnswerIndex === -1) return;
54
- tagUnphasedText(content.slice(0, finalAnswerIndex).filter((block) => !preservedVisibleText.has(block)), "commentary", "commentary");
55
- tagUnphasedText(content.filter((block, index) => index >= finalAnswerIndex || preservedVisibleText.has(block)), "final_answer", "final-answer");
56
- }
57
- /** Prevents unresolved completion text from becoming a fallback answer after stream failure. */
58
- function tagUnresolvedTextAsCommentary(message) {
59
- if (message.openclawDelivery?.textPhaseRequiresTerminal) tagUnphasedText(message.content, "commentary", "commentary");
60
- }
61
- /** Rolls back only the exact provisional signatures created by this transport turn. */
62
- function clearPendingCommentaryText(tags) {
63
- for (const [block, signature] of tags) if (block.textSignature === signature) delete block.textSignature;
64
- tags.clear();
65
- }
66
- function rememberPendingCommentaryTags(target, tagged) {
67
- for (const [block, signature] of tagged) target.set(block, signature);
68
- }
69
- //#endregion
70
25
  //#region packages/ai/src/transports/deepseek-dsml-grammar.ts
71
26
  const DEEPSEEK_DSML_MARKERS = [
72
27
  "|",
@@ -162,141 +117,132 @@ function longestDsmlOpenPrefixSuffixLength(text) {
162
117
  return 0;
163
118
  }
164
119
  //#endregion
165
- //#region packages/ai/src/providers/openai-response-format.ts
166
- const JSON_SCHEMA_RESPONSE_FORMAT_NAME = "openclaw_response";
167
- const OLLAMA_CLOUD_ORIGIN = "https://ollama.com";
168
- function isKnownOpenAIJsonSchemaModelId(modelId) {
169
- if (typeof modelId !== "string") return false;
170
- if (/^gpt-5(?:[.-]|$)/i.test(modelId) || /^gpt-4\.1(?:-|$)/i.test(modelId)) return true;
171
- const gpt4o = /^gpt-4o(-mini)?(?:-(\d{4}-\d{2}-\d{2}))?$/i.exec(modelId);
172
- if (gpt4o) {
173
- const snapshot = gpt4o[2];
174
- return !snapshot || snapshot >= (gpt4o[1] ? "2024-07-18" : "2024-08-06");
175
- }
176
- return /^(?:o1|o3(?:-mini|-pro)?|o4-mini)(?:-\d{4}-\d{2}-\d{2})?$/i.test(modelId);
177
- }
178
- function shouldOmitOllamaCompatResponseFormat(params) {
179
- if (!params.provider.includes("ollama")) return false;
180
- if (params.hasTools()) return true;
181
- try {
182
- return new URL(params.baseUrl).origin === OLLAMA_CLOUD_ORIGIN;
183
- } catch {
184
- return false;
120
+ //#region packages/ai/src/transports/model-max-tokens-params.ts
121
+ /**
122
+ * Max-token parameter normalization across provider/native naming variants.
123
+ * Callers canonicalize aliases before dispatch so payloads cannot carry
124
+ * conflicting limits.
125
+ */
126
+ const MAX_TOKENS_PARAM_KEYS = [
127
+ "maxTokens",
128
+ "max_completion_tokens",
129
+ "max_tokens"
130
+ ];
131
+ /** Resolve the first supported max-token parameter present in a params object. */
132
+ function resolveMaxTokensParam(params) {
133
+ if (!params) return;
134
+ for (const key of MAX_TOKENS_PARAM_KEYS) {
135
+ const resolved = asNonNegativeFiniteNumber(params[key]);
136
+ if (resolved !== void 0) return resolved;
185
137
  }
186
138
  }
187
139
  /**
188
- * Maps the shared JSON Schema option to Chat Completions while preserving the
189
- * older provider-shaped json_object/json_schema inputs accepted by model params.
140
+ * Canonicalize merged params to `maxTokens`, preserving source precedence from
141
+ * left to right across the provided source objects.
190
142
  */
191
- function resolveOpenAICompletionsResponseFormat(responseFormat, supportsJsonSchemaResponseFormat) {
192
- if (!responseFormat) return;
193
- if (responseFormat.type === "json_object") return responseFormat;
194
- if (responseFormat.type === "text") return responseFormat;
195
- if (responseFormat.type === "json_schema" && isRecord(responseFormat.json_schema)) return responseFormat;
196
- if (!supportsJsonSchemaResponseFormat) return;
197
- return {
198
- type: "json_schema",
199
- json_schema: {
200
- name: JSON_SCHEMA_RESPONSE_FORMAT_NAME,
201
- schema: responseFormat
202
- }
203
- };
143
+ function canonicalizeMaxTokensParam(params) {
144
+ let resolved;
145
+ for (const source of params.sources) {
146
+ const sourceValue = resolveMaxTokensParam(source);
147
+ if (sourceValue !== void 0) resolved = sourceValue;
148
+ }
149
+ if (resolved === void 0) return;
150
+ for (const key of MAX_TOKENS_PARAM_KEYS) delete params.merged[key];
151
+ params.merged.maxTokens = resolved;
204
152
  }
205
153
  //#endregion
206
- //#region packages/ai/src/transports/openai-completions-compat.ts
207
- function isDefaultRouteProvider(provider, ...ids) {
208
- return provider !== void 0 && ids.includes(provider);
209
- }
210
- /** Resolves default request flags for an OpenAI-compatible completions endpoint. */
211
- function resolveOpenAICompletionsCompatDefaults(input) {
212
- const { provider, modelId, endpointClass, knownProviderFamily, supportsNativeStreamingUsageCompat = false, supportsOpenAICompletionsStreamingUsageCompat = false, usesExplicitProxyLikeEndpoint = false } = input;
213
- const isDefaultRoute = endpointClass === "default";
214
- const usesConfiguredNonOpenAIEndpoint = endpointClass !== "default" && endpointClass !== "openai-public";
215
- const isMoonshot = knownProviderFamily === "moonshot" || endpointClass === "moonshot-native";
216
- const isMoonshotLike = isMoonshot || knownProviderFamily === "modelstudio" || endpointClass === "modelstudio-native";
217
- const isModelStudioLike = knownProviderFamily === "modelstudio" || endpointClass === "modelstudio-native" || isDefaultRoute && isDefaultRouteProvider(provider, "dashscope", "modelstudio", "qwen");
218
- const isZai = endpointClass === "zai-native" || isDefaultRoute && isDefaultRouteProvider(input.provider, "zai");
219
- const isDeepSeek = endpointClass === "deepseek-native" || isDefaultRoute && isDefaultRouteProvider(input.provider, "deepseek");
220
- const isTogether = knownProviderFamily === "together" || input.baseUrl?.includes("api.together.ai") === true || input.baseUrl?.includes("api.together.xyz") === true || isDefaultRoute && isDefaultRouteProvider(input.provider, "together");
221
- const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || input.baseUrl?.includes("gateway.ai.cloudflare.com") === true;
222
- const isXiaomi = endpointClass === "xiaomi-native" || isDefaultRoute && isDefaultRouteProvider(input.provider, "xiaomi");
223
- const isNonStandard = endpointClass === "cerebras-native" || endpointClass === "chutes-native" || endpointClass === "deepseek-native" || endpointClass === "mistral-public" || endpointClass === "opencode-native" || endpointClass === "opencode-go-native" || endpointClass === "xai-native" || isXiaomi || isZai || isDefaultRoute && isDefaultRouteProvider(input.provider, "cerebras", "chutes", "deepseek", "opencode", "xai");
224
- const isOpenRouterLike = input.provider === "openrouter" || endpointClass === "openrouter";
225
- const isLocalEndpoint = endpointClass === "local";
226
- const usesMaxTokens = endpointClass === "chutes-native" || endpointClass === "mistral-public" || knownProviderFamily === "mistral" || isMoonshot || isCloudflareAiGateway || isZai || isTogether || isDefaultRoute && isDefaultRouteProvider(provider, "chutes");
154
+ //#region packages/ai/src/transports/openai-completions-cache-control.ts
155
+ const shapedPayloads = /* @__PURE__ */ new WeakSet();
156
+ function resolveCompletionsCacheControl(compat, retention, openRouterRoute) {
157
+ if (compat.cacheControlFormat !== "anthropic" || retention === "none") return;
227
158
  return {
228
- supportsStore: !isNonStandard && knownProviderFamily !== "mistral" && !usesExplicitProxyLikeEndpoint,
229
- supportsDeveloperRole: !isNonStandard && !isMoonshotLike && !usesConfiguredNonOpenAIEndpoint,
230
- supportsReasoningEffort: !isZai && !isTogether && knownProviderFamily !== "mistral" && endpointClass !== "xai-native" && !usesExplicitProxyLikeEndpoint,
231
- supportsUsageInStreaming: supportsOpenAICompletionsStreamingUsageCompat || !isNonStandard && (isLocalEndpoint || !usesConfiguredNonOpenAIEndpoint || supportsNativeStreamingUsageCompat),
232
- maxTokensField: usesMaxTokens ? "max_tokens" : "max_completion_tokens",
233
- thinkingFormat: isDeepSeek || isXiaomi ? "deepseek" : isZai ? "zai" : isTogether ? "together" : isOpenRouterLike ? "openrouter" : "openai",
234
- visibleReasoningDetailTypes: isOpenRouterLike ? ["response.output_text", "response.text"] : [],
235
- supportsStrictMode: !isZai && !usesConfiguredNonOpenAIEndpoint,
236
- supportsJsonSchemaResponseFormat: (endpointClass === "openai-public" || isDefaultRoute && isDefaultRouteProvider(provider, "openai")) && isKnownOpenAIJsonSchemaModelId(modelId),
237
- requiresReasoningContentOnAssistantMessages: isDeepSeek || isXiaomi,
238
- requiresNonEmptyUserOrAssistantMessage: isModelStudioLike,
239
- cacheControlFormat: isModelStudioLike && endpointClass !== "custom" || provider === "openrouter" && modelId?.startsWith("anthropic/") === true ? "anthropic" : void 0,
240
- sessionAffinityFormat: isOpenRouterLike ? "openrouter" : "openai",
241
- supportsLongCacheRetention: !isModelStudioLike && provider !== "cloudflare-workers-ai" && provider !== "cloudflare-ai-gateway" && knownProviderFamily !== "together" && !input.baseUrl?.includes("api.cloudflare.com") && !input.baseUrl?.includes("gateway.ai.cloudflare.com") && !input.baseUrl?.includes("api.together.ai") && !input.baseUrl?.includes("api.together.xyz")
159
+ type: "ephemeral",
160
+ ...retention === "long" && compat.supportsLongCacheRetention && (openRouterRoute || compat.configuredSupportsLongCacheRetention === true) ? { ttl: "1h" } : {}
242
161
  };
243
162
  }
244
- function resolveOpenAICompletionsCompatDefaultsFromCapabilities(input) {
245
- return resolveOpenAICompletionsCompatDefaults(input);
246
- }
247
- /** Detects endpoint capabilities and defaults for an OpenAI-completions model. */
248
- function detectOpenAICompletionsCompat(model, resolveCapabilities) {
249
- const capabilities = (resolveCapabilities ?? ((input) => resolveProviderRequestCapabilities(input, model)))({
250
- provider: model.provider,
251
- api: "openai-completions",
252
- baseUrl: model.baseUrl,
253
- capability: "llm",
254
- transport: "stream",
255
- modelId: model.id,
256
- compat: model.compat && typeof model.compat === "object" ? model.compat : void 0
257
- });
258
- return {
259
- capabilities,
260
- defaults: resolveOpenAICompletionsCompatDefaultsFromCapabilities({
261
- provider: model.provider,
262
- modelId: model.id,
263
- baseUrl: model.baseUrl,
264
- ...capabilities
265
- })
163
+ /** Shared Chat Completions policy; repeated wrapper application preserves existing checkpoints. */
164
+ function applyCompletionsAnthropicCacheControl(payload, cacheControl = { type: "ephemeral" }, cacheOptOutIndexes = /* @__PURE__ */ new Set(), markTools = true, markMessages = true) {
165
+ if (shapedPayloads.has(payload)) return;
166
+ shapedPayloads.add(payload);
167
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
168
+ const tools = Array.isArray(payload.tools) ? payload.tools.filter(isRecord) : [];
169
+ const blocks = messages.filter(isRecord).flatMap((message) => Array.isArray(message.content) ? message.content.filter(isRecord) : []);
170
+ for (const block of [...tools, ...blocks]) delete block.cache_control;
171
+ if (!cacheControl) return;
172
+ const markText = (message, splitBoundary) => {
173
+ if (typeof message.content === "string" && message.content) message.content = [{
174
+ type: "text",
175
+ text: message.content
176
+ }];
177
+ const content = message.content;
178
+ if (!Array.isArray(content)) return false;
179
+ for (let i = content.length - 1; i >= 0; i--) {
180
+ const block = content[i];
181
+ if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string" || !block.text) continue;
182
+ const split = splitBoundary ? splitSystemPromptCacheBoundary(block.text) : void 0;
183
+ if (split) {
184
+ content.splice(i, 1, ...split.stablePrefix ? [{
185
+ type: "text",
186
+ text: split.stablePrefix,
187
+ cache_control: cacheControl
188
+ }] : [], ...split.dynamicSuffix ? [{
189
+ type: "text",
190
+ text: split.dynamicSuffix
191
+ }] : []);
192
+ if (!split.stablePrefix) return false;
193
+ } else block.cache_control = cacheControl;
194
+ return true;
195
+ }
196
+ return false;
266
197
  };
198
+ const lastTool = tools.at(-1);
199
+ if (markTools && lastTool) lastTool.cache_control = cacheControl;
200
+ if (!markMessages) return;
201
+ const system = messages.find((message) => isRecord(message) && (message.role === "system" || message.role === "developer"));
202
+ if (isRecord(system)) markText(system, true);
203
+ for (let i = messages.length - 1; i >= 0; i--) {
204
+ const message = messages[i];
205
+ if (!cacheOptOutIndexes.has(i) && isRecord(message) && (message.role === "user" || message.role === "tool") && markText(message, false)) return;
206
+ }
267
207
  }
268
- function resolveSessionAffinity(model, detectedFormat) {
269
- if (model.compat?.sendSessionAffinityHeaders !== true) return "none";
270
- if (detectedFormat === "openrouter" || model.compat.thinkingFormat === "openrouter" || model.compat.openRouterRouting !== void 0) return "openrouter";
271
- return "openai";
208
+ //#endregion
209
+ //#region packages/ai/src/transports/openai-completions-string-content.ts
210
+ /**
211
+ * OpenAI Chat Completions compatibility helpers. Some providers only accept
212
+ * role/content messages with plain string content instead of text block arrays.
213
+ */
214
+ function flattenStringOnlyCompletionContent(content) {
215
+ if (!Array.isArray(content)) return content;
216
+ const textParts = [];
217
+ for (const item of content) {
218
+ if (!item || typeof item !== "object" || item.type !== "text" || typeof item.text !== "string") return content;
219
+ textParts.push(item.text);
220
+ }
221
+ return textParts.join("\n");
222
+ }
223
+ /** Flatten string-only text block content arrays into newline-joined strings. */
224
+ function flattenCompletionMessagesToStringContent(messages) {
225
+ return messages.map((message) => {
226
+ if (!message || typeof message !== "object") return message;
227
+ const content = message.content;
228
+ const flattenedContent = flattenStringOnlyCompletionContent(content);
229
+ if (flattenedContent === content) return message;
230
+ return {
231
+ ...message,
232
+ content: flattenedContent
233
+ };
234
+ });
272
235
  }
273
- /** Applies explicit model overrides once on top of the canonical transport defaults. */
274
- function resolveOpenAICompletionsCompat(model, resolveCapabilities) {
275
- const { defaults } = detectOpenAICompletionsCompat(model, resolveCapabilities);
276
- const configured = model.compat;
277
- return {
278
- supportsStore: configured?.supportsStore ?? defaults.supportsStore,
279
- supportsDeveloperRole: configured?.supportsDeveloperRole ?? defaults.supportsDeveloperRole,
280
- supportsReasoningEffort: configured?.supportsReasoningEffort ?? defaults.supportsReasoningEffort,
281
- supportsUsageInStreaming: configured?.supportsUsageInStreaming ?? defaults.supportsUsageInStreaming,
282
- maxTokensField: configured?.maxTokensField ?? defaults.maxTokensField,
283
- requiresToolResultName: configured?.requiresToolResultName ?? false,
284
- requiresAssistantAfterToolResult: configured?.requiresAssistantAfterToolResult ?? false,
285
- requiresThinkingAsText: configured?.requiresThinkingAsText ?? false,
286
- requiresReasoningContentOnAssistantMessages: configured?.requiresReasoningContentOnAssistantMessages ?? defaults.requiresReasoningContentOnAssistantMessages,
287
- thinkingFormat: configured?.thinkingFormat ?? defaults.thinkingFormat,
288
- openRouterRouting: configured?.openRouterRouting,
289
- vercelGatewayRouting: configured?.vercelGatewayRouting ?? {},
290
- zaiToolStream: configured?.zaiToolStream ?? false,
291
- supportsStrictMode: configured?.supportsStrictMode ?? defaults.supportsStrictMode,
292
- supportsJsonSchemaResponseFormat: configured?.supportsJsonSchemaResponseFormat ?? defaults.supportsJsonSchemaResponseFormat,
293
- cacheControlFormat: configured?.cacheControlFormat ?? defaults.cacheControlFormat,
294
- sessionAffinity: resolveSessionAffinity(model, defaults.sessionAffinityFormat),
295
- supportsPromptCacheKey: configured?.supportsPromptCacheKey ?? false,
296
- supportsLongCacheRetention: configured?.supportsLongCacheRetention ?? defaults.supportsLongCacheRetention,
297
- visibleReasoningDetailTypes: configured && "visibleReasoningDetailTypes" in configured ? configured.visibleReasoningDetailTypes ?? defaults.visibleReasoningDetailTypes : defaults.visibleReasoningDetailTypes,
298
- requiresNonEmptyUserOrAssistantMessage: defaults.requiresNonEmptyUserOrAssistantMessage
299
- };
236
+ /** Strip completion messages to role/content fields for strict providers. */
237
+ function stripCompletionMessagesToRoleContent(messages) {
238
+ return messages.map((message) => {
239
+ if (!message || typeof message !== "object" || Array.isArray(message)) return message;
240
+ const record = message;
241
+ const stripped = {};
242
+ if (Object.hasOwn(record, "role")) stripped.role = record.role;
243
+ if (Object.hasOwn(record, "content")) stripped.content = record.content;
244
+ return stripped;
245
+ });
300
246
  }
301
247
  //#endregion
302
248
  //#region packages/ai/src/providers/openai-completions-tool-calls.ts
@@ -481,17 +427,18 @@ function finalizeOpenAICompletionsToolCalls(output, options = {}) {
481
427
  }
482
428
  }
483
429
  //#endregion
430
+ //#region packages/ai/src/transports/openai-completions-host.ts
431
+ /**
432
+ * Chat Completions accepts Azure AI Foundry hosts in addition to traditional
433
+ * Azure OpenAI hosts. Do not replace this with isTraditionalAzureOpenAIHost,
434
+ * which intentionally excludes the .services.ai.azure.com Foundry suffix.
435
+ */
436
+ function isAzureOpenAICompatibleHost(hostname) {
437
+ return hostname.endsWith(".openai.azure.com") || hostname.endsWith(".services.ai.azure.com") || hostname.endsWith(".cognitiveservices.azure.com");
438
+ }
439
+ //#endregion
484
440
  //#region packages/ai/src/openai-completions-messages.ts
485
441
  const EMPTY_TOOL_RESULT_TEXT = "(no output)";
486
- function isTextContentBlock(block) {
487
- return block.type === "text";
488
- }
489
- function isThinkingContentBlock(block) {
490
- return block.type === "thinking";
491
- }
492
- function isToolCallBlock(block) {
493
- return block.type === "toolCall";
494
- }
495
442
  function sanitizeToolResultText(text, fallback) {
496
443
  const sanitized = sanitizeSurrogates(text);
497
444
  return sanitized.trim().length > 0 ? sanitized : fallback;
@@ -509,9 +456,18 @@ function convertMessages(model, context, compat, options = {}) {
509
456
  return id;
510
457
  };
511
458
  const transformedMessages = transformProviderMessages(context.messages, model, (id) => normalizeToolCallId(id));
459
+ let relocatableSplit;
460
+ let systemParamIndex;
512
461
  if (context.systemPrompt) {
513
462
  const role = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
514
- const systemPrompt = options.preserveSystemPromptCacheBoundary ? context.systemPrompt : stripSystemPromptCacheBoundary(context.systemPrompt);
463
+ let systemPrompt;
464
+ if (options.preserveSystemPromptCacheBoundary) systemPrompt = stripSystemPromptRelocatableBoundary(context.systemPrompt);
465
+ else {
466
+ const split = splitSystemPromptRelocatableBoundary(context.systemPrompt);
467
+ if (split && split.relocatable.length > 0) relocatableSplit = split;
468
+ systemPrompt = stripSystemPromptCacheBoundary(context.systemPrompt);
469
+ }
470
+ systemParamIndex = params.length;
515
471
  params.push({
516
472
  role,
517
473
  content: sanitizeSurrogates(systemPrompt)
@@ -562,25 +518,30 @@ function convertMessages(model, context, compat, options = {}) {
562
518
  role: "assistant",
563
519
  content: compat.requiresAssistantAfterToolResult ? "" : null
564
520
  };
565
- const assistantTexts = msg.content.filter(isTextContentBlock).filter((block) => block.text.trim().length > 0).map((block) => sanitizeSurrogates(block.text));
566
- const assistantText = assistantTexts.join("\n");
567
- const nonEmptyThinkingBlocks = msg.content.filter(isThinkingContentBlock).filter((block) => block.thinking.trim().length > 0);
568
- if (nonEmptyThinkingBlocks.length > 0) {
569
- if (compat.requiresThinkingAsText) assistantMsg.content = [{
570
- type: "text",
571
- text: nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n\n")
572
- }, ...assistantTexts.map((text) => ({
573
- type: "text",
574
- text
575
- }))];
576
- else {
577
- if (assistantText.length > 0) assistantMsg.content = assistantText;
521
+ const assistantTexts = [];
522
+ const nonEmptyThinkingBlocks = [];
523
+ const toolCalls = [];
524
+ msg.content.forEach((block) => {
525
+ if (block.type === "text" && block.text.trim().length > 0) assistantTexts.push(sanitizeSurrogates(block.text));
526
+ else if (block.type === "thinking" && block.thinking.trim().length > 0) nonEmptyThinkingBlocks.push(block);
527
+ else if (block.type === "toolCall") toolCalls.push(block);
528
+ });
529
+ if (nonEmptyThinkingBlocks.length > 0 && compat.requiresThinkingAsText) assistantMsg.content = [{
530
+ type: "text",
531
+ text: nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n\n")
532
+ }, ...assistantTexts.map((text) => ({
533
+ type: "text",
534
+ text
535
+ }))];
536
+ else {
537
+ const assistantText = assistantTexts.join("\n");
538
+ if (assistantText.length > 0) assistantMsg.content = assistantText;
539
+ if (nonEmptyThinkingBlocks.length > 0) {
578
540
  let signature = nonEmptyThinkingBlocks.at(0)?.thinkingSignature;
579
541
  if (model.provider === "opencode-go" && signature === "reasoning") signature = "reasoning_content";
580
542
  if (signature && signature.length > 0) assistantMsg[signature] = nonEmptyThinkingBlocks.map((block) => block.thinking).join("\n");
581
543
  }
582
- } else if (assistantText.length > 0) assistantMsg.content = assistantText;
583
- const toolCalls = msg.content.filter(isToolCallBlock);
544
+ }
584
545
  if (toolCalls.length > 0) {
585
546
  assistantMsg.tool_calls = toolCalls.map((toolCall) => ({
586
547
  id: toolCall.id,
@@ -604,17 +565,17 @@ function convertMessages(model, context, compat, options = {}) {
604
565
  }
605
566
  if (compat.requiresReasoningContentOnAssistantMessages && model.reasoning && assistantMsg.reasoning_content === void 0) assistantMsg.reasoning_content = "";
606
567
  const content = assistantMsg.content;
607
- if (!(content !== null && content !== void 0 && (typeof content === "string" ? content.length > 0 : content.length > 0)) && !assistantMsg.tool_calls) continue;
568
+ if (!(content !== null && content !== void 0 && content.length > 0) && !assistantMsg.tool_calls) continue;
608
569
  params.push(assistantMsg);
609
570
  } else if (msg.role === "toolResult") {
610
- const imageBlocks = [];
571
+ const imageContentParts = [];
611
572
  let j = i;
612
573
  while (j < transformedMessages.length) {
613
574
  const toolMsg = transformedMessages.at(j);
614
575
  if (toolMsg?.role !== "toolResult") break;
615
576
  const textResult = extractToolResultText(toolMsg.content);
616
577
  const mediaPlaceholder = describeToolResultMediaPlaceholder(toolMsg.content);
617
- const hasImages = toolMsg.content.some(isImageWithMediaPayload);
578
+ const images = toolMsg.content.filter(isImageWithMediaPayload);
618
579
  const toolResultMsg = {
619
580
  role: "tool",
620
581
  content: sanitizeToolResultText(textResult, mediaPlaceholder ?? EMPTY_TOOL_RESULT_TEXT),
@@ -622,8 +583,13 @@ function convertMessages(model, context, compat, options = {}) {
622
583
  };
623
584
  if (compat.requiresToolResultName && toolMsg.toolName) toolResultMsg.name = toolMsg.toolName;
624
585
  params.push(toolResultMsg);
625
- if (hasImages && model.input.includes("image")) {
626
- for (const block of toolMsg.content) if (isImageWithMediaPayload(block)) imageBlocks.push({
586
+ if (images.length > 0 && model.input.includes("image")) {
587
+ const boundedToolName = sanitizeSurrogates(truncateUtf16Safe(toolMsg.toolName ?? "", 64));
588
+ imageContentParts.push({
589
+ type: "text",
590
+ text: `Image(s) from tool result #${j - i + 1}${boundedToolName ? ` (${boundedToolName})` : ""}:`
591
+ });
592
+ for (const block of images) imageContentParts.push({
627
593
  type: "image_url",
628
594
  image_url: { url: `data:${block.mimeType};base64,${block.data}` }
629
595
  });
@@ -631,17 +597,14 @@ function convertMessages(model, context, compat, options = {}) {
631
597
  j += 1;
632
598
  }
633
599
  i = j - 1;
634
- if (imageBlocks.length > 0) {
600
+ if (imageContentParts.length > 0) {
635
601
  if (compat.requiresAssistantAfterToolResult) params.push({
636
602
  role: "assistant",
637
603
  content: "I have processed the tool results."
638
604
  });
639
605
  params.push({
640
606
  role: "user",
641
- content: [{
642
- type: "text",
643
- text: "Attached image(s) from tool result:"
644
- }, ...imageBlocks]
607
+ content: imageContentParts
645
608
  });
646
609
  lastRole = "user";
647
610
  } else lastRole = "toolResult";
@@ -649,261 +612,501 @@ function convertMessages(model, context, compat, options = {}) {
649
612
  }
650
613
  lastRole = msg.role;
651
614
  }
615
+ if (relocatableSplit !== void 0 && systemParamIndex !== void 0) relocateNonBehavioralRegion({
616
+ params,
617
+ systemParamIndex,
618
+ split: relocatableSplit,
619
+ cacheOptOutIndexes: options.cacheOptOutIndexes
620
+ });
652
621
  return params;
653
622
  }
654
- //#endregion
655
- //#region packages/ai/src/transports/openai-reasoning-compat.ts
656
- /**
657
- * OpenAI reasoning-effort compatibility helpers.
658
- *
659
- * Keeps provider metadata and built-in model exceptions on one path before request payloads are built.
660
- */
661
- const OPENAI_MEDIUM_ONLY_REASONING_MODEL_IDS = /* @__PURE__ */ new Set(["gpt-5.1-codex-mini"]);
662
- function readCompatReasoningEffortMap(compat) {
663
- const rawMap = asOptionalObjectRecord(asOptionalObjectRecord(compat)?.reasoningEffortMap);
664
- if (!rawMap) return {};
665
- return Object.fromEntries(Object.entries(rawMap).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string"));
666
- }
667
- /** Resolves the reasoning effort remap for an OpenAI-compatible model. */
668
- function resolveOpenAIReasoningEffortMap(model, fallbackMap = {}) {
669
- const provider = normalizeLowercaseStringOrEmpty(model.provider ?? "");
670
- const id = normalizeLowercaseStringOrEmpty(model.id ?? "");
671
- const builtinMap = provider === "openai" && OPENAI_MEDIUM_ONLY_REASONING_MODEL_IDS.has(id) ? {
672
- minimal: "medium",
673
- low: "medium"
674
- } : {};
675
- return {
676
- ...fallbackMap,
677
- ...builtinMap,
678
- ...readCompatReasoningEffortMap(model.compat)
679
- };
680
- }
681
- //#endregion
682
- //#region packages/ai/src/transports/openai-transport-params.ts
683
- const MAX_OPENAI_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS = 256;
684
- const OPENAI_CODEX_RESPONSES_PROVIDERS = /* @__PURE__ */ new Set(["openai"]);
685
- const loggedOpenAIStrictToolDowngradeDiagnosticKeys = /* @__PURE__ */ new Set();
686
- function readToolPayloadField(record, field) {
687
- try {
688
- return Object.hasOwn(record, field) ? record[field] : void 0;
689
- } catch {
623
+ /** Commit relocation only after finding an emitted carrier. */
624
+ function relocateNonBehavioralRegion(args) {
625
+ const text = sanitizeSurrogates(stripSystemPromptCacheBoundary(args.split.relocatable));
626
+ for (let index = args.systemParamIndex + 1; index < args.params.length; index++) {
627
+ const param = args.params[index];
628
+ if (!param || param.role !== "user") continue;
629
+ if (typeof param.content === "string") param.content = `${param.content}\n\n${text}`;
630
+ else if (Array.isArray(param.content)) param.content = [...param.content, {
631
+ type: "text",
632
+ text
633
+ }];
634
+ else continue;
635
+ const systemParam = args.params[args.systemParamIndex];
636
+ if (systemParam) systemParam.content = sanitizeSurrogates(stripSystemPromptCacheBoundary(args.split.remainingPrompt));
637
+ args.cacheOptOutIndexes?.add(index);
690
638
  return;
691
639
  }
692
640
  }
693
- function readCodeModePayloadToolName(tool) {
694
- if (!isRecord(tool)) return;
695
- const name = readToolPayloadField(tool, "name");
696
- if (typeof name === "string") return name;
697
- const fn = readToolPayloadField(tool, "function");
698
- if (!isRecord(fn)) return;
699
- const fnName = readToolPayloadField(fn, "name");
700
- return typeof fnName === "string" ? fnName : void 0;
701
- }
702
- function readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes) {
703
- if (!isRecord(tool)) return;
704
- const type = readToolPayloadField(tool, "type");
705
- if (typeof type === "string" && allowedHostedToolTypes?.has(type)) {
706
- try {
707
- if (Object.hasOwn(tool, "name") || Object.hasOwn(tool, "function") || Object.hasOwn(tool, "functionDeclarations") || Object.hasOwn(tool, "function_declarations")) return false;
708
- } catch {
709
- return false;
641
+ //#endregion
642
+ //#region packages/ai/src/transports/openai-completions-direct-policy.ts
643
+ function applyDirectCompletionsReasoningAndRouting(params, model, options, compat) {
644
+ const reasoningEffortMap = resolveOpenAIReasoningEffortMap(model);
645
+ const thinkingLevelMap = model.thinkingLevelMap;
646
+ const offReasoningEffort = reasoningEffortMap.off ?? model.thinkingLevelMap?.off;
647
+ const reasoningEffort = options?.reasoningEffort === void 0 ? offReasoningEffort ?? void 0 : reasoningEffortMap[options.reasoningEffort] ?? thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
648
+ const reasoningEnabled = reasoningEffort !== void 0 && reasoningEffort !== "none";
649
+ if (compat.thinkingFormat === "zai" && model.reasoning) params.thinking = reasoningEnabled ? {
650
+ type: "enabled",
651
+ clear_thinking: false
652
+ } : { type: "disabled" };
653
+ else if (compat.thinkingFormat === "qwen" && model.reasoning) params.enable_thinking = reasoningEnabled;
654
+ else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) params.chat_template_kwargs = {
655
+ enable_thinking: reasoningEnabled,
656
+ preserve_thinking: true
657
+ };
658
+ else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
659
+ params.thinking = { type: reasoningEnabled ? "enabled" : "disabled" };
660
+ if (reasoningEnabled && compat.supportsReasoningEffort) params.reasoning_effort = reasoningEffort;
661
+ } else if (compat.thinkingFormat === "openrouter" && model.reasoning) {
662
+ if (reasoningEnabled) params.reasoning = { effort: reasoningEffort };
663
+ else if (offReasoningEffort !== null) params.reasoning = { effort: offReasoningEffort ?? "none" };
664
+ } else if (compat.thinkingFormat === "together" && model.reasoning) {
665
+ params.reasoning = { enabled: reasoningEnabled };
666
+ if (reasoningEnabled && compat.supportsReasoningEffort) params.reasoning_effort = reasoningEffort;
667
+ } else if (reasoningEnabled && model.reasoning && compat.supportsReasoningEffort) params.reasoning_effort = reasoningEffort;
668
+ else if (model.reasoning && compat.supportsReasoningEffort) {
669
+ if (typeof offReasoningEffort === "string") params.reasoning_effort = offReasoningEffort;
670
+ }
671
+ if (compat.openRouterRouting) params.provider = compat.openRouterRouting;
672
+ if (model.baseUrl.includes("ai-gateway.vercel.sh") && model.compat?.vercelGatewayRouting) {
673
+ const routing = model.compat.vercelGatewayRouting;
674
+ if (routing.only || routing.order) {
675
+ const gatewayOptions = {};
676
+ if (routing.only) gatewayOptions.only = routing.only;
677
+ if (routing.order) gatewayOptions.order = routing.order;
678
+ params.providerOptions = { gateway: gatewayOptions };
710
679
  }
711
- return `hosted:${type}`;
712
680
  }
713
- const name = readCodeModePayloadToolName(tool);
714
- return typeof name === "string" && isCodeModeModelVisibleToolName(name, visibleToolNames) ? `client:${name}` : void 0;
715
681
  }
716
- function readCodeModePayloadToolIdentities(payload) {
717
- if (!isRecord(payload)) return [];
718
- const tools = readToolPayloadField(payload, "tools");
719
- if (!Array.isArray(tools)) return [];
720
- return tools.flatMap((tool) => {
721
- if (!isRecord(tool)) return [];
722
- const identities = [];
723
- const name = readCodeModePayloadToolName(tool);
724
- if (name) identities.push(`client:${name}`);
725
- for (const key of ["functionDeclarations", "function_declarations"]) {
726
- const declarations = readToolPayloadField(tool, key);
727
- if (!Array.isArray(declarations)) continue;
728
- for (const declaration of declarations) {
729
- const declarationName = readCodeModePayloadToolName(declaration);
730
- if (declarationName) identities.push(`client:${declarationName}`);
682
+ //#endregion
683
+ //#region packages/ai/src/transports/openai-completions-replay.ts
684
+ function isGoogleOpenAICompatModel(model) {
685
+ const endpointClass = detectOpenAICompletionsCompat(model).capabilities.endpointClass;
686
+ return model.provider === "google" || endpointClass === "google-generative-ai" || endpointClass === "google-vertex";
687
+ }
688
+ function requiresGoogleCompatToolCallThoughtSignature(model) {
689
+ return isGoogleGemini3ProModel(model.id) || isGoogleGemini3FlashModel(model.id);
690
+ }
691
+ const GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE = /[\u2026]|\.\.\./;
692
+ const GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE = /^[A-Za-z0-9+/=]+$/;
693
+ function hasGoogleCompatThoughtSignatureTruncationFootprint(value) {
694
+ return GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE.test(value) || GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE.test(value) && value.length % 4 !== 0;
695
+ }
696
+ function injectToolCallThoughtSignatures(outgoingMessages, context, model) {
697
+ if (!isGoogleOpenAICompatModel(model)) return;
698
+ const sigById = /* @__PURE__ */ new Map();
699
+ const fallbackSig = requiresGoogleCompatToolCallThoughtSignature(model) ? GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP : void 0;
700
+ for (const msg of context.messages ?? []) {
701
+ if (msg.role !== "assistant") continue;
702
+ const source = msg;
703
+ if (!Array.isArray(source.content)) continue;
704
+ for (const block of source.content) {
705
+ if (block.type !== "toolCall") continue;
706
+ const id = block.id;
707
+ const sig = block.thoughtSignature;
708
+ if (typeof id === "string" && typeof sig === "string" && sig.length > 0) {
709
+ const isSameRoute = source.api === model.api && source.provider === model.provider && source.model === model.id;
710
+ if (!isSameRoute && !fallbackSig) continue;
711
+ sigById.set(id, isSameRoute ? sig : fallbackSig ?? sig);
731
712
  }
732
713
  }
733
- const type = readToolPayloadField(tool, "type");
734
- if (typeof type === "string" && type !== "function") identities.push(`hosted:${type}`);
735
- return identities;
736
- });
737
- }
738
- function filterCodeModePayloadTools(payload, visibleToolNames, allowedHostedToolTypes, observer) {
739
- if (!isRecord(payload)) return;
740
- const tools = readToolPayloadField(payload, "tools");
741
- if (!Array.isArray(tools)) return;
742
- const beforeToolIdentities = observer ? readCodeModePayloadToolIdentities(payload) : void 0;
743
- payload.tools = tools.flatMap((tool) => {
744
- const identity = readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes);
745
- if (identity) return [tool];
746
- if (identity === false) return [];
747
- if (!isRecord(tool)) return [];
748
- const filteredGroups = {};
749
- for (const key of ["functionDeclarations", "function_declarations"]) {
750
- const declarations = readToolPayloadField(tool, key);
751
- if (!Array.isArray(declarations)) continue;
752
- const filtered = declarations.filter((declaration) => {
753
- const declarationName = readCodeModePayloadToolName(declaration);
754
- return typeof declarationName === "string" && isCodeModeModelVisibleToolName(declarationName, visibleToolNames);
755
- });
756
- if (filtered.length > 0) filteredGroups[key] = filtered;
714
+ }
715
+ if (sigById.size === 0 && !fallbackSig) return;
716
+ for (const message of outgoingMessages) {
717
+ const toolCalls = message.tool_calls;
718
+ if (!Array.isArray(toolCalls)) continue;
719
+ for (const toolCall of toolCalls) {
720
+ const id = toolCall.id;
721
+ if (typeof id !== "string") continue;
722
+ let sig = sigById.get(id) ?? fallbackSig;
723
+ if (typeof sig === "string" && sig.length > 0) {
724
+ if (hasGoogleCompatThoughtSignatureTruncationFootprint(sig.trim())) sig = fallbackSig;
725
+ }
726
+ if (typeof sig !== "string" || sig.length === 0) continue;
727
+ const extra = toolCall.extra_content && typeof toolCall.extra_content === "object" ? toolCall.extra_content : {};
728
+ toolCall.extra_content = extra;
729
+ const google = extra.google && typeof extra.google === "object" ? extra.google : {};
730
+ extra.google = google;
731
+ google.thought_signature = sig;
757
732
  }
758
- return Object.keys(filteredGroups).length > 0 ? [filteredGroups] : [];
759
- });
760
- if (beforeToolIdentities) observer?.({
761
- beforeToolIdentities,
762
- afterToolIdentities: readCodeModePayloadToolIdentities(payload)
763
- });
764
- }
765
- function resolveCodeModeResponsesVisibleToolNames(context) {
766
- return new Set((context.tools ?? []).map(readCodeModePayloadToolName).filter((name) => typeof name === "string"));
767
- }
768
- function enforceCodeModeResponsesToolSurface(payload, visibleToolNames, allowedHostedToolTypes, observer) {
769
- if (!isRecord(payload)) return;
770
- const tools = readToolPayloadField(payload, "tools");
771
- if (!Array.isArray(tools)) return;
772
- const beforeToolIdentities = observer ? readCodeModePayloadToolIdentities(payload) : void 0;
773
- payload.tools = tools.filter((tool) => Boolean(readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes)));
774
- if (beforeToolIdentities) observer?.({
775
- beforeToolIdentities,
776
- afterToolIdentities: readCodeModePayloadToolIdentities(payload)
777
- });
733
+ }
778
734
  }
779
- function assertCodeModeResponsesToolSurface(payload, visibleToolNames, allowedHostedToolTypes) {
780
- const tools = isRecord(payload) ? readToolPayloadField(payload, "tools") : void 0;
781
- if (!Array.isArray(tools)) throw new Error("Code mode payload tool surface violation: expected exec,wait; got no tools");
782
- const identities = tools.map((tool) => readCodeModePayloadToolIdentity(tool, visibleToolNames, allowedHostedToolTypes));
783
- const names = identities.flatMap((identity) => typeof identity === "string" && identity.startsWith("client:") ? [identity.slice(7)] : []).toSorted((left, right) => left.localeCompare(right));
784
- if (names.length >= 2 && identities.every((identity) => typeof identity === "string") && new Set(identities).size === identities.length && names.includes("exec") && names.includes("wait")) return;
785
- throw new Error(`Code mode payload tool surface violation: expected exec,wait plus direct-only tools; got ${names.length > 0 ? names.join(",") : "none"}`);
735
+ const COMPLETIONS_REASONING_REPLAY_FIELDS = [
736
+ "reasoning_details",
737
+ "reasoning_content",
738
+ "reasoning",
739
+ "reasoning_text"
740
+ ];
741
+ function stripCompletionsReasoningReplayFields(record) {
742
+ for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) if (field in record) delete record[field];
743
+ }
744
+ function sanitizeOpenRouterReasoningReplayFields(record) {
745
+ const reasoningDetails = record.reasoning_details;
746
+ if (typeof reasoningDetails === "string") {
747
+ if (reasoningDetails.length > 0 && typeof record.reasoning !== "string") record.reasoning = reasoningDetails;
748
+ delete record.reasoning_details;
749
+ } else if (reasoningDetails !== void 0 && !Array.isArray(reasoningDetails)) delete record.reasoning_details;
750
+ if ("reasoning" in record && (typeof record.reasoning !== "string" || record.reasoning === "")) delete record.reasoning;
751
+ if ("reasoning_content" in record && (typeof record.reasoning_content !== "string" || record.reasoning_content === "")) delete record.reasoning_content;
752
+ const reasoningText = record.reasoning_text;
753
+ if (typeof reasoningText === "string" && reasoningText.length > 0 && typeof record.reasoning !== "string" && typeof record.reasoning_content !== "string") record.reasoning = reasoningText;
754
+ if ("reasoning_text" in record) delete record.reasoning_text;
755
+ }
756
+ function sanitizeReasoningContentReplayFields(record) {
757
+ if ("reasoning_content" in record && typeof record.reasoning_content !== "string") delete record.reasoning_content;
758
+ delete record.reasoning_details;
759
+ delete record.reasoning;
760
+ delete record.reasoning_text;
761
+ }
762
+ const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([
763
+ "deepseek-v4-flash",
764
+ "deepseek-v4-pro",
765
+ "kimi-for-coding",
766
+ "kimi-k2.5",
767
+ "kimi-k2.6",
768
+ "kimi-k2.7-code",
769
+ "kimi-k2.7-code-highspeed",
770
+ "kimi-k3",
771
+ "kimi-k2-thinking",
772
+ "kimi-k2-thinking-turbo",
773
+ "mimo-v2-pro",
774
+ "mimo-v2-omni",
775
+ "mimo-v2.5",
776
+ "mimo-v2.5-pro",
777
+ "mimo-v2.6-pro"
778
+ ]);
779
+ const REASONING_CONTENT_REPLAY_TIER_SUFFIXES = [
780
+ "-free",
781
+ "-paid",
782
+ "-trial"
783
+ ];
784
+ function stripReasoningContentReplayTierSuffix(modelId) {
785
+ for (const suffix of REASONING_CONTENT_REPLAY_TIER_SUFFIXES) if (modelId.length > suffix.length && modelId.endsWith(suffix)) return modelId.slice(0, -suffix.length);
786
+ return modelId;
787
+ }
788
+ function getReasoningContentReplayModelIdCandidates(modelId) {
789
+ if (typeof modelId !== "string") return [];
790
+ const normalized = modelId.trim().toLowerCase();
791
+ if (!normalized) return [];
792
+ const parts = normalized.split("/").filter(Boolean);
793
+ const finalPart = parts[parts.length - 1] ?? normalized;
794
+ const candidates = [finalPart];
795
+ const colonParts = finalPart.split(":").filter(Boolean);
796
+ if (colonParts.length > 1) candidates.push(colonParts[0] ?? "", colonParts[colonParts.length - 1] ?? "");
797
+ const baseCount = candidates.length;
798
+ for (let index = 0; index < baseCount; index += 1) {
799
+ const candidate = candidates[index];
800
+ if (typeof candidate !== "string") continue;
801
+ const stripped = stripReasoningContentReplayTierSuffix(candidate);
802
+ if (stripped !== candidate) candidates.push(stripped);
803
+ }
804
+ return uniqueStrings(candidates.filter(Boolean));
786
805
  }
787
- function buildOpenAIStrictToolDowngradeDiagnosticKey(diagnostics, context) {
788
- return sha256Hex(JSON.stringify({
789
- transport: context.transport,
790
- provider: context.model.provider ?? null,
791
- model: context.model.id ?? null,
792
- diagnostics: diagnostics.map((entry) => ({
793
- toolIndex: entry.toolIndex,
794
- toolName: entry.toolName ?? null,
795
- violations: entry.violations
796
- }))
797
- }));
806
+ function shouldPreserveReasoningContentReplay(model, compat) {
807
+ if (compat.requiresReasoningContentOnAssistantMessages || compat.thinkingFormat === "deepseek" || compat.thinkingFormat === "zai" || shouldTrustReasoningContentReplayMetadata(model)) return true;
808
+ return getReasoningContentReplayModelIdCandidates(model.id).some((modelId) => REASONING_CONTENT_REPLAY_MODEL_IDS.has(modelId));
798
809
  }
799
- function shouldLogOpenAIStrictToolDowngradeDiagnostic(diagnostics, context) {
800
- const key = buildOpenAIStrictToolDowngradeDiagnosticKey(diagnostics, context);
801
- if (loggedOpenAIStrictToolDowngradeDiagnosticKeys.has(key)) return false;
802
- if (loggedOpenAIStrictToolDowngradeDiagnosticKeys.size >= MAX_OPENAI_STRICT_TOOL_DOWNGRADE_DIAGNOSTIC_KEYS) loggedOpenAIStrictToolDowngradeDiagnosticKeys.clear();
803
- loggedOpenAIStrictToolDowngradeDiagnosticKeys.add(key);
804
- return true;
810
+ function shouldPreserveOpenRouterReasoningReplay(model) {
811
+ if (model.provider !== "openrouter") return true;
812
+ const normalizedModelId = model.id.trim().toLowerCase();
813
+ return !(normalizedModelId.startsWith("anthropic/") || normalizedModelId.startsWith("x-ai/"));
805
814
  }
806
- function resolveOpenAIStrictToolFlagWithDiagnostics(projection, strictSetting, context) {
807
- const strict = resolveOpenAIProjectedToolsStrictToolFlag(projection, strictSetting);
808
- if (strictSetting === true && strict === false) {
809
- const diagnostics = findOpenAIStrictToolProjectionDiagnostics(projection);
810
- getAiTransportHost().logDebug("openai-transport", () => {
811
- if (!shouldLogOpenAIStrictToolDowngradeDiagnostic(diagnostics, context)) return null;
812
- const sample = diagnostics.slice(0, 5).map((entry) => ({
813
- tool: entry.toolName ?? `tool[${entry.toolIndex}]`,
814
- violations: entry.violations.slice(0, 8)
815
- }));
816
- return {
817
- 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`,
818
- data: {
819
- transport: context.transport,
820
- provider: context.model.provider,
821
- model: context.model.id,
822
- incompatibleToolCount: diagnostics.length,
823
- sample
824
- }
825
- };
826
- });
815
+ function shouldTrustReasoningContentReplayMetadata(model) {
816
+ if (!model.reasoning) return false;
817
+ if (model.provider.trim().toLowerCase() === "openai") return false;
818
+ return shouldPreserveOpenRouterReasoningReplay(model);
819
+ }
820
+ function sanitizeCompletionsReasoningReplayFields(messages, options) {
821
+ if (!Array.isArray(messages)) return;
822
+ for (const msg of messages) {
823
+ if (!msg || typeof msg !== "object") continue;
824
+ const record = msg;
825
+ if (record.role !== "assistant") continue;
826
+ if (options.preserveOpenRouterReasoning) sanitizeOpenRouterReasoningReplayFields(record);
827
+ else if (options.preserveReasoningContent) sanitizeReasoningContentReplayFields(record);
828
+ else stripCompletionsReasoningReplayFields(record);
827
829
  }
828
- return strict;
829
830
  }
830
- function isOpenAICodexResponsesModel(model) {
831
- return OPENAI_CODEX_RESPONSES_PROVIDERS.has(model.provider) && (model.api === "openai-chatgpt-responses" || model.api === "openclaw-openai-chatgpt-responses-transport");
831
+ function applyCompletionsReplay(outgoingMessages, context, model, compat) {
832
+ injectToolCallThoughtSignatures(outgoingMessages, context, model);
833
+ sanitizeCompletionsReasoningReplayFields(outgoingMessages, {
834
+ preserveOpenRouterReasoning: compat.thinkingFormat === "openrouter" && shouldPreserveOpenRouterReasoningReplay(model),
835
+ preserveReasoningContent: shouldPreserveReasoningContentReplay(model, compat)
836
+ });
832
837
  }
833
- function isNativeOpenAICodexResponsesBaseUrl(baseUrl) {
834
- const trimmed = typeof baseUrl === "string" ? baseUrl.trim() : "";
835
- if (!trimmed) return false;
838
+ //#endregion
839
+ //#region packages/ai/src/transports/openai-completions-params.ts
840
+ function isKnownOpenAICompletionsEndpoint(model) {
841
+ if (!model.baseUrl.trim()) return true;
842
+ const endpointClass = resolveProviderEndpoint(model).endpointClass;
843
+ if (endpointClass === "openai-public" || endpointClass === "azure-openai") return true;
836
844
  try {
837
- const url = new URL(trimmed);
838
- if (url.protocol !== "http:" && url.protocol !== "https:") return false;
839
- if (url.hostname.toLowerCase() !== "chatgpt.com") return false;
840
- const pathname = url.pathname.replace(/\/+$/u, "").toLowerCase();
841
- return [
842
- "/backend-api",
843
- "/backend-api/v1",
844
- "/backend-api/codex",
845
- "/backend-api/codex/v1"
846
- ].includes(pathname);
845
+ return isAzureOpenAICompatibleHost(new URL(model.baseUrl).hostname.toLowerCase());
847
846
  } catch {
848
847
  return false;
849
848
  }
850
849
  }
851
- function usesNativeOpenAICodexResponsesBackend(model) {
852
- return isOpenAICodexResponsesModel(model) && isNativeOpenAICodexResponsesBaseUrl(model.baseUrl);
850
+ function resolveOpenAICompletionsReasoningEffort(options) {
851
+ return options?.reasoningEffort ?? options?.reasoning ?? "high";
853
852
  }
854
- function buildOpenAIClientHeaders(model, context, optionHeaders, turnHeaders, sessionId) {
855
- const providerHeaders = { ...model.headers };
856
- if (model.provider === "github-copilot") Object.assign(providerHeaders, getAiTransportHost().buildCopilotDynamicHeaders(context.messages));
857
- const callerHeaders = {
858
- ...optionHeaders,
859
- ...turnHeaders
853
+ function resolveOpenAICompletionsMaxTokens(model, options) {
854
+ if (options?.maxTokens) return {
855
+ maxTokens: options.maxTokens,
856
+ clampToModelMaxTokens: true
857
+ };
858
+ const paramsMaxTokens = resolveMaxTokensParam(model.params);
859
+ if (paramsMaxTokens) return {
860
+ maxTokens: paramsMaxTokens,
861
+ clampToModelMaxTokens: false
860
862
  };
861
- const resolvedHeaders = resolveProviderRequestPolicyConfig(model, {
862
- provider: model.provider,
863
- api: model.api,
864
- baseUrl: model.baseUrl,
865
- capability: "llm",
866
- transport: "stream",
867
- providerHeaders,
868
- callerHeaders: Object.keys(callerHeaders).length > 0 ? callerHeaders : void 0,
869
- precedence: "caller-wins"
870
- }).headers ?? {};
871
- if (sessionId && !Object.keys(resolvedHeaders).some((key) => normalizeLowercaseStringOrEmpty(key) === "session_id") && usesNativeOpenAICodexResponsesBackend(model)) resolvedHeaders.session_id = clampOpenAIPromptCacheKey(sessionId) ?? sessionId;
872
- return resolvedHeaders;
873
- }
874
- function resolveOpenAISdkTimeoutMs(model, timeoutMs) {
875
- return resolveModelRequestTimeoutMs(model, timeoutMs);
876
- }
877
- function buildOpenAISdkClientOptions(model) {
878
- const timeout = resolveOpenAISdkTimeoutMs(model);
879
863
  return {
880
- ...timeout === void 0 ? {} : { timeout },
881
- maxRetries: 0
864
+ maxTokens: model.maxTokens,
865
+ clampToModelMaxTokens: false
882
866
  };
883
867
  }
884
- function buildOpenAISdkRequestOptions(model, signal, options) {
885
- const timeout = resolveOpenAISdkTimeoutMs(model, options?.timeoutMs);
886
- const headers = options?.stream === true && usesNativeOpenAICodexResponsesBackend(model) ? { Accept: "text/event-stream" } : void 0;
887
- if (timeout === void 0 && !signal && !headers) return;
868
+ function resolveOpenAICompletionsModelMaxTokens(model) {
869
+ return typeof model.maxTokens === "number" && Number.isFinite(model.maxTokens) && model.maxTokens > 0 ? Math.floor(model.maxTokens) : void 0;
870
+ }
871
+ const OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN = 1.25;
872
+ const OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE = 8e3;
873
+ function estimateOpenAICompletionsInputTokens(payload) {
874
+ let adjustedChars = 0;
875
+ adjustedChars += estimateOpenAICompletionsMessagesChars(payload.messages);
876
+ if (Array.isArray(payload.tools) && payload.tools.length > 0) try {
877
+ adjustedChars += estimateStringChars(JSON.stringify(payload.tools));
878
+ } catch {
879
+ adjustedChars += 1024;
880
+ }
881
+ if (payload.response_format !== void 0) try {
882
+ adjustedChars += estimateStringChars(JSON.stringify(payload.response_format));
883
+ } catch {
884
+ adjustedChars += 256;
885
+ }
886
+ return Math.ceil(adjustedChars / 4 * OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN);
887
+ }
888
+ function estimateOpenAICompletionsMessagesChars(messages) {
889
+ if (!Array.isArray(messages)) return 0;
890
+ let adjustedChars = 0;
891
+ for (const message of messages) {
892
+ if (!message || typeof message !== "object") continue;
893
+ const record = message;
894
+ adjustedChars += estimateOpenAICompletionsContentChars(record.content);
895
+ for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) adjustedChars += estimateOpenAICompletionsContentChars(record[field]);
896
+ if (record.tool_calls !== void 0) try {
897
+ adjustedChars += estimateStringChars(JSON.stringify(record.tool_calls));
898
+ } catch {
899
+ adjustedChars += 256;
900
+ }
901
+ }
902
+ return adjustedChars;
903
+ }
904
+ function estimateOpenAICompletionsContentChars(value) {
905
+ if (typeof value === "string") return estimateStringChars(value);
906
+ if (!Array.isArray(value)) return 0;
907
+ let adjustedChars = 0;
908
+ for (const block of value) {
909
+ if (!block || typeof block !== "object") continue;
910
+ const record = block;
911
+ if (record.type === "image_url" || record.type === "input_image") {
912
+ adjustedChars += OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE;
913
+ continue;
914
+ }
915
+ const text = record.text;
916
+ if (typeof text === "string") {
917
+ adjustedChars += estimateStringChars(text);
918
+ continue;
919
+ }
920
+ try {
921
+ adjustedChars += estimateStringChars(JSON.stringify(block));
922
+ } catch {
923
+ adjustedChars += 256;
924
+ }
925
+ }
926
+ return adjustedChars;
927
+ }
928
+ function resolveOpenAICompletionsEffectiveContextTokens(model) {
929
+ const contextTokens = model.contextTokens;
930
+ if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens > 0) return contextTokens;
931
+ return typeof model.contextWindow === "number" && Number.isFinite(model.contextWindow) && model.contextWindow > 0 ? model.contextWindow : void 0;
932
+ }
933
+ function isQwenOpenAICompletionsThinkingFormat(format) {
934
+ return format === "qwen" || format === "qwen-chat-template";
935
+ }
936
+ function setQwenChatTemplateThinking(params, enabled) {
937
+ const existing = params.chat_template_kwargs;
938
+ params.chat_template_kwargs = existing && typeof existing === "object" && !Array.isArray(existing) ? {
939
+ ...existing,
940
+ enable_thinking: enabled
941
+ } : { enable_thinking: enabled };
942
+ }
943
+ function applyQwenOpenAICompletionsThinkingParams(params) {
944
+ if (!params.modelReasoning || !isQwenOpenAICompletionsThinkingFormat(params.compatThinkingFormat)) return false;
945
+ const enabled = isOpenAICompletionsThinkingEnabled(params.requestedEffort);
946
+ if (params.compatThinkingFormat === "qwen-chat-template") setQwenChatTemplateThinking(params.payload, enabled);
947
+ else params.payload.enable_thinking = enabled;
948
+ return true;
949
+ }
950
+ function applyTogetherOpenAICompletionsThinkingParams(params) {
951
+ if (!params.modelReasoning || params.compatThinkingFormat !== "together") return;
952
+ params.payload.reasoning = { enabled: isOpenAICompletionsThinkingEnabled(params.requestedEffort) };
953
+ }
954
+ function convertTools(tools, compat, model, mode) {
955
+ const projection = projectOpenAITools(tools);
956
+ const strict = mode === "direct" ? compat.supportsStrictMode ? false : void 0 : resolveOpenAIStrictToolFlagWithDiagnostics(projection, resolveOpenAIStrictToolSetting(model, {
957
+ transport: "stream",
958
+ supportsStrictMode: compat?.supportsStrictMode
959
+ }), {
960
+ transport: "completions",
961
+ model
962
+ });
888
963
  return {
889
- ...headers ? { headers } : {},
890
- ...signal ? { signal } : {},
891
- ...timeout !== void 0 ? { timeout } : {},
892
- maxRetries: 0
964
+ projection,
965
+ tools: sortPromptCacheToolsByName(projection.tools).map((tool) => {
966
+ const functionTool = {
967
+ name: tool.name,
968
+ description: tool.description,
969
+ parameters: mode === "direct" ? tool.parameters : normalizeOpenAIStrictToolParameters(tool.parameters, strict === true, model.compat)
970
+ };
971
+ if (strict !== void 0) functionTool.strict = strict;
972
+ return {
973
+ type: "function",
974
+ function: functionTool
975
+ };
976
+ })
893
977
  };
894
978
  }
895
- function getCompat(model) {
896
- const resolved = resolveOpenAICompletionsCompat(model);
897
- const compat = model.compat ?? {};
898
- return {
899
- ...resolved,
900
- cacheControlFormat: resolved.cacheControlFormat,
901
- reasoningEffortMap: resolveOpenAIReasoningEffortMap(model, {}),
902
- openRouterRouting: resolved.openRouterRouting ?? {},
903
- vercelGatewayRouting: resolved.vercelGatewayRouting,
904
- requiresStringContent: compat.requiresStringContent ?? false,
905
- strictMessageKeys: compat.strictMessageKeys === true
979
+ function buildOpenAICompletionsParams(model, context, options) {
980
+ return buildOpenAICompletionsRequest(model, context, options, { mode: "managed" });
981
+ }
982
+ function buildOpenAICompletionsRequest(model, context, options, policy) {
983
+ const resolvedPolicy = policy.mode === "direct" ? policy : {
984
+ ...policy,
985
+ compat: getCompat(model)
986
+ };
987
+ const compat = resolvedPolicy.compat;
988
+ const managedCompat = resolvedPolicy.mode === "managed" ? resolvedPolicy.compat : void 0;
989
+ const endpointDetection = detectOpenAICompletionsCompat(model);
990
+ const compatDetection = policy.mode === "managed" ? endpointDetection : void 0;
991
+ const { endpointClass } = endpointDetection.capabilities;
992
+ const cacheRetention = policy.mode === "direct" ? policy.cacheRetention : resolveCacheRetention(options?.cacheRetention);
993
+ const cacheControl = resolveCompletionsCacheControl(compat, cacheRetention, endpointClass === "openrouter" || endpointClass === "default" && model.provider === "openrouter");
994
+ const markTools = endpointClass !== "modelstudio-native" && !(endpointClass === "default" && [
995
+ "modelstudio",
996
+ "dashscope",
997
+ "qwen"
998
+ ].includes(model.provider));
999
+ const cacheOptOutIndexes = /* @__PURE__ */ new Set();
1000
+ let messages = convertMessages(model, context, compat, {
1001
+ cacheOptOutIndexes,
1002
+ preserveSystemPromptCacheBoundary: cacheControl !== void 0 && !managedCompat?.requiresStringContent
1003
+ });
1004
+ if (managedCompat) {
1005
+ applyCompletionsReplay(messages, context, model, managedCompat);
1006
+ if (managedCompat.strictMessageKeys) messages = stripCompletionMessagesToRoleContent(messages);
1007
+ if (managedCompat.requiresStringContent) messages = flattenCompletionMessagesToStringContent(messages);
1008
+ }
1009
+ const promptCacheKey = resolvePromptCacheKey(options, cacheRetention);
1010
+ const params = {
1011
+ model: model.id,
1012
+ messages,
1013
+ stream: true,
1014
+ ...resolveOpenAIPromptCacheParams(model, cacheRetention, compat)
906
1015
  };
1016
+ if (compat.supportsUsageInStreaming) params.stream_options = { include_usage: true };
1017
+ if (compat.supportsStore) params.store = false;
1018
+ if (policy.mode === "direct" || compat.supportsPromptCacheKey && promptCacheKey) params.prompt_cache_key = compat.supportsPromptCacheKey ? promptCacheKey : void 0;
1019
+ if (options?.temperature !== void 0) params.temperature = options.temperature;
1020
+ if (policy.mode === "managed" && options?.topP !== void 0) params.top_p = options.topP;
1021
+ const requestedResponseFormat = options?.responseFormat;
1022
+ const responseFormat = policy.mode === "direct" && requestedResponseFormat === void 0 ? void 0 : resolveOpenAICompletionsResponseFormat(shouldOmitOllamaCompatResponseFormat({
1023
+ provider: model.provider,
1024
+ baseUrl: model.baseUrl,
1025
+ hasTools: () => Boolean(context.tools?.length)
1026
+ }) ? void 0 : requestedResponseFormat, compat.supportsJsonSchemaResponseFormat);
1027
+ if (responseFormat !== void 0) params.response_format = responseFormat;
1028
+ if (policy.mode === "managed" && options?.frequencyPenalty !== void 0) params.frequency_penalty = options.frequencyPenalty;
1029
+ if (policy.mode === "managed" && options?.presencePenalty !== void 0) params.presence_penalty = options.presencePenalty;
1030
+ if (policy.mode === "managed" && options?.seed !== void 0) params.seed = options.seed;
1031
+ if (options?.stop !== void 0 && options.stop.length > 0) params.stop = options.stop;
1032
+ let directToolProjection;
1033
+ if (policy.mode === "direct" || supportsModelTools(model)) {
1034
+ if (context.tools) {
1035
+ const converted = convertTools(context.tools, compat, model, policy.mode);
1036
+ if (policy.mode === "direct") directToolProjection = converted.projection;
1037
+ if (converted.tools.length > 0 || policy.mode === "managed" && converted.projection.inputToolCount === 0 && converted.projection.diagnostics.length === 0) params.tools = converted.tools;
1038
+ else if (hasToolCallHistory(context.messages)) params.tools = [];
1039
+ if (policy.mode === "direct" && compat.zaiToolStream && converted.tools.length > 0) params.tool_stream = true;
1040
+ if (policy.mode === "managed" && options?.toolChoice) {
1041
+ const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, converted.projection);
1042
+ if (toolChoice !== void 0) params.tool_choice = toolChoice;
1043
+ } else if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length > 0) params.tool_choice = "auto";
1044
+ } else if (hasToolCallHistory(context.messages)) params.tools = [];
1045
+ if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length === 0) {
1046
+ delete params.tools;
1047
+ delete params.tool_choice;
1048
+ }
1049
+ }
1050
+ if (policy.mode === "direct" && compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools);
1051
+ if (policy.mode === "direct" && options?.toolChoice) {
1052
+ const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, directToolProjection ?? projectOpenAITools([]));
1053
+ if (toolChoice !== void 0) params.tool_choice = toolChoice;
1054
+ }
1055
+ {
1056
+ const maxTokenBudget = policy.mode === "direct" ? {
1057
+ maxTokens: options?.maxTokens,
1058
+ clampToModelMaxTokens: true
1059
+ } : resolveOpenAICompletionsMaxTokens(model, options);
1060
+ const effectiveMaxTokens = maxTokenBudget.maxTokens;
1061
+ const effectiveContextTokens = resolveOpenAICompletionsEffectiveContextTokens(model);
1062
+ let clampedMaxTokens = effectiveMaxTokens;
1063
+ const modelMaxTokens = resolveOpenAICompletionsModelMaxTokens(model);
1064
+ if (maxTokenBudget.clampToModelMaxTokens && clampedMaxTokens !== void 0 && modelMaxTokens !== void 0 && clampedMaxTokens > modelMaxTokens) {
1065
+ clampedMaxTokens = modelMaxTokens;
1066
+ if (policy.mode === "managed") emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} modelMaxTokens=${modelMaxTokens}`);
1067
+ }
1068
+ if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && clampedMaxTokens !== void 0 && effectiveContextTokens !== void 0) {
1069
+ const estimatedInputTokens = estimateOpenAICompletionsInputTokens(params);
1070
+ const remainingBudget = Math.max(1, effectiveContextTokens - estimatedInputTokens - 1);
1071
+ if (clampedMaxTokens > remainingBudget) {
1072
+ clampedMaxTokens = remainingBudget;
1073
+ emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} effectiveContext=${effectiveContextTokens} estimatedInput=${estimatedInputTokens}`);
1074
+ }
1075
+ }
1076
+ if (policy.mode === "direct" ? options?.maxTokens : clampedMaxTokens) {
1077
+ if (compat.maxTokensField === "max_tokens") params.max_tokens = clampedMaxTokens;
1078
+ else params.max_completion_tokens = clampedMaxTokens;
1079
+ }
1080
+ }
1081
+ if (policy.mode === "direct") {
1082
+ applyDirectCompletionsReasoningAndRouting(params, model, options, compat);
1083
+ return params;
1084
+ }
1085
+ const completionsReasoningEffort = resolveOpenAICompletionsReasoningEffort(options);
1086
+ const resolvedCompletionsReasoningEffort = completionsReasoningEffort ? resolveOpenAIReasoningEffortForModel({
1087
+ model,
1088
+ effort: completionsReasoningEffort,
1089
+ fallbackMap: managedCompat?.reasoningEffortMap
1090
+ }) : void 0;
1091
+ const omitChatCompletionsToolReasoningEffort = Array.isArray(params.tools) && params.tools.length > 0 && (isOpenAIGpt54MiniModel(model) || isOpenAIGpt55Model(model) && isKnownOpenAICompletionsEndpoint(model));
1092
+ const disableChatCompletionsToolReasoning = Array.isArray(params.tools) && params.tools.length > 0 && isOpenAIGpt56Model(model) && isKnownOpenAICompletionsEndpoint(model);
1093
+ const handledQwenThinkingFormat = applyQwenOpenAICompletionsThinkingParams({
1094
+ compatThinkingFormat: compat.thinkingFormat,
1095
+ modelReasoning: model.reasoning,
1096
+ payload: params,
1097
+ requestedEffort: completionsReasoningEffort
1098
+ });
1099
+ applyTogetherOpenAICompletionsThinkingParams({
1100
+ compatThinkingFormat: compat.thinkingFormat,
1101
+ modelReasoning: model.reasoning,
1102
+ payload: params,
1103
+ requestedEffort: completionsReasoningEffort
1104
+ });
1105
+ if (disableChatCompletionsToolReasoning) params.reasoning_effort = "none";
1106
+ else if (compat.thinkingFormat === "openrouter" && model.reasoning && resolvedCompletionsReasoningEffort) params.reasoning = { effort: resolvedCompletionsReasoningEffort };
1107
+ else if (resolvedCompletionsReasoningEffort && model.reasoning && compat.supportsReasoningEffort && !handledQwenThinkingFormat && !omitChatCompletionsToolReasoningEffort) params.reasoning_effort = resolvedCompletionsReasoningEffort;
1108
+ if (compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools, !managedCompat?.requiresStringContent);
1109
+ return params;
907
1110
  }
908
1111
  //#endregion
909
1112
  //#region packages/ai/src/transports/openai-completions-dsml.ts
@@ -1270,7 +1473,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
1270
1473
  partial: output
1271
1474
  });
1272
1475
  }
1273
- currentBlock.thinking += reasoningDelta.text;
1476
+ appendAssistantThinking(currentBlock, reasoningDelta.text);
1274
1477
  pushStreamEvent({
1275
1478
  type: "thinking_delta",
1276
1479
  contentIndex: blockIndex(),
@@ -1588,6 +1791,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
1588
1791
  emitReasoningUsageActivity(hasReasoningUsageActivity);
1589
1792
  if (cooperativeScheduler) await cooperativeScheduler.afterEvent();
1590
1793
  }
1794
+ throwIfModelStreamAborted(options?.signal);
1591
1795
  if (!finishReason && (directMode || options?.sawStreamDONE?.() === false)) throw new Error("Stream ended without finish_reason");
1592
1796
  flushReasoningTagTextPartitioner();
1593
1797
  flushDeepSeekToolCallRecovererAtEnd();
@@ -1611,12 +1815,9 @@ async function processCompletionsStream(responseStream, output, model, stream, o
1611
1815
  if (output.stopReason === "error" || output.stopReason === "aborted") tagUnresolvedTextAsCommentary(output);
1612
1816
  if (output.stopReason === "toolUse") tagPendingCommentaryText(output.content);
1613
1817
  }
1614
- function resolveOpenAICompletionsReasoningEffort(options) {
1615
- return options?.reasoningEffort ?? options?.reasoning ?? "high";
1616
- }
1617
1818
  function shouldEmitOpenAICompletionsReasoning(model, options) {
1618
1819
  if (!model.reasoning) return false;
1619
- const effort = resolveOpenAICompletionsReasoningEffort(options);
1820
+ const effort = options?.reasoningEffort ?? options?.reasoning ?? "high";
1620
1821
  if (!effort || !isOpenAICompletionsThinkingEnabled(effort)) return false;
1621
1822
  return true;
1622
1823
  }
@@ -1625,4 +1826,4 @@ function hasOpenAICompletionsReasoningUsageActivity(rawUsage) {
1625
1826
  return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) && reasoningTokens > 0;
1626
1827
  }
1627
1828
  //#endregion
1628
- export { createDeepSeekTextFilter as C, tagUnresolvedTextAsCommentary as E, shouldOmitOllamaCompatResponseFormat as S, tagPendingCommentaryText as T, hasToolCallHistory as _, buildOpenAISdkClientOptions as a, resolveOpenAICompletionsCompat as b, filterCodeModePayloadTools as c, readCodeModePayloadToolName as d, resolveCodeModeResponsesVisibleToolNames as f, convertMessages as g, resolveOpenAIReasoningEffortMap as h, buildOpenAIClientHeaders as i, getCompat as l, usesNativeOpenAICodexResponsesBackend as m, shouldEmitOpenAICompletionsReasoning as n, buildOpenAISdkRequestOptions as o, resolveOpenAIStrictToolFlagWithDiagnostics as p, assertCodeModeResponsesToolSurface as r, enforceCodeModeResponsesToolSurface as s, processCompletionsStream as t, isOpenAICodexResponsesModel as u, finalizeOpenAICompletionsToolCalls as v, clearPendingCommentaryText as w, resolveOpenAICompletionsResponseFormat as x, detectOpenAICompletionsCompat as y };
1829
+ export { convertMessages as a, flattenCompletionMessagesToStringContent as c, canonicalizeMaxTokensParam as d, resolveMaxTokensParam as f, buildOpenAICompletionsRequest as i, stripCompletionMessagesToRoleContent as l, shouldEmitOpenAICompletionsReasoning as n, isAzureOpenAICompatibleHost as o, createDeepSeekTextFilter as p, buildOpenAICompletionsParams as r, finalizeOpenAICompletionsToolCalls as s, processCompletionsStream as t, applyCompletionsAnthropicCacheControl as u };