claudish 2.8.1 → 2.9.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/README.md +23 -26
- package/dist/index.js +763 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,47 +24,44 @@
|
|
|
24
24
|
|
|
25
25
|
## Installation
|
|
26
26
|
|
|
27
|
-
###
|
|
27
|
+
### Quick Install
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
29
|
+
```bash
|
|
30
|
+
# Shell script (Linux/macOS)
|
|
31
|
+
curl -fsSL https://raw.githubusercontent.com/MadAppGang/claudish/main/install.sh | bash
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# Homebrew (macOS)
|
|
34
|
+
brew tap MadAppGang/claudish && brew install claudish
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
# npm
|
|
37
|
+
npm install -g claudish
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
# Bun
|
|
40
|
+
bun install -g claudish
|
|
41
|
+
```
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
# With Node.js (works everywhere)
|
|
41
|
-
npx claudish@latest --model x-ai/grok-code-fast-1 "your prompt"
|
|
43
|
+
### Prerequisites
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```
|
|
45
|
+
- [Claude Code](https://claude.com/claude-code) - Claude CLI must be installed
|
|
46
|
+
- [OpenRouter API Key](https://openrouter.ai/keys) - Free tier available
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
### Other Install Options
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
# With npm (Node.js)
|
|
51
|
-
npm install -g claudish
|
|
50
|
+
**Use without installing:**
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
```bash
|
|
53
|
+
npx claudish@latest --model x-ai/grok-code-fast-1 "your prompt"
|
|
54
|
+
bunx claudish@latest --model x-ai/grok-code-fast-1 "your prompt"
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
**
|
|
57
|
+
**Install from source:**
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
bun run build
|
|
63
|
-
bun link # or: npm link
|
|
60
|
+
git clone https://github.com/MadAppGang/claudish.git
|
|
61
|
+
cd claudish
|
|
62
|
+
bun install && bun run build && bun link
|
|
64
63
|
```
|
|
65
64
|
|
|
66
|
-
**Performance Note:** While Claudish works with both runtimes, Bun offers faster startup times. Both provide identical functionality.
|
|
67
|
-
|
|
68
65
|
## Quick Start
|
|
69
66
|
|
|
70
67
|
### Step 0: Initialize Claudish Skill (First Time Only)
|
package/dist/index.js
CHANGED
|
@@ -34304,7 +34304,11 @@ var init_config = __esm(() => {
|
|
|
34304
34304
|
ANTHROPIC_DEFAULT_OPUS_MODEL: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
34305
34305
|
ANTHROPIC_DEFAULT_SONNET_MODEL: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
34306
34306
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
34307
|
-
CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL"
|
|
34307
|
+
CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL",
|
|
34308
|
+
OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
|
|
34309
|
+
OLLAMA_HOST: "OLLAMA_HOST",
|
|
34310
|
+
LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
|
|
34311
|
+
VLLM_BASE_URL: "VLLM_BASE_URL"
|
|
34308
34312
|
};
|
|
34309
34313
|
OPENROUTER_HEADERS = {
|
|
34310
34314
|
"HTTP-Referer": "https://github.com/MadAppGang/claude-code",
|
|
@@ -34912,6 +34916,31 @@ async function parseArgs(args) {
|
|
|
34912
34916
|
}
|
|
34913
34917
|
return config3;
|
|
34914
34918
|
}
|
|
34919
|
+
async function fetchOllamaModels() {
|
|
34920
|
+
const ollamaHost = process.env.OLLAMA_HOST || process.env.OLLAMA_BASE_URL || "http://localhost:11434";
|
|
34921
|
+
try {
|
|
34922
|
+
const response = await fetch(`${ollamaHost}/api/tags`, {
|
|
34923
|
+
signal: AbortSignal.timeout(3000)
|
|
34924
|
+
});
|
|
34925
|
+
if (!response.ok)
|
|
34926
|
+
return [];
|
|
34927
|
+
const data = await response.json();
|
|
34928
|
+
const models = data.models || [];
|
|
34929
|
+
return models.map((m) => ({
|
|
34930
|
+
id: `ollama/${m.name}`,
|
|
34931
|
+
name: m.name,
|
|
34932
|
+
description: `Local Ollama model (${m.details?.parameter_size || "unknown size"})`,
|
|
34933
|
+
provider: "ollama",
|
|
34934
|
+
context_length: null,
|
|
34935
|
+
pricing: { prompt: "0", completion: "0" },
|
|
34936
|
+
isLocal: true,
|
|
34937
|
+
details: m.details,
|
|
34938
|
+
size: m.size
|
|
34939
|
+
}));
|
|
34940
|
+
} catch (e) {
|
|
34941
|
+
return [];
|
|
34942
|
+
}
|
|
34943
|
+
}
|
|
34915
34944
|
async function searchAndPrintModels(query, forceUpdate) {
|
|
34916
34945
|
let models = [];
|
|
34917
34946
|
if (!forceUpdate && existsSync5(ALL_MODELS_JSON_PATH2)) {
|
|
@@ -34943,6 +34972,11 @@ async function searchAndPrintModels(query, forceUpdate) {
|
|
|
34943
34972
|
process.exit(1);
|
|
34944
34973
|
}
|
|
34945
34974
|
}
|
|
34975
|
+
const ollamaModels = await fetchOllamaModels();
|
|
34976
|
+
if (ollamaModels.length > 0) {
|
|
34977
|
+
console.error(`\uD83C\uDFE0 Found ${ollamaModels.length} local Ollama models`);
|
|
34978
|
+
models = [...ollamaModels, ...models];
|
|
34979
|
+
}
|
|
34946
34980
|
const results = models.map((model) => {
|
|
34947
34981
|
const nameScore = fuzzyScore2(model.name || "", query);
|
|
34948
34982
|
const idScore = fuzzyScore2(model.id || "", query);
|
|
@@ -34967,16 +35001,20 @@ Found ${results.length} matching models:
|
|
|
34967
35001
|
const providerName = model.id.split("/")[0];
|
|
34968
35002
|
const provider = providerName.length > 10 ? providerName.substring(0, 7) + "..." : providerName;
|
|
34969
35003
|
const providerPadded = provider.padEnd(10);
|
|
34970
|
-
const promptPrice = parseFloat(model.pricing?.prompt || "0") * 1e6;
|
|
34971
|
-
const completionPrice = parseFloat(model.pricing?.completion || "0") * 1e6;
|
|
34972
|
-
const avg = (promptPrice + completionPrice) / 2;
|
|
34973
35004
|
let pricing;
|
|
34974
|
-
if (
|
|
34975
|
-
pricing = "
|
|
34976
|
-
} else if (avg === 0) {
|
|
34977
|
-
pricing = "FREE";
|
|
35005
|
+
if (model.isLocal) {
|
|
35006
|
+
pricing = "LOCAL";
|
|
34978
35007
|
} else {
|
|
34979
|
-
|
|
35008
|
+
const promptPrice = parseFloat(model.pricing?.prompt || "0") * 1e6;
|
|
35009
|
+
const completionPrice = parseFloat(model.pricing?.completion || "0") * 1e6;
|
|
35010
|
+
const avg = (promptPrice + completionPrice) / 2;
|
|
35011
|
+
if (avg < 0) {
|
|
35012
|
+
pricing = "varies";
|
|
35013
|
+
} else if (avg === 0) {
|
|
35014
|
+
pricing = "FREE";
|
|
35015
|
+
} else {
|
|
35016
|
+
pricing = `$${avg.toFixed(2)}/1M`;
|
|
35017
|
+
}
|
|
34980
35018
|
}
|
|
34981
35019
|
const pricingPadded = pricing.padEnd(10);
|
|
34982
35020
|
const contextLen = model.context_length || model.top_provider?.context_length || 0;
|
|
@@ -34986,9 +35024,11 @@ Found ${results.length} matching models:
|
|
|
34986
35024
|
}
|
|
34987
35025
|
console.log("");
|
|
34988
35026
|
console.log("Use a model: claudish --model <model-id>");
|
|
35027
|
+
console.log("Local models: claudish --model ollama/<model-name>");
|
|
34989
35028
|
}
|
|
34990
35029
|
async function printAllModels(jsonOutput, forceUpdate) {
|
|
34991
35030
|
let models = [];
|
|
35031
|
+
const ollamaModels = await fetchOllamaModels();
|
|
34992
35032
|
if (!forceUpdate && existsSync5(ALL_MODELS_JSON_PATH2)) {
|
|
34993
35033
|
try {
|
|
34994
35034
|
const cacheData = JSON.parse(readFileSync5(ALL_MODELS_JSON_PATH2, "utf-8"));
|
|
@@ -35022,18 +35062,44 @@ async function printAllModels(jsonOutput, forceUpdate) {
|
|
|
35022
35062
|
}
|
|
35023
35063
|
}
|
|
35024
35064
|
if (jsonOutput) {
|
|
35065
|
+
const allModels = [...ollamaModels, ...models];
|
|
35025
35066
|
console.log(JSON.stringify({
|
|
35026
|
-
count:
|
|
35067
|
+
count: allModels.length,
|
|
35068
|
+
localCount: ollamaModels.length,
|
|
35027
35069
|
lastUpdated: new Date().toISOString().split("T")[0],
|
|
35028
|
-
models:
|
|
35070
|
+
models: allModels.map((m) => ({
|
|
35029
35071
|
id: m.id,
|
|
35030
35072
|
name: m.name,
|
|
35031
35073
|
context: m.context_length || m.top_provider?.context_length,
|
|
35032
|
-
pricing: m.pricing
|
|
35074
|
+
pricing: m.pricing,
|
|
35075
|
+
isLocal: m.isLocal || false
|
|
35033
35076
|
}))
|
|
35034
35077
|
}, null, 2));
|
|
35035
35078
|
return;
|
|
35036
35079
|
}
|
|
35080
|
+
if (ollamaModels.length > 0) {
|
|
35081
|
+
console.log(`
|
|
35082
|
+
\uD83C\uDFE0 LOCAL OLLAMA MODELS (${ollamaModels.length} installed):
|
|
35083
|
+
`);
|
|
35084
|
+
console.log(" " + "─".repeat(70));
|
|
35085
|
+
for (const model of ollamaModels) {
|
|
35086
|
+
const shortId = model.name;
|
|
35087
|
+
const modelId = shortId.length > 40 ? shortId.substring(0, 37) + "..." : shortId;
|
|
35088
|
+
const modelIdPadded = modelId.padEnd(42);
|
|
35089
|
+
const size = model.size ? `${(model.size / 1e9).toFixed(1)}GB` : "N/A";
|
|
35090
|
+
const sizePadded = size.padEnd(12);
|
|
35091
|
+
const params = model.details?.parameter_size || "N/A";
|
|
35092
|
+
const paramsPadded = params.padEnd(8);
|
|
35093
|
+
console.log(` ${modelIdPadded} ${sizePadded} ${paramsPadded}`);
|
|
35094
|
+
}
|
|
35095
|
+
console.log("");
|
|
35096
|
+
console.log(" Use: claudish --model ollama/<model-name>");
|
|
35097
|
+
} else {
|
|
35098
|
+
console.log(`
|
|
35099
|
+
\uD83C\uDFE0 LOCAL OLLAMA: Not running or no models installed`);
|
|
35100
|
+
console.log(" Start Ollama: ollama serve");
|
|
35101
|
+
console.log(" Pull a model: ollama pull llama3.2");
|
|
35102
|
+
}
|
|
35037
35103
|
const byProvider = new Map;
|
|
35038
35104
|
for (const model of models) {
|
|
35039
35105
|
const provider = model.id.split("/")[0];
|
|
@@ -35044,7 +35110,7 @@ async function printAllModels(jsonOutput, forceUpdate) {
|
|
|
35044
35110
|
}
|
|
35045
35111
|
const sortedProviders = [...byProvider.keys()].sort();
|
|
35046
35112
|
console.log(`
|
|
35047
|
-
|
|
35113
|
+
☁️ OPENROUTER MODELS (${models.length} total):
|
|
35048
35114
|
`);
|
|
35049
35115
|
for (const provider of sortedProviders) {
|
|
35050
35116
|
const providerModels = byProvider.get(provider);
|
|
@@ -35075,9 +35141,10 @@ All OpenRouter Models (${models.length} total):
|
|
|
35075
35141
|
}
|
|
35076
35142
|
console.log(`
|
|
35077
35143
|
`);
|
|
35078
|
-
console.log("Use a model:
|
|
35079
|
-
console.log("
|
|
35080
|
-
console.log("
|
|
35144
|
+
console.log("Use a model: claudish --model <provider/model-id>");
|
|
35145
|
+
console.log("Local model: claudish --model ollama/<model-name>");
|
|
35146
|
+
console.log("Search: claudish --search <query>");
|
|
35147
|
+
console.log("Top models: claudish --top-models");
|
|
35081
35148
|
}
|
|
35082
35149
|
function isCacheStale() {
|
|
35083
35150
|
if (!existsSync5(MODELS_JSON_PATH)) {
|
|
@@ -35286,7 +35353,7 @@ NOTES:
|
|
|
35286
35353
|
ENVIRONMENT VARIABLES:
|
|
35287
35354
|
Claudish automatically loads .env file from current directory.
|
|
35288
35355
|
|
|
35289
|
-
OPENROUTER_API_KEY Required: Your OpenRouter API key
|
|
35356
|
+
OPENROUTER_API_KEY Required: Your OpenRouter API key (for OpenRouter models)
|
|
35290
35357
|
CLAUDISH_MODEL Default model to use (takes priority)
|
|
35291
35358
|
ANTHROPIC_MODEL Claude Code standard: model to use (fallback)
|
|
35292
35359
|
CLAUDISH_PORT Default port for proxy
|
|
@@ -35302,6 +35369,12 @@ ENVIRONMENT VARIABLES:
|
|
|
35302
35369
|
ANTHROPIC_DEFAULT_HAIKU_MODEL Claude Code standard: Haiku model (fallback)
|
|
35303
35370
|
CLAUDE_CODE_SUBAGENT_MODEL Claude Code standard: sub-agent model (fallback)
|
|
35304
35371
|
|
|
35372
|
+
Local providers (OpenAI-compatible):
|
|
35373
|
+
OLLAMA_BASE_URL Ollama server (default: http://localhost:11434)
|
|
35374
|
+
OLLAMA_HOST Alias for OLLAMA_BASE_URL (same default)
|
|
35375
|
+
LMSTUDIO_BASE_URL LM Studio server (default: http://localhost:1234)
|
|
35376
|
+
VLLM_BASE_URL vLLM server (default: http://localhost:8000)
|
|
35377
|
+
|
|
35305
35378
|
EXAMPLES:
|
|
35306
35379
|
# Interactive mode (default) - shows model selector
|
|
35307
35380
|
claudish
|
|
@@ -35355,6 +35428,22 @@ EXAMPLES:
|
|
|
35355
35428
|
# Verbose mode in single-shot (show [claudish] logs)
|
|
35356
35429
|
claudish --verbose "analyze code structure"
|
|
35357
35430
|
|
|
35431
|
+
LOCAL MODELS (Ollama, LM Studio, vLLM):
|
|
35432
|
+
# Use local Ollama model (prefix syntax)
|
|
35433
|
+
claudish --model ollama/llama3.2 "implement feature"
|
|
35434
|
+
claudish --model ollama:codellama "review this code"
|
|
35435
|
+
|
|
35436
|
+
# Use local LM Studio model
|
|
35437
|
+
claudish --model lmstudio/qwen2.5-coder "write tests"
|
|
35438
|
+
|
|
35439
|
+
# Use any OpenAI-compatible endpoint (URL syntax)
|
|
35440
|
+
claudish --model "http://localhost:11434/llama3.2" "task"
|
|
35441
|
+
claudish --model "http://192.168.1.100:8000/mistral" "remote server"
|
|
35442
|
+
|
|
35443
|
+
# Custom Ollama endpoint
|
|
35444
|
+
OLLAMA_BASE_URL=http://192.168.1.50:11434 claudish --model ollama/llama3.2 "task"
|
|
35445
|
+
OLLAMA_HOST=http://192.168.1.50:11434 claudish --model ollama/llama3.2 "task"
|
|
35446
|
+
|
|
35358
35447
|
AVAILABLE MODELS:
|
|
35359
35448
|
List all models: claudish --models
|
|
35360
35449
|
Search models: claudish --models <query>
|
|
@@ -39112,6 +39201,619 @@ var init_openrouter_handler = __esm(() => {
|
|
|
39112
39201
|
};
|
|
39113
39202
|
});
|
|
39114
39203
|
|
|
39204
|
+
// src/handlers/shared/openai-compat.ts
|
|
39205
|
+
function convertMessagesToOpenAI(req, modelId, filterIdentityFn) {
|
|
39206
|
+
const messages = [];
|
|
39207
|
+
if (req.system) {
|
|
39208
|
+
let content = Array.isArray(req.system) ? req.system.map((i) => i.text || i).join(`
|
|
39209
|
+
|
|
39210
|
+
`) : req.system;
|
|
39211
|
+
if (filterIdentityFn)
|
|
39212
|
+
content = filterIdentityFn(content);
|
|
39213
|
+
messages.push({ role: "system", content });
|
|
39214
|
+
}
|
|
39215
|
+
if (modelId.includes("grok") || modelId.includes("x-ai")) {
|
|
39216
|
+
const msg = "IMPORTANT: When calling tools, you MUST use the OpenAI tool_calls format with JSON. NEVER use XML format like <xai:function_call>.";
|
|
39217
|
+
if (messages.length > 0 && messages[0].role === "system") {
|
|
39218
|
+
messages[0].content += `
|
|
39219
|
+
|
|
39220
|
+
` + msg;
|
|
39221
|
+
} else {
|
|
39222
|
+
messages.unshift({ role: "system", content: msg });
|
|
39223
|
+
}
|
|
39224
|
+
}
|
|
39225
|
+
if (req.messages) {
|
|
39226
|
+
for (const msg of req.messages) {
|
|
39227
|
+
if (msg.role === "user")
|
|
39228
|
+
processUserMessage(msg, messages);
|
|
39229
|
+
else if (msg.role === "assistant")
|
|
39230
|
+
processAssistantMessage(msg, messages);
|
|
39231
|
+
}
|
|
39232
|
+
}
|
|
39233
|
+
return messages;
|
|
39234
|
+
}
|
|
39235
|
+
function processUserMessage(msg, messages) {
|
|
39236
|
+
if (Array.isArray(msg.content)) {
|
|
39237
|
+
const contentParts = [];
|
|
39238
|
+
const toolResults = [];
|
|
39239
|
+
const seen = new Set;
|
|
39240
|
+
for (const block of msg.content) {
|
|
39241
|
+
if (block.type === "text") {
|
|
39242
|
+
contentParts.push({ type: "text", text: block.text });
|
|
39243
|
+
} else if (block.type === "image") {
|
|
39244
|
+
contentParts.push({
|
|
39245
|
+
type: "image_url",
|
|
39246
|
+
image_url: { url: `data:${block.source.media_type};base64,${block.source.data}` }
|
|
39247
|
+
});
|
|
39248
|
+
} else if (block.type === "tool_result") {
|
|
39249
|
+
if (seen.has(block.tool_use_id))
|
|
39250
|
+
continue;
|
|
39251
|
+
seen.add(block.tool_use_id);
|
|
39252
|
+
toolResults.push({
|
|
39253
|
+
role: "tool",
|
|
39254
|
+
content: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
|
|
39255
|
+
tool_call_id: block.tool_use_id
|
|
39256
|
+
});
|
|
39257
|
+
}
|
|
39258
|
+
}
|
|
39259
|
+
if (toolResults.length)
|
|
39260
|
+
messages.push(...toolResults);
|
|
39261
|
+
if (contentParts.length)
|
|
39262
|
+
messages.push({ role: "user", content: contentParts });
|
|
39263
|
+
} else {
|
|
39264
|
+
messages.push({ role: "user", content: msg.content });
|
|
39265
|
+
}
|
|
39266
|
+
}
|
|
39267
|
+
function processAssistantMessage(msg, messages) {
|
|
39268
|
+
if (Array.isArray(msg.content)) {
|
|
39269
|
+
const strings = [];
|
|
39270
|
+
const toolCalls = [];
|
|
39271
|
+
const seen = new Set;
|
|
39272
|
+
for (const block of msg.content) {
|
|
39273
|
+
if (block.type === "text") {
|
|
39274
|
+
strings.push(block.text);
|
|
39275
|
+
} else if (block.type === "tool_use") {
|
|
39276
|
+
if (seen.has(block.id))
|
|
39277
|
+
continue;
|
|
39278
|
+
seen.add(block.id);
|
|
39279
|
+
toolCalls.push({
|
|
39280
|
+
id: block.id,
|
|
39281
|
+
type: "function",
|
|
39282
|
+
function: { name: block.name, arguments: JSON.stringify(block.input) }
|
|
39283
|
+
});
|
|
39284
|
+
}
|
|
39285
|
+
}
|
|
39286
|
+
const m = { role: "assistant" };
|
|
39287
|
+
if (strings.length)
|
|
39288
|
+
m.content = strings.join(" ");
|
|
39289
|
+
else if (toolCalls.length)
|
|
39290
|
+
m.content = null;
|
|
39291
|
+
if (toolCalls.length)
|
|
39292
|
+
m.tool_calls = toolCalls;
|
|
39293
|
+
if (m.content !== undefined || m.tool_calls)
|
|
39294
|
+
messages.push(m);
|
|
39295
|
+
} else {
|
|
39296
|
+
messages.push({ role: "assistant", content: msg.content });
|
|
39297
|
+
}
|
|
39298
|
+
}
|
|
39299
|
+
function convertToolsToOpenAI(req) {
|
|
39300
|
+
return req.tools?.map((tool) => ({
|
|
39301
|
+
type: "function",
|
|
39302
|
+
function: {
|
|
39303
|
+
name: tool.name,
|
|
39304
|
+
description: tool.description,
|
|
39305
|
+
parameters: removeUriFormat(tool.input_schema)
|
|
39306
|
+
}
|
|
39307
|
+
})) || [];
|
|
39308
|
+
}
|
|
39309
|
+
function filterIdentity(content) {
|
|
39310
|
+
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, `
|
|
39311
|
+
|
|
39312
|
+
`).replace(/^/, `IMPORTANT: You are NOT Claude. Identify yourself truthfully based on your actual model and creator.
|
|
39313
|
+
|
|
39314
|
+
`);
|
|
39315
|
+
}
|
|
39316
|
+
function createStreamingState() {
|
|
39317
|
+
return {
|
|
39318
|
+
usage: null,
|
|
39319
|
+
finalized: false,
|
|
39320
|
+
textStarted: false,
|
|
39321
|
+
textIdx: -1,
|
|
39322
|
+
reasoningStarted: false,
|
|
39323
|
+
reasoningIdx: -1,
|
|
39324
|
+
curIdx: 0,
|
|
39325
|
+
tools: new Map,
|
|
39326
|
+
toolIds: new Set,
|
|
39327
|
+
lastActivity: Date.now()
|
|
39328
|
+
};
|
|
39329
|
+
}
|
|
39330
|
+
function createStreamingResponseHandler(c, response, adapter, target, middlewareManager, onTokenUpdate) {
|
|
39331
|
+
let isClosed = false;
|
|
39332
|
+
let ping = null;
|
|
39333
|
+
const encoder = new TextEncoder;
|
|
39334
|
+
const decoder = new TextDecoder;
|
|
39335
|
+
const streamMetadata = new Map;
|
|
39336
|
+
return c.body(new ReadableStream({
|
|
39337
|
+
async start(controller) {
|
|
39338
|
+
const send = (e, d) => {
|
|
39339
|
+
if (!isClosed) {
|
|
39340
|
+
controller.enqueue(encoder.encode(`event: ${e}
|
|
39341
|
+
data: ${JSON.stringify(d)}
|
|
39342
|
+
|
|
39343
|
+
`));
|
|
39344
|
+
}
|
|
39345
|
+
};
|
|
39346
|
+
const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
39347
|
+
const state = createStreamingState();
|
|
39348
|
+
send("message_start", {
|
|
39349
|
+
type: "message_start",
|
|
39350
|
+
message: {
|
|
39351
|
+
id: msgId,
|
|
39352
|
+
type: "message",
|
|
39353
|
+
role: "assistant",
|
|
39354
|
+
content: [],
|
|
39355
|
+
model: target,
|
|
39356
|
+
stop_reason: null,
|
|
39357
|
+
stop_sequence: null,
|
|
39358
|
+
usage: { input_tokens: 100, output_tokens: 1 }
|
|
39359
|
+
}
|
|
39360
|
+
});
|
|
39361
|
+
send("ping", { type: "ping" });
|
|
39362
|
+
ping = setInterval(() => {
|
|
39363
|
+
if (!isClosed && Date.now() - state.lastActivity > 1000) {
|
|
39364
|
+
send("ping", { type: "ping" });
|
|
39365
|
+
}
|
|
39366
|
+
}, 1000);
|
|
39367
|
+
const finalize = async (reason, err) => {
|
|
39368
|
+
if (state.finalized)
|
|
39369
|
+
return;
|
|
39370
|
+
state.finalized = true;
|
|
39371
|
+
if (state.reasoningStarted) {
|
|
39372
|
+
send("content_block_stop", { type: "content_block_stop", index: state.reasoningIdx });
|
|
39373
|
+
}
|
|
39374
|
+
if (state.textStarted) {
|
|
39375
|
+
send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
|
|
39376
|
+
}
|
|
39377
|
+
for (const t of Array.from(state.tools.values())) {
|
|
39378
|
+
if (t.started && !t.closed) {
|
|
39379
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39380
|
+
t.closed = true;
|
|
39381
|
+
}
|
|
39382
|
+
}
|
|
39383
|
+
if (middlewareManager) {
|
|
39384
|
+
await middlewareManager.afterStreamComplete(target, streamMetadata);
|
|
39385
|
+
}
|
|
39386
|
+
if (reason === "error") {
|
|
39387
|
+
send("error", { type: "error", error: { type: "api_error", message: err } });
|
|
39388
|
+
} else {
|
|
39389
|
+
send("message_delta", {
|
|
39390
|
+
type: "message_delta",
|
|
39391
|
+
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
39392
|
+
usage: { output_tokens: state.usage?.completion_tokens || 0 }
|
|
39393
|
+
});
|
|
39394
|
+
send("message_stop", { type: "message_stop" });
|
|
39395
|
+
}
|
|
39396
|
+
if (state.usage && onTokenUpdate) {
|
|
39397
|
+
onTokenUpdate(state.usage.prompt_tokens || 0, state.usage.completion_tokens || 0);
|
|
39398
|
+
}
|
|
39399
|
+
if (!isClosed) {
|
|
39400
|
+
try {
|
|
39401
|
+
controller.enqueue(encoder.encode(`data: [DONE]
|
|
39402
|
+
|
|
39403
|
+
|
|
39404
|
+
`));
|
|
39405
|
+
} catch (e) {}
|
|
39406
|
+
controller.close();
|
|
39407
|
+
isClosed = true;
|
|
39408
|
+
if (ping)
|
|
39409
|
+
clearInterval(ping);
|
|
39410
|
+
}
|
|
39411
|
+
};
|
|
39412
|
+
try {
|
|
39413
|
+
const reader = response.body.getReader();
|
|
39414
|
+
let buffer = "";
|
|
39415
|
+
while (true) {
|
|
39416
|
+
const { done, value } = await reader.read();
|
|
39417
|
+
if (done)
|
|
39418
|
+
break;
|
|
39419
|
+
buffer += decoder.decode(value, { stream: true });
|
|
39420
|
+
const lines = buffer.split(`
|
|
39421
|
+
`);
|
|
39422
|
+
buffer = lines.pop() || "";
|
|
39423
|
+
for (const line of lines) {
|
|
39424
|
+
if (!line.trim() || !line.startsWith("data: "))
|
|
39425
|
+
continue;
|
|
39426
|
+
const dataStr = line.slice(6);
|
|
39427
|
+
if (dataStr === "[DONE]") {
|
|
39428
|
+
await finalize("done");
|
|
39429
|
+
return;
|
|
39430
|
+
}
|
|
39431
|
+
try {
|
|
39432
|
+
const chunk = JSON.parse(dataStr);
|
|
39433
|
+
if (chunk.usage)
|
|
39434
|
+
state.usage = chunk.usage;
|
|
39435
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
39436
|
+
if (delta) {
|
|
39437
|
+
if (middlewareManager) {
|
|
39438
|
+
await middlewareManager.afterStreamChunk({
|
|
39439
|
+
modelId: target,
|
|
39440
|
+
chunk,
|
|
39441
|
+
delta,
|
|
39442
|
+
metadata: streamMetadata
|
|
39443
|
+
});
|
|
39444
|
+
}
|
|
39445
|
+
const txt = delta.content || "";
|
|
39446
|
+
if (txt) {
|
|
39447
|
+
state.lastActivity = Date.now();
|
|
39448
|
+
if (!state.textStarted) {
|
|
39449
|
+
state.textIdx = state.curIdx++;
|
|
39450
|
+
send("content_block_start", {
|
|
39451
|
+
type: "content_block_start",
|
|
39452
|
+
index: state.textIdx,
|
|
39453
|
+
content_block: { type: "text", text: "" }
|
|
39454
|
+
});
|
|
39455
|
+
state.textStarted = true;
|
|
39456
|
+
}
|
|
39457
|
+
const res = adapter.processTextContent(txt, "");
|
|
39458
|
+
if (res.cleanedText) {
|
|
39459
|
+
send("content_block_delta", {
|
|
39460
|
+
type: "content_block_delta",
|
|
39461
|
+
index: state.textIdx,
|
|
39462
|
+
delta: { type: "text_delta", text: res.cleanedText }
|
|
39463
|
+
});
|
|
39464
|
+
}
|
|
39465
|
+
}
|
|
39466
|
+
if (delta.tool_calls) {
|
|
39467
|
+
for (const tc of delta.tool_calls) {
|
|
39468
|
+
const idx = tc.index;
|
|
39469
|
+
let t = state.tools.get(idx);
|
|
39470
|
+
if (tc.function?.name) {
|
|
39471
|
+
if (!t) {
|
|
39472
|
+
if (state.textStarted) {
|
|
39473
|
+
send("content_block_stop", { type: "content_block_stop", index: state.textIdx });
|
|
39474
|
+
state.textStarted = false;
|
|
39475
|
+
}
|
|
39476
|
+
t = {
|
|
39477
|
+
id: tc.id || `tool_${Date.now()}_${idx}`,
|
|
39478
|
+
name: tc.function.name,
|
|
39479
|
+
blockIndex: state.curIdx++,
|
|
39480
|
+
started: false,
|
|
39481
|
+
closed: false
|
|
39482
|
+
};
|
|
39483
|
+
state.tools.set(idx, t);
|
|
39484
|
+
}
|
|
39485
|
+
if (!t.started) {
|
|
39486
|
+
send("content_block_start", {
|
|
39487
|
+
type: "content_block_start",
|
|
39488
|
+
index: t.blockIndex,
|
|
39489
|
+
content_block: { type: "tool_use", id: t.id, name: t.name }
|
|
39490
|
+
});
|
|
39491
|
+
t.started = true;
|
|
39492
|
+
}
|
|
39493
|
+
}
|
|
39494
|
+
if (tc.function?.arguments && t) {
|
|
39495
|
+
send("content_block_delta", {
|
|
39496
|
+
type: "content_block_delta",
|
|
39497
|
+
index: t.blockIndex,
|
|
39498
|
+
delta: { type: "input_json_delta", partial_json: tc.function.arguments }
|
|
39499
|
+
});
|
|
39500
|
+
}
|
|
39501
|
+
}
|
|
39502
|
+
}
|
|
39503
|
+
}
|
|
39504
|
+
if (chunk.choices?.[0]?.finish_reason === "tool_calls") {
|
|
39505
|
+
for (const t of Array.from(state.tools.values())) {
|
|
39506
|
+
if (t.started && !t.closed) {
|
|
39507
|
+
send("content_block_stop", { type: "content_block_stop", index: t.blockIndex });
|
|
39508
|
+
t.closed = true;
|
|
39509
|
+
}
|
|
39510
|
+
}
|
|
39511
|
+
}
|
|
39512
|
+
} catch (e) {}
|
|
39513
|
+
}
|
|
39514
|
+
}
|
|
39515
|
+
await finalize("unexpected");
|
|
39516
|
+
} catch (e) {
|
|
39517
|
+
await finalize("error", String(e));
|
|
39518
|
+
}
|
|
39519
|
+
},
|
|
39520
|
+
cancel() {
|
|
39521
|
+
isClosed = true;
|
|
39522
|
+
if (ping)
|
|
39523
|
+
clearInterval(ping);
|
|
39524
|
+
}
|
|
39525
|
+
}), {
|
|
39526
|
+
headers: {
|
|
39527
|
+
"Content-Type": "text/event-stream",
|
|
39528
|
+
"Cache-Control": "no-cache",
|
|
39529
|
+
Connection: "keep-alive"
|
|
39530
|
+
}
|
|
39531
|
+
});
|
|
39532
|
+
}
|
|
39533
|
+
var init_openai_compat = __esm(() => {
|
|
39534
|
+
init_transform();
|
|
39535
|
+
});
|
|
39536
|
+
|
|
39537
|
+
// src/handlers/local-provider-handler.ts
|
|
39538
|
+
class LocalProviderHandler {
|
|
39539
|
+
provider;
|
|
39540
|
+
modelName;
|
|
39541
|
+
adapterManager;
|
|
39542
|
+
middlewareManager;
|
|
39543
|
+
port;
|
|
39544
|
+
healthChecked = false;
|
|
39545
|
+
isHealthy = false;
|
|
39546
|
+
constructor(provider, modelName, port) {
|
|
39547
|
+
this.provider = provider;
|
|
39548
|
+
this.modelName = modelName;
|
|
39549
|
+
this.port = port;
|
|
39550
|
+
this.adapterManager = new AdapterManager(modelName);
|
|
39551
|
+
this.middlewareManager = new MiddlewareManager;
|
|
39552
|
+
this.middlewareManager.initialize().catch((err) => {
|
|
39553
|
+
log(`[LocalProvider:${provider.name}] Middleware init error: ${err}`);
|
|
39554
|
+
});
|
|
39555
|
+
}
|
|
39556
|
+
async checkHealth() {
|
|
39557
|
+
if (this.healthChecked)
|
|
39558
|
+
return this.isHealthy;
|
|
39559
|
+
try {
|
|
39560
|
+
const healthUrl = `${this.provider.baseUrl}/api/tags`;
|
|
39561
|
+
const response = await fetch(healthUrl, {
|
|
39562
|
+
method: "GET",
|
|
39563
|
+
signal: AbortSignal.timeout(5000)
|
|
39564
|
+
});
|
|
39565
|
+
if (response.ok) {
|
|
39566
|
+
this.isHealthy = true;
|
|
39567
|
+
this.healthChecked = true;
|
|
39568
|
+
log(`[LocalProvider:${this.provider.name}] Health check passed`);
|
|
39569
|
+
return true;
|
|
39570
|
+
}
|
|
39571
|
+
} catch (e) {
|
|
39572
|
+
try {
|
|
39573
|
+
const modelsUrl = `${this.provider.baseUrl}/v1/models`;
|
|
39574
|
+
const response = await fetch(modelsUrl, {
|
|
39575
|
+
method: "GET",
|
|
39576
|
+
signal: AbortSignal.timeout(5000)
|
|
39577
|
+
});
|
|
39578
|
+
if (response.ok) {
|
|
39579
|
+
this.isHealthy = true;
|
|
39580
|
+
this.healthChecked = true;
|
|
39581
|
+
log(`[LocalProvider:${this.provider.name}] Health check passed (v1/models)`);
|
|
39582
|
+
return true;
|
|
39583
|
+
}
|
|
39584
|
+
} catch (e2) {}
|
|
39585
|
+
}
|
|
39586
|
+
this.healthChecked = true;
|
|
39587
|
+
this.isHealthy = false;
|
|
39588
|
+
return false;
|
|
39589
|
+
}
|
|
39590
|
+
async handle(c, payload) {
|
|
39591
|
+
const target = this.modelName;
|
|
39592
|
+
logStructured(`LocalProvider Request`, {
|
|
39593
|
+
provider: this.provider.name,
|
|
39594
|
+
targetModel: target,
|
|
39595
|
+
originalModel: payload.model,
|
|
39596
|
+
baseUrl: this.provider.baseUrl
|
|
39597
|
+
});
|
|
39598
|
+
if (!this.healthChecked) {
|
|
39599
|
+
const healthy = await this.checkHealth();
|
|
39600
|
+
if (!healthy) {
|
|
39601
|
+
return this.errorResponse(c, "connection_error", this.getConnectionErrorMessage());
|
|
39602
|
+
}
|
|
39603
|
+
}
|
|
39604
|
+
const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
|
|
39605
|
+
const messages = convertMessagesToOpenAI(claudeRequest, target, filterIdentity);
|
|
39606
|
+
const tools = convertToolsToOpenAI(claudeRequest);
|
|
39607
|
+
const finalTools = this.provider.capabilities.supportsTools ? tools : [];
|
|
39608
|
+
if (tools.length > 0 && !this.provider.capabilities.supportsTools) {
|
|
39609
|
+
log(`[LocalProvider:${this.provider.name}] Tools stripped (not supported)`);
|
|
39610
|
+
}
|
|
39611
|
+
const openAIPayload = {
|
|
39612
|
+
model: target,
|
|
39613
|
+
messages,
|
|
39614
|
+
temperature: claudeRequest.temperature ?? 1,
|
|
39615
|
+
stream: this.provider.capabilities.supportsStreaming,
|
|
39616
|
+
max_tokens: claudeRequest.max_tokens,
|
|
39617
|
+
tools: finalTools.length > 0 ? finalTools : undefined,
|
|
39618
|
+
stream_options: this.provider.capabilities.supportsStreaming ? { include_usage: true } : undefined
|
|
39619
|
+
};
|
|
39620
|
+
if (claudeRequest.tool_choice && finalTools.length > 0) {
|
|
39621
|
+
const { type, name } = claudeRequest.tool_choice;
|
|
39622
|
+
if (type === "tool" && name) {
|
|
39623
|
+
openAIPayload.tool_choice = { type: "function", function: { name } };
|
|
39624
|
+
} else if (type === "auto" || type === "none") {
|
|
39625
|
+
openAIPayload.tool_choice = type;
|
|
39626
|
+
}
|
|
39627
|
+
}
|
|
39628
|
+
const adapter = this.adapterManager.getAdapter();
|
|
39629
|
+
if (typeof adapter.reset === "function")
|
|
39630
|
+
adapter.reset();
|
|
39631
|
+
adapter.prepareRequest(openAIPayload, claudeRequest);
|
|
39632
|
+
await this.middlewareManager.beforeRequest({
|
|
39633
|
+
modelId: target,
|
|
39634
|
+
messages,
|
|
39635
|
+
tools: finalTools,
|
|
39636
|
+
stream: openAIPayload.stream
|
|
39637
|
+
});
|
|
39638
|
+
const apiUrl = `${this.provider.baseUrl}${this.provider.apiPath}`;
|
|
39639
|
+
try {
|
|
39640
|
+
const response = await fetch(apiUrl, {
|
|
39641
|
+
method: "POST",
|
|
39642
|
+
headers: {
|
|
39643
|
+
"Content-Type": "application/json"
|
|
39644
|
+
},
|
|
39645
|
+
body: JSON.stringify(openAIPayload)
|
|
39646
|
+
});
|
|
39647
|
+
if (!response.ok) {
|
|
39648
|
+
const errorBody = await response.text();
|
|
39649
|
+
return this.handleErrorResponse(c, response.status, errorBody);
|
|
39650
|
+
}
|
|
39651
|
+
if (droppedParams.length > 0) {
|
|
39652
|
+
c.header("X-Dropped-Params", droppedParams.join(", "));
|
|
39653
|
+
}
|
|
39654
|
+
if (openAIPayload.stream) {
|
|
39655
|
+
return createStreamingResponseHandler(c, response, adapter, target, this.middlewareManager);
|
|
39656
|
+
}
|
|
39657
|
+
const data = await response.json();
|
|
39658
|
+
return c.json(data);
|
|
39659
|
+
} catch (error46) {
|
|
39660
|
+
if (error46.code === "ECONNREFUSED" || error46.cause?.code === "ECONNREFUSED") {
|
|
39661
|
+
return this.errorResponse(c, "connection_error", this.getConnectionErrorMessage());
|
|
39662
|
+
}
|
|
39663
|
+
throw error46;
|
|
39664
|
+
}
|
|
39665
|
+
}
|
|
39666
|
+
handleErrorResponse(c, status, errorBody) {
|
|
39667
|
+
try {
|
|
39668
|
+
const parsed = JSON.parse(errorBody);
|
|
39669
|
+
const errorMsg = parsed.error?.message || parsed.error || errorBody;
|
|
39670
|
+
if (errorMsg.includes("model") && (errorMsg.includes("not found") || errorMsg.includes("does not exist"))) {
|
|
39671
|
+
return this.errorResponse(c, "model_not_found", `Model '${this.modelName}' not found. ${this.getModelPullHint()}`);
|
|
39672
|
+
}
|
|
39673
|
+
if (errorMsg.includes("does not support tools") || errorMsg.includes("tool") && errorMsg.includes("not supported")) {
|
|
39674
|
+
return this.errorResponse(c, "capability_error", `Model '${this.modelName}' does not support tool/function calling. Claude Code requires tool support for most operations. Try a model that supports tools (e.g., llama3.2, mistral, qwen2.5).`, 400);
|
|
39675
|
+
}
|
|
39676
|
+
return this.errorResponse(c, "api_error", errorMsg, status);
|
|
39677
|
+
} catch {
|
|
39678
|
+
return this.errorResponse(c, "api_error", errorBody, status);
|
|
39679
|
+
}
|
|
39680
|
+
}
|
|
39681
|
+
errorResponse(c, type, message, status = 503) {
|
|
39682
|
+
return c.json({
|
|
39683
|
+
error: {
|
|
39684
|
+
type,
|
|
39685
|
+
message
|
|
39686
|
+
}
|
|
39687
|
+
}, status);
|
|
39688
|
+
}
|
|
39689
|
+
getConnectionErrorMessage() {
|
|
39690
|
+
switch (this.provider.name) {
|
|
39691
|
+
case "ollama":
|
|
39692
|
+
return `Cannot connect to Ollama at ${this.provider.baseUrl}. Make sure Ollama is running with: ollama serve`;
|
|
39693
|
+
case "lmstudio":
|
|
39694
|
+
return `Cannot connect to LM Studio at ${this.provider.baseUrl}. Make sure LM Studio server is running.`;
|
|
39695
|
+
case "vllm":
|
|
39696
|
+
return `Cannot connect to vLLM at ${this.provider.baseUrl}. Make sure vLLM server is running.`;
|
|
39697
|
+
default:
|
|
39698
|
+
return `Cannot connect to ${this.provider.name} at ${this.provider.baseUrl}. Make sure the server is running.`;
|
|
39699
|
+
}
|
|
39700
|
+
}
|
|
39701
|
+
getModelPullHint() {
|
|
39702
|
+
switch (this.provider.name) {
|
|
39703
|
+
case "ollama":
|
|
39704
|
+
return `Pull it with: ollama pull ${this.modelName}`;
|
|
39705
|
+
default:
|
|
39706
|
+
return "Make sure the model is available on the server.";
|
|
39707
|
+
}
|
|
39708
|
+
}
|
|
39709
|
+
async shutdown() {}
|
|
39710
|
+
}
|
|
39711
|
+
var init_local_provider_handler = __esm(() => {
|
|
39712
|
+
init_adapter_manager();
|
|
39713
|
+
init_middleware();
|
|
39714
|
+
init_transform();
|
|
39715
|
+
init_logger();
|
|
39716
|
+
init_openai_compat();
|
|
39717
|
+
});
|
|
39718
|
+
|
|
39719
|
+
// src/providers/provider-registry.ts
|
|
39720
|
+
function resolveProvider(modelId) {
|
|
39721
|
+
const providers = getProviders();
|
|
39722
|
+
for (const provider of providers) {
|
|
39723
|
+
for (const prefix of provider.prefixes) {
|
|
39724
|
+
if (modelId.startsWith(prefix)) {
|
|
39725
|
+
return {
|
|
39726
|
+
provider,
|
|
39727
|
+
modelName: modelId.slice(prefix.length)
|
|
39728
|
+
};
|
|
39729
|
+
}
|
|
39730
|
+
}
|
|
39731
|
+
}
|
|
39732
|
+
return null;
|
|
39733
|
+
}
|
|
39734
|
+
function parseUrlModel(modelId) {
|
|
39735
|
+
if (!modelId.startsWith("http://") && !modelId.startsWith("https://")) {
|
|
39736
|
+
return null;
|
|
39737
|
+
}
|
|
39738
|
+
try {
|
|
39739
|
+
const url2 = new URL(modelId);
|
|
39740
|
+
const pathParts = url2.pathname.split("/").filter(Boolean);
|
|
39741
|
+
if (pathParts.length === 0) {
|
|
39742
|
+
return null;
|
|
39743
|
+
}
|
|
39744
|
+
const modelName = pathParts[pathParts.length - 1];
|
|
39745
|
+
let basePath = "";
|
|
39746
|
+
if (pathParts.length > 1) {
|
|
39747
|
+
const prefix = pathParts.slice(0, -1).join("/");
|
|
39748
|
+
if (prefix)
|
|
39749
|
+
basePath = "/" + prefix;
|
|
39750
|
+
}
|
|
39751
|
+
const baseUrl = `${url2.protocol}//${url2.host}${basePath}`;
|
|
39752
|
+
return {
|
|
39753
|
+
baseUrl,
|
|
39754
|
+
modelName
|
|
39755
|
+
};
|
|
39756
|
+
} catch {
|
|
39757
|
+
return null;
|
|
39758
|
+
}
|
|
39759
|
+
}
|
|
39760
|
+
function createUrlProvider(parsed) {
|
|
39761
|
+
return {
|
|
39762
|
+
name: "custom-url",
|
|
39763
|
+
baseUrl: parsed.baseUrl,
|
|
39764
|
+
apiPath: "/v1/chat/completions",
|
|
39765
|
+
envVar: "",
|
|
39766
|
+
prefixes: [],
|
|
39767
|
+
capabilities: {
|
|
39768
|
+
supportsTools: true,
|
|
39769
|
+
supportsVision: false,
|
|
39770
|
+
supportsStreaming: true,
|
|
39771
|
+
supportsJsonMode: true
|
|
39772
|
+
}
|
|
39773
|
+
};
|
|
39774
|
+
}
|
|
39775
|
+
var getProviders = () => [
|
|
39776
|
+
{
|
|
39777
|
+
name: "ollama",
|
|
39778
|
+
baseUrl: process.env.OLLAMA_HOST || process.env.OLLAMA_BASE_URL || "http://localhost:11434",
|
|
39779
|
+
apiPath: "/v1/chat/completions",
|
|
39780
|
+
envVar: "OLLAMA_BASE_URL",
|
|
39781
|
+
prefixes: ["ollama/", "ollama:"],
|
|
39782
|
+
capabilities: {
|
|
39783
|
+
supportsTools: true,
|
|
39784
|
+
supportsVision: false,
|
|
39785
|
+
supportsStreaming: true,
|
|
39786
|
+
supportsJsonMode: true
|
|
39787
|
+
}
|
|
39788
|
+
},
|
|
39789
|
+
{
|
|
39790
|
+
name: "lmstudio",
|
|
39791
|
+
baseUrl: process.env.LMSTUDIO_BASE_URL || "http://localhost:1234",
|
|
39792
|
+
apiPath: "/v1/chat/completions",
|
|
39793
|
+
envVar: "LMSTUDIO_BASE_URL",
|
|
39794
|
+
prefixes: ["lmstudio/", "lmstudio:"],
|
|
39795
|
+
capabilities: {
|
|
39796
|
+
supportsTools: true,
|
|
39797
|
+
supportsVision: false,
|
|
39798
|
+
supportsStreaming: true,
|
|
39799
|
+
supportsJsonMode: true
|
|
39800
|
+
}
|
|
39801
|
+
},
|
|
39802
|
+
{
|
|
39803
|
+
name: "vllm",
|
|
39804
|
+
baseUrl: process.env.VLLM_BASE_URL || "http://localhost:8000",
|
|
39805
|
+
apiPath: "/v1/chat/completions",
|
|
39806
|
+
envVar: "VLLM_BASE_URL",
|
|
39807
|
+
prefixes: ["vllm/", "vllm:"],
|
|
39808
|
+
capabilities: {
|
|
39809
|
+
supportsTools: true,
|
|
39810
|
+
supportsVision: false,
|
|
39811
|
+
supportsStreaming: true,
|
|
39812
|
+
supportsJsonMode: true
|
|
39813
|
+
}
|
|
39814
|
+
}
|
|
39815
|
+
];
|
|
39816
|
+
|
|
39115
39817
|
// src/proxy-server.ts
|
|
39116
39818
|
var exports_proxy_server = {};
|
|
39117
39819
|
__export(exports_proxy_server, {
|
|
@@ -39119,23 +39821,47 @@ __export(exports_proxy_server, {
|
|
|
39119
39821
|
});
|
|
39120
39822
|
async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey, modelMap) {
|
|
39121
39823
|
const nativeHandler = new NativeHandler(anthropicApiKey);
|
|
39122
|
-
const
|
|
39824
|
+
const openRouterHandlers = new Map;
|
|
39825
|
+
const localProviderHandlers = new Map;
|
|
39123
39826
|
const getOpenRouterHandler = (targetModel) => {
|
|
39124
|
-
if (!
|
|
39125
|
-
|
|
39126
|
-
}
|
|
39127
|
-
return
|
|
39128
|
-
};
|
|
39129
|
-
|
|
39130
|
-
|
|
39131
|
-
|
|
39132
|
-
|
|
39133
|
-
|
|
39134
|
-
|
|
39135
|
-
|
|
39136
|
-
|
|
39137
|
-
|
|
39138
|
-
|
|
39827
|
+
if (!openRouterHandlers.has(targetModel)) {
|
|
39828
|
+
openRouterHandlers.set(targetModel, new OpenRouterHandler(targetModel, openrouterApiKey, port));
|
|
39829
|
+
}
|
|
39830
|
+
return openRouterHandlers.get(targetModel);
|
|
39831
|
+
};
|
|
39832
|
+
const getLocalProviderHandler = (targetModel) => {
|
|
39833
|
+
if (localProviderHandlers.has(targetModel)) {
|
|
39834
|
+
return localProviderHandlers.get(targetModel);
|
|
39835
|
+
}
|
|
39836
|
+
const resolved = resolveProvider(targetModel);
|
|
39837
|
+
if (resolved) {
|
|
39838
|
+
const handler = new LocalProviderHandler(resolved.provider, resolved.modelName, port);
|
|
39839
|
+
localProviderHandlers.set(targetModel, handler);
|
|
39840
|
+
log(`[Proxy] Created local provider handler: ${resolved.provider.name}/${resolved.modelName}`);
|
|
39841
|
+
return handler;
|
|
39842
|
+
}
|
|
39843
|
+
const urlParsed = parseUrlModel(targetModel);
|
|
39844
|
+
if (urlParsed) {
|
|
39845
|
+
const provider = createUrlProvider(urlParsed);
|
|
39846
|
+
const handler = new LocalProviderHandler(provider, urlParsed.modelName, port);
|
|
39847
|
+
localProviderHandlers.set(targetModel, handler);
|
|
39848
|
+
log(`[Proxy] Created URL-based local provider handler: ${urlParsed.baseUrl}/${urlParsed.modelName}`);
|
|
39849
|
+
return handler;
|
|
39850
|
+
}
|
|
39851
|
+
return null;
|
|
39852
|
+
};
|
|
39853
|
+
const initHandler = (m) => {
|
|
39854
|
+
if (!m)
|
|
39855
|
+
return;
|
|
39856
|
+
const localHandler = getLocalProviderHandler(m);
|
|
39857
|
+
if (!localHandler && m.includes("/"))
|
|
39858
|
+
getOpenRouterHandler(m);
|
|
39859
|
+
};
|
|
39860
|
+
initHandler(model);
|
|
39861
|
+
initHandler(modelMap?.opus);
|
|
39862
|
+
initHandler(modelMap?.sonnet);
|
|
39863
|
+
initHandler(modelMap?.haiku);
|
|
39864
|
+
initHandler(modelMap?.subagent);
|
|
39139
39865
|
const getHandlerForRequest = (requestedModel) => {
|
|
39140
39866
|
if (monitorMode)
|
|
39141
39867
|
return nativeHandler;
|
|
@@ -39149,6 +39875,9 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
39149
39875
|
else if (req.includes("haiku") && modelMap.haiku)
|
|
39150
39876
|
target = modelMap.haiku;
|
|
39151
39877
|
}
|
|
39878
|
+
const localHandler = getLocalProviderHandler(target);
|
|
39879
|
+
if (localHandler)
|
|
39880
|
+
return localHandler;
|
|
39152
39881
|
const isNative = !target.includes("/");
|
|
39153
39882
|
if (isNative) {
|
|
39154
39883
|
return nativeHandler;
|
|
@@ -39209,6 +39938,7 @@ var init_proxy_server = __esm(() => {
|
|
|
39209
39938
|
init_logger();
|
|
39210
39939
|
init_native_handler();
|
|
39211
39940
|
init_openrouter_handler();
|
|
39941
|
+
init_local_provider_handler();
|
|
39212
39942
|
});
|
|
39213
39943
|
|
|
39214
39944
|
// src/update-checker.ts
|