abelworkflow 1.1.3 → 1.2.0
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/LICENSE +21 -0
- package/README.md +30 -29
- package/extensions/gpt-responses-compat.ts +213 -0
- package/extensions/k2think-compat.ts +40 -0
- package/lib/cli/args.mjs +5 -19
- package/lib/cli/main.mjs +105 -72
- package/lib/cli/prompts.mjs +9 -2
- package/lib/config/dotenv.mjs +1 -1
- package/lib/config/jsonc.mjs +1 -1
- package/lib/config/store.mjs +197 -45
- package/lib/config/toml.mjs +147 -453
- package/lib/installer/assets.mjs +67 -45
- package/lib/installer/install.mjs +202 -45
- package/lib/installer/links.mjs +124 -101
- package/lib/installer/lock.mjs +82 -0
- package/lib/installer/state.mjs +14 -21
- package/lib/paths.mjs +9 -9
- package/lib/providers/claude.mjs +58 -114
- package/lib/providers/codex.mjs +101 -198
- package/lib/providers/pi.mjs +331 -497
- package/lib/providers/skills.mjs +88 -68
- package/lib/providers/url.mjs +35 -0
- package/lib/templates/workflow/commands/abel-design.md +23 -161
- package/lib/templates/workflow/commands/abel-diagnose.md +30 -56
- package/lib/templates/workflow/commands/abel-implement.md +30 -150
- package/lib/templates/workflow/commands/abel-init.md +1 -1
- package/lib/tools/cli-installer.mjs +29 -190
- package/package.json +7 -9
- package/skills/context7-auto-research/context7-api.cjs +75 -17
- package/skills/dev-browser/SKILL.md +15 -1
- package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
- package/skills/dev-browser/dist/scripts/start.js +8 -36
- package/skills/dev-browser/dist/src/client.d.ts +3 -4
- package/skills/dev-browser/dist/src/client.js +117 -117
- package/skills/dev-browser/dist/src/entrypoint.d.ts +1 -1
- package/skills/dev-browser/dist/src/entrypoint.js +38 -31
- package/skills/dev-browser/dist/src/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/index.js +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts +0 -1
- package/skills/dev-browser/dist/src/page-api.js +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts +0 -1
- package/skills/dev-browser/dist/src/relay.js +53 -7
- package/skills/dev-browser/dist/src/runtime.d.ts +3 -14
- package/skills/dev-browser/dist/src/runtime.js +5 -15
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts +0 -1
- package/skills/dev-browser/dist/src/standalone.js +2 -3
- package/skills/dev-browser/dist/src/startup.d.ts +3 -12
- package/skills/dev-browser/dist/src/startup.js +6 -10
- package/skills/dev-browser/dist/src/target-registry.d.ts +0 -1
- package/skills/dev-browser/dist/src/target-registry.js +0 -1
- package/skills/dev-browser/dist/src/types.d.ts +0 -1
- package/skills/dev-browser/dist/src/types.js +0 -1
- package/skills/dev-browser/package-lock.json +1 -1
- package/skills/dev-browser/package.json +2 -2
- package/skills/grok-search/.env.example +1 -1
- package/skills/grok-search/SKILL.md +13 -16
- package/skills/grok-search/scripts/_dotenv.py +1 -1
- package/skills/grok-search/scripts/groksearch_cli.py +271 -195
- package/skills/time/SKILL.md +5 -2
- package/skills/time/scripts/time_cli.py +61 -22
- package/extensions/pi-gpt-responses-compat/index.ts +0 -25
- package/lib/templates/codex/config-base.toml +0 -103
- package/skills/dev-browser/dist/scripts/start.d.ts.map +0 -1
- package/skills/dev-browser/dist/scripts/start.js.map +0 -1
- package/skills/dev-browser/dist/src/client.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/client.js.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.js.map +0 -1
- package/skills/dev-browser/dist/src/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/index.js.map +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/page-api.js.map +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/relay.js.map +0 -1
- package/skills/dev-browser/dist/src/runtime.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/runtime.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/standalone.js.map +0 -1
- package/skills/dev-browser/dist/src/startup.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/startup.js.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.js.map +0 -1
- package/skills/dev-browser/dist/src/types.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/types.js.map +0 -1
- package/skills/grok-search/gitignore.template +0 -4
- package/skills/grok-search/scripts/groksearch_entry.py +0 -131
package/lib/providers/pi.mjs
CHANGED
|
@@ -1,198 +1,14 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import * as p from "@clack/prompts";
|
|
4
2
|
import {
|
|
3
|
+
assertPlainObject,
|
|
5
4
|
readJsonFileSafe,
|
|
6
5
|
readJsoncFileSafe,
|
|
7
|
-
|
|
8
|
-
writeJson,
|
|
9
|
-
writeText
|
|
6
|
+
withFileTransaction
|
|
10
7
|
} from "../config/store.mjs";
|
|
11
|
-
import {
|
|
8
|
+
import { normalizeOpenAiBaseUrl } from "./url.mjs";
|
|
9
|
+
import { stripJsonComments } from "../config/jsonc.mjs";
|
|
12
10
|
|
|
13
|
-
const
|
|
14
|
-
const piBootstrapProviderId = "gpt";
|
|
15
|
-
const piBootstrapApi = "openai-completions";
|
|
16
|
-
const piBootstrapBaseUrl = "https://api.openai.com/v1";
|
|
17
|
-
const piBootstrapModelId = "gpt-5.5";
|
|
18
|
-
const piRpcRequestId = "abelworkflow-provider";
|
|
19
|
-
const piRpcArgs = [
|
|
20
|
-
"--mode", "rpc",
|
|
21
|
-
"--no-session",
|
|
22
|
-
"--offline",
|
|
23
|
-
"--no-context-files",
|
|
24
|
-
"--no-skills",
|
|
25
|
-
"--no-prompt-templates",
|
|
26
|
-
"--no-themes"
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
function requirePiProviderId(value) {
|
|
30
|
-
const providerId = typeof value === "string" ? value.trim() : "";
|
|
31
|
-
if (!providerId) {
|
|
32
|
-
throw new Error("未检测到 Pi 当前有效 Provider;请先在 Pi 中配置可用模型。");
|
|
33
|
-
}
|
|
34
|
-
return providerId;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function updatePiAuthFile(path, providerId, apiKey) {
|
|
38
|
-
const targetProviderId = requirePiProviderId(providerId);
|
|
39
|
-
return updateLockedJson(path, (auth) => {
|
|
40
|
-
if (!auth || typeof auth !== "object" || Array.isArray(auth)) {
|
|
41
|
-
throw new TypeError("Pi auth.json must contain a JSON object");
|
|
42
|
-
}
|
|
43
|
-
return buildPiAuthConfig(auth, targetProviderId, apiKey);
|
|
44
|
-
}, {
|
|
45
|
-
sensitive: true,
|
|
46
|
-
retries: {
|
|
47
|
-
retries: 10,
|
|
48
|
-
factor: 2,
|
|
49
|
-
minTimeout: 100,
|
|
50
|
-
maxTimeout: 10000,
|
|
51
|
-
randomize: true
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function parsePiVersion(value) {
|
|
57
|
-
const match = String(value || "").match(/(?:^|\D)v?(\d+)\.(\d+)\.(\d+)(?:\D|$)/u);
|
|
58
|
-
return match ? match.slice(1).map(Number) : null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function assertSupportedPiVersion(value) {
|
|
62
|
-
const version = parsePiVersion(value);
|
|
63
|
-
if (!version) {
|
|
64
|
-
throw new Error("无法检测 Pi 版本(可能尚未安装);请先安装 Pi 0.80.0 或更高版本。");
|
|
65
|
-
}
|
|
66
|
-
for (let index = 0; index < minimumPiVersion.length; index += 1) {
|
|
67
|
-
if (version[index] > minimumPiVersion[index]) return version;
|
|
68
|
-
if (version[index] < minimumPiVersion[index]) {
|
|
69
|
-
throw new Error(`当前 Pi ${version.join(".")} 不支持 auth-only 自定义 Provider;请先升级到 Pi 0.80.0 或更高版本。`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return version;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getPiProcessInvocation(args, {
|
|
76
|
-
platform = process.platform,
|
|
77
|
-
comspec = process.env.ComSpec || process.env.COMSPEC || "cmd.exe"
|
|
78
|
-
} = {}) {
|
|
79
|
-
return platform === "win32"
|
|
80
|
-
? { command: comspec, args: ["/d", "/c", "pi", ...args] }
|
|
81
|
-
: { command: "pi", args };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function detectPiVersion() {
|
|
85
|
-
const invocation = getPiProcessInvocation(["--version"]);
|
|
86
|
-
const result = spawnSync(invocation.command, invocation.args, {
|
|
87
|
-
encoding: "utf8",
|
|
88
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
89
|
-
});
|
|
90
|
-
return result.status === 0 ? `${result.stdout || ""} ${result.stderr || ""}`.trim() : undefined;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function isPiUnknownModel(model) {
|
|
94
|
-
return model?.provider === "unknown" && model?.id === "unknown" && model?.api === "unknown";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function parsePiRpcEffectiveModel(value) {
|
|
98
|
-
for (const line of String(value || "").split(/\r?\n/u)) {
|
|
99
|
-
let payload;
|
|
100
|
-
try {
|
|
101
|
-
payload = JSON.parse(line);
|
|
102
|
-
} catch {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (payload?.type !== "response"
|
|
106
|
-
|| payload.command !== "get_state"
|
|
107
|
-
|| payload.success !== true) continue;
|
|
108
|
-
const model = payload.data?.model;
|
|
109
|
-
if (model === null) return null;
|
|
110
|
-
const effectiveModel = {
|
|
111
|
-
provider: typeof model?.provider === "string" ? model.provider.trim() : "",
|
|
112
|
-
id: typeof model?.id === "string" ? model.id.trim() : "",
|
|
113
|
-
api: typeof model?.api === "string" ? model.api.trim() : "",
|
|
114
|
-
baseUrl: typeof model?.baseUrl === "string" ? model.baseUrl.trim() : ""
|
|
115
|
-
};
|
|
116
|
-
if (isPiUnknownModel(effectiveModel)) return null;
|
|
117
|
-
if (effectiveModel.provider && effectiveModel.id) return effectiveModel;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function runPiRpcCommand(command, args, {
|
|
122
|
-
input = "",
|
|
123
|
-
maxBuffer = 1024 * 1024,
|
|
124
|
-
platform = process.platform,
|
|
125
|
-
start = spawn,
|
|
126
|
-
timeout = 20000
|
|
127
|
-
} = {}) {
|
|
128
|
-
return new Promise((resolve) => {
|
|
129
|
-
let child;
|
|
130
|
-
let stdout = "";
|
|
131
|
-
let settled = false;
|
|
132
|
-
let timer;
|
|
133
|
-
const finish = (status) => {
|
|
134
|
-
if (settled) return;
|
|
135
|
-
settled = true;
|
|
136
|
-
clearTimeout(timer);
|
|
137
|
-
child?.stdin?.destroy();
|
|
138
|
-
child?.stdout?.destroy();
|
|
139
|
-
child?.stderr?.destroy();
|
|
140
|
-
if (child?.exitCode === null && child.signalCode === null && !child.killed) child.kill();
|
|
141
|
-
child?.unref();
|
|
142
|
-
resolve({ status, stdout });
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const env = { ...process.env };
|
|
147
|
-
delete env.NODE_TEST_CONTEXT;
|
|
148
|
-
const invocation = command === "pi"
|
|
149
|
-
? getPiProcessInvocation(args, { platform })
|
|
150
|
-
: { command, args };
|
|
151
|
-
child = start(invocation.command, invocation.args, {
|
|
152
|
-
env,
|
|
153
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
154
|
-
windowsHide: true
|
|
155
|
-
});
|
|
156
|
-
} catch {
|
|
157
|
-
finish(null);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
timer = setTimeout(() => finish(null), timeout);
|
|
162
|
-
child.on("error", () => finish(null));
|
|
163
|
-
child.on("exit", (code) => finish(code));
|
|
164
|
-
child.stdout.setEncoding("utf8");
|
|
165
|
-
child.stdout.on("data", (chunk) => {
|
|
166
|
-
stdout += chunk;
|
|
167
|
-
if (Buffer.byteLength(stdout, "utf8") > maxBuffer) {
|
|
168
|
-
finish(null);
|
|
169
|
-
} else if (parsePiRpcEffectiveModel(stdout) !== undefined) {
|
|
170
|
-
finish(0);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
child.stdin.on("error", () => finish(null));
|
|
174
|
-
child.stdin.write(input, (error) => {
|
|
175
|
-
if (error) finish(null);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function detectPiEffectiveModel(run = runPiRpcCommand) {
|
|
181
|
-
let result;
|
|
182
|
-
try {
|
|
183
|
-
result = await run("pi", piRpcArgs, {
|
|
184
|
-
encoding: "utf8",
|
|
185
|
-
input: `${JSON.stringify({ id: piRpcRequestId, type: "get_state" })}\n`,
|
|
186
|
-
maxBuffer: 1024 * 1024,
|
|
187
|
-
platform: process.platform,
|
|
188
|
-
timeout: 20000
|
|
189
|
-
});
|
|
190
|
-
} catch {
|
|
191
|
-
return undefined;
|
|
192
|
-
}
|
|
193
|
-
if (result?.error || result?.status !== 0) return undefined;
|
|
194
|
-
return parsePiRpcEffectiveModel(result.stdout);
|
|
195
|
-
}
|
|
11
|
+
const piProviderId = "abelworkflow";
|
|
196
12
|
|
|
197
13
|
function parsePiModelIds(value) {
|
|
198
14
|
return [...new Set(String(value || "")
|
|
@@ -202,43 +18,20 @@ function parsePiModelIds(value) {
|
|
|
202
18
|
}
|
|
203
19
|
|
|
204
20
|
function getOpenAiUrlPathname(value) {
|
|
205
|
-
const input = String(value || "").trim();
|
|
206
21
|
try {
|
|
207
|
-
return new URL(
|
|
22
|
+
return new URL(String(value || "").trim()).pathname.replace(/\/+$/u, "");
|
|
208
23
|
} catch {
|
|
209
|
-
return
|
|
24
|
+
return "";
|
|
210
25
|
}
|
|
211
26
|
}
|
|
212
27
|
|
|
213
28
|
function inferPiApiFromBaseUrl(value) {
|
|
214
29
|
const pathname = getOpenAiUrlPathname(value);
|
|
215
|
-
if (pathname.endsWith("/v1/chat/completions"))
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
if (pathname.endsWith("/v1/responses")) {
|
|
219
|
-
return "openai-responses";
|
|
220
|
-
}
|
|
30
|
+
if (pathname.endsWith("/v1/chat/completions")) return "openai-completions";
|
|
31
|
+
if (pathname.endsWith("/v1/responses")) return "openai-responses";
|
|
221
32
|
return null;
|
|
222
33
|
}
|
|
223
34
|
|
|
224
|
-
function normalizeOpenAiBaseUrl(value) {
|
|
225
|
-
const input = String(value || "").trim();
|
|
226
|
-
try {
|
|
227
|
-
const url = new URL(input);
|
|
228
|
-
let pathname = url.pathname.replace(/\/+$/u, "")
|
|
229
|
-
.replace(/\/v1\/(?:chat\/completions|responses)$/u, "/v1");
|
|
230
|
-
if (!pathname.endsWith("/v1")) {
|
|
231
|
-
pathname = `${pathname}/v1`;
|
|
232
|
-
}
|
|
233
|
-
url.pathname = pathname;
|
|
234
|
-
return url.toString();
|
|
235
|
-
} catch {
|
|
236
|
-
const baseUrl = input.replace(/\/+$/u, "")
|
|
237
|
-
.replace(/\/v1\/(?:chat\/completions|responses)$/u, "/v1");
|
|
238
|
-
return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
35
|
function getPiApiPromptOptions() {
|
|
243
36
|
return [
|
|
244
37
|
{ value: "openai-completions", label: "OpenAI Chat Completions(推荐)" },
|
|
@@ -246,351 +39,392 @@ function getPiApiPromptOptions() {
|
|
|
246
39
|
];
|
|
247
40
|
}
|
|
248
41
|
|
|
249
|
-
function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
&&
|
|
262
|
-
|
|
263
|
-
const providerId = effectiveProviderId && effectiveModelId
|
|
264
|
-
? effectiveProviderId
|
|
265
|
-
: savedTargetExists ? savedProviderId : "";
|
|
266
|
-
const defaultModel = effectiveProviderId && effectiveModelId
|
|
267
|
-
? effectiveModelId
|
|
268
|
-
: savedTargetExists ? savedModelId : "";
|
|
269
|
-
const provider = providerId
|
|
270
|
-
&& modelsConfig.providers?.[providerId]
|
|
271
|
-
&& typeof modelsConfig.providers[providerId] === "object"
|
|
272
|
-
? modelsConfig.providers[providerId]
|
|
273
|
-
: {};
|
|
274
|
-
const credential = providerId && auth[providerId] && typeof auth[providerId] === "object"
|
|
275
|
-
? auth[providerId]
|
|
276
|
-
: {};
|
|
277
|
-
const authApiKey = credential.type === "api_key" && typeof credential.key === "string"
|
|
278
|
-
? credential.key
|
|
42
|
+
function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {}) {
|
|
43
|
+
assertPlainObject(modelsConfig, "Pi models");
|
|
44
|
+
assertPlainObject(settings, "Pi settings");
|
|
45
|
+
assertPlainObject(auth, "Pi auth");
|
|
46
|
+
const providers = modelsConfig.providers === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: assertPlainObject(modelsConfig.providers, "Pi models providers");
|
|
49
|
+
const provider = providers[piProviderId];
|
|
50
|
+
const target = provider === undefined ? {} : assertPlainObject(provider, `Pi provider ${piProviderId}`);
|
|
51
|
+
const models = Array.isArray(target.models) ? target.models.filter((model) => model?.id) : [];
|
|
52
|
+
const modelIds = models.map((model) => model.id);
|
|
53
|
+
const savedModel = settings.defaultProvider === piProviderId
|
|
54
|
+
&& modelIds.includes(settings.defaultModel)
|
|
55
|
+
? settings.defaultModel
|
|
279
56
|
: "";
|
|
280
|
-
const
|
|
281
|
-
const
|
|
282
|
-
const runtimeMatches = Boolean(effectiveProviderId
|
|
283
|
-
&& effectiveModelId
|
|
284
|
-
&& effectiveProviderId === providerId
|
|
285
|
-
&& effectiveModelId === defaultModel);
|
|
286
|
-
return {
|
|
287
|
-
providerId,
|
|
288
|
-
baseUrl: runtimeMatches && typeof effectiveModel.baseUrl === "string"
|
|
289
|
-
? effectiveModel.baseUrl
|
|
290
|
-
: typeof model.baseUrl === "string" ? model.baseUrl
|
|
291
|
-
: typeof provider.baseUrl === "string" ? provider.baseUrl : "",
|
|
292
|
-
api: runtimeMatches && typeof effectiveModel.api === "string"
|
|
293
|
-
? effectiveModel.api
|
|
294
|
-
: typeof model.api === "string" ? model.api
|
|
295
|
-
: typeof provider.api === "string" ? provider.api : "",
|
|
296
|
-
apiKey: authApiKey || (typeof provider.apiKey === "string" ? provider.apiKey : ""),
|
|
297
|
-
modelIds: models.map((model) => model.id),
|
|
298
|
-
defaultModel
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function resolvePiApiTarget(modelsConfig = {}, settings = {}, auth = {}, effectiveModel) {
|
|
303
|
-
const hasNoEffectiveModel = effectiveModel === null || isPiUnknownModel(effectiveModel);
|
|
304
|
-
const configuration = resolveExistingPiApiConfig(
|
|
305
|
-
modelsConfig,
|
|
306
|
-
settings,
|
|
307
|
-
auth,
|
|
308
|
-
hasNoEffectiveModel ? undefined : effectiveModel
|
|
309
|
-
);
|
|
310
|
-
if (configuration.providerId || !hasNoEffectiveModel) {
|
|
311
|
-
return { bootstrap: false, configuration };
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const provider = modelsConfig.providers?.[piBootstrapProviderId];
|
|
315
|
-
const models = Array.isArray(provider?.models) ? provider.models.filter((model) => model?.id) : [];
|
|
316
|
-
const credential = auth[piBootstrapProviderId];
|
|
317
|
-
const apiKey = credential?.type === "api_key" && typeof credential.key === "string"
|
|
57
|
+
const credential = auth[piProviderId];
|
|
58
|
+
const authApiKey = credential?.type === "api_key" && typeof credential.key === "string"
|
|
318
59
|
? credential.key
|
|
319
|
-
:
|
|
320
|
-
return {
|
|
321
|
-
bootstrap: true,
|
|
322
|
-
configuration: {
|
|
323
|
-
providerId: piBootstrapProviderId,
|
|
324
|
-
baseUrl: typeof provider?.baseUrl === "string" ? provider.baseUrl : piBootstrapBaseUrl,
|
|
325
|
-
api: typeof provider?.api === "string" ? provider.api : piBootstrapApi,
|
|
326
|
-
apiKey,
|
|
327
|
-
modelIds: models.map((model) => model.id),
|
|
328
|
-
defaultModel: models[0]?.id || piBootstrapModelId
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function assertConfigurablePiProvider(modelsConfig = {}, configuration = {}) {
|
|
334
|
-
const { providerId, defaultModel, api } = configuration;
|
|
335
|
-
const provider = modelsConfig.providers?.[providerId];
|
|
336
|
-
const models = Array.isArray(provider?.models) ? provider.models : [];
|
|
337
|
-
if (!provider || typeof provider !== "object" || !models.some((model) => model?.id === defaultModel)) {
|
|
338
|
-
throw new Error(`Pi 当前有效 Provider ${providerId || "未知"} 不是 models.json 中的自定义 Provider;为避免覆盖内置模型,已停止配置。`);
|
|
339
|
-
}
|
|
340
|
-
const supportedApis = new Set(getPiApiPromptOptions().map((option) => option.value));
|
|
341
|
-
if (api && !supportedApis.has(api)) {
|
|
342
|
-
throw new Error(`Pi 当前有效 Provider ${providerId} 使用不受支持的 API 类型 ${api};此配置器仅支持 OpenAI-compatible API。`);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function buildPiModelConfig(modelId, existingModel = {}) {
|
|
347
|
-
const model = { ...existingModel };
|
|
348
|
-
delete model.baseUrl;
|
|
349
|
-
delete model.api;
|
|
60
|
+
: "";
|
|
350
61
|
return {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
contextWindow: model.contextWindow ?? 262144,
|
|
357
|
-
maxTokens: model.maxTokens ?? 64000
|
|
62
|
+
baseUrl: typeof target.baseUrl === "string" ? target.baseUrl : "",
|
|
63
|
+
api: typeof target.api === "string" ? target.api : "",
|
|
64
|
+
apiKey: authApiKey,
|
|
65
|
+
modelIds,
|
|
66
|
+
defaultModel: savedModel || modelIds[0] || ""
|
|
358
67
|
};
|
|
359
68
|
}
|
|
360
69
|
|
|
361
|
-
function buildPiModelsConfig(modelsConfig = {}, {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const currentProvider = providers[targetProviderId] && typeof providers[targetProviderId] === "object"
|
|
370
|
-
? providers[targetProviderId]
|
|
371
|
-
: {};
|
|
70
|
+
function buildPiModelsConfig(modelsConfig = {}, { baseUrl, api, apiKey, modelIds }) {
|
|
71
|
+
assertPlainObject(modelsConfig, "Pi models");
|
|
72
|
+
const providers = modelsConfig.providers === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: assertPlainObject(modelsConfig.providers, "Pi models providers");
|
|
75
|
+
const currentProvider = providers[piProviderId] === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: assertPlainObject(providers[piProviderId], `Pi provider ${piProviderId}`);
|
|
372
78
|
const existingModels = new Map(
|
|
373
79
|
(Array.isArray(currentProvider.models) ? currentProvider.models : [])
|
|
374
80
|
.filter((model) => model?.id)
|
|
375
81
|
.map((model) => [model.id, model])
|
|
376
82
|
);
|
|
377
|
-
|
|
83
|
+
const responses = api === "openai-responses";
|
|
378
84
|
const provider = {
|
|
379
|
-
|
|
380
|
-
baseUrl,
|
|
85
|
+
baseUrl: normalizeOpenAiBaseUrl(baseUrl),
|
|
381
86
|
api,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
supportsDeveloperRole: false
|
|
385
|
-
},
|
|
386
|
-
models: modelIds.map((modelId) =>
|
|
87
|
+
...(responses ? {
|
|
88
|
+
apiKey,
|
|
89
|
+
compat: { supportsDeveloperRole: false }
|
|
90
|
+
} : {}),
|
|
91
|
+
models: modelIds.map((modelId) => {
|
|
92
|
+
const name = existingModels.get(modelId)?.name;
|
|
93
|
+
const model = { id: modelId, name: typeof name === "string" && name ? name : modelId };
|
|
94
|
+
return responses ? {
|
|
95
|
+
...model,
|
|
96
|
+
reasoning: true,
|
|
97
|
+
input: ["text", "image"],
|
|
98
|
+
thinkingLevelMap: {
|
|
99
|
+
off: null,
|
|
100
|
+
minimal: null,
|
|
101
|
+
low: null,
|
|
102
|
+
medium: null,
|
|
103
|
+
high: null,
|
|
104
|
+
xhigh: null,
|
|
105
|
+
max: "max"
|
|
106
|
+
},
|
|
107
|
+
contextWindow: 262144,
|
|
108
|
+
maxTokens: 131072
|
|
109
|
+
} : model;
|
|
110
|
+
})
|
|
387
111
|
};
|
|
388
|
-
delete provider.apiKey;
|
|
389
112
|
|
|
390
113
|
return {
|
|
391
114
|
...modelsConfig,
|
|
392
115
|
providers: {
|
|
393
116
|
...providers,
|
|
394
|
-
[
|
|
117
|
+
[piProviderId]: provider
|
|
395
118
|
}
|
|
396
119
|
};
|
|
397
120
|
}
|
|
398
121
|
|
|
399
|
-
function buildPiAuthConfig(auth = {},
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
};
|
|
122
|
+
function buildPiAuthConfig(auth = {}, apiKey) {
|
|
123
|
+
assertPlainObject(auth, "Pi auth");
|
|
124
|
+
const nextAuth = { ...auth };
|
|
125
|
+
if (!apiKey) {
|
|
126
|
+
delete nextAuth[piProviderId];
|
|
127
|
+
return nextAuth;
|
|
128
|
+
}
|
|
129
|
+
const credential = auth[piProviderId] === undefined
|
|
130
|
+
? {}
|
|
131
|
+
: assertPlainObject(auth[piProviderId], `Pi credential ${piProviderId}`);
|
|
132
|
+
nextAuth[piProviderId] = { ...credential, type: "api_key", key: apiKey };
|
|
133
|
+
return nextAuth;
|
|
412
134
|
}
|
|
413
135
|
|
|
414
|
-
function buildPiSettingsConfig(settings = {},
|
|
136
|
+
function buildPiSettingsConfig(settings = {}, defaultModel) {
|
|
137
|
+
assertPlainObject(settings, "Pi settings");
|
|
415
138
|
return {
|
|
416
139
|
...settings,
|
|
417
|
-
defaultProvider:
|
|
418
|
-
defaultModel
|
|
419
|
-
defaultThinkingLevel: settings.defaultThinkingLevel || "high",
|
|
420
|
-
enableSkillCommands: settings.enableSkillCommands ?? true
|
|
140
|
+
defaultProvider: piProviderId,
|
|
141
|
+
defaultModel
|
|
421
142
|
};
|
|
422
143
|
}
|
|
423
144
|
|
|
424
145
|
async function readExistingPiConfiguration(paths, operations = {}) {
|
|
425
|
-
const readAuth = operations.readAuth ?? ((path) => readJsonFileSafe(path, {}, {
|
|
426
|
-
|
|
427
|
-
|
|
146
|
+
const readAuth = operations.readAuth ?? ((path) => readJsonFileSafe(path, {}, {
|
|
147
|
+
safeRoot: paths.homeDir,
|
|
148
|
+
sensitive: true
|
|
149
|
+
}));
|
|
150
|
+
const readModels = operations.readModels ?? ((path) => readJsoncFileSafe(path, {}, {
|
|
151
|
+
safeRoot: paths.homeDir,
|
|
152
|
+
sensitive: true
|
|
153
|
+
}));
|
|
154
|
+
const readSettings = operations.readSettings ?? ((path) => readJsonFileSafe(path, {}, {
|
|
155
|
+
safeRoot: paths.homeDir
|
|
156
|
+
}));
|
|
428
157
|
const auth = await readAuth(paths.piAuthPath);
|
|
429
158
|
const models = await readModels(paths.piModelsPath);
|
|
430
159
|
const settings = await readSettings(paths.piSettingsPath);
|
|
160
|
+
assertPlainObject(auth, "Pi auth");
|
|
161
|
+
assertPlainObject(models, "Pi models");
|
|
162
|
+
assertPlainObject(settings, "Pi settings");
|
|
431
163
|
return { auth, models, settings };
|
|
432
164
|
}
|
|
433
165
|
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
api,
|
|
451
|
-
modelIds
|
|
452
|
-
}),
|
|
453
|
-
settings: buildPiSettingsConfig(settings, targetProviderId, defaultModel)
|
|
454
|
-
};
|
|
166
|
+
function skipJsoncTrivia(content, index) {
|
|
167
|
+
while (index < content.length) {
|
|
168
|
+
if (/\s/u.test(content[index])) {
|
|
169
|
+
index += 1;
|
|
170
|
+
} else if (content.startsWith("//", index)) {
|
|
171
|
+
index = content.indexOf("\n", index + 2);
|
|
172
|
+
if (index === -1) return content.length;
|
|
173
|
+
} else if (content.startsWith("/*", index)) {
|
|
174
|
+
const end = content.indexOf("*/", index + 2);
|
|
175
|
+
if (end === -1) throw new SyntaxError("Unterminated JSONC block comment");
|
|
176
|
+
index = end + 2;
|
|
177
|
+
} else {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return index;
|
|
455
182
|
}
|
|
456
183
|
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
184
|
+
function parseJsoncStringNode(content, index) {
|
|
185
|
+
const start = index;
|
|
186
|
+
index += 1;
|
|
187
|
+
while (index < content.length) {
|
|
188
|
+
if (content[index] === "\\") index += 2;
|
|
189
|
+
else if (content[index] === '"') {
|
|
190
|
+
index += 1;
|
|
191
|
+
return {
|
|
192
|
+
end: index,
|
|
193
|
+
start,
|
|
194
|
+
type: "string",
|
|
195
|
+
value: JSON.parse(content.slice(start, index))
|
|
196
|
+
};
|
|
197
|
+
} else index += 1;
|
|
198
|
+
}
|
|
199
|
+
throw new SyntaxError("Unterminated JSON string");
|
|
468
200
|
}
|
|
469
201
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
202
|
+
function parseJsoncNode(content, startIndex = 0) {
|
|
203
|
+
let index = skipJsoncTrivia(content, startIndex);
|
|
204
|
+
const start = index;
|
|
205
|
+
if (content[index] === '"') return parseJsoncStringNode(content, index);
|
|
206
|
+
if (content[index] === "{") {
|
|
207
|
+
index += 1;
|
|
208
|
+
const properties = [];
|
|
209
|
+
while (true) {
|
|
210
|
+
index = skipJsoncTrivia(content, index);
|
|
211
|
+
if (content[index] === "}") {
|
|
212
|
+
return { end: index + 1, properties, start, type: "object" };
|
|
213
|
+
}
|
|
214
|
+
const key = parseJsoncStringNode(content, index);
|
|
215
|
+
index = skipJsoncTrivia(content, key.end);
|
|
216
|
+
if (content[index] !== ":") throw new SyntaxError("Invalid JSONC object property");
|
|
217
|
+
const value = parseJsoncNode(content, index + 1);
|
|
218
|
+
index = skipJsoncTrivia(content, value.end);
|
|
219
|
+
let comma = false;
|
|
220
|
+
if (content[index] === ",") {
|
|
221
|
+
comma = true;
|
|
222
|
+
index += 1;
|
|
223
|
+
}
|
|
224
|
+
properties.push({ comma, key, value });
|
|
225
|
+
if (!comma) {
|
|
226
|
+
index = skipJsoncTrivia(content, index);
|
|
227
|
+
if (content[index] !== "}") throw new SyntaxError("Invalid JSONC object");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
480
230
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} = await readExistingPiConfiguration(paths);
|
|
492
|
-
const detectionSpinner = spinner();
|
|
493
|
-
detectionSpinner.start("正在识别 Pi 当前有效模型");
|
|
494
|
-
let effectiveModel;
|
|
495
|
-
try {
|
|
496
|
-
effectiveModel = await (runtime.getPiEffectiveModel ?? detectPiEffectiveModel)();
|
|
497
|
-
} finally {
|
|
498
|
-
detectionSpinner.stop(effectiveModel
|
|
499
|
-
? `已识别 ${effectiveModel.provider}/${effectiveModel.id}`
|
|
500
|
-
: "未识别到 Pi 当前有效模型");
|
|
231
|
+
if (content[index] === "[") {
|
|
232
|
+
index += 1;
|
|
233
|
+
while (true) {
|
|
234
|
+
index = skipJsoncTrivia(content, index);
|
|
235
|
+
if (content[index] === "]") return { end: index + 1, start, type: "array" };
|
|
236
|
+
const value = parseJsoncNode(content, index);
|
|
237
|
+
index = skipJsoncTrivia(content, value.end);
|
|
238
|
+
if (content[index] === ",") index += 1;
|
|
239
|
+
else if (content[index] !== "]") throw new SyntaxError("Invalid JSONC array");
|
|
240
|
+
}
|
|
501
241
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
} else {
|
|
508
|
-
assertConfigurablePiProvider(modelsConfig, existing);
|
|
242
|
+
while (index < content.length
|
|
243
|
+
&& !/[\s,\]}]/u.test(content[index])
|
|
244
|
+
&& !content.startsWith("//", index)
|
|
245
|
+
&& !content.startsWith("/*", index)) {
|
|
246
|
+
index += 1;
|
|
509
247
|
}
|
|
510
|
-
if (
|
|
511
|
-
|
|
512
|
-
|
|
248
|
+
if (index === start) throw new SyntaxError("Invalid JSONC value");
|
|
249
|
+
return { end: index, start, type: "scalar" };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function lineIndentAt(content, index) {
|
|
253
|
+
const lineStart = content.lastIndexOf("\n", index - 1) + 1;
|
|
254
|
+
return content.slice(lineStart, index).match(/^[ \t]*/u)?.[0] ?? "";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function formatJsoncValue(value, indent, lineEnding) {
|
|
258
|
+
return JSON.stringify(value, null, 2).replace(/\n/gu, `${lineEnding}${indent}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function findJsoncProperty(objectNode, key) {
|
|
262
|
+
return objectNode.properties.find((property) => property.key.value === key);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function findUniqueJsoncProperty(objectNode, key, label) {
|
|
266
|
+
const matches = objectNode.properties.filter((property) => property.key.value === key);
|
|
267
|
+
if (matches.length > 1) throw new SyntaxError(`Duplicate Pi ${label} key`);
|
|
268
|
+
return matches[0];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function setJsoncObjectProperty(content, objectNode, key, value) {
|
|
272
|
+
const existing = findJsoncProperty(objectNode, key);
|
|
273
|
+
const lineEnding = content.includes("\r\n") ? "\r\n" : "\n";
|
|
274
|
+
if (existing) {
|
|
275
|
+
const indent = lineIndentAt(content, existing.key.start);
|
|
276
|
+
return `${content.slice(0, existing.value.start)}${formatJsoncValue(value, indent, lineEnding)}${content.slice(existing.value.end)}`;
|
|
513
277
|
}
|
|
514
|
-
const providerLabel = `Pi ${providerId}`;
|
|
515
278
|
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const
|
|
279
|
+
const close = objectNode.end - 1;
|
|
280
|
+
let insertionPoint = close;
|
|
281
|
+
while (insertionPoint > objectNode.start && /[ \t]/u.test(content[insertionPoint - 1])) insertionPoint -= 1;
|
|
282
|
+
const objectIndent = lineIndentAt(content, objectNode.start);
|
|
283
|
+
const childIndent = objectNode.properties.length
|
|
284
|
+
? lineIndentAt(content, objectNode.properties[0].key.start)
|
|
285
|
+
: `${objectIndent} `;
|
|
286
|
+
const last = objectNode.properties.at(-1);
|
|
287
|
+
const needsComma = last && !last.comma;
|
|
288
|
+
const gapStart = last?.value.end ?? objectNode.start + 1;
|
|
289
|
+
const gap = content.slice(gapStart, insertionPoint);
|
|
290
|
+
const needsLineBreak = !gap.includes("\n") && !gap.includes("\r");
|
|
291
|
+
const property = `${childIndent}${JSON.stringify(key)}: ${formatJsoncValue(value, childIndent, lineEnding)}${lineEnding}`;
|
|
292
|
+
return `${content.slice(0, gapStart)}${needsComma ? "," : ""}${gap}${needsLineBreak ? lineEnding : ""}${property}${content.slice(insertionPoint)}`;
|
|
293
|
+
}
|
|
524
294
|
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
295
|
+
function updatePiModelsContent(content, provider) {
|
|
296
|
+
const root = parseJsoncNode(content);
|
|
297
|
+
if (root.type !== "object") throw new TypeError("Pi models must contain a JSON object");
|
|
298
|
+
const providersProperty = findUniqueJsoncProperty(root, "providers", "providers");
|
|
299
|
+
if (!providersProperty) {
|
|
300
|
+
return setJsoncObjectProperty(content, root, "providers", { [piProviderId]: provider });
|
|
301
|
+
}
|
|
302
|
+
if (providersProperty.value.type !== "object") {
|
|
303
|
+
throw new TypeError("Pi models providers must contain a JSON object");
|
|
304
|
+
}
|
|
305
|
+
findUniqueJsoncProperty(providersProperty.value, piProviderId, `providers.${piProviderId}`);
|
|
306
|
+
return setJsoncObjectProperty(content, providersProperty.value, piProviderId, provider);
|
|
307
|
+
}
|
|
534
308
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
309
|
+
async function persistPiConfiguration(paths, intent, operations = {}) {
|
|
310
|
+
return withFileTransaction([
|
|
311
|
+
{ path: paths.piAuthPath, safeRoot: paths.homeDir, sensitive: true },
|
|
312
|
+
{ path: paths.piModelsPath, safeRoot: paths.homeDir, sensitive: true },
|
|
313
|
+
{ path: paths.piSettingsPath, safeRoot: paths.homeDir }
|
|
314
|
+
], async (transaction) => {
|
|
315
|
+
const auth = transaction.readJson(paths.piAuthPath, {});
|
|
316
|
+
const models = transaction.readJson(paths.piModelsPath, {}, { jsonc: true });
|
|
317
|
+
const settings = transaction.readJson(paths.piSettingsPath, {});
|
|
318
|
+
assertPlainObject(auth, "Pi auth");
|
|
319
|
+
assertPlainObject(models, "Pi models");
|
|
320
|
+
assertPlainObject(settings, "Pi settings");
|
|
321
|
+
const nextModels = buildPiModelsConfig(models, intent);
|
|
322
|
+
const currentModelsContent = transaction.readText(paths.piModelsPath, "{}\n");
|
|
323
|
+
const nextAuth = buildPiAuthConfig(auth, intent.apiKey);
|
|
324
|
+
const nextSettings = buildPiSettingsConfig(settings, intent.defaultModel);
|
|
325
|
+
const nextModelsContent = updatePiModelsContent(
|
|
326
|
+
currentModelsContent,
|
|
327
|
+
nextModels.providers[piProviderId]
|
|
328
|
+
);
|
|
329
|
+
const validatedModels = JSON.parse(stripJsonComments(nextModelsContent));
|
|
330
|
+
assertPlainObject(validatedModels, "Pi models");
|
|
331
|
+
|
|
332
|
+
await transaction.writeJson(paths.piAuthPath, nextAuth, {
|
|
333
|
+
...(operations.writeAuth ? { writer: operations.writeAuth } : {})
|
|
334
|
+
});
|
|
335
|
+
await transaction.writeText(
|
|
336
|
+
paths.piModelsPath,
|
|
337
|
+
nextModelsContent,
|
|
338
|
+
{ ...(operations.writeModels ? { writer: operations.writeModels } : {}) }
|
|
339
|
+
);
|
|
340
|
+
await transaction.writeJson(paths.piSettingsPath, nextSettings, {
|
|
341
|
+
...(operations.writeSettings ? { writer: operations.writeSettings } : {})
|
|
342
|
+
});
|
|
538
343
|
});
|
|
344
|
+
}
|
|
539
345
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
346
|
+
async function configurePiApi(paths, withExtensionsPresent, promptApi, runtime = {}) {
|
|
347
|
+
if (typeof withExtensionsPresent !== "function") {
|
|
348
|
+
throw new TypeError("Expected a Pi Extensions deployment function");
|
|
349
|
+
}
|
|
350
|
+
return withExtensionsPresent(paths, async (guard) => {
|
|
351
|
+
await guard();
|
|
352
|
+
const text = runtime.text ?? p.text;
|
|
353
|
+
const {
|
|
354
|
+
assertNotCancelled,
|
|
355
|
+
passwordOrExisting,
|
|
356
|
+
selectOrCancel
|
|
357
|
+
} = promptApi;
|
|
358
|
+
const { auth, models, settings } = await readExistingPiConfiguration(paths);
|
|
359
|
+
const existing = resolveExistingPiApiConfig(models, settings, auth);
|
|
360
|
+
const providerLabel = `Pi ${piProviderId}`;
|
|
361
|
+
|
|
362
|
+
const baseUrlInput = await text({
|
|
363
|
+
message: `${providerLabel} Base URL`,
|
|
364
|
+
initialValue: existing.baseUrl || undefined,
|
|
365
|
+
validate: (value) => {
|
|
366
|
+
try {
|
|
367
|
+
normalizeOpenAiBaseUrl(value);
|
|
368
|
+
} catch (error) {
|
|
369
|
+
return error.message;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
assertNotCancelled(baseUrlInput);
|
|
374
|
+
const inferredApi = inferPiApiFromBaseUrl(baseUrlInput);
|
|
375
|
+
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
376
|
+
|
|
377
|
+
const apiOptions = getPiApiPromptOptions();
|
|
378
|
+
const initialApi = apiOptions.some((option) => option.value === existing.api)
|
|
379
|
+
? existing.api
|
|
380
|
+
: undefined;
|
|
381
|
+
const api = inferredApi || await selectOrCancel({
|
|
382
|
+
message: `${providerLabel} API 类型`,
|
|
383
|
+
options: apiOptions,
|
|
384
|
+
...(initialApi ? { initialValue: initialApi } : {})
|
|
385
|
+
});
|
|
386
|
+
const apiKey = await passwordOrExisting({
|
|
387
|
+
message: `${providerLabel} API Key`,
|
|
388
|
+
existingValue: existing.apiKey
|
|
389
|
+
});
|
|
549
390
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
391
|
+
const modelIdsText = await text({
|
|
392
|
+
message: `${providerLabel} 模型 ID(多个用逗号分隔)`,
|
|
393
|
+
initialValue: existing.modelIds.join(",") || undefined,
|
|
394
|
+
validate: (value) => parsePiModelIds(value).length ? undefined : "至少需要一个模型 ID"
|
|
395
|
+
});
|
|
396
|
+
assertNotCancelled(modelIdsText);
|
|
397
|
+
const modelIds = parsePiModelIds(modelIdsText);
|
|
398
|
+
const defaultModel = await text({
|
|
399
|
+
message: "Pi 默认模型",
|
|
400
|
+
initialValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
|
|
401
|
+
validate: (value) => modelIds.includes(String(value || "").trim())
|
|
402
|
+
? undefined
|
|
403
|
+
: "默认模型必须在模型 ID 列表中"
|
|
404
|
+
});
|
|
405
|
+
assertNotCancelled(defaultModel);
|
|
556
406
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
407
|
+
await guard();
|
|
408
|
+
await persistPiConfiguration(paths, {
|
|
409
|
+
apiKey,
|
|
410
|
+
baseUrl,
|
|
411
|
+
api,
|
|
412
|
+
modelIds,
|
|
413
|
+
defaultModel: String(defaultModel).trim()
|
|
414
|
+
});
|
|
565
415
|
});
|
|
566
|
-
await ensurePiResourcesLinked(paths);
|
|
567
|
-
await persistPiConfiguration(paths, configuration);
|
|
568
|
-
|
|
569
|
-
log.step(`已更新 ${pathToLabel(paths.piModelsPath, paths.homeDir)} (${providerId}, ${baseUrl})`);
|
|
570
|
-
log.step(`已更新 ${pathToLabel(paths.piSettingsPath, paths.homeDir)} (默认模型: ${finalDefaultModel})`);
|
|
571
|
-
log.step(`已更新 ${pathToLabel(paths.piAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
|
|
572
|
-
log.step(`已链接 Pi 扩展到 ${pathToLabel(join(paths.piAgentDir, "extensions"), paths.homeDir)}`);
|
|
573
416
|
}
|
|
574
417
|
|
|
575
418
|
export {
|
|
576
|
-
assertConfigurablePiProvider,
|
|
577
|
-
assertSupportedPiVersion,
|
|
578
419
|
buildPiAuthConfig,
|
|
579
|
-
buildPiConfiguration,
|
|
580
420
|
buildPiModelsConfig,
|
|
581
421
|
buildPiSettingsConfig,
|
|
582
422
|
configurePiApi,
|
|
583
|
-
detectPiEffectiveModel,
|
|
584
423
|
getPiApiPromptOptions,
|
|
585
|
-
getPiProcessInvocation,
|
|
586
424
|
inferPiApiFromBaseUrl,
|
|
587
425
|
normalizeOpenAiBaseUrl,
|
|
588
|
-
parsePiRpcEffectiveModel,
|
|
589
426
|
parsePiModelIds,
|
|
590
427
|
persistPiConfiguration,
|
|
591
428
|
readExistingPiConfiguration,
|
|
592
|
-
resolveExistingPiApiConfig
|
|
593
|
-
resolvePiApiTarget,
|
|
594
|
-
runPiRpcCommand,
|
|
595
|
-
updatePiAuthFile
|
|
429
|
+
resolveExistingPiApiConfig
|
|
596
430
|
};
|