easyrouter-config 1.0.7 → 1.0.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 +71 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
import {
|
|
5
5
|
intro,
|
|
6
6
|
outro,
|
|
7
|
-
text,
|
|
8
7
|
multiselect,
|
|
9
8
|
confirm,
|
|
10
9
|
spinner,
|
|
11
10
|
isCancel as isCancel2,
|
|
12
11
|
cancel,
|
|
13
12
|
log as log3,
|
|
14
|
-
note
|
|
13
|
+
note,
|
|
14
|
+
password
|
|
15
15
|
} from "@clack/prompts";
|
|
16
16
|
import pc6 from "picocolors";
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ import { parseArgs } from "util";
|
|
|
20
20
|
import pc from "picocolors";
|
|
21
21
|
|
|
22
22
|
// src/config/constants.ts
|
|
23
|
-
var VERSION = true ? "1.0.
|
|
23
|
+
var VERSION = true ? "1.0.9" : "0.0.0-dev";
|
|
24
24
|
var PRIMARY_HOST = "https://easyrouter.io";
|
|
25
25
|
var FALLBACK_HOSTS = ["https://ezr.sh"];
|
|
26
26
|
var HOST_PROBE_TIMEOUT_MS = 5e3;
|
|
@@ -250,21 +250,21 @@ async function runZcf(opts) {
|
|
|
250
250
|
windowsHide: true
|
|
251
251
|
});
|
|
252
252
|
const handleChunk = (chunk, target) => {
|
|
253
|
-
const
|
|
254
|
-
if (target === "out") capturedOut +=
|
|
255
|
-
else capturedErr +=
|
|
253
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
254
|
+
if (target === "out") capturedOut += text;
|
|
255
|
+
else capturedErr += text;
|
|
256
256
|
lastDataAt = Date.now();
|
|
257
257
|
if (opts.onProgress) {
|
|
258
258
|
const now = Date.now();
|
|
259
259
|
if (now - lastProgressAt >= PROGRESS_THROTTLE_MS) {
|
|
260
260
|
lastProgressAt = now;
|
|
261
|
-
const lines =
|
|
261
|
+
const lines = text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
262
262
|
const last = lines[lines.length - 1];
|
|
263
263
|
if (last) opts.onProgress(last.slice(0, 60));
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
if (opts.verbose) {
|
|
267
|
-
process.stderr.write(
|
|
267
|
+
process.stderr.write(text);
|
|
268
268
|
}
|
|
269
269
|
};
|
|
270
270
|
child.stdout?.on("data", (c) => handleChunk(c, "out"));
|
|
@@ -362,7 +362,46 @@ var claudeClient = {
|
|
|
362
362
|
// src/config/models.ts
|
|
363
363
|
import { log as log2, select, isCancel } from "@clack/prompts";
|
|
364
364
|
import pc4 from "picocolors";
|
|
365
|
-
function
|
|
365
|
+
async function fetchOpenRouterContextMap(verbose) {
|
|
366
|
+
const map = /* @__PURE__ */ new Map();
|
|
367
|
+
try {
|
|
368
|
+
const ctrl = new AbortController();
|
|
369
|
+
const timer = setTimeout(() => ctrl.abort(), FETCH_MODELS_TIMEOUT_MS);
|
|
370
|
+
const res = await fetch("https://openrouter.ai/api/v1/models", {
|
|
371
|
+
signal: ctrl.signal,
|
|
372
|
+
headers: { "User-Agent": "easyrouter-config/1.0" }
|
|
373
|
+
});
|
|
374
|
+
clearTimeout(timer);
|
|
375
|
+
if (!res.ok) return map;
|
|
376
|
+
const json = await res.json();
|
|
377
|
+
const models = Array.isArray(json?.data) ? json.data : [];
|
|
378
|
+
for (const m of models) {
|
|
379
|
+
const short = m.id.split("/").pop() ?? m.id;
|
|
380
|
+
const ctx = m.context_length ?? 0;
|
|
381
|
+
const maxOut = m.max_completion_tokens ?? Math.min(32768, Math.max(4096, Math.floor(ctx / 4)));
|
|
382
|
+
const entry = { contextLength: ctx, maxTokens: maxOut };
|
|
383
|
+
map.set(short.toLowerCase(), entry);
|
|
384
|
+
map.set(normalizeModelId(short), entry);
|
|
385
|
+
}
|
|
386
|
+
if (verbose) log2.info(`\u2713 OpenRouter: \u62C9\u53D6\u5230 ${models.length} \u4E2A\u6A21\u578B\u4E0A\u4E0B\u6587\u4FE1\u606F`);
|
|
387
|
+
} catch (err) {
|
|
388
|
+
if (verbose) log2.warn(`OpenRouter \u62C9\u53D6\u5931\u8D25\uFF1A${err?.message}\uFF08\u5C06\u7528\u515C\u5E95 contextWindow\uFF09`);
|
|
389
|
+
}
|
|
390
|
+
return map;
|
|
391
|
+
}
|
|
392
|
+
function normalizeModelId(s) {
|
|
393
|
+
s = s.replace(/-\d{6}$/, "");
|
|
394
|
+
s = s.replace(/(?<=[a-zA-Z])-(\d+)-(\d+)(?=-|$)/g, "-$1.$2");
|
|
395
|
+
return s.toLowerCase();
|
|
396
|
+
}
|
|
397
|
+
var _orContextMapPromise = null;
|
|
398
|
+
function getOpenRouterContextMap(verbose) {
|
|
399
|
+
if (!_orContextMapPromise) {
|
|
400
|
+
_orContextMapPromise = fetchOpenRouterContextMap(verbose);
|
|
401
|
+
}
|
|
402
|
+
return _orContextMapPromise;
|
|
403
|
+
}
|
|
404
|
+
function toOpenClawCost(m, orContextMap) {
|
|
366
405
|
const ratio = m.model_ratio ?? 0;
|
|
367
406
|
const compRatio = m.completion_ratio ?? 1;
|
|
368
407
|
const cacheRatio = m.cache_ratio ?? 0;
|
|
@@ -370,9 +409,16 @@ function toOpenClawCost(m) {
|
|
|
370
409
|
const output = round6(ratio * compRatio * 2e-3);
|
|
371
410
|
const cacheRead = round6(ratio * cacheRatio * 2e-3);
|
|
372
411
|
const cacheWrite = round6(input * 1.25);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
412
|
+
let contextWindow = 131072;
|
|
413
|
+
let maxTokens = 32768;
|
|
414
|
+
if (orContextMap) {
|
|
415
|
+
const name = m.model_name;
|
|
416
|
+
const hit = orContextMap.get(name.toLowerCase()) ?? orContextMap.get(normalizeModelId(name));
|
|
417
|
+
if (hit && hit.contextLength > 0) {
|
|
418
|
+
contextWindow = hit.contextLength;
|
|
419
|
+
maxTokens = hit.maxTokens;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
376
422
|
return {
|
|
377
423
|
cost: { input, output, cacheRead, cacheWrite },
|
|
378
424
|
contextWindow,
|
|
@@ -577,7 +623,8 @@ var openClawClient = {
|
|
|
577
623
|
await runOpenClawSubprocess(args, ctx);
|
|
578
624
|
let registered = 0;
|
|
579
625
|
if (llms.length > 1) {
|
|
580
|
-
|
|
626
|
+
const orContextMap = await getOpenRouterContextMap(ctx.verbose);
|
|
627
|
+
registered = await patchOpenClawJson(llms, defaultModel, ctx, orContextMap);
|
|
581
628
|
}
|
|
582
629
|
return {
|
|
583
630
|
configPaths: ["~/.openclaw/openclaw.json"],
|
|
@@ -585,7 +632,7 @@ var openClawClient = {
|
|
|
585
632
|
};
|
|
586
633
|
}
|
|
587
634
|
};
|
|
588
|
-
async function patchOpenClawJson(llms, defaultModel, ctx) {
|
|
635
|
+
async function patchOpenClawJson(llms, defaultModel, ctx, orContextMap) {
|
|
589
636
|
const configPath = join(homedir(), ".openclaw", "openclaw.json");
|
|
590
637
|
let json;
|
|
591
638
|
try {
|
|
@@ -623,11 +670,13 @@ async function patchOpenClawJson(llms, defaultModel, ctx) {
|
|
|
623
670
|
let updated = 0;
|
|
624
671
|
const agentsModels = json?.agents?.defaults?.models ?? null;
|
|
625
672
|
for (const m of llms) {
|
|
626
|
-
const { cost, contextWindow, maxTokens } = toOpenClawCost(m);
|
|
673
|
+
const { cost, contextWindow, maxTokens } = toOpenClawCost(m, orContextMap);
|
|
627
674
|
const idx = existingById.get(m.model_name);
|
|
628
675
|
if (idx !== void 0) {
|
|
629
676
|
const existing = provider.models[idx];
|
|
630
|
-
|
|
677
|
+
const costChanged = existing.cost?.input !== cost.input || existing.cost?.output !== cost.output;
|
|
678
|
+
const ctxChanged = existing.contextWindow !== contextWindow;
|
|
679
|
+
if (costChanged || ctxChanged) {
|
|
631
680
|
existing.cost = cost;
|
|
632
681
|
existing.contextWindow = contextWindow;
|
|
633
682
|
existing.maxTokens = maxTokens;
|
|
@@ -692,20 +741,20 @@ async function runOpenClawSubprocess(args, ctx) {
|
|
|
692
741
|
windowsHide: true
|
|
693
742
|
});
|
|
694
743
|
const handleChunk = (chunk, target) => {
|
|
695
|
-
const
|
|
696
|
-
if (target === "out") capturedOut +=
|
|
697
|
-
else capturedErr +=
|
|
744
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
745
|
+
if (target === "out") capturedOut += text;
|
|
746
|
+
else capturedErr += text;
|
|
698
747
|
lastDataAt = Date.now();
|
|
699
748
|
if (ctx.onProgress) {
|
|
700
749
|
const now = Date.now();
|
|
701
750
|
if (now - lastProgressAt >= PROGRESS_THROTTLE_MS) {
|
|
702
751
|
lastProgressAt = now;
|
|
703
|
-
const lines =
|
|
752
|
+
const lines = text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
704
753
|
const last = lines[lines.length - 1];
|
|
705
754
|
if (last) ctx.onProgress(last.slice(0, 60));
|
|
706
755
|
}
|
|
707
756
|
}
|
|
708
|
-
if (ctx.verbose) process.stderr.write(
|
|
757
|
+
if (ctx.verbose) process.stderr.write(text);
|
|
709
758
|
};
|
|
710
759
|
child.stdout?.on("data", (c) => handleChunk(c, "out"));
|
|
711
760
|
child.stderr?.on("data", (c) => handleChunk(c, "err"));
|
|
@@ -892,9 +941,8 @@ async function main() {
|
|
|
892
941
|
apiKey = normalizeApiKey(args.apiKey);
|
|
893
942
|
log3.info(`API Key: ${pc6.dim(maskKey(apiKey))}\uFF08\u6765\u81EA\u547D\u4EE4\u884C\u53C2\u6570\uFF09`);
|
|
894
943
|
} else {
|
|
895
|
-
const input = await
|
|
944
|
+
const input = await password({
|
|
896
945
|
message: "\u8BF7\u8F93\u5165\u4F60\u7684 EasyRouter API Key",
|
|
897
|
-
placeholder: "sk-xxxxxxxxxxxxxxxx",
|
|
898
946
|
validate(value) {
|
|
899
947
|
if (!value) return "\u4E0D\u80FD\u4E3A\u7A7A";
|
|
900
948
|
if (value.trim().length < 8) return "Key \u957F\u5EA6\u770B\u8D77\u6765\u4E0D\u5BF9";
|