@aexol/spectral 0.9.80 → 0.9.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.js +2 -2
- package/dist/memory/tools/compact-context.d.ts.map +1 -1
- package/dist/memory/tools/compact-context.js +7 -4
- package/dist/sdk/agent-core/harness/agent-harness.js +1 -1
- package/dist/sdk/agent-core/harness/types.d.ts +2 -0
- package/dist/sdk/agent-core/harness/types.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.js +435 -92
- package/dist/sdk/ai/types.d.ts +4 -0
- package/dist/sdk/ai/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts +1 -1
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +1 -1
- package/dist/sdk/coding-agent/core/extensions/runner.d.ts +3 -1
- package/dist/sdk/coding-agent/core/extensions/runner.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/runner.js +6 -2
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +2 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/sdk.d.ts +2 -2
- package/dist/sdk/coding-agent/core/sdk.js +2 -2
- package/dist/sdk/coding-agent/core/system-prompt.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/system-prompt.js +22 -4
- package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts +27 -0
- package/dist/sdk/coding-agent/core/tools/apply-patch.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/tools/apply-patch.js +353 -0
- package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts +1 -6
- package/dist/sdk/coding-agent/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/edit-diff.js +179 -100
- package/dist/sdk/coding-agent/core/tools/edit.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/edit.js +9 -5
- package/dist/sdk/coding-agent/core/tools/index.d.ts +4 -1
- package/dist/sdk/coding-agent/core/tools/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/tools/index.js +11 -0
- package/dist/sdk/coding-agent/index.d.ts +1 -1
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/index.js +1 -1
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +274 -54
- package/dist/server/handlers/queue.d.ts +3 -11
- package/dist/server/handlers/queue.d.ts.map +1 -1
- package/dist/server/handlers/queue.js +48 -24
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +43 -9
- package/dist/server/storage.d.ts +2 -1
- package/dist/server/storage.d.ts.map +1 -1
- package/dist/server/storage.js +16 -8
- package/dist/server/wire.d.ts +11 -1
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
|
5
5
|
import { headersToRecord } from "../utils/headers.js";
|
|
6
6
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
7
7
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
8
|
-
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
8
|
+
import { buildCopilotDynamicHeaders, hasCopilotVisionInput, } from "./github-copilot-headers.js";
|
|
9
9
|
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
|
|
10
10
|
import { buildBaseOptions } from "./simple-options.js";
|
|
11
11
|
import { transformMessages } from "./transform-messages.js";
|
|
@@ -39,8 +39,18 @@ function isToolCallBlock(block) {
|
|
|
39
39
|
function isImageContentBlock(block) {
|
|
40
40
|
return block.type === "image";
|
|
41
41
|
}
|
|
42
|
+
const SAFE_REASONING_REPLAY_FIELDS = [
|
|
43
|
+
"reasoning_content",
|
|
44
|
+
"reasoning",
|
|
45
|
+
"reasoning_text",
|
|
46
|
+
"reasoning_summary",
|
|
47
|
+
"reasoning_summary_text",
|
|
48
|
+
"summary_text",
|
|
49
|
+
"summary",
|
|
50
|
+
];
|
|
42
51
|
function isOpenRouterModel(model) {
|
|
43
|
-
return model.provider === "openrouter" ||
|
|
52
|
+
return model.provider === "openrouter" ||
|
|
53
|
+
model.baseUrl.includes("openrouter.ai");
|
|
44
54
|
}
|
|
45
55
|
function isOpenRouterAnthropicModel(model) {
|
|
46
56
|
return isOpenRouterModel(model) && model.id.startsWith("anthropic/");
|
|
@@ -52,11 +62,206 @@ function resolveCacheRetention(cacheRetention, model) {
|
|
|
52
62
|
if (model && isOpenRouterAnthropicModel(model)) {
|
|
53
63
|
return "long";
|
|
54
64
|
}
|
|
55
|
-
if (typeof process !== "undefined" &&
|
|
65
|
+
if (typeof process !== "undefined" &&
|
|
66
|
+
process.env.PI_CACHE_RETENTION === "long") {
|
|
56
67
|
return "long";
|
|
57
68
|
}
|
|
58
69
|
return "short";
|
|
59
70
|
}
|
|
71
|
+
function asRecord(value) {
|
|
72
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
73
|
+
? value
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
76
|
+
function collectReasoningText(value) {
|
|
77
|
+
if (typeof value === "string")
|
|
78
|
+
return value;
|
|
79
|
+
if (Array.isArray(value))
|
|
80
|
+
return value.map(collectReasoningText).join("");
|
|
81
|
+
const record = asRecord(value);
|
|
82
|
+
if (!record)
|
|
83
|
+
return "";
|
|
84
|
+
const direct = [
|
|
85
|
+
record.delta,
|
|
86
|
+
record.text,
|
|
87
|
+
record.value,
|
|
88
|
+
record.output_text,
|
|
89
|
+
record.reasoning_text,
|
|
90
|
+
record.reasoning_content,
|
|
91
|
+
record.reasoning_summary,
|
|
92
|
+
record.reasoning_summary_text,
|
|
93
|
+
record.summary_text,
|
|
94
|
+
];
|
|
95
|
+
for (const candidate of direct) {
|
|
96
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
97
|
+
return candidate;
|
|
98
|
+
}
|
|
99
|
+
return [record.summary, record.parts, record.content, record.annotations]
|
|
100
|
+
.map(collectReasoningText)
|
|
101
|
+
.join("");
|
|
102
|
+
}
|
|
103
|
+
function extractReasoningDelta(deltaFields) {
|
|
104
|
+
const reasoningFields = [
|
|
105
|
+
"reasoning_content",
|
|
106
|
+
"reasoning",
|
|
107
|
+
"reasoning_text",
|
|
108
|
+
"reasoning_summary",
|
|
109
|
+
"reasoning_summary_text",
|
|
110
|
+
"summary_text",
|
|
111
|
+
"summary",
|
|
112
|
+
];
|
|
113
|
+
for (const field of reasoningFields) {
|
|
114
|
+
const delta = collectReasoningText(deltaFields[field]);
|
|
115
|
+
if (delta.length > 0)
|
|
116
|
+
return { field, delta };
|
|
117
|
+
}
|
|
118
|
+
const details = collectReasoningText(deltaFields.reasoning_details);
|
|
119
|
+
return details.length > 0
|
|
120
|
+
? { field: "reasoning_details", delta: details }
|
|
121
|
+
: undefined;
|
|
122
|
+
}
|
|
123
|
+
function extractReasoningSignature(value) {
|
|
124
|
+
if (typeof value === "string" && value.length > 0)
|
|
125
|
+
return value;
|
|
126
|
+
const record = asRecord(value);
|
|
127
|
+
if (!record)
|
|
128
|
+
return undefined;
|
|
129
|
+
for (const candidate of [
|
|
130
|
+
record.id,
|
|
131
|
+
record.item_id,
|
|
132
|
+
record.data,
|
|
133
|
+
]) {
|
|
134
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
function extractReasoningMetadata(value) {
|
|
140
|
+
const record = asRecord(value);
|
|
141
|
+
if (!record)
|
|
142
|
+
return undefined;
|
|
143
|
+
const metadata = {};
|
|
144
|
+
for (const key of [
|
|
145
|
+
"id",
|
|
146
|
+
"item_id",
|
|
147
|
+
"output_index",
|
|
148
|
+
"summary_index",
|
|
149
|
+
"encrypted_content",
|
|
150
|
+
"type",
|
|
151
|
+
]) {
|
|
152
|
+
if (record[key] !== undefined && record[key] !== null)
|
|
153
|
+
metadata[key] = record[key];
|
|
154
|
+
}
|
|
155
|
+
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
156
|
+
}
|
|
157
|
+
function stringMetadata(metadata, key) {
|
|
158
|
+
const value = metadata?.[key];
|
|
159
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
160
|
+
}
|
|
161
|
+
function numberMetadata(metadata, key) {
|
|
162
|
+
const value = metadata?.[key];
|
|
163
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
164
|
+
}
|
|
165
|
+
function hasEncryptedReasoningMetadata(block) {
|
|
166
|
+
return stringMetadata(block.thinkingMetadata, "encrypted_content") !== undefined;
|
|
167
|
+
}
|
|
168
|
+
function buildResponsesReasoningSidecar(metadata, includeOutputIndex) {
|
|
169
|
+
const sidecar = {};
|
|
170
|
+
const id = stringMetadata(metadata, "id");
|
|
171
|
+
if (id)
|
|
172
|
+
sidecar.id = id;
|
|
173
|
+
const itemId = stringMetadata(metadata, "item_id");
|
|
174
|
+
if (itemId)
|
|
175
|
+
sidecar.item_id = itemId;
|
|
176
|
+
if (includeOutputIndex) {
|
|
177
|
+
const outputIndex = numberMetadata(metadata, "output_index");
|
|
178
|
+
if (outputIndex !== undefined)
|
|
179
|
+
sidecar.output_index = outputIndex;
|
|
180
|
+
const summaryIndex = numberMetadata(metadata, "summary_index");
|
|
181
|
+
if (summaryIndex !== undefined)
|
|
182
|
+
sidecar.summary_index = summaryIndex;
|
|
183
|
+
}
|
|
184
|
+
const encryptedContent = stringMetadata(metadata, "encrypted_content");
|
|
185
|
+
if (encryptedContent)
|
|
186
|
+
sidecar.encrypted_content = encryptedContent;
|
|
187
|
+
return sidecar;
|
|
188
|
+
}
|
|
189
|
+
function isSafeReasoningReplayField(field) {
|
|
190
|
+
return SAFE_REASONING_REPLAY_FIELDS.includes(field);
|
|
191
|
+
}
|
|
192
|
+
function normalizeReasoningReplayField(field, model) {
|
|
193
|
+
const normalized = model.provider === "opencode-go" && field === "reasoning"
|
|
194
|
+
? "reasoning_content"
|
|
195
|
+
: field;
|
|
196
|
+
return normalized && isSafeReasoningReplayField(normalized)
|
|
197
|
+
? normalized
|
|
198
|
+
: "reasoning_content";
|
|
199
|
+
}
|
|
200
|
+
function setAssistantReasoningField(message, field, value) {
|
|
201
|
+
const messageWithSidecars = message;
|
|
202
|
+
messageWithSidecars[field] = value;
|
|
203
|
+
}
|
|
204
|
+
function setAssistantReasoningLifecycleSidecars(message, blocks) {
|
|
205
|
+
const encryptedBlock = blocks.find(hasEncryptedReasoningMetadata);
|
|
206
|
+
if (!encryptedBlock)
|
|
207
|
+
return;
|
|
208
|
+
const messageWithSidecars = message;
|
|
209
|
+
messageWithSidecars.reasoning_start = buildResponsesReasoningSidecar(encryptedBlock.thinkingMetadata, true);
|
|
210
|
+
messageWithSidecars.reasoning_content = blocks
|
|
211
|
+
.map((block) => block.thinking)
|
|
212
|
+
.join("\n");
|
|
213
|
+
messageWithSidecars.reasoning_end = buildResponsesReasoningSidecar(encryptedBlock.thinkingMetadata, false);
|
|
214
|
+
}
|
|
215
|
+
function assistantHasReasoningReplay(message) {
|
|
216
|
+
const messageWithSidecars = message;
|
|
217
|
+
return messageWithSidecars.reasoning_start !== undefined ||
|
|
218
|
+
messageWithSidecars.reasoning_end !== undefined ||
|
|
219
|
+
SAFE_REASONING_REPLAY_FIELDS.some((field) => typeof messageWithSidecars[field] === "string");
|
|
220
|
+
}
|
|
221
|
+
function mergeThinkingMetadata(block, metadata) {
|
|
222
|
+
if (!metadata)
|
|
223
|
+
return;
|
|
224
|
+
block.thinkingMetadata = { ...(block.thinkingMetadata ?? {}), ...metadata };
|
|
225
|
+
}
|
|
226
|
+
function splitInlineThinkingDelta(pending, inInlineThinking, delta) {
|
|
227
|
+
const parts = [];
|
|
228
|
+
let buffer = pending + delta;
|
|
229
|
+
let thinking = inInlineThinking;
|
|
230
|
+
const append = (type, value) => {
|
|
231
|
+
if (!value)
|
|
232
|
+
return;
|
|
233
|
+
const last = parts[parts.length - 1];
|
|
234
|
+
if (last?.type === type) {
|
|
235
|
+
last.delta += value;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
parts.push({ type, delta: value });
|
|
239
|
+
};
|
|
240
|
+
while (buffer.length > 0) {
|
|
241
|
+
const tag = thinking ? "</think>" : "<think>";
|
|
242
|
+
const idx = buffer.toLowerCase().indexOf(tag);
|
|
243
|
+
if (idx >= 0) {
|
|
244
|
+
append(thinking ? "thinking" : "text", buffer.slice(0, idx));
|
|
245
|
+
buffer = buffer.slice(idx + tag.length);
|
|
246
|
+
thinking = !thinking;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const maxTagLength = "</think>".length - 1;
|
|
250
|
+
let keep = 0;
|
|
251
|
+
const limit = Math.min(maxTagLength, buffer.length);
|
|
252
|
+
for (let size = limit; size > 0; size--) {
|
|
253
|
+
const suffix = buffer.slice(-size).toLowerCase();
|
|
254
|
+
if ("<think>".startsWith(suffix) || "</think>".startsWith(suffix)) {
|
|
255
|
+
keep = size;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
append(thinking ? "thinking" : "text", buffer.slice(0, buffer.length - keep));
|
|
260
|
+
buffer = buffer.slice(buffer.length - keep);
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
return { parts, pending: buffer, inInlineThinking: thinking };
|
|
264
|
+
}
|
|
60
265
|
export const streamOpenAICompletions = (model, context, options) => {
|
|
61
266
|
const stream = new AssistantMessageEventStream();
|
|
62
267
|
(async () => {
|
|
@@ -86,12 +291,17 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
86
291
|
let params = buildParams(model, context, options, compat, cacheRetention);
|
|
87
292
|
const nextParams = await options?.onPayload?.(params, model);
|
|
88
293
|
if (nextParams !== undefined) {
|
|
89
|
-
params =
|
|
294
|
+
params =
|
|
295
|
+
nextParams;
|
|
90
296
|
}
|
|
91
297
|
const requestOptions = {
|
|
92
298
|
...(options?.signal ? { signal: options.signal } : {}),
|
|
93
|
-
...(options?.timeoutMs !== undefined
|
|
94
|
-
|
|
299
|
+
...(options?.timeoutMs !== undefined
|
|
300
|
+
? { timeout: options.timeoutMs }
|
|
301
|
+
: {}),
|
|
302
|
+
...(options?.maxRetries !== undefined
|
|
303
|
+
? { maxRetries: options.maxRetries }
|
|
304
|
+
: {}),
|
|
95
305
|
};
|
|
96
306
|
const { data: openaiStream, response } = await client.chat.completions
|
|
97
307
|
.create(params, requestOptions)
|
|
@@ -101,6 +311,9 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
101
311
|
let textBlock = null;
|
|
102
312
|
let thinkingBlock = null;
|
|
103
313
|
let hasFinishReason = false;
|
|
314
|
+
let sawStreamOutput = false;
|
|
315
|
+
let inlineThinkingPending = "";
|
|
316
|
+
let inInlineThinking = false;
|
|
104
317
|
const toolCallBlocksByIndex = new Map();
|
|
105
318
|
const toolCallBlocksById = new Map();
|
|
106
319
|
const blocks = output.content;
|
|
@@ -124,6 +337,7 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
124
337
|
contentIndex,
|
|
125
338
|
content: block.thinking,
|
|
126
339
|
partial: output,
|
|
340
|
+
metadata: block.thinkingMetadata,
|
|
127
341
|
});
|
|
128
342
|
}
|
|
129
343
|
else if (block.type === "toolCall") {
|
|
@@ -144,25 +358,91 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
144
358
|
if (!textBlock) {
|
|
145
359
|
textBlock = { type: "text", text: "" };
|
|
146
360
|
blocks.push(textBlock);
|
|
147
|
-
stream.push({
|
|
361
|
+
stream.push({
|
|
362
|
+
type: "text_start",
|
|
363
|
+
contentIndex: getContentIndex(textBlock),
|
|
364
|
+
partial: output,
|
|
365
|
+
});
|
|
148
366
|
}
|
|
149
367
|
return textBlock;
|
|
150
368
|
};
|
|
151
|
-
const ensureThinkingBlock = (thinkingSignature) => {
|
|
369
|
+
const ensureThinkingBlock = (thinkingSignature, metadata) => {
|
|
152
370
|
if (!thinkingBlock) {
|
|
153
371
|
thinkingBlock = {
|
|
154
372
|
type: "thinking",
|
|
155
373
|
thinking: "",
|
|
156
374
|
thinkingSignature,
|
|
157
375
|
};
|
|
376
|
+
mergeThinkingMetadata(thinkingBlock, metadata);
|
|
158
377
|
blocks.push(thinkingBlock);
|
|
159
|
-
stream.push({
|
|
378
|
+
stream.push({
|
|
379
|
+
type: "thinking_start",
|
|
380
|
+
contentIndex: getContentIndex(thinkingBlock),
|
|
381
|
+
partial: output,
|
|
382
|
+
metadata: thinkingBlock.thinkingMetadata,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
if (!thinkingBlock.thinkingSignature && thinkingSignature) {
|
|
387
|
+
thinkingBlock.thinkingSignature = thinkingSignature;
|
|
388
|
+
}
|
|
389
|
+
mergeThinkingMetadata(thinkingBlock, metadata);
|
|
160
390
|
}
|
|
161
391
|
return thinkingBlock;
|
|
162
392
|
};
|
|
393
|
+
const updateThinkingSignature = (value) => {
|
|
394
|
+
const signature = extractReasoningSignature(value);
|
|
395
|
+
const metadata = extractReasoningMetadata(value);
|
|
396
|
+
if (!signature && !metadata)
|
|
397
|
+
return;
|
|
398
|
+
const block = ensureThinkingBlock(signature ?? "reasoning_content", metadata);
|
|
399
|
+
if (signature)
|
|
400
|
+
block.thinkingSignature = signature;
|
|
401
|
+
};
|
|
402
|
+
const emitThinkingDelta = (field, delta, metadata) => {
|
|
403
|
+
const thinkingSignature = model.provider === "opencode-go" && field === "reasoning"
|
|
404
|
+
? "reasoning_content"
|
|
405
|
+
: field;
|
|
406
|
+
const block = ensureThinkingBlock(thinkingSignature, metadata);
|
|
407
|
+
block.thinking += delta;
|
|
408
|
+
sawStreamOutput = true;
|
|
409
|
+
stream.push({
|
|
410
|
+
type: "thinking_delta",
|
|
411
|
+
contentIndex: getContentIndex(block),
|
|
412
|
+
delta,
|
|
413
|
+
partial: output,
|
|
414
|
+
metadata: block.thinkingMetadata,
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
const emitTextDelta = (delta) => {
|
|
418
|
+
const block = ensureTextBlock();
|
|
419
|
+
block.text += delta;
|
|
420
|
+
sawStreamOutput = true;
|
|
421
|
+
stream.push({
|
|
422
|
+
type: "text_delta",
|
|
423
|
+
contentIndex: getContentIndex(block),
|
|
424
|
+
delta,
|
|
425
|
+
partial: output,
|
|
426
|
+
});
|
|
427
|
+
};
|
|
428
|
+
const emitContentDelta = (delta) => {
|
|
429
|
+
const split = splitInlineThinkingDelta(inlineThinkingPending, inInlineThinking, delta);
|
|
430
|
+
inlineThinkingPending = split.pending;
|
|
431
|
+
inInlineThinking = split.inInlineThinking;
|
|
432
|
+
for (const part of split.parts) {
|
|
433
|
+
if (part.type === "thinking") {
|
|
434
|
+
emitThinkingDelta("inline_think", part.delta);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
emitTextDelta(part.delta);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
163
441
|
const ensureToolCallBlock = (toolCall) => {
|
|
164
442
|
const streamIndex = typeof toolCall.index === "number" ? toolCall.index : undefined;
|
|
165
|
-
let block = streamIndex !== undefined
|
|
443
|
+
let block = streamIndex !== undefined
|
|
444
|
+
? toolCallBlocksByIndex.get(streamIndex)
|
|
445
|
+
: undefined;
|
|
166
446
|
if (!block && toolCall.id) {
|
|
167
447
|
block = toolCallBlocksById.get(toolCall.id);
|
|
168
448
|
}
|
|
@@ -203,13 +483,17 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
203
483
|
// OpenAI documents ChatCompletionChunk.id as the unique chat completion identifier,
|
|
204
484
|
// and each chunk in a streamed completion carries the same id.
|
|
205
485
|
output.responseId ||= chunk.id;
|
|
206
|
-
if (typeof chunk.model === "string" &&
|
|
486
|
+
if (typeof chunk.model === "string" &&
|
|
487
|
+
chunk.model.length > 0 &&
|
|
488
|
+
chunk.model !== model.id) {
|
|
207
489
|
output.responseModel ||= chunk.model;
|
|
208
490
|
}
|
|
209
491
|
if (chunk.usage) {
|
|
210
492
|
output.usage = parseChunkUsage(chunk.usage, model);
|
|
211
493
|
}
|
|
212
|
-
const choice = Array.isArray(chunk.choices)
|
|
494
|
+
const choice = Array.isArray(chunk.choices)
|
|
495
|
+
? chunk.choices[0]
|
|
496
|
+
: undefined;
|
|
213
497
|
if (!choice)
|
|
214
498
|
continue;
|
|
215
499
|
// Fallback: some providers (e.g., Moonshot) return usage
|
|
@@ -226,47 +510,24 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
226
510
|
hasFinishReason = true;
|
|
227
511
|
}
|
|
228
512
|
if (choice.delta) {
|
|
513
|
+
const deltaFields = choice.delta;
|
|
514
|
+
const reasoningStart = deltaFields.reasoning_start;
|
|
515
|
+
if (reasoningStart) {
|
|
516
|
+
ensureThinkingBlock(extractReasoningSignature(reasoningStart) ?? "reasoning_content", extractReasoningMetadata(reasoningStart));
|
|
517
|
+
}
|
|
229
518
|
if (choice.delta.content !== null &&
|
|
230
519
|
choice.delta.content !== undefined &&
|
|
231
520
|
choice.delta.content.length > 0) {
|
|
232
|
-
|
|
233
|
-
block.text += choice.delta.content;
|
|
234
|
-
stream.push({
|
|
235
|
-
type: "text_delta",
|
|
236
|
-
contentIndex: getContentIndex(block),
|
|
237
|
-
delta: choice.delta.content,
|
|
238
|
-
partial: output,
|
|
239
|
-
});
|
|
521
|
+
emitContentDelta(choice.delta.content);
|
|
240
522
|
}
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const value = deltaFields[field];
|
|
250
|
-
if (typeof value === "string" && value.length > 0) {
|
|
251
|
-
foundReasoningField = field;
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (foundReasoningField) {
|
|
256
|
-
const delta = deltaFields[foundReasoningField];
|
|
257
|
-
if (typeof delta === "string" && delta.length > 0) {
|
|
258
|
-
const thinkingSignature = model.provider === "opencode-go" && foundReasoningField === "reasoning"
|
|
259
|
-
? "reasoning_content"
|
|
260
|
-
: foundReasoningField;
|
|
261
|
-
const block = ensureThinkingBlock(thinkingSignature);
|
|
262
|
-
block.thinking += delta;
|
|
263
|
-
stream.push({
|
|
264
|
-
type: "thinking_delta",
|
|
265
|
-
contentIndex: getContentIndex(block),
|
|
266
|
-
delta,
|
|
267
|
-
partial: output,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
523
|
+
// OpenAI-compatible providers do not agree on reasoning field names.
|
|
524
|
+
// Normalize chat deltas, Responses summary deltas, and reasoning_details
|
|
525
|
+
// into Spectral's single thinking stream, mirroring opencode's
|
|
526
|
+
// canonical `reasoning` message part.
|
|
527
|
+
const reasoningDelta = extractReasoningDelta(deltaFields);
|
|
528
|
+
if (reasoningDelta) {
|
|
529
|
+
emitThinkingDelta(reasoningDelta.field, reasoningDelta.delta, extractReasoningMetadata(deltaFields.reasoning_start) ??
|
|
530
|
+
extractReasoningMetadata(deltaFields.reasoning_end));
|
|
270
531
|
}
|
|
271
532
|
if (choice?.delta?.tool_calls) {
|
|
272
533
|
for (const toolCall of choice.delta.tool_calls) {
|
|
@@ -281,9 +542,11 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
281
542
|
let delta = "";
|
|
282
543
|
if (toolCall.function?.arguments) {
|
|
283
544
|
delta = toolCall.function.arguments;
|
|
284
|
-
block.partialArgs =
|
|
545
|
+
block.partialArgs =
|
|
546
|
+
(block.partialArgs ?? "") + toolCall.function.arguments;
|
|
285
547
|
block.arguments = parseStreamingJson(block.partialArgs);
|
|
286
548
|
}
|
|
549
|
+
sawStreamOutput = true;
|
|
287
550
|
stream.push({
|
|
288
551
|
type: "toolcall_delta",
|
|
289
552
|
contentIndex: getContentIndex(block),
|
|
@@ -292,10 +555,16 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
292
555
|
});
|
|
293
556
|
}
|
|
294
557
|
}
|
|
558
|
+
const reasoningEnd = deltaFields.reasoning_end;
|
|
559
|
+
if (reasoningEnd) {
|
|
560
|
+
updateThinkingSignature(reasoningEnd);
|
|
561
|
+
}
|
|
295
562
|
const reasoningDetails = choice.delta.reasoning_details;
|
|
296
563
|
if (reasoningDetails && Array.isArray(reasoningDetails)) {
|
|
297
564
|
for (const detail of reasoningDetails) {
|
|
298
|
-
if (detail.type === "reasoning.encrypted" &&
|
|
565
|
+
if (detail.type === "reasoning.encrypted" &&
|
|
566
|
+
detail.id &&
|
|
567
|
+
detail.data) {
|
|
299
568
|
const matchingToolCall = output.content.find((b) => b.type === "toolCall" && b.id === detail.id);
|
|
300
569
|
if (matchingToolCall) {
|
|
301
570
|
matchingToolCall.thoughtSignature = JSON.stringify(detail);
|
|
@@ -305,6 +574,15 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
305
574
|
}
|
|
306
575
|
}
|
|
307
576
|
}
|
|
577
|
+
if (inlineThinkingPending) {
|
|
578
|
+
if (inInlineThinking) {
|
|
579
|
+
emitThinkingDelta("inline_think", inlineThinkingPending);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
emitTextDelta(inlineThinkingPending);
|
|
583
|
+
}
|
|
584
|
+
inlineThinkingPending = "";
|
|
585
|
+
}
|
|
308
586
|
for (const block of blocks) {
|
|
309
587
|
finishBlock(block);
|
|
310
588
|
}
|
|
@@ -318,7 +596,12 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
318
596
|
throw new Error(output.errorMessage || "Provider returned an error stop reason");
|
|
319
597
|
}
|
|
320
598
|
if (!hasFinishReason) {
|
|
321
|
-
|
|
599
|
+
if (!sawStreamOutput && blocks.length === 0) {
|
|
600
|
+
throw new Error("Stream ended without finish_reason");
|
|
601
|
+
}
|
|
602
|
+
output.stopReason = blocks.some((block) => block.type === "toolCall")
|
|
603
|
+
? "toolUse"
|
|
604
|
+
: "stop";
|
|
322
605
|
}
|
|
323
606
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
324
607
|
stream.end();
|
|
@@ -331,7 +614,8 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
331
614
|
delete block.streamIndex;
|
|
332
615
|
}
|
|
333
616
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
334
|
-
output.errorMessage =
|
|
617
|
+
output.errorMessage =
|
|
618
|
+
error instanceof Error ? error.message : JSON.stringify(error);
|
|
335
619
|
// Some providers via OpenRouter give additional information in this field.
|
|
336
620
|
const rawMetadata = error?.error?.metadata?.raw;
|
|
337
621
|
if (rawMetadata)
|
|
@@ -348,9 +632,12 @@ export const streamSimpleOpenAICompletions = (model, context, options) => {
|
|
|
348
632
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
349
633
|
}
|
|
350
634
|
const base = buildBaseOptions(model, options, apiKey);
|
|
351
|
-
const clampedReasoning = options?.reasoning
|
|
635
|
+
const clampedReasoning = options?.reasoning
|
|
636
|
+
? clampThinkingLevel(model, options.reasoning)
|
|
637
|
+
: undefined;
|
|
352
638
|
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
|
|
353
|
-
const toolChoice = options
|
|
639
|
+
const toolChoice = options
|
|
640
|
+
?.toolChoice;
|
|
354
641
|
return streamOpenAICompletions(model, context, {
|
|
355
642
|
...base,
|
|
356
643
|
reasoningEffort,
|
|
@@ -455,17 +742,21 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
455
742
|
else if (compat.thinkingFormat === "qwen" && model.reasoning) {
|
|
456
743
|
params.enable_thinking = !!options?.reasoningEffort;
|
|
457
744
|
}
|
|
458
|
-
else if (compat.thinkingFormat === "qwen-chat-template" &&
|
|
745
|
+
else if (compat.thinkingFormat === "qwen-chat-template" &&
|
|
746
|
+
model.reasoning) {
|
|
459
747
|
params.chat_template_kwargs = {
|
|
460
748
|
enable_thinking: !!options?.reasoningEffort,
|
|
461
749
|
preserve_thinking: true,
|
|
462
750
|
};
|
|
463
751
|
}
|
|
464
752
|
else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
|
|
465
|
-
params.thinking = {
|
|
753
|
+
params.thinking = {
|
|
754
|
+
type: options?.reasoningEffort ? "enabled" : "disabled",
|
|
755
|
+
};
|
|
466
756
|
if (options?.reasoningEffort) {
|
|
467
757
|
params.reasoning_effort =
|
|
468
|
-
model.thinkingLevelMap?.[options.reasoningEffort] ??
|
|
758
|
+
model.thinkingLevelMap?.[options.reasoningEffort] ??
|
|
759
|
+
options.reasoningEffort;
|
|
469
760
|
}
|
|
470
761
|
}
|
|
471
762
|
else if (compat.thinkingFormat === "openrouter" && model.reasoning) {
|
|
@@ -473,36 +764,49 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
473
764
|
const openRouterParams = params;
|
|
474
765
|
if (options?.reasoningEffort) {
|
|
475
766
|
openRouterParams.reasoning = {
|
|
476
|
-
effort: model.thinkingLevelMap?.[options.reasoningEffort] ??
|
|
767
|
+
effort: model.thinkingLevelMap?.[options.reasoningEffort] ??
|
|
768
|
+
options.reasoningEffort,
|
|
477
769
|
};
|
|
478
770
|
}
|
|
479
771
|
else if (model.thinkingLevelMap?.off !== null) {
|
|
480
|
-
openRouterParams.reasoning = {
|
|
772
|
+
openRouterParams.reasoning = {
|
|
773
|
+
effort: model.thinkingLevelMap?.off ?? "none",
|
|
774
|
+
};
|
|
481
775
|
}
|
|
482
776
|
}
|
|
483
777
|
else if (compat.thinkingFormat === "together" && model.reasoning) {
|
|
484
778
|
const togetherParams = params;
|
|
485
779
|
togetherParams.reasoning = { enabled: !!options?.reasoningEffort };
|
|
486
780
|
if (options?.reasoningEffort && compat.supportsReasoningEffort) {
|
|
487
|
-
togetherParams.reasoning_effort =
|
|
781
|
+
togetherParams.reasoning_effort =
|
|
782
|
+
model.thinkingLevelMap?.[options.reasoningEffort] ??
|
|
783
|
+
options.reasoningEffort;
|
|
488
784
|
}
|
|
489
785
|
}
|
|
490
|
-
else if (options?.reasoningEffort &&
|
|
786
|
+
else if (options?.reasoningEffort &&
|
|
787
|
+
model.reasoning &&
|
|
788
|
+
compat.supportsReasoningEffort) {
|
|
491
789
|
// OpenAI-style reasoning_effort
|
|
492
|
-
params.reasoning_effort =
|
|
790
|
+
params.reasoning_effort =
|
|
791
|
+
model.thinkingLevelMap?.[options.reasoningEffort] ??
|
|
792
|
+
options.reasoningEffort;
|
|
493
793
|
}
|
|
494
|
-
else if (!options?.reasoningEffort &&
|
|
794
|
+
else if (!options?.reasoningEffort &&
|
|
795
|
+
model.reasoning &&
|
|
796
|
+
compat.supportsReasoningEffort) {
|
|
495
797
|
const offValue = model.thinkingLevelMap?.off;
|
|
496
798
|
if (typeof offValue === "string") {
|
|
497
799
|
params.reasoning_effort = offValue;
|
|
498
800
|
}
|
|
499
801
|
}
|
|
500
802
|
// OpenRouter provider routing preferences
|
|
501
|
-
if (model.baseUrl.includes("openrouter.ai") &&
|
|
803
|
+
if (model.baseUrl.includes("openrouter.ai") &&
|
|
804
|
+
model.compat?.openRouterRouting) {
|
|
502
805
|
params.provider = model.compat.openRouterRouting;
|
|
503
806
|
}
|
|
504
807
|
// Vercel AI Gateway provider routing preferences
|
|
505
|
-
if (model.baseUrl.includes("ai-gateway.vercel.sh") &&
|
|
808
|
+
if (model.baseUrl.includes("ai-gateway.vercel.sh") &&
|
|
809
|
+
model.compat?.vercelGatewayRouting) {
|
|
506
810
|
const routing = model.compat.vercelGatewayRouting;
|
|
507
811
|
if (routing.only || routing.order) {
|
|
508
812
|
const gatewayOptions = {};
|
|
@@ -519,7 +823,9 @@ function getCompatCacheControl(compat, cacheRetention) {
|
|
|
519
823
|
if (compat.cacheControlFormat !== "anthropic" || cacheRetention === "none") {
|
|
520
824
|
return undefined;
|
|
521
825
|
}
|
|
522
|
-
const ttl = cacheRetention === "long" && compat.supportsLongCacheRetention
|
|
826
|
+
const ttl = cacheRetention === "long" && compat.supportsLongCacheRetention
|
|
827
|
+
? "1h"
|
|
828
|
+
: undefined;
|
|
523
829
|
return { type: "ephemeral", ...(ttl ? { ttl } : {}) };
|
|
524
830
|
}
|
|
525
831
|
function applyAnthropicCacheControl(messages, tools, cacheControl) {
|
|
@@ -609,14 +915,19 @@ export function convertMessages(model, context, compat) {
|
|
|
609
915
|
if (context.systemPrompt) {
|
|
610
916
|
const useDeveloperRole = model.reasoning && compat.supportsDeveloperRole;
|
|
611
917
|
const role = useDeveloperRole ? "developer" : "system";
|
|
612
|
-
params.push({
|
|
918
|
+
params.push({
|
|
919
|
+
role: role,
|
|
920
|
+
content: sanitizeSurrogates(context.systemPrompt),
|
|
921
|
+
});
|
|
613
922
|
}
|
|
614
923
|
let lastRole = null;
|
|
615
924
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
616
925
|
const msg = transformedMessages[i];
|
|
617
926
|
// Some providers don't allow user messages directly after tool results
|
|
618
927
|
// Insert a synthetic assistant message to bridge the gap
|
|
619
|
-
if (compat.requiresAssistantAfterToolResult &&
|
|
928
|
+
if (compat.requiresAssistantAfterToolResult &&
|
|
929
|
+
lastRole === "toolResult" &&
|
|
930
|
+
msg.role === "user") {
|
|
620
931
|
params.push({
|
|
621
932
|
role: "assistant",
|
|
622
933
|
content: "I have processed the tool results.",
|
|
@@ -667,7 +978,9 @@ export function convertMessages(model, context, compat) {
|
|
|
667
978
|
type: "text",
|
|
668
979
|
text: sanitizeSurrogates(block.text),
|
|
669
980
|
}));
|
|
670
|
-
const assistantText = assistantTextParts
|
|
981
|
+
const assistantText = assistantTextParts
|
|
982
|
+
.map((part) => part.text)
|
|
983
|
+
.join("");
|
|
671
984
|
const nonEmptyThinkingBlocks = msg.content
|
|
672
985
|
.filter(isThinkingContentBlock)
|
|
673
986
|
.filter((block) => block.thinking.trim().length > 0);
|
|
@@ -677,7 +990,10 @@ export function convertMessages(model, context, compat) {
|
|
|
677
990
|
const thinkingText = nonEmptyThinkingBlocks
|
|
678
991
|
.map((block) => sanitizeSurrogates(block.thinking))
|
|
679
992
|
.join("\n\n");
|
|
680
|
-
assistantMsg.content = [
|
|
993
|
+
assistantMsg.content = [
|
|
994
|
+
{ type: "text", text: thinkingText },
|
|
995
|
+
...assistantTextParts,
|
|
996
|
+
];
|
|
681
997
|
}
|
|
682
998
|
else {
|
|
683
999
|
// Always send assistant content as a plain string (OpenAI Chat Completions
|
|
@@ -688,13 +1004,14 @@ export function convertMessages(model, context, compat) {
|
|
|
688
1004
|
if (assistantText.length > 0) {
|
|
689
1005
|
assistantMsg.content = assistantText;
|
|
690
1006
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
1007
|
+
const reasoningText = nonEmptyThinkingBlocks
|
|
1008
|
+
.map((block) => block.thinking)
|
|
1009
|
+
.join("\n");
|
|
1010
|
+
if (nonEmptyThinkingBlocks.some(hasEncryptedReasoningMetadata)) {
|
|
1011
|
+
setAssistantReasoningLifecycleSidecars(assistantMsg, nonEmptyThinkingBlocks);
|
|
695
1012
|
}
|
|
696
|
-
|
|
697
|
-
assistantMsg[
|
|
1013
|
+
else {
|
|
1014
|
+
setAssistantReasoningField(assistantMsg, normalizeReasoningReplayField(nonEmptyThinkingBlocks[0].thinkingSignature, model), reasoningText);
|
|
698
1015
|
}
|
|
699
1016
|
}
|
|
700
1017
|
}
|
|
@@ -733,7 +1050,8 @@ export function convertMessages(model, context, compat) {
|
|
|
733
1050
|
}
|
|
734
1051
|
if (compat.requiresReasoningContentOnAssistantMessages &&
|
|
735
1052
|
model.reasoning &&
|
|
736
|
-
assistantMsg.reasoning_content ===
|
|
1053
|
+
assistantMsg.reasoning_content ===
|
|
1054
|
+
undefined) {
|
|
737
1055
|
assistantMsg.reasoning_content = "";
|
|
738
1056
|
}
|
|
739
1057
|
// Skip assistant messages that have no content and no tool calls.
|
|
@@ -744,7 +1062,8 @@ export function convertMessages(model, context, compat) {
|
|
|
744
1062
|
const hasContent = content !== null &&
|
|
745
1063
|
content !== undefined &&
|
|
746
1064
|
(typeof content === "string" ? content.length > 0 : content.length > 0);
|
|
747
|
-
if (!hasContent && !assistantMsg.tool_calls
|
|
1065
|
+
if (!hasContent && !assistantMsg.tool_calls &&
|
|
1066
|
+
!assistantHasReasoningReplay(assistantMsg)) {
|
|
748
1067
|
continue;
|
|
749
1068
|
}
|
|
750
1069
|
params.push(assistantMsg);
|
|
@@ -752,7 +1071,8 @@ export function convertMessages(model, context, compat) {
|
|
|
752
1071
|
else if (msg.role === "toolResult") {
|
|
753
1072
|
const imageBlocks = [];
|
|
754
1073
|
let j = i;
|
|
755
|
-
for (; j < transformedMessages.length &&
|
|
1074
|
+
for (; j < transformedMessages.length &&
|
|
1075
|
+
transformedMessages[j].role === "toolResult"; j++) {
|
|
756
1076
|
const toolMsg = transformedMessages[j];
|
|
757
1077
|
// Extract text and image content
|
|
758
1078
|
const textResult = toolMsg.content
|
|
@@ -828,7 +1148,9 @@ function convertTools(tools, compat) {
|
|
|
828
1148
|
}
|
|
829
1149
|
function parseChunkUsage(rawUsage, model) {
|
|
830
1150
|
const promptTokens = rawUsage.prompt_tokens || 0;
|
|
831
|
-
const cacheReadTokens = rawUsage.prompt_tokens_details?.cached_tokens ??
|
|
1151
|
+
const cacheReadTokens = rawUsage.prompt_tokens_details?.cached_tokens ??
|
|
1152
|
+
rawUsage.prompt_cache_hit_tokens ??
|
|
1153
|
+
0;
|
|
832
1154
|
const cacheWriteTokens = rawUsage.prompt_tokens_details?.cache_write_tokens || 0;
|
|
833
1155
|
// Follow documented OpenAI/OpenRouter semantics: cached_tokens is cache-read
|
|
834
1156
|
// tokens (hits). OpenAI does not document or emit cache_write_tokens, but
|
|
@@ -865,9 +1187,15 @@ function mapStopReason(reason) {
|
|
|
865
1187
|
case "tool_calls":
|
|
866
1188
|
return { stopReason: "toolUse" };
|
|
867
1189
|
case "content_filter":
|
|
868
|
-
return {
|
|
1190
|
+
return {
|
|
1191
|
+
stopReason: "error",
|
|
1192
|
+
errorMessage: "Provider finish_reason: content_filter",
|
|
1193
|
+
};
|
|
869
1194
|
case "network_error":
|
|
870
|
-
return {
|
|
1195
|
+
return {
|
|
1196
|
+
stopReason: "error",
|
|
1197
|
+
errorMessage: "Provider finish_reason: network_error",
|
|
1198
|
+
};
|
|
871
1199
|
default:
|
|
872
1200
|
return {
|
|
873
1201
|
stopReason: "error",
|
|
@@ -884,8 +1212,12 @@ function detectCompat(model) {
|
|
|
884
1212
|
const provider = model.provider;
|
|
885
1213
|
const baseUrl = model.baseUrl;
|
|
886
1214
|
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
|
|
887
|
-
const isTogether = provider === "together" ||
|
|
888
|
-
|
|
1215
|
+
const isTogether = provider === "together" ||
|
|
1216
|
+
baseUrl.includes("api.together.ai") ||
|
|
1217
|
+
baseUrl.includes("api.together.xyz");
|
|
1218
|
+
const isMoonshot = provider === "moonshotai" ||
|
|
1219
|
+
provider === "moonshotai-cn" ||
|
|
1220
|
+
baseUrl.includes("api.moonshot.");
|
|
889
1221
|
// Cloudflare providers not supported (cloudflare.ts was removed)
|
|
890
1222
|
const isCloudflareWorkersAI = false;
|
|
891
1223
|
const isCloudflareAiGateway = false;
|
|
@@ -902,10 +1234,15 @@ function detectCompat(model) {
|
|
|
902
1234
|
baseUrl.includes("opencode.ai") ||
|
|
903
1235
|
isCloudflareWorkersAI ||
|
|
904
1236
|
isCloudflareAiGateway;
|
|
905
|
-
const useMaxTokens = baseUrl.includes("chutes.ai") ||
|
|
1237
|
+
const useMaxTokens = baseUrl.includes("chutes.ai") ||
|
|
1238
|
+
isMoonshot ||
|
|
1239
|
+
isCloudflareAiGateway ||
|
|
1240
|
+
isTogether;
|
|
906
1241
|
const isGrok = provider === "xai" || baseUrl.includes("api.x.ai");
|
|
907
1242
|
const isDeepSeek = provider === "deepseek" || baseUrl.includes("deepseek.com");
|
|
908
|
-
const cacheControlFormat = isOpenRouterAnthropicModel(model)
|
|
1243
|
+
const cacheControlFormat = isOpenRouterAnthropicModel(model)
|
|
1244
|
+
? "anthropic"
|
|
1245
|
+
: undefined;
|
|
909
1246
|
return {
|
|
910
1247
|
supportsStore: !isNonStandard,
|
|
911
1248
|
supportsDeveloperRole: !isNonStandard,
|
|
@@ -931,7 +1268,9 @@ function detectCompat(model) {
|
|
|
931
1268
|
supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway,
|
|
932
1269
|
cacheControlFormat,
|
|
933
1270
|
sendSessionAffinityHeaders: isOpenRouterModel(model),
|
|
934
|
-
supportsLongCacheRetention: !(isTogether ||
|
|
1271
|
+
supportsLongCacheRetention: !(isTogether ||
|
|
1272
|
+
isCloudflareWorkersAI ||
|
|
1273
|
+
isCloudflareAiGateway),
|
|
935
1274
|
};
|
|
936
1275
|
}
|
|
937
1276
|
/**
|
|
@@ -946,10 +1285,12 @@ function getCompat(model) {
|
|
|
946
1285
|
supportsStore: model.compat.supportsStore ?? detected.supportsStore,
|
|
947
1286
|
supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
|
|
948
1287
|
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
|
|
949
|
-
supportsUsageInStreaming: model.compat.supportsUsageInStreaming ??
|
|
1288
|
+
supportsUsageInStreaming: model.compat.supportsUsageInStreaming ??
|
|
1289
|
+
detected.supportsUsageInStreaming,
|
|
950
1290
|
maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
|
|
951
1291
|
requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
|
|
952
|
-
requiresAssistantAfterToolResult: model.compat.requiresAssistantAfterToolResult ??
|
|
1292
|
+
requiresAssistantAfterToolResult: model.compat.requiresAssistantAfterToolResult ??
|
|
1293
|
+
detected.requiresAssistantAfterToolResult,
|
|
953
1294
|
requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
|
|
954
1295
|
requiresReasoningContentOnAssistantMessages: model.compat.requiresReasoningContentOnAssistantMessages ??
|
|
955
1296
|
detected.requiresReasoningContentOnAssistantMessages,
|
|
@@ -959,7 +1300,9 @@ function getCompat(model) {
|
|
|
959
1300
|
zaiToolStream: model.compat.zaiToolStream ?? detected.zaiToolStream,
|
|
960
1301
|
supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
|
|
961
1302
|
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
|
|
962
|
-
sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ??
|
|
963
|
-
|
|
1303
|
+
sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ??
|
|
1304
|
+
detected.sendSessionAffinityHeaders,
|
|
1305
|
+
supportsLongCacheRetention: model.compat.supportsLongCacheRetention ??
|
|
1306
|
+
detected.supportsLongCacheRetention,
|
|
964
1307
|
};
|
|
965
1308
|
}
|