@fleetagent/pi-ai 0.1.8 → 0.2.0

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 (148) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +29 -24
  3. package/dist/cli.js.map +1 -1
  4. package/dist/env-api-keys.d.ts.map +1 -1
  5. package/dist/env-api-keys.js +1 -1
  6. package/dist/env-api-keys.js.map +1 -1
  7. package/dist/image-models.generated.d.ts +225 -0
  8. package/dist/image-models.generated.d.ts.map +1 -1
  9. package/dist/image-models.generated.js +227 -2
  10. package/dist/image-models.generated.js.map +1 -1
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/models.d.ts +37 -2
  15. package/dist/models.d.ts.map +1 -1
  16. package/dist/models.generated.d.ts +6120 -2593
  17. package/dist/models.generated.d.ts.map +1 -1
  18. package/dist/models.generated.js +5347 -2110
  19. package/dist/models.generated.js.map +1 -1
  20. package/dist/models.js +259 -9
  21. package/dist/models.js.map +1 -1
  22. package/dist/oauth.d.ts +6 -1
  23. package/dist/oauth.d.ts.map +1 -1
  24. package/dist/oauth.js +4 -1
  25. package/dist/oauth.js.map +1 -1
  26. package/dist/providers/amazon-bedrock.d.ts +5 -4
  27. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  28. package/dist/providers/amazon-bedrock.js +303 -330
  29. package/dist/providers/amazon-bedrock.js.map +1 -1
  30. package/dist/providers/anthropic.d.ts +5 -4
  31. package/dist/providers/anthropic.d.ts.map +1 -1
  32. package/dist/providers/anthropic.js +492 -499
  33. package/dist/providers/anthropic.js.map +1 -1
  34. package/dist/providers/azure-openai-responses.d.ts +4 -2
  35. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  36. package/dist/providers/azure-openai-responses.js +52 -35
  37. package/dist/providers/azure-openai-responses.js.map +1 -1
  38. package/dist/providers/faux.d.ts +19 -22
  39. package/dist/providers/faux.d.ts.map +1 -1
  40. package/dist/providers/faux.js +106 -70
  41. package/dist/providers/faux.js.map +1 -1
  42. package/dist/providers/github-copilot-headers.d.ts +7 -4
  43. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  44. package/dist/providers/github-copilot-headers.js.map +1 -1
  45. package/dist/providers/google-shared.d.ts +24 -5
  46. package/dist/providers/google-shared.d.ts.map +1 -1
  47. package/dist/providers/google-shared.js +139 -140
  48. package/dist/providers/google-shared.js.map +1 -1
  49. package/dist/providers/google-vertex.d.ts +3 -7
  50. package/dist/providers/google-vertex.d.ts.map +1 -1
  51. package/dist/providers/google-vertex.js +216 -219
  52. package/dist/providers/google-vertex.js.map +1 -1
  53. package/dist/providers/google.d.ts +4 -7
  54. package/dist/providers/google.d.ts.map +1 -1
  55. package/dist/providers/google.js +214 -218
  56. package/dist/providers/google.js.map +1 -1
  57. package/dist/providers/images/openrouter.d.ts.map +1 -1
  58. package/dist/providers/images/openrouter.js +37 -27
  59. package/dist/providers/images/openrouter.js.map +1 -1
  60. package/dist/providers/mistral.d.ts +7 -6
  61. package/dist/providers/mistral.d.ts.map +1 -1
  62. package/dist/providers/mistral.js +287 -265
  63. package/dist/providers/mistral.js.map +1 -1
  64. package/dist/providers/openai-codex-responses.d.ts +8 -5
  65. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  66. package/dist/providers/openai-codex-responses.js +493 -391
  67. package/dist/providers/openai-codex-responses.js.map +1 -1
  68. package/dist/providers/openai-completions.d.ts +7 -10
  69. package/dist/providers/openai-completions.d.ts.map +1 -1
  70. package/dist/providers/openai-completions.js +670 -664
  71. package/dist/providers/openai-completions.js.map +1 -1
  72. package/dist/providers/openai-responses-shared.d.ts +5 -4
  73. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  74. package/dist/providers/openai-responses-shared.js +390 -360
  75. package/dist/providers/openai-responses-shared.js.map +1 -1
  76. package/dist/providers/openai-responses.d.ts +5 -5
  77. package/dist/providers/openai-responses.d.ts.map +1 -1
  78. package/dist/providers/openai-responses.js +134 -109
  79. package/dist/providers/openai-responses.js.map +1 -1
  80. package/dist/providers/simple-options.d.ts +4 -2
  81. package/dist/providers/simple-options.d.ts.map +1 -1
  82. package/dist/providers/simple-options.js.map +1 -1
  83. package/dist/providers/transform-messages.d.ts.map +1 -1
  84. package/dist/providers/transform-messages.js +112 -137
  85. package/dist/providers/transform-messages.js.map +1 -1
  86. package/dist/stream.d.ts +0 -1
  87. package/dist/stream.d.ts.map +1 -1
  88. package/dist/stream.js +0 -1
  89. package/dist/stream.js.map +1 -1
  90. package/dist/types.d.ts +91 -63
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js.map +1 -1
  93. package/dist/utils/error-body.d.ts.map +1 -1
  94. package/dist/utils/error-body.js +15 -4
  95. package/dist/utils/error-body.js.map +1 -1
  96. package/dist/utils/estimate.d.ts +4 -0
  97. package/dist/utils/estimate.d.ts.map +1 -1
  98. package/dist/utils/estimate.js +3 -1
  99. package/dist/utils/estimate.js.map +1 -1
  100. package/dist/utils/headers.d.ts +3 -0
  101. package/dist/utils/headers.d.ts.map +1 -1
  102. package/dist/utils/headers.js +21 -0
  103. package/dist/utils/headers.js.map +1 -1
  104. package/dist/utils/json-parse.d.ts.map +1 -1
  105. package/dist/utils/json-parse.js +17 -19
  106. package/dist/utils/json-parse.js.map +1 -1
  107. package/dist/utils/oauth/anthropic.d.ts +4 -11
  108. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  109. package/dist/utils/oauth/anthropic.js +100 -117
  110. package/dist/utils/oauth/anthropic.js.map +1 -1
  111. package/dist/utils/oauth/device-code.d.ts.map +1 -1
  112. package/dist/utils/oauth/device-code.js +51 -38
  113. package/dist/utils/oauth/device-code.js.map +1 -1
  114. package/dist/utils/oauth/github-copilot.d.ts +6 -9
  115. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  116. package/dist/utils/oauth/github-copilot.js +47 -42
  117. package/dist/utils/oauth/github-copilot.js.map +1 -1
  118. package/dist/utils/oauth/index.d.ts +8 -17
  119. package/dist/utils/oauth/index.d.ts.map +1 -1
  120. package/dist/utils/oauth/index.js +27 -23
  121. package/dist/utils/oauth/index.js.map +1 -1
  122. package/dist/utils/oauth/oauth-page.d.ts.map +1 -1
  123. package/dist/utils/oauth/oauth-page.js.map +1 -1
  124. package/dist/utils/oauth/openai-codex.d.ts +7 -12
  125. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  126. package/dist/utils/oauth/openai-codex.js +72 -89
  127. package/dist/utils/oauth/openai-codex.js.map +1 -1
  128. package/dist/utils/oauth/pkce.d.ts +5 -4
  129. package/dist/utils/oauth/pkce.d.ts.map +1 -1
  130. package/dist/utils/oauth/pkce.js.map +1 -1
  131. package/dist/utils/oauth/types.d.ts +7 -2
  132. package/dist/utils/oauth/types.d.ts.map +1 -1
  133. package/dist/utils/oauth/types.js.map +1 -1
  134. package/dist/utils/provider-retry.d.ts +16 -0
  135. package/dist/utils/provider-retry.d.ts.map +1 -0
  136. package/dist/utils/provider-retry.js +94 -0
  137. package/dist/utils/provider-retry.js.map +1 -0
  138. package/dist/utils/retry.d.ts +14 -0
  139. package/dist/utils/retry.d.ts.map +1 -1
  140. package/dist/utils/retry.js +84 -0
  141. package/dist/utils/retry.js.map +1 -1
  142. package/dist/utils/typebox-helpers.d.ts +5 -4
  143. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  144. package/dist/utils/typebox-helpers.js.map +1 -1
  145. package/dist/utils/validation.d.ts.map +1 -1
  146. package/dist/utils/validation.js +102 -99
  147. package/dist/utils/validation.js.map +1 -1
  148. package/package.json +2 -2
@@ -3,8 +3,10 @@ import { getEnvApiKey } from "../env-api-keys.js";
3
3
  import { calculateCost, clampThinkingLevel } from "../models.js";
4
4
  import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
5
5
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
6
+ import { shortHash } from "../utils/hash.js";
6
7
  import { headersToRecord } from "../utils/headers.js";
7
8
  import { parseStreamingJson } from "../utils/json-parse.js";
9
+ import { retryProviderRequest } from "../utils/provider-retry.js";
8
10
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
9
11
  import { isCloudflareProvider, resolveCloudflareBaseUrl } from "./cloudflare.js";
