claudish 4.6.7 → 4.6.9
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 +408 -39
- package/package.json +1 -1
- package/recommended-models.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31010,11 +31010,11 @@ async function doesModelSupportReasoning(modelId) {
|
|
|
31010
31010
|
}
|
|
31011
31011
|
return false;
|
|
31012
31012
|
}
|
|
31013
|
-
async function fetchLiteLLMModels(baseUrl, apiKey) {
|
|
31013
|
+
async function fetchLiteLLMModels(baseUrl, apiKey, forceUpdate = false) {
|
|
31014
31014
|
const hash2 = createHash2("sha256").update(baseUrl).digest("hex").substring(0, 16);
|
|
31015
31015
|
const cacheDir = join6(homedir5(), ".claudish");
|
|
31016
31016
|
const cachePath = join6(cacheDir, `litellm-models-${hash2}.json`);
|
|
31017
|
-
if (existsSync6(cachePath)) {
|
|
31017
|
+
if (!forceUpdate && existsSync6(cachePath)) {
|
|
31018
31018
|
try {
|
|
31019
31019
|
const cacheData = JSON.parse(readFileSync5(cachePath, "utf-8"));
|
|
31020
31020
|
const timestamp = new Date(cacheData.timestamp);
|
|
@@ -31572,11 +31572,11 @@ async function getFreeModels() {
|
|
|
31572
31572
|
});
|
|
31573
31573
|
return combined;
|
|
31574
31574
|
}
|
|
31575
|
-
async function getAllModelsForSearch() {
|
|
31575
|
+
async function getAllModelsForSearch(forceUpdate = false) {
|
|
31576
31576
|
const litellmBaseUrl = process.env.LITELLM_BASE_URL;
|
|
31577
31577
|
const litellmApiKey = process.env.LITELLM_API_KEY;
|
|
31578
31578
|
const fetchEntries = [
|
|
31579
|
-
{ name: "OpenRouter", promise: fetchAllModels().then((models) => models.map(toModelInfo)) },
|
|
31579
|
+
{ name: "OpenRouter", promise: fetchAllModels(forceUpdate).then((models) => models.map(toModelInfo)) },
|
|
31580
31580
|
{ name: "xAI", promise: fetchXAIModels() },
|
|
31581
31581
|
{ name: "Gemini", promise: fetchGeminiModels() },
|
|
31582
31582
|
{ name: "OpenAI", promise: fetchOpenAIModels() },
|
|
@@ -31586,7 +31586,7 @@ async function getAllModelsForSearch() {
|
|
|
31586
31586
|
{ name: "Zen", promise: fetchZenFreeModels() }
|
|
31587
31587
|
];
|
|
31588
31588
|
if (litellmBaseUrl && litellmApiKey) {
|
|
31589
|
-
fetchEntries.push({ name: "LiteLLM", promise: fetchLiteLLMModels(litellmBaseUrl, litellmApiKey) });
|
|
31589
|
+
fetchEntries.push({ name: "LiteLLM", promise: fetchLiteLLMModels(litellmBaseUrl, litellmApiKey, forceUpdate) });
|
|
31590
31590
|
}
|
|
31591
31591
|
const settled = await Promise.allSettled(fetchEntries.map((e) => e.promise));
|
|
31592
31592
|
const fetchResults = {};
|
|
@@ -31686,7 +31686,7 @@ function fuzzyMatch(text, query) {
|
|
|
31686
31686
|
return queryIdx === lowerQuery.length ? score / lowerQuery.length * 0.6 : 0;
|
|
31687
31687
|
}
|
|
31688
31688
|
async function selectModel(options = {}) {
|
|
31689
|
-
const { freeOnly = false, recommended = true, message } = options;
|
|
31689
|
+
const { freeOnly = false, recommended = true, message, forceUpdate = false } = options;
|
|
31690
31690
|
let models;
|
|
31691
31691
|
if (freeOnly) {
|
|
31692
31692
|
models = await getFreeModels();
|
|
@@ -31695,7 +31695,7 @@ async function selectModel(options = {}) {
|
|
|
31695
31695
|
}
|
|
31696
31696
|
} else {
|
|
31697
31697
|
const [allModels, recommendedModels] = await Promise.all([
|
|
31698
|
-
getAllModelsForSearch(),
|
|
31698
|
+
getAllModelsForSearch(forceUpdate),
|
|
31699
31699
|
Promise.resolve(recommended ? loadRecommendedModels2() : [])
|
|
31700
31700
|
]);
|
|
31701
31701
|
const seenIds = new Set;
|
|
@@ -32800,6 +32800,8 @@ var init_model_parser = __esm(() => {
|
|
|
32800
32800
|
lc: "ollamacloud",
|
|
32801
32801
|
meta: "ollamacloud",
|
|
32802
32802
|
poe: "poe",
|
|
32803
|
+
litellm: "litellm",
|
|
32804
|
+
ll: "litellm",
|
|
32803
32805
|
ollama: "ollama",
|
|
32804
32806
|
lms: "lmstudio",
|
|
32805
32807
|
lmstudio: "lmstudio",
|
|
@@ -32820,7 +32822,8 @@ var init_model_parser = __esm(() => {
|
|
|
32820
32822
|
"opencode-zen",
|
|
32821
32823
|
"vertex",
|
|
32822
32824
|
"gemini-codeassist",
|
|
32823
|
-
"poe"
|
|
32825
|
+
"poe",
|
|
32826
|
+
"litellm"
|
|
32824
32827
|
]);
|
|
32825
32828
|
LOCAL_PROVIDERS = new Set(["ollama", "lmstudio", "vllm", "mlx"]);
|
|
32826
32829
|
NATIVE_MODEL_PATTERNS = [
|
|
@@ -33041,7 +33044,8 @@ function resolveRemoteProvider(modelId) {
|
|
|
33041
33044
|
ollamacloud: "ollamacloud",
|
|
33042
33045
|
"opencode-zen": "opencode-zen",
|
|
33043
33046
|
vertex: "vertex",
|
|
33044
|
-
"gemini-codeassist": "gemini-codeassist"
|
|
33047
|
+
"gemini-codeassist": "gemini-codeassist",
|
|
33048
|
+
litellm: "litellm"
|
|
33045
33049
|
};
|
|
33046
33050
|
const mappedProviderName = providerNameMap[parsed.provider];
|
|
33047
33051
|
if (mappedProviderName) {
|
|
@@ -33253,6 +33257,20 @@ var getRemoteProviders = () => [
|
|
|
33253
33257
|
supportsJsonMode: false,
|
|
33254
33258
|
supportsReasoning: true
|
|
33255
33259
|
}
|
|
33260
|
+
},
|
|
33261
|
+
{
|
|
33262
|
+
name: "litellm",
|
|
33263
|
+
baseUrl: process.env.LITELLM_BASE_URL || "",
|
|
33264
|
+
apiPath: "/v1/chat/completions",
|
|
33265
|
+
apiKeyEnvVar: "LITELLM_API_KEY",
|
|
33266
|
+
prefixes: ["litellm/", "ll/"],
|
|
33267
|
+
capabilities: {
|
|
33268
|
+
supportsTools: true,
|
|
33269
|
+
supportsVision: true,
|
|
33270
|
+
supportsStreaming: true,
|
|
33271
|
+
supportsJsonMode: true,
|
|
33272
|
+
supportsReasoning: true
|
|
33273
|
+
}
|
|
33256
33274
|
}
|
|
33257
33275
|
];
|
|
33258
33276
|
var init_remote_provider_registry = __esm(() => {
|
|
@@ -33592,6 +33610,11 @@ var init_provider_resolver = __esm(() => {
|
|
|
33592
33610
|
envVar: "ZAI_API_KEY",
|
|
33593
33611
|
description: "Z.AI API Key",
|
|
33594
33612
|
url: "https://z.ai/"
|
|
33613
|
+
},
|
|
33614
|
+
litellm: {
|
|
33615
|
+
envVar: "LITELLM_API_KEY",
|
|
33616
|
+
description: "LiteLLM API Key",
|
|
33617
|
+
url: "https://docs.litellm.ai/"
|
|
33595
33618
|
}
|
|
33596
33619
|
};
|
|
33597
33620
|
PROVIDER_DISPLAY_NAMES = {
|
|
@@ -33607,7 +33630,8 @@ var init_provider_resolver = __esm(() => {
|
|
|
33607
33630
|
"glm-coding": "GLM Coding",
|
|
33608
33631
|
zai: "Z.AI",
|
|
33609
33632
|
ollamacloud: "OllamaCloud",
|
|
33610
|
-
"opencode-zen": "OpenCode Zen"
|
|
33633
|
+
"opencode-zen": "OpenCode Zen",
|
|
33634
|
+
litellm: "LiteLLM"
|
|
33611
33635
|
};
|
|
33612
33636
|
});
|
|
33613
33637
|
|
|
@@ -33624,13 +33648,34 @@ __export(exports_cli, {
|
|
|
33624
33648
|
getMissingKeyResolutions: () => getMissingKeyResolutions,
|
|
33625
33649
|
getMissingKeyError: () => getMissingKeyError
|
|
33626
33650
|
});
|
|
33627
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync6, copyFileSync } from "node:fs";
|
|
33651
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync9, mkdirSync as mkdirSync6, copyFileSync, readdirSync, unlinkSync as unlinkSync3 } from "node:fs";
|
|
33628
33652
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
33629
33653
|
import { dirname as dirname4, join as join9 } from "node:path";
|
|
33630
33654
|
import { homedir as homedir8 } from "node:os";
|
|
33631
33655
|
function getVersion() {
|
|
33632
33656
|
return VERSION;
|
|
33633
33657
|
}
|
|
33658
|
+
function clearAllModelCaches() {
|
|
33659
|
+
const cacheDir = join9(homedir8(), ".claudish");
|
|
33660
|
+
if (!existsSync9(cacheDir))
|
|
33661
|
+
return;
|
|
33662
|
+
const cachePatterns = ["all-models.json", "pricing-cache.json"];
|
|
33663
|
+
let cleared = 0;
|
|
33664
|
+
try {
|
|
33665
|
+
const files = readdirSync(cacheDir);
|
|
33666
|
+
for (const file2 of files) {
|
|
33667
|
+
if (cachePatterns.includes(file2) || file2.startsWith("litellm-models-")) {
|
|
33668
|
+
unlinkSync3(join9(cacheDir, file2));
|
|
33669
|
+
cleared++;
|
|
33670
|
+
}
|
|
33671
|
+
}
|
|
33672
|
+
if (cleared > 0) {
|
|
33673
|
+
console.error(`\uD83D\uDDD1️ Cleared ${cleared} cache file(s)`);
|
|
33674
|
+
}
|
|
33675
|
+
} catch (error46) {
|
|
33676
|
+
console.error(`Warning: Could not clear caches: ${error46}`);
|
|
33677
|
+
}
|
|
33678
|
+
}
|
|
33634
33679
|
async function parseArgs(args) {
|
|
33635
33680
|
const config3 = {
|
|
33636
33681
|
model: undefined,
|
|
@@ -33770,6 +33815,8 @@ async function parseArgs(args) {
|
|
|
33770
33815
|
} else if (arg === "--top-models") {
|
|
33771
33816
|
const hasJsonFlag = args.includes("--json");
|
|
33772
33817
|
const forceUpdate = args.includes("--force-update");
|
|
33818
|
+
if (forceUpdate)
|
|
33819
|
+
clearAllModelCaches();
|
|
33773
33820
|
await checkAndUpdateModelsCache(forceUpdate);
|
|
33774
33821
|
if (hasJsonFlag) {
|
|
33775
33822
|
printAvailableModelsJSON();
|
|
@@ -33777,12 +33824,14 @@ async function parseArgs(args) {
|
|
|
33777
33824
|
printAvailableModels();
|
|
33778
33825
|
}
|
|
33779
33826
|
process.exit(0);
|
|
33780
|
-
} else if (arg === "--models" || arg === "-s" || arg === "--search") {
|
|
33827
|
+
} else if (arg === "--models" || arg === "--list-models" || arg === "-s" || arg === "--search") {
|
|
33781
33828
|
const nextArg = args[i + 1];
|
|
33782
33829
|
const hasQuery = nextArg && !nextArg.startsWith("--");
|
|
33783
33830
|
const query = hasQuery ? args[++i] : null;
|
|
33784
33831
|
const hasJsonFlag = args.includes("--json");
|
|
33785
33832
|
const forceUpdate = args.includes("--force-update");
|
|
33833
|
+
if (forceUpdate)
|
|
33834
|
+
clearAllModelCaches();
|
|
33786
33835
|
if (query) {
|
|
33787
33836
|
await searchAndPrintModels(query, forceUpdate);
|
|
33788
33837
|
} else {
|
|
@@ -34890,7 +34939,7 @@ async function fetchGLMCodingModels2() {
|
|
|
34890
34939
|
return [];
|
|
34891
34940
|
}
|
|
34892
34941
|
}
|
|
34893
|
-
var __filename5, __dirname5, VERSION = "4.6.
|
|
34942
|
+
var __filename5, __dirname5, VERSION = "4.6.9", CACHE_MAX_AGE_DAYS3 = 2, MODELS_JSON_PATH, CLAUDISH_CACHE_DIR3, ALL_MODELS_JSON_PATH2;
|
|
34894
34943
|
var init_cli = __esm(() => {
|
|
34895
34944
|
init_config();
|
|
34896
34945
|
init_model_loader();
|
|
@@ -34914,7 +34963,7 @@ __export(exports_claude_runner, {
|
|
|
34914
34963
|
checkClaudeInstalled: () => checkClaudeInstalled
|
|
34915
34964
|
});
|
|
34916
34965
|
import { spawn } from "node:child_process";
|
|
34917
|
-
import { writeFileSync as writeFileSync7, unlinkSync as
|
|
34966
|
+
import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync4, mkdirSync as mkdirSync7, existsSync as existsSync10 } from "node:fs";
|
|
34918
34967
|
import { tmpdir, homedir as homedir9 } from "node:os";
|
|
34919
34968
|
import { join as join10 } from "node:path";
|
|
34920
34969
|
function isWindows() {
|
|
@@ -35137,7 +35186,7 @@ Or set CLAUDE_PATH to your custom installation:`);
|
|
|
35137
35186
|
});
|
|
35138
35187
|
});
|
|
35139
35188
|
try {
|
|
35140
|
-
|
|
35189
|
+
unlinkSync4(tempSettingsPath);
|
|
35141
35190
|
} catch (error46) {}
|
|
35142
35191
|
return exitCode;
|
|
35143
35192
|
}
|
|
@@ -35151,7 +35200,7 @@ function setupSignalHandlers(proc, tempSettingsPath, quiet) {
|
|
|
35151
35200
|
}
|
|
35152
35201
|
proc.kill();
|
|
35153
35202
|
try {
|
|
35154
|
-
|
|
35203
|
+
unlinkSync4(tempSettingsPath);
|
|
35155
35204
|
} catch {}
|
|
35156
35205
|
process.exit(0);
|
|
35157
35206
|
});
|
|
@@ -65417,10 +65466,320 @@ var init_ollamacloud_handler = __esm(() => {
|
|
|
65417
65466
|
init_remote_provider_types();
|
|
65418
65467
|
});
|
|
65419
65468
|
|
|
65420
|
-
// src/
|
|
65421
|
-
import {
|
|
65469
|
+
// src/handlers/shared/remote-provider-handler.ts
|
|
65470
|
+
import { writeFileSync as writeFileSync16, mkdirSync as mkdirSync16 } from "node:fs";
|
|
65422
65471
|
import { homedir as homedir19 } from "node:os";
|
|
65423
65472
|
import { join as join20 } from "node:path";
|
|
65473
|
+
|
|
65474
|
+
class RemoteProviderHandler {
|
|
65475
|
+
targetModel;
|
|
65476
|
+
modelName;
|
|
65477
|
+
apiKey;
|
|
65478
|
+
adapterManager;
|
|
65479
|
+
middlewareManager;
|
|
65480
|
+
port;
|
|
65481
|
+
sessionTotalCost = 0;
|
|
65482
|
+
sessionInputTokens = 0;
|
|
65483
|
+
sessionOutputTokens = 0;
|
|
65484
|
+
contextWindow = 200000;
|
|
65485
|
+
CLAUDE_INTERNAL_CONTEXT_MAX = 200000;
|
|
65486
|
+
constructor(targetModel, modelName, apiKey, port) {
|
|
65487
|
+
this.targetModel = targetModel;
|
|
65488
|
+
this.modelName = modelName;
|
|
65489
|
+
this.apiKey = apiKey;
|
|
65490
|
+
this.port = port;
|
|
65491
|
+
this.adapterManager = new AdapterManager(targetModel);
|
|
65492
|
+
this.middlewareManager = new MiddlewareManager;
|
|
65493
|
+
this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
|
|
65494
|
+
this.middlewareManager.initialize().catch((err) => log(`[Handler:${targetModel}] Middleware init error: ${err}`));
|
|
65495
|
+
}
|
|
65496
|
+
getAdditionalHeaders() {
|
|
65497
|
+
return {};
|
|
65498
|
+
}
|
|
65499
|
+
getApiEndpoint() {
|
|
65500
|
+
const config3 = this.getProviderConfig();
|
|
65501
|
+
return `${config3.baseUrl}${config3.apiPath}`;
|
|
65502
|
+
}
|
|
65503
|
+
supportsReasoning() {
|
|
65504
|
+
return false;
|
|
65505
|
+
}
|
|
65506
|
+
writeTokenFile(input, output) {
|
|
65507
|
+
try {
|
|
65508
|
+
const total = input + output;
|
|
65509
|
+
const leftPct = this.contextWindow > 0 ? Math.max(0, Math.min(100, Math.round((this.contextWindow - total) / this.contextWindow * 100))) : 100;
|
|
65510
|
+
const pricing = this.getPricing();
|
|
65511
|
+
const data = {
|
|
65512
|
+
input_tokens: input,
|
|
65513
|
+
output_tokens: output,
|
|
65514
|
+
total_tokens: total,
|
|
65515
|
+
total_cost: this.sessionTotalCost,
|
|
65516
|
+
context_window: this.contextWindow,
|
|
65517
|
+
context_left_percent: leftPct,
|
|
65518
|
+
is_free: pricing.isFree || false,
|
|
65519
|
+
is_estimated: pricing.isEstimate || false,
|
|
65520
|
+
provider_name: this.getProviderName(),
|
|
65521
|
+
model_name: this.modelName,
|
|
65522
|
+
updated_at: Date.now()
|
|
65523
|
+
};
|
|
65524
|
+
const claudishDir = join20(homedir19(), ".claudish");
|
|
65525
|
+
mkdirSync16(claudishDir, { recursive: true });
|
|
65526
|
+
writeFileSync16(join20(claudishDir, `tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
|
|
65527
|
+
} catch (e) {
|
|
65528
|
+
log(`[Handler] Error writing token file: ${e}`);
|
|
65529
|
+
}
|
|
65530
|
+
}
|
|
65531
|
+
updateTokenTracking(inputTokens, outputTokens) {
|
|
65532
|
+
this.sessionInputTokens = inputTokens;
|
|
65533
|
+
this.sessionOutputTokens += outputTokens;
|
|
65534
|
+
const pricing = this.getPricing();
|
|
65535
|
+
const cost = inputTokens / 1e6 * pricing.inputCostPer1M + outputTokens / 1e6 * pricing.outputCostPer1M;
|
|
65536
|
+
this.sessionTotalCost += cost;
|
|
65537
|
+
this.writeTokenFile(inputTokens, this.sessionOutputTokens);
|
|
65538
|
+
}
|
|
65539
|
+
supportsVision() {
|
|
65540
|
+
return true;
|
|
65541
|
+
}
|
|
65542
|
+
convertMessages(claudeRequest) {
|
|
65543
|
+
const messages = convertMessagesToOpenAI(claudeRequest, this.targetModel, filterIdentity);
|
|
65544
|
+
if (!this.supportsVision()) {
|
|
65545
|
+
for (const msg of messages) {
|
|
65546
|
+
if (Array.isArray(msg.content)) {
|
|
65547
|
+
msg.content = msg.content.filter((part) => part.type !== "image_url");
|
|
65548
|
+
if (msg.content.length === 1 && msg.content[0].type === "text") {
|
|
65549
|
+
msg.content = msg.content[0].text;
|
|
65550
|
+
} else if (msg.content.length === 0) {
|
|
65551
|
+
msg.content = "";
|
|
65552
|
+
}
|
|
65553
|
+
}
|
|
65554
|
+
}
|
|
65555
|
+
}
|
|
65556
|
+
return messages;
|
|
65557
|
+
}
|
|
65558
|
+
convertTools(claudeRequest) {
|
|
65559
|
+
return convertToolsToOpenAI(claudeRequest);
|
|
65560
|
+
}
|
|
65561
|
+
handleStreamingResponse(c, response, adapter, claudeRequest, toolNameMap) {
|
|
65562
|
+
return createStreamingResponseHandler(c, response, adapter, this.targetModel, this.middlewareManager, (input, output) => this.updateTokenTracking(input, output), claudeRequest.tools, toolNameMap);
|
|
65563
|
+
}
|
|
65564
|
+
async handle(c, payload) {
|
|
65565
|
+
const config3 = this.getProviderConfig();
|
|
65566
|
+
const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
|
|
65567
|
+
const messages = this.convertMessages(claudeRequest);
|
|
65568
|
+
const tools = this.convertTools(claudeRequest);
|
|
65569
|
+
const systemPromptLength = typeof claudeRequest.system === "string" ? claudeRequest.system.length : 0;
|
|
65570
|
+
logStructured(`${config3.name} Request`, {
|
|
65571
|
+
targetModel: this.targetModel,
|
|
65572
|
+
originalModel: payload.model,
|
|
65573
|
+
messageCount: messages.length,
|
|
65574
|
+
toolCount: tools.length,
|
|
65575
|
+
systemPromptLength,
|
|
65576
|
+
maxTokens: claudeRequest.max_tokens
|
|
65577
|
+
});
|
|
65578
|
+
if (getLogLevel() === "debug") {
|
|
65579
|
+
const lastUserMsg = messages.filter((m) => m.role === "user").pop();
|
|
65580
|
+
if (lastUserMsg) {
|
|
65581
|
+
const content = typeof lastUserMsg.content === "string" ? lastUserMsg.content : JSON.stringify(lastUserMsg.content);
|
|
65582
|
+
log(`[${config3.name}] Last user message: ${truncateContent(content, 500)}`);
|
|
65583
|
+
}
|
|
65584
|
+
if (tools.length > 0) {
|
|
65585
|
+
const toolNames = tools.map((t) => t.function?.name || t.name).join(", ");
|
|
65586
|
+
log(`[${config3.name}] Tools: ${toolNames}`);
|
|
65587
|
+
}
|
|
65588
|
+
}
|
|
65589
|
+
const requestPayload = this.buildRequestPayload(claudeRequest, messages, tools);
|
|
65590
|
+
const adapter = this.adapterManager.getAdapter();
|
|
65591
|
+
if (typeof adapter.reset === "function")
|
|
65592
|
+
adapter.reset();
|
|
65593
|
+
adapter.prepareRequest(requestPayload, claudeRequest);
|
|
65594
|
+
const toolNameMap = adapter.getToolNameMap();
|
|
65595
|
+
await this.middlewareManager.beforeRequest({
|
|
65596
|
+
modelId: this.targetModel,
|
|
65597
|
+
messages,
|
|
65598
|
+
tools,
|
|
65599
|
+
stream: true
|
|
65600
|
+
});
|
|
65601
|
+
const endpoint = this.getApiEndpoint();
|
|
65602
|
+
const headers = {
|
|
65603
|
+
"Content-Type": "application/json",
|
|
65604
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
65605
|
+
...this.getAdditionalHeaders()
|
|
65606
|
+
};
|
|
65607
|
+
log(`[${config3.name}] Calling API: ${endpoint}`);
|
|
65608
|
+
const response = await fetch(endpoint, {
|
|
65609
|
+
method: "POST",
|
|
65610
|
+
headers,
|
|
65611
|
+
body: JSON.stringify(requestPayload)
|
|
65612
|
+
});
|
|
65613
|
+
log(`[${config3.name}] Response status: ${response.status}`);
|
|
65614
|
+
if (!response.ok) {
|
|
65615
|
+
const errorText = await response.text();
|
|
65616
|
+
log(`[${config3.name}] Error: ${errorText}`);
|
|
65617
|
+
return c.json({ error: errorText }, response.status);
|
|
65618
|
+
}
|
|
65619
|
+
if (droppedParams.length > 0) {
|
|
65620
|
+
c.header("X-Dropped-Params", droppedParams.join(", "));
|
|
65621
|
+
}
|
|
65622
|
+
return this.handleStreamingResponse(c, response, adapter, claudeRequest, toolNameMap);
|
|
65623
|
+
}
|
|
65624
|
+
async shutdown() {}
|
|
65625
|
+
}
|
|
65626
|
+
var init_remote_provider_handler = __esm(() => {
|
|
65627
|
+
init_adapter_manager();
|
|
65628
|
+
init_middleware();
|
|
65629
|
+
init_transform();
|
|
65630
|
+
init_logger();
|
|
65631
|
+
init_openai_compat();
|
|
65632
|
+
});
|
|
65633
|
+
|
|
65634
|
+
// src/handlers/litellm-handler.ts
|
|
65635
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
65636
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
65637
|
+
import { homedir as homedir20 } from "node:os";
|
|
65638
|
+
import { join as join21 } from "node:path";
|
|
65639
|
+
var INLINE_IMAGE_MODEL_PATTERNS, MODEL_EXTRA_HEADERS, LiteLLMHandler;
|
|
65640
|
+
var init_litellm_handler = __esm(() => {
|
|
65641
|
+
init_remote_provider_handler();
|
|
65642
|
+
init_logger();
|
|
65643
|
+
INLINE_IMAGE_MODEL_PATTERNS = ["minimax"];
|
|
65644
|
+
MODEL_EXTRA_HEADERS = [
|
|
65645
|
+
{ pattern: "kimi", headers: { "User-Agent": "claude-code/1.0" } }
|
|
65646
|
+
];
|
|
65647
|
+
LiteLLMHandler = class LiteLLMHandler extends RemoteProviderHandler {
|
|
65648
|
+
baseUrl;
|
|
65649
|
+
modelVisionSupported;
|
|
65650
|
+
needsInlineImages;
|
|
65651
|
+
constructor(targetModel, modelName, apiKey, port, baseUrl) {
|
|
65652
|
+
super(targetModel, modelName, apiKey, port);
|
|
65653
|
+
this.baseUrl = baseUrl;
|
|
65654
|
+
this.modelVisionSupported = this.checkVisionSupport();
|
|
65655
|
+
this.needsInlineImages = INLINE_IMAGE_MODEL_PATTERNS.some((p) => this.modelName.toLowerCase().includes(p));
|
|
65656
|
+
}
|
|
65657
|
+
supportsVision() {
|
|
65658
|
+
return this.modelVisionSupported;
|
|
65659
|
+
}
|
|
65660
|
+
convertMessages(claudeRequest) {
|
|
65661
|
+
const messages = super.convertMessages(claudeRequest);
|
|
65662
|
+
if (!this.needsInlineImages)
|
|
65663
|
+
return messages;
|
|
65664
|
+
for (const msg of messages) {
|
|
65665
|
+
if (!Array.isArray(msg.content))
|
|
65666
|
+
continue;
|
|
65667
|
+
const newContent = [];
|
|
65668
|
+
let inlineImages = "";
|
|
65669
|
+
for (const part of msg.content) {
|
|
65670
|
+
if (part.type === "image_url") {
|
|
65671
|
+
const url2 = typeof part.image_url === "string" ? part.image_url : part.image_url?.url;
|
|
65672
|
+
if (url2?.startsWith("data:")) {
|
|
65673
|
+
const base64Match = url2.match(/^data:[^;]+;base64,(.+)$/);
|
|
65674
|
+
if (base64Match) {
|
|
65675
|
+
inlineImages += `
|
|
65676
|
+
[Image base64:${base64Match[1]}]`;
|
|
65677
|
+
log(`[LiteLLM] Converted image_url to inline base64 for ${this.modelName}`);
|
|
65678
|
+
}
|
|
65679
|
+
} else if (url2) {
|
|
65680
|
+
inlineImages += `
|
|
65681
|
+
[Image URL: ${url2}]`;
|
|
65682
|
+
}
|
|
65683
|
+
} else {
|
|
65684
|
+
newContent.push(part);
|
|
65685
|
+
}
|
|
65686
|
+
}
|
|
65687
|
+
if (inlineImages) {
|
|
65688
|
+
const lastText = newContent.findLast((p) => p.type === "text");
|
|
65689
|
+
if (lastText) {
|
|
65690
|
+
lastText.text += inlineImages;
|
|
65691
|
+
} else {
|
|
65692
|
+
newContent.push({ type: "text", text: inlineImages.trim() });
|
|
65693
|
+
}
|
|
65694
|
+
}
|
|
65695
|
+
if (newContent.length === 1 && newContent[0].type === "text") {
|
|
65696
|
+
msg.content = newContent[0].text;
|
|
65697
|
+
} else if (newContent.length > 0) {
|
|
65698
|
+
msg.content = newContent;
|
|
65699
|
+
}
|
|
65700
|
+
}
|
|
65701
|
+
return messages;
|
|
65702
|
+
}
|
|
65703
|
+
checkVisionSupport() {
|
|
65704
|
+
try {
|
|
65705
|
+
const hash2 = createHash4("sha256").update(this.baseUrl).digest("hex").substring(0, 16);
|
|
65706
|
+
const cachePath = join21(homedir20(), ".claudish", `litellm-models-${hash2}.json`);
|
|
65707
|
+
if (!existsSync12(cachePath))
|
|
65708
|
+
return true;
|
|
65709
|
+
const cacheData = JSON.parse(readFileSync9(cachePath, "utf-8"));
|
|
65710
|
+
const model = cacheData.models?.find((m) => m.name === this.modelName);
|
|
65711
|
+
if (model && model.supportsVision === false) {
|
|
65712
|
+
log(`[LiteLLM] Model ${this.modelName} does not support vision, images will be stripped`);
|
|
65713
|
+
return false;
|
|
65714
|
+
}
|
|
65715
|
+
return true;
|
|
65716
|
+
} catch {
|
|
65717
|
+
return true;
|
|
65718
|
+
}
|
|
65719
|
+
}
|
|
65720
|
+
getProviderConfig() {
|
|
65721
|
+
return {
|
|
65722
|
+
name: "litellm",
|
|
65723
|
+
baseUrl: this.baseUrl,
|
|
65724
|
+
apiPath: "/v1/chat/completions",
|
|
65725
|
+
apiKeyEnvVar: "LITELLM_API_KEY"
|
|
65726
|
+
};
|
|
65727
|
+
}
|
|
65728
|
+
getPricing() {
|
|
65729
|
+
return {
|
|
65730
|
+
inputCostPer1M: 1,
|
|
65731
|
+
outputCostPer1M: 4,
|
|
65732
|
+
isEstimate: true
|
|
65733
|
+
};
|
|
65734
|
+
}
|
|
65735
|
+
getProviderName() {
|
|
65736
|
+
return "LiteLLM";
|
|
65737
|
+
}
|
|
65738
|
+
buildRequestPayload(claudeRequest, messages, tools) {
|
|
65739
|
+
const payload = {
|
|
65740
|
+
model: this.modelName,
|
|
65741
|
+
messages,
|
|
65742
|
+
temperature: claudeRequest.temperature ?? 1,
|
|
65743
|
+
stream: true,
|
|
65744
|
+
stream_options: { include_usage: true },
|
|
65745
|
+
max_tokens: claudeRequest.max_tokens
|
|
65746
|
+
};
|
|
65747
|
+
if (tools.length > 0) {
|
|
65748
|
+
payload.tools = tools;
|
|
65749
|
+
}
|
|
65750
|
+
if (claudeRequest.tool_choice) {
|
|
65751
|
+
const { type, name } = claudeRequest.tool_choice;
|
|
65752
|
+
if (type === "tool" && name) {
|
|
65753
|
+
payload.tool_choice = { type: "function", function: { name } };
|
|
65754
|
+
} else if (type === "auto" || type === "none") {
|
|
65755
|
+
payload.tool_choice = type;
|
|
65756
|
+
}
|
|
65757
|
+
}
|
|
65758
|
+
const extraHeaders = this.getExtraHeaders();
|
|
65759
|
+
if (extraHeaders) {
|
|
65760
|
+
payload.extra_headers = extraHeaders;
|
|
65761
|
+
}
|
|
65762
|
+
return payload;
|
|
65763
|
+
}
|
|
65764
|
+
getExtraHeaders() {
|
|
65765
|
+
const model = this.modelName.toLowerCase();
|
|
65766
|
+
const merged = {};
|
|
65767
|
+
let found = false;
|
|
65768
|
+
for (const { pattern, headers } of MODEL_EXTRA_HEADERS) {
|
|
65769
|
+
if (model.includes(pattern)) {
|
|
65770
|
+
Object.assign(merged, headers);
|
|
65771
|
+
found = true;
|
|
65772
|
+
}
|
|
65773
|
+
}
|
|
65774
|
+
return found ? merged : null;
|
|
65775
|
+
}
|
|
65776
|
+
};
|
|
65777
|
+
});
|
|
65778
|
+
|
|
65779
|
+
// src/services/pricing-cache.ts
|
|
65780
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync17, existsSync as existsSync13, mkdirSync as mkdirSync17, statSync } from "node:fs";
|
|
65781
|
+
import { homedir as homedir21 } from "node:os";
|
|
65782
|
+
import { join as join22 } from "node:path";
|
|
65424
65783
|
function getDynamicPricingSync(provider, modelName) {
|
|
65425
65784
|
if (provider === "openrouter") {
|
|
65426
65785
|
const direct = pricingMap.get(modelName);
|
|
@@ -65485,12 +65844,12 @@ async function warmPricingCache() {
|
|
|
65485
65844
|
}
|
|
65486
65845
|
function loadDiskCache() {
|
|
65487
65846
|
try {
|
|
65488
|
-
if (!
|
|
65847
|
+
if (!existsSync13(CACHE_FILE))
|
|
65489
65848
|
return false;
|
|
65490
65849
|
const stat = statSync(CACHE_FILE);
|
|
65491
65850
|
const age = Date.now() - stat.mtimeMs;
|
|
65492
65851
|
const isFresh = age < CACHE_TTL_MS;
|
|
65493
|
-
const raw2 =
|
|
65852
|
+
const raw2 = readFileSync10(CACHE_FILE, "utf-8");
|
|
65494
65853
|
const data = JSON.parse(raw2);
|
|
65495
65854
|
for (const [key, pricing] of Object.entries(data)) {
|
|
65496
65855
|
pricingMap.set(key, pricing);
|
|
@@ -65502,12 +65861,12 @@ function loadDiskCache() {
|
|
|
65502
65861
|
}
|
|
65503
65862
|
function saveDiskCache() {
|
|
65504
65863
|
try {
|
|
65505
|
-
|
|
65864
|
+
mkdirSync17(CACHE_DIR, { recursive: true });
|
|
65506
65865
|
const data = {};
|
|
65507
65866
|
for (const [key, pricing] of pricingMap) {
|
|
65508
65867
|
data[key] = pricing;
|
|
65509
65868
|
}
|
|
65510
|
-
|
|
65869
|
+
writeFileSync17(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
65511
65870
|
} catch (error46) {
|
|
65512
65871
|
log(`[PricingCache] Error saving disk cache: ${error46}`);
|
|
65513
65872
|
}
|
|
@@ -65537,8 +65896,8 @@ var init_pricing_cache = __esm(() => {
|
|
|
65537
65896
|
init_model_loader();
|
|
65538
65897
|
init_remote_provider_types();
|
|
65539
65898
|
pricingMap = new Map;
|
|
65540
|
-
CACHE_DIR =
|
|
65541
|
-
CACHE_FILE =
|
|
65899
|
+
CACHE_DIR = join22(homedir21(), ".claudish");
|
|
65900
|
+
CACHE_FILE = join22(CACHE_DIR, "pricing-cache.json");
|
|
65542
65901
|
CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
65543
65902
|
PROVIDER_TO_OR_PREFIX = {
|
|
65544
65903
|
openai: ["openai/"],
|
|
@@ -65660,6 +66019,15 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
65660
66019
|
} else if (resolved.provider.name === "ollamacloud") {
|
|
65661
66020
|
handler = new OllamaCloudHandler(resolved.provider, resolved.modelName, apiKey, port);
|
|
65662
66021
|
log(`[Proxy] Created OllamaCloud handler: ${resolved.modelName}`);
|
|
66022
|
+
} else if (resolved.provider.name === "litellm") {
|
|
66023
|
+
if (!resolved.provider.baseUrl) {
|
|
66024
|
+
console.error("Error: LITELLM_BASE_URL or --litellm-url is required for LiteLLM provider.");
|
|
66025
|
+
console.error("Set it with: export LITELLM_BASE_URL='https://your-litellm-instance.com'");
|
|
66026
|
+
console.error("Or use: claudish --litellm-url https://your-instance.com --model litellm@model 'task'");
|
|
66027
|
+
return null;
|
|
66028
|
+
}
|
|
66029
|
+
handler = new LiteLLMHandler(targetModel, resolved.modelName, apiKey, port, resolved.provider.baseUrl);
|
|
66030
|
+
log(`[Proxy] Created LiteLLM handler: ${resolved.modelName} (${resolved.provider.baseUrl})`);
|
|
65663
66031
|
} else if (resolved.provider.name === "vertex") {
|
|
65664
66032
|
const hasApiKey = !!process.env.VERTEX_API_KEY;
|
|
65665
66033
|
const vertexConfig = getVertexConfig();
|
|
@@ -65794,6 +66162,7 @@ var init_proxy_server = __esm(() => {
|
|
|
65794
66162
|
init_vertex_oauth_handler();
|
|
65795
66163
|
init_poe_handler();
|
|
65796
66164
|
init_ollamacloud_handler();
|
|
66165
|
+
init_litellm_handler();
|
|
65797
66166
|
init_provider_registry();
|
|
65798
66167
|
init_model_parser();
|
|
65799
66168
|
init_remote_provider_registry();
|
|
@@ -65811,9 +66180,9 @@ __export(exports_update_checker, {
|
|
|
65811
66180
|
checkForUpdates: () => checkForUpdates
|
|
65812
66181
|
});
|
|
65813
66182
|
import { execSync } from "node:child_process";
|
|
65814
|
-
import { existsSync as
|
|
65815
|
-
import { homedir as
|
|
65816
|
-
import { join as
|
|
66183
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync18, readFileSync as readFileSync11, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
|
|
66184
|
+
import { homedir as homedir22, platform as platform2, tmpdir as tmpdir2 } from "node:os";
|
|
66185
|
+
import { join as join23 } from "node:path";
|
|
65817
66186
|
import { createInterface as createInterface2 } from "node:readline";
|
|
65818
66187
|
function getUpdateCommand() {
|
|
65819
66188
|
const scriptPath = process.argv[1] || "";
|
|
@@ -65825,27 +66194,27 @@ function getUpdateCommand() {
|
|
|
65825
66194
|
function getCacheFilePath() {
|
|
65826
66195
|
let cacheDir;
|
|
65827
66196
|
if (isWindows2) {
|
|
65828
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
65829
|
-
cacheDir =
|
|
66197
|
+
const localAppData = process.env.LOCALAPPDATA || join23(homedir22(), "AppData", "Local");
|
|
66198
|
+
cacheDir = join23(localAppData, "claudish");
|
|
65830
66199
|
} else {
|
|
65831
|
-
cacheDir =
|
|
66200
|
+
cacheDir = join23(homedir22(), ".cache", "claudish");
|
|
65832
66201
|
}
|
|
65833
66202
|
try {
|
|
65834
|
-
if (!
|
|
65835
|
-
|
|
66203
|
+
if (!existsSync14(cacheDir)) {
|
|
66204
|
+
mkdirSync18(cacheDir, { recursive: true });
|
|
65836
66205
|
}
|
|
65837
|
-
return
|
|
66206
|
+
return join23(cacheDir, "update-check.json");
|
|
65838
66207
|
} catch {
|
|
65839
|
-
return
|
|
66208
|
+
return join23(tmpdir2(), "claudish-update-check.json");
|
|
65840
66209
|
}
|
|
65841
66210
|
}
|
|
65842
66211
|
function readCache() {
|
|
65843
66212
|
try {
|
|
65844
66213
|
const cachePath = getCacheFilePath();
|
|
65845
|
-
if (!
|
|
66214
|
+
if (!existsSync14(cachePath)) {
|
|
65846
66215
|
return null;
|
|
65847
66216
|
}
|
|
65848
|
-
const data = JSON.parse(
|
|
66217
|
+
const data = JSON.parse(readFileSync11(cachePath, "utf-8"));
|
|
65849
66218
|
return data;
|
|
65850
66219
|
} catch {
|
|
65851
66220
|
return null;
|
|
@@ -65858,7 +66227,7 @@ function writeCache(latestVersion) {
|
|
|
65858
66227
|
lastCheck: Date.now(),
|
|
65859
66228
|
latestVersion
|
|
65860
66229
|
};
|
|
65861
|
-
|
|
66230
|
+
writeFileSync18(cachePath, JSON.stringify(data), "utf-8");
|
|
65862
66231
|
} catch {}
|
|
65863
66232
|
}
|
|
65864
66233
|
function isCacheValid(cache) {
|
|
@@ -65868,8 +66237,8 @@ function isCacheValid(cache) {
|
|
|
65868
66237
|
function clearCache() {
|
|
65869
66238
|
try {
|
|
65870
66239
|
const cachePath = getCacheFilePath();
|
|
65871
|
-
if (
|
|
65872
|
-
|
|
66240
|
+
if (existsSync14(cachePath)) {
|
|
66241
|
+
unlinkSync5(cachePath);
|
|
65873
66242
|
}
|
|
65874
66243
|
} catch {}
|
|
65875
66244
|
}
|
package/package.json
CHANGED