@jeffusion/bungee-llms 3.3.0 → 3.3.1

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/index.js CHANGED
@@ -2678,8 +2678,9 @@ function mapOpenAIFinishReasonToAnthropic(finishReason) {
2678
2678
  case "length":
2679
2679
  return "max_tokens";
2680
2680
  case "content_filter":
2681
- return "stop_sequence";
2681
+ return "end_turn";
2682
2682
  case "tool_calls":
2683
+ case "function_call":
2683
2684
  return "tool_use";
2684
2685
  default:
2685
2686
  return "end_turn";
@@ -2853,7 +2854,11 @@ class AnthropicToGeminiConverter {
2853
2854
  if (block.type === "text" && block.text) {
2854
2855
  parts.push({ text: block.text });
2855
2856
  } else if (block.type === "thinking" && block.thinking) {
2856
- parts.push({ text: block.thinking, thought: true });
2857
+ const thinkingPart = { text: block.thinking, thought: true };
2858
+ if (block.thought_signature) {
2859
+ thinkingPart.thoughtSignature = block.thought_signature;
2860
+ }
2861
+ parts.push(thinkingPart);
2857
2862
  } else if (block.type === "image" && block.source) {
2858
2863
  parts.push({
2859
2864
  inlineData: {
@@ -3057,10 +3062,14 @@ class AnthropicToGeminiConverter {
3057
3062
  for (const part of parts) {
3058
3063
  if (part.text) {
3059
3064
  if (part.thought) {
3060
- content.push({
3065
+ const thinkingBlock = {
3061
3066
  type: "thinking",
3062
3067
  thinking: part.text
3063
- });
3068
+ };
3069
+ if (part.thoughtSignature) {
3070
+ thinkingBlock.thought_signature = part.thoughtSignature;
3071
+ }
3072
+ content.push(thinkingBlock);
3064
3073
  } else {
3065
3074
  content.push({
3066
3075
  type: "text",
@@ -3137,6 +3146,10 @@ class AnthropicToGeminiConverter {
3137
3146
  name: part.functionCall.name,
3138
3147
  input: {}
3139
3148
  };
3149
+ if (part.thoughtSignature) {
3150
+ contentBlock.thought_signature = part.thoughtSignature;
3151
+ ctx.streamState.set(`part_${partIdx}_sig`, part.thoughtSignature);
3152
+ }
3140
3153
  } else if (part.thought !== undefined) {
3141
3154
  contentBlock = {
3142
3155
  type: "thinking",
@@ -3159,6 +3172,9 @@ class AnthropicToGeminiConverter {
3159
3172
  const argsStr = JSON.stringify(part.functionCall.args || {});
3160
3173
  const lastArgsLength = Number(ctx.streamState.get(argsKey) ?? 0);
3161
3174
  const currentArgsLength = argsStr.length;
3175
+ if (part.thoughtSignature && !ctx.streamState.has(`part_${partIdx}_sig`)) {
3176
+ ctx.streamState.set(`part_${partIdx}_sig`, part.thoughtSignature);
3177
+ }
3162
3178
  if (currentArgsLength > lastArgsLength) {
3163
3179
  const argsDelta = argsStr.substring(lastArgsLength);
3164
3180
  events.push({
@@ -3175,6 +3191,9 @@ class AnthropicToGeminiConverter {
3175
3191
  const thinkingKey = `part_${partIdx}_thinking_length`;
3176
3192
  const lastLength = Number(ctx.streamState.get(thinkingKey) ?? 0);
3177
3193
  const currentLength = part.thought.length;
3194
+ if (part.thoughtSignature && !ctx.streamState.has(`part_${partIdx}_sig`)) {
3195
+ ctx.streamState.set(`part_${partIdx}_sig`, part.thoughtSignature);
3196
+ }
3178
3197
  if (currentLength > lastLength) {
3179
3198
  const delta = part.thought.substring(lastLength);
3180
3199
  events.push({
@@ -3205,10 +3224,15 @@ class AnthropicToGeminiConverter {
3205
3224
  }
3206
3225
  }
3207
3226
  if (hasFinish) {
3208
- events.push({
3227
+ const stopEvent = {
3209
3228
  type: "content_block_stop",
3210
3229
  index: partIdx
3211
- });
3230
+ };
3231
+ const sig = ctx.streamState.get(`part_${partIdx}_sig`);
3232
+ if (sig) {
3233
+ stopEvent.thought_signature = sig;
3234
+ }
3235
+ events.push(stopEvent);
3212
3236
  }
3213
3237
  }
3214
3238
  return events;
@@ -3230,18 +3254,6 @@ class AnthropicToOpenAIConverter {
3230
3254
  from = "anthropic";
3231
3255
  to = "openai";
3232
3256
  configuredApiMode;
3233
- protocolConversion = new OpenAIProtocolConversion;
3234
- runtime = new LLMSRuntime;
3235
- constructor() {
3236
- this.runtime.registerAdapter(new AnthropicAdapter, {
3237
- provider: "anthropic",
3238
- displayName: "Anthropic"
3239
- });
3240
- this.runtime.registerAdapter(new OpenAIAdapter, {
3241
- provider: "openai",
3242
- displayName: "OpenAI"
3243
- });
3244
- }
3245
3257
  setApiMode(mode) {
3246
3258
  if (typeof mode !== "string") {
3247
3259
  this.configuredApiMode = undefined;
@@ -3262,21 +3274,6 @@ class AnthropicToOpenAIConverter {
3262
3274
  const value = options;
3263
3275
  this.setApiMode(value.anthropicToOpenAIApiMode);
3264
3276
  }
3265
- parseIntegerOption(value) {
3266
- if (typeof value === "number" && Number.isFinite(value)) {
3267
- return Math.trunc(value);
3268
- }
3269
- if (typeof value === "string" && value.trim().length > 0) {
3270
- const parsed = parseInt(value, 10);
3271
- if (Number.isFinite(parsed)) {
3272
- return parsed;
3273
- }
3274
- }
3275
- return;
3276
- }
3277
- resolveReasoningMaxCompletionTokens(maxTokens) {
3278
- return this.parseIntegerOption(maxTokens);
3279
- }
3280
3277
  resolveTargetApiMode() {
3281
3278
  if (this.configuredApiMode) {
3282
3279
  return this.configuredApiMode;
@@ -3301,26 +3298,6 @@ class AnthropicToOpenAIConverter {
3301
3298
  }
3302
3299
  if (ctx.url.pathname === "/v1/messages" || ctx.url.pathname === "/messages") {
3303
3300
  const targetApiMode = this.resolveTargetApiMode();
3304
- if (this.canUseRuntimeRequestConversion(body, targetApiMode)) {
3305
- ctx.url.pathname = "/v1/chat/completions";
3306
- let runtimeRequestBody;
3307
- try {
3308
- runtimeRequestBody = this.runtime.convertRequest("anthropic", "openai", body, { pathname: "/v1/chat/completions" });
3309
- } catch {
3310
- ctx.body = this.buildOpenAIRequest(body);
3311
- return;
3312
- }
3313
- if (!runtimeRequestBody || typeof runtimeRequestBody !== "object" || Array.isArray(runtimeRequestBody)) {
3314
- ctx.body = this.buildOpenAIRequest(body);
3315
- return;
3316
- }
3317
- const model = runtimeRequestBody.model;
3318
- if (typeof model !== "string" || model.trim().length === 0) {
3319
- runtimeRequestBody.model = "gpt-4";
3320
- }
3321
- ctx.body = runtimeRequestBody;
3322
- return;
3323
- }
3324
3301
  if (targetApiMode === "responses") {
3325
3302
  ctx.url.pathname = "/v1/responses";
3326
3303
  ctx.body = this.buildOpenAIResponsesRequest(body);
@@ -3330,43 +3307,6 @@ class AnthropicToOpenAIConverter {
3330
3307
  }
3331
3308
  }
3332
3309
  }
3333
- canUseRuntimeRequestConversion(anthropicBody, targetApiMode) {
3334
- if (targetApiMode !== "chat_completions") {
3335
- return false;
3336
- }
3337
- if (!anthropicBody || typeof anthropicBody !== "object" || Array.isArray(anthropicBody)) {
3338
- return false;
3339
- }
3340
- const body = anthropicBody;
3341
- if (typeof body.system !== "string" && body.system !== undefined) {
3342
- return false;
3343
- }
3344
- if (body.tools !== undefined || body.tool_choice !== undefined || body.thinking !== undefined || body.output_config !== undefined || body.top_k !== undefined) {
3345
- return false;
3346
- }
3347
- if (!Array.isArray(body.messages)) {
3348
- return false;
3349
- }
3350
- if (body.messages.length === 0) {
3351
- const hasNonEmptySystem = typeof body.system === "string" && body.system.trim().length > 0;
3352
- if (!hasNonEmptySystem) {
3353
- return false;
3354
- }
3355
- }
3356
- for (const message of body.messages) {
3357
- if (!message || typeof message !== "object" || Array.isArray(message)) {
3358
- return false;
3359
- }
3360
- const typedMessage = message;
3361
- if (typedMessage.role !== "user" && typedMessage.role !== "assistant") {
3362
- return false;
3363
- }
3364
- if (typeof typedMessage.content !== "string") {
3365
- return false;
3366
- }
3367
- }
3368
- return true;
3369
- }
3370
3310
  isAnthropicCountTokensPath(pathname) {
3371
3311
  return pathname === "/v1/messages/count_tokens" || pathname === "/messages/count_tokens" || pathname.endsWith("/messages/count_tokens");
3372
3312
  }
@@ -3387,10 +3327,12 @@ class AnthropicToOpenAIConverter {
3387
3327
  }
3388
3328
  buildOpenAIRequest(anthropicBody) {
3389
3329
  const openaiBody = {};
3390
- openaiBody.model = anthropicBody.model || "gpt-4";
3330
+ if (typeof anthropicBody.model === "string") {
3331
+ openaiBody.model = anthropicBody.model;
3332
+ }
3391
3333
  const messages = [];
3392
3334
  messages.push(...this.convertSystemMessages(anthropicBody.system));
3393
- if (anthropicBody.messages) {
3335
+ if (Array.isArray(anthropicBody.messages)) {
3394
3336
  for (const msg of anthropicBody.messages) {
3395
3337
  if (msg.role === "user") {
3396
3338
  this.convertUserMessage(msg, messages);
@@ -3399,13 +3341,18 @@ class AnthropicToOpenAIConverter {
3399
3341
  }
3400
3342
  }
3401
3343
  }
3402
- openaiBody.messages = this.validateAndCleanToolCalls(messages);
3344
+ this.normalizeOpenAISystemMessages(messages);
3345
+ openaiBody.messages = messages;
3403
3346
  if (!Array.isArray(openaiBody.messages) || openaiBody.messages.length === 0) {
3404
3347
  openaiBody.messages = [{ role: "user", content: "" }];
3405
3348
  }
3406
- const maxTokens = anthropicBody.max_tokens || anthropicBody.max_tokens_to_sample;
3349
+ const maxTokens = anthropicBody.max_tokens ?? anthropicBody.max_tokens_to_sample;
3407
3350
  if (maxTokens !== undefined) {
3408
- openaiBody.max_tokens = maxTokens;
3351
+ if (typeof openaiBody.model === "string" && this.isOpenAINumericOSeriesModel(openaiBody.model)) {
3352
+ openaiBody.max_completion_tokens = maxTokens;
3353
+ } else {
3354
+ openaiBody.max_tokens = maxTokens;
3355
+ }
3409
3356
  }
3410
3357
  if (anthropicBody.temperature !== undefined) {
3411
3358
  openaiBody.temperature = anthropicBody.temperature;
@@ -3414,156 +3361,289 @@ class AnthropicToOpenAIConverter {
3414
3361
  openaiBody.top_p = anthropicBody.top_p;
3415
3362
  }
3416
3363
  if (anthropicBody.stop_sequences !== undefined) {
3417
- openaiBody.stop = Array.isArray(anthropicBody.stop_sequences) ? anthropicBody.stop_sequences : [anthropicBody.stop_sequences];
3364
+ openaiBody.stop = anthropicBody.stop_sequences;
3418
3365
  }
3419
3366
  if (anthropicBody.stream !== undefined) {
3420
3367
  openaiBody.stream = anthropicBody.stream;
3421
3368
  }
3422
3369
  if (Array.isArray(anthropicBody.tools)) {
3423
- openaiBody.tools = anthropicBody.tools.filter((tool) => typeof tool?.name === "string" && tool.name.trim().length > 0).map((tool) => ({
3424
- type: "function",
3425
- function: {
3426
- name: tool.name.trim(),
3427
- description: tool.description || "",
3428
- parameters: this.normalizeToolParametersSchema(tool.input_schema)
3370
+ openaiBody.tools = anthropicBody.tools.filter((tool) => tool?.type !== "BatchTool").map((tool) => {
3371
+ const mappedTool = {
3372
+ type: "function",
3373
+ function: {
3374
+ name: typeof tool?.name === "string" ? tool.name : "",
3375
+ description: tool?.description,
3376
+ parameters: this.cleanOpenAIToolSchema(tool?.input_schema ?? {})
3377
+ }
3378
+ };
3379
+ if (tool.cache_control !== undefined) {
3380
+ mappedTool.cache_control = tool.cache_control;
3429
3381
  }
3430
- }));
3382
+ return mappedTool;
3383
+ });
3431
3384
  if (openaiBody.tools.length === 0) {
3432
3385
  delete openaiBody.tools;
3433
3386
  }
3434
3387
  }
3435
- const toolChoice = this.mapAnthropicToolChoiceToOpenAI(anthropicBody.tool_choice);
3436
- if (toolChoice !== undefined) {
3437
- openaiBody.tool_choice = toolChoice;
3388
+ if (anthropicBody.tool_choice !== undefined) {
3389
+ openaiBody.tool_choice = anthropicBody.tool_choice;
3438
3390
  }
3439
- const thinkingEnabled = this.isAnthropicThinkingEnabled(anthropicBody.thinking);
3440
- const thinkingEffort = this.resolveAnthropicThinkingEffort(anthropicBody.thinking);
3441
- const outputConfigEffort = this.resolveOutputConfigEffort(anthropicBody.output_config);
3442
- const shouldApplyReasoningFields = thinkingEnabled || thinkingEffort !== undefined || outputConfigEffort !== undefined;
3443
- if (shouldApplyReasoningFields) {
3444
- const effort = this.normalizeOpenAIReasoningEffort(thinkingEffort || outputConfigEffort, openaiBody.model);
3445
- if (effort) {
3446
- openaiBody.reasoning_effort = effort;
3447
- }
3448
- const maxCompletionTokens = this.resolveReasoningMaxCompletionTokens(openaiBody.max_tokens);
3449
- if (maxCompletionTokens !== undefined) {
3450
- openaiBody.max_completion_tokens = maxCompletionTokens;
3451
- delete openaiBody.max_tokens;
3452
- }
3391
+ const promptCacheKey = this.resolvePromptCacheKey(anthropicBody);
3392
+ if (promptCacheKey) {
3393
+ openaiBody.prompt_cache_key = promptCacheKey;
3394
+ }
3395
+ const reasoningEffort = this.resolveAnthropicReasoningEffort(anthropicBody);
3396
+ if (reasoningEffort && this.supportsReasoningEffort(openaiBody.model)) {
3397
+ openaiBody.reasoning_effort = reasoningEffort;
3453
3398
  }
3454
3399
  return openaiBody;
3455
3400
  }
3456
- isAnthropicThinkingEnabled(thinking) {
3457
- if (!thinking || typeof thinking !== "object") {
3458
- return false;
3459
- }
3460
- const rawType = typeof thinking.type === "string" ? thinking.type.trim().toLowerCase() : "";
3461
- return rawType === "enabled" || rawType === "adaptive";
3401
+ isOpenAINumericOSeriesModel(model) {
3402
+ const normalized = model.trim().toLowerCase();
3403
+ return normalized.length > 1 && normalized[0] === "o" && /\d/.test(normalized[1]);
3462
3404
  }
3463
- resolveAnthropicThinkingEffort(thinking) {
3464
- if (!thinking || typeof thinking !== "object") {
3465
- return;
3405
+ buildOpenAIResponsesRequest(anthropicBody) {
3406
+ const responsesBody = {};
3407
+ if (typeof anthropicBody.model === "string") {
3408
+ responsesBody.model = anthropicBody.model;
3409
+ }
3410
+ const instructions = this.convertAnthropicSystemToInstructions(anthropicBody.system);
3411
+ if (instructions) {
3412
+ responsesBody.instructions = instructions;
3466
3413
  }
3467
- const rawEffort = typeof thinking.effort === "string" ? thinking.effort.trim().toLowerCase() : "";
3468
- if (rawEffort === "low" || rawEffort === "medium" || rawEffort === "high") {
3469
- return rawEffort;
3414
+ const input = this.convertAnthropicMessagesToResponsesInput(anthropicBody.messages);
3415
+ if (input.length > 0) {
3416
+ responsesBody.input = input;
3470
3417
  }
3471
- if (rawEffort === "max") {
3472
- return "xhigh";
3418
+ const maxTokens = anthropicBody.max_tokens ?? anthropicBody.max_tokens_to_sample;
3419
+ if (maxTokens !== undefined) {
3420
+ responsesBody.max_output_tokens = maxTokens;
3473
3421
  }
3474
- if (rawEffort === "min") {
3475
- return "low";
3422
+ if (anthropicBody.temperature !== undefined) {
3423
+ responsesBody.temperature = anthropicBody.temperature;
3476
3424
  }
3477
- return;
3478
- }
3479
- resolveOutputConfigEffort(outputConfig) {
3480
- if (!outputConfig || typeof outputConfig !== "object") {
3481
- return;
3425
+ if (anthropicBody.top_p !== undefined) {
3426
+ responsesBody.top_p = anthropicBody.top_p;
3482
3427
  }
3483
- const rawEffort = typeof outputConfig.effort === "string" ? outputConfig.effort.trim().toLowerCase() : "";
3484
- if (rawEffort === "low" || rawEffort === "medium" || rawEffort === "high") {
3485
- return rawEffort;
3428
+ if (anthropicBody.stream !== undefined) {
3429
+ responsesBody.stream = anthropicBody.stream;
3486
3430
  }
3487
- if (rawEffort === "max") {
3488
- return "xhigh";
3431
+ if (Array.isArray(anthropicBody.tools)) {
3432
+ const tools = anthropicBody.tools.filter((tool) => tool?.type !== "BatchTool").map((tool) => ({
3433
+ type: "function",
3434
+ name: tool.name || "",
3435
+ description: tool.description,
3436
+ parameters: this.normalizeToolParametersSchema(tool.input_schema)
3437
+ }));
3438
+ if (tools.length > 0) {
3439
+ responsesBody.tools = tools;
3440
+ }
3489
3441
  }
3490
- if (rawEffort === "min") {
3491
- return "low";
3442
+ const toolChoice = this.mapAnthropicToolChoiceToResponses(anthropicBody.tool_choice);
3443
+ if (toolChoice !== undefined) {
3444
+ responsesBody.tool_choice = toolChoice;
3492
3445
  }
3493
- return;
3494
- }
3495
- normalizeOpenAIReasoningEffort(effort, model) {
3496
- if (!effort) {
3497
- return;
3446
+ const reasoningEffort = this.resolveAnthropicReasoningEffort(anthropicBody);
3447
+ if (reasoningEffort && this.supportsReasoningEffort(responsesBody.model)) {
3448
+ responsesBody.reasoning = {
3449
+ effort: reasoningEffort
3450
+ };
3498
3451
  }
3499
- if (effort !== "xhigh") {
3500
- return effort;
3452
+ const promptCacheKey = this.resolvePromptCacheKey(anthropicBody);
3453
+ if (promptCacheKey) {
3454
+ responsesBody.prompt_cache_key = promptCacheKey;
3501
3455
  }
3502
- return this.supportsXHighReasoningEffort(model) ? "xhigh" : "high";
3456
+ return responsesBody;
3503
3457
  }
3504
- supportsXHighReasoningEffort(model) {
3505
- const normalized = typeof model === "string" ? model.trim().toLowerCase() : "";
3506
- if (!normalized) {
3507
- return false;
3458
+ convertAnthropicSystemToInstructions(system) {
3459
+ if (typeof system === "string") {
3460
+ const cleaned = this.sanitizeSystemText(system).trim();
3461
+ return cleaned || undefined;
3508
3462
  }
3509
- if (normalized.includes("gpt-5-pro")) {
3510
- return false;
3463
+ if (!Array.isArray(system)) {
3464
+ return;
3511
3465
  }
3512
- if (normalized.includes("codex-max")) {
3513
- return true;
3466
+ const texts = [];
3467
+ for (const block of system) {
3468
+ if (typeof block === "string") {
3469
+ const cleaned = this.sanitizeSystemText(block).trim();
3470
+ if (cleaned)
3471
+ texts.push(cleaned);
3472
+ } else if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
3473
+ const cleaned = this.sanitizeSystemText(block.text).trim();
3474
+ if (cleaned)
3475
+ texts.push(cleaned);
3476
+ }
3514
3477
  }
3515
- const gpt5MinorMatch = normalized.match(/gpt-5\.(\d+)/);
3516
- if (gpt5MinorMatch) {
3517
- const minor = parseInt(gpt5MinorMatch[1], 10);
3518
- if (Number.isFinite(minor)) {
3519
- return minor >= 2;
3478
+ return texts.length > 0 ? texts.join(`
3479
+
3480
+ `) : undefined;
3481
+ }
3482
+ convertAnthropicMessagesToResponsesInput(messages) {
3483
+ const input = [];
3484
+ if (!Array.isArray(messages)) {
3485
+ return input;
3486
+ }
3487
+ for (const msg of messages) {
3488
+ if (!msg || typeof msg !== "object")
3489
+ continue;
3490
+ const role = msg.role === "assistant" ? "assistant" : "user";
3491
+ const content = msg.content;
3492
+ if (typeof content === "string") {
3493
+ input.push({
3494
+ role,
3495
+ content: [{ type: role === "assistant" ? "output_text" : "input_text", text: content }]
3496
+ });
3497
+ continue;
3498
+ }
3499
+ if (Array.isArray(content)) {
3500
+ let contentItems = [];
3501
+ const flushContentItems = () => {
3502
+ if (contentItems.length > 0) {
3503
+ input.push({ role, content: contentItems });
3504
+ contentItems = [];
3505
+ }
3506
+ };
3507
+ for (const block of content) {
3508
+ if (!block || typeof block !== "object")
3509
+ continue;
3510
+ switch (block.type) {
3511
+ case "text":
3512
+ if (typeof block.text === "string") {
3513
+ contentItems.push({
3514
+ type: role === "assistant" ? "output_text" : "input_text",
3515
+ text: block.text
3516
+ });
3517
+ }
3518
+ break;
3519
+ case "image":
3520
+ if (role !== "assistant" && block.source) {
3521
+ const imageUrl = this.convertAnthropicImageSourceToUrl(block.source);
3522
+ if (imageUrl) {
3523
+ contentItems.push({ type: "input_image", image_url: imageUrl });
3524
+ }
3525
+ }
3526
+ break;
3527
+ case "tool_use":
3528
+ flushContentItems();
3529
+ input.push({
3530
+ type: "function_call",
3531
+ call_id: block.id || "",
3532
+ name: block.name || "",
3533
+ arguments: typeof block.input === "object" ? JSON.stringify(block.input) : "{}"
3534
+ });
3535
+ break;
3536
+ case "tool_result":
3537
+ flushContentItems();
3538
+ input.push({
3539
+ type: "function_call_output",
3540
+ call_id: block.tool_use_id || "",
3541
+ output: this.normalizeToolResultContent(block.content)
3542
+ });
3543
+ break;
3544
+ case "thinking":
3545
+ break;
3546
+ }
3547
+ }
3548
+ flushContentItems();
3549
+ continue;
3520
3550
  }
3551
+ input.push({ role });
3521
3552
  }
3522
- return false;
3553
+ return input;
3523
3554
  }
3524
- buildOpenAIResponsesRequest(anthropicBody) {
3525
- const chatBody = this.buildOpenAIRequest(anthropicBody);
3526
- const responsesInput = this.protocolConversion.convertChatMessagesToResponsesInput(chatBody.messages);
3527
- const responsesBody = {
3528
- model: chatBody.model,
3529
- input: responsesInput.input
3530
- };
3531
- if (responsesInput.instructions) {
3532
- responsesBody.instructions = responsesInput.instructions;
3555
+ convertAnthropicImageSourceToUrl(source) {
3556
+ if (!source || typeof source !== "object")
3557
+ return null;
3558
+ if (source.type === "base64" && source.media_type && source.data) {
3559
+ return `data:${source.media_type};base64,${source.data}`;
3533
3560
  }
3534
- const maxOutputTokens = chatBody.max_tokens ?? chatBody.max_completion_tokens;
3535
- if (maxOutputTokens !== undefined) {
3536
- responsesBody.max_output_tokens = maxOutputTokens;
3561
+ return null;
3562
+ }
3563
+ mapAnthropicToolChoiceToResponses(toolChoice) {
3564
+ if (toolChoice === undefined || toolChoice === null) {
3565
+ return;
3537
3566
  }
3538
- if (chatBody.temperature !== undefined) {
3539
- responsesBody.temperature = chatBody.temperature;
3567
+ if (typeof toolChoice === "string") {
3568
+ return toolChoice;
3540
3569
  }
3541
- if (chatBody.top_p !== undefined) {
3542
- responsesBody.top_p = chatBody.top_p;
3570
+ if (typeof toolChoice !== "object" || Array.isArray(toolChoice)) {
3571
+ return;
3543
3572
  }
3544
- if (chatBody.tools !== undefined) {
3545
- const mappedTools = this.protocolConversion.mapChatToolsToResponsesTools(chatBody.tools);
3546
- if (mappedTools.length > 0) {
3547
- responsesBody.tools = mappedTools;
3548
- }
3573
+ const choice = toolChoice;
3574
+ const type = typeof choice.type === "string" ? choice.type.trim().toLowerCase() : "";
3575
+ if (type === "any") {
3576
+ return "required";
3549
3577
  }
3550
- if (chatBody.stop !== undefined) {
3551
- responsesBody.stop = chatBody.stop;
3578
+ if (type === "auto") {
3579
+ return "auto";
3552
3580
  }
3553
- if (chatBody.reasoning_effort !== undefined) {
3554
- responsesBody.reasoning = {
3555
- effort: chatBody.reasoning_effort,
3556
- summary: "auto"
3581
+ if (type === "none") {
3582
+ return "none";
3583
+ }
3584
+ if (type === "tool") {
3585
+ return {
3586
+ type: "function",
3587
+ name: typeof choice.name === "string" ? choice.name : ""
3557
3588
  };
3558
3589
  }
3559
- if (chatBody.stream === true) {
3560
- responsesBody.stream = true;
3590
+ return toolChoice;
3591
+ }
3592
+ resolveAnthropicReasoningEffort(anthropicBody) {
3593
+ if (anthropicBody.output_config && typeof anthropicBody.output_config === "object") {
3594
+ const effort = typeof anthropicBody.output_config.effort === "string" ? anthropicBody.output_config.effort.trim().toLowerCase() : "";
3595
+ if (effort === "low" || effort === "medium" || effort === "high") {
3596
+ return effort;
3597
+ }
3598
+ if (effort === "max") {
3599
+ return "xhigh";
3600
+ }
3561
3601
  }
3562
- return responsesBody;
3602
+ if (anthropicBody.thinking && typeof anthropicBody.thinking === "object") {
3603
+ const thinkingType = typeof anthropicBody.thinking.type === "string" ? anthropicBody.thinking.type.trim().toLowerCase() : "";
3604
+ const thinkingEffort = typeof anthropicBody.thinking.effort === "string" ? anthropicBody.thinking.effort.trim().toLowerCase() : "";
3605
+ if (thinkingEffort === "low" || thinkingEffort === "medium" || thinkingEffort === "high") {
3606
+ return thinkingEffort;
3607
+ }
3608
+ if (thinkingEffort === "max") {
3609
+ return "xhigh";
3610
+ }
3611
+ if (thinkingType === "adaptive") {
3612
+ return "xhigh";
3613
+ }
3614
+ if (thinkingType === "enabled") {
3615
+ const budgetTokens = anthropicBody.thinking.budget_tokens;
3616
+ if (typeof budgetTokens === "number") {
3617
+ if (budgetTokens < 4000)
3618
+ return "low";
3619
+ if (budgetTokens < 16000)
3620
+ return "medium";
3621
+ }
3622
+ return "high";
3623
+ }
3624
+ }
3625
+ return;
3626
+ }
3627
+ supportsReasoningEffort(model) {
3628
+ if (!model || typeof model !== "string") {
3629
+ return false;
3630
+ }
3631
+ const normalized = model.trim().toLowerCase();
3632
+ if (normalized.length > 1 && normalized[0] === "o" && /\d/.test(normalized[1])) {
3633
+ return true;
3634
+ }
3635
+ if (normalized.startsWith("gpt-")) {
3636
+ const match = normalized.match(/gpt-(\d+)/);
3637
+ if (match) {
3638
+ const version = parseInt(match[1], 10);
3639
+ return version >= 5;
3640
+ }
3641
+ }
3642
+ return false;
3563
3643
  }
3564
3644
  convertSystemMessages(system) {
3565
3645
  if (typeof system === "string") {
3566
- const content = system.trim();
3646
+ const content = this.sanitizeSystemText(system).trim();
3567
3647
  return content ? [{ role: "system", content }] : [];
3568
3648
  }
3569
3649
  if (!Array.isArray(system)) {
@@ -3572,45 +3652,64 @@ class AnthropicToOpenAIConverter {
3572
3652
  const results = [];
3573
3653
  for (const block of system) {
3574
3654
  if (typeof block === "string") {
3575
- const content = block.trim();
3655
+ const content = this.sanitizeSystemText(block).trim();
3576
3656
  if (content) {
3577
3657
  results.push({ role: "system", content });
3578
3658
  }
3579
3659
  continue;
3580
3660
  }
3581
3661
  if (block && typeof block === "object" && block.type === "text") {
3582
- const content = typeof block.text === "string" ? block.text.trim() : "";
3662
+ const content = typeof block.text === "string" ? this.sanitizeSystemText(block.text).trim() : "";
3583
3663
  if (content) {
3584
- results.push({ role: "system", content });
3664
+ const message = { role: "system", content };
3665
+ if (block.cache_control !== undefined) {
3666
+ message.cache_control = block.cache_control;
3667
+ }
3668
+ results.push(message);
3585
3669
  }
3586
3670
  }
3587
3671
  }
3588
3672
  return results;
3589
3673
  }
3590
- validateAndCleanToolCalls(messages) {
3591
- const toolResultIds = new Set(messages.filter((msg) => msg.role === "tool" && typeof msg.tool_call_id === "string" && msg.tool_call_id).map((msg) => msg.tool_call_id));
3592
- const cleanedMessages = [];
3593
- for (const msg of messages) {
3594
- if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) {
3595
- cleanedMessages.push(msg);
3596
- continue;
3597
- }
3598
- const matchedToolCalls = msg.tool_calls.filter((tc) => typeof tc?.id === "string" && toolResultIds.has(tc.id));
3599
- const hasTextContent = typeof msg.content === "string" && msg.content.trim().length > 0;
3600
- if (matchedToolCalls.length > 0) {
3601
- cleanedMessages.push({
3602
- ...msg,
3603
- tool_calls: matchedToolCalls,
3604
- content: hasTextContent ? msg.content : null
3605
- });
3606
- continue;
3674
+ sanitizeSystemText(text) {
3675
+ return text.split(/(?<=\n)/).filter((segment) => !segment.replace(/\n$/, "").trimStart().startsWith("x-anthropic-billing-header:")).join("");
3676
+ }
3677
+ extractTextFromOpenAIContent(content) {
3678
+ if (typeof content === "string") {
3679
+ return content;
3680
+ }
3681
+ if (!Array.isArray(content)) {
3682
+ return "";
3683
+ }
3684
+ return content.map((part) => part?.type === "text" && typeof part.text === "string" ? part.text : "").filter((text) => text.length > 0).join(`
3685
+ `);
3686
+ }
3687
+ normalizeOpenAISystemMessages(messages) {
3688
+ const systemMessages = messages.filter((message) => message?.role === "system");
3689
+ if (systemMessages.length === 0) {
3690
+ return;
3691
+ }
3692
+ if (systemMessages.length === 1) {
3693
+ const index = messages.indexOf(systemMessages[0]);
3694
+ if (index > 0) {
3695
+ messages.splice(index, 1);
3696
+ messages.unshift(systemMessages[0]);
3607
3697
  }
3608
- if (hasTextContent) {
3609
- const { tool_calls, ...assistantTextOnly } = msg;
3610
- cleanedMessages.push(assistantTextOnly);
3698
+ return;
3699
+ }
3700
+ const combinedContent = systemMessages.map((message) => this.extractTextFromOpenAIContent(message.content)).filter((text) => text.length > 0).join(`
3701
+ `);
3702
+ for (let index = messages.length - 1;index >= 0; index -= 1) {
3703
+ if (messages[index]?.role === "system") {
3704
+ messages.splice(index, 1);
3611
3705
  }
3612
3706
  }
3613
- return cleanedMessages;
3707
+ if (combinedContent) {
3708
+ messages.unshift({
3709
+ role: "system",
3710
+ content: combinedContent
3711
+ });
3712
+ }
3614
3713
  }
3615
3714
  normalizeToolResultContent(content) {
3616
3715
  if (content === undefined || content === null) {
@@ -3619,59 +3718,16 @@ class AnthropicToOpenAIConverter {
3619
3718
  if (typeof content === "string") {
3620
3719
  return content;
3621
3720
  }
3622
- if (Array.isArray(content)) {
3623
- const normalizedBlocks = content.map((part) => {
3624
- if (typeof part === "string") {
3625
- return { type: "text", text: part };
3626
- }
3627
- if (!part || typeof part !== "object") {
3628
- return null;
3629
- }
3630
- if (part.type === "text" && typeof part.text === "string") {
3631
- return { type: "text", text: part.text };
3632
- }
3633
- if (part.type === "image" && part.source) {
3634
- if (part.source.type === "url" && typeof part.source.url === "string") {
3635
- return {
3636
- type: "image_url",
3637
- image_url: {
3638
- url: part.source.url
3639
- }
3640
- };
3641
- }
3642
- if (part.source.type === "base64" && part.source.media_type && part.source.data) {
3643
- return {
3644
- type: "image_url",
3645
- image_url: {
3646
- url: `data:${part.source.media_type};base64,${part.source.data}`
3647
- }
3648
- };
3649
- }
3650
- }
3651
- return {
3652
- type: "text",
3653
- text: JSON.stringify(part)
3654
- };
3655
- }).filter((block) => block !== null);
3656
- if (normalizedBlocks.length === 0) {
3657
- return "";
3658
- }
3659
- const firstBlock = normalizedBlocks[0];
3660
- if (normalizedBlocks.length === 1 && firstBlock && firstBlock.type === "text") {
3661
- return typeof firstBlock.text === "string" ? firstBlock.text : "";
3662
- }
3663
- return JSON.stringify(normalizedBlocks);
3664
- }
3665
- if (typeof content === "object") {
3666
- if ("content" in content) {
3667
- return this.normalizeToolResultContent(content.content);
3668
- }
3721
+ if (typeof content === "object")
3669
3722
  return JSON.stringify(content);
3670
- }
3671
3723
  return String(content);
3672
3724
  }
3673
3725
  convertUserMessage(msg, messages) {
3674
3726
  const content = msg.content;
3727
+ if (content === undefined) {
3728
+ messages.push({ role: "user", content: null });
3729
+ return;
3730
+ }
3675
3731
  if (typeof content === "string") {
3676
3732
  messages.push({
3677
3733
  role: "user",
@@ -3683,8 +3739,8 @@ class AnthropicToOpenAIConverter {
3683
3739
  const hasToolResult = content.some((block) => block?.type === "tool_result");
3684
3740
  if (hasToolResult) {
3685
3741
  for (const block of content) {
3686
- const toolUseId = typeof block?.tool_use_id === "string" ? block.tool_use_id.trim() : "";
3687
- if (block?.type !== "tool_result" || toolUseId.length === 0) {
3742
+ const toolUseId = typeof block?.tool_use_id === "string" ? block.tool_use_id : "";
3743
+ if (block?.type !== "tool_result") {
3688
3744
  continue;
3689
3745
  }
3690
3746
  messages.push({
@@ -3698,39 +3754,43 @@ class AnthropicToOpenAIConverter {
3698
3754
  if (userContentBlocks.length > 0) {
3699
3755
  messages.push({
3700
3756
  role: "user",
3701
- content: userContentBlocks
3757
+ content: this.normalizeOpenAIMessageContent(userContentBlocks)
3702
3758
  });
3703
3759
  }
3760
+ return;
3704
3761
  }
3762
+ messages.push({ role: "user", content });
3705
3763
  }
3706
3764
  convertUserContentBlock(block) {
3707
3765
  if (!block || typeof block !== "object") {
3708
3766
  return null;
3709
3767
  }
3710
3768
  if (block.type === "text") {
3711
- return {
3769
+ const textBlock = {
3712
3770
  type: "text",
3713
3771
  text: block.text || ""
3714
3772
  };
3773
+ if (block.cache_control !== undefined) {
3774
+ textBlock.cache_control = block.cache_control;
3775
+ }
3776
+ return textBlock;
3715
3777
  }
3716
3778
  if (block.type === "image" && block.source) {
3717
- if (block.source.type === "url" && block.source.url) {
3718
- return {
3719
- type: "image_url",
3720
- image_url: { url: block.source.url }
3721
- };
3722
- }
3723
- if (block.source.type === "base64" && block.source.media_type && block.source.data) {
3724
- return {
3725
- type: "image_url",
3726
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
3727
- };
3728
- }
3779
+ const mediaType = typeof block.source.media_type === "string" ? block.source.media_type : "image/png";
3780
+ const data = typeof block.source.data === "string" ? block.source.data : "";
3781
+ return {
3782
+ type: "image_url",
3783
+ image_url: { url: `data:${mediaType};base64,${data}` }
3784
+ };
3729
3785
  }
3730
3786
  return null;
3731
3787
  }
3732
3788
  convertAssistantMessage(msg, messages) {
3733
3789
  const content = msg.content;
3790
+ if (content === undefined) {
3791
+ messages.push({ role: "assistant", content: null });
3792
+ return;
3793
+ }
3734
3794
  if (typeof content === "string") {
3735
3795
  messages.push({
3736
3796
  role: "assistant",
@@ -3739,122 +3799,49 @@ class AnthropicToOpenAIConverter {
3739
3799
  return;
3740
3800
  }
3741
3801
  if (Array.isArray(content)) {
3742
- const toolUseBlocks = content.filter((block) => block.type === "tool_use");
3743
- if (toolUseBlocks.length > 0) {
3744
- const validToolUseBlocks = toolUseBlocks.filter((block) => typeof block?.name === "string" && block.name.trim().length > 0);
3745
- const toolCalls = validToolUseBlocks.map((block, index) => ({
3746
- id: this.normalizeToolCallId(block.id, block.name, index),
3747
- type: "function",
3748
- function: {
3749
- name: String(block.name).trim(),
3750
- arguments: JSON.stringify(block.input || {})
3751
- }
3752
- }));
3753
- let textContent = "";
3754
- for (const block of content) {
3755
- if (block.type === "text") {
3756
- textContent += block.text || "";
3757
- } else if (block.type === "thinking") {
3758
- textContent += `<thinking>
3759
- ${block.thinking || ""}
3760
- </thinking>
3761
-
3762
- `;
3802
+ const contentParts = [];
3803
+ const toolCalls = [];
3804
+ for (const block of content) {
3805
+ if (!block || typeof block !== "object")
3806
+ continue;
3807
+ if (block.type === "text" && typeof block.text === "string") {
3808
+ const textBlock = { type: "text", text: block.text };
3809
+ if (block.cache_control !== undefined) {
3810
+ textBlock.cache_control = block.cache_control;
3763
3811
  }
3812
+ contentParts.push(textBlock);
3813
+ continue;
3764
3814
  }
3765
- if (toolCalls.length > 0 || textContent.trim()) {
3766
- messages.push({
3767
- role: "assistant",
3768
- content: textContent.trim() || null,
3769
- ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
3815
+ if (block.type === "tool_use") {
3816
+ toolCalls.push({
3817
+ id: typeof block.id === "string" ? block.id : "",
3818
+ type: "function",
3819
+ function: {
3820
+ name: typeof block.name === "string" ? block.name : "",
3821
+ arguments: JSON.stringify(block.input ?? {})
3822
+ }
3770
3823
  });
3771
3824
  }
3772
- } else {
3773
- let textContent = "";
3774
- for (const block of content) {
3775
- if (block.type === "text") {
3776
- textContent += block.text || "";
3777
- } else if (block.type === "thinking") {
3778
- textContent += `<thinking>
3779
- ${block.thinking || ""}
3780
- </thinking>
3781
-
3782
- `;
3783
- }
3784
- }
3825
+ }
3826
+ if (contentParts.length > 0 || toolCalls.length > 0) {
3785
3827
  messages.push({
3786
3828
  role: "assistant",
3787
- content: textContent.trim()
3829
+ content: contentParts.length > 0 ? this.normalizeOpenAIMessageContent(contentParts) : null,
3830
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
3788
3831
  });
3789
3832
  }
3790
- }
3791
- }
3792
- normalizeToolCallId(rawId, name, index) {
3793
- if (typeof rawId === "string" && rawId.trim()) {
3794
- return rawId.trim();
3795
- }
3796
- const normalizedName = typeof name === "string" && name.trim() ? name.trim() : "tool";
3797
- return `call_${normalizedName}_${index}`;
3798
- }
3799
- mapAnthropicToolChoiceToOpenAI(toolChoice) {
3800
- if (toolChoice === undefined || toolChoice === null) {
3801
- return;
3802
- }
3803
- if (typeof toolChoice === "string") {
3804
- const normalized = toolChoice.trim().toLowerCase();
3805
- if (normalized === "auto" || normalized === "none" || normalized === "required") {
3806
- return normalized;
3807
- }
3808
- if (normalized === "any") {
3809
- return "required";
3810
- }
3811
- return;
3812
- }
3813
- if (typeof toolChoice !== "object" || Array.isArray(toolChoice)) {
3814
3833
  return;
3815
3834
  }
3816
- const choice = toolChoice;
3817
- const type = typeof choice.type === "string" ? choice.type.trim().toLowerCase() : "";
3818
- if (type === "tool") {
3819
- const toolName = typeof choice.name === "string" ? choice.name.trim() : "";
3820
- if (!toolName) {
3821
- return;
3822
- }
3823
- return {
3824
- type: "function",
3825
- function: {
3826
- name: toolName
3827
- }
3828
- };
3829
- }
3830
- if (type === "any") {
3831
- return "required";
3832
- }
3833
- if (type === "auto" || type === "none") {
3834
- return type;
3835
- }
3836
- return;
3835
+ messages.push({ role: "assistant", content });
3837
3836
  }
3838
- parseOpenAIImageUrlSource(url) {
3839
- if (!url)
3840
- return null;
3841
- if (url.startsWith("data:")) {
3842
- const parts = url.split(";base64,");
3843
- if (parts.length !== 2)
3844
- return null;
3845
- return {
3846
- type: "base64",
3847
- media_type: parts[0].replace("data:", ""),
3848
- data: parts[1]
3849
- };
3850
- }
3851
- if (url.startsWith("http://") || url.startsWith("https://")) {
3852
- return {
3853
- type: "url",
3854
- url
3855
- };
3837
+ normalizeOpenAIMessageContent(contentParts) {
3838
+ if (contentParts.length === 1) {
3839
+ const first = contentParts[0];
3840
+ if (first?.type === "text" && first.cache_control === undefined) {
3841
+ return first.text;
3842
+ }
3856
3843
  }
3857
- return null;
3844
+ return contentParts;
3858
3845
  }
3859
3846
  normalizeToolParametersSchema(schema) {
3860
3847
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
@@ -3868,7 +3855,24 @@ ${block.thinking || ""}
3868
3855
  normalized.properties = {};
3869
3856
  }
3870
3857
  }
3871
- return normalized;
3858
+ return normalized;
3859
+ }
3860
+ cleanOpenAIToolSchema(schema) {
3861
+ return this.normalizeJsonSchema(schema);
3862
+ }
3863
+ resolvePromptCacheKey(anthropicBody) {
3864
+ const candidates = [
3865
+ anthropicBody?.prompt_cache_key,
3866
+ anthropicBody?.metadata?.prompt_cache_key,
3867
+ anthropicBody?.metadata?.provider_id,
3868
+ anthropicBody?.metadata?.providerId
3869
+ ];
3870
+ for (const candidate of candidates) {
3871
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
3872
+ return candidate.trim();
3873
+ }
3874
+ }
3875
+ return;
3872
3876
  }
3873
3877
  normalizeJsonSchema(schema) {
3874
3878
  if (!schema || typeof schema !== "object") {
@@ -3879,6 +3883,9 @@ ${block.thinking || ""}
3879
3883
  }
3880
3884
  const result = {};
3881
3885
  for (const [key, value] of Object.entries(schema)) {
3886
+ if (key === "format" && value === "uri") {
3887
+ continue;
3888
+ }
3882
3889
  if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
3883
3890
  const normalizedProperties = {};
3884
3891
  for (const [propName, propSchema] of Object.entries(value)) {
@@ -3944,6 +3951,14 @@ ${block.thinking || ""}
3944
3951
  headers: ctx.response.headers
3945
3952
  });
3946
3953
  }
3954
+ if (this.isLikelyOpenAIErrorBody(openaiBody) && !this.isLikelyOpenAISuccessBody(openaiBody)) {
3955
+ const anthropicError = this.convertOpenAIErrorToAnthropic(openaiBody, ctx.response.status);
3956
+ return new Response(JSON.stringify(anthropicError), {
3957
+ status: ctx.response.status,
3958
+ statusText: ctx.response.statusText,
3959
+ headers: ctx.response.headers
3960
+ });
3961
+ }
3947
3962
  if (!this.isLikelyOpenAISuccessBody(openaiBody)) {
3948
3963
  return ctx.response;
3949
3964
  }
@@ -3970,11 +3985,12 @@ ${block.thinking || ""}
3970
3985
  return 0;
3971
3986
  }
3972
3987
  convertOpenAIErrorToAnthropic(openaiBody, status) {
3973
- const message = typeof openaiBody?.error?.message === "string" && openaiBody.error.message || typeof openaiBody?.message === "string" && openaiBody.message || `OpenAI request failed with status ${status}`;
3988
+ const message = typeof openaiBody?.error?.message === "string" && openaiBody.error.message || typeof openaiBody?.message === "string" && openaiBody.message || "Upstream error";
3989
+ const type = typeof openaiBody?.error?.type === "string" && openaiBody.error.type.length > 0 ? openaiBody.error.type : "invalid_request_error";
3974
3990
  return {
3975
3991
  type: "error",
3976
3992
  error: {
3977
- type: "api_error",
3993
+ type,
3978
3994
  message
3979
3995
  }
3980
3996
  };
@@ -4065,68 +4081,11 @@ ${block.thinking || ""}
4065
4081
  }
4066
4082
  return texts;
4067
4083
  }
4068
- appendUniqueThinkingBlocks(content, texts, seen) {
4069
- for (const text of texts) {
4070
- if (typeof text !== "string")
4071
- continue;
4072
- const normalized = text.trim();
4073
- if (!normalized)
4074
- continue;
4075
- if (seen.has(normalized))
4076
- continue;
4077
- content.push({
4078
- type: "thinking",
4079
- thinking: normalized
4080
- });
4081
- seen.add(normalized);
4082
- }
4083
- }
4084
- appendParsedContent(content, parsedContent, seenThinking) {
4085
- for (const block of parsedContent) {
4086
- if (!block || typeof block !== "object")
4087
- continue;
4088
- if (block.type === "thinking") {
4089
- const thinkingText = typeof block.thinking === "string" ? block.thinking.trim() : "";
4090
- if (!thinkingText || seenThinking.has(thinkingText)) {
4091
- continue;
4092
- }
4093
- content.push({
4094
- type: "thinking",
4095
- thinking: thinkingText
4096
- });
4097
- seenThinking.add(thinkingText);
4098
- continue;
4099
- }
4100
- if (block.type === "text") {
4101
- const text = typeof block.text === "string" ? block.text : "";
4102
- if (!text)
4103
- continue;
4104
- content.push({
4105
- type: "text",
4106
- text
4107
- });
4108
- }
4109
- }
4110
- }
4111
- extractChatCompletionReasoningTexts(message) {
4112
- const texts = [];
4113
- if (!message || typeof message !== "object") {
4114
- return texts;
4115
- }
4116
- const directReasoning = this.extractReasoningSummaryTexts({
4117
- summary: message.reasoning,
4118
- text: message.reasoning_content
4119
- });
4120
- texts.push(...directReasoning);
4121
- if (typeof message.reasoning === "string") {
4122
- texts.push(message.reasoning);
4123
- }
4124
- if (Array.isArray(message.reasoning_details)) {
4125
- for (const detail of message.reasoning_details) {
4126
- texts.push(...this.extractReasoningSummaryTexts(detail));
4127
- }
4084
+ extractStrictResponsesReasoningText(reasoningItem) {
4085
+ if (!reasoningItem || typeof reasoningItem !== "object" || !Array.isArray(reasoningItem.summary)) {
4086
+ return "";
4128
4087
  }
4129
- return texts;
4088
+ return reasoningItem.summary.filter((item) => item?.type === "summary_text" && typeof item.text === "string").map((item) => item.text).join("");
4130
4089
  }
4131
4090
  extractReasoningTextFromDeltaContentPart(part) {
4132
4091
  if (!part || typeof part !== "object") {
@@ -4230,6 +4189,19 @@ ${block.thinking || ""}
4230
4189
  ctx.streamState.set("chat_thinking_index", index);
4231
4190
  return index;
4232
4191
  }
4192
+ closeChatNonToolBlock(events, ctx) {
4193
+ const textIndex = ctx.streamState.get("chat_text_index");
4194
+ if (typeof textIndex === "number" && Number.isFinite(textIndex)) {
4195
+ events.push({ type: "content_block_stop", index: textIndex });
4196
+ ctx.streamState.delete("chat_text_index");
4197
+ ctx.streamState.delete("text_block_started");
4198
+ }
4199
+ const thinkingIndex = ctx.streamState.get("chat_thinking_index");
4200
+ if (typeof thinkingIndex === "number" && Number.isFinite(thinkingIndex)) {
4201
+ events.push({ type: "content_block_stop", index: thinkingIndex });
4202
+ ctx.streamState.delete("chat_thinking_index");
4203
+ }
4204
+ }
4233
4205
  resolveChatToolIndex(rawIndex, ctx) {
4234
4206
  const normalizedRawIndex = Number.isFinite(rawIndex) ? Math.trunc(rawIndex) : 0;
4235
4207
  const mapKey = `chat_tool_index_${normalizedRawIndex}`;
@@ -4255,6 +4227,59 @@ ${block.thinking || ""}
4255
4227
  }
4256
4228
  return 1;
4257
4229
  }
4230
+ nextResponsesContentIndex(ctx) {
4231
+ const current = ctx.streamState.get("resp_next_content_index");
4232
+ const next = typeof current === "number" && Number.isFinite(current) ? current : 0;
4233
+ ctx.streamState.set("resp_next_content_index", next + 1);
4234
+ return next;
4235
+ }
4236
+ resolveResponsesContentIndex(chunk, ctx) {
4237
+ const itemId = typeof chunk?.item_id === "string" ? chunk.item_id : "";
4238
+ const contentIndex = typeof chunk?.content_index === "number" ? chunk.content_index : undefined;
4239
+ const outputIndex = typeof chunk?.output_index === "number" ? chunk.output_index : undefined;
4240
+ const key = itemId && contentIndex !== undefined ? `resp_part_${itemId}_${contentIndex}` : outputIndex !== undefined && contentIndex !== undefined ? `resp_part_out_${outputIndex}_${contentIndex}` : "";
4241
+ if (key) {
4242
+ const existing = ctx.streamState.get(key);
4243
+ if (typeof existing === "number" && Number.isFinite(existing))
4244
+ return existing;
4245
+ const assigned2 = this.nextResponsesContentIndex(ctx);
4246
+ ctx.streamState.set(key, assigned2);
4247
+ return assigned2;
4248
+ }
4249
+ const fallback = ctx.streamState.get("resp_fallback_text_index");
4250
+ if (typeof fallback === "number" && Number.isFinite(fallback))
4251
+ return fallback;
4252
+ const assigned = this.nextResponsesContentIndex(ctx);
4253
+ ctx.streamState.set("resp_fallback_text_index", assigned);
4254
+ return assigned;
4255
+ }
4256
+ ensureResponsesTextBlockStarted(events, ctx, index) {
4257
+ const key = `resp_text_block_${index}`;
4258
+ if (ctx.streamState.get(key) === true)
4259
+ return;
4260
+ events.push({
4261
+ type: "content_block_start",
4262
+ index,
4263
+ content_block: { type: "text", text: "" }
4264
+ });
4265
+ ctx.streamState.set(key, true);
4266
+ }
4267
+ resolveResponsesToolIndex(chunk, ctx) {
4268
+ const itemId = typeof chunk?.item_id === "string" ? chunk.item_id : "";
4269
+ if (itemId) {
4270
+ const byItem = ctx.streamState.get(`resp_tool_item_${itemId}`);
4271
+ if (typeof byItem === "number" && Number.isFinite(byItem))
4272
+ return byItem;
4273
+ }
4274
+ const outputIndex = typeof chunk?.output_index === "number" ? chunk.output_index : undefined;
4275
+ if (outputIndex !== undefined) {
4276
+ const byOutput = ctx.streamState.get(`resp_tool_out_${outputIndex}`);
4277
+ if (typeof byOutput === "number" && Number.isFinite(byOutput))
4278
+ return byOutput;
4279
+ }
4280
+ const last = ctx.streamState.get("resp_last_tool_index");
4281
+ return typeof last === "number" && Number.isFinite(last) ? last : undefined;
4282
+ }
4258
4283
  ensureResponsesThinkingBlockStarted(events, ctx, index) {
4259
4284
  const blockKey = `resp_reasoning_block_${index}`;
4260
4285
  if (ctx.streamState.get(blockKey) === true) {
@@ -4305,15 +4330,16 @@ ${block.thinking || ""}
4305
4330
  convertOpenAIResponsesToAnthropic(openaiBody) {
4306
4331
  const content = [];
4307
4332
  const output = Array.isArray(openaiBody.output) ? openaiBody.output : [];
4333
+ let hasToolUse = false;
4308
4334
  for (const item of output) {
4309
4335
  if (!item || typeof item !== "object")
4310
4336
  continue;
4311
4337
  if (item.type === "reasoning") {
4312
- const summaryTexts = this.extractReasoningSummaryTexts(item);
4313
- for (const summaryText of summaryTexts) {
4338
+ const thinkingText = this.extractStrictResponsesReasoningText(item);
4339
+ if (thinkingText) {
4314
4340
  content.push({
4315
4341
  type: "thinking",
4316
- thinking: summaryText
4342
+ thinking: thinkingText
4317
4343
  });
4318
4344
  }
4319
4345
  continue;
@@ -4337,6 +4363,7 @@ ${block.thinking || ""}
4337
4363
  name: item.name || "",
4338
4364
  input: parsedInput
4339
4365
  });
4366
+ hasToolUse = true;
4340
4367
  continue;
4341
4368
  }
4342
4369
  if (item.type === "message" && Array.isArray(item.content)) {
@@ -4344,28 +4371,64 @@ ${block.thinking || ""}
4344
4371
  if (!part || typeof part !== "object")
4345
4372
  continue;
4346
4373
  if (part.type === "output_text" && typeof part.text === "string") {
4347
- content.push(...parseThinkingTags2(part.text));
4374
+ if (part.text)
4375
+ content.push({ type: "text", text: part.text });
4348
4376
  continue;
4349
4377
  }
4350
- if (part.type === "text" && typeof part.text === "string") {
4351
- content.push(...parseThinkingTags2(part.text));
4378
+ if (part.type === "refusal" && typeof part.refusal === "string") {
4379
+ if (part.refusal)
4380
+ content.push({ type: "text", text: part.refusal });
4352
4381
  }
4353
4382
  }
4354
4383
  }
4355
4384
  }
4356
- const stopReason = content.some((block) => block.type === "tool_use") ? "tool_use" : openaiBody.status === "incomplete" ? "max_tokens" : "end_turn";
4385
+ const stopReason = this.mapResponsesStopReason(openaiBody.status, hasToolUse, openaiBody.incomplete_details?.reason);
4386
+ const usage = this.buildAnthropicUsageFromResponses(openaiBody.usage);
4357
4387
  return {
4358
- id: openaiBody.id || generateAnthropicMessageId(),
4388
+ id: typeof openaiBody.id === "string" ? openaiBody.id : "",
4359
4389
  type: "message",
4360
4390
  role: "assistant",
4361
4391
  content,
4362
- model: openaiBody.model || "gpt-4",
4392
+ model: typeof openaiBody.model === "string" ? openaiBody.model : "",
4363
4393
  stop_reason: stopReason,
4364
- usage: {
4365
- input_tokens: openaiBody.usage?.input_tokens || 0,
4366
- output_tokens: openaiBody.usage?.output_tokens || 0
4367
- }
4394
+ stop_sequence: null,
4395
+ usage
4396
+ };
4397
+ }
4398
+ mapResponsesStopReason(status, hasToolUse, incompleteReason) {
4399
+ if (typeof status !== "string") {
4400
+ return null;
4401
+ }
4402
+ if (status === "completed") {
4403
+ return hasToolUse ? "tool_use" : "end_turn";
4404
+ }
4405
+ if (status === "incomplete") {
4406
+ return incompleteReason === undefined || incompleteReason === "max_output_tokens" || incompleteReason === "max_tokens" ? "max_tokens" : "end_turn";
4407
+ }
4408
+ return "end_turn";
4409
+ }
4410
+ buildAnthropicUsageFromResponses(usage) {
4411
+ if (!usage || typeof usage !== "object") {
4412
+ return { input_tokens: 0, output_tokens: 0 };
4413
+ }
4414
+ const result = {
4415
+ input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
4416
+ output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
4368
4417
  };
4418
+ const cachedFromInput = usage.input_tokens_details?.cached_tokens;
4419
+ const cachedFromPrompt = usage.prompt_tokens_details?.cached_tokens;
4420
+ if (typeof cachedFromInput === "number") {
4421
+ result.cache_read_input_tokens = cachedFromInput;
4422
+ } else if (typeof cachedFromPrompt === "number") {
4423
+ result.cache_read_input_tokens = cachedFromPrompt;
4424
+ }
4425
+ if (usage.cache_read_input_tokens !== undefined) {
4426
+ result.cache_read_input_tokens = usage.cache_read_input_tokens;
4427
+ }
4428
+ if (usage.cache_creation_input_tokens !== undefined) {
4429
+ result.cache_creation_input_tokens = usage.cache_creation_input_tokens;
4430
+ }
4431
+ return result;
4369
4432
  }
4370
4433
  convertOpenAIResponseToAnthropic(openaiBody) {
4371
4434
  const choice = openaiBody.choices?.[0];
@@ -4385,77 +4448,90 @@ ${block.thinking || ""}
4385
4448
  }
4386
4449
  const message = choice.message;
4387
4450
  const content = [];
4388
- const seenThinking = new Set;
4389
- this.appendUniqueThinkingBlocks(content, this.extractChatCompletionReasoningTexts(message), seenThinking);
4390
- if (Array.isArray(message.tool_calls)) {
4391
- for (const tc of message.tool_calls) {
4392
- let parsedInput = {};
4393
- try {
4394
- const parsedValue = JSON.parse(tc.function?.arguments || "{}");
4395
- if (parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue)) {
4396
- parsedInput = parsedValue;
4397
- }
4398
- } catch {
4399
- parsedInput = {};
4400
- }
4401
- content.push({
4402
- type: "tool_use",
4403
- id: tc.id || "",
4404
- name: tc.function?.name || "",
4405
- input: parsedInput
4406
- });
4407
- }
4451
+ const hasToolCalls2 = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
4452
+ if (message.refusal && typeof message.refusal === "string") {
4453
+ content.push({
4454
+ type: "text",
4455
+ text: message.refusal
4456
+ });
4408
4457
  }
4409
4458
  if (typeof message.content === "string" && message.content.length > 0) {
4410
- const parsedContent = parseThinkingTags2(message.content);
4411
- this.appendParsedContent(content, parsedContent, seenThinking);
4459
+ content.push({ type: "text", text: message.content });
4412
4460
  } else if (Array.isArray(message.content)) {
4413
4461
  for (const part of message.content) {
4414
- if (part?.type === "text" && typeof part.text === "string") {
4415
- this.appendParsedContent(content, parseThinkingTags2(part.text), seenThinking);
4462
+ if ((part?.type === "text" || part?.type === "output_text") && typeof part.text === "string") {
4463
+ if (part.text)
4464
+ content.push({ type: "text", text: part.text });
4416
4465
  continue;
4417
4466
  }
4418
- if (typeof part === "string") {
4419
- this.appendParsedContent(content, parseThinkingTags2(part), seenThinking);
4467
+ if (part?.type === "refusal" && typeof part.refusal === "string") {
4468
+ if (part.refusal)
4469
+ content.push({ type: "text", text: part.refusal });
4420
4470
  continue;
4421
4471
  }
4422
- if (part && typeof part === "object") {
4423
- const partType = typeof part.type === "string" ? part.type : "";
4424
- if (partType === "reasoning" || partType === "thinking" || part.thought === true) {
4425
- this.appendUniqueThinkingBlocks(content, this.extractReasoningSummaryTexts(part), seenThinking);
4426
- continue;
4427
- }
4428
- if (part.type === "image_url" && typeof part.image_url?.url === "string") {
4429
- const source = this.parseOpenAIImageUrlSource(part.image_url.url);
4430
- if (source) {
4431
- content.push({
4432
- type: "image",
4433
- source
4434
- });
4435
- continue;
4436
- }
4437
- }
4438
- content.push({
4439
- type: "text",
4440
- text: JSON.stringify(part)
4441
- });
4442
- }
4443
4472
  }
4444
4473
  }
4445
- const stopReason = mapOpenAIFinishReasonToAnthropic(choice.finish_reason || "stop");
4474
+ if (hasToolCalls2) {
4475
+ for (const tc of message.tool_calls) {
4476
+ content.push({
4477
+ type: "tool_use",
4478
+ id: tc.id || "",
4479
+ name: tc.function?.name || "",
4480
+ input: this.parseToolArguments(tc.function?.arguments)
4481
+ });
4482
+ }
4483
+ }
4484
+ if (!hasToolCalls2 && message.function_call) {
4485
+ const functionCall = message.function_call;
4486
+ const hasArguments = functionCall.arguments !== undefined;
4487
+ const name = typeof functionCall.name === "string" ? functionCall.name : "";
4488
+ if (name || hasArguments) {
4489
+ content.push({
4490
+ type: "tool_use",
4491
+ id: typeof functionCall.id === "string" ? functionCall.id : "",
4492
+ name,
4493
+ input: typeof functionCall.arguments === "string" ? this.parseToolArguments(functionCall.arguments) : functionCall.arguments && typeof functionCall.arguments === "object" ? functionCall.arguments : {}
4494
+ });
4495
+ }
4496
+ }
4497
+ let stopReason = mapOpenAIFinishReasonToAnthropic(choice.finish_reason || "stop");
4498
+ if (!choice.finish_reason && content.some((block) => block.type === "tool_use")) {
4499
+ stopReason = "tool_use";
4500
+ }
4501
+ const usage = {
4502
+ input_tokens: openaiBody.usage?.prompt_tokens ?? openaiBody.usage?.input_tokens ?? 0,
4503
+ output_tokens: openaiBody.usage?.completion_tokens ?? openaiBody.usage?.output_tokens ?? 0
4504
+ };
4505
+ const cacheReadTokens = openaiBody.usage?.prompt_tokens_details?.cached_tokens ?? openaiBody.usage?.cache_read_input_tokens;
4506
+ if (typeof cacheReadTokens === "number") {
4507
+ usage.cache_read_input_tokens = cacheReadTokens;
4508
+ }
4509
+ const cacheCreationTokens = openaiBody.usage?.cache_creation_input_tokens;
4510
+ if (typeof cacheCreationTokens === "number") {
4511
+ usage.cache_creation_input_tokens = cacheCreationTokens;
4512
+ }
4446
4513
  return {
4447
- id: openaiBody.id || generateAnthropicMessageId(),
4514
+ id: typeof openaiBody.id === "string" ? openaiBody.id : "",
4448
4515
  type: "message",
4449
4516
  role: "assistant",
4450
4517
  content,
4451
- model: openaiBody.model || "gpt-4",
4518
+ model: typeof openaiBody.model === "string" ? openaiBody.model : "",
4452
4519
  stop_reason: stopReason,
4453
- usage: {
4454
- input_tokens: openaiBody.usage?.prompt_tokens || 0,
4455
- output_tokens: openaiBody.usage?.completion_tokens || 0
4456
- }
4520
+ stop_sequence: null,
4521
+ usage
4457
4522
  };
4458
4523
  }
4524
+ parseToolArguments(argumentsValue) {
4525
+ if (typeof argumentsValue !== "string") {
4526
+ return {};
4527
+ }
4528
+ try {
4529
+ const parsedValue = JSON.parse(argumentsValue || "{}");
4530
+ return parsedValue && typeof parsedValue === "object" && !Array.isArray(parsedValue) ? parsedValue : {};
4531
+ } catch {
4532
+ return {};
4533
+ }
4534
+ }
4459
4535
  async processStreamChunk(chunk, ctx) {
4460
4536
  if (this.isResponsesStreamChunk(chunk)) {
4461
4537
  return this.processResponsesStreamChunk(chunk, ctx);
@@ -4489,7 +4565,7 @@ ${block.thinking || ""}
4489
4565
  const deltaText = this.extractDeltaText(delta?.content);
4490
4566
  const finishReason = choice.finish_reason;
4491
4567
  if (!ctx.streamState.has("message_started")) {
4492
- if (delta && (delta.content || delta.tool_calls || delta.role || thinkingDeltaText)) {
4568
+ if (delta && (delta.content || delta.tool_calls || delta.function_call || delta.role || thinkingDeltaText)) {
4493
4569
  events.push({
4494
4570
  type: "message_start",
4495
4571
  message: {
@@ -4497,14 +4573,20 @@ ${block.thinking || ""}
4497
4573
  type: "message",
4498
4574
  role: "assistant",
4499
4575
  content: [],
4500
- model: chunk.model || "gpt-4",
4501
- usage: { input_tokens: 0, output_tokens: 0 }
4576
+ model: chunk.model || "",
4577
+ usage: streamUsage || { input_tokens: 0, output_tokens: 0 }
4502
4578
  }
4503
4579
  });
4504
4580
  ctx.streamState.set("message_started", true);
4505
4581
  }
4506
4582
  }
4507
4583
  if (thinkingDeltaText) {
4584
+ const textIndex = ctx.streamState.get("chat_text_index");
4585
+ if (typeof textIndex === "number" && Number.isFinite(textIndex)) {
4586
+ events.push({ type: "content_block_stop", index: textIndex });
4587
+ ctx.streamState.delete("chat_text_index");
4588
+ ctx.streamState.delete("text_block_started");
4589
+ }
4508
4590
  const thinkingIndex = this.ensureChatThinkingBlockStarted(events, ctx);
4509
4591
  events.push({
4510
4592
  type: "content_block_delta",
@@ -4516,6 +4598,11 @@ ${block.thinking || ""}
4516
4598
  });
4517
4599
  }
4518
4600
  if (deltaText) {
4601
+ const thinkingIndex = ctx.streamState.get("chat_thinking_index");
4602
+ if (typeof thinkingIndex === "number" && Number.isFinite(thinkingIndex)) {
4603
+ events.push({ type: "content_block_stop", index: thinkingIndex });
4604
+ ctx.streamState.delete("chat_thinking_index");
4605
+ }
4519
4606
  const textIndex = this.ensureChatTextBlockStarted(events, ctx);
4520
4607
  events.push({
4521
4608
  type: "content_block_delta",
@@ -4526,7 +4613,8 @@ ${block.thinking || ""}
4526
4613
  }
4527
4614
  });
4528
4615
  }
4529
- if (delta.tool_calls) {
4616
+ if (delta?.tool_calls) {
4617
+ this.closeChatNonToolBlock(events, ctx);
4530
4618
  for (const tc of delta.tool_calls) {
4531
4619
  const rawIndex = typeof tc.index === "number" ? tc.index : 0;
4532
4620
  const index = this.resolveChatToolIndex(rawIndex, ctx);
@@ -4535,52 +4623,123 @@ ${block.thinking || ""}
4535
4623
  ctx.streamState.set(toolKey, {
4536
4624
  id: tc.id || "",
4537
4625
  name: tc.function?.name || "",
4538
- arguments: ""
4626
+ arguments: "",
4627
+ started: false
4539
4628
  });
4629
+ }
4630
+ const toolState = ctx.streamState.get(toolKey);
4631
+ if (typeof tc.id === "string")
4632
+ toolState.id = tc.id;
4633
+ if (typeof tc.function?.name === "string")
4634
+ toolState.name = tc.function.name;
4635
+ const argsDelta = typeof tc.function?.arguments === "string" ? tc.function.arguments : "";
4636
+ if (!toolState.started && toolState.id && toolState.name) {
4637
+ toolState.started = true;
4540
4638
  events.push({
4541
4639
  type: "content_block_start",
4542
4640
  index,
4543
4641
  content_block: {
4544
4642
  type: "tool_use",
4545
- id: tc.id || "",
4546
- name: tc.function?.name || "",
4643
+ id: toolState.id,
4644
+ name: toolState.name,
4547
4645
  input: {}
4548
4646
  }
4549
4647
  });
4648
+ if (toolState.arguments) {
4649
+ events.push({
4650
+ type: "content_block_delta",
4651
+ index,
4652
+ delta: {
4653
+ type: "input_json_delta",
4654
+ partial_json: toolState.arguments
4655
+ }
4656
+ });
4657
+ toolState.arguments = "";
4658
+ }
4550
4659
  }
4551
- const toolState = ctx.streamState.get(toolKey);
4552
- if (tc.function?.arguments) {
4553
- toolState.arguments += tc.function.arguments;
4660
+ if (argsDelta) {
4661
+ if (toolState.started) {
4662
+ events.push({
4663
+ type: "content_block_delta",
4664
+ index,
4665
+ delta: {
4666
+ type: "input_json_delta",
4667
+ partial_json: argsDelta
4668
+ }
4669
+ });
4670
+ } else {
4671
+ toolState.arguments += argsDelta;
4672
+ }
4554
4673
  ctx.streamState.set(toolKey, toolState);
4555
- events.push({
4556
- type: "content_block_delta",
4557
- index,
4558
- delta: {
4559
- type: "input_json_delta",
4560
- partial_json: tc.function.arguments
4561
- }
4562
- });
4563
4674
  }
4564
4675
  }
4565
4676
  }
4566
- if (finishReason) {
4567
- const textIndex = ctx.streamState.get("chat_text_index");
4568
- if (typeof textIndex === "number" && Number.isFinite(textIndex)) {
4677
+ if (delta?.function_call) {
4678
+ this.closeChatNonToolBlock(events, ctx);
4679
+ const functionCall = delta.function_call;
4680
+ if (typeof functionCall.name === "string") {
4681
+ ctx.streamState.set("legacy_function_name", functionCall.name);
4682
+ }
4683
+ if (!ctx.streamState.has("legacy_function_index")) {
4684
+ const index2 = this.allocateChatContentBlockIndex(ctx, 0, 0);
4685
+ ctx.streamState.set("legacy_function_index", index2);
4569
4686
  events.push({
4570
- type: "content_block_stop",
4571
- index: textIndex
4687
+ type: "content_block_start",
4688
+ index: index2,
4689
+ content_block: {
4690
+ type: "tool_use",
4691
+ id: "",
4692
+ name: ctx.streamState.get("legacy_function_name") || "",
4693
+ input: {}
4694
+ }
4572
4695
  });
4573
4696
  }
4574
- const thinkingIndex = ctx.streamState.get("chat_thinking_index");
4575
- if (typeof thinkingIndex === "number" && Number.isFinite(thinkingIndex)) {
4697
+ const index = ctx.streamState.get("legacy_function_index");
4698
+ if (typeof functionCall.arguments === "string" && functionCall.arguments.length > 0) {
4576
4699
  events.push({
4577
- type: "content_block_stop",
4578
- index: thinkingIndex
4700
+ type: "content_block_delta",
4701
+ index,
4702
+ delta: {
4703
+ type: "input_json_delta",
4704
+ partial_json: functionCall.arguments
4705
+ }
4579
4706
  });
4580
4707
  }
4708
+ }
4709
+ if (finishReason) {
4710
+ this.closeChatNonToolBlock(events, ctx);
4711
+ const legacyFunctionIndex = ctx.streamState.get("legacy_function_index");
4712
+ if (typeof legacyFunctionIndex === "number" && Number.isFinite(legacyFunctionIndex)) {
4713
+ events.push({ type: "content_block_stop", index: legacyFunctionIndex });
4714
+ }
4581
4715
  const toolKeys = Array.from(ctx.streamState.keys()).filter((k) => k.startsWith("tool_")).sort((a, b) => Number(a.slice(5)) - Number(b.slice(5)));
4582
4716
  for (const toolKey of toolKeys) {
4583
4717
  const toolIndex = Number(toolKey.slice(5));
4718
+ const toolState = ctx.streamState.get(toolKey);
4719
+ if (toolState && !toolState.started && (toolState.arguments || toolState.id || toolState.name)) {
4720
+ const fallbackId = toolState.id || `tool_call_${toolIndex}`;
4721
+ const fallbackName = toolState.name || "unknown_tool";
4722
+ events.push({
4723
+ type: "content_block_start",
4724
+ index: Number.isFinite(toolIndex) ? toolIndex : 0,
4725
+ content_block: {
4726
+ type: "tool_use",
4727
+ id: fallbackId,
4728
+ name: fallbackName,
4729
+ input: {}
4730
+ }
4731
+ });
4732
+ if (toolState.arguments) {
4733
+ events.push({
4734
+ type: "content_block_delta",
4735
+ index: Number.isFinite(toolIndex) ? toolIndex : 0,
4736
+ delta: {
4737
+ type: "input_json_delta",
4738
+ partial_json: toolState.arguments
4739
+ }
4740
+ });
4741
+ }
4742
+ }
4584
4743
  events.push({
4585
4744
  type: "content_block_stop",
4586
4745
  index: Number.isFinite(toolIndex) ? toolIndex : 0
@@ -4659,8 +4818,8 @@ ${block.thinking || ""}
4659
4818
  type: "message",
4660
4819
  role: "assistant",
4661
4820
  content: [],
4662
- model: chunk.response?.model || "gpt-4",
4663
- usage: { input_tokens: 0, output_tokens: 0 }
4821
+ model: chunk.response?.model || "",
4822
+ usage: this.buildAnthropicUsageFromResponses(chunk.response?.usage)
4664
4823
  }
4665
4824
  });
4666
4825
  ctx.streamState.set("message_started", true);
@@ -4681,22 +4840,42 @@ ${block.thinking || ""}
4681
4840
  ensureMessageStarted();
4682
4841
  return this.finalizeResponsesStreamEvents(events, ctx);
4683
4842
  }
4684
- if (eventType === "response.in_progress" || eventType === "keepalive") {
4843
+ if (eventType === "response.content_part.added") {
4685
4844
  ensureMessageStarted();
4686
- events.push({
4687
- type: "ping"
4688
- });
4845
+ const partType = chunk.part?.type;
4846
+ if (partType === "output_text" || partType === "refusal") {
4847
+ const index = this.resolveResponsesContentIndex(chunk, ctx);
4848
+ this.ensureResponsesTextBlockStarted(events, ctx, index);
4849
+ }
4850
+ return this.finalizeResponsesStreamEvents(events, ctx);
4851
+ }
4852
+ if (eventType === "response.content_part.done" || eventType === "response.refusal.done" || eventType === "response.reasoning.done") {
4853
+ const index = this.resolveResponsesContentIndex(chunk, ctx);
4854
+ if (ctx.streamState.get(`resp_text_block_${index}`) === true || ctx.streamState.get(`resp_reasoning_block_${index}`) === true) {
4855
+ events.push({ type: "content_block_stop", index });
4856
+ ctx.streamState.delete(`resp_text_block_${index}`);
4857
+ ctx.streamState.delete(`resp_reasoning_block_${index}`);
4858
+ }
4859
+ return this.finalizeResponsesStreamEvents(events, ctx);
4860
+ }
4861
+ if (eventType === "response.in_progress" || eventType === "keepalive") {
4689
4862
  return this.finalizeResponsesStreamEvents(events, ctx);
4690
4863
  }
4691
4864
  if (eventType === "response.output_item.added") {
4692
4865
  const item = chunk.item;
4693
4866
  if (item?.type === "function_call") {
4694
- const rawIndex = typeof chunk.output_index === "number" ? chunk.output_index + 1 : typeof item.output_index === "number" ? item.output_index + 1 : 1;
4695
- const index = Number.isFinite(rawIndex) ? rawIndex : 1;
4867
+ const index = this.nextResponsesContentIndex(ctx);
4696
4868
  const metaKey = `resp_tool_meta_${index}`;
4697
4869
  const callId = typeof item.call_id === "string" ? item.call_id : typeof item.id === "string" ? item.id : `tool_${index}`;
4698
4870
  const name = typeof item.name === "string" ? item.name : "tool";
4699
4871
  ctx.streamState.set(metaKey, { callId, name });
4872
+ if (typeof item.id === "string")
4873
+ ctx.streamState.set(`resp_tool_item_${item.id}`, index);
4874
+ if (typeof chunk.item_id === "string")
4875
+ ctx.streamState.set(`resp_tool_item_${chunk.item_id}`, index);
4876
+ if (typeof chunk.output_index === "number")
4877
+ ctx.streamState.set(`resp_tool_out_${chunk.output_index}`, index);
4878
+ ctx.streamState.set("resp_last_tool_index", index);
4700
4879
  if (!ctx.streamState.has("message_started")) {
4701
4880
  events.push({
4702
4881
  type: "message_start",
@@ -4705,7 +4884,7 @@ ${block.thinking || ""}
4705
4884
  type: "message",
4706
4885
  role: "assistant",
4707
4886
  content: [],
4708
- model: chunk.response?.model || "gpt-4",
4887
+ model: chunk.response?.model || "",
4709
4888
  usage: { input_tokens: 0, output_tokens: 0 }
4710
4889
  }
4711
4890
  });
@@ -4734,8 +4913,7 @@ ${block.thinking || ""}
4734
4913
  if (eventType === "response.output_item.done") {
4735
4914
  const item = chunk.item;
4736
4915
  if (item?.type === "function_call") {
4737
- const rawIndex = typeof chunk.output_index === "number" ? chunk.output_index + 1 : typeof item.output_index === "number" ? item.output_index + 1 : 1;
4738
- const index = Number.isFinite(rawIndex) ? rawIndex : 1;
4916
+ const index = this.resolveResponsesToolIndex({ ...chunk, item_id: chunk.item_id || item.id }, ctx) ?? this.nextResponsesContentIndex(ctx);
4739
4917
  const toolKey = `resp_tool_${index}`;
4740
4918
  const metaKey = `resp_tool_meta_${index}`;
4741
4919
  const meta = ctx.streamState.get(metaKey);
@@ -4749,7 +4927,7 @@ ${block.thinking || ""}
4749
4927
  type: "message",
4750
4928
  role: "assistant",
4751
4929
  content: [],
4752
- model: chunk.response?.model || "gpt-4",
4930
+ model: chunk.response?.model || "",
4753
4931
  usage: { input_tokens: 0, output_tokens: 0 }
4754
4932
  }
4755
4933
  });
@@ -4787,8 +4965,10 @@ ${block.thinking || ""}
4787
4965
  return this.finalizeResponsesStreamEvents(events, ctx);
4788
4966
  }
4789
4967
  if (eventType === "response.function_call_arguments.done") {
4790
- const rawIndex = typeof chunk.output_index === "number" ? chunk.output_index + 1 : 1;
4791
- const index = Number.isFinite(rawIndex) ? rawIndex : 1;
4968
+ const index = this.resolveResponsesToolIndex(chunk, ctx);
4969
+ if (index === undefined) {
4970
+ return this.finalizeResponsesStreamEvents(events, ctx);
4971
+ }
4792
4972
  const toolKey = `resp_tool_${index}`;
4793
4973
  const metaKey = `resp_tool_meta_${index}`;
4794
4974
  const meta = ctx.streamState.get(metaKey);
@@ -4802,7 +4982,7 @@ ${block.thinking || ""}
4802
4982
  type: "message",
4803
4983
  role: "assistant",
4804
4984
  content: [],
4805
- model: chunk.response?.model || "gpt-4",
4985
+ model: chunk.response?.model || "",
4806
4986
  usage: { input_tokens: 0, output_tokens: 0 }
4807
4987
  }
4808
4988
  });
@@ -4821,36 +5001,24 @@ ${block.thinking || ""}
4821
5001
  }
4822
5002
  });
4823
5003
  }
4824
- const hasDeltaKey = `resp_tool_has_delta_${index}`;
4825
- const hasDelta = ctx.streamState.get(hasDeltaKey) === true;
4826
- const fullArgsSentKey = `resp_tool_full_args_sent_${index}`;
4827
- const fullArgsSent = ctx.streamState.get(fullArgsSentKey) === true;
4828
- const argsDoneSeenKey = `resp_tool_args_done_seen_${index}`;
4829
- const argsDoneSeen = ctx.streamState.get(argsDoneSeenKey) === true;
4830
- const doneArgsKey = `resp_tool_done_arguments_${index}`;
4831
- const previousDoneArgs = ctx.streamState.get(doneArgsKey);
4832
- const fullArgs = typeof chunk.arguments === "string" ? chunk.arguments : "";
4833
- if (argsDoneSeen && (fullArgsSent || hasDelta || fullArgs && previousDoneArgs === fullArgs)) {
4834
- return this.finalizeResponsesStreamEvents(events, ctx);
4835
- }
4836
- ctx.streamState.set(argsDoneSeenKey, true);
4837
- if (fullArgs) {
4838
- ctx.streamState.set(doneArgsKey, fullArgs);
4839
- }
4840
- if (fullArgs && !hasDelta && !fullArgsSent) {
4841
- ctx.streamState.set(fullArgsSentKey, true);
5004
+ const doneArguments = typeof chunk.arguments === "string" ? chunk.arguments : "";
5005
+ const hasDelta = ctx.streamState.get(`resp_tool_has_delta_${index}`) === true;
5006
+ if (doneArguments && !hasDelta) {
5007
+ ctx.streamState.set(`resp_tool_has_delta_${index}`, true);
4842
5008
  events.push({
4843
5009
  type: "content_block_delta",
4844
5010
  index,
4845
5011
  delta: {
4846
5012
  type: "input_json_delta",
4847
- partial_json: fullArgs
5013
+ partial_json: doneArguments
4848
5014
  }
4849
5015
  });
4850
5016
  }
5017
+ events.push({ type: "content_block_stop", index });
5018
+ ctx.streamState.set(`resp_tool_closed_${index}`, true);
4851
5019
  return this.finalizeResponsesStreamEvents(events, ctx);
4852
5020
  }
4853
- if (!ctx.streamState.has("message_started") && eventType === "response.output_text.delta") {
5021
+ if (!ctx.streamState.has("message_started") && (eventType === "response.output_text.delta" || eventType === "response.refusal.delta")) {
4854
5022
  events.push({
4855
5023
  type: "message_start",
4856
5024
  message: {
@@ -4858,29 +5026,20 @@ ${block.thinking || ""}
4858
5026
  type: "message",
4859
5027
  role: "assistant",
4860
5028
  content: [],
4861
- model: chunk.response?.model || "gpt-4",
5029
+ model: chunk.response?.model || "",
4862
5030
  usage: { input_tokens: 0, output_tokens: 0 }
4863
5031
  }
4864
5032
  });
4865
5033
  ctx.streamState.set("message_started", true);
4866
5034
  }
4867
- if (eventType === "response.output_text.delta") {
4868
- if (!ctx.streamState.has("text_block_started")) {
4869
- events.push({
4870
- type: "content_block_start",
4871
- index: 0,
4872
- content_block: {
4873
- type: "text",
4874
- text: ""
4875
- }
4876
- });
4877
- ctx.streamState.set("text_block_started", true);
4878
- }
5035
+ if (eventType === "response.output_text.delta" || eventType === "response.refusal.delta") {
5036
+ const index = this.resolveResponsesContentIndex(chunk, ctx);
5037
+ this.ensureResponsesTextBlockStarted(events, ctx, index);
4879
5038
  const deltaText = typeof chunk.delta === "string" ? chunk.delta : typeof chunk.text === "string" ? chunk.text : "";
4880
5039
  if (deltaText) {
4881
5040
  events.push({
4882
5041
  type: "content_block_delta",
4883
- index: 0,
5042
+ index,
4884
5043
  delta: {
4885
5044
  type: "text_delta",
4886
5045
  text: deltaText
@@ -4915,8 +5074,7 @@ ${block.thinking || ""}
4915
5074
  return this.finalizeResponsesStreamEvents(events, ctx);
4916
5075
  }
4917
5076
  if (eventType === "response.function_call_arguments.delta") {
4918
- const rawIndex = typeof chunk.output_index === "number" ? chunk.output_index + 1 : 1;
4919
- const index = Number.isFinite(rawIndex) ? rawIndex : 1;
5077
+ const index = this.resolveResponsesToolIndex(chunk, ctx) ?? this.nextResponsesContentIndex(ctx);
4920
5078
  const argsDoneSeen = ctx.streamState.get(`resp_tool_args_done_seen_${index}`) === true;
4921
5079
  const fullArgsSent = ctx.streamState.get(`resp_tool_full_args_sent_${index}`) === true;
4922
5080
  if (argsDoneSeen || fullArgsSent) {
@@ -4930,13 +5088,16 @@ ${block.thinking || ""}
4930
5088
  type: "message",
4931
5089
  role: "assistant",
4932
5090
  content: [],
4933
- model: chunk.response?.model || "gpt-4",
5091
+ model: chunk.response?.model || "",
4934
5092
  usage: { input_tokens: 0, output_tokens: 0 }
4935
5093
  }
4936
5094
  });
4937
5095
  ctx.streamState.set("message_started", true);
4938
5096
  }
4939
5097
  const toolKey = `resp_tool_${index}`;
5098
+ if (ctx.streamState.get(`resp_tool_closed_${index}`) === true) {
5099
+ return this.finalizeResponsesStreamEvents(events, ctx);
5100
+ }
4940
5101
  const metaKey = `resp_tool_meta_${index}`;
4941
5102
  const meta = ctx.streamState.get(metaKey);
4942
5103
  const callId = typeof chunk.call_id === "string" ? chunk.call_id : typeof meta?.callId === "string" ? meta.callId : typeof chunk.item_id === "string" ? chunk.item_id : `tool_${index}`;
@@ -4978,17 +5139,16 @@ ${block.thinking || ""}
4978
5139
  type: "message",
4979
5140
  role: "assistant",
4980
5141
  content: [],
4981
- model: chunk.response?.model || "gpt-4",
5142
+ model: chunk.response?.model || "",
4982
5143
  usage: { input_tokens: 0, output_tokens: 0 }
4983
5144
  }
4984
5145
  });
4985
5146
  ctx.streamState.set("message_started", true);
4986
5147
  }
4987
- if (ctx.streamState.has("text_block_started")) {
4988
- events.push({
4989
- type: "content_block_stop",
4990
- index: 0
4991
- });
5148
+ const textBlockKeys = Array.from(ctx.streamState.keys()).filter((k) => typeof k === "string" && /^resp_text_block_\d+$/.test(k)).sort((a, b) => Number(a.slice("resp_text_block_".length)) - Number(b.slice("resp_text_block_".length)));
5149
+ for (const blockKey of textBlockKeys) {
5150
+ const textIndex = Number(blockKey.slice("resp_text_block_".length));
5151
+ events.push({ type: "content_block_stop", index: Number.isFinite(textIndex) ? textIndex : 0 });
4992
5152
  }
4993
5153
  const reasoningBlockKeys = Array.from(ctx.streamState.keys()).filter((k) => typeof k === "string" && /^resp_reasoning_block_\d+$/.test(k)).sort((a, b) => Number(a.slice("resp_reasoning_block_".length)) - Number(b.slice("resp_reasoning_block_".length)));
4994
5154
  for (const blockKey of reasoningBlockKeys) {
@@ -5001,20 +5161,22 @@ ${block.thinking || ""}
5001
5161
  const toolKeys = Array.from(ctx.streamState.keys()).filter((k) => typeof k === "string" && /^resp_tool_\d+$/.test(k)).sort((a, b) => Number(a.slice(10)) - Number(b.slice(10)));
5002
5162
  for (const toolKey of toolKeys) {
5003
5163
  const toolIndex = Number(toolKey.slice(10));
5164
+ if (ctx.streamState.get(`resp_tool_closed_${toolIndex}`) === true) {
5165
+ continue;
5166
+ }
5004
5167
  events.push({
5005
5168
  type: "content_block_stop",
5006
5169
  index: Number.isFinite(toolIndex) ? toolIndex : 1
5007
5170
  });
5008
5171
  }
5009
5172
  const usage = chunk.response?.usage && typeof chunk.response.usage === "object" ? chunk.response.usage : chunk.usage;
5010
- const normalizedUsage = usage ? {
5011
- input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
5012
- output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0
5013
- } : undefined;
5173
+ const normalizedUsage = usage ? this.buildAnthropicUsageFromResponses(usage) : undefined;
5174
+ const responseStatus = typeof chunk.response?.status === "string" ? chunk.response.status : eventType === "response.completed" ? "completed" : eventType === "response.incomplete" ? "incomplete" : "failed";
5175
+ const hasToolUse = toolKeys.length > 0;
5014
5176
  events.push({
5015
5177
  type: "message_delta",
5016
5178
  delta: {
5017
- stop_reason: toolKeys.length > 0 ? "tool_use" : eventType === "response.incomplete" ? "max_tokens" : "end_turn"
5179
+ stop_reason: this.mapResponsesStopReason(responseStatus, hasToolUse, chunk.response?.incomplete_details?.reason)
5018
5180
  },
5019
5181
  ...normalizedUsage && { usage: normalizedUsage }
5020
5182
  });
@@ -5197,10 +5359,14 @@ class GeminiToAnthropicConverter {
5197
5359
  const text = part.text.trim();
5198
5360
  if (text) {
5199
5361
  if (part.thought) {
5200
- anthropicContent.push({
5362
+ const thinkingBlock = {
5201
5363
  type: "thinking",
5202
5364
  thinking: text
5203
- });
5365
+ };
5366
+ if (part.thoughtSignature) {
5367
+ thinkingBlock.thought_signature = part.thoughtSignature;
5368
+ }
5369
+ anthropicContent.push(thinkingBlock);
5204
5370
  } else {
5205
5371
  anthropicContent.push({
5206
5372
  type: "text",
@@ -5223,12 +5389,16 @@ class GeminiToAnthropicConverter {
5223
5389
  const queue = pendingToolUseIdsByName.get(fc.name) || [];
5224
5390
  queue.push(toolUseId);
5225
5391
  pendingToolUseIdsByName.set(fc.name, queue);
5226
- anthropicContent.push({
5392
+ const toolUseBlock = {
5227
5393
  type: "tool_use",
5228
5394
  id: toolUseId,
5229
5395
  name: fc.name,
5230
5396
  input: fc.args || {}
5231
- });
5397
+ };
5398
+ if (part.thoughtSignature) {
5399
+ toolUseBlock.thought_signature = part.thoughtSignature;
5400
+ }
5401
+ anthropicContent.push(toolUseBlock);
5232
5402
  } else if (part.functionResponse) {
5233
5403
  const fr = part.functionResponse;
5234
5404
  const respContent = typeof fr.response === "object" ? fr.response.content || JSON.stringify(fr.response) : String(fr.response);
@@ -5421,15 +5591,23 @@ class GeminiToAnthropicConverter {
5421
5591
  } else if (item.type === "thinking") {
5422
5592
  const thinking = item.thinking || "";
5423
5593
  if (thinking.trim()) {
5424
- parts.push({ text: thinking, thought: true });
5594
+ const thinkingPart = { text: thinking, thought: true };
5595
+ if (item.thought_signature) {
5596
+ thinkingPart.thoughtSignature = item.thought_signature;
5597
+ }
5598
+ parts.push(thinkingPart);
5425
5599
  }
5426
5600
  } else if (item.type === "tool_use") {
5427
- parts.push({
5601
+ const fcPart = {
5428
5602
  functionCall: {
5429
5603
  name: item.name || "",
5430
5604
  args: item.input || {}
5431
5605
  }
5432
- });
5606
+ };
5607
+ if (item.thought_signature) {
5608
+ fcPart.thoughtSignature = item.thought_signature;
5609
+ }
5610
+ parts.push(fcPart);
5433
5611
  }
5434
5612
  }
5435
5613
  }
@@ -5464,7 +5642,15 @@ class GeminiToAnthropicConverter {
5464
5642
  }
5465
5643
  async processStreamChunk(chunk, ctx) {
5466
5644
  const eventType = chunk.type;
5467
- if (eventType === "message_start" || eventType === "content_block_start" || eventType === "content_block_stop" || eventType === "message_stop" || eventType === "ping") {
5645
+ if (eventType === "content_block_start") {
5646
+ const block = chunk.content_block;
5647
+ if (block && block.thought_signature) {
5648
+ const idx = chunk.index ?? 0;
5649
+ ctx.streamState.set(`part_${idx}_sig`, block.thought_signature);
5650
+ }
5651
+ return [];
5652
+ }
5653
+ if (eventType === "message_start" || eventType === "content_block_stop" || eventType === "message_stop" || eventType === "ping") {
5468
5654
  return [];
5469
5655
  }
5470
5656
  if (eventType === "content_block_delta") {
@@ -5490,7 +5676,12 @@ class GeminiToAnthropicConverter {
5490
5676
  accumulatedThinking += delta.thinking || "";
5491
5677
  ctx.streamState.set("accumulated_thinking", accumulatedThinking);
5492
5678
  if (accumulatedThinking) {
5493
- parts.push({ text: accumulatedThinking, thought: true });
5679
+ const thinkingPart = { text: accumulatedThinking, thought: true };
5680
+ const sig = ctx.streamState.get(`part_${chunk.index ?? 0}_sig`);
5681
+ if (sig) {
5682
+ thinkingPart.thoughtSignature = sig;
5683
+ }
5684
+ parts.push(thinkingPart);
5494
5685
  }
5495
5686
  } else if (delta.type === "input_json_delta") {
5496
5687
  return [];
@@ -5640,7 +5831,7 @@ class GeminiToOpenAIConverter {
5640
5831
  const queue = pendingToolCallIdsByName.get(functionName) || [];
5641
5832
  queue.push(toolCallId);
5642
5833
  pendingToolCallIdsByName.set(functionName, queue);
5643
- return {
5834
+ const tc = {
5644
5835
  id: toolCallId,
5645
5836
  type: "function",
5646
5837
  function: {
@@ -5648,6 +5839,10 @@ class GeminiToOpenAIConverter {
5648
5839
  arguments: JSON.stringify(p.functionCall.args || {})
5649
5840
  }
5650
5841
  };
5842
+ if (p.thoughtSignature) {
5843
+ tc.extra_content = { google: { thought_signature: p.thoughtSignature } };
5844
+ }
5845
+ return tc;
5651
5846
  })
5652
5847
  });
5653
5848
  } else {
@@ -5835,12 +6030,17 @@ class GeminiToOpenAIConverter {
5835
6030
  const parts = [];
5836
6031
  if (message.tool_calls) {
5837
6032
  for (const tc of message.tool_calls) {
5838
- parts.push({
6033
+ const fcPart = {
5839
6034
  functionCall: {
5840
6035
  name: tc.function.name,
5841
6036
  args: this.parseToolArgumentsToObject(tc.function.arguments)
5842
6037
  }
5843
- });
6038
+ };
6039
+ const sig = tc.extra_content?.google?.thought_signature || tc.thought_signature;
6040
+ if (sig) {
6041
+ fcPart.thoughtSignature = sig;
6042
+ }
6043
+ parts.push(fcPart);
5844
6044
  }
5845
6045
  } else if (message.content) {
5846
6046
  parts.push({ text: message.content });
@@ -6321,12 +6521,19 @@ class OpenAIToGeminiConverter {
6321
6521
  role: "model",
6322
6522
  parts: [
6323
6523
  ...textParts,
6324
- ...m.tool_calls.map((tc) => ({
6325
- functionCall: {
6326
- name: tc.function.name,
6327
- args: this.parseToolArgumentsToObject(tc.function.arguments)
6524
+ ...m.tool_calls.map((tc) => {
6525
+ const fcPart = {
6526
+ functionCall: {
6527
+ name: tc.function.name,
6528
+ args: this.parseToolArgumentsToObject(tc.function.arguments)
6529
+ }
6530
+ };
6531
+ const sig = tc.extra_content?.google?.thought_signature || tc.thought_signature;
6532
+ if (sig) {
6533
+ fcPart.thoughtSignature = sig;
6328
6534
  }
6329
- }))
6535
+ return fcPart;
6536
+ })
6330
6537
  ]
6331
6538
  };
6332
6539
  }
@@ -6528,14 +6735,18 @@ class OpenAIToGeminiConverter {
6528
6735
  if (part.text) {
6529
6736
  textContent += part.text;
6530
6737
  } else if (part.functionCall) {
6531
- toolCalls.push({
6738
+ const tc = {
6532
6739
  id: `call_${part.functionCall.name}_${index}`,
6533
6740
  type: "function",
6534
6741
  function: {
6535
6742
  name: part.functionCall.name,
6536
6743
  arguments: JSON.stringify(part.functionCall.args || {})
6537
6744
  }
6538
- });
6745
+ };
6746
+ if (part.thoughtSignature) {
6747
+ tc.extra_content = { google: { thought_signature: part.thoughtSignature } };
6748
+ }
6749
+ toolCalls.push(tc);
6539
6750
  }
6540
6751
  }
6541
6752
  const message = { role: "assistant" };