claudish 9.4.0 → 9.6.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 +1455 -635
  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.6.0";
719
719
 
720
720
  // src/logger.ts
721
721
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -815,14 +815,17 @@ function redactLogLine(message, timestamp) {
815
815
  return `[${timestamp}] ${message}
816
816
  `;
817
817
  }
818
+ function logFileStamp() {
819
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
820
+ return `${timestamp}_${process.pid}`;
821
+ }
818
822
  function initLogger(debugMode, level = "info", noLogs = false) {
819
823
  if (!noLogs) {
820
824
  const logsDir = join2(homedir2(), ".claudish", "logs");
821
825
  if (!existsSync2(logsDir)) {
822
826
  mkdirSync(logsDir, { recursive: true });
823
827
  }
824
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
825
- alwaysOnLogPath = join2(logsDir, `claudish_${timestamp}.log`);
828
+ alwaysOnLogPath = join2(logsDir, `claudish_${logFileStamp()}.log`);
826
829
  writeFileSync2(alwaysOnLogPath, `Claudish Session Log - ${new Date().toISOString()}
827
830
  Mode: structural (content redacted)
828
831
  ${"=".repeat(60)}
@@ -837,8 +840,7 @@ ${"=".repeat(60)}
837
840
  if (!existsSync2(logsDir)) {
838
841
  mkdirSync(logsDir, { recursive: true });
839
842
  }
840
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").split("T").join("_").slice(0, -5);
841
- logFilePath = join2(logsDir, `claudish_${timestamp}.log`);
843
+ logFilePath = join2(logsDir, `claudish_${logFileStamp()}.log`);
842
844
  writeFileSync2(logFilePath, `Claudish Debug Log - ${new Date().toISOString()}
843
845
  Log Level: ${level}
844
846
  ${"=".repeat(80)}
@@ -18167,6 +18169,12 @@ var init_stdio2 = __esm(() => {
18167
18169
  import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
18168
18170
  import { homedir as homedir7 } from "os";
18169
18171
  import { dirname as dirname4, join as join7 } from "path";
18172
+ function reasoningStatusOf(entry) {
18173
+ if (entry.reasoningStatus === "known" || entry.reasoningStatus === "unknown") {
18174
+ return entry.reasoningStatus;
18175
+ }
18176
+ return entry.reasoning !== undefined ? "known" : "unknown";
18177
+ }
18170
18178
  function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
18171
18179
  if (!existsSync5(path))
18172
18180
  return null;
@@ -18183,12 +18191,14 @@ function readAllModelsCache(path = ALL_MODELS_CACHE_PATH) {
18183
18191
  const models = Array.isArray(data.models) ? data.models : [];
18184
18192
  const entries = Array.isArray(data.entries) ? data.entries : [];
18185
18193
  const plans = Array.isArray(data.plans) ? data.plans : undefined;
18194
+ const catalogRevision = typeof data.catalogRevision === "string" ? data.catalogRevision : undefined;
18186
18195
  return {
18187
18196
  version: 2,
18188
18197
  lastUpdated,
18189
18198
  entries,
18190
18199
  models,
18191
- ...plans !== undefined ? { plans } : {}
18200
+ ...plans !== undefined ? { plans } : {},
18201
+ ...catalogRevision !== undefined ? { catalogRevision } : {}
18192
18202
  };
18193
18203
  }
18194
18204
  function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
@@ -18198,7 +18208,8 @@ function writeAllModelsCache(data, path = ALL_MODELS_CACHE_PATH) {
18198
18208
  lastUpdated: data.lastUpdated ?? new Date().toISOString(),
18199
18209
  entries: data.entries ?? existing?.entries ?? [],
18200
18210
  models: data.models ?? existing?.models ?? [],
18201
- ...data.plans !== undefined || existing?.plans !== undefined ? { plans: data.plans ?? existing?.plans ?? [] } : {}
18211
+ ...data.plans !== undefined || existing?.plans !== undefined ? { plans: data.plans ?? existing?.plans ?? [] } : {},
18212
+ ...data.catalogRevision !== undefined ? { catalogRevision: data.catalogRevision } : {}
18202
18213
  };
18203
18214
  mkdirSync5(dirname4(path), { recursive: true });
18204
18215
  writeFileSync5(path, JSON.stringify(merged), "utf-8");
@@ -18285,6 +18296,10 @@ function lookupModel(modelId, cachePath) {
18285
18296
  function lookupModelReasoning(modelId, cachePath) {
18286
18297
  return findCacheEntry(modelId, cachePath)?.reasoning;
18287
18298
  }
18299
+ function lookupModelReasoningStatus(modelId, cachePath) {
18300
+ const entry = findCacheEntry(modelId, cachePath);
18301
+ return entry ? reasoningStatusOf(entry) : undefined;
18302
+ }
18288
18303
  function lookupModelTokenParam(modelId, cachePath) {
18289
18304
  return findCacheEntry(modelId, cachePath)?.tokenParam;
18290
18305
  }
@@ -18398,6 +18413,26 @@ function findCacheEntry(modelId, cachePath) {
18398
18413
  return entry;
18399
18414
  }
18400
18415
  }
18416
+ const canonical = resolvePlanDescribedModelId(modelId, cache.plans);
18417
+ if (!canonical)
18418
+ return;
18419
+ const canonicalLower = canonical.toLowerCase();
18420
+ for (const entry of cache.entries) {
18421
+ if (entry.modelId.toLowerCase() === canonicalLower)
18422
+ return entry;
18423
+ if (entry.aliases?.some((a) => a.toLowerCase() === canonicalLower))
18424
+ return entry;
18425
+ }
18426
+ return;
18427
+ }
18428
+ function resolvePlanDescribedModelId(wireId, plans) {
18429
+ if (!plans)
18430
+ return;
18431
+ for (const plan of plans) {
18432
+ const described = plan.modelDescriptions?.[wireId];
18433
+ if (described?.status === "described" && described.modelId)
18434
+ return described.modelId;
18435
+ }
18401
18436
  return;
18402
18437
  }
18403
18438
  function classifyCatalogHit(entry, q) {
@@ -19045,6 +19080,28 @@ var init_remote_provider_types = __esm(() => {
19045
19080
  };
19046
19081
  });
19047
19082
 
19083
+ // src/adapters/optional-param-rejection.ts
19084
+ function escapeName(name) {
19085
+ return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
19086
+ }
19087
+ function namesParameterField(errorText, param) {
19088
+ const p = escapeName(param);
19089
+ const tail = "(?![A-Za-z0-9_])";
19090
+ const quoted = new RegExp(`['"\`]${p}['"\`]`);
19091
+ 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");
19092
+ const leading = new RegExp(`(?:^|["'])\\s*${p}\\s*:`);
19093
+ return quoted.test(errorText) || introduced.test(errorText) || leading.test(errorText);
19094
+ }
19095
+ function rejectedOptionalParams(errorText, candidates) {
19096
+ if (!errorText || !PARAMETER_COMPLAINT.test(errorText))
19097
+ return [];
19098
+ return candidates.filter((param) => namesParameterField(errorText, param));
19099
+ }
19100
+ var PARAMETER_COMPLAINT;
19101
+ var init_optional_param_rejection = __esm(() => {
19102
+ PARAMETER_COMPLAINT = /(unknown|unrecognized|unexpected|unsupported|not supported|does not support|invalid|not permitted|not allowed|extra (fields|inputs))/i;
19103
+ });
19104
+
19048
19105
  // src/adapters/tool-name-utils.ts
19049
19106
  function hashToolName(name) {
19050
19107
  let h1 = 3735928559;
@@ -19061,20 +19118,46 @@ function hashToolName(name) {
19061
19118
  const combined = 4294967296 * (2097151 & h2) + (h1 >>> 0);
19062
19119
  return combined.toString(16).padStart(8, "0").slice(0, 8);
19063
19120
  }
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;
19121
+ function newToolNameBindings() {
19122
+ return { byEncoded: new Map, byOriginal: new Map };
19123
+ }
19124
+ function hashedForm(transformed, original, limit, salt) {
19125
+ const seed = salt === 0 ? original : `${original}#${salt}`;
19126
+ const prefix = transformed.slice(0, Math.max(0, limit - 9));
19127
+ return `${prefix}_${hashToolName(seed)}`;
19128
+ }
19129
+ function encodeToolName(original, limit, bindings) {
19130
+ const already = bindings.byOriginal.get(original);
19131
+ if (already !== undefined)
19132
+ return already;
19133
+ const transformed = original.replace(UNWIRABLE_CHARACTER, "_");
19134
+ const takenByAnother = (name) => {
19135
+ const owner = bindings.byEncoded.get(name);
19136
+ return owner !== undefined && owner !== original;
19137
+ };
19138
+ let encoded = transformed.length <= limit ? transformed : hashedForm(transformed, original, limit, 0);
19139
+ if (takenByAnother(encoded)) {
19140
+ let salt = 0;
19141
+ do {
19142
+ encoded = hashedForm(transformed, original, limit, salt++);
19143
+ } while (takenByAnother(encoded));
19144
+ }
19145
+ bindings.byEncoded.set(encoded, original);
19146
+ bindings.byOriginal.set(original, encoded);
19147
+ if (encoded !== original) {
19148
+ log(`[ToolName] Encoded: "${original}" -> "${encoded}" (${original.length} -> ${encoded.length})`);
19149
+ }
19150
+ return encoded;
19151
+ }
19152
+ function wireDecodesToolNames(format) {
19153
+ return format !== undefined && TOOL_NAME_DECODING_WIRES.includes(format);
19154
+ }
19155
+ var TOOL_NAME_SOURCE = "[A-Za-z_][A-Za-z0-9_.-]{0,63}", TOOL_NAME_SHAPE, UNWIRABLE_CHARACTER, TOOL_NAME_DECODING_WIRES;
19075
19156
  var init_tool_name_utils = __esm(() => {
19076
19157
  init_logger();
19077
19158
  TOOL_NAME_SHAPE = new RegExp(`^${TOOL_NAME_SOURCE}$`);
19159
+ UNWIRABLE_CHARACTER = /[^A-Za-z0-9_-]/g;
19160
+ TOOL_NAME_DECODING_WIRES = ["openai-sse", "openai-responses-sse"];
19078
19161
  });
19079
19162
 
19080
19163
  // src/handlers/shared/format/openai-messages.ts
@@ -19106,13 +19189,154 @@ ${msg}`;
19106
19189
  processAssistantMessage(msg, messages, simpleFormat);
19107
19190
  }
19108
19191
  }
19109
- return messages;
19192
+ return normalizeMessageSequence(messages);
19110
19193
  }
19111
- function imageBlockToUrlPart(block) {
19112
- return {
19113
- type: "image_url",
19114
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
19194
+ function mergeUserContent(a, b) {
19195
+ if (typeof a === "string" && typeof b === "string") {
19196
+ if (!a)
19197
+ return b;
19198
+ if (!b)
19199
+ return a;
19200
+ return `${a}
19201
+
19202
+ ${b}`;
19203
+ }
19204
+ const toParts = (c) => {
19205
+ if (typeof c === "string")
19206
+ return c ? [{ type: "text", text: c }] : [];
19207
+ if (Array.isArray(c))
19208
+ return c;
19209
+ return;
19210
+ };
19211
+ const pa = toParts(a);
19212
+ const pb = toParts(b);
19213
+ if (!pa || !pb)
19214
+ return;
19215
+ return [...pa, ...pb];
19216
+ }
19217
+ function pushUserMessage(out, msg) {
19218
+ const prev = out[out.length - 1];
19219
+ if (prev?.role === "user") {
19220
+ const merged = mergeUserContent(prev.content, msg.content);
19221
+ if (merged !== undefined) {
19222
+ prev.content = merged;
19223
+ return;
19224
+ }
19225
+ }
19226
+ out.push(msg);
19227
+ }
19228
+ function normalizeMessageSequence(messages) {
19229
+ const out = [];
19230
+ let openRound = null;
19231
+ let collected = [];
19232
+ const flushRound = (isFinal = false) => {
19233
+ if (!openRound)
19234
+ return;
19235
+ if (isFinal && collected.length === 0) {
19236
+ openRound = null;
19237
+ return;
19238
+ }
19239
+ out.push(...alignToolRound(openRound, collected));
19240
+ openRound = null;
19241
+ collected = [];
19115
19242
  };
19243
+ for (const msg of messages) {
19244
+ if (msg.role === "tool") {
19245
+ if (openRound) {
19246
+ collected.push(msg);
19247
+ continue;
19248
+ }
19249
+ log(`[OpenAIMessages] error \u2014 tool result ${msg.tool_call_id} answers no open tool round; ` + "re-emitted as a user message");
19250
+ const text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
19251
+ pushUserMessage(out, { role: "user", content: `[Tool Result]: ${text}` });
19252
+ continue;
19253
+ }
19254
+ flushRound();
19255
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0) {
19256
+ out.push(msg);
19257
+ openRound = msg;
19258
+ continue;
19259
+ }
19260
+ if (msg.role === "user")
19261
+ pushUserMessage(out, msg);
19262
+ else
19263
+ out.push(msg);
19264
+ }
19265
+ flushRound(true);
19266
+ return out;
19267
+ }
19268
+ function missingResultText(name) {
19269
+ const call = name ? `\`${name}\` call` : "call";
19270
+ return `[No tool result was provided for this ${call} \u2014 it was interrupted, cancelled, ` + "or dropped from the conversation history.]";
19271
+ }
19272
+ function alignToolRound(assistant, collected) {
19273
+ const calls = Array.isArray(assistant.tool_calls) ? assistant.tool_calls : [];
19274
+ const byId = new Map;
19275
+ for (const m of collected) {
19276
+ const list = byId.get(m.tool_call_id);
19277
+ if (list)
19278
+ list.push(m);
19279
+ else
19280
+ byId.set(m.tool_call_id, [m]);
19281
+ }
19282
+ const ordered = [];
19283
+ let synthesized = 0;
19284
+ for (const call of calls) {
19285
+ const matched = byId.get(call.id);
19286
+ if (matched) {
19287
+ ordered.push(...matched);
19288
+ byId.delete(call.id);
19289
+ continue;
19290
+ }
19291
+ synthesized++;
19292
+ log(`[OpenAIMessages] error \u2014 tool call ${call.id} (${call.function?.name || "?"}) has no ` + "result; a synthetic tool message names the omission");
19293
+ ordered.push({
19294
+ role: "tool",
19295
+ content: missingResultText(call.function?.name || ""),
19296
+ tool_call_id: call.id
19297
+ });
19298
+ }
19299
+ let dropped = 0;
19300
+ for (const [id, list] of byId) {
19301
+ dropped += list.length;
19302
+ log(`[OpenAIMessages] error \u2014 tool result ${id} matches no call in the round it follows; dropped`);
19303
+ }
19304
+ const reordered = ordered.some((m, i) => m !== collected[i]);
19305
+ if (reordered && !synthesized && !dropped) {
19306
+ log(`[OpenAIMessages] Reordered ${ordered.length} tool results to their tool_calls order`);
19307
+ }
19308
+ return ordered;
19309
+ }
19310
+ function imageBlockToUrlPart(block) {
19311
+ const source = block?.source;
19312
+ if (!source || typeof source !== "object") {
19313
+ log("[OpenAIMessages] Dropping image block: error \u2014 no source object");
19314
+ return null;
19315
+ }
19316
+ const url = typeof source.url === "string" ? source.url : "";
19317
+ const data = typeof source.data === "string" ? source.data : "";
19318
+ const kind = source.type || (url ? "url" : data ? "base64" : "");
19319
+ if (kind === "url") {
19320
+ if (!url) {
19321
+ log("[OpenAIMessages] Dropping image block: error \u2014 url source carries no url");
19322
+ return null;
19323
+ }
19324
+ return { type: "image_url", image_url: { url } };
19325
+ }
19326
+ if (kind === "base64") {
19327
+ if (!data) {
19328
+ log("[OpenAIMessages] Dropping image block: error \u2014 base64 source carries no data");
19329
+ return null;
19330
+ }
19331
+ const mediaType = typeof source.media_type === "string" ? source.media_type : "";
19332
+ if (!mediaType) {
19333
+ log("[OpenAIMessages] Dropping image block: error \u2014 base64 source carries no media_type");
19334
+ return null;
19335
+ }
19336
+ return { type: "image_url", image_url: { url: `data:${mediaType};base64,${data}` } };
19337
+ }
19338
+ log(`[OpenAIMessages] Dropping image block: error \u2014 unsupported source type ${kind || "(none)"}`);
19339
+ return null;
19116
19340
  }
19117
19341
  function processUserMessage(msg, messages, simpleFormat = false) {
19118
19342
  if (Array.isArray(msg.content)) {
@@ -19129,7 +19353,9 @@ function processUserMessage(msg, messages, simpleFormat = false) {
19129
19353
  }
19130
19354
  } else if (block.type === "image") {
19131
19355
  if (!simpleFormat) {
19132
- contentParts.push(imageBlockToUrlPart(block));
19356
+ const part = imageBlockToUrlPart(block);
19357
+ if (part)
19358
+ contentParts.push(part);
19133
19359
  }
19134
19360
  } else if (block.type === "tool_result") {
19135
19361
  if (seen.has(block.tool_use_id))
@@ -19141,12 +19367,21 @@ function processUserMessage(msg, messages, simpleFormat = false) {
19141
19367
  } else if (Array.isArray(block.content)) {
19142
19368
  const texts = [];
19143
19369
  const others = [];
19370
+ let forwardedImages = 0;
19371
+ let droppedImages = 0;
19144
19372
  for (const inner of block.content) {
19145
19373
  if (inner.type === "text") {
19146
19374
  texts.push(inner.text);
19147
19375
  } else if (inner.type === "image" && inner.source) {
19148
- if (!simpleFormat)
19149
- toolResultImages.push(imageBlockToUrlPart(inner));
19376
+ if (!simpleFormat) {
19377
+ const part = imageBlockToUrlPart(inner);
19378
+ if (part) {
19379
+ toolResultImages.push(part);
19380
+ forwardedImages++;
19381
+ } else {
19382
+ droppedImages++;
19383
+ }
19384
+ }
19150
19385
  } else {
19151
19386
  others.push(inner);
19152
19387
  }
@@ -19157,7 +19392,12 @@ function processUserMessage(msg, messages, simpleFormat = false) {
19157
19392
  resultText += (resultText ? `
19158
19393
  ` : "") + JSON.stringify(others);
19159
19394
  if (!resultText) {
19160
- resultText = toolResultImages.length ? "[image returned; see following message]" : "";
19395
+ if (forwardedImages)
19396
+ resultText = "[image returned; see following message]";
19397
+ else if (droppedImages)
19398
+ resultText = "[image returned, but its source could not be forwarded]";
19399
+ else
19400
+ resultText = "";
19161
19401
  }
19162
19402
  } else {
19163
19403
  resultText = JSON.stringify(block.content);
@@ -19243,6 +19483,9 @@ function processAssistantMessage(msg, messages, simpleFormat = false) {
19243
19483
  messages.push({ role: "assistant", content: msg.content });
19244
19484
  }
19245
19485
  }
19486
+ var init_openai_messages = __esm(() => {
19487
+ init_logger();
19488
+ });
19246
19489
 
19247
19490
  // src/transform.ts
19248
19491
  function removeUriFormat(schema) {
@@ -19452,6 +19695,45 @@ function summarizeToolParameters(schema) {
19452
19695
  }
19453
19696
  return summarized;
19454
19697
  }
19698
+ function mapToolChoiceToOpenAI(choice, encodeName) {
19699
+ if (!choice)
19700
+ return;
19701
+ const { type, name } = choice;
19702
+ if (type === "tool" && name) {
19703
+ return { type: "function", function: { name: encodeName ? encodeName(name) : name } };
19704
+ }
19705
+ if (type === "any")
19706
+ return "required";
19707
+ if (type === "auto" || type === "none")
19708
+ return type;
19709
+ return;
19710
+ }
19711
+ function mapToolChoiceToResponsesAPI(choice, encodeName) {
19712
+ const mapped = mapToolChoiceToOpenAI(choice, encodeName);
19713
+ if (mapped === undefined || typeof mapped === "string")
19714
+ return mapped;
19715
+ return { type: "function", name: mapped.function.name };
19716
+ }
19717
+ function mapToolChoiceToGemini(choice, encodeName) {
19718
+ if (!choice)
19719
+ return;
19720
+ const { type, name } = choice;
19721
+ if (type === "tool" && name) {
19722
+ return {
19723
+ functionCallingConfig: {
19724
+ mode: "ANY",
19725
+ allowedFunctionNames: [encodeName ? encodeName(name) : name]
19726
+ }
19727
+ };
19728
+ }
19729
+ if (type === "any")
19730
+ return { functionCallingConfig: { mode: "ANY" } };
19731
+ if (type === "auto")
19732
+ return { functionCallingConfig: { mode: "AUTO" } };
19733
+ if (type === "none")
19734
+ return { functionCallingConfig: { mode: "NONE" } };
19735
+ return;
19736
+ }
19455
19737
  var PORTABLE_ESCAPE_LETTERS, NAMED_SCHEMA_MAPS;
19456
19738
  var init_openai_tools = __esm(() => {
19457
19739
  init_logger();
@@ -19490,11 +19772,38 @@ function matchesModelFamily(modelId, family) {
19490
19772
  function isEffortLevel(value) {
19491
19773
  return typeof value === "string" && EFFORT_ORDER.includes(value);
19492
19774
  }
19775
+ function outputCeilingOf(originalRequest, payload) {
19776
+ const candidates = [
19777
+ originalRequest?.max_tokens,
19778
+ payload?.max_tokens,
19779
+ payload?.max_completion_tokens,
19780
+ payload?.max_output_tokens
19781
+ ];
19782
+ for (const value of candidates) {
19783
+ if (typeof value === "number" && Number.isFinite(value) && value > 0)
19784
+ return value;
19785
+ }
19786
+ return;
19787
+ }
19788
+ function clampThinkingBudget(budget, ceiling) {
19789
+ if (budget === undefined)
19790
+ return;
19791
+ if (ceiling === undefined)
19792
+ return budget;
19793
+ const allowed = ceiling - ANSWER_TOKEN_RESERVE;
19794
+ if (allowed < MIN_THINKING_BUDGET)
19795
+ return "no-room";
19796
+ return Math.min(budget, allowed);
19797
+ }
19493
19798
 
19494
19799
  class BaseAPIFormat {
19495
19800
  modelId;
19496
19801
  wireFormat;
19497
- toolNameMap = new Map;
19802
+ responseWireFormat;
19803
+ setResponseWireFormat(format) {
19804
+ this.responseWireFormat = format;
19805
+ }
19806
+ toolNameBindings = newToolNameBindings();
19498
19807
  constructor(modelId, wireFormat) {
19499
19808
  this.modelId = modelId;
19500
19809
  this.wireFormat = wireFormat;
@@ -19505,20 +19814,52 @@ class BaseAPIFormat {
19505
19814
  getWireFormat() {
19506
19815
  return this.wireFormat;
19507
19816
  }
19817
+ recoverFromRejection(payload, errorText) {
19818
+ return this.recoverFromSamplingParamRejection(payload, errorText);
19819
+ }
19820
+ recoverFromSamplingParamRejection(payload, errorText) {
19821
+ if (!payload)
19822
+ return null;
19823
+ const present = OPTIONAL_SAMPLING_PARAMS.filter((p) => payload[p] !== undefined);
19824
+ if (present.length === 0)
19825
+ return null;
19826
+ const rejected = rejectedOptionalParams(errorText, present);
19827
+ if (rejected.length === 0)
19828
+ return null;
19829
+ const next = { ...payload };
19830
+ for (const p of rejected)
19831
+ delete next[p];
19832
+ return { payload: next, note: `dropped ${rejected.join(", ")} for ${this.modelId}` };
19833
+ }
19834
+ applyOpenAISamplingParams(payload, claudeRequest) {
19835
+ if (!payload || !claudeRequest)
19836
+ return;
19837
+ const sequences = claudeRequest.stop_sequences;
19838
+ if (Array.isArray(sequences)) {
19839
+ const usable = sequences.filter((s) => typeof s === "string" && s.length > 0);
19840
+ if (usable.length > 0)
19841
+ payload.stop = usable;
19842
+ }
19843
+ if (claudeRequest.top_p !== undefined && claudeRequest.top_p !== null) {
19844
+ payload.top_p = claudeRequest.top_p;
19845
+ }
19846
+ }
19508
19847
  getToolNameLimit() {
19509
- return null;
19848
+ const wire = this.responseWireFormat ?? this.wireFormat ?? this.getStreamFormat();
19849
+ return wireDecodesToolNames(wire) ? OPENAI_TOOL_NAME_LIMIT : null;
19510
19850
  }
19511
19851
  getMaxToolCount() {
19512
19852
  return null;
19513
19853
  }
19514
19854
  getToolNameMap() {
19515
- return this.toolNameMap;
19855
+ return this.toolNameBindings.byEncoded;
19516
19856
  }
19517
19857
  restoreToolName(name) {
19518
- return this.toolNameMap.get(name) || name;
19858
+ return this.toolNameBindings.byEncoded.get(name) || name;
19519
19859
  }
19520
19860
  prepareRequest(request, originalRequest) {
19521
19861
  const prepared = this.prepareRequestCommon(request, originalRequest) ?? request;
19862
+ this.encodeToolNames(prepared);
19522
19863
  if (!this.isAnthropicWire()) {
19523
19864
  return this.applyNativeReasoning(prepared, originalRequest) ?? prepared;
19524
19865
  }
@@ -19543,6 +19884,11 @@ class BaseAPIFormat {
19543
19884
  }
19544
19885
  }
19545
19886
  applyAnthropicWireReasoning(request, originalRequest) {
19887
+ const status = this.lookupReasoningStatus();
19888
+ if (status !== "known") {
19889
+ log(`[${this.getName()}] ${this.modelId} reasoning control ${status === "unknown" ? "unknown" : "absent from the catalog"} -> no reasoning parameter emitted`);
19890
+ return request;
19891
+ }
19546
19892
  const reasoning = this.lookupReasoningCapability();
19547
19893
  if (reasoning?.supported === false) {
19548
19894
  request.thinking = { type: "disabled" };
@@ -19553,36 +19899,63 @@ class BaseAPIFormat {
19553
19899
  if (!effort)
19554
19900
  return request;
19555
19901
  if (effort === "none" || effort === "minimal") {
19902
+ if (reasoning?.mandatory) {
19903
+ return this.enableAnthropicEffort(request, effort, reasoning, "mandatory reasoning");
19904
+ }
19556
19905
  request.thinking = { type: "disabled" };
19557
19906
  log(`[${this.getName()}] effort ${effort} -> thinking.type: disabled for ${this.modelId}`);
19558
19907
  return request;
19559
19908
  }
19560
- if (reasoning && (reasoning.control === "budget" || reasoning.supportsBudgetTokens)) {
19561
- return this.enableAnthropicThinkingWithBudget(request, effort, "catalog: budget-controlled");
19909
+ const control = reasoning?.control;
19910
+ const advertisesEfforts = (reasoning?.efforts?.length ?? 0) > 0;
19911
+ if (control === "effort" || control === undefined && advertisesEfforts) {
19912
+ return this.enableAnthropicEffort(request, effort, reasoning, "catalog: effort-controlled");
19562
19913
  }
19563
- const advertised = reasoning?.efforts?.length ? reasoning : undefined;
19564
- if (advertised) {
19565
- const level = this.clampToAdvertisedEffort(effort, advertised);
19566
- request.thinking = { type: "enabled" };
19567
- if (level) {
19568
- request.output_config = { ...request.output_config ?? {}, effort: level };
19569
- }
19570
- log(`[${this.getName()}] effort ${effort} -> thinking: enabled, output_config.effort: ${level ?? "(none advertised)"} for ${this.modelId} (advertised: ${advertised.efforts?.join("/")})`);
19571
- return request;
19914
+ if (control === "budget" || control === undefined && reasoning?.supportsBudgetTokens) {
19915
+ return this.enableAnthropicBudget(request, effort, originalRequest, reasoning);
19572
19916
  }
19573
- if (reasoning) {
19574
- request.thinking = { type: "enabled" };
19575
- log(`[${this.getName()}] effort ${effort} -> thinking: enabled (no depth knob; catalog control=${reasoning.control ?? "unknown"}) for ${this.modelId}`);
19576
- return request;
19917
+ request.thinking = { type: "enabled" };
19918
+ this.stripAnthropicEffortField(request);
19919
+ log(`[${this.getName()}] effort ${effort} -> thinking: enabled (no depth knob; catalog control=${control ?? "unspecified"}) for ${this.modelId}`);
19920
+ return request;
19921
+ }
19922
+ enableAnthropicEffort(request, effort, reasoning, why) {
19923
+ const level = reasoning ? this.clampToAdvertisedEffort(effort, reasoning) : undefined;
19924
+ request.thinking = { type: "enabled" };
19925
+ if (level) {
19926
+ request.output_config = { ...request.output_config ?? {}, effort: level };
19577
19927
  }
19578
- return this.enableAnthropicThinkingWithBudget(request, effort, "no catalog entry");
19928
+ log(`[${this.getName()}] effort ${effort} -> thinking: enabled, output_config.effort: ${level ?? "(none advertised)"} for ${this.modelId} (${why}; advertised: ${reasoning?.efforts?.join("/") ?? "none"})`);
19929
+ return request;
19579
19930
  }
19580
- enableAnthropicThinkingWithBudget(request, effort, why) {
19581
- const budget = this.effortToThinkingTokenBudget(effort);
19931
+ enableAnthropicBudget(request, effort, originalRequest, reasoning) {
19932
+ const requested = this.effortToThinkingTokenBudget(effort);
19933
+ const ceiling = outputCeilingOf(originalRequest, request);
19934
+ const budget = clampThinkingBudget(requested, ceiling);
19935
+ if (budget === "no-room") {
19936
+ if (reasoning?.mandatory) {
19937
+ request.thinking = { type: "enabled" };
19938
+ } else {
19939
+ request.thinking = { type: "disabled" };
19940
+ }
19941
+ this.stripAnthropicEffortField(request);
19942
+ log(`[${this.getName()}] effort ${effort} -> budget ${requested ?? "(model max)"} does not fit under max_tokens ${ceiling}; sent thinking.type: ${request.thinking.type} for ${this.modelId}`);
19943
+ return request;
19944
+ }
19582
19945
  request.thinking = budget === undefined ? { type: "enabled" } : { type: "enabled", budget_tokens: budget };
19583
- log(`[${this.getName()}] effort ${effort} -> thinking: enabled, budget_tokens: ${budget ?? "(model max)"} for ${this.modelId} (${why})`);
19946
+ this.stripAnthropicEffortField(request);
19947
+ log(`[${this.getName()}] effort ${effort} -> thinking: enabled, budget_tokens: ${budget ?? "(model max)"}${budget !== undefined && budget !== requested ? ` (clamped from ${requested} under max_tokens ${ceiling})` : ""} for ${this.modelId} (catalog: budget-controlled)`);
19584
19948
  return request;
19585
19949
  }
19950
+ stripAnthropicEffortField(request) {
19951
+ if (!request?.output_config || typeof request.output_config !== "object")
19952
+ return;
19953
+ if (request.output_config.effort === undefined)
19954
+ return;
19955
+ delete request.output_config.effort;
19956
+ if (Object.keys(request.output_config).length === 0)
19957
+ delete request.output_config;
19958
+ }
19586
19959
  clampToAdvertisedEffort(requested, reasoning) {
19587
19960
  if (this.pinnedEffort)
19588
19961
  return this.pinnedEffort;
@@ -19611,6 +19984,13 @@ class BaseAPIFormat {
19611
19984
  return;
19612
19985
  }
19613
19986
  }
19987
+ lookupReasoningStatus() {
19988
+ try {
19989
+ return lookupModelReasoningStatus(this.modelId);
19990
+ } catch {
19991
+ return;
19992
+ }
19993
+ }
19614
19994
  effortToThinkingTokenBudget(effort) {
19615
19995
  switch (effort) {
19616
19996
  case "low":
@@ -19656,7 +20036,7 @@ class BaseAPIFormat {
19656
20036
  return;
19657
20037
  }
19658
20038
  reset() {
19659
- this.toolNameMap.clear();
20039
+ this.toolNameBindings = newToolNameBindings();
19660
20040
  }
19661
20041
  convertMessages(claudeRequest, filterIdentityFn) {
19662
20042
  return convertMessagesToOpenAI(claudeRequest, this.modelId, filterIdentityFn);
@@ -19672,6 +20052,10 @@ class BaseAPIFormat {
19672
20052
  };
19673
20053
  if (tools.length > 0) {
19674
20054
  payload.tools = tools;
20055
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
20056
+ if (toolChoice !== undefined) {
20057
+ payload.tool_choice = toolChoice;
20058
+ }
19675
20059
  }
19676
20060
  if (claudeRequest.max_tokens) {
19677
20061
  payload.max_tokens = claudeRequest.max_tokens;
@@ -19679,6 +20063,7 @@ class BaseAPIFormat {
19679
20063
  if (claudeRequest.temperature !== undefined) {
19680
20064
  payload.temperature = claudeRequest.temperature;
19681
20065
  }
20066
+ this.applyOpenAISamplingParams(payload, claudeRequest);
19682
20067
  return payload;
19683
20068
  }
19684
20069
  getStreamFormat() {
@@ -19696,50 +20081,56 @@ class BaseAPIFormat {
19696
20081
  shouldFilterThinking() {
19697
20082
  return this.isAnthropicWire();
19698
20083
  }
19699
- truncateToolNames(request) {
20084
+ encodeToolNames(request) {
19700
20085
  const limit = this.getToolNameLimit();
19701
- if (!limit || !request.tools)
20086
+ if (!limit || !request)
19702
20087
  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;
20088
+ const encode = (name) => encodeToolName(name, limit, this.toolNameBindings);
20089
+ if (Array.isArray(request.tools)) {
20090
+ for (const tool of request.tools) {
20091
+ if (tool?.function?.name) {
20092
+ tool.function.name = encode(tool.function.name);
20093
+ } else if (tool?.name) {
20094
+ tool.name = encode(tool.name);
19712
20095
  }
19713
20096
  }
19714
20097
  }
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)) {
20098
+ if (Array.isArray(request.messages)) {
20099
+ for (const msg of request.messages) {
20100
+ if (msg?.role !== "assistant" || !Array.isArray(msg.tool_calls))
20101
+ continue;
19722
20102
  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
- }
20103
+ if (tc?.function?.name)
20104
+ tc.function.name = encode(tc.function.name);
19731
20105
  }
19732
20106
  }
19733
20107
  }
20108
+ if (Array.isArray(request.input)) {
20109
+ for (const item of request.input) {
20110
+ if (item?.type === "function_call" && item.name)
20111
+ item.name = encode(item.name);
20112
+ }
20113
+ }
20114
+ const choice = request.tool_choice;
20115
+ if (choice && typeof choice === "object") {
20116
+ if (choice.function?.name) {
20117
+ choice.function.name = encode(choice.function.name);
20118
+ } else if (choice.name) {
20119
+ choice.name = encode(choice.name);
20120
+ }
20121
+ }
19734
20122
  }
19735
20123
  }
19736
- var EFFORT_ORDER, EFFORT_LEVELS, NON_ANTHROPIC_REASONING_FIELDS, DefaultAPIFormat;
20124
+ var OPTIONAL_SAMPLING_PARAMS, OPENAI_TOOL_NAME_LIMIT = 64, EFFORT_ORDER, EFFORT_LEVELS, NON_ANTHROPIC_REASONING_FIELDS, MIN_THINKING_BUDGET = 1024, ANSWER_TOKEN_RESERVE = 1024, DefaultAPIFormat;
19737
20125
  var init_base_api_format = __esm(() => {
19738
20126
  init_remote_provider_types();
19739
20127
  init_logger();
19740
20128
  init_model_catalog();
20129
+ init_optional_param_rejection();
19741
20130
  init_tool_name_utils();
20131
+ init_openai_messages();
19742
20132
  init_openai_tools();
20133
+ OPTIONAL_SAMPLING_PARAMS = ["stop", "top_p"];
19743
20134
  EFFORT_ORDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
19744
20135
  EFFORT_LEVELS = EFFORT_ORDER;
19745
20136
  NON_ANTHROPIC_REASONING_FIELDS = [
@@ -19922,6 +20313,7 @@ function normalizeCodexModel(modelId) {
19922
20313
  }
19923
20314
  var CodexAPIFormat;
19924
20315
  var init_codex_api_format = __esm(() => {
20316
+ init_openai_tools();
19925
20317
  init_logger();
19926
20318
  init_base_api_format();
19927
20319
  init_reasoning_cache();
@@ -19987,6 +20379,10 @@ var init_codex_api_format = __esm(() => {
19987
20379
  }
19988
20380
  return tool;
19989
20381
  });
20382
+ const toolChoice = mapToolChoiceToResponsesAPI(claudeRequest.tool_choice);
20383
+ if (toolChoice !== undefined) {
20384
+ payload.tool_choice = toolChoice;
20385
+ }
19990
20386
  }
19991
20387
  return payload;
19992
20388
  }
@@ -20568,31 +20964,308 @@ function filterIdentity(content) {
20568
20964
  `);
20569
20965
  }
20570
20966
 
20967
+ // src/handlers/shared/schema-validate.ts
20968
+ function missingRequired(schema, args) {
20969
+ const required = schema?.required;
20970
+ if (!Array.isArray(required) || required.length === 0)
20971
+ return [];
20972
+ const supplied = args ?? {};
20973
+ return required.filter((key) => {
20974
+ if (typeof key !== "string")
20975
+ return false;
20976
+ if (!Object.hasOwn(supplied, key))
20977
+ return true;
20978
+ const value = supplied[key];
20979
+ return value === undefined || value === null;
20980
+ });
20981
+ }
20982
+ function isAbsent(args, key) {
20983
+ if (!Object.hasOwn(args, key))
20984
+ return true;
20985
+ const value = args[key];
20986
+ return value === undefined || value === null;
20987
+ }
20988
+ function declaredTypes(node) {
20989
+ if (!node)
20990
+ return [];
20991
+ const t = node.type;
20992
+ if (typeof t === "string")
20993
+ return [t];
20994
+ if (Array.isArray(t))
20995
+ return t.filter((x) => typeof x === "string");
20996
+ if (Array.isArray(node.enum) && node.enum.length > 0) {
20997
+ const kinds = new Set;
20998
+ for (const member of node.enum) {
20999
+ if (member === null || member === undefined)
21000
+ continue;
21001
+ if (Array.isArray(member))
21002
+ kinds.add("array");
21003
+ else if (typeof member === "object")
21004
+ kinds.add("object");
21005
+ else if (typeof member === "number")
21006
+ kinds.add(Number.isInteger(member) ? "integer" : "number");
21007
+ else if (typeof member === "boolean")
21008
+ kinds.add("boolean");
21009
+ else if (typeof member === "string")
21010
+ kinds.add("string");
21011
+ }
21012
+ return [...kinds];
21013
+ }
21014
+ return [];
21015
+ }
21016
+ function coerceOne(value, type, node) {
21017
+ switch (type) {
21018
+ case "string":
21019
+ return typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" ? String(value) : undefined;
21020
+ case "number":
21021
+ case "integer": {
21022
+ if (typeof value === "number") {
21023
+ return type === "integer" && !Number.isInteger(value) ? undefined : value;
21024
+ }
21025
+ if (typeof value !== "string" || !FULL_NUMBER.test(value.trim()))
21026
+ return;
21027
+ const n = Number(value.trim());
21028
+ if (!Number.isFinite(n))
21029
+ return;
21030
+ return type === "integer" && !Number.isInteger(n) ? undefined : n;
21031
+ }
21032
+ case "boolean": {
21033
+ if (typeof value === "boolean")
21034
+ return value;
21035
+ if (typeof value !== "string")
21036
+ return;
21037
+ const s = value.trim().toLowerCase();
21038
+ return s === "true" ? true : s === "false" ? false : undefined;
21039
+ }
21040
+ case "array": {
21041
+ const arr = Array.isArray(value) ? value : parseJsonOfKind(value, "array");
21042
+ if (!Array.isArray(arr))
21043
+ return;
21044
+ const items = node?.items;
21045
+ if (!items)
21046
+ return arr;
21047
+ return arr.map((el) => {
21048
+ const target = declaredTypes(items);
21049
+ for (const t of target) {
21050
+ const c = coerceOne(el, t, items);
21051
+ if (c !== undefined)
21052
+ return c;
21053
+ }
21054
+ return el;
21055
+ });
21056
+ }
21057
+ case "object":
21058
+ return value && typeof value === "object" && !Array.isArray(value) ? value : parseJsonOfKind(value, "object");
21059
+ default:
21060
+ return;
21061
+ }
21062
+ }
21063
+ function parseJsonOfKind(value, kind) {
21064
+ if (typeof value !== "string")
21065
+ return;
21066
+ try {
21067
+ const parsed = JSON.parse(value);
21068
+ if (kind === "array")
21069
+ return Array.isArray(parsed) ? parsed : undefined;
21070
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
21071
+ } catch {
21072
+ return;
21073
+ }
21074
+ }
21075
+ function coerceToSchema(schema, args) {
21076
+ const properties = schema?.properties;
21077
+ if (!properties)
21078
+ return { args, coerced: [] };
21079
+ const out = { ...args };
21080
+ const coerced = [];
21081
+ for (const [key, value] of Object.entries(args)) {
21082
+ const node = properties[key];
21083
+ if (!node || value === undefined || value === null)
21084
+ continue;
21085
+ const types = declaredTypes(node);
21086
+ if (types.length === 0)
21087
+ continue;
21088
+ if (types.some((t) => matchesType(value, t)))
21089
+ continue;
21090
+ for (const t of types) {
21091
+ const c = coerceOne(value, t, node);
21092
+ if (c !== undefined) {
21093
+ out[key] = c;
21094
+ coerced.push(key);
21095
+ break;
21096
+ }
21097
+ }
21098
+ }
21099
+ return { args: coerced.length > 0 ? out : args, coerced };
21100
+ }
21101
+ function matchesType(value, type) {
21102
+ switch (type) {
21103
+ case "string":
21104
+ return typeof value === "string";
21105
+ case "number":
21106
+ return typeof value === "number";
21107
+ case "integer":
21108
+ return typeof value === "number" && Number.isInteger(value);
21109
+ case "boolean":
21110
+ return typeof value === "boolean";
21111
+ case "array":
21112
+ return Array.isArray(value);
21113
+ case "object":
21114
+ return !!value && typeof value === "object" && !Array.isArray(value);
21115
+ case "null":
21116
+ return value === null;
21117
+ default:
21118
+ return false;
21119
+ }
21120
+ }
21121
+ function applySchemaDefaults(schema, args) {
21122
+ const properties = schema?.properties;
21123
+ const required = schema?.required;
21124
+ if (!properties || !Array.isArray(required))
21125
+ return { args, applied: [] };
21126
+ const out = { ...args };
21127
+ const applied = [];
21128
+ for (const key of required) {
21129
+ if (typeof key !== "string" || !isAbsent(out, key))
21130
+ continue;
21131
+ const node = properties[key];
21132
+ if (!node || !Object.hasOwn(node, "default") || node.default === undefined)
21133
+ continue;
21134
+ out[key] = node.default;
21135
+ applied.push(key);
21136
+ }
21137
+ return { args: applied.length > 0 ? out : args, applied };
21138
+ }
21139
+ function renameToDeclaredKeys(schema, args) {
21140
+ const properties = schema?.properties;
21141
+ const required = schema?.required;
21142
+ if (!properties || !Array.isArray(required))
21143
+ return { args, renamed: [] };
21144
+ const out = { ...args };
21145
+ const renamed = [];
21146
+ for (const target of required) {
21147
+ if (typeof target !== "string")
21148
+ continue;
21149
+ if (!properties[target] || !isAbsent(out, target))
21150
+ continue;
21151
+ for (const source of KEY_SYNONYMS[target] ?? []) {
21152
+ if (properties[source])
21153
+ continue;
21154
+ if (isAbsent(out, source))
21155
+ continue;
21156
+ out[target] = out[source];
21157
+ delete out[source];
21158
+ renamed.push(`${source}\u2192${target}`);
21159
+ break;
21160
+ }
21161
+ }
21162
+ return { args: renamed.length > 0 ? out : args, renamed };
21163
+ }
21164
+ var FULL_NUMBER, KEY_SYNONYMS;
21165
+ var init_schema_validate = __esm(() => {
21166
+ FULL_NUMBER = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
21167
+ KEY_SYNONYMS = {
21168
+ command: ["cmd", "shell", "script"],
21169
+ file_path: ["path", "file", "filename"],
21170
+ content: ["text", "data", "body"],
21171
+ pattern: ["query", "search", "regex", "glob"],
21172
+ query: ["search", "keyword"],
21173
+ prompt: ["query", "task"]
21174
+ };
21175
+ });
21176
+
20571
21177
  // src/handlers/shared/tool-call-recovery.ts
20572
21178
  function hasExtractableFunctionTag(text) {
20573
21179
  return FUNCTION_TAG_PRESENT.test(text);
20574
21180
  }
20575
- function keepOnlyRealTools(extracted, knownToolNames) {
21181
+ function keepOnlyRealTools(extracted, knownToolNames, decodeToolName) {
20576
21182
  const kept = [];
20577
21183
  for (const call of extracted) {
20578
21184
  if (!TOOL_NAME_SHAPE.test(call.name)) {
20579
21185
  log(`[ToolRecovery] Dropped extracted call: name is not an identifier: ${JSON.stringify(call.name.slice(0, 120))}`);
20580
21186
  continue;
20581
21187
  }
21188
+ const decoded = decodeToolName ? decodeToolName(call.name) : call.name;
21189
+ const named = decoded === call.name ? call : { ...call, name: decoded };
20582
21190
  if (!knownToolNames || knownToolNames.length === 0) {
20583
- kept.push(call);
21191
+ kept.push(named);
20584
21192
  continue;
20585
21193
  }
20586
- const canonical = knownToolNames.find((t) => t.toLowerCase() === call.name.toLowerCase());
21194
+ const canonical = knownToolNames.find((t) => t.toLowerCase() === named.name.toLowerCase());
20587
21195
  if (!canonical) {
20588
- log(`[ToolRecovery] Dropped extracted call for unadvertised tool: ${call.name}`);
21196
+ log(`[ToolRecovery] Dropped extracted call for unadvertised tool: ${named.name}`);
20589
21197
  continue;
20590
21198
  }
20591
- kept.push(canonical === call.name ? call : { ...call, name: canonical });
21199
+ kept.push(canonical === named.name ? named : { ...named, name: canonical });
20592
21200
  }
20593
21201
  return kept;
20594
21202
  }
20595
- function extractToolCallsFromText(text, knownToolNames) {
21203
+ function parseFunctionTagEnvelope(text) {
21204
+ const body = text.trim();
21205
+ if (body.length === 0)
21206
+ return null;
21207
+ if (!body.startsWith("<function="))
21208
+ return null;
21209
+ const calls = [];
21210
+ let cursor = 0;
21211
+ while (cursor < body.length) {
21212
+ FUNCTION_TAG_AT_CURSOR.lastIndex = cursor;
21213
+ const open = FUNCTION_TAG_AT_CURSOR.exec(body);
21214
+ if (!open)
21215
+ return null;
21216
+ cursor = FUNCTION_TAG_AT_CURSOR.lastIndex;
21217
+ const args = {};
21218
+ while (cursor < body.length) {
21219
+ const rest = body.slice(cursor);
21220
+ const closeLen = /^\s*<\/function>/.exec(rest)?.[0].length;
21221
+ if (closeLen !== undefined) {
21222
+ cursor += closeLen;
21223
+ break;
21224
+ }
21225
+ if (/^\s*<function=/.test(rest))
21226
+ break;
21227
+ const lead = /^\s*/.exec(rest)?.[0].length ?? 0;
21228
+ PARAMETER_TAG_AT_CURSOR.lastIndex = cursor + lead;
21229
+ const param = PARAMETER_TAG_AT_CURSOR.exec(body);
21230
+ if (!param)
21231
+ return null;
21232
+ let valueStart = PARAMETER_TAG_AT_CURSOR.lastIndex;
21233
+ if (body[valueStart] === `
21234
+ `)
21235
+ valueStart += 1;
21236
+ const tail = body.slice(valueStart);
21237
+ const end = /<\/parameter>|<\/function>|<parameter=|<function=/.exec(tail);
21238
+ const raw = end ? tail.slice(0, end.index) : tail;
21239
+ args[param[1]] = raw.trim();
21240
+ cursor = valueStart + raw.length;
21241
+ const consumedClose = /^<\/parameter>/.exec(body.slice(cursor))?.[0].length;
21242
+ if (consumedClose !== undefined)
21243
+ cursor += consumedClose;
21244
+ }
21245
+ calls.push({ name: open[1], arguments: args, source: "xml_text" });
21246
+ const gap = /^\s*/.exec(body.slice(cursor))?.[0].length ?? 0;
21247
+ cursor += gap;
21248
+ }
21249
+ return calls.length > 0 ? calls : null;
21250
+ }
21251
+ function normalizeAgainstSchema(call, toolSchemas) {
21252
+ const schema = toolSchemas?.find((t) => t.name === call.name)?.input_schema;
21253
+ if (!schema)
21254
+ return call;
21255
+ const renamed = renameToDeclaredKeys(schema, call.arguments);
21256
+ const defaulted = applySchemaDefaults(schema, renamed.args);
21257
+ const coerced = coerceToSchema(schema, defaulted.args);
21258
+ if (renamed.renamed.length + defaulted.applied.length + coerced.coerced.length === 0) {
21259
+ return call;
21260
+ }
21261
+ log(`[ToolRecovery] Normalized ${call.name} against its schema: ` + `renamed=[${renamed.renamed}] defaults=[${defaulted.applied}] coerced=[${coerced.coerced}]`);
21262
+ return { ...call, arguments: coerced.args };
21263
+ }
21264
+ function extractToolCallsFromText(text, knownToolNames, toolSchemas, decodeToolName) {
21265
+ const envelope = parseFunctionTagEnvelope(text);
21266
+ if (envelope) {
21267
+ return keepOnlyRealTools(envelope, knownToolNames, decodeToolName).map((call) => normalizeAgainstSchema(call, toolSchemas));
21268
+ }
20596
21269
  const extracted = [];
20597
21270
  const qwenPattern = new RegExp(FUNCTION_TAG_SOURCE, "gi");
20598
21271
  let match;
@@ -20719,73 +21392,6 @@ function extractToolCallsFromText(text, knownToolNames) {
20719
21392
  while ((kvMatch = kvPattern.exec(paramText)) !== null) {
20720
21393
  args[kvMatch[1]] = kvMatch[2];
20721
21394
  }
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
21395
  if (Object.keys(args).length > 0) {
20790
21396
  extracted.push({
20791
21397
  name: normalizedToolName,
@@ -20796,139 +21402,7 @@ function extractToolCallsFromText(text, knownToolNames) {
20796
21402
  }
20797
21403
  }
20798
21404
  }
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;
21405
+ return keepOnlyRealTools(extracted, knownToolNames, decodeToolName);
20932
21406
  }
20933
21407
  function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent) {
20934
21408
  const schema = toolSchemas.find((t) => t.name === toolName);
@@ -20940,7 +21414,7 @@ function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent)
20940
21414
  parsedArgs = argsStr ? JSON.parse(argsStr) : {};
20941
21415
  } catch (e) {
20942
21416
  if (textContent) {
20943
- const extracted = extractToolCallsFromText(textContent);
21417
+ const extracted = extractToolCallsFromText(textContent, toolSchemas.map((t) => t.name), toolSchemas);
20944
21418
  const matching = extracted.find((tc) => tc.name === toolName);
20945
21419
  if (matching) {
20946
21420
  parsedArgs = matching.arguments;
@@ -20948,25 +21422,33 @@ function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent)
20948
21422
  }
20949
21423
  }
20950
21424
  }
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: [] };
21425
+ const input = schema.input_schema;
21426
+ const renamed = renameToDeclaredKeys(input, parsedArgs);
21427
+ const defaulted = applySchemaDefaults(input, renamed.args);
21428
+ const coerced = coerceToSchema(input, defaulted.args);
21429
+ const args = coerced.args;
21430
+ const repaired = renamed.renamed.length > 0 || defaulted.applied.length > 0;
21431
+ if (repaired) {
21432
+ log(`[ToolRecovery] ${toolName}: applied schema defaults [${defaulted.applied}] ` + `and key renames [${renamed.renamed}] \u2014 nothing was invented`);
20955
21433
  }
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: [] };
21434
+ if (coerced.coerced.length > 0) {
21435
+ log(`[ToolRecovery] ${toolName}: coerced to declared types [${coerced.coerced}]`);
21436
+ }
21437
+ const missingParams = missingRequired(input, args);
21438
+ if (missingParams.length === 0) {
21439
+ return { valid: true, args, repaired, missingParams: [] };
20961
21440
  }
20962
- return { valid: false, args: repairedArgs, repaired: false, missingParams: stillMissing };
21441
+ return { valid: false, args, repaired: false, missingParams };
20963
21442
  }
20964
- var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT;
21443
+ var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT, FUNCTION_TAG_AT_CURSOR, PARAMETER_TAG_AT_CURSOR;
20965
21444
  var init_tool_call_recovery = __esm(() => {
20966
21445
  init_tool_name_utils();
20967
21446
  init_logger();
21447
+ init_schema_validate();
20968
21448
  FUNCTION_TAG_SOURCE = `<function=(${TOOL_NAME_SOURCE})>([\\s\\S]*?)(?=<function=|$)`;
20969
21449
  FUNCTION_TAG_PRESENT = new RegExp(`<function=${TOOL_NAME_SOURCE}>`);
21450
+ FUNCTION_TAG_AT_CURSOR = new RegExp(`<function=(${TOOL_NAME_SOURCE})>`, "y");
21451
+ PARAMETER_TAG_AT_CURSOR = /<parameter=([^>\s]+)>/y;
20970
21452
  });
20971
21453
 
20972
21454
  // src/handlers/shared/web-search-detector.ts
@@ -20983,11 +21465,218 @@ var init_web_search_detector = __esm(() => {
20983
21465
  WEB_SEARCH_NAMES = new Set(["web_search", "brave_web_search", "tavily_search"]);
20984
21466
  });
20985
21467
 
21468
+ // src/handlers/shared/stream-parsers/block-writer.ts
21469
+ function createBlockWriter(send) {
21470
+ let nextIndex = 0;
21471
+ let openRef = null;
21472
+ let anyBlockEmitted = false;
21473
+ const emittedToolRefs = [];
21474
+ const stopped = new Set;
21475
+ const describe = (ref) => ref ? `${ref.kind}@${ref.index}` : "none";
21476
+ const spendableIndex = (requested, kind) => {
21477
+ if (requested === undefined)
21478
+ return nextIndex++;
21479
+ if (!stopped.has(requested))
21480
+ return requested;
21481
+ const replacement = nextIndex++;
21482
+ log(`[BlockWriter] open error: index ${requested} was already stopped; ${kind} block re-indexed to ${replacement}`);
21483
+ return replacement;
21484
+ };
21485
+ const closeCurrent = () => {
21486
+ if (!openRef)
21487
+ return;
21488
+ send("content_block_stop", { type: "content_block_stop", index: openRef.index });
21489
+ stopped.add(openRef.index);
21490
+ openRef = null;
21491
+ };
21492
+ const start = (ref, contentBlock) => {
21493
+ send("content_block_start", {
21494
+ type: "content_block_start",
21495
+ index: ref.index,
21496
+ content_block: contentBlock
21497
+ });
21498
+ openRef = ref;
21499
+ anyBlockEmitted = true;
21500
+ return ref;
21501
+ };
21502
+ const writer = {
21503
+ openText(opts) {
21504
+ if (openRef?.kind === "text") {
21505
+ if (opts?.index !== undefined && opts.index !== openRef.index) {
21506
+ log(`[BlockWriter] reusing open text block @${openRef.index}; reserved index ${opts.index} goes unused`);
21507
+ }
21508
+ return openRef;
21509
+ }
21510
+ closeCurrent();
21511
+ const index = spendableIndex(opts?.index, "text");
21512
+ return start({ index, kind: "text" }, { type: "text", text: "" });
21513
+ },
21514
+ openThinking() {
21515
+ if (openRef?.kind === "thinking")
21516
+ return openRef;
21517
+ closeCurrent();
21518
+ return start({ index: nextIndex++, kind: "thinking" }, { type: "thinking", thinking: "" });
21519
+ },
21520
+ openTool({ id, name, index }) {
21521
+ closeCurrent();
21522
+ const ref = {
21523
+ index: spendableIndex(index, "tool_use"),
21524
+ kind: "tool_use",
21525
+ toolId: id
21526
+ };
21527
+ start(ref, { type: "tool_use", id, name, input: {} });
21528
+ emittedToolRefs.push(ref);
21529
+ return ref;
21530
+ },
21531
+ append(ref, payload) {
21532
+ if (!openRef || openRef.index !== ref.index || openRef.kind !== ref.kind) {
21533
+ log(`[BlockWriter] append error: ${describe(ref)} is not the open block (open=${describe(openRef)}); ${payload.length} chars withheld for the caller to handle`);
21534
+ return false;
21535
+ }
21536
+ 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 };
21537
+ send("content_block_delta", { type: "content_block_delta", index: ref.index, delta });
21538
+ return true;
21539
+ },
21540
+ close(ref) {
21541
+ if (openRef && openRef.index === ref.index && openRef.kind === ref.kind) {
21542
+ closeCurrent();
21543
+ return;
21544
+ }
21545
+ if (stopped.has(ref.index))
21546
+ return;
21547
+ log(`[BlockWriter] close error: ${describe(ref)} was never opened and is not stopped (open=${describe(openRef)})`);
21548
+ },
21549
+ closeCurrent,
21550
+ reserve() {
21551
+ return nextIndex++;
21552
+ },
21553
+ get openRef() {
21554
+ return openRef;
21555
+ },
21556
+ get anyBlockEmitted() {
21557
+ return anyBlockEmitted;
21558
+ },
21559
+ get emittedToolRefs() {
21560
+ return emittedToolRefs;
21561
+ }
21562
+ };
21563
+ return writer;
21564
+ }
21565
+ var init_block_writer = __esm(() => {
21566
+ init_logger();
21567
+ });
21568
+
20986
21569
  // src/handlers/shared/stream-parsers/message-start-usage.ts
20987
21570
  function messageStartUsage(priorInputTokens) {
20988
21571
  return {
20989
21572
  input_tokens: priorInputTokens && priorInputTokens > 0 ? priorInputTokens : 100,
20990
- output_tokens: 1
21573
+ output_tokens: 1,
21574
+ cache_read_input_tokens: 0,
21575
+ cache_creation_input_tokens: 0
21576
+ };
21577
+ }
21578
+
21579
+ // src/handlers/shared/stream-parsers/think-tag-splitter.ts
21580
+ function createThinkTagSplitter() {
21581
+ let phase = "deciding";
21582
+ let pending = "";
21583
+ let openArmed = true;
21584
+ const heldSuffixLength = (s) => {
21585
+ for (let n = Math.min(CLOSE.length - 1, s.length);n > 0; n--) {
21586
+ if (CLOSE.startsWith(s.slice(s.length - n)))
21587
+ return n;
21588
+ }
21589
+ return 0;
21590
+ };
21591
+ const consumeThinking = (chunk) => {
21592
+ pending += chunk;
21593
+ const at = pending.indexOf(CLOSE);
21594
+ if (at >= 0) {
21595
+ const thinking = pending.slice(0, at);
21596
+ const text = pending.slice(at + CLOSE.length);
21597
+ pending = "";
21598
+ phase = "passthrough";
21599
+ return { thinking, text };
21600
+ }
21601
+ const hold = heldSuffixLength(pending);
21602
+ const thinking = pending.slice(0, pending.length - hold);
21603
+ pending = hold === 0 ? "" : pending.slice(pending.length - hold);
21604
+ return { thinking, text: "" };
21605
+ };
21606
+ const decide = (isFlush) => {
21607
+ const lead = pending.replace(/^\s+/, "");
21608
+ const releaseAsText = () => {
21609
+ phase = "passthrough";
21610
+ const text = pending;
21611
+ pending = "";
21612
+ return { thinking: "", text };
21613
+ };
21614
+ if (lead === "")
21615
+ return isFlush ? releaseAsText() : EMPTY;
21616
+ if (openArmed && lead.startsWith(OPEN)) {
21617
+ phase = "thinking";
21618
+ pending = "";
21619
+ return consumeThinking(lead.slice(OPEN.length));
21620
+ }
21621
+ if (lead.startsWith(CLOSE)) {
21622
+ phase = "passthrough";
21623
+ pending = "";
21624
+ return { thinking: "", text: lead.slice(CLOSE.length) };
21625
+ }
21626
+ if (openArmed && OPEN.startsWith(lead) || CLOSE.startsWith(lead)) {
21627
+ return isFlush ? releaseAsText() : EMPTY;
21628
+ }
21629
+ return releaseAsText();
21630
+ };
21631
+ return {
21632
+ push(chunk) {
21633
+ if (!chunk)
21634
+ return EMPTY;
21635
+ if (phase === "passthrough")
21636
+ return { thinking: "", text: chunk };
21637
+ if (phase === "thinking")
21638
+ return consumeThinking(chunk);
21639
+ pending += chunk;
21640
+ return decide(false);
21641
+ },
21642
+ flush() {
21643
+ if (phase === "passthrough")
21644
+ return EMPTY;
21645
+ if (phase === "thinking") {
21646
+ const thinking = pending;
21647
+ pending = "";
21648
+ return { thinking, text: "" };
21649
+ }
21650
+ return decide(true);
21651
+ },
21652
+ disarmOpen() {
21653
+ openArmed = false;
21654
+ },
21655
+ get inThinking() {
21656
+ return phase === "thinking";
21657
+ }
21658
+ };
21659
+ }
21660
+ var OPEN = "<think>", CLOSE = "</think>", EMPTY;
21661
+ var init_think_tag_splitter = __esm(() => {
21662
+ EMPTY = { thinking: "", text: "" };
21663
+ });
21664
+
21665
+ // src/handlers/shared/stream-parsers/usage-cache-split.ts
21666
+ function nonNegativeInt(value) {
21667
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0;
21668
+ }
21669
+ function splitPromptTokens(usage) {
21670
+ const u = usage ?? {};
21671
+ const promptTokens = nonNegativeInt(u.prompt_tokens);
21672
+ const details = u.prompt_tokens_details ?? {};
21673
+ const cacheReadTokens = Math.min(nonNegativeInt(details.cached_tokens), promptTokens);
21674
+ const cacheCreationTokens = Math.min(nonNegativeInt(details.cache_write_tokens), promptTokens - cacheReadTokens);
21675
+ return {
21676
+ promptTokens,
21677
+ inputTokens: promptTokens - cacheReadTokens - cacheCreationTokens,
21678
+ cacheReadTokens,
21679
+ cacheCreationTokens
20991
21680
  };
20992
21681
  }
20993
21682
 
@@ -20997,6 +21686,31 @@ function formatRawSseLogPayload(dataStr) {
20997
21686
  return dataStr;
20998
21687
  return `${dataStr.substring(0, SSE_LOG_MAX_CHARS)} ${SSE_LOG_TRUNCATION_MARKER} original_chars=${dataStr.length}`;
20999
21688
  }
21689
+ function describeInStreamError(chunk) {
21690
+ if (!chunk || typeof chunk !== "object")
21691
+ return;
21692
+ const error = chunk.error;
21693
+ if (!error)
21694
+ return;
21695
+ if (typeof error === "string")
21696
+ return error;
21697
+ if (typeof error !== "object")
21698
+ return String(error);
21699
+ const e = error;
21700
+ const metadata = e.metadata ?? {};
21701
+ const parts = [];
21702
+ const provider = chunk.provider;
21703
+ if (typeof provider === "string" && provider)
21704
+ parts.push(`[${provider}]`);
21705
+ const code = e.code ?? metadata.provider_code;
21706
+ const type = e.type ?? metadata.error_type;
21707
+ const label = [code, type].filter((v) => v !== undefined && v !== null && v !== "").join(" ");
21708
+ if (label)
21709
+ parts.push(label);
21710
+ const message = e.message ?? metadata.raw;
21711
+ parts.push(typeof message === "string" && message.trim() ? message : JSON.stringify(error).slice(0, 500));
21712
+ return parts.join(" ");
21713
+ }
21000
21714
  function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
21001
21715
  const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
21002
21716
  if (result.repaired) {
@@ -21014,12 +21728,9 @@ function createStreamingState() {
21014
21728
  return {
21015
21729
  usage: null,
21016
21730
  finalized: false,
21017
- textStarted: false,
21018
- textIdx: -1,
21019
- reasoningStarted: false,
21020
- reasoningIdx: -1,
21021
- curIdx: 0,
21022
21731
  tools: new Map,
21732
+ pendingToolArgs: new Map,
21733
+ pendingToolName: new Map,
21023
21734
  toolIds: new Set,
21024
21735
  lastActivity: Date.now(),
21025
21736
  accumulatedText: "",
@@ -21066,6 +21777,24 @@ data: ${JSON.stringify(d)}
21066
21777
  };
21067
21778
  const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
21068
21779
  const state = createStreamingState();
21780
+ const writer = createBlockWriter(send);
21781
+ const thinkSplitter = createThinkTagSplitter();
21782
+ const emitSplitContent = ({ thinking, text }) => {
21783
+ if (thinking) {
21784
+ behavior?.onAssistantText?.(thinking, "reasoning");
21785
+ writer.append(writer.openThinking(), thinking);
21786
+ }
21787
+ if (!text)
21788
+ return;
21789
+ state.accumulatedText += text;
21790
+ 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);
21791
+ const shouldHoldBack = hasStructuredToolPattern && state.accumulatedText.length < 1000;
21792
+ if (shouldHoldBack) {
21793
+ log(`[Streaming] Text held back (structured tool pattern): ${state.accumulatedText.length} chars accumulated`);
21794
+ return;
21795
+ }
21796
+ writer.append(writer.openText(), text);
21797
+ };
21069
21798
  send("message_start", {
21070
21799
  type: "message_start",
21071
21800
  message: {
@@ -21110,67 +21839,50 @@ data: ${JSON.stringify(d)}
21110
21839
  }
21111
21840
  state.finalized = true;
21112
21841
  try {
21842
+ emitSplitContent(thinkSplitter.flush());
21843
+ for (const [idx, pending] of state.pendingToolArgs) {
21844
+ log(`[Streaming] Tool argument error: discarding ${pending.length} buffered chars for tool_calls index ${idx} \u2014 function.name never arrived`);
21845
+ }
21846
+ state.pendingToolArgs.clear();
21113
21847
  if (state.accumulatedText.length > 0) {
21114
21848
  const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
21115
21849
  log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
21116
21850
  }
21117
- const textToolCalls = state.tools.size > 0 ? [] : extractToolCallsFromText(state.accumulatedText, toolSchemas?.map((t) => t?.name).filter((n) => !!n));
21851
+ 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
21852
  if (state.tools.size > 0 && state.accumulatedText.length > 0) {
21119
21853
  log(`[Streaming] Skipping text-based tool extraction: ${state.tools.size} structured tool call(s) already present`);
21120
21854
  }
21121
21855
  log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
21122
- if (textToolCalls.length > 0) {
21856
+ const producedContent = state.accumulatedText.length > 0 || writer.anyBlockEmitted || state.tools.size > 0 || textToolCalls.length > 0;
21857
+ const toolInFlight = writer.emittedToolRefs.length > 0 || Array.from(state.tools.values()).some((t) => !t.closed && (t.started || t.buffered)) || textToolCalls.length > 0;
21858
+ const ending = reason === "error" ? "failure" : state.finishReason !== null || !producedContent ? "success" : toolInFlight ? "failure" : "silent-truncation";
21859
+ if (ending !== "success") {
21860
+ log(`[Streaming] Stream ending error: reason=${reason} finish_reason=${state.finishReason ?? "null"} content=${producedContent} tool_in_flight=${toolInFlight} \u2192 ${ending}`);
21861
+ }
21862
+ const emitToolCalls = ending !== "failure";
21863
+ if (textToolCalls.length > 0 && !emitToolCalls) {
21864
+ log(`[Streaming] Suppressing ${textToolCalls.length} text-recovered tool call(s): the turn ended in failure`);
21865
+ } else if (textToolCalls.length > 0) {
21123
21866
  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
21867
  for (const tc of textToolCalls) {
21129
- const toolIdx = state.curIdx++;
21868
+ const toolIdx = writer.reserve();
21130
21869
  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 });
21870
+ const ref = writer.openTool({ id: toolId, name: tc.name, index: toolIdx });
21871
+ writer.append(ref, repairArgs(tc.name, JSON.stringify(tc.arguments)));
21872
+ writer.close(ref);
21145
21873
  }
21146
21874
  }
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())) {
21875
+ writer.closeCurrent();
21876
+ for (const t of emitToolCalls ? Array.from(state.tools.values()) : []) {
21154
21877
  if (!t.closed && t.buffered && !t.started) {
21155
21878
  if (toolSchemas && toolSchemas.length > 0) {
21156
21879
  const validation = validateToolArguments(t.name, t.arguments, toolSchemas, state.accumulatedText);
21157
21880
  if (validation.valid || validation.repaired && validation.repairedArgs) {
21158
21881
  const argsJson = repairArgs(t.name, JSON.stringify(validation.repaired ? validation.repairedArgs : validation.parsedArgs));
21159
21882
  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
- });
21883
+ t.ref = writer.openTool({ id: t.id, name: t.name, index: t.blockIndex });
21884
+ writer.append(t.ref, argsJson);
21885
+ writer.close(t.ref);
21174
21886
  t.started = true;
21175
21887
  t.closed = true;
21176
21888
  } else {
@@ -21180,20 +21892,9 @@ data: ${JSON.stringify(d)}
21180
21892
  } else {
21181
21893
  const argsJson = repairArgs(t.name, t.arguments || "{}");
21182
21894
  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
- });
21895
+ t.ref = writer.openTool({ id: t.id, name: t.name, index: t.blockIndex });
21896
+ writer.append(t.ref, argsJson);
21897
+ writer.close(t.ref);
21197
21898
  t.started = true;
21198
21899
  t.closed = true;
21199
21900
  }
@@ -21201,28 +21902,41 @@ data: ${JSON.stringify(d)}
21201
21902
  }
21202
21903
  for (const t of Array.from(state.tools.values())) {
21203
21904
  if (t.started && !t.closed) {
21204
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
21905
+ if (t.ref)
21906
+ writer.close(t.ref);
21205
21907
  t.closed = true;
21206
21908
  }
21207
21909
  }
21208
21910
  if (middlewareManager) {
21209
21911
  await middlewareManager.afterStreamComplete(target, streamMetadata);
21210
21912
  }
21211
- if (reason === "error") {
21212
- send("error", { type: "error", error: { type: "api_error", message: err } });
21913
+ if (ending === "failure") {
21914
+ 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.";
21915
+ send("error", { type: "error", error: { type: "api_error", message } });
21213
21916
  } else {
21214
21917
  const hasStructuredTools = Array.from(state.tools.values()).some((t) => t.started);
21215
- const truncated = state.finishReason === "length";
21918
+ const truncated = state.finishReason === "length" || ending === "silent-truncation";
21216
21919
  const refused = state.finishReason === "content_filter";
21217
21920
  const stopReason = refused ? "refusal" : truncated ? "max_tokens" : textToolCalls.length > 0 || hasStructuredTools ? "tool_use" : "end_turn";
21218
21921
  if (truncated || refused) {
21219
21922
  log(`[Streaming] Upstream finish_reason=${state.finishReason} \u2192 stop_reason=${stopReason} (${state.accumulatedText.length} chars produced)`);
21220
21923
  }
21924
+ if (stopReason === "end_turn" && !writer.anyBlockEmitted) {
21925
+ log(`[Streaming] Contentless turn error: end_turn with no content block emitted (finish_reason=${state.finishReason ?? "null"}) \u2014 emitting an empty text block`);
21926
+ writer.close(writer.openText());
21927
+ }
21928
+ const split = splitPromptTokens(state.usage);
21929
+ const fullyCached = split.promptTokens > 0 && split.inputTokens === 0;
21930
+ const wireInput = fullyCached ? split.promptTokens : split.inputTokens;
21931
+ const wireCacheRead = fullyCached ? 0 : split.cacheReadTokens;
21932
+ const wireCacheCreation = fullyCached ? 0 : split.cacheCreationTokens;
21221
21933
  send("message_delta", {
21222
21934
  type: "message_delta",
21223
21935
  delta: { stop_reason: stopReason, stop_sequence: null },
21224
21936
  usage: {
21225
- ...state.usage?.prompt_tokens ? { input_tokens: state.usage.prompt_tokens } : {},
21937
+ input_tokens: wireInput,
21938
+ cache_read_input_tokens: wireCacheRead,
21939
+ cache_creation_input_tokens: wireCacheCreation,
21226
21940
  output_tokens: state.usage?.completion_tokens || 0
21227
21941
  }
21228
21942
  });
@@ -21232,7 +21946,11 @@ data: ${JSON.stringify(d)}
21232
21946
  if (onTokenUpdate) {
21233
21947
  if (state.usage) {
21234
21948
  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);
21949
+ const costSplit = splitPromptTokens(state.usage);
21950
+ onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0, {
21951
+ cacheReadTokens: costSplit.cacheReadTokens,
21952
+ cacheCreationTokens: costSplit.cacheCreationTokens
21953
+ });
21236
21954
  } else {
21237
21955
  const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
21238
21956
  log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
@@ -21269,6 +21987,12 @@ data: ${JSON.stringify(d)}
21269
21987
  state.usage = chunk.usage;
21270
21988
  log(`[Streaming] Usage data received: prompt=${chunk.usage.prompt_tokens}, completion=${chunk.usage.completion_tokens}, total=${chunk.usage.total_tokens}`);
21271
21989
  }
21990
+ const inStreamError = describeInStreamError(chunk);
21991
+ if (inStreamError) {
21992
+ log(`[Streaming] Upstream error inside a 200 stream: ${inStreamError}`);
21993
+ await finalize("error", inStreamError);
21994
+ return;
21995
+ }
21272
21996
  const delta = chunk.choices?.[0]?.delta;
21273
21997
  const finishReason = chunk.choices?.[0]?.finish_reason;
21274
21998
  if (finishReason)
@@ -21287,22 +22011,10 @@ data: ${JSON.stringify(d)}
21287
22011
  }
21288
22012
  const reasoningText = delta.reasoning_content || delta.reasoning;
21289
22013
  if (reasoningText) {
22014
+ thinkSplitter.disarmOpen();
21290
22015
  behavior?.onAssistantText?.(reasoningText, "reasoning");
21291
22016
  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
- });
22017
+ writer.append(writer.openThinking(), reasoningText);
21306
22018
  }
21307
22019
  const txt = delta.content || "";
21308
22020
  if (txt)
@@ -21310,42 +22022,13 @@ data: ${JSON.stringify(d)}
21310
22022
  log(`[Streaming] Text chunk: "${txt.substring(0, 30).replace(/\n/g, "\\n")}" (${txt.length} chars)`);
21311
22023
  if (txt) {
21312
22024
  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
22025
  const res = adapter.processTextContent(txt, "");
21321
22026
  log(`[Streaming] After adapter: "${res.cleanedText.substring(0, 30).replace(/\n/g, "\\n")}" (${res.cleanedText.length} chars, transformed=${res.wasTransformed})`);
21322
22027
  if (txt.length > 0 && res.cleanedText.length === 0) {
21323
22028
  log(`[Streaming] Text filtered out by adapter: "${txt.substring(0, 50)}"`);
21324
22029
  }
21325
22030
  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
- }
22031
+ emitSplitContent(thinkSplitter.push(res.cleanedText));
21349
22032
  }
21350
22033
  }
21351
22034
  if (delta.tool_calls) {
@@ -21354,57 +22037,62 @@ data: ${JSON.stringify(d)}
21354
22037
  const idx = tc.index;
21355
22038
  let t = state.tools.get(idx);
21356
22039
  if (tc.function?.name) {
22040
+ const accumulatedName = (state.pendingToolName.get(idx) ?? "") + tc.function.name;
22041
+ state.pendingToolName.set(idx, accumulatedName);
22042
+ const restoredName = toolNameMap?.get(accumulatedName) || accumulatedName;
21357
22043
  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
22044
  t = {
21375
22045
  id: tc.id || `tool_${Date.now()}_${idx}`,
21376
22046
  name: restoredName,
21377
- blockIndex: state.curIdx++,
22047
+ blockIndex: writer.reserve(),
21378
22048
  started: false,
21379
22049
  closed: false,
21380
- arguments: "",
22050
+ arguments: state.pendingToolArgs.get(idx) ?? "",
22051
+ ref: null,
21381
22052
  buffered: !!toolSchemas && toolSchemas.length > 0 || behavior?.shouldBufferTool?.(restoredName) === true
21382
22053
  };
22054
+ if (t.arguments) {
22055
+ log(`[Streaming] tool ${t.name} (index ${idx}): seeded ${t.arguments.length} argument chars that arrived before function.name`);
22056
+ }
22057
+ state.pendingToolArgs.delete(idx);
21383
22058
  state.tools.set(idx, t);
21384
22059
  if (isWebSearchToolCall(restoredName)) {
21385
22060
  warnWebSearchUnsupported(restoredName, target);
21386
22061
  }
22062
+ } else if (t.name !== restoredName) {
22063
+ if (t.started) {
22064
+ 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`);
22065
+ } else {
22066
+ t.name = restoredName;
22067
+ t.buffered = !!toolSchemas && toolSchemas.length > 0 || behavior?.shouldBufferTool?.(restoredName) === true;
22068
+ if (isWebSearchToolCall(restoredName)) {
22069
+ warnWebSearchUnsupported(restoredName, target);
22070
+ }
22071
+ }
21387
22072
  }
21388
22073
  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 }
22074
+ t.ref = writer.openTool({
22075
+ id: t.id,
22076
+ name: t.name,
22077
+ index: t.blockIndex
21393
22078
  });
21394
22079
  t.started = true;
22080
+ if (t.arguments)
22081
+ writer.append(t.ref, t.arguments);
21395
22082
  }
21396
22083
  }
22084
+ if (tc.function?.arguments && !t) {
22085
+ state.pendingToolArgs.set(idx, (state.pendingToolArgs.get(idx) ?? "") + tc.function.arguments);
22086
+ }
21397
22087
  if (tc.function?.arguments && t) {
21398
22088
  t.arguments += tc.function.arguments;
21399
22089
  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
- });
22090
+ if (!t.ref || !writer.append(t.ref, tc.function.arguments)) {
22091
+ log(`[Streaming] tool ${t.name} (index ${idx}) lost its open block mid-arguments \u2014 buffering the rest`);
22092
+ t.buffered = true;
22093
+ t.started = false;
22094
+ t.ref = null;
22095
+ }
21408
22096
  }
21409
22097
  }
21410
22098
  }
@@ -21420,110 +22108,74 @@ data: ${JSON.stringify(d)}
21420
22108
  const repairedJson = repairArgs(t.name, JSON.stringify(validation.repairedArgs));
21421
22109
  log(`[Streaming] Sending repaired tool call: ${t.name} with args: ${repairedJson}`);
21422
22110
  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",
22111
+ t.ref = writer.openTool({
22112
+ id: t.id,
22113
+ name: t.name,
21435
22114
  index: t.blockIndex
21436
22115
  });
22116
+ writer.append(t.ref, repairedJson);
22117
+ writer.close(t.ref);
21437
22118
  t.started = true;
21438
22119
  t.closed = true;
21439
22120
  continue;
21440
22121
  }
21441
22122
  if (t.started) {
21442
- send("content_block_stop", {
21443
- type: "content_block_stop",
21444
- index: t.blockIndex
21445
- });
21446
- const repairedIdx = state.curIdx++;
22123
+ if (t.ref)
22124
+ writer.close(t.ref);
22125
+ const repairedIdx = writer.reserve();
21447
22126
  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",
22127
+ const repairedRef = writer.openTool({
22128
+ id: repairedId,
22129
+ name: t.name,
21460
22130
  index: repairedIdx
21461
22131
  });
22132
+ writer.append(repairedRef, repairedJson);
22133
+ writer.close(repairedRef);
22134
+ t.ref = repairedRef;
21462
22135
  t.closed = true;
21463
22136
  continue;
21464
22137
  }
21465
22138
  }
21466
22139
  if (!validation.valid) {
21467
22140
  log(`[Streaming] Tool call ${t.name} validation failed: ${validation.missingParams.join(", ")}`);
21468
- const errorIdx = t.buffered ? t.blockIndex : state.curIdx++;
22141
+ const errorIdx = t.buffered ? t.blockIndex : undefined;
21469
22142
  const errorMsg = `
21470
22143
 
21471
22144
  \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
- });
22145
+ const errorRef = writer.openText({ index: errorIdx });
22146
+ writer.append(errorRef, errorMsg);
22147
+ writer.close(errorRef);
22148
+ if (t.started && !t.buffered && t.ref) {
22149
+ writer.close(t.ref);
21491
22150
  }
21492
22151
  t.closed = true;
21493
22152
  continue;
21494
22153
  }
21495
22154
  if (t.buffered && !t.started) {
21496
22155
  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",
22156
+ t.ref = writer.openTool({
22157
+ id: t.id,
22158
+ name: t.name,
21509
22159
  index: t.blockIndex
21510
22160
  });
22161
+ writer.append(t.ref, argsJson);
22162
+ writer.close(t.ref);
21511
22163
  t.started = true;
21512
22164
  t.closed = true;
21513
22165
  continue;
21514
22166
  }
21515
22167
  }
21516
22168
  if (t.started && !t.closed) {
21517
- send("content_block_stop", {
21518
- type: "content_block_stop",
21519
- index: t.blockIndex
21520
- });
22169
+ if (t.ref)
22170
+ writer.close(t.ref);
21521
22171
  t.closed = true;
21522
22172
  }
21523
22173
  }
21524
22174
  }
21525
22175
  }
21526
- } catch (e) {}
22176
+ } catch (e) {
22177
+ log(`[Streaming] Chunk processing error (chunk dropped): ${e} \u2014 payload starts: ${dataStr.slice(0, 120)}`);
22178
+ }
21527
22179
  }
21528
22180
  }
21529
22181
  await finalize("unexpected");
@@ -21549,10 +22201,13 @@ var init_openai_sse = __esm(() => {
21549
22201
  init_logger();
21550
22202
  init_tool_call_recovery();
21551
22203
  init_web_search_detector();
22204
+ init_block_writer();
22205
+ init_think_tag_splitter();
21552
22206
  });
21553
22207
 
21554
22208
  // src/handlers/shared/openai-compat.ts
21555
22209
  var init_openai_compat = __esm(() => {
22210
+ init_openai_messages();
21556
22211
  init_openai_tools();
21557
22212
  init_openai_sse();
21558
22213
  });
@@ -21560,6 +22215,7 @@ var init_openai_compat = __esm(() => {
21560
22215
  // src/adapters/gemini-api-format.ts
21561
22216
  var GeminiAPIFormat;
21562
22217
  var init_gemini_api_format = __esm(() => {
22218
+ init_openai_tools();
21563
22219
  init_gemini_schema();
21564
22220
  init_openai_compat();
21565
22221
  init_logger();
@@ -21704,6 +22360,11 @@ CRITICAL INSTRUCTION FOR OUTPUT FORMAT:
21704
22360
  }
21705
22361
  if (tools && tools.length > 0) {
21706
22362
  payload.tools = tools;
22363
+ const toolConfig = mapToolChoiceToGemini(claudeRequest.tool_choice);
22364
+ if (toolConfig) {
22365
+ payload.toolConfig = toolConfig;
22366
+ log(`[GeminiAPIFormat] toolConfig.mode -> ${toolConfig.functionCallingConfig.mode} for ${this.modelId}`);
22367
+ }
21707
22368
  }
21708
22369
  const effort = this.resolveEffortLevel(claudeRequest);
21709
22370
  if (effort) {
@@ -21825,6 +22486,7 @@ CRITICAL INSTRUCTION FOR OUTPUT FORMAT:
21825
22486
  // src/adapters/litellm-api-format.ts
21826
22487
  var INLINE_IMAGE_MODEL_PATTERNS, LiteLLMAPIFormat;
21827
22488
  var init_litellm_api_format = __esm(() => {
22489
+ init_openai_tools();
21828
22490
  init_logger();
21829
22491
  init_base_api_format();
21830
22492
  INLINE_IMAGE_MODEL_PATTERNS = ["minimax"];
@@ -21900,14 +22562,11 @@ var init_litellm_api_format = __esm(() => {
21900
22562
  if (tools.length > 0) {
21901
22563
  payload.tools = tools;
21902
22564
  }
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
- }
22565
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
22566
+ if (toolChoice !== undefined) {
22567
+ payload.tool_choice = toolChoice;
21910
22568
  }
22569
+ this.applyOpenAISamplingParams(payload, claudeRequest);
21911
22570
  return payload;
21912
22571
  }
21913
22572
  checkVisionSupport() {
@@ -22010,6 +22669,7 @@ var init_ollama_api_format = __esm(() => {
22010
22669
  // src/adapters/openai-api-format.ts
22011
22670
  var OpenAIAPIFormat;
22012
22671
  var init_openai_api_format = __esm(() => {
22672
+ init_openai_tools();
22013
22673
  init_logger();
22014
22674
  init_base_api_format();
22015
22675
  init_model_catalog();
@@ -22027,13 +22687,6 @@ var init_openai_api_format = __esm(() => {
22027
22687
  getMaxToolCount() {
22028
22688
  return 128;
22029
22689
  }
22030
- prepareRequestCommon(request, _originalRequest) {
22031
- this.truncateToolNames(request);
22032
- if (request.messages) {
22033
- this.truncateToolNamesInMessages(request.messages);
22034
- }
22035
- return request;
22036
- }
22037
22690
  applyNativeReasoning(request, originalRequest) {
22038
22691
  if (this.supportsReasoningEffort() && request.reasoning_effort === undefined) {
22039
22692
  const effort = this.resolveReasoningEffort(originalRequest);
@@ -22187,13 +22840,9 @@ var init_openai_api_format = __esm(() => {
22187
22840
  if (tools.length > 0) {
22188
22841
  payload.tools = tools;
22189
22842
  }
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
- }
22843
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
22844
+ if (toolChoice !== undefined) {
22845
+ payload.tool_choice = toolChoice;
22197
22846
  }
22198
22847
  if (this.supportsReasoningEffort()) {
22199
22848
  const effort = this.resolveReasoningEffort(claudeRequest);
@@ -22202,6 +22851,7 @@ var init_openai_api_format = __esm(() => {
22202
22851
  log(`[OpenAIAPIFormat] reasoning_effort -> ${effort} for ${this.modelId}`);
22203
22852
  }
22204
22853
  }
22854
+ this.applyOpenAISamplingParams(payload, claudeRequest);
22205
22855
  return payload;
22206
22856
  }
22207
22857
  };
@@ -22688,30 +23338,30 @@ var init_grok_model_dialect = __esm(() => {
22688
23338
  return value;
22689
23339
  }
22690
23340
  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
- };
23341
+ if (payload?.reasoning_effort !== undefined) {
23342
+ if (isReasoningEffortRejection(errorText)) {
23343
+ rememberReasoningEffortRejected(this.modelId);
23344
+ const next = { ...payload };
23345
+ delete next.reasoning_effort;
23346
+ return { payload: next, note: `dropped reasoning_effort for ${this.modelId}` };
23347
+ }
23348
+ const rejected = rejectedReasoningEffortValue(errorText);
23349
+ if (rejected) {
23350
+ rememberReasoningEffortValueRejected(this.modelId, rejected);
23351
+ const fallback = fallbackReasoningEffortValue(rejected);
23352
+ const next = { ...payload };
23353
+ if (fallback) {
23354
+ next.reasoning_effort = fallback;
23355
+ return {
23356
+ payload: next,
23357
+ note: `reasoning_effort "${rejected}" -> "${fallback}" for ${this.modelId}`
23358
+ };
23359
+ }
23360
+ delete next.reasoning_effort;
23361
+ return { payload: next, note: `dropped unsupported reasoning_effort for ${this.modelId}` };
22710
23362
  }
22711
- delete next.reasoning_effort;
22712
- return { payload: next, note: `dropped unsupported reasoning_effort for ${this.modelId}` };
22713
23363
  }
22714
- return null;
23364
+ return super.recoverFromRejection(payload, errorText);
22715
23365
  }
22716
23366
  parseXmlParameters(xmlContent) {
22717
23367
  const params = {};
@@ -22738,6 +23388,7 @@ var init_grok_model_dialect = __esm(() => {
22738
23388
  return lookupModel(this.modelId)?.contextWindow ?? 0;
22739
23389
  }
22740
23390
  reset() {
23391
+ super.reset();
22741
23392
  this.xmlBuffer = "";
22742
23393
  }
22743
23394
  };
@@ -22847,11 +23498,18 @@ var init_qwen_model_dialect = __esm(() => {
22847
23498
  log(`[QwenModelDialect] effort ${effort} -> enable_thinking: false for ${this.modelId}`);
22848
23499
  } else {
22849
23500
  request.enable_thinking = true;
22850
- const budget = this.effortToThinkingTokenBudget(effort);
22851
- if (budget !== undefined) {
22852
- request.thinking_budget = budget;
23501
+ const requested = this.effortToThinkingTokenBudget(effort);
23502
+ const ceiling = outputCeilingOf(originalRequest, request);
23503
+ const budget = clampThinkingBudget(requested, ceiling);
23504
+ if (budget === "no-room") {
23505
+ delete request.thinking_budget;
23506
+ log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, no thinking_budget (${requested} does not fit under ceiling ${ceiling}) for ${this.modelId}`);
23507
+ } else {
23508
+ if (budget !== undefined) {
23509
+ request.thinking_budget = budget;
23510
+ }
23511
+ log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, thinking_budget: ${budget ?? "(model max)"}${budget !== undefined && budget !== requested ? ` (clamped from ${requested} under ceiling ${ceiling})` : ""} for ${this.modelId}`);
22853
23512
  }
22854
- log(`[QwenModelDialect] effort ${effort} -> enable_thinking: true, thinking_budget: ${budget ?? "(model max)"} for ${this.modelId}`);
22855
23513
  }
22856
23514
  if (originalRequest.thinking)
22857
23515
  delete request.thinking;
@@ -22881,16 +23539,6 @@ var init_xiaomi_model_dialect = __esm(() => {
22881
23539
  wasTransformed: false
22882
23540
  };
22883
23541
  }
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
23542
  applyNativeReasoning(request, originalRequest) {
22895
23543
  if (originalRequest.thinking) {
22896
23544
  log("[XiaomiModelDialect] Stripping thinking object (not supported by Xiaomi API)");
@@ -31672,6 +32320,31 @@ var init_stats = __esm(() => {
31672
32320
  });
31673
32321
  });
31674
32322
 
32323
+ // src/handlers/shared/request-shape.ts
32324
+ function isRequestShapeError(errorBody) {
32325
+ if (!errorBody)
32326
+ return false;
32327
+ const lower = errorBody.toLowerCase();
32328
+ 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:");
32329
+ }
32330
+ function isContextOverflowError(status, errorBody) {
32331
+ if (!errorBody)
32332
+ return false;
32333
+ if (isRequestShapeError(errorBody))
32334
+ return false;
32335
+ const lower = errorBody.toLowerCase();
32336
+ 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")) {
32337
+ return true;
32338
+ }
32339
+ 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)) {
32340
+ return true;
32341
+ }
32342
+ if (status === 413 && (lower.includes("token") || lower.includes("context")))
32343
+ return true;
32344
+ return false;
32345
+ }
32346
+ var CONTEXT_OVERFLOW_PHRASE = "prompt is too long";
32347
+
31675
32348
  // src/handlers/shared/anthropic-error.ts
31676
32349
  function statusToErrorType(status) {
31677
32350
  switch (status) {
@@ -31750,6 +32423,8 @@ function isTerminalError(status, bodyText, terminal429) {
31750
32423
  return true;
31751
32424
  if (status === 429 && terminal429)
31752
32425
  return true;
32426
+ if (isContextOverflowError(status, bodyText))
32427
+ return true;
31753
32428
  const lower = (bodyText || "").toLowerCase();
31754
32429
  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
32430
  return true;
@@ -31763,11 +32438,13 @@ function isTerminalError(status, bodyText, terminal429) {
31763
32438
  return false;
31764
32439
  }
31765
32440
  function buildSurfacedErrorMessage(opts) {
31766
- const { providerDisplayName, status, hint, providerMessage } = opts;
32441
+ const { providerDisplayName, status, hint, providerMessage, leadPhrase } = opts;
31767
32442
  const head = `${providerDisplayName} error (HTTP ${status})`;
31768
32443
  const parts = [head];
31769
32444
  if (hint)
31770
32445
  parts[0] = `${head}: ${hint}`;
32446
+ if (leadPhrase && !parts[0].startsWith(leadPhrase))
32447
+ parts[0] = `${leadPhrase} \u2014 ${parts[0]}`;
31771
32448
  const detail = (providerMessage || "").trim();
31772
32449
  if (detail && !parts[0].includes(detail)) {
31773
32450
  const trimmed = detail.length > 600 ? `${detail.slice(0, 600)}\u2026` : detail;
@@ -32177,14 +32854,6 @@ var init_model_unsupported = __esm(() => {
32177
32854
  ];
32178
32855
  });
32179
32856
 
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
32857
  // src/handlers/shared/stream-head-sniffer.ts
32189
32858
  function isRetryableStreamError(code, type, message) {
32190
32859
  if (RETRYABLE_ERROR_CODES.has(code))
@@ -33938,6 +34607,17 @@ function stripProviderPrefix(name) {
33938
34607
  const at = name.indexOf("@");
33939
34608
  return at === -1 ? name : name.slice(at + 1);
33940
34609
  }
34610
+ function computeCacheReadDiscount(pricing, billedInputTokens, detail) {
34611
+ const cacheReadTokens = detail?.cacheReadTokens ?? 0;
34612
+ if (cacheReadTokens <= 0 || billedInputTokens <= 0)
34613
+ return 0;
34614
+ const rate = pricing.cacheReadCostPer1M ?? pricing.inputCostPer1M;
34615
+ const perMillionSaved = pricing.inputCostPer1M - rate;
34616
+ if (!(perMillionSaved > 0))
34617
+ return 0;
34618
+ const discountedTokens = Math.min(cacheReadTokens, billedInputTokens);
34619
+ return discountedTokens / 1e6 * perMillionSaved;
34620
+ }
33941
34621
 
33942
34622
  class TokenTracker {
33943
34623
  port;
@@ -33952,6 +34632,7 @@ class TokenTracker {
33952
34632
  toolCallsByName = new Map;
33953
34633
  startedAt = Date.now();
33954
34634
  sessionBilledInputTokens = 0;
34635
+ sessionCacheReadTokens = 0;
33955
34636
  constructor(port, config) {
33956
34637
  this.port = port;
33957
34638
  this.config = config;
@@ -33987,27 +34668,32 @@ class TokenTracker {
33987
34668
  rewrite() {
33988
34669
  this.writeFile(this.getLastInputTokens(), this.sessionOutputTokens);
33989
34670
  }
33990
- update(inputTokens, outputTokens) {
34671
+ update(inputTokens, outputTokens, detail) {
33991
34672
  this.sessionInputTokens = inputTokens;
33992
34673
  this.lastInputTokens = inputTokens;
33993
34674
  this.sessionOutputTokens += outputTokens;
33994
34675
  this.sessionBilledInputTokens += inputTokens;
34676
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
33995
34677
  const pricing = this.getPricing();
33996
- const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
34678
+ const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M - this.cacheReadDiscount(pricing, inputTokens, detail);
33997
34679
  this.sessionTotalCost += cost;
33998
34680
  this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
33999
34681
  }
34000
- accumulateBoth(inputTokens, outputTokens) {
34682
+ accumulateBoth(inputTokens, outputTokens, detail) {
34001
34683
  this.sessionInputTokens += inputTokens;
34002
34684
  this.lastInputTokens = this.sessionInputTokens;
34003
34685
  this.sessionOutputTokens += outputTokens;
34686
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
34004
34687
  const pricing = this.getPricing();
34005
- const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M;
34688
+ const cost = this.sessionInputTokens / 1e6 * pricing.inputCostPer1M + this.sessionOutputTokens / 1e6 * pricing.outputCostPer1M - this.cacheReadDiscount(pricing, this.sessionInputTokens, {
34689
+ cacheReadTokens: this.sessionCacheReadTokens,
34690
+ cacheCreationTokens: 0
34691
+ });
34006
34692
  this.sessionTotalCost = cost;
34007
34693
  this.sessionBilledInputTokens = this.sessionInputTokens;
34008
34694
  this.writeFile(this.sessionInputTokens, this.sessionOutputTokens, pricing.isEstimate);
34009
34695
  }
34010
- updateWithDelta(inputTokens, outputTokens) {
34696
+ updateWithDelta(inputTokens, outputTokens, detail) {
34011
34697
  let incrementalInputTokens;
34012
34698
  this.lastInputTokens = inputTokens;
34013
34699
  if (inputTokens >= this.sessionInputTokens) {
@@ -34022,17 +34708,19 @@ class TokenTracker {
34022
34708
  log(`[TokenTracker] Ambiguous token decrease (${inputTokens} vs ${this.sessionInputTokens}), charging full input`);
34023
34709
  }
34024
34710
  this.sessionOutputTokens += outputTokens;
34711
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
34025
34712
  const pricing = this.getPricing();
34026
34713
  this.sessionBilledInputTokens += incrementalInputTokens;
34027
- const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
34714
+ const cost = incrementalInputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M - this.cacheReadDiscount(pricing, incrementalInputTokens, detail);
34028
34715
  this.sessionTotalCost += cost;
34029
34716
  this.writeFile(inputTokens, this.sessionOutputTokens, pricing.isEstimate);
34030
34717
  }
34031
- updateWithActualCost(inputTokens, outputTokens, actualCost) {
34718
+ updateWithActualCost(inputTokens, outputTokens, actualCost, detail) {
34032
34719
  this.sessionInputTokens = inputTokens;
34033
34720
  this.lastInputTokens = inputTokens;
34034
34721
  this.sessionOutputTokens += outputTokens;
34035
34722
  this.sessionBilledInputTokens += inputTokens;
34723
+ this.sessionCacheReadTokens += detail?.cacheReadTokens ?? 0;
34036
34724
  if (typeof actualCost === "number" && actualCost > 0) {
34037
34725
  this.sessionTotalCost += actualCost;
34038
34726
  log(`[TokenTracker] Actual cost from API: $${actualCost.toFixed(6)}`);
@@ -34040,11 +34728,11 @@ class TokenTracker {
34040
34728
  const pricing = this.getPricing();
34041
34729
  const inputCost = inputTokens / 1e6 * pricing.inputCostPer1M;
34042
34730
  const outputCost = outputTokens / 1e6 * pricing.outputCostPer1M;
34043
- this.sessionTotalCost += inputCost + outputCost;
34731
+ this.sessionTotalCost += inputCost + outputCost - this.cacheReadDiscount(pricing, inputTokens, detail);
34044
34732
  }
34045
34733
  this.writeFile(inputTokens, this.sessionOutputTokens);
34046
34734
  }
34047
- updateLocal(inputTokens, outputTokens) {
34735
+ updateLocal(inputTokens, outputTokens, _detail) {
34048
34736
  if (inputTokens > 0) {
34049
34737
  this.sessionInputTokens = inputTokens;
34050
34738
  this.lastInputTokens = inputTokens;
@@ -34074,6 +34762,9 @@ class TokenTracker {
34074
34762
  getPricing() {
34075
34763
  return getModelPricing(this.config.providerName, this.config.modelName);
34076
34764
  }
34765
+ cacheReadDiscount(pricing, billedInputTokens, detail) {
34766
+ return computeCacheReadDiscount(pricing, billedInputTokens, detail);
34767
+ }
34077
34768
  getDisplayName() {
34078
34769
  if (this.config.providerDisplayName)
34079
34770
  return this.config.providerDisplayName;
@@ -34242,6 +34933,9 @@ class ComposedHandler {
34242
34933
  if (resolvedModelAdapter.getName() !== "DefaultAPIFormat") {
34243
34934
  this.modelAdapter = resolvedModelAdapter;
34244
34935
  }
34936
+ const responseWire = this.resolveStreamFormat();
34937
+ this.resolvedDialect.setResponseWireFormat(responseWire);
34938
+ this.explicitAdapter?.setResponseWireFormat(responseWire);
34245
34939
  this.middlewareManager = new MiddlewareManager;
34246
34940
  if (this.bareModelName.includes("gemini") || this.bareModelName.includes("google/")) {
34247
34941
  this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
@@ -34508,9 +35202,15 @@ class ComposedHandler {
34508
35202
  log(`[${this.provider.displayName}] Response status: ${response.status}`);
34509
35203
  this.capturePlanUsage(response);
34510
35204
  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);
35205
+ if (response.status >= 400 && response.status < 500) {
35206
+ const candidates = [this.modelAdapter, this.getAdapter()].filter((a, i, all) => !!a?.recoverFromRejection && all.indexOf(a) === i);
35207
+ const errorText = candidates.length > 0 ? await response.clone().text() : "";
35208
+ let recovery = null;
35209
+ for (const candidate of candidates) {
35210
+ recovery = candidate.recoverFromRejection(requestPayload, errorText);
35211
+ if (recovery)
35212
+ break;
35213
+ }
34514
35214
  if (recovery) {
34515
35215
  log(`[${this.provider.displayName}] Parameter rejected \u2014 retrying: ${recovery.note}`);
34516
35216
  requestPayload = recovery.payload;
@@ -34690,7 +35390,8 @@ class ComposedHandler {
34690
35390
  providerDisplayName: this.provider.displayName,
34691
35391
  status: response.status,
34692
35392
  hint,
34693
- providerMessage: providerMsg
35393
+ providerMessage: providerMsg,
35394
+ leadPhrase: isContextOverflowError(response.status, errorText) ? CONTEXT_OVERFLOW_PHRASE : undefined
34694
35395
  });
34695
35396
  return c.json(wrapAnthropicError(400, surfaced, "invalid_request_error", response.status), 400);
34696
35397
  }
@@ -34913,20 +35614,20 @@ class ComposedHandler {
34913
35614
  }
34914
35615
  handleStream(c, response, adapter, claudeRequest, toolNameMap, onComplete, onApiError, behaviorSession) {
34915
35616
  let pendingOnComplete = onComplete;
34916
- const onTokenUpdate = (input, output) => {
35617
+ const onTokenUpdate = (input, output, detail) => {
34917
35618
  const strategy = this.options.tokenStrategy || "standard";
34918
35619
  switch (strategy) {
34919
35620
  case "accumulate-both":
34920
- this.tokenTracker.accumulateBoth(input, output);
35621
+ this.tokenTracker.accumulateBoth(input, output, detail);
34921
35622
  break;
34922
35623
  case "delta-aware":
34923
- this.tokenTracker.updateWithDelta(input, output);
35624
+ this.tokenTracker.updateWithDelta(input, output, detail);
34924
35625
  break;
34925
35626
  case "local":
34926
- this.tokenTracker.updateLocal(input, output);
35627
+ this.tokenTracker.updateLocal(input, output, detail);
34927
35628
  break;
34928
35629
  default:
34929
- this.tokenTracker.update(input, output);
35630
+ this.tokenTracker.update(input, output, detail);
34930
35631
  break;
34931
35632
  }
34932
35633
  if (pendingOnComplete) {
@@ -34955,7 +35656,7 @@ class ComposedHandler {
34955
35656
  return createResponsesStreamHandler(c, response, {
34956
35657
  modelName: this.bareModelName,
34957
35658
  onTokenUpdate,
34958
- toolNameMap: adapter.getToolNameMap(),
35659
+ toolNameMap,
34959
35660
  contextWindow: lookupModelForProvider(this.bareModelName, this.provider.name),
34960
35661
  onApiError,
34961
35662
  priorInputTokens,
@@ -35111,6 +35812,9 @@ function getRecoveryHint(status, errorText, providerName, transportTerminal429)
35111
35812
  }
35112
35813
  return "Request format may be incompatible with provider.";
35113
35814
  }
35815
+ if (isContextOverflowError(status, errorText)) {
35816
+ return "Input too large. Reduce message history or use a larger-context model.";
35817
+ }
35114
35818
  if (status >= 500) {
35115
35819
  return "Server error \u2014 retry after a brief wait.";
35116
35820
  }
@@ -37055,6 +37759,12 @@ var init_auto_route = __esm(() => {
37055
37759
  });
37056
37760
 
37057
37761
  // src/providers/catalog-client.ts
37762
+ function catalogUrl() {
37763
+ return process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? DEFAULT_CATALOG_URL;
37764
+ }
37765
+ function plansUrl() {
37766
+ return process.env.CLAUDISH_PLANS_URL ?? derivePlansUrl(catalogUrl());
37767
+ }
37058
37768
  function derivePlansUrl(catalogUrl) {
37059
37769
  try {
37060
37770
  const url = new URL(catalogUrl);
@@ -37203,49 +37913,97 @@ function catalogWarmDisabledFor(value) {
37203
37913
  function catalogWarmDisabled() {
37204
37914
  return catalogWarmDisabledFor(process.env.CLAUDISH_DISABLE_CATALOG_WARM);
37205
37915
  }
37206
- async function refreshCatalog(timeoutMs) {
37207
- if (catalogWarmDisabled()) {
37208
- return { kind: "fetch_failed", reason: "disabled" };
37209
- }
37210
- const plansPromise = fetchSubscriptionPlans(timeoutMs);
37916
+ function buildCatalogPageUrl(baseUrl, offset, limit, revision) {
37917
+ const url = new URL(baseUrl);
37918
+ url.searchParams.set("offset", String(offset));
37919
+ url.searchParams.set("limit", String(limit));
37920
+ if (revision)
37921
+ url.searchParams.set("revision", revision);
37922
+ return url.toString();
37923
+ }
37924
+ async function fetchCatalogPage(url, timeoutMs) {
37211
37925
  let response;
37212
37926
  try {
37213
- response = await fetch(FIREBASE_CATALOG_URL, { signal: AbortSignal.timeout(timeoutMs) });
37927
+ response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
37214
37928
  } catch (err) {
37215
37929
  const name = err?.name;
37216
37930
  const reason = name === "TimeoutError" || name === "AbortError" ? "timeout" : "network";
37217
- return { kind: "fetch_failed", reason };
37931
+ return { ok: false, reason };
37218
37932
  }
37219
37933
  if (!response.ok)
37220
- return { kind: "fetch_failed", reason: "http_error" };
37221
- let data;
37934
+ return { ok: false, reason: "http_error" };
37935
+ let page;
37222
37936
  try {
37223
- data = await response.json();
37937
+ page = await response.json();
37224
37938
  } catch {
37225
- return { kind: "fetch_failed", reason: "network" };
37939
+ return { ok: false, reason: "network" };
37226
37940
  }
37227
- if (!Array.isArray(data.models) || data.models.length === 0) {
37228
- return { kind: "fetch_failed", reason: "empty" };
37941
+ const revision = response.headers.get(CATALOG_REVISION_HEADER) ?? undefined;
37942
+ return { ok: true, page, revision };
37943
+ }
37944
+ async function refreshCatalog(timeoutMs, options = {}) {
37945
+ if (catalogWarmDisabled()) {
37946
+ return { kind: "fetch_failed", reason: "disabled" };
37947
+ }
37948
+ const entries = [];
37949
+ let revision;
37950
+ let pages = 0;
37951
+ let offset = 0;
37952
+ for (;; ) {
37953
+ const url = buildCatalogPageUrl(catalogUrl(), offset, CATALOG_PAGE_LIMIT, revision);
37954
+ const result = await fetchCatalogPage(url, timeoutMs);
37955
+ if (!result.ok) {
37956
+ return {
37957
+ kind: "fetch_failed",
37958
+ reason: pages === 0 ? result.reason : "incomplete"
37959
+ };
37960
+ }
37961
+ const { page } = result;
37962
+ if (!Array.isArray(page.models))
37963
+ return { kind: "fetch_failed", reason: "empty" };
37964
+ if (pages === 0) {
37965
+ revision = result.revision;
37966
+ if (page.models.length === 0)
37967
+ return { kind: "fetch_failed", reason: "empty" };
37968
+ } else if (revision && result.revision && result.revision !== revision) {
37969
+ return { kind: "fetch_failed", reason: "revision_mismatch" };
37970
+ }
37971
+ entries.push(...page.models);
37972
+ pages++;
37973
+ if (page.hasMore !== true)
37974
+ break;
37975
+ if (page.models.length === 0)
37976
+ return { kind: "fetch_failed", reason: "incomplete" };
37977
+ offset += page.models.length;
37978
+ if (pages >= MAX_CATALOG_PAGES) {
37979
+ return { kind: "fetch_failed", reason: "incomplete" };
37980
+ }
37229
37981
  }
37982
+ if (entries.length === 0)
37983
+ return { kind: "fetch_failed", reason: "empty" };
37984
+ const plans = await fetchSubscriptionPlans(timeoutMs, revision);
37230
37985
  const backwardCompatModels = [];
37231
- for (const entry of data.models) {
37986
+ for (const entry of entries) {
37232
37987
  const id = externalIdFor(entry, "openrouter");
37233
37988
  if (id)
37234
37989
  backwardCompatModels.push({ id });
37235
37990
  }
37236
- _memCache = data.models;
37237
- const plans = await plansPromise;
37991
+ _memCache = entries;
37238
37992
  writeAllModelsCache({
37239
- entries: data.models,
37993
+ entries,
37240
37994
  models: backwardCompatModels,
37241
- ...plans !== undefined ? { plans } : {}
37242
- });
37995
+ ...plans !== undefined ? { plans } : {},
37996
+ ...revision !== undefined ? { catalogRevision: revision } : {}
37997
+ }, options.cachePath);
37243
37998
  _warmPromise = Promise.resolve();
37244
- return { kind: "refreshed", modelCount: data.models.length };
37999
+ return { kind: "refreshed", modelCount: entries.length, catalogRevision: revision, pages };
37245
38000
  }
37246
- async function fetchSubscriptionPlans(timeoutMs) {
38001
+ async function fetchSubscriptionPlans(timeoutMs, revision) {
37247
38002
  try {
37248
- const response = await fetch(FIREBASE_PLANS_URL, {
38003
+ const url = new URL(plansUrl());
38004
+ if (revision)
38005
+ url.searchParams.set("revision", revision);
38006
+ const response = await fetch(url.toString(), {
37249
38007
  signal: AbortSignal.timeout(timeoutMs)
37250
38008
  });
37251
38009
  if (!response.ok)
@@ -37277,11 +38035,9 @@ async function ensureCatalogReady(timeoutMs = 5000) {
37277
38035
  new Promise((resolve) => setTimeout(resolve, timeoutMs))
37278
38036
  ]);
37279
38037
  }
37280
- var FIREBASE_CATALOG_URL, FIREBASE_PLANS_URL, _memCache = null, _catalogEntriesForTest, _warmPromise = null;
38038
+ var DEFAULT_CATALOG_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000", _memCache = null, _catalogEntriesForTest, _warmPromise = null, CATALOG_REVISION_HEADER = "x-catalog-revision", MAX_CATALOG_PAGES = 40, CATALOG_PAGE_LIMIT = 1000;
37281
38039
  var init_catalog_client = __esm(() => {
37282
38040
  init_all_models_cache();
37283
- FIREBASE_CATALOG_URL = process.env.CLAUDISH_CATALOG_URL ?? process.env.FIREBASE_CATALOG_URL ?? "https://us-central1-claudish-6da10.cloudfunctions.net/queryModels?status=active&catalog=slim&limit=1000";
37284
- FIREBASE_PLANS_URL = process.env.CLAUDISH_PLANS_URL ?? derivePlansUrl(FIREBASE_CATALOG_URL);
37285
38041
  });
37286
38042
 
37287
38043
  // src/config-schema.ts
@@ -40480,6 +41236,18 @@ async function shutdownAllTeamRuns() {
40480
41236
  return;
40481
41237
  })));
40482
41238
  }
41239
+ function snippetHeadAndTail(text) {
41240
+ if (text.length <= SNIPPET_LIMIT)
41241
+ return text;
41242
+ const head = text.slice(0, SNIPPET_HEAD);
41243
+ const tail = text.slice(-(SNIPPET_LIMIT - SNIPPET_HEAD));
41244
+ const omitted = text.length - head.length - tail.length;
41245
+ return `${head}
41246
+
41247
+ \u2026 [${omitted} bytes omitted] \u2026
41248
+
41249
+ ${tail}`;
41250
+ }
40483
41251
  function classifyRunOutput(opts) {
40484
41252
  const {
40485
41253
  outputSize,
@@ -40850,7 +41618,7 @@ async function startModels(sessionPath, opts = {}) {
40850
41618
  reason,
40851
41619
  detail,
40852
41620
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
40853
- stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
41621
+ stdoutSnippet: stdoutTail ? snippetHeadAndTail(redactSecrets(stdoutTail)) : undefined,
40854
41622
  errorLogPath,
40855
41623
  upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
40856
41624
  workDir: sessionPath
@@ -41127,7 +41895,7 @@ function formatVerdict(verdict, sessionPath) {
41127
41895
  }
41128
41896
  return output;
41129
41897
  }
41130
- var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, BENIGN_STDERR_PATTERNS;
41898
+ var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LIMIT = 4000, SNIPPET_LIMIT = 2000, SNIPPET_HEAD = 600, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, BENIGN_STDERR_PATTERNS;
41131
41899
  var init_team_orchestrator = __esm(() => {
41132
41900
  init_prehydrate();
41133
41901
  init_stream_json_reducer();
@@ -42866,8 +43634,10 @@ function accountStreamEvent(rawEvent) {
42866
43634
  if (typeof text === "string" && text.length > 0) {
42867
43635
  contentDelta = true;
42868
43636
  textChars = text.length;
42869
- } else if (parsed?.type === "content_block_delta" || parsed?.type === "content_block_start") {
43637
+ } else if (parsed?.type === "content_block_delta") {
42870
43638
  contentDelta = true;
43639
+ } else if (parsed?.type === "content_block_start") {
43640
+ contentDelta = parsed.content_block?.type === "tool_use";
42871
43641
  }
42872
43642
  const outputTokens = parsed?.usage?.output_tokens ?? parsed?.message?.usage?.output_tokens ?? parsed?.usage?.completion_tokens;
42873
43643
  const stopReason = parsed?.delta?.stop_reason ?? (Array.isArray(parsed?.choices) ? parsed.choices[0]?.finish_reason : undefined);
@@ -44591,6 +45361,7 @@ var init_cors = () => {};
44591
45361
  // src/adapters/local-adapter.ts
44592
45362
  var LocalModelAdapter;
44593
45363
  var init_local_adapter = __esm(() => {
45364
+ init_openai_tools();
44594
45365
  init_logger();
44595
45366
  init_base_api_format();
44596
45367
  init_dialect_manager();
@@ -44657,26 +45428,26 @@ ${messages[0].content}`;
44657
45428
  tools: tools.length > 0 ? tools : undefined,
44658
45429
  stream_options: { include_usage: true }
44659
45430
  };
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;
45431
+ if (tools.length > 0) {
45432
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
45433
+ if (toolChoice !== undefined) {
45434
+ payload.tool_choice = toolChoice;
44666
45435
  }
44667
45436
  }
45437
+ this.applyOpenAISamplingParams(payload, claudeRequest);
44668
45438
  return payload;
44669
45439
  }
44670
45440
  prepareRequestCommon(request, originalRequest) {
44671
45441
  this.innerAdapter.prepareRequest(request, originalRequest);
44672
- for (const [k, v] of this.innerAdapter.getToolNameMap()) {
44673
- this.toolNameMap.set(k, v);
44674
- }
44675
45442
  delete request.enable_thinking;
44676
45443
  delete request.thinking_budget;
44677
45444
  delete request.thinking;
44678
45445
  return request;
44679
45446
  }
45447
+ setResponseWireFormat(format) {
45448
+ super.setResponseWireFormat(format);
45449
+ this.innerAdapter.setResponseWireFormat(format);
45450
+ }
44680
45451
  getToolNameMap() {
44681
45452
  const map = new Map(super.getToolNameMap());
44682
45453
  for (const [k, v] of this.innerAdapter.getToolNameMap()) {
@@ -44835,19 +45606,20 @@ ${text}`;
44835
45606
  if (claudeRequest.thinking) {
44836
45607
  payload.thinking = claudeRequest.thinking;
44837
45608
  }
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
- }
45609
+ const toolChoice = mapToolChoiceToOpenAI(claudeRequest.tool_choice);
45610
+ if (toolChoice !== undefined) {
45611
+ payload.tool_choice = toolChoice;
44845
45612
  }
45613
+ this.applyOpenAISamplingParams(payload, claudeRequest);
44846
45614
  return payload;
44847
45615
  }
44848
45616
  prepareRequestCommon(request, originalRequest) {
44849
45617
  return this.innerAdapter.prepareRequest(request, originalRequest);
44850
45618
  }
45619
+ setResponseWireFormat(format) {
45620
+ super.setResponseWireFormat(format);
45621
+ this.innerAdapter.setResponseWireFormat(format);
45622
+ }
44851
45623
  getToolNameMap() {
44852
45624
  const map = new Map(super.getToolNameMap());
44853
45625
  for (const [k, v] of this.innerAdapter.getToolNameMap()) {
@@ -67211,6 +67983,46 @@ function resolveAdvisorModelArg(config, cwd = process.cwd()) {
67211
67983
  source: "claudish"
67212
67984
  };
67213
67985
  }
67986
+ function isSuppressibleChildStderrLine(line) {
67987
+ return CHILD_STDERR_NOISE.some((re) => re.test(line));
67988
+ }
67989
+ function relayChildStderr(stream) {
67990
+ let buffered = "";
67991
+ const emit = (line) => {
67992
+ if (isSuppressibleChildStderrLine(line)) {
67993
+ log(`[Suppressed] claude-code-stderr: ${line.trimEnd()}`);
67994
+ return;
67995
+ }
67996
+ process.stderr.write(`${line}
67997
+ `);
67998
+ };
67999
+ stream.setEncoding("utf8");
68000
+ stream.on("data", (chunk) => {
68001
+ buffered += chunk;
68002
+ const lines = buffered.split(`
68003
+ `);
68004
+ buffered = lines.pop() ?? "";
68005
+ for (const line of lines)
68006
+ emit(line);
68007
+ });
68008
+ const flush = () => {
68009
+ if (buffered.length === 0)
68010
+ return;
68011
+ const tail = buffered;
68012
+ buffered = "";
68013
+ if (isSuppressibleChildStderrLine(tail)) {
68014
+ log(`[Suppressed] claude-code-stderr: ${tail}`);
68015
+ } else {
68016
+ process.stderr.write(tail);
68017
+ }
68018
+ };
68019
+ stream.on("end", flush);
68020
+ stream.on("close", flush);
68021
+ stream.on("error", (err) => {
68022
+ flush();
68023
+ logStderr(`child stderr relay ended: ${err}`);
68024
+ });
68025
+ }
67214
68026
  async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
67215
68027
  const hasProfileMappings = config.modelOpus || config.modelSonnet || config.modelHaiku || config.modelSubagent;
67216
68028
  const advisorNativeSession = isAdvisorNativeSession(config);
@@ -67285,6 +68097,9 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
67285
68097
  }
67286
68098
  let hidAnthropicApiKey = false;
67287
68099
  delete env.CLAUDECODE;
68100
+ if (!config.interactive && env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC === undefined) {
68101
+ env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1";
68102
+ }
67288
68103
  if (config.monitor || advisorNativeSession) {
67289
68104
  delete env.ANTHROPIC_API_KEY;
67290
68105
  delete env.ANTHROPIC_AUTH_TOKEN;
@@ -67382,12 +68197,16 @@ Or set CLAUDE_PATH to your custom installation:`);
67382
68197
  } else if (config.interactive && !process.stdout.isTTY && !process.stdin.isTTY) {
67383
68198
  console.error("[claudish] An interactive session was requested but no terminal is attached (stdin and stdout are both non-TTY). Pass a prompt argument, or use --stdin / -p for non-interactive mode.");
67384
68199
  }
67385
- const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : "inherit";
68200
+ const filterChildStderr = !config.interactive && ttyFd === undefined;
68201
+ const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : filterChildStderr ? ["inherit", "inherit", "pipe"] : "inherit";
67386
68202
  const proc = spawn5(spawnCommand, claudeArgs, {
67387
68203
  env,
67388
68204
  stdio,
67389
68205
  shell: needsShell
67390
68206
  });
68207
+ if (filterChildStderr && proc.stderr) {
68208
+ relayChildStderr(proc.stderr);
68209
+ }
67391
68210
  if (config.interactive) {
67392
68211
  restoreTerminal = beginTerminalIsolation((entry) => {
67393
68212
  logStderr(`[Suppressed] ${entry.source}: ${entry.text.trimEnd()}`);
@@ -67525,7 +68344,7 @@ var restoreTerminal = null, macosKeychainAnthropicResult, defaultKeychainAnthrop
67525
68344
  macosKeychainAnthropicResult = false;
67526
68345
  }
67527
68346
  return macosKeychainAnthropicResult;
67528
- }, CLAUDISH_PLACEHOLDER_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx", CLAUDISH_PLACEHOLDER_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth", STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, ADVISOR_TOOL_ENV_VAR = "CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL", CLAUDISH_CHILD_ADVISOR_MODEL = "sonnet", SIGNAL_EXIT_NUMBERS;
68347
+ }, CLAUDISH_PLACEHOLDER_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx", CLAUDISH_PLACEHOLDER_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth", STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, ADVISOR_TOOL_ENV_VAR = "CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL", CLAUDISH_CHILD_ADVISOR_MODEL = "sonnet", CHILD_STDERR_NOISE, SIGNAL_EXIT_NUMBERS;
67529
68348
  var init_claude_runner = __esm(() => {
67530
68349
  init_model_catalog();
67531
68350
  init_config2();
@@ -67539,6 +68358,7 @@ var init_claude_runner = __esm(() => {
67539
68358
  init_terminal_isolation();
67540
68359
  init_theme_mode();
67541
68360
  STALE_TOKEN_FILE_MS = 7 * 24 * 60 * 60 * 1000;
68361
+ CHILD_STDERR_NOISE = [/^\[claude-code:unrecognized_model\]/];
67542
68362
  SIGNAL_EXIT_NUMBERS = {
67543
68363
  SIGHUP: 1,
67544
68364
  SIGINT: 2,