@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
@@ -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
- let consumed = consumeLine(buffer);
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
- let consumed = consumeLine(buffer);
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
- let sawMessageStart = false;
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
- try {
266
- const event = parseJsonWithRepair(sse.data);
267
- if (event.type === "message_start") {
268
- sawMessageStart = true;
269
- }
270
- else if (event.type === "message_stop") {
271
- sawMessageEnd = true;
272
- }
273
- yield event;
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
- catch (error) {
276
- const message = error instanceof Error ? error.message : String(error);
277
- throw new Error(`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join("\\n")}`);
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
- if (sawMessageStart && !sawMessageEnd) {
281
- throw new Error("Anthropic stream ended before message_stop");
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
- let client;
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
- for (const block of output.content) {
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 buildParams(model, context, isOAuthToken, options) {
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
- else if (context.systemPrompt) {
699
- // Add cache control to system prompt for non-OAuth tokens
700
- params.system = [
701
- {
702
- type: "text",
703
- text: sanitizeSurrogates(context.systemPrompt),
704
- ...(cacheControl ? { cache_control: cacheControl } : {}),
705
- },
706
- ];
707
- }
708
- // Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.
709
- if (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {
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
- if (context.tools && context.tools.length > 0) {
713
- params.tools = convertTools(context.tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined);
714
- }
715
- // Configure thinking mode: adaptive (Opus 4.6+ and Sonnet 4.6),
716
- // budget-based (older models), or explicitly disabled.
717
- if (model.reasoning) {
718
- if (options?.thinkingEnabled) {
719
- // Default to "summarized" so Opus 4.7 and Mythos Preview behave like
720
- // older Claude 4 models (whose API default is also "summarized").
721
- const display = options.thinkingDisplay ?? "summarized";
722
- if (supportsAdaptiveThinking(model.id)) {
723
- // Adaptive thinking: Claude decides when and how much to think.
724
- params.thinking = { type: "adaptive", display };
725
- if (options.effort) {
726
- // The Anthropic SDK types can lag newly supported effort values such as "xhigh".
727
- params.output_config =
728
- options.effort === "xhigh"
729
- ? { effort: options.effort }
730
- : { effort: options.effort };
731
- }
732
- }
733
- else {
734
- // Budget-based thinking for older models
735
- params.thinking = {
736
- type: "enabled",
737
- budget_tokens: options.thinkingBudgetTokens || 1024,
738
- display,
739
- };
740
- }
741
- }
742
- else if (options?.thinkingEnabled === false) {
743
- params.thinking = { type: "disabled" };
744
- }
745
- }
746
- if (options?.metadata) {
747
- const userId = options.metadata.user_id;
748
- if (typeof userId === "string") {
749
- params.metadata = { user_id: userId };
750
- }
751
- }
752
- if (options?.toolChoice) {
753
- if (typeof options.toolChoice === "string") {
754
- params.tool_choice = { type: options.toolChoice };
755
- }
756
- else {
757
- params.tool_choice = options.toolChoice;
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 convertMessages(messages, model, isOAuthToken, cacheControl, allowEmptySignature = false) {
767
- const params = [];
768
- // Transform messages for cross-provider compatibility
769
- const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
770
- for (let i = 0; i < transformedMessages.length; i++) {
771
- const msg = transformedMessages[i];
772
- if (msg.role === "user") {
773
- if (typeof msg.content === "string") {
774
- if (msg.content.trim().length > 0) {
775
- params.push({
776
- role: "user",
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 if (msg.role === "assistant") {
815
- const blocks = [];
816
- for (const block of msg.content) {
817
- if (block.type === "text") {
818
- if (block.text.trim().length === 0)
819
- continue;
820
- blocks.push({
821
- type: "text",
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
- else if (msg.role === "toolResult") {
876
- // Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint
877
- const toolResults = [];
878
- // Add the current tool result
879
- toolResults.push({
880
- type: "tool_result",
881
- tool_use_id: msg.toolCallId,
882
- content: convertContentBlocks(msg.content),
883
- is_error: msg.isError,
884
- });
885
- // Look ahead for consecutive toolResult messages
886
- let j = i + 1;
887
- while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
888
- const nextMsg = transformedMessages[j]; // We know it's a toolResult
889
- toolResults.push({
890
- type: "tool_result",
891
- tool_use_id: nextMsg.toolCallId,
892
- content: convertContentBlocks(nextMsg.content),
893
- is_error: nextMsg.isError,
894
- });
895
- j++;
896
- }
897
- // Skip the messages we've already processed
898
- i = j - 1;
899
- // Add a single user message with all tool results
900
- params.push({
901
- role: "user",
902
- content: toolResults,
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
- // Add cache_control to the last user message to cache conversation history
907
- if (cacheControl && params.length > 0) {
908
- const lastMessage = params[params.length - 1];
909
- if (lastMessage.role === "user") {
910
- if (Array.isArray(lastMessage.content)) {
911
- const lastBlock = lastMessage.content[lastMessage.content.length - 1];
912
- if (lastBlock &&
913
- (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) {
914
- lastBlock.cache_control = cacheControl;
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
- else if (typeof lastMessage.content === "string") {
918
- lastMessage.content = [
919
- {
920
- type: "text",
921
- text: lastMessage.content,
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) {