@yhong91/cpac 0.1.12 → 0.1.14
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 +37 -2
- package/dist/cpac.js +35 -3
- package/dist/pi-extension.template +42 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ CPAC 将 Codex 和 Claude Code 接入远端 CLIProxyAPI(CPA):
|
|
|
10
10
|
## 要求
|
|
11
11
|
|
|
12
12
|
- [Node.js](https://nodejs.org/) 20 或更新版本(npm 随 Node 安装)
|
|
13
|
-
- 已安装需要使用的 agent:`codex` 或 `
|
|
13
|
+
- 已安装需要使用的 agent:`codex`、`claude`、`pi`、`kimi` 或 `opencode`
|
|
14
14
|
- 有权访问的 CPA API key
|
|
15
15
|
|
|
16
16
|
## 安装
|
|
@@ -31,6 +31,41 @@ npm install -g .
|
|
|
31
31
|
cpac
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## 快速上手
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 1. 安装
|
|
38
|
+
npm install -g @yhong91/cpac
|
|
39
|
+
|
|
40
|
+
# 2. 配置密钥:无参数运行进入交互引导,自动写入 shell 启动文件;
|
|
41
|
+
# 也可手动写入(zsh 示例):
|
|
42
|
+
cpac
|
|
43
|
+
echo 'export CPA_API_KEY="<你的key>"' >> ~/.zshrc && source ~/.zshrc
|
|
44
|
+
|
|
45
|
+
# 3. 验证
|
|
46
|
+
cpac status
|
|
47
|
+
|
|
48
|
+
# 4a. 持久注入(长期生效,写入 agent 配置,可完整恢复)
|
|
49
|
+
cpac install --all # 注入所有检测到的目标(codex/pi/kimi)
|
|
50
|
+
cpac install --target codex # 只注入 Codex(默认开启 multi-agent v2)
|
|
51
|
+
cpac install --target codex --v2_models # 先多选 spawn_agent 模型再注入
|
|
52
|
+
cpac uninstall --target codex # 字节级恢复原配置
|
|
53
|
+
|
|
54
|
+
# 4b. 临时接管(退出无痕,不写任何配置文件)
|
|
55
|
+
cpac claude # Claude Code 走 CPA
|
|
56
|
+
cpac opencode # opencode 走 CPA(模型选 cpac/<模型名>)
|
|
57
|
+
|
|
58
|
+
# 5. 可选:Claude 模型槽位配置(持久缓存,每次 cpac claude 自动生效)
|
|
59
|
+
cpac claude-models --haiku # 下拉选择 haiku 槽模型
|
|
60
|
+
cpac claude-models --classifier <模型> # Auto mode 审批分类器模型
|
|
61
|
+
|
|
62
|
+
# 6. 升级
|
|
63
|
+
cpac upgrade --check
|
|
64
|
+
npm install -g @yhong91/cpac@latest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
各命令的完整参数与行为见下方对应章节。
|
|
68
|
+
|
|
34
69
|
## 首次引导
|
|
35
70
|
|
|
36
71
|
无参数运行 `cpac` 会显示当前 CPA、密钥状态和可用命令:
|
|
@@ -157,7 +192,7 @@ Claude Code 的发现协议不携带 context 信息,claude 前缀的未知模
|
|
|
157
192
|
注入 CPA 模型目录:
|
|
158
193
|
|
|
159
194
|
```bash
|
|
160
|
-
cpac inject
|
|
195
|
+
cpac inject [--v2_off] [--v2_models] [--config PATH]
|
|
161
196
|
```
|
|
162
197
|
|
|
163
198
|
查看状态:
|
package/dist/cpac.js
CHANGED
|
@@ -734,6 +734,10 @@ export async function createLoopbackProxy(cpaUrl, apiKey, proxyId, port) {
|
|
|
734
734
|
response.end(JSON.stringify({ error: "CPA upstream request failed" }));
|
|
735
735
|
});
|
|
736
736
|
request.once("aborted", () => upstream.destroy());
|
|
737
|
+
response.once("close", () => {
|
|
738
|
+
if (!response.writableEnded)
|
|
739
|
+
upstream.destroy();
|
|
740
|
+
});
|
|
737
741
|
request.pipe(upstream);
|
|
738
742
|
});
|
|
739
743
|
server.on("upgrade", (_request, socket) => {
|
|
@@ -941,6 +945,11 @@ export async function createClaudeProxy(cpaUrl, apiKey) {
|
|
|
941
945
|
if (!response.writableEnded)
|
|
942
946
|
response.end(JSON.stringify({ error: "CPA upstream request failed" }));
|
|
943
947
|
});
|
|
948
|
+
request.once("aborted", () => upstream.destroy());
|
|
949
|
+
response.once("close", () => {
|
|
950
|
+
if (!response.writableEnded)
|
|
951
|
+
upstream.destroy();
|
|
952
|
+
});
|
|
944
953
|
upstream.end(body);
|
|
945
954
|
});
|
|
946
955
|
});
|
|
@@ -1980,7 +1989,8 @@ export async function runUpgrade(checkOnly) {
|
|
|
1980
1989
|
function usage() {
|
|
1981
1990
|
return [
|
|
1982
1991
|
"Usage: cpac",
|
|
1983
|
-
" cpac
|
|
1992
|
+
" cpac inject [--v2_off] [--v2_models] [--config PATH]",
|
|
1993
|
+
" cpac <status|restore> [--config PATH]",
|
|
1984
1994
|
" cpac proxy [--config PATH]",
|
|
1985
1995
|
" cpac claude [--config PATH] [--] [claude args...]",
|
|
1986
1996
|
" cpac opencode [--config PATH] [--] [opencode args...]",
|
|
@@ -2166,6 +2176,8 @@ function parseArgs(args) {
|
|
|
2166
2176
|
}
|
|
2167
2177
|
let configPath = defaultConfigPath();
|
|
2168
2178
|
const positional = [];
|
|
2179
|
+
let v2Off = false;
|
|
2180
|
+
let v2Models = false;
|
|
2169
2181
|
for (let index = 0; index < args.length; index += 1) {
|
|
2170
2182
|
if (args[index] === "--config") {
|
|
2171
2183
|
const value = args[++index];
|
|
@@ -2173,6 +2185,12 @@ function parseArgs(args) {
|
|
|
2173
2185
|
throw new CPACError("--config requires a path");
|
|
2174
2186
|
configPath = resolve(expandUserPath(value));
|
|
2175
2187
|
}
|
|
2188
|
+
else if (args[index] === "--v2_off" || args[index] === "--v2-off") {
|
|
2189
|
+
v2Off = true;
|
|
2190
|
+
}
|
|
2191
|
+
else if (args[index] === "--v2_models" || args[index] === "--v2-models") {
|
|
2192
|
+
v2Models = true;
|
|
2193
|
+
}
|
|
2176
2194
|
else if (args[index].startsWith("-")) {
|
|
2177
2195
|
throw new CPACError(`unknown option: ${args[index]}`);
|
|
2178
2196
|
}
|
|
@@ -2183,6 +2201,12 @@ function parseArgs(args) {
|
|
|
2183
2201
|
!["inject", "status", "restore", "proxy"].includes(positional[0])) {
|
|
2184
2202
|
throw new CPACError(usage());
|
|
2185
2203
|
}
|
|
2204
|
+
if (positional[0] !== "inject" && (v2Off || v2Models)) {
|
|
2205
|
+
throw new CPACError("--v2_off / --v2_models is only valid with inject or install");
|
|
2206
|
+
}
|
|
2207
|
+
if (positional[0] === "inject") {
|
|
2208
|
+
return { command: "inject", configPath, v2Off, v2Models };
|
|
2209
|
+
}
|
|
2186
2210
|
return {
|
|
2187
2211
|
command: positional[0],
|
|
2188
2212
|
configPath,
|
|
@@ -2244,8 +2268,16 @@ export async function main(args = process.argv.slice(2)) {
|
|
|
2244
2268
|
return await runPi(config, parsed.action);
|
|
2245
2269
|
if (parsed.command === "kimi")
|
|
2246
2270
|
return await runKimi(config, parsed.action);
|
|
2247
|
-
if (parsed.command === "inject")
|
|
2248
|
-
|
|
2271
|
+
if (parsed.command === "inject") {
|
|
2272
|
+
let injectConfig = config;
|
|
2273
|
+
if (parsed.v2Models) {
|
|
2274
|
+
const picked = await pickSpawnModels(config);
|
|
2275
|
+
saveSpawnModels(parsed.configPath, picked);
|
|
2276
|
+
console.log(`spawn_models saved to ${parsed.configPath}: ${picked.join(", ")}`);
|
|
2277
|
+
injectConfig = { ...config, spawn_models: picked };
|
|
2278
|
+
}
|
|
2279
|
+
await inject(injectConfig, parsed.v2Off);
|
|
2280
|
+
}
|
|
2249
2281
|
else if (parsed.command === "restore")
|
|
2250
2282
|
await restore(config);
|
|
2251
2283
|
else
|
|
@@ -5,6 +5,7 @@ const CPA = "__CPA_URL__";
|
|
|
5
5
|
const BUILTIN = new Set(["openai", "github-copilot", "xai", "deepseek", "anthropic", "google"]);
|
|
6
6
|
const VENDOR_PREFIXES = [
|
|
7
7
|
["gpt-", "openai"], ["o1-", "openai"], ["o3-", "openai"], ["o4-", "openai"],
|
|
8
|
+
["gpt-image", "openai"],
|
|
8
9
|
["claude-", "anthropic"], ["gemini-", "google"], ["grok-", "xai"],
|
|
9
10
|
["deepseek-", "deepseek"], ["glm-", "zhipu"], ["kimi-", "moonshot"],
|
|
10
11
|
["mimo-", "xiaomi"], ["doubao-", "volcengine"], ["ark-", "volcengine"],
|
|
@@ -21,6 +22,9 @@ const MAX_TOKENS = {
|
|
|
21
22
|
"doubao-seed-2.0-lite": 128000, "doubao-seed-2.1-turbo": 128000,
|
|
22
23
|
};
|
|
23
24
|
const DEFAULT_MAX_TOKENS = 65536;
|
|
25
|
+
const REASONING_EFFORTS = new Set([
|
|
26
|
+
"minimal", "low", "medium", "high", "xhigh", "max", "ultra",
|
|
27
|
+
]);
|
|
24
28
|
|
|
25
29
|
function vendorFor(slug) {
|
|
26
30
|
for (const [prefix, vendor] of VENDOR_PREFIXES) {
|
|
@@ -33,6 +37,32 @@ function groupName(vendor) {
|
|
|
33
37
|
return BUILTIN.has(vendor) ? vendor + "-cpa" : vendor;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
function effortsFor(row) {
|
|
41
|
+
const levels = row.supported_reasoning_levels;
|
|
42
|
+
if (!Array.isArray(levels)) return undefined;
|
|
43
|
+
const efforts = levels.flatMap(function (level) {
|
|
44
|
+
if (!level || typeof level !== "object") return [];
|
|
45
|
+
const effort = level.effort;
|
|
46
|
+
return typeof effort === "string" && REASONING_EFFORTS.has(effort) ? [effort] : [];
|
|
47
|
+
});
|
|
48
|
+
return efforts.length > 0 ? efforts : undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function thinkingLevelMapFor(efforts) {
|
|
52
|
+
if (!efforts || !efforts.some(function (effort) { return REASONING_EFFORTS.has(effort); })) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const available = new Set(efforts);
|
|
56
|
+
return {
|
|
57
|
+
minimal: available.has("minimal") ? "minimal" : available.has("low") ? "low" : null,
|
|
58
|
+
low: available.has("low") ? "low" : null,
|
|
59
|
+
medium: available.has("medium") ? "medium" : null,
|
|
60
|
+
high: available.has("high") ? "high" : null,
|
|
61
|
+
xhigh: available.has("xhigh") ? "xhigh" : null,
|
|
62
|
+
max: available.has("max") || available.has("ultra") ? "max" : null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
36
66
|
function intField(value) {
|
|
37
67
|
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
38
68
|
? Math.floor(value) : undefined;
|
|
@@ -72,7 +102,12 @@ export default async function (pi) {
|
|
|
72
102
|
const slug = typeof m.slug === "string" && m.slug.trim()
|
|
73
103
|
? m.slug.trim()
|
|
74
104
|
: typeof m.id === "string" && m.id.trim() ? m.id.trim() : null;
|
|
75
|
-
if (slug) rows.push({
|
|
105
|
+
if (slug) rows.push({
|
|
106
|
+
slug: slug,
|
|
107
|
+
display_name: m.display_name,
|
|
108
|
+
supported_reasoning_levels: m.supported_reasoning_levels,
|
|
109
|
+
context_window: m.context_window,
|
|
110
|
+
});
|
|
76
111
|
}
|
|
77
112
|
if (rows.length === 0) {
|
|
78
113
|
console.error("[cpac] CPA models response has no usable models");
|
|
@@ -92,15 +127,19 @@ export default async function (pi) {
|
|
|
92
127
|
apiKey: apiKey,
|
|
93
128
|
api: "openai-responses",
|
|
94
129
|
models: models.map(function (m) {
|
|
95
|
-
|
|
130
|
+
const efforts = effortsFor(m);
|
|
131
|
+
const thinkingLevelMap = thinkingLevelMapFor(efforts);
|
|
132
|
+
const modelObj = {
|
|
96
133
|
id: m.slug,
|
|
97
134
|
name: typeof m.display_name === "string" ? m.display_name : m.slug,
|
|
98
|
-
reasoning:
|
|
135
|
+
reasoning: efforts === undefined || thinkingLevelMap !== undefined,
|
|
99
136
|
input: ["text", "image"],
|
|
100
137
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
101
138
|
contextWindow: intField(m.context_window) || 200000,
|
|
102
139
|
maxTokens: MAX_TOKENS[m.slug] || DEFAULT_MAX_TOKENS,
|
|
103
140
|
};
|
|
141
|
+
if (thinkingLevelMap) modelObj.thinkingLevelMap = thinkingLevelMap;
|
|
142
|
+
return modelObj;
|
|
104
143
|
}),
|
|
105
144
|
});
|
|
106
145
|
}
|