10
12
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
@@ -22,7 +24,9 @@ function hasToolHistory(messages) {
22
24
  return true;
23
25
  }
24
26
  if (msg.role === "assistant") {
25
- if (msg.content.some((block) => block.type === "toolCall")) {
27
+ // This helper reads the original context after conversion, so retain the
28
+ // transform boundary's tolerance for untyped null or missing content.
29
+ if (msg.content?.some((block) => block.type === "toolCall")) {
26
30
  return true;
27
31
  }
28
32
  }
@@ -50,289 +54,285 @@ function resolveCacheRetention(cacheRetention) {
50
54
  }
51
55
  return "short";
52
56
  }
57
+ function createOpenAICompletionsOutput(model) {
58
+ return {
59
+ role: "assistant",
60
+ content: [],
61
+ api: model.api,
62
+ provider: model.provider,
63
+ model: model.id,
64
+ usage: {
65
+ input: 0,
66
+ output: 0,
67
+ cacheRead: 0,
68
+ cacheWrite: 0,
69
+ totalTokens: 0,
70
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
71
+ },
72
+ stopReason: "stop",
73
+ timestamp: Date.now(),
74
+ };
75
+ }
76
+ async function startOpenAICompletionsRequest(model, context, options) {
77
+ const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
78
+ const compat = getCompat(model);
79
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention);
80
+ const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
81
+ const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat);
82
+ let params = buildParams(model, context, options, compat, cacheRetention);
83
+ const nextParams = await options?.onPayload?.(params, model);
84
+ if (nextParams !== undefined)
85
+ params = nextParams;
86
+ const requestOptions = {
87
+ ...(options?.signal ? { signal: options.signal } : {}),
88
+ ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
89
+ maxRetries: 0,
90
+ };
91
+ const { data, response } = await retryProviderRequest(() => client.chat.completions
92
+ .create(params, requestOptions)
93
+ .withResponse(), {
94
+ maxRetries: options?.maxRetries,
95
+ maxRetryDelayMs: options?.maxRetryDelayMs,
96
+ signal: options?.signal,
97
+ });
98
+ await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
99
+ return data;
100
+ }
101
+ function createOpenAICompletionStreamState(model, output, stream) {
102
+ return {
103
+ model,
104
+ output,
105
+ stream,
106
+ blocks: output.content,
107
+ textBlock: null,
108
+ thinkingBlock: null,
109
+ hasFinishReason: false,
110
+ toolCallBlocksByIndex: new Map(),
111
+ toolCallBlocksById: new Map(),
112
+ };
113
+ }
114
+ function openAICompletionContentIndex(state, block) {
115
+ return state.blocks.indexOf(block);
116
+ }
117
+ function finishOpenAICompletionBlock(state, block) {
118
+ const contentIndex = openAICompletionContentIndex(state, block);
119
+ if (contentIndex === -1)
120
+ return;
121
+ if (block.type === "text") {
122
+ state.stream.push({ type: "text_end", contentIndex, content: block.text, partial: state.output });
123
+ return;
124
+ }
125
+ if (block.type === "thinking") {
126
+ state.stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial: state.output });
127
+ return;
128
+ }
129
+ block.arguments = parseStreamingJson(block.partialArgs);
130
+ delete block.partialArgs;
131
+ delete block.streamIndex;
132
+ state.stream.push({ type: "toolcall_end", contentIndex, toolCall: block, partial: state.output });
133
+ }
134
+ function appendOpenAITextDelta(state, delta) {
135
+ if (!state.textBlock) {
136
+ state.textBlock = { type: "text", text: "" };
137
+ state.blocks.push(state.textBlock);
138
+ state.stream.push({
139
+ type: "text_start",
140
+ contentIndex: openAICompletionContentIndex(state, state.textBlock),
141
+ partial: state.output,
142
+ });
143
+ }
144
+ state.textBlock.text += delta;
145
+ state.stream.push({
146
+ type: "text_delta",
147
+ contentIndex: openAICompletionContentIndex(state, state.textBlock),
148
+ delta,
149
+ partial: state.output,
150
+ });
151
+ }
152
+ function appendOpenAIThinkingDelta(state, reasoning) {
153
+ if (!state.thinkingBlock) {
154
+ state.thinkingBlock = { type: "thinking", thinking: "", thinkingSignature: reasoning.signature };
155
+ state.blocks.push(state.thinkingBlock);
156
+ state.stream.push({
157
+ type: "thinking_start",
158
+ contentIndex: openAICompletionContentIndex(state, state.thinkingBlock),
159
+ partial: state.output,
160
+ });
161
+ }
162
+ state.thinkingBlock.thinking += reasoning.delta;
163
+ state.stream.push({
164
+ type: "thinking_delta",
165
+ contentIndex: openAICompletionContentIndex(state, state.thinkingBlock),
166
+ delta: reasoning.delta,
167
+ partial: state.output,
168
+ });
169
+ }
170
+ function resolveOpenAIReasoningDelta(model, delta) {
171
+ const fields = ["reasoning_content", "reasoning", "reasoning_text"];
172
+ const deltaFields = delta;
173
+ for (const field of fields) {
174
+ const value = deltaFields[field];
175
+ if (typeof value !== "string" || value.length === 0)
176
+ continue;
177
+ return {
178
+ delta: value,
179
+ signature: model.provider === "opencode-go" && field === "reasoning" ? "reasoning_content" : field,
180
+ };
181
+ }
182
+ return undefined;
183
+ }
184
+ function ensureOpenAIToolCallBlock(state, toolCall) {
185
+ const streamIndex = typeof toolCall.index === "number" ? toolCall.index : undefined;
186
+ let block = streamIndex !== undefined ? state.toolCallBlocksByIndex.get(streamIndex) : undefined;
187
+ if (!block && toolCall.id)
188
+ block = state.toolCallBlocksById.get(toolCall.id);
189
+ if (!block) {
190
+ block = {
191
+ type: "toolCall",
192
+ id: toolCall.id || "",
193
+ name: toolCall.function?.name || "",
194
+ arguments: {},
195
+ partialArgs: "",
196
+ streamIndex,
197
+ };
198
+ if (streamIndex !== undefined)
199
+ state.toolCallBlocksByIndex.set(streamIndex, block);
200
+ if (toolCall.id)
201
+ state.toolCallBlocksById.set(toolCall.id, block);
202
+ state.blocks.push(block);
203
+ state.stream.push({
204
+ type: "toolcall_start",
205
+ contentIndex: openAICompletionContentIndex(state, block),
206
+ partial: state.output,
207
+ });
208
+ }
209
+ if (streamIndex !== undefined && block.streamIndex === undefined) {
210
+ block.streamIndex = streamIndex;
211
+ state.toolCallBlocksByIndex.set(streamIndex, block);
212
+ }
213
+ if (toolCall.id)
214
+ state.toolCallBlocksById.set(toolCall.id, block);
215
+ return block;
216
+ }
217
+ function appendOpenAIToolCallDelta(state, toolCall) {
218
+ const block = ensureOpenAIToolCallBlock(state, toolCall);
219
+ if (!block.id && toolCall.id) {
220
+ block.id = toolCall.id;
221
+ state.toolCallBlocksById.set(toolCall.id, block);
222
+ }
223
+ if (!block.name && toolCall.function?.name)
224
+ block.name = toolCall.function.name;
225
+ let delta = "";
226
+ if (toolCall.function?.arguments) {
227
+ delta = toolCall.function.arguments;
228
+ block.partialArgs = (block.partialArgs ?? "") + delta;
229
+ block.arguments = parseStreamingJson(block.partialArgs);
230
+ }
231
+ state.stream.push({
232
+ type: "toolcall_delta",
233
+ contentIndex: openAICompletionContentIndex(state, block),
234
+ delta,
235
+ partial: state.output,
236
+ });
237
+ }
238
+ function applyOpenAIReasoningDetails(state, delta) {
239
+ const details = delta.reasoning_details;
240
+ if (!Array.isArray(details))
241
+ return;
242
+ for (const detail of details) {
243
+ if (detail.type !== "reasoning.encrypted" || typeof detail.id !== "string" || !detail.data)
244
+ continue;
245
+ const matchingToolCall = state.toolCallBlocksById.get(detail.id);
246
+ if (matchingToolCall)
247
+ matchingToolCall.thoughtSignature = JSON.stringify(detail);
248
+ }
249
+ }
250
+ function consumeOpenAICompletionDelta(state, delta) {
251
+ if (delta.content !== null && delta.content !== undefined && delta.content.length > 0) {
252
+ appendOpenAITextDelta(state, delta.content);
253
+ }
254
+ const reasoning = resolveOpenAIReasoningDelta(state.model, delta);
255
+ if (reasoning)
256
+ appendOpenAIThinkingDelta(state, reasoning);
257
+ for (const toolCall of delta.tool_calls ?? [])
258
+ appendOpenAIToolCallDelta(state, toolCall);
259
+ applyOpenAIReasoningDetails(state, delta);
260
+ }
261
+ function applyOpenAICompletionFinishReason(state, finishReason) {
262
+ if (!finishReason)
263
+ return;
264
+ const result = mapStopReason(finishReason);
265
+ state.output.stopReason = result.stopReason;
266
+ if (result.errorMessage)
267
+ state.output.errorMessage = result.errorMessage;
268
+ state.hasFinishReason = true;
269
+ }
270
+ function consumeOpenAICompletionChunk(state, chunk) {
271
+ state.output.responseId ||= chunk.id;
272
+ if (typeof chunk.model === "string" && chunk.model.length > 0 && chunk.model !== state.model.id) {
273
+ state.output.responseModel ||= chunk.model;
274
+ }
275
+ if (chunk.usage)
276
+ state.output.usage = parseChunkUsage(chunk.usage, state.model);
277
+ const choice = (Array.isArray(chunk.choices) ? chunk.choices[0] : undefined);
278
+ if (!choice)
279
+ return;
280
+ if (!chunk.usage && choice.usage)
281
+ state.output.usage = parseChunkUsage(choice.usage, state.model);
282
+ applyOpenAICompletionFinishReason(state, choice.finish_reason);
283
+ if (choice.delta)
284
+ consumeOpenAICompletionDelta(state, choice.delta);
285
+ }
286
+ async function consumeOpenAICompletionStream(state, openaiStream) {
287
+ for await (const chunk of openaiStream) {
288
+ if (chunk && typeof chunk === "object")
289
+ consumeOpenAICompletionChunk(state, chunk);
290
+ }
291
+ }
292
+ function finalizeOpenAICompletionStream(state, options) {
293
+ for (const block of state.blocks)
294
+ finishOpenAICompletionBlock(state, block);
295
+ if (options?.signal?.aborted || state.output.stopReason === "aborted")
296
+ throw new Error("Request was aborted");
297
+ if (state.output.stopReason === "error") {
298
+ throw new Error(state.output.errorMessage || "Provider returned an error stop reason");
299
+ }
300
+ if (!state.hasFinishReason || state.output.stopReason === "pending") {
301
+ throw new Error("Stream ended without finish_reason");
302
+ }
303
+ state.stream.push({ type: "done", reason: state.output.stopReason, message: state.output });
304
+ state.stream.end();
305
+ }
306
+ function failOpenAICompletionStream(output, stream, error, options) {
307
+ for (const block of output.content) {
308
+ delete block.index;
309
+ delete block.partialArgs;
310
+ delete block.streamIndex;
311
+ }
312
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
313
+ output.errorMessage = formatProviderError(normalizeProviderError(error));
314
+ const rawMetadata = error?.error?.metadata?.raw;
315
+ if (typeof rawMetadata === "string" && rawMetadata.length > 0)
316
+ output.errorMessage += `\n${rawMetadata}`;
317
+ stream.push({ type: "error", reason: output.stopReason, error: output });
318
+ stream.end();
319
+ }
320
+ async function runOpenAICompletionsStream(model, context, options, stream) {
321
+ const output = createOpenAICompletionsOutput(model);
322
+ try {
323
+ const openaiStream = await startOpenAICompletionsRequest(model, context, options);
324
+ stream.push({ type: "start", partial: output });
325
+ const state = createOpenAICompletionStreamState(model, output, stream);
326
+ await consumeOpenAICompletionStream(state, openaiStream);
327
+ finalizeOpenAICompletionStream(state, options);
328
+ }
329
+ catch (error) {
330
+ failOpenAICompletionStream(output, stream, error, options);
331
+ }
332
+ }
53
333
  export const streamOpenAICompletions = (model, context, options) => {
54
334
  const stream = new AssistantMessageEventStream();
55
- (async () => {
56
- const output = {
57
- role: "assistant",
58
- content: [],
59
- api: model.api,
60
- provider: model.provider,
61
- model: model.id,
62
- usage: {
63
- input: 0,
64
- output: 0,
65
- cacheRead: 0,
66
- cacheWrite: 0,
67
- totalTokens: 0,
68
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
69
- },
70
- stopReason: "stop",
71
- timestamp: Date.now(),
72
- };
73
- try {
74
- const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
75
- const compat = getCompat(model);
76
- const cacheRetention = resolveCacheRetention(options?.cacheRetention);
77
- const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
78
- const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat);
79
- let params = buildParams(model, context, options, compat, cacheRetention);
80
- const nextParams = await options?.onPayload?.(params, model);
81
- if (nextParams !== undefined) {
82
- params = nextParams;
83
- }
84
- const requestOptions = {
85
- ...(options?.signal ? { signal: options.signal } : {}),
86
- ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
87
- maxRetries: options?.maxRetries ?? 0,
88
- };
89
- const { data: openaiStream, response } = await client.chat.completions
90
- .create(params, requestOptions)
91
- .withResponse();
92
- await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
93
- stream.push({ type: "start", partial: output });
94
- let textBlock = null;
95
- let thinkingBlock = null;
96
- let hasFinishReason = false;
97
- const toolCallBlocksByIndex = new Map();
98
- const toolCallBlocksById = new Map();
99
- const blocks = output.content;
100
- const getContentIndex = (block) => blocks.indexOf(block);
101
- const finishBlock = (block) => {
102
- const contentIndex = getContentIndex(block);
103
- if (contentIndex === -1) {
104
- return;
105
- }
106
- if (block.type === "text") {
107
- stream.push({
108
- type: "text_end",
109
- contentIndex,
110
- content: block.text,
111
- partial: output,
112
- });
113
- }
114
- else if (block.type === "thinking") {
115
- stream.push({
116
- type: "thinking_end",
117
- contentIndex,
118
- content: block.thinking,
119
- partial: output,
120
- });
121
- }
122
- else if (block.type === "toolCall") {
123
- block.arguments = parseStreamingJson(block.partialArgs);
124
- // Finalize in-place and strip the scratch buffers so replay only
125
- // carries parsed arguments.
126
- delete block.partialArgs;
127
- delete block.streamIndex;
128
- stream.push({
129
- type: "toolcall_end",
130
- contentIndex,
131
- toolCall: block,
132
- partial: output,
133
- });
134
- }
135
- };
136
- const ensureTextBlock = () => {
137
- if (!textBlock) {
138
- textBlock = { type: "text", text: "" };
139
- blocks.push(textBlock);
140
- stream.push({ type: "text_start", contentIndex: getContentIndex(textBlock), partial: output });
141
- }
142
- return textBlock;
143
- };
144
- const ensureThinkingBlock = (thinkingSignature) => {
145
- if (!thinkingBlock) {
146
- thinkingBlock = {
147
- type: "thinking",
148
- thinking: "",
149
- thinkingSignature,
150
- };
151
- blocks.push(thinkingBlock);
152
- stream.push({ type: "thinking_start", contentIndex: getContentIndex(thinkingBlock), partial: output });
153
- }
154
- return thinkingBlock;
155
- };
156
- const ensureToolCallBlock = (toolCall) => {
157
- const streamIndex = typeof toolCall.index === "number" ? toolCall.index : undefined;
158
- let block = streamIndex !== undefined ? toolCallBlocksByIndex.get(streamIndex) : undefined;
159
- if (!block && toolCall.id) {
160
- block = toolCallBlocksById.get(toolCall.id);
161
- }
162
- if (!block) {
163
- block = {
164
- type: "toolCall",
165
- id: toolCall.id || "",
166
- name: toolCall.function?.name || "",
167
- arguments: {},
168
- partialArgs: "",
169
- streamIndex,
170
- };
171
- if (streamIndex !== undefined) {
172
- toolCallBlocksByIndex.set(streamIndex, block);
173
- }
174
- if (toolCall.id) {
175
- toolCallBlocksById.set(toolCall.id, block);
176
- }
177
- blocks.push(block);
178
- stream.push({
179
- type: "toolcall_start",
180
- contentIndex: getContentIndex(block),
181
- partial: output,
182
- });
183
- }
184
- if (streamIndex !== undefined && block.streamIndex === undefined) {
185
- block.streamIndex = streamIndex;
186
- toolCallBlocksByIndex.set(streamIndex, block);
187
- }
188
- if (toolCall.id) {
189
- toolCallBlocksById.set(toolCall.id, block);
190
- }
191
- return block;
192
- };
193
- for await (const chunk of openaiStream) {
194
- if (!chunk || typeof chunk !== "object")
195
- continue;
196
- // OpenAI documents ChatCompletionChunk.id as the unique chat completion identifier,
197
- // and each chunk in a streamed completion carries the same id.
198
- output.responseId ||= chunk.id;
199
- if (typeof chunk.model === "string" && chunk.model.length > 0 && chunk.model !== model.id) {
200
- output.responseModel ||= chunk.model;
201
- }
202
- if (chunk.usage) {
203
- output.usage = parseChunkUsage(chunk.usage, model);
204
- }
205
- const choice = Array.isArray(chunk.choices) ? chunk.choices[0] : undefined;
206
- if (!choice)
207
- continue;
208
- // Fallback: some providers (e.g., Moonshot) return usage
209
- // in choice.usage instead of the standard chunk.usage
210
- if (!chunk.usage && choice.usage) {
211
- output.usage = parseChunkUsage(choice.usage, model);
212
- }
213
- if (choice.finish_reason) {
214
- const finishReasonResult = mapStopReason(choice.finish_reason);
215
- output.stopReason = finishReasonResult.stopReason;
216
- if (finishReasonResult.errorMessage) {
217
- output.errorMessage = finishReasonResult.errorMessage;
218
- }
219
- hasFinishReason = true;
220
- }
221
- if (choice.delta) {
222
- if (choice.delta.content !== null &&
223
- choice.delta.content !== undefined &&
224
- choice.delta.content.length > 0) {
225
- const block = ensureTextBlock();
226
- block.text += choice.delta.content;
227
- stream.push({
228
- type: "text_delta",
229
- contentIndex: getContentIndex(block),
230
- delta: choice.delta.content,
231
- partial: output,
232
- });
233
- }
234
- // Some endpoints return reasoning in reasoning_content (llama.cpp),
235
- // or reasoning (other openai compatible endpoints)
236
- // Use the first non-empty reasoning field to avoid duplication
237
- // (e.g., chutes.ai returns both reasoning_content and reasoning with same content)
238
- const reasoningFields = ["reasoning_content", "reasoning", "reasoning_text"];
239
- const deltaFields = choice.delta;
240
- let foundReasoningField = null;
241
- for (const field of reasoningFields) {
242
- const value = deltaFields[field];
243
- if (typeof value === "string" && value.length > 0) {
244
- foundReasoningField = field;
245
- break;
246
- }
247
- }
248
- if (foundReasoningField) {
249
- const delta = deltaFields[foundReasoningField];
250
- if (typeof delta === "string" && delta.length > 0) {
251
- const thinkingSignature = model.provider === "opencode-go" && foundReasoningField === "reasoning"
252
- ? "reasoning_content"
253
- : foundReasoningField;
254
- const block = ensureThinkingBlock(thinkingSignature);
255
- block.thinking += delta;
256
- stream.push({
257
- type: "thinking_delta",
258
- contentIndex: getContentIndex(block),
259
- delta,
260
- partial: output,
261
- });
262
- }
263
- }
264
- if (choice?.delta?.tool_calls) {
265
- for (const toolCall of choice.delta.tool_calls) {
266
- const block = ensureToolCallBlock(toolCall);
267
- if (!block.id && toolCall.id) {
268
- block.id = toolCall.id;
269
- toolCallBlocksById.set(toolCall.id, block);
270
- }
271
- if (!block.name && toolCall.function?.name) {
272
- block.name = toolCall.function.name;
273
- }
274
- let delta = "";
275
- if (toolCall.function?.arguments) {
276
- delta = toolCall.function.arguments;
277
- block.partialArgs = (block.partialArgs ?? "") + toolCall.function.arguments;
278
- block.arguments = parseStreamingJson(block.partialArgs);
279
- }
280
- stream.push({
281
- type: "toolcall_delta",
282
- contentIndex: getContentIndex(block),
283
- delta,
284
- partial: output,
285
- });
286
- }
287
- }
288
- const reasoningDetails = choice.delta.reasoning_details;
289
- if (reasoningDetails && Array.isArray(reasoningDetails)) {
290
- for (const detail of reasoningDetails) {
291
- if (detail.type === "reasoning.encrypted" && detail.id && detail.data) {
292
- const matchingToolCall = output.content.find((b) => b.type === "toolCall" && b.id === detail.id);
293
- if (matchingToolCall) {
294
- matchingToolCall.thoughtSignature = JSON.stringify(detail);
295
- }
296
- }
297
- }
298
- }
299
- }
300
- }
301
- for (const block of blocks) {
302
- finishBlock(block);
303
- }
304
- if (options?.signal?.aborted) {
305
- throw new Error("Request was aborted");
306
- }
307
- if (output.stopReason === "aborted") {
308
- throw new Error("Request was aborted");
309
- }
310
- if (output.stopReason === "error") {
311
- throw new Error(output.errorMessage || "Provider returned an error stop reason");
312
- }
313
- if (!hasFinishReason) {
314
- throw new Error("Stream ended without finish_reason");
315
- }
316
- stream.push({ type: "done", reason: output.stopReason, message: output });
317
- stream.end();
318
- }
319
- catch (error) {
320
- for (const block of output.content) {
321
- delete block.index;
322
- // Streaming scratch buffers are only used during parsing; never persist them.
323
- delete block.partialArgs;
324
- delete block.streamIndex;
325
- }
326
- output.stopReason = options?.signal?.aborted ? "aborted" : "error";
327
- output.errorMessage = formatProviderError(normalizeProviderError(error));
328
- // Some providers via OpenRouter give additional information in this field.
329
- const rawMetadata = error?.error?.metadata?.raw;
330
- if (typeof rawMetadata === "string" && rawMetadata.length > 0)
331
- output.errorMessage += `\n${rawMetadata}`;
332
- stream.push({ type: "error", reason: output.stopReason, error: output });
333
- stream.end();
334
- }
335
- })();
335
+ void runOpenAICompletionsStream(model, context, options, stream);
336
336
  return stream;
337
337
  };
338
338
  export const streamSimpleOpenAICompletions = (model, context, options) => {
@@ -371,151 +371,168 @@ function createClient(model, context, apiKey, optionsHeaders, sessionId, compat
371
371
  headers["x-client-request-id"] = sessionId;
372
372
  headers["x-session-affinity"] = sessionId;
373
373
  }
374
- // Merge options headers last so they can override defaults
374
+ if (model.provider === "cloudflare-ai-gateway") {
375
+ if (!("Authorization" in headers))
376
+ headers.Authorization = null;
377
+ if (!("cf-aig-authorization" in headers))
378
+ headers["cf-aig-authorization"] = `Bearer ${apiKey}`;
379
+ }
380
+ // Merge options headers last so they can override or suppress defaults.
375
381
  if (optionsHeaders) {
376
382
  Object.assign(headers, optionsHeaders);
377
383
  }
378
- const defaultHeaders = model.provider === "cloudflare-ai-gateway"
379
- ? {
380
- ...headers,
381
- Authorization: headers.Authorization ?? null,
382
- "cf-aig-authorization": `Bearer ${apiKey}`,
383
- }
384
- : headers;
385
384
  return new OpenAI({
386
385
  apiKey,
387
386
  baseURL: isCloudflareProvider(model.provider) ? resolveCloudflareBaseUrl(model) : model.baseUrl,
388
387
  dangerouslyAllowBrowser: true,
389
- defaultHeaders,
388
+ defaultHeaders: headers,
390
389
  });
391
390
  }
