@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/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
- // Installed by: cpac pi install
2
+ // Loaded via package.json "pi.extensions" when settings include npm:@yhong91/cpac
3
3
 
4
- const CPA = "__CPA_URL__";
5
- const BUILTIN = new Set(["openai", "github-copilot", "xai", "deepseek", "anthropic", "google"]);
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"], ["o1-", "openai"], ["o3-", "openai"], ["o4-", "openai"],
18
+ ["gpt-", "openai"],
19
+ ["o1-", "openai"],
20
+ ["o3-", "openai"],
21
+ ["o4-", "openai"],
8
22
  ["gpt-image", "openai"],
9
- ["claude-", "anthropic"], ["gemini-", "google"], ["grok-", "xai"],
10
- ["deepseek-", "deepseek"], ["glm-", "zhipu"], ["kimi-", "moonshot"],
11
- ["mimo-", "xiaomi"], ["doubao-", "volcengine"], ["ark-", "volcengine"],
12
- ["minimax-", "minimax"], ["step-", "stepfun"], ["qwen-", "alibaba"],
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, "gpt-5.6-terra": 128000, "gpt-5.6-luna": 128000,
17
- "claude-opus-4-6-thinking": 128000, "claude-sonnet-4-6": 64000,
18
- "gemini-3.6-flash": 65536, "gemini-3.6-flash-high": 65536,
19
- "deepseek-v4-pro": 128000, "deepseek-v4-flash": 128000,
20
- "glm-5.3": 128000, "kimi-k2.7-code": 128000, "minimax-m3": 128000,
21
- "mimo-v2.5": 131072, "mimo-v2.5-pro": 131072, "grok-4.5": 128000,
22
- "doubao-seed-2.0-lite": 128000, "doubao-seed-2.1-turbo": 128000,
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", "low", "medium", "high", "xhigh", "max", "ultra",
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(function (level) {
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) ? [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(function (effort) { return REASONING_EFFORTS.has(effort); })) {
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") ? "minimal" : available.has("low") ? "low" : null,
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) : undefined;
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("[pi-cpac] CPA models request failed: " + (error && error.message || 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 = payload && Array.isArray(payload.models)
93
- ? payload.models
94
- : payload && Array.isArray(payload.data) ? payload.data : null;
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 = typeof m.slug === "string" && m.slug.trim()
103
- ? m.slug.trim()
104
- : typeof m.id === "string" && m.id.trim() ? m.id.trim() : null;
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
- });
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(function (m) {
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.32",
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 && cp src/pi-extension.template dist/ && 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{}\"",
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 && cp src/pi-extension.template dist-test/src/ && node --test --test-reporter=spec dist-test/cpac.test.js",
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": {