easyrouter-config 1.0.4 → 1.0.5

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 +67 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16,11 +16,11 @@ import {
16
16
  import { execa } from "execa";
17
17
  import pc from "picocolors";
18
18
  import { parseArgs } from "util";
19
- var VERSION = true ? "1.0.4" : "0.0.0-dev";
19
+ var VERSION = true ? "1.0.5" : "0.0.0-dev";
20
20
  var PRIMARY_HOST = "https://easyrouter.io";
21
21
  var FALLBACK_HOSTS = ["https://ezr.sh"];
22
22
  var PROBE_TIMEOUT_MS = 5e3;
23
- var CODEX_DEFAULT_MODEL = "gpt-5-codex";
23
+ var CODEX_FALLBACK_MODEL = "gpt-5.5";
24
24
  function parseCliArgs() {
25
25
  try {
26
26
  const { values } = parseArgs({
@@ -104,6 +104,61 @@ async function probeHost(host) {
104
104
  return false;
105
105
  }
106
106
  }
107
+ var FETCH_MODELS_TIMEOUT_MS = 8e3;
108
+ async function fetchEasyRouterModels(host, verbose) {
109
+ const url = host.replace(/\/+$/, "") + "/api/pricing";
110
+ try {
111
+ const ctrl = new AbortController();
112
+ const timer = setTimeout(() => ctrl.abort(), FETCH_MODELS_TIMEOUT_MS);
113
+ const res = await fetch(url, { signal: ctrl.signal });
114
+ clearTimeout(timer);
115
+ if (!res.ok) {
116
+ if (verbose) log.warn(`\u62C9\u53D6\u6A21\u578B\u5217\u8868\u5931\u8D25\uFF1AHTTP ${res.status}\uFF08\u5C06\u4F7F\u7528\u515C\u5E95\u6A21\u578B\uFF09`);
117
+ return [];
118
+ }
119
+ const json = await res.json();
120
+ const models = Array.isArray(json?.data) ? json.data : [];
121
+ if (verbose) log.info(`\u2713 \u62C9\u53D6\u5230 ${models.length} \u4E2A EasyRouter \u6A21\u578B`);
122
+ return models;
123
+ } catch (err) {
124
+ if (verbose) log.warn(`\u62C9\u53D6\u6A21\u578B\u5217\u8868\u5F02\u5E38\uFF1A${err?.message || err}\uFF08\u5C06\u4F7F\u7528\u515C\u5E95\u6A21\u578B\uFF09`);
125
+ return [];
126
+ }
127
+ }
128
+ function pickBestCodexModel(models) {
129
+ const isLlm = (m) => (m.tags || "").toUpperCase().includes("LLM");
130
+ const supportsOpenAI = (m) => !m.supported_endpoint_types || m.supported_endpoint_types.includes("openai");
131
+ const isGpt5 = (m) => /^gpt-5(\b|[.\-])/i.test(m.model_name);
132
+ const isSmallTier = (m) => /(^|[\-_.])(mini|nano|small|lite|tiny)([\-_.]|$)/i.test(m.model_name);
133
+ const byNameDesc = (a, b) => b.model_name.localeCompare(a.model_name, "en");
134
+ const gpt5Big = models.filter((m) => isGpt5(m) && isLlm(m) && supportsOpenAI(m) && !isSmallTier(m)).sort(byNameDesc);
135
+ if (gpt5Big.length) return gpt5Big[0].model_name;
136
+ const gpt5Any = models.filter((m) => isGpt5(m) && isLlm(m) && supportsOpenAI(m)).sort(byNameDesc);
137
+ if (gpt5Any.length) return gpt5Any[0].model_name;
138
+ const anyBig = models.filter((m) => isLlm(m) && supportsOpenAI(m) && !isSmallTier(m)).sort(byNameDesc);
139
+ if (anyBig.length) return anyBig[0].model_name;
140
+ return null;
141
+ }
142
+ async function resolveCodexModel(host, interactive, verbose) {
143
+ const models = await fetchEasyRouterModels(host, verbose);
144
+ const auto = pickBestCodexModel(models);
145
+ if (auto) {
146
+ if (verbose) log.info(`\u81EA\u52A8\u9009\u62E9 Codex \u6A21\u578B\uFF1A${pc.cyan(auto)}`);
147
+ return auto;
148
+ }
149
+ if (interactive && models.length) {
150
+ const candidates = models.filter((m) => /^gpt-5|codex/i.test(m.model_name)).sort((a, b) => b.model_name.localeCompare(a.model_name, "en")).slice(0, 8);
151
+ if (candidates.length) {
152
+ const picked = await select({
153
+ message: "\u672A\u81EA\u52A8\u8BC6\u522B Codex \u63A8\u8350\u6A21\u578B\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9",
154
+ options: candidates.map((m) => ({ value: m.model_name, label: m.model_name }))
155
+ });
156
+ if (!isCancel(picked)) return picked;
157
+ }
158
+ }
159
+ if (verbose) log.warn(`\u65E0\u6CD5\u81EA\u52A8\u9009\u62E9 Codex \u6A21\u578B\uFF0C\u4F7F\u7528\u515C\u5E95\uFF1A${CODEX_FALLBACK_MODEL}`);
160
+ return CODEX_FALLBACK_MODEL;
161
+ }
107
162
  var IDLE_TIMEOUT_MS = 6e4;
108
163
  var HARD_TIMEOUT_MS = 3e5;
109
164
  async function configureViaZcf(opts) {
@@ -137,7 +192,7 @@ async function configureViaZcf(opts) {
137
192
  "false"
138
193
  ];
139
194
  if (opts.target === "codex") {
140
- args.push("--api-model", CODEX_DEFAULT_MODEL);
195
+ args.push("--api-model", opts.apiModel || CODEX_FALLBACK_MODEL);
141
196
  }
142
197
  const isWin = process.platform === "win32";
143
198
  const cmd = isWin ? "npx.cmd" : "npx";
@@ -368,6 +423,12 @@ async function main() {
368
423
  }
369
424
  }
370
425
  if (configCodex) {
426
+ const codexModel = await resolveCodexModel(
427
+ selectedHost,
428
+ /* interactive */
429
+ !args.apiKey,
430
+ verbose
431
+ );
371
432
  const useSpinner = !verbose && process.stdout.isTTY;
372
433
  const s = useSpinner ? spinner() : null;
373
434
  s?.start("\u914D\u7F6E Codex");
@@ -377,11 +438,12 @@ async function main() {
377
438
  target: "codex",
378
439
  baseUrl: codexBaseUrl,
379
440
  apiKey,
441
+ apiModel: codexModel,
380
442
  verbose,
381
443
  onProgress: (line) => s?.message(`\u914D\u7F6E Codex \xB7 ${pc.dim(line)}`)
382
444
  });
383
- s?.stop(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 ") + pc.dim("\u2192 ~/.codex/config.toml + auth.json"));
384
- if (!s) log.success(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 \u2192 ~/.codex/config.toml + auth.json"));
445
+ s?.stop(pc.green("\u2713 Codex \u914D\u7F6E\u5B8C\u6210 ") + pc.dim(`\u2192 model=${codexModel}`));
446
+ if (!s) log.success(pc.green(`\u2713 Codex \u914D\u7F6E\u5B8C\u6210 \u2192 model=${codexModel}`));
385
447
  } catch (err) {
386
448
  s?.stop(pc.red("\u2717 Codex \u914D\u7F6E\u5931\u8D25"));
387
449
  console.error(err.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "easyrouter-config",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "🚀 一键把 EasyRouter 接入 Claude Code & Codex —— 粘贴 Key 即用",
5
5
  "type": "module",
6
6
  "bin": {