claudish 3.9.0 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +599 -105
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -53247,6 +53247,157 @@ var init_undici = __esm(() => {
53247
53247
  ({ EventSource } = require_eventsource());
53248
53248
  });
53249
53249
 
53250
+ // ../core/dist/handlers/shared/local-queue.js
53251
+ class LocalModelQueue {
53252
+ static instance = null;
53253
+ queue = [];
53254
+ activeRequests = 0;
53255
+ maxParallel;
53256
+ maxQueueSize = 100;
53257
+ requestDelay = 100;
53258
+ totalProcessed = 0;
53259
+ totalErrors = 0;
53260
+ totalOOMErrors = 0;
53261
+ constructor() {
53262
+ this.maxParallel = this.getMaxParallelFromEnv();
53263
+ if (getLogLevel() === "debug") {
53264
+ log(`[LocalQueue] Queue initialized with maxParallel=${this.maxParallel}, maxQueueSize=${this.maxQueueSize}`);
53265
+ }
53266
+ }
53267
+ static getInstance() {
53268
+ if (!LocalModelQueue.instance) {
53269
+ LocalModelQueue.instance = new LocalModelQueue;
53270
+ }
53271
+ return LocalModelQueue.instance;
53272
+ }
53273
+ static isEnabled() {
53274
+ const enabled = process.env.CLAUDISH_LOCAL_QUEUE_ENABLED;
53275
+ if (enabled === undefined || enabled === "")
53276
+ return true;
53277
+ return enabled !== "false" && enabled !== "0";
53278
+ }
53279
+ async enqueue(fetchFn, providerId) {
53280
+ if (this.queue.length >= this.maxQueueSize) {
53281
+ if (getLogLevel() === "debug") {
53282
+ log(`[LocalQueue] Queue full (${this.queue.length}/${this.maxQueueSize}), rejecting request`);
53283
+ }
53284
+ throw new Error(`Local model queue full (${this.queue.length}/${this.maxQueueSize}). GPU is overloaded. Please wait for current requests to complete.`);
53285
+ }
53286
+ return new Promise((resolve, reject) => {
53287
+ const queuedRequest = {
53288
+ fetchFn,
53289
+ resolve,
53290
+ reject,
53291
+ providerId
53292
+ };
53293
+ this.queue.push(queuedRequest);
53294
+ if (getLogLevel() === "debug") {
53295
+ log(`[LocalQueue] Request enqueued for ${providerId} (queue length: ${this.queue.length}, active: ${this.activeRequests}/${this.maxParallel})`);
53296
+ }
53297
+ this.processQueue();
53298
+ });
53299
+ }
53300
+ async processQueue() {
53301
+ while (this.queue.length > 0 && this.activeRequests < this.maxParallel) {
53302
+ const request = this.queue.shift();
53303
+ if (!request)
53304
+ break;
53305
+ if (getLogLevel() === "debug") {
53306
+ log(`[LocalQueue] Processing request for ${request.providerId} (${this.queue.length} remaining in queue, ${this.activeRequests + 1}/${this.maxParallel} active)`);
53307
+ }
53308
+ this.executeRequest(request).catch((err) => {
53309
+ if (getLogLevel() === "debug") {
53310
+ log(`[LocalQueue] Request execution failed: ${err}`);
53311
+ }
53312
+ });
53313
+ await this.delay(this.requestDelay);
53314
+ }
53315
+ }
53316
+ async executeRequest(request) {
53317
+ this.activeRequests++;
53318
+ try {
53319
+ const response = await request.fetchFn();
53320
+ if (response.status === 500) {
53321
+ const errorBody = await response.clone().text();
53322
+ if (this.isOOMError(errorBody)) {
53323
+ this.totalOOMErrors++;
53324
+ if (getLogLevel() === "debug") {
53325
+ log(`[LocalQueue] GPU out-of-memory detected for ${request.providerId}. Consider reducing CLAUDISH_LOCAL_MAX_PARALLEL (current: ${this.maxParallel})`);
53326
+ }
53327
+ await this.delay(2000);
53328
+ const retryResponse = await request.fetchFn();
53329
+ if (retryResponse.status === 500) {
53330
+ const retryErrorBody = await retryResponse.clone().text();
53331
+ if (this.isOOMError(retryErrorBody)) {
53332
+ throw new Error(`GPU out-of-memory error persisted after retry. Try setting CLAUDISH_LOCAL_MAX_PARALLEL=1 for sequential processing.`);
53333
+ }
53334
+ }
53335
+ this.totalProcessed++;
53336
+ request.resolve(retryResponse);
53337
+ return;
53338
+ }
53339
+ }
53340
+ this.totalProcessed++;
53341
+ request.resolve(response);
53342
+ } catch (error46) {
53343
+ this.totalErrors++;
53344
+ if (getLogLevel() === "debug") {
53345
+ log(`[LocalQueue] Request failed for ${request.providerId}: ${error46}`);
53346
+ }
53347
+ request.reject(error46 instanceof Error ? error46 : new Error(String(error46)));
53348
+ } finally {
53349
+ this.activeRequests--;
53350
+ if (this.queue.length > 0) {
53351
+ this.processQueue();
53352
+ }
53353
+ }
53354
+ }
53355
+ isOOMError(errorBody) {
53356
+ const oomPatterns = [
53357
+ "failed to allocate memory",
53358
+ "CUDA out of memory",
53359
+ "OOM",
53360
+ "out of memory",
53361
+ "memory allocation failed",
53362
+ "insufficient memory",
53363
+ "GPU memory"
53364
+ ];
53365
+ const bodyLower = errorBody.toLowerCase();
53366
+ return oomPatterns.some((pattern) => bodyLower.includes(pattern.toLowerCase()));
53367
+ }
53368
+ getMaxParallelFromEnv() {
53369
+ const envValue = process.env.CLAUDISH_LOCAL_MAX_PARALLEL;
53370
+ if (!envValue)
53371
+ return 1;
53372
+ const parsed = Number.parseInt(envValue, 10);
53373
+ if (Number.isNaN(parsed) || parsed < 1) {
53374
+ log(`[LocalQueue] Invalid CLAUDISH_LOCAL_MAX_PARALLEL: ${envValue}, using default: 1`);
53375
+ return 1;
53376
+ }
53377
+ if (parsed > 8) {
53378
+ log(`[LocalQueue] CLAUDISH_LOCAL_MAX_PARALLEL too high: ${parsed}, capping at 8`);
53379
+ return 8;
53380
+ }
53381
+ return parsed;
53382
+ }
53383
+ delay(ms) {
53384
+ return new Promise((resolve) => setTimeout(resolve, ms));
53385
+ }
53386
+ getStats() {
53387
+ return {
53388
+ queueLength: this.queue.length,
53389
+ activeRequests: this.activeRequests,
53390
+ maxParallel: this.maxParallel,
53391
+ totalProcessed: this.totalProcessed,
53392
+ totalErrors: this.totalErrors,
53393
+ totalOOMErrors: this.totalOOMErrors
53394
+ };
53395
+ }
53396
+ }
53397
+ var init_local_queue = __esm(() => {
53398
+ init_logger();
53399
+ });
53400
+
53250
53401
  // ../core/dist/handlers/local-provider-handler.js
