claudish 3.7.8 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +482 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32119,6 +32119,250 @@ var init_openai_compat = __esm(() => {
|
|
|
32119
32119
|
init_tool_call_recovery();
|
|
32120
32120
|
});
|
|
32121
32121
|
|
|
32122
|
+
// ../core/dist/handlers/shared/openrouter-queue.js
|
|
32123
|
+
class OpenRouterRequestQueue {
|
|
32124
|
+
static instance = null;
|
|
32125
|
+
queue = [];
|
|
32126
|
+
processing = false;
|
|
32127
|
+
rateLimitState = {
|
|
32128
|
+
limitRequests: null,
|
|
32129
|
+
limitTokens: null,
|
|
32130
|
+
remainingRequests: null,
|
|
32131
|
+
remainingTokens: null,
|
|
32132
|
+
resetTime: null,
|
|
32133
|
+
lastRequestTime: 0,
|
|
32134
|
+
consecutiveErrors: 0,
|
|
32135
|
+
currentDelayMs: 1000,
|
|
32136
|
+
totalProcessed: 0,
|
|
32137
|
+
totalErrors: 0,
|
|
32138
|
+
total429Errors: 0
|
|
32139
|
+
};
|
|
32140
|
+
baseDelayMs = 1000;
|
|
32141
|
+
maxDelayMs = 1e4;
|
|
32142
|
+
maxQueueSize = 100;
|
|
32143
|
+
constructor() {
|
|
32144
|
+
if (getLogLevel() === "debug") {
|
|
32145
|
+
log("[OpenRouterQueue] Queue initialized with baseDelay=1000ms, maxQueueSize=100");
|
|
32146
|
+
}
|
|
32147
|
+
}
|
|
32148
|
+
static getInstance() {
|
|
32149
|
+
if (!OpenRouterRequestQueue.instance) {
|
|
32150
|
+
OpenRouterRequestQueue.instance = new OpenRouterRequestQueue;
|
|
32151
|
+
}
|
|
32152
|
+
return OpenRouterRequestQueue.instance;
|
|
32153
|
+
}
|
|
32154
|
+
async enqueue(fetchFn) {
|
|
32155
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
32156
|
+
if (getLogLevel() === "debug") {
|
|
32157
|
+
log(`[OpenRouterQueue] Queue full (${this.queue.length}/${this.maxQueueSize}), rejecting request`);
|
|
32158
|
+
}
|
|
32159
|
+
throw new Error(`OpenRouter request queue full (${this.queue.length}/${this.maxQueueSize}). The API is rate-limited. Please wait and try again.`);
|
|
32160
|
+
}
|
|
32161
|
+
return new Promise((resolve, reject) => {
|
|
32162
|
+
const queuedRequest = {
|
|
32163
|
+
fetchFn,
|
|
32164
|
+
resolve,
|
|
32165
|
+
reject
|
|
32166
|
+
};
|
|
32167
|
+
this.queue.push(queuedRequest);
|
|
32168
|
+
if (getLogLevel() === "debug") {
|
|
32169
|
+
log(`[OpenRouterQueue] Request enqueued (queue length: ${this.queue.length})`);
|
|
32170
|
+
}
|
|
32171
|
+
if (!this.processing) {
|
|
32172
|
+
this.processQueue();
|
|
32173
|
+
}
|
|
32174
|
+
});
|
|
32175
|
+
}
|
|
32176
|
+
async processQueue() {
|
|
32177
|
+
if (this.processing) {
|
|
32178
|
+
return;
|
|
32179
|
+
}
|
|
32180
|
+
this.processing = true;
|
|
32181
|
+
if (getLogLevel() === "debug") {
|
|
32182
|
+
log("[OpenRouterQueue] Worker started");
|
|
32183
|
+
}
|
|
32184
|
+
while (this.queue.length > 0) {
|
|
32185
|
+
const request = this.queue.shift();
|
|
32186
|
+
if (!request)
|
|
32187
|
+
break;
|
|
32188
|
+
if (getLogLevel() === "debug") {
|
|
32189
|
+
log(`[OpenRouterQueue] Processing request (${this.queue.length} remaining in queue)`);
|
|
32190
|
+
}
|
|
32191
|
+
try {
|
|
32192
|
+
await this.waitForNextSlot();
|
|
32193
|
+
const response = await request.fetchFn();
|
|
32194
|
+
this.rateLimitState.lastRequestTime = Date.now();
|
|
32195
|
+
this.parseRateLimitHeaders(response);
|
|
32196
|
+
if (response.status === 429) {
|
|
32197
|
+
this.rateLimitState.totalErrors++;
|
|
32198
|
+
this.rateLimitState.total429Errors++;
|
|
32199
|
+
await this.handleRateLimitError(response);
|
|
32200
|
+
if (getLogLevel() === "debug") {
|
|
32201
|
+
log(`[OpenRouterQueue] Rate limit hit (429), adjusted delay to ${this.rateLimitState.currentDelayMs}ms`);
|
|
32202
|
+
}
|
|
32203
|
+
} else {
|
|
32204
|
+
this.handleSuccessResponse();
|
|
32205
|
+
}
|
|
32206
|
+
this.rateLimitState.totalProcessed++;
|
|
32207
|
+
request.resolve(response);
|
|
32208
|
+
} catch (error46) {
|
|
32209
|
+
this.rateLimitState.totalErrors++;
|
|
32210
|
+
this.rateLimitState.consecutiveErrors++;
|
|
32211
|
+
if (getLogLevel() === "debug") {
|
|
32212
|
+
log(`[OpenRouterQueue] Request failed with error: ${error46}`);
|
|
32213
|
+
}
|
|
32214
|
+
request.reject(error46 instanceof Error ? error46 : new Error(String(error46)));
|
|
32215
|
+
}
|
|
32216
|
+
}
|
|
32217
|
+
this.processing = false;
|
|
32218
|
+
if (getLogLevel() === "debug") {
|
|
32219
|
+
log("[OpenRouterQueue] Worker stopped (queue empty)");
|
|
32220
|
+
}
|
|
32221
|
+
}
|
|
32222
|
+
async waitForNextSlot() {
|
|
32223
|
+
const now = Date.now();
|
|
32224
|
+
const timeSinceLastRequest = now - this.rateLimitState.lastRequestTime;
|
|
32225
|
+
const delayMs = this.calculateDelay();
|
|
32226
|
+
this.rateLimitState.currentDelayMs = delayMs;
|
|
32227
|
+
if (timeSinceLastRequest < delayMs) {
|
|
32228
|
+
const waitMs = delayMs - timeSinceLastRequest;
|
|
32229
|
+
if (getLogLevel() === "debug") {
|
|
32230
|
+
log(`[OpenRouterQueue] Waiting ${waitMs}ms before next request`);
|
|
32231
|
+
}
|
|
32232
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
32233
|
+
}
|
|
32234
|
+
}
|
|
32235
|
+
calculateDelay() {
|
|
32236
|
+
let delayMs = this.baseDelayMs;
|
|
32237
|
+
if (this.rateLimitState.remainingRequests !== null && this.rateLimitState.limitRequests !== null && this.rateLimitState.limitRequests > 0) {
|
|
32238
|
+
const quotaPercent = this.rateLimitState.remainingRequests / this.rateLimitState.limitRequests;
|
|
32239
|
+
if (quotaPercent < 0.2) {
|
|
32240
|
+
delayMs = Math.max(delayMs, 3000);
|
|
32241
|
+
if (getLogLevel() === "debug") {
|
|
32242
|
+
log(`[OpenRouterQueue] Low quota (${(quotaPercent * 100).toFixed(1)}%), increasing delay to ${delayMs}ms`);
|
|
32243
|
+
}
|
|
32244
|
+
} else if (quotaPercent < 0.5) {
|
|
32245
|
+
delayMs = Math.max(delayMs, 2000);
|
|
32246
|
+
if (getLogLevel() === "debug") {
|
|
32247
|
+
log(`[OpenRouterQueue] Medium quota (${(quotaPercent * 100).toFixed(1)}%), increasing delay to ${delayMs}ms`);
|
|
32248
|
+
}
|
|
32249
|
+
}
|
|
32250
|
+
}
|
|
32251
|
+
if (this.rateLimitState.resetTime !== null && this.rateLimitState.remainingRequests !== null) {
|
|
32252
|
+
const now = Date.now() / 1000;
|
|
32253
|
+
const timeUntilReset = this.rateLimitState.resetTime - now;
|
|
32254
|
+
if (timeUntilReset > 0 && this.rateLimitState.remainingRequests > 0) {
|
|
32255
|
+
const optimalDelay = timeUntilReset * 1000 / Math.max(this.rateLimitState.remainingRequests, 1);
|
|
32256
|
+
delayMs = Math.max(delayMs, Math.min(optimalDelay, this.maxDelayMs));
|
|
32257
|
+
if (getLogLevel() === "debug") {
|
|
32258
|
+
log(`[OpenRouterQueue] Spreading ${this.rateLimitState.remainingRequests} requests ` + `over ${timeUntilReset.toFixed(1)}s, optimal delay: ${optimalDelay.toFixed(0)}ms`);
|
|
32259
|
+
}
|
|
32260
|
+
}
|
|
32261
|
+
}
|
|
32262
|
+
if (this.rateLimitState.consecutiveErrors > 0) {
|
|
32263
|
+
const backoffMultiplier = 1 + this.rateLimitState.consecutiveErrors * 0.5;
|
|
32264
|
+
delayMs = delayMs * backoffMultiplier;
|
|
32265
|
+
if (getLogLevel() === "debug") {
|
|
32266
|
+
log(`[OpenRouterQueue] Applying backoff (${this.rateLimitState.consecutiveErrors} errors): ${delayMs.toFixed(0)}ms`);
|
|
32267
|
+
}
|
|
32268
|
+
}
|
|
32269
|
+
return Math.min(delayMs, this.maxDelayMs);
|
|
32270
|
+
}
|
|
32271
|
+
parseRateLimitHeaders(response) {
|
|
32272
|
+
const limitRequests = response.headers.get("X-RateLimit-Limit-Requests");
|
|
32273
|
+
if (limitRequests) {
|
|
32274
|
+
this.rateLimitState.limitRequests = Number.parseInt(limitRequests, 10);
|
|
32275
|
+
}
|
|
32276
|
+
const remainingRequests = response.headers.get("X-RateLimit-Remaining-Requests");
|
|
32277
|
+
if (remainingRequests) {
|
|
32278
|
+
this.rateLimitState.remainingRequests = Number.parseInt(remainingRequests, 10);
|
|
32279
|
+
}
|
|
32280
|
+
const resetRequests = response.headers.get("X-RateLimit-Reset-Requests");
|
|
32281
|
+
if (resetRequests) {
|
|
32282
|
+
this.rateLimitState.resetTime = Number.parseFloat(resetRequests);
|
|
32283
|
+
}
|
|
32284
|
+
const limitTokens = response.headers.get("X-RateLimit-Limit-Tokens");
|
|
32285
|
+
if (limitTokens) {
|
|
32286
|
+
this.rateLimitState.limitTokens = Number.parseInt(limitTokens, 10);
|
|
32287
|
+
}
|
|
32288
|
+
const remainingTokens = response.headers.get("X-RateLimit-Remaining-Tokens");
|
|
32289
|
+
if (remainingTokens) {
|
|
32290
|
+
this.rateLimitState.remainingTokens = Number.parseInt(remainingTokens, 10);
|
|
32291
|
+
}
|
|
32292
|
+
if (getLogLevel() === "debug") {
|
|
32293
|
+
const headers = {
|
|
32294
|
+
limitRequests: this.rateLimitState.limitRequests,
|
|
32295
|
+
remainingRequests: this.rateLimitState.remainingRequests,
|
|
32296
|
+
resetTime: this.rateLimitState.resetTime ? new Date(this.rateLimitState.resetTime * 1000).toISOString() : null,
|
|
32297
|
+
limitTokens: this.rateLimitState.limitTokens,
|
|
32298
|
+
remainingTokens: this.rateLimitState.remainingTokens
|
|
32299
|
+
};
|
|
32300
|
+
log(`[OpenRouterQueue] Rate limit headers: ${JSON.stringify(headers)}`);
|
|
32301
|
+
}
|
|
32302
|
+
}
|
|
32303
|
+
async handleRateLimitError(response) {
|
|
32304
|
+
this.rateLimitState.consecutiveErrors++;
|
|
32305
|
+
this.rateLimitState.remainingRequests = 0;
|
|
32306
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
32307
|
+
if (retryAfter) {
|
|
32308
|
+
const retryAfterSeconds = Number.parseInt(retryAfter, 10);
|
|
32309
|
+
if (!Number.isNaN(retryAfterSeconds)) {
|
|
32310
|
+
const retryAfterMs = retryAfterSeconds * 1000;
|
|
32311
|
+
this.rateLimitState.currentDelayMs = Math.min(retryAfterMs, this.maxDelayMs);
|
|
32312
|
+
if (getLogLevel() === "debug") {
|
|
32313
|
+
log(`[OpenRouterQueue] Retry-After header: ${retryAfterSeconds}s (${retryAfterMs}ms)`);
|
|
32314
|
+
}
|
|
32315
|
+
}
|
|
32316
|
+
}
|
|
32317
|
+
try {
|
|
32318
|
+
const errorText = await response.clone().text();
|
|
32319
|
+
const errorData = JSON.parse(errorText);
|
|
32320
|
+
if (errorData?.error?.message) {
|
|
32321
|
+
if (getLogLevel() === "debug") {
|
|
32322
|
+
log(`[OpenRouterQueue] 429 error message: ${errorData.error.message}`);
|
|
32323
|
+
}
|
|
32324
|
+
}
|
|
32325
|
+
} catch {}
|
|
32326
|
+
const backoffMultiplier = 1 + this.rateLimitState.consecutiveErrors * 0.5;
|
|
32327
|
+
const backoffDelay = Math.min(this.baseDelayMs * backoffMultiplier, this.maxDelayMs);
|
|
32328
|
+
this.rateLimitState.currentDelayMs = Math.max(this.rateLimitState.currentDelayMs, backoffDelay);
|
|
32329
|
+
if (getLogLevel() === "debug") {
|
|
32330
|
+
log(`[OpenRouterQueue] Applied exponential backoff: ${this.rateLimitState.currentDelayMs}ms ` + `(${this.rateLimitState.consecutiveErrors} consecutive errors)`);
|
|
32331
|
+
}
|
|
32332
|
+
}
|
|
32333
|
+
handleSuccessResponse() {
|
|
32334
|
+
if (this.rateLimitState.consecutiveErrors > 0) {
|
|
32335
|
+
if (getLogLevel() === "debug") {
|
|
32336
|
+
log(`[OpenRouterQueue] Success after ${this.rateLimitState.consecutiveErrors} errors, resetting counter`);
|
|
32337
|
+
}
|
|
32338
|
+
this.rateLimitState.consecutiveErrors = 0;
|
|
32339
|
+
}
|
|
32340
|
+
if (this.rateLimitState.currentDelayMs > this.baseDelayMs) {
|
|
32341
|
+
this.rateLimitState.currentDelayMs = Math.max(this.baseDelayMs, this.rateLimitState.currentDelayMs * 0.9);
|
|
32342
|
+
if (getLogLevel() === "debug") {
|
|
32343
|
+
log(`[OpenRouterQueue] Reducing delay to ${this.rateLimitState.currentDelayMs}ms`);
|
|
32344
|
+
}
|
|
32345
|
+
}
|
|
32346
|
+
}
|
|
32347
|
+
getStats() {
|
|
32348
|
+
return {
|
|
32349
|
+
queueLength: this.queue.length,
|
|
32350
|
+
processing: this.processing,
|
|
32351
|
+
consecutiveErrors: this.rateLimitState.consecutiveErrors,
|
|
32352
|
+
currentDelayMs: this.rateLimitState.currentDelayMs,
|
|
32353
|
+
totalProcessed: this.rateLimitState.totalProcessed,
|
|
32354
|
+
totalErrors: this.rateLimitState.totalErrors,
|
|
32355
|
+
total429Errors: this.rateLimitState.total429Errors,
|
|
32356
|
+
remainingRequests: this.rateLimitState.remainingRequests,
|
|
32357
|
+
remainingTokens: this.rateLimitState.remainingTokens,
|
|
32358
|
+
resetTime: this.rateLimitState.resetTime
|
|
32359
|
+
};
|
|
32360
|
+
}
|
|
32361
|
+
}
|
|
32362
|
+
var init_openrouter_queue = __esm(() => {
|
|
32363
|
+
init_logger();
|
|
32364
|
+
});
|
|
32365
|
+
|
|
32122
32366
|
// ../core/dist/handlers/openrouter-handler.js
|
|
32123
32367
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
32124
32368
|
import { homedir } from "node:os";
|
|
@@ -32133,6 +32377,7 @@ class OpenRouterHandler {
|
|
|
32133
32377
|
port;
|
|
32134
32378
|
sessionTotalCost = 0;
|
|
32135
32379
|
CLAUDE_INTERNAL_CONTEXT_MAX = 200000;
|
|
32380
|
+
queue;
|
|
32136
32381
|
constructor(targetModel, apiKey, port) {
|
|
32137
32382
|
this.targetModel = targetModel;
|
|
32138
32383
|
this.apiKey = apiKey;
|
|
@@ -32142,6 +32387,7 @@ class OpenRouterHandler {
|
|
|
32142
32387
|
this.middlewareManager.register(new GeminiThoughtSignatureMiddleware);
|
|
32143
32388
|
this.middlewareManager.initialize().catch((err) => log(`[Handler:${targetModel}] Middleware init error: ${err}`));
|
|
32144
32389
|
this.fetchContextWindow(targetModel);
|
|
32390
|
+
this.queue = OpenRouterRequestQueue.getInstance();
|
|
32145
32391
|
}
|
|
32146
32392
|
async fetchContextWindow(model) {
|
|
32147
32393
|
if (this.contextWindowCache.has(model))
|
|
@@ -32161,6 +32407,7 @@ class OpenRouterHandler {
|
|
|
32161
32407
|
const limit = this.contextWindowCache.get(this.targetModel) || 200000;
|
|
32162
32408
|
const leftPct = limit > 0 ? Math.max(0, Math.min(100, Math.round((limit - total) / limit * 100))) : 100;
|
|
32163
32409
|
const displayModelName = this.targetModel.replace(/^(go|g|gemini|v|vertex|oai|mmax|mm|kimi|moonshot|glm|zhipu|oc|ollama|lmstudio|vllm|mlx)[\/:]/, "");
|
|
32410
|
+
const isFreeModel = this.targetModel.endsWith(":free") || this.sessionTotalCost === 0;
|
|
32164
32411
|
const data = {
|
|
32165
32412
|
input_tokens: input,
|
|
32166
32413
|
output_tokens: output,
|
|
@@ -32170,7 +32417,8 @@ class OpenRouterHandler {
|
|
|
32170
32417
|
context_left_percent: leftPct,
|
|
32171
32418
|
provider_name: "OpenRouter",
|
|
32172
32419
|
model_name: displayModelName,
|
|
32173
|
-
updated_at: Date.now()
|
|
32420
|
+
updated_at: Date.now(),
|
|
32421
|
+
is_free: isFreeModel
|
|
32174
32422
|
};
|
|
32175
32423
|
const claudishDir = join4(homedir(), ".claudish");
|
|
32176
32424
|
mkdirSync2(claudishDir, { recursive: true });
|
|
@@ -32232,7 +32480,7 @@ class OpenRouterHandler {
|
|
|
32232
32480
|
await this.middlewareManager.beforeRequest({ modelId: target, messages, tools, stream: true });
|
|
32233
32481
|
let response;
|
|
32234
32482
|
try {
|
|
32235
|
-
response = await fetch(OPENROUTER_API_URL2, {
|
|
32483
|
+
response = await this.queue.enqueue(() => fetch(OPENROUTER_API_URL2, {
|
|
32236
32484
|
method: "POST",
|
|
32237
32485
|
headers: {
|
|
32238
32486
|
"Content-Type": "application/json",
|
|
@@ -32240,16 +32488,26 @@ class OpenRouterHandler {
|
|
|
32240
32488
|
...OPENROUTER_HEADERS2
|
|
32241
32489
|
},
|
|
32242
32490
|
body: JSON.stringify(openRouterPayload)
|
|
32243
|
-
});
|
|
32491
|
+
}));
|
|
32244
32492
|
} catch (fetchError) {
|
|
32245
32493
|
log(`[OpenRouter] Fetch error: ${fetchError.message || fetchError}`);
|
|
32246
32494
|
return this.createStreamingErrorResponse(c, target, `Network error: ${fetchError.message || "Connection failed"}`);
|
|
32247
32495
|
}
|
|
32248
32496
|
log(`[OpenRouter] Response status: ${response.status}`);
|
|
32497
|
+
if (getLogLevel() === "debug") {
|
|
32498
|
+
const rateLimitHeaders = {
|
|
32499
|
+
remaining: response.headers.get("X-RateLimit-Remaining-Requests"),
|
|
32500
|
+
limit: response.headers.get("X-RateLimit-Limit-Requests"),
|
|
32501
|
+
reset: response.headers.get("X-RateLimit-Reset-Requests"),
|
|
32502
|
+
remainingTokens: response.headers.get("X-RateLimit-Remaining-Tokens")
|
|
32503
|
+
};
|
|
32504
|
+
log(`[OpenRouter] Rate limit headers: ${JSON.stringify(rateLimitHeaders)}`);
|
|
32505
|
+
}
|
|
32249
32506
|
if (!response.ok) {
|
|
32250
32507
|
const errorText = await response.text().catch(() => "Unknown error");
|
|
32251
32508
|
log(`[OpenRouter] API error ${response.status}: ${errorText}`);
|
|
32252
|
-
|
|
32509
|
+
const friendlyMessage = this.formatErrorMessage(response.status, errorText, target);
|
|
32510
|
+
return this.createStreamingErrorResponse(c, target, friendlyMessage);
|
|
32253
32511
|
}
|
|
32254
32512
|
if (droppedParams.length > 0)
|
|
32255
32513
|
c.header("X-Dropped-Params", droppedParams.join(", "));
|
|
@@ -32677,6 +32935,36 @@ data: ${JSON.stringify(d)}
|
|
|
32677
32935
|
}
|
|
32678
32936
|
});
|
|
32679
32937
|
}
|
|
32938
|
+
formatErrorMessage(status, errorText, model) {
|
|
32939
|
+
try {
|
|
32940
|
+
const error46 = JSON.parse(errorText);
|
|
32941
|
+
const msg = error46?.error?.message || "";
|
|
32942
|
+
const metadata = error46?.error?.metadata || {};
|
|
32943
|
+
if (status === 429) {
|
|
32944
|
+
const provider = metadata.provider_name || "Provider";
|
|
32945
|
+
if (msg.includes("rate-limited")) {
|
|
32946
|
+
return `⏳ Rate limited by ${provider}. The model "${model}" is temporarily unavailable. Please wait a moment and try again, or try a different model.`;
|
|
32947
|
+
}
|
|
32948
|
+
return `⏳ Rate limit exceeded. Please wait and try again.`;
|
|
32949
|
+
}
|
|
32950
|
+
if (status === 404) {
|
|
32951
|
+
if (msg.includes("tool use")) {
|
|
32952
|
+
return `❌ Model "${model}" does not support tool calling, which is required by Claude Code. Try a different model with tool support.`;
|
|
32953
|
+
}
|
|
32954
|
+
return `❌ Model not found or unavailable: ${model}`;
|
|
32955
|
+
}
|
|
32956
|
+
if (status === 401 || status === 403) {
|
|
32957
|
+
return `\uD83D\uDD11 Authentication error. Please check your OPENROUTER_API_KEY.`;
|
|
32958
|
+
}
|
|
32959
|
+
if (status >= 500) {
|
|
32960
|
+
return `⚠️ OpenRouter server error (${status}). Please try again later.`;
|
|
32961
|
+
}
|
|
32962
|
+
if (msg) {
|
|
32963
|
+
return `OpenRouter error (${status}): ${msg}`;
|
|
32964
|
+
}
|
|
32965
|
+
} catch {}
|
|
32966
|
+
return `OpenRouter API error (${status}): ${errorText}`;
|
|
32967
|
+
}
|
|
32680
32968
|
createStreamingErrorResponse(c, model, errorMessage) {
|
|
32681
32969
|
const encoder = new TextEncoder;
|
|
32682
32970
|
const msgId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
@@ -32744,6 +33032,7 @@ var init_openrouter_handler = __esm(() => {
|
|
|
32744
33032
|
init_logger();
|
|
32745
33033
|
init_model_loader();
|
|
32746
33034
|
init_openai_compat();
|
|
33035
|
+
init_openrouter_queue();
|
|
32747
33036
|
OPENROUTER_HEADERS2 = {
|
|
32748
33037
|
"HTTP-Referer": "https://claudish.com",
|
|
32749
33038
|
"X-Title": "Claudish - OpenRouter Proxy"
|
|
@@ -53726,6 +54015,10 @@ function getModelPricing(provider, modelName) {
|
|
|
53726
54015
|
case "oc":
|
|
53727
54016
|
pricingTable = OLLAMACLOUD_PRICING;
|
|
53728
54017
|
break;
|
|
54018
|
+
case "opencode-zen":
|
|
54019
|
+
case "zen":
|
|
54020
|
+
pricingTable = OPENCODE_ZEN_PRICING;
|
|
54021
|
+
break;
|
|
53729
54022
|
default:
|
|
53730
54023
|
return { inputCostPer1M: 1, outputCostPer1M: 4 };
|
|
53731
54024
|
}
|
|
@@ -53745,7 +54038,7 @@ function calculateCost(provider, modelName, inputTokens, outputTokens) {
|
|
|
53745
54038
|
const outputCost = outputTokens / 1e6 * pricing.outputCostPer1M;
|
|
53746
54039
|
return inputCost + outputCost;
|
|
53747
54040
|
}
|
|
53748
|
-
var GEMINI_PRICING, OPENAI_PRICING, MINIMAX_PRICING, KIMI_PRICING, GLM_PRICING, OLLAMACLOUD_PRICING, VERTEX_PRICING;
|
|
54041
|
+
var GEMINI_PRICING, OPENAI_PRICING, MINIMAX_PRICING, KIMI_PRICING, GLM_PRICING, OLLAMACLOUD_PRICING, VERTEX_PRICING, OPENCODE_ZEN_PRICING;
|
|
53749
54042
|
var init_remote_provider_types = __esm(() => {
|
|
53750
54043
|
GEMINI_PRICING = {
|
|
53751
54044
|
"gemini-2.5-flash": { inputCostPer1M: 0.15, outputCostPer1M: 0.6 },
|
|
@@ -53834,6 +54127,15 @@ var init_remote_provider_types = __esm(() => {
|
|
|
53834
54127
|
"gemini-2.0-flash-thinking": { inputCostPer1M: 0.1, outputCostPer1M: 0.4 },
|
|
53835
54128
|
default: { inputCostPer1M: 0.5, outputCostPer1M: 2, isEstimate: true }
|
|
53836
54129
|
};
|
|
54130
|
+
OPENCODE_ZEN_PRICING = {
|
|
54131
|
+
"grok-code": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
|
|
54132
|
+
"grok-code-fast-1": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
|
|
54133
|
+
"glm-4.7-free": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
|
|
54134
|
+
"minimax-m2.1-free": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
|
|
54135
|
+
"big-pickle": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
|
|
54136
|
+
"gpt-5-nano": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
|
|
54137
|
+
default: { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true }
|
|
54138
|
+
};
|
|
53837
54139
|
});
|
|
53838
54140
|
|
|
53839
54141
|
// ../core/dist/handlers/base-gemini-handler.js
|
|
@@ -55344,7 +55646,7 @@ class OpenAIHandler {
|
|
|
55344
55646
|
}
|
|
55345
55647
|
}
|
|
55346
55648
|
getPricing() {
|
|
55347
|
-
return getModelPricing(
|
|
55649
|
+
return getModelPricing(this.provider.name, this.modelName);
|
|
55348
55650
|
}
|
|
55349
55651
|
getApiEndpoint() {
|
|
55350
55652
|
if (this.isCodexModel()) {
|
|
@@ -55356,7 +55658,16 @@ class OpenAIHandler {
|
|
|
55356
55658
|
try {
|
|
55357
55659
|
const total = input + output;
|
|
55358
55660
|
const leftPct = this.contextWindow > 0 ? Math.max(0, Math.min(100, Math.round((this.contextWindow - total) / this.contextWindow * 100))) : 100;
|
|
55359
|
-
const displayModelName = this.modelName.replace(/^(go|g|gemini|v|vertex|oai|mmax|mm|kimi|moonshot|glm|zhipu|oc|ollama|lmstudio|vllm|mlx)[\/:]/, "");
|
|
55661
|
+
const displayModelName = this.modelName.replace(/^(go|g|gemini|v|vertex|oai|mmax|mm|kimi|moonshot|glm|zhipu|oc|zen|ollama|lmstudio|vllm|mlx)[\/:]/, "");
|
|
55662
|
+
const formatProviderName = (name) => {
|
|
55663
|
+
if (name === "opencode-zen")
|
|
55664
|
+
return "Zen";
|
|
55665
|
+
if (name === "glm")
|
|
55666
|
+
return "GLM";
|
|
55667
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
55668
|
+
};
|
|
55669
|
+
const pricing = this.getPricing();
|
|
55670
|
+
const isFreeModel = pricing.isFree || pricing.inputCostPer1M === 0 && pricing.outputCostPer1M === 0;
|
|
55360
55671
|
const data = {
|
|
55361
55672
|
input_tokens: input,
|
|
55362
55673
|
output_tokens: output,
|
|
@@ -55364,13 +55675,12 @@ class OpenAIHandler {
|
|
|
55364
55675
|
total_cost: this.sessionTotalCost,
|
|
55365
55676
|
context_window: this.contextWindow,
|
|
55366
55677
|
context_left_percent: leftPct,
|
|
55367
|
-
provider_name:
|
|
55678
|
+
provider_name: formatProviderName(this.provider.name),
|
|
55368
55679
|
model_name: displayModelName,
|
|
55369
|
-
updated_at: Date.now()
|
|
55680
|
+
updated_at: Date.now(),
|
|
55681
|
+
is_free: isFreeModel,
|
|
55682
|
+
is_estimated: isEstimate || false
|
|
55370
55683
|
};
|
|
55371
|
-
if (isEstimate) {
|
|
55372
|
-
data.cost_is_estimate = true;
|
|
55373
|
-
}
|
|
55374
55684
|
const claudishDir = join9(homedir6(), ".claudish");
|
|
55375
55685
|
mkdirSync6(claudishDir, { recursive: true });
|
|
55376
55686
|
writeFileSync7(join9(claudishDir, `tokens-${this.port}.json`), JSON.stringify(data), "utf-8");
|
|
@@ -55831,7 +56141,7 @@ data: ${JSON.stringify(data)}
|
|
|
55831
56141
|
method: "POST",
|
|
55832
56142
|
headers: {
|
|
55833
56143
|
"Content-Type": "application/json",
|
|
55834
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
56144
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
55835
56145
|
},
|
|
55836
56146
|
body: JSON.stringify(apiPayload),
|
|
55837
56147
|
signal: controller.signal
|
|
@@ -58121,7 +58431,8 @@ function validateRemoteProviderApiKey(provider) {
|
|
|
58121
58431
|
MINIMAX_API_KEY: "export MINIMAX_API_KEY='your-key' (get from https://www.minimaxi.com/)",
|
|
58122
58432
|
MOONSHOT_API_KEY: "export MOONSHOT_API_KEY='your-key' (get from https://platform.moonshot.cn/)",
|
|
58123
58433
|
ZHIPU_API_KEY: "export ZHIPU_API_KEY='your-key' (get from https://open.bigmodel.cn/)",
|
|
58124
|
-
OLLAMA_API_KEY: "export OLLAMA_API_KEY='your-key' (get from https://ollama.com/account)"
|
|
58434
|
+
OLLAMA_API_KEY: "export OLLAMA_API_KEY='your-key' (get from https://ollama.com/account)",
|
|
58435
|
+
OPENCODE_API_KEY: "export OPENCODE_API_KEY='your-key' (get from https://opencode.ai/)"
|
|
58125
58436
|
};
|
|
58126
58437
|
const example = examples[provider.apiKeyEnvVar] || `export ${provider.apiKeyEnvVar}='your-key'`;
|
|
58127
58438
|
return `Missing ${provider.apiKeyEnvVar} environment variable.
|
|
@@ -58250,6 +58561,20 @@ var getRemoteProviders = () => [
|
|
|
58250
58561
|
supportsJsonMode: false,
|
|
58251
58562
|
supportsReasoning: false
|
|
58252
58563
|
}
|
|
58564
|
+
},
|
|
58565
|
+
{
|
|
58566
|
+
name: "opencode-zen",
|
|
58567
|
+
baseUrl: process.env.OPENCODE_BASE_URL || "https://opencode.ai/zen",
|
|
58568
|
+
apiPath: "/v1/chat/completions",
|
|
58569
|
+
apiKeyEnvVar: "",
|
|
58570
|
+
prefixes: ["zen/"],
|
|
58571
|
+
capabilities: {
|
|
58572
|
+
supportsTools: true,
|
|
58573
|
+
supportsVision: false,
|
|
58574
|
+
supportsStreaming: true,
|
|
58575
|
+
supportsJsonMode: true,
|
|
58576
|
+
supportsReasoning: false
|
|
58577
|
+
}
|
|
58253
58578
|
}
|
|
58254
58579
|
];
|
|
58255
58580
|
|
|
@@ -58319,7 +58644,7 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
58319
58644
|
if (apiKeyError) {
|
|
58320
58645
|
throw new Error(apiKeyError);
|
|
58321
58646
|
}
|
|
58322
|
-
const apiKey = process.env[resolved.provider.apiKeyEnvVar];
|
|
58647
|
+
const apiKey = resolved.provider.apiKeyEnvVar ? process.env[resolved.provider.apiKeyEnvVar] || "" : "";
|
|
58323
58648
|
let handler;
|
|
58324
58649
|
if (resolved.provider.name === "gemini") {
|
|
58325
58650
|
handler = new GeminiHandler(resolved.provider, resolved.modelName, apiKey, port);
|
|
@@ -58336,6 +58661,14 @@ async function createProxyServer(port, openrouterApiKey, model, monitorMode = fa
|
|
|
58336
58661
|
} else if (resolved.provider.name === "glm") {
|
|
58337
58662
|
handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
|
|
58338
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")) {
|
|
58666
|
+
handler = new AnthropicCompatHandler(resolved.provider, resolved.modelName, apiKey, port);
|
|
58667
|
+
log(`[Proxy] Created OpenCode Zen (Anthropic) handler: ${resolved.modelName}`);
|
|
58668
|
+
} else {
|
|
58669
|
+
handler = new OpenAIHandler(resolved.provider, resolved.modelName, apiKey, port);
|
|
58670
|
+
log(`[Proxy] Created OpenCode Zen (OpenAI) handler: ${resolved.modelName}`);
|
|
58671
|
+
}
|
|
58339
58672
|
} else if (resolved.provider.name === "ollamacloud") {
|
|
58340
58673
|
handler = new OllamaCloudHandler(resolved.provider, resolved.modelName, apiKey, port);
|
|
58341
58674
|
log(`[Proxy] Created OllamaCloud handler: ${resolved.modelName}`);
|
|
@@ -60899,6 +61232,40 @@ __export(exports_model_selector, {
|
|
|
60899
61232
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync12, existsSync as existsSync7 } from "node:fs";
|
|
60900
61233
|
import { join as join15, dirname as dirname3 } from "node:path";
|
|
60901
61234
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
61235
|
+
async function fetchZenModels() {
|
|
61236
|
+
try {
|
|
61237
|
+
const response = await fetch("https://models.dev/api.json", {
|
|
61238
|
+
signal: AbortSignal.timeout(5000)
|
|
61239
|
+
});
|
|
61240
|
+
if (!response.ok)
|
|
61241
|
+
return [];
|
|
61242
|
+
const data = await response.json();
|
|
61243
|
+
const opencode = data.opencode;
|
|
61244
|
+
if (!opencode?.models)
|
|
61245
|
+
return [];
|
|
61246
|
+
return Object.entries(opencode.models).map(([id, m]) => {
|
|
61247
|
+
const isFree = m.cost?.input === 0 && m.cost?.output === 0;
|
|
61248
|
+
return {
|
|
61249
|
+
id: `zen/${id}`,
|
|
61250
|
+
name: m.name || id,
|
|
61251
|
+
description: isFree ? "OpenCode Zen - FREE (no API key needed)" : `OpenCode Zen - $${((m.cost?.input || 0) + (m.cost?.output || 0)).toFixed(1)}/M`,
|
|
61252
|
+
provider: "zen",
|
|
61253
|
+
pricing: { input: String(m.cost?.input || 0), output: String(m.cost?.output || 0), average: isFree ? "FREE" : `$${((m.cost?.input || 0) + (m.cost?.output || 0)).toFixed(1)}/M` },
|
|
61254
|
+
context: m.limit?.context ? `${Math.round(m.limit.context / 1000)}K` : "128K",
|
|
61255
|
+
contextLength: m.limit?.context || 128000,
|
|
61256
|
+
supportsTools: m.tool_call || false,
|
|
61257
|
+
supportsReasoning: m.reasoning || false,
|
|
61258
|
+
isFree
|
|
61259
|
+
};
|
|
61260
|
+
});
|
|
61261
|
+
} catch {
|
|
61262
|
+
return [];
|
|
61263
|
+
}
|
|
61264
|
+
}
|
|
61265
|
+
async function fetchZenFreeModels() {
|
|
61266
|
+
const allZen = await fetchZenModels();
|
|
61267
|
+
return allZen.filter((m) => m.isFree);
|
|
61268
|
+
}
|
|
60902
61269
|
function loadRecommendedModels2() {
|
|
60903
61270
|
if (existsSync7(RECOMMENDED_MODELS_JSON_PATH)) {
|
|
60904
61271
|
try {
|
|
@@ -60977,13 +61344,19 @@ function toModelInfo(model) {
|
|
|
60977
61344
|
};
|
|
60978
61345
|
}
|
|
60979
61346
|
async function getFreeModels() {
|
|
60980
|
-
const allModels = await
|
|
61347
|
+
const [allModels, zenModels] = await Promise.all([
|
|
61348
|
+
fetchAllModels(),
|
|
61349
|
+
fetchZenFreeModels()
|
|
61350
|
+
]);
|
|
60981
61351
|
const freeModels = allModels.filter((model) => {
|
|
60982
61352
|
const promptPrice = parseFloat(model.pricing?.prompt || "0");
|
|
60983
61353
|
const completionPrice = parseFloat(model.pricing?.completion || "0");
|
|
60984
61354
|
const isFree = promptPrice === 0 && completionPrice === 0;
|
|
60985
61355
|
if (!isFree)
|
|
60986
61356
|
return false;
|
|
61357
|
+
const supportsTools = (model.supported_parameters || []).includes("tools");
|
|
61358
|
+
if (!supportsTools)
|
|
61359
|
+
return false;
|
|
60987
61360
|
const provider = model.id.split("/")[0].toLowerCase();
|
|
60988
61361
|
return TRUSTED_FREE_PROVIDERS.includes(provider);
|
|
60989
61362
|
});
|
|
@@ -61000,7 +61373,8 @@ async function getFreeModels() {
|
|
|
61000
61373
|
seenBase.add(baseId);
|
|
61001
61374
|
return true;
|
|
61002
61375
|
});
|
|
61003
|
-
|
|
61376
|
+
const openRouterFree = dedupedModels.slice(0, 15).map(toModelInfo);
|
|
61377
|
+
return [...zenModels, ...openRouterFree];
|
|
61004
61378
|
}
|
|
61005
61379
|
async function getAllModelsForSearch() {
|
|
61006
61380
|
const allModels = await fetchAllModels();
|
|
@@ -61191,7 +61565,8 @@ var init_model_selector = __esm(() => {
|
|
|
61191
61565
|
"microsoft",
|
|
61192
61566
|
"mistralai",
|
|
61193
61567
|
"nvidia",
|
|
61194
|
-
"cohere"
|
|
61568
|
+
"cohere",
|
|
61569
|
+
"zen"
|
|
61195
61570
|
];
|
|
61196
61571
|
});
|
|
61197
61572
|
|
|
@@ -61812,8 +62187,15 @@ async function parseArgs(args) {
|
|
|
61812
62187
|
console.log("[claudish] Ensure you are logged in to Claude Code (claude auth login)");
|
|
61813
62188
|
}
|
|
61814
62189
|
} else {
|
|
61815
|
-
const
|
|
61816
|
-
|
|
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) {
|
|
61817
62199
|
const apiKey = process.env[ENV.OPENROUTER_API_KEY];
|
|
61818
62200
|
if (!apiKey) {
|
|
61819
62201
|
if (!config3.interactive) {
|
|
@@ -62003,7 +62385,10 @@ Found ${results.length} matching models:
|
|
|
62003
62385
|
}
|
|
62004
62386
|
async function printAllModels(jsonOutput, forceUpdate) {
|
|
62005
62387
|
let models = [];
|
|
62006
|
-
const ollamaModels = await
|
|
62388
|
+
const [ollamaModels, zenModels] = await Promise.all([
|
|
62389
|
+
fetchOllamaModels(),
|
|
62390
|
+
fetchZenModels2()
|
|
62391
|
+
]);
|
|
62007
62392
|
if (!forceUpdate && existsSync9(ALL_MODELS_JSON_PATH2)) {
|
|
62008
62393
|
try {
|
|
62009
62394
|
const cacheData = JSON.parse(readFileSync7(ALL_MODELS_JSON_PATH2, "utf-8"));
|
|
@@ -62037,17 +62422,19 @@ async function printAllModels(jsonOutput, forceUpdate) {
|
|
|
62037
62422
|
}
|
|
62038
62423
|
}
|
|
62039
62424
|
if (jsonOutput) {
|
|
62040
|
-
const allModels = [...ollamaModels, ...models];
|
|
62425
|
+
const allModels = [...ollamaModels, ...zenModels, ...models];
|
|
62041
62426
|
console.log(JSON.stringify({
|
|
62042
62427
|
count: allModels.length,
|
|
62043
62428
|
localCount: ollamaModels.length,
|
|
62429
|
+
zenCount: zenModels.length,
|
|
62044
62430
|
lastUpdated: new Date().toISOString().split("T")[0],
|
|
62045
62431
|
models: allModels.map((m) => ({
|
|
62046
62432
|
id: m.id,
|
|
62047
62433
|
name: m.name,
|
|
62048
62434
|
context: m.context_length || m.top_provider?.context_length,
|
|
62049
62435
|
pricing: m.pricing,
|
|
62050
|
-
isLocal: m.isLocal || false
|
|
62436
|
+
isLocal: m.isLocal || false,
|
|
62437
|
+
isZen: m.isZen || false
|
|
62051
62438
|
}))
|
|
62052
62439
|
}, null, 2));
|
|
62053
62440
|
return;
|
|
@@ -62089,6 +62476,35 @@ async function printAllModels(jsonOutput, forceUpdate) {
|
|
|
62089
62476
|
console.log(" Start Ollama: ollama serve");
|
|
62090
62477
|
console.log(" Pull a model: ollama pull llama3.2");
|
|
62091
62478
|
}
|
|
62479
|
+
if (zenModels.length > 0) {
|
|
62480
|
+
const freeCount = zenModels.filter((m) => m.isFree).length;
|
|
62481
|
+
console.log(`
|
|
62482
|
+
\uD83D\uDD2E OPENCODE ZEN (${zenModels.length} models, ${freeCount} FREE - no API key needed):
|
|
62483
|
+
`);
|
|
62484
|
+
console.log(" Model Context Pricing Tools");
|
|
62485
|
+
console.log(" " + "─".repeat(68));
|
|
62486
|
+
const sortedModels = [...zenModels].sort((a, b) => {
|
|
62487
|
+
if (a.isFree && !b.isFree)
|
|
62488
|
+
return -1;
|
|
62489
|
+
if (!a.isFree && b.isFree)
|
|
62490
|
+
return 1;
|
|
62491
|
+
return (b.context_length || 0) - (a.context_length || 0);
|
|
62492
|
+
});
|
|
62493
|
+
for (const model of sortedModels) {
|
|
62494
|
+
const modelId = model.id.length > 30 ? model.id.substring(0, 27) + "..." : model.id;
|
|
62495
|
+
const modelIdPadded = modelId.padEnd(32);
|
|
62496
|
+
const contextLen = model.context_length || 0;
|
|
62497
|
+
const context = contextLen > 0 ? `${Math.round(contextLen / 1000)}K` : "N/A";
|
|
62498
|
+
const contextPadded = context.padEnd(10);
|
|
62499
|
+
const pricing = model.isFree ? `${GREEN2}FREE${RESET2}` : `$${(parseFloat(model.pricing?.prompt || "0") + parseFloat(model.pricing?.completion || "0")).toFixed(1)}/M`;
|
|
62500
|
+
const pricingPadded = model.isFree ? "FREE " : pricing.padEnd(12);
|
|
62501
|
+
const tools = model.supportsTools ? `${GREEN2}✓${RESET2}` : `${RED}✗${RESET2}`;
|
|
62502
|
+
console.log(` ${modelIdPadded} ${contextPadded} ${pricingPadded} ${tools}`);
|
|
62503
|
+
}
|
|
62504
|
+
console.log("");
|
|
62505
|
+
console.log(` ${DIM2}FREE models work without API key!${RESET2}`);
|
|
62506
|
+
console.log(" Use: claudish --model zen/<model-id>");
|
|
62507
|
+
}
|
|
62092
62508
|
const byProvider = new Map;
|
|
62093
62509
|
for (const model of models) {
|
|
62094
62510
|
const provider = model.id.split("/")[0];
|
|
@@ -62290,6 +62706,7 @@ MODEL ROUTING (prefix-based):
|
|
|
62290
62706
|
kimi/, moonshot/ Kimi Direct API claudish --model kimi/kimi-k2-thinking-turbo "task"
|
|
62291
62707
|
glm/, zhipu/ GLM Direct API claudish --model glm/glm-4.7 "task"
|
|
62292
62708
|
oc/ OllamaCloud claudish --model oc/gpt-oss:20b "task"
|
|
62709
|
+
zen/ OpenCode Zen (free) claudish --model zen/grok-code "task"
|
|
62293
62710
|
ollama/ Ollama (local) claudish --model ollama/llama3.2 "task"
|
|
62294
62711
|
lmstudio/ LM Studio (local) claudish --model lmstudio/qwen "task"
|
|
62295
62712
|
vllm/ vLLM (local) claudish --model vllm/model "task"
|
|
@@ -62314,7 +62731,7 @@ OPTIONS:
|
|
|
62314
62731
|
--cost-tracker Enable cost tracking for API usage (NB!)
|
|
62315
62732
|
--audit-costs Show cost analysis report
|
|
62316
62733
|
--reset-costs Reset accumulated cost statistics
|
|
62317
|
-
--models List ALL OpenRouter
|
|
62734
|
+
--models List ALL models (OpenRouter + OpenCode Zen + Ollama)
|
|
62318
62735
|
--models <query> Fuzzy search all models by name, ID, or description
|
|
62319
62736
|
--top-models List recommended/top programming models (curated)
|
|
62320
62737
|
--json Output in JSON format (use with --models or --top-models)
|
|
@@ -62379,6 +62796,7 @@ ENVIRONMENT VARIABLES:
|
|
|
62379
62796
|
ZHIPU_API_KEY GLM/Zhipu API key (for glm/, zhipu/ prefix)
|
|
62380
62797
|
GLM_API_KEY Alias for ZHIPU_API_KEY
|
|
62381
62798
|
OLLAMA_API_KEY OllamaCloud API key (for oc/ prefix)
|
|
62799
|
+
OPENCODE_API_KEY OpenCode Zen API key (optional - free models work without it)
|
|
62382
62800
|
ANTHROPIC_API_KEY Placeholder (prevents Claude Code dialog)
|
|
62383
62801
|
ANTHROPIC_AUTH_TOKEN Placeholder (prevents Claude Code login screen)
|
|
62384
62802
|
|
|
@@ -62391,6 +62809,7 @@ ENVIRONMENT VARIABLES:
|
|
|
62391
62809
|
ZHIPU_BASE_URL Custom GLM/Zhipu endpoint
|
|
62392
62810
|
GLM_BASE_URL Alias for ZHIPU_BASE_URL
|
|
62393
62811
|
OLLAMACLOUD_BASE_URL Custom OllamaCloud endpoint (default: https://ollama.com)
|
|
62812
|
+
OPENCODE_BASE_URL Custom OpenCode Zen endpoint (default: https://opencode.ai/zen)
|
|
62394
62813
|
|
|
62395
62814
|
Local providers:
|
|
62396
62815
|
OLLAMA_BASE_URL Ollama server (default: http://localhost:11434)
|
|
@@ -62451,6 +62870,11 @@ EXAMPLES:
|
|
|
62451
62870
|
claudish --model glm/glm-4.7 "code generation"
|
|
62452
62871
|
claudish --model zhipu/glm-4-plus "complex task"
|
|
62453
62872
|
|
|
62873
|
+
# OpenCode Zen (free models)
|
|
62874
|
+
claudish --model zen/grok-code "implement feature"
|
|
62875
|
+
claudish --model zen/glm-4.7-free "code review"
|
|
62876
|
+
claudish --model zen/minimax-m2.1-free "complex task"
|
|
62877
|
+
|
|
62454
62878
|
# Local models (free, private)
|
|
62455
62879
|
claudish --model ollama/llama3.2 "code review"
|
|
62456
62880
|
claudish --model lmstudio/qwen2.5-coder "refactor"
|
|
@@ -62503,10 +62927,11 @@ LOCAL MODELS (Ollama, LM Studio, vLLM):
|
|
|
62503
62927
|
OLLAMA_HOST=http://192.168.1.50:11434 claudish --model ollama/llama3.2 "task"
|
|
62504
62928
|
|
|
62505
62929
|
AVAILABLE MODELS:
|
|
62506
|
-
List all models: claudish --models
|
|
62930
|
+
List all models: claudish --models (includes OpenRouter, OpenCode Zen, Ollama)
|
|
62507
62931
|
Search models: claudish --models <query>
|
|
62508
62932
|
Top recommended: claudish --top-models
|
|
62509
|
-
|
|
62933
|
+
Free models only: claudish --free (interactive selector with free models)
|
|
62934
|
+
JSON output: claudish --models --json
|
|
62510
62935
|
Force cache update: claudish --models --force-update
|
|
62511
62936
|
(Cache auto-updates every 2 days)
|
|
62512
62937
|
|
|
@@ -62684,7 +63109,37 @@ function printAvailableModelsJSON() {
|
|
|
62684
63109
|
console.log(JSON.stringify(output, null, 2));
|
|
62685
63110
|
}
|
|
62686
63111
|
}
|
|
62687
|
-
|
|
63112
|
+
async function fetchZenModels2() {
|
|
63113
|
+
try {
|
|
63114
|
+
const response = await fetch("https://models.dev/api.json", {
|
|
63115
|
+
signal: AbortSignal.timeout(1e4)
|
|
63116
|
+
});
|
|
63117
|
+
if (!response.ok) {
|
|
63118
|
+
return [];
|
|
63119
|
+
}
|
|
63120
|
+
const data = await response.json();
|
|
63121
|
+
const opencode = data.opencode;
|
|
63122
|
+
if (!opencode?.models)
|
|
63123
|
+
return [];
|
|
63124
|
+
return Object.entries(opencode.models).map(([id, m]) => {
|
|
63125
|
+
const isFree = m.cost?.input === 0 && m.cost?.output === 0;
|
|
63126
|
+
return {
|
|
63127
|
+
id: `zen/${id}`,
|
|
63128
|
+
name: m.name || id,
|
|
63129
|
+
context_length: m.limit?.context || 128000,
|
|
63130
|
+
max_output: m.limit?.output || 32000,
|
|
63131
|
+
pricing: isFree ? { prompt: "0", completion: "0" } : { prompt: String(m.cost?.input || 0), completion: String(m.cost?.output || 0) },
|
|
63132
|
+
isZen: true,
|
|
63133
|
+
isFree,
|
|
63134
|
+
supportsTools: m.tool_call || false,
|
|
63135
|
+
supportsReasoning: m.reasoning || false
|
|
63136
|
+
};
|
|
63137
|
+
});
|
|
63138
|
+
} catch {
|
|
63139
|
+
return [];
|
|
63140
|
+
}
|
|
63141
|
+
}
|
|
63142
|
+
var __filename6, __dirname6, VERSION = "3.8.0", CACHE_MAX_AGE_DAYS3 = 2, MODELS_JSON_PATH, ALL_MODELS_JSON_PATH2;
|
|
62688
63143
|
var init_cli = __esm(() => {
|
|
62689
63144
|
init_dist3();
|
|
62690
63145
|
init_model_loader2();
|