@yhong91/cpac 0.1.8 → 0.1.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/README.md CHANGED
@@ -134,14 +134,18 @@ CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST=1
134
134
 
135
135
  代理开启 Claude Code 的 gateway model discovery:`/model` 选择器会列出 CPA 目录里的全部模型。claude 系模型直接使用原 id;其它模型以 `claude-cpac--<模型名>` 别名出现(例如 `claude-cpac--gpt-5.6-sol`),请求发出时由代理还原为真实模型名。
136
136
 
137
- 启动时还会把模型槽位(`ANTHROPIC_DEFAULT_OPUS/SONNET/HAIKU_MODEL` 及 `ANTHROPIC_SMALL_FAST_MODEL`)指向目录里的模型,避免 haiku/subagent 任务打到网关不认识的默认 id。默认自动选择(claude 系优先:最大窗口给 opus/sonnet,最小窗口给 haiku),可用命令覆盖:
137
+ 启动时还会把模型槽位(`ANTHROPIC_DEFAULT_OPUS/SONNET/HAIKU_MODEL` 及 `ANTHROPIC_SMALL_FAST_MODEL`)指向目录里的模型,避免 haiku/subagent 任务打到网关不认识的默认 id。默认自动选择(claude 系优先:按家族名匹配 opus/sonnet/haiku,否则最大窗口给 opus/sonnet、最小窗口给 haiku),可用命令覆盖:
138
138
 
139
139
  ```sh
140
140
  cpac claude-models # 查看当前覆盖
141
141
  cpac claude-models --opus claude-opus-4-8 --haiku glm-5.3 # 设置/合并
142
+ cpac claude-models --haiku # 不带值:arrow-key 下拉选择
143
+ cpac claude-models --classifier claude-sonnet-4-6 # Auto mode 审批分类器模型
142
144
  cpac claude-models --reset # 恢复自动选择
143
145
  ```
144
146
 
147
+ 所有设定写入配置文件持久缓存,每次 `cpac claude` 启动自动生效。`--classifier` 对应 `CLAUDE_CODE_AUTO_MODE_MODEL`(Auto mode 的审批分类器,默认跟随 Claude Code 内置选择);不设置则不注入。
148
+
145
149
  你自己 export 同名环境变量时以你为准。
146
150
 
147
151
  Claude Code 的发现协议不携带 context 信息,claude 前缀的未知模型默认按 200K 记账,且 `CLAUDE_CODE_MAX_CONTEXT_TOKENS` 对 claude 前缀的名字不生效。cpac 采用与 opencodex 相同的机制:目录里窗口足够大的别名模型会带 `[1m]` 标记出现在选择器中(Claude Code 按 1M 记账,发请求前自己剥掉标记),同时 `cpac claude` 注入 `CLAUDE_CODE_AUTO_COMPACT_WINDOW`(默认 350K,与 opencodex 一致,接受范围 100K–1M)作为压缩阈值。你自己 export 该变量时以你为准。claude 系模型如需 1M 窗口,在模型名后加 `[1m]`(如 `cpac claude --model claude-sonnet-4-6[1m]`)。
package/cpac.example.json CHANGED
@@ -5,7 +5,8 @@
5
5
  "claude_models": {
6
6
  "opus": "claude-opus-4-8",
7
7
  "sonnet": "claude-sonnet-4-6",
8
- "haiku": "glm-5.3"
8
+ "haiku": "glm-5.3",
9
+ "classifier": "claude-sonnet-4-6"
9
10
  },
10
11
  "spawn_models": ["gpt-5.5", "claude-opus-4-8"]
11
12
  }
package/dist/cpac.js CHANGED
@@ -25,7 +25,7 @@ const CONFIG_KEYS = new Set([
25
25
  // Codex advertises only the first 5 picker-visible catalog models as
26
26
  // spawn_agent overrides (codex-rs MAX_SPAWN_AGENT_MODEL_OVERRIDES).
27
27
  const MAX_SPAWN_MODELS = 5;
28
- const CLAUDE_SLOT_KEYS = ["opus", "sonnet", "haiku"];
28
+ const CLAUDE_SLOT_KEYS = ["opus", "sonnet", "haiku", "classifier"];
29
29
  const REQUIRED_STATE_KEYS = new Set([
30
30
  "config_path",
31
31
  "config_existed",
@@ -343,6 +343,12 @@ async function checkboxPicker(title, items, max) {
343
343
  else if (key === "\x1b[B")
344
344
  cursor = (cursor + 1) % items.length;
345
345
  else if (key === " ") {
346
+ if (max === 1) {
347
+ selected.clear();
348
+ selected.add(cursor);
349
+ finish();
350
+ return;
351
+ }
346
352
  if (selected.has(cursor))
347
353
  selected.delete(cursor);
348
354
  else if (selected.size < max)
@@ -768,8 +774,10 @@ function shouldMarkOneMillion(id, window, compactWindow) {
768
774
  return true;
769
775
  return compactWindow !== undefined && window > 200_000 && window >= compactWindow;
770
776
  }
771
- // Auto tier defaults: prefer claude-family models; biggest window serves
772
- // opus/sonnet, smallest known window serves haiku.
777
+ // Auto tier defaults: prefer claude-family models; within the pool match by
778
+ // family keyword first (opus/sonnet/haiku) so equal context windows don't
779
+ // collapse every slot onto the first catalog entry, then fall back to
780
+ // biggest-window (opus/sonnet) and smallest-window (haiku).
773
781
  function defaultClaudeSlots(models) {
774
782
  if (!models.length)
775
783
  return undefined;
@@ -779,10 +787,11 @@ function defaultClaudeSlots(models) {
779
787
  const smallest = pool
780
788
  .filter((model) => model.window > 0)
781
789
  .sort((a, b) => a.window - b.window)[0];
790
+ const family = (keyword) => pool.find((model) => model.id.includes(keyword));
782
791
  return {
783
- opus: biggest.id,
784
- sonnet: biggest.id,
785
- haiku: (smallest ?? biggest).id,
792
+ opus: (family("opus") ?? biggest).id,
793
+ sonnet: (family("sonnet") ?? biggest).id,
794
+ haiku: (family("haiku") ?? smallest ?? biggest).id,
786
795
  };
787
796
  }
788
797
  // Resolve the tier-model env values: config overrides win over catalog
@@ -1418,7 +1427,22 @@ async function guide(config) {
1418
1427
  console.log(`\nSaved ${config.api_key_env} to ${profile}. Open a new terminal or run:\n source ${profile}`);
1419
1428
  return 0;
1420
1429
  }
1421
- export function runClaudeModels(parsed) {
1430
+ export async function runClaudeModels(parsed) {
1431
+ if (parsed.pick.length) {
1432
+ const config = loadConfig(parsed.configPath);
1433
+ const apiKey = process.env[config.api_key_env]?.trim();
1434
+ if (!apiKey)
1435
+ throw new CPACError(`environment variable ${config.api_key_env} is not set`);
1436
+ const catalog = await fetchCatalog(config.cpa_url, apiKey);
1437
+ const document = JSON.parse(new TextDecoder().decode(catalog.bytes));
1438
+ const slugs = document.models.map((model) => model.slug);
1439
+ for (const slot of parsed.pick) {
1440
+ const [picked] = await checkboxPicker(`Select ${slot} model`, slugs, 1);
1441
+ if (!picked)
1442
+ throw new CPACError("no model selected");
1443
+ parsed.models[slot] = picked;
1444
+ }
1445
+ }
1422
1446
  if (!parsed.reset && !Object.keys(parsed.models).length) {
1423
1447
  const config = loadConfig(parsed.configPath);
1424
1448
  console.log(config.claude_models
@@ -1499,18 +1523,20 @@ export async function runClaude(config, args, executable = "claude") {
1499
1523
  if (compactWindow !== undefined && !(Number(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW) > 0)) {
1500
1524
  env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = String(compactWindow);
1501
1525
  }
1526
+ const setSlot = (name, value) => {
1527
+ if (process.env[name] === undefined)
1528
+ env[name] = value;
1529
+ };
1502
1530
  if (slots) {
1503
1531
  // Route haiku/subagent tier traffic at catalog models instead of the
1504
1532
  // stock claude-haiku-* ids the gateway does not carry.
1505
- const setSlot = (name, value) => {
1506
- if (process.env[name] === undefined)
1507
- env[name] = value;
1508
- };
1509
1533
  setSlot("ANTHROPIC_DEFAULT_OPUS_MODEL", slots.opus);
1510
1534
  setSlot("ANTHROPIC_DEFAULT_SONNET_MODEL", slots.sonnet);
1511
1535
  setSlot("ANTHROPIC_DEFAULT_HAIKU_MODEL", slots.haiku);
1512
1536
  setSlot("ANTHROPIC_SMALL_FAST_MODEL", slots.haiku);
1513
1537
  }
1538
+ if (config.claude_models?.classifier)
1539
+ setSlot("CLAUDE_CODE_AUTO_MODE_MODEL", config.claude_models.classifier);
1514
1540
  delete env.ANTHROPIC_API_KEY;
1515
1541
  delete env.CLAUDE_CODE_USE_ANTHROPIC_AWS;
1516
1542
  delete env.CLAUDE_CODE_USE_BEDROCK;
@@ -1919,7 +1945,7 @@ function usage() {
1919
1945
  " cpac <inject|status|restore> [--config PATH]",
1920
1946
  " cpac proxy [--config PATH]",
1921
1947
  " cpac claude [--config PATH] [--] [claude args...]",
1922
- " cpac claude-models [--opus M] [--sonnet M] [--haiku M] [--reset] [--config PATH]",
1948
+ " cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--classifier [M]] [--reset] [--config PATH]",
1923
1949
  " cpac pi <install|uninstall|status> [--config PATH]",
1924
1950
  " cpac kimi <install|uninstall|status> [--config PATH]",
1925
1951
  " cpac detect [--json] [--home PATH]",
@@ -2009,8 +2035,27 @@ function parseArgs(args) {
2009
2035
  return { command: args[0], configPath, action };
2010
2036
  }
2011
2037
  if (args[0] === "claude-models") {
2038
+ if (args.includes("-h") || args.includes("--help")) {
2039
+ console.log([
2040
+ "Usage: cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--classifier [M]] [--reset] [--config PATH]",
2041
+ "",
2042
+ "Sets the model Claude Code uses per task tier. Saved to the config file",
2043
+ "and applied on every `cpac claude` launch:",
2044
+ " --opus heavy reasoning / planning fallback tier (ANTHROPIC_DEFAULT_OPUS_MODEL)",
2045
+ " --sonnet main conversation default tier (ANTHROPIC_DEFAULT_SONNET_MODEL)",
2046
+ " --haiku high-frequency small tasks: titles, summaries, background",
2047
+ " (ANTHROPIC_DEFAULT_HAIKU_MODEL + ANTHROPIC_SMALL_FAST_MODEL)",
2048
+ " --classifier auto-mode approval classifier (CLAUDE_CODE_AUTO_MODE_MODEL);",
2049
+ " unset follows Claude Code's built-in default",
2050
+ "",
2051
+ "A slot without a value opens an arrow-key picker over the CPA catalog;",
2052
+ "--reset clears all overrides back to catalog auto-selection.",
2053
+ ].join("\n"));
2054
+ return null;
2055
+ }
2012
2056
  let configPath = defaultConfigPath();
2013
2057
  const models = {};
2058
+ const pick = [];
2014
2059
  let reset = false;
2015
2060
  for (let index = 1; index < args.length; index += 1) {
2016
2061
  const arg = args[index];
@@ -2023,17 +2068,27 @@ function parseArgs(args) {
2023
2068
  else if (arg === "--reset") {
2024
2069
  reset = true;
2025
2070
  }
2026
- else if (arg === "--opus" || arg === "--sonnet" || arg === "--haiku") {
2027
- const value = args[++index];
2028
- if (typeof value !== "string" || !value.trim())
2029
- throw new CPACError(`${arg} requires a model id`);
2030
- models[arg.slice(2)] = value.trim();
2071
+ else if (arg === "--opus" ||
2072
+ arg === "--sonnet" ||
2073
+ arg === "--haiku" ||
2074
+ arg === "--classifier") {
2075
+ const slot = arg.slice(2);
2076
+ const value = args[index + 1];
2077
+ if (value !== undefined && !value.startsWith("-")) {
2078
+ index += 1;
2079
+ if (!value.trim())
2080
+ throw new CPACError(`${arg} requires a model id`);
2081
+ models[slot] = value.trim();
2082
+ }
2083
+ else {
2084
+ pick.push(slot);
2085
+ }
2031
2086
  }
2032
2087
  else {
2033
2088
  throw new CPACError(`unknown option: ${arg}`);
2034
2089
  }
2035
2090
  }
2036
- return { command: "claude-models", configPath, models, reset };
2091
+ return { command: "claude-models", configPath, models, pick, reset };
2037
2092
  }
2038
2093
  if (args[0] === "claude") {
2039
2094
  let configPath = defaultConfigPath();
@@ -2124,7 +2179,7 @@ export async function main(args = process.argv.slice(2)) {
2124
2179
  if (parsed.command === "claude")
2125
2180
  return await runClaude(config, parsed.args);
2126
2181
  if (parsed.command === "claude-models")
2127
- return runClaudeModels(parsed);
2182
+ return await runClaudeModels(parsed);
2128
2183
  if (parsed.command === "proxy")
2129
2184
  return await runProxy(config);
2130
2185
  if (parsed.command === "pi")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {