@yhong91/cpac 0.1.32 → 0.1.34
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 -46
- package/cpac.example.json +3 -1
- package/dist/agents.js +67 -26
- package/dist/config.js +93 -11
- package/dist/cpac.js +120 -69
- package/dist/targets/claude.js +39 -18
- package/dist/targets/codex.js +31 -4
- package/dist/targets/pi.js +119 -35
- package/dist/util.js +173 -0
- package/{dist/pi-extension.template → extensions/pi-cpac.ts} +91 -36
- package/package.json +12 -3
package/dist/util.js
CHANGED
|
@@ -114,6 +114,179 @@ export async function checkboxPicker(title, items, max) {
|
|
|
114
114
|
render();
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
+
export function wizardFrame(tabs, index) {
|
|
118
|
+
const confirming = index >= tabs.length;
|
|
119
|
+
const bar = tabs
|
|
120
|
+
.map((tab, i) => (i === index && !confirming ? `[${tab.label}]` : tab.label))
|
|
121
|
+
.concat(confirming ? ["[Confirm]"] : [])
|
|
122
|
+
.join(" ");
|
|
123
|
+
if (confirming) {
|
|
124
|
+
const width = Math.max(...tabs.map((tab) => tab.label.length));
|
|
125
|
+
return [
|
|
126
|
+
bar,
|
|
127
|
+
"",
|
|
128
|
+
...tabs.map((tab) => {
|
|
129
|
+
const values = [...tab.selected]
|
|
130
|
+
.sort((a, b) => a - b)
|
|
131
|
+
.map((i) => tab.items[i])
|
|
132
|
+
.join(", ");
|
|
133
|
+
return ` ${tab.label.padEnd(width)} ${values || "(none)"}`;
|
|
134
|
+
}),
|
|
135
|
+
"",
|
|
136
|
+
"enter: apply ←: back q: cancel",
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
const tab = tabs[index];
|
|
140
|
+
const hint = tab.max === 1
|
|
141
|
+
? "space/enter: select ←/→: tab q: cancel"
|
|
142
|
+
: `space: toggle enter: next ←/→: tab q: cancel max ${tab.max}`;
|
|
143
|
+
return [
|
|
144
|
+
bar,
|
|
145
|
+
hint,
|
|
146
|
+
...tab.items.map((item, i) => {
|
|
147
|
+
const box = tab.selected.has(i) ? "[x]" : "[ ]";
|
|
148
|
+
return `${i === tab.cursor ? ">" : " "} ${box} ${item}`;
|
|
149
|
+
}),
|
|
150
|
+
];
|
|
151
|
+
}
|
|
152
|
+
function initWizardTab(tab) {
|
|
153
|
+
if (tab.items.length === 0)
|
|
154
|
+
throw new CPACError(`${tab.label} has no options`);
|
|
155
|
+
const selected = new Set();
|
|
156
|
+
for (const label of tab.selected ?? []) {
|
|
157
|
+
const found = tab.items.indexOf(label);
|
|
158
|
+
if (found < 0)
|
|
159
|
+
continue;
|
|
160
|
+
selected.add(found);
|
|
161
|
+
if (selected.size >= tab.max)
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (tab.max === 1 && selected.size === 0)
|
|
165
|
+
selected.add(0);
|
|
166
|
+
return {
|
|
167
|
+
id: tab.id,
|
|
168
|
+
label: tab.label,
|
|
169
|
+
items: tab.items,
|
|
170
|
+
max: tab.max,
|
|
171
|
+
selected,
|
|
172
|
+
cursor: selected.size > 0 ? Math.min(...selected) : 0,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export async function tabWizard(tabs) {
|
|
176
|
+
if (!process.stdin.isTTY || !process.stderr.isTTY) {
|
|
177
|
+
throw new CPACError("model selection requires an interactive terminal");
|
|
178
|
+
}
|
|
179
|
+
if (tabs.length === 0)
|
|
180
|
+
throw new CPACError("setup has no options");
|
|
181
|
+
const state = tabs.map(initWizardTab);
|
|
182
|
+
let index = 0;
|
|
183
|
+
const stdin = process.stdin;
|
|
184
|
+
const out = process.stderr;
|
|
185
|
+
let painted = 0;
|
|
186
|
+
const paint = (lines) => {
|
|
187
|
+
if (painted > 0) {
|
|
188
|
+
out.write(`\x1b[${painted}F`);
|
|
189
|
+
for (let line = 0; line < painted; line += 1)
|
|
190
|
+
out.write("\x1b[2K\x1b[1E");
|
|
191
|
+
out.write(`\x1b[${painted}F`);
|
|
192
|
+
}
|
|
193
|
+
out.write(`${lines.join("\n")}\n`);
|
|
194
|
+
painted = lines.length;
|
|
195
|
+
};
|
|
196
|
+
const clear = () => {
|
|
197
|
+
if (painted > 0) {
|
|
198
|
+
out.write(`\x1b[${painted}F`);
|
|
199
|
+
for (let line = 0; line < painted; line += 1)
|
|
200
|
+
out.write("\x1b[2K\x1b[1E");
|
|
201
|
+
out.write(`\x1b[${painted}F`);
|
|
202
|
+
painted = 0;
|
|
203
|
+
}
|
|
204
|
+
out.write("\x1b[?25h");
|
|
205
|
+
};
|
|
206
|
+
const resultOf = () => Object.fromEntries(state.map((tab) => [
|
|
207
|
+
tab.id,
|
|
208
|
+
[...tab.selected]
|
|
209
|
+
.sort((a, b) => a - b)
|
|
210
|
+
.map((i) => tab.items[i]),
|
|
211
|
+
]));
|
|
212
|
+
const goNext = () => {
|
|
213
|
+
if (index < state.length && state[index].selected.size === 0)
|
|
214
|
+
return;
|
|
215
|
+
if (index < state.length)
|
|
216
|
+
index += 1;
|
|
217
|
+
};
|
|
218
|
+
const goPrev = () => {
|
|
219
|
+
if (index > 0)
|
|
220
|
+
index -= 1;
|
|
221
|
+
};
|
|
222
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
223
|
+
const finish = (error) => {
|
|
224
|
+
stdin.removeListener("data", onData);
|
|
225
|
+
try {
|
|
226
|
+
stdin.setRawMode(false);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
// Terminal already gone; nothing to restore.
|
|
230
|
+
}
|
|
231
|
+
stdin.pause();
|
|
232
|
+
clear();
|
|
233
|
+
if (error)
|
|
234
|
+
rejectPromise(error);
|
|
235
|
+
else
|
|
236
|
+
resolvePromise(resultOf());
|
|
237
|
+
};
|
|
238
|
+
const onData = (chunk) => {
|
|
239
|
+
const key = chunk.toString("utf8");
|
|
240
|
+
if (key === "\x03" || key === "q" || key === "\x1b") {
|
|
241
|
+
finish(new CPACError("model selection cancelled"));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (index >= state.length) {
|
|
245
|
+
if (key === "\r" || key === "\n") {
|
|
246
|
+
finish();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (key === "\x1b[D")
|
|
250
|
+
goPrev();
|
|
251
|
+
paint(wizardFrame(state, index));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const tab = state[index];
|
|
255
|
+
if (key === "\x1b[A")
|
|
256
|
+
tab.cursor = (tab.cursor + tab.items.length - 1) % tab.items.length;
|
|
257
|
+
else if (key === "\x1b[B")
|
|
258
|
+
tab.cursor = (tab.cursor + 1) % tab.items.length;
|
|
259
|
+
else if (key === "\x1b[C")
|
|
260
|
+
goNext();
|
|
261
|
+
else if (key === "\x1b[D")
|
|
262
|
+
goPrev();
|
|
263
|
+
else if (key === " ") {
|
|
264
|
+
if (tab.max === 1) {
|
|
265
|
+
tab.selected.clear();
|
|
266
|
+
tab.selected.add(tab.cursor);
|
|
267
|
+
goNext();
|
|
268
|
+
}
|
|
269
|
+
else if (tab.selected.has(tab.cursor))
|
|
270
|
+
tab.selected.delete(tab.cursor);
|
|
271
|
+
else if (tab.selected.size < tab.max)
|
|
272
|
+
tab.selected.add(tab.cursor);
|
|
273
|
+
}
|
|
274
|
+
else if (key === "\r" || key === "\n") {
|
|
275
|
+
if (tab.max === 1) {
|
|
276
|
+
tab.selected.clear();
|
|
277
|
+
tab.selected.add(tab.cursor);
|
|
278
|
+
}
|
|
279
|
+
goNext();
|
|
280
|
+
}
|
|
281
|
+
paint(wizardFrame(state, index));
|
|
282
|
+
};
|
|
283
|
+
stdin.setRawMode(true);
|
|
284
|
+
stdin.resume();
|
|
285
|
+
stdin.on("data", onData);
|
|
286
|
+
out.write("\x1b[?25l");
|
|
287
|
+
paint(wizardFrame(state, index));
|
|
288
|
+
});
|
|
289
|
+
}
|
|
117
290
|
export function tomlString(value) {
|
|
118
291
|
return JSON.stringify(value);
|
|
119
292
|
}
|
|
@@ -1,29 +1,67 @@
|
|
|
1
1
|
// Pi CPAC extension: register CPA models as Pi providers
|
|
2
|
-
//
|
|
2
|
+
// Loaded via package.json "pi.extensions" when settings include npm:@yhong91/cpac
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
// keep in sync with src/config.ts DEFAULT_CPA_URL
|
|
5
|
+
const CPA = (process.env.CPA_BASE_URL || "http://124.223.178.52:8317").replace(
|
|
6
|
+
/\/+$/,
|
|
7
|
+
"",
|
|
8
|
+
);
|
|
9
|
+
const BUILTIN = new Set([
|
|
10
|
+
"openai",
|
|
11
|
+
"github-copilot",
|
|
12
|
+
"xai",
|
|
13
|
+
"deepseek",
|
|
14
|
+
"anthropic",
|
|
15
|
+
"google",
|
|
16
|
+
]);
|
|
6
17
|
const VENDOR_PREFIXES = [
|
|
7
|
-
["gpt-", "openai"],
|
|
18
|
+
["gpt-", "openai"],
|
|
19
|
+
["o1-", "openai"],
|
|
20
|
+
["o3-", "openai"],
|
|
21
|
+
["o4-", "openai"],
|
|
8
22
|
["gpt-image", "openai"],
|
|
9
|
-
["claude-", "anthropic"],
|
|
10
|
-
["
|
|
11
|
-
["
|
|
12
|
-
["
|
|
23
|
+
["claude-", "anthropic"],
|
|
24
|
+
["gemini-", "google"],
|
|
25
|
+
["grok-", "xai"],
|
|
26
|
+
["deepseek-", "deepseek"],
|
|
27
|
+
["glm-", "zhipu"],
|
|
28
|
+
["kimi-", "moonshot"],
|
|
29
|
+
["mimo-", "xiaomi"],
|
|
30
|
+
["doubao-", "volcengine"],
|
|
31
|
+
["ark-", "volcengine"],
|
|
32
|
+
["minimax-", "minimax"],
|
|
33
|
+
["step-", "stepfun"],
|
|
34
|
+
["qwen-", "alibaba"],
|
|
13
35
|
["hunyuan-", "tencent"],
|
|
14
36
|
];
|
|
15
37
|
const MAX_TOKENS = {
|
|
16
|
-
"gpt-5.6-sol": 128000,
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
38
|
+
"gpt-5.6-sol": 128000,
|
|
39
|
+
"gpt-5.6-terra": 128000,
|
|
40
|
+
"gpt-5.6-luna": 128000,
|
|
41
|
+
"claude-opus-4-6-thinking": 128000,
|
|
42
|
+
"claude-sonnet-4-6": 64000,
|
|
43
|
+
"gemini-3.6-flash": 65536,
|
|
44
|
+
"gemini-3.6-flash-high": 65536,
|
|
45
|
+
"deepseek-v4-pro": 128000,
|
|
46
|
+
"deepseek-v4-flash": 128000,
|
|
47
|
+
"glm-5.3": 128000,
|
|
48
|
+
"kimi-k2.7-code": 128000,
|
|
49
|
+
"minimax-m3": 128000,
|
|
50
|
+
"mimo-v2.5": 131072,
|
|
51
|
+
"mimo-v2.5-pro": 131072,
|
|
52
|
+
"grok-4.5": 128000,
|
|
53
|
+
"doubao-seed-2.0-lite": 128000,
|
|
54
|
+
"doubao-seed-2.1-turbo": 128000,
|
|
23
55
|
};
|
|
24
56
|
const DEFAULT_MAX_TOKENS = 65536;
|
|
25
57
|
const REASONING_EFFORTS = new Set([
|
|
26
|
-
"minimal",
|
|
58
|
+
"minimal",
|
|
59
|
+
"low",
|
|
60
|
+
"medium",
|
|
61
|
+
"high",
|
|
62
|
+
"xhigh",
|
|
63
|
+
"max",
|
|
64
|
+
"ultra",
|
|
27
65
|
]);
|
|
28
66
|
|
|
29
67
|
function vendorFor(slug) {
|
|
@@ -40,21 +78,27 @@ function groupName(vendor) {
|
|
|
40
78
|
function effortsFor(row) {
|
|
41
79
|
const levels = row.supported_reasoning_levels;
|
|
42
80
|
if (!Array.isArray(levels)) return undefined;
|
|
43
|
-
const efforts = levels.flatMap(
|
|
81
|
+
const efforts = levels.flatMap((level) => {
|
|
44
82
|
if (!level || typeof level !== "object") return [];
|
|
45
83
|
const effort = level.effort;
|
|
46
|
-
return typeof effort === "string" && REASONING_EFFORTS.has(effort)
|
|
84
|
+
return typeof effort === "string" && REASONING_EFFORTS.has(effort)
|
|
85
|
+
? [effort]
|
|
86
|
+
: [];
|
|
47
87
|
});
|
|
48
88
|
return efforts.length > 0 ? efforts : undefined;
|
|
49
89
|
}
|
|
50
90
|
|
|
51
91
|
function thinkingLevelMapFor(efforts) {
|
|
52
|
-
if (!efforts || !efforts.some(
|
|
92
|
+
if (!efforts || !efforts.some((effort) => REASONING_EFFORTS.has(effort))) {
|
|
53
93
|
return undefined;
|
|
54
94
|
}
|
|
55
95
|
const available = new Set(efforts);
|
|
56
96
|
return {
|
|
57
|
-
minimal: available.has("minimal")
|
|
97
|
+
minimal: available.has("minimal")
|
|
98
|
+
? "minimal"
|
|
99
|
+
: available.has("low")
|
|
100
|
+
? "low"
|
|
101
|
+
: null,
|
|
58
102
|
low: available.has("low") ? "low" : null,
|
|
59
103
|
medium: available.has("medium") ? "medium" : null,
|
|
60
104
|
high: available.has("high") ? "high" : null,
|
|
@@ -65,7 +109,8 @@ function thinkingLevelMapFor(efforts) {
|
|
|
65
109
|
|
|
66
110
|
function intField(value) {
|
|
67
111
|
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
68
|
-
? Math.floor(value)
|
|
112
|
+
? Math.floor(value)
|
|
113
|
+
: undefined;
|
|
69
114
|
}
|
|
70
115
|
|
|
71
116
|
export default async function (pi) {
|
|
@@ -86,12 +131,18 @@ export default async function (pi) {
|
|
|
86
131
|
}
|
|
87
132
|
payload = await res.json();
|
|
88
133
|
} catch (error) {
|
|
89
|
-
console.error(
|
|
134
|
+
console.error(
|
|
135
|
+
"[pi-cpac] CPA models request failed: " +
|
|
136
|
+
((error && error.message) || error),
|
|
137
|
+
);
|
|
90
138
|
return;
|
|
91
139
|
}
|
|
92
|
-
const source =
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
const source =
|
|
141
|
+
payload && Array.isArray(payload.models)
|
|
142
|
+
? payload.models
|
|
143
|
+
: payload && Array.isArray(payload.data)
|
|
144
|
+
? payload.data
|
|
145
|
+
: null;
|
|
95
146
|
if (!source) {
|
|
96
147
|
console.error("[pi-cpac] CPA models response has no models list");
|
|
97
148
|
return;
|
|
@@ -99,15 +150,19 @@ export default async function (pi) {
|
|
|
99
150
|
const rows = [];
|
|
100
151
|
for (const m of source) {
|
|
101
152
|
if (!m || typeof m !== "object") continue;
|
|
102
|
-
const slug =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
153
|
+
const slug =
|
|
154
|
+
typeof m.slug === "string" && m.slug.trim()
|
|
155
|
+
? m.slug.trim()
|
|
156
|
+
: typeof m.id === "string" && m.id.trim()
|
|
157
|
+
? m.id.trim()
|
|
158
|
+
: null;
|
|
159
|
+
if (slug)
|
|
160
|
+
rows.push({
|
|
161
|
+
slug: slug,
|
|
162
|
+
display_name: m.display_name,
|
|
163
|
+
supported_reasoning_levels: m.supported_reasoning_levels,
|
|
164
|
+
context_window: m.context_window,
|
|
165
|
+
});
|
|
111
166
|
}
|
|
112
167
|
if (rows.length === 0) {
|
|
113
168
|
console.error("[pi-cpac] CPA models response has no usable models");
|
|
@@ -126,7 +181,7 @@ export default async function (pi) {
|
|
|
126
181
|
baseUrl: CPA + "/v1",
|
|
127
182
|
apiKey: apiKey,
|
|
128
183
|
api: "openai-responses",
|
|
129
|
-
models: models.map(
|
|
184
|
+
models: models.map((m) => {
|
|
130
185
|
const efforts = effortsFor(m);
|
|
131
186
|
const thinkingLevelMap = thinkingLevelMapFor(efforts);
|
|
132
187
|
const modelObj = {
|
|
@@ -139,7 +194,7 @@ export default async function (pi) {
|
|
|
139
194
|
maxTokens: MAX_TOKENS[m.slug] || DEFAULT_MAX_TOKENS,
|
|
140
195
|
compat: {
|
|
141
196
|
sendSessionAffinityHeaders: true,
|
|
142
|
-
sessionAffinityFormat: "openai",
|
|
197
|
+
sessionAffinityFormat: "openai-nosession",
|
|
143
198
|
},
|
|
144
199
|
};
|
|
145
200
|
if (thinkingLevelMap) modelObj.thinkingLevelMap = thinkingLevelMap;
|
package/package.json
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yhong91/cpac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.34",
|
|
4
4
|
"description": "Connect Codex and Claude Code to a remote CLIProxyAPI gateway",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cpac": "dist/cpac.js"
|
|
8
8
|
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"pi-package"
|
|
11
|
+
],
|
|
12
|
+
"pi": {
|
|
13
|
+
"extensions": [
|
|
14
|
+
"./extensions/pi-cpac.ts"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
9
17
|
"files": [
|
|
10
18
|
"dist",
|
|
19
|
+
"extensions",
|
|
11
20
|
"cpac.example.json",
|
|
12
21
|
"README.md"
|
|
13
22
|
],
|
|
@@ -28,9 +37,9 @@
|
|
|
28
37
|
},
|
|
29
38
|
"scripts": {
|
|
30
39
|
"clean": "node -e \"for (const d of ['dist','dist-test']) require('fs').rmSync(d,{recursive:true,force:true})\"",
|
|
31
|
-
"build": "npm run clean --if-present && tsc -p tsconfig.build.json &&
|
|
40
|
+
"build": "npm run clean --if-present && tsc -p tsconfig.build.json && node -e \"const fs=require('fs');const p='dist/cpac.js';const s=fs.readFileSync(p,'utf8');if(!s.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+s);try{fs.chmodSync(p,0o755)}catch{}\"",
|
|
32
41
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
33
|
-
"test": "npm run clean && tsc -p tsconfig.test.json &&
|
|
42
|
+
"test": "npm run clean && tsc -p tsconfig.test.json && node --test --test-reporter=spec dist-test/cpac.test.js",
|
|
34
43
|
"pack:check": "npm pack --dry-run"
|
|
35
44
|
},
|
|
36
45
|
"devDependencies": {
|