@yhong91/cpac 0.1.15 → 0.1.17

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/cpac.js +56 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -259,6 +259,8 @@ Codex App/CLI 发给本地代理的 ChatGPT bearer 不会转发到 CPA;代理
259
259
 
260
260
  Codex App 的长驻 `app-server` 可能缓存旧目录。`inject` 会删除 `models_cache.json`;如果选择器仍未更新,请重启 Codex App。
261
261
 
262
+ 注入命令可选 `--max_context`(`cpac inject --max_context` 或 `cpac install --target codex --max_context`,默认不带):把 catalog 中 `max_context_window` 高于 `context_window` 的模型提升到上限,并把 `auto_compact_token_limit` 设为上限的 90%。Codex 把 `context_window` 当输入预算而非展示标签,上游默认保留保守运营值(如 GPT-5.6 家族 272k),带上该参数后可用到实测上限(约 921k,opencodex 实测);90% 压缩线确保在硬上限前触发 auto-compact。不带参数注入的仍是原本上下文的目录。该参数仅限 codex,与其他 target 组合使用会直接报错。
263
+
262
264
  第一次注入时,原文件按原始 bytes 备份到 `state_dir/config.toml.backup`。重复注入不会覆盖首次备份,并会保留 CPAC 管理字段之外的用户编辑。`restore` 按原 mode 逐字节恢复首次备份、关闭 CPAC loopback 代理;若原文件不存在,则删除注入创建的 `config.toml`。
263
265
 
264
266
  loopback 代理是 detached 用户进程,不安装系统服务。机器重启或进程意外退出后,`cpac status` 会报告 `loopback proxy stopped`;重新执行 `cpac inject` 即可恢复。
package/dist/cpac.js CHANGED
@@ -258,6 +258,29 @@ export async function fetchCatalog(cpaUrl, apiKey) {
258
258
  modelCount: models.length,
259
259
  };
260
260
  }
261
+ // Codex treats a catalog model's context_window as its input budget, not a
262
+ // display label; upstream keeps it a conservative operating cap while
263
+ // max_context_window holds the real ceiling (gpt-5.6: 272k vs ~921k, measured
264
+ // by opencodex). Opt-in lift: raise context_window to max_context_window and
265
+ // set auto_compact_token_limit at 90% (Codex's own convention), keeping
266
+ // compaction ahead of the hard ceiling.
267
+ export function liftContextWindows(bytes) {
268
+ const document = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
269
+ for (const model of document.models) {
270
+ const maxWindow = typeof model.max_context_window === "number" &&
271
+ model.max_context_window > 0
272
+ ? Math.floor(model.max_context_window)
273
+ : 0;
274
+ const window = typeof model.context_window === "number"
275
+ ? Math.floor(model.context_window)
276
+ : 0;
277
+ if (maxWindow <= window)
278
+ continue;
279
+ model.context_window = maxWindow;
280
+ model.auto_compact_token_limit = Math.floor(maxWindow * 0.9);
281
+ }
282
+ return Buffer.from(`${JSON.stringify(document, null, 2)}\n`);
283
+ }
261
284
  // Move `order` slugs to the front of the rich catalog (stable for the rest),
262
285
  // so the Codex client's first-5 spawn_agent advertisement picks them.
263
286
  export function reorderCatalog(bytes, order) {
@@ -1173,7 +1196,7 @@ function proxyFingerprint(config, apiKey) {
1173
1196
  .update(apiKey)
1174
1197
  .digest("hex");
1175
1198
  }
1176
- export async function inject(config, v2Off = false) {
1199
+ export async function inject(config, v2Off = false, maxContext = false) {
1177
1200
  const apiKey = process.env[config.api_key_env]?.trim();
1178
1201
  if (!apiKey)
1179
1202
  throw new CPACError(`environment variable ${config.api_key_env} is not set`);
@@ -1211,6 +1234,8 @@ export async function inject(config, v2Off = false) {
1211
1234
  const catalog = await fetchCatalog(config.cpa_url, apiKey);
1212
1235
  if (config.spawn_models?.length)
1213
1236
  catalog.bytes = reorderCatalog(catalog.bytes, config.spawn_models);
1237
+ if (maxContext)
1238
+ catalog.bytes = liftContextWindows(catalog.bytes);
1214
1239
  const stateDirExisted = existsSync(config.state_dir);
1215
1240
  if (!state &&
1216
1241
  STATE_FILES.some((name) => existsSync(join(config.state_dir, name)))) {
@@ -1729,6 +1754,13 @@ export function isKimiConfigInstalled() {
1729
1754
  function writeKimiBlock(path, block) {
1730
1755
  let content = existsSync(path) ? readFileSync(path, "utf8") : "";
1731
1756
  content = content.replace(kimiBlockRegex, "");
1757
+ // A truncated managed block (start marker without the end marker) leaves its
1758
+ // tables behind; appending again would duplicate [providers.cpac], make the
1759
+ // file invalid TOML, and block `kimi login`. The block is always appended
1760
+ // last, so dropping from an orphaned start marker to EOF is safe.
1761
+ const orphan = content.indexOf(KIMI_BLOCK_START);
1762
+ if (orphan !== -1)
1763
+ content = content.slice(0, orphan);
1732
1764
  if (block) {
1733
1765
  content = `${content}${content && !content.endsWith("\n") ? "\n" : ""}${block}\n`;
1734
1766
  }
@@ -1838,12 +1870,12 @@ const TARGETS = [
1838
1870
  return false;
1839
1871
  }
1840
1872
  },
1841
- install: async (config, dryRun, v2Off) => {
1873
+ install: async (config, dryRun, v2Off, maxContext) => {
1842
1874
  if (dryRun) {
1843
1875
  console.log("would inject CPA catalog and loopback proxy into Codex");
1844
1876
  return;
1845
1877
  }
1846
- await inject(config, v2Off);
1878
+ await inject(config, v2Off, maxContext);
1847
1879
  },
1848
1880
  uninstall: async (config, dryRun) => {
1849
1881
  if (dryRun) {
@@ -1927,12 +1959,17 @@ export async function runInstall(config, requested, options) {
1927
1959
  if (selected.length === 0) {
1928
1960
  throw new CPACError(`no supported targets detected; use --target ${TARGETS.map((target) => target.id).join(",")} or --all`);
1929
1961
  }
1962
+ // Catalog context lifting is Codex-specific; refuse to silently skip it on
1963
+ // other targets so the flag never gains cross-agent meaning.
1964
+ if (options.maxContext && selected.some((target) => target.id !== "codex")) {
1965
+ throw new CPACError("--max_context is only valid with --target codex");
1966
+ }
1930
1967
  for (const target of selected) {
1931
1968
  if (target.installed(config) && !options.force) {
1932
1969
  console.log(`${target.id}: already installed; use --force to reinstall`);
1933
1970
  continue;
1934
1971
  }
1935
- await target.install(config, options.dryRun, options.v2Off);
1972
+ await target.install(config, options.dryRun, options.v2Off, options.maxContext);
1936
1973
  }
1937
1974
  return 0;
1938
1975
  }
@@ -1998,7 +2035,7 @@ export async function runUpgrade(checkOnly) {
1998
2035
  function usage() {
1999
2036
  return [
2000
2037
  "Usage: cpac",
2001
- " cpac inject [--v2_off] [--v2_models] [--config PATH]",
2038
+ " cpac inject [--v2_off] [--v2_models] [--max_context] [--config PATH]",
2002
2039
  " cpac <status|restore> [--config PATH]",
2003
2040
  " cpac proxy [--config PATH]",
2004
2041
  " cpac claude [--config PATH] [--] [claude args...]",
@@ -2007,7 +2044,7 @@ function usage() {
2007
2044
  " cpac pi <install|uninstall|status> [--config PATH]",
2008
2045
  " cpac kimi <install|uninstall|status> [--config PATH]",
2009
2046
  " cpac detect [--json] [--home PATH]",
2010
- " cpac install [--target codex,pi,kimi] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--home PATH]",
2047
+ " cpac install [--target codex,pi,kimi] [--all] [--dry-run] [--force] [--v2_off] [--v2_models] [--max_context] [--home PATH]",
2011
2048
  " cpac uninstall [--target codex,pi,kimi] [--all] [--dry-run] [--home PATH]",
2012
2049
  " cpac upgrade [--check]",
2013
2050
  " cpac version",
@@ -2036,6 +2073,7 @@ function parseArgs(args) {
2036
2073
  check: false,
2037
2074
  v2Off: false,
2038
2075
  v2Models: false,
2076
+ maxContext: false,
2039
2077
  };
2040
2078
  for (let index = 1; index < args.length; index++) {
2041
2079
  const arg = args[index];
@@ -2076,6 +2114,9 @@ function parseArgs(args) {
2076
2114
  else if (arg === "--v2_models" || arg === "--v2-models") {
2077
2115
  parsed.v2Models = true;
2078
2116
  }
2117
+ else if (arg === "--max_context" || arg === "--max-context") {
2118
+ parsed.maxContext = true;
2119
+ }
2079
2120
  else {
2080
2121
  throw new CPACError(`unknown option: ${arg}`);
2081
2122
  }
@@ -2187,6 +2228,7 @@ function parseArgs(args) {
2187
2228
  const positional = [];
2188
2229
  let v2Off = false;
2189
2230
  let v2Models = false;
2231
+ let maxContext = false;
2190
2232
  for (let index = 0; index < args.length; index += 1) {
2191
2233
  if (args[index] === "--config") {
2192
2234
  const value = args[++index];
@@ -2200,6 +2242,9 @@ function parseArgs(args) {
2200
2242
  else if (args[index] === "--v2_models" || args[index] === "--v2-models") {
2201
2243
  v2Models = true;
2202
2244
  }
2245
+ else if (args[index] === "--max_context" || args[index] === "--max-context") {
2246
+ maxContext = true;
2247
+ }
2203
2248
  else if (args[index].startsWith("-")) {
2204
2249
  throw new CPACError(`unknown option: ${args[index]}`);
2205
2250
  }
@@ -2210,11 +2255,11 @@ function parseArgs(args) {
2210
2255
  !["inject", "status", "restore", "proxy"].includes(positional[0])) {
2211
2256
  throw new CPACError(usage());
2212
2257
  }
2213
- if (positional[0] !== "inject" && (v2Off || v2Models)) {
2214
- throw new CPACError("--v2_off / --v2_models is only valid with inject or install");
2258
+ if (positional[0] !== "inject" && (v2Off || v2Models || maxContext)) {
2259
+ throw new CPACError("--v2_off / --v2_models / --max_context is only valid with inject or install");
2215
2260
  }
2216
2261
  if (positional[0] === "inject") {
2217
- return { command: "inject", configPath, v2Off, v2Models };
2262
+ return { command: "inject", configPath, v2Off, v2Models, maxContext };
2218
2263
  }
2219
2264
  return {
2220
2265
  command: positional[0],
@@ -2252,6 +2297,7 @@ export async function main(args = process.argv.slice(2)) {
2252
2297
  dryRun: parsed.dryRun,
2253
2298
  force: parsed.force,
2254
2299
  v2Off: parsed.v2Off,
2300
+ maxContext: parsed.maxContext,
2255
2301
  });
2256
2302
  }
2257
2303
  if (parsed.command === "uninstall")
@@ -2285,7 +2331,7 @@ export async function main(args = process.argv.slice(2)) {
2285
2331
  console.log(`spawn_models saved to ${parsed.configPath}: ${picked.join(", ")}`);
2286
2332
  injectConfig = { ...config, spawn_models: picked };
2287
2333
  }
2288
- await inject(injectConfig, parsed.v2Off);
2334
+ await inject(injectConfig, parsed.v2Off, parsed.maxContext);
2289
2335
  }
2290
2336
  else if (parsed.command === "restore")
2291
2337
  await restore(config);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {