easyrouter-config 1.0.6 → 1.0.8

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 +113 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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.6" : "0.0.0-dev";
23
+ var VERSION = true ? "1.0.8" : "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;
@@ -362,6 +362,72 @@ 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
+ 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) {
405
+ const ratio = m.model_ratio ?? 0;
406
+ const compRatio = m.completion_ratio ?? 1;
407
+ const cacheRatio = m.cache_ratio ?? 0;
408
+ const input = round6(ratio * 2e-3);
409
+ const output = round6(ratio * compRatio * 2e-3);
410
+ const cacheRead = round6(ratio * cacheRatio * 2e-3);
411
+ const cacheWrite = round6(input * 1.25);
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
+ }
422
+ return {
423
+ cost: { input, output, cacheRead, cacheWrite },
424
+ contextWindow,
425
+ maxTokens
426
+ };
427
+ }
428
+ function round6(n) {
429
+ return Math.round(n * 1e6) / 1e6;
430
+ }
365
431
  async function fetchEasyRouterModels(host, verbose) {
366
432
  const url = host.replace(/\/+$/, "") + "/api/pricing";
367
433
  try {
@@ -557,7 +623,8 @@ var openClawClient = {
557
623
  await runOpenClawSubprocess(args, ctx);
558
624
  let registered = 0;
559
625
  if (llms.length > 1) {
560
- registered = await patchOpenClawJson(llms, defaultModel, ctx);
626
+ const orContextMap = await getOpenRouterContextMap(ctx.verbose);
627
+ registered = await patchOpenClawJson(llms, defaultModel, ctx, orContextMap);
561
628
  }
562
629
  return {
563
630
  configPaths: ["~/.openclaw/openclaw.json"],
@@ -565,7 +632,7 @@ var openClawClient = {
565
632
  };
566
633
  }
567
634
  };
568
- async function patchOpenClawJson(llms, defaultModel, ctx) {
635
+ async function patchOpenClawJson(llms, defaultModel, ctx, orContextMap) {
569
636
  const configPath = join(homedir(), ".openclaw", "openclaw.json");
570
637
  let json;
571
638
  try {
@@ -595,27 +662,50 @@ async function patchOpenClawJson(llms, defaultModel, ctx) {
595
662
  }
596
663
  return 1;
597
664
  }
598
- const existingIds = new Set(
599
- provider.models.map((m) => m.id)
600
- );
665
+ const existingById = /* @__PURE__ */ new Map();
666
+ provider.models.forEach((m, i) => {
667
+ existingById.set(m.id, i);
668
+ });
601
669
  const before = provider.models.length;
670
+ let updated = 0;
671
+ const agentsModels = json?.agents?.defaults?.models ?? null;
602
672
  for (const m of llms) {
603
- if (existingIds.has(m.model_name)) continue;
604
- provider.models.push({
605
- id: m.model_name,
606
- name: m.model_name + " (EasyRouter)",
607
- contextWindow: 16e3,
608
- // OpenClaw 默认值;用户可自行编辑
609
- maxTokens: 4096,
610
- input: ["text"],
611
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
612
- reasoning: false
613
- });
673
+ const { cost, contextWindow, maxTokens } = toOpenClawCost(m, orContextMap);
674
+ const idx = existingById.get(m.model_name);
675
+ if (idx !== void 0) {
676
+ const existing = provider.models[idx];
677
+ const costChanged = existing.cost?.input !== cost.input || existing.cost?.output !== cost.output;
678
+ const ctxChanged = existing.contextWindow !== contextWindow;
679
+ if (costChanged || ctxChanged) {
680
+ existing.cost = cost;
681
+ existing.contextWindow = contextWindow;
682
+ existing.maxTokens = maxTokens;
683
+ updated++;
684
+ }
685
+ } else {
686
+ provider.models.push({
687
+ id: m.model_name,
688
+ name: m.model_name + " (EasyRouter)",
689
+ contextWindow,
690
+ maxTokens,
691
+ input: ["text"],
692
+ cost,
693
+ reasoning: false
694
+ });
695
+ }
696
+ if (agentsModels !== null) {
697
+ const agentKey = `${OPENCLAW_PROVIDER_ID}/${m.model_name}`;
698
+ const existing = agentsModels[agentKey];
699
+ if (!existing || Object.keys(existing).length === 0) {
700
+ agentsModels[agentKey] = { alias: agentKey };
701
+ }
702
+ }
614
703
  }
615
- if (provider.models.length === before) {
704
+ const added = provider.models.length - before;
705
+ if (added === 0 && updated === 0) {
616
706
  if (ctx.verbose) {
617
707
  console.warn(
618
- `[openclaw] \u5DF2\u6CE8\u518C\u8FC7\u6240\u6709 ${llms.length} \u4E2A\u6A21\u578B\uFF0C\u8DF3\u8FC7 patch`
708
+ `[openclaw] \u6240\u6709 ${llms.length} \u4E2A\u6A21\u578B\u5747\u5DF2\u662F\u6700\u65B0\uFF0C\u8DF3\u8FC7\u5199\u5165`
619
709
  );
620
710
  }
621
711
  return provider.models.length;
@@ -623,8 +713,11 @@ async function patchOpenClawJson(llms, defaultModel, ctx) {
623
713
  await fs.copyFile(configPath, configPath + ".bak." + Date.now());
624
714
  await atomicWriteJson(configPath, json);
625
715
  if (ctx.verbose) {
716
+ const parts = [];
717
+ if (added > 0) parts.push(`\u65B0\u589E ${added} \u4E2A`);
718
+ if (updated > 0) parts.push(`\u4FEE\u6B63 cost/ctx ${updated} \u4E2A`);
626
719
  console.warn(
627
- `[openclaw] \u2713 \u6CE8\u518C ${provider.models.length - before} \u4E2A\u65B0\u6A21\u578B\uFF0C\u5171 ${provider.models.length} \u4E2A\uFF08\u9ED8\u8BA4\uFF1A${defaultModel}\uFF09`
720
+ `[openclaw] \u2713 ${parts.join("\uFF0C")}\uFF0C\u5171 ${provider.models.length} \u4E2A\uFF08\u9ED8\u8BA4\uFF1A${defaultModel}\uFF09`
628
721
  );
629
722
  }
630
723
  return provider.models.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easyrouter-config",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "🚀 一键把 EasyRouter 接入 Claude Code & Codex —— 粘贴 Key 即用",
5
5
  "type": "module",
6
6
  "bin": {