claudish 2.10.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 +696 -632
  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();
@@ -34382,7 +34385,7 @@ function createTempSettingsFile(modelDisplay, port) {
34382
34385
  const tempPath = join4(tempDir, `claudish-settings-${timestamp}.json`);
34383
34386
  const tokenFilePath = join4(tempDir, `claudish-tokens-${port}.json`);
34384
34387
  let statusCommand;
34385
- if (isWindows) {
34388
+ if (isWindows()) {
34386
34389
  const scriptPath = createStatusLineScript(tokenFilePath);
34387
34390
  statusCommand = `node "${scriptPath}"`;
34388
34391
  } else {
@@ -34471,7 +34474,7 @@ async function runClaudeWithProxy(config3, proxyUrl) {
34471
34474
  const proc = spawn("claude", claudeArgs, {
34472
34475
  env,
34473
34476
  stdio: "inherit",
34474
- shell: isWindows
34477
+ shell: isWindows()
34475
34478
  });
34476
34479
  setupSignalHandlers(proc, tempSettingsPath, config3.quiet);
34477
34480
  const exitCode = await new Promise((resolve) => {
@@ -34485,7 +34488,7 @@ async function runClaudeWithProxy(config3, proxyUrl) {
34485
34488
  return exitCode;
34486
34489
  }
34487
34490
  function setupSignalHandlers(proc, tempSettingsPath, quiet) {
34488
- const signals2 = isWindows ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
34491
+ const signals2 = isWindows() ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
34489
34492
  for (const signal of signals2) {
34490
34493
  process.on(signal, () => {
34491
34494
  if (!quiet) {
@@ -34518,10 +34521,8 @@ async function checkClaudeInstalled() {
34518
34521
  return false;
34519
34522
  }
34520
34523
  }
34521
- var isWindows;
34522
34524
  var init_claude_runner = __esm(() => {
34523
34525
  init_config();
34524
- isWindows = platform() === "win32";
34525
34526
  });
34526
34527
 
34527
34528
  // src/types.ts
@@ -38866,724 +38867,787 @@ function transformOpenAIToClaude(claudeRequestInput) {
38866
38867
  }
38867
38868
  var init_transform = () => {};
38868
38869
 
38869
- // src/handlers/openrouter-handler.ts
38870
- import { writeFileSync as writeFileSync8 } from "node:fs";
38871
- import { tmpdir as tmpdir2 } from "node:os";
38872
- import { join as join8 } from "node:path";
38873
-
38874
- class OpenRouterHandler {
38875
- targetModel;
38876
- apiKey;
38877
- adapterManager;
38878
- middlewareManager;
38879
- contextWindowCache = new Map;
38880
- port;
38881
- sessionTotalCost = 0;
38882
- CLAUDE_INTERNAL_CONTEXT_MAX = 200000;
38883
- constructor(targetModel, apiKey, port) {
38884
- this.targetModel = targetModel;
38885
- this.apiKey = apiKey;
38886
- this.port = port;
38887
- this.adapterManager = new AdapterManager(targetModel);
38888
- this.middlewareManager = new MiddlewareManager;
38889
- this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
38890
- this.middlewareManager.initialize().catch((err) => log(`[Handler:${targetModel}] Middleware init error: ${err}`));
38891
- this.fetchContextWindow(targetModel);
38892
- }
38893
- async fetchContextWindow(model) {
38894
- if (this.contextWindowCache.has(model))
38895
- return;
38896
- try {
38897
- const limit = await fetchModelContextWindow(model);
38898
- this.contextWindowCache.set(model, limit);
38899
- } catch (e) {}
38900
- }
38901
- getTokenScaleFactor(model) {
38902
- const limit = this.contextWindowCache.get(model) || 200000;
38903
- return limit === 0 ? 1 : this.CLAUDE_INTERNAL_CONTEXT_MAX / limit;
38904
- }
38905
- writeTokenFile(input, output) {
38906
- try {
38907
- const total = input + output;
38908
- const limit = this.contextWindowCache.get(this.targetModel) || 200000;
38909
- const leftPct = limit > 0 ? Math.max(0, Math.min(100, Math.round((limit - total) / limit * 100))) : 100;
38910
- const data = {
38911
- input_tokens: input,
38912
- output_tokens: output,
38913
- total_tokens: total,
38914
- total_cost: this.sessionTotalCost,
38915
- context_window: limit,
38916
- context_left_percent: leftPct,
38917
- updated_at: Date.now()
38918
- };
38919
- writeFileSync8(join8(tmpdir2(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
38920
- } 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: {} };
38921
38875
  }
38922
- async handle(c, payload) {
38923
- const claudePayload = payload;
38924
- const target = this.targetModel;
38925
- await this.fetchContextWindow(target);
38926
- logStructured(`OpenRouter Request`, { targetModel: target, originalModel: claudePayload.model });
38927
- const { claudeRequest, droppedParams } = transformOpenAIToClaude(claudePayload);
38928
- const messages = this.convertMessages(claudeRequest, target);
38929
- const tools = this.convertTools(claudeRequest);
38930
- const supportsReasoning = await doesModelSupportReasoning(target);
38931
- const openRouterPayload = {
38932
- model: target,
38933
- messages,
38934
- temperature: claudeRequest.temperature ?? 1,
38935
- stream: true,
38936
- max_tokens: claudeRequest.max_tokens,
38937
- tools: tools.length > 0 ? tools : undefined,
38938
- stream_options: { include_usage: true }
38939
- };
38940
- if (supportsReasoning)
38941
- openRouterPayload.include_reasoning = true;
38942
- if (claudeRequest.thinking)
38943
- openRouterPayload.thinking = claudeRequest.thinking;
38944
- if (claudeRequest.tool_choice) {
38945
- const { type, name } = claudeRequest.tool_choice;
38946
- if (type === "tool" && name)
38947
- openRouterPayload.tool_choice = { type: "function", function: { name } };
38948
- else if (type === "auto" || type === "none")
38949
- openRouterPayload.tool_choice = type;
38950
- }
38951
- const adapter = this.adapterManager.getAdapter();
38952
- if (typeof adapter.reset === "function")
38953
- adapter.reset();
38954
- adapter.prepareRequest(openRouterPayload, claudeRequest);
38955
- await this.middlewareManager.beforeRequest({ modelId: target, messages, tools, stream: true });
38956
- const response = await fetch(OPENROUTER_API_URL2, {
38957
- method: "POST",
38958
- headers: {
38959
- "Content-Type": "application/json",
38960
- Authorization: `Bearer ${this.apiKey}`,
38961
- ...OPENROUTER_HEADERS2
38962
- },
38963
- body: JSON.stringify(openRouterPayload)
38964
- });
38965
- if (!response.ok)
38966
- return c.json({ error: await response.text() }, response.status);
38967
- if (droppedParams.length > 0)
38968
- c.header("X-Dropped-Params", droppedParams.join(", "));
38969
- 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: {} };
38970
38881
  }
38971
- convertMessages(req, modelId) {
38972
- const messages = [];
38973
- if (req.system) {
38974
- 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(`
38975
38896
 
38976
38897
  `) : req.system;
38977
- content = this.filterIdentity(content);
38978
- messages.push({ role: "system", content });
38979
- }
38980
- if (modelId.includes("grok") || modelId.includes("x-ai")) {
38981
- const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
38982
- if (messages.length > 0 && messages[0].role === "system")
38983
- 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 += `
38984
38906
 
38985
38907
  ` + msg;
38986
- else
38987
- messages.unshift({ role: "system", content: msg });
38908
+ } else {
38909
+ messages.unshift({ role: "system", content: msg });
38988
38910
  }
38989
- if (req.messages) {
38990
- for (const msg of req.messages) {
38991
- if (msg.role === "user")
38992
- this.processUserMessage(msg, messages);
38993
- else if (msg.role === "assistant")
38994
- this.processAssistantMessage(msg, messages);
38995
- }
38911
+ }
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);
38996
38918
  }
38997
- return messages;
38998
38919
  }
38999
- processUserMessage(msg, messages) {
39000
- if (Array.isArray(msg.content)) {
39001
- const contentParts = [];
39002
- const toolResults = [];
39003
- const seen = new Set;
39004
- for (const block of msg.content) {
39005
- if (block.type === "text")
39006
- contentParts.push({ type: "text", text: block.text });
39007
- else if (block.type === "image")
39008
- contentParts.push({ type: "image_url", image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` } });
39009
- else if (block.type === "tool_result") {
39010
- if (seen.has(block.tool_use_id))
39011
- continue;
39012
- seen.add(block.tool_use_id);
39013
- toolResults.push({ role: "tool", content: typeof block.content === "string" ? block.content : JSON.stringify(block.content), tool_call_id: block.tool_use_id });
39014
- }
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
+ });
39015
38944
  }
39016
- if (toolResults.length)
39017
- messages.push(...toolResults);
39018
- if (contentParts.length)
39019
- messages.push({ role: "user", content: contentParts });
39020
- } else {
39021
- messages.push({ role: "user", content: msg.content });
39022
38945
  }
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 });
39023
38952
  }
39024
- processAssistantMessage(msg, messages) {
39025
- if (Array.isArray(msg.content)) {
39026
- const strings = [];
39027
- const toolCalls = [];
39028
- const seen = new Set;
39029
- for (const block of msg.content) {
39030
- if (block.type === "text")
39031
- strings.push(block.text);
39032
- else if (block.type === "tool_use") {
39033
- if (seen.has(block.id))
39034
- continue;
39035
- seen.add(block.id);
39036
- toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input) } });
39037
- }
38953
+ }
38954
+ function processAssistantMessage(msg, messages) {
38955
+ if (Array.isArray(msg.content)) {
38956
+ const strings = [];
38957
+ const toolCalls = [];
38958
+ const seen = new Set;
38959
+ for (const block of msg.content) {
38960
+ if (block.type === "text") {
38961
+ strings.push(block.text);
38962
+ } else if (block.type === "tool_use") {
38963
+ if (seen.has(block.id))
38964
+ continue;
38965
+ seen.add(block.id);
38966
+ toolCalls.push({
38967
+ id: block.id,
38968
+ type: "function",
38969
+ function: { name: block.name, arguments: JSON.stringify(block.input) }
38970
+ });
39038
38971
  }
39039
- const m = { role: "assistant" };
39040
- if (strings.length)
39041
- m.content = strings.join(" ");
39042
- else if (toolCalls.length)
39043
- m.content = null;
39044
- if (toolCalls.length)
39045
- m.tool_calls = toolCalls;
39046
- if (m.content !== undefined || m.tool_calls)
39047
- messages.push(m);
39048
- } else {
39049
- messages.push({ role: "assistant", content: msg.content });
39050
38972
  }
38973
+ const m = { role: "assistant" };
38974
+ if (strings.length)
38975
+ m.content = strings.join(" ");
38976
+ else if (toolCalls.length)
38977
+ m.content = null;
38978
+ if (toolCalls.length)
38979
+ m.tool_calls = toolCalls;
38980
+ if (m.content !== undefined || m.tool_calls)
38981
+ messages.push(m);
38982
+ } else {
38983
+ messages.push({ role: "assistant", content: msg.content });
39051
38984
  }
39052
- filterIdentity(content) {
39053
- 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, `
38985
+ }
38986
+ function convertToolsToOpenAI(req) {
38987
+ return req.tools?.map((tool) => ({
38988
+ type: "function",
38989
+ function: {
38990
+ name: tool.name,
38991
+ description: tool.description,
38992
+ parameters: removeUriFormat(tool.input_schema)
38993
+ }
38994
+ })) || [];
38995
+ }
38996
+ function filterIdentity(content) {
38997
+ 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, `
39054
38998
 
39055
38999
  `).replace(/^/, `IMPORTANT: You are NOT Claude. Identify yourself truthfully based on your actual model and creator.
39056
39000
 
39057
39001
  `);
39058
- }
39059
- convertTools(req) {
39060
- return req.tools?.map((tool) => ({
39061
- type: "function",
39062
- function: {
39063
- name: tool.name,
39064
- description: tool.description,
39065
- parameters: removeUriFormat(tool.input_schema)
39066
- }
39067
- })) || [];
39068
- }
39069
- handleStreamingResponse(c, response, adapter, target, request) {
39070
- let isClosed = false;
39071
- let ping = null;
39072
- const encoder = new TextEncoder;
39073
- const decoder = new TextDecoder;
39074
- const middlewareManager = this.middlewareManager;
39075
- const streamMetadata = new Map;
39076
- return c.body(new ReadableStream({
39077
- async start(controller) {
39078
- const send = (e, d) => {
39079
- if (!isClosed)
39080
- controller.enqueue(encoder.encode(`event: ${e}
39002
+ }
39003
+ function createStreamingState() {
39004
+ return {
39005
+ usage: null,
39006
+ finalized: false,
39007
+ textStarted: false,
39008
+ textIdx: -1,
39009
+ reasoningStarted: false,
39010
+ reasoningIdx: -1,
39011
+ curIdx: 0,
39012
+ tools: new Map,
39013
+ toolIds: new Set,
39014
+ lastActivity: Date.now()
39015
+ };
39016
+ }
39017
+ function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate, toolSchemas) {
39018
+ let isClosed = false;
39019
+ let ping = null;
39020
+ const encoder = new TextEncoder;
39021
+ const decoder = new TextDecoder;
39022
+ const streamMetadata = new Map;
39023
+ return c.body(new ReadableStream({
39024
+ async start(controller) {
39025
+ const send = (e, d) => {
39026
+ if (!isClosed) {
39027
+ controller.enqueue(encoder.encode(`event: ${e}
39081
39028
  data: ${JSON.stringify(d)}
39082
39029
 
39083
39030
  `));
39084
- };
39085
- const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
39086
- let usage = null;
39087
- let finalized = false;
39088
- let textStarted = false;
39089
- let textIdx = -1;
39090
- let reasoningStarted = false;
39091
- let reasoningIdx = -1;
39092
- let curIdx = 0;
39093
- const tools = new Map;
39094
- const toolIds = new Set;
39095
- let accTxt = 0;
39096
- let lastActivity = Date.now();
39097
- send("message_start", {
39098
- type: "message_start",
39099
- message: {
39100
- id: msgId,
39101
- type: "message",
39102
- role: "assistant",
39103
- content: [],
39104
- model: target,
39105
- stop_reason: null,
39106
- stop_sequence: null,
39107
- usage: { input_tokens: 100, output_tokens: 1 }
39108
- }
39109
- });
39110
- send("ping", { type: "ping" });
39111
- ping = setInterval(() => {
39112
- if (!isClosed && Date.now() - lastActivity > 1000)
39113
- send("ping", { type: "ping" });
39114
- }, 1000);
39115
- const finalize = async (reason, err) => {
39116
- if (finalized)
39117
- return;
39118
- finalized = true;
39119
- if (reasoningStarted) {
39120
- send("content_block_stop", { type: "content_block_stop", index: reasoningIdx });
39121
- reasoningStarted = false;
39122
- }
39123
- if (textStarted) {
39124
- send("content_block_stop", { type: "content_block_stop", index: textIdx });
39125
- textStarted = false;
39031
+ }
39032
+ };
39033
+ const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
39034
+ const state = createStreamingState();
39035
+ send("message_start", {
39036
+ type: "message_start",
39037
+ message: {
39038
+ id: msgId,
39039
+ type: "message",
39040
+ role: "assistant",
39041
+ content: [],
39042
+ model: target,
39043
+ stop_reason: null,
39044
+ stop_sequence: null,
39045
+ usage: { input_tokens: 100, output_tokens: 1 }
39046
+ }
39047
+ });
39048
+ send("ping", { type: "ping" });
39049
+ ping = setInterval(() => {
39050
+ if (!isClosed && Date.now() - state.lastActivity > 1000) {
39051
+ send("ping", { type: "ping" });
39052
+ }
39053
+ }, 1000);
39054
+ const finalize = async (reason, err) => {
39055
+ if (state.finalized)
39056
+ return;
39057
+ state.finalized = true;
39058
+ if (state.reasoningStarted) {
39059
+ send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
39060
+ }
39061
+ if (state.textStarted) {
39062
+ send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
39063
+ }
39064
+ for (const t of Array.from(state.tools.values())) {
39065
+ if (t.started && !t.closed) {
39066
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39067
+ t.closed = true;
39126
39068
  }
39127
- for (const [_, t] of tools)
39128
- if (t.started && !t.closed) {
39129
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39130
- t.closed = true;
39131
- }
39069
+ }
39070
+ if (middlewareManager) {
39132
39071
  await middlewareManager.afterStreamComplete(target, streamMetadata);
39133
- if (reason === "error") {
39134
- send("error", { type: "error", error: { type: "api_error", message: err } });
39135
- } else {
39136
- send("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: usage?.completion_tokens || 0 } });
39137
- send("message_stop", { type: "message_stop" });
39138
- }
39139
- if (!isClosed) {
39140
- try {
39141
- controller.enqueue(encoder.encode(`data: [DONE]
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]
39142
39089
 
39143
39090
 
39144
39091
  `));
39145
- } catch (e) {}
39146
- controller.close();
39147
- isClosed = true;
39148
- if (ping)
39149
- clearInterval(ping);
39150
- }
39151
- };
39152
- try {
39153
- const reader = response.body.getReader();
39154
- let buffer = "";
39155
- while (true) {
39156
- const { done, value } = await reader.read();
39157
- if (done)
39158
- break;
39159
- buffer += decoder.decode(value, { stream: true });
39160
- const lines = buffer.split(`
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(`
39161
39108
  `);
39162
- buffer = lines.pop() || "";
39163
- for (const line of lines) {
39164
- if (!line.trim() || !line.startsWith("data: "))
39165
- continue;
39166
- const dataStr = line.slice(6);
39167
- if (dataStr === "[DONE]") {
39168
- await finalize("done");
39169
- return;
39170
- }
39171
- try {
39172
- const chunk = JSON.parse(dataStr);
39173
- if (chunk.usage)
39174
- usage = chunk.usage;
39175
- const delta = chunk.choices?.[0]?.delta;
39176
- if (delta) {
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) {
39177
39125
  await middlewareManager.afterStreamChunk({
39178
39126
  modelId: target,
39179
39127
  chunk,
39180
39128
  delta,
39181
39129
  metadata: streamMetadata
39182
39130
  });
39183
- const txt = delta.content || "";
39184
- if (txt) {
39185
- lastActivity = Date.now();
39186
- if (!textStarted) {
39187
- textIdx = curIdx++;
39188
- send("content_block_start", { type: "content_block_start", index: textIdx, content_block: { type: "text", text: "" } });
39189
- textStarted = true;
39190
- }
39191
- const res = adapter.processTextContent(txt, "");
39192
- if (res.cleanedText)
39193
- send("content_block_delta", { type: "content_block_delta", index: textIdx, delta: { type: "text_delta", text: res.cleanedText } });
39194
- }
39195
- if (delta.tool_calls) {
39196
- for (const tc of delta.tool_calls) {
39197
- const idx = tc.index;
39198
- let t = tools.get(idx);
39199
- if (tc.function?.name) {
39200
- if (!t) {
39201
- if (textStarted) {
39202
- send("content_block_stop", { type: "content_block_stop", index: textIdx });
39203
- textStarted = false;
39204
- }
39205
- t = { id: tc.id || `tool_${Date.now()}_${idx}`, name: tc.function.name, blockIndex: curIdx++, started: false, closed: false };
39206
- tools.set(idx, t);
39207
- }
39208
- if (!t.started) {
39209
- send("content_block_start", { type: "content_block_start", index: t.blockIndex, content_block: { type: "tool_use", id: t.id, name: t.name } });
39210
- t.started = true;
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;
39211
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);
39212
39172
  }
39213
- if (tc.function?.arguments && t) {
39214
- send("content_block_delta", { type: "content_block_delta", index: t.blockIndex, delta: { type: "input_json_delta", partial_json: tc.function.arguments } });
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;
39215
39180
  }
39216
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
+ }
39217
39190
  }
39218
39191
  }
39219
- if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39220
- for (const [_, t] of tools)
39221
- if (t.started && !t.closed) {
39222
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39223
- t.closed = true;
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
+ }
39224
39217
  }
39218
+ send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39219
+ t.closed = true;
39220
+ }
39225
39221
  }
39226
- } catch (e) {}
39227
- }
39222
+ }
39223
+ } catch (e) {}
39228
39224
  }
39229
- await finalize("unexpected");
39230
- } catch (e) {
39231
- await finalize("error", String(e));
39232
39225
  }
39233
- },
39234
- cancel() {
39235
- isClosed = true;
39236
- if (ping)
39237
- clearInterval(ping);
39226
+ await finalize("unexpected");
39227
+ } catch (e) {
39228
+ await finalize("error", String(e));
39238
39229
  }
39239
- }), { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
39240
- }
39241
- async shutdown() {}
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
+ });
39242
39243
  }
39243
- var OPENROUTER_API_URL2 = "https://openrouter.ai/api/v1/chat/completions", OPENROUTER_HEADERS2;
39244
- var init_openrouter_handler = __esm(() => {
39245
- init_adapter_manager();
39246
- init_middleware();
39244
+ var init_openai_compat = __esm(() => {
39247
39245
  init_transform();
39248
- init_logger();
39249
- init_model_loader();
39250
- OPENROUTER_HEADERS2 = {
39251
- "HTTP-Referer": "https://github.com/MadAppGang/claude-code",
39252
- "X-Title": "Claudish - OpenRouter Proxy"
39253
- };
39254
39246
  });
39255
39247
 
39256
- // src/handlers/shared/openai-compat.ts
39257
- function convertMessagesToOpenAI(req, modelId, filterIdentityFn) {
39258
- const messages = [];
39259
- if (req.system) {
39260
- let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
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";
39261
39252
 
39262
- `) : req.system;
39263
- if (filterIdentityFn)
39264
- content = filterIdentityFn(content);
39265
- messages.push({ role: "system", content });
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);
39266
39271
  }
39267
- if (modelId.includes("grok") || modelId.includes("x-ai")) {
39268
- const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
39269
- if (messages.length > 0 && messages[0].role === "system") {
39270
- messages[0].content += `
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 += `
39271
39363
 
39272
39364
  ` + msg;
39273
- } else {
39274
- messages.unshift({ role: "system", content: msg });
39365
+ else
39366
+ messages.unshift({ role: "system", content: msg });
39275
39367
  }
39276
- }
39277
- if (req.messages) {
39278
- for (const msg of req.messages) {
39279
- if (msg.role === "user")
39280
- processUserMessage(msg, messages);
39281
- else if (msg.role === "assistant")
39282
- processAssistantMessage(msg, messages);
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
+ }
39283
39375
  }
39376
+ return messages;
39284
39377
  }
39285
- return messages;
39286
- }
39287
- function processUserMessage(msg, messages) {
39288
- if (Array.isArray(msg.content)) {
39289
- const contentParts = [];
39290
- const toolResults = [];
39291
- const seen = new Set;
39292
- for (const block of msg.content) {
39293
- if (block.type === "text") {
39294
- contentParts.push({ type: "text", text: block.text });
39295
- } else if (block.type === "image") {
39296
- contentParts.push({
39297
- type: "image_url",
39298
- image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
39299
- });
39300
- } else if (block.type === "tool_result") {
39301
- if (seen.has(block.tool_use_id))
39302
- continue;
39303
- seen.add(block.tool_use_id);
39304
- toolResults.push({
39305
- role: "tool",
39306
- content: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
39307
- tool_call_id: block.tool_use_id
39308
- });
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
+ }
39309
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 });
39310
39401
  }
39311
- if (toolResults.length)
39312
- messages.push(...toolResults);
39313
- if (contentParts.length)
39314
- messages.push({ role: "user", content: contentParts });
39315
- } else {
39316
- messages.push({ role: "user", content: msg.content });
39317
39402
  }
39318
- }
39319
- function processAssistantMessage(msg, messages) {
39320
- if (Array.isArray(msg.content)) {
39321
- const strings = [];
39322
- const toolCalls = [];
39323
- const seen = new Set;
39324
- for (const block of msg.content) {
39325
- if (block.type === "text") {
39326
- strings.push(block.text);
39327
- } else if (block.type === "tool_use") {
39328
- if (seen.has(block.id))
39329
- continue;
39330
- seen.add(block.id);
39331
- toolCalls.push({
39332
- id: block.id,
39333
- type: "function",
39334
- function: { name: block.name, arguments: JSON.stringify(block.input) }
39335
- });
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
+ }
39336
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 });
39337
39429
  }
39338
- const m = { role: "assistant" };
39339
- if (strings.length)
39340
- m.content = strings.join(" ");
39341
- else if (toolCalls.length)
39342
- m.content = null;
39343
- if (toolCalls.length)
39344
- m.tool_calls = toolCalls;
39345
- if (m.content !== undefined || m.tool_calls)
39346
- messages.push(m);
39347
- } else {
39348
- messages.push({ role: "assistant", content: msg.content });
39349
39430
  }
39350
- }
39351
- function convertToolsToOpenAI(req) {
39352
- return req.tools?.map((tool) => ({
39353
- type: "function",
39354
- function: {
39355
- name: tool.name,
39356
- description: tool.description,
39357
- parameters: removeUriFormat(tool.input_schema)
39358
- }
39359
- })) || [];
39360
- }
39361
- function filterIdentity(content) {
39362
- 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, `
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, `
39363
39433
 
39364
39434
  `).replace(/^/, `IMPORTANT: You are NOT Claude. Identify yourself truthfully based on your actual model and creator.
39365
39435
 
39366
39436
  `);
39367
- }
39368
- function createStreamingState() {
39369
- return {
39370
- usage: null,
39371
- finalized: false,
39372
- textStarted: false,
39373
- textIdx: -1,
39374
- reasoningStarted: false,
39375
- reasoningIdx: -1,
39376
- curIdx: 0,
39377
- tools: new Map,
39378
- toolIds: new Set,
39379
- lastActivity: Date.now()
39380
- };
39381
- }
39382
- function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate) {
39383
- let isClosed = false;
39384
- let ping = null;
39385
- const encoder = new TextEncoder;
39386
- const decoder = new TextDecoder;
39387
- const streamMetadata = new Map;
39388
- return c.body(new ReadableStream({
39389
- async start(controller) {
39390
- const send = (e, d) => {
39391
- if (!isClosed) {
39392
- controller.enqueue(encoder.encode(`event: ${e}
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}
39393
39460
  data: ${JSON.stringify(d)}
39394
39461
 
39395
39462
  `));
39396
- }
39397
- };
39398
- const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
39399
- const state = createStreamingState();
39400
- send("message_start", {
39401
- type: "message_start",
39402
- message: {
39403
- id: msgId,
39404
- type: "message",
39405
- role: "assistant",
39406
- content: [],
39407
- model: target,
39408
- stop_reason: null,
39409
- stop_sequence: null,
39410
- usage: { input_tokens: 100, output_tokens: 1 }
39411
- }
39412
- });
39413
- send("ping", { type: "ping" });
39414
- ping = setInterval(() => {
39415
- if (!isClosed && Date.now() - state.lastActivity > 1000) {
39416
- send("ping", { type: "ping" });
39417
- }
39418
- }, 1000);
39419
- const finalize = async (reason, err) => {
39420
- if (state.finalized)
39421
- return;
39422
- state.finalized = true;
39423
- if (state.reasoningStarted) {
39424
- send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
39425
- }
39426
- if (state.textStarted) {
39427
- send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
39428
- }
39429
- for (const t of Array.from(state.tools.values())) {
39430
- if (t.started && !t.closed) {
39431
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39432
- t.closed = true;
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 }
39433
39487
  }
39434
- }
39435
- if (middlewareManager) {
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
+ }
39436
39511
  await middlewareManager.afterStreamComplete(target, streamMetadata);
39437
- }
39438
- if (reason === "error") {
39439
- send("error", { type: "error", error: { type: "api_error", message: err } });
39440
- } else {
39441
- send("message_delta", {
39442
- type: "message_delta",
39443
- delta: { stop_reason: "end_turn", stop_sequence: null },
39444
- usage: { output_tokens: state.usage?.completion_tokens || 0 }
39445
- });
39446
- send("message_stop", { type: "message_stop" });
39447
- }
39448
- if (state.usage && onTokenUpdate) {
39449
- onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
39450
- }
39451
- if (!isClosed) {
39452
- try {
39453
- 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]
39454
39521
 
39455
39522
 
39456
39523
  `));
39457
- } catch (e) {}
39458
- controller.close();
39459
- isClosed = true;
39460
- if (ping)
39461
- clearInterval(ping);
39462
- }
39463
- };
39464
- try {
39465
- const reader = response.body.getReader();
39466
- let buffer = "";
39467
- while (true) {
39468
- const { done, value } = await reader.read();
39469
- if (done)
39470
- break;
39471
- buffer += decoder.decode(value, { stream: true });
39472
- 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(`
39473
39540
  `);
39474
- buffer = lines.pop() || "";
39475
- for (const line of lines) {
39476
- if (!line.trim() || !line.startsWith("data: "))
39477
- continue;
39478
- const dataStr = line.slice(6);
39479
- if (dataStr === "[DONE]") {
39480
- await finalize("done");
39481
- return;
39482
- }
39483
- try {
39484
- const chunk = JSON.parse(dataStr);
39485
- if (chunk.usage)
39486
- state.usage = chunk.usage;
39487
- const delta = chunk.choices?.[0]?.delta;
39488
- if (delta) {
39489
- 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) {
39490
39556
  await middlewareManager.afterStreamChunk({
39491
39557
  modelId: target,
39492
39558
  chunk,
39493
39559
  delta,
39494
39560
  metadata: streamMetadata
39495
39561
  });
39496
- }
39497
- const txt = delta.content || "";
39498
- if (txt) {
39499
- state.lastActivity = Date.now();
39500
- if (!state.textStarted) {
39501
- state.textIdx = state.curIdx++;
39502
- send("content_block_start", {
39503
- type: "content_block_start",
39504
- index: state.textIdx,
39505
- content_block: { type: "text", text: "" }
39506
- });
39507
- state.textStarted = true;
39508
- }
39509
- const res = adapter.processTextContent(txt, "");
39510
- if (res.cleanedText) {
39511
- send("content_block_delta", {
39512
- type: "content_block_delta",
39513
- index: state.textIdx,
39514
- delta: { type: "text_delta", text: res.cleanedText }
39515
- });
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 } });
39516
39573
  }
39517
- }
39518
- if (delta.tool_calls) {
39519
- for (const tc of delta.tool_calls) {
39520
- const idx = tc.index;
39521
- let t = state.tools.get(idx);
39522
- if (tc.function?.name) {
39523
- if (!t) {
39524
- if (state.textStarted) {
39525
- send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
39526
- 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;
39527
39590
  }
39528
- t = {
39529
- id: tc.id || `tool_${Date.now()}_${idx}`,
39530
- name: tc.function.name,
39531
- blockIndex: state.curIdx++,
39532
- started: false,
39533
- closed: false
39534
- };
39535
- state.tools.set(idx, t);
39536
39591
  }
39537
- if (!t.started) {
39538
- send("content_block_start", {
39539
- type: "content_block_start",
39540
- index: t.blockIndex,
39541
- content_block: { type: "tool_use", id: t.id, name: t.name }
39542
- });
39543
- 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 } });
39544
39595
  }
39545
39596
  }
39546
- if (tc.function?.arguments && t) {
39547
- send("content_block_delta", {
39548
- type: "content_block_delta",
39549
- index: t.blockIndex,
39550
- delta: { type: "input_json_delta", partial_json: tc.function.arguments }
39551
- });
39552
- }
39553
39597
  }
39554
39598
  }
39555
- }
39556
- if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
39557
- for (const t of Array.from(state.tools.values())) {
39558
- if (t.started && !t.closed) {
39559
- send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
39560
- 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
+ }
39561
39620
  }
39562
39621
  }
39563
- }
39564
- } catch (e) {}
39622
+ } catch (e) {}
39623
+ }
39565
39624
  }
39625
+ await finalize("unexpected");
39626
+ } catch (e) {
39627
+ await finalize("error", String(e));
39566
39628
  }
39567
- await finalize("unexpected");
39568
- } catch (e) {
39569
- await finalize("error", String(e));
39629
+ },
39630
+ cancel() {
39631
+ isClosed = true;
39632
+ if (ping)
39633
+ clearInterval(ping);
39570
39634
  }
39571
- },
39572
- cancel() {
39573
- isClosed = true;
39574
- if (ping)
39575
- clearInterval(ping);
39576
- }
39577
- }), {
39578
- headers: {
39579
- "Content-Type": "text/event-stream",
39580
- "Cache-Control": "no-cache",
39581
- Connection: "keep-alive"
39582
- }
39583
- });
39635
+ }), { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
39636
+ }
39637
+ async shutdown() {}
39584
39638
  }
39585
- 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();
39586
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
+ };
39587
39651
  });
39588
39652
 
39589
39653
  // src/handlers/local-provider-handler.ts
@@ -39755,7 +39819,7 @@ class LocalProviderHandler {
39755
39819
  c.header("X-Dropped-Params", droppedParams.join(", "));
39756
39820
  }
39757
39821
  if (openAIPayload.stream) {
39758
- return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager, (input, output) => this.writeTokenFile(input, output));
39822
+ return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager, (input, output) => this.writeTokenFile(input, output), claudeRequest.tools);
39759
39823
  }
39760
39824
  const data = await response.json();
39761
39825
  return c.json(data);
@@ -40053,7 +40117,7 @@ import { execSync } from "node:child_process";
40053
40117
  import { createInterface as createInterface2 } from "node:readline";
40054
40118
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
40055
40119
  import { join as join10 } from "node:path";
40056
- import { tmpdir as tmpdir4, homedir as homedir2, platform as platform2 } from "node:os";
40120
+ import { tmpdir as tmpdir4, homedir as homedir2, platform } from "node:os";
40057
40121
  function getCacheFilePath() {
40058
40122
  let cacheDir;
40059
40123
  if (isWindows2) {
@@ -40219,7 +40283,7 @@ async function checkForUpdates(currentVersion, options = {}) {
40219
40283
  }
40220
40284
  var isWindows2, NPM_REGISTRY_URL = "https://registry.npmjs.org/claudish/latest", CACHE_MAX_AGE_MS;
40221
40285
  var init_update_checker = __esm(() => {
40222
- isWindows2 = platform2() === "win32";
40286
+ isWindows2 = platform() === "win32";
40223
40287
  CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
40224
40288
  });
40225
40289