claudish 2.10.1 → 2.11.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 +536 -62
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34308,7 +34308,8 @@ var init_config = __esm(() => {
34308
34308
  OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
34309
34309
  OLLAMA_HOST: "OLLAMA_HOST",
34310
34310
  LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
34311
- VLLM_BASE_URL: "VLLM_BASE_URL"
34311
+ VLLM_BASE_URL: "VLLM_BASE_URL",
34312
+ CLAUDISH_SUMMARIZE_TOOLS: "CLAUDISH_SUMMARIZE_TOOLS"
34312
34313
  };
34313
34314
  OPENROUTER_HEADERS = {
34314
34315
  "HTTP-Referer": "https://github.com/MadAppGang/claude-code",
@@ -34743,6 +34744,10 @@ async function parseArgs(args) {
34743
34744
  config3.port = port;
34744
34745
  }
34745
34746
  }
34747
+ const envSummarizeTools = process.env[ENV.CLAUDISH_SUMMARIZE_TOOLS];
34748
+ if (envSummarizeTools === "true" || envSummarizeTools === "1") {
34749
+ config3.summarizeTools = true;
34750
+ }
34746
34751
  let i = 0;
34747
34752
  while (i < args.length) {
34748
34753
  const arg = args[i];
@@ -34859,6 +34864,8 @@ async function parseArgs(args) {
34859
34864
  await printAllModels(hasJsonFlag, forceUpdate);
34860
34865
  }
34861
34866
  process.exit(0);
34867
+ } else if (arg === "--summarize-tools") {
34868
+ config3.summarizeTools = true;
34862
34869
  } else {
34863
34870
  config3.claudeArgs = args.slice(i);
34864
34871
  break;
@@ -38867,26 +38874,222 @@ function transformOpenAIToClaude(claudeRequestInput) {
38867
38874
  }
38868
38875
  var init_transform = () => {};
38869
38876
 
38870
- // src/handlers/shared/openai-compat.ts
38871
- function validateToolArguments(toolName, argsStr, toolSchemas) {
38872
- const schema = toolSchemas?.find((t) => t.name === toolName);
38877
+ // src/handlers/shared/tool-call-recovery.ts
38878
+ function extractToolCallsFromText(text) {
38879
+ const extracted = [];
38880
+ const qwenPattern = /<function=([^>]+)>([\s\S]*?)(?=<function=|$)/gi;
38881
+ let match2;
38882
+ while ((match2 = qwenPattern.exec(text)) !== null) {
38883
+ const funcName = match2[1];
38884
+ const paramsText = match2[2];
38885
+ const args = {};
38886
+ const paramPattern = /<parameter=([^>]+)>\s*([\s\S]*?)(?=<parameter=|<function=|$)/gi;
38887
+ let paramMatch;
38888
+ while ((paramMatch = paramPattern.exec(paramsText)) !== null) {
38889
+ const paramName = paramMatch[1];
38890
+ const paramValue = paramMatch[2].trim();
38891
+ args[paramName] = paramValue;
38892
+ }
38893
+ if (funcName) {
38894
+ extracted.push({
38895
+ name: funcName,
38896
+ arguments: args,
38897
+ source: "xml_text"
38898
+ });
38899
+ log(`[ToolRecovery] Extracted Qwen-style tool call: ${funcName}`);
38900
+ }
38901
+ }
38902
+ const xmlPattern = /<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/gi;
38903
+ while ((match2 = xmlPattern.exec(text)) !== null) {
38904
+ try {
38905
+ const parsed = JSON.parse(match2[1]);
38906
+ if (parsed.name) {
38907
+ extracted.push({
38908
+ name: parsed.name,
38909
+ arguments: parsed.arguments || parsed.input || parsed.parameters || {},
38910
+ source: "xml_text"
38911
+ });
38912
+ }
38913
+ } catch (e) {}
38914
+ }
38915
+ const funcCallPattern = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"(?:arguments|input|parameters)"\s*:\s*(\{[\s\S]*?\})\s*\}/gi;
38916
+ while ((match2 = funcCallPattern.exec(text)) !== null) {
38917
+ try {
38918
+ const args = JSON.parse(match2[2]);
38919
+ extracted.push({
38920
+ name: match2[1],
38921
+ arguments: args,
38922
+ source: "json_text"
38923
+ });
38924
+ } catch (e) {}
38925
+ }
38926
+ const anthropicPattern = /\{\s*"type"\s*:\s*"tool_use"\s*,\s*"id"\s*:\s*"[^"]*"\s*,\s*"name"\s*:\s*"([^"]+)"\s*,\s*"input"\s*:\s*(\{[\s\S]*?\})\s*\}/gi;
38927
+ while ((match2 = anthropicPattern.exec(text)) !== null) {
38928
+ try {
38929
+ const args = JSON.parse(match2[2]);
38930
+ extracted.push({
38931
+ name: match2[1],
38932
+ arguments: args,
38933
+ source: "json_text"
38934
+ });
38935
+ } catch (e) {}
38936
+ }
38937
+ const jsonBlockPattern = /```(?:json)?\s*(\{[\s\S]*?\})\s*```/gi;
38938
+ while ((match2 = jsonBlockPattern.exec(text)) !== null) {
38939
+ try {
38940
+ const parsed = JSON.parse(match2[1]);
38941
+ if (parsed.name && (parsed.arguments || parsed.input || parsed.parameters)) {
38942
+ extracted.push({
38943
+ name: parsed.name,
38944
+ arguments: parsed.arguments || parsed.input || parsed.parameters,
38945
+ source: "json_text"
38946
+ });
38947
+ }
38948
+ } catch (e) {}
38949
+ }
38950
+ return extracted;
38951
+ }
38952
+ function inferMissingParameters(toolName, args, missingParams, context) {
38953
+ const inferred = { ...args };
38954
+ if (toolName === "Task") {
38955
+ if (missingParams.includes("subagent_type") && !inferred.subagent_type) {
38956
+ inferred.subagent_type = "general-purpose";
38957
+ log(`[ToolRecovery] Inferred subagent_type: general-purpose`);
38958
+ }
38959
+ let extractedTask = "";
38960
+ if (context) {
38961
+ const patterns = [
38962
+ /(?:I(?:'ll| will| need to| want to| am going to)|Let me|Going to)\s+([^.!?\n]+)/i,
38963
+ /(?:help you|assist with)\s+([^.!?\n]+)/i,
38964
+ /(?:explore|search|find|look for|investigate)\s+([^.!?\n]+)/i,
38965
+ /(?:implement|create|build|add|fix|update)\s+([^.!?\n]+)/i
38966
+ ];
38967
+ for (const pattern of patterns) {
38968
+ const match2 = context.match(pattern);
38969
+ if (match2 && match2[1] && match2[1].length > 10) {
38970
+ extractedTask = match2[1].trim();
38971
+ log(`[ToolRecovery] Extracted task from context: "${extractedTask.substring(0, 50)}..."`);
38972
+ break;
38973
+ }
38974
+ }
38975
+ if (!extractedTask && context.length > 20) {
38976
+ const sentences = context.split(/[.!?\n]+/).filter((s) => s.trim().length > 15);
38977
+ if (sentences.length > 0) {
38978
+ extractedTask = sentences[sentences.length - 1].trim();
38979
+ }
38980
+ }
38981
+ }
38982
+ if (missingParams.includes("prompt") && !inferred.prompt) {
38983
+ if (inferred.description && inferred.description !== "Execute task") {
38984
+ inferred.prompt = inferred.description;
38985
+ } else if (inferred.task) {
38986
+ inferred.prompt = inferred.task;
38987
+ } else if (extractedTask) {
38988
+ inferred.prompt = extractedTask;
38989
+ } else if (context && context.length > 20) {
38990
+ inferred.prompt = context.substring(0, 500).trim();
38991
+ }
38992
+ if (inferred.prompt) {
38993
+ log(`[ToolRecovery] Inferred prompt: "${inferred.prompt.substring(0, 50)}..."`);
38994
+ }
38995
+ }
38996
+ if (missingParams.includes("description") && !inferred.description) {
38997
+ if (inferred.prompt) {
38998
+ inferred.description = inferred.prompt.substring(0, 50).replace(/\s+/g, " ").trim();
38999
+ if (inferred.description.length < inferred.prompt.length) {
39000
+ inferred.description += "...";
39001
+ }
39002
+ } else if (extractedTask) {
39003
+ inferred.description = extractedTask.substring(0, 50).trim();
39004
+ } else {
39005
+ inferred.description = "Execute task";
39006
+ }
39007
+ log(`[ToolRecovery] Inferred description: ${inferred.description}`);
39008
+ }
39009
+ }
39010
+ if (toolName === "Bash") {
39011
+ if (missingParams.includes("command") && !inferred.command) {
39012
+ inferred.command = inferred.cmd || inferred.shell || inferred.script || "";
39013
+ }
39014
+ if (missingParams.includes("description") && !inferred.description) {
39015
+ if (inferred.command) {
39016
+ const cmd = inferred.command.split(" ")[0];
39017
+ inferred.description = `Run ${cmd} command`;
39018
+ }
39019
+ }
39020
+ }
39021
+ if (toolName === "Read") {
39022
+ if (missingParams.includes("file_path") && !inferred.file_path) {
39023
+ inferred.file_path = inferred.path || inferred.file || inferred.filename || "";
39024
+ }
39025
+ }
39026
+ if (toolName === "Write") {
39027
+ if (missingParams.includes("file_path") && !inferred.file_path) {
39028
+ inferred.file_path = inferred.path || inferred.file || inferred.filename || "";
39029
+ }
39030
+ if (missingParams.includes("content") && !inferred.content) {
39031
+ inferred.content = inferred.text || inferred.data || inferred.body || "";
39032
+ }
39033
+ }
39034
+ if (toolName === "Grep") {
39035
+ if (missingParams.includes("pattern") && !inferred.pattern) {
39036
+ inferred.pattern = inferred.query || inferred.search || inferred.regex || "";
39037
+ }
39038
+ }
39039
+ if (toolName === "Glob") {
39040
+ if (missingParams.includes("pattern") && !inferred.pattern) {
39041
+ inferred.pattern = inferred.glob || inferred.path || inferred.search || "**/*";
39042
+ }
39043
+ }
39044
+ return inferred;
39045
+ }
39046
+ function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent) {
39047
+ const schema = toolSchemas.find((t) => t.name === toolName);
38873
39048
  if (!schema?.input_schema) {
38874
- return { valid: true, missingParams: [], parsedArgs: {} };
39049
+ return { valid: true, args: {}, repaired: false, missingParams: [] };
38875
39050
  }
38876
39051
  let parsedArgs = {};
38877
39052
  try {
38878
39053
  parsedArgs = argsStr ? JSON.parse(argsStr) : {};
38879
39054
  } catch (e) {
38880
- return { valid: true, missingParams: [], parsedArgs: {} };
39055
+ if (textContent) {
39056
+ const extracted = extractToolCallsFromText(textContent);
39057
+ const matching = extracted.find((tc) => tc.name === toolName);
39058
+ if (matching) {
39059
+ parsedArgs = matching.arguments;
39060
+ log(`[ToolRecovery] Extracted tool args from text for ${toolName}`);
39061
+ }
39062
+ }
38881
39063
  }
38882
39064
  const required2 = schema.input_schema.required || [];
38883
- const missingParams = required2.filter((param) => {
38884
- return parsedArgs[param] === undefined || parsedArgs[param] === null || parsedArgs[param] === "";
38885
- });
39065
+ const missingParams = required2.filter((param) => parsedArgs[param] === undefined || parsedArgs[param] === null || parsedArgs[param] === "");
39066
+ if (missingParams.length === 0) {
39067
+ return { valid: true, args: parsedArgs, repaired: false, missingParams: [] };
39068
+ }
39069
+ const repairedArgs = inferMissingParameters(toolName, parsedArgs, missingParams, textContent);
39070
+ const stillMissing = required2.filter((param) => repairedArgs[param] === undefined || repairedArgs[param] === null || repairedArgs[param] === "");
39071
+ if (stillMissing.length === 0) {
39072
+ log(`[ToolRecovery] Successfully repaired tool call ${toolName}`);
39073
+ return { valid: true, args: repairedArgs, repaired: true, missingParams: [] };
39074
+ }
39075
+ return { valid: false, args: repairedArgs, repaired: false, missingParams: stillMissing };
39076
+ }
39077
+ var init_tool_call_recovery = __esm(() => {
39078
+ init_logger();
39079
+ });
39080
+
39081
+ // src/handlers/shared/openai-compat.ts
39082
+ function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
39083
+ const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
39084
+ if (result.repaired) {
39085
+ log(`[ToolValidation] Repaired tool call ${toolName} - inferred missing parameters`);
39086
+ }
38886
39087
  return {
38887
- valid: missingParams.length === 0,
38888
- missingParams,
38889
- parsedArgs
39088
+ valid: result.valid,
39089
+ missingParams: result.missingParams,
39090
+ parsedArgs: result.args,
39091
+ repaired: result.repaired,
39092
+ repairedArgs: result.repaired ? result.args : undefined
38890
39093
  };
38891
39094
  }
38892
39095
  function convertMessagesToOpenAI(req, modelId, filterIdentityFn) {
@@ -38983,16 +39186,44 @@ function processAssistantMessage(msg, messages) {
38983
39186
  messages.push({ role: "assistant", content: msg.content });
38984
39187
  }
38985
39188
  }
38986
- function convertToolsToOpenAI(req) {
39189
+ function convertToolsToOpenAI(req, summarize = false) {
38987
39190
  return req.tools?.map((tool) => ({
38988
39191
  type: "function",
38989
39192
  function: {
38990
39193
  name: tool.name,
38991
- description: tool.description,
38992
- parameters: removeUriFormat(tool.input_schema)
39194
+ description: summarize ? summarizeToolDescription(tool.name, tool.description) : tool.description,
39195
+ parameters: summarize ? summarizeToolParameters(tool.input_schema) : removeUriFormat(tool.input_schema)
38993
39196
  }
38994
39197
  })) || [];
38995
39198
  }
39199
+ function summarizeToolDescription(name, description) {
39200
+ if (!description)
39201
+ return name;
39202
+ let clean = description.replace(/```[\s\S]*?```/g, "").replace(/<[^>]+>/g, "").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
39203
+ const firstSentence = clean.match(/^[^.!?]+[.!?]/)?.[0] || clean;
39204
+ if (firstSentence.length > 150) {
39205
+ return firstSentence.slice(0, 147) + "...";
39206
+ }
39207
+ return firstSentence;
39208
+ }
39209
+ function summarizeToolParameters(schema) {
39210
+ if (!schema)
39211
+ return schema;
39212
+ const summarized = removeUriFormat({ ...schema });
39213
+ if (summarized.properties) {
39214
+ for (const [key, prop] of Object.entries(summarized.properties)) {
39215
+ const p = prop;
39216
+ if (p.description && p.description.length > 80) {
39217
+ const firstSentence = p.description.match(/^[^.!?]+[.!?]/)?.[0] || p.description;
39218
+ p.description = firstSentence.length > 80 ? firstSentence.slice(0, 77) + "..." : firstSentence;
39219
+ }
39220
+ if (p.enum && Array.isArray(p.enum) && p.enum.length > 5) {
39221
+ p.enum = p.enum.slice(0, 5);
39222
+ }
39223
+ }
39224
+ }
39225
+ return summarized;
39226
+ }
38996
39227
  function filterIdentity(content) {
38997
39228
  return content.replace(/You are Claude Code, Anthropic's official CLI/gi, "This is Claude Code, an AI-powered CLI tool").replace(/You are powered by the model named [^.]+\./gi, "You are powered by an AI model.").replace(/<claude_background_info>[\s\S]*?<\/claude_background_info>/gi, "").replace(/\n{3,}/g, `
38998
39229
 
@@ -39011,10 +39242,12 @@ function createStreamingState() {
39011
39242
  curIdx: 0,
39012
39243
  tools: new Map,
39013
39244
  toolIds: new Set,
39014
- lastActivity: Date.now()
39245
+ lastActivity: Date.now(),
39246
+ accumulatedText: ""
39015
39247
  };
39016
39248
  }
39017
39249
  function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate, toolSchemas) {
39250
+ log(`[Streaming] ===== HANDLER STARTED for ${target} =====`);
39018
39251
  let isClosed = false;
39019
39252
  let ping = null;
39020
39253
  const encoder = new TextEncoder;
@@ -39055,6 +39288,34 @@ data: ${JSON.stringify(d)}
39055
39288
  if (state.finalized)
39056
39289
  return;
39057
39290
  state.finalized = true;
39291
+ if (state.accumulatedText.length > 0) {
39292
+ const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
39293
+ log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
39294
+ }
39295
+ const textToolCalls = extractToolCallsFromText(state.accumulatedText);
39296
+ log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
39297
+ if (textToolCalls.length > 0) {
39298
+ log(`[Streaming] Found ${textToolCalls.length} text-based tool call(s), converting to structured format`);
39299
+ if (state.textStarted) {
39300
+ send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
39301
+ state.textStarted = false;
39302
+ }
39303
+ for (const tc of textToolCalls) {
39304
+ const toolIdx = state.curIdx++;
39305
+ const toolId = `tool_${Date.now()}_${toolIdx}`;
39306
+ send("content_block_start", {
39307
+ type: "content_block_start",
39308
+ index: toolIdx,
39309
+ content_block: { type: "tool_use", id: toolId, name: tc.name }
39310
+ });
39311
+ send("content_block_delta", {
39312
+ type: "content_block_delta",
39313
+ index: toolIdx,
39314
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(tc.arguments) }
39315
+ });
39316
+ send("content_block_stop", { type: "content_block_stop", index: toolIdx });
39317
+ }
39318
+ }
39058
39319
  if (state.reasoningStarted) {
39059
39320
  send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
39060
39321
  }
@@ -39073,15 +39334,23 @@ data: ${JSON.stringify(d)}
39073
39334
  if (reason === "error") {
39074
39335
  send("error", { type: "error", error: { type: "api_error", message: err } });
39075
39336
  } else {
39337
+ const stopReason = textToolCalls.length > 0 ? "tool_use" : "end_turn";
39076
39338
  send("message_delta", {
39077
39339
  type: "message_delta",
39078
- delta: { stop_reason: "end_turn", stop_sequence: null },
39340
+ delta: { stop_reason: stopReason, stop_sequence: null },
39079
39341
  usage: { output_tokens: state.usage?.completion_tokens || 0 }
39080
39342
  });
39081
39343
  send("message_stop", { type: "message_stop" });
39082
39344
  }
39083
- if (state.usage && onTokenUpdate) {
39084
- onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
39345
+ if (onTokenUpdate) {
39346
+ if (state.usage) {
39347
+ log(`[Streaming] Final usage: prompt=${state.usage.prompt_tokens || 0}, completion=${state.usage.completion_tokens || 0}`);
39348
+ onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
39349
+ } else {
39350
+ const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
39351
+ log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
39352
+ onTokenUpdate(100, estimatedOutputTokens);
39353
+ }
39085
39354
  }
39086
39355
  if (!isClosed) {
39087
39356
  try {
@@ -39117,8 +39386,10 @@ data: ${JSON.stringify(d)}
39117
39386
  }
39118
39387
  try {
39119
39388
  const chunk = JSON.parse(dataStr);
39120
- if (chunk.usage)
39389
+ if (chunk.usage) {
39121
39390
  state.usage = chunk.usage;
39391
+ log(`[Streaming] Usage data received: prompt=${chunk.usage.prompt_tokens}, completion=${chunk.usage.completion_tokens}, total=${chunk.usage.total_tokens}`);
39392
+ }
39122
39393
  const delta = chunk.choices?.[0]?.delta;
39123
39394
  if (delta) {
39124
39395
  if (middlewareManager) {
@@ -39132,25 +39403,30 @@ data: ${JSON.stringify(d)}
39132
39403
  const txt = delta.content || "";
39133
39404
  if (txt) {
39134
39405
  state.lastActivity = Date.now();
39135
- if (!state.textStarted) {
39136
- state.textIdx = state.curIdx++;
39137
- send("content_block_start", {
39138
- type: "content_block_start",
39139
- index: state.textIdx,
39140
- content_block: { type: "text", text: "" }
39141
- });
39142
- state.textStarted = true;
39143
- }
39144
39406
  const res = adapter.processTextContent(txt, "");
39145
39407
  if (res.cleanedText) {
39146
- send("content_block_delta", {
39147
- type: "content_block_delta",
39148
- index: state.textIdx,
39149
- delta: { type: "text_delta", text: res.cleanedText }
39150
- });
39408
+ state.accumulatedText += res.cleanedText;
39409
+ const hasToolPattern = /<function=[^>]+>/.test(state.accumulatedText);
39410
+ if (!hasToolPattern) {
39411
+ if (!state.textStarted) {
39412
+ state.textIdx = state.curIdx++;
39413
+ send("content_block_start", {
39414
+ type: "content_block_start",
39415
+ index: state.textIdx,
39416
+ content_block: { type: "text", text: "" }
39417
+ });
39418
+ state.textStarted = true;
39419
+ }
39420
+ send("content_block_delta", {
39421
+ type: "content_block_delta",
39422
+ index: state.textIdx,
39423
+ delta: { type: "text_delta", text: res.cleanedText }
39424
+ });
39425
+ }
39151
39426
  }
39152
39427
  }
39153
39428
  if (delta.tool_calls) {
39429
+ log(`[Streaming] Received ${delta.tool_calls.length} structured tool call(s) from model`);
39154
39430
  for (const tc of delta.tool_calls) {
39155
39431
  const idx = tc.index;
39156
39432
  let t = state.tools.get(idx);
@@ -39166,11 +39442,12 @@ data: ${JSON.stringify(d)}
39166
39442
  blockIndex: state.curIdx++,
39167
39443
  started: false,
39168
39444
  closed: false,
39169
- arguments: ""
39445
+ arguments: "",
39446
+ buffered: !!toolSchemas && toolSchemas.length > 0
39170
39447
  };
39171
39448
  state.tools.set(idx, t);
39172
39449
  }
39173
- if (!t.started) {
39450
+ if (!t.started && !t.buffered) {
39174
39451
  send("content_block_start", {
39175
39452
  type: "content_block_start",
39176
39453
  index: t.blockIndex,
@@ -39181,25 +39458,67 @@ data: ${JSON.stringify(d)}
39181
39458
  }
39182
39459
  if (tc.function?.arguments && t) {
39183
39460
  t.arguments += tc.function.arguments;
39184
- send("content_block_delta", {
39185
- type: "content_block_delta",
39186
- index: t.blockIndex,
39187
- delta: { type: "input_json_delta", partial_json: tc.function.arguments }
39188
- });
39461
+ if (!t.buffered) {
39462
+ send("content_block_delta", {
39463
+ type: "content_block_delta",
39464
+ index: t.blockIndex,
39465
+ delta: { type: "input_json_delta", partial_json: tc.function.arguments }
39466
+ });
39467
+ }
39189
39468
  }
39190
39469
  }
39191
39470
  }
39192
39471
  }
39193
39472
  if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39194
39473
  for (const t of Array.from(state.tools.values())) {
39195
- if (t.started && !t.closed) {
39474
+ if (!t.closed) {
39196
39475
  if (toolSchemas && toolSchemas.length > 0) {
39197
- const validation = validateToolArguments(t.name, t.arguments, toolSchemas);
39476
+ const validation = validateToolArguments(t.name, t.arguments, toolSchemas, state.accumulatedText);
39477
+ if (validation.repaired && validation.repairedArgs) {
39478
+ log(`[Streaming] Tool call ${t.name} was repaired with inferred parameters`);
39479
+ const repairedJson = JSON.stringify(validation.repairedArgs);
39480
+ log(`[Streaming] Sending repaired tool call: ${t.name} with args: ${repairedJson}`);
39481
+ if (t.buffered && !t.started) {
39482
+ send("content_block_start", {
39483
+ type: "content_block_start",
39484
+ index: t.blockIndex,
39485
+ content_block: { type: "tool_use", id: t.id, name: t.name }
39486
+ });
39487
+ send("content_block_delta", {
39488
+ type: "content_block_delta",
39489
+ index: t.blockIndex,
39490
+ delta: { type: "input_json_delta", partial_json: repairedJson }
39491
+ });
39492
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39493
+ t.started = true;
39494
+ t.closed = true;
39495
+ continue;
39496
+ }
39497
+ if (t.started) {
39498
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39499
+ const repairedIdx = state.curIdx++;
39500
+ const repairedId = `tool_repaired_${Date.now()}_${repairedIdx}`;
39501
+ send("content_block_start", {
39502
+ type: "content_block_start",
39503
+ index: repairedIdx,
39504
+ content_block: { type: "tool_use", id: repairedId, name: t.name }
39505
+ });
39506
+ send("content_block_delta", {
39507
+ type: "content_block_delta",
39508
+ index: repairedIdx,
39509
+ delta: { type: "input_json_delta", partial_json: repairedJson }
39510
+ });
39511
+ send("content_block_stop", { type: "content_block_stop", index: repairedIdx });
39512
+ t.closed = true;
39513
+ continue;
39514
+ }
39515
+ }
39198
39516
  if (!validation.valid) {
39199
- const errorIdx = state.curIdx++;
39517
+ log(`[Streaming] Tool call ${t.name} validation failed: ${validation.missingParams.join(", ")}`);
39518
+ const errorIdx = t.buffered ? t.blockIndex : state.curIdx++;
39200
39519
  const errorMsg = `
39201
39520
 
39202
- ⚠️ Tool call "${t.name}" failed validation: missing required parameters: ${validation.missingParams.join(", ")}. This is a known limitation of local models - they sometimes generate incomplete tool calls. Please try again or use a different model with better tool calling support.`;
39521
+ ⚠️ 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.`;
39203
39522
  send("content_block_start", {
39204
39523
  type: "content_block_start",
39205
39524
  index: errorIdx,
@@ -39211,12 +39530,34 @@ data: ${JSON.stringify(d)}
39211
39530
  delta: { type: "text_delta", text: errorMsg }
39212
39531
  });
39213
39532
  send("content_block_stop", { type: "content_block_stop", index: errorIdx });
39533
+ if (t.started && !t.buffered) {
39534
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39535
+ }
39536
+ t.closed = true;
39537
+ continue;
39538
+ }
39539
+ if (t.buffered && !t.started) {
39540
+ const argsJson = JSON.stringify(validation.parsedArgs);
39541
+ send("content_block_start", {
39542
+ type: "content_block_start",
39543
+ index: t.blockIndex,
39544
+ content_block: { type: "tool_use", id: t.id, name: t.name }
39545
+ });
39546
+ send("content_block_delta", {
39547
+ type: "content_block_delta",
39548
+ index: t.blockIndex,
39549
+ delta: { type: "input_json_delta", partial_json: argsJson }
39550
+ });
39551
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39552
+ t.started = true;
39214
39553
  t.closed = true;
39215
39554
  continue;
39216
39555
  }
39217
39556
  }
39218
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39219
- t.closed = true;
39557
+ if (t.started && !t.closed) {
39558
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39559
+ t.closed = true;
39560
+ }
39220
39561
  }
39221
39562
  }
39222
39563
  }
@@ -39243,6 +39584,8 @@ data: ${JSON.stringify(d)}
39243
39584
  }
39244
39585
  var init_openai_compat = __esm(() => {
39245
39586
  init_transform();
39587
+ init_logger();
39588
+ init_tool_call_recovery();
39246
39589
  });
39247
39590
 
39248
39591
  // src/handlers/openrouter-handler.ts
@@ -39663,18 +40006,24 @@ class LocalProviderHandler {
39663
40006
  port;
39664
40007
  healthChecked = false;
39665
40008
  isHealthy = false;
39666
- contextWindow = 8192;
40009
+ contextWindow = 32768;
39667
40010
  sessionInputTokens = 0;
39668
40011
  sessionOutputTokens = 0;
39669
- constructor(provider, modelName, port) {
40012
+ options;
40013
+ constructor(provider, modelName, port, options = {}) {
39670
40014
  this.provider = provider;
39671
40015
  this.modelName = modelName;
39672
40016
  this.port = port;
40017
+ this.options = options;
39673
40018
  this.adapterManager = new AdapterManager(modelName);
39674
40019
  this.middlewareManager = new MiddlewareManager;
39675
40020
  this.middlewareManager.initialize().catch((err) => {
39676
40021
  log(`[LocalProvider:${provider.name}] Middleware init error: ${err}`);
39677
40022
  });
40023
+ this.writeTokenFile(0, 0);
40024
+ if (options.summarizeTools) {
40025
+ log(`[LocalProvider:${provider.name}] Tool summarization enabled`);
40026
+ }
39678
40027
  }
39679
40028
  async checkHealth() {
39680
40029
  if (this.healthChecked)
@@ -39711,8 +40060,16 @@ class LocalProviderHandler {
39711
40060
  return false;
39712
40061
  }
39713
40062
  async fetchContextWindow() {
39714
- if (this.provider.name !== "ollama")
39715
- return;
40063
+ log(`[LocalProvider:${this.provider.name}] Fetching context window...`);
40064
+ if (this.provider.name === "ollama") {
40065
+ await this.fetchOllamaContextWindow();
40066
+ } else if (this.provider.name === "lmstudio") {
40067
+ await this.fetchLMStudioContextWindow();
40068
+ } else {
40069
+ log(`[LocalProvider:${this.provider.name}] No context window fetch for this provider, using default: ${this.contextWindow}`);
40070
+ }
40071
+ }
40072
+ async fetchOllamaContextWindow() {
39716
40073
  try {
39717
40074
  const response = await fetch(`${this.provider.baseUrl}/api/show`, {
39718
40075
  method: "POST",
@@ -39722,32 +40079,72 @@ class LocalProviderHandler {
39722
40079
  });
39723
40080
  if (response.ok) {
39724
40081
  const data = await response.json();
39725
- const ctxFromInfo = data.model_info?.["general.context_length"];
40082
+ let ctxFromInfo = data.model_info?.["general.context_length"];
40083
+ if (!ctxFromInfo && data.model_info) {
40084
+ for (const key of Object.keys(data.model_info)) {
40085
+ if (key.endsWith(".context_length")) {
40086
+ ctxFromInfo = data.model_info[key];
40087
+ break;
40088
+ }
40089
+ }
40090
+ }
39726
40091
  const ctxFromParams = data.parameters?.match(/num_ctx\s+(\d+)/)?.[1];
39727
40092
  if (ctxFromInfo) {
39728
- this.contextWindow = parseInt(ctxFromInfo, 10);
40093
+ this.contextWindow = parseInt(String(ctxFromInfo), 10);
39729
40094
  } else if (ctxFromParams) {
39730
40095
  this.contextWindow = parseInt(ctxFromParams, 10);
39731
40096
  } else {
39732
- this.contextWindow = 8192;
40097
+ log(`[LocalProvider:${this.provider.name}] No context info found, using default: ${this.contextWindow}`);
40098
+ }
40099
+ if (ctxFromInfo || ctxFromParams) {
40100
+ log(`[LocalProvider:${this.provider.name}] Context window: ${this.contextWindow}`);
39733
40101
  }
39734
- log(`[LocalProvider:${this.provider.name}] Context window: ${this.contextWindow}`);
39735
40102
  }
39736
40103
  } catch (e) {}
39737
40104
  }
40105
+ async fetchLMStudioContextWindow() {
40106
+ try {
40107
+ const response = await fetch(`${this.provider.baseUrl}/v1/models`, {
40108
+ method: "GET",
40109
+ signal: AbortSignal.timeout(3000)
40110
+ });
40111
+ if (response.ok) {
40112
+ const data = await response.json();
40113
+ log(`[LocalProvider:lmstudio] Models response: ${JSON.stringify(data).slice(0, 500)}`);
40114
+ const models = data.data || [];
40115
+ const targetModel = models.find((m) => m.id === this.modelName) || models.find((m) => m.id?.endsWith(`/${this.modelName}`)) || models.find((m) => this.modelName.includes(m.id));
40116
+ if (targetModel) {
40117
+ const ctxLength = targetModel.context_length || targetModel.max_context_length || targetModel.context_window || targetModel.max_tokens;
40118
+ if (ctxLength && typeof ctxLength === "number") {
40119
+ this.contextWindow = ctxLength;
40120
+ log(`[LocalProvider:lmstudio] Context window from model: ${this.contextWindow}`);
40121
+ return;
40122
+ }
40123
+ }
40124
+ this.contextWindow = 32768;
40125
+ log(`[LocalProvider:lmstudio] Using default context window: ${this.contextWindow}`);
40126
+ }
40127
+ } catch (e) {
40128
+ this.contextWindow = 32768;
40129
+ log(`[LocalProvider:lmstudio] Failed to fetch model info: ${e?.message || e}. Using default: ${this.contextWindow}`);
40130
+ }
40131
+ }
39738
40132
  writeTokenFile(input, output) {
39739
40133
  try {
39740
40134
  this.sessionInputTokens += input;
39741
40135
  this.sessionOutputTokens += output;
39742
- const total = this.sessionInputTokens + this.sessionOutputTokens;
39743
- const leftPct = this.contextWindow > 0 ? Math.max(0, Math.min(100, Math.round((this.contextWindow - total) / this.contextWindow * 100))) : 100;
40136
+ const sessionTotal = this.sessionInputTokens + this.sessionOutputTokens;
40137
+ const used = input + output;
40138
+ const leftPct = this.contextWindow > 0 ? Math.max(0, Math.min(100, Math.round((this.contextWindow - used) / this.contextWindow * 100))) : 100;
39744
40139
  const data = {
39745
40140
  input_tokens: this.sessionInputTokens,
39746
40141
  output_tokens: this.sessionOutputTokens,
39747
- total_tokens: total,
40142
+ total_tokens: sessionTotal,
39748
40143
  total_cost: 0,
39749
40144
  context_window: this.contextWindow,
39750
40145
  context_left_percent: leftPct,
40146
+ last_request_input: input,
40147
+ last_request_output: output,
39751
40148
  updated_at: Date.now()
39752
40149
  };
39753
40150
  writeFileSync9(join9(tmpdir3(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
@@ -39770,11 +40167,66 @@ class LocalProviderHandler {
39770
40167
  }
39771
40168
  const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
39772
40169
  const messages = convertMessagesToOpenAI(claudeRequest, target, filterIdentity);
39773
- const tools = convertToolsToOpenAI(claudeRequest);
40170
+ const tools = convertToolsToOpenAI(claudeRequest, this.options.summarizeTools);
39774
40171
  const finalTools = this.provider.capabilities.supportsTools ? tools : [];
39775
40172
  if (tools.length > 0 && !this.provider.capabilities.supportsTools) {
39776
40173
  log(`[LocalProvider:${this.provider.name}] Tools stripped (not supported)`);
39777
40174
  }
40175
+ if (tools.length > 0 && this.options.summarizeTools) {
40176
+ log(`[LocalProvider:${this.provider.name}] Tools summarized (${tools.length} tools)`);
40177
+ }
40178
+ if (messages.length > 0 && messages[0].role === "system") {
40179
+ let guidance = `
40180
+
40181
+ IMPORTANT INSTRUCTIONS FOR THIS MODEL:
40182
+
40183
+ 1. OUTPUT BEHAVIOR:
40184
+ - NEVER output your internal reasoning, thinking process, or chain-of-thought as visible text.
40185
+ - Only output your final response, actions, or tool calls.
40186
+ - Do NOT ramble or speculate about what the user might want.
40187
+
40188
+ 2. CONVERSATION HANDLING:
40189
+ - Always look back at the ORIGINAL user request in the conversation history.
40190
+ - When you receive results from a Task/agent you called, SYNTHESIZE those results and continue fulfilling the user's original request.
40191
+ - Do NOT ask "What would you like help with?" if there's already a user request in the conversation.
40192
+ - Only ask for clarification if the FIRST user message in the conversation is unclear.
40193
+ - After calling tools or agents, continue with the next step - don't restart or ask what to do.
40194
+
40195
+ 3. CRITICAL - AFTER TOOL RESULTS:
40196
+ - When you see tool results (like file lists, search results, or command output), ALWAYS continue working.
40197
+ - Analyze the results and take the next action toward completing the user's request.
40198
+ - If the user asked for "evaluation and suggestions", you MUST provide analysis and recommendations after seeing the data.
40199
+ - NEVER stop after just calling one tool - continue until you've fully addressed the user's request.
40200
+ - If you called a Glob/Search and got files, READ important files next, then ANALYZE, then SUGGEST improvements.`;
40201
+ if (finalTools.length > 0) {
40202
+ const isQwen = target.toLowerCase().includes("qwen");
40203
+ if (isQwen) {
40204
+ guidance += `
40205
+
40206
+ 4. TOOL CALLING FORMAT (CRITICAL FOR QWEN):
40207
+ You MUST use proper OpenAI-style function calling. Do NOT output tool calls as XML text.
40208
+ When you want to call a tool, use the API's tool_calls mechanism, NOT text like <function=...>.
40209
+ The tool calls must be structured JSON in the API response, not XML in your text output.
40210
+
40211
+ If you cannot use structured tool_calls, format as JSON:
40212
+ {"name": "tool_name", "arguments": {"param1": "value1", "param2": "value2"}}
40213
+
40214
+ 5. TOOL PARAMETER REQUIREMENTS:`;
40215
+ } else {
40216
+ guidance += `
40217
+
40218
+ 4. TOOL CALLING REQUIREMENTS:`;
40219
+ }
40220
+ guidance += `
40221
+ - When calling tools, you MUST include ALL required parameters. Incomplete tool calls will fail.
40222
+ - For Task: always include "description" (3-5 words), "prompt" (detailed instructions), and "subagent_type"
40223
+ - For Bash: always include "command" and "description"
40224
+ - For Read/Write/Edit: always include the full "file_path"
40225
+ - For Grep/Glob: always include "pattern"
40226
+ - Ensure your tool call JSON is complete with all required fields before submitting.`;
40227
+ }
40228
+ messages[0].content += guidance;
40229
+ }
39778
40230
  const openAIPayload = {
39779
40231
  model: target,
39780
40232
  messages,
@@ -39784,6 +40236,11 @@ class LocalProviderHandler {
39784
40236
  tools: finalTools.length > 0 ? finalTools : undefined,
39785
40237
  stream_options: this.provider.capabilities.supportsStreaming ? { include_usage: true } : undefined
39786
40238
  };
40239
+ if (this.provider.name === "ollama") {
40240
+ const numCtx = Math.max(this.contextWindow, 32768);
40241
+ openAIPayload.options = { num_ctx: numCtx };
40242
+ log(`[LocalProvider:${this.provider.name}] Setting num_ctx: ${numCtx} (detected: ${this.contextWindow})`);
40243
+ }
39787
40244
  if (claudeRequest.tool_choice && finalTools.length > 0) {
39788
40245
  const { type, name } = claudeRequest.tool_choice;
39789
40246
  if (type === "tool" && name) {
@@ -39803,6 +40260,12 @@ class LocalProviderHandler {
39803
40260
  stream: openAIPayload.stream
39804
40261
  });
39805
40262
  const apiUrl = `${this.provider.baseUrl}${this.provider.apiPath}`;
40263
+ log(`[LocalProvider:${this.provider.name}] Tools: ${openAIPayload.tools?.length || 0}, Messages: ${messages.length}`);
40264
+ if (openAIPayload.tools?.length > 0) {
40265
+ log(`[LocalProvider:${this.provider.name}] First tool: ${openAIPayload.tools[0]?.function?.name || "unknown"}`);
40266
+ }
40267
+ console.log(`[LocalProvider:${this.provider.name}] ===== ABOUT TO FETCH from ${apiUrl} =====`);
40268
+ log(`[LocalProvider:${this.provider.name}] ===== ABOUT TO FETCH from ${apiUrl} =====`);
39806
40269
  try {
39807
40270
  const response = await fetch(apiUrl, {
39808
40271
  method: "POST",
@@ -39811,14 +40274,19 @@ class LocalProviderHandler {
39811
40274
  },
39812
40275
  body: JSON.stringify(openAIPayload)
39813
40276
  });
40277
+ log(`[LocalProvider:${this.provider.name}] ===== FETCH COMPLETED, status: ${response.status} =====`);
39814
40278
  if (!response.ok) {
39815
40279
  const errorBody = await response.text();
40280
+ log(`[LocalProvider:${this.provider.name}] ERROR: ${errorBody.slice(0, 200)}`);
39816
40281
  return this.handleErrorResponse(c, response.status, errorBody);
39817
40282
  }
40283
+ log(`[LocalProvider:${this.provider.name}] Response OK, proceeding to streaming...`);
39818
40284
  if (droppedParams.length > 0) {
39819
40285
  c.header("X-Dropped-Params", droppedParams.join(", "));
39820
40286
  }
40287
+ log(`[LocalProvider:${this.provider.name}] Streaming: ${openAIPayload.stream}`);
39821
40288
  if (openAIPayload.stream) {
40289
+ log(`[LocalProvider:${this.provider.name}] ===== ENTERING STREAMING HANDLER =====`);
39822
40290
  return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager, (input, output) => this.writeTokenFile(input, output), claudeRequest.tools);
39823
40291
  }
39824
40292
  const data = await response.json();
@@ -39986,7 +40454,7 @@ var exports_proxy_server = {};
39986
40454
  __export(exports_proxy_server, {
39987
40455
  createProxyServer: () => createProxyServer
39988
40456
  });
39989
- async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey, modelMap) {
40457
+ async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey, modelMap, options = {}) {
39990
40458
  const nativeHandler = new NativeHandler(anthropicApiKey);
39991
40459
  const openRouterHandlers = new Map;
39992
40460
  const localProviderHandlers = new Map;
@@ -39996,13 +40464,16 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
39996
40464
  }
39997
40465
  return openRouterHandlers.get(targetModel);
39998
40466
  };
40467
+ const localProviderOptions = {
40468
+ summarizeTools: options.summarizeTools
40469
+ };
39999
40470
  const getLocalProviderHandler = (targetModel) => {
40000
40471
  if (localProviderHandlers.has(targetModel)) {
40001
40472
  return localProviderHandlers.get(targetModel);
40002
40473
  }
40003
40474
  const resolved = resolveProvider(targetModel);
40004
40475
  if (resolved) {
40005
- const handler = new LocalProviderHandler(resolved.provider, resolved.modelName, port);
40476
+ const handler = new LocalProviderHandler(resolved.provider, resolved.modelName, port, localProviderOptions);
40006
40477
  localProviderHandlers.set(targetModel, handler);
40007
40478
  log(`[Proxy] Created local provider handler: ${resolved.provider.name}/${resolved.modelName}`);
40008
40479
  return handler;
@@ -40010,7 +40481,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
40010
40481
  const urlParsed = parseUrlModel(targetModel);
40011
40482
  if (urlParsed) {
40012
40483
  const provider = createUrlProvider(urlParsed);
40013
- const handler = new LocalProviderHandler(provider, urlParsed.modelName, port);
40484
+ const handler = new LocalProviderHandler(provider, urlParsed.modelName, port, localProviderOptions);
40014
40485
  localProviderHandlers.set(targetModel, handler);
40015
40486
  log(`[Proxy] Created URL-based local provider handler: ${urlParsed.baseUrl}/${urlParsed.modelName}`);
40016
40487
  return handler;
@@ -40289,6 +40760,7 @@ var init_update_checker = __esm(() => {
40289
40760
 
40290
40761
  // src/index.ts
40291
40762
  var import_dotenv2 = __toESM(require_main(), 1);
40763
+ console.log("===== CLAUDISH FRESH START - CODE UPDATED =====");
40292
40764
  import_dotenv2.config();
40293
40765
  var isMcpMode = process.argv.includes("--mcp");
40294
40766
  var args = process.argv.slice(2);
@@ -40367,6 +40839,8 @@ async function runCli() {
40367
40839
  sonnet: cliConfig.modelSonnet,
40368
40840
  haiku: cliConfig.modelHaiku,
40369
40841
  subagent: cliConfig.modelSubagent
40842
+ }, {
40843
+ summarizeTools: cliConfig.summarizeTools
40370
40844
  });
40371
40845
  let exitCode = 0;
40372
40846
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "2.10.1",
3
+ "version": "2.11.0",
4
4
  "description": "Run Claude Code with any OpenRouter model - CLI tool and MCP server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",