53251
53402
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "node:fs";
53252
53403
  import { homedir as homedir2 } from "node:os";
@@ -53620,7 +53771,7 @@ If you cannot use structured tool_calls, format as JSON:
53620
53771
  log(`[LocalProvider:${this.provider.name}] Request timeout (10 min) - aborting`);
53621
53772
  controller.abort();
53622
53773
  }, 600000);
53623
- const response = await fetch(apiUrl, {
53774
+ const doFetch = () => fetch(apiUrl, {
53624
53775
  method: "POST",
53625
53776
  headers: {
53626
53777
  "Content-Type": "application/json"
@@ -53629,6 +53780,7 @@ If you cannot use structured tool_calls, format as JSON:
53629
53780
  signal: controller.signal,
53630
53781
  dispatcher: localProviderAgent
53631
53782
  });
53783
+ const response = LocalModelQueue.isEnabled() ? await LocalModelQueue.getInstance().enqueue(doFetch, this.provider.name) : await doFetch();
53632
53784
  clearTimeout(timeoutId);
53633
53785
  log(`[LocalProvider:${this.provider.name}] Response status: ${response.status}`);
53634
53786
  if (!response.ok) {
@@ -53706,6 +53858,7 @@ var init_local_provider_handler = __esm(() => {
53706
53858
  init_logger();
53707
53859
  init_undici();
53708
53860
  init_openai_compat();
53861
+ init_local_queue();
53709
53862
  localProviderAgent = new $Agent({
53710
53863
  headersTimeout: 600000,
53711
53864
  bodyTimeout: 600000,
@@ -58545,7 +58698,21 @@ var getRemoteProviders = () => [
58545
58698
  supportsVision: true,
58546
58699
  supportsStreaming: true,
58547
58700
  supportsJsonMode: true,
58548
- supportsReasoning: false
58701
+ supportsReasoning: true
58702
+ }
58703
+ },
58704
+ {
58705
+ name: "zai",
58706
+ baseUrl: process.env.ZAI_BASE_URL || "https://api.z.ai",
58707
+ apiPath: "/api/anthropic/v1/messages",
58708
+ apiKeyEnvVar: "ZAI_API_KEY",
58709
+ prefixes: ["zai/"],
58710
+ capabilities: {
58711
+ supportsTools: true,
58712
+ supportsVision: true,
58713
+ supportsStreaming: true,
58714
+ supportsJsonMode: true,
58715
+ supportsReasoning: true
58549
58716
  }
58550
58717
  },
58551
58718
  {
@@ -58578,6 +58745,323 @@ var getRemoteProviders = () => [
58578
58745
  }
58579
58746
  ];
58580
58747
 
58748
+ // ../core/dist/providers/provider-resolver.js
58749
+ function isApiKeyAvailable(info) {
58750
+ if (!info.envVar) {
58751
+ return true;
58752
+ }
58753
+ if (process.env[info.envVar]) {
58754
+ return true;
58755
+ }
58756
+ if (info.aliases) {
58757
+ for (const alias of info.aliases) {
58758
+ if (process.env[alias]) {
58759
+ return true;
58760
+ }
58761
+ }
58762
+ }
58763
+ return false;
58764
+ }
58765
+ function resolveModelProvider(modelId) {
58766
+ if (!modelId) {
58767
+ const info2 = API_KEY_INFO.openrouter;
58768
+ return {
58769
+ category: "openrouter",
58770
+ providerName: "OpenRouter",
58771
+ modelName: "",
58772
+ fullModelId: "",
58773
+ requiredApiKeyEnvVar: info2.envVar,
58774
+ apiKeyAvailable: isApiKeyAvailable(info2),
58775
+ apiKeyDescription: info2.description,
58776
+ apiKeyUrl: info2.url
58777
+ };
58778
+ }
58779
+ const lowerModelId = modelId.toLowerCase();
58780
+ if (LOCAL_PREFIXES.some((prefix) => lowerModelId.startsWith(prefix))) {
58781
+ const resolved = resolveProvider(modelId);
58782
+ const urlParsed = parseUrlModel(modelId);
58783
+ let providerName = "Local";
58784
+ let modelName = modelId;
58785
+ if (resolved) {
58786
+ providerName = resolved.provider.name.charAt(0).toUpperCase() + resolved.provider.name.slice(1);
58787
+ modelName = resolved.modelName;
58788
+ } else if (urlParsed) {
58789
+ providerName = "Custom URL";
58790
+ modelName = urlParsed.modelName;
58791
+ }
58792
+ return {
58793
+ category: "local",
58794
+ providerName,
58795
+ modelName,
58796
+ fullModelId: modelId,
58797
+ requiredApiKeyEnvVar: null,
58798
+ apiKeyAvailable: true,
58799
+ apiKeyDescription: null,
58800
+ apiKeyUrl: null
58801
+ };
58802
+ }
58803
+ const remoteResolved = resolveRemoteProvider(modelId);
58804
+ if (remoteResolved) {
58805
+ const provider = remoteResolved.provider;
58806
+ if (provider.name === "openrouter") {
58807
+ const info3 = API_KEY_INFO.openrouter;
58808
+ return {
58809
+ category: "openrouter",
58810
+ providerName: "OpenRouter",
58811
+ modelName: remoteResolved.modelName,
58812
+ fullModelId: modelId,
58813
+ requiredApiKeyEnvVar: info3.envVar,
58814
+ apiKeyAvailable: isApiKeyAvailable(info3),
58815
+ apiKeyDescription: info3.description,
58816
+ apiKeyUrl: info3.url
58817
+ };
58818
+ }
58819
+ const info2 = API_KEY_INFO[provider.name] || {
58820
+ envVar: provider.apiKeyEnvVar,
58821
+ description: `${provider.name} API Key`,
58822
+ url: ""
58823
+ };
58824
+ if (isApiKeyAvailable(info2)) {
58825
+ const providerDisplayName2 = PROVIDER_DISPLAY_NAMES[provider.name] || provider.name.charAt(0).toUpperCase() + provider.name.slice(1);
58826
+ return {
58827
+ category: "direct-api",
58828
+ providerName: providerDisplayName2,
58829
+ modelName: remoteResolved.modelName,
58830
+ fullModelId: modelId,
58831
+ requiredApiKeyEnvVar: info2.envVar || null,
58832
+ apiKeyAvailable: isApiKeyAvailable(info2),
58833
+ apiKeyDescription: info2.envVar ? info2.description : null,
58834
+ apiKeyUrl: info2.envVar ? info2.url : null
58835
+ };
58836
+ }
58837
+ if (isApiKeyAvailable(API_KEY_INFO.openrouter)) {
58838
+ const orInfo = API_KEY_INFO.openrouter;
58839
+ return {
58840
+ category: "openrouter",
58841
+ providerName: "OpenRouter (fallback)",
58842
+ modelName: modelId,
58843
+ fullModelId: modelId,
58844
+ requiredApiKeyEnvVar: orInfo.envVar,
58845
+ apiKeyAvailable: true,
58846
+ apiKeyDescription: orInfo.description,
58847
+ apiKeyUrl: orInfo.url
58848
+ };
58849
+ }
58850
+ if (isApiKeyAvailable(API_KEY_INFO.vertex)) {
58851
+ const vertexInfo = API_KEY_INFO.vertex;
58852
+ return {
58853
+ category: "direct-api",
58854
+ providerName: "Vertex AI (fallback)",
58855
+ modelName: modelId,
58856
+ fullModelId: modelId,
58857
+ requiredApiKeyEnvVar: vertexInfo.envVar,
58858
+ apiKeyAvailable: true,
58859
+ apiKeyDescription: vertexInfo.description,
58860
+ apiKeyUrl: vertexInfo.url
58861
+ };
58862
+ }
58863
+ const providerDisplayName = PROVIDER_DISPLAY_NAMES[provider.name] || provider.name.charAt(0).toUpperCase() + provider.name.slice(1);
58864
+ return {
58865
+ category: "direct-api",
58866
+ providerName: providerDisplayName,
58867
+ modelName: remoteResolved.modelName,
58868
+ fullModelId: modelId,
58869
+ requiredApiKeyEnvVar: info2.envVar || null,
58870
+ apiKeyAvailable: false,
58871
+ apiKeyDescription: info2.envVar ? info2.description : null,
58872
+ apiKeyUrl: info2.envVar ? info2.url : null
58873
+ };
58874
+ }
58875
+ if (!modelId.includes("/")) {
58876
+ return {
58877
+ category: "native-anthropic",
58878
+ providerName: "Anthropic (Native)",
58879
+ modelName: modelId,
58880
+ fullModelId: modelId,
58881
+ requiredApiKeyEnvVar: null,
58882
+ apiKeyAvailable: true,
58883
+ apiKeyDescription: null,
58884
+ apiKeyUrl: null
58885
+ };
58886
+ }
58887
+ const info = API_KEY_INFO.openrouter;
58888
+ return {
58889
+ category: "openrouter",
58890
+ providerName: "OpenRouter",
58891
+ modelName: modelId,
58892
+ fullModelId: modelId,
58893
+ requiredApiKeyEnvVar: info.envVar,
58894
+ apiKeyAvailable: isApiKeyAvailable(info),
58895
+ apiKeyDescription: info.description,
58896
+ apiKeyUrl: info.url
58897
+ };
58898
+ }
58899
+ function validateApiKeysForModels(models) {
58900
+ return models.filter((m) => m !== undefined).map((m) => resolveModelProvider(m));
58901
+ }
58902
+ function getMissingKeyResolutions(resolutions) {
58903
+ return resolutions.filter((r) => r.requiredApiKeyEnvVar && !r.apiKeyAvailable);
58904
+ }
58905
+ function getMissingKeyError(resolution) {
58906
+ if (!resolution.requiredApiKeyEnvVar || resolution.apiKeyAvailable) {
58907
+ return "";
58908
+ }
58909
+ const lines = [];
58910
+ lines.push(`Error: ${resolution.apiKeyDescription} is required for model "${resolution.fullModelId}"`);
58911
+ lines.push("");
58912
+ lines.push("Set it with:");
58913
+ lines.push(` export ${resolution.requiredApiKeyEnvVar}='your-key-here'`);
58914
+ if (resolution.apiKeyUrl) {
58915
+ lines.push("");
58916
+ lines.push(`Get your API key from: ${resolution.apiKeyUrl}`);
58917
+ }
58918
+ if (resolution.category === "openrouter") {
58919
+ const provider = resolution.fullModelId.split("/")[0];
58920
+ lines.push("");
58921
+ lines.push(`Tip: "${resolution.fullModelId}" is an OpenRouter model.`);
58922
+ lines.push(` OpenRouter routes to ${provider}'s API through their unified interface.`);
58923
+ if (provider === "google") {
58924
+ lines.push("");
58925
+ lines.push(" For direct Gemini API (no OpenRouter), use prefix 'g/' or 'gemini/':");
58926
+ lines.push(' claudish --model g/gemini-2.0-flash "task"');
58927
+ } else if (provider === "openai") {
58928
+ lines.push("");
58929
+ lines.push(" For direct OpenAI API (no OpenRouter), use prefix 'oai/':");
58930
+ lines.push(' claudish --model oai/gpt-4o "task"');
58931
+ }
58932
+ }
58933
+ return lines.join(`
58934
+ `);
58935
+ }
58936
+ function getMissingKeysError(resolutions) {
58937
+ const missing = getMissingKeyResolutions(resolutions);
58938
+ if (missing.length === 0) {
58939
+ return "";
58940
+ }
58941
+ if (missing.length === 1) {
58942
+ return getMissingKeyError(missing[0]);
58943
+ }
58944
+ const lines = [];
58945
+ lines.push("Error: Multiple API keys are required for the configured models:");
58946
+ lines.push("");
58947
+ const byEnvVar = new Map;
58948
+ for (const r of missing) {
58949
+ if (r.requiredApiKeyEnvVar && !byEnvVar.has(r.requiredApiKeyEnvVar)) {
58950
+ byEnvVar.set(r.requiredApiKeyEnvVar, r);
58951
+ }
58952
+ }
58953
+ for (const [envVar, resolution] of byEnvVar) {
58954
+ lines.push(` ${resolution.apiKeyDescription}:`);
58955
+ lines.push(` export ${envVar}='your-key-here'`);
58956
+ if (resolution.apiKeyUrl) {
58957
+ lines.push(` Get from: ${resolution.apiKeyUrl}`);
58958
+ }
58959
+ lines.push("");
58960
+ }
58961
+ return lines.join(`
58962
+ `);
58963
+ }
58964
+ function requiresOpenRouterKey(modelId) {
58965
+ const resolution = resolveModelProvider(modelId);
58966
+ return resolution.category === "openrouter";
58967
+ }
58968
+ function isLocalModel(modelId) {
58969
+ if (!modelId)
58970
+ return false;
58971
+ const resolution = resolveModelProvider(modelId);
58972
+ return resolution.category === "local";
58973
+ }
58974
+ var API_KEY_INFO, LOCAL_PREFIXES, PROVIDER_DISPLAY_NAMES;
58975
+ var init_provider_resolver = __esm(() => {
58976
+ API_KEY_INFO = {
58977
+ openrouter: {
58978
+ envVar: "OPENROUTER_API_KEY",
58979
+ description: "OpenRouter API Key",
58980
+ url: "https://openrouter.ai/keys"
58981
+ },
58982
+ gemini: {
58983
+ envVar: "GEMINI_API_KEY",
58984
+ description: "Google Gemini API Key",
58985
+ url: "https://aistudio.google.com/app/apikey"
58986
+ },
58987
+ "gemini-codeassist": {
58988
+ envVar: "",
58989
+ description: "Gemini Code Assist (OAuth)",
58990
+ url: "https://cloud.google.com/code-assist"
58991
+ },
58992
+ vertex: {
58993
+ envVar: "VERTEX_API_KEY",
58994
+ description: "Vertex AI API Key",
58995
+ url: "https://console.cloud.google.com/vertex-ai",
58996
+ aliases: ["VERTEX_PROJECT"]
58997
+ },
58998
+ openai: {
58999
+ envVar: "OPENAI_API_KEY",
59000
+ description: "OpenAI API Key",
59001
+ url: "https://platform.openai.com/api-keys"
59002
+ },
59003
+ minimax: {
59004
+ envVar: "MINIMAX_API_KEY",
59005
+ description: "MiniMax API Key",
59006
+ url: "https://www.minimaxi.com/"
59007
+ },
59008
+ kimi: {
59009
+ envVar: "MOONSHOT_API_KEY",
59010
+ description: "Kimi/Moonshot API Key",
59011
+ url: "https://platform.moonshot.cn/",
59012
+ aliases: ["KIMI_API_KEY"]
59013
+ },
59014
+ glm: {
59015
+ envVar: "ZHIPU_API_KEY",
59016
+ description: "GLM/Zhipu API Key",
59017
+ url: "https://open.bigmodel.cn/",
59018
+ aliases: ["GLM_API_KEY"]
59019
+ },
59020
+ ollamacloud: {
59021
+ envVar: "OLLAMA_API_KEY",
59022
+ description: "OllamaCloud API Key",
59023
+ url: "https://ollama.com/account"
59024
+ },
59025
+ "opencode-zen": {
59026
+ envVar: "",
59027
+ description: "OpenCode Zen (Free)",
59028
+ url: "https://opencode.ai/"
59029
+ },
59030
+ zai: {
59031
+ envVar: "ZAI_API_KEY",
59032
+ description: "Z.AI API Key",
59033
+ url: "https://z.ai/"
59034
+ }
59035
+ };
59036
+ LOCAL_PREFIXES = [
59037
+ "ollama/",
59038
+ "ollama:",
59039
+ "lmstudio/",
59040
+ "lmstudio:",
59041
+ "mlstudio/",
59042
+ "mlstudio:",
59043
+ "vllm/",
59044
+ "vllm:",
59045
+ "mlx/",
59046
+ "mlx:",
59047
+ "http://",
59048
+ "https://localhost"
59049
+ ];
59050
+ PROVIDER_DISPLAY_NAMES = {
59051
+ gemini: "Gemini",
59052
+ "gemini-codeassist": "Gemini Code Assist",
59053
+ vertex: "Vertex AI",
59054
+ openai: "OpenAI",
59055
+ openrouter: "OpenRouter",
59056
+ minimax: "MiniMax",
59057
+ kimi: "Kimi",
59058
+ glm: "GLM",
59059
+ zai: "Z.AI",
59060
+ ollamacloud: "OllamaCloud",
59061
+ "opencode-zen": "OpenCode Zen"
59062
+ };
59063
+ });
59064
+
58581
59065
  // ../core/dist/proxy-server.js
58582
59066
  async function createProxyServer(port, openrouterApiKey, model, monitorMode = false, anthropicApiKey, modelMap, options = {}) {
58583
59067
  const nativeHandler = new NativeHandler(anthropicApiKey);
@@ -58633,68 +59117,70 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
58633
59117
  if (remoteProviderHandlers.has(targetModel)) {
58634
59118
  return remoteProviderHandlers.get(targetModel);
58635
59119
  }
58636
- const resolved = resolveRemoteProvider(targetModel);
58637
- if (!resolved) {
59120
+ const resolution = resolveModelProvider(targetModel);
59121
+ if (resolution.category === "openrouter") {
58638
59122
  return null;
58639
59123
  }
58640
- if (resolved.provider.name === "openrouter") {
58641
- return null;
58642
- }
58643
- const apiKeyError = validateRemoteProviderApiKey(resolved.provider);
58644
- if (apiKeyError) {
58645
- throw new Error(apiKeyError);
58646
- }
58647
- const apiKey = resolved.provider.apiKeyEnvVar ? process.env[resolved.provider.apiKeyEnvVar] || "" : "";
58648
- let handler;
58649
- if (resolved.provider.name === "gemini") {
58650
- handler = new GeminiHandler(resolved.provider, resolved.modelName, apiKey, port);
58651
- log(`[Proxy] Created Gemini handler: ${resolved.modelName}`);
58652
- } else if (resolved.provider.name === "gemini-codeassist") {
58653
- handler = new GeminiCodeAssistHandler(resolved.modelName, port);
58654
- log(`[Proxy] Created Gemini Code Assist handler: ${resolved.modelName}`);
58655
- } else if (resolved.provider.name === "openai") {
58656
- handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
58657
- log(`[Proxy] Created OpenAI handler: ${resolved.modelName}`);
58658
- } else if (resolved.provider.name === "minimax" || resolved.provider.name === "kimi") {
58659
- handler = new AnthropicCompatHandler(resolved.provider, resolved.modelName, apiKey, port);
58660
- log(`[Proxy] Created ${resolved.provider.name} handler: ${resolved.modelName}`);
58661
- } else if (resolved.provider.name === "glm") {
58662
- handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
58663
- log(`[Proxy] Created ${resolved.provider.name} handler: ${resolved.modelName}`);
58664
- } else if (resolved.provider.name === "opencode-zen") {
58665
- if (resolved.modelName.toLowerCase().includes("minimax")) {
59124
+ if (resolution.category === "direct-api" && resolution.apiKeyAvailable) {
59125
+ const resolved = resolveRemoteProvider(targetModel);
59126
+ if (!resolved)
59127
+ return null;
59128
+ if (resolved.provider.name === "openrouter") {
59129
+ return null;
59130
+ }
59131
+ const apiKey = resolved.provider.apiKeyEnvVar ? process.env[resolved.provider.apiKeyEnvVar] || "" : "";
59132
+ let handler;
59133
+ if (resolved.provider.name === "gemini") {
59134
+ handler = new GeminiHandler(resolved.provider, resolved.modelName, apiKey, port);
59135
+ log(`[Proxy] Created Gemini handler: ${resolved.modelName}`);
59136
+ } else if (resolved.provider.name === "gemini-codeassist") {
59137
+ handler = new GeminiCodeAssistHandler(resolved.modelName, port);
59138
+ log(`[Proxy] Created Gemini Code Assist handler: ${resolved.modelName}`);
59139
+ } else if (resolved.provider.name === "openai") {
59140
+ handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
59141
+ log(`[Proxy] Created OpenAI handler: ${resolved.modelName}`);
59142
+ } else if (resolved.provider.name === "minimax" || resolved.provider.name === "kimi" || resolved.provider.name === "zai") {
58666
59143
  handler = new AnthropicCompatHandler(resolved.provider, resolved.modelName, apiKey, port);
58667
- log(`[Proxy] Created OpenCode Zen (Anthropic) handler: ${resolved.modelName}`);
58668
- } else {
59144
+ log(`[Proxy] Created ${resolved.provider.name} handler: ${resolved.modelName}`);
59145
+ } else if (resolved.provider.name === "glm") {
58669
59146
  handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
58670
- log(`[Proxy] Created OpenCode Zen (OpenAI) handler: ${resolved.modelName}`);
58671
- }
58672
- } else if (resolved.provider.name === "ollamacloud") {
58673
- handler = new OllamaCloudHandler(resolved.provider, resolved.modelName, apiKey, port);
58674
- log(`[Proxy] Created OllamaCloud handler: ${resolved.modelName}`);
58675
- } else if (resolved.provider.name === "vertex") {
58676
- const hasApiKey = !!process.env.VERTEX_API_KEY;
58677
- const vertexConfig = getVertexConfig();
58678
- if (hasApiKey) {
58679
- handler = new GeminiHandler(resolved.provider, resolved.modelName, apiKey, port);
58680
- log(`[Proxy] Created Vertex AI Express handler: ${resolved.modelName}`);
58681
- } else if (vertexConfig) {
58682
- const oauthError = validateVertexOAuthConfig();
58683
- if (oauthError) {
58684
- log(`[Proxy] Vertex OAuth config error: ${oauthError}`);
59147
+ log(`[Proxy] Created ${resolved.provider.name} handler: ${resolved.modelName}`);
59148
+ } else if (resolved.provider.name === "opencode-zen") {
59149
+ if (resolved.modelName.toLowerCase().includes("minimax")) {
59150
+ handler = new AnthropicCompatHandler(resolved.provider, resolved.modelName, apiKey, port);
59151
+ log(`[Proxy] Created OpenCode Zen (Anthropic) handler: ${resolved.modelName}`);
59152
+ } else {
59153
+ handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
59154
+ log(`[Proxy] Created OpenCode Zen (OpenAI) handler: ${resolved.modelName}`);
59155
+ }
59156
+ } else if (resolved.provider.name === "ollamacloud") {
59157
+ handler = new OllamaCloudHandler(resolved.provider, resolved.modelName, apiKey, port);
59158
+ log(`[Proxy] Created OllamaCloud handler: ${resolved.modelName}`);
59159
+ } else if (resolved.provider.name === "vertex") {
59160
+ const hasApiKey = !!process.env.VERTEX_API_KEY;
59161
+ const vertexConfig = getVertexConfig();
59162
+ if (hasApiKey) {
59163
+ handler = new GeminiHandler(resolved.provider, resolved.modelName, apiKey, port);
59164
+ log(`[Proxy] Created Vertex AI Express handler: ${resolved.modelName}`);
59165
+ } else if (vertexConfig) {
59166
+ const oauthError = validateVertexOAuthConfig();
59167
+ if (oauthError) {
59168
+ log(`[Proxy] Vertex OAuth config error: ${oauthError}`);
59169
+ return null;
59170
+ }
59171
+ handler = new VertexOAuthHandler(resolved.modelName, port);
59172
+ log(`[Proxy] Created Vertex AI OAuth handler: ${resolved.modelName} (project: ${vertexConfig.projectId})`);
59173
+ } else {
59174
+ log(`[Proxy] Vertex AI requires either VERTEX_API_KEY or VERTEX_PROJECT`);
58685
59175
  return null;
58686
59176
  }
58687
- handler = new VertexOAuthHandler(resolved.modelName, port);
58688
- log(`[Proxy] Created Vertex AI OAuth handler: ${resolved.modelName} (project: ${vertexConfig.projectId})`);
58689
59177
  } else {
58690
- log(`[Proxy] Vertex AI requires either VERTEX_API_KEY or VERTEX_PROJECT`);
58691
59178
  return null;
58692
59179
  }
58693
- } else {
58694
- return null;
59180
+ remoteProviderHandlers.set(targetModel, handler);
59181
+ return handler;
58695
59182
  }
58696
- remoteProviderHandlers.set(targetModel, handler);
58697
- return handler;
59183
+ return null;
58698
59184
  };
58699
59185
  const getHandlerForRequest = (requestedModel) => {
58700
59186
  if (monitorMode)
@@ -58799,18 +59285,22 @@ var init_proxy_server = __esm(() => {
58799
59285
  init_poe_handler();
58800
59286
  init_ollamacloud_handler();
58801
59287
  init_vertex_auth();
59288
+ init_provider_resolver();
58802
59289
  });
58803
59290
 
58804
59291
  // ../core/dist/index.js
58805
59292
  var exports_dist = {};
58806
59293
  __export(exports_dist, {
58807
59294
  validateRemoteProviderApiKey: () => validateRemoteProviderApiKey,
59295
+ validateApiKeysForModels: () => validateApiKeysForModels,
58808
59296
  transformOpenAIToClaude: () => transformOpenAIToClaude,
58809
59297
  transformMessages: () => transformMessages,
58810
59298
  setupGeminiUser: () => setupGeminiUser,
58811
59299
  sanitizeRoot: () => sanitizeRoot,
58812
59300
  resolveRemoteProvider: () => resolveRemoteProvider,
58813
59301
  resolveProvider: () => resolveProvider,
59302
+ resolveModelProvider: () => resolveModelProvider,
59303
+ requiresOpenRouterKey: () => requiresOpenRouterKey,
58814
59304
  removeUriFormat: () => removeUriFormat,
58815
59305
  parseUrlModel: () => parseUrlModel,
58816
59306
  mapTools: () => mapTools,
@@ -58819,10 +59309,14 @@ __export(exports_dist, {
58819
59309
  log: () => log,
58820
59310
  isLoggingEnabled: () => isLoggingEnabled,
58821
59311
  isLocalProvider: () => isLocalProvider,
59312
+ isLocalModel: () => isLocalModel,
58822
59313
  initLogger: () => initLogger,
58823
59314
  getValidAccessToken: () => getValidAccessToken,
58824
59315
  getRegisteredRemoteProviders: () => getRegisteredRemoteProviders,
58825
59316
  getRegisteredProviders: () => getRegisteredProviders,
59317
+ getMissingKeysError: () => getMissingKeysError,
59318
+ getMissingKeyResolutions: () => getMissingKeyResolutions,
59319
+ getMissingKeyError: () => getMissingKeyError,
58826
59320
  getLogFilePath: () => getLogFilePath,
58827
59321
  createUrlProvider: () => createUrlProvider,
58828
59322
  createProxyServer: () => createProxyServer,
@@ -58851,6 +59345,7 @@ var init_dist3 = __esm(() => {
58851
59345
  init_poe_handler();
58852
59346
  init_gemini_codeassist_handler();
58853
59347
  init_gemini_oauth();
59348
+ init_provider_resolver();
58854
59349
  init_transform();
58855
59350
  init_manager();
58856
59351
  init_gemini_thought_signature();
@@ -61983,9 +62478,15 @@ function fuzzyScore2(text, query) {
61983
62478
  // src/cli.ts
61984
62479
  var exports_cli = {};
61985
62480
  __export(exports_cli, {
62481
+ validateApiKeysForModels: () => validateApiKeysForModels,
62482
+ resolveModelProvider: () => resolveModelProvider,
62483
+ requiresOpenRouterKey: () => requiresOpenRouterKey,
61986
62484
  parseArgs: () => parseArgs,
61987
62485
  isLocalModel: () => isLocalModel,
61988
- getVersion: () => getVersion
62486
+ getVersion: () => getVersion,
62487
+ getMissingKeysError: () => getMissingKeysError,
62488
+ getMissingKeyResolutions: () => getMissingKeyResolutions,
62489
+ getMissingKeyError: () => getMissingKeyError
61989
62490
  });
61990
62491
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync13, existsSync as existsSync9, mkdirSync as mkdirSync11, copyFileSync } from "node:fs";
61991
62492
  import { fileURLToPath as fileURLToPath5 } from "node:url";
@@ -61993,20 +62494,6 @@ import { dirname as dirname5, join as join17 } from "node:path";
61993
62494
  function getVersion() {
61994
62495
  return VERSION;
61995
62496
  }
61996
- function isLocalModel(modelId) {
61997
- if (!modelId)
61998
- return false;
61999
- const localPrefixes = [
62000
- "ollama/",
62001
- "ollama:",
62002
- "lmstudio/",
62003
- "vllm/",
62004
- "mlx/",
62005
- "http://",
62006
- "https://localhost"
62007
- ];
62008
- return localPrefixes.some((prefix) => modelId.toLowerCase().startsWith(prefix));
62009
- }
62010
62497
  async function parseArgs(args) {
62011
62498
  const config3 = {
62012
62499
  model: undefined,
@@ -62186,35 +62673,9 @@ async function parseArgs(args) {
62186
62673
  console.log("[claudish] API key will be extracted from Claude Code's requests");
62187
62674
  console.log("[claudish] Ensure you are logged in to Claude Code (claude auth login)");
62188
62675
  }
62189
- } else {
62190
- const allModels = [
62191
- config3.model,
62192
- config3.modelOpus,
62193
- config3.modelSonnet,
62194
- config3.modelHaiku,
62195
- config3.modelSubagent
62196
- ];
62197
- const hasNonLocalModel = allModels.some((m) => m && !isLocalModel(m));
62198
- if (hasNonLocalModel) {
62199
- const apiKey = process.env[ENV.OPENROUTER_API_KEY];
62200
- if (!apiKey) {
62201
- if (!config3.interactive) {
62202
- console.error("Error: OPENROUTER_API_KEY environment variable is required");
62203
- console.error("Get your API key from: https://openrouter.ai/keys");
62204
- console.error("");
62205
- console.error("Set it now:");
62206
- console.error(" export OPENROUTER_API_KEY='sk-or-v1-...'");
62207
- process.exit(1);
62208
- }
62209
- config3.openrouterApiKey = undefined;
62210
- } else {
62211
- config3.openrouterApiKey = apiKey;
62212
- }
62213
- } else {
62214
- config3.openrouterApiKey = process.env[ENV.OPENROUTER_API_KEY];
62215
- }
62216
- config3.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
62217
62676
  }
62677
+ config3.openrouterApiKey = process.env[ENV.OPENROUTER_API_KEY];
62678
+ config3.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
62218
62679
  if (config3.quiet === undefined) {
62219
62680
  config3.quiet = !config3.interactive;
62220
62681
  }
@@ -62691,7 +63152,7 @@ function printVersion() {
62691
63152
  }
62692
63153
  function printHelp() {
62693
63154
  console.log(`
62694
- claudish - Run Claude Code with any AI model (OpenRouter, Gemini, OpenAI, MiniMax, Kimi, GLM, Local)
63155
+ claudish - Run Claude Code with any AI model (OpenRouter, Gemini, OpenAI, MiniMax, Kimi, GLM, Z.AI, Local)
62695
63156
 
62696
63157
  USAGE:
62697
63158
  claudish # Interactive mode (default, shows model selector)
@@ -62705,6 +63166,7 @@ MODEL ROUTING (prefix-based):
62705
63166
  mmax/, mm/ MiniMax Direct API claudish --model mmax/MiniMax-M2.1 "task"
62706
63167
  kimi/, moonshot/ Kimi Direct API claudish --model kimi/kimi-k2-thinking-turbo "task"
62707
63168
  glm/, zhipu/ GLM Direct API claudish --model glm/glm-4.7 "task"
63169
+ zai/ Z.AI Direct API claudish --model zai/glm-4.7 "task"
62708
63170
  oc/ OllamaCloud claudish --model oc/gpt-oss:20b "task"
62709
63171
  zen/ OpenCode Zen (free) claudish --model zen/grok-code "task"
62710
63172
  ollama/ Ollama (local) claudish --model ollama/llama3.2 "task"
@@ -63139,11 +63601,12 @@ async function fetchZenModels2() {
63139
63601
  return [];
63140
63602
  }
63141
63603
  }
63142
- var __filename6, __dirname6, VERSION = "3.9.0", CACHE_MAX_AGE_DAYS3 = 2, MODELS_JSON_PATH, ALL_MODELS_JSON_PATH2;
63604
+ var __filename6, __dirname6, VERSION = "3.11.0", CACHE_MAX_AGE_DAYS3 = 2, MODELS_JSON_PATH, ALL_MODELS_JSON_PATH2;
63143
63605
  var init_cli = __esm(() => {
63144
63606
  init_dist3();
63145
63607
  init_model_loader2();
63146
63608
  init_profile_config();
63609
+ init_dist3();
63147
63610
  __filename6 = fileURLToPath5(import.meta.url);
63148
63611
  __dirname6 = dirname5(__filename6);
63149
63612
  try {
@@ -63842,6 +64305,12 @@ async function runCli() {
63842
64305
  const { parseArgs: parseArgs2, getVersion: getVersion2 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
63843
64306
  const { DEFAULT_PORT_RANGE: DEFAULT_PORT_RANGE2 } = await Promise.resolve().then(() => (init_dist3(), exports_dist));
63844
64307
  const { selectModel: selectModel2, promptForApiKey: promptForApiKey2 } = await Promise.resolve().then(() => (init_model_selector(), exports_model_selector));
64308
+ const {
64309
+ resolveModelProvider: resolveModelProvider2,
64310
+ validateApiKeysForModels: validateApiKeysForModels2,
64311
+ getMissingKeyResolutions: getMissingKeyResolutions2,
64312
+ getMissingKeysError: getMissingKeysError2
64313
+ } = await Promise.resolve().then(() => (init_dist3(), exports_dist));
63845
64314
  const { initLogger: initLogger2, getLogFilePath: getLogFilePath2 } = await Promise.resolve().then(() => (init_dist3(), exports_dist));
63846
64315
  const { findAvailablePort: findAvailablePort2 } = await Promise.resolve().then(() => (init_port_manager(), exports_port_manager));
63847
64316
  const { createProxyServer: createProxyServer2 } = await Promise.resolve().then(() => (init_dist3(), exports_dist));
@@ -63879,12 +64348,6 @@ async function runCli() {
63879
64348
  console.error(" export CLAUDE_PATH=~/.claude/local/claude");
63880
64349
  process.exit(1);
63881
64350
  }
63882
- const { isLocalModel: isLocalModel2 } = await Promise.resolve().then(() => (init_cli(), exports_cli));
63883
- const usingLocalModel = isLocalModel2(cliConfig.model);
63884
- if (cliConfig.interactive && !cliConfig.monitor && !cliConfig.openrouterApiKey && !usingLocalModel) {
63885
- cliConfig.openrouterApiKey = await promptForApiKey2();
63886
- console.log("");
63887
- }
63888
64351
  if (cliConfig.interactive && !cliConfig.monitor && !cliConfig.model) {
63889
64352
  cliConfig.model = await selectModel2({ freeOnly: cliConfig.freeOnly });
63890
64353
  console.log("");
@@ -63895,6 +64358,37 @@ async function runCli() {
63895
64358
  console.error("Try: claudish --list-models");
63896
64359
  process.exit(1);
63897
64360
  }
64361
+ if (!cliConfig.monitor) {
64362
+ const hasExplicitModel = typeof cliConfig.model === "string";
64363
+ const modelsToValidate = hasExplicitModel ? [cliConfig.model] : [
64364
+ cliConfig.model,
64365
+ cliConfig.modelOpus,
64366
+ cliConfig.modelSonnet,
64367
+ cliConfig.modelHaiku,
64368
+ cliConfig.modelSubagent
64369
+ ];
64370
+ const resolutions = validateApiKeysForModels2(modelsToValidate);
64371
+ const missingKeys = getMissingKeyResolutions2(resolutions);
64372
+ if (missingKeys.length > 0) {
64373
+ if (cliConfig.interactive) {
64374
+ const needsOpenRouter = missingKeys.some((r) => r.category === "openrouter");
64375
+ if (needsOpenRouter && !cliConfig.openrouterApiKey) {
64376
+ cliConfig.openrouterApiKey = await promptForApiKey2();
64377
+ console.log("");
64378
+ process.env.OPENROUTER_API_KEY = cliConfig.openrouterApiKey;
64379
+ }
64380
+ const stillMissing = getMissingKeyResolutions2(validateApiKeysForModels2(modelsToValidate));
64381
+ const nonOpenRouterMissing = stillMissing.filter((r) => r.category !== "openrouter");
64382
+ if (nonOpenRouterMissing.length > 0) {
64383
+ console.error(getMissingKeysError2(nonOpenRouterMissing));
64384
+ process.exit(1);
64385
+ }
64386
+ } else {
64387
+ console.error(getMissingKeysError2(missingKeys));
64388
+ process.exit(1);
64389
+ }
64390
+ }
64391
+ }
63898
64392
  if (cliConfig.model && typeof cliConfig.model === "string" && cliConfig.model.startsWith("go/")) {
63899
64393
  const { GeminiOAuth: GeminiOAuth2 } = await Promise.resolve().then(() => (init_dist3(), exports_dist));
63900
64394
  const oauth = GeminiOAuth2.getInstance();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "3.9.0",
3
+ "version": "3.11.0",
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",