@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.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +29 -24
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -1
- package/dist/env-api-keys.js.map +1 -1
- package/dist/image-models.generated.d.ts +225 -0
- package/dist/image-models.generated.d.ts.map +1 -1
- package/dist/image-models.generated.js +227 -2
- package/dist/image-models.generated.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +37 -2
- package/dist/models.d.ts.map +1 -1
- package/dist/models.generated.d.ts +6120 -2593
- package/dist/models.generated.d.ts.map +1 -1
- package/dist/models.generated.js +5347 -2110
- package/dist/models.generated.js.map +1 -1
- package/dist/models.js +259 -9
- package/dist/models.js.map +1 -1
- package/dist/oauth.d.ts +6 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +4 -1
- package/dist/oauth.js.map +1 -1
- package/dist/providers/amazon-bedrock.d.ts +5 -4
- package/dist/providers/amazon-bedrock.d.ts.map +1 -1
- package/dist/providers/amazon-bedrock.js +303 -330
- package/dist/providers/amazon-bedrock.js.map +1 -1
- package/dist/providers/anthropic.d.ts +5 -4
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +492 -499
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/azure-openai-responses.d.ts +4 -2
- package/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/dist/providers/azure-openai-responses.js +52 -35
- package/dist/providers/azure-openai-responses.js.map +1 -1
- package/dist/providers/faux.d.ts +19 -22
- package/dist/providers/faux.d.ts.map +1 -1
- package/dist/providers/faux.js +106 -70
- package/dist/providers/faux.js.map +1 -1
- package/dist/providers/github-copilot-headers.d.ts +7 -4
- package/dist/providers/github-copilot-headers.d.ts.map +1 -1
- package/dist/providers/github-copilot-headers.js.map +1 -1
- package/dist/providers/google-shared.d.ts +24 -5
- package/dist/providers/google-shared.d.ts.map +1 -1
- package/dist/providers/google-shared.js +139 -140
- package/dist/providers/google-shared.js.map +1 -1
- package/dist/providers/google-vertex.d.ts +3 -7
- package/dist/providers/google-vertex.d.ts.map +1 -1
- package/dist/providers/google-vertex.js +216 -219
- package/dist/providers/google-vertex.js.map +1 -1
- package/dist/providers/google.d.ts +4 -7
- package/dist/providers/google.d.ts.map +1 -1
- package/dist/providers/google.js +214 -218
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/images/openrouter.d.ts.map +1 -1
- package/dist/providers/images/openrouter.js +37 -27
- package/dist/providers/images/openrouter.js.map +1 -1
- package/dist/providers/mistral.d.ts +7 -6
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +287 -265
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/openai-codex-responses.d.ts +8 -5
- package/dist/providers/openai-codex-responses.d.ts.map +1 -1
- package/dist/providers/openai-codex-responses.js +493 -391
- package/dist/providers/openai-codex-responses.js.map +1 -1
- package/dist/providers/openai-completions.d.ts +7 -10
- package/dist/providers/openai-completions.d.ts.map +1 -1
- package/dist/providers/openai-completions.js +670 -664
- package/dist/providers/openai-completions.js.map +1 -1
- package/dist/providers/openai-responses-shared.d.ts +5 -4
- package/dist/providers/openai-responses-shared.d.ts.map +1 -1
- package/dist/providers/openai-responses-shared.js +390 -360
- package/dist/providers/openai-responses-shared.js.map +1 -1
- package/dist/providers/openai-responses.d.ts +5 -5
- package/dist/providers/openai-responses.d.ts.map +1 -1
- package/dist/providers/openai-responses.js +134 -109
- package/dist/providers/openai-responses.js.map +1 -1
- package/dist/providers/simple-options.d.ts +4 -2
- package/dist/providers/simple-options.d.ts.map +1 -1
- package/dist/providers/simple-options.js.map +1 -1
- package/dist/providers/transform-messages.d.ts.map +1 -1
- package/dist/providers/transform-messages.js +112 -137
- package/dist/providers/transform-messages.js.map +1 -1
- package/dist/stream.d.ts +0 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +0 -1
- package/dist/stream.js.map +1 -1
- package/dist/types.d.ts +91 -63
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/error-body.d.ts.map +1 -1
- package/dist/utils/error-body.js +15 -4
- package/dist/utils/error-body.js.map +1 -1
- package/dist/utils/estimate.d.ts +4 -0
- package/dist/utils/estimate.d.ts.map +1 -1
- package/dist/utils/estimate.js +3 -1
- package/dist/utils/estimate.js.map +1 -1
- package/dist/utils/headers.d.ts +3 -0
- package/dist/utils/headers.d.ts.map +1 -1
- package/dist/utils/headers.js +21 -0
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/json-parse.d.ts.map +1 -1
- package/dist/utils/json-parse.js +17 -19
- package/dist/utils/json-parse.js.map +1 -1
- package/dist/utils/oauth/anthropic.d.ts +4 -11
- package/dist/utils/oauth/anthropic.d.ts.map +1 -1
- package/dist/utils/oauth/anthropic.js +100 -117
- package/dist/utils/oauth/anthropic.js.map +1 -1
- package/dist/utils/oauth/device-code.d.ts.map +1 -1
- package/dist/utils/oauth/device-code.js +51 -38
- package/dist/utils/oauth/device-code.js.map +1 -1
- package/dist/utils/oauth/github-copilot.d.ts +6 -9
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/dist/utils/oauth/github-copilot.js +47 -42
- package/dist/utils/oauth/github-copilot.js.map +1 -1
- package/dist/utils/oauth/index.d.ts +8 -17
- package/dist/utils/oauth/index.d.ts.map +1 -1
- package/dist/utils/oauth/index.js +27 -23
- package/dist/utils/oauth/index.js.map +1 -1
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -1
- package/dist/utils/oauth/oauth-page.js.map +1 -1
- package/dist/utils/oauth/openai-codex.d.ts +7 -12
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
- package/dist/utils/oauth/openai-codex.js +72 -89
- package/dist/utils/oauth/openai-codex.js.map +1 -1
- package/dist/utils/oauth/pkce.d.ts +5 -4
- package/dist/utils/oauth/pkce.d.ts.map +1 -1
- package/dist/utils/oauth/pkce.js.map +1 -1
- package/dist/utils/oauth/types.d.ts +7 -2
- package/dist/utils/oauth/types.d.ts.map +1 -1
- package/dist/utils/oauth/types.js.map +1 -1
- package/dist/utils/provider-retry.d.ts +16 -0
- package/dist/utils/provider-retry.d.ts.map +1 -0
- package/dist/utils/provider-retry.js +94 -0
- package/dist/utils/provider-retry.js.map +1 -0
- package/dist/utils/retry.d.ts +14 -0
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +84 -0
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/typebox-helpers.d.ts +5 -4
- package/dist/utils/typebox-helpers.d.ts.map +1 -1
- package/dist/utils/typebox-helpers.js.map +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +102 -99
- package/dist/utils/validation.js.map +1 -1
- package/package.json +2 -2
|
@@ -4,6 +4,7 @@ import { calculateCost } from "../models.js";
|
|
|
4
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
5
|
import { headersToRecord } from "../utils/headers.js";
|
|
6
6
|
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
|
|
7
|
+
import { retryProviderRequest } from "../utils/provider-retry.js";
|
|
7
8
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
8
9
|
import { resolveCloudflareBaseUrl } from "./cloudflare.js";
|
|
9
10
|
import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
|
|
@@ -69,9 +70,6 @@ const fromClaudeCodeName = (name, tools) => {
|
|
|
69
70
|
}
|
|
70
71
|
return name;
|
|
71
72
|
};
|
|
72
|
-
/**
|
|
73
|
-
* Convert content blocks to Anthropic API format
|
|
74
|
-
*/
|
|
75
73
|
function convertContentBlocks(content) {
|
|
76
74
|
// If only text blocks, return as concatenated string for simplicity
|
|
77
75
|
const hasImages = content.some((c) => c.type === "image");
|
|
@@ -199,6 +197,17 @@ function consumeLine(text) {
|
|
|
199
197
|
rest: text.slice(nextIndex),
|
|
200
198
|
};
|
|
201
199
|
}
|
|
200
|
+
function* decodeCompleteSseLines(buffer, state) {
|
|
201
|
+
let consumed = consumeLine(buffer);
|
|
202
|
+
while (consumed) {
|
|
203
|
+
buffer = consumed.rest;
|
|
204
|
+
const event = decodeSseLine(consumed.line, state);
|
|
205
|
+
if (event)
|
|
206
|
+
yield event;
|
|
207
|
+
consumed = consumeLine(buffer);
|
|
208
|
+
}
|
|
209
|
+
return buffer;
|
|
210
|
+
}
|
|
202
211
|
async function* iterateSseMessages(body, signal) {
|
|
203
212
|
const reader = body.getReader();
|
|
204
213
|
const decoder = new TextDecoder();
|
|
@@ -214,26 +223,10 @@ async function* iterateSseMessages(body, signal) {
|
|
|
214
223
|
break;
|
|
215
224
|
}
|
|
216
225
|
buffer += decoder.decode(value, { stream: true });
|
|
217
|
-
|
|
218
|
-
while (consumed) {
|
|
219
|
-
buffer = consumed.rest;
|
|
220
|
-
const event = decodeSseLine(consumed.line, state);
|
|
221
|
-
if (event) {
|
|
222
|
-
yield event;
|
|
223
|
-
}
|
|
224
|
-
consumed = consumeLine(buffer);
|
|
225
|
-
}
|
|
226
|
+
buffer = (yield* decodeCompleteSseLines(buffer, state));
|
|
226
227
|
}
|
|
227
228
|
buffer += decoder.decode();
|
|
228
|
-
|
|
229
|
-
while (consumed) {
|
|
230
|
-
buffer = consumed.rest;
|
|
231
|
-
const event = decodeSseLine(consumed.line, state);
|
|
232
|
-
if (event) {
|
|
233
|
-
yield event;
|
|
234
|
-
}
|
|
235
|
-
consumed = consumeLine(buffer);
|
|
236
|
-
}
|
|
229
|
+
buffer = (yield* decodeCompleteSseLines(buffer, state));
|
|
237
230
|
if (buffer.length > 0) {
|
|
238
231
|
const event = decodeSseLine(buffer, state);
|
|
239
232
|
if (event) {
|
|
@@ -249,12 +242,25 @@ async function* iterateSseMessages(body, signal) {
|
|
|
249
242
|
reader.releaseLock();
|
|
250
243
|
}
|
|
251
244
|
}
|
|
245
|
+
function* decodeAnthropicSseEvent(sse, lifecycle) {
|
|
246
|
+
try {
|
|
247
|
+
const event = parseJsonWithRepair(sse.data);
|
|
248
|
+
if (event.type === "message_start")
|
|
249
|
+
lifecycle.sawStart = true;
|
|
250
|
+
else if (event.type === "message_stop")
|
|
251
|
+
lifecycle.sawEnd = true;
|
|
252
|
+
yield event;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
256
|
+
throw new Error(`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join("\\n")}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
252
259
|
async function* iterateAnthropicEvents(response, signal) {
|
|
253
260
|
if (!response.body) {
|
|
254
261
|
throw new Error("Attempted to iterate over an Anthropic response with no body");
|
|
255
262
|
}
|
|
256
|
-
|
|
257
|
-
let sawMessageEnd = false;
|
|
263
|
+
const lifecycle = { sawStart: false, sawEnd: false };
|
|
258
264
|
for await (const sse of iterateSseMessages(response.body, signal)) {
|
|
259
265
|
if (sse.event === "error") {
|
|
260
266
|
throw new Error(sse.data);
|
|
@@ -262,270 +268,283 @@ async function* iterateAnthropicEvents(response, signal) {
|
|
|
262
268
|
if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) {
|
|
263
269
|
continue;
|
|
264
270
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
271
|
+
yield* decodeAnthropicSseEvent(sse, lifecycle);
|
|
272
|
+
}
|
|
273
|
+
if (lifecycle.sawStart && !lifecycle.sawEnd) {
|
|
274
|
+
throw new Error("Anthropic stream ended before message_stop");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function createAnthropicStreamState(model, context, stream) {
|
|
278
|
+
const output = {
|
|
279
|
+
role: "assistant",
|
|
280
|
+
content: [],
|
|
281
|
+
api: model.api,
|
|
282
|
+
provider: model.provider,
|
|
283
|
+
model: model.id,
|
|
284
|
+
usage: {
|
|
285
|
+
input: 0,
|
|
286
|
+
output: 0,
|
|
287
|
+
cacheRead: 0,
|
|
288
|
+
cacheWrite: 0,
|
|
289
|
+
totalTokens: 0,
|
|
290
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
291
|
+
},
|
|
292
|
+
stopReason: "stop",
|
|
293
|
+
timestamp: Date.now(),
|
|
294
|
+
};
|
|
295
|
+
return { output, blocks: output.content, stream, model, context, isOAuth: false };
|
|
296
|
+
}
|
|
297
|
+
function handleAnthropicMessageStart(event, state) {
|
|
298
|
+
state.output.responseId = event.message.id;
|
|
299
|
+
state.output.usage.input = event.message.usage.input_tokens || 0;
|
|
300
|
+
state.output.usage.output = event.message.usage.output_tokens || 0;
|
|
301
|
+
state.output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
|
|
302
|
+
state.output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
|
|
303
|
+
state.output.usage.totalTokens =
|
|
304
|
+
state.output.usage.input +
|
|
305
|
+
state.output.usage.output +
|
|
306
|
+
state.output.usage.cacheRead +
|
|
307
|
+
state.output.usage.cacheWrite;
|
|
308
|
+
calculateCost(state.model, state.output.usage);
|
|
309
|
+
}
|
|
310
|
+
function handleAnthropicContentBlockStart(event, state) {
|
|
311
|
+
const contentIndex = state.output.content.length;
|
|
312
|
+
switch (event.content_block.type) {
|
|
313
|
+
case "text": {
|
|
314
|
+
const block = {
|
|
315
|
+
type: "text",
|
|
316
|
+
text: event.content_block.text ?? "",
|
|
317
|
+
index: event.index,
|
|
318
|
+
};
|
|
319
|
+
state.output.content.push(block);
|
|
320
|
+
state.stream.push({ type: "text_start", contentIndex, partial: state.output });
|
|
321
|
+
return;
|
|
274
322
|
}
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
323
|
+
case "thinking": {
|
|
324
|
+
const block = {
|
|
325
|
+
type: "thinking",
|
|
326
|
+
thinking: event.content_block.thinking ?? "",
|
|
327
|
+
thinkingSignature: event.content_block.signature ?? "",
|
|
328
|
+
index: event.index,
|
|
329
|
+
};
|
|
330
|
+
state.output.content.push(block);
|
|
331
|
+
state.stream.push({ type: "thinking_start", contentIndex, partial: state.output });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
case "redacted_thinking": {
|
|
335
|
+
const block = {
|
|
336
|
+
type: "thinking",
|
|
337
|
+
thinking: "[Reasoning redacted]",
|
|
338
|
+
thinkingSignature: event.content_block.data,
|
|
339
|
+
redacted: true,
|
|
340
|
+
index: event.index,
|
|
341
|
+
};
|
|
342
|
+
state.output.content.push(block);
|
|
343
|
+
state.stream.push({ type: "thinking_start", contentIndex, partial: state.output });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
case "tool_use": {
|
|
347
|
+
const block = {
|
|
348
|
+
type: "toolCall",
|
|
349
|
+
id: event.content_block.id,
|
|
350
|
+
name: state.isOAuth
|
|
351
|
+
? fromClaudeCodeName(event.content_block.name, state.context.tools)
|
|
352
|
+
: event.content_block.name,
|
|
353
|
+
arguments: event.content_block.input ?? {},
|
|
354
|
+
partialJson: "",
|
|
355
|
+
index: event.index,
|
|
356
|
+
};
|
|
357
|
+
state.output.content.push(block);
|
|
358
|
+
state.stream.push({ type: "toolcall_start", contentIndex, partial: state.output });
|
|
278
359
|
}
|
|
279
360
|
}
|
|
280
|
-
|
|
281
|
-
|
|
361
|
+
}
|
|
362
|
+
function handleAnthropicContentBlockDelta(event, state) {
|
|
363
|
+
const index = state.blocks.findIndex((block) => block.index === event.index);
|
|
364
|
+
const block = state.blocks[index];
|
|
365
|
+
if (!block)
|
|
366
|
+
return;
|
|
367
|
+
switch (event.delta.type) {
|
|
368
|
+
case "text_delta":
|
|
369
|
+
if (block.type === "text") {
|
|
370
|
+
block.text += event.delta.text;
|
|
371
|
+
state.stream.push({
|
|
372
|
+
type: "text_delta",
|
|
373
|
+
contentIndex: index,
|
|
374
|
+
delta: event.delta.text,
|
|
375
|
+
partial: state.output,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return;
|
|
379
|
+
case "thinking_delta":
|
|
380
|
+
if (block.type === "thinking") {
|
|
381
|
+
block.thinking += event.delta.thinking;
|
|
382
|
+
state.stream.push({
|
|
383
|
+
type: "thinking_delta",
|
|
384
|
+
contentIndex: index,
|
|
385
|
+
delta: event.delta.thinking,
|
|
386
|
+
partial: state.output,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
case "input_json_delta":
|
|
391
|
+
if (block.type === "toolCall") {
|
|
392
|
+
block.partialJson = (block.partialJson ?? "") + event.delta.partial_json;
|
|
393
|
+
block.arguments = parseStreamingJson(block.partialJson);
|
|
394
|
+
state.stream.push({
|
|
395
|
+
type: "toolcall_delta",
|
|
396
|
+
contentIndex: index,
|
|
397
|
+
delta: event.delta.partial_json,
|
|
398
|
+
partial: state.output,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
case "signature_delta":
|
|
403
|
+
if (block.type === "thinking") {
|
|
404
|
+
block.thinkingSignature = (block.thinkingSignature || "") + event.delta.signature;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function handleAnthropicContentBlockStop(event, state) {
|
|
409
|
+
const index = state.blocks.findIndex((block) => block.index === event.index);
|
|
410
|
+
const block = state.blocks[index];
|
|
411
|
+
if (!block)
|
|
412
|
+
return;
|
|
413
|
+
delete block.index;
|
|
414
|
+
switch (block.type) {
|
|
415
|
+
case "text":
|
|
416
|
+
state.stream.push({
|
|
417
|
+
type: "text_end",
|
|
418
|
+
contentIndex: index,
|
|
419
|
+
content: block.text,
|
|
420
|
+
partial: state.output,
|
|
421
|
+
});
|
|
422
|
+
return;
|
|
423
|
+
case "thinking":
|
|
424
|
+
state.stream.push({
|
|
425
|
+
type: "thinking_end",
|
|
426
|
+
contentIndex: index,
|
|
427
|
+
content: block.thinking,
|
|
428
|
+
partial: state.output,
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
case "toolCall":
|
|
432
|
+
block.arguments = parseStreamingJson(block.partialJson ?? "");
|
|
433
|
+
delete block.partialJson;
|
|
434
|
+
state.stream.push({
|
|
435
|
+
type: "toolcall_end",
|
|
436
|
+
contentIndex: index,
|
|
437
|
+
toolCall: block,
|
|
438
|
+
partial: state.output,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function handleAnthropicMessageDelta(event, state) {
|
|
443
|
+
if (event.delta.stop_reason)
|
|
444
|
+
state.output.stopReason = mapStopReason(event.delta.stop_reason);
|
|
445
|
+
if (event.usage.input_tokens != null)
|
|
446
|
+
state.output.usage.input = event.usage.input_tokens;
|
|
447
|
+
if (event.usage.output_tokens != null)
|
|
448
|
+
state.output.usage.output = event.usage.output_tokens;
|
|
449
|
+
if (event.usage.cache_read_input_tokens != null) {
|
|
450
|
+
state.output.usage.cacheRead = event.usage.cache_read_input_tokens;
|
|
451
|
+
}
|
|
452
|
+
if (event.usage.cache_creation_input_tokens != null) {
|
|
453
|
+
state.output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
|
454
|
+
}
|
|
455
|
+
state.output.usage.totalTokens =
|
|
456
|
+
state.output.usage.input +
|
|
457
|
+
state.output.usage.output +
|
|
458
|
+
state.output.usage.cacheRead +
|
|
459
|
+
state.output.usage.cacheWrite;
|
|
460
|
+
calculateCost(state.model, state.output.usage);
|
|
461
|
+
}
|
|
462
|
+
function handleAnthropicStreamEvent(event, state) {
|
|
463
|
+
switch (event.type) {
|
|
464
|
+
case "message_start":
|
|
465
|
+
handleAnthropicMessageStart(event, state);
|
|
466
|
+
return;
|
|
467
|
+
case "content_block_start":
|
|
468
|
+
handleAnthropicContentBlockStart(event, state);
|
|
469
|
+
return;
|
|
470
|
+
case "content_block_delta":
|
|
471
|
+
handleAnthropicContentBlockDelta(event, state);
|
|
472
|
+
return;
|
|
473
|
+
case "content_block_stop":
|
|
474
|
+
handleAnthropicContentBlockStop(event, state);
|
|
475
|
+
return;
|
|
476
|
+
case "message_delta":
|
|
477
|
+
handleAnthropicMessageDelta(event, state);
|
|
282
478
|
}
|
|
283
479
|
}
|
|
480
|
+
function resolveAnthropicStreamClient(model, context, options) {
|
|
481
|
+
if (options?.client)
|
|
482
|
+
return { client: options.client, isOAuthToken: false };
|
|
483
|
+
const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
|
484
|
+
const copilotDynamicHeaders = model.provider === "github-copilot"
|
|
485
|
+
? buildCopilotDynamicHeaders({
|
|
486
|
+
messages: context.messages,
|
|
487
|
+
hasImages: hasCopilotVisionInput(context.messages),
|
|
488
|
+
})
|
|
489
|
+
: undefined;
|
|
490
|
+
const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
|
|
491
|
+
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
492
|
+
return createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
|
|
493
|
+
}
|
|
494
|
+
function validateAnthropicStreamCompletion(output, signal) {
|
|
495
|
+
if (signal?.aborted)
|
|
496
|
+
throw new Error("Request was aborted");
|
|
497
|
+
if (output.stopReason === "pending")
|
|
498
|
+
throw new Error("Anthropic stream ended without a stop reason");
|
|
499
|
+
if (output.stopReason === "aborted" || output.stopReason === "error")
|
|
500
|
+
throw new Error("An unknown error occurred");
|
|
501
|
+
}
|
|
502
|
+
async function executeAnthropicStream(state, options) {
|
|
503
|
+
const configuredClient = resolveAnthropicStreamClient(state.model, state.context, options);
|
|
504
|
+
state.isOAuth = configuredClient.isOAuthToken;
|
|
505
|
+
let params = buildParams(state.model, state.context, state.isOAuth, options);
|
|
506
|
+
const nextParams = await options?.onPayload?.(params, state.model);
|
|
507
|
+
if (nextParams !== undefined)
|
|
508
|
+
params = nextParams;
|
|
509
|
+
const requestOptions = {
|
|
510
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
511
|
+
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
|
|
512
|
+
maxRetries: 0,
|
|
513
|
+
};
|
|
514
|
+
const response = await retryProviderRequest(() => configuredClient.client.messages.create({ ...params, stream: true }, requestOptions).asResponse(), {
|
|
515
|
+
maxRetries: options?.maxRetries,
|
|
516
|
+
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
517
|
+
signal: options?.signal,
|
|
518
|
+
});
|
|
519
|
+
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, state.model);
|
|
520
|
+
state.stream.push({ type: "start", partial: state.output });
|
|
521
|
+
for await (const event of iterateAnthropicEvents(response, options?.signal)) {
|
|
522
|
+
handleAnthropicStreamEvent(event, state);
|
|
523
|
+
}
|
|
524
|
+
validateAnthropicStreamCompletion(state.output, options?.signal);
|
|
525
|
+
state.stream.push({ type: "done", reason: state.output.stopReason, message: state.output });
|
|
526
|
+
state.stream.end();
|
|
527
|
+
}
|
|
528
|
+
function handleAnthropicStreamFailure(error, options, state) {
|
|
529
|
+
for (const block of state.blocks) {
|
|
530
|
+
delete block.index;
|
|
531
|
+
if (block.type === "toolCall")
|
|
532
|
+
delete block.partialJson;
|
|
533
|
+
}
|
|
534
|
+
state.output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
535
|
+
state.output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
536
|
+
state.stream.push({ type: "error", reason: state.output.stopReason, error: state.output });
|
|
537
|
+
state.stream.end();
|
|
538
|
+
}
|
|
284
539
|
export const streamAnthropic = (model, context, options) => {
|
|
285
540
|
const stream = new AssistantMessageEventStream();
|
|
541
|
+
const state = createAnthropicStreamState(model, context, stream);
|
|
286
542
|
(async () => {
|
|
287
|
-
const output = {
|
|
288
|
-
role: "assistant",
|
|
289
|
-
content: [],
|
|
290
|
-
api: model.api,
|
|
291
|
-
provider: model.provider,
|
|
292
|
-
model: model.id,
|
|
293
|
-
usage: {
|
|
294
|
-
input: 0,
|
|
295
|
-
output: 0,
|
|
296
|
-
cacheRead: 0,
|
|
297
|
-
cacheWrite: 0,
|
|
298
|
-
totalTokens: 0,
|
|
299
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
300
|
-
},
|
|
301
|
-
stopReason: "stop",
|
|
302
|
-
timestamp: Date.now(),
|
|
303
|
-
};
|
|
304
543
|
try {
|
|
305
|
-
|
|
306
|
-
let isOAuth;
|
|
307
|
-
if (options?.client) {
|
|
308
|
-
client = options.client;
|
|
309
|
-
isOAuth = false;
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
|
313
|
-
let copilotDynamicHeaders;
|
|
314
|
-
if (model.provider === "github-copilot") {
|
|
315
|
-
const hasImages = hasCopilotVisionInput(context.messages);
|
|
316
|
-
copilotDynamicHeaders = buildCopilotDynamicHeaders({
|
|
317
|
-
messages: context.messages,
|
|
318
|
-
hasImages,
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
|
|
322
|
-
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
323
|
-
const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
|
|
324
|
-
client = created.client;
|
|
325
|
-
isOAuth = created.isOAuthToken;
|
|
326
|
-
}
|
|
327
|
-
let params = buildParams(model, context, isOAuth, options);
|
|
328
|
-
const nextParams = await options?.onPayload?.(params, model);
|
|
329
|
-
if (nextParams !== undefined) {
|
|
330
|
-
params = nextParams;
|
|
331
|
-
}
|
|
332
|
-
const requestOptions = {
|
|
333
|
-
...(options?.signal ? { signal: options.signal } : {}),
|
|
334
|
-
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
|
|
335
|
-
maxRetries: options?.maxRetries ?? 0,
|
|
336
|
-
};
|
|
337
|
-
const response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse();
|
|
338
|
-
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
339
|
-
stream.push({ type: "start", partial: output });
|
|
340
|
-
const blocks = output.content;
|
|
341
|
-
for await (const event of iterateAnthropicEvents(response, options?.signal)) {
|
|
342
|
-
if (event.type === "message_start") {
|
|
343
|
-
output.responseId = event.message.id;
|
|
344
|
-
// Capture initial token usage from message_start event
|
|
345
|
-
// This ensures we have input token counts even if the stream is aborted early
|
|
346
|
-
output.usage.input = event.message.usage.input_tokens || 0;
|
|
347
|
-
output.usage.output = event.message.usage.output_tokens || 0;
|
|
348
|
-
output.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;
|
|
349
|
-
output.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;
|
|
350
|
-
// Anthropic doesn't provide total_tokens, compute from components
|
|
351
|
-
output.usage.totalTokens =
|
|
352
|
-
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
353
|
-
calculateCost(model, output.usage);
|
|
354
|
-
}
|
|
355
|
-
else if (event.type === "content_block_start") {
|
|
356
|
-
if (event.content_block.type === "text") {
|
|
357
|
-
const block = {
|
|
358
|
-
type: "text",
|
|
359
|
-
text: "",
|
|
360
|
-
index: event.index,
|
|
361
|
-
};
|
|
362
|
-
output.content.push(block);
|
|
363
|
-
stream.push({ type: "text_start", contentIndex: output.content.length - 1, partial: output });
|
|
364
|
-
}
|
|
365
|
-
else if (event.content_block.type === "thinking") {
|
|
366
|
-
const block = {
|
|
367
|
-
type: "thinking",
|
|
368
|
-
thinking: "",
|
|
369
|
-
thinkingSignature: "",
|
|
370
|
-
index: event.index,
|
|
371
|
-
};
|
|
372
|
-
output.content.push(block);
|
|
373
|
-
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
374
|
-
}
|
|
375
|
-
else if (event.content_block.type === "redacted_thinking") {
|
|
376
|
-
const block = {
|
|
377
|
-
type: "thinking",
|
|
378
|
-
thinking: "[Reasoning redacted]",
|
|
379
|
-
thinkingSignature: event.content_block.data,
|
|
380
|
-
redacted: true,
|
|
381
|
-
index: event.index,
|
|
382
|
-
};
|
|
383
|
-
output.content.push(block);
|
|
384
|
-
stream.push({ type: "thinking_start", contentIndex: output.content.length - 1, partial: output });
|
|
385
|
-
}
|
|
386
|
-
else if (event.content_block.type === "tool_use") {
|
|
387
|
-
const block = {
|
|
388
|
-
type: "toolCall",
|
|
389
|
-
id: event.content_block.id,
|
|
390
|
-
name: isOAuth
|
|
391
|
-
? fromClaudeCodeName(event.content_block.name, context.tools)
|
|
392
|
-
: event.content_block.name,
|
|
393
|
-
arguments: event.content_block.input ?? {},
|
|
394
|
-
partialJson: "",
|
|
395
|
-
index: event.index,
|
|
396
|
-
};
|
|
397
|
-
output.content.push(block);
|
|
398
|
-
stream.push({ type: "toolcall_start", contentIndex: output.content.length - 1, partial: output });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
else if (event.type === "content_block_delta") {
|
|
402
|
-
if (event.delta.type === "text_delta") {
|
|
403
|
-
const index = blocks.findIndex((b) => b.index === event.index);
|
|
404
|
-
const block = blocks[index];
|
|
405
|
-
if (block && block.type === "text") {
|
|
406
|
-
block.text += event.delta.text;
|
|
407
|
-
stream.push({
|
|
408
|
-
type: "text_delta",
|
|
409
|
-
contentIndex: index,
|
|
410
|
-
delta: event.delta.text,
|
|
411
|
-
partial: output,
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
else if (event.delta.type === "thinking_delta") {
|
|
416
|
-
const index = blocks.findIndex((b) => b.index === event.index);
|
|
417
|
-
const block = blocks[index];
|
|
418
|
-
if (block && block.type === "thinking") {
|
|
419
|
-
block.thinking += event.delta.thinking;
|
|
420
|
-
stream.push({
|
|
421
|
-
type: "thinking_delta",
|
|
422
|
-
contentIndex: index,
|
|
423
|
-
delta: event.delta.thinking,
|
|
424
|
-
partial: output,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
else if (event.delta.type === "input_json_delta") {
|
|
429
|
-
const index = blocks.findIndex((b) => b.index === event.index);
|
|
430
|
-
const block = blocks[index];
|
|
431
|
-
if (block && block.type === "toolCall") {
|
|
432
|
-
block.partialJson += event.delta.partial_json;
|
|
433
|
-
block.arguments = parseStreamingJson(block.partialJson);
|
|
434
|
-
stream.push({
|
|
435
|
-
type: "toolcall_delta",
|
|
436
|
-
contentIndex: index,
|
|
437
|
-
delta: event.delta.partial_json,
|
|
438
|
-
partial: output,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
else if (event.delta.type === "signature_delta") {
|
|
443
|
-
const index = blocks.findIndex((b) => b.index === event.index);
|
|
444
|
-
const block = blocks[index];
|
|
445
|
-
if (block && block.type === "thinking") {
|
|
446
|
-
block.thinkingSignature = block.thinkingSignature || "";
|
|
447
|
-
block.thinkingSignature += event.delta.signature;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else if (event.type === "content_block_stop") {
|
|
452
|
-
const index = blocks.findIndex((b) => b.index === event.index);
|
|
453
|
-
const block = blocks[index];
|
|
454
|
-
if (block) {
|
|
455
|
-
delete block.index;
|
|
456
|
-
if (block.type === "text") {
|
|
457
|
-
stream.push({
|
|
458
|
-
type: "text_end",
|
|
459
|
-
contentIndex: index,
|
|
460
|
-
content: block.text,
|
|
461
|
-
partial: output,
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
else if (block.type === "thinking") {
|
|
465
|
-
stream.push({
|
|
466
|
-
type: "thinking_end",
|
|
467
|
-
contentIndex: index,
|
|
468
|
-
content: block.thinking,
|
|
469
|
-
partial: output,
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
else if (block.type === "toolCall") {
|
|
473
|
-
block.arguments = parseStreamingJson(block.partialJson);
|
|
474
|
-
// Finalize in-place and strip the scratch buffer so replay only
|
|
475
|
-
// carries parsed arguments.
|
|
476
|
-
delete block.partialJson;
|
|
477
|
-
stream.push({
|
|
478
|
-
type: "toolcall_end",
|
|
479
|
-
contentIndex: index,
|
|
480
|
-
toolCall: block,
|
|
481
|
-
partial: output,
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
else if (event.type === "message_delta") {
|
|
487
|
-
if (event.delta.stop_reason) {
|
|
488
|
-
output.stopReason = mapStopReason(event.delta.stop_reason);
|
|
489
|
-
}
|
|
490
|
-
// Only update usage fields if present (not null).
|
|
491
|
-
// Preserves input_tokens from message_start when proxies omit it in message_delta.
|
|
492
|
-
if (event.usage.input_tokens != null) {
|
|
493
|
-
output.usage.input = event.usage.input_tokens;
|
|
494
|
-
}
|
|
495
|
-
if (event.usage.output_tokens != null) {
|
|
496
|
-
output.usage.output = event.usage.output_tokens;
|
|
497
|
-
}
|
|
498
|
-
if (event.usage.cache_read_input_tokens != null) {
|
|
499
|
-
output.usage.cacheRead = event.usage.cache_read_input_tokens;
|
|
500
|
-
}
|
|
501
|
-
if (event.usage.cache_creation_input_tokens != null) {
|
|
502
|
-
output.usage.cacheWrite = event.usage.cache_creation_input_tokens;
|
|
503
|
-
}
|
|
504
|
-
// Anthropic doesn't provide total_tokens, compute from components
|
|
505
|
-
output.usage.totalTokens =
|
|
506
|
-
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
507
|
-
calculateCost(model, output.usage);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
if (options?.signal?.aborted) {
|
|
511
|
-
throw new Error("Request was aborted");
|
|
512
|
-
}
|
|
513
|
-
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
514
|
-
throw new Error("An unknown error occurred");
|
|
515
|
-
}
|
|
516
|
-
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
517
|
-
stream.end();
|
|
544
|
+
await executeAnthropicStream(state, options);
|
|
518
545
|
}
|
|
519
546
|
catch (error) {
|
|
520
|
-
|
|
521
|
-
delete block.index;
|
|
522
|
-
// partialJson is only a streaming scratch buffer; never persist it.
|
|
523
|
-
delete block.partialJson;
|
|
524
|
-
}
|
|
525
|
-
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
526
|
-
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
|
527
|
-
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
528
|
-
stream.end();
|
|
547
|
+
handleAnthropicStreamFailure(error, options, state);
|
|
529
548
|
}
|
|
530
549
|
})();
|
|
531
550
|
return stream;
|
|
@@ -669,16 +688,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
|
|
|
669
688
|
});
|
|
670
689
|
return { client, isOAuthToken: false };
|
|
671
690
|
}
|
|
672
|
-
function
|
|
673
|
-
const { cacheControl } = getCacheControl(model, options?.cacheRetention);
|
|
674
|
-
const compat = getAnthropicCompat(model);
|
|
675
|
-
const params = {
|
|
676
|
-
model: model.id,
|
|
677
|
-
messages: convertMessages(context.messages, model, isOAuthToken, cacheControl, compat.allowEmptySignature),
|
|
678
|
-
max_tokens: options?.maxTokens ?? model.maxTokens,
|
|
679
|
-
stream: true,
|
|
680
|
-
};
|
|
681
|
-
// For OAuth tokens, we MUST include Claude Code identity
|
|
691
|
+
function applyAnthropicSystemPrompt(params, context, isOAuthToken, cacheControl) {
|
|
682
692
|
if (isOAuthToken) {
|
|
683
693
|
params.system = [
|
|
684
694
|
{
|
|
@@ -694,237 +704,220 @@ function buildParams(model, context, isOAuthToken, options) {
|
|
|
694
704
|
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
695
705
|
});
|
|
696
706
|
}
|
|
707
|
+
return;
|
|
697
708
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (options?.temperature !== undefined && !options
|
|
709
|
+
if (!context.systemPrompt)
|
|
710
|
+
return;
|
|
711
|
+
params.system = [
|
|
712
|
+
{
|
|
713
|
+
type: "text",
|
|
714
|
+
text: sanitizeSurrogates(context.systemPrompt),
|
|
715
|
+
...(cacheControl ? { cache_control: cacheControl } : {}),
|
|
716
|
+
},
|
|
717
|
+
];
|
|
718
|
+
}
|
|
719
|
+
function applyAnthropicTemperature(params, options, compat) {
|
|
720
|
+
if (options?.temperature !== undefined && !options.thinkingEnabled && compat.supportsTemperature) {
|
|
710
721
|
params.temperature = options.temperature;
|
|
711
722
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
723
|
+
}
|
|
724
|
+
function applyAnthropicTools(params, context, isOAuthToken, cacheControl, compat) {
|
|
725
|
+
if (!context.tools || context.tools.length === 0)
|
|
726
|
+
return;
|
|
727
|
+
params.tools = convertTools(context.tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined);
|
|
728
|
+
}
|
|
729
|
+
function applyAdaptiveThinkingEffort(params, effort) {
|
|
730
|
+
if (!effort)
|
|
731
|
+
return;
|
|
732
|
+
// The Anthropic SDK types can lag newly supported effort values such as "xhigh".
|
|
733
|
+
params.output_config =
|
|
734
|
+
effort === "xhigh"
|
|
735
|
+
? { effort }
|
|
736
|
+
: { effort };
|
|
737
|
+
}
|
|
738
|
+
function applyEnabledAnthropicThinking(params, model, options) {
|
|
739
|
+
const display = options.thinkingDisplay ?? "summarized";
|
|
740
|
+
if (supportsAdaptiveThinking(model.id)) {
|
|
741
|
+
params.thinking = { type: "adaptive", display };
|
|
742
|
+
applyAdaptiveThinkingEffort(params, options.effort);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
params.thinking = {
|
|
746
|
+
type: "enabled",
|
|
747
|
+
budget_tokens: options.thinkingBudgetTokens || 1024,
|
|
748
|
+
display,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function applyAnthropicThinking(params, model, options) {
|
|
752
|
+
if (!model.reasoning)
|
|
753
|
+
return;
|
|
754
|
+
if (options?.thinkingEnabled) {
|
|
755
|
+
applyEnabledAnthropicThinking(params, model, options);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (options?.thinkingEnabled === false)
|
|
759
|
+
params.thinking = { type: "disabled" };
|
|
760
|
+
}
|
|
761
|
+
function applyAnthropicMetadata(params, options) {
|
|
762
|
+
const userId = options?.metadata?.user_id;
|
|
763
|
+
if (typeof userId === "string")
|
|
764
|
+
params.metadata = { user_id: userId };
|
|
765
|
+
}
|
|
766
|
+
function applyAnthropicToolChoice(params, options) {
|
|
767
|
+
if (!options?.toolChoice)
|
|
768
|
+
return;
|
|
769
|
+
params.tool_choice = typeof options.toolChoice === "string" ? { type: options.toolChoice } : options.toolChoice;
|
|
770
|
+
}
|
|
771
|
+
function buildParams(model, context, isOAuthToken, options) {
|
|
772
|
+
const { cacheControl } = getCacheControl(model, options?.cacheRetention);
|
|
773
|
+
const compat = getAnthropicCompat(model);
|
|
774
|
+
const params = {
|
|
775
|
+
model: model.id,
|
|
776
|
+
messages: convertMessages(context.messages, model, isOAuthToken, cacheControl, compat.allowEmptySignature),
|
|
777
|
+
max_tokens: options?.maxTokens ?? model.maxTokens,
|
|
778
|
+
stream: true,
|
|
779
|
+
};
|
|
780
|
+
applyAnthropicSystemPrompt(params, context, isOAuthToken, cacheControl);
|
|
781
|
+
applyAnthropicTemperature(params, options, compat);
|
|
782
|
+
applyAnthropicTools(params, context, isOAuthToken, cacheControl, compat);
|
|
783
|
+
applyAnthropicThinking(params, model, options);
|
|
784
|
+
applyAnthropicMetadata(params, options);
|
|
785
|
+
applyAnthropicToolChoice(params, options);
|
|
760
786
|
return params;
|
|
761
787
|
}
|
|
762
788
|
// Normalize tool call IDs to match Anthropic's required pattern and length
|
|
763
789
|
function normalizeToolCallId(id) {
|
|
764
790
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
765
791
|
}
|
|
766
|
-
function
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
content: sanitizeSurrogates(msg.content),
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
else {
|
|
782
|
-
const blocks = msg.content.map((item) => {
|
|
783
|
-
if (item.type === "text") {
|
|
784
|
-
return {
|
|
785
|
-
type: "text",
|
|
786
|
-
text: sanitizeSurrogates(item.text),
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
return {
|
|
791
|
-
type: "image",
|
|
792
|
-
source: {
|
|
793
|
-
type: "base64",
|
|
794
|
-
media_type: item.mimeType,
|
|
795
|
-
data: item.data,
|
|
796
|
-
},
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
const filteredBlocks = blocks.filter((b) => {
|
|
801
|
-
if (b.type === "text") {
|
|
802
|
-
return b.text.trim().length > 0;
|
|
803
|
-
}
|
|
804
|
-
return true;
|
|
805
|
-
});
|
|
806
|
-
if (filteredBlocks.length === 0)
|
|
807
|
-
continue;
|
|
808
|
-
params.push({
|
|
809
|
-
role: "user",
|
|
810
|
-
content: filteredBlocks,
|
|
811
|
-
});
|
|
812
|
-
}
|
|
792
|
+
function convertAnthropicUserMessage(message) {
|
|
793
|
+
if (typeof message.content === "string") {
|
|
794
|
+
return message.content.trim().length > 0
|
|
795
|
+
? { role: "user", content: sanitizeSurrogates(message.content) }
|
|
796
|
+
: undefined;
|
|
797
|
+
}
|
|
798
|
+
const content = [];
|
|
799
|
+
for (const block of message.content) {
|
|
800
|
+
if (block.type === "text") {
|
|
801
|
+
if (block.text.trim().length > 0)
|
|
802
|
+
content.push({ type: "text", text: sanitizeSurrogates(block.text) });
|
|
813
803
|
}
|
|
814
|
-
else
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
text: sanitizeSurrogates(block.text),
|
|
823
|
-
});
|
|
824
|
-
}
|
|
825
|
-
else if (block.type === "thinking") {
|
|
826
|
-
// Redacted thinking: pass the opaque payload back as redacted_thinking
|
|
827
|
-
if (block.redacted) {
|
|
828
|
-
blocks.push({
|
|
829
|
-
type: "redacted_thinking",
|
|
830
|
-
data: block.thinkingSignature,
|
|
831
|
-
});
|
|
832
|
-
continue;
|
|
833
|
-
}
|
|
834
|
-
if (block.thinking.trim().length === 0)
|
|
835
|
-
continue;
|
|
836
|
-
// If thinking signature is missing/empty (e.g., from aborted stream),
|
|
837
|
-
// convert to plain text for Anthropic. Some compatible providers emit
|
|
838
|
-
// and accept empty signatures, so let marked models preserve the block.
|
|
839
|
-
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
840
|
-
blocks.push(allowEmptySignature
|
|
841
|
-
? {
|
|
842
|
-
type: "thinking",
|
|
843
|
-
thinking: sanitizeSurrogates(block.thinking),
|
|
844
|
-
signature: "",
|
|
845
|
-
}
|
|
846
|
-
: {
|
|
847
|
-
type: "text",
|
|
848
|
-
text: sanitizeSurrogates(block.thinking),
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
else {
|
|
852
|
-
blocks.push({
|
|
853
|
-
type: "thinking",
|
|
854
|
-
thinking: sanitizeSurrogates(block.thinking),
|
|
855
|
-
signature: block.thinkingSignature,
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
else if (block.type === "toolCall") {
|
|
860
|
-
blocks.push({
|
|
861
|
-
type: "tool_use",
|
|
862
|
-
id: block.id,
|
|
863
|
-
name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
|
|
864
|
-
input: block.arguments ?? {},
|
|
865
|
-
});
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
if (blocks.length === 0)
|
|
869
|
-
continue;
|
|
870
|
-
params.push({
|
|
871
|
-
role: "assistant",
|
|
872
|
-
content: blocks,
|
|
804
|
+
else {
|
|
805
|
+
content.push({
|
|
806
|
+
type: "image",
|
|
807
|
+
source: {
|
|
808
|
+
type: "base64",
|
|
809
|
+
media_type: block.mimeType,
|
|
810
|
+
data: block.data,
|
|
811
|
+
},
|
|
873
812
|
});
|
|
874
813
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
814
|
+
}
|
|
815
|
+
return content.length > 0 ? { role: "user", content } : undefined;
|
|
816
|
+
}
|
|
817
|
+
function convertAnthropicThinkingBlock(block, allowEmptySignature) {
|
|
818
|
+
if (block.redacted)
|
|
819
|
+
return { type: "redacted_thinking", data: block.thinkingSignature };
|
|
820
|
+
if (block.thinking.trim().length === 0)
|
|
821
|
+
return undefined;
|
|
822
|
+
if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
|
|
823
|
+
return allowEmptySignature
|
|
824
|
+
? { type: "thinking", thinking: sanitizeSurrogates(block.thinking), signature: "" }
|
|
825
|
+
: { type: "text", text: sanitizeSurrogates(block.thinking) };
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
type: "thinking",
|
|
829
|
+
thinking: sanitizeSurrogates(block.thinking),
|
|
830
|
+
signature: block.thinkingSignature,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
function convertAnthropicAssistantBlock(block, isOAuthToken, allowEmptySignature) {
|
|
834
|
+
switch (block.type) {
|
|
835
|
+
case "text":
|
|
836
|
+
return block.text.trim().length > 0 ? { type: "text", text: sanitizeSurrogates(block.text) } : undefined;
|
|
837
|
+
case "thinking":
|
|
838
|
+
return convertAnthropicThinkingBlock(block, allowEmptySignature);
|
|
839
|
+
case "toolCall":
|
|
840
|
+
return {
|
|
841
|
+
type: "tool_use",
|
|
842
|
+
id: block.id,
|
|
843
|
+
name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
|
|
844
|
+
input: block.arguments ?? {},
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function convertAnthropicAssistantMessage(message, isOAuthToken, allowEmptySignature) {
|
|
849
|
+
const content = [];
|
|
850
|
+
for (const block of message.content) {
|
|
851
|
+
const converted = convertAnthropicAssistantBlock(block, isOAuthToken, allowEmptySignature);
|
|
852
|
+
if (converted)
|
|
853
|
+
content.push(converted);
|
|
854
|
+
}
|
|
855
|
+
return content.length > 0 ? { role: "assistant", content } : undefined;
|
|
856
|
+
}
|
|
857
|
+
function convertAnthropicToolResult(message) {
|
|
858
|
+
return {
|
|
859
|
+
type: "tool_result",
|
|
860
|
+
tool_use_id: message.toolCallId,
|
|
861
|
+
content: convertContentBlocks(message.content),
|
|
862
|
+
is_error: message.isError,
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
function collectAnthropicToolResultBatch(messages, startIndex) {
|
|
866
|
+
const content = [];
|
|
867
|
+
let lastIndex = startIndex;
|
|
868
|
+
for (let index = startIndex; index < messages.length; index++) {
|
|
869
|
+
const message = messages[index];
|
|
870
|
+
if (message.role !== "toolResult")
|
|
871
|
+
break;
|
|
872
|
+
content.push(convertAnthropicToolResult(message));
|
|
873
|
+
lastIndex = index;
|
|
874
|
+
}
|
|
875
|
+
return { content, lastIndex };
|
|
876
|
+
}
|
|
877
|
+
function applyAnthropicConversationCacheControl(params, cacheControl) {
|
|
878
|
+
if (!cacheControl || params.length === 0)
|
|
879
|
+
return;
|
|
880
|
+
const lastMessage = params[params.length - 1];
|
|
881
|
+
if (lastMessage.role !== "user")
|
|
882
|
+
return;
|
|
883
|
+
if (Array.isArray(lastMessage.content)) {
|
|
884
|
+
const lastBlock = lastMessage.content[lastMessage.content.length - 1];
|
|
885
|
+
if (lastBlock && (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) {
|
|
886
|
+
lastBlock.cache_control = cacheControl;
|
|
904
887
|
}
|
|
888
|
+
return;
|
|
905
889
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
890
|
+
if (typeof lastMessage.content === "string") {
|
|
891
|
+
lastMessage.content = [{ type: "text", text: lastMessage.content, cache_control: cacheControl }];
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmptySignature = false) {
|
|
895
|
+
const params = [];
|
|
896
|
+
const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
|
|
897
|
+
for (let index = 0; index < transformedMessages.length; index++) {
|
|
898
|
+
const message = transformedMessages[index];
|
|
899
|
+
switch (message.role) {
|
|
900
|
+
case "user": {
|
|
901
|
+
const converted = convertAnthropicUserMessage(message);
|
|
902
|
+
if (converted)
|
|
903
|
+
params.push(converted);
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
case "assistant": {
|
|
907
|
+
const converted = convertAnthropicAssistantMessage(message, isOAuthToken, allowEmptySignature);
|
|
908
|
+
if (converted)
|
|
909
|
+
params.push(converted);
|
|
910
|
+
break;
|
|
916
911
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
cache_control: cacheControl,
|
|
923
|
-
},
|
|
924
|
-
];
|
|
912
|
+
case "toolResult": {
|
|
913
|
+
const batch = collectAnthropicToolResultBatch(transformedMessages, index);
|
|
914
|
+
params.push({ role: "user", content: batch.content });
|
|
915
|
+
index = batch.lastIndex;
|
|
916
|
+
break;
|
|
925
917
|
}
|
|
926
918
|
}
|
|
927
919
|
}
|
|
920
|
+
applyAnthropicConversationCacheControl(params, cacheControl);
|
|
928
921
|
return params;
|
|
929
922
|
}
|
|
930
923
|
function shouldUseFineGrainedToolStreamingBeta(model, context) {
|