@kkmila/cpc 1.0.1 → 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.
- package/README.md +36 -0
- package/package.json +6 -2
- package/registry.yaml +6 -0
- package/src/commands/provider.mjs +561 -91
- package/src/lib/agents.mjs +94 -1
- package/src/lib/baseline-models.mjs +338 -0
- package/src/lib/codex-toml.mjs +175 -0
- package/src/lib/env.mjs +115 -0
|
@@ -4,7 +4,10 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
|
4
4
|
import { resolve, join } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import yaml from "js-yaml";
|
|
7
|
-
import { loadRegistry, getAgents, resolveAgentPaths } from "../lib/agents.mjs";
|
|
7
|
+
import { loadRegistry, getAgents, resolveAgentPaths, detectCurrentAgent } from "../lib/agents.mjs";
|
|
8
|
+
import { enrichModelWithBaseline } from "../lib/baseline-models.mjs";
|
|
9
|
+
import { persistEnvVar, envKeyNameForProvider } from "../lib/env.mjs";
|
|
10
|
+
import { writeCodexProviderConfig, readCodexProviderInfo } from "../lib/codex-toml.mjs";
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Get the config directory path (~/.config/cpc)
|
|
@@ -120,10 +123,10 @@ const MODEL_DEFAULTS = {
|
|
|
120
123
|
"gpt-4o": { contextWindow: 128000, maxTokens: 16384, reasoning: false },
|
|
121
124
|
"gpt-4o-mini": { contextWindow: 128000, maxTokens: 16384, reasoning: false },
|
|
122
125
|
"gpt-3.5-turbo": { contextWindow: 16385, maxTokens: 4096, reasoning: false },
|
|
123
|
-
o1: { contextWindow: 200000, maxTokens: 100000, reasoning:
|
|
124
|
-
"o1-mini": { contextWindow: 128000, maxTokens: 65536, reasoning:
|
|
125
|
-
"o1-preview": { contextWindow: 128000, maxTokens: 32768, reasoning:
|
|
126
|
-
"o3-mini": { contextWindow: 200000, maxTokens: 100000, reasoning:
|
|
126
|
+
o1: { contextWindow: 200000, maxTokens: 100000, reasoning: false },
|
|
127
|
+
"o1-mini": { contextWindow: 128000, maxTokens: 65536, reasoning: false },
|
|
128
|
+
"o1-preview": { contextWindow: 128000, maxTokens: 32768, reasoning: false },
|
|
129
|
+
"o3-mini": { contextWindow: 200000, maxTokens: 100000, reasoning: false },
|
|
127
130
|
|
|
128
131
|
// Anthropic
|
|
129
132
|
"claude-3-opus-20240229": {
|
|
@@ -158,7 +161,7 @@ const MODEL_DEFAULTS = {
|
|
|
158
161
|
"deepseek-reasoner": {
|
|
159
162
|
contextWindow: 65536,
|
|
160
163
|
maxTokens: 8192,
|
|
161
|
-
reasoning:
|
|
164
|
+
reasoning: false,
|
|
162
165
|
},
|
|
163
166
|
|
|
164
167
|
// Qwen
|
|
@@ -166,6 +169,10 @@ const MODEL_DEFAULTS = {
|
|
|
166
169
|
"qwen-plus": { contextWindow: 32768, maxTokens: 8192, reasoning: false },
|
|
167
170
|
"qwen-max": { contextWindow: 32768, maxTokens: 8192, reasoning: false },
|
|
168
171
|
|
|
172
|
+
// 智谱 GLM-5.2
|
|
173
|
+
"glm-5.2": { contextWindow: 1000000, maxTokens: 131072, reasoning: false },
|
|
174
|
+
"glm-5.2-anthropic": { contextWindow: 1000000, maxTokens: 131072, reasoning: false },
|
|
175
|
+
|
|
169
176
|
// 通用默认值
|
|
170
177
|
default: { contextWindow: 32768, maxTokens: 4096, reasoning: false },
|
|
171
178
|
};
|
|
@@ -187,16 +194,9 @@ function getModelDefaults(modelId) {
|
|
|
187
194
|
}
|
|
188
195
|
}
|
|
189
196
|
|
|
190
|
-
// 检测是否是推理模型
|
|
191
|
-
const isReasoning =
|
|
192
|
-
lowerId.includes("reason") ||
|
|
193
|
-
lowerId.includes("o1") ||
|
|
194
|
-
lowerId.includes("o3") ||
|
|
195
|
-
lowerId.includes("think");
|
|
196
|
-
|
|
197
197
|
return {
|
|
198
198
|
...MODEL_DEFAULTS["default"],
|
|
199
|
-
reasoning:
|
|
199
|
+
reasoning: false,
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
202
|
|
|
@@ -322,24 +322,26 @@ function getAgentProviderConfig(agentId, provider) {
|
|
|
322
322
|
},
|
|
323
323
|
};
|
|
324
324
|
|
|
325
|
-
case "codex":
|
|
325
|
+
case "codex": {
|
|
326
|
+
// codex 一次只能用一个模型(顶层 model + model_provider)。
|
|
327
|
+
// provider 段用 env_key 引用环境变量,key 本身由 cpc 跨平台落地。
|
|
328
|
+
const envKey = envKeyNameForProvider(provider.name);
|
|
326
329
|
return {
|
|
327
|
-
|
|
328
|
-
|
|
330
|
+
codex: {
|
|
331
|
+
providerName: provider.name,
|
|
332
|
+
baseUrl: provider.baseUrl,
|
|
333
|
+
wireApi:
|
|
334
|
+
provider.api === "anthropic-messages"
|
|
335
|
+
? "chat_completions"
|
|
336
|
+
: provider.api === "openai-completions"
|
|
337
|
+
? "chat_completions"
|
|
338
|
+
: "responses",
|
|
339
|
+
envKey,
|
|
329
340
|
model: provider.models[0]?.id || "default",
|
|
330
|
-
|
|
331
|
-
[provider.name]: {
|
|
332
|
-
name: provider.name,
|
|
333
|
-
base_url: provider.baseUrl,
|
|
334
|
-
wire_api:
|
|
335
|
-
provider.api === "anthropic-messages"
|
|
336
|
-
? "chat_completions"
|
|
337
|
-
: "responses",
|
|
338
|
-
requires_openai_auth: false,
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
+
apiKey: provider.apiKey,
|
|
341
342
|
},
|
|
342
343
|
};
|
|
344
|
+
}
|
|
343
345
|
|
|
344
346
|
case "openclaw":
|
|
345
347
|
return {
|
|
@@ -387,6 +389,27 @@ function getAgentProviderConfig(agentId, provider) {
|
|
|
387
389
|
},
|
|
388
390
|
};
|
|
389
391
|
|
|
392
|
+
case "opencode":
|
|
393
|
+
return {
|
|
394
|
+
opencodeJson: {
|
|
395
|
+
provider: {
|
|
396
|
+
[provider.name]: {
|
|
397
|
+
models: provider.models.reduce((acc, m) => {
|
|
398
|
+
acc[m.id] = {
|
|
399
|
+
name: m.name || m.id,
|
|
400
|
+
};
|
|
401
|
+
return acc;
|
|
402
|
+
}, {}),
|
|
403
|
+
npm: "@ai-sdk/openai-compatible",
|
|
404
|
+
options: {
|
|
405
|
+
apiKey: provider.apiKey,
|
|
406
|
+
baseURL: provider.baseUrl,
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
|
|
390
413
|
default:
|
|
391
414
|
return null;
|
|
392
415
|
}
|
|
@@ -395,7 +418,7 @@ function getAgentProviderConfig(agentId, provider) {
|
|
|
395
418
|
/**
|
|
396
419
|
* Install provider configuration to agent
|
|
397
420
|
*/
|
|
398
|
-
function installProviderToAgent(agentId, provider, agentHome) {
|
|
421
|
+
function installProviderToAgent(agentId, provider, agentHome, opts = {}) {
|
|
399
422
|
const config = getAgentProviderConfig(agentId, provider);
|
|
400
423
|
if (!config) {
|
|
401
424
|
console.log(chalk.yellow(` ⚠ 未知 agent: ${agentId}`));
|
|
@@ -420,7 +443,13 @@ function installProviderToAgent(agentId, provider, agentHome) {
|
|
|
420
443
|
const modelsPath = join(agentHome, "models.json");
|
|
421
444
|
let models = { providers: {} };
|
|
422
445
|
if (existsSync(modelsPath)) {
|
|
423
|
-
|
|
446
|
+
try {
|
|
447
|
+
const content = readFileSync(modelsPath, "utf8").trim();
|
|
448
|
+
if (content) models = JSON.parse(content);
|
|
449
|
+
} catch {
|
|
450
|
+
// 空文件或损坏的 JSON,重置为默认值
|
|
451
|
+
models = { providers: {} };
|
|
452
|
+
}
|
|
424
453
|
}
|
|
425
454
|
models.providers = {
|
|
426
455
|
...models.providers,
|
|
@@ -445,38 +474,55 @@ function installProviderToAgent(agentId, provider, agentHome) {
|
|
|
445
474
|
|
|
446
475
|
case "codex": {
|
|
447
476
|
const configPath = join(agentHome, "config.toml");
|
|
448
|
-
|
|
449
|
-
if (existsSync(configPath)) {
|
|
450
|
-
configContent = readFileSync(configPath, "utf8");
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const providerSection = `\n[model_providers.${provider.name}]\nname = "${provider.name}"\nbase_url = "${provider.baseUrl}"\nwire_api = "${provider.api === "anthropic-messages" ? "chat_completions" : "responses"}"\nrequires_openai_auth = false\n`;
|
|
454
|
-
|
|
455
|
-
const modelLine = `model_provider = "${provider.name}"\n`;
|
|
456
|
-
const modelIdLine = `model = "${provider.models[0]?.id || "default"}"\n`;
|
|
477
|
+
const c = config.codex;
|
|
457
478
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
479
|
+
// 1) 跨平台落地 API key 到环境变量
|
|
480
|
+
let envResult;
|
|
481
|
+
try {
|
|
482
|
+
envResult = persistEnvVar(c.envKey, c.apiKey);
|
|
483
|
+
} catch (err) {
|
|
484
|
+
console.error(
|
|
485
|
+
chalk.red(` ✗ 落地环境变量 ${c.envKey} 失败: ${err.message}`),
|
|
462
486
|
);
|
|
463
|
-
|
|
464
|
-
configContent = modelLine + configContent;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (configContent.includes("model =")) {
|
|
468
|
-
configContent = configContent.replace(/model = .*\n/, modelIdLine);
|
|
469
|
-
} else {
|
|
470
|
-
configContent = modelIdLine + configContent;
|
|
487
|
+
return false;
|
|
471
488
|
}
|
|
472
489
|
|
|
473
|
-
|
|
474
|
-
|
|
490
|
+
// 2) 稳健写入 config.toml(顶层 + provider 段,备份原文件)
|
|
491
|
+
try {
|
|
492
|
+
const { backed } = writeCodexProviderConfig(configPath, {
|
|
493
|
+
providerName: c.providerName,
|
|
494
|
+
baseUrl: c.baseUrl,
|
|
495
|
+
wireApi: opts.wireApi || c.wireApi,
|
|
496
|
+
envKey: c.envKey,
|
|
497
|
+
model: c.model,
|
|
498
|
+
modelReasoningEffort: opts.modelReasoningEffort,
|
|
499
|
+
});
|
|
500
|
+
console.log(chalk.green(` ✓ 更新 Codex config.toml`));
|
|
501
|
+
if (backed) {
|
|
502
|
+
console.log(chalk.dim(` • 备份: ${backed}`));
|
|
503
|
+
}
|
|
504
|
+
console.log(
|
|
505
|
+
chalk.dim(
|
|
506
|
+
` • env_key=$${c.envKey} (${envResult.method}${envResult.target ? ": " + envResult.target : ""}${envResult.created ? ", 新增" : ", 已存在"})`,
|
|
507
|
+
),
|
|
508
|
+
);
|
|
509
|
+
if (process.platform !== "win32") {
|
|
510
|
+
console.log(
|
|
511
|
+
chalk.dim(
|
|
512
|
+
` • 新终端自动加载; 当前终端需执行: source ${envResult.target}`,
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
console.log(
|
|
517
|
+
chalk.dim(` • 启动: codex (全局默认即 ${c.providerName}/${c.model})`),
|
|
518
|
+
);
|
|
519
|
+
return true;
|
|
520
|
+
} catch (err) {
|
|
521
|
+
console.error(
|
|
522
|
+
chalk.red(` ✗ 更新 ${agentId} 失败: ${err.message}`),
|
|
523
|
+
);
|
|
524
|
+
return false;
|
|
475
525
|
}
|
|
476
|
-
|
|
477
|
-
writeFileSync(configPath, configContent);
|
|
478
|
-
console.log(chalk.green(` ✓ 更新 Codex config.toml`));
|
|
479
|
-
return true;
|
|
480
526
|
}
|
|
481
527
|
|
|
482
528
|
case "openclaw": {
|
|
@@ -511,6 +557,22 @@ function installProviderToAgent(agentId, provider, agentHome) {
|
|
|
511
557
|
return true;
|
|
512
558
|
}
|
|
513
559
|
|
|
560
|
+
case "opencode": {
|
|
561
|
+
const configPath = join(agentHome, "opencode.json");
|
|
562
|
+
let configData = {};
|
|
563
|
+
if (existsSync(configPath)) {
|
|
564
|
+
configData = JSON.parse(readFileSync(configPath, "utf8"));
|
|
565
|
+
}
|
|
566
|
+
if (!configData.provider) configData.provider = {};
|
|
567
|
+
configData.provider = {
|
|
568
|
+
...configData.provider,
|
|
569
|
+
...config.opencodeJson.provider,
|
|
570
|
+
};
|
|
571
|
+
writeFileSync(configPath, JSON.stringify(configData, null, 2));
|
|
572
|
+
console.log(chalk.green(` ✓ 更新 OpenCode opencode.json`));
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
514
576
|
default:
|
|
515
577
|
console.log(chalk.yellow(` ⚠ 未知 agent: ${agentId}`));
|
|
516
578
|
return false;
|
|
@@ -521,6 +583,15 @@ function installProviderToAgent(agentId, provider, agentHome) {
|
|
|
521
583
|
}
|
|
522
584
|
}
|
|
523
585
|
|
|
586
|
+
/**
|
|
587
|
+
* Check if an agent is installed by looking at its home directory
|
|
588
|
+
*/
|
|
589
|
+
function isAgentInstalled(agentId, agents) {
|
|
590
|
+
const agent = resolveAgentPaths(agents[agentId]);
|
|
591
|
+
if (!agent || !agent.home) return false;
|
|
592
|
+
return existsSync(agent.home);
|
|
593
|
+
}
|
|
594
|
+
|
|
524
595
|
export function registerProvider(program, repoRoot) {
|
|
525
596
|
// ── cpc add ──────────────────────────────────────────────────
|
|
526
597
|
program
|
|
@@ -647,14 +718,23 @@ export function registerProvider(program, repoRoot) {
|
|
|
647
718
|
for (const modelId of selected) {
|
|
648
719
|
const model = result.models.find((m) => m.id === modelId);
|
|
649
720
|
const defaults = getModelDefaults(modelId);
|
|
650
|
-
|
|
721
|
+
// 先用 baseline 字典补全 contextWindow / input / description,
|
|
722
|
+
// 未命中时回退到 API 响应与 MODEL_DEFAULTS
|
|
723
|
+
const configured = enrichModelWithBaseline({
|
|
651
724
|
id: modelId,
|
|
652
725
|
name: model?.name || modelId,
|
|
653
726
|
contextWindow: model?.contextWindow || defaults.contextWindow,
|
|
654
727
|
maxTokens: model?.maxTokens || defaults.maxTokens,
|
|
655
728
|
reasoning: model?.reasoning || defaults.reasoning,
|
|
656
729
|
input: model?.input || ["text"],
|
|
657
|
-
};
|
|
730
|
+
});
|
|
731
|
+
if (configured.description) {
|
|
732
|
+
console.log(
|
|
733
|
+
chalk.dim(
|
|
734
|
+
` ↪ ${modelId}: 命中 baseline, 已注入 contextWindow=${configured.contextWindow}, input=[${configured.input.join(",")}]`,
|
|
735
|
+
),
|
|
736
|
+
);
|
|
737
|
+
}
|
|
658
738
|
models.push(configured);
|
|
659
739
|
}
|
|
660
740
|
} else {
|
|
@@ -976,14 +1056,23 @@ export function registerProvider(program, repoRoot) {
|
|
|
976
1056
|
for (const modelId of selected) {
|
|
977
1057
|
const model = result.models.find((m) => m.id === modelId);
|
|
978
1058
|
const defaults = getModelDefaults(modelId);
|
|
979
|
-
|
|
1059
|
+
// 先用 baseline 字典补全 contextWindow / input / description,
|
|
1060
|
+
// 未命中时回退到 API 响应与 MODEL_DEFAULTS
|
|
1061
|
+
const configured = enrichModelWithBaseline({
|
|
980
1062
|
id: modelId,
|
|
981
1063
|
name: model?.name || modelId,
|
|
982
1064
|
contextWindow: model?.contextWindow || defaults.contextWindow,
|
|
983
1065
|
maxTokens: model?.maxTokens || defaults.maxTokens,
|
|
984
1066
|
reasoning: model?.reasoning || defaults.reasoning,
|
|
985
1067
|
input: model?.input || ["text"],
|
|
986
|
-
};
|
|
1068
|
+
});
|
|
1069
|
+
if (configured.description) {
|
|
1070
|
+
console.log(
|
|
1071
|
+
chalk.dim(
|
|
1072
|
+
` ↪ ${modelId}: 命中 baseline, 已注入 contextWindow=${configured.contextWindow}, input=[${configured.input.join(",")}]`,
|
|
1073
|
+
),
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
987
1076
|
|
|
988
1077
|
if (opts.merge) {
|
|
989
1078
|
config.models = config.models || [];
|
|
@@ -1005,6 +1094,192 @@ export function registerProvider(program, repoRoot) {
|
|
|
1005
1094
|
);
|
|
1006
1095
|
});
|
|
1007
1096
|
|
|
1097
|
+
// ── cpc update ───────────────────────────────────────────────
|
|
1098
|
+
program
|
|
1099
|
+
.command("update [providerName]")
|
|
1100
|
+
.description("重新从 API 拉取模型并更新 providers.json + 已安装的 agent")
|
|
1101
|
+
.option("--all", "选择全部模型")
|
|
1102
|
+
.option("--agent <agent>", "指定目标 agent")
|
|
1103
|
+
.action(async (providerName, opts) => {
|
|
1104
|
+
const providersData = loadProviders();
|
|
1105
|
+
const providers = Object.entries(providersData.providers);
|
|
1106
|
+
|
|
1107
|
+
if (providers.length === 0) {
|
|
1108
|
+
console.log(chalk.yellow("No providers configured."));
|
|
1109
|
+
console.log(chalk.dim("Use `cpc add` to add a new provider first."));
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Step 1: 选择 provider
|
|
1114
|
+
if (!providerName) {
|
|
1115
|
+
providerName = await select({
|
|
1116
|
+
message: "Select provider to update:",
|
|
1117
|
+
choices: providers.map(([name, config]) => ({
|
|
1118
|
+
name: `${name.padEnd(20)} (${config.models?.length || 0} models)`,
|
|
1119
|
+
value: name,
|
|
1120
|
+
})),
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const provider = providersData.providers[providerName];
|
|
1125
|
+
if (!provider) {
|
|
1126
|
+
console.error(chalk.red(`Provider "${providerName}" not found.`));
|
|
1127
|
+
process.exit(1);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Step 2: 从 API 拉取模型
|
|
1131
|
+
console.log(chalk.dim(`\nFetching models from ${provider.baseUrl}...`));
|
|
1132
|
+
const result = await fetchModelsFromAPI(provider.baseUrl, provider.apiKey, provider.api);
|
|
1133
|
+
|
|
1134
|
+
if (!result.success) {
|
|
1135
|
+
console.log(chalk.red(`✗ Failed to fetch models: ${result.error}`));
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
console.log(chalk.green(`✓ Found ${result.models.length} models from API`));
|
|
1140
|
+
|
|
1141
|
+
// Step 3: 选择模型
|
|
1142
|
+
const oldIds = (provider.models || []).map((m) => m.id);
|
|
1143
|
+
const newIds = result.models.map((m) => m.id);
|
|
1144
|
+
|
|
1145
|
+
// 报告新增和移除的模型
|
|
1146
|
+
const addedIds = newIds.filter((id) => !oldIds.includes(id));
|
|
1147
|
+
const removedIds = oldIds.filter((id) => !newIds.includes(id));
|
|
1148
|
+
const keptIds = oldIds.filter((id) => newIds.includes(id));
|
|
1149
|
+
|
|
1150
|
+
if (addedIds.length > 0) {
|
|
1151
|
+
console.log(chalk.cyan(` + ${addedIds.length} new model(s): ${addedIds.join(", ")}`));
|
|
1152
|
+
}
|
|
1153
|
+
if (removedIds.length > 0) {
|
|
1154
|
+
console.log(chalk.yellow(` - ${removedIds.length} removed model(s): ${removedIds.join(", ")}`));
|
|
1155
|
+
}
|
|
1156
|
+
if (keptIds.length > 0) {
|
|
1157
|
+
console.log(chalk.dim(` = ${keptIds.length} kept model(s): ${keptIds.join(", ")}`));
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// 让用户选择要安装的模型
|
|
1161
|
+
let selectedIds;
|
|
1162
|
+
if (opts.all) {
|
|
1163
|
+
selectedIds = result.models.map((m) => m.id);
|
|
1164
|
+
console.log(chalk.dim(`Auto-selected all ${selectedIds.length} models`));
|
|
1165
|
+
} else {
|
|
1166
|
+
selectedIds = await checkbox({
|
|
1167
|
+
message: "Select models to install:",
|
|
1168
|
+
choices: result.models.map((m) => {
|
|
1169
|
+
const isNew = !oldIds.includes(m.id);
|
|
1170
|
+
return {
|
|
1171
|
+
name: `${isNew ? chalk.cyan("+ ") : chalk.dim(" ")}${m.id.padEnd(40)} ctx:${m.contextWindow || "?"}`,
|
|
1172
|
+
value: m.id,
|
|
1173
|
+
checked: true,
|
|
1174
|
+
};
|
|
1175
|
+
}),
|
|
1176
|
+
instructions: {
|
|
1177
|
+
navigator: "↑↓ navigate",
|
|
1178
|
+
select: "space select",
|
|
1179
|
+
all: "a toggle all",
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (selectedIds.length === 0) {
|
|
1185
|
+
console.log(chalk.yellow("No models selected. Cancelled."));
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Step 4: 用 baseline + MODEL_DEFAULTS 补全模型属性
|
|
1190
|
+
const updatedModels = [];
|
|
1191
|
+
for (const modelId of selectedIds) {
|
|
1192
|
+
const apiModel = result.models.find((m) => m.id === modelId);
|
|
1193
|
+
const defaults = getModelDefaults(modelId);
|
|
1194
|
+
const configured = enrichModelWithBaseline({
|
|
1195
|
+
id: modelId,
|
|
1196
|
+
name: apiModel?.name || modelId,
|
|
1197
|
+
contextWindow: apiModel?.contextWindow || defaults.contextWindow,
|
|
1198
|
+
maxTokens: apiModel?.maxTokens || defaults.maxTokens,
|
|
1199
|
+
reasoning: false,
|
|
1200
|
+
input: apiModel?.input || defaults.input || ["text"],
|
|
1201
|
+
});
|
|
1202
|
+
if (configured.description) {
|
|
1203
|
+
console.log(chalk.dim(` ↪ ${modelId}: baseline 命中, contextWindow=${configured.contextWindow}, input=[${configured.input.join(",")}]`));
|
|
1204
|
+
}
|
|
1205
|
+
updatedModels.push(configured);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Step 5: 更新 providers.json
|
|
1209
|
+
provider.models = updatedModels;
|
|
1210
|
+
provider.updatedAt = new Date().toISOString();
|
|
1211
|
+
saveProviders(providersData);
|
|
1212
|
+
console.log(chalk.green(`\n✓ Provider "${providerName}" updated: ${updatedModels.length} model(s)`));
|
|
1213
|
+
|
|
1214
|
+
// Step 6: 更新已安装的 agent
|
|
1215
|
+
const registry = loadRegistry(repoRoot);
|
|
1216
|
+
const agents = getAgents(registry);
|
|
1217
|
+
const resolvedAgents = Object.fromEntries(
|
|
1218
|
+
Object.entries(agents).map(([id, a]) => [id, resolveAgentPaths(a)]),
|
|
1219
|
+
);
|
|
1220
|
+
const installProvider = { ...provider, models: updatedModels };
|
|
1221
|
+
|
|
1222
|
+
// 找出哪些 agent 之前安装过这个 provider
|
|
1223
|
+
let agentIds;
|
|
1224
|
+
if (opts.agent) {
|
|
1225
|
+
agentIds = opts.agent === "all" ? Object.keys(agents) : [opts.agent];
|
|
1226
|
+
} else {
|
|
1227
|
+
// 只显示 pi 和 opencode,默认不勾选
|
|
1228
|
+
const visibleAgentIds = Object.keys(resolvedAgents)
|
|
1229
|
+
.filter((id) => id === "pi" || id === "opencode");
|
|
1230
|
+
|
|
1231
|
+
if (visibleAgentIds.length === 0) {
|
|
1232
|
+
console.log(chalk.dim("\nNo pi or opencode agents found. Skipping agent update."));
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
agentIds = await checkbox({
|
|
1237
|
+
message: "Select target agents to update:",
|
|
1238
|
+
choices: visibleAgentIds.map((id) => {
|
|
1239
|
+
const a = resolvedAgents[id];
|
|
1240
|
+
const installed = isAgentInstalled(id, resolvedAgents);
|
|
1241
|
+
const mark = installed ? " (installed)" : "";
|
|
1242
|
+
return {
|
|
1243
|
+
name: `${id.padEnd(14)} ${a.description || ""}${mark}`,
|
|
1244
|
+
value: id,
|
|
1245
|
+
checked: false,
|
|
1246
|
+
};
|
|
1247
|
+
}),
|
|
1248
|
+
instructions: {
|
|
1249
|
+
navigator: "↑↓ navigate",
|
|
1250
|
+
select: "space select",
|
|
1251
|
+
all: "a toggle all",
|
|
1252
|
+
},
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (agentIds.length === 0) {
|
|
1257
|
+
console.log(chalk.yellow("No agents selected. Skipping agent update."));
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
console.log();
|
|
1262
|
+
let successCount = 0;
|
|
1263
|
+
for (const aid of agentIds) {
|
|
1264
|
+
const agent = resolvedAgents[aid];
|
|
1265
|
+
if (!agent) {
|
|
1266
|
+
console.log(chalk.red(`Unknown agent: ${aid}`));
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
console.log(chalk.bold(`→ ${aid}`));
|
|
1270
|
+
console.log(chalk.dim(` ${updatedModels.length} model(s)`));
|
|
1271
|
+
const success = installProviderToAgent(aid, installProvider, agent.home);
|
|
1272
|
+
if (success) successCount++;
|
|
1273
|
+
console.log();
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (successCount > 0) {
|
|
1277
|
+
console.log(chalk.green.bold(`✓ Provider updated in ${successCount} agent(s)!`));
|
|
1278
|
+
} else {
|
|
1279
|
+
console.log(chalk.yellow("No agents were updated."));
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1008
1283
|
// ── cpc remove ───────────────────────────────────────────────
|
|
1009
1284
|
program
|
|
1010
1285
|
.command("remove [name]")
|
|
@@ -1057,6 +1332,8 @@ export function registerProvider(program, repoRoot) {
|
|
|
1057
1332
|
.option("--model <model>", "Install only specific model")
|
|
1058
1333
|
.option("--all", "Install all models")
|
|
1059
1334
|
.option("--first", "Install only first model")
|
|
1335
|
+
.option("--wire-api <api>", "Codex wire_api (responses | chat_completions)")
|
|
1336
|
+
.option("--reasoning-effort <level>", "Codex model_reasoning_effort (minimal|low|medium|high)")
|
|
1060
1337
|
.action(async (providerName, opts) => {
|
|
1061
1338
|
const registry = loadRegistry(repoRoot);
|
|
1062
1339
|
const agents = getAgents(registry);
|
|
@@ -1134,44 +1411,132 @@ export function registerProvider(program, repoRoot) {
|
|
|
1134
1411
|
}
|
|
1135
1412
|
|
|
1136
1413
|
// Step 3: Select agent(s)
|
|
1137
|
-
let
|
|
1414
|
+
let agentIds;
|
|
1138
1415
|
if (opts.agent) {
|
|
1139
|
-
|
|
1416
|
+
agentIds = opts.agent === "all" ? Object.keys(agents) : [opts.agent];
|
|
1140
1417
|
} else {
|
|
1141
|
-
|
|
1142
|
-
|
|
1418
|
+
// Auto-detect: 只显示 pi 和 opencode,默认不勾选
|
|
1419
|
+
const visibleAgentIds = Object.keys(agents)
|
|
1420
|
+
.filter((id) => id === "pi" || id === "opencode");
|
|
1421
|
+
const currentAgent = detectCurrentAgent(agents);
|
|
1422
|
+
const selected = await checkbox({
|
|
1423
|
+
message: "Select target agents:",
|
|
1143
1424
|
choices: [
|
|
1144
|
-
...
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1425
|
+
...visibleAgentIds.map((id) => {
|
|
1426
|
+
const a = agents[id];
|
|
1427
|
+
const mark = id === currentAgent ? " ← current" : "";
|
|
1428
|
+
return {
|
|
1429
|
+
name: `${id.padEnd(14)} ${a.description || ""}${mark}`,
|
|
1430
|
+
value: id,
|
|
1431
|
+
checked: false,
|
|
1432
|
+
};
|
|
1433
|
+
}),
|
|
1150
1434
|
],
|
|
1435
|
+
instructions: {
|
|
1436
|
+
navigator: "↑↓ navigate",
|
|
1437
|
+
select: "space select",
|
|
1438
|
+
all: "a toggle all",
|
|
1439
|
+
},
|
|
1151
1440
|
});
|
|
1152
|
-
}
|
|
1153
1441
|
|
|
1154
|
-
|
|
1442
|
+
if (selected.length === 0) {
|
|
1443
|
+
console.log(chalk.yellow("No agents selected. Cancelled."));
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
agentIds = selected;
|
|
1448
|
+
}
|
|
1155
1449
|
|
|
1156
1450
|
// Step 4: Install provider to agents
|
|
1157
1451
|
console.log();
|
|
1158
1452
|
let successCount = 0;
|
|
1159
1453
|
|
|
1454
|
+
// codex 特殊处理:全局只能用一个 model,需确定 wire_api(协议)。
|
|
1455
|
+
// 如果目标含 codex 且选了多个模型,先让用户挑一个作为默认。
|
|
1456
|
+
const hasCodex = agentIds.includes("codex");
|
|
1457
|
+
let codexDefaultModel = null;
|
|
1458
|
+
let codexWireApi = opts.wireApi || null;
|
|
1459
|
+
let codexReasoningEffort = opts.reasoningEffort || null;
|
|
1460
|
+
if (hasCodex) {
|
|
1461
|
+
let codexModels = installProvider.models;
|
|
1462
|
+
if (codexModels.length > 1) {
|
|
1463
|
+
console.log(
|
|
1464
|
+
chalk.yellow(
|
|
1465
|
+
`⚠ codex 一次只能用一个模型(顶层 model),多模型时仅生效默认这一个`,
|
|
1466
|
+
),
|
|
1467
|
+
);
|
|
1468
|
+
const picked = await select({
|
|
1469
|
+
message: "选择 codex 的默认 model:",
|
|
1470
|
+
choices: codexModels.map((m) => ({
|
|
1471
|
+
name: `${m.id}${m.description ? " — " + m.description : ""}`,
|
|
1472
|
+
value: m.id,
|
|
1473
|
+
})),
|
|
1474
|
+
});
|
|
1475
|
+
codexDefaultModel = picked;
|
|
1476
|
+
} else {
|
|
1477
|
+
codexDefaultModel = codexModels[0]?.id;
|
|
1478
|
+
}
|
|
1479
|
+
// wire_api 选择(未传 --wire-api 时交互确认)
|
|
1480
|
+
if (!codexWireApi) {
|
|
1481
|
+
const inferred =
|
|
1482
|
+
installProvider.api === "responses"
|
|
1483
|
+
? "responses"
|
|
1484
|
+
: installProvider.api === "anthropic-messages"
|
|
1485
|
+
? "chat_completions"
|
|
1486
|
+
: "chat_completions";
|
|
1487
|
+
codexWireApi = await select({
|
|
1488
|
+
message: `codex wire_api(与 ${installProvider.name} 通信协议):`,
|
|
1489
|
+
choices: [
|
|
1490
|
+
{
|
|
1491
|
+
name: `responses — Codex Responses 原生协议(CCX 网关用这个)${inferred === "responses" ? " [推断]" : ""}`,
|
|
1492
|
+
value: "responses",
|
|
1493
|
+
},
|
|
1494
|
+
{
|
|
1495
|
+
name: `chat_completions — 标准 OpenAI 兼容协议(多数网关用这个)${inferred === "chat_completions" ? " [推断]" : ""}`,
|
|
1496
|
+
value: "chat_completions",
|
|
1497
|
+
},
|
|
1498
|
+
],
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1160
1503
|
for (const aid of agentIds) {
|
|
1161
1504
|
const agentDef = agents[aid];
|
|
1162
1505
|
if (!agentDef) {
|
|
1163
1506
|
console.log(chalk.red(`Unknown agent: ${aid}`));
|
|
1164
1507
|
continue;
|
|
1165
|
-
|
|
1508
|
+
}
|
|
1166
1509
|
|
|
1167
1510
|
const agent = resolveAgentPaths(agentDef);
|
|
1168
1511
|
console.log(chalk.bold(`→ ${aid}`));
|
|
1169
|
-
|
|
1512
|
+
|
|
1513
|
+
let targetProvider = installProvider;
|
|
1514
|
+
let targetOpts = {};
|
|
1515
|
+
if (aid === "codex") {
|
|
1516
|
+
const picked =
|
|
1517
|
+
installProvider.models.find(
|
|
1518
|
+
(m) => m.id === codexDefaultModel,
|
|
1519
|
+
) || installProvider.models[0];
|
|
1520
|
+
targetProvider = { ...installProvider, models: [picked] };
|
|
1521
|
+
targetOpts = {
|
|
1522
|
+
wireApi: codexWireApi,
|
|
1523
|
+
modelReasoningEffort: codexReasoningEffort,
|
|
1524
|
+
};
|
|
1525
|
+
console.log(
|
|
1526
|
+
chalk.dim(` model: ${picked.id} (codex 全局默认, 单模型)`),
|
|
1527
|
+
);
|
|
1528
|
+
console.log(chalk.dim(` wire_api: ${codexWireApi}`));
|
|
1529
|
+
} else {
|
|
1530
|
+
console.log(
|
|
1531
|
+
chalk.dim(` ${targetProvider.models.length} model(s)`),
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1170
1534
|
|
|
1171
1535
|
const success = installProviderToAgent(
|
|
1172
1536
|
aid,
|
|
1173
|
-
|
|
1537
|
+
targetProvider,
|
|
1174
1538
|
agent.home,
|
|
1539
|
+
targetOpts,
|
|
1175
1540
|
);
|
|
1176
1541
|
if (success) successCount++;
|
|
1177
1542
|
console.log();
|
|
@@ -1445,7 +1810,13 @@ export function registerProvider(program, repoRoot) {
|
|
|
1445
1810
|
case "pi": {
|
|
1446
1811
|
const modelsPath = join(agent.home, "models.json");
|
|
1447
1812
|
if (existsSync(modelsPath)) {
|
|
1448
|
-
|
|
1813
|
+
let models = { providers: {} };
|
|
1814
|
+
try {
|
|
1815
|
+
const content = readFileSync(modelsPath, "utf8").trim();
|
|
1816
|
+
if (content) models = JSON.parse(content);
|
|
1817
|
+
} catch {
|
|
1818
|
+
models = { providers: {} };
|
|
1819
|
+
}
|
|
1449
1820
|
if (
|
|
1450
1821
|
models.providers &&
|
|
1451
1822
|
Object.keys(models.providers).length > 0
|
|
@@ -1494,20 +1865,15 @@ export function registerProvider(program, repoRoot) {
|
|
|
1494
1865
|
|
|
1495
1866
|
case "codex": {
|
|
1496
1867
|
const configPath = join(agent.home, "config.toml");
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
` Model: ${chalk.dim(modelMatch ? modelMatch[1] : "not set")}`,
|
|
1505
|
-
);
|
|
1506
|
-
} else {
|
|
1507
|
-
console.log(chalk.dim(" No provider configured"));
|
|
1508
|
-
}
|
|
1868
|
+
const info = readCodexProviderInfo(configPath);
|
|
1869
|
+
if (info.provider) {
|
|
1870
|
+
console.log(` Provider: ${chalk.dim(info.provider)}`);
|
|
1871
|
+
console.log(` Model: ${chalk.dim(info.model || "not set")}`);
|
|
1872
|
+
console.log(` Base URL: ${chalk.dim(info.baseUrl || "not set")}`);
|
|
1873
|
+
console.log(` Wire API: ${chalk.dim(info.wireApi || "not set")}`);
|
|
1874
|
+
console.log(` Env Key: ${chalk.dim(info.envKey || "(none)")}`);
|
|
1509
1875
|
} else {
|
|
1510
|
-
console.log(chalk.dim("
|
|
1876
|
+
console.log(chalk.dim(" No provider configured"));
|
|
1511
1877
|
}
|
|
1512
1878
|
break;
|
|
1513
1879
|
}
|
|
@@ -1563,6 +1929,29 @@ export function registerProvider(program, repoRoot) {
|
|
|
1563
1929
|
break;
|
|
1564
1930
|
}
|
|
1565
1931
|
|
|
1932
|
+
case "opencode": {
|
|
1933
|
+
const configPath = join(agent.home, "opencode.json");
|
|
1934
|
+
if (existsSync(configPath)) {
|
|
1935
|
+
const configData = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1936
|
+
if (configData.provider && Object.keys(configData.provider).length > 0) {
|
|
1937
|
+
for (const [name, config] of Object.entries(
|
|
1938
|
+
configData.provider,
|
|
1939
|
+
)) {
|
|
1940
|
+
console.log(` ${chalk.blue(name)}`);
|
|
1941
|
+
console.log(` URL: ${chalk.dim(config.options?.baseURL || "not set")}`);
|
|
1942
|
+
console.log(
|
|
1943
|
+
` Models: ${chalk.dim(Object.keys(config.models || {}).length)}`,
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
} else {
|
|
1947
|
+
console.log(chalk.dim(" No providers configured"));
|
|
1948
|
+
}
|
|
1949
|
+
} else {
|
|
1950
|
+
console.log(chalk.dim(" Config file not found"));
|
|
1951
|
+
}
|
|
1952
|
+
break;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1566
1955
|
default:
|
|
1567
1956
|
console.log(chalk.dim(" Unsupported agent"));
|
|
1568
1957
|
}
|
|
@@ -1570,7 +1959,88 @@ export function registerProvider(program, repoRoot) {
|
|
|
1570
1959
|
console.log(chalk.red(` Error reading config: ${err.message}`));
|
|
1571
1960
|
}
|
|
1572
1961
|
|
|
1573
|
-
|
|
1962
|
+
console.log();
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
// ── cpc doctor ───────────────────────────────────────────────
|
|
1967
|
+
program
|
|
1968
|
+
.command("doctor")
|
|
1969
|
+
.description("Check if cpc is properly configured and working")
|
|
1970
|
+
.action(() => {
|
|
1971
|
+
console.log(chalk.bold("\n🔍 CPC Doctor\n"));
|
|
1972
|
+
|
|
1973
|
+
let issues = 0;
|
|
1974
|
+
|
|
1975
|
+
// Check Node.js version
|
|
1976
|
+
const nodeVersion = process.version;
|
|
1977
|
+
const requiredVersion = "18.0.0";
|
|
1978
|
+
const nodeOk = compareVersions(nodeVersion.slice(1), requiredVersion) >= 0;
|
|
1979
|
+
console.log(`${nodeOk ? "✅" : "❌"} Node.js version: ${nodeVersion} (required: >=${requiredVersion})`);
|
|
1980
|
+
if (!nodeOk) issues++;
|
|
1981
|
+
|
|
1982
|
+
// Check registry.yaml
|
|
1983
|
+
try {
|
|
1984
|
+
const registry = loadRegistry(repoRoot);
|
|
1985
|
+
const agents = getAgents(registry);
|
|
1986
|
+
const agentCount = Object.keys(agents).length;
|
|
1987
|
+
console.log(`✅ registry.yaml: ${agentCount} agents defined`);
|
|
1988
|
+
} catch (err) {
|
|
1989
|
+
console.log(`❌ registry.yaml: ${err.message}`);
|
|
1990
|
+
issues++;
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// Check config directory
|
|
1994
|
+
const configDir = getConfigDir();
|
|
1995
|
+
if (existsSync(configDir)) {
|
|
1996
|
+
console.log(`✅ Config directory: ${configDir}`);
|
|
1997
|
+
|
|
1998
|
+
// Check providers.json
|
|
1999
|
+
try {
|
|
2000
|
+
const providers = loadProviders();
|
|
2001
|
+
const providerCount = Object.keys(providers.providers).length;
|
|
2002
|
+
console.log(`✅ providers.json: ${providerCount} provider(s) configured`);
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
console.log(`❌ providers.json: ${err.message}`);
|
|
2005
|
+
issues++;
|
|
2006
|
+
}
|
|
2007
|
+
} else {
|
|
2008
|
+
console.log(`⚠️ Config directory: ${configDir} (will be created on first use)`);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// Check installed agents
|
|
2012
|
+
console.log(chalk.bold("\nInstalled Agents:"));
|
|
2013
|
+
try {
|
|
2014
|
+
const registry = loadRegistry(repoRoot);
|
|
2015
|
+
const agents = getAgents(registry);
|
|
2016
|
+
for (const [id, agent] of Object.entries(agents)) {
|
|
2017
|
+
const installed = isAgentInstalled(id, agents);
|
|
2018
|
+
console.log(`${installed ? "✅" : "⬜"} ${id.padEnd(14)} ${agent.description || ""}`);
|
|
2019
|
+
}
|
|
2020
|
+
} catch (err) {
|
|
2021
|
+
console.log(chalk.red(` Error: ${err.message}`));
|
|
1574
2022
|
}
|
|
2023
|
+
|
|
2024
|
+
// Summary
|
|
2025
|
+
console.log(chalk.bold(`\n${"═".repeat(40)}`));
|
|
2026
|
+
if (issues === 0) {
|
|
2027
|
+
console.log(chalk.green.bold("✅ All checks passed! CPC is ready to use."));
|
|
2028
|
+
} else {
|
|
2029
|
+
console.log(chalk.red.bold(`❌ Found ${issues} issue(s). Please fix them before using CPC.`));
|
|
2030
|
+
}
|
|
2031
|
+
console.log();
|
|
1575
2032
|
});
|
|
1576
2033
|
}
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* Compare semver versions
|
|
2037
|
+
*/
|
|
2038
|
+
function compareVersions(a, b) {
|
|
2039
|
+
const pa = a.split(".").map(Number);
|
|
2040
|
+
const pb = b.split(".").map(Number);
|
|
2041
|
+
for (let i = 0; i < 3; i++) {
|
|
2042
|
+
if (pa[i] > pb[i]) return 1;
|
|
2043
|
+
if (pa[i] < pb[i]) return -1;
|
|
2044
|
+
}
|
|
2045
|
+
return 0;
|
|
2046
|
+
}
|