392
- function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention)) {
393
- const messages = convertMessages(model, context, compat);
394
- const cacheControl = getCompatCacheControl(compat, cacheRetention);
395
- const params = {
396
- model: model.id,
397
- messages,
398
- stream: true,
399
- prompt_cache_key: (model.baseUrl.includes("api.openai.com") && cacheRetention !== "none") ||
400
- (cacheRetention === "long" && compat.supportsLongCacheRetention)
401
- ? clampOpenAIPromptCacheKey(options?.sessionId)
402
- : undefined,
403
- prompt_cache_retention: cacheRetention === "long" && compat.supportsLongCacheRetention ? "24h" : undefined,
404
- };
405
- if (compat.supportsUsageInStreaming !== false) {
391
+ function configureOpenAIRequestFields(params, options, compat) {
392
+ if (compat.supportsUsageInStreaming !== false)
406
393
  params.stream_options = { include_usage: true };
407
- }
408
- if (compat.supportsStore) {
394
+ if (compat.supportsStore)
409
395
  params.store = false;
410
- }
411
396
  if (options?.maxTokens) {
412
- if (compat.maxTokensField === "max_tokens") {
397
+ if (compat.maxTokensField === "max_tokens")
413
398
  params.max_tokens = options.maxTokens;
414
- }
415
- else {
399
+ else
416
400
  params.max_completion_tokens = options.maxTokens;
417
- }
418
401
  }
419
- if (options?.temperature !== undefined) {
402
+ if (options?.temperature !== undefined)
420
403
  params.temperature = options.temperature;
421
- }
404
+ }
405
+ function configureOpenAIRequestTools(params, context, compat, cacheControl) {
422
406
  if (context.tools && context.tools.length > 0) {
423
407
  params.tools = convertTools(context.tools, compat);
424
- if (compat.zaiToolStream) {
408
+ if (compat.zaiToolStream)
425
409
  params.tool_stream = true;
426
- }
427
410
  }
428
411
  else if (hasToolHistory(context.messages)) {
429
- // Anthropic (via LiteLLM/proxy) requires tools param when conversation has tool_calls/tool_results
412
+ // Anthropic-compatible proxies require tools when conversation history contains tool calls or results.
430
413
  params.tools = [];
431
414
  }
432
- if (cacheControl) {
433
- applyAnthropicCacheControl(messages, params.tools, cacheControl);
434
- }
435
- if (options?.toolChoice) {
436
- params.tool_choice = options.toolChoice;
415
+ if (cacheControl)
416
+ applyAnthropicCacheControl(params.messages, params.tools, cacheControl);
417
+ }
418
+ function resolveOpenAIReasoningEffort(model, effort) {
419
+ return model.thinkingLevelMap?.[effort] ?? effort;
420
+ }
421
+ function configureZaiReasoning(params, model, options, compat) {
422
+ params.thinking = options?.reasoningEffort ? { type: "enabled", clear_thinking: false } : { type: "disabled" };
423
+ if (!options?.reasoningEffort || !compat.supportsReasoningEffort)
424
+ return;
425
+ const mappedEffort = model.thinkingLevelMap?.[options.reasoningEffort];
426
+ const effort = mappedEffort === undefined ? options.reasoningEffort : mappedEffort;
427
+ if (typeof effort === "string")
428
+ params.reasoning_effort = effort;
429
+ }
430
+ function configureDeepSeekReasoning(params, model, options, compat) {
431
+ params.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
432
+ if (!options?.reasoningEffort || !compat.supportsReasoningEffort)
433
+ return;
434
+ params.reasoning_effort = resolveOpenAIReasoningEffort(model, options.reasoningEffort);
435
+ }
436
+ function configureOpenRouterReasoning(params, model, options) {
437
+ if (options?.reasoningEffort) {
438
+ params.reasoning = { effort: resolveOpenAIReasoningEffort(model, options.reasoningEffort) };
437
439
  }
438
- if (compat.thinkingFormat === "zai" && model.reasoning) {
439
- const zaiParams = params;
440
- zaiParams.thinking = options?.reasoningEffort ? { type: "enabled", clear_thinking: false } : { type: "disabled" };
441
- if (options?.reasoningEffort && compat.supportsReasoningEffort) {
442
- const mappedEffort = model.thinkingLevelMap?.[options.reasoningEffort];
443
- const effort = mappedEffort === undefined ? options.reasoningEffort : mappedEffort;
444
- if (typeof effort === "string") {
445
- zaiParams.reasoning_effort = effort;
446
- }
447
- }
440
+ else if (model.thinkingLevelMap?.off !== null) {
441
+ params.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" };
448
442
  }
449
- else if (compat.thinkingFormat === "qwen" && model.reasoning) {
450
- params.enable_thinking = !!options?.reasoningEffort;
443
+ }
444
+ function configureTogetherReasoning(params, model, options, compat) {
445
+ params.reasoning = { enabled: !!options?.reasoningEffort };
446
+ if (!options?.reasoningEffort || !compat.supportsReasoningEffort)
447
+ return;
448
+ params.reasoning_effort = resolveOpenAIReasoningEffort(model, options.reasoningEffort);
449
+ }
450
+ function configureStringThinking(params, model, options) {
451
+ if (options?.reasoningEffort) {
452
+ params.thinking = resolveOpenAIReasoningEffort(model, options.reasoningEffort);
451
453
  }
452
- else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) {
453
- params.chat_template_kwargs = {
454
- enable_thinking: !!options?.reasoningEffort,
455
- preserve_thinking: true,
456
- };
454
+ else if (model.thinkingLevelMap?.off !== null) {
455
+ params.thinking = model.thinkingLevelMap?.off ?? "none";
457
456
  }
458
- else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
459
- params.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
460
- if (options?.reasoningEffort && compat.supportsReasoningEffort) {
461
- params.reasoning_effort =
462
- model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
463
- }
457
+ }
458
+ function configureStandardOpenAIReasoning(params, model, options, compat) {
459
+ if (!compat.supportsReasoningEffort)
460
+ return;
461
+ if (options?.reasoningEffort) {
462
+ params.reasoning_effort = resolveOpenAIReasoningEffort(model, options.reasoningEffort);
463
+ return;
464
464
  }
465
- else if (compat.thinkingFormat === "openrouter" && model.reasoning) {
466
- // OpenRouter normalizes reasoning across providers via a nested reasoning object.
467
- const openRouterParams = params;
468
- if (options?.reasoningEffort) {
469
- openRouterParams.reasoning = {
470
- effort: model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort,
465
+ const offValue = model.thinkingLevelMap?.off;
466
+ if (typeof offValue === "string")
467
+ params.reasoning_effort = offValue;
468
+ }
469
+ function configureOpenAIReasoning(params, model, options, compat) {
470
+ if (!model.reasoning)
471
+ return;
472
+ switch (compat.thinkingFormat) {
473
+ case "zai":
474
+ configureZaiReasoning(params, model, options, compat);
475
+ return;
476
+ case "qwen":
477
+ params.enable_thinking = !!options?.reasoningEffort;
478
+ return;
479
+ case "qwen-chat-template":
480
+ params.chat_template_kwargs = {
481
+ enable_thinking: !!options?.reasoningEffort,
482
+ preserve_thinking: true,
471
483
  };
472
- }
473
- else if (model.thinkingLevelMap?.off !== null) {
474
- openRouterParams.reasoning = { effort: model.thinkingLevelMap?.off ?? "none" };
475
- }
476
- }
477
- else if (compat.thinkingFormat === "together" && model.reasoning) {
478
- const togetherParams = params;
479
- togetherParams.reasoning = { enabled: !!options?.reasoningEffort };
480
- if (options?.reasoningEffort && compat.supportsReasoningEffort) {
481
- togetherParams.reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
482
- }
483
- }
484
- else if (compat.thinkingFormat === "string-thinking" && model.reasoning) {
485
- const stringThinkingParams = params;
486
- if (options?.reasoningEffort) {
487
- stringThinkingParams.thinking = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
488
- }
489
- else if (model.thinkingLevelMap?.off !== null) {
490
- stringThinkingParams.thinking = model.thinkingLevelMap?.off ?? "none";
491
- }
492
- }
493
- else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
494
- // OpenAI-style reasoning_effort
495
- params.reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
496
- }
497
- else if (!options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
498
- const offValue = model.thinkingLevelMap?.off;
499
- if (typeof offValue === "string") {
500
- params.reasoning_effort = offValue;
501
- }
484
+ return;
485
+ case "deepseek":
486
+ configureDeepSeekReasoning(params, model, options, compat);
487
+ return;
488
+ case "openrouter":
489
+ configureOpenRouterReasoning(params, model, options);
490
+ return;
491
+ case "together":
492
+ configureTogetherReasoning(params, model, options, compat);
493
+ return;
494
+ case "string-thinking":
495
+ configureStringThinking(params, model, options);
496
+ return;
497
+ default:
498
+ configureStandardOpenAIReasoning(params, model, options, compat);
502
499
  }
503
- // OpenRouter provider routing preferences
500
+ }
501
+ function configureOpenAIProviderRouting(params, model) {
504
502
  if (model.baseUrl.includes("openrouter.ai") && model.compat?.openRouterRouting) {
505
503
  params.provider = model.compat.openRouterRouting;
506
504
  }
507
- // Vercel AI Gateway provider routing preferences
508
- if (model.baseUrl.includes("ai-gateway.vercel.sh") && model.compat?.vercelGatewayRouting) {
509
- const routing = model.compat.vercelGatewayRouting;
510
- if (routing.only || routing.order) {
511
- const gatewayOptions = {};
512
- if (routing.only)
513
- gatewayOptions.only = routing.only;
514
- if (routing.order)
515
- gatewayOptions.order = routing.order;
516
- params.providerOptions = { gateway: gatewayOptions };
517
- }
518
- }
505
+ if (!model.baseUrl.includes("ai-gateway.vercel.sh") || !model.compat?.vercelGatewayRouting)
506
+ return;
507
+ const routing = model.compat.vercelGatewayRouting;
508
+ if (!routing.only && !routing.order)
509
+ return;
510
+ const gatewayOptions = {};
511
+ if (routing.only)
512
+ gatewayOptions.only = routing.only;
513
+ if (routing.order)
514
+ gatewayOptions.order = routing.order;
515
+ params.providerOptions = { gateway: gatewayOptions };
516
+ }
517
+ function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention)) {
518
+ const messages = convertMessages(model, context, compat);
519
+ const cacheControl = getCompatCacheControl(compat, cacheRetention);
520
+ const params = {
521
+ model: model.id,
522
+ messages,
523
+ stream: true,
524
+ prompt_cache_key: (model.baseUrl.includes("api.openai.com") && cacheRetention !== "none") ||
525
+ (cacheRetention === "long" && compat.supportsLongCacheRetention)
526
+ ? clampOpenAIPromptCacheKey(options?.sessionId)
527
+ : undefined,
528
+ prompt_cache_retention: cacheRetention === "long" && compat.supportsLongCacheRetention ? "24h" : undefined,
529
+ };
530
+ configureOpenAIRequestFields(params, options, compat);
531
+ configureOpenAIRequestTools(params, context, compat, cacheControl);
532
+ if (options?.toolChoice)
533
+ params.tool_choice = options.toolChoice;
534
+ configureOpenAIReasoning(params, model, options, compat);
535
+ configureOpenAIProviderRouting(params, model);
519
536
  return params;
520
537
  }
521
538
  function getCompatCacheControl(compat, cacheRetention) {
@@ -592,228 +609,191 @@ function addCacheControlToTextContent(message, cacheControl) {
592
609
  }
593
610
  return false;
594
611
  }
595
- export function convertMessages(model, context, compat) {
596
- const params = [];
597
- const normalizeToolCallId = (id) => {
598
- // Handle pipe-separated IDs from OpenAI Responses API
599
- // Format: {call_id}|{id} where {id} can be 400+ chars with special chars (+, /, =)
600
- // These come from providers like github-copilot, openai-codex, opencode
601
- // Extract just the call_id part and normalize it
602
- if (id.includes("|")) {
603
- const [callId] = id.split("|");
604
- // Sanitize to allowed chars and truncate to 40 chars (OpenAI limit)
605
- return callId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
612
+ function normalizeOpenAICompletionsToolCallId(model, id) {
613
+ if (id.includes("|")) {
614
+ const separatorIndex = id.indexOf("|");
615
+ const callId = id.slice(0, separatorIndex).replace(/[^a-zA-Z0-9_-]/g, "_");
616
+ const itemId = id.slice(separatorIndex + 1).replace(/[^a-zA-Z0-9_-]/g, "_");
617
+ const combinedId = itemId.length > 0 ? `${callId}_${itemId}` : callId;
618
+ if (combinedId.length <= 40)
619
+ return combinedId;
620
+ const hash = shortHash(id).slice(0, 8);
621
+ const prefix = callId.slice(0, Math.max(1, 40 - hash.length - 1));
622
+ return `${prefix}_${hash}`;
623
+ }
624
+ if (model.provider === "openai")
625
+ return id.length > 40 ? id.slice(0, 40) : id;
626
+ return id;
627
+ }
628
+ function convertOpenAIUserMessage(message) {
629
+ if (typeof message.content === "string") {
630
+ return { role: "user", content: sanitizeSurrogates(message.content) };
631
+ }
632
+ const content = message.content.map((item) => {
633
+ if (item.type === "text") {
634
+ return { type: "text", text: sanitizeSurrogates(item.text) };
606
635
  }
607
- if (model.provider === "openai")
608
- return id.length > 40 ? id.slice(0, 40) : id;
609
- return id;
636
+ return {
637
+ type: "image_url",
638
+ image_url: { url: `data:${item.mimeType};base64,${item.data}` },
639
+ };
640
+ });
641
+ return content.length > 0 ? { role: "user", content } : undefined;
642
+ }
643
+ function createOpenAIAssistantConversion(message, model, compat) {
644
+ const textParts = message.content
645
+ .filter((block) => isTextContentBlock(block) && block.text.trim().length > 0)
646
+ .map((block) => ({ type: "text", text: sanitizeSurrogates(block.text) }));
647
+ return {
648
+ model,
649
+ compat,
650
+ source: message,
651
+ output: { role: "assistant", content: compat.requiresAssistantAfterToolResult ? "" : null },
652
+ textParts,
653
+ text: textParts.map((part) => part.text).join(""),
610
654
  };
611
- const transformedMessages = transformMessages(context.messages, model, (id) => normalizeToolCallId(id));
612
- if (context.systemPrompt) {
613
- const useDeveloperRole = model.reasoning && compat.supportsDeveloperRole;
614
- const role = useDeveloperRole ? "developer" : "system";
615
- params.push({ role: role, content: sanitizeSurrogates(context.systemPrompt) });
616
- }
617
- let lastRole = null;
618
- for (let i = 0; i < transformedMessages.length; i++) {
619
- const msg = transformedMessages[i];
620
- // Some providers don't allow user messages directly after tool results
621
- // Insert a synthetic assistant message to bridge the gap
622
- if (compat.requiresAssistantAfterToolResult && lastRole === "toolResult" && msg.role === "user") {
623
- params.push({
624
- role: "assistant",
625
- content: "I have processed the tool results.",
626
- });
627
- }
628
- if (msg.role === "user") {
629
- if (typeof msg.content === "string") {
630
- params.push({
631
- role: "user",
632
- content: sanitizeSurrogates(msg.content),
633
- });
634
- }
635
- else {
636
- const content = msg.content.map((item) => {
637
- if (item.type === "text") {
638
- return {
639
- type: "text",
640
- text: sanitizeSurrogates(item.text),
641
- };
642
- }
643
- else {
644
- return {
645
- type: "image_url",
646
- image_url: {
647
- url: `data:${item.mimeType};base64,${item.data}`,
648
- },
649
- };
650
- }
651
- });
652
- if (content.length === 0)
653
- continue;
654
- params.push({
655
- role: "user",
656
- content,
657
- });
658
- }
655
+ }
656
+ function applyOpenAIAssistantThinking(conversion) {
657
+ const thinkingBlocks = conversion.source.content.filter((block) => isThinkingContentBlock(block) && block.thinking.trim().length > 0);
658
+ if (thinkingBlocks.length === 0) {
659
+ if (conversion.text.length > 0)
660
+ conversion.output.content = conversion.text;
661
+ return;
662
+ }
663
+ if (conversion.compat.requiresThinkingAsText) {
664
+ const thinkingText = thinkingBlocks.map((block) => sanitizeSurrogates(block.thinking)).join("\n\n");
665
+ conversion.output.content = [{ type: "text", text: thinkingText }, ...conversion.textParts];
666
+ return;
667
+ }
668
+ if (conversion.text.length > 0)
669
+ conversion.output.content = conversion.text;
670
+ let signature = thinkingBlocks[0].thinkingSignature;
671
+ if (conversion.model.provider === "opencode-go" && signature === "reasoning")
672
+ signature = "reasoning_content";
673
+ if (signature && signature.length > 0) {
674
+ conversion.output[signature] = thinkingBlocks.map((block) => block.thinking).join("\n");
675
+ }
676
+ }
677
+ function applyOpenAIAssistantToolCalls(conversion) {
678
+ const toolCalls = conversion.source.content.filter(isToolCallBlock);
679
+ if (toolCalls.length === 0)
680
+ return;
681
+ conversion.output.tool_calls = toolCalls.map((toolCall) => ({
682
+ id: toolCall.id,
683
+ type: "function",
684
+ function: { name: toolCall.name, arguments: JSON.stringify(toolCall.arguments) },
685
+ }));
686
+ const reasoningDetails = toolCalls
687
+ .filter((toolCall) => toolCall.thoughtSignature)
688
+ .map((toolCall) => {
689
+ try {
690
+ return JSON.parse(toolCall.thoughtSignature);
659
691
  }
660
- else if (msg.role === "assistant") {
661
- // Some providers don't accept null content, use empty string instead
662
- const assistantMsg = {
663
- role: "assistant",
664
- content: compat.requiresAssistantAfterToolResult ? "" : null,
665
- };
666
- const assistantTextParts = msg.content
667
- .filter(isTextContentBlock)
668
- .filter((block) => block.text.trim().length > 0)
669
- .map((block) => ({
670
- type: "text",
671
- text: sanitizeSurrogates(block.text),
672
- }));
673
- const assistantText = assistantTextParts.map((part) => part.text).join("");
674
- const nonEmptyThinkingBlocks = msg.content
675
- .filter(isThinkingContentBlock)
676
- .filter((block) => block.thinking.trim().length > 0);
677
- if (nonEmptyThinkingBlocks.length > 0) {
678
- if (compat.requiresThinkingAsText) {
679
- // Convert thinking blocks to plain text (no tags to avoid model mimicking them)
680
- const thinkingText = nonEmptyThinkingBlocks
681
- .map((block) => sanitizeSurrogates(block.thinking))
682
- .join("\n\n");
683
- assistantMsg.content = [{ type: "text", text: thinkingText }, ...assistantTextParts];
684
- }
685
- else {
686
- // Always send assistant content as a plain string (OpenAI Chat Completions
687
- // API standard format). Sending as an array of {type:"text", text:"..."}
688
- // objects is non-standard and causes some models (e.g. DeepSeek V3.2 via
689
- // NVIDIA NIM) to mirror the content-block structure literally in their
690
- // output, producing recursive nesting like [{'type':'text','text':'[{...}]'}].
691
- if (assistantText.length > 0) {
692
- assistantMsg.content = assistantText;
693
- }
694
- // Use the signature from the first thinking block if available (for llama.cpp server + gpt-oss)
695
- let signature = nonEmptyThinkingBlocks[0].thinkingSignature;
696
- if (model.provider === "opencode-go" && signature === "reasoning") {
697
- signature = "reasoning_content";
698
- }
699
- if (signature && signature.length > 0) {
700
- assistantMsg[signature] = nonEmptyThinkingBlocks.map((block) => block.thinking).join("\n");
701
- }
702
- }
703
- }
704
- else if (assistantText.length > 0) {
705
- // Always send assistant content as a plain string (OpenAI Chat Completions
706
- // API standard format). Sending as an array of {type:"text", text:"..."}
707
- // objects is non-standard and causes some models (e.g. DeepSeek V3.2 via
708
- // NVIDIA NIM) to mirror the content-block structure literally in their
709
- // output, producing recursive nesting like [{'type':'text','text':'[{...}]'}].
710
- assistantMsg.content = assistantText;
711
- }
712
- const toolCalls = msg.content.filter(isToolCallBlock);
713
- if (toolCalls.length > 0) {
714
- assistantMsg.tool_calls = toolCalls.map((tc) => ({
715
- id: tc.id,
716
- type: "function",
717
- function: {
718
- name: tc.name,
719
- arguments: JSON.stringify(tc.arguments),
720
- },
721
- }));
722
- const reasoningDetails = toolCalls
723
- .filter((tc) => tc.thoughtSignature)
724
- .map((tc) => {
725
- try {
726
- return JSON.parse(tc.thoughtSignature);
727
- }
728
- catch {
729
- return null;
730
- }
731
- })
732
- .filter(Boolean);
733
- if (reasoningDetails.length > 0) {
734
- assistantMsg.reasoning_details = reasoningDetails;
735
- }
736
- }
737
- if (compat.requiresReasoningContentOnAssistantMessages &&
738
- model.reasoning &&
739
- assistantMsg.reasoning_content === undefined) {
740
- assistantMsg.reasoning_content = "";
741
- }
742
- // Skip assistant messages that have no content and no tool calls.
743
- // Some providers require "either content or tool_calls, but not none".
744
- // Other providers also don't accept empty assistant messages.
745
- // This handles aborted assistant responses that got no content.
746
- const content = assistantMsg.content;
747
- const hasContent = content !== null &&
748
- content !== undefined &&
749
- (typeof content === "string" ? content.length > 0 : content.length > 0);
750
- if (!hasContent && !assistantMsg.tool_calls) {
751
- continue;
752
- }
753
- params.push(assistantMsg);
692
+ catch {
693
+ return null;
754
694
  }
755
- else if (msg.role === "toolResult") {
756
- const imageBlocks = [];
757
- let j = i;
758
- for (; j < transformedMessages.length && transformedMessages[j].role === "toolResult"; j++) {
759
- const toolMsg = transformedMessages[j];
760
- // Extract text and image content
761
- const textResult = toolMsg.content
762
- .filter(isTextContentBlock)
763
- .map((block) => block.text)
764
- .join("\n");
765
- const hasImages = toolMsg.content.some((c) => c.type === "image");
766
- // Always send tool result with text (or placeholder if only images)
767
- const hasText = textResult.length > 0;
768
- // Some providers require the 'name' field in tool results
769
- const toolResultMsg = {
770
- role: "tool",
771
- content: sanitizeSurrogates(hasText ? textResult : "(see attached image)"),
772
- tool_call_id: toolMsg.toolCallId,
773
- };
774
- if (compat.requiresToolResultName && toolMsg.toolName) {
775
- toolResultMsg.name = toolMsg.toolName;
776
- }
777
- params.push(toolResultMsg);
778
- if (hasImages && model.input.includes("image")) {
779
- for (const block of toolMsg.content) {
780
- if (isImageContentBlock(block)) {
781
- imageBlocks.push({
782
- type: "image_url",
783
- image_url: {
784
- url: `data:${block.mimeType};base64,${block.data}`,
785
- },
786
- });
787
- }
788
- }
789
- }
790
- }
791
- i = j - 1;
792
- if (imageBlocks.length > 0) {
793
- if (compat.requiresAssistantAfterToolResult) {
794
- params.push({
795
- role: "assistant",
796
- content: "I have processed the tool results.",
797
- });
798
- }
799
- params.push({
800
- role: "user",
801
- content: [
802
- {
803
- type: "text",
804
- text: "Attached image(s) from tool result:",
805
- },
806
- ...imageBlocks,
807
- ],
808
- });
809
- lastRole = "user";
810
- }
811
- else {
812
- lastRole = "toolResult";
813
- }
695
+ })
696
+ .filter(Boolean);
697
+ if (reasoningDetails.length > 0)
698
+ conversion.output.reasoning_details = reasoningDetails;
699
+ }
700
+ function ensureOpenAIAssistantReasoningContent(conversion) {
701
+ if (conversion.compat.requiresReasoningContentOnAssistantMessages &&
702
+ conversion.model.reasoning &&
703
+ conversion.output.reasoning_content === undefined) {
704
+ conversion.output.reasoning_content = "";
705
+ }
706
+ }
707
+ function hasOpenAIAssistantOutput(message) {
708
+ const content = message.content;
709
+ const hasContent = content !== null &&
710
+ content !== undefined &&
711
+ (typeof content === "string" ? content.length > 0 : content.length > 0);
712
+ return hasContent || message.tool_calls !== undefined;
713
+ }
714
+ function convertOpenAIAssistantMessage(message, model, compat) {
715
+ const conversion = createOpenAIAssistantConversion(message, model, compat);
716
+ applyOpenAIAssistantThinking(conversion);
717
+ applyOpenAIAssistantToolCalls(conversion);
718
+ ensureOpenAIAssistantReasoningContent(conversion);
719
+ return hasOpenAIAssistantOutput(conversion.output) ? conversion.output : undefined;
720
+ }
721
+ function convertOpenAIToolResultMessage(message, compat) {
722
+ const textResult = message.content
723
+ .filter(isTextContentBlock)
724
+ .map((block) => block.text)
725
+ .join("\n");
726
+ const hasImages = message.content.some((content) => content.type === "image");
727
+ const toolResultText = textResult.length > 0 ? textResult : hasImages ? "(see attached image)" : "(no tool output)";
728
+ const result = {
729
+ role: "tool",
730
+ content: sanitizeSurrogates(toolResultText),
731
+ tool_call_id: message.toolCallId,
732
+ };
733
+ if (compat.requiresToolResultName && message.toolName)
734
+ result.name = message.toolName;
735
+ return result;
736
+ }
737
+ function collectOpenAIToolResultImages(message, model) {
738
+ if (!model.input.includes("image"))
739
+ return [];
740
+ return message.content.filter(isImageContentBlock).map((block) => ({
741
+ type: "image_url",
742
+ image_url: { url: `data:${block.mimeType};base64,${block.data}` },
743
+ }));
744
+ }
745
+ function appendOpenAIToolResultBatch(state, startIndex) {
746
+ const imageBlocks = [];
747
+ let index = startIndex;
748
+ for (; index < state.messages.length && state.messages[index].role === "toolResult"; index++) {
749
+ const message = state.messages[index];
750
+ state.params.push(convertOpenAIToolResultMessage(message, state.compat));
751
+ imageBlocks.push(...collectOpenAIToolResultImages(message, state.model));
752
+ }
753
+ if (imageBlocks.length === 0) {
754
+ state.lastRole = "toolResult";
755
+ return index - 1;
756
+ }
757
+ if (state.compat.requiresAssistantAfterToolResult) {
758
+ state.params.push({ role: "assistant", content: "I have processed the tool results." });
759
+ }
760
+ state.params.push({
761
+ role: "user",
762
+ content: [{ type: "text", text: "Attached image(s) from tool result:" }, ...imageBlocks],
763
+ });
764
+ state.lastRole = "user";
765
+ return index - 1;
766
+ }
767
+ function appendAssistantAfterToolResultBridge(state, message) {
768
+ if (state.compat.requiresAssistantAfterToolResult && state.lastRole === "toolResult" && message.role === "user") {
769
+ state.params.push({ role: "assistant", content: "I have processed the tool results." });
770
+ }
771
+ }
772
+ function convertOpenAINonToolMessage(message, state) {
773
+ return message.role === "user"
774
+ ? convertOpenAIUserMessage(message)
775
+ : convertOpenAIAssistantMessage(message, state.model, state.compat);
776
+ }
777
+ export function convertMessages(model, context, compat) {
778
+ const params = [];
779
+ const transformedMessages = transformMessages(context.messages, model, (id) => normalizeOpenAICompletionsToolCallId(model, id));
780
+ if (context.systemPrompt) {
781
+ const role = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
782
+ params.push({ role, content: sanitizeSurrogates(context.systemPrompt) });
783
+ }
784
+ const state = { model, compat, messages: transformedMessages, params, lastRole: null };
785
+ for (let index = 0; index < transformedMessages.length; index++) {
786
+ const message = transformedMessages[index];
787
+ appendAssistantAfterToolResultBridge(state, message);
788
+ if (message.role === "toolResult") {
789
+ index = appendOpenAIToolResultBatch(state, index);
814
790
  continue;
815
791
  }
816
- lastRole = msg.role;
792
+ const converted = convertOpenAINonToolMessage(message, state);
793
+ if (!converted)
794
+ continue;
795
+ params.push(converted);
796
+ state.lastRole = message.role;
817
797
  }
818
798
  return params;
819
799
  }
@@ -883,87 +863,113 @@ function mapStopReason(reason) {
883
863
  * Provider takes precedence over URL-based detection since it's explicitly configured.
884
864
  * Returns a fully resolved OpenAICompletionsCompat object with all fields set.
885
865
  */
866
+ function matchesOpenAICompatibilityFamily(provider, baseUrl, providers, baseUrlFragments) {
867
+ return providers.includes(provider) || baseUrlFragments.some((fragment) => baseUrl.includes(fragment));
868
+ }
869
+ function detectOpenAIThinkingFormat(isDeepSeek, isZai, isTogether, isOpenRouter) {
870
+ if (isDeepSeek)
871
+ return "deepseek";
872
+ if (isZai)
873
+ return "zai";
874
+ if (isTogether)
875
+ return "together";
876
+ return isOpenRouter ? "openrouter" : "openai";
877
+ }
886
878
  function detectCompat(model) {
887
879
  const provider = model.provider;
888
880
  const baseUrl = model.baseUrl;
889
- const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
890
- const isTogether = provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
891
- const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
892
- const isOpenRouter = provider === "openrouter" || baseUrl.includes("openrouter.ai");
893
- const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com");
894
- const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com");
895
- const isNonStandard = provider === "cerebras" ||
896
- baseUrl.includes("cerebras.ai") ||
897
- provider === "xai" ||
898
- baseUrl.includes("api.x.ai") ||
899
- isTogether ||
900
- baseUrl.includes("chutes.ai") ||
901
- baseUrl.includes("deepseek.com") ||
902
- isZai ||
903
- isMoonshot ||
904
- provider === "opencode" ||
905
- baseUrl.includes("opencode.ai") ||
906
- isCloudflareWorkersAI ||
907
- isCloudflareAiGateway;
908
- const useMaxTokens = baseUrl.includes("chutes.ai") || isMoonshot || isCloudflareAiGateway || isTogether;
909
- const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
910
- const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com");
881
+ const isZai = matchesOpenAICompatibilityFamily(provider, baseUrl, ["zai", "zai-coding-cn"], ["api.z.ai", "open.bigmodel.cn"]);
882
+ const isTogether = matchesOpenAICompatibilityFamily(provider, baseUrl, ["together"], ["api.together.ai", "api.together.xyz"]);
883
+ const isMoonshot = matchesOpenAICompatibilityFamily(provider, baseUrl, ["moonshotai", "moonshotai-cn"], ["api.moonshot."]);
884
+ const isOpenRouter = matchesOpenAICompatibilityFamily(provider, baseUrl, ["openrouter"], ["openrouter.ai"]);
885
+ const isCloudflareWorkersAI = matchesOpenAICompatibilityFamily(provider, baseUrl, ["cloudflare-workers-ai"], ["api.cloudflare.com"]);
886
+ const isCloudflareAiGateway = matchesOpenAICompatibilityFamily(provider, baseUrl, ["cloudflare-ai-gateway"], ["gateway.ai.cloudflare.com"]);
887
+ const isNonStandard = matchesOpenAICompatibilityFamily(provider, baseUrl, [
888
+ "cerebras",
889
+ "xai",
890
+ "together",
891
+ "zai",
892
+ "zai-coding-cn",
893
+ "moonshotai",
894
+ "moonshotai-cn",
895
+ "opencode",
896
+ "cloudflare-workers-ai",
897
+ "cloudflare-ai-gateway",
898
+ ], [
899
+ "cerebras.ai",
900
+ "api.x.ai",
901
+ "api.together.ai",
902
+ "api.together.xyz",
903
+ "chutes.ai",
904
+ "deepseek.com",
905
+ "api.z.ai",
906
+ "open.bigmodel.cn",
907
+ "api.moonshot.",
908
+ "opencode.ai",
909
+ "api.cloudflare.com",
910
+ "gateway.ai.cloudflare.com",
911
+ ]);
912
+ const isDeepSeek = matchesOpenAICompatibilityFamily(provider, baseUrl, ["deepseek"], ["deepseek.com"]);
913
+ const useMaxTokens = [
914
+ baseUrl.includes("chutes.ai"),
915
+ isDeepSeek,
916
+ isZai,
917
+ isMoonshot,
918
+ isCloudflareAiGateway,
919
+ isTogether,
920
+ ].some(Boolean);
921
+ const isGrok = matchesOpenAICompatibilityFamily(provider, baseUrl, ["xai"], ["api.x.ai"]);
911
922
  const cacheControlFormat = provider === "openrouter" && model.id.startsWith("anthropic/") ? "anthropic" : undefined;
912
923
  return {
913
924
  supportsStore: !isNonStandard,
914
- supportsDeveloperRole: !isNonStandard && !isOpenRouter,
915
- supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway,
925
+ supportsDeveloperRole: ![isNonStandard, isOpenRouter].some(Boolean),
926
+ supportsReasoningEffort: ![isGrok, isZai, isMoonshot, isTogether, isCloudflareAiGateway].some(Boolean),
916
927
  supportsUsageInStreaming: true,
917
928
  maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
918
929
  requiresToolResultName: false,
919
930
  requiresAssistantAfterToolResult: false,
920
931
  requiresThinkingAsText: false,
921
932
  requiresReasoningContentOnAssistantMessages: isDeepSeek,
922
- thinkingFormat: isDeepSeek
923
- ? "deepseek"
924
- : isZai
925
- ? "zai"
926
- : isTogether
927
- ? "together"
928
- : isOpenRouter
929
- ? "openrouter"
930
- : "openai",
933
+ thinkingFormat: detectOpenAIThinkingFormat(isDeepSeek, isZai, isTogether, isOpenRouter),
931
934
  openRouterRouting: {},
932
935
  vercelGatewayRouting: {},
933
936
  zaiToolStream: false,
934
- supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway,
937
+ supportsStrictMode: ![isMoonshot, isTogether, isCloudflareAiGateway].some(Boolean),
935
938
  cacheControlFormat,
936
939
  sendSessionAffinityHeaders: false,
937
- supportsLongCacheRetention: !(isTogether || isCloudflareWorkersAI || isCloudflareAiGateway),
940
+ supportsLongCacheRetention: ![isTogether, isCloudflareWorkersAI, isCloudflareAiGateway].some(Boolean),
938
941
  };
939
942
  }
943
+ function resolveOpenAICompatibilityValue(configuredValue, detectedValue) {
944
+ return configuredValue ?? detectedValue;
945
+ }
940
946
  /**
941
947
  * Get resolved compatibility settings for a model.
942
948
  * Uses explicit model.compat if provided, otherwise auto-detects from provider/URL.
943
949
  */
944
950
  function getCompat(model) {
945
951
  const detected = detectCompat(model);
946
- if (!model.compat)
952
+ const configured = model.compat;
953
+ if (!configured)
947
954
  return detected;
948
955
  return {
949
- supportsStore: model.compat.supportsStore ?? detected.supportsStore,
950
- supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
951
- supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
952
- supportsUsageInStreaming: model.compat.supportsUsageInStreaming ?? detected.supportsUsageInStreaming,
953
- maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
954
- requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
955
- requiresAssistantAfterToolResult: model.compat.requiresAssistantAfterToolResult ?? detected.requiresAssistantAfterToolResult,
956
- requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
957
- requiresReasoningContentOnAssistantMessages: model.compat.requiresReasoningContentOnAssistantMessages ??
958
- detected.requiresReasoningContentOnAssistantMessages,
959
- thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
960
- openRouterRouting: model.compat.openRouterRouting ?? {},
961
- vercelGatewayRouting: model.compat.vercelGatewayRouting ?? detected.vercelGatewayRouting,
962
- zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
963
- supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
964
- cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
965
- sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ?? detected.sendSessionAffinityHeaders,
966
- supportsLongCacheRetention: model.compat.supportsLongCacheRetention ?? detected.supportsLongCacheRetention,
956
+ supportsStore: resolveOpenAICompatibilityValue(configured.supportsStore, detected.supportsStore),
957
+ supportsDeveloperRole: resolveOpenAICompatibilityValue(configured.supportsDeveloperRole, detected.supportsDeveloperRole),
958
+ supportsReasoningEffort: resolveOpenAICompatibilityValue(configured.supportsReasoningEffort, detected.supportsReasoningEffort),
959
+ supportsUsageInStreaming: resolveOpenAICompatibilityValue(configured.supportsUsageInStreaming, detected.supportsUsageInStreaming),
960
+ maxTokensField: resolveOpenAICompatibilityValue(configured.maxTokensField, detected.maxTokensField),
961
+ requiresToolResultName: resolveOpenAICompatibilityValue(configured.requiresToolResultName, detected.requiresToolResultName),
962
+ requiresAssistantAfterToolResult: resolveOpenAICompatibilityValue(configured.requiresAssistantAfterToolResult, detected.requiresAssistantAfterToolResult),
963
+ requiresThinkingAsText: resolveOpenAICompatibilityValue(configured.requiresThinkingAsText, detected.requiresThinkingAsText),
964
+ requiresReasoningContentOnAssistantMessages: resolveOpenAICompatibilityValue(configured.requiresReasoningContentOnAssistantMessages, detected.requiresReasoningContentOnAssistantMessages),
965
+ thinkingFormat: resolveOpenAICompatibilityValue(configured.thinkingFormat, detected.thinkingFormat),
966
+ openRouterRouting: resolveOpenAICompatibilityValue(configured.openRouterRouting, {}),
967
+ vercelGatewayRouting: resolveOpenAICompatibilityValue(configured.vercelGatewayRouting, detected.vercelGatewayRouting),
968
+ zaiToolStream: resolveOpenAICompatibilityValue(configured.zaiToolStream, detected.zaiToolStream),
969
+ supportsStrictMode: resolveOpenAICompatibilityValue(configured.supportsStrictMode, detected.supportsStrictMode),
970
+ cacheControlFormat: resolveOpenAICompatibilityValue(configured.cacheControlFormat, detected.cacheControlFormat),
971
+ sendSessionAffinityHeaders: resolveOpenAICompatibilityValue(configured.sendSessionAffinityHeaders, detected.sendSessionAffinityHeaders),
972
+ supportsLongCacheRetention: resolveOpenAICompatibilityValue(configured.supportsLongCacheRetention, detected.supportsLongCacheRetention),
967
973
  };
968
974
  }
969
975
  //# sourceMappingURL=openai-completions.js.map