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 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.7", CACHE_MAX_AGE_DAYS3 = 2, MODELS_JSON_PATH, CLAUDISH_CACHE_DIR3, ALL_MODELS_JSON_PATH2;
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 unlinkSync3, mkdirSync as mkdirSync7, existsSync as existsSync10 } from "node:fs";
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
- unlinkSync3(tempSettingsPath);
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
- unlinkSync3(tempSettingsPath);
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/services/pricing-cache.ts
65421
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync16, existsSync as existsSync12, mkdirSync as mkdirSync16, statSync } from "node:fs";
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 (!existsSync12(CACHE_FILE))
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 = readFileSync9(CACHE_FILE, "utf-8");
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
- mkdirSync16(CACHE_DIR, { recursive: true });
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
- writeFileSync16(CACHE_FILE, JSON.stringify(data), "utf-8");
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 = join20(homedir19(), ".claudish");
65541
- CACHE_FILE = join20(CACHE_DIR, "pricing-cache.json");
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 existsSync13, mkdirSync as mkdirSync17, readFileSync as readFileSync10, unlinkSync as unlinkSync4, writeFileSync as writeFileSync17 } from "node:fs";
65815
- import { homedir as homedir20, platform as platform2, tmpdir as tmpdir2 } from "node:os";
65816
- import { join as join21 } from "node:path";
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 || join21(homedir20(), "AppData", "Local");
65829
- cacheDir = join21(localAppData, "claudish");
66197
+ const localAppData = process.env.LOCALAPPDATA || join23(homedir22(), "AppData", "Local");
66198
+ cacheDir = join23(localAppData, "claudish");
65830
66199
  } else {
65831
- cacheDir = join21(homedir20(), ".cache", "claudish");
66200
+ cacheDir = join23(homedir22(), ".cache", "claudish");
65832
66201
  }
65833
66202
  try {
65834
- if (!existsSync13(cacheDir)) {
65835
- mkdirSync17(cacheDir, { recursive: true });
66203
+ if (!existsSync14(cacheDir)) {
66204
+ mkdirSync18(cacheDir, { recursive: true });
65836
66205
  }
65837
- return join21(cacheDir, "update-check.json");
66206
+ return join23(cacheDir, "update-check.json");
65838
66207
  } catch {
65839
- return join21(tmpdir2(), "claudish-update-check.json");
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 (!existsSync13(cachePath)) {
66214
+ if (!existsSync14(cachePath)) {
65846
66215
  return null;
65847
66216
  }
65848
- const data = JSON.parse(readFileSync10(cachePath, "utf-8"));
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
- writeFileSync17(cachePath, JSON.stringify(data), "utf-8");
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 (existsSync13(cachePath)) {
65872
- unlinkSync4(cachePath);
66240
+ if (existsSync14(cachePath)) {
66241
+ unlinkSync5(cachePath);
65873
66242
  }
65874
66243
  } catch {}
65875
66244
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "4.6.7",
3
+ "version": "4.6.9",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "1.2.0",
3
- "lastUpdated": "2026-02-14",
3
+ "lastUpdated": "2026-02-15",
4
4
  "source": "https://openrouter.ai/models?categories=programming&fmt=cards&order=top-weekly",
5
5
  "models": [
6
6
  {