@openclaw/ai 2026.9.1 → 2026.9.3

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 (111) hide show
  1. package/README.md +3 -2
  2. package/dist/anthropic-CZy5U0NY.mjs +376 -0
  3. package/dist/anthropic-payload-policy-wuRCb6MH.d.mts +85 -0
  4. package/dist/anthropic-stream-reducer-B_yo_7pf.mjs +1669 -0
  5. package/dist/{api-registry-Cs6HGNqY.d.mts → api-registry-CB1-1oQ2.d.mts} +2 -2
  6. package/dist/assistant-output-iqnlJCV2.mjs +16 -0
  7. package/dist/assistant-text-phase-C20rxWwP.mjs +55 -0
  8. package/dist/{azure-openai-responses-CN4Fy5zV.mjs → azure-openai-responses-Crpa40QR.mjs} +5 -5
  9. package/dist/{base64-CEFBpSkN.mjs → base64-D-su8YVo.mjs} +1 -27
  10. package/dist/{diagnostics-KhXK-QJI.mjs → diagnostics-dV98PqIy.mjs} +96 -21
  11. package/dist/diagnostics.d.mts +3 -1
  12. package/dist/diagnostics.mjs +3 -3
  13. package/dist/{event-stream-vK_7r3bj.d.mts → event-stream-C1Z2piyk.d.mts} +11 -3
  14. package/dist/{event-stream-uSMZJ3FA.mjs → event-stream-D8PARQfL.mjs} +48 -10
  15. package/dist/event-stream-I_GlBsuZ.d.mts +1 -0
  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-CC-nrwXg.mjs → google-BDPriaVe.mjs} +10 -10
  21. package/dist/google-messages-CVn9eFpF.mjs +449 -0
  22. package/dist/google-shared-BedY23XS.mjs +185 -0
  23. package/dist/{google-vertex-DCr0pyzQ.mjs → google-vertex-DMz8XQCn.mjs} +7 -6
  24. package/dist/{host-BIaiBURL.mjs → host-CWuF-sS3.mjs} +84 -34
  25. package/dist/{host-BztR4tQj.d.mts → host-DK3wmS3e.d.mts} +3 -3
  26. package/dist/host-policy-CAopLRKA.mjs +37 -0
  27. package/dist/{index-AfaxKT8w.d.mts → index-CQ6LTHw8.d.mts} +11 -5
  28. package/dist/index.d.mts +7 -7
  29. package/dist/index.mjs +5 -5
  30. package/dist/internal/anthropic.d.mts +14 -11
  31. package/dist/internal/anthropic.mjs +5 -5
  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 +1 -1
  35. package/dist/internal/openai-responses-payload-policy.mjs +1 -1
  36. package/dist/internal/openai.d.mts +8 -103
  37. package/dist/internal/openai.mjs +10 -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 +7 -6
  41. package/dist/internal/runtime.mjs +8 -7
  42. package/dist/internal/shared.d.mts +14 -3
  43. package/dist/internal/shared.mjs +6 -4
  44. package/dist/internal/tool-schema.d.mts +63 -0
  45. package/dist/internal/tool-schema.mjs +3 -0
  46. package/dist/{json-parse-BuAJEbdW.mjs → json-parse-Dw_hnxsA.mjs} +1 -1
  47. package/dist/{mistral-B7etBd_H.mjs → mistral--m-Jm6VZ.mjs} +16 -38
  48. package/dist/model-transport-url-DKocrEsb.d.mts +28 -0
  49. package/dist/{openai-chatgpt-responses-BAJ4gq3i.mjs → openai-chatgpt-responses-nDZccFW-.mjs} +62 -54
  50. package/dist/openai-completions-KuoZyx0d.mjs +187 -0
  51. package/dist/{openai-completions-compat-4IjSBpr6.d.mts → openai-completions-compat-CXn3T1we.d.mts} +26 -4
  52. package/dist/{openai-completions-stream-DZjwK8vp.mjs → openai-completions-stream-BQk3SkLD.mjs} +621 -451
  53. package/dist/openai-prompt-cache-BI0rkM-5.mjs +220 -0
  54. package/dist/openai-prompt-cache-rrutvqxq.d.mts +16 -0
  55. package/dist/openai-provider-client-ha_WxTyj.mjs +24 -0
  56. package/dist/openai-reasoning-effort-BK7FbcLT.mjs +167 -0
  57. package/dist/{openai-responses-BEUlxfDu.mjs → openai-responses-D99dOzKI.mjs} +14 -32
  58. package/dist/{openai-responses-compaction-window-DO7yV7az.mjs → openai-responses-compaction-window-D5jbzCi3.mjs} +7 -159
  59. package/dist/{openai-responses-contracts-bPzSN_Ba.d.mts → openai-responses-contracts-B55afwRo.d.mts} +12 -4
  60. package/dist/{openai-responses-prompt-observer-internal-C7x4IK7r.mjs → openai-responses-prompt-observer-internal-tApyTLVu.mjs} +3 -3
  61. package/dist/{openai-responses-shared-DiAdpNAM.mjs → openai-responses-shared-ZyQEzS5i.mjs} +217 -212
  62. package/dist/openai-tool-projection-793cE3QX.d.mts +37 -0
  63. package/dist/{openai-tool-schema-BMiHFH36.mjs → openai-tool-schema-CzjyYXun.mjs} +53 -583
  64. package/dist/openai-tool-schema-ynZBgqW-.d.mts +38 -0
  65. package/dist/openai-transport-params-DNasp2fU.mjs +674 -0
  66. package/dist/{provider-error-B6EGq6gg.mjs → provider-error-C6TbKiey.mjs} +29 -13
  67. package/dist/{provider-options-CxFPtvh7.d.mts → provider-options-BXr9Ec83.d.mts} +19 -42
  68. package/dist/provider-replay-context-BuSUaAk5.mjs +21 -0
  69. package/dist/{provider-transcript-transform-WvJmFUAf.mjs → provider-transcript-transform-pPmUIwKt.mjs} +1 -1
  70. package/dist/provider-types-CAV0Og3m.d.mts +29 -0
  71. package/dist/provider-types.d.mts +6 -31
  72. package/dist/providers.d.mts +2 -2
  73. package/dist/providers.mjs +11 -11
  74. package/dist/{reasoning-tag-text-partitioner-C-4uedDb.mjs → reasoning-tag-text-partitioner-BQBi4B9d.mjs} +90 -35
  75. package/dist/record-coerce-DwRYMj3t.mjs +32 -0
  76. package/dist/retry-after-CdCURCVg.d.mts +15 -0
  77. package/dist/{sanitize-unicode-S6binQG-.mjs → sanitize-unicode-Bb-v9meu.mjs} +1 -1
  78. package/dist/session-affinity-CCH7eYdB.mjs +20 -0
  79. package/dist/{simple-options-0PLDyJ-d.mjs → simple-options-tcKOqnpF.mjs} +3 -3
  80. package/dist/{src-C8U7lkoa.mjs → src-B2Q_6G8V.mjs} +10 -2
  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-BtZiml7r.mjs → tool-schema-json-projection-ClptDdAO.mjs} +2 -38
  85. package/dist/{transport-stream-shared-CytPVLIg.mjs → transport-stream-shared-Cu3ZPhNW.mjs} +5 -5
  86. package/dist/{transport-stream-shared-BrvFTkoO.d.mts → transport-stream-shared-DOxpGJ-H.d.mts} +4 -4
  87. package/dist/transport-utils-CCooe-cr.mjs +121 -0
  88. package/dist/transports.d.mts +114 -41
  89. package/dist/transports.mjs +796 -1596
  90. package/dist/types-BADKjDBI.d.mts +1 -0
  91. package/dist/{types-DbrhszyQ.d.mts → types-Dy1q0CSu.d.mts} +101 -64
  92. package/dist/types.d.mts +6 -6
  93. package/dist/types.mjs +4 -4
  94. package/dist/utf16-slice-CvGodqok.mjs +29 -0
  95. package/dist/{validation-CJZtym2g.mjs → validation-BDzVDnTs.mjs} +9 -1
  96. package/dist/{validation-BOwtcl9X.d.mts → validation-CaFUZN9B.d.mts} +1 -1
  97. package/dist/validation.d.mts +1 -1
  98. package/dist/validation.mjs +1 -1
  99. package/package.json +14 -4
  100. package/dist/anthropic-compaction-replay-OMJZ0uyo.mjs +0 -838
  101. package/dist/anthropic-hk7F7ptG.mjs +0 -883
  102. package/dist/anthropic-payload-policy-DBT1itQ-.d.mts +0 -51
  103. package/dist/event-stream-DeDhbCc5.d.mts +0 -1
  104. package/dist/google-shared-CjPY0hZM.mjs +0 -634
  105. package/dist/google-thinking-level-C-V3tecN.mjs +0 -9
  106. package/dist/openai-completions-D0QZ0AyB.mjs +0 -403
  107. package/dist/openai-prompt-cache-2uo_1OR1.d.mts +0 -7
  108. package/dist/openai-prompt-cache-mZTCdRPo.mjs +0 -12
  109. package/dist/transport-utils-CtuS1Upe.mjs +0 -138
  110. package/dist/types-B5EFUmXs.d.mts +0 -1
  111. package/dist/utf16-slice-qz3nsy87.mjs +0 -84
@@ -1,72 +1,29 @@
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-S6binQG-.mjs";
3
- import { a as describeToolResultMediaPlaceholder, c as extractToolResultText, d as isImageWithMediaPayload, n as getAiTransportHost } from "./host-BIaiBURL.mjs";
4
- import { n as clampOpenAIPromptCacheKey } from "./openai-prompt-cache-mZTCdRPo.mjs";
5
- import { d as stripSystemPromptCacheBoundary } from "./simple-options-0PLDyJ-d.mjs";
6
- import { o as resolveProviderRequestCapabilities, r as resolveModelRequestTimeoutMs, s as resolveProviderRequestPolicyConfig } from "./tool-schema-json-projection-BtZiml7r.mjs";
7
- import { i as isCodeModeModelVisibleToolName, p as sha256Hex } from "./transport-utils-CtuS1Upe.mjs";
8
- import { s as finalizeTerminalToolCallArguments } from "./transport-stream-shared-CytPVLIg.mjs";
9
- import { r as parseStreamingJson, t as createToolArgumentPreviewSchedule } from "./json-parse-BuAJEbdW.mjs";
1
+ import { r as appendAssistantThinking } from "./event-stream-D8PARQfL.mjs";
2
+ import { t as sanitizeSurrogates } from "./sanitize-unicode-Bb-v9meu.mjs";
3
+ import { a as describeToolResultMediaPlaceholder, c as extractToolResultText, d as isImageWithMediaPayload } from "./host-CWuF-sS3.mjs";
4
+ import { o as isRecord } from "./record-coerce-DwRYMj3t.mjs";
5
+ import { t as truncateUtf16Safe } from "./utf16-slice-CvGodqok.mjs";
6
+ import { d as shouldOmitOllamaCompatResponseFormat, i as detectOpenAICompletionsCompat, r as resolveOpenAIPromptCacheParams, u as resolveOpenAICompletionsResponseFormat } from "./openai-prompt-cache-BI0rkM-5.mjs";
7
+ import { r as emitModelTransportDebug } from "./diagnostics-dV98PqIy.mjs";
8
+ import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-BQBi4B9d.mjs";
9
+ import { i as asNonNegativeFiniteNumber } from "./base64-D-su8YVo.mjs";
10
+ import { l as supportsModelTools, r as estimateStringChars } from "./transport-utils-CCooe-cr.mjs";
11
+ import { n as uniqueStrings } from "./string-normalization-CmLIasuf.mjs";
12
+ import { i as resolveProviderEndpoint, r as resolveOpenAIStrictToolSetting } from "./host-policy-CAopLRKA.mjs";
13
+ import { t as resolveCacheRetention } from "./cache-retention-0x979a5V.mjs";
14
+ import { d as stripSystemPromptCacheBoundary, m as sortPromptCacheToolsByName, u as splitSystemPromptCacheBoundary } from "./simple-options-tcKOqnpF.mjs";
15
+ 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-DNasp2fU.mjs";
16
+ import { s as finalizeTerminalToolCallArguments } from "./transport-stream-shared-Cu3ZPhNW.mjs";
17
+ import { a as tagUnresolvedTextAsCommentary, i as tagPendingCommentaryText, n as rememberPendingCommentaryTags, r as tagInterruptedTextPhases, t as clearPendingCommentaryText } from "./assistant-text-phase-C20rxWwP.mjs";
18
+ import { r as parseStreamingJson, t as createToolArgumentPreviewSchedule } from "./json-parse-Dw_hnxsA.mjs";
10
19
  import { t as notifyLlmRequestActivity } from "./llm-request-activity-BjtkplhG.mjs";
11
- 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-BMiHFH36.mjs";
12
- import { a as withFirstStreamEventTimeout } from "./stream-first-event-timeout-DcNjoFQE.mjs";
13
- import { t as transformProviderMessages } from "./provider-transcript-transform-WvJmFUAf.mjs";
20
+ import { a as withFirstStreamEventTimeout } from "./stream-first-event-timeout-2hfrquuz.mjs";
21
+ import { t as transformProviderMessages } from "./provider-transcript-transform-pPmUIwKt.mjs";
22
+ import { n as isOpenAIGpt55Model, o as resolveOpenAIReasoningEffortForModel, r as isOpenAIGpt56Model, t as isOpenAIGpt54MiniModel } from "./openai-reasoning-effort-BK7FbcLT.mjs";
23
+ import { r as normalizeOpenAIStrictToolParameters } from "./openai-tool-schema-CzjyYXun.mjs";
24
+ import { isGoogleGemini3FlashModel, isGoogleGemini3ProModel } from "./internal/google-model-family.mjs";
14
25
  import { t as mapOpenAIStopReason } from "./openai-stop-reason-Drnn_6Qj.mjs";
15
- import { t as createReasoningTagTextPartitioner } from "./reasoning-tag-text-partitioner-C-4uedDb.mjs";
16
26
  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
27
  //#region packages/ai/src/transports/deepseek-dsml-grammar.ts
71
28
  const DEEPSEEK_DSML_MARKERS = [
72
29
  "|",
@@ -162,141 +119,132 @@ function longestDsmlOpenPrefixSuffixLength(text) {
162
119
  return 0;
163
120
  }
164
121
  //#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;
122
+ //#region packages/ai/src/transports/model-max-tokens-params.ts
123
+ /**
124
+ * Max-token parameter normalization across provider/native naming variants.
125
+ * Callers canonicalize aliases before dispatch so payloads cannot carry
126
+ * conflicting limits.
127
+ */
128
+ const MAX_TOKENS_PARAM_KEYS = [
129
+ "maxTokens",
130
+ "max_completion_tokens",
131
+ "max_tokens"
132
+ ];
133
+ /** Resolve the first supported max-token parameter present in a params object. */
134
+ function resolveMaxTokensParam(params) {
135
+ if (!params) return;
136
+ for (const key of MAX_TOKENS_PARAM_KEYS) {
137
+ const resolved = asNonNegativeFiniteNumber(params[key]);
138
+ if (resolved !== void 0) return resolved;
185
139
  }
186
140
  }
187
141
  /**
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.
142
+ * Canonicalize merged params to `maxTokens`, preserving source precedence from
143
+ * left to right across the provided source objects.
190
144
  */
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
- };
145
+ function canonicalizeMaxTokensParam(params) {
146
+ let resolved;
147
+ for (const source of params.sources) {
148
+ const sourceValue = resolveMaxTokensParam(source);
149
+ if (sourceValue !== void 0) resolved = sourceValue;
150
+ }
151
+ if (resolved === void 0) return;
152
+ for (const key of MAX_TOKENS_PARAM_KEYS) delete params.merged[key];
153
+ params.merged.maxTokens = resolved;
204
154
  }
205
155
  //#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");
156
+ //#region packages/ai/src/transports/openai-completions-cache-control.ts
157
+ const shapedPayloads = /* @__PURE__ */ new WeakSet();
158
+ function resolveCompletionsCacheControl(compat, retention, openRouterRoute) {
159
+ if (compat.cacheControlFormat !== "anthropic" || retention === "none") return;
227
160
  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")
161
+ type: "ephemeral",
162
+ ...retention === "long" && compat.supportsLongCacheRetention && (openRouterRoute || compat.configuredSupportsLongCacheRetention === true) ? { ttl: "1h" } : {}
242
163
  };
243
164
  }
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
- })
165
+ /** Shared Chat Completions policy; repeated wrapper application preserves existing checkpoints. */
166
+ function applyCompletionsAnthropicCacheControl(payload, cacheControl = { type: "ephemeral" }, cacheOptOutIndexes = /* @__PURE__ */ new Set(), markTools = true, markMessages = true) {
167
+ if (shapedPayloads.has(payload)) return;
168
+ shapedPayloads.add(payload);
169
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
170
+ const tools = Array.isArray(payload.tools) ? payload.tools.filter(isRecord) : [];
171
+ const blocks = messages.filter(isRecord).flatMap((message) => Array.isArray(message.content) ? message.content.filter(isRecord) : []);
172
+ for (const block of [...tools, ...blocks]) delete block.cache_control;
173
+ if (!cacheControl) return;
174
+ const markText = (message, splitBoundary) => {
175
+ if (typeof message.content === "string" && message.content) message.content = [{
176
+ type: "text",
177
+ text: message.content
178
+ }];
179
+ const content = message.content;
180
+ if (!Array.isArray(content)) return false;
181
+ for (let i = content.length - 1; i >= 0; i--) {
182
+ const block = content[i];
183
+ if (!isRecord(block) || block.type !== "text" || typeof block.text !== "string" || !block.text) continue;
184
+ const split = splitBoundary ? splitSystemPromptCacheBoundary(block.text) : void 0;
185
+ if (split) {
186
+ content.splice(i, 1, ...split.stablePrefix ? [{
187
+ type: "text",
188
+ text: split.stablePrefix,
189
+ cache_control: cacheControl
190
+ }] : [], ...split.dynamicSuffix ? [{
191
+ type: "text",
192
+ text: split.dynamicSuffix
193
+ }] : []);
194
+ if (!split.stablePrefix) return false;
195
+ } else block.cache_control = cacheControl;
196
+ return true;
197
+ }
198
+ return false;
266
199
  };
200
+ const lastTool = tools.at(-1);
201
+ if (markTools && lastTool) lastTool.cache_control = cacheControl;
202
+ if (!markMessages) return;
203
+ const system = messages.find((message) => isRecord(message) && (message.role === "system" || message.role === "developer"));
204
+ if (isRecord(system)) markText(system, true);
205
+ for (let i = messages.length - 1; i >= 0; i--) {
206
+ const message = messages[i];
207
+ if (!cacheOptOutIndexes.has(i) && isRecord(message) && (message.role === "user" || message.role === "tool") && markText(message, false)) return;
208
+ }
267
209
  }
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";
210
+ //#endregion
211
+ //#region packages/ai/src/transports/openai-completions-string-content.ts
212
+ /**
213
+ * OpenAI Chat Completions compatibility helpers. Some providers only accept
214
+ * role/content messages with plain string content instead of text block arrays.
215
+ */
216
+ function flattenStringOnlyCompletionContent(content) {
217
+ if (!Array.isArray(content)) return content;
218
+ const textParts = [];
219
+ for (const item of content) {
220
+ if (!item || typeof item !== "object" || item.type !== "text" || typeof item.text !== "string") return content;
221
+ textParts.push(item.text);
222
+ }
223
+ return textParts.join("\n");
224
+ }
225
+ /** Flatten string-only text block content arrays into newline-joined strings. */
226
+ function flattenCompletionMessagesToStringContent(messages) {
227
+ return messages.map((message) => {
228
+ if (!message || typeof message !== "object") return message;
229
+ const content = message.content;
230
+ const flattenedContent = flattenStringOnlyCompletionContent(content);
231
+ if (flattenedContent === content) return message;
232
+ return {
233
+ ...message,
234
+ content: flattenedContent
235
+ };
236
+ });
272
237
  }
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
- };
238
+ /** Strip completion messages to role/content fields for strict providers. */
239
+ function stripCompletionMessagesToRoleContent(messages) {
240
+ return messages.map((message) => {
241
+ if (!message || typeof message !== "object" || Array.isArray(message)) return message;
242
+ const record = message;
243
+ const stripped = {};
244
+ if (Object.hasOwn(record, "role")) stripped.role = record.role;
245
+ if (Object.hasOwn(record, "content")) stripped.content = record.content;
246
+ return stripped;
247
+ });
300
248
  }
301
249
  //#endregion
302
250
  //#region packages/ai/src/providers/openai-completions-tool-calls.ts
@@ -481,17 +429,18 @@ function finalizeOpenAICompletionsToolCalls(output, options = {}) {
481
429
  }
482
430
  }
483
431
  //#endregion
432
+ //#region packages/ai/src/transports/openai-completions-host.ts
433
+ /**
434
+ * Chat Completions accepts Azure AI Foundry hosts in addition to traditional
435
+ * Azure OpenAI hosts. Do not replace this with isTraditionalAzureOpenAIHost,
436
+ * which intentionally excludes the .services.ai.azure.com Foundry suffix.
437
+ */
438
+ function isAzureOpenAICompatibleHost(hostname) {
439
+ return hostname.endsWith(".openai.azure.com") || hostname.endsWith(".services.ai.azure.com") || hostname.endsWith(".cognitiveservices.azure.com");
440
+ }
441
+ //#endregion
484
442
  //#region packages/ai/src/openai-completions-messages.ts
485
443
  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
444
  function sanitizeToolResultText(text, fallback) {
496
445
  const sanitized = sanitizeSurrogates(text);
497
446
  return sanitized.trim().length > 0 ? sanitized : fallback;
@@ -562,25 +511,30 @@ function convertMessages(model, context, compat, options = {}) {
562
511
  role: "assistant",
563
512
  content: compat.requiresAssistantAfterToolResult ? "" : null
564
513
  };
565
- const assistantTextParts = msg.content.filter(isTextContentBlock).filter((block) => block.text.trim().length > 0).map((block) => ({
514
+ const assistantTexts = [];
515
+ const nonEmptyThinkingBlocks = [];
516
+ const toolCalls = [];
517
+ msg.content.forEach((block) => {
518
+ if (block.type === "text" && block.text.trim().length > 0) assistantTexts.push(sanitizeSurrogates(block.text));
519
+ else if (block.type === "thinking" && block.thinking.trim().length > 0) nonEmptyThinkingBlocks.push(block);
520
+ else if (block.type === "toolCall") toolCalls.push(block);
521
+ });
522
+ if (nonEmptyThinkingBlocks.length > 0 && compat.requiresThinkingAsText) assistantMsg.content = [{
566
523
  type: "text",
567
- text: sanitizeSurrogates(block.text)
568
- }));
569
- const assistantText = assistantTextParts.map((part) => part.text).join("\n");
570
- const nonEmptyThinkingBlocks = msg.content.filter(isThinkingContentBlock).filter((block) => block.thinking.trim().length > 0);
571
- if (nonEmptyThinkingBlocks.length > 0) {
572
- if (compat.requiresThinkingAsText) assistantMsg.content = [{
573
- type: "text",
574
- text: nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n\n")
575
- }, ...assistantTextParts];
576
- else {
577
- if (assistantText.length > 0) assistantMsg.content = assistantText;
524
+ text: nonEmptyThinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n\n")
525
+ }, ...assistantTexts.map((text) => ({
526
+ type: "text",
527
+ text
528
+ }))];
529
+ else {
530
+ const assistantText = assistantTexts.join("\n");
531
+ if (assistantText.length > 0) assistantMsg.content = assistantText;
532
+ if (nonEmptyThinkingBlocks.length > 0) {
578
533
  let signature = nonEmptyThinkingBlocks.at(0)?.thinkingSignature;
579
534
  if (model.provider === "opencode-go" && signature === "reasoning") signature = "reasoning_content";
580
535
  if (signature && signature.length > 0) assistantMsg[signature] = nonEmptyThinkingBlocks.map((block) => block.thinking).join("\n");
581
536
  }
582
- } else if (assistantText.length > 0) assistantMsg.content = assistantText;
583
- const toolCalls = msg.content.filter(isToolCallBlock);
537
+ }
584
538
  if (toolCalls.length > 0) {
585
539
  assistantMsg.tool_calls = toolCalls.map((toolCall) => ({
586
540
  id: toolCall.id,
@@ -604,17 +558,17 @@ function convertMessages(model, context, compat, options = {}) {
604
558
  }
605
559
  if (compat.requiresReasoningContentOnAssistantMessages && model.reasoning && assistantMsg.reasoning_content === void 0) assistantMsg.reasoning_content = "";
606
560
  const content = assistantMsg.content;
607
- if (!(content !== null && content !== void 0 && (typeof content === "string" ? content.length > 0 : content.length > 0)) && !assistantMsg.tool_calls) continue;
561
+ if (!(content !== null && content !== void 0 && content.length > 0) && !assistantMsg.tool_calls) continue;
608
562
  params.push(assistantMsg);
609
563
  } else if (msg.role === "toolResult") {
610
- const imageBlocks = [];
564
+ const imageContentParts = [];
611
565
  let j = i;
612
566
  while (j < transformedMessages.length) {
613
567
  const toolMsg = transformedMessages.at(j);
614
568
  if (toolMsg?.role !== "toolResult") break;
615
569
  const textResult = extractToolResultText(toolMsg.content);
616
570
  const mediaPlaceholder = describeToolResultMediaPlaceholder(toolMsg.content);
617
- const hasImages = toolMsg.content.some(isImageWithMediaPayload);
571
+ const images = toolMsg.content.filter(isImageWithMediaPayload);
618
572
  const toolResultMsg = {
619
573
  role: "tool",
620
574
  content: sanitizeToolResultText(textResult, mediaPlaceholder ?? EMPTY_TOOL_RESULT_TEXT),
@@ -622,8 +576,13 @@ function convertMessages(model, context, compat, options = {}) {
622
576
  };
623
577
  if (compat.requiresToolResultName && toolMsg.toolName) toolResultMsg.name = toolMsg.toolName;
624
578
  params.push(toolResultMsg);
625
- if (hasImages && model.input.includes("image")) {
626
- for (const block of toolMsg.content) if (isImageWithMediaPayload(block)) imageBlocks.push({
579
+ if (images.length > 0 && model.input.includes("image")) {
580
+ const boundedToolName = sanitizeSurrogates(truncateUtf16Safe(toolMsg.toolName ?? "", 64));
581
+ imageContentParts.push({
582
+ type: "text",
583
+ text: `Image(s) from tool result #${j - i + 1}${boundedToolName ? ` (${boundedToolName})` : ""}:`
584
+ });
585
+ for (const block of images) imageContentParts.push({
627
586
  type: "image_url",
628
587
  image_url: { url: `data:${block.mimeType};base64,${block.data}` }
629
588
  });
@@ -631,17 +590,14 @@ function convertMessages(model, context, compat, options = {}) {
631
590
  j += 1;
632
591
  }
633
592
  i = j - 1;
634
- if (imageBlocks.length > 0) {
593
+ if (imageContentParts.length > 0) {
635
594
  if (compat.requiresAssistantAfterToolResult) params.push({
636
595
  role: "assistant",
637
596
  content: "I have processed the tool results."
638
597
  });
639
598
  params.push({
640
599
  role: "user",
641
- content: [{
642
- type: "text",
643
- text: "Attached image(s) from tool result:"
644
- }, ...imageBlocks]
600
+ content: imageContentParts
645
601
  });
646
602
  lastRole = "user";
647
603
  } else lastRole = "toolResult";
@@ -652,258 +608,474 @@ function convertMessages(model, context, compat, options = {}) {
652
608
  return params;
653
609
  }
654
610
  //#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)
611
+ //#region packages/ai/src/transports/openai-completions-direct-policy.ts
612
+ function applyDirectCompletionsReasoningAndRouting(params, model, options, compat) {
613
+ const reasoningEffortMap = resolveOpenAIReasoningEffortMap(model);
614
+ const thinkingLevelMap = model.thinkingLevelMap;
615
+ const offReasoningEffort = reasoningEffortMap.off ?? model.thinkingLevelMap?.off;
616
+ const reasoningEffort = options?.reasoningEffort === void 0 ? offReasoningEffort ?? void 0 : reasoningEffortMap[options.reasoningEffort] ?? thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
617
+ const reasoningEnabled = reasoningEffort !== void 0 && reasoningEffort !== "none";
618
+ if (compat.thinkingFormat === "zai" && model.reasoning) params.thinking = reasoningEnabled ? {
619
+ type: "enabled",
620
+ clear_thinking: false
621
+ } : { type: "disabled" };
622
+ else if (compat.thinkingFormat === "qwen" && model.reasoning) params.enable_thinking = reasoningEnabled;
623
+ else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) params.chat_template_kwargs = {
624
+ enable_thinking: reasoningEnabled,
625
+ preserve_thinking: true
679
626
  };
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 {
690
- return;
627
+ else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
628
+ params.thinking = { type: reasoningEnabled ? "enabled" : "disabled" };
629
+ if (reasoningEnabled && compat.supportsReasoningEffort) params.reasoning_effort = reasoningEffort;
630
+ } else if (compat.thinkingFormat === "openrouter" && model.reasoning) {
631
+ if (reasoningEnabled) params.reasoning = { effort: reasoningEffort };
632
+ else if (offReasoningEffort !== null) params.reasoning = { effort: offReasoningEffort ?? "none" };
633
+ } else if (compat.thinkingFormat === "together" && model.reasoning) {
634
+ params.reasoning = { enabled: reasoningEnabled };
635
+ if (reasoningEnabled && compat.supportsReasoningEffort) params.reasoning_effort = reasoningEffort;
636
+ } else if (reasoningEnabled && model.reasoning && compat.supportsReasoningEffort) params.reasoning_effort = reasoningEffort;
637
+ else if (model.reasoning && compat.supportsReasoningEffort) {
638
+ if (typeof offReasoningEffort === "string") params.reasoning_effort = offReasoningEffort;
691
639
  }
692
- }
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;
640
+ if (compat.openRouterRouting) params.provider = compat.openRouterRouting;
641
+ if (model.baseUrl.includes("ai-gateway.vercel.sh") && model.compat?.vercelGatewayRouting) {
642
+ const routing = model.compat.vercelGatewayRouting;
643
+ if (routing.only || routing.order) {
644
+ const gatewayOptions = {};
645
+ if (routing.only) gatewayOptions.only = routing.only;
646
+ if (routing.order) gatewayOptions.order = routing.order;
647
+ params.providerOptions = { gateway: gatewayOptions };
710
648
  }
711
- return `hosted:${type}`;
712
649
  }
713
- const name = readCodeModePayloadToolName(tool);
714
- return typeof name === "string" && isCodeModeModelVisibleToolName(name, visibleToolNames) ? `client:${name}` : void 0;
715
650
  }
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}`);
651
+ //#endregion
652
+ //#region packages/ai/src/transports/openai-completions-replay.ts
653
+ function isGoogleOpenAICompatModel(model) {
654
+ const endpointClass = detectOpenAICompletionsCompat(model).capabilities.endpointClass;
655
+ return model.provider === "google" || endpointClass === "google-generative-ai" || endpointClass === "google-vertex";
656
+ }
657
+ function requiresGoogleCompatToolCallThoughtSignature(model) {
658
+ return isGoogleGemini3ProModel(model.id) || isGoogleGemini3FlashModel(model.id);
659
+ }
660
+ const GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE = /[\u2026]|\.\.\./;
661
+ const GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE = /^[A-Za-z0-9+/=]+$/;
662
+ function hasGoogleCompatThoughtSignatureTruncationFootprint(value) {
663
+ return GOOGLE_COMPAT_THOUGHT_SIGNATURE_ELLIPSIS_RE.test(value) || GOOGLE_COMPAT_THOUGHT_SIGNATURE_BASE64_RE.test(value) && value.length % 4 !== 0;
664
+ }
665
+ function injectToolCallThoughtSignatures(outgoingMessages, context, model) {
666
+ if (!isGoogleOpenAICompatModel(model)) return;
667
+ const sigById = /* @__PURE__ */ new Map();
668
+ const fallbackSig = requiresGoogleCompatToolCallThoughtSignature(model) ? GEMINI_THOUGHT_SIGNATURE_VALIDATOR_SKIP : void 0;
669
+ for (const msg of context.messages ?? []) {
670
+ if (msg.role !== "assistant") continue;
671
+ const source = msg;
672
+ if (!Array.isArray(source.content)) continue;
673
+ for (const block of source.content) {
674
+ if (block.type !== "toolCall") continue;
675
+ const id = block.id;
676
+ const sig = block.thoughtSignature;
677
+ if (typeof id === "string" && typeof sig === "string" && sig.length > 0) {
678
+ const isSameRoute = source.api === model.api && source.provider === model.provider && source.model === model.id;
679
+ if (!isSameRoute && !fallbackSig) continue;
680
+ sigById.set(id, isSameRoute ? sig : fallbackSig ?? sig);
731
681
  }
732
682
  }
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;
683
+ }
684
+ if (sigById.size === 0 && !fallbackSig) return;
685
+ for (const message of outgoingMessages) {
686
+ const toolCalls = message.tool_calls;
687
+ if (!Array.isArray(toolCalls)) continue;
688
+ for (const toolCall of toolCalls) {
689
+ const id = toolCall.id;
690
+ if (typeof id !== "string") continue;
691
+ let sig = sigById.get(id) ?? fallbackSig;
692
+ if (typeof sig === "string" && sig.length > 0) {
693
+ if (hasGoogleCompatThoughtSignatureTruncationFootprint(sig.trim())) sig = fallbackSig;
694
+ }
695
+ if (typeof sig !== "string" || sig.length === 0) continue;
696
+ const extra = toolCall.extra_content && typeof toolCall.extra_content === "object" ? toolCall.extra_content : {};
697
+ toolCall.extra_content = extra;
698
+ const google = extra.google && typeof extra.google === "object" ? extra.google : {};
699
+ extra.google = google;
700
+ google.thought_signature = sig;
757
701
  }
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
- });
702
+ }
778
703
  }
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"}`);
704
+ const COMPLETIONS_REASONING_REPLAY_FIELDS = [
705
+ "reasoning_details",
706
+ "reasoning_content",
707
+ "reasoning",
708
+ "reasoning_text"
709
+ ];
710
+ function stripCompletionsReasoningReplayFields(record) {
711
+ for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) if (field in record) delete record[field];
712
+ }
713
+ function sanitizeOpenRouterReasoningReplayFields(record) {
714
+ const reasoningDetails = record.reasoning_details;
715
+ if (typeof reasoningDetails === "string") {
716
+ if (reasoningDetails.length > 0 && typeof record.reasoning !== "string") record.reasoning = reasoningDetails;
717
+ delete record.reasoning_details;
718
+ } else if (reasoningDetails !== void 0 && !Array.isArray(reasoningDetails)) delete record.reasoning_details;
719
+ if ("reasoning" in record && (typeof record.reasoning !== "string" || record.reasoning === "")) delete record.reasoning;
720
+ if ("reasoning_content" in record && (typeof record.reasoning_content !== "string" || record.reasoning_content === "")) delete record.reasoning_content;
721
+ const reasoningText = record.reasoning_text;
722
+ if (typeof reasoningText === "string" && reasoningText.length > 0 && typeof record.reasoning !== "string" && typeof record.reasoning_content !== "string") record.reasoning = reasoningText;
723
+ if ("reasoning_text" in record) delete record.reasoning_text;
724
+ }
725
+ function sanitizeReasoningContentReplayFields(record) {
726
+ if ("reasoning_content" in record && typeof record.reasoning_content !== "string") delete record.reasoning_content;
727
+ delete record.reasoning_details;
728
+ delete record.reasoning;
729
+ delete record.reasoning_text;
730
+ }
731
+ const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([
732
+ "deepseek-v4-flash",
733
+ "deepseek-v4-pro",
734
+ "kimi-for-coding",
735
+ "kimi-k2.5",
736
+ "kimi-k2.6",
737
+ "kimi-k2.7-code",
738
+ "kimi-k2.7-code-highspeed",
739
+ "kimi-k3",
740
+ "kimi-k2-thinking",
741
+ "kimi-k2-thinking-turbo",
742
+ "mimo-v2-pro",
743
+ "mimo-v2-omni",
744
+ "mimo-v2.5",
745
+ "mimo-v2.5-pro",
746
+ "mimo-v2.6-pro"
747
+ ]);
748
+ const REASONING_CONTENT_REPLAY_TIER_SUFFIXES = [
749
+ "-free",
750
+ "-paid",
751
+ "-trial"
752
+ ];
753
+ function stripReasoningContentReplayTierSuffix(modelId) {
754
+ for (const suffix of REASONING_CONTENT_REPLAY_TIER_SUFFIXES) if (modelId.length > suffix.length && modelId.endsWith(suffix)) return modelId.slice(0, -suffix.length);
755
+ return modelId;
756
+ }
757
+ function getReasoningContentReplayModelIdCandidates(modelId) {
758
+ if (typeof modelId !== "string") return [];
759
+ const normalized = modelId.trim().toLowerCase();
760
+ if (!normalized) return [];
761
+ const parts = normalized.split("/").filter(Boolean);
762
+ const finalPart = parts[parts.length - 1] ?? normalized;
763
+ const candidates = [finalPart];
764
+ const colonParts = finalPart.split(":").filter(Boolean);
765
+ if (colonParts.length > 1) candidates.push(colonParts[0] ?? "", colonParts[colonParts.length - 1] ?? "");
766
+ const baseCount = candidates.length;
767
+ for (let index = 0; index < baseCount; index += 1) {
768
+ const candidate = candidates[index];
769
+ if (typeof candidate !== "string") continue;
770
+ const stripped = stripReasoningContentReplayTierSuffix(candidate);
771
+ if (stripped !== candidate) candidates.push(stripped);
772
+ }
773
+ return uniqueStrings(candidates.filter(Boolean));
786
774
  }
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
- }));
775
+ function shouldPreserveReasoningContentReplay(model, compat) {
776
+ if (compat.requiresReasoningContentOnAssistantMessages || compat.thinkingFormat === "deepseek" || compat.thinkingFormat === "zai" || shouldTrustReasoningContentReplayMetadata(model)) return true;
777
+ return getReasoningContentReplayModelIdCandidates(model.id).some((modelId) => REASONING_CONTENT_REPLAY_MODEL_IDS.has(modelId));
798
778
  }
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;
779
+ function shouldPreserveOpenRouterReasoningReplay(model) {
780
+ if (model.provider !== "openrouter") return true;
781
+ const normalizedModelId = model.id.trim().toLowerCase();
782
+ return !(normalizedModelId.startsWith("anthropic/") || normalizedModelId.startsWith("x-ai/"));
805
783
  }
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
- });
784
+ function shouldTrustReasoningContentReplayMetadata(model) {
785
+ if (!model.reasoning) return false;
786
+ if (model.provider.trim().toLowerCase() === "openai") return false;
787
+ return shouldPreserveOpenRouterReasoningReplay(model);
788
+ }
789
+ function sanitizeCompletionsReasoningReplayFields(messages, options) {
790
+ if (!Array.isArray(messages)) return;
791
+ for (const msg of messages) {
792
+ if (!msg || typeof msg !== "object") continue;
793
+ const record = msg;
794
+ if (record.role !== "assistant") continue;
795
+ if (options.preserveOpenRouterReasoning) sanitizeOpenRouterReasoningReplayFields(record);
796
+ else if (options.preserveReasoningContent) sanitizeReasoningContentReplayFields(record);
797
+ else stripCompletionsReasoningReplayFields(record);
827
798
  }
828
- return strict;
829
799
  }
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");
800
+ function applyCompletionsReplay(outgoingMessages, context, model, compat) {
801
+ injectToolCallThoughtSignatures(outgoingMessages, context, model);
802
+ sanitizeCompletionsReasoningReplayFields(outgoingMessages, {
803
+ preserveOpenRouterReasoning: compat.thinkingFormat === "openrouter" && shouldPreserveOpenRouterReasoningReplay(model),
804
+ preserveReasoningContent: shouldPreserveReasoningContentReplay(model, compat)
805
+ });
832
806
  }
833
- function isNativeOpenAICodexResponsesBaseUrl(baseUrl) {
834
- const trimmed = typeof baseUrl === "string" ? baseUrl.trim() : "";
835
- if (!trimmed) return false;
807
+ //#endregion
808
+ //#region packages/ai/src/transports/openai-completions-params.ts
809
+ function isKnownOpenAICompletionsEndpoint(model) {
810
+ if (!model.baseUrl.trim()) return true;
811
+ const endpointClass = resolveProviderEndpoint(model).endpointClass;
812
+ if (endpointClass === "openai-public" || endpointClass === "azure-openai") return true;
836
813
  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);
814
+ return isAzureOpenAICompatibleHost(new URL(model.baseUrl).hostname.toLowerCase());
847
815
  } catch {
848
816
  return false;
849
817
  }
850
818
  }
851
- function usesNativeOpenAICodexResponsesBackend(model) {
852
- return isOpenAICodexResponsesModel(model) && isNativeOpenAICodexResponsesBaseUrl(model.baseUrl);
819
+ function resolveOpenAICompletionsReasoningEffort(options) {
820
+ return options?.reasoningEffort ?? options?.reasoning ?? "high";
853
821
  }
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
822
+ function resolveOpenAICompletionsMaxTokens(model, options) {
823
+ if (options?.maxTokens) return {
824
+ maxTokens: options.maxTokens,
825
+ clampToModelMaxTokens: true
826
+ };
827
+ const paramsMaxTokens = resolveMaxTokensParam(model.params);
828
+ if (paramsMaxTokens) return {
829
+ maxTokens: paramsMaxTokens,
830
+ clampToModelMaxTokens: false
860
831
  };
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
832
  return {
880
- ...timeout === void 0 ? {} : { timeout },
881
- maxRetries: 0
833
+ maxTokens: model.maxTokens,
834
+ clampToModelMaxTokens: false
882
835
  };
883
836
  }
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;
837
+ function resolveOpenAICompletionsModelMaxTokens(model) {
838
+ return typeof model.maxTokens === "number" && Number.isFinite(model.maxTokens) && model.maxTokens > 0 ? Math.floor(model.maxTokens) : void 0;
839
+ }
840
+ const OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN = 1.25;
841
+ const OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE = 8e3;
842
+ function estimateOpenAICompletionsInputTokens(payload) {
843
+ let adjustedChars = 0;
844
+ adjustedChars += estimateOpenAICompletionsMessagesChars(payload.messages);
845
+ if (Array.isArray(payload.tools) && payload.tools.length > 0) try {
846
+ adjustedChars += estimateStringChars(JSON.stringify(payload.tools));
847
+ } catch {
848
+ adjustedChars += 1024;
849
+ }
850
+ if (payload.response_format !== void 0) try {
851
+ adjustedChars += estimateStringChars(JSON.stringify(payload.response_format));
852
+ } catch {
853
+ adjustedChars += 256;
854
+ }
855
+ return Math.ceil(adjustedChars / 4 * OPENAI_COMPLETIONS_INPUT_TOKEN_SAFETY_MARGIN);
856
+ }
857
+ function estimateOpenAICompletionsMessagesChars(messages) {
858
+ if (!Array.isArray(messages)) return 0;
859
+ let adjustedChars = 0;
860
+ for (const message of messages) {
861
+ if (!message || typeof message !== "object") continue;
862
+ const record = message;
863
+ adjustedChars += estimateOpenAICompletionsContentChars(record.content);
864
+ for (const field of COMPLETIONS_REASONING_REPLAY_FIELDS) adjustedChars += estimateOpenAICompletionsContentChars(record[field]);
865
+ if (record.tool_calls !== void 0) try {
866
+ adjustedChars += estimateStringChars(JSON.stringify(record.tool_calls));
867
+ } catch {
868
+ adjustedChars += 256;
869
+ }
870
+ }
871
+ return adjustedChars;
872
+ }
873
+ function estimateOpenAICompletionsContentChars(value) {
874
+ if (typeof value === "string") return estimateStringChars(value);
875
+ if (!Array.isArray(value)) return 0;
876
+ let adjustedChars = 0;
877
+ for (const block of value) {
878
+ if (!block || typeof block !== "object") continue;
879
+ const record = block;
880
+ if (record.type === "image_url" || record.type === "input_image") {
881
+ adjustedChars += OPENAI_COMPLETIONS_IMAGE_CHAR_ESTIMATE;
882
+ continue;
883
+ }
884
+ const text = record.text;
885
+ if (typeof text === "string") {
886
+ adjustedChars += estimateStringChars(text);
887
+ continue;
888
+ }
889
+ try {
890
+ adjustedChars += estimateStringChars(JSON.stringify(block));
891
+ } catch {
892
+ adjustedChars += 256;
893
+ }
894
+ }
895
+ return adjustedChars;
896
+ }
897
+ function resolveOpenAICompletionsEffectiveContextTokens(model) {
898
+ const contextTokens = model.contextTokens;
899
+ if (typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens > 0) return contextTokens;
900
+ return typeof model.contextWindow === "number" && Number.isFinite(model.contextWindow) && model.contextWindow > 0 ? model.contextWindow : void 0;
901
+ }
902
+ function isQwenOpenAICompletionsThinkingFormat(format) {
903
+ return format === "qwen" || format === "qwen-chat-template";
904
+ }
905
+ function setQwenChatTemplateThinking(params, enabled) {
906
+ const existing = params.chat_template_kwargs;
907
+ params.chat_template_kwargs = existing && typeof existing === "object" && !Array.isArray(existing) ? {
908
+ ...existing,
909
+ enable_thinking: enabled
910
+ } : { enable_thinking: enabled };
911
+ }
912
+ function applyQwenOpenAICompletionsThinkingParams(params) {
913
+ if (!params.modelReasoning || !isQwenOpenAICompletionsThinkingFormat(params.compatThinkingFormat)) return false;
914
+ const enabled = isOpenAICompletionsThinkingEnabled(params.requestedEffort);
915
+ if (params.compatThinkingFormat === "qwen-chat-template") setQwenChatTemplateThinking(params.payload, enabled);
916
+ else params.payload.enable_thinking = enabled;
917
+ return true;
918
+ }
919
+ function applyTogetherOpenAICompletionsThinkingParams(params) {
920
+ if (!params.modelReasoning || params.compatThinkingFormat !== "together") return;
921
+ params.payload.reasoning = { enabled: isOpenAICompletionsThinkingEnabled(params.requestedEffort) };
922
+ }
923
+ function convertTools(tools, compat, model, mode) {
924
+ const projection = projectOpenAITools(tools);
925
+ const strict = mode === "direct" ? compat.supportsStrictMode ? false : void 0 : resolveOpenAIStrictToolFlagWithDiagnostics(projection, resolveOpenAIStrictToolSetting(model, {
926
+ transport: "stream",
927
+ supportsStrictMode: compat?.supportsStrictMode
928
+ }), {
929
+ transport: "completions",
930
+ model
931
+ });
888
932
  return {
889
- ...headers ? { headers } : {},
890
- ...signal ? { signal } : {},
891
- ...timeout !== void 0 ? { timeout } : {},
892
- maxRetries: 0
933
+ projection,
934
+ tools: sortPromptCacheToolsByName(projection.tools).map((tool) => {
935
+ const functionTool = {
936
+ name: tool.name,
937
+ description: tool.description,
938
+ parameters: mode === "direct" ? tool.parameters : normalizeOpenAIStrictToolParameters(tool.parameters, strict === true, model.compat)
939
+ };
940
+ if (strict !== void 0) functionTool.strict = strict;
941
+ return {
942
+ type: "function",
943
+ function: functionTool
944
+ };
945
+ })
893
946
  };
894
947
  }
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
948
+ function buildOpenAICompletionsParams(model, context, options) {
949
+ return buildOpenAICompletionsRequest(model, context, options, { mode: "managed" });
950
+ }
951
+ function buildOpenAICompletionsRequest(model, context, options, policy) {
952
+ const resolvedPolicy = policy.mode === "direct" ? policy : {
953
+ ...policy,
954
+ compat: getCompat(model)
955
+ };
956
+ const compat = resolvedPolicy.compat;
957
+ const managedCompat = resolvedPolicy.mode === "managed" ? resolvedPolicy.compat : void 0;
958
+ const endpointDetection = detectOpenAICompletionsCompat(model);
959
+ const compatDetection = policy.mode === "managed" ? endpointDetection : void 0;
960
+ const { endpointClass } = endpointDetection.capabilities;
961
+ const cacheRetention = policy.mode === "direct" ? policy.cacheRetention : resolveCacheRetention(options?.cacheRetention);
962
+ const cacheControl = resolveCompletionsCacheControl(compat, cacheRetention, endpointClass === "openrouter" || endpointClass === "default" && model.provider === "openrouter");
963
+ const markTools = endpointClass !== "modelstudio-native" && !(endpointClass === "default" && [
964
+ "modelstudio",
965
+ "dashscope",
966
+ "qwen"
967
+ ].includes(model.provider));
968
+ const cacheOptOutIndexes = /* @__PURE__ */ new Set();
969
+ let messages = convertMessages(model, context, compat, {
970
+ cacheOptOutIndexes,
971
+ preserveSystemPromptCacheBoundary: cacheControl !== void 0 && !managedCompat?.requiresStringContent
972
+ });
973
+ if (managedCompat) {
974
+ applyCompletionsReplay(messages, context, model, managedCompat);
975
+ if (managedCompat.strictMessageKeys) messages = stripCompletionMessagesToRoleContent(messages);
976
+ if (managedCompat.requiresStringContent) messages = flattenCompletionMessagesToStringContent(messages);
977
+ }
978
+ const promptCacheKey = resolvePromptCacheKey(options, cacheRetention);
979
+ const params = {
980
+ model: model.id,
981
+ messages,
982
+ stream: true,
983
+ ...resolveOpenAIPromptCacheParams(model, cacheRetention, compat)
906
984
  };
985
+ if (compat.supportsUsageInStreaming) params.stream_options = { include_usage: true };
986
+ if (compat.supportsStore) params.store = false;
987
+ if (policy.mode === "direct" || compat.supportsPromptCacheKey && promptCacheKey) params.prompt_cache_key = compat.supportsPromptCacheKey ? promptCacheKey : void 0;
988
+ if (options?.temperature !== void 0) params.temperature = options.temperature;
989
+ if (policy.mode === "managed" && options?.topP !== void 0) params.top_p = options.topP;
990
+ const requestedResponseFormat = options?.responseFormat;
991
+ const responseFormat = policy.mode === "direct" && requestedResponseFormat === void 0 ? void 0 : resolveOpenAICompletionsResponseFormat(shouldOmitOllamaCompatResponseFormat({
992
+ provider: model.provider,
993
+ baseUrl: model.baseUrl,
994
+ hasTools: () => Boolean(context.tools?.length)
995
+ }) ? void 0 : requestedResponseFormat, compat.supportsJsonSchemaResponseFormat);
996
+ if (responseFormat !== void 0) params.response_format = responseFormat;
997
+ if (policy.mode === "managed" && options?.frequencyPenalty !== void 0) params.frequency_penalty = options.frequencyPenalty;
998
+ if (policy.mode === "managed" && options?.presencePenalty !== void 0) params.presence_penalty = options.presencePenalty;
999
+ if (policy.mode === "managed" && options?.seed !== void 0) params.seed = options.seed;
1000
+ if (options?.stop !== void 0 && options.stop.length > 0) params.stop = options.stop;
1001
+ let directToolProjection;
1002
+ if (policy.mode === "direct" || supportsModelTools(model)) {
1003
+ if (context.tools) {
1004
+ const converted = convertTools(context.tools, compat, model, policy.mode);
1005
+ if (policy.mode === "direct") directToolProjection = converted.projection;
1006
+ if (converted.tools.length > 0 || policy.mode === "managed" && converted.projection.inputToolCount === 0 && converted.projection.diagnostics.length === 0) params.tools = converted.tools;
1007
+ else if (hasToolCallHistory(context.messages)) params.tools = [];
1008
+ if (policy.mode === "direct" && compat.zaiToolStream && converted.tools.length > 0) params.tool_stream = true;
1009
+ if (policy.mode === "managed" && options?.toolChoice) {
1010
+ const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, converted.projection);
1011
+ if (toolChoice !== void 0) params.tool_choice = toolChoice;
1012
+ } else if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length > 0) params.tool_choice = "auto";
1013
+ } else if (hasToolCallHistory(context.messages)) params.tools = [];
1014
+ if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && Array.isArray(params.tools) && params.tools.length === 0) {
1015
+ delete params.tools;
1016
+ delete params.tool_choice;
1017
+ }
1018
+ }
1019
+ if (policy.mode === "direct" && compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools);
1020
+ if (policy.mode === "direct" && options?.toolChoice) {
1021
+ const toolChoice = reconcileOpenAICompletionsToolChoice(options.toolChoice, directToolProjection ?? projectOpenAITools([]));
1022
+ if (toolChoice !== void 0) params.tool_choice = toolChoice;
1023
+ }
1024
+ {
1025
+ const maxTokenBudget = policy.mode === "direct" ? {
1026
+ maxTokens: options?.maxTokens,
1027
+ clampToModelMaxTokens: true
1028
+ } : resolveOpenAICompletionsMaxTokens(model, options);
1029
+ const effectiveMaxTokens = maxTokenBudget.maxTokens;
1030
+ const effectiveContextTokens = resolveOpenAICompletionsEffectiveContextTokens(model);
1031
+ let clampedMaxTokens = effectiveMaxTokens;
1032
+ const modelMaxTokens = resolveOpenAICompletionsModelMaxTokens(model);
1033
+ if (maxTokenBudget.clampToModelMaxTokens && clampedMaxTokens !== void 0 && modelMaxTokens !== void 0 && clampedMaxTokens > modelMaxTokens) {
1034
+ clampedMaxTokens = modelMaxTokens;
1035
+ 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}`);
1036
+ }
1037
+ if (compatDetection?.capabilities.usesExplicitProxyLikeEndpoint && clampedMaxTokens !== void 0 && effectiveContextTokens !== void 0) {
1038
+ const estimatedInputTokens = estimateOpenAICompletionsInputTokens(params);
1039
+ const remainingBudget = Math.max(1, effectiveContextTokens - estimatedInputTokens - 1);
1040
+ if (clampedMaxTokens > remainingBudget) {
1041
+ clampedMaxTokens = remainingBudget;
1042
+ emitModelTransportDebug(log, `[completions] clamp_max_tokens provider=${model.provider} api=${model.api} model=${model.id} requested=${effectiveMaxTokens} output=${clampedMaxTokens} effectiveContext=${effectiveContextTokens} estimatedInput=${estimatedInputTokens}`);
1043
+ }
1044
+ }
1045
+ if (policy.mode === "direct" ? options?.maxTokens : clampedMaxTokens) {
1046
+ if (compat.maxTokensField === "max_tokens") params.max_tokens = clampedMaxTokens;
1047
+ else params.max_completion_tokens = clampedMaxTokens;
1048
+ }
1049
+ }
1050
+ if (policy.mode === "direct") {
1051
+ applyDirectCompletionsReasoningAndRouting(params, model, options, compat);
1052
+ return params;
1053
+ }
1054
+ const completionsReasoningEffort = resolveOpenAICompletionsReasoningEffort(options);
1055
+ const resolvedCompletionsReasoningEffort = completionsReasoningEffort ? resolveOpenAIReasoningEffortForModel({
1056
+ model,
1057
+ effort: completionsReasoningEffort,
1058
+ fallbackMap: managedCompat?.reasoningEffortMap
1059
+ }) : void 0;
1060
+ const omitChatCompletionsToolReasoningEffort = Array.isArray(params.tools) && params.tools.length > 0 && (isOpenAIGpt54MiniModel(model) || isOpenAIGpt55Model(model) && isKnownOpenAICompletionsEndpoint(model));
1061
+ const disableChatCompletionsToolReasoning = Array.isArray(params.tools) && params.tools.length > 0 && isOpenAIGpt56Model(model) && isKnownOpenAICompletionsEndpoint(model);
1062
+ const handledQwenThinkingFormat = applyQwenOpenAICompletionsThinkingParams({
1063
+ compatThinkingFormat: compat.thinkingFormat,
1064
+ modelReasoning: model.reasoning,
1065
+ payload: params,
1066
+ requestedEffort: completionsReasoningEffort
1067
+ });
1068
+ applyTogetherOpenAICompletionsThinkingParams({
1069
+ compatThinkingFormat: compat.thinkingFormat,
1070
+ modelReasoning: model.reasoning,
1071
+ payload: params,
1072
+ requestedEffort: completionsReasoningEffort
1073
+ });
1074
+ if (disableChatCompletionsToolReasoning) params.reasoning_effort = "none";
1075
+ else if (compat.thinkingFormat === "openrouter" && model.reasoning && resolvedCompletionsReasoningEffort) params.reasoning = { effort: resolvedCompletionsReasoningEffort };
1076
+ else if (resolvedCompletionsReasoningEffort && model.reasoning && compat.supportsReasoningEffort && !handledQwenThinkingFormat && !omitChatCompletionsToolReasoningEffort) params.reasoning_effort = resolvedCompletionsReasoningEffort;
1077
+ if (compat.cacheControlFormat === "anthropic") applyCompletionsAnthropicCacheControl(params, cacheControl ?? null, cacheOptOutIndexes, markTools, !managedCompat?.requiresStringContent);
1078
+ return params;
907
1079
  }
908
1080
  //#endregion
909
1081
  //#region packages/ai/src/transports/openai-completions-dsml.ts
@@ -1270,7 +1442,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
1270
1442
  partial: output
1271
1443
  });
1272
1444
  }
1273
- currentBlock.thinking += reasoningDelta.text;
1445
+ appendAssistantThinking(currentBlock, reasoningDelta.text);
1274
1446
  pushStreamEvent({
1275
1447
  type: "thinking_delta",
1276
1448
  contentIndex: blockIndex(),
@@ -1588,6 +1760,7 @@ async function processCompletionsStream(responseStream, output, model, stream, o
1588
1760
  emitReasoningUsageActivity(hasReasoningUsageActivity);
1589
1761
  if (cooperativeScheduler) await cooperativeScheduler.afterEvent();
1590
1762
  }
1763
+ throwIfModelStreamAborted(options?.signal);
1591
1764
  if (!finishReason && (directMode || options?.sawStreamDONE?.() === false)) throw new Error("Stream ended without finish_reason");
1592
1765
  flushReasoningTagTextPartitioner();
1593
1766
  flushDeepSeekToolCallRecovererAtEnd();
@@ -1611,12 +1784,9 @@ async function processCompletionsStream(responseStream, output, model, stream, o
1611
1784
  if (output.stopReason === "error" || output.stopReason === "aborted") tagUnresolvedTextAsCommentary(output);
1612
1785
  if (output.stopReason === "toolUse") tagPendingCommentaryText(output.content);
1613
1786
  }
1614
- function resolveOpenAICompletionsReasoningEffort(options) {
1615
- return options?.reasoningEffort ?? options?.reasoning ?? "high";
1616
- }
1617
1787
  function shouldEmitOpenAICompletionsReasoning(model, options) {
1618
1788
  if (!model.reasoning) return false;
1619
- const effort = resolveOpenAICompletionsReasoningEffort(options);
1789
+ const effort = options?.reasoningEffort ?? options?.reasoning ?? "high";
1620
1790
  if (!effort || !isOpenAICompletionsThinkingEnabled(effort)) return false;
1621
1791
  return true;
1622
1792
  }
@@ -1625,4 +1795,4 @@ function hasOpenAICompletionsReasoningUsageActivity(rawUsage) {
1625
1795
  return typeof reasoningTokens === "number" && Number.isFinite(reasoningTokens) && reasoningTokens > 0;
1626
1796
  }
1627
1797
  //#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 };
1798
+ 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 };