@yhong91/cpac 0.1.9 → 0.1.11

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
@@ -215,6 +215,14 @@ CPAC state:~/.cpac
215
215
 
216
216
  为防止误删或覆盖,`state_dir` 不能是文件系统根目录、用户 home、系统临时目录,也不能包含 Codex 配置文件。
217
217
 
218
+ ### OpenCode(临时接管)
219
+
220
+ ```bash
221
+ cpac opencode [--] [opencode args...]
222
+ ```
223
+
224
+ 通过 `OPENCODE_CONFIG_CONTENT` 内联注入一个 `cpac` OpenAI 兼容 provider(baseURL 直指 CPA,模型列表来自 CPA 目录),不触碰任何 opencode 配置文件,退出即无痕。首次使用时 opencode 会自行安装 `@ai-sdk/openai-compatible` 适配器。模型选择用 `cpac/<模型名>`。
225
+
218
226
  ### Pi
219
227
 
220
228
  安装 CPA provider 扩展到 Pi:
package/dist/cpac.js CHANGED
@@ -1427,9 +1427,61 @@ async function guide(config) {
1427
1427
  console.log(`\nSaved ${config.api_key_env} to ${profile}. Open a new terminal or run:\n source ${profile}`);
1428
1428
  return 0;
1429
1429
  }
1430
+ // opencode's config schema rejects a limit block with context but no output;
1431
+ // the catalog has no output field, so a safe budget stands in, clamped to the
1432
+ // context window (same scheme as opencodex's SCHEMA_REQUIRED_OUTPUT_BUDGET).
1433
+ const OPENCODE_OUTPUT_BUDGET = 32_000;
1434
+ // Ephemeral takeover: inline a CPA provider via OPENCODE_CONFIG_CONTENT so no
1435
+ // opencode config file is touched; the session ends with zero residue.
1436
+ export async function runOpencode(config, args, executable = "opencode") {
1437
+ const apiKey = process.env[config.api_key_env]?.trim();
1438
+ if (!apiKey)
1439
+ throw new CPACError(`environment variable ${config.api_key_env} is not set`);
1440
+ const catalog = await fetchCatalog(config.cpa_url, apiKey);
1441
+ const document = JSON.parse(new TextDecoder().decode(catalog.bytes));
1442
+ const models = {};
1443
+ for (const row of document.models) {
1444
+ const id = catalogModelId(row);
1445
+ if (!id)
1446
+ continue;
1447
+ const window = typeof row.context_window === "number" && row.context_window > 0
1448
+ ? Math.floor(row.context_window)
1449
+ : 0;
1450
+ models[id] = window > 0
1451
+ ? {
1452
+ limit: {
1453
+ context: window,
1454
+ output: Math.min(OPENCODE_OUTPUT_BUDGET, window),
1455
+ },
1456
+ }
1457
+ : {};
1458
+ }
1459
+ const content = JSON.stringify({
1460
+ provider: {
1461
+ cpac: {
1462
+ npm: "@ai-sdk/openai-compatible",
1463
+ options: { baseURL: apiBase(config.cpa_url), apiKey },
1464
+ models,
1465
+ },
1466
+ },
1467
+ });
1468
+ const env = {
1469
+ ...process.env,
1470
+ OPENCODE_CONFIG_CONTENT: content,
1471
+ };
1472
+ return await new Promise((resolve, reject) => {
1473
+ const child = spawn(executable, args, { env, stdio: "inherit" });
1474
+ child.once("error", (error) => {
1475
+ reject(new CPACError(error instanceof Error && "code" in error && error.code === "ENOENT"
1476
+ ? `${executable} not found`
1477
+ : `cannot start ${executable}: ${error.message}`));
1478
+ });
1479
+ child.once("close", (code) => resolve(code ?? 1));
1480
+ });
1481
+ }
1430
1482
  export async function runClaudeModels(parsed) {
1431
1483
  if (parsed.pick.length) {
1432
- const config = loadConfig(parsed.configPath);
1484
+ const config = loadConfig(parsed.configPath, true);
1433
1485
  const apiKey = process.env[config.api_key_env]?.trim();
1434
1486
  if (!apiKey)
1435
1487
  throw new CPACError(`environment variable ${config.api_key_env} is not set`);
@@ -1444,7 +1496,7 @@ export async function runClaudeModels(parsed) {
1444
1496
  }
1445
1497
  }
1446
1498
  if (!parsed.reset && !Object.keys(parsed.models).length) {
1447
- const config = loadConfig(parsed.configPath);
1499
+ const config = loadConfig(parsed.configPath, true);
1448
1500
  console.log(config.claude_models
1449
1501
  ? JSON.stringify(config.claude_models, null, 2)
1450
1502
  : "claude_models not set; cpac claude auto-picks tier models from the CPA catalog");
@@ -1945,6 +1997,7 @@ function usage() {
1945
1997
  " cpac <inject|status|restore> [--config PATH]",
1946
1998
  " cpac proxy [--config PATH]",
1947
1999
  " cpac claude [--config PATH] [--] [claude args...]",
2000
+ " cpac opencode [--config PATH] [--] [opencode args...]",
1948
2001
  " cpac claude-models [--opus [M]] [--sonnet [M]] [--haiku [M]] [--classifier [M]] [--reset] [--config PATH]",
1949
2002
  " cpac pi <install|uninstall|status> [--config PATH]",
1950
2003
  " cpac kimi <install|uninstall|status> [--config PATH]",
@@ -2104,6 +2157,20 @@ function parseArgs(args) {
2104
2157
  index += 1;
2105
2158
  return { command: "claude", configPath, args: args.slice(index) };
2106
2159
  }
2160
+ if (args[0] === "opencode") {
2161
+ let configPath = defaultConfigPath();
2162
+ let index = 1;
2163
+ if (args[index] === "--config") {
2164
+ const value = args[++index];
2165
+ if (!value)
2166
+ throw new CPACError("--config requires a path");
2167
+ configPath = resolve(expandUserPath(value));
2168
+ index += 1;
2169
+ }
2170
+ if (args[index] === "--")
2171
+ index += 1;
2172
+ return { command: "opencode", configPath, args: args.slice(index) };
2173
+ }
2107
2174
  if (args.includes("-h") || args.includes("--help")) {
2108
2175
  console.log(usage());
2109
2176
  return null;
@@ -2178,6 +2245,8 @@ export async function main(args = process.argv.slice(2)) {
2178
2245
  }
2179
2246
  if (parsed.command === "claude")
2180
2247
  return await runClaude(config, parsed.args);
2248
+ if (parsed.command === "opencode")
2249
+ return await runOpencode(config, parsed.args);
2181
2250
  if (parsed.command === "claude-models")
2182
2251
  return await runClaudeModels(parsed);
2183
2252
  if (parsed.command === "proxy")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yhong91/cpac",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
5
5
  "type": "module",
6
6
  "bin": {