claudish 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1075 -537
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34308,7 +34308,8 @@ var init_config = __esm(() => {
|
|
|
34308
34308
|
OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
|
|
34309
34309
|
OLLAMA_HOST: "OLLAMA_HOST",
|
|
34310
34310
|
LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
|
|
34311
|
-
VLLM_BASE_URL: "VLLM_BASE_URL"
|
|
34311
|
+
VLLM_BASE_URL: "VLLM_BASE_URL",
|
|
34312
|
+
CLAUDISH_SUMMARIZE_TOOLS: "CLAUDISH_SUMMARIZE_TOOLS"
|
|
34312
34313
|
};
|
|
34313
34314
|
OPENROUTER_HEADERS = {
|
|
34314
34315
|
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
@@ -34324,8 +34325,11 @@ __export(exports_claude_runner, {
|
|
|
34324
34325
|
});
|
|
34325
34326
|
import { spawn } from "node:child_process";
|
|
34326
34327
|
import { writeFileSync as writeFileSync4, unlinkSync } from "node:fs";
|
|
34327
|
-
import { tmpdir
|
|
34328
|
+
import { tmpdir } from "node:os";
|
|
34328
34329
|
import { join as join4 } from "node:path";
|
|
34330
|
+
function isWindows() {
|
|
34331
|
+
return process.platform === "win32";
|
|
34332
|
+
}
|
|
34329
34333
|
function createStatusLineScript(tokenFilePath) {
|
|
34330
34334
|
const tempDir = tmpdir();
|
|
34331
34335
|
const timestamp = Date.now();
|
|
@@ -34382,7 +34386,7 @@ function createTempSettingsFile(modelDisplay, port) {
|
|
|
34382
34386
|
const tempPath = join4(tempDir, `claudish-settings-${timestamp}.json`);
|
|
34383
34387
|
const tokenFilePath = join4(tempDir, `claudish-tokens-${port}.json`);
|
|
34384
34388
|
let statusCommand;
|
|
34385
|
-
if (isWindows) {
|
|
34389
|
+
if (isWindows()) {
|
|
34386
34390
|
const scriptPath = createStatusLineScript(tokenFilePath);
|
|
34387
34391
|
statusCommand = `node "${scriptPath}"`;
|
|
34388
34392
|
} else {
|
|
@@ -34471,7 +34475,7 @@ async function runClaudeWithProxy(config3, proxyUrl) {
|
|
|
34471
34475
|
const proc = spawn("claude", claudeArgs, {
|
|
34472
34476
|
env,
|
|
34473
34477
|
stdio: "inherit",
|
|
34474
|
-
shell: isWindows
|
|
34478
|
+
shell: isWindows()
|
|
34475
34479
|
});
|
|
34476
34480
|
setupSignalHandlers(proc, tempSettingsPath, config3.quiet);
|
|
34477
34481
|
const exitCode = await new Promise((resolve) => {
|
|
@@ -34485,7 +34489,7 @@ async function runClaudeWithProxy(config3, proxyUrl) {
|
|
|
34485
34489
|
return exitCode;
|
|
34486
34490
|
}
|
|
34487
34491
|
function setupSignalHandlers(proc, tempSettingsPath, quiet) {
|
|
34488
|
-
const signals2 = isWindows ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
34492
|
+
const signals2 = isWindows() ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
34489
34493
|
for (const signal of signals2) {
|
|
34490
34494
|
process.on(signal, () => {
|
|
34491
34495
|
if (!quiet) {
|
|
@@ -34518,10 +34522,8 @@ async function checkClaudeInstalled() {
|
|
|
34518
34522
|
return false;
|
|
34519
34523
|
}
|
|
34520
34524
|
}
|
|
34521
|
-
var isWindows;
|
|
34522
34525
|
var init_claude_runner = __esm(() => {
|
|
34523
34526
|
init_config();
|
|
34524
|
-
isWindows = platform() === "win32";
|
|
34525
34527
|
});
|
|
34526
34528
|
|
|
34527
34529
|
// src/types.ts
|
|
@@ -34742,6 +34744,10 @@ async function parseArgs(args) {
|
|
|
34742
34744
|
config3.port = port;
|
|
34743
34745
|
}
|
|
34744
34746
|
}
|
|
34747
|
+
const envSummarizeTools = process.env[ENV.CLAUDISH_SUMMARIZE_TOOLS];
|
|
34748
|
+
if (envSummarizeTools === "true" || envSummarizeTools === "1") {
|
|
34749
|
+
config3.summarizeTools = true;
|
|
34750
|
+
}
|
|
34745
34751
|
let i = 0;
|
|
34746
34752
|
while (i < args.length) {
|
|
34747
34753
|
const arg = args[i];
|
|
@@ -34858,6 +34864,8 @@ async function parseArgs(args) {
|
|
|
34858
34864
|
await printAllModels(hasJsonFlag, forceUpdate);
|
|
34859
34865
|
}
|
|
34860
34866
|
process.exit(0);
|
|
34867
|
+
} else if (arg === "--summarize-tools") {
|
|
34868
|
+
config3.summarizeTools = true;
|
|
34861
34869
|
} else {
|
|
34862
34870
|
config3.claudeArgs = args.slice(i);
|
|
34863
34871
|
break;
|
|
@@ -38866,408 +38874,238 @@ function transformOpenAIToClaude(claudeRequestInput) {
|
|
|
38866
38874
|
}
|
|
38867
38875
|
var init_transform = () => {};
|
|
38868
38876
|
|
|
38869
|
-
// src/handlers/
|
|
38870
|
-
|
|
38871
|
-
|
|
38872
|
-
|
|
38873
|
-
|
|
38874
|
-
|
|
38875
|
-
|
|
38876
|
-
|
|
38877
|
-
|
|
38878
|
-
|
|
38879
|
-
|
|
38880
|
-
|
|
38881
|
-
|
|
38882
|
-
|
|
38883
|
-
|
|
38884
|
-
|
|
38885
|
-
|
|
38886
|
-
|
|
38887
|
-
|
|
38888
|
-
|
|
38889
|
-
|
|
38890
|
-
|
|
38891
|
-
|
|
38877
|
+
// src/handlers/shared/tool-call-recovery.ts
|
|
38878
|
+
function extractToolCallsFromText(text) {
|
|
38879
|
+
const extracted = [];
|
|
38880
|
+
const qwenPattern = /<function=([^>]+)>([\s\S]*?)(?=<function=|$)/gi;
|
|
38881
|
+
let match2;
|
|
38882
|
+
while ((match2 = qwenPattern.exec(text)) !== null) {
|
|
38883
|
+
const funcName = match2[1];
|
|
38884
|
+
const paramsText = match2[2];
|
|
38885
|
+
const args = {};
|
|
38886
|
+
const paramPattern = /<parameter=([^>]+)>\s*([\s\S]*?)(?=<parameter=|<function=|$)/gi;
|
|
38887
|
+
let paramMatch;
|
|
38888
|
+
while ((paramMatch = paramPattern.exec(paramsText)) !== null) {
|
|
38889
|
+
const paramName = paramMatch[1];
|
|
38890
|
+
const paramValue = paramMatch[2].trim();
|
|
38891
|
+
args[paramName] = paramValue;
|
|
38892
|
+
}
|
|
38893
|
+
if (funcName) {
|
|
38894
|
+
extracted.push({
|
|
38895
|
+
name: funcName,
|
|
38896
|
+
arguments: args,
|
|
38897
|
+
source: "xml_text"
|
|
38898
|
+
});
|
|
38899
|
+
log(`[ToolRecovery] Extracted Qwen-style tool call: ${funcName}`);
|
|
38900
|
+
}
|
|
38892
38901
|
}
|
|
38893
|
-
|
|
38894
|
-
|
|
38895
|
-
return;
|
|
38902
|
+
const xmlPattern = /<tool_call>\s*(\{[\s\S]*?\})\s*<\/tool_call>/gi;
|
|
38903
|
+
while ((match2 = xmlPattern.exec(text)) !== null) {
|
|
38896
38904
|
try {
|
|
38897
|
-
const
|
|
38898
|
-
|
|
38905
|
+
const parsed = JSON.parse(match2[1]);
|
|
38906
|
+
if (parsed.name) {
|
|
38907
|
+
extracted.push({
|
|
38908
|
+
name: parsed.name,
|
|
38909
|
+
arguments: parsed.arguments || parsed.input || parsed.parameters || {},
|
|
38910
|
+
source: "xml_text"
|
|
38911
|
+
});
|
|
38912
|
+
}
|
|
38899
38913
|
} catch (e) {}
|
|
38900
38914
|
}
|
|
38901
|
-
|
|
38902
|
-
|
|
38903
|
-
|
|
38915
|
+
const funcCallPattern = /\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"(?:arguments|input|parameters)"\s*:\s*(\{[\s\S]*?\})\s*\}/gi;
|
|
38916
|
+
while ((match2 = funcCallPattern.exec(text)) !== null) {
|
|
38917
|
+
try {
|
|
38918
|
+
const args = JSON.parse(match2[2]);
|
|
38919
|
+
extracted.push({
|
|
38920
|
+
name: match2[1],
|
|
38921
|
+
arguments: args,
|
|
38922
|
+
source: "json_text"
|
|
38923
|
+
});
|
|
38924
|
+
} catch (e) {}
|
|
38904
38925
|
}
|
|
38905
|
-
|
|
38926
|
+
const anthropicPattern = /\{\s*"type"\s*:\s*"tool_use"\s*,\s*"id"\s*:\s*"[^"]*"\s*,\s*"name"\s*:\s*"([^"]+)"\s*,\s*"input"\s*:\s*(\{[\s\S]*?\})\s*\}/gi;
|
|
38927
|
+
while ((match2 = anthropicPattern.exec(text)) !== null) {
|
|
38906
38928
|
try {
|
|
38907
|
-
const
|
|
38908
|
-
|
|
38909
|
-
|
|
38910
|
-
|
|
38911
|
-
|
|
38912
|
-
|
|
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");
|
|
38929
|
+
const args = JSON.parse(match2[2]);
|
|
38930
|
+
extracted.push({
|
|
38931
|
+
name: match2[1],
|
|
38932
|
+
arguments: args,
|
|
38933
|
+
source: "json_text"
|
|
38934
|
+
});
|
|
38920
38935
|
} catch (e) {}
|
|
38921
38936
|
}
|
|
38922
|
-
|
|
38923
|
-
|
|
38924
|
-
|
|
38925
|
-
|
|
38926
|
-
|
|
38927
|
-
|
|
38928
|
-
|
|
38929
|
-
|
|
38930
|
-
|
|
38931
|
-
|
|
38932
|
-
|
|
38933
|
-
|
|
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);
|
|
38937
|
+
const jsonBlockPattern = /```(?:json)?\s*(\{[\s\S]*?\})\s*```/gi;
|
|
38938
|
+
while ((match2 = jsonBlockPattern.exec(text)) !== null) {
|
|
38939
|
+
try {
|
|
38940
|
+
const parsed = JSON.parse(match2[1]);
|
|
38941
|
+
if (parsed.name && (parsed.arguments || parsed.input || parsed.parameters)) {
|
|
38942
|
+
extracted.push({
|
|
38943
|
+
name: parsed.name,
|
|
38944
|
+
arguments: parsed.arguments || parsed.input || parsed.parameters,
|
|
38945
|
+
source: "json_text"
|
|
38946
|
+
});
|
|
38947
|
+
}
|
|
38948
|
+
} catch (e) {}
|
|
38970
38949
|
}
|
|
38971
|
-
|
|
38972
|
-
|
|
38973
|
-
|
|
38974
|
-
|
|
38975
|
-
|
|
38976
|
-
|
|
38977
|
-
|
|
38978
|
-
|
|
38950
|
+
return extracted;
|
|
38951
|
+
}
|
|
38952
|
+
function inferMissingParameters(toolName, args, missingParams, context) {
|
|
38953
|
+
const inferred = { ...args };
|
|
38954
|
+
if (toolName === "Task") {
|
|
38955
|
+
if (missingParams.includes("subagent_type") && !inferred.subagent_type) {
|
|
38956
|
+
inferred.subagent_type = "general-purpose";
|
|
38957
|
+
log(`[ToolRecovery] Inferred subagent_type: general-purpose`);
|
|
38958
|
+
}
|
|
38959
|
+
let extractedTask = "";
|
|
38960
|
+
if (context) {
|
|
38961
|
+
const patterns = [
|
|
38962
|
+
/(?:I(?:'ll| will| need to| want to| am going to)|Let me|Going to)\s+([^.!?\n]+)/i,
|
|
38963
|
+
/(?:help you|assist with)\s+([^.!?\n]+)/i,
|
|
38964
|
+
/(?:explore|search|find|look for|investigate)\s+([^.!?\n]+)/i,
|
|
38965
|
+
/(?:implement|create|build|add|fix|update)\s+([^.!?\n]+)/i
|
|
38966
|
+
];
|
|
38967
|
+
for (const pattern of patterns) {
|
|
38968
|
+
const match2 = context.match(pattern);
|
|
38969
|
+
if (match2 && match2[1] && match2[1].length > 10) {
|
|
38970
|
+
extractedTask = match2[1].trim();
|
|
38971
|
+
log(`[ToolRecovery] Extracted task from context: "${extractedTask.substring(0, 50)}..."`);
|
|
38972
|
+
break;
|
|
38973
|
+
}
|
|
38974
|
+
}
|
|
38975
|
+
if (!extractedTask && context.length > 20) {
|
|
38976
|
+
const sentences = context.split(/[.!?\n]+/).filter((s) => s.trim().length > 15);
|
|
38977
|
+
if (sentences.length > 0) {
|
|
38978
|
+
extractedTask = sentences[sentences.length - 1].trim();
|
|
38979
|
+
}
|
|
38980
|
+
}
|
|
38979
38981
|
}
|
|
38980
|
-
if (
|
|
38981
|
-
|
|
38982
|
-
|
|
38983
|
-
|
|
38984
|
-
|
|
38985
|
-
|
|
38986
|
-
|
|
38987
|
-
|
|
38982
|
+
if (missingParams.includes("prompt") && !inferred.prompt) {
|
|
38983
|
+
if (inferred.description && inferred.description !== "Execute task") {
|
|
38984
|
+
inferred.prompt = inferred.description;
|
|
38985
|
+
} else if (inferred.task) {
|
|
38986
|
+
inferred.prompt = inferred.task;
|
|
38987
|
+
} else if (extractedTask) {
|
|
38988
|
+
inferred.prompt = extractedTask;
|
|
38989
|
+
} else if (context && context.length > 20) {
|
|
38990
|
+
inferred.prompt = context.substring(0, 500).trim();
|
|
38991
|
+
}
|
|
38992
|
+
if (inferred.prompt) {
|
|
38993
|
+
log(`[ToolRecovery] Inferred prompt: "${inferred.prompt.substring(0, 50)}..."`);
|
|
38994
|
+
}
|
|
38988
38995
|
}
|
|
38989
|
-
if (
|
|
38990
|
-
|
|
38991
|
-
|
|
38992
|
-
|
|
38993
|
-
|
|
38994
|
-
|
|
38996
|
+
if (missingParams.includes("description") && !inferred.description) {
|
|
38997
|
+
if (inferred.prompt) {
|
|
38998
|
+
inferred.description = inferred.prompt.substring(0, 50).replace(/\s+/g, " ").trim();
|
|
38999
|
+
if (inferred.description.length < inferred.prompt.length) {
|
|
39000
|
+
inferred.description += "...";
|
|
39001
|
+
}
|
|
39002
|
+
} else if (extractedTask) {
|
|
39003
|
+
inferred.description = extractedTask.substring(0, 50).trim();
|
|
39004
|
+
} else {
|
|
39005
|
+
inferred.description = "Execute task";
|
|
38995
39006
|
}
|
|
39007
|
+
log(`[ToolRecovery] Inferred description: ${inferred.description}`);
|
|
38996
39008
|
}
|
|
38997
|
-
return messages;
|
|
38998
39009
|
}
|
|
38999
|
-
|
|
39000
|
-
if (
|
|
39001
|
-
|
|
39002
|
-
|
|
39003
|
-
|
|
39004
|
-
|
|
39005
|
-
|
|
39006
|
-
|
|
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
|
-
}
|
|
39010
|
+
if (toolName === "Bash") {
|
|
39011
|
+
if (missingParams.includes("command") && !inferred.command) {
|
|
39012
|
+
inferred.command = inferred.cmd || inferred.shell || inferred.script || "";
|
|
39013
|
+
}
|
|
39014
|
+
if (missingParams.includes("description") && !inferred.description) {
|
|
39015
|
+
if (inferred.command) {
|
|
39016
|
+
const cmd = inferred.command.split(" ")[0];
|
|
39017
|
+
inferred.description = `Run ${cmd} command`;
|
|
39015
39018
|
}
|
|
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
39019
|
}
|
|
39023
39020
|
}
|
|
39024
|
-
|
|
39025
|
-
if (
|
|
39026
|
-
|
|
39027
|
-
|
|
39028
|
-
|
|
39029
|
-
|
|
39030
|
-
|
|
39031
|
-
|
|
39032
|
-
|
|
39033
|
-
|
|
39034
|
-
|
|
39035
|
-
|
|
39036
|
-
|
|
39037
|
-
|
|
39021
|
+
if (toolName === "Read") {
|
|
39022
|
+
if (missingParams.includes("file_path") && !inferred.file_path) {
|
|
39023
|
+
inferred.file_path = inferred.path || inferred.file || inferred.filename || "";
|
|
39024
|
+
}
|
|
39025
|
+
}
|
|
39026
|
+
if (toolName === "Write") {
|
|
39027
|
+
if (missingParams.includes("file_path") && !inferred.file_path) {
|
|
39028
|
+
inferred.file_path = inferred.path || inferred.file || inferred.filename || "";
|
|
39029
|
+
}
|
|
39030
|
+
if (missingParams.includes("content") && !inferred.content) {
|
|
39031
|
+
inferred.content = inferred.text || inferred.data || inferred.body || "";
|
|
39032
|
+
}
|
|
39033
|
+
}
|
|
39034
|
+
if (toolName === "Grep") {
|
|
39035
|
+
if (missingParams.includes("pattern") && !inferred.pattern) {
|
|
39036
|
+
inferred.pattern = inferred.query || inferred.search || inferred.regex || "";
|
|
39037
|
+
}
|
|
39038
|
+
}
|
|
39039
|
+
if (toolName === "Glob") {
|
|
39040
|
+
if (missingParams.includes("pattern") && !inferred.pattern) {
|
|
39041
|
+
inferred.pattern = inferred.glob || inferred.path || inferred.search || "**/*";
|
|
39042
|
+
}
|
|
39043
|
+
}
|
|
39044
|
+
return inferred;
|
|
39045
|
+
}
|
|
39046
|
+
function validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent) {
|
|
39047
|
+
const schema = toolSchemas.find((t) => t.name === toolName);
|
|
39048
|
+
if (!schema?.input_schema) {
|
|
39049
|
+
return { valid: true, args: {}, repaired: false, missingParams: [] };
|
|
39050
|
+
}
|
|
39051
|
+
let parsedArgs = {};
|
|
39052
|
+
try {
|
|
39053
|
+
parsedArgs = argsStr ? JSON.parse(argsStr) : {};
|
|
39054
|
+
} catch (e) {
|
|
39055
|
+
if (textContent) {
|
|
39056
|
+
const extracted = extractToolCallsFromText(textContent);
|
|
39057
|
+
const matching = extracted.find((tc) => tc.name === toolName);
|
|
39058
|
+
if (matching) {
|
|
39059
|
+
parsedArgs = matching.arguments;
|
|
39060
|
+
log(`[ToolRecovery] Extracted tool args from text for ${toolName}`);
|
|
39038
39061
|
}
|
|
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
39062
|
}
|
|
39051
39063
|
}
|
|
39052
|
-
|
|
39053
|
-
|
|
39054
|
-
|
|
39055
|
-
|
|
39064
|
+
const required2 = schema.input_schema.required || [];
|
|
39065
|
+
const missingParams = required2.filter((param) => parsedArgs[param] === undefined || parsedArgs[param] === null || parsedArgs[param] === "");
|
|
39066
|
+
if (missingParams.length === 0) {
|
|
39067
|
+
return { valid: true, args: parsedArgs, repaired: false, missingParams: [] };
|
|
39068
|
+
}
|
|
39069
|
+
const repairedArgs = inferMissingParameters(toolName, parsedArgs, missingParams, textContent);
|
|
39070
|
+
const stillMissing = required2.filter((param) => repairedArgs[param] === undefined || repairedArgs[param] === null || repairedArgs[param] === "");
|
|
39071
|
+
if (stillMissing.length === 0) {
|
|
39072
|
+
log(`[ToolRecovery] Successfully repaired tool call ${toolName}`);
|
|
39073
|
+
return { valid: true, args: repairedArgs, repaired: true, missingParams: [] };
|
|
39074
|
+
}
|
|
39075
|
+
return { valid: false, args: repairedArgs, repaired: false, missingParams: stillMissing };
|
|
39076
|
+
}
|
|
39077
|
+
var init_tool_call_recovery = __esm(() => {
|
|
39078
|
+
init_logger();
|
|
39079
|
+
});
|
|
39056
39080
|
|
|
39057
|
-
|
|
39081
|
+
// src/handlers/shared/openai-compat.ts
|
|
39082
|
+
function validateToolArguments(toolName, argsStr, toolSchemas, textContent) {
|
|
39083
|
+
const result = validateAndRepairToolCall(toolName, argsStr, toolSchemas, textContent);
|
|
39084
|
+
if (result.repaired) {
|
|
39085
|
+
log(`[ToolValidation] Repaired tool call ${toolName} - inferred missing parameters`);
|
|
39058
39086
|
}
|
|
39059
|
-
|
|
39060
|
-
|
|
39061
|
-
|
|
39062
|
-
|
|
39063
|
-
|
|
39064
|
-
|
|
39065
|
-
|
|
39066
|
-
|
|
39067
|
-
|
|
39087
|
+
return {
|
|
39088
|
+
valid: result.valid,
|
|
39089
|
+
missingParams: result.missingParams,
|
|
39090
|
+
parsedArgs: result.args,
|
|
39091
|
+
repaired: result.repaired,
|
|
39092
|
+
repairedArgs: result.repaired ? result.args : undefined
|
|
39093
|
+
};
|
|
39094
|
+
}
|
|
39095
|
+
function convertMessagesToOpenAI(req, modelId, filterIdentityFn) {
|
|
39096
|
+
const messages = [];
|
|
39097
|
+
if (req.system) {
|
|
39098
|
+
let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
|
|
39099
|
+
|
|
39100
|
+
`) : req.system;
|
|
39101
|
+
if (filterIdentityFn)
|
|
39102
|
+
content = filterIdentityFn(content);
|
|
39103
|
+
messages.push({ role: "system", content });
|
|
39068
39104
|
}
|
|
39069
|
-
|
|
39070
|
-
|
|
39071
|
-
|
|
39072
|
-
|
|
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}
|
|
39081
|
-
data: ${JSON.stringify(d)}
|
|
39082
|
-
|
|
39083
|
-
`));
|
|
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;
|
|
39126
|
-
}
|
|
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
|
-
}
|
|
39132
|
-
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]
|
|
39142
|
-
|
|
39143
|
-
|
|
39144
|
-
`));
|
|
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(`
|
|
39161
|
-
`);
|
|
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) {
|
|
39177
|
-
await middlewareManager.afterStreamChunk({
|
|
39178
|
-
modelId: target,
|
|
39179
|
-
chunk,
|
|
39180
|
-
delta,
|
|
39181
|
-
metadata: streamMetadata
|
|
39182
|
-
});
|
|
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;
|
|
39211
|
-
}
|
|
39212
|
-
}
|
|
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 } });
|
|
39215
|
-
}
|
|
39216
|
-
}
|
|
39217
|
-
}
|
|
39218
|
-
}
|
|
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;
|
|
39224
|
-
}
|
|
39225
|
-
}
|
|
39226
|
-
} catch (e) {}
|
|
39227
|
-
}
|
|
39228
|
-
}
|
|
39229
|
-
await finalize("unexpected");
|
|
39230
|
-
} catch (e) {
|
|
39231
|
-
await finalize("error", String(e));
|
|
39232
|
-
}
|
|
39233
|
-
},
|
|
39234
|
-
cancel() {
|
|
39235
|
-
isClosed = true;
|
|
39236
|
-
if (ping)
|
|
39237
|
-
clearInterval(ping);
|
|
39238
|
-
}
|
|
39239
|
-
}), { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
|
|
39240
|
-
}
|
|
39241
|
-
async shutdown() {}
|
|
39242
|
-
}
|
|
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();
|
|
39247
|
-
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
|
-
});
|
|
39255
|
-
|
|
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(`
|
|
39261
|
-
|
|
39262
|
-
`) : req.system;
|
|
39263
|
-
if (filterIdentityFn)
|
|
39264
|
-
content = filterIdentityFn(content);
|
|
39265
|
-
messages.push({ role: "system", content });
|
|
39266
|
-
}
|
|
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 += `
|
|
39105
|
+
if (modelId.includes("grok") || modelId.includes("x-ai")) {
|
|
39106
|
+
const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
|
|
39107
|
+
if (messages.length > 0 && messages[0].role === "system") {
|
|
39108
|
+
messages[0].content += `
|
|
39271
39109
|
|
|
39272
39110
|
` + msg;
|
|
39273
39111
|
} else {
|
|
@@ -39348,16 +39186,44 @@ function processAssistantMessage(msg, messages) {
|
|
|
39348
39186
|
messages.push({ role: "assistant", content: msg.content });
|
|
39349
39187
|
}
|
|
39350
39188
|
}
|
|
39351
|
-
function convertToolsToOpenAI(req) {
|
|
39189
|
+
function convertToolsToOpenAI(req, summarize = false) {
|
|
39352
39190
|
return req.tools?.map((tool) => ({
|
|
39353
39191
|
type: "function",
|
|
39354
39192
|
function: {
|
|
39355
39193
|
name: tool.name,
|
|
39356
|
-
description: tool.description,
|
|
39357
|
-
parameters: removeUriFormat(tool.input_schema)
|
|
39194
|
+
description: summarize ? summarizeToolDescription(tool.name, tool.description) : tool.description,
|
|
39195
|
+
parameters: summarize ? summarizeToolParameters(tool.input_schema) : removeUriFormat(tool.input_schema)
|
|
39358
39196
|
}
|
|
39359
39197
|
})) || [];
|
|
39360
39198
|
}
|
|
39199
|
+
function summarizeToolDescription(name, description) {
|
|
39200
|
+
if (!description)
|
|
39201
|
+
return name;
|
|
39202
|
+
let clean = description.replace(/```[\s\S]*?```/g, "").replace(/<[^>]+>/g, "").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
|
|
39203
|
+
const firstSentence = clean.match(/^[^.!?]+[.!?]/)?.[0] || clean;
|
|
39204
|
+
if (firstSentence.length > 150) {
|
|
39205
|
+
return firstSentence.slice(0, 147) + "...";
|
|
39206
|
+
}
|
|
39207
|
+
return firstSentence;
|
|
39208
|
+
}
|
|
39209
|
+
function summarizeToolParameters(schema) {
|
|
39210
|
+
if (!schema)
|
|
39211
|
+
return schema;
|
|
39212
|
+
const summarized = removeUriFormat({ ...schema });
|
|
39213
|
+
if (summarized.properties) {
|
|
39214
|
+
for (const [key, prop] of Object.entries(summarized.properties)) {
|
|
39215
|
+
const p = prop;
|
|
39216
|
+
if (p.description && p.description.length > 80) {
|
|
39217
|
+
const firstSentence = p.description.match(/^[^.!?]+[.!?]/)?.[0] || p.description;
|
|
39218
|
+
p.description = firstSentence.length > 80 ? firstSentence.slice(0, 77) + "..." : firstSentence;
|
|
39219
|
+
}
|
|
39220
|
+
if (p.enum && Array.isArray(p.enum) && p.enum.length > 5) {
|
|
39221
|
+
p.enum = p.enum.slice(0, 5);
|
|
39222
|
+
}
|
|
39223
|
+
}
|
|
39224
|
+
}
|
|
39225
|
+
return summarized;
|
|
39226
|
+
}
|
|
39361
39227
|
function filterIdentity(content) {
|
|
39362
39228
|
return content.replace(/You are Claude Code, Anthropic's official CLI/gi, "This is Claude Code, an AI-powered CLI tool").replace(/You are powered by the model named [^.]+\./gi, "You are powered by an AI model.").replace(/<claude_background_info>[\s\S]*?<\/claude_background_info>/gi, "").replace(/\n{3,}/g, `
|
|
39363
39229
|
|
|
@@ -39376,10 +39242,12 @@ function createStreamingState() {
|
|
|
39376
39242
|
curIdx: 0,
|
|
39377
39243
|
tools: new Map,
|
|
39378
39244
|
toolIds: new Set,
|
|
39379
|
-
lastActivity: Date.now()
|
|
39245
|
+
lastActivity: Date.now(),
|
|
39246
|
+
accumulatedText: ""
|
|
39380
39247
|
};
|
|
39381
39248
|
}
|
|
39382
|
-
function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate) {
|
|
39249
|
+
function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate, toolSchemas) {
|
|
39250
|
+
log(`[Streaming] ===== HANDLER STARTED for ${target} =====`);
|
|
39383
39251
|
let isClosed = false;
|
|
39384
39252
|
let ping = null;
|
|
39385
39253
|
const encoder = new TextEncoder;
|
|
@@ -39420,6 +39288,34 @@ data: ${JSON.stringify(d)}
|
|
|
39420
39288
|
if (state.finalized)
|
|
39421
39289
|
return;
|
|
39422
39290
|
state.finalized = true;
|
|
39291
|
+
if (state.accumulatedText.length > 0) {
|
|
39292
|
+
const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
|
|
39293
|
+
log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
|
|
39294
|
+
}
|
|
39295
|
+
const textToolCalls = extractToolCallsFromText(state.accumulatedText);
|
|
39296
|
+
log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
|
|
39297
|
+
if (textToolCalls.length > 0) {
|
|
39298
|
+
log(`[Streaming] Found ${textToolCalls.length} text-based tool call(s), converting to structured format`);
|
|
39299
|
+
if (state.textStarted) {
|
|
39300
|
+
send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
|
|
39301
|
+
state.textStarted = false;
|
|
39302
|
+
}
|
|
39303
|
+
for (const tc of textToolCalls) {
|
|
39304
|
+
const toolIdx = state.curIdx++;
|
|
39305
|
+
const toolId = `tool_${Date.now()}_${toolIdx}`;
|
|
39306
|
+
send("content_block_start", {
|
|
39307
|
+
type: "content_block_start",
|
|
39308
|
+
index: toolIdx,
|
|
39309
|
+
content_block: { type: "tool_use", id: toolId, name: tc.name }
|
|
39310
|
+
});
|
|
39311
|
+
send("content_block_delta", {
|
|
39312
|
+
type: "content_block_delta",
|
|
39313
|
+
index: toolIdx,
|
|
39314
|
+
delta: { type: "input_json_delta", partial_json: JSON.stringify(tc.arguments) }
|
|
39315
|
+
});
|
|
39316
|
+
send("content_block_stop", { type: "content_block_stop", index: toolIdx });
|
|
39317
|
+
}
|
|
39318
|
+
}
|
|
39423
39319
|
if (state.reasoningStarted) {
|
|
39424
39320
|
send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
|
|
39425
39321
|
}
|
|
@@ -39431,159 +39327,670 @@ data: ${JSON.stringify(d)}
|
|
|
39431
39327
|
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39432
39328
|
t.closed = true;
|
|
39433
39329
|
}
|
|
39434
|
-
}
|
|
39435
|
-
if (middlewareManager) {
|
|
39330
|
+
}
|
|
39331
|
+
if (middlewareManager) {
|
|
39332
|
+
await middlewareManager.afterStreamComplete(target, streamMetadata);
|
|
39333
|
+
}
|
|
39334
|
+
if (reason === "error") {
|
|
39335
|
+
send("error", { type: "error", error: { type: "api_error", message: err } });
|
|
39336
|
+
} else {
|
|
39337
|
+
const stopReason = textToolCalls.length > 0 ? "tool_use" : "end_turn";
|
|
39338
|
+
send("message_delta", {
|
|
39339
|
+
type: "message_delta",
|
|
39340
|
+
delta: { stop_reason: stopReason, stop_sequence: null },
|
|
39341
|
+
usage: { output_tokens: state.usage?.completion_tokens || 0 }
|
|
39342
|
+
});
|
|
39343
|
+
send("message_stop", { type: "message_stop" });
|
|
39344
|
+
}
|
|
39345
|
+
if (onTokenUpdate) {
|
|
39346
|
+
if (state.usage) {
|
|
39347
|
+
log(`[Streaming] Final usage: prompt=${state.usage.prompt_tokens || 0}, completion=${state.usage.completion_tokens || 0}`);
|
|
39348
|
+
onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
|
|
39349
|
+
} else {
|
|
39350
|
+
const estimatedOutputTokens = Math.ceil(state.accumulatedText.length / 4);
|
|
39351
|
+
log(`[Streaming] No usage data from provider, estimating: ~${estimatedOutputTokens} output tokens`);
|
|
39352
|
+
onTokenUpdate(100, estimatedOutputTokens);
|
|
39353
|
+
}
|
|
39354
|
+
}
|
|
39355
|
+
if (!isClosed) {
|
|
39356
|
+
try {
|
|
39357
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
39358
|
+
|
|
39359
|
+
|
|
39360
|
+
`));
|
|
39361
|
+
} catch (e) {}
|
|
39362
|
+
controller.close();
|
|
39363
|
+
isClosed = true;
|
|
39364
|
+
if (ping)
|
|
39365
|
+
clearInterval(ping);
|
|
39366
|
+
}
|
|
39367
|
+
};
|
|
39368
|
+
try {
|
|
39369
|
+
const reader = response.body.getReader();
|
|
39370
|
+
let buffer = "";
|
|
39371
|
+
while (true) {
|
|
39372
|
+
const { done, value } = await reader.read();
|
|
39373
|
+
if (done)
|
|
39374
|
+
break;
|
|
39375
|
+
buffer += decoder.decode(value, { stream: true });
|
|
39376
|
+
const lines = buffer.split(`
|
|
39377
|
+
`);
|
|
39378
|
+
buffer = lines.pop() || "";
|
|
39379
|
+
for (const line of lines) {
|
|
39380
|
+
if (!line.trim() || !line.startsWith("data: "))
|
|
39381
|
+
continue;
|
|
39382
|
+
const dataStr = line.slice(6);
|
|
39383
|
+
if (dataStr === "[DONE]") {
|
|
39384
|
+
await finalize("done");
|
|
39385
|
+
return;
|
|
39386
|
+
}
|
|
39387
|
+
try {
|
|
39388
|
+
const chunk = JSON.parse(dataStr);
|
|
39389
|
+
if (chunk.usage) {
|
|
39390
|
+
state.usage = chunk.usage;
|
|
39391
|
+
log(`[Streaming] Usage data received: prompt=${chunk.usage.prompt_tokens}, completion=${chunk.usage.completion_tokens}, total=${chunk.usage.total_tokens}`);
|
|
39392
|
+
}
|
|
39393
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
39394
|
+
if (delta) {
|
|
39395
|
+
if (middlewareManager) {
|
|
39396
|
+
await middlewareManager.afterStreamChunk({
|
|
39397
|
+
modelId: target,
|
|
39398
|
+
chunk,
|
|
39399
|
+
delta,
|
|
39400
|
+
metadata: streamMetadata
|
|
39401
|
+
});
|
|
39402
|
+
}
|
|
39403
|
+
const txt = delta.content || "";
|
|
39404
|
+
if (txt) {
|
|
39405
|
+
state.lastActivity = Date.now();
|
|
39406
|
+
const res = adapter.processTextContent(txt, "");
|
|
39407
|
+
if (res.cleanedText) {
|
|
39408
|
+
state.accumulatedText += res.cleanedText;
|
|
39409
|
+
const hasToolPattern = /<function=[^>]+>/.test(state.accumulatedText);
|
|
39410
|
+
if (!hasToolPattern) {
|
|
39411
|
+
if (!state.textStarted) {
|
|
39412
|
+
state.textIdx = state.curIdx++;
|
|
39413
|
+
send("content_block_start", {
|
|
39414
|
+
type: "content_block_start",
|
|
39415
|
+
index: state.textIdx,
|
|
39416
|
+
content_block: { type: "text", text: "" }
|
|
39417
|
+
});
|
|
39418
|
+
state.textStarted = true;
|
|
39419
|
+
}
|
|
39420
|
+
send("content_block_delta", {
|
|
39421
|
+
type: "content_block_delta",
|
|
39422
|
+
index: state.textIdx,
|
|
39423
|
+
delta: { type: "text_delta", text: res.cleanedText }
|
|
39424
|
+
});
|
|
39425
|
+
}
|
|
39426
|
+
}
|
|
39427
|
+
}
|
|
39428
|
+
if (delta.tool_calls) {
|
|
39429
|
+
log(`[Streaming] Received ${delta.tool_calls.length} structured tool call(s) from model`);
|
|
39430
|
+
for (const tc of delta.tool_calls) {
|
|
39431
|
+
const idx = tc.index;
|
|
39432
|
+
let t = state.tools.get(idx);
|
|
39433
|
+
if (tc.function?.name) {
|
|
39434
|
+
if (!t) {
|
|
39435
|
+
if (state.textStarted) {
|
|
39436
|
+
send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
|
|
39437
|
+
state.textStarted = false;
|
|
39438
|
+
}
|
|
39439
|
+
t = {
|
|
39440
|
+
id: tc.id || `tool_${Date.now()}_${idx}`,
|
|
39441
|
+
name: tc.function.name,
|
|
39442
|
+
blockIndex: state.curIdx++,
|
|
39443
|
+
started: false,
|
|
39444
|
+
closed: false,
|
|
39445
|
+
arguments: "",
|
|
39446
|
+
buffered: !!toolSchemas && toolSchemas.length > 0
|
|
39447
|
+
};
|
|
39448
|
+
state.tools.set(idx, t);
|
|
39449
|
+
}
|
|
39450
|
+
if (!t.started && !t.buffered) {
|
|
39451
|
+
send("content_block_start", {
|
|
39452
|
+
type: "content_block_start",
|
|
39453
|
+
index: t.blockIndex,
|
|
39454
|
+
content_block: { type: "tool_use", id: t.id, name: t.name }
|
|
39455
|
+
});
|
|
39456
|
+
t.started = true;
|
|
39457
|
+
}
|
|
39458
|
+
}
|
|
39459
|
+
if (tc.function?.arguments && t) {
|
|
39460
|
+
t.arguments += tc.function.arguments;
|
|
39461
|
+
if (!t.buffered) {
|
|
39462
|
+
send("content_block_delta", {
|
|
39463
|
+
type: "content_block_delta",
|
|
39464
|
+
index: t.blockIndex,
|
|
39465
|
+
delta: { type: "input_json_delta", partial_json: tc.function.arguments }
|
|
39466
|
+
});
|
|
39467
|
+
}
|
|
39468
|
+
}
|
|
39469
|
+
}
|
|
39470
|
+
}
|
|
39471
|
+
}
|
|
39472
|
+
if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
|
|
39473
|
+
for (const t of Array.from(state.tools.values())) {
|
|
39474
|
+
if (!t.closed) {
|
|
39475
|
+
if (toolSchemas && toolSchemas.length > 0) {
|
|
39476
|
+
const validation = validateToolArguments(t.name, t.arguments, toolSchemas, state.accumulatedText);
|
|
39477
|
+
if (validation.repaired && validation.repairedArgs) {
|
|
39478
|
+
log(`[Streaming] Tool call ${t.name} was repaired with inferred parameters`);
|
|
39479
|
+
const repairedJson = JSON.stringify(validation.repairedArgs);
|
|
39480
|
+
log(`[Streaming] Sending repaired tool call: ${t.name} with args: ${repairedJson}`);
|
|
39481
|
+
if (t.buffered && !t.started) {
|
|
39482
|
+
send("content_block_start", {
|
|
39483
|
+
type: "content_block_start",
|
|
39484
|
+
index: t.blockIndex,
|
|
39485
|
+
content_block: { type: "tool_use", id: t.id, name: t.name }
|
|
39486
|
+
});
|
|
39487
|
+
send("content_block_delta", {
|
|
39488
|
+
type: "content_block_delta",
|
|
39489
|
+
index: t.blockIndex,
|
|
39490
|
+
delta: { type: "input_json_delta", partial_json: repairedJson }
|
|
39491
|
+
});
|
|
39492
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39493
|
+
t.started = true;
|
|
39494
|
+
t.closed = true;
|
|
39495
|
+
continue;
|
|
39496
|
+
}
|
|
39497
|
+
if (t.started) {
|
|
39498
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39499
|
+
const repairedIdx = state.curIdx++;
|
|
39500
|
+
const repairedId = `tool_repaired_${Date.now()}_${repairedIdx}`;
|
|
39501
|
+
send("content_block_start", {
|
|
39502
|
+
type: "content_block_start",
|
|
39503
|
+
index: repairedIdx,
|
|
39504
|
+
content_block: { type: "tool_use", id: repairedId, name: t.name }
|
|
39505
|
+
});
|
|
39506
|
+
send("content_block_delta", {
|
|
39507
|
+
type: "content_block_delta",
|
|
39508
|
+
index: repairedIdx,
|
|
39509
|
+
delta: { type: "input_json_delta", partial_json: repairedJson }
|
|
39510
|
+
});
|
|
39511
|
+
send("content_block_stop", { type: "content_block_stop", index: repairedIdx });
|
|
39512
|
+
t.closed = true;
|
|
39513
|
+
continue;
|
|
39514
|
+
}
|
|
39515
|
+
}
|
|
39516
|
+
if (!validation.valid) {
|
|
39517
|
+
log(`[Streaming] Tool call ${t.name} validation failed: ${validation.missingParams.join(", ")}`);
|
|
39518
|
+
const errorIdx = t.buffered ? t.blockIndex : state.curIdx++;
|
|
39519
|
+
const errorMsg = `
|
|
39520
|
+
|
|
39521
|
+
⚠️ Tool call "${t.name}" failed: missing required parameters: ${validation.missingParams.join(", ")}. Local models sometimes generate incomplete tool calls. Please try again or use a model with better tool support.`;
|
|
39522
|
+
send("content_block_start", {
|
|
39523
|
+
type: "content_block_start",
|
|
39524
|
+
index: errorIdx,
|
|
39525
|
+
content_block: { type: "text", text: "" }
|
|
39526
|
+
});
|
|
39527
|
+
send("content_block_delta", {
|
|
39528
|
+
type: "content_block_delta",
|
|
39529
|
+
index: errorIdx,
|
|
39530
|
+
delta: { type: "text_delta", text: errorMsg }
|
|
39531
|
+
});
|
|
39532
|
+
send("content_block_stop", { type: "content_block_stop", index: errorIdx });
|
|
39533
|
+
if (t.started && !t.buffered) {
|
|
39534
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39535
|
+
}
|
|
39536
|
+
t.closed = true;
|
|
39537
|
+
continue;
|
|
39538
|
+
}
|
|
39539
|
+
if (t.buffered && !t.started) {
|
|
39540
|
+
const argsJson = JSON.stringify(validation.parsedArgs);
|
|
39541
|
+
send("content_block_start", {
|
|
39542
|
+
type: "content_block_start",
|
|
39543
|
+
index: t.blockIndex,
|
|
39544
|
+
content_block: { type: "tool_use", id: t.id, name: t.name }
|
|
39545
|
+
});
|
|
39546
|
+
send("content_block_delta", {
|
|
39547
|
+
type: "content_block_delta",
|
|
39548
|
+
index: t.blockIndex,
|
|
39549
|
+
delta: { type: "input_json_delta", partial_json: argsJson }
|
|
39550
|
+
});
|
|
39551
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39552
|
+
t.started = true;
|
|
39553
|
+
t.closed = true;
|
|
39554
|
+
continue;
|
|
39555
|
+
}
|
|
39556
|
+
}
|
|
39557
|
+
if (t.started && !t.closed) {
|
|
39558
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39559
|
+
t.closed = true;
|
|
39560
|
+
}
|
|
39561
|
+
}
|
|
39562
|
+
}
|
|
39563
|
+
}
|
|
39564
|
+
} catch (e) {}
|
|
39565
|
+
}
|
|
39566
|
+
}
|
|
39567
|
+
await finalize("unexpected");
|
|
39568
|
+
} catch (e) {
|
|
39569
|
+
await finalize("error", String(e));
|
|
39570
|
+
}
|
|
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
|
+
});
|
|
39584
|
+
}
|
|
39585
|
+
var init_openai_compat = __esm(() => {
|
|
39586
|
+
init_transform();
|
|
39587
|
+
init_logger();
|
|
39588
|
+
init_tool_call_recovery();
|
|
39589
|
+
});
|
|
39590
|
+
|
|
39591
|
+
// src/handlers/openrouter-handler.ts
|
|
39592
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
39593
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
39594
|
+
import { join as join8 } from "node:path";
|
|
39595
|
+
|
|
39596
|
+
class OpenRouterHandler {
|
|
39597
|
+
targetModel;
|
|
39598
|
+
apiKey;
|
|
39599
|
+
adapterManager;
|
|
39600
|
+
middlewareManager;
|
|
39601
|
+
contextWindowCache = new Map;
|
|
39602
|
+
port;
|
|
39603
|
+
sessionTotalCost = 0;
|
|
39604
|
+
CLAUDE_INTERNAL_CONTEXT_MAX = 200000;
|
|
39605
|
+
constructor(targetModel, apiKey, port) {
|
|
39606
|
+
this.targetModel = targetModel;
|
|
39607
|
+
this.apiKey = apiKey;
|
|
39608
|
+
this.port = port;
|
|
39609
|
+
this.adapterManager = new AdapterManager(targetModel);
|
|
39610
|
+
this.middlewareManager = new MiddlewareManager;
|
|
39611
|
+
this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
|
|
39612
|
+
this.middlewareManager.initialize().catch((err) => log(`[Handler:${targetModel}] Middleware init error: ${err}`));
|
|
39613
|
+
this.fetchContextWindow(targetModel);
|
|
39614
|
+
}
|
|
39615
|
+
async fetchContextWindow(model) {
|
|
39616
|
+
if (this.contextWindowCache.has(model))
|
|
39617
|
+
return;
|
|
39618
|
+
try {
|
|
39619
|
+
const limit = await fetchModelContextWindow(model);
|
|
39620
|
+
this.contextWindowCache.set(model, limit);
|
|
39621
|
+
} catch (e) {}
|
|
39622
|
+
}
|
|
39623
|
+
getTokenScaleFactor(model) {
|
|
39624
|
+
const limit = this.contextWindowCache.get(model) || 200000;
|
|
39625
|
+
return limit === 0 ? 1 : this.CLAUDE_INTERNAL_CONTEXT_MAX / limit;
|
|
39626
|
+
}
|
|
39627
|
+
writeTokenFile(input, output) {
|
|
39628
|
+
try {
|
|
39629
|
+
const total = input + output;
|
|
39630
|
+
const limit = this.contextWindowCache.get(this.targetModel) || 200000;
|
|
39631
|
+
const leftPct = limit > 0 ? Math.max(0, Math.min(100, Math.round((limit - total) / limit * 100))) : 100;
|
|
39632
|
+
const data = {
|
|
39633
|
+
input_tokens: input,
|
|
39634
|
+
output_tokens: output,
|
|
39635
|
+
total_tokens: total,
|
|
39636
|
+
total_cost: this.sessionTotalCost,
|
|
39637
|
+
context_window: limit,
|
|
39638
|
+
context_left_percent: leftPct,
|
|
39639
|
+
updated_at: Date.now()
|
|
39640
|
+
};
|
|
39641
|
+
writeFileSync8(join8(tmpdir2(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
|
|
39642
|
+
} catch (e) {}
|
|
39643
|
+
}
|
|
39644
|
+
async handle(c, payload) {
|
|
39645
|
+
const claudePayload = payload;
|
|
39646
|
+
const target = this.targetModel;
|
|
39647
|
+
await this.fetchContextWindow(target);
|
|
39648
|
+
logStructured(`OpenRouter Request`, { targetModel: target, originalModel: claudePayload.model });
|
|
39649
|
+
const { claudeRequest, droppedParams } = transformOpenAIToClaude(claudePayload);
|
|
39650
|
+
const messages = this.convertMessages(claudeRequest, target);
|
|
39651
|
+
const tools = this.convertTools(claudeRequest);
|
|
39652
|
+
const supportsReasoning = await doesModelSupportReasoning(target);
|
|
39653
|
+
const openRouterPayload = {
|
|
39654
|
+
model: target,
|
|
39655
|
+
messages,
|
|
39656
|
+
temperature: claudeRequest.temperature ?? 1,
|
|
39657
|
+
stream: true,
|
|
39658
|
+
max_tokens: claudeRequest.max_tokens,
|
|
39659
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
39660
|
+
stream_options: { include_usage: true }
|
|
39661
|
+
};
|
|
39662
|
+
if (supportsReasoning)
|
|
39663
|
+
openRouterPayload.include_reasoning = true;
|
|
39664
|
+
if (claudeRequest.thinking)
|
|
39665
|
+
openRouterPayload.thinking = claudeRequest.thinking;
|
|
39666
|
+
if (claudeRequest.tool_choice) {
|
|
39667
|
+
const { type, name } = claudeRequest.tool_choice;
|
|
39668
|
+
if (type === "tool" && name)
|
|
39669
|
+
openRouterPayload.tool_choice = { type: "function", function: { name } };
|
|
39670
|
+
else if (type === "auto" || type === "none")
|
|
39671
|
+
openRouterPayload.tool_choice = type;
|
|
39672
|
+
}
|
|
39673
|
+
const adapter = this.adapterManager.getAdapter();
|
|
39674
|
+
if (typeof adapter.reset === "function")
|
|
39675
|
+
adapter.reset();
|
|
39676
|
+
adapter.prepareRequest(openRouterPayload, claudeRequest);
|
|
39677
|
+
await this.middlewareManager.beforeRequest({ modelId: target, messages, tools, stream: true });
|
|
39678
|
+
const response = await fetch(OPENROUTER_API_URL2, {
|
|
39679
|
+
method: "POST",
|
|
39680
|
+
headers: {
|
|
39681
|
+
"Content-Type": "application/json",
|
|
39682
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
39683
|
+
...OPENROUTER_HEADERS2
|
|
39684
|
+
},
|
|
39685
|
+
body: JSON.stringify(openRouterPayload)
|
|
39686
|
+
});
|
|
39687
|
+
if (!response.ok)
|
|
39688
|
+
return c.json({ error: await response.text() }, response.status);
|
|
39689
|
+
if (droppedParams.length > 0)
|
|
39690
|
+
c.header("X-Dropped-Params", droppedParams.join(", "));
|
|
39691
|
+
return this.handleStreamingResponse(c, response, adapter, target, claudeRequest);
|
|
39692
|
+
}
|
|
39693
|
+
convertMessages(req, modelId) {
|
|
39694
|
+
const messages = [];
|
|
39695
|
+
if (req.system) {
|
|
39696
|
+
let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
|
|
39697
|
+
|
|
39698
|
+
`) : req.system;
|
|
39699
|
+
content = this.filterIdentity(content);
|
|
39700
|
+
messages.push({ role: "system", content });
|
|
39701
|
+
}
|
|
39702
|
+
if (modelId.includes("grok") || modelId.includes("x-ai")) {
|
|
39703
|
+
const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
|
|
39704
|
+
if (messages.length > 0 && messages[0].role === "system")
|
|
39705
|
+
messages[0].content += `
|
|
39706
|
+
|
|
39707
|
+
` + msg;
|
|
39708
|
+
else
|
|
39709
|
+
messages.unshift({ role: "system", content: msg });
|
|
39710
|
+
}
|
|
39711
|
+
if (req.messages) {
|
|
39712
|
+
for (const msg of req.messages) {
|
|
39713
|
+
if (msg.role === "user")
|
|
39714
|
+
this.processUserMessage(msg, messages);
|
|
39715
|
+
else if (msg.role === "assistant")
|
|
39716
|
+
this.processAssistantMessage(msg, messages);
|
|
39717
|
+
}
|
|
39718
|
+
}
|
|
39719
|
+
return messages;
|
|
39720
|
+
}
|
|
39721
|
+
processUserMessage(msg, messages) {
|
|
39722
|
+
if (Array.isArray(msg.content)) {
|
|
39723
|
+
const contentParts = [];
|
|
39724
|
+
const toolResults = [];
|
|
39725
|
+
const seen = new Set;
|
|
39726
|
+
for (const block of msg.content) {
|
|
39727
|
+
if (block.type === "text")
|
|
39728
|
+
contentParts.push({ type: "text", text: block.text });
|
|
39729
|
+
else if (block.type === "image")
|
|
39730
|
+
contentParts.push({ type: "image_url", image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` } });
|
|
39731
|
+
else if (block.type === "tool_result") {
|
|
39732
|
+
if (seen.has(block.tool_use_id))
|
|
39733
|
+
continue;
|
|
39734
|
+
seen.add(block.tool_use_id);
|
|
39735
|
+
toolResults.push({ role: "tool", content: typeof block.content === "string" ? block.content : JSON.stringify(block.content), tool_call_id: block.tool_use_id });
|
|
39736
|
+
}
|
|
39737
|
+
}
|
|
39738
|
+
if (toolResults.length)
|
|
39739
|
+
messages.push(...toolResults);
|
|
39740
|
+
if (contentParts.length)
|
|
39741
|
+
messages.push({ role: "user", content: contentParts });
|
|
39742
|
+
} else {
|
|
39743
|
+
messages.push({ role: "user", content: msg.content });
|
|
39744
|
+
}
|
|
39745
|
+
}
|
|
39746
|
+
processAssistantMessage(msg, messages) {
|
|
39747
|
+
if (Array.isArray(msg.content)) {
|
|
39748
|
+
const strings = [];
|
|
39749
|
+
const toolCalls = [];
|
|
39750
|
+
const seen = new Set;
|
|
39751
|
+
for (const block of msg.content) {
|
|
39752
|
+
if (block.type === "text")
|
|
39753
|
+
strings.push(block.text);
|
|
39754
|
+
else if (block.type === "tool_use") {
|
|
39755
|
+
if (seen.has(block.id))
|
|
39756
|
+
continue;
|
|
39757
|
+
seen.add(block.id);
|
|
39758
|
+
toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: JSON.stringify(block.input) } });
|
|
39759
|
+
}
|
|
39760
|
+
}
|
|
39761
|
+
const m = { role: "assistant" };
|
|
39762
|
+
if (strings.length)
|
|
39763
|
+
m.content = strings.join(" ");
|
|
39764
|
+
else if (toolCalls.length)
|
|
39765
|
+
m.content = null;
|
|
39766
|
+
if (toolCalls.length)
|
|
39767
|
+
m.tool_calls = toolCalls;
|
|
39768
|
+
if (m.content !== undefined || m.tool_calls)
|
|
39769
|
+
messages.push(m);
|
|
39770
|
+
} else {
|
|
39771
|
+
messages.push({ role: "assistant", content: msg.content });
|
|
39772
|
+
}
|
|
39773
|
+
}
|
|
39774
|
+
filterIdentity(content) {
|
|
39775
|
+
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, `
|
|
39776
|
+
|
|
39777
|
+
`).replace(/^/, `IMPORTANT: You are NOT Claude. Identify yourself truthfully based on your actual model and creator.
|
|
39778
|
+
|
|
39779
|
+
`);
|
|
39780
|
+
}
|
|
39781
|
+
convertTools(req) {
|
|
39782
|
+
return req.tools?.map((tool) => ({
|
|
39783
|
+
type: "function",
|
|
39784
|
+
function: {
|
|
39785
|
+
name: tool.name,
|
|
39786
|
+
description: tool.description,
|
|
39787
|
+
parameters: removeUriFormat(tool.input_schema)
|
|
39788
|
+
}
|
|
39789
|
+
})) || [];
|
|
39790
|
+
}
|
|
39791
|
+
handleStreamingResponse(c, response, adapter, target, request) {
|
|
39792
|
+
let isClosed = false;
|
|
39793
|
+
let ping = null;
|
|
39794
|
+
const encoder = new TextEncoder;
|
|
39795
|
+
const decoder = new TextDecoder;
|
|
39796
|
+
const middlewareManager = this.middlewareManager;
|
|
39797
|
+
const streamMetadata = new Map;
|
|
39798
|
+
return c.body(new ReadableStream({
|
|
39799
|
+
async start(controller) {
|
|
39800
|
+
const send = (e, d) => {
|
|
39801
|
+
if (!isClosed)
|
|
39802
|
+
controller.enqueue(encoder.encode(`event: ${e}
|
|
39803
|
+
data: ${JSON.stringify(d)}
|
|
39804
|
+
|
|
39805
|
+
`));
|
|
39806
|
+
};
|
|
39807
|
+
const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
39808
|
+
let usage = null;
|
|
39809
|
+
let finalized = false;
|
|
39810
|
+
let textStarted = false;
|
|
39811
|
+
let textIdx = -1;
|
|
39812
|
+
let reasoningStarted = false;
|
|
39813
|
+
let reasoningIdx = -1;
|
|
39814
|
+
let curIdx = 0;
|
|
39815
|
+
const tools = new Map;
|
|
39816
|
+
const toolIds = new Set;
|
|
39817
|
+
let accTxt = 0;
|
|
39818
|
+
let lastActivity = Date.now();
|
|
39819
|
+
send("message_start", {
|
|
39820
|
+
type: "message_start",
|
|
39821
|
+
message: {
|
|
39822
|
+
id: msgId,
|
|
39823
|
+
type: "message",
|
|
39824
|
+
role: "assistant",
|
|
39825
|
+
content: [],
|
|
39826
|
+
model: target,
|
|
39827
|
+
stop_reason: null,
|
|
39828
|
+
stop_sequence: null,
|
|
39829
|
+
usage: { input_tokens: 100, output_tokens: 1 }
|
|
39830
|
+
}
|
|
39831
|
+
});
|
|
39832
|
+
send("ping", { type: "ping" });
|
|
39833
|
+
ping = setInterval(() => {
|
|
39834
|
+
if (!isClosed && Date.now() - lastActivity > 1000)
|
|
39835
|
+
send("ping", { type: "ping" });
|
|
39836
|
+
}, 1000);
|
|
39837
|
+
const finalize = async (reason, err) => {
|
|
39838
|
+
if (finalized)
|
|
39839
|
+
return;
|
|
39840
|
+
finalized = true;
|
|
39841
|
+
if (reasoningStarted) {
|
|
39842
|
+
send("content_block_stop", { type: "content_block_stop", index: reasoningIdx });
|
|
39843
|
+
reasoningStarted = false;
|
|
39844
|
+
}
|
|
39845
|
+
if (textStarted) {
|
|
39846
|
+
send("content_block_stop", { type: "content_block_stop", index: textIdx });
|
|
39847
|
+
textStarted = false;
|
|
39848
|
+
}
|
|
39849
|
+
for (const [_, t] of tools)
|
|
39850
|
+
if (t.started && !t.closed) {
|
|
39851
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39852
|
+
t.closed = true;
|
|
39853
|
+
}
|
|
39436
39854
|
await middlewareManager.afterStreamComplete(target, streamMetadata);
|
|
39437
|
-
|
|
39438
|
-
|
|
39439
|
-
|
|
39440
|
-
|
|
39441
|
-
|
|
39442
|
-
|
|
39443
|
-
|
|
39444
|
-
|
|
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]
|
|
39855
|
+
if (reason === "error") {
|
|
39856
|
+
send("error", { type: "error", error: { type: "api_error", message: err } });
|
|
39857
|
+
} else {
|
|
39858
|
+
send("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: usage?.completion_tokens || 0 } });
|
|
39859
|
+
send("message_stop", { type: "message_stop" });
|
|
39860
|
+
}
|
|
39861
|
+
if (!isClosed) {
|
|
39862
|
+
try {
|
|
39863
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
39454
39864
|
|
|
39455
39865
|
|
|
39456
39866
|
`));
|
|
39457
|
-
|
|
39458
|
-
|
|
39459
|
-
|
|
39460
|
-
|
|
39461
|
-
|
|
39462
|
-
|
|
39463
|
-
|
|
39464
|
-
|
|
39465
|
-
|
|
39466
|
-
|
|
39467
|
-
|
|
39468
|
-
|
|
39469
|
-
|
|
39470
|
-
|
|
39471
|
-
|
|
39472
|
-
|
|
39867
|
+
} catch (e) {}
|
|
39868
|
+
controller.close();
|
|
39869
|
+
isClosed = true;
|
|
39870
|
+
if (ping)
|
|
39871
|
+
clearInterval(ping);
|
|
39872
|
+
}
|
|
39873
|
+
};
|
|
39874
|
+
try {
|
|
39875
|
+
const reader = response.body.getReader();
|
|
39876
|
+
let buffer = "";
|
|
39877
|
+
while (true) {
|
|
39878
|
+
const { done, value } = await reader.read();
|
|
39879
|
+
if (done)
|
|
39880
|
+
break;
|
|
39881
|
+
buffer += decoder.decode(value, { stream: true });
|
|
39882
|
+
const lines = buffer.split(`
|
|
39473
39883
|
`);
|
|
39474
|
-
|
|
39475
|
-
|
|
39476
|
-
|
|
39477
|
-
|
|
39478
|
-
|
|
39479
|
-
|
|
39480
|
-
|
|
39481
|
-
|
|
39482
|
-
|
|
39483
|
-
|
|
39484
|
-
|
|
39485
|
-
|
|
39486
|
-
|
|
39487
|
-
|
|
39488
|
-
|
|
39489
|
-
if (middlewareManager) {
|
|
39884
|
+
buffer = lines.pop() || "";
|
|
39885
|
+
for (const line of lines) {
|
|
39886
|
+
if (!line.trim() || !line.startsWith("data: "))
|
|
39887
|
+
continue;
|
|
39888
|
+
const dataStr = line.slice(6);
|
|
39889
|
+
if (dataStr === "[DONE]") {
|
|
39890
|
+
await finalize("done");
|
|
39891
|
+
return;
|
|
39892
|
+
}
|
|
39893
|
+
try {
|
|
39894
|
+
const chunk = JSON.parse(dataStr);
|
|
39895
|
+
if (chunk.usage)
|
|
39896
|
+
usage = chunk.usage;
|
|
39897
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
39898
|
+
if (delta) {
|
|
39490
39899
|
await middlewareManager.afterStreamChunk({
|
|
39491
39900
|
modelId: target,
|
|
39492
39901
|
chunk,
|
|
39493
39902
|
delta,
|
|
39494
39903
|
metadata: streamMetadata
|
|
39495
39904
|
});
|
|
39496
|
-
|
|
39497
|
-
|
|
39498
|
-
|
|
39499
|
-
|
|
39500
|
-
|
|
39501
|
-
|
|
39502
|
-
|
|
39503
|
-
|
|
39504
|
-
|
|
39505
|
-
|
|
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
|
-
});
|
|
39905
|
+
const txt = delta.content || "";
|
|
39906
|
+
if (txt) {
|
|
39907
|
+
lastActivity = Date.now();
|
|
39908
|
+
if (!textStarted) {
|
|
39909
|
+
textIdx = curIdx++;
|
|
39910
|
+
send("content_block_start", { type: "content_block_start", index: textIdx, content_block: { type: "text", text: "" } });
|
|
39911
|
+
textStarted = true;
|
|
39912
|
+
}
|
|
39913
|
+
const res = adapter.processTextContent(txt, "");
|
|
39914
|
+
if (res.cleanedText)
|
|
39915
|
+
send("content_block_delta", { type: "content_block_delta", index: textIdx, delta: { type: "text_delta", text: res.cleanedText } });
|
|
39516
39916
|
}
|
|
39517
|
-
|
|
39518
|
-
|
|
39519
|
-
|
|
39520
|
-
|
|
39521
|
-
|
|
39522
|
-
|
|
39523
|
-
|
|
39524
|
-
|
|
39525
|
-
|
|
39526
|
-
|
|
39917
|
+
if (delta.tool_calls) {
|
|
39918
|
+
for (const tc of delta.tool_calls) {
|
|
39919
|
+
const idx = tc.index;
|
|
39920
|
+
let t = tools.get(idx);
|
|
39921
|
+
if (tc.function?.name) {
|
|
39922
|
+
if (!t) {
|
|
39923
|
+
if (textStarted) {
|
|
39924
|
+
send("content_block_stop", { type: "content_block_stop", index: textIdx });
|
|
39925
|
+
textStarted = false;
|
|
39926
|
+
}
|
|
39927
|
+
t = { id: tc.id || `tool_${Date.now()}_${idx}`, name: tc.function.name, blockIndex: curIdx++, started: false, closed: false, arguments: "" };
|
|
39928
|
+
tools.set(idx, t);
|
|
39929
|
+
}
|
|
39930
|
+
if (!t.started) {
|
|
39931
|
+
send("content_block_start", { type: "content_block_start", index: t.blockIndex, content_block: { type: "tool_use", id: t.id, name: t.name } });
|
|
39932
|
+
t.started = true;
|
|
39527
39933
|
}
|
|
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
39934
|
}
|
|
39537
|
-
if (
|
|
39538
|
-
|
|
39539
|
-
|
|
39540
|
-
index: t.blockIndex,
|
|
39541
|
-
content_block: { type: "tool_use", id: t.id, name: t.name }
|
|
39542
|
-
});
|
|
39543
|
-
t.started = true;
|
|
39935
|
+
if (tc.function?.arguments && t) {
|
|
39936
|
+
t.arguments += tc.function.arguments;
|
|
39937
|
+
send("content_block_delta", { type: "content_block_delta", index: t.blockIndex, delta: { type: "input_json_delta", partial_json: tc.function.arguments } });
|
|
39544
39938
|
}
|
|
39545
39939
|
}
|
|
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
39940
|
}
|
|
39554
39941
|
}
|
|
39555
|
-
|
|
39556
|
-
|
|
39557
|
-
|
|
39558
|
-
|
|
39559
|
-
|
|
39560
|
-
|
|
39942
|
+
if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
|
|
39943
|
+
const toolSchemas = request.tools || [];
|
|
39944
|
+
for (const [_, t] of tools) {
|
|
39945
|
+
if (t.started && !t.closed) {
|
|
39946
|
+
if (toolSchemas.length > 0) {
|
|
39947
|
+
const validation = validateToolArguments(t.name, t.arguments, toolSchemas);
|
|
39948
|
+
if (!validation.valid) {
|
|
39949
|
+
const errorIdx = curIdx++;
|
|
39950
|
+
const errorMsg = `
|
|
39951
|
+
|
|
39952
|
+
⚠️ 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.`;
|
|
39953
|
+
send("content_block_start", { type: "content_block_start", index: errorIdx, content_block: { type: "text", text: "" } });
|
|
39954
|
+
send("content_block_delta", { type: "content_block_delta", index: errorIdx, delta: { type: "text_delta", text: errorMsg } });
|
|
39955
|
+
send("content_block_stop", { type: "content_block_stop", index: errorIdx });
|
|
39956
|
+
t.closed = true;
|
|
39957
|
+
continue;
|
|
39958
|
+
}
|
|
39959
|
+
}
|
|
39960
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39961
|
+
t.closed = true;
|
|
39962
|
+
}
|
|
39561
39963
|
}
|
|
39562
39964
|
}
|
|
39563
|
-
}
|
|
39564
|
-
}
|
|
39965
|
+
} catch (e) {}
|
|
39966
|
+
}
|
|
39565
39967
|
}
|
|
39968
|
+
await finalize("unexpected");
|
|
39969
|
+
} catch (e) {
|
|
39970
|
+
await finalize("error", String(e));
|
|
39566
39971
|
}
|
|
39567
|
-
|
|
39568
|
-
|
|
39569
|
-
|
|
39972
|
+
},
|
|
39973
|
+
cancel() {
|
|
39974
|
+
isClosed = true;
|
|
39975
|
+
if (ping)
|
|
39976
|
+
clearInterval(ping);
|
|
39570
39977
|
}
|
|
39571
|
-
},
|
|
39572
|
-
|
|
39573
|
-
|
|
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
|
-
});
|
|
39978
|
+
}), { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" } });
|
|
39979
|
+
}
|
|
39980
|
+
async shutdown() {}
|
|
39584
39981
|
}
|
|
39585
|
-
var
|
|
39982
|
+
var OPENROUTER_API_URL2 = "https://openrouter.ai/api/v1/chat/completions", OPENROUTER_HEADERS2;
|
|
39983
|
+
var init_openrouter_handler = __esm(() => {
|
|
39984
|
+
init_adapter_manager();
|
|
39985
|
+
init_middleware();
|
|
39586
39986
|
init_transform();
|
|
39987
|
+
init_logger();
|
|
39988
|
+
init_model_loader();
|
|
39989
|
+
init_openai_compat();
|
|
39990
|
+
OPENROUTER_HEADERS2 = {
|
|
39991
|
+
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
39992
|
+
"X-Title": "Claudish - OpenRouter Proxy"
|
|
39993
|
+
};
|
|
39587
39994
|
});
|
|
39588
39995
|
|
|
39589
39996
|
// src/handlers/local-provider-handler.ts
|
|
@@ -39599,18 +40006,24 @@ class LocalProviderHandler {
|
|
|
39599
40006
|
port;
|
|
39600
40007
|
healthChecked = false;
|
|
39601
40008
|
isHealthy = false;
|
|
39602
|
-
contextWindow =
|
|
40009
|
+
contextWindow = 32768;
|
|
39603
40010
|
sessionInputTokens = 0;
|
|
39604
40011
|
sessionOutputTokens = 0;
|
|
39605
|
-
|
|
40012
|
+
options;
|
|
40013
|
+
constructor(provider, modelName, port, options = {}) {
|
|
39606
40014
|
this.provider = provider;
|
|
39607
40015
|
this.modelName = modelName;
|
|
39608
40016
|
this.port = port;
|
|
40017
|
+
this.options = options;
|
|
39609
40018
|
this.adapterManager = new AdapterManager(modelName);
|
|
39610
40019
|
this.middlewareManager = new MiddlewareManager;
|
|
39611
40020
|
this.middlewareManager.initialize().catch((err) => {
|
|
39612
40021
|
log(`[LocalProvider:${provider.name}] Middleware init error: ${err}`);
|
|
39613
40022
|
});
|
|
40023
|
+
this.writeTokenFile(0, 0);
|
|
40024
|
+
if (options.summarizeTools) {
|
|
40025
|
+
log(`[LocalProvider:${provider.name}] Tool summarization enabled`);
|
|
40026
|
+
}
|
|
39614
40027
|
}
|
|
39615
40028
|
async checkHealth() {
|
|
39616
40029
|
if (this.healthChecked)
|
|
@@ -39647,8 +40060,16 @@ class LocalProviderHandler {
|
|
|
39647
40060
|
return false;
|
|
39648
40061
|
}
|
|
39649
40062
|
async fetchContextWindow() {
|
|
39650
|
-
|
|
39651
|
-
|
|
40063
|
+
log(`[LocalProvider:${this.provider.name}] Fetching context window...`);
|
|
40064
|
+
if (this.provider.name === "ollama") {
|
|
40065
|
+
await this.fetchOllamaContextWindow();
|
|
40066
|
+
} else if (this.provider.name === "lmstudio") {
|
|
40067
|
+
await this.fetchLMStudioContextWindow();
|
|
40068
|
+
} else {
|
|
40069
|
+
log(`[LocalProvider:${this.provider.name}] No context window fetch for this provider, using default: ${this.contextWindow}`);
|
|
40070
|
+
}
|
|
40071
|
+
}
|
|
40072
|
+
async fetchOllamaContextWindow() {
|
|
39652
40073
|
try {
|
|
39653
40074
|
const response = await fetch(`${this.provider.baseUrl}/api/show`, {
|
|
39654
40075
|
method: "POST",
|
|
@@ -39658,32 +40079,72 @@ class LocalProviderHandler {
|
|
|
39658
40079
|
});
|
|
39659
40080
|
if (response.ok) {
|
|
39660
40081
|
const data = await response.json();
|
|
39661
|
-
|
|
40082
|
+
let ctxFromInfo = data.model_info?.["general.context_length"];
|
|
40083
|
+
if (!ctxFromInfo && data.model_info) {
|
|
40084
|
+
for (const key of Object.keys(data.model_info)) {
|
|
40085
|
+
if (key.endsWith(".context_length")) {
|
|
40086
|
+
ctxFromInfo = data.model_info[key];
|
|
40087
|
+
break;
|
|
40088
|
+
}
|
|
40089
|
+
}
|
|
40090
|
+
}
|
|
39662
40091
|
const ctxFromParams = data.parameters?.match(/num_ctx\s+(\d+)/)?.[1];
|
|
39663
40092
|
if (ctxFromInfo) {
|
|
39664
|
-
this.contextWindow = parseInt(ctxFromInfo, 10);
|
|
40093
|
+
this.contextWindow = parseInt(String(ctxFromInfo), 10);
|
|
39665
40094
|
} else if (ctxFromParams) {
|
|
39666
40095
|
this.contextWindow = parseInt(ctxFromParams, 10);
|
|
39667
40096
|
} else {
|
|
39668
|
-
this.
|
|
40097
|
+
log(`[LocalProvider:${this.provider.name}] No context info found, using default: ${this.contextWindow}`);
|
|
40098
|
+
}
|
|
40099
|
+
if (ctxFromInfo || ctxFromParams) {
|
|
40100
|
+
log(`[LocalProvider:${this.provider.name}] Context window: ${this.contextWindow}`);
|
|
39669
40101
|
}
|
|
39670
|
-
log(`[LocalProvider:${this.provider.name}] Context window: ${this.contextWindow}`);
|
|
39671
40102
|
}
|
|
39672
40103
|
} catch (e) {}
|
|
39673
40104
|
}
|
|
40105
|
+
async fetchLMStudioContextWindow() {
|
|
40106
|
+
try {
|
|
40107
|
+
const response = await fetch(`${this.provider.baseUrl}/v1/models`, {
|
|
40108
|
+
method: "GET",
|
|
40109
|
+
signal: AbortSignal.timeout(3000)
|
|
40110
|
+
});
|
|
40111
|
+
if (response.ok) {
|
|
40112
|
+
const data = await response.json();
|
|
40113
|
+
log(`[LocalProvider:lmstudio] Models response: ${JSON.stringify(data).slice(0, 500)}`);
|
|
40114
|
+
const models = data.data || [];
|
|
40115
|
+
const targetModel = models.find((m) => m.id === this.modelName) || models.find((m) => m.id?.endsWith(`/${this.modelName}`)) || models.find((m) => this.modelName.includes(m.id));
|
|
40116
|
+
if (targetModel) {
|
|
40117
|
+
const ctxLength = targetModel.context_length || targetModel.max_context_length || targetModel.context_window || targetModel.max_tokens;
|
|
40118
|
+
if (ctxLength && typeof ctxLength === "number") {
|
|
40119
|
+
this.contextWindow = ctxLength;
|
|
40120
|
+
log(`[LocalProvider:lmstudio] Context window from model: ${this.contextWindow}`);
|
|
40121
|
+
return;
|
|
40122
|
+
}
|
|
40123
|
+
}
|
|
40124
|
+
this.contextWindow = 32768;
|
|
40125
|
+
log(`[LocalProvider:lmstudio] Using default context window: ${this.contextWindow}`);
|
|
40126
|
+
}
|
|
40127
|
+
} catch (e) {
|
|
40128
|
+
this.contextWindow = 32768;
|
|
40129
|
+
log(`[LocalProvider:lmstudio] Failed to fetch model info: ${e?.message || e}. Using default: ${this.contextWindow}`);
|
|
40130
|
+
}
|
|
40131
|
+
}
|
|
39674
40132
|
writeTokenFile(input, output) {
|
|
39675
40133
|
try {
|
|
39676
40134
|
this.sessionInputTokens += input;
|
|
39677
40135
|
this.sessionOutputTokens += output;
|
|
39678
|
-
const
|
|
39679
|
-
const
|
|
40136
|
+
const sessionTotal = this.sessionInputTokens + this.sessionOutputTokens;
|
|
40137
|
+
const used = input + output;
|
|
40138
|
+
const leftPct = this.contextWindow > 0 ? Math.max(0, Math.min(100, Math.round((this.contextWindow - used) / this.contextWindow * 100))) : 100;
|
|
39680
40139
|
const data = {
|
|
39681
40140
|
input_tokens: this.sessionInputTokens,
|
|
39682
40141
|
output_tokens: this.sessionOutputTokens,
|
|
39683
|
-
total_tokens:
|
|
40142
|
+
total_tokens: sessionTotal,
|
|
39684
40143
|
total_cost: 0,
|
|
39685
40144
|
context_window: this.contextWindow,
|
|
39686
40145
|
context_left_percent: leftPct,
|
|
40146
|
+
last_request_input: input,
|
|
40147
|
+
last_request_output: output,
|
|
39687
40148
|
updated_at: Date.now()
|
|
39688
40149
|
};
|
|
39689
40150
|
writeFileSync9(join9(tmpdir3(), `claudish-tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
|
|
@@ -39706,11 +40167,66 @@ class LocalProviderHandler {
|
|
|
39706
40167
|
}
|
|
39707
40168
|
const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
|
|
39708
40169
|
const messages = convertMessagesToOpenAI(claudeRequest, target, filterIdentity);
|
|
39709
|
-
const tools = convertToolsToOpenAI(claudeRequest);
|
|
40170
|
+
const tools = convertToolsToOpenAI(claudeRequest, this.options.summarizeTools);
|
|
39710
40171
|
const finalTools = this.provider.capabilities.supportsTools ? tools : [];
|
|
39711
40172
|
if (tools.length > 0 && !this.provider.capabilities.supportsTools) {
|
|
39712
40173
|
log(`[LocalProvider:${this.provider.name}] Tools stripped (not supported)`);
|
|
39713
40174
|
}
|
|
40175
|
+
if (tools.length > 0 && this.options.summarizeTools) {
|
|
40176
|
+
log(`[LocalProvider:${this.provider.name}] Tools summarized (${tools.length} tools)`);
|
|
40177
|
+
}
|
|
40178
|
+
if (messages.length > 0 && messages[0].role === "system") {
|
|
40179
|
+
let guidance = `
|
|
40180
|
+
|
|
40181
|
+
IMPORTANT INSTRUCTIONS FOR THIS MODEL:
|
|
40182
|
+
|
|
40183
|
+
1. OUTPUT BEHAVIOR:
|
|
40184
|
+
- NEVER output your internal reasoning, thinking process, or chain-of-thought as visible text.
|
|
40185
|
+
- Only output your final response, actions, or tool calls.
|
|
40186
|
+
- Do NOT ramble or speculate about what the user might want.
|
|
40187
|
+
|
|
40188
|
+
2. CONVERSATION HANDLING:
|
|
40189
|
+
- Always look back at the ORIGINAL user request in the conversation history.
|
|
40190
|
+
- When you receive results from a Task/agent you called, SYNTHESIZE those results and continue fulfilling the user's original request.
|
|
40191
|
+
- Do NOT ask "What would you like help with?" if there's already a user request in the conversation.
|
|
40192
|
+
- Only ask for clarification if the FIRST user message in the conversation is unclear.
|
|
40193
|
+
- After calling tools or agents, continue with the next step - don't restart or ask what to do.
|
|
40194
|
+
|
|
40195
|
+
3. CRITICAL - AFTER TOOL RESULTS:
|
|
40196
|
+
- When you see tool results (like file lists, search results, or command output), ALWAYS continue working.
|
|
40197
|
+
- Analyze the results and take the next action toward completing the user's request.
|
|
40198
|
+
- If the user asked for "evaluation and suggestions", you MUST provide analysis and recommendations after seeing the data.
|
|
40199
|
+
- NEVER stop after just calling one tool - continue until you've fully addressed the user's request.
|
|
40200
|
+
- If you called a Glob/Search and got files, READ important files next, then ANALYZE, then SUGGEST improvements.`;
|
|
40201
|
+
if (finalTools.length > 0) {
|
|
40202
|
+
const isQwen = target.toLowerCase().includes("qwen");
|
|
40203
|
+
if (isQwen) {
|
|
40204
|
+
guidance += `
|
|
40205
|
+
|
|
40206
|
+
4. TOOL CALLING FORMAT (CRITICAL FOR QWEN):
|
|
40207
|
+
You MUST use proper OpenAI-style function calling. Do NOT output tool calls as XML text.
|
|
40208
|
+
When you want to call a tool, use the API's tool_calls mechanism, NOT text like <function=...>.
|
|
40209
|
+
The tool calls must be structured JSON in the API response, not XML in your text output.
|
|
40210
|
+
|
|
40211
|
+
If you cannot use structured tool_calls, format as JSON:
|
|
40212
|
+
{"name": "tool_name", "arguments": {"param1": "value1", "param2": "value2"}}
|
|
40213
|
+
|
|
40214
|
+
5. TOOL PARAMETER REQUIREMENTS:`;
|
|
40215
|
+
} else {
|
|
40216
|
+
guidance += `
|
|
40217
|
+
|
|
40218
|
+
4. TOOL CALLING REQUIREMENTS:`;
|
|
40219
|
+
}
|
|
40220
|
+
guidance += `
|
|
40221
|
+
- When calling tools, you MUST include ALL required parameters. Incomplete tool calls will fail.
|
|
40222
|
+
- For Task: always include "description" (3-5 words), "prompt" (detailed instructions), and "subagent_type"
|
|
40223
|
+
- For Bash: always include "command" and "description"
|
|
40224
|
+
- For Read/Write/Edit: always include the full "file_path"
|
|
40225
|
+
- For Grep/Glob: always include "pattern"
|
|
40226
|
+
- Ensure your tool call JSON is complete with all required fields before submitting.`;
|
|
40227
|
+
}
|
|
40228
|
+
messages[0].content += guidance;
|
|
40229
|
+
}
|
|
39714
40230
|
const openAIPayload = {
|
|
39715
40231
|
model: target,
|
|
39716
40232
|
messages,
|
|
@@ -39720,6 +40236,11 @@ class LocalProviderHandler {
|
|
|
39720
40236
|
tools: finalTools.length > 0 ? finalTools : undefined,
|
|
39721
40237
|
stream_options: this.provider.capabilities.supportsStreaming ? { include_usage: true } : undefined
|
|
39722
40238
|
};
|
|
40239
|
+
if (this.provider.name === "ollama") {
|
|
40240
|
+
const numCtx = Math.max(this.contextWindow, 32768);
|
|
40241
|
+
openAIPayload.options = { num_ctx: numCtx };
|
|
40242
|
+
log(`[LocalProvider:${this.provider.name}] Setting num_ctx: ${numCtx} (detected: ${this.contextWindow})`);
|
|
40243
|
+
}
|
|
39723
40244
|
if (claudeRequest.tool_choice && finalTools.length > 0) {
|
|
39724
40245
|
const { type, name } = claudeRequest.tool_choice;
|
|
39725
40246
|
if (type === "tool" && name) {
|
|
@@ -39739,6 +40260,12 @@ class LocalProviderHandler {
|
|
|
39739
40260
|
stream: openAIPayload.stream
|
|
39740
40261
|
});
|
|
39741
40262
|
const apiUrl = `${this.provider.baseUrl}${this.provider.apiPath}`;
|
|
40263
|
+
log(`[LocalProvider:${this.provider.name}] Tools: ${openAIPayload.tools?.length || 0}, Messages: ${messages.length}`);
|
|
40264
|
+
if (openAIPayload.tools?.length > 0) {
|
|
40265
|
+
log(`[LocalProvider:${this.provider.name}] First tool: ${openAIPayload.tools[0]?.function?.name || "unknown"}`);
|
|
40266
|
+
}
|
|
40267
|
+
console.log(`[LocalProvider:${this.provider.name}] ===== ABOUT TO FETCH from ${apiUrl} =====`);
|
|
40268
|
+
log(`[LocalProvider:${this.provider.name}] ===== ABOUT TO FETCH from ${apiUrl} =====`);
|
|
39742
40269
|
try {
|
|
39743
40270
|
const response = await fetch(apiUrl, {
|
|
39744
40271
|
method: "POST",
|
|
@@ -39747,15 +40274,20 @@ class LocalProviderHandler {
|
|
|
39747
40274
|
},
|
|
39748
40275
|
body: JSON.stringify(openAIPayload)
|
|
39749
40276
|
});
|
|
40277
|
+
log(`[LocalProvider:${this.provider.name}] ===== FETCH COMPLETED, status: ${response.status} =====`);
|
|
39750
40278
|
if (!response.ok) {
|
|
39751
40279
|
const errorBody = await response.text();
|
|
40280
|
+
log(`[LocalProvider:${this.provider.name}] ERROR: ${errorBody.slice(0, 200)}`);
|
|
39752
40281
|
return this.handleErrorResponse(c, response.status, errorBody);
|
|
39753
40282
|
}
|
|
40283
|
+
log(`[LocalProvider:${this.provider.name}] Response OK, proceeding to streaming...`);
|
|
39754
40284
|
if (droppedParams.length > 0) {
|
|
39755
40285
|
c.header("X-Dropped-Params", droppedParams.join(", "));
|
|
39756
40286
|
}
|
|
40287
|
+
log(`[LocalProvider:${this.provider.name}] Streaming: ${openAIPayload.stream}`);
|
|
39757
40288
|
if (openAIPayload.stream) {
|
|
39758
|
-
|
|
40289
|
+
log(`[LocalProvider:${this.provider.name}] ===== ENTERING STREAMING HANDLER =====`);
|
|
40290
|
+
return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager, (input, output) => this.writeTokenFile(input, output), claudeRequest.tools);
|
|
39759
40291
|
}
|
|
39760
40292
|
const data = await response.json();
|
|
39761
40293
|
return c.json(data);
|
|
@@ -39922,7 +40454,7 @@ var exports_proxy_server = {};
|
|
|
39922
40454
|
__export(exports_proxy_server, {
|
|
39923
40455
|
createProxyServer: () => createProxyServer
|
|
39924
40456
|
});
|
|
39925
|
-
async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey, modelMap) {
|
|
40457
|
+
async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey, modelMap, options = {}) {
|
|
39926
40458
|
const nativeHandler = new NativeHandler(anthropicApiKey);
|
|
39927
40459
|
const openRouterHandlers = new Map;
|
|
39928
40460
|
const localProviderHandlers = new Map;
|
|
@@ -39932,13 +40464,16 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
39932
40464
|
}
|
|
39933
40465
|
return openRouterHandlers.get(targetModel);
|
|
39934
40466
|
};
|
|
40467
|
+
const localProviderOptions = {
|
|
40468
|
+
summarizeTools: options.summarizeTools
|
|
40469
|
+
};
|
|
39935
40470
|
const getLocalProviderHandler = (targetModel) => {
|
|
39936
40471
|
if (localProviderHandlers.has(targetModel)) {
|
|
39937
40472
|
return localProviderHandlers.get(targetModel);
|
|
39938
40473
|
}
|
|
39939
40474
|
const resolved = resolveProvider(targetModel);
|
|
39940
40475
|
if (resolved) {
|
|
39941
|
-
const handler = new LocalProviderHandler(resolved.provider, resolved.modelName, port);
|
|
40476
|
+
const handler = new LocalProviderHandler(resolved.provider, resolved.modelName, port, localProviderOptions);
|
|
39942
40477
|
localProviderHandlers.set(targetModel, handler);
|
|
39943
40478
|
log(`[Proxy] Created local provider handler: ${resolved.provider.name}/${resolved.modelName}`);
|
|
39944
40479
|
return handler;
|
|
@@ -39946,7 +40481,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
39946
40481
|
const urlParsed = parseUrlModel(targetModel);
|
|
39947
40482
|
if (urlParsed) {
|
|
39948
40483
|
const provider = createUrlProvider(urlParsed);
|
|
39949
|
-
const handler = new LocalProviderHandler(provider, urlParsed.modelName, port);
|
|
40484
|
+
const handler = new LocalProviderHandler(provider, urlParsed.modelName, port, localProviderOptions);
|
|
39950
40485
|
localProviderHandlers.set(targetModel, handler);
|
|
39951
40486
|
log(`[Proxy] Created URL-based local provider handler: ${urlParsed.baseUrl}/${urlParsed.modelName}`);
|
|
39952
40487
|
return handler;
|
|
@@ -40053,7 +40588,7 @@ import { execSync } from "node:child_process";
|
|
|
40053
40588
|
import { createInterface as createInterface2 } from "node:readline";
|
|
40054
40589
|
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync10, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "node:fs";
|
|
40055
40590
|
import { join as join10 } from "node:path";
|
|
40056
|
-
import { tmpdir as tmpdir4, homedir as homedir2, platform
|
|
40591
|
+
import { tmpdir as tmpdir4, homedir as homedir2, platform } from "node:os";
|
|
40057
40592
|
function getCacheFilePath() {
|
|
40058
40593
|
let cacheDir;
|
|
40059
40594
|
if (isWindows2) {
|
|
@@ -40219,12 +40754,13 @@ async function checkForUpdates(currentVersion, options = {}) {
|
|
|
40219
40754
|
}
|
|
40220
40755
|
var isWindows2, NPM_REGISTRY_URL = "https://registry.npmjs.org/claudish/latest", CACHE_MAX_AGE_MS;
|
|
40221
40756
|
var init_update_checker = __esm(() => {
|
|
40222
|
-
isWindows2 =
|
|
40757
|
+
isWindows2 = platform() === "win32";
|
|
40223
40758
|
CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
40224
40759
|
});
|
|
40225
40760
|
|
|
40226
40761
|
// src/index.ts
|
|
40227
40762
|
var import_dotenv2 = __toESM(require_main(), 1);
|
|
40763
|
+
console.log("===== CLAUDISH FRESH START - CODE UPDATED =====");
|
|
40228
40764
|
import_dotenv2.config();
|
|
40229
40765
|
var isMcpMode = process.argv.includes("--mcp");
|
|
40230
40766
|
var args = process.argv.slice(2);
|
|
@@ -40303,6 +40839,8 @@ async function runCli() {
|
|
|
40303
40839
|
sonnet: cliConfig.modelSonnet,
|
|
40304
40840
|
haiku: cliConfig.modelHaiku,
|
|
40305
40841
|
subagent: cliConfig.modelSubagent
|
|
40842
|
+
}, {
|
|
40843
|
+
summarizeTools: cliConfig.summarizeTools
|
|
40306
40844
|
});
|
|
40307
40845
|
let exitCode = 0;
|
|
40308
40846
|
try {
|