claudish 9.4.0 → 9.5.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 (2) hide show
  1. package/dist/index.js +1150 -577
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -715,7 +715,7 @@ var init_onepassword_config = __esm(() => {
715
715
  });
716
716
 
717
717
  // src/version.ts
718
- var VERSION = "9.4.0";
718
+ var VERSION = "9.5.0";
719
719
 
720
720
  // src/logger.ts
721
721
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -19045,6 +19045,28 @@ var init_remote_provider_types = __esm(() => {
19045
19045
  };
19046
19046
  });
19047
19047
 
19048
+ // src/adapters/optional-param-rejection.ts
19049
+ function escapeName(name) {
19050
+ return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19051
+ }
19052
+ function namesParameterField(errorText, param) {
19053
+ const p = escapeName(param);
19054
+ const tail = "(?![A-Za-z0-9_])";
19055
+ const quoted = new RegExp(`['"\`]${p}['"\`]`);
19056
+ const introduced = new RegExp("(?:parameter|argument|field|property|key|input)s?\\b" + "(?:[^A-Za-z0-9_]|\\b(?:supplied|provided|given|named|name|is|was|of|in|the|for|body|request|json)\\b){0,6}" + `${p}${tail}`, "i");
19057
+ const leading = new RegExp(`(?:^|["'])\\s*${p}\\s*:`);
19058
+ return quoted.test(errorText) || introduced.test(errorText) || leading.test(errorText);
19059
+ }
19060
+ function rejectedOptionalParams(errorText, candidates) {
19061
+ if (!errorText || !PARAMETER_COMPLAINT.test(errorText))
19062
+ return [];
19063
+ return candidates.filter((param) => namesParameterField(errorText, param));
19064
+ }
19065
+ var PARAMETER_COMPLAINT;
19066
+ var init_optional_param_rejection = __esm(() => {
19067
+ PARAMETER_COMPLAINT = /(unknown|unrecognized|unexpected|unsupported|not supported|does not support|invalid|not permitted|not allowed|extra (fields|inputs))/i;
19068
+ });
19069
+
19048
19070
  // src/adapters/tool-name-utils.ts
19049
19071
  function hashToolName(name) {
19050
19072
  let h1 = 3735928559;
@@ -19061,20 +19083,46 @@ function hashToolName(name) {
19061
19083
  const combined = 4294967296 * (2097151 & h2) + (h1 >>> 0);
19062
19084
  return combined.toString(16).padStart(8, "0").slice(0, 8);
19063
19085
  }
19064
- function truncateToolName(name, maxLength) {
19065
- if (name.length <= maxLength)
19066
- return name;
19067
- const prefixLen = maxLength - 9;
19068
- const prefix = name.slice(0, prefixLen);
19069
- const hash = hashToolName(name);
19070
- const truncated = `${prefix}_${hash}`;
19071
- log(`[ToolName] Truncated: "${name}" -> "${truncated}" (${name.length} -> ${truncated.length} chars)`);
19072
- return truncated;
19073
- }
19074
- var TOOL_NAME_SOURCE = "[A-Za-z_][A-Za-z0-9_.-]{0,63}", TOOL_NAME_SHAPE;
19086
+ function newToolNameBindings() {
19087
+ return { byEncoded: new Map, byOriginal: new Map };
19088
+ }
19089
+ function hashedForm(transformed, original, limit, salt) {
19090
+ const seed = salt === 0 ? original : `${original}#${salt}`;
19091
+ const prefix = transformed.slice(0, Math.max(0, limit - 9));
19092
+ return `${prefix}_${hashToolName(seed)}`;
19093
+ }
19094
+ function encodeToolName(original, limit, bindings) {
19095
+ const already = bindings.byOriginal.get(original);
19096
+ if (already !== undefined)
19097
+ return already;
19098
+ const transformed = original.replace(UNWIRABLE_CHARACTER, "_");
19099
+ const takenByAnother = (name) => {
19100
+ const owner = bindings.byEncoded.get(name);
19101
+ return owner !== undefined && owner !== original;
19102
+ };
19103
+ let encoded = transformed.length <= limit ? transformed : hashedForm(transformed, original, limit, 0);
19104
+ if (takenByAnother(encoded)) {
19105
+ let salt = 0;
19106
+ do {
19107
+ encoded = hashedForm(transformed, original, limit, salt++);
19108
+ } while (takenByAnother(encoded));
19109
+ }
19110
+ bindings.byEncoded.set(encoded, original);
19111
+ bindings.byOriginal.set(original, encoded);
19112
+ if (encoded !== original) {
19113
+ log(`[ToolName] Encoded: "${original}" -> "${encoded}" (${original.length} -> ${encoded.length})`);
19114
+ }
19115
+ return encoded;
19116
+ }
19117
+ function wireDecodesToolNames(format) {
19118
+ return format !== undefined && TOOL_NAME_DECODING_WIRES.includes(format);
19119
+ }
19120
+ var TOOL_NAME_SOURCE = "[A-Za-z_][A-Za-z0-9_.-]{0,63}", TOOL_NAME_SHAPE, UNWIRABLE_CHARACTER, TOOL_NAME_DECODING_WIRES;
19075
19121
  var init_tool_name_utils = __esm(() => {
19076
19122
  init_logger();
19077
19123
  TOOL_NAME_SHAPE = new RegExp(`^${TOOL_NAME_SOURCE}$`);
19124
+ UNWIRABLE_CHARACTER = /[^A-Za-z0-9_-]/g;
19125
+ TOOL_NAME_DECODING_WIRES = ["openai-sse", "openai-responses-sse"];
19078
19126
  });
19079
19127
 
19080
19128
  // src/handlers/shared/format/openai-messages.ts
@@ -19106,13 +19154,154 @@ ${msg}`;
19106
19154
  processAssistantMessage(msg, messages, simpleFormat);
19107
19155
  }
19108
19156
  }
19109
- return messages;
19157
+ return normalizeMessageSequence(messages);
19110
19158
  }
19111
- function imageBlockToUrlPart(block) {
19112
- return {
19113
- type: "image_url",
19114
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
19159
+ function mergeUserContent(a, b) {
19160
+ if (typeof a === "string" && typeof b === "string") {
19161
+ if (!a)
19162
+ return b;
19163
+ if (!b)
19164
+ return a;
19165
+ return `${a}
19166
+
19167
+ ${b}`;
19168
+ }
19169
+ const toParts = (c) => {
19170
+ if (typeof c === "string")
19171
+ return c ? [{ type: "text", text: c }] : [];
19172
+ if (Array.isArray(c))
19173
+ return c;
19174
+ return;
19115
19175
  };
19176
+ const pa = toParts(a);
19177
+ const pb = toParts(b);
19178
+ if (!pa || !pb)
19179
+ return;
19180
+ return [...pa, ...pb];
19181
+ }
19182
+ function pushUserMessage(out, msg) {
19183
+ const prev = out[out.length - 1];
19184
+ if (prev?.role === "user") {
19185
+ const merged = mergeUserContent(prev.content, msg.content);
19186
+ if (merged !== undefined) {
19187
+ prev.content = merged;
19188
+ return;
19189
+ }
19190
+ }
19191
+ out.push(msg);
19192
+ }
19193
+ function normalizeMessageSequence(messages) {
19194
+ const out = [];
19195
+ let openRound = null;
19196
+ let collected = [];
19197
+ const flushRound = (isFinal = false) => {
19198
+ if (!openRound)
19199
+ return;
19200
+ if (isFinal && collected.length === 0) {
19201
+ openRound = null;
19202
+ return;
19203
+ }
19204
+ out.push(...alignToolRound(openRound, collected));
19205
+ openRound = null;
19206
+ collected = [];
19207
+ };
19208
+ for (const msg of messages) {
19209
+ if (msg.role === "tool") {
19210
+ if (openRound) {
19211
+ collected.push(msg);
19212
+ continue;
19213
+ }
19214
+ log(`[OpenAIMessages] error \u2014 tool result ${msg.tool_call_id} answers no open tool round; ` + "re-emitted as a user message");
19215
+ const text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
19216
+ pushUserMessage(out, { role: "user", content: `[Tool Result]: ${text}` });
19217
+ continue;
19218
+ }
19219
+ flushRound();
19220
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
19221
+ out.push(msg);
19222
+ openRound = msg;
19223
+ continue;
19224
+ }
19225
+ if (msg.role === "user")
19226
+ pushUserMessage(out, msg);
19227
+ else
19228
+ out.push(msg);
19229
+ }
19230
+ flushRound(true);
19231
+ return out;
19232
+ }
19233
+ function missingResultText(name) {
19234
+ const call = name ? `\`${name}\` call` : "call";
19235
+ return `[No tool result was provided for this ${call} \u2014 it was interrupted, cancelled, ` + "or dropped from the conversation history.]";
19236
+ }
19237
+ function alignToolRound(assistant, collected) {
19238
+ const calls = Array.isArray(assistant.tool_calls) ? assistant.tool_calls : [];
19239
+ const byId = new Map;
19240
+ for (const m of collected) {
19241
+ const list = byId.get(m.tool_call_id);
19242
+ if (list)
19243
+ list.push(m);
19244
+ else
19245
+ byId.set(m.tool_call_id, [m]);
19246
+ }
19247
+ const ordered = [];
19248
+ let synthesized = 0;
19249
+ for (const call of calls) {
19250
+ const matched = byId.get(call.id);
19251
+ if (matched) {
19252
+ ordered.push(...matched);
19253
+ byId.delete(call.id);
19254
+ continue;
19255
+ }
19256
+ synthesized++;
19257
+ log(`[OpenAIMessages] error \u2014 tool call ${call.id} (${call.function?.name || "?"}) has no ` + "result; a synthetic tool message names the omission");
19258
+ ordered.push({
19259
+ role: "tool",
19260
+ content: missingResultText(call.function?.name || ""),
19261
+ tool_call_id: call.id
19262
+ });
19263
+ }
19264
+ let dropped = 0;
19265
+ for (const [id, list] of byId) {
19266
+ dropped += list.length;
19267
+ log(`[OpenAIMessages] error \u2014 tool result ${id} matches no call in the round it follows; dropped`);
19268
+ }
19269
+ const reordered = ordered.some((m, i) => m !== collected[i]);
19270
+ if (reordered && !synthesized && !dropped) {
19271
+ log(`[OpenAIMessages] Reordered ${ordered.length} tool results to their tool_calls order`);
19272
+ }
19273
+ return ordered;
19274
+ }
19275
+ function imageBlockToUrlPart(block) {
19276
+ const source = block?.source;
19277
+ if (!source || typeof source !== "object") {
19278
+ log("[OpenAIMessages] Dropping image block: error \u2014 no source object");
19279
+ return null;
19280
+ }
19281
+ const url = typeof source.url === "string" ? source.url : "";
19282
+ const data = typeof source.data === "string" ? source.data : "";
19283
+ const kind = source.type || (url ? "url" : data ? "base64" : "");
19284
+ if (kind === "url") {
19285
+ if (!url) {
19286
+ log("[OpenAIMessages] Dropping image block: error \u2014 url source carries no url");
19287
+ return null;
19288
+ }
19289
+ return { type: "image_url", image_url: { url } };
19290
+ }
19291
+ if (kind === "base64") {
19292
+ if (!data) {
19293
+ log("[OpenAIMessages] Dropping image block: error \u2014 base64 source carries no data");
19294
+ return null;
19295
+ }
19296
+ const mediaType = typeof source.media_type === "string" ? source.media_type : "";
19297
+ if (!mediaType) {
19298
+ log("[OpenAIMessages] Dropping image block: error \u2014 base64 source carries no media_type");
19299
+ return null;
19300
+ }
19301
+ return { type: "image_url", image_url: { url: `data:${mediaType};base64,${data}` } };
19302
+ }
19303
+ log(`[OpenAIMessages] Dropping image block: error \u2014 unsupported source type ${kind || "(none)"}`);
19304
+ return null;
19116
19305
  }
19117
19306
  function processUserMessage(msg, messages, simpleFormat = false) {
19118
19307
  if (Array.isArray(msg.content)) {
@@ -19129,7 +19318,9 @@ function processUserMessage(msg, messages, simpleFormat = false) {
19129
19318
  }
19130
19319
  } else if (block.type === "image") {
19131
19320
  if (!simpleFormat) {
19132
- contentParts.push(imageBlockToUrlPart(block));
19321
+ const part = imageBlockToUrlPart(block);
19322
+ if (part)
19323
+ contentParts.push(part);
19133
19324
  }
19134
19325
  } else if (block.type === "tool_result") {
19135
19326
  if (seen.has(block.tool_use_id))
@@ -19141,12 +19332,21 @@ function processUserMessage(msg, messages, simpleFormat = false) {
19141
19332
  } else if (Array.isArray(block.content)) {
19142
19333
  const texts = [];
19143
19334
  const others = [];
19335
+ let forwardedImages = 0;
19336
+ let droppedImages = 0;
19144
19337
  for (const inner of block.content) {
19145
19338
  if (inner.type === "text") {
19146
19339
  texts.push(inner.text);
19147
19340
  } else if (inner.type === "image" && inner.source) {
19148
- if (!simpleFormat)
19149
- toolResultImages.push(imageBlockToUrlPart(inner));
19341
+ if (!simpleFormat) {
19342
+ const part = imageBlockToUrlPart(inner);
19343
+ if (part) {
19344
+ toolResultImages.push(part);
19345
+ forwardedImages++;
19346
+ } else {
19347
+ droppedImages++;
19348
+ }
19349
+ }
19150
19350
  } else {
19151
19351
  others.push(inner);
19152
19352
  }
@@ -19157,7 +19357,12 @@ function processUserMessage(msg, messages, simpleFormat = false) {
19157
19357
  resultText += (resultText ? `
19158
19358
  ` : "") + JSON.stringify(others);
19159
19359
  if (!resultText) {
19160
- resultText = toolResultImages.length ? "[image returned; see following message]" : "";
19360
+ if (forwardedImages)
19361
+ resultText = "[image returned; see following message]";
19362
+ else if (droppedImages)
19363
+ resultText = "[image returned, but its source could not be forwarded]";
19364
+ else
19365
+ resultText = "";
19161
19366
  }
19162
19367
  } else {
19163
19368
  resultText = JSON.stringify(block.content);
@@ -19243,6 +19448,9 @@ function processAssistantMessage(msg, messages, simpleFormat = false) {
19243
19448
  messages.push({ role: "assistant", content: msg.content });
19244
19449
  }
19245
19450
  }
19451
+ var init_openai_messages = __esm(() => {
19452
+ init_logger();
19453
+ });
19246
19454
 
19247
19455
  // src/transform.ts
19248
19456
  function removeUriFormat(schema) {
@@ -19452,6 +19660,45 @@ function summarizeToolParameters(schema) {
19452
19660
  }
19453
19661
  return summarized;
19454
19662
  }
19663
+ function mapToolChoiceToOpenAI(choice, encodeName) {
19664
+ if (!choice)
19665
+ return;
19666
+ const { type, name } = choice;
19667
+ if (type === "tool" && name) {
19668
+ return { type: "function", function: { name: encodeName ? encodeName(name) : name } };
19669
+ }
19670
+ if (type === "any")
19671
+ return "required";
19672
+ if (type === "auto" || type === "none")
19673
+ return type;
19674
+ return;
19675
+ }
19676
+ function mapToolChoiceToResponsesAPI(choice, encodeName) {
19677
+ const mapped = mapToolChoiceToOpenAI(choice, encodeName);
19678
+ if (mapped === undefined || typeof mapped === "string")
19679
+ return mapped;
19680
+ return { type: "function", name: mapped.function.name };
19681
+ }
19682
+ function mapToolChoiceToGemini(choice, encodeName) {
19683
+ if (!choice)
19684
+ return;
19685
+ const { type, name } = choice;
19686
+ if (type === "tool" && name) {
19687
+ return {
19688
+ functionCallingConfig: {
19689
+ mode: "ANY",
19690
+ allowedFunctionNames: [encodeName ? encodeName(name) : name]
19691
+ }
19692
+ };
19693
+ }
19694
+ if (type === "any")
19695
+ return { functionCallingConfig: { mode: "ANY" } };
19696
+ if (type === "auto")
19697
+ return { functionCallingConfig: { mode: "AUTO" } };
19698
+ if (type === "none")
19699
+ return { functionCallingConfig: { mode: "NONE" } };
19700
+ return;
19701
+ }
19455
19702
  var PORTABLE_ESCAPE_LETTERS, NAMED_SCHEMA_MAPS;
19456
19703
  var init_openai_tools = __esm(() => {
19457
19704
  init_logger();
@@ -19494,7 +19741,11 @@ function isEffortLevel(value) {
19494
19741
  class BaseAPIFormat {
19495
19742
  modelId;
19496
19743
  wireFormat;
19497
- toolNameMap = new Map;
19744
+ responseWireFormat;
19745
+ setResponseWireFormat(format) {
19746
+ this.responseWireFormat = format;
19747
+ }
19748
+ toolNameBindings = newToolNameBindings();
19498
19749
  constructor(modelId, wireFormat) {
19499
19750
  this.modelId = modelId;
19500
19751
  this.wireFormat = wireFormat;
@@ -19505,20 +19756,52 @@ class BaseAPIFormat {
19505
19756
  getWireFormat() {
19506
19757
  return this.wireFormat;
19507
19758
  }
19759
+ recoverFromRejection(payload, errorText) {
19760
+ return this.recoverFromSamplingParamRejection(payload, errorText);
19761
+ }
19762
+ recoverFromSamplingParamRejection(payload, errorText) {
19763
+ if (!payload)
19764
+ return null;
19765
+ const present = OPTIONAL_SAMPLING_PARAMS.filter((p) => payload[p] !== undefined);
19766
+ if (present.length === 0)
19767
+ return null;
19768
+ const rejected = rejectedOptionalParams(errorText, present);
19769
+ if (rejected.length === 0)
19770
+ return null;
19771
+ const next = { ...payload };
19772
+ for (const p of rejected)
19773
+ delete next[p];
19774
+ return { payload: next, note: `dropped ${rejected.join(", ")} for ${this.modelId}` };
19775
+ }
19776
+ applyOpenAISamplingParams(payload, claudeRequest) {
19777
+ if (!payload || !claudeRequest)
19778
+ return;
19779
+ const sequences = claudeRequest.stop_sequences;
19780
+ if (Array.isArray(sequences)) {
19781
+ const usable = sequences.filter((s) => typeof s === "string" && s.length > 0);
19782
+ if (usable.length > 0)
19783
+ payload.stop = usable;
19784
+ }
19785
+ if (claudeRequest.top_p !== undefined && claudeRequest.top_p !== null) {
19786
+ payload.top_p = claudeRequest.top_p;
19787
+ }
19788
+ }
19508
19789
  getToolNameLimit() {
19509
- return null;
19790
+ const wire = this.responseWireFormat ?? this.wireFormat ?? this.getStreamFormat();
19791
+ return wireDecodesToolNames(wire) ? OPENAI_TOOL_NAME_LIMIT : null;
19510
19792
  }
19511
19793
  getMaxToolCount() {
19512
19794
  return null;
19513
19795
  }
19514
19796
  getToolNameMap() {
19515
- return this.toolNameMap;
19797
+ return this.toolNameBindings.byEncoded;
19516
19798
  }
19517
19799
  restoreToolName(name) {
19518
- return this.toolNameMap.get(name) || name;
19800
+ return this.toolNameBindings.byEncoded.get(name) || name;
19519
19801
  }
19520
19802
  prepareRequest(request, originalRequest) {
19521
19803
  const prepared = this.prepareRequestCommon(request, originalRequest) ?? request;
19804
+ this.encodeToolNames(prepared);
19522
19805
  if (!this.isAnthropicWire()) {
19523
19806
  return this.applyNativeReasoning(prepared, originalRequest) ?? prepared;
19524
19807
  }
@@ -19656,7 +19939,7 @@ class BaseAPIFormat {
19656
19939
  return;
19657
19940
  }
19658
19941
  reset() {
19659
- this.toolNameMap.clear();
19942
+ this.toolNameBindings = newToolNameBindings();
19660
19943
  }
19661
19944
  convertMessages(claudeRequest, filterIdentityFn) {
19662
19945
  return convertMessagesToOpenAI(claudeRequest, this.modelId, filterIdentityFn);
@@ -19672,6 +19955,10 @@ class BaseAPIFormat {
19672
19955
  };
19673
19956
  if (tools.length > 0) {
19674
19957
  payload.tools = tools;
19958
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
19959
+ if (toolChoice !== undefined) {
19960
+ payload.tool_choice = toolChoice;
19961
+ }
19675
19962
  }
19676
19963
  if (claudeRequest.max_tokens) {
19677
19964
  payload.max_tokens = claudeRequest.max_tokens;
@@ -19679,6 +19966,7 @@ class BaseAPIFormat {
19679
19966
  if (claudeRequest.temperature !== undefined) {
19680
19967
  payload.temperature = claudeRequest.temperature;
19681
19968
  }
19969
+ this.applyOpenAISamplingParams(payload, claudeRequest);
19682
19970
  return payload;
19683
19971
  }
19684
19972
  getStreamFormat() {
@@ -19696,50 +19984,56 @@ class BaseAPIFormat {
19696
19984
  shouldFilterThinking() {
19697
19985
  return this.isAnthropicWire();
19698
19986
  }
19699
- truncateToolNames(request) {
19987
+ encodeToolNames(request) {
19700
19988
  const limit = this.getToolNameLimit();
19701
- if (!limit || !request.tools)
19989
+ if (!limit || !request)
19702
19990
  return;
19703
- for (const tool of request.tools) {
19704
- const originalName = tool.function?.name || tool.name;
19705
- if (originalName && originalName.length > limit) {
19706
- const truncated = truncateToolName(originalName, limit);
19707
- this.toolNameMap.set(truncated, originalName);
19708
- if (tool.function?.name) {
19709
- tool.function.name = truncated;
19710
- } else if (tool.name) {
19711
- tool.name = truncated;
19991
+ const encode = (name) => encodeToolName(name, limit, this.toolNameBindings);
19992
+ if (Array.isArray(request.tools)) {
19993
+ for (const tool of request.tools) {
19994
+ if (tool?.function?.name) {
19995
+ tool.function.name = encode(tool.function.name);
19996
+ } else if (tool?.name) {
19997
+ tool.name = encode(tool.name);
19712
19998
  }
19713
19999
  }
19714
20000
  }
19715
- }
19716
- truncateToolNamesInMessages(messages) {
19717
- const limit = this.getToolNameLimit();
19718
- if (!limit)
19719
- return;
19720
- for (const msg of messages) {
19721
- if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
20001
+ if (Array.isArray(request.messages)) {
20002
+ for (const msg of request.messages) {
20003
+ if (msg?.role !== "assistant" || !Array.isArray(msg.tool_calls))
20004
+ continue;
19722
20005
  for (const tc of msg.tool_calls) {
19723
- const name = tc.function?.name;
19724
- if (name && name.length > limit) {
19725
- const truncated = truncateToolName(name, limit);
19726
- tc.function.name = truncated;
19727
- if (!this.toolNameMap.has(truncated)) {
19728
- this.toolNameMap.set(truncated, name);
19729
- }
19730
- }
20006
+ if (tc?.function?.name)
20007
+ tc.function.name = encode(tc.function.name);
19731
20008
  }
19732
20009
  }
19733
20010
  }
20011
+ if (Array.isArray(request.input)) {
20012
+ for (const item of request.input) {
20013
+ if (item?.type === "function_call" && item.name)
20014
+ item.name = encode(item.name);
20015
+ }
20016
+ }
20017
+ const choice = request.tool_choice;
20018
+ if (choice && typeof choice === "object") {
20019
+ if (choice.function?.name) {
20020
+ choice.function.name = encode(choice.function.name);
20021
+ } else if (choice.name) {
20022
+ choice.name = encode(choice.name);
20023
+ }
20024
+ }
19734
20025
  }
19735
20026
  }
19736
- var EFFORT_ORDER, EFFORT_LEVELS, NON_ANTHROPIC_REASONING_FIELDS, DefaultAPIFormat;
20027
+ var OPTIONAL_SAMPLING_PARAMS, OPENAI_TOOL_NAME_LIMIT = 64, EFFORT_ORDER, EFFORT_LEVELS, NON_ANTHROPIC_REASONING_FIELDS, DefaultAPIFormat;
19737
20028
  var init_base_api_format = __esm(() => {
19738
20029
  init_remote_provider_types();
19739
20030
  init_logger();
19740
20031
  init_model_catalog();
20032
+ init_optional_param_rejection();
19741
20033
  init_tool_name_utils();
20034
+ init_openai_messages();
19742
20035
  init_openai_tools();
20036
+ OPTIONAL_SAMPLING_PARAMS = ["stop", "top_p"];
19743
20037
  EFFORT_ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
19744
20038
  EFFORT_LEVELS = EFFORT_ORDER;
19745
20039
  NON_ANTHROPIC_REASONING_FIELDS = [
@@ -19922,6 +20216,7 @@ function normalizeCodexModel(modelId) {
19922
20216
  }
19923
20217
  var CodexAPIFormat;
19924
20218
  var init_codex_api_format = __esm(() => {
20219
+ init_openai_tools();
19925
20220
  init_logger();
19926
20221
  init_base_api_format();
19927
20222
  init_reasoning_cache();
@@ -19987,6 +20282,10 @@ var init_codex_api_format = __esm(() => {
19987
20282
  }
19988
20283
  return tool;
19989
20284
  });
20285
+ const toolChoice = mapToolChoiceToResponsesAPI(claudeRequest.tool_choice);
20286
+ if (toolChoice !== undefined) {
20287
+ payload.tool_choice = toolChoice;
20288
+ }
19990
20289
  }
19991
20290
  return payload;
19992
20291
  }
@@ -20568,31 +20867,308 @@ function filterIdentity(content) {
20568
20867
  `);
20569
20868
  }
20570
20869
 
20870
+ // src/handlers/shared/schema-validate.ts
20871
+ function missingRequired(schema, args) {
20872
+ const required = schema?.required;
20873
+ if (!Array.isArray(required) || required.length === 0)
20874
+ return [];
20875
+ const supplied = args ?? {};
20876
+ return required.filter((key) => {
20877
+ if (typeof key !== "string")
20878
+ return false;
20879
+ if (!Object.hasOwn(supplied, key))
20880
+ return true;
20881
+ const value = supplied[key];
20882
+ return value === undefined || value === null;
20883
+ });
20884
+ }
20885
+ function isAbsent(args, key) {
20886
+ if (!Object.hasOwn(args, key))
20887
+ return true;
20888
+ const value = args[key];
20889
+ return value === undefined || value === null;
20890
+ }
20891
+ function declaredTypes(node) {
20892
+ if (!node)
20893
+ return [];
20894
+ const t = node.type;
20895
+ if (typeof t === "string")
20896
+ return [t];
20897
+ if (Array.isArray(t))
20898
+ return t.filter((x) => typeof x === "string");
20899
+ if (Array.isArray(node.enum) && node.enum.length > 0) {
20900
+ const kinds = new Set;
20901
+ for (const member of node.enum) {
20902
+ if (member === null || member === undefined)
20903
+ continue;
20904
+ if (Array.isArray(member))
20905
+ kinds.add("array");
20906
+ else if (typeof member === "object")
20907
+ kinds.add("object");
20908
+ else if (typeof member === "number")
20909
+ kinds.add(Number.isInteger(member) ? "integer" : "number");
20910
+ else if (typeof member === "boolean")
20911
+ kinds.add("boolean");
20912
+ else if (typeof member === "string")
20913
+ kinds.add("string");
20914
+ }
20915
+ return [...kinds];
20916
+ }
20917
+ return [];
20918
+ }
20919
+ function coerceOne(value, type, node) {
20920
+ switch (type) {
20921
+ case "string":
20922
+ return typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" ? String(value) : undefined;
20923
+ case "number":
20924
+ case "integer": {
20925
+ if (typeof value === "number") {
20926
+ return type === "integer" && !Number.isInteger(value) ? undefined : value;
20927
+ }
20928
+ if (typeof value !== "string" || !FULL_NUMBER.test(value.trim()))
20929
+ return;
20930
+ const n = Number(value.trim());
20931
+ if (!Number.isFinite(n))
20932
+ return;
20933
+ return type === "integer" && !Number.isInteger(n) ? undefined : n;
20934
+ }
20935
+ case "boolean": {
20936
+ if (typeof value === "boolean")
20937
+ return value;
20938
+ if (typeof value !== "string")
20939
+ return;
20940
+ const s = value.trim().toLowerCase();
20941
+ return s === "true" ? true : s === "false" ? false : undefined;
20942
+ }
20943
+ case "array": {
20944
+ const arr = Array.isArray(value) ? value : parseJsonOfKind(value, "array");
20945
+ if (!Array.isArray(arr))
20946
+ return;
20947
+ const items = node?.items;
20948
+ if (!items)
20949
+ return arr;
20950
+ return arr.map((el) => {
20951
+ const target = declaredTypes(items);
20952
+ for (const t of target) {
20953
+ const c = coerceOne(el, t, items);
20954
+ if (c !== undefined)
20955
+ return c;
20956
+ }
20957
+ return el;
20958
+ });
20959
+ }
20960
+ case "object":
20961
+ return value && typeof value === "object" && !Array.isArray(value) ? value : parseJsonOfKind(value, "object");
20962
+ default:
20963
+ return;
20964
+ }
20965
+ }
20966
+ function parseJsonOfKind(value, kind) {
20967
+ if (typeof value !== "string")
20968
+ return;
20969
+ try {
20970
+ const parsed = JSON.parse(value);
20971
+ if (kind === "array")
20972
+ return Array.isArray(parsed) ? parsed : undefined;
20973
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
20974
+ } catch {
20975
+ return;
20976
+ }
20977
+ }
20978
+ function coerceToSchema(schema, args) {
20979
+ const properties = schema?.properties;
20980
+ if (!properties)
20981
+ return { args, coerced: [] };
20982
+ const out = { ...args };
20983
+ const coerced = [];
20984
+ for (const [key, value] of Object.entries(args)) {
20985
+ const node = properties[key];
20986
+ if (!node || value === undefined || value === null)
20987
+ continue;
20988
+ const types = declaredTypes(node);
20989
+ if (types.length === 0)
20990
+ continue;
20991
+ if (types.some((t) => matchesType(value, t)))
20992
+ continue;
20993
+ for (const t of types) {
20994
+ const c = coerceOne(value, t, node);
20995
+ if (c !== undefined) {
20996
+ out[key] = c;
20997
+ coerced.push(key);
20998
+ break;
20999
+ }
21000
+ }
21001
+ }
21002
+ return { args: coerced.length > 0 ? out : args, coerced };
21003
+ }
21004
+ function matchesType(value, type) {
21005
+ switch (type) {
21006
+ case "string":
21007
+ return typeof value === "string";
21008
+ case "number":
21009
+ return typeof value === "number";
21010
+ case "integer":
21011
+ return typeof value === "number" && Number.isInteger(value);
21012
+ case "boolean":
21013
+ return typeof value === "boolean";
21014
+ case "array":
21015
+ return Array.isArray(value);
21016
+ case "object":
21017
+ return !!value && typeof value === "object" && !Array.isArray(value);
21018
+ case "null":
21019
+ return value === null;
21020
+ default:
21021
+ return false;
21022
+ }
21023
+ }
21024
+ function applySchemaDefaults(schema, args) {
21025
+ const properties = schema?.properties;
21026
+ const required = schema?.required;
21027
+ if (!properties || !Array.isArray(required))
21028
+ return { args, applied: [] };
21029
+ const out = { ...args };
21030
+ const applied = [];
21031
+ for (const key of required) {
21032
+ if (typeof key !== "string" || !isAbsent(out, key))
21033
+ continue;
21034
+ const node = properties[key];
21035
+ if (!node || !Object.hasOwn(node, "default") || node.default === undefined)
21036
+ continue;
21037
+ out[key] = node.default;
21038
+ applied.push(key);
21039
+ }
21040
+ return { args: applied.length > 0 ? out : args, applied };
21041
+ }
21042
+ function renameToDeclaredKeys(schema, args) {
21043
+ const properties = schema?.properties;
21044
+ const required = schema?.required;
21045
+ if (!properties || !Array.isArray(required))
21046
+ return { args, renamed: [] };
21047
+ const out = { ...args };
21048
+ const renamed = [];
21049
+ for (const target of required) {
21050
+ if (typeof target !== "string")
21051
+ continue;
21052
+ if (!properties[target] || !isAbsent(out, target))
21053
+ continue;
21054
+ for (const source of KEY_SYNONYMS[target] ?? []) {
21055
+ if (properties[source])
21056
+ continue;
21057
+ if (isAbsent(out, source))
21058
+ continue;
21059
+ out[target] = out[source];
21060
+ delete out[source];
21061
+ renamed.push(`${source}\u2192${target}`);
21062
+ break;
21063
+ }
21064
+ }
21065
+ return { args: renamed.length > 0 ? out : args, renamed };
21066
+ }
21067
+ var FULL_NUMBER, KEY_SYNONYMS;
21068
+ var init_schema_validate = __esm(() => {
21069
+ FULL_NUMBER = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
21070
+ KEY_SYNONYMS = {
21071
+ command: ["cmd", "shell", "script"],
21072
+ file_path: ["path", "file", "filename"],
21073
+ content: ["text", "data", "body"],
21074
+ pattern: ["query", "search", "regex", "glob"],
21075
+ query: ["search", "keyword"],
21076
+ prompt: ["query", "task"]
21077
+ };
21078
+ });
21079
+
20571
21080
  // src/handlers/shared/tool-call-recovery.ts
20572
21081
  function hasExtractableFunctionTag(text) {
20573
21082
  return FUNCTION_TAG_PRESENT.test(text);
20574
21083
  }
20575
- function keepOnlyRealTools(extracted, knownToolNames) {
21084
+ function keepOnlyRealTools(extracted, knownToolNames, decodeToolName) {
20576
21085
  const kept = [];
20577
21086
  for (const call of extracted) {
20578
21087
  if (!TOOL_NAME_SHAPE.test(call.name)) {
20579
21088
  log(`[ToolRecovery] Dropped extracted call: name is not an identifier: ${JSON.stringify(call.name.slice(0, 120))}`);
20580
21089
  continue;
20581
21090
  }
21091
+ const decoded = decodeToolName ? decodeToolName(call.name) : call.name;
21092
+ const named = decoded === call.name ? call : { ...call, name: decoded };
20582
21093
  if (!knownToolNames || knownToolNames.length === 0) {
20583
- kept.push(call);
21094
+ kept.push(named);
20584
21095
  continue;
20585
21096
  }
20586
- const canonical = knownToolNames.find((t) => t.toLowerCase() === call.name.toLowerCase());
21097
+ const canonical = knownToolNames.find((t) => t.toLowerCase() === named.name.toLowerCase());
20587
21098
  if (!canonical) {
20588
- log(`[ToolRecovery] Dropped extracted call for unadvertised tool: ${call.name}`);
21099
+ log(`[ToolRecovery] Dropped extracted call for unadvertised tool: ${named.name}`);
20589
21100
  continue;
20590
21101
  }
20591
- kept.push(canonical === call.name ? call : { ...call, name: canonical });
21102
+ kept.push(canonical === named.name ? named : { ...named, name: canonical });
20592
21103
  }
20593
21104
  return kept;
20594
21105
  }
20595
- function extractToolCallsFromText(text, knownToolNames) {
21106
+ function parseFunctionTagEnvelope(text) {
21107
+ const body = text.trim();
21108
+ if (body.length === 0)
21109
+ return null;
21110
+ if (!body.startsWith("<function="))
21111
+ return null;
21112
+ const calls = [];
21113
+ let cursor = 0;
21114
+ while (cursor < body.length) {
21115
+ FUNCTION_TAG_AT_CURSOR.lastIndex = cursor;
21116
+ const open = FUNCTION_TAG_AT_CURSOR.exec(body);
21117
+ if (!open)
21118
+ return null;
21119
+ cursor = FUNCTION_TAG_AT_CURSOR.lastIndex;
21120
+ const args = {};
21121
+ while (cursor < body.length) {
21122
+ const rest = body.slice(cursor);
21123
+ const closeLen = /^\s*<\/function>/.exec(rest)?.[0].length;
21124
+ if (closeLen !== undefined) {
21125
+ cursor += closeLen;
21126
+ break;
21127
+ }
21128
+ if (/^\s*<function=/.test(rest))
21129
+ break;
21130
+ const lead = /^\s*/.exec(rest)?.[0].length ?? 0;
21131
+ PARAMETER_TAG_AT_CURSOR.lastIndex = cursor + lead;
21132
+ const param = PARAMETER_TAG_AT_CURSOR.exec(body);
21133
+ if (!param)
21134
+ return null;
21135
+ let valueStart = PARAMETER_TAG_AT_CURSOR.lastIndex;
21136
+ if (body[valueStart] === `
21137
+ `)
21138
+ valueStart += 1;
21139
+ const tail = body.slice(valueStart);
21140
+ const end = /<\/parameter>|<\/function>|<parameter=|<function=/.exec(tail);
21141
+ const raw = end ? tail.slice(0, end.index) : tail;
21142
+ args[param[1]] = raw.trim();
21143
+ cursor = valueStart + raw.length;
21144
+ const consumedClose = /^<\/parameter>/.exec(body.slice(cursor))?.[0].length;
21145
+ if (consumedClose !== undefined)
21146
+ cursor += consumedClose;
21147
+ }
21148
+ calls.push({ name: open[1], arguments: args, source: "xml_text" });
21149
+ const gap = /^\s*/.exec(body.slice(cursor))?.[0].length ?? 0;
21150
+ cursor += gap;
21151
+ }
21152
+ return calls.length > 0 ? calls : null;
21153
+ }
21154
+ function normalizeAgainstSchema(call, toolSchemas) {
21155
+ const schema = toolSchemas?.find((t) => t.name === call.name)?.input_schema;
21156
+ if (!schema)
21157
+ return call;
21158
+ const renamed = renameToDeclaredKeys(schema, call.arguments);
21159
+ const defaulted = applySchemaDefaults(schema, renamed.args);
21160
+ const coerced = coerceToSchema(schema, defaulted.args);
21161
+ if (renamed.renamed.length + defaulted.applied.length + coerced.coerced.length === 0) {
21162
+ return call;
21163
+ }
21164
+ log(`[ToolRecovery] Normalized ${call.name} against its schema: ` + `renamed=[${renamed.renamed}] defaults=[${defaulted.applied}] coerced=[${coerced.coerced}]`);
21165
+ return { ...call, arguments: coerced.args };
21166
+ }
21167
+ function extractToolCallsFromText(text, knownToolNames, toolSchemas, decodeToolName) {
21168
+ const envelope = parseFunctionTagEnvelope(text);
21169
+ if (envelope) {
21170
+ return keepOnlyRealTools(envelope, knownToolNames, decodeToolName).map((call) => normalizeAgainstSchema(call, toolSchemas));
21171
+ }
20596
21172
  const extracted = [];
20597
21173
  const qwenPattern = new RegExp(FUNCTION_TAG_SOURCE, "gi");
20598
21174
  let match;
@@ -20719,73 +21295,6 @@ function extractToolCallsFromText(text, knownToolNames) {
20719
21295
  while ((kvMatch = kvPattern.exec(paramText)) !== null) {
20720
21296
  args[kvMatch[1]] = kvMatch[2];
20721
21297
  }
20722
- const quotedPattern = /["']([^"']+)["']/g;
20723
- let quotedMatch;
20724
- const quotedValues = [];
20725
- while ((quotedMatch = quotedPattern.exec(paramText)) !== null) {
20726
- quotedValues.push(quotedMatch[1]);
20727
- }
20728
- if (normalizedToolName === "Task") {
20729
- if (!args.subagent_type) {
20730
- const stMatch = paramText.match(/subagent_type\s*[=:]\s*["']?(\w+)["']?/i);
20731
- if (stMatch) {
20732
- args.subagent_type = stMatch[1];
20733
- } else if (/explore|codebase|structure/i.test(paramText)) {
20734
- args.subagent_type = "Explore";
20735
- } else if (/plan|architect/i.test(paramText)) {
20736
- args.subagent_type = "Plan";
20737
- } else {
20738
- args.subagent_type = "general-purpose";
20739
- }
20740
- }
20741
- if (!args.prompt) {
20742
- const toMatch = paramText.match(/\bto\s+(.+)/i);
20743
- if (toMatch) {
20744
- args.prompt = toMatch[1].trim();
20745
- } else {
20746
- args.prompt = paramText.trim();
20747
- }
20748
- }
20749
- if (!args.description) {
20750
- args.description = (args.prompt || paramText).substring(0, 50).trim();
20751
- }
20752
- } else if (normalizedToolName === "Read") {
20753
- if (!args.file_path) {
20754
- if (quotedValues.length > 0) {
20755
- args.file_path = quotedValues[0];
20756
- } else {
20757
- const pathMatch = paramText.match(/(?:read|file)\s+([\/\w.-]+)/i);
20758
- if (pathMatch) {
20759
- args.file_path = pathMatch[1];
20760
- }
20761
- }
20762
- }
20763
- } else if (normalizedToolName === "Bash") {
20764
- if (!args.command) {
20765
- if (quotedValues.length > 0) {
20766
- args.command = quotedValues[0];
20767
- } else {
20768
- const cmdMatch = paramText.match(/(?:run|execute)\s+(.+)/i);
20769
- if (cmdMatch) {
20770
- args.command = cmdMatch[1].trim();
20771
- }
20772
- }
20773
- }
20774
- if (args.command && !args.description) {
20775
- args.description = `Run ${args.command.split(" ")[0]} command`;
20776
- }
20777
- } else if (normalizedToolName === "Grep" || normalizedToolName === "Glob") {
20778
- if (!args.pattern) {
20779
- if (quotedValues.length > 0) {
20780
- args.pattern = quotedValues[0];
20781
- } else {
20782
- const searchMatch = paramText.match(/(?:search|find|look for)\s+(.+)/i);
20783
- if (searchMatch) {
20784
- args.pattern = searchMatch[1].trim();
20785
- }
20786
- }
20787
- }
20788
- }
20789
21298
  if (Object.keys(args).length > 0) {
20790
21299
  extracted.push({
20791
21300
  name: normalizedToolName,
@@ -20796,139 +21305,7 @@ function extractToolCallsFromText(text, knownToolNames) {
20796
21305
  }
20797
21306
  }
20798
21307
  }
20799
- return keepOnlyRealTools(extracted, knownToolNames);
20800
- }
20801
- function inferMissingParameters(toolName, args, missingParams, context) {
20802
- const inferred = { ...args };
20803
- if (toolName === "Task") {
20804
- const validSubagentTypes = [
20805
- "general-purpose",
20806
- "Explore",
20807
- "Plan",
20808
- "claude-code-guide",
20809
- "code-analysis:detective",
20810
- "feature-dev:code-architect",
20811
- "feature-dev:code-explorer",
20812
- "feature-dev:code-reviewer"
20813
- ];
20814
- if (inferred.subagent_type) {
20815
- const st = inferred.subagent_type.toLowerCase();
20816
- if (st.includes("explore") || st.includes("codebase") || st.includes("file")) {
20817
- inferred.subagent_type = "Explore";
20818
- } else if (st.includes("plan") || st.includes("architect")) {
20819
- inferred.subagent_type = "Plan";
20820
- } else if (st.includes("analysis") || st.includes("analyz") || st.includes("config") || st.includes("git") || st.includes("test") || st.includes("doc") || st.includes("version")) {
20821
- inferred.subagent_type = "general-purpose";
20822
- } else if (!validSubagentTypes.includes(inferred.subagent_type)) {
20823
- log(`[ToolRecovery] Unknown subagent_type "${inferred.subagent_type}", mapping to general-purpose`);
20824
- inferred.subagent_type = "general-purpose";
20825
- }
20826
- }
20827
- if (missingParams.includes("subagent_type") && !inferred.subagent_type) {
20828
- inferred.subagent_type = "general-purpose";
20829
- log("[ToolRecovery] Inferred subagent_type: general-purpose");
20830
- }
20831
- let extractedTask = "";
20832
- if (context) {
20833
- const patterns = [
20834
- /(?:I(?:'ll| will| need to| want to| am going to)|Let me|Going to)\s+([^.!?\n]+)/i,
20835
- /(?:help you|assist with)\s+([^.!?\n]+)/i,
20836
- /(?:explore|search|find|look for|investigate)\s+([^.!?\n]+)/i,
20837
- /(?:implement|create|build|add|fix|update)\s+([^.!?\n]+)/i
20838
- ];
20839
- for (const pattern of patterns) {
20840
- const match = context.match(pattern);
20841
- if (match?.[1] && match[1].length > 10) {
20842
- extractedTask = match[1].trim();
20843
- log(`[ToolRecovery] Extracted task from context: "${extractedTask.substring(0, 50)}..."`);
20844
- break;
20845
- }
20846
- }
20847
- if (!extractedTask && context.length > 20) {
20848
- const sentences = context.split(/[.!?\n]+/).filter((s) => s.trim().length > 15);
20849
- if (sentences.length > 0) {
20850
- extractedTask = sentences[sentences.length - 1].trim();
20851
- }
20852
- }
20853
- }
20854
- if (missingParams.includes("prompt") && !inferred.prompt) {
20855
- if (inferred.query) {
20856
- inferred.prompt = inferred.query;
20857
- log(`[ToolRecovery] Mapped query -> prompt: "${inferred.query.substring(0, 50)}..."`);
20858
- } else if (inferred.description && inferred.description !== "Execute task") {
20859
- inferred.prompt = inferred.description;
20860
- } else if (inferred.task) {
20861
- inferred.prompt = inferred.task;
20862
- } else if (extractedTask) {
20863
- inferred.prompt = extractedTask;
20864
- } else if (context && context.length > 20) {
20865
- inferred.prompt = context.substring(0, 500).trim();
20866
- }
20867
- if (inferred.prompt) {
20868
- log(`[ToolRecovery] Inferred prompt: "${inferred.prompt.substring(0, 50)}..."`);
20869
- }
20870
- }
20871
- if (missingParams.includes("description") && !inferred.description) {
20872
- if (inferred.prompt) {
20873
- inferred.description = inferred.prompt.substring(0, 50).replace(/\s+/g, " ").trim();
20874
- if (inferred.description.length < inferred.prompt.length) {
20875
- inferred.description += "...";
20876
- }
20877
- } else if (extractedTask) {
20878
- inferred.description = extractedTask.substring(0, 50).trim();
20879
- } else {
20880
- inferred.description = "Execute task";
20881
- }
20882
- log(`[ToolRecovery] Inferred description: ${inferred.description}`);
20883
- }
20884
- }
20885
- if (toolName === "Bash") {
20886
- if (missingParams.includes("command") && !inferred.command) {
20887
- inferred.command = inferred.cmd || inferred.shell || inferred.script || "";
20888
- }
20889
- if (missingParams.includes("description") && !inferred.description) {
20890
- if (inferred.command) {
20891
- const cmd = inferred.command.split(" ")[0];
20892
- inferred.description = `Run ${cmd} command`;
20893
- }
20894
- }
20895
- }
20896
- if (toolName === "Read") {
20897
- if (missingParams.includes("file_path") && !inferred.file_path) {
20898
- inferred.file_path = inferred.path || inferred.file || inferred.filename || "";
20899
- }
20900
- }
20901
- if (toolName === "Write") {
20902
- if (missingParams.includes("file_path") && !inferred.file_path) {
20903
- inferred.file_path = inferred.path || inferred.file || inferred.filename || "";
20904
- }
20905
- if (missingParams.includes("content") && !inferred.content) {
20906
- inferred.content = inferred.text || inferred.data || inferred.body || "";
20907
- }
20908
- }
20909
- if (toolName === "Grep") {
20910
- if (missingParams.includes("pattern") && !inferred.pattern) {
20911
- inferred.pattern = inferred.query || inferred.search || inferred.regex || "";
20912
- }
20913
- }
20914
- if (toolName === "Glob") {
20915
- if (missingParams.includes("pattern") && !inferred.pattern) {
20916
- inferred.pattern = inferred.glob || inferred.path || inferred.search || "**/*";
20917
- }
20918
- }
20919
- if (toolName === "ToolSearch") {
20920
- if (missingParams.includes("max_results") && inferred.max_results === undefined) {
20921
- inferred.max_results = 5;
20922
- log("[ToolRecovery] Inferred max_results: 5 (default)");
20923
- }
20924
- if (missingParams.includes("query") && !inferred.query) {
20925
- inferred.query = inferred.search || inferred.keyword || inferred.tool || "";
20926
- if (inferred.query) {
20927
- log(`[ToolRecovery] Inferred ToolSearch query: "${inferred.query}"`);
20928
- }
20929
- }
20930
- }
20931
- return inferred;
21308
+ return keepOnlyRealTools(extracted, knownToolNames, decodeToolName);
20932
21309
  }
20933
21310
  function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent) {
20934
21311
  const schema = toolSchemas.find((t) => t.name === toolName);
@@ -20940,7 +21317,7 @@ function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent)
20940
21317
  parsedArgs = argsStr ? JSON.parse(argsStr) : {};
20941
21318
  } catch (e) {
20942
21319
  if (textContent) {
20943
- const extracted = extractToolCallsFromText(textContent);
21320
+ const extracted = extractToolCallsFromText(textContent, toolSchemas.map((t) => t.name), toolSchemas);
20944
21321
  const matching = extracted.find((tc) => tc.name === toolName);
20945
21322
  if (matching) {
20946
21323
  parsedArgs = matching.arguments;
@@ -20948,25 +21325,33 @@ function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent)
20948
21325
  }
20949
21326
  }
20950
21327
  }
20951
- const required = schema.input_schema.required || [];
20952
- const missingParams = required.filter((param) => parsedArgs[param] === undefined || parsedArgs[param] === null || parsedArgs[param] === "");
20953
- if (missingParams.length === 0) {
20954
- return { valid: true, args: parsedArgs, repaired: false, missingParams: [] };
21328
+ const input = schema.input_schema;
21329
+ const renamed = renameToDeclaredKeys(input, parsedArgs);
21330
+ const defaulted = applySchemaDefaults(input, renamed.args);
21331
+ const coerced = coerceToSchema(input, defaulted.args);
21332
+ const args = coerced.args;
21333
+ const repaired = renamed.renamed.length > 0 || defaulted.applied.length > 0;
21334
+ if (repaired) {
21335
+ log(`[ToolRecovery] ${toolName}: applied schema defaults [${defaulted.applied}] ` + `and key renames [${renamed.renamed}] \u2014 nothing was invented`);
21336
+ }
21337
+ if (coerced.coerced.length > 0) {
21338
+ log(`[ToolRecovery] ${toolName}: coerced to declared types [${coerced.coerced}]`);
20955
21339
  }
20956
- const repairedArgs = inferMissingParameters(toolName, parsedArgs, missingParams, textContent);
20957
- const stillMissing = required.filter((param) => repairedArgs[param] === undefined || repairedArgs[param] === null || repairedArgs[param] === "");
20958
- if (stillMissing.length === 0) {
20959
- log(`[ToolRecovery] Successfully repaired tool call ${toolName}`);
20960
- return { valid: true, args: repairedArgs, repaired: true, missingParams: [] };
21340
+ const missingParams = missingRequired(input, args);
21341
+ if (missingParams.length === 0) {
21342
+ return { valid: true, args, repaired, missingParams: [] };
20961
21343
  }
20962
- return { valid: false, args: repairedArgs, repaired: false, missingParams: stillMissing };
21344
+ return { valid: false, args, repaired: false, missingParams };
20963
21345
  }
20964
- var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT;
21346
+ var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT, FUNCTION_TAG_AT_CURSOR, PARAMETER_TAG_AT_CURSOR;
20965
21347
  var init_tool_call_recovery = __esm(() => {
20966
21348
  init_tool_name_utils();
20967
21349
  init_logger();
21350
+ init_schema_validate();
20968
21351
  FUNCTION_TAG_SOURCE = `<function=(${TOOL_NAME_SOURCE})>([\\s\\S]*?)(?=<function=|$)`;
20969
21352
  FUNCTION_TAG_PRESENT = new RegExp(`<function=${TOOL_NAME_SOURCE}>`);
21353
+ FUNCTION_TAG_AT_CURSOR = new RegExp(`<function=(${TOOL_NAME_SOURCE})>`, "y");
21354
+ PARAMETER_TAG_AT_CURSOR = /<parameter=([^>\s]+)>/y;
20970
21355
  });
20971
21356
 
20972
21357
  // src/handlers/shared/web-search-detector.ts
@@ -20983,11 +21368,218 @@ var init_web_search_detector = __esm(() => {
20983
21368
  WEB_SEARCH_NAMES = new Set(["web_search", "brave_web_search", "tavily_search"]);
20984
21369
  });
20985
21370
 
21371
+ // src/handlers/shared/stream-parsers/block-writer.ts
21372
+ function createBlockWriter(send) {
21373
+ let nextIndex = 0;
21374
+ let openRef = null;
21375
+ let anyBlockEmitted = false;
21376
+ const emittedToolRefs = [];
21377
+ const stopped = new Set;
21378
+ const describe = (ref) => ref ? `${ref.kind}@${ref.index}` : "none";
21379
+ const spendableIndex = (requested, kind) => {
21380
+ if (requested === undefined)
21381
+ return nextIndex++;
21382
+ if (!stopped.has(requested))
21383
+ return requested;
21384
+ const replacement = nextIndex++;
21385
+ log(`[BlockWriter] open error: index ${requested} was already stopped; ${kind} block re-indexed to ${replacement}`);
21386
+ return replacement;
21387
+ };
21388
+ const closeCurrent = () => {
21389
+ if (!openRef)
21390
+ return;
21391
+ send("content_block_stop", { type: "content_block_stop", index: openRef.index });
21392
+ stopped.add(openRef.index);
21393
+ openRef = null;
21394
+ };
21395
+ const start = (ref, contentBlock) => {
21396
+ send("content_block_start", {
21397
+ type: "content_block_start",
21398
+ index: ref.index,
21399
+ content_block: contentBlock
21400
+ });
21401
+ openRef = ref;
21402
+ anyBlockEmitted = true;
21403
+ return ref;
21404
+ };
21405
+ const writer = {
21406
+ openText(opts) {
21407
+ if (openRef?.kind === "text") {
21408
+ if (opts?.index !== undefined && opts.index !== openRef.index) {
21409
+ log(`[BlockWriter] reusing open text block @${openRef.index}; reserved index ${opts.index} goes unused`);
21410
+ }
21411
+ return openRef;
21412
+ }
21413
+ closeCurrent();
21414
+ const index = spendableIndex(opts?.index, "text");
21415
+ return start({ index, kind: "text" }, { type: "text", text: "" });
21416
+ },
21417
+ openThinking() {
21418
+ if (openRef?.kind === "thinking")
21419
+ return openRef;
21420
+ closeCurrent();
21421
+ return start({ index: nextIndex++, kind: "thinking" }, { type: "thinking", thinking: "" });
21422
+ },
21423
+ openTool({ id, name, index }) {
21424
+ closeCurrent();
21425
+ const ref = {
21426
+ index: spendableIndex(index, "tool_use"),
21427
+ kind: "tool_use",
21428
+ toolId: id
21429
+ };
21430
+ start(ref, { type: "tool_use", id, name, input: {} });
21431
+ emittedToolRefs.push(ref);
21432
+ return ref;
21433
+ },
21434
+ append(ref, payload) {
21435
+ if (!openRef || openRef.index !== ref.index || openRef.kind !== ref.kind) {
21436
+ log(`[BlockWriter] append error: ${describe(ref)} is not the open block (open=${describe(openRef)}); ${payload.length} chars withheld for the caller to handle`);
21437
+ return false;
21438
+ }
21439
+ const delta = ref.kind === "text" ? { type: "text_delta", text: payload } : ref.kind === "thinking" ? { type: "thinking_delta", thinking: payload } : { type: "input_json_delta", partial_json: payload };
21440
+ send("content_block_delta", { type: "content_block_delta", index: ref.index, delta });
21441
+ return true;
21442
+ },
21443
+ close(ref) {
21444
+ if (openRef && openRef.index === ref.index && openRef.kind === ref.kind) {
21445
+ closeCurrent();
21446
+ return;
21447
+ }
21448
+ if (stopped.has(ref.index))
21449
+ return;
21450
+ log(`[BlockWriter] close error: ${describe(ref)} was never opened and is not stopped (open=${describe(openRef)})`);
21451
+ },
21452
+ closeCurrent,
21453
+ reserve() {
21454
+ return nextIndex++;
21455
+ },
21456
+ get openRef() {
21457
+ return openRef;
21458
+ },
21459
+ get anyBlockEmitted() {
21460
+ return anyBlockEmitted;
21461
+ },
21462
+ get emittedToolRefs() {
21463
+ return emittedToolRefs;
21464
+ }
21465
+ };
21466
+ return writer;
21467
+ }
21468
+ var init_block_writer = __esm(() => {
21469
+ init_logger();
21470
+ });
21471
+
20986
21472
  // src/handlers/shared/stream-parsers/message-start-usage.ts
20987
21473
  function messageStartUsage(priorInputTokens) {
20988
21474
  return {
20989
21475
  input_tokens: priorInputTokens && priorInputTokens > 0 ? priorInputTokens : 100,
20990
- output_tokens: 1
21476
+ output_tokens: 1,
21477
+ cache_read_input_tokens: 0,
21478
+ cache_creation_input_tokens: 0
21479
+ };
21480
+ }
21481
+
21482
+ // src/handlers/shared/stream-parsers/think-tag-splitter.ts
21483
+ function createThinkTagSplitter() {
21484
+ let phase = "deciding";
21485
+ let pending = "";
21486
+ let openArmed = true;
21487
+ const heldSuffixLength = (s) => {
21488
+ for (let n = Math.min(CLOSE.length - 1, s.length);n > 0; n--) {
21489
+ if (CLOSE.startsWith(s.slice(s.length - n)))
21490
+ return n;
21491
+ }
21492
+ return 0;
21493
+ };
21494
+ const consumeThinking = (chunk) => {
21495
+ pending += chunk;
21496
+ const at = pending.indexOf(CLOSE);
21497
+ if (at >= 0) {
21498
+ const thinking = pending.slice(0, at);
21499
+ const text = pending.slice(at + CLOSE.length);
21500
+ pending = "";
21501
+ phase = "passthrough";
21502
+ return { thinking, text };
21503
+ }
21504
+ const hold = heldSuffixLength(pending);
21505
+ const thinking = pending.slice(0, pending.length - hold);
21506
+ pending = hold === 0 ? "" : pending.slice(pending.length - hold);
21507
+ return { thinking, text: "" };
21508
+ };
21509
+ const decide = (isFlush) => {
21510
+ const lead = pending.replace(/^\s+/, "");
21511
+ const releaseAsText = () => {
21512
+ phase = "passthrough";
21513
+ const text = pending;
21514
+ pending = "";
21515
+ return { thinking: "", text };
21516
+ };
21517
+ if (lead === "")
21518
+ return isFlush ? releaseAsText() : EMPTY;
21519
+ if (openArmed && lead.startsWith(OPEN)) {
21520
+ phase = "thinking";
21521
+ pending = "";
21522
+ return consumeThinking(lead.slice(OPEN.length));
21523
+ }
21524
+ if (lead.startsWith(CLOSE)) {
21525
+ phase = "passthrough";
21526
+ pending = "";
21527
+ return { thinking: "", text: lead.slice(CLOSE.length) };
21528
+ }
21529
+ if (openArmed && OPEN.startsWith(lead) || CLOSE.startsWith(lead)) {
21530
+ return isFlush ? releaseAsText() : EMPTY;
21531
+ }
21532
+ return releaseAsText();
21533
+ };
21534
+ return {
21535
+ push(chunk) {
21536
+ if (!chunk)
21537
+ return EMPTY;
21538
+ if (phase === "passthrough")
21539
+ return { thinking: "", text: chunk };
21540
+ if (phase === "thinking")
21541
+ return consumeThinking(chunk);
21542
+ pending += chunk;
21543
+ return decide(false);
21544
+ },
21545
+ flush() {
21546
+ if (phase === "passthrough")
21547
+ return EMPTY;
21548
+ if (phase === "thinking") {
21549
+ const thinking = pending;
21550
+ pending = "";
21551
+ return { thinking, text: "" };
21552
+ }
21553
+ return decide(true);
21554
+ },
21555
+ disarmOpen() {
21556
+ openArmed = false;
21557
+ },
21558
+ get inThinking() {
21559
+ return phase === "thinking";
21560
+ }
21561
+ };
21562
+ }
21563
+ var OPEN = "<think>", CLOSE = "</think>", EMPTY;
21564
+ var init_think_tag_splitter = __esm(() => {
21565
+ EMPTY = { thinking: "", text: "" };
21566
+ });
21567
+
21568
+ // src/handlers/shared/stream-parsers/usage-cache-split.ts
21569
+ function nonNegativeInt(value) {
21570
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
21571
+ }
21572
+ function splitPromptTokens(usage) {
21573
+ const u = usage ?? {};
21574
+ const promptTokens = nonNegativeInt(u.prompt_tokens);
21575
+ const details = u.prompt_tokens_details ?? {};
21576
+ const cacheReadTokens = Math.min(nonNegativeInt(details.cached_tokens), promptTokens);
21577
+ const cacheCreationTokens = Math.min(nonNegativeInt(details.cache_write_tokens), promptTokens - cacheReadTokens);
21578
+ return {
21579
+ promptTokens,
21580
+ inputTokens: promptTokens - cacheReadTokens - cacheCreationTokens,
21581
+ cacheReadTokens,
21582
+ cacheCreationTokens
20991
21583
  };
20992
21584
  }
20993
21585
 
@@ -21014,12 +21606,9 @@ function createStreamingState() {
21014
21606
  return {
21015
21607
  usage: null,
21016
21608
  finalized: false,
21017
- textStarted: false,
21018
- textIdx: -1,
21019
- reasoningStarted: false,
21020
- reasoningIdx: -1,
21021
- curIdx: 0,
21022
21609
  tools: new Map,
21610
+ pendingToolArgs: new Map,
21611
+ pendingToolName: new Map,
21023
21612
  toolIds: new Set,
21024
21613
  lastActivity: Date.now(),
21025
21614
  accumulatedText: "",
@@ -21066,6 +21655,24 @@ data: ${JSON.stringify(d)}
21066
21655
  };
21067
21656
  const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
21068
21657
  const state = createStreamingState();
21658
+ const writer = createBlockWriter(send);
21659
+ const thinkSplitter = createThinkTagSplitter();
21660
+ const emitSplitContent = ({ thinking, text }) => {
21661
+ if (thinking) {
21662
+ behavior?.onAssistantText?.(thinking, "reasoning");
21663
+ writer.append(writer.openThinking(), thinking);
21664
+ }
21665
+ if (!text)
21666
+ return;
21667
+ state.accumulatedText += text;
21668
+ const hasStructuredToolPattern = hasExtractableFunctionTag(state.accumulatedText) || /\{\s*"(?:name|tool)"\s*:\s*"(?:Task|Read|Write|Edit|Bash|Grep|Glob)"/i.test(state.accumulatedText) || /<tool_call>/.test(state.accumulatedText);
21669
+ const shouldHoldBack = hasStructuredToolPattern && state.accumulatedText.length < 1000;
21670
+ if (shouldHoldBack) {
21671
+ log(`[Streaming] Text held back (structured tool pattern): ${state.accumulatedText.length} chars accumulated`);
21672
+ return;
21673
+ }
21674
+ writer.append(writer.openText(), text);
21675
+ };
21069
21676
  send("message_start", {
21070
21677
  type: "message_start",
21071
21678
  message: {
@@ -21110,67 +21717,50 @@ data: ${JSON.stringify(d)}
21110
21717
  }
21111
21718
  state.finalized = true;
21112
21719
  try {
21720
+ emitSplitContent(thinkSplitter.flush());
21721
+ for (const [idx, pending] of state.pendingToolArgs) {
21722
+ log(`[Streaming] Tool argument error: discarding ${pending.length} buffered chars for tool_calls index ${idx} \u2014 function.name never arrived`);
21723
+ }
21724
+ state.pendingToolArgs.clear();
21113
21725
  if (state.accumulatedText.length > 0) {
21114
21726
  const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
21115
21727
  log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
21116
21728
  }
21117
- const textToolCalls = state.tools.size > 0 ? [] : extractToolCallsFromText(state.accumulatedText, toolSchemas?.map((t) => t?.name).filter((n) => !!n));
21729
+ const textToolCalls = state.tools.size > 0 ? [] : extractToolCallsFromText(state.accumulatedText, toolSchemas?.map((t) => t?.name).filter((n) => !!n), toolSchemas, toolNameMap ? (name) => toolNameMap.get(name) ?? name : undefined);
21118
21730
  if (state.tools.size > 0 && state.accumulatedText.length > 0) {
21119
21731
  log(`[Streaming] Skipping text-based tool extraction: ${state.tools.size} structured tool call(s) already present`);
21120
21732
  }
21121
21733
  log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
21122
- if (textToolCalls.length > 0) {
21734
+ const producedContent = state.accumulatedText.length > 0 || writer.anyBlockEmitted || state.tools.size > 0 || textToolCalls.length > 0;
21735
+ const toolInFlight = writer.emittedToolRefs.length > 0 || Array.from(state.tools.values()).some((t) => !t.closed && (t.started || t.buffered)) || textToolCalls.length > 0;
21736
+ const ending = reason === "error" ? "failure" : state.finishReason !== null || !producedContent ? "success" : toolInFlight ? "failure" : "silent-truncation";
21737
+ if (ending !== "success") {
21738
+ log(`[Streaming] Stream ending error: reason=${reason} finish_reason=${state.finishReason ?? "null"} content=${producedContent} tool_in_flight=${toolInFlight} \u2192 ${ending}`);
21739
+ }
21740
+ const emitToolCalls = ending !== "failure";
21741
+ if (textToolCalls.length > 0 && !emitToolCalls) {
21742
+ log(`[Streaming] Suppressing ${textToolCalls.length} text-recovered tool call(s): the turn ended in failure`);
21743
+ } else if (textToolCalls.length > 0) {
21123
21744
  log(`[Streaming] Found ${textToolCalls.length} text-based tool call(s), converting to structured format`);
21124
- if (state.textStarted) {
21125
- send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
21126
- state.textStarted = false;
21127
- }
21128
21745
  for (const tc of textToolCalls) {
21129
- const toolIdx = state.curIdx++;
21746
+ const toolIdx = writer.reserve();
21130
21747
  const toolId = `tool_${Date.now()}_${toolIdx}`;
21131
- send("content_block_start", {
21132
- type: "content_block_start",
21133
- index: toolIdx,
21134
- content_block: { type: "tool_use", id: toolId, name: tc.name }
21135
- });
21136
- send("content_block_delta", {
21137
- type: "content_block_delta",
21138
- index: toolIdx,
21139
- delta: {
21140
- type: "input_json_delta",
21141
- partial_json: repairArgs(tc.name, JSON.stringify(tc.arguments))
21142
- }
21143
- });
21144
- send("content_block_stop", { type: "content_block_stop", index: toolIdx });
21748
+ const ref = writer.openTool({ id: toolId, name: tc.name, index: toolIdx });
21749
+ writer.append(ref, repairArgs(tc.name, JSON.stringify(tc.arguments)));
21750
+ writer.close(ref);
21145
21751
  }
21146
21752
  }
21147
- if (state.reasoningStarted) {
21148
- send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
21149
- }
21150
- if (state.textStarted) {
21151
- send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
21152
- }
21153
- for (const t of Array.from(state.tools.values())) {
21753
+ writer.closeCurrent();
21754
+ for (const t of emitToolCalls ? Array.from(state.tools.values()) : []) {
21154
21755
  if (!t.closed && t.buffered && !t.started) {
21155
21756
  if (toolSchemas && toolSchemas.length > 0) {
21156
21757
  const validation = validateToolArguments(t.name, t.arguments, toolSchemas, state.accumulatedText);
21157
21758
  if (validation.valid || validation.repaired && validation.repairedArgs) {
21158
21759
  const argsJson = repairArgs(t.name, JSON.stringify(validation.repaired ? validation.repairedArgs : validation.parsedArgs));
21159
21760
  log(`[Streaming] Sending buffered tool call (finish_reason!=tool_calls): ${t.name} with args: ${argsJson}`);
21160
- send("content_block_start", {
21161
- type: "content_block_start",
21162
- index: t.blockIndex,
21163
- content_block: { type: "tool_use", id: t.id, name: t.name }
21164
- });
21165
- send("content_block_delta", {
21166
- type: "content_block_delta",
21167
- index: t.blockIndex,
21168
- delta: { type: "input_json_delta", partial_json: argsJson }
21169
- });
21170
- send("content_block_stop", {
21171
- type: "content_block_stop",
21172
- index: t.blockIndex
21173
- });
21761
+ t.ref = writer.openTool({ id: t.id, name: t.name, index: t.blockIndex });
21762
+ writer.append(t.ref, argsJson);
21763
+ writer.close(t.ref);
21174
21764
  t.started = true;
21175
21765
  t.closed = true;
21176
21766
  } else {
@@ -21180,20 +21770,9 @@ data: ${JSON.stringify(d)}
21180
21770
  } else {
21181
21771
  const argsJson = repairArgs(t.name, t.arguments || "{}");
21182
21772
  log(`[Streaming] Sending buffered tool call (no validation): ${t.name} with args: ${argsJson}`);
21183
- send("content_block_start", {
21184
- type: "content_block_start",
21185
- index: t.blockIndex,
21186
- content_block: { type: "tool_use", id: t.id, name: t.name }
21187
- });
21188
- send("content_block_delta", {
21189
- type: "content_block_delta",
21190
- index: t.blockIndex,
21191
- delta: { type: "input_json_delta", partial_json: argsJson }
21192
- });
21193
- send("content_block_stop", {
21194
- type: "content_block_stop",
21195
- index: t.blockIndex
21196
- });
21773
+ t.ref = writer.openTool({ id: t.id, name: t.name, index: t.blockIndex });
21774
+ writer.append(t.ref, argsJson);
21775
+ writer.close(t.ref);
21197
21776
  t.started = true;
21198
21777
  t.closed = true;
21199
21778
  }
@@ -21201,28 +21780,41 @@ data: ${JSON.stringify(d)}
21201
21780
  }
21202
21781
  for (const t of Array.from(state.tools.values())) {
21203
21782
  if (t.started && !t.closed) {
21204
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
21783
+ if (t.ref)
21784
+ writer.close(t.ref);
21205
21785
  t.closed = true;
21206
21786
  }
21207
21787
  }
21208
21788
  if (middlewareManager) {
21209
21789
  await middlewareManager.afterStreamComplete(target, streamMetadata);
21210
21790
  }
21211
- if (reason === "error") {
21212
- send("error", { type: "error", error: { type: "api_error", message: err } });
21791
+ if (ending === "failure") {
21792
+ const message = err ?? "Upstream stream ended with no finish_reason while a tool call was in flight. " + "The tool call is incomplete and was not dispatched.";
21793
+ send("error", { type: "error", error: { type: "api_error", message } });
21213
21794
  } else {
21214
21795
  const hasStructuredTools = Array.from(state.tools.values()).some((t) => t.started);
21215
- const truncated = state.finishReason === "length";
21796
+ const truncated = state.finishReason === "length" || ending === "silent-truncation";
21216
21797
  const refused = state.finishReason === "content_filter";
21217
21798
  const stopReason = refused ? "refusal" : truncated ? "max_tokens" : textToolCalls.length > 0 || hasStructuredTools ? "tool_use" : "end_turn";
21218
21799
  if (truncated || refused) {
21219
21800
  log(`[Streaming] Upstream finish_reason=${state.finishReason} \u2192 stop_reason=${stopReason} (${state.accumulatedText.length} chars produced)`);
21220
21801
  }
21802
+ if (stopReason === "end_turn" && !writer.anyBlockEmitted) {
21803
+ log(`[Streaming] Contentless turn error: end_turn with no content block emitted (finish_reason=${state.finishReason ?? "null"}) \u2014 emitting an empty text block`);
21804
+ writer.close(writer.openText());
21805
+ }
21806
+ const split = splitPromptTokens(state.usage);
21807
+ const fullyCached = split.promptTokens > 0 && split.inputTokens === 0;
21808
+ const wireInput = fullyCached ? split.promptTokens : split.inputTokens;
21809
+ const wireCacheRead = fullyCached ? 0 : split.cacheReadTokens;
21810
+ const wireCacheCreation = fullyCached ? 0 : split.cacheCreationTokens;
21221
21811
  send("message_delta", {
21222
21812
  type: "message_delta",
21223
21813
  delta: { stop_reason: stopReason, stop_sequence: null },
21224
21814
  usage: {
21225
- ...state.usage?.prompt_tokens ? { input_tokens: state.usage.prompt_tokens } : {},
21815
+ input_tokens: wireInput,
21816
+ cache_read_input_tokens: wireCacheRead,
21817
+ cache_creation_input_tokens: wireCacheCreation,
21226
21818
  output_tokens: state.usage?.completion_tokens || 0
21227
21819
  }
21228
21820
  });
@@ -21232,7 +21824,11 @@ data: ${JSON.stringify(d)}
21232
21824
  if (onTokenUpdate) {
21233
21825
  if (state.usage) {
21234
21826
  log(`[Streaming] Final usage: prompt=${state.usage.prompt_tokens || 0}, completion=${state.usage.completion_tokens || 0}`);
21235
- onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
21827
+ const costSplit = splitPromptTokens(state.usage);
21828
+ onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0, {
21829
+ cacheReadTokens: costSplit.cacheReadTokens,
21830
+ cacheCreationTokens: costSplit.cacheCreationTokens
21831
+ });
21236
21832
  } else {
21237
21833
  const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
21238
21834
  log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
@@ -21287,22 +21883,10 @@ data: ${JSON.stringify(d)}
21287
21883
  }
21288
21884
  const reasoningText = delta.reasoning_content || delta.reasoning;
21289
21885
  if (reasoningText) {
21886
+ thinkSplitter.disarmOpen();
21290
21887
  behavior?.onAssistantText?.(reasoningText, "reasoning");
21291
21888
  state.lastActivity = Date.now();
21292
- if (!state.reasoningStarted) {
21293
- state.reasoningIdx = state.curIdx++;
21294
- send("content_block_start", {
21295
- type: "content_block_start",
21296
- index: state.reasoningIdx,
21297
- content_block: { type: "thinking", thinking: "" }
21298
- });
21299
- state.reasoningStarted = true;
21300
- }
21301
- send("content_block_delta", {
21302
- type: "content_block_delta",
21303
- index: state.reasoningIdx,
21304
- delta: { type: "thinking_delta", thinking: reasoningText }
21305
- });
21889
+ writer.append(writer.openThinking(), reasoningText);
21306
21890
  }
21307
21891
  const txt = delta.content || "";
21308
21892
  if (txt)
@@ -21310,42 +21894,13 @@ data: ${JSON.stringify(d)}
21310
21894
  log(`[Streaming] Text chunk: "${txt.substring(0, 30).replace(/\n/g, "\\n")}" (${txt.length} chars)`);
21311
21895
  if (txt) {
21312
21896
  state.lastActivity = Date.now();
21313
- if (state.reasoningStarted) {
21314
- send("content_block_stop", {
21315
- type: "content_block_stop",
21316
- index: state.reasoningIdx
21317
- });
21318
- state.reasoningStarted = false;
21319
- }
21320
21897
  const res = adapter.processTextContent(txt, "");
21321
21898
  log(`[Streaming] After adapter: "${res.cleanedText.substring(0, 30).replace(/\n/g, "\\n")}" (${res.cleanedText.length} chars, transformed=${res.wasTransformed})`);
21322
21899
  if (txt.length > 0 && res.cleanedText.length === 0) {
21323
21900
  log(`[Streaming] Text filtered out by adapter: "${txt.substring(0, 50)}"`);
21324
21901
  }
21325
21902
  if (res.cleanedText) {
21326
- state.accumulatedText += res.cleanedText;
21327
- const hasStructuredToolPattern = hasExtractableFunctionTag(state.accumulatedText) || /\{\s*"(?:name|tool)"\s*:\s*"(?:Task|Read|Write|Edit|Bash|Grep|Glob)"/i.test(state.accumulatedText) || /<tool_call>/.test(state.accumulatedText);
21328
- const shouldHoldBack = hasStructuredToolPattern && state.accumulatedText.length < 1000;
21329
- if (shouldHoldBack) {
21330
- log(`[Streaming] Text held back (structured tool pattern): ${state.accumulatedText.length} chars accumulated`);
21331
- }
21332
- if (!shouldHoldBack) {
21333
- if (!state.textStarted) {
21334
- state.textIdx = state.curIdx++;
21335
- send("content_block_start", {
21336
- type: "content_block_start",
21337
- index: state.textIdx,
21338
- content_block: { type: "text", text: "" }
21339
- });
21340
- state.textStarted = true;
21341
- log(`[Streaming] Started text block at index ${state.textIdx}`);
21342
- }
21343
- send("content_block_delta", {
21344
- type: "content_block_delta",
21345
- index: state.textIdx,
21346
- delta: { type: "text_delta", text: res.cleanedText }
21347
- });
21348
- }
21903
+ emitSplitContent(thinkSplitter.push(res.cleanedText));
21349
21904
  }
21350
21905
  }
21351
21906
  if (delta.tool_calls) {
@@ -21354,57 +21909,62 @@ data: ${JSON.stringify(d)}
21354
21909
  const idx = tc.index;
21355
21910
  let t = state.tools.get(idx);
21356
21911
  if (tc.function?.name) {
21912
+ const accumulatedName = (state.pendingToolName.get(idx) ?? "") + tc.function.name;
21913
+ state.pendingToolName.set(idx, accumulatedName);
21914
+ const restoredName = toolNameMap?.get(accumulatedName) || accumulatedName;
21357
21915
  if (!t) {
21358
- if (state.reasoningStarted) {
21359
- send("content_block_stop", {
21360
- type: "content_block_stop",
21361
- index: state.reasoningIdx
21362
- });
21363
- state.reasoningStarted = false;
21364
- }
21365
- if (state.textStarted) {
21366
- send("content_block_stop", {
21367
- type: "content_block_stop",
21368
- index: state.textIdx
21369
- });
21370
- state.textStarted = false;
21371
- }
21372
- const rawName = tc.function.name;
21373
- const restoredName = toolNameMap?.get(rawName) || rawName;
21374
21916
  t = {
21375
21917
  id: tc.id || `tool_${Date.now()}_${idx}`,
21376
21918
  name: restoredName,
21377
- blockIndex: state.curIdx++,
21919
+ blockIndex: writer.reserve(),
21378
21920
  started: false,
21379
21921
  closed: false,
21380
- arguments: "",
21922
+ arguments: state.pendingToolArgs.get(idx) ?? "",
21923
+ ref: null,
21381
21924
  buffered: !!toolSchemas && toolSchemas.length > 0 || behavior?.shouldBufferTool?.(restoredName) === true
21382
21925
  };
21926
+ if (t.arguments) {
21927
+ log(`[Streaming] tool ${t.name} (index ${idx}): seeded ${t.arguments.length} argument chars that arrived before function.name`);
21928
+ }
21929
+ state.pendingToolArgs.delete(idx);
21383
21930
  state.tools.set(idx, t);
21384
21931
  if (isWebSearchToolCall(restoredName)) {
21385
21932
  warnWebSearchUnsupported(restoredName, target);
21386
21933
  }
21934
+ } else if (t.name !== restoredName) {
21935
+ if (t.started) {
21936
+ log(`[Streaming] error: tool block ${t.blockIndex} was started as "${t.name}" but the full name is "${restoredName}" \u2014 the client sees the wrong name`);
21937
+ } else {
21938
+ t.name = restoredName;
21939
+ t.buffered = !!toolSchemas && toolSchemas.length > 0 || behavior?.shouldBufferTool?.(restoredName) === true;
21940
+ if (isWebSearchToolCall(restoredName)) {
21941
+ warnWebSearchUnsupported(restoredName, target);
21942
+ }
21943
+ }
21387
21944
  }
21388
21945
  if (!t.started && !t.buffered) {
21389
- send("content_block_start", {
21390
- type: "content_block_start",
21391
- index: t.blockIndex,
21392
- content_block: { type: "tool_use", id: t.id, name: t.name }
21946
+ t.ref = writer.openTool({
21947
+ id: t.id,
21948
+ name: t.name,
21949
+ index: t.blockIndex
21393
21950
  });
21394
21951
  t.started = true;
21952
+ if (t.arguments)
21953
+ writer.append(t.ref, t.arguments);
21395
21954
  }
21396
21955
  }
21956
+ if (tc.function?.arguments && !t) {
21957
+ state.pendingToolArgs.set(idx, (state.pendingToolArgs.get(idx) ?? "") + tc.function.arguments);
21958
+ }
21397
21959
  if (tc.function?.arguments && t) {
21398
21960
  t.arguments += tc.function.arguments;
21399
21961
  if (!t.buffered) {
21400
- send("content_block_delta", {
21401
- type: "content_block_delta",
21402
- index: t.blockIndex,
21403
- delta: {
21404
- type: "input_json_delta",
21405
- partial_json: tc.function.arguments
21406
- }
21407
- });
21962
+ if (!t.ref || !writer.append(t.ref, tc.function.arguments)) {
21963
+ log(`[Streaming] tool ${t.name} (index ${idx}) lost its open block mid-arguments \u2014 buffering the rest`);
21964
+ t.buffered = true;
21965
+ t.started = false;
21966
+ t.ref = null;
21967
+ }
21408
21968
  }
21409
21969
  }
21410
21970
  }
@@ -21420,110 +21980,74 @@ data: ${JSON.stringify(d)}
21420
21980
  const repairedJson = repairArgs(t.name, JSON.stringify(validation.repairedArgs));
21421
21981
  log(`[Streaming] Sending repaired tool call: ${t.name} with args: ${repairedJson}`);
21422
21982
  if (t.buffered && !t.started) {
21423
- send("content_block_start", {
21424
- type: "content_block_start",
21425
- index: t.blockIndex,
21426
- content_block: { type: "tool_use", id: t.id, name: t.name }
21427
- });
21428
- send("content_block_delta", {
21429
- type: "content_block_delta",
21430
- index: t.blockIndex,
21431
- delta: { type: "input_json_delta", partial_json: repairedJson }
21432
- });
21433
- send("content_block_stop", {
21434
- type: "content_block_stop",
21983
+ t.ref = writer.openTool({
21984
+ id: t.id,
21985
+ name: t.name,
21435
21986
  index: t.blockIndex
21436
21987
  });
21988
+ writer.append(t.ref, repairedJson);
21989
+ writer.close(t.ref);
21437
21990
  t.started = true;
21438
21991
  t.closed = true;
21439
21992
  continue;
21440
21993
  }
21441
21994
  if (t.started) {
21442
- send("content_block_stop", {
21443
- type: "content_block_stop",
21444
- index: t.blockIndex
21445
- });
21446
- const repairedIdx = state.curIdx++;
21995
+ if (t.ref)
21996
+ writer.close(t.ref);
21997
+ const repairedIdx = writer.reserve();
21447
21998
  const repairedId = `tool_repaired_${Date.now()}_${repairedIdx}`;
21448
- send("content_block_start", {
21449
- type: "content_block_start",
21450
- index: repairedIdx,
21451
- content_block: { type: "tool_use", id: repairedId, name: t.name }
21452
- });
21453
- send("content_block_delta", {
21454
- type: "content_block_delta",
21455
- index: repairedIdx,
21456
- delta: { type: "input_json_delta", partial_json: repairedJson }
21457
- });
21458
- send("content_block_stop", {
21459
- type: "content_block_stop",
21999
+ const repairedRef = writer.openTool({
22000
+ id: repairedId,
22001
+ name: t.name,
21460
22002
  index: repairedIdx
21461
22003
  });
22004
+ writer.append(repairedRef, repairedJson);
22005
+ writer.close(repairedRef);
22006
+ t.ref = repairedRef;
21462
22007
  t.closed = true;
21463
22008
  continue;
21464
22009
  }
21465
22010
  }
21466
22011
  if (!validation.valid) {
21467
22012
  log(`[Streaming] Tool call ${t.name} validation failed: ${validation.missingParams.join(", ")}`);
21468
- const errorIdx = t.buffered ? t.blockIndex : state.curIdx++;
22013
+ const errorIdx = t.buffered ? t.blockIndex : undefined;
21469
22014
  const errorMsg = `
21470
22015
 
21471
22016
  \u26A0\uFE0F Tool call "${t.name}" failed: missing required parameters: ${validation.missingParams.join(", ")}. Local models sometimes generate incomplete tool calls. Please try again or use a model with better tool support.`;
21472
- send("content_block_start", {
21473
- type: "content_block_start",
21474
- index: errorIdx,
21475
- content_block: { type: "text", text: "" }
21476
- });
21477
- send("content_block_delta", {
21478
- type: "content_block_delta",
21479
- index: errorIdx,
21480
- delta: { type: "text_delta", text: errorMsg }
21481
- });
21482
- send("content_block_stop", {
21483
- type: "content_block_stop",
21484
- index: errorIdx
21485
- });
21486
- if (t.started && !t.buffered) {
21487
- send("content_block_stop", {
21488
- type: "content_block_stop",
21489
- index: t.blockIndex
21490
- });
22017
+ const errorRef = writer.openText({ index: errorIdx });
22018
+ writer.append(errorRef, errorMsg);
22019
+ writer.close(errorRef);
22020
+ if (t.started && !t.buffered && t.ref) {
22021
+ writer.close(t.ref);
21491
22022
  }
21492
22023
  t.closed = true;
21493
22024
  continue;
21494
22025
  }
21495
22026
  if (t.buffered && !t.started) {
21496
22027
  const argsJson = repairArgs(t.name, JSON.stringify(validation.parsedArgs));
21497
- send("content_block_start", {
21498
- type: "content_block_start",
21499
- index: t.blockIndex,
21500
- content_block: { type: "tool_use", id: t.id, name: t.name }
21501
- });
21502
- send("content_block_delta", {
21503
- type: "content_block_delta",
21504
- index: t.blockIndex,
21505
- delta: { type: "input_json_delta", partial_json: argsJson }
21506
- });
21507
- send("content_block_stop", {
21508
- type: "content_block_stop",
22028
+ t.ref = writer.openTool({
22029
+ id: t.id,
22030
+ name: t.name,
21509
22031
  index: t.blockIndex
21510
22032
  });
22033
+ writer.append(t.ref, argsJson);
22034
+ writer.close(t.ref);
21511
22035
  t.started = true;
21512
22036
  t.closed = true;
21513
22037
  continue;
21514
22038
  }
21515
22039
  }
21516
22040
  if (t.started && !t.closed) {
21517
- send("content_block_stop", {
21518
- type: "content_block_stop",
21519
- index: t.blockIndex
21520
- });
22041
+ if (t.ref)
22042
+ writer.close(t.ref);
21521
22043
  t.closed = true;
21522
22044
  }
21523
22045
  }
21524
22046
  }
21525
22047
  }
21526
- } catch (e) {}
22048
+ } catch (e) {
22049
+ log(`[Streaming] Chunk processing error (chunk dropped): ${e} \u2014 payload starts: ${dataStr.slice(0, 120)}`);
22050
+ }
21527
22051
  }
21528
22052
  }
21529
22053
  await finalize("unexpected");
@@ -21549,10 +22073,13 @@ var init_openai_sse = __esm(() => {
21549
22073
  init_logger();
21550
22074
  init_tool_call_recovery();
21551
22075
  init_web_search_detector();
22076
+ init_block_writer();
22077
+ init_think_tag_splitter();
21552
22078
  });
21553
22079
 
21554
22080
  // src/handlers/shared/openai-compat.ts
21555
22081
  var init_openai_compat = __esm(() => {
22082
+ init_openai_messages();
21556
22083
  init_openai_tools();
21557
22084
  init_openai_sse();
21558
22085
  });
@@ -21560,6 +22087,7 @@ var init_openai_compat = __esm(() => {
21560
22087
  // src/adapters/gemini-api-format.ts
21561
22088
  var GeminiAPIFormat;
21562
22089
  var init_gemini_api_format = __esm(() => {
22090
+ init_openai_tools();
21563
22091
  init_gemini_schema();
21564
22092
  init_openai_compat();
21565
22093
  init_logger();
@@ -21704,6 +22232,11 @@ CRITICAL INSTRUCTION FOR OUTPUT FORMAT:
21704
22232
  }
21705
22233
  if (tools && tools.length > 0) {
21706
22234
  payload.tools = tools;
22235
+ const toolConfig = mapToolChoiceToGemini(claudeRequest.tool_choice);
22236
+ if (toolConfig) {
22237
+ payload.toolConfig = toolConfig;
22238
+ log(`[GeminiAPIFormat] toolConfig.mode -> ${toolConfig.functionCallingConfig.mode} for ${this.modelId}`);
22239
+ }
21707
22240
  }
21708
22241
  const effort = this.resolveEffortLevel(claudeRequest);
21709
22242
  if (effort) {
@@ -21825,6 +22358,7 @@ CRITICAL INSTRUCTION FOR OUTPUT FORMAT:
21825
22358
  // src/adapters/litellm-api-format.ts
21826
22359
  var INLINE_IMAGE_MODEL_PATTERNS, LiteLLMAPIFormat;
21827
22360
  var init_litellm_api_format = __esm(() => {
22361
+ init_openai_tools();
21828
22362
  init_logger();
21829
22363
  init_base_api_format();
21830
22364
  INLINE_IMAGE_MODEL_PATTERNS = ["minimax"];
@@ -21900,14 +22434,11 @@ var init_litellm_api_format = __esm(() => {
21900
22434
  if (tools.length > 0) {
21901
22435
  payload.tools = tools;
21902
22436
  }
21903
- if (claudeRequest.tool_choice) {
21904
- const { type, name } = claudeRequest.tool_choice;
21905
- if (type === "tool" && name) {
21906
- payload.tool_choice = { type: "function", function: { name } };
21907
- } else if (type === "auto" || type === "none") {
21908
- payload.tool_choice = type;
21909
- }
22437
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
22438
+ if (toolChoice !== undefined) {
22439
+ payload.tool_choice = toolChoice;
21910
22440
  }
22441
+ this.applyOpenAISamplingParams(payload, claudeRequest);
21911
22442
  return payload;
21912
22443
  }
21913
22444
  checkVisionSupport() {
@@ -22010,6 +22541,7 @@ var init_ollama_api_format = __esm(() => {
22010
22541
  // src/adapters/openai-api-format.ts
22011
22542
  var OpenAIAPIFormat;
22012
22543
  var init_openai_api_format = __esm(() => {
22544
+ init_openai_tools();
22013
22545
  init_logger();
22014
22546
  init_base_api_format();
22015
22547
  init_model_catalog();
@@ -22027,13 +22559,6 @@ var init_openai_api_format = __esm(() => {
22027
22559
  getMaxToolCount() {
22028
22560
  return 128;
22029
22561
  }
22030
- prepareRequestCommon(request, _originalRequest) {
22031
- this.truncateToolNames(request);
22032
- if (request.messages) {
22033
- this.truncateToolNamesInMessages(request.messages);
22034
- }
22035
- return request;
22036
- }
22037
22562
  applyNativeReasoning(request, originalRequest) {
22038
22563
  if (this.supportsReasoningEffort() && request.reasoning_effort === undefined) {
22039
22564
  const effort = this.resolveReasoningEffort(originalRequest);
@@ -22187,13 +22712,9 @@ var init_openai_api_format = __esm(() => {
22187
22712
  if (tools.length > 0) {
22188
22713
  payload.tools = tools;
22189
22714
  }
22190
- if (claudeRequest.tool_choice) {
22191
- const { type, name } = claudeRequest.tool_choice;
22192
- if (type === "tool" && name) {
22193
- payload.tool_choice = { type: "function", function: { name } };
22194
- } else if (type === "auto" || type === "none") {
22195
- payload.tool_choice = type;
22196
- }
22715
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
22716
+ if (toolChoice !== undefined) {
22717
+ payload.tool_choice = toolChoice;
22197
22718
  }
22198
22719
  if (this.supportsReasoningEffort()) {
22199
22720
  const effort = this.resolveReasoningEffort(claudeRequest);
@@ -22202,6 +22723,7 @@ var init_openai_api_format = __esm(() => {
22202
22723
  log(`[OpenAIAPIFormat] reasoning_effort -> ${effort} for ${this.modelId}`);
22203
22724
  }
22204
22725
  }
22726
+ this.applyOpenAISamplingParams(payload, claudeRequest);
22205
22727
  return payload;
22206
22728
  }
22207
22729
  };
@@ -22688,30 +23210,30 @@ var init_grok_model_dialect = __esm(() => {
22688
23210
  return value;
22689
23211
  }
22690
23212
  recoverFromRejection(payload, errorText) {
22691
- if (!payload || payload.reasoning_effort === undefined)
22692
- return null;
22693
- if (isReasoningEffortRejection(errorText)) {
22694
- rememberReasoningEffortRejected(this.modelId);
22695
- const next = { ...payload };
22696
- delete next.reasoning_effort;
22697
- return { payload: next, note: `dropped reasoning_effort for ${this.modelId}` };
22698
- }
22699
- const rejected = rejectedReasoningEffortValue(errorText);
22700
- if (rejected) {
22701
- rememberReasoningEffortValueRejected(this.modelId, rejected);
22702
- const fallback = fallbackReasoningEffortValue(rejected);
22703
- const next = { ...payload };
22704
- if (fallback) {
22705
- next.reasoning_effort = fallback;
22706
- return {
22707
- payload: next,
22708
- note: `reasoning_effort "${rejected}" -> "${fallback}" for ${this.modelId}`
22709
- };
23213
+ if (payload?.reasoning_effort !== undefined) {
23214
+ if (isReasoningEffortRejection(errorText)) {
23215
+ rememberReasoningEffortRejected(this.modelId);
23216
+ const next = { ...payload };
23217
+ delete next.reasoning_effort;
23218
+ return { payload: next, note: `dropped reasoning_effort for ${this.modelId}` };
23219
+ }
23220
+ const rejected = rejectedReasoningEffortValue(errorText);
23221
+ if (rejected) {
23222
+ rememberReasoningEffortValueRejected(this.modelId, rejected);
23223
+ const fallback = fallbackReasoningEffortValue(rejected);
23224
+ const next = { ...payload };
23225
+ if (fallback) {
23226
+ next.reasoning_effort = fallback;
23227
+ return {
23228
+ payload: next,
23229
+ note: `reasoning_effort "${rejected}" -> "${fallback}" for ${this.modelId}`
23230
+ };
23231
+ }
23232
+ delete next.reasoning_effort;
23233
+ return { payload: next, note: `dropped unsupported reasoning_effort for ${this.modelId}` };
22710
23234
  }
22711
- delete next.reasoning_effort;
22712
- return { payload: next, note: `dropped unsupported reasoning_effort for ${this.modelId}` };
22713
23235
  }
22714
- return null;
23236
+ return super.recoverFromRejection(payload, errorText);
22715
23237
  }
22716
23238
  parseXmlParameters(xmlContent) {
22717
23239
  const params = {};
@@ -22738,6 +23260,7 @@ var init_grok_model_dialect = __esm(() => {
22738
23260
  return lookupModel(this.modelId)?.contextWindow ?? 0;
22739
23261
  }
22740
23262
  reset() {
23263
+ super.reset();
22741
23264
  this.xmlBuffer = "";
22742
23265
  }
22743
23266
  };
@@ -22881,16 +23404,6 @@ var init_xiaomi_model_dialect = __esm(() => {
22881
23404
  wasTransformed: false
22882
23405
  };
22883
23406
  }
22884
- getToolNameLimit() {
22885
- return 64;
22886
- }
22887
- prepareRequestCommon(request, _originalRequest) {
22888
- this.truncateToolNames(request);
22889
- if (request.messages) {
22890
- this.truncateToolNamesInMessages(request.messages);
22891
- }
22892
- return request;
22893
- }
22894
23407
  applyNativeReasoning(request, originalRequest) {
22895
23408
  if (originalRequest.thinking) {
22896
23409
  log("[XiaomiModelDialect] Stripping thinking object (not supported by Xiaomi API)");
@@ -31672,6 +32185,31 @@ var init_stats = __esm(() => {
31672
32185
  });
31673
32186
  });
31674
32187
 
32188
+ // src/handlers/shared/request-shape.ts
32189
+ function isRequestShapeError(errorBody) {
32190
+ if (!errorBody)
32191
+ return false;
32192
+ const lower = errorBody.toLowerCase();
32193
+ return lower.includes("unknown_parameter") || lower.includes("unsupported_parameter") || lower.includes("invalid_parameter") || lower.includes("unknown parameter:") || lower.includes("unsupported parameter:") || lower.includes("invalid parameter:");
32194
+ }
32195
+ function isContextOverflowError(status, errorBody) {
32196
+ if (!errorBody)
32197
+ return false;
32198
+ if (isRequestShapeError(errorBody))
32199
+ return false;
32200
+ const lower = errorBody.toLowerCase();
32201
+ if (lower.includes("context_length_exceeded") || lower.includes("string_above_max_length") || lower.includes("request_too_large") || lower.includes("context_window_exceeded") || lower.includes("prompt_too_long")) {
32202
+ return true;
32203
+ }
32204
+ if (lower.includes("maximum context length is") || lower.includes("reduce the length of the messages") || lower.includes("input is too long") || lower.includes(CONTEXT_OVERFLOW_PHRASE)) {
32205
+ return true;
32206
+ }
32207
+ if (status === 413 && (lower.includes("token") || lower.includes("context")))
32208
+ return true;
32209
+ return false;
32210
+ }
32211
+ var CONTEXT_OVERFLOW_PHRASE = "prompt is too long";
32212
+
31675
32213
  // src/handlers/shared/anthropic-error.ts
31676
32214
  function statusToErrorType(status) {
31677
32215
  switch (status) {
@@ -31750,6 +32288,8 @@ function isTerminalError(status, bodyText, terminal429) {
31750
32288
  return true;
31751
32289
  if (status === 429 && terminal429)
31752
32290
  return true;
32291
+ if (isContextOverflowError(status, bodyText))
32292
+ return true;
31753
32293
  const lower = (bodyText || "").toLowerCase();
31754
32294
  if (lower.includes("insufficient balance") || lower.includes("insufficient_balance") || lower.includes("insufficient_quota") || lower.includes("insufficient quota") || lower.includes("billing_not_active") || lower.includes("billing not active") || lower.includes("out of credits") || lower.includes("no credits remaining") || lower.includes("exceeded your current quota") || lower.includes("quota_exceeded")) {
31755
32295
  return true;
@@ -31763,11 +32303,13 @@ function isTerminalError(status, bodyText, terminal429) {
31763
32303
  return false;
31764
32304
  }
31765
32305
  function buildSurfacedErrorMessage(opts) {
31766
- const { providerDisplayName, status, hint, providerMessage } = opts;
32306
+ const { providerDisplayName, status, hint, providerMessage, leadPhrase } = opts;
31767
32307
  const head = `${providerDisplayName} error (HTTP ${status})`;
31768
32308
  const parts = [head];
31769
32309
  if (hint)
31770
32310
  parts[0] = `${head}: ${hint}`;
32311
+ if (leadPhrase && !parts[0].startsWith(leadPhrase))
32312
+ parts[0] = `${leadPhrase} \u2014 ${parts[0]}`;
31771
32313
  const detail = (providerMessage || "").trim();
31772
32314
  if (detail && !parts[0].includes(detail)) {
31773
32315
  const trimmed = detail.length > 600 ? `${detail.slice(0, 600)}\u2026` : detail;
@@ -32177,14 +32719,6 @@ var init_model_unsupported = __esm(() => {
32177
32719
  ];
32178
32720
  });
32179
32721
 
32180
- // src/handlers/shared/request-shape.ts
32181
- function isRequestShapeError(errorBody) {
32182
- if (!errorBody)
32183
- return false;
32184
- const lower = errorBody.toLowerCase();
32185
- return lower.includes("unknown_parameter") || lower.includes("unsupported_parameter") || lower.includes("invalid_parameter") || lower.includes("unknown parameter:") || lower.includes("unsupported parameter:") || lower.includes("invalid parameter:");
32186
- }
32187
-
32188
32722
  // src/handlers/shared/stream-head-sniffer.ts
32189
32723
  function isRetryableStreamError(code, type, message) {
32190
32724
  if (RETRYABLE_ERROR_CODES.has(code))
@@ -33938,6 +34472,17 @@ function stripProviderPrefix(name) {
33938
34472
  const at = name.indexOf("@");
33939
34473
  return at === -1 ? name : name.slice(at + 1);
33940
34474
  }
34475
+ function computeCacheReadDiscount(pricing, billedInputTokens, detail) {
34476
+ const cacheReadTokens = detail?.cacheReadTokens ?? 0;
34477
+ if (cacheReadTokens <= 0 || billedInputTokens <= 0)
34478
+ return 0;
34479
+ const rate = pricing.cacheReadCostPer1M ?? pricing.inputCostPer1M;
34480
+ const perMillionSaved = pricing.inputCostPer1M - rate;
34481
+ if (!(perMillionSaved > 0))
34482
+ return 0;
34483
+ const discountedTokens = Math.min(cacheReadTokens, billedInputTokens);
34484
+ return discountedTokens / 1e6 * perMillionSaved;
34485
+ }
33941
34486
 
33942
34487
  class TokenTracker {
33943
34488
  port;
@@ -33952,6 +34497,7 @@ class TokenTracker {
33952
34497
  toolCallsByName = new Map;
33953
34498
  startedAt = Date.now();
33954
34499
  sessionBilledInputTokens = 0;
34500
+ sessionCacheReadTokens = 0;
33955
34501
  constructor(port, config) {
33956
34502
  this.port = port;
33957
34503
  this.config = config;
@@ -33987,27 +34533,32 @@ class TokenTracker {
33987
34533
  rewrite() {
33988
34534
  this.writeFile(this.getLastInputTokens(), this.sessionOutputTokens);
33989
34535
  }
33990
- update(inputTokens, outputTokens) {
34536
+ update(inputTokens, outputTokens, detail) {
33991
34537
  this.sessionInputTokens = inputTokens;
33992
34538
  this.lastInputTokens = inputTokens;
33993
34539
  this.sessionOutputTokens += outputTokens;
33994
34540
  this.sessionBilledInputTokens += inputTokens;
34541
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
33995
34542
  const pricing = this.getPricing();
33996
- const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
34543
+ const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M - this.cacheReadDiscount(pricing, inputTokens, detail);
33997
34544
  this.sessionTotalCost += cost;
33998
34545
  this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
33999
34546
  }
34000
- accumulateBoth(inputTokens, outputTokens) {
34547
+ accumulateBoth(inputTokens, outputTokens, detail) {
34001
34548
  this.sessionInputTokens += inputTokens;
34002
34549
  this.lastInputTokens = this.sessionInputTokens;
34003
34550
  this.sessionOutputTokens += outputTokens;
34551
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
34004
34552
  const pricing = this.getPricing();
34005
- const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M;
34553
+ const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M - this.cacheReadDiscount(pricing, this.sessionInputTokens, {
34554
+ cacheReadTokens: this.sessionCacheReadTokens,
34555
+ cacheCreationTokens: 0
34556
+ });
34006
34557
  this.sessionTotalCost = cost;
34007
34558
  this.sessionBilledInputTokens = this.sessionInputTokens;
34008
34559
  this.writeFile(this.sessionInputTokens, this.sessionOutputTokens, pricing.isEstimate);
34009
34560
  }
34010
- updateWithDelta(inputTokens, outputTokens) {
34561
+ updateWithDelta(inputTokens, outputTokens, detail) {
34011
34562
  let incrementalInputTokens;
34012
34563
  this.lastInputTokens = inputTokens;
34013
34564
  if (inputTokens >= this.sessionInputTokens) {
@@ -34022,17 +34573,19 @@ class TokenTracker {
34022
34573
  log(`[TokenTracker] Ambiguous token decrease (${inputTokens} vs ${this.sessionInputTokens}), charging full input`);
34023
34574
  }
34024
34575
  this.sessionOutputTokens += outputTokens;
34576
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
34025
34577
  const pricing = this.getPricing();
34026
34578
  this.sessionBilledInputTokens += incrementalInputTokens;
34027
- const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
34579
+ const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M - this.cacheReadDiscount(pricing, incrementalInputTokens, detail);
34028
34580
  this.sessionTotalCost += cost;
34029
34581
  this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
34030
34582
  }
34031
- updateWithActualCost(inputTokens, outputTokens, actualCost) {
34583
+ updateWithActualCost(inputTokens, outputTokens, actualCost, detail) {
34032
34584
  this.sessionInputTokens = inputTokens;
34033
34585
  this.lastInputTokens = inputTokens;
34034
34586
  this.sessionOutputTokens += outputTokens;
34035
34587
  this.sessionBilledInputTokens += inputTokens;
34588
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
34036
34589
  if (typeof actualCost === "number" && actualCost > 0) {
34037
34590
  this.sessionTotalCost += actualCost;
34038
34591
  log(`[TokenTracker] Actual cost from API: $${actualCost.toFixed(6)}`);
@@ -34040,11 +34593,11 @@ class TokenTracker {
34040
34593
  const pricing = this.getPricing();
34041
34594
  const inputCost = inputTokens / 1e6 * pricing.inputCostPer1M;
34042
34595
  const outputCost = outputTokens / 1e6 * pricing.outputCostPer1M;
34043
- this.sessionTotalCost += inputCost + outputCost;
34596
+ this.sessionTotalCost += inputCost + outputCost - this.cacheReadDiscount(pricing, inputTokens, detail);
34044
34597
  }
34045
34598
  this.writeFile(inputTokens, this.sessionOutputTokens);
34046
34599
  }
34047
- updateLocal(inputTokens, outputTokens) {
34600
+ updateLocal(inputTokens, outputTokens, _detail) {
34048
34601
  if (inputTokens > 0) {
34049
34602
  this.sessionInputTokens = inputTokens;
34050
34603
  this.lastInputTokens = inputTokens;
@@ -34074,6 +34627,9 @@ class TokenTracker {
34074
34627
  getPricing() {
34075
34628
  return getModelPricing(this.config.providerName, this.config.modelName);
34076
34629
  }
34630
+ cacheReadDiscount(pricing, billedInputTokens, detail) {
34631
+ return computeCacheReadDiscount(pricing, billedInputTokens, detail);
34632
+ }
34077
34633
  getDisplayName() {
34078
34634
  if (this.config.providerDisplayName)
34079
34635
  return this.config.providerDisplayName;
@@ -34242,6 +34798,9 @@ class ComposedHandler {
34242
34798
  if (resolvedModelAdapter.getName() !== "DefaultAPIFormat") {
34243
34799
  this.modelAdapter = resolvedModelAdapter;
34244
34800
  }
34801
+ const responseWire = this.resolveStreamFormat();
34802
+ this.resolvedDialect.setResponseWireFormat(responseWire);
34803
+ this.explicitAdapter?.setResponseWireFormat(responseWire);
34245
34804
  this.middlewareManager = new MiddlewareManager;
34246
34805
  if (this.bareModelName.includes("gemini") || this.bareModelName.includes("google/")) {
34247
34806
  this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
@@ -34508,9 +35067,15 @@ class ComposedHandler {
34508
35067
  log(`[${this.provider.displayName}] Response status: ${response.status}`);
34509
35068
  this.capturePlanUsage(response);
34510
35069
  if (!response.ok) {
34511
- if (response.status >= 400 && response.status < 500 && this.modelAdapter?.recoverFromRejection) {
34512
- const errorText = await response.clone().text();
34513
- const recovery = this.modelAdapter.recoverFromRejection(requestPayload, errorText);
35070
+ if (response.status >= 400 && response.status < 500) {
35071
+ const candidates = [this.modelAdapter, this.getAdapter()].filter((a, i, all) => !!a?.recoverFromRejection && all.indexOf(a) === i);
35072
+ const errorText = candidates.length > 0 ? await response.clone().text() : "";
35073
+ let recovery = null;
35074
+ for (const candidate of candidates) {
35075
+ recovery = candidate.recoverFromRejection(requestPayload, errorText);
35076
+ if (recovery)
35077
+ break;
35078
+ }
34514
35079
  if (recovery) {
34515
35080
  log(`[${this.provider.displayName}] Parameter rejected \u2014 retrying: ${recovery.note}`);
34516
35081
  requestPayload = recovery.payload;
@@ -34690,7 +35255,8 @@ class ComposedHandler {
34690
35255
  providerDisplayName: this.provider.displayName,
34691
35256
  status: response.status,
34692
35257
  hint,
34693
- providerMessage: providerMsg
35258
+ providerMessage: providerMsg,
35259
+ leadPhrase: isContextOverflowError(response.status, errorText) ? CONTEXT_OVERFLOW_PHRASE : undefined
34694
35260
  });
34695
35261
  return c.json(wrapAnthropicError(400, surfaced, "invalid_request_error", response.status), 400);
34696
35262
  }
@@ -34913,20 +35479,20 @@ class ComposedHandler {
34913
35479
  }
34914
35480
  handleStream(c, response, adapter, claudeRequest, toolNameMap, onComplete, onApiError, behaviorSession) {
34915
35481
  let pendingOnComplete = onComplete;
34916
- const onTokenUpdate = (input, output) => {
35482
+ const onTokenUpdate = (input, output, detail) => {
34917
35483
  const strategy = this.options.tokenStrategy || "standard";
34918
35484
  switch (strategy) {
34919
35485
  case "accumulate-both":
34920
- this.tokenTracker.accumulateBoth(input, output);
35486
+ this.tokenTracker.accumulateBoth(input, output, detail);
34921
35487
  break;
34922
35488
  case "delta-aware":
34923
- this.tokenTracker.updateWithDelta(input, output);
35489
+ this.tokenTracker.updateWithDelta(input, output, detail);
34924
35490
  break;
34925
35491
  case "local":
34926
- this.tokenTracker.updateLocal(input, output);
35492
+ this.tokenTracker.updateLocal(input, output, detail);
34927
35493
  break;
34928
35494
  default:
34929
- this.tokenTracker.update(input, output);
35495
+ this.tokenTracker.update(input, output, detail);
34930
35496
  break;
34931
35497
  }
34932
35498
  if (pendingOnComplete) {
@@ -34955,7 +35521,7 @@ class ComposedHandler {
34955
35521
  return createResponsesStreamHandler(c, response, {
34956
35522
  modelName: this.bareModelName,
34957
35523
  onTokenUpdate,
34958
- toolNameMap: adapter.getToolNameMap(),
35524
+ toolNameMap,
34959
35525
  contextWindow: lookupModelForProvider(this.bareModelName, this.provider.name),
34960
35526
  onApiError,
34961
35527
  priorInputTokens,
@@ -35111,6 +35677,9 @@ function getRecoveryHint(status, errorText, providerName, transportTerminal429)
35111
35677
  }
35112
35678
  return "Request format may be incompatible with provider.";
35113
35679
  }
35680
+ if (isContextOverflowError(status, errorText)) {
35681
+ return "Input too large. Reduce message history or use a larger-context model.";
35682
+ }
35114
35683
  if (status >= 500) {
35115
35684
  return "Server error \u2014 retry after a brief wait.";
35116
35685
  }
@@ -42866,8 +43435,10 @@ function accountStreamEvent(rawEvent) {
42866
43435
  if (typeof text === "string" && text.length > 0) {
42867
43436
  contentDelta = true;
42868
43437
  textChars = text.length;
42869
- } else if (parsed?.type === "content_block_delta" || parsed?.type === "content_block_start") {
43438
+ } else if (parsed?.type === "content_block_delta") {
42870
43439
  contentDelta = true;
43440
+ } else if (parsed?.type === "content_block_start") {
43441
+ contentDelta = parsed.content_block?.type === "tool_use";
42871
43442
  }
42872
43443
  const outputTokens = parsed?.usage?.output_tokens ?? parsed?.message?.usage?.output_tokens ?? parsed?.usage?.completion_tokens;
42873
43444
  const stopReason = parsed?.delta?.stop_reason ?? (Array.isArray(parsed?.choices) ? parsed.choices[0]?.finish_reason : undefined);
@@ -44591,6 +45162,7 @@ var init_cors = () => {};
44591
45162
  // src/adapters/local-adapter.ts
44592
45163
  var LocalModelAdapter;
44593
45164
  var init_local_adapter = __esm(() => {
45165
+ init_openai_tools();
44594
45166
  init_logger();
44595
45167
  init_base_api_format();
44596
45168
  init_dialect_manager();
@@ -44657,26 +45229,26 @@ ${messages[0].content}`;
44657
45229
  tools: tools.length > 0 ? tools : undefined,
44658
45230
  stream_options: { include_usage: true }
44659
45231
  };
44660
- if (claudeRequest.tool_choice && tools.length > 0) {
44661
- const { type, name } = claudeRequest.tool_choice;
44662
- if (type === "tool" && name) {
44663
- payload.tool_choice = { type: "function", function: { name } };
44664
- } else if (type === "auto" || type === "none") {
44665
- payload.tool_choice = type;
45232
+ if (tools.length > 0) {
45233
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
45234
+ if (toolChoice !== undefined) {
45235
+ payload.tool_choice = toolChoice;
44666
45236
  }
44667
45237
  }
45238
+ this.applyOpenAISamplingParams(payload, claudeRequest);
44668
45239
  return payload;
44669
45240
  }
44670
45241
  prepareRequestCommon(request, originalRequest) {
44671
45242
  this.innerAdapter.prepareRequest(request, originalRequest);
44672
- for (const [k, v] of this.innerAdapter.getToolNameMap()) {
44673
- this.toolNameMap.set(k, v);
44674
- }
44675
45243
  delete request.enable_thinking;
44676
45244
  delete request.thinking_budget;
44677
45245
  delete request.thinking;
44678
45246
  return request;
44679
45247
  }
45248
+ setResponseWireFormat(format) {
45249
+ super.setResponseWireFormat(format);
45250
+ this.innerAdapter.setResponseWireFormat(format);
45251
+ }
44680
45252
  getToolNameMap() {
44681
45253
  const map = new Map(super.getToolNameMap());
44682
45254
  for (const [k, v] of this.innerAdapter.getToolNameMap()) {
@@ -44835,19 +45407,20 @@ ${text}`;
44835
45407
  if (claudeRequest.thinking) {
44836
45408
  payload.thinking = claudeRequest.thinking;
44837
45409
  }
44838
- if (claudeRequest.tool_choice) {
44839
- const { type, name } = claudeRequest.tool_choice;
44840
- if (type === "tool" && name) {
44841
- payload.tool_choice = { type: "function", function: { name } };
44842
- } else if (type === "auto" || type === "none") {
44843
- payload.tool_choice = type;
44844
- }
45410
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
45411
+ if (toolChoice !== undefined) {
45412
+ payload.tool_choice = toolChoice;
44845
45413
  }
45414
+ this.applyOpenAISamplingParams(payload, claudeRequest);
44846
45415
  return payload;
44847
45416
  }
44848
45417
  prepareRequestCommon(request, originalRequest) {
44849
45418
  return this.innerAdapter.prepareRequest(request, originalRequest);
44850
45419
  }
45420
+ setResponseWireFormat(format) {
45421
+ super.setResponseWireFormat(format);
45422
+ this.innerAdapter.setResponseWireFormat(format);
45423
+ }
44851
45424
  getToolNameMap() {
44852
45425
  const map = new Map(super.getToolNameMap());
44853
45426
  for (const [k, v] of this.innerAdapter.getToolNameMap()) {