claudish 2.9.0 → 2.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +769 -602
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34324,8 +34324,11 @@ __export(exports_claude_runner, {
34324
34324
  });
34325
34325
  import { spawn } from "node:child_process";
34326
34326
  import { writeFileSync as writeFileSync4, unlinkSync } from "node:fs";
34327
- import { tmpdir, platform } from "node:os";
34327
+ import { tmpdir } from "node:os";
34328
34328
  import { join as join4 } from "node:path";
34329
+ function isWindows() {
34330
+ return process.platform === "win32";
34331
+ }
34329
34332
  function createStatusLineScript(tokenFilePath) {
34330
34333
  const tempDir = tmpdir();
34331
34334
  const timestamp = Date.now();
@@ -34353,6 +34356,7 @@ process.stdin.on('end', () => {
34353
34356
 
34354
34357
  let ctx = 100, cost = 0;
34355
34358
  const model = process.env.CLAUDISH_ACTIVE_MODEL_NAME || 'unknown';
34359
+ const isLocal = process.env.CLAUDISH_IS_LOCAL === 'true';
34356
34360
 
34357
34361
  try {
34358
34362
  const tokens = JSON.parse(fs.readFileSync('${escapedTokenPath}', 'utf-8'));
@@ -34365,8 +34369,8 @@ process.stdin.on('end', () => {
34365
34369
  } catch {}
34366
34370
  }
34367
34371
 
34368
- const costStr = cost.toFixed(3);
34369
- console.log(\`\${CYAN}\${BOLD}\${dir}\${RESET} \${DIM}•\${RESET} \${YELLOW}\${model}\${RESET} \${DIM}•\${RESET} \${GREEN}$\${costStr}\${RESET} \${DIM}•\${RESET} \${MAGENTA}\${ctx}%\${RESET}\`);
34372
+ const costDisplay = isLocal ? 'LOCAL' : ('$' + cost.toFixed(3));
34373
+ console.log(\`\${CYAN}\${BOLD}\${dir}\${RESET} \${DIM}•\${RESET} \${YELLOW}\${model}\${RESET} \${DIM}•\${RESET} \${GREEN}\${costDisplay}\${RESET} \${DIM}•\${RESET} \${MAGENTA}\${ctx}%\${RESET}\`);
34370
34374
  } catch (e) {
34371
34375
  console.log('claudish');
34372
34376
  }
@@ -34381,7 +34385,7 @@ function createTempSettingsFile(modelDisplay, port) {
34381
34385
  const tempPath = join4(tempDir, `claudish-settings-${timestamp}.json`);
34382
34386
  const tokenFilePath = join4(tempDir, `claudish-tokens-${port}.json`);
34383
34387
  let statusCommand;
34384
- if (isWindows) {
34388
+ if (isWindows()) {
34385
34389
  const scriptPath = createStatusLineScript(tokenFilePath);
34386
34390
  statusCommand = `node "${scriptPath}"`;
34387
34391
  } else {
@@ -34392,7 +34396,7 @@ function createTempSettingsFile(modelDisplay, port) {
34392
34396
  const DIM2 = "\\033[2m";
34393
34397
  const RESET2 = "\\033[0m";
34394
34398
  const BOLD2 = "\\033[1m";
34395
- statusCommand = `JSON=$(cat) && DIR=$(basename "$(pwd)") && [ \${#DIR} -gt 15 ] && DIR="\${DIR:0:12}..." || true && CTX=100 && COST="0" && if [ -f "${tokenFilePath}" ]; then TOKENS=$(cat "${tokenFilePath}" 2>/dev/null) && REAL_COST=$(echo "$TOKENS" | grep -o '"total_cost":[0-9.]*' | cut -d: -f2) && REAL_CTX=$(echo "$TOKENS" | grep -o '"context_left_percent":[0-9]*' | grep -o '[0-9]*') && if [ ! -z "$REAL_COST" ]; then COST="$REAL_COST"; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && if [ ! -z "$REAL_CTX" ]; then CTX="$REAL_CTX"; fi; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && [ -z "$COST" ] && COST="0" || true && printf "${CYAN2}${BOLD2}%s${RESET2} ${DIM2}•${RESET2} ${YELLOW2}%s${RESET2} ${DIM2}•${RESET2} ${GREEN2}\\$%.3f${RESET2} ${DIM2}•${RESET2} ${MAGENTA}%s%%${RESET2}\\n" "$DIR" "$CLAUDISH_ACTIVE_MODEL_NAME" "$COST" "$CTX"`;
34399
+ statusCommand = `JSON=$(cat) && DIR=$(basename "$(pwd)") && [ \${#DIR} -gt 15 ] && DIR="\${DIR:0:12}..." || true && CTX=100 && COST="0" && if [ -f "${tokenFilePath}" ]; then TOKENS=$(cat "${tokenFilePath}" 2>/dev/null) && REAL_COST=$(echo "$TOKENS" | grep -o '"total_cost":[0-9.]*' | cut -d: -f2) && REAL_CTX=$(echo "$TOKENS" | grep -o '"context_left_percent":[0-9]*' | grep -o '[0-9]*') && if [ ! -z "$REAL_COST" ]; then COST="$REAL_COST"; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && if [ ! -z "$REAL_CTX" ]; then CTX="$REAL_CTX"; fi; else COST=$(echo "$JSON" | grep -o '"total_cost_usd":[0-9.]*' | cut -d: -f2); fi && [ -z "$COST" ] && COST="0" || true && if [ "$CLAUDISH_IS_LOCAL" = "true" ]; then COST_DISPLAY="LOCAL"; else COST_DISPLAY=$(printf "\\$%.3f" "$COST"); fi && printf "${CYAN2}${BOLD2}%s${RESET2} ${DIM2}•${RESET2} ${YELLOW2}%s${RESET2} ${DIM2}•${RESET2} ${GREEN2}%s${RESET2} ${DIM2}•${RESET2} ${MAGENTA}%s%%${RESET2}\\n" "$DIR" "$CLAUDISH_ACTIVE_MODEL_NAME" "$COST_DISPLAY" "$CTX"`;
34396
34400
  }
34397
34401
  const settings = {
34398
34402
  statusLine: {
@@ -34438,10 +34442,12 @@ async function runClaudeWithProxy(config3, proxyUrl) {
34438
34442
  claudeArgs.push(...config3.claudeArgs);
34439
34443
  }
34440
34444
  }
34445
+ const isLocalModel = modelId.startsWith("ollama/") || modelId.startsWith("ollama:") || modelId.startsWith("lmstudio/") || modelId.startsWith("lmstudio:") || modelId.startsWith("vllm/") || modelId.startsWith("vllm:") || modelId.startsWith("http://") || modelId.startsWith("https://");
34441
34446
  const env = {
34442
34447
  ...process.env,
34443
34448
  ANTHROPIC_BASE_URL: proxyUrl,
34444
34449
  [ENV.CLAUDISH_ACTIVE_MODEL_NAME]: modelId,
34450
+ CLAUDISH_IS_LOCAL: isLocalModel ? "true" : "false",
34445
34451
  [ENV.ANTHROPIC_MODEL]: modelId,
34446
34452
  [ENV.ANTHROPIC_SMALL_FAST_MODEL]: modelId
34447
34453
  };
@@ -34468,7 +34474,7 @@ async function runClaudeWithProxy(config3, proxyUrl) {
34468
34474
  const proc = spawn("claude", claudeArgs, {
34469
34475
  env,
34470
34476
  stdio: "inherit",
34471
- shell: isWindows
34477
+ shell: isWindows()
34472
34478
  });
34473
34479
  setupSignalHandlers(proc, tempSettingsPath, config3.quiet);
34474
34480
  const exitCode = await new Promise((resolve) => {
@@ -34482,7 +34488,7 @@ async function runClaudeWithProxy(config3, proxyUrl) {
34482
34488
  return exitCode;
34483
34489
  }
34484
34490
  function setupSignalHandlers(proc, tempSettingsPath, quiet) {
34485
- const signals2 = isWindows ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
34491
+ const signals2 = isWindows() ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
34486
34492
  for (const signal of signals2) {
34487
34493
  process.on(signal, () => {
34488
34494
  if (!quiet) {
@@ -34515,10 +34521,8 @@ async function checkClaudeInstalled() {
34515
34521
  return false;
34516
34522
  }
34517
34523
  }
34518
- var isWindows;
34519
34524
  var init_claude_runner = __esm(() => {
34520
34525
  init_config();
34521
- isWindows = platform() === "win32";
34522
34526
  });
34523
34527
 
34524
34528
  // src/types.ts
@@ -34926,17 +34930,40 @@ async function fetchOllamaModels() {
34926
34930
  return [];
34927
34931
  const data = await response.json();
34928
34932
  const models = data.models || [];
34929
- return models.map((m) => ({
34930
- id: `ollama/${m.name}`,
34931
- name: m.name,
34932
- description: `Local Ollama model (${m.details?.parameter_size || "unknown size"})`,
34933
- provider: "ollama",
34934
- context_length: null,
34935
- pricing: { prompt: "0", completion: "0" },
34936
- isLocal: true,
34937
- details: m.details,
34938
- size: m.size
34933
+ const modelsWithCapabilities = await Promise.all(models.map(async (m) => {
34934
+ let capabilities = [];
34935
+ try {
34936
+ const showResponse = await fetch(`${ollamaHost}/api/show`, {
34937
+ method: "POST",
34938
+ headers: { "Content-Type": "application/json" },
34939
+ body: JSON.stringify({ name: m.name }),
34940
+ signal: AbortSignal.timeout(2000)
34941
+ });
34942
+ if (showResponse.ok) {
34943
+ const showData = await showResponse.json();
34944
+ capabilities = showData.capabilities || [];
34945
+ }
34946
+ } catch {}
34947
+ const supportsTools = capabilities.includes("tools");
34948
+ const isEmbeddingModel = capabilities.includes("embedding") || m.name.toLowerCase().includes("embed");
34949
+ const sizeInfo = m.details?.parameter_size || "unknown size";
34950
+ const toolsIndicator = supportsTools ? "✓ tools" : "✗ no tools";
34951
+ return {
34952
+ id: `ollama/${m.name}`,
34953
+ name: m.name,
34954
+ description: `Local Ollama model (${sizeInfo}, ${toolsIndicator})`,
34955
+ provider: "ollama",
34956
+ context_length: null,
34957
+ pricing: { prompt: "0", completion: "0" },
34958
+ isLocal: true,
34959
+ supportsTools,
34960
+ isEmbeddingModel,
34961
+ capabilities,
34962
+ details: m.details,
34963
+ size: m.size
34964
+ };
34939
34965
  }));
34966
+ return modelsWithCapabilities.filter((m) => !m.isEmbeddingModel);
34940
34967
  } catch (e) {
34941
34968
  return [];
34942
34969
  }
@@ -34990,6 +35017,10 @@ async function searchAndPrintModels(query, forceUpdate) {
34990
35017
  console.log(`No models found matching "${query}"`);
34991
35018
  return;
34992
35019
  }
35020
+ const RED = "\x1B[31m";
35021
+ const GREEN2 = "\x1B[32m";
35022
+ const RESET2 = "\x1B[0m";
35023
+ const DIM2 = "\x1B[2m";
34993
35024
  console.log(`
34994
35025
  Found ${results.length} matching models:
34995
35026
  `);
@@ -35020,9 +35051,17 @@ Found ${results.length} matching models:
35020
35051
  const contextLen = model.context_length || model.top_provider?.context_length || 0;
35021
35052
  const context = contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A";
35022
35053
  const contextPadded = context.padEnd(7);
35023
- console.log(` ${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
35054
+ if (model.isLocal && model.supportsTools === false) {
35055
+ console.log(` ${RED}${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}% ✗ no tools${RESET2}`);
35056
+ } else if (model.isLocal && model.supportsTools === true) {
35057
+ console.log(` ${GREEN2}${modelIdPadded}${RESET2} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
35058
+ } else {
35059
+ console.log(` ${modelIdPadded} ${providerPadded} ${pricingPadded} ${contextPadded} ${(score * 100).toFixed(0)}%`);
35060
+ }
35024
35061
  }
35025
35062
  console.log("");
35063
+ console.log(`${DIM2}Local models: ${RED}red${RESET2}${DIM2} = no tool support (incompatible), ${GREEN2}green${RESET2}${DIM2} = compatible${RESET2}`);
35064
+ console.log("");
35026
35065
  console.log("Use a model: claudish --model <model-id>");
35027
35066
  console.log("Local models: claudish --model ollama/<model-name>");
35028
35067
  }
@@ -35077,23 +35116,37 @@ async function printAllModels(jsonOutput, forceUpdate) {
35077
35116
  }, null, 2));
35078
35117
  return;
35079
35118
  }
35119
+ const RED = "\x1B[31m";
35120
+ const GREEN2 = "\x1B[32m";
35121
+ const RESET2 = "\x1B[0m";
35122
+ const DIM2 = "\x1B[2m";
35080
35123
  if (ollamaModels.length > 0) {
35124
+ const toolCapableCount = ollamaModels.filter((m) => m.supportsTools).length;
35081
35125
  console.log(`
35082
- \uD83C\uDFE0 LOCAL OLLAMA MODELS (${ollamaModels.length} installed):
35126
+ \uD83C\uDFE0 LOCAL OLLAMA MODELS (${ollamaModels.length} installed, ${toolCapableCount} with tool support):
35083
35127
  `);
35084
- console.log(" " + "─".repeat(70));
35128
+ console.log(" Model Size Params Tools");
35129
+ console.log(" " + "─".repeat(76));
35085
35130
  for (const model of ollamaModels) {
35086
- const shortId = model.name;
35087
- const modelId = shortId.length > 40 ? shortId.substring(0, 37) + "..." : shortId;
35088
- const modelIdPadded = modelId.padEnd(42);
35131
+ const fullId = model.id;
35132
+ const modelId = fullId.length > 35 ? fullId.substring(0, 32) + "..." : fullId;
35133
+ const modelIdPadded = modelId.padEnd(38);
35089
35134
  const size = model.size ? `${(model.size / 1e9).toFixed(1)}GB` : "N/A";
35090
35135
  const sizePadded = size.padEnd(12);
35091
35136
  const params = model.details?.parameter_size || "N/A";
35092
35137
  const paramsPadded = params.padEnd(8);
35093
- console.log(` ${modelIdPadded} ${sizePadded} ${paramsPadded}`);
35138
+ if (model.supportsTools) {
35139
+ console.log(` ${modelIdPadded} ${sizePadded} ${paramsPadded} ${GREEN2}✓${RESET2}`);
35140
+ } else {
35141
+ console.log(` ${RED}${modelIdPadded} ${sizePadded} ${paramsPadded} ✗ no tools${RESET2}`);
35142
+ }
35094
35143
  }
35095
35144
  console.log("");
35145
+ console.log(` ${GREEN2}✓${RESET2} = Compatible with Claude Code (supports tool calling)`);
35146
+ console.log(` ${RED}✗${RESET2} = Not compatible ${DIM2}(Claude Code requires tool support)${RESET2}`);
35147
+ console.log("");
35096
35148
  console.log(" Use: claudish --model ollama/<model-name>");
35149
+ console.log(" Pull a compatible model: ollama pull llama3.2");
35097
35150
  } else {
35098
35151
  console.log(`
35099
35152
  \uD83C\uDFE0 LOCAL OLLAMA: Not running or no models installed`);
@@ -38814,454 +38867,88 @@ function transformOpenAIToClaude(claudeRequestInput) {
38814
38867
  }
38815
38868
  var init_transform = () => {};
38816
38869
 
38817
- // src/handlers/openrouter-handler.ts
38818
- import { writeFileSync as writeFileSync8 } from "node:fs";
38819
- import { tmpdir as tmpdir2 } from "node:os";
38820
- import { join as join8 } from "node:path";
38821
-
38822
- class OpenRouterHandler {
38823
- targetModel;
38824
- apiKey;
38825
- adapterManager;
38826
- middlewareManager;
38827
- contextWindowCache = new Map;
38828
- port;
38829
- sessionTotalCost = 0;
38830
- CLAUDE_INTERNAL_CONTEXT_MAX = 200000;
38831
- constructor(targetModel, apiKey, port) {
38832
- this.targetModel = targetModel;
38833
- this.apiKey = apiKey;
38834
- this.port = port;
38835
- this.adapterManager = new AdapterManager(targetModel);
38836
- this.middlewareManager = new MiddlewareManager;
38837
- this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
38838
- this.middlewareManager.initialize().catch((err) => log(`[Handler:${targetModel}] Middleware init error: ${err}`));
38839
- this.fetchContextWindow(targetModel);
38840
- }
38841
- async fetchContextWindow(model) {
38842
- if (this.contextWindowCache.has(model))
38843
- return;
38844
- try {
38845
- const limit = await fetchModelContextWindow(model);
38846
- this.contextWindowCache.set(model, limit);
38847
- } catch (e) {}
38848
- }
38849
- getTokenScaleFactor(model) {
38850
- const limit = this.contextWindowCache.get(model) || 200000;
38851
- return limit === 0 ? 1 : this.CLAUDE_INTERNAL_CONTEXT_MAX / limit;
38852
- }
38853
- writeTokenFile(input, output) {
38854
- try {
38855
- const total = input + output;
38856
- const limit = this.contextWindowCache.get(this.targetModel) || 200000;
38857
- const leftPct = limit > 0 ? Math.max(0, Math.min(100, Math.round((limit - total) / limit * 100))) : 100;
38858
- const data = {
38859
- input_tokens: input,
38860
- output_tokens: output,
38861
- total_tokens: total,
38862
- total_cost: this.sessionTotalCost,
38863
- context_window: limit,
38864
- context_left_percent: leftPct,
38865
- updated_at: Date.now()
38866
- };
38867
- writeFileSync8(join8(tmpdir2(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
38868
- } catch (e) {}
38870
+ // src/handlers/shared/openai-compat.ts
38871
+ function validateToolArguments(toolName, argsStr, toolSchemas) {
38872
+ const schema = toolSchemas?.find((t) => t.name === toolName);
38873
+ if (!schema?.input_schema) {
38874
+ return { valid: true, missingParams: [], parsedArgs: {} };
38869
38875
  }
38870
- async handle(c, payload) {
38871
- const claudePayload = payload;
38872
- const target = this.targetModel;
38873
- await this.fetchContextWindow(target);
38874
- logStructured(`OpenRouter Request`, { targetModel: target, originalModel: claudePayload.model });
38875
- const { claudeRequest, droppedParams } = transformOpenAIToClaude(claudePayload);
38876
- const messages = this.convertMessages(claudeRequest, target);
38877
- const tools = this.convertTools(claudeRequest);
38878
- const supportsReasoning = await doesModelSupportReasoning(target);
38879
- const openRouterPayload = {
38880
- model: target,
38881
- messages,
38882
- temperature: claudeRequest.temperature ?? 1,
38883
- stream: true,
38884
- max_tokens: claudeRequest.max_tokens,
38885
- tools: tools.length > 0 ? tools : undefined,
38886
- stream_options: { include_usage: true }
38887
- };
38888
- if (supportsReasoning)
38889
- openRouterPayload.include_reasoning = true;
38890
- if (claudeRequest.thinking)
38891
- openRouterPayload.thinking = claudeRequest.thinking;
38892
- if (claudeRequest.tool_choice) {
38893
- const { type, name } = claudeRequest.tool_choice;
38894
- if (type === "tool" && name)
38895
- openRouterPayload.tool_choice = { type: "function", function: { name } };
38896
- else if (type === "auto" || type === "none")
38897
- openRouterPayload.tool_choice = type;
38898
- }
38899
- const adapter = this.adapterManager.getAdapter();
38900
- if (typeof adapter.reset === "function")
38901
- adapter.reset();
38902
- adapter.prepareRequest(openRouterPayload, claudeRequest);
38903
- await this.middlewareManager.beforeRequest({ modelId: target, messages, tools, stream: true });
38904
- const response = await fetch(OPENROUTER_API_URL2, {
38905
- method: "POST",
38906
- headers: {
38907
- "Content-Type": "application/json",
38908
- Authorization: `Bearer ${this.apiKey}`,
38909
- ...OPENROUTER_HEADERS2
38910
- },
38911
- body: JSON.stringify(openRouterPayload)
38912
- });
38913
- if (!response.ok)
38914
- return c.json({ error: await response.text() }, response.status);
38915
- if (droppedParams.length > 0)
38916
- c.header("X-Dropped-Params", droppedParams.join(", "));
38917
- return this.handleStreamingResponse(c, response, adapter, target, claudeRequest);
38876
+ let parsedArgs = {};
38877
+ try {
38878
+ parsedArgs = argsStr ? JSON.parse(argsStr) : {};
38879
+ } catch (e) {
38880
+ return { valid: true, missingParams: [], parsedArgs: {} };
38918
38881
  }
38919
- convertMessages(req, modelId) {
38920
- const messages = [];
38921
- if (req.system) {
38922
- let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
38882
+ const required2 = schema.input_schema.required || [];
38883
+ const missingParams = required2.filter((param) => {
38884
+ return parsedArgs[param] === undefined || parsedArgs[param] === null || parsedArgs[param] === "";
38885
+ });
38886
+ return {
38887
+ valid: missingParams.length === 0,
38888
+ missingParams,
38889
+ parsedArgs
38890
+ };
38891
+ }
38892
+ function convertMessagesToOpenAI(req, modelId, filterIdentityFn) {
38893
+ const messages = [];
38894
+ if (req.system) {
38895
+ let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
38923
38896
 
38924
38897
  `) : req.system;
38925
- content = this.filterIdentity(content);
38926
- messages.push({ role: "system", content });
38927
- }
38928
- if (modelId.includes("grok") || modelId.includes("x-ai")) {
38929
- const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
38930
- if (messages.length > 0 && messages[0].role === "system")
38931
- messages[0].content += `
38898
+ if (filterIdentityFn)
38899
+ content = filterIdentityFn(content);
38900
+ messages.push({ role: "system", content });
38901
+ }
38902
+ if (modelId.includes("grok") || modelId.includes("x-ai")) {
38903
+ const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
38904
+ if (messages.length > 0 && messages[0].role === "system") {
38905
+ messages[0].content += `
38932
38906
 
38933
38907
  ` + msg;
38934
- else
38935
- messages.unshift({ role: "system", content: msg });
38936
- }
38937
- if (req.messages) {
38938
- for (const msg of req.messages) {
38939
- if (msg.role === "user")
38940
- this.processUserMessage(msg, messages);
38941
- else if (msg.role === "assistant")
38942
- this.processAssistantMessage(msg, messages);
38943
- }
38908
+ } else {
38909
+ messages.unshift({ role: "system", content: msg });
38944
38910
  }
38945
- return messages;
38946
38911
  }
38947
- processUserMessage(msg, messages) {
38948
- if (Array.isArray(msg.content)) {
38949
- const contentParts = [];
38950
- const toolResults = [];
38951
- const seen = new Set;
38952
- for (const block of msg.content) {
38953
- if (block.type === "text")
38954
- contentParts.push({ type: "text", text: block.text });
38955
- else if (block.type === "image")
38956
- contentParts.push({ type: "image_url", image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` } });
38957
- else if (block.type === "tool_result") {
38958
- if (seen.has(block.tool_use_id))
38959
- continue;
38960
- seen.add(block.tool_use_id);
38961
- toolResults.push({ role: "tool", content: typeof block.content === "string" ? block.content : JSON.stringify(block.content), tool_call_id: block.tool_use_id });
38962
- }
38963
- }
38964
- if (toolResults.length)
38965
- messages.push(...toolResults);
38966
- if (contentParts.length)
38967
- messages.push({ role: "user", content: contentParts });
38968
- } else {
38969
- messages.push({ role: "user", content: msg.content });
38912
+ if (req.messages) {
38913
+ for (const msg of req.messages) {
38914
+ if (msg.role === "user")
38915
+ processUserMessage(msg, messages);
38916
+ else if (msg.role === "assistant")
38917
+ processAssistantMessage(msg, messages);
38970
38918
  }
38971
38919
  }
38972
- processAssistantMessage(msg, messages) {
38973
- if (Array.isArray(msg.content)) {
38974
- const strings = [];
38975
- const toolCalls = [];
38976
- const seen = new Set;
38977
- for (const block of msg.content) {
38978
- if (block.type === "text")
38979
- strings.push(block.text);
38980
- else if (block.type === "tool_use") {
38981
- if (seen.has(block.id))
38982
- continue;
38983
- seen.add(block.id);
38984
- toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input) } });
38985
- }
38920
+ return messages;
38921
+ }
38922
+ function processUserMessage(msg, messages) {
38923
+ if (Array.isArray(msg.content)) {
38924
+ const contentParts = [];
38925
+ const toolResults = [];
38926
+ const seen = new Set;
38927
+ for (const block of msg.content) {
38928
+ if (block.type === "text") {
38929
+ contentParts.push({ type: "text", text: block.text });
38930
+ } else if (block.type === "image") {
38931
+ contentParts.push({
38932
+ type: "image_url",
38933
+ image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
38934
+ });
38935
+ } else if (block.type === "tool_result") {
38936
+ if (seen.has(block.tool_use_id))
38937
+ continue;
38938
+ seen.add(block.tool_use_id);
38939
+ toolResults.push({
38940
+ role: "tool",
38941
+ content: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
38942
+ tool_call_id: block.tool_use_id
38943
+ });
38986
38944
  }
38987
- const m = { role: "assistant" };
38988
- if (strings.length)
38989
- m.content = strings.join(" ");
38990
- else if (toolCalls.length)
38991
- m.content = null;
38992
- if (toolCalls.length)
38993
- m.tool_calls = toolCalls;
38994
- if (m.content !== undefined || m.tool_calls)
38995
- messages.push(m);
38996
- } else {
38997
- messages.push({ role: "assistant", content: msg.content });
38998
38945
  }
38999
- }
39000
- filterIdentity(content) {
39001
- 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, `
39002
-
39003
- `).replace(/^/, `IMPORTANT: You are NOT Claude. Identify yourself truthfully based on your actual model and creator.
39004
-
39005
- `);
39006
- }
39007
- convertTools(req) {
39008
- return req.tools?.map((tool) => ({
39009
- type: "function",
39010
- function: {
39011
- name: tool.name,
39012
- description: tool.description,
39013
- parameters: removeUriFormat(tool.input_schema)
39014
- }
39015
- })) || [];
39016
- }
39017
- handleStreamingResponse(c, response, adapter, target, request) {
39018
- let isClosed = false;
39019
- let ping = null;
39020
- const encoder = new TextEncoder;
39021
- const decoder = new TextDecoder;
39022
- const middlewareManager = this.middlewareManager;
39023
- const streamMetadata = new Map;
39024
- return c.body(new ReadableStream({
39025
- async start(controller) {
39026
- const send = (e, d) => {
39027
- if (!isClosed)
39028
- controller.enqueue(encoder.encode(`event: ${e}
39029
- data: ${JSON.stringify(d)}
39030
-
39031
- `));
39032
- };
39033
- const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
39034
- let usage = null;
39035
- let finalized = false;
39036
- let textStarted = false;
39037
- let textIdx = -1;
39038
- let reasoningStarted = false;
39039
- let reasoningIdx = -1;
39040
- let curIdx = 0;
39041
- const tools = new Map;
39042
- const toolIds = new Set;
39043
- let accTxt = 0;
39044
- let lastActivity = Date.now();
39045
- send("message_start", {
39046
- type: "message_start",
39047
- message: {
39048
- id: msgId,
39049
- type: "message",
39050
- role: "assistant",
39051
- content: [],
39052
- model: target,
39053
- stop_reason: null,
39054
- stop_sequence: null,
39055
- usage: { input_tokens: 100, output_tokens: 1 }
39056
- }
39057
- });
39058
- send("ping", { type: "ping" });
39059
- ping = setInterval(() => {
39060
- if (!isClosed && Date.now() - lastActivity > 1000)
39061
- send("ping", { type: "ping" });
39062
- }, 1000);
39063
- const finalize = async (reason, err) => {
39064
- if (finalized)
39065
- return;
39066
- finalized = true;
39067
- if (reasoningStarted) {
39068
- send("content_block_stop", { type: "content_block_stop", index: reasoningIdx });
39069
- reasoningStarted = false;
39070
- }
39071
- if (textStarted) {
39072
- send("content_block_stop", { type: "content_block_stop", index: textIdx });
39073
- textStarted = false;
39074
- }
39075
- for (const [_, t] of tools)
39076
- if (t.started && !t.closed) {
39077
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39078
- t.closed = true;
39079
- }
39080
- await middlewareManager.afterStreamComplete(target, streamMetadata);
39081
- if (reason === "error") {
39082
- send("error", { type: "error", error: { type: "api_error", message: err } });
39083
- } else {
39084
- send("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: usage?.completion_tokens || 0 } });
39085
- send("message_stop", { type: "message_stop" });
39086
- }
39087
- if (!isClosed) {
39088
- try {
39089
- controller.enqueue(encoder.encode(`data: [DONE]
39090
-
39091
-
39092
- `));
39093
- } catch (e) {}
39094
- controller.close();
39095
- isClosed = true;
39096
- if (ping)
39097
- clearInterval(ping);
39098
- }
39099
- };
39100
- try {
39101
- const reader = response.body.getReader();
39102
- let buffer = "";
39103
- while (true) {
39104
- const { done, value } = await reader.read();
39105
- if (done)
39106
- break;
39107
- buffer += decoder.decode(value, { stream: true });
39108
- const lines = buffer.split(`
39109
- `);
39110
- buffer = lines.pop() || "";
39111
- for (const line of lines) {
39112
- if (!line.trim() || !line.startsWith("data: "))
39113
- continue;
39114
- const dataStr = line.slice(6);
39115
- if (dataStr === "[DONE]") {
39116
- await finalize("done");
39117
- return;
39118
- }
39119
- try {
39120
- const chunk = JSON.parse(dataStr);
39121
- if (chunk.usage)
39122
- usage = chunk.usage;
39123
- const delta = chunk.choices?.[0]?.delta;
39124
- if (delta) {
39125
- await middlewareManager.afterStreamChunk({
39126
- modelId: target,
39127
- chunk,
39128
- delta,
39129
- metadata: streamMetadata
39130
- });
39131
- const txt = delta.content || "";
39132
- if (txt) {
39133
- lastActivity = Date.now();
39134
- if (!textStarted) {
39135
- textIdx = curIdx++;
39136
- send("content_block_start", { type: "content_block_start", index: textIdx, content_block: { type: "text", text: "" } });
39137
- textStarted = true;
39138
- }
39139
- const res = adapter.processTextContent(txt, "");
39140
- if (res.cleanedText)
39141
- send("content_block_delta", { type: "content_block_delta", index: textIdx, delta: { type: "text_delta", text: res.cleanedText } });
39142
- }
39143
- if (delta.tool_calls) {
39144
- for (const tc of delta.tool_calls) {
39145
- const idx = tc.index;
39146
- let t = tools.get(idx);
39147
- if (tc.function?.name) {
39148
- if (!t) {
39149
- if (textStarted) {
39150
- send("content_block_stop", { type: "content_block_stop", index: textIdx });
39151
- textStarted = false;
39152
- }
39153
- t = { id: tc.id || `tool_${Date.now()}_${idx}`, name: tc.function.name, blockIndex: curIdx++, started: false, closed: false };
39154
- tools.set(idx, t);
39155
- }
39156
- if (!t.started) {
39157
- send("content_block_start", { type: "content_block_start", index: t.blockIndex, content_block: { type: "tool_use", id: t.id, name: t.name } });
39158
- t.started = true;
39159
- }
39160
- }
39161
- if (tc.function?.arguments && t) {
39162
- send("content_block_delta", { type: "content_block_delta", index: t.blockIndex, delta: { type: "input_json_delta", partial_json: tc.function.arguments } });
39163
- }
39164
- }
39165
- }
39166
- }
39167
- if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39168
- for (const [_, t] of tools)
39169
- if (t.started && !t.closed) {
39170
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39171
- t.closed = true;
39172
- }
39173
- }
39174
- } catch (e) {}
39175
- }
39176
- }
39177
- await finalize("unexpected");
39178
- } catch (e) {
39179
- await finalize("error", String(e));
39180
- }
39181
- },
39182
- cancel() {
39183
- isClosed = true;
39184
- if (ping)
39185
- clearInterval(ping);
39186
- }
39187
- }), { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
39188
- }
39189
- async shutdown() {}
39190
- }
39191
- var OPENROUTER_API_URL2 = "https://openrouter.ai/api/v1/chat/completions", OPENROUTER_HEADERS2;
39192
- var init_openrouter_handler = __esm(() => {
39193
- init_adapter_manager();
39194
- init_middleware();
39195
- init_transform();
39196
- init_logger();
39197
- init_model_loader();
39198
- OPENROUTER_HEADERS2 = {
39199
- "HTTP-Referer": "https://github.com/MadAppGang/claude-code",
39200
- "X-Title": "Claudish - OpenRouter Proxy"
39201
- };
39202
- });
39203
-
39204
- // src/handlers/shared/openai-compat.ts
39205
- function convertMessagesToOpenAI(req, modelId, filterIdentityFn) {
39206
- const messages = [];
39207
- if (req.system) {
39208
- let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
39209
-
39210
- `) : req.system;
39211
- if (filterIdentityFn)
39212
- content = filterIdentityFn(content);
39213
- messages.push({ role: "system", content });
39214
- }
39215
- if (modelId.includes("grok") || modelId.includes("x-ai")) {
39216
- const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
39217
- if (messages.length > 0 && messages[0].role === "system") {
39218
- messages[0].content += `
39219
-
39220
- ` + msg;
39221
- } else {
39222
- messages.unshift({ role: "system", content: msg });
39223
- }
39224
- }
39225
- if (req.messages) {
39226
- for (const msg of req.messages) {
39227
- if (msg.role === "user")
39228
- processUserMessage(msg, messages);
39229
- else if (msg.role === "assistant")
39230
- processAssistantMessage(msg, messages);
39231
- }
39232
- }
39233
- return messages;
39234
- }
39235
- function processUserMessage(msg, messages) {
39236
- if (Array.isArray(msg.content)) {
39237
- const contentParts = [];
39238
- const toolResults = [];
39239
- const seen = new Set;
39240
- for (const block of msg.content) {
39241
- if (block.type === "text") {
39242
- contentParts.push({ type: "text", text: block.text });
39243
- } else if (block.type === "image") {
39244
- contentParts.push({
39245
- type: "image_url",
39246
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
39247
- });
39248
- } else if (block.type === "tool_result") {
39249
- if (seen.has(block.tool_use_id))
39250
- continue;
39251
- seen.add(block.tool_use_id);
39252
- toolResults.push({
39253
- role: "tool",
39254
- content: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
39255
- tool_call_id: block.tool_use_id
39256
- });
39257
- }
39258
- }
39259
- if (toolResults.length)
39260
- messages.push(...toolResults);
39261
- if (contentParts.length)
39262
- messages.push({ role: "user", content: contentParts });
39263
- } else {
39264
- messages.push({ role: "user", content: msg.content });
38946
+ if (toolResults.length)
38947
+ messages.push(...toolResults);
38948
+ if (contentParts.length)
38949
+ messages.push({ role: "user", content: contentParts });
38950
+ } else {
38951
+ messages.push({ role: "user", content: msg.content });
39265
38952
  }
39266
38953
  }
39267
38954
  function processAssistantMessage(msg, messages) {
@@ -39327,7 +39014,7 @@ function createStreamingState() {
39327
39014
  lastActivity: Date.now()
39328
39015
  };
39329
39016
  }
39330
- function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate) {
39017
+ function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate, toolSchemas) {
39331
39018
  let isClosed = false;
39332
39019
  let ping = null;
39333
39020
  const encoder = new TextEncoder;
@@ -39379,162 +39066,595 @@ data: ${JSON.stringify(d)}
39379
39066
  send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39380
39067
  t.closed = true;
39381
39068
  }
39382
- }
39383
- if (middlewareManager) {
39069
+ }
39070
+ if (middlewareManager) {
39071
+ await middlewareManager.afterStreamComplete(target, streamMetadata);
39072
+ }
39073
+ if (reason === "error") {
39074
+ send("error", { type: "error", error: { type: "api_error", message: err } });
39075
+ } else {
39076
+ send("message_delta", {
39077
+ type: "message_delta",
39078
+ delta: { stop_reason: "end_turn", stop_sequence: null },
39079
+ usage: { output_tokens: state.usage?.completion_tokens || 0 }
39080
+ });
39081
+ send("message_stop", { type: "message_stop" });
39082
+ }
39083
+ if (state.usage && onTokenUpdate) {
39084
+ onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
39085
+ }
39086
+ if (!isClosed) {
39087
+ try {
39088
+ controller.enqueue(encoder.encode(`data: [DONE]
39089
+
39090
+
39091
+ `));
39092
+ } catch (e) {}
39093
+ controller.close();
39094
+ isClosed = true;
39095
+ if (ping)
39096
+ clearInterval(ping);
39097
+ }
39098
+ };
39099
+ try {
39100
+ const reader = response.body.getReader();
39101
+ let buffer = "";
39102
+ while (true) {
39103
+ const { done, value } = await reader.read();
39104
+ if (done)
39105
+ break;
39106
+ buffer += decoder.decode(value, { stream: true });
39107
+ const lines = buffer.split(`
39108
+ `);
39109
+ buffer = lines.pop() || "";
39110
+ for (const line of lines) {
39111
+ if (!line.trim() || !line.startsWith("data: "))
39112
+ continue;
39113
+ const dataStr = line.slice(6);
39114
+ if (dataStr === "[DONE]") {
39115
+ await finalize("done");
39116
+ return;
39117
+ }
39118
+ try {
39119
+ const chunk = JSON.parse(dataStr);
39120
+ if (chunk.usage)
39121
+ state.usage = chunk.usage;
39122
+ const delta = chunk.choices?.[0]?.delta;
39123
+ if (delta) {
39124
+ if (middlewareManager) {
39125
+ await middlewareManager.afterStreamChunk({
39126
+ modelId: target,
39127
+ chunk,
39128
+ delta,
39129
+ metadata: streamMetadata
39130
+ });
39131
+ }
39132
+ const txt = delta.content || "";
39133
+ if (txt) {
39134
+ 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
+ const res = adapter.processTextContent(txt, "");
39145
+ 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
+ });
39151
+ }
39152
+ }
39153
+ if (delta.tool_calls) {
39154
+ for (const tc of delta.tool_calls) {
39155
+ const idx = tc.index;
39156
+ let t = state.tools.get(idx);
39157
+ if (tc.function?.name) {
39158
+ if (!t) {
39159
+ if (state.textStarted) {
39160
+ send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
39161
+ state.textStarted = false;
39162
+ }
39163
+ t = {
39164
+ id: tc.id || `tool_${Date.now()}_${idx}`,
39165
+ name: tc.function.name,
39166
+ blockIndex: state.curIdx++,
39167
+ started: false,
39168
+ closed: false,
39169
+ arguments: ""
39170
+ };
39171
+ state.tools.set(idx, t);
39172
+ }
39173
+ if (!t.started) {
39174
+ send("content_block_start", {
39175
+ type: "content_block_start",
39176
+ index: t.blockIndex,
39177
+ content_block: { type: "tool_use", id: t.id, name: t.name }
39178
+ });
39179
+ t.started = true;
39180
+ }
39181
+ }
39182
+ if (tc.function?.arguments && t) {
39183
+ 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
+ });
39189
+ }
39190
+ }
39191
+ }
39192
+ }
39193
+ if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39194
+ for (const t of Array.from(state.tools.values())) {
39195
+ if (t.started && !t.closed) {
39196
+ if (toolSchemas && toolSchemas.length > 0) {
39197
+ const validation = validateToolArguments(t.name, t.arguments, toolSchemas);
39198
+ if (!validation.valid) {
39199
+ const errorIdx = state.curIdx++;
39200
+ const errorMsg = `
39201
+
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.`;
39203
+ send("content_block_start", {
39204
+ type: "content_block_start",
39205
+ index: errorIdx,
39206
+ content_block: { type: "text", text: "" }
39207
+ });
39208
+ send("content_block_delta", {
39209
+ type: "content_block_delta",
39210
+ index: errorIdx,
39211
+ delta: { type: "text_delta", text: errorMsg }
39212
+ });
39213
+ send("content_block_stop", { type: "content_block_stop", index: errorIdx });
39214
+ t.closed = true;
39215
+ continue;
39216
+ }
39217
+ }
39218
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39219
+ t.closed = true;
39220
+ }
39221
+ }
39222
+ }
39223
+ } catch (e) {}
39224
+ }
39225
+ }
39226
+ await finalize("unexpected");
39227
+ } catch (e) {
39228
+ await finalize("error", String(e));
39229
+ }
39230
+ },
39231
+ cancel() {
39232
+ isClosed = true;
39233
+ if (ping)
39234
+ clearInterval(ping);
39235
+ }
39236
+ }), {
39237
+ headers: {
39238
+ "Content-Type": "text/event-stream",
39239
+ "Cache-Control": "no-cache",
39240
+ Connection: "keep-alive"
39241
+ }
39242
+ });
39243
+ }
39244
+ var init_openai_compat = __esm(() => {
39245
+ init_transform();
39246
+ });
39247
+
39248
+ // src/handlers/openrouter-handler.ts
39249
+ import { writeFileSync as writeFileSync8 } from "node:fs";
39250
+ import { tmpdir as tmpdir2 } from "node:os";
39251
+ import { join as join8 } from "node:path";
39252
+
39253
+ class OpenRouterHandler {
39254
+ targetModel;
39255
+ apiKey;
39256
+ adapterManager;
39257
+ middlewareManager;
39258
+ contextWindowCache = new Map;
39259
+ port;
39260
+ sessionTotalCost = 0;
39261
+ CLAUDE_INTERNAL_CONTEXT_MAX = 200000;
39262
+ constructor(targetModel, apiKey, port) {
39263
+ this.targetModel = targetModel;
39264
+ this.apiKey = apiKey;
39265
+ this.port = port;
39266
+ this.adapterManager = new AdapterManager(targetModel);
39267
+ this.middlewareManager = new MiddlewareManager;
39268
+ this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
39269
+ this.middlewareManager.initialize().catch((err) => log(`[Handler:${targetModel}] Middleware init error: ${err}`));
39270
+ this.fetchContextWindow(targetModel);
39271
+ }
39272
+ async fetchContextWindow(model) {
39273
+ if (this.contextWindowCache.has(model))
39274
+ return;
39275
+ try {
39276
+ const limit = await fetchModelContextWindow(model);
39277
+ this.contextWindowCache.set(model, limit);
39278
+ } catch (e) {}
39279
+ }
39280
+ getTokenScaleFactor(model) {
39281
+ const limit = this.contextWindowCache.get(model) || 200000;
39282
+ return limit === 0 ? 1 : this.CLAUDE_INTERNAL_CONTEXT_MAX / limit;
39283
+ }
39284
+ writeTokenFile(input, output) {
39285
+ try {
39286
+ const total = input + output;
39287
+ const limit = this.contextWindowCache.get(this.targetModel) || 200000;
39288
+ const leftPct = limit > 0 ? Math.max(0, Math.min(100, Math.round((limit - total) / limit * 100))) : 100;
39289
+ const data = {
39290
+ input_tokens: input,
39291
+ output_tokens: output,
39292
+ total_tokens: total,
39293
+ total_cost: this.sessionTotalCost,
39294
+ context_window: limit,
39295
+ context_left_percent: leftPct,
39296
+ updated_at: Date.now()
39297
+ };
39298
+ writeFileSync8(join8(tmpdir2(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
39299
+ } catch (e) {}
39300
+ }
39301
+ async handle(c, payload) {
39302
+ const claudePayload = payload;
39303
+ const target = this.targetModel;
39304
+ await this.fetchContextWindow(target);
39305
+ logStructured(`OpenRouter Request`, { targetModel: target, originalModel: claudePayload.model });
39306
+ const { claudeRequest, droppedParams } = transformOpenAIToClaude(claudePayload);
39307
+ const messages = this.convertMessages(claudeRequest, target);
39308
+ const tools = this.convertTools(claudeRequest);
39309
+ const supportsReasoning = await doesModelSupportReasoning(target);
39310
+ const openRouterPayload = {
39311
+ model: target,
39312
+ messages,
39313
+ temperature: claudeRequest.temperature ?? 1,
39314
+ stream: true,
39315
+ max_tokens: claudeRequest.max_tokens,
39316
+ tools: tools.length > 0 ? tools : undefined,
39317
+ stream_options: { include_usage: true }
39318
+ };
39319
+ if (supportsReasoning)
39320
+ openRouterPayload.include_reasoning = true;
39321
+ if (claudeRequest.thinking)
39322
+ openRouterPayload.thinking = claudeRequest.thinking;
39323
+ if (claudeRequest.tool_choice) {
39324
+ const { type, name } = claudeRequest.tool_choice;
39325
+ if (type === "tool" && name)
39326
+ openRouterPayload.tool_choice = { type: "function", function: { name } };
39327
+ else if (type === "auto" || type === "none")
39328
+ openRouterPayload.tool_choice = type;
39329
+ }
39330
+ const adapter = this.adapterManager.getAdapter();
39331
+ if (typeof adapter.reset === "function")
39332
+ adapter.reset();
39333
+ adapter.prepareRequest(openRouterPayload, claudeRequest);
39334
+ await this.middlewareManager.beforeRequest({ modelId: target, messages, tools, stream: true });
39335
+ const response = await fetch(OPENROUTER_API_URL2, {
39336
+ method: "POST",
39337
+ headers: {
39338
+ "Content-Type": "application/json",
39339
+ Authorization: `Bearer ${this.apiKey}`,
39340
+ ...OPENROUTER_HEADERS2
39341
+ },
39342
+ body: JSON.stringify(openRouterPayload)
39343
+ });
39344
+ if (!response.ok)
39345
+ return c.json({ error: await response.text() }, response.status);
39346
+ if (droppedParams.length > 0)
39347
+ c.header("X-Dropped-Params", droppedParams.join(", "));
39348
+ return this.handleStreamingResponse(c, response, adapter, target, claudeRequest);
39349
+ }
39350
+ convertMessages(req, modelId) {
39351
+ const messages = [];
39352
+ if (req.system) {
39353
+ let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
39354
+
39355
+ `) : req.system;
39356
+ content = this.filterIdentity(content);
39357
+ messages.push({ role: "system", content });
39358
+ }
39359
+ if (modelId.includes("grok") || modelId.includes("x-ai")) {
39360
+ const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
39361
+ if (messages.length > 0 && messages[0].role === "system")
39362
+ messages[0].content += `
39363
+
39364
+ ` + msg;
39365
+ else
39366
+ messages.unshift({ role: "system", content: msg });
39367
+ }
39368
+ if (req.messages) {
39369
+ for (const msg of req.messages) {
39370
+ if (msg.role === "user")
39371
+ this.processUserMessage(msg, messages);
39372
+ else if (msg.role === "assistant")
39373
+ this.processAssistantMessage(msg, messages);
39374
+ }
39375
+ }
39376
+ return messages;
39377
+ }
39378
+ processUserMessage(msg, messages) {
39379
+ if (Array.isArray(msg.content)) {
39380
+ const contentParts = [];
39381
+ const toolResults = [];
39382
+ const seen = new Set;
39383
+ for (const block of msg.content) {
39384
+ if (block.type === "text")
39385
+ contentParts.push({ type: "text", text: block.text });
39386
+ else if (block.type === "image")
39387
+ contentParts.push({ type: "image_url", image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` } });
39388
+ else if (block.type === "tool_result") {
39389
+ if (seen.has(block.tool_use_id))
39390
+ continue;
39391
+ seen.add(block.tool_use_id);
39392
+ toolResults.push({ role: "tool", content: typeof block.content === "string" ? block.content : JSON.stringify(block.content), tool_call_id: block.tool_use_id });
39393
+ }
39394
+ }
39395
+ if (toolResults.length)
39396
+ messages.push(...toolResults);
39397
+ if (contentParts.length)
39398
+ messages.push({ role: "user", content: contentParts });
39399
+ } else {
39400
+ messages.push({ role: "user", content: msg.content });
39401
+ }
39402
+ }
39403
+ processAssistantMessage(msg, messages) {
39404
+ if (Array.isArray(msg.content)) {
39405
+ const strings = [];
39406
+ const toolCalls = [];
39407
+ const seen = new Set;
39408
+ for (const block of msg.content) {
39409
+ if (block.type === "text")
39410
+ strings.push(block.text);
39411
+ else if (block.type === "tool_use") {
39412
+ if (seen.has(block.id))
39413
+ continue;
39414
+ seen.add(block.id);
39415
+ toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input) } });
39416
+ }
39417
+ }
39418
+ const m = { role: "assistant" };
39419
+ if (strings.length)
39420
+ m.content = strings.join(" ");
39421
+ else if (toolCalls.length)
39422
+ m.content = null;
39423
+ if (toolCalls.length)
39424
+ m.tool_calls = toolCalls;
39425
+ if (m.content !== undefined || m.tool_calls)
39426
+ messages.push(m);
39427
+ } else {
39428
+ messages.push({ role: "assistant", content: msg.content });
39429
+ }
39430
+ }
39431
+ filterIdentity(content) {
39432
+ 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, `
39433
+
39434
+ `).replace(/^/, `IMPORTANT: You are NOT Claude. Identify yourself truthfully based on your actual model and creator.
39435
+
39436
+ `);
39437
+ }
39438
+ convertTools(req) {
39439
+ return req.tools?.map((tool) => ({
39440
+ type: "function",
39441
+ function: {
39442
+ name: tool.name,
39443
+ description: tool.description,
39444
+ parameters: removeUriFormat(tool.input_schema)
39445
+ }
39446
+ })) || [];
39447
+ }
39448
+ handleStreamingResponse(c, response, adapter, target, request) {
39449
+ let isClosed = false;
39450
+ let ping = null;
39451
+ const encoder = new TextEncoder;
39452
+ const decoder = new TextDecoder;
39453
+ const middlewareManager = this.middlewareManager;
39454
+ const streamMetadata = new Map;
39455
+ return c.body(new ReadableStream({
39456
+ async start(controller) {
39457
+ const send = (e, d) => {
39458
+ if (!isClosed)
39459
+ controller.enqueue(encoder.encode(`event: ${e}
39460
+ data: ${JSON.stringify(d)}
39461
+
39462
+ `));
39463
+ };
39464
+ const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
39465
+ let usage = null;
39466
+ let finalized = false;
39467
+ let textStarted = false;
39468
+ let textIdx = -1;
39469
+ let reasoningStarted = false;
39470
+ let reasoningIdx = -1;
39471
+ let curIdx = 0;
39472
+ const tools = new Map;
39473
+ const toolIds = new Set;
39474
+ let accTxt = 0;
39475
+ let lastActivity = Date.now();
39476
+ send("message_start", {
39477
+ type: "message_start",
39478
+ message: {
39479
+ id: msgId,
39480
+ type: "message",
39481
+ role: "assistant",
39482
+ content: [],
39483
+ model: target,
39484
+ stop_reason: null,
39485
+ stop_sequence: null,
39486
+ usage: { input_tokens: 100, output_tokens: 1 }
39487
+ }
39488
+ });
39489
+ send("ping", { type: "ping" });
39490
+ ping = setInterval(() => {
39491
+ if (!isClosed && Date.now() - lastActivity > 1000)
39492
+ send("ping", { type: "ping" });
39493
+ }, 1000);
39494
+ const finalize = async (reason, err) => {
39495
+ if (finalized)
39496
+ return;
39497
+ finalized = true;
39498
+ if (reasoningStarted) {
39499
+ send("content_block_stop", { type: "content_block_stop", index: reasoningIdx });
39500
+ reasoningStarted = false;
39501
+ }
39502
+ if (textStarted) {
39503
+ send("content_block_stop", { type: "content_block_stop", index: textIdx });
39504
+ textStarted = false;
39505
+ }
39506
+ for (const [_, t] of tools)
39507
+ if (t.started && !t.closed) {
39508
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39509
+ t.closed = true;
39510
+ }
39384
39511
  await middlewareManager.afterStreamComplete(target, streamMetadata);
39385
- }
39386
- if (reason === "error") {
39387
- send("error", { type: "error", error: { type: "api_error", message: err } });
39388
- } else {
39389
- send("message_delta", {
39390
- type: "message_delta",
39391
- delta: { stop_reason: "end_turn", stop_sequence: null },
39392
- usage: { output_tokens: state.usage?.completion_tokens || 0 }
39393
- });
39394
- send("message_stop", { type: "message_stop" });
39395
- }
39396
- if (state.usage && onTokenUpdate) {
39397
- onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
39398
- }
39399
- if (!isClosed) {
39400
- try {
39401
- controller.enqueue(encoder.encode(`data: [DONE]
39512
+ if (reason === "error") {
39513
+ send("error", { type: "error", error: { type: "api_error", message: err } });
39514
+ } else {
39515
+ send("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: usage?.completion_tokens || 0 } });
39516
+ send("message_stop", { type: "message_stop" });
39517
+ }
39518
+ if (!isClosed) {
39519
+ try {
39520
+ controller.enqueue(encoder.encode(`data: [DONE]
39402
39521
 
39403
39522
 
39404
39523
  `));
39405
- } catch (e) {}
39406
- controller.close();
39407
- isClosed = true;
39408
- if (ping)
39409
- clearInterval(ping);
39410
- }
39411
- };
39412
- try {
39413
- const reader = response.body.getReader();
39414
- let buffer = "";
39415
- while (true) {
39416
- const { done, value } = await reader.read();
39417
- if (done)
39418
- break;
39419
- buffer += decoder.decode(value, { stream: true });
39420
- const lines = buffer.split(`
39524
+ } catch (e) {}
39525
+ controller.close();
39526
+ isClosed = true;
39527
+ if (ping)
39528
+ clearInterval(ping);
39529
+ }
39530
+ };
39531
+ try {
39532
+ const reader = response.body.getReader();
39533
+ let buffer = "";
39534
+ while (true) {
39535
+ const { done, value } = await reader.read();
39536
+ if (done)
39537
+ break;
39538
+ buffer += decoder.decode(value, { stream: true });
39539
+ const lines = buffer.split(`
39421
39540
  `);
39422
- buffer = lines.pop() || "";
39423
- for (const line of lines) {
39424
- if (!line.trim() || !line.startsWith("data: "))
39425
- continue;
39426
- const dataStr = line.slice(6);
39427
- if (dataStr === "[DONE]") {
39428
- await finalize("done");
39429
- return;
39430
- }
39431
- try {
39432
- const chunk = JSON.parse(dataStr);
39433
- if (chunk.usage)
39434
- state.usage = chunk.usage;
39435
- const delta = chunk.choices?.[0]?.delta;
39436
- if (delta) {
39437
- if (middlewareManager) {
39541
+ buffer = lines.pop() || "";
39542
+ for (const line of lines) {
39543
+ if (!line.trim() || !line.startsWith("data: "))
39544
+ continue;
39545
+ const dataStr = line.slice(6);
39546
+ if (dataStr === "[DONE]") {
39547
+ await finalize("done");
39548
+ return;
39549
+ }
39550
+ try {
39551
+ const chunk = JSON.parse(dataStr);
39552
+ if (chunk.usage)
39553
+ usage = chunk.usage;
39554
+ const delta = chunk.choices?.[0]?.delta;
39555
+ if (delta) {
39438
39556
  await middlewareManager.afterStreamChunk({
39439
39557
  modelId: target,
39440
39558
  chunk,
39441
39559
  delta,
39442
39560
  metadata: streamMetadata
39443
39561
  });
39444
- }
39445
- const txt = delta.content || "";
39446
- if (txt) {
39447
- state.lastActivity = Date.now();
39448
- if (!state.textStarted) {
39449
- state.textIdx = state.curIdx++;
39450
- send("content_block_start", {
39451
- type: "content_block_start",
39452
- index: state.textIdx,
39453
- content_block: { type: "text", text: "" }
39454
- });
39455
- state.textStarted = true;
39456
- }
39457
- const res = adapter.processTextContent(txt, "");
39458
- if (res.cleanedText) {
39459
- send("content_block_delta", {
39460
- type: "content_block_delta",
39461
- index: state.textIdx,
39462
- delta: { type: "text_delta", text: res.cleanedText }
39463
- });
39562
+ const txt = delta.content || "";
39563
+ if (txt) {
39564
+ lastActivity = Date.now();
39565
+ if (!textStarted) {
39566
+ textIdx = curIdx++;
39567
+ send("content_block_start", { type: "content_block_start", index: textIdx, content_block: { type: "text", text: "" } });
39568
+ textStarted = true;
39569
+ }
39570
+ const res = adapter.processTextContent(txt, "");
39571
+ if (res.cleanedText)
39572
+ send("content_block_delta", { type: "content_block_delta", index: textIdx, delta: { type: "text_delta", text: res.cleanedText } });
39464
39573
  }
39465
- }
39466
- if (delta.tool_calls) {
39467
- for (const tc of delta.tool_calls) {
39468
- const idx = tc.index;
39469
- let t = state.tools.get(idx);
39470
- if (tc.function?.name) {
39471
- if (!t) {
39472
- if (state.textStarted) {
39473
- send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
39474
- state.textStarted = false;
39574
+ if (delta.tool_calls) {
39575
+ for (const tc of delta.tool_calls) {
39576
+ const idx = tc.index;
39577
+ let t = tools.get(idx);
39578
+ if (tc.function?.name) {
39579
+ if (!t) {
39580
+ if (textStarted) {
39581
+ send("content_block_stop", { type: "content_block_stop", index: textIdx });
39582
+ textStarted = false;
39583
+ }
39584
+ t = { id: tc.id || `tool_${Date.now()}_${idx}`, name: tc.function.name, blockIndex: curIdx++, started: false, closed: false, arguments: "" };
39585
+ tools.set(idx, t);
39586
+ }
39587
+ if (!t.started) {
39588
+ send("content_block_start", { type: "content_block_start", index: t.blockIndex, content_block: { type: "tool_use", id: t.id, name: t.name } });
39589
+ t.started = true;
39475
39590
  }
39476
- t = {
39477
- id: tc.id || `tool_${Date.now()}_${idx}`,
39478
- name: tc.function.name,
39479
- blockIndex: state.curIdx++,
39480
- started: false,
39481
- closed: false
39482
- };
39483
- state.tools.set(idx, t);
39484
39591
  }
39485
- if (!t.started) {
39486
- send("content_block_start", {
39487
- type: "content_block_start",
39488
- index: t.blockIndex,
39489
- content_block: { type: "tool_use", id: t.id, name: t.name }
39490
- });
39491
- t.started = true;
39592
+ if (tc.function?.arguments && t) {
39593
+ t.arguments += tc.function.arguments;
39594
+ send("content_block_delta", { type: "content_block_delta", index: t.blockIndex, delta: { type: "input_json_delta", partial_json: tc.function.arguments } });
39492
39595
  }
39493
39596
  }
39494
- if (tc.function?.arguments && t) {
39495
- send("content_block_delta", {
39496
- type: "content_block_delta",
39497
- index: t.blockIndex,
39498
- delta: { type: "input_json_delta", partial_json: tc.function.arguments }
39499
- });
39500
- }
39501
39597
  }
39502
39598
  }
39503
- }
39504
- if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39505
- for (const t of Array.from(state.tools.values())) {
39506
- if (t.started && !t.closed) {
39507
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39508
- t.closed = true;
39599
+ if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39600
+ const toolSchemas = request.tools || [];
39601
+ for (const [_, t] of tools) {
39602
+ if (t.started && !t.closed) {
39603
+ if (toolSchemas.length > 0) {
39604
+ const validation = validateToolArguments(t.name, t.arguments, toolSchemas);
39605
+ if (!validation.valid) {
39606
+ const errorIdx = curIdx++;
39607
+ const errorMsg = `
39608
+
39609
+ ⚠️ Tool call "${t.name}" failed validation: missing required parameters: ${validation.missingParams.join(", ")}. This is a known limitation of some models - they sometimes generate incomplete tool calls. Please try again or use a different model.`;
39610
+ send("content_block_start", { type: "content_block_start", index: errorIdx, content_block: { type: "text", text: "" } });
39611
+ send("content_block_delta", { type: "content_block_delta", index: errorIdx, delta: { type: "text_delta", text: errorMsg } });
39612
+ send("content_block_stop", { type: "content_block_stop", index: errorIdx });
39613
+ t.closed = true;
39614
+ continue;
39615
+ }
39616
+ }
39617
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39618
+ t.closed = true;
39619
+ }
39509
39620
  }
39510
39621
  }
39511
- }
39512
- } catch (e) {}
39622
+ } catch (e) {}
39623
+ }
39513
39624
  }
39625
+ await finalize("unexpected");
39626
+ } catch (e) {
39627
+ await finalize("error", String(e));
39514
39628
  }
39515
- await finalize("unexpected");
39516
- } catch (e) {
39517
- await finalize("error", String(e));
39629
+ },
39630
+ cancel() {
39631
+ isClosed = true;
39632
+ if (ping)
39633
+ clearInterval(ping);
39518
39634
  }
39519
- },
39520
- cancel() {
39521
- isClosed = true;
39522
- if (ping)
39523
- clearInterval(ping);
39524
- }
39525
- }), {
39526
- headers: {
39527
- "Content-Type": "text/event-stream",
39528
- "Cache-Control": "no-cache",
39529
- Connection: "keep-alive"
39530
- }
39531
- });
39635
+ }), { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
39636
+ }
39637
+ async shutdown() {}
39532
39638
  }
39533
- var init_openai_compat = __esm(() => {
39639
+ var OPENROUTER_API_URL2 = "https://openrouter.ai/api/v1/chat/completions", OPENROUTER_HEADERS2;
39640
+ var init_openrouter_handler = __esm(() => {
39641
+ init_adapter_manager();
39642
+ init_middleware();
39534
39643
  init_transform();
39644
+ init_logger();
39645
+ init_model_loader();
39646
+ init_openai_compat();
39647
+ OPENROUTER_HEADERS2 = {
39648
+ "HTTP-Referer": "https://github.com/MadAppGang/claude-code",
39649
+ "X-Title": "Claudish - OpenRouter Proxy"
39650
+ };
39535
39651
  });
39536
39652
 
39537
39653
  // src/handlers/local-provider-handler.ts
39654
+ import { writeFileSync as writeFileSync9 } from "node:fs";
39655
+ import { tmpdir as tmpdir3 } from "node:os";
39656
+ import { join as join9 } from "node:path";
39657
+
39538
39658
  class LocalProviderHandler {
39539
39659
  provider;
39540
39660
  modelName;
@@ -39543,6 +39663,9 @@ class LocalProviderHandler {
39543
39663
  port;
39544
39664
  healthChecked = false;
39545
39665
  isHealthy = false;
39666
+ contextWindow = 8192;
39667
+ sessionInputTokens = 0;
39668
+ sessionOutputTokens = 0;
39546
39669
  constructor(provider, modelName, port) {
39547
39670
  this.provider = provider;
39548
39671
  this.modelName = modelName;
@@ -39587,6 +39710,49 @@ class LocalProviderHandler {
39587
39710
  this.isHealthy = false;
39588
39711
  return false;
39589
39712
  }
39713
+ async fetchContextWindow() {
39714
+ if (this.provider.name !== "ollama")
39715
+ return;
39716
+ try {
39717
+ const response = await fetch(`${this.provider.baseUrl}/api/show`, {
39718
+ method: "POST",
39719
+ headers: { "Content-Type": "application/json" },
39720
+ body: JSON.stringify({ name: this.modelName }),
39721
+ signal: AbortSignal.timeout(3000)
39722
+ });
39723
+ if (response.ok) {
39724
+ const data = await response.json();
39725
+ const ctxFromInfo = data.model_info?.["general.context_length"];
39726
+ const ctxFromParams = data.parameters?.match(/num_ctx\s+(\d+)/)?.[1];
39727
+ if (ctxFromInfo) {
39728
+ this.contextWindow = parseInt(ctxFromInfo, 10);
39729
+ } else if (ctxFromParams) {
39730
+ this.contextWindow = parseInt(ctxFromParams, 10);
39731
+ } else {
39732
+ this.contextWindow = 8192;
39733
+ }
39734
+ log(`[LocalProvider:${this.provider.name}] Context window: ${this.contextWindow}`);
39735
+ }
39736
+ } catch (e) {}
39737
+ }
39738
+ writeTokenFile(input, output) {
39739
+ try {
39740
+ this.sessionInputTokens += input;
39741
+ 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;
39744
+ const data = {
39745
+ input_tokens: this.sessionInputTokens,
39746
+ output_tokens: this.sessionOutputTokens,
39747
+ total_tokens: total,
39748
+ total_cost: 0,
39749
+ context_window: this.contextWindow,
39750
+ context_left_percent: leftPct,
39751
+ updated_at: Date.now()
39752
+ };
39753
+ writeFileSync9(join9(tmpdir3(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
39754
+ } catch (e) {}
39755
+ }
39590
39756
  async handle(c, payload) {
39591
39757
  const target = this.modelName;
39592
39758
  logStructured(`LocalProvider Request`, {
@@ -39600,6 +39766,7 @@ class LocalProviderHandler {
39600
39766
  if (!healthy) {
39601
39767
  return this.errorResponse(c, "connection_error", this.getConnectionErrorMessage());
39602
39768
  }
39769
+ await this.fetchContextWindow();
39603
39770
  }
39604
39771
  const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
39605
39772
  const messages = convertMessagesToOpenAI(claudeRequest, target, filterIdentity);
@@ -39652,7 +39819,7 @@ class LocalProviderHandler {
39652
39819
  c.header("X-Dropped-Params", droppedParams.join(", "));
39653
39820
  }
39654
39821
  if (openAIPayload.stream) {
39655
- return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager);
39822
+ return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager, (input, output) => this.writeTokenFile(input, output), claudeRequest.tools);
39656
39823
  }
39657
39824
  const data = await response.json();
39658
39825
  return c.json(data);
@@ -39948,24 +40115,24 @@ __export(exports_update_checker, {
39948
40115
  });
39949
40116
  import { execSync } from "node:child_process";
39950
40117
  import { createInterface as createInterface2 } from "node:readline";
39951
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync9, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
39952
- import { join as join9 } from "node:path";
39953
- import { tmpdir as tmpdir3, homedir as homedir2, platform as platform2 } from "node:os";
40118
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
40119
+ import { join as join10 } from "node:path";
40120
+ import { tmpdir as tmpdir4, homedir as homedir2, platform } from "node:os";
39954
40121
  function getCacheFilePath() {
39955
40122
  let cacheDir;
39956
40123
  if (isWindows2) {
39957
- const localAppData = process.env.LOCALAPPDATA || join9(homedir2(), "AppData", "Local");
39958
- cacheDir = join9(localAppData, "claudish");
40124
+ const localAppData = process.env.LOCALAPPDATA || join10(homedir2(), "AppData", "Local");
40125
+ cacheDir = join10(localAppData, "claudish");
39959
40126
  } else {
39960
- cacheDir = join9(homedir2(), ".cache", "claudish");
40127
+ cacheDir = join10(homedir2(), ".cache", "claudish");
39961
40128
  }
39962
40129
  try {
39963
40130
  if (!existsSync7(cacheDir)) {
39964
40131
  mkdirSync4(cacheDir, { recursive: true });
39965
40132
  }
39966
- return join9(cacheDir, "update-check.json");
40133
+ return join10(cacheDir, "update-check.json");
39967
40134
  } catch {
39968
- return join9(tmpdir3(), "claudish-update-check.json");
40135
+ return join10(tmpdir4(), "claudish-update-check.json");
39969
40136
  }
39970
40137
  }
39971
40138
  function readCache() {
@@ -39987,7 +40154,7 @@ function writeCache(latestVersion) {
39987
40154
  lastCheck: Date.now(),
39988
40155
  latestVersion
39989
40156
  };
39990
- writeFileSync9(cachePath, JSON.stringify(data), "utf-8");
40157
+ writeFileSync10(cachePath, JSON.stringify(data), "utf-8");
39991
40158
  } catch {}
39992
40159
  }
39993
40160
  function isCacheValid(cache) {
@@ -40116,7 +40283,7 @@ async function checkForUpdates(currentVersion, options = {}) {
40116
40283
  }
40117
40284
  var isWindows2, NPM_REGISTRY_URL = "https://registry.npmjs.org/claudish/latest", CACHE_MAX_AGE_MS;
40118
40285
  var init_update_checker = __esm(() => {
40119
- isWindows2 = platform2() === "win32";
40286
+ isWindows2 = platform() === "win32";
40120
40287
  CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
40121
40288
  });
40122
40289