abelworkflow 1.1.2 → 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 +332 -439
- 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,180 +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 piRpcRequestId = "abelworkflow-provider";
|
|
15
|
-
const piRpcArgs = [
|
|
16
|
-
"--mode", "rpc",
|
|
17
|
-
"--no-session",
|
|
18
|
-
"--offline",
|
|
19
|
-
"--no-context-files",
|
|
20
|
-
"--no-skills",
|
|
21
|
-
"--no-prompt-templates",
|
|
22
|
-
"--no-themes"
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
function requirePiProviderId(value) {
|
|
26
|
-
const providerId = typeof value === "string" ? value.trim() : "";
|
|
27
|
-
if (!providerId) {
|
|
28
|
-
throw new Error("未检测到 Pi 当前有效 Provider;请先在 Pi 中配置可用模型。");
|
|
29
|
-
}
|
|
30
|
-
return providerId;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function updatePiAuthFile(path, providerId, apiKey) {
|
|
34
|
-
const targetProviderId = requirePiProviderId(providerId);
|
|
35
|
-
return updateLockedJson(path, (auth) => {
|
|
36
|
-
if (!auth || typeof auth !== "object" || Array.isArray(auth)) {
|
|
37
|
-
throw new TypeError("Pi auth.json must contain a JSON object");
|
|
38
|
-
}
|
|
39
|
-
return buildPiAuthConfig(auth, targetProviderId, apiKey);
|
|
40
|
-
}, {
|
|
41
|
-
sensitive: true,
|
|
42
|
-
retries: {
|
|
43
|
-
retries: 10,
|
|
44
|
-
factor: 2,
|
|
45
|
-
minTimeout: 100,
|
|
46
|
-
maxTimeout: 10000,
|
|
47
|
-
randomize: true
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function parsePiVersion(value) {
|
|
53
|
-
const match = String(value || "").match(/(?:^|\D)v?(\d+)\.(\d+)\.(\d+)(?:\D|$)/u);
|
|
54
|
-
return match ? match.slice(1).map(Number) : null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function assertSupportedPiVersion(value) {
|
|
58
|
-
const version = parsePiVersion(value);
|
|
59
|
-
if (!version) {
|
|
60
|
-
throw new Error("无法检测 Pi 版本(可能尚未安装);请先安装 Pi 0.80.0 或更高版本。");
|
|
61
|
-
}
|
|
62
|
-
for (let index = 0; index < minimumPiVersion.length; index += 1) {
|
|
63
|
-
if (version[index] > minimumPiVersion[index]) return version;
|
|
64
|
-
if (version[index] < minimumPiVersion[index]) {
|
|
65
|
-
throw new Error(`当前 Pi ${version.join(".")} 不支持 auth-only 自定义 Provider;请先升级到 Pi 0.80.0 或更高版本。`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return version;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function detectPiVersion() {
|
|
72
|
-
const result = spawnSync("pi", ["--version"], {
|
|
73
|
-
encoding: "utf8",
|
|
74
|
-
shell: process.platform === "win32",
|
|
75
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
76
|
-
});
|
|
77
|
-
return result.status === 0 ? `${result.stdout || ""} ${result.stderr || ""}`.trim() : undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function parsePiRpcEffectiveModel(value) {
|
|
81
|
-
for (const line of String(value || "").split(/\r?\n/u)) {
|
|
82
|
-
let payload;
|
|
83
|
-
try {
|
|
84
|
-
payload = JSON.parse(line);
|
|
85
|
-
} catch {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const model = payload?.type === "response"
|
|
89
|
-
&& payload.command === "get_state"
|
|
90
|
-
&& payload.success === true
|
|
91
|
-
? payload.data?.model
|
|
92
|
-
: undefined;
|
|
93
|
-
const provider = typeof model?.provider === "string" ? model.provider.trim() : "";
|
|
94
|
-
const id = typeof model?.id === "string" ? model.id.trim() : "";
|
|
95
|
-
if (!provider || !id) continue;
|
|
96
|
-
return {
|
|
97
|
-
provider,
|
|
98
|
-
id,
|
|
99
|
-
api: typeof model.api === "string" ? model.api.trim() : "",
|
|
100
|
-
baseUrl: typeof model.baseUrl === "string" ? model.baseUrl.trim() : ""
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function runPiRpcCommand(command, args, {
|
|
106
|
-
input = "",
|
|
107
|
-
maxBuffer = 1024 * 1024,
|
|
108
|
-
shell = process.platform === "win32",
|
|
109
|
-
start = spawn,
|
|
110
|
-
timeout = 20000
|
|
111
|
-
} = {}) {
|
|
112
|
-
return new Promise((resolve) => {
|
|
113
|
-
let child;
|
|
114
|
-
let stdout = "";
|
|
115
|
-
let settled = false;
|
|
116
|
-
let timer;
|
|
117
|
-
const finish = (status) => {
|
|
118
|
-
if (settled) return;
|
|
119
|
-
settled = true;
|
|
120
|
-
clearTimeout(timer);
|
|
121
|
-
child?.stdin?.destroy();
|
|
122
|
-
child?.stdout?.destroy();
|
|
123
|
-
child?.stderr?.destroy();
|
|
124
|
-
if (child?.exitCode === null && child.signalCode === null && !child.killed) child.kill();
|
|
125
|
-
child?.unref();
|
|
126
|
-
resolve({ status, stdout });
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const env = { ...process.env };
|
|
131
|
-
delete env.NODE_TEST_CONTEXT;
|
|
132
|
-
child = start(command, args, {
|
|
133
|
-
env,
|
|
134
|
-
shell,
|
|
135
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
136
|
-
windowsHide: true
|
|
137
|
-
});
|
|
138
|
-
} catch {
|
|
139
|
-
finish(null);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
timer = setTimeout(() => finish(null), timeout);
|
|
144
|
-
child.on("error", () => finish(null));
|
|
145
|
-
child.on("exit", (code) => finish(code));
|
|
146
|
-
child.stdout.setEncoding("utf8");
|
|
147
|
-
child.stdout.on("data", (chunk) => {
|
|
148
|
-
stdout += chunk;
|
|
149
|
-
if (Buffer.byteLength(stdout, "utf8") > maxBuffer) {
|
|
150
|
-
finish(null);
|
|
151
|
-
} else if (parsePiRpcEffectiveModel(stdout)) {
|
|
152
|
-
finish(0);
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
child.stdin.on("error", () => finish(null));
|
|
156
|
-
child.stdin.write(input, (error) => {
|
|
157
|
-
if (error) finish(null);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function detectPiEffectiveModel(run = runPiRpcCommand) {
|
|
163
|
-
let result;
|
|
164
|
-
try {
|
|
165
|
-
result = await run("pi", piRpcArgs, {
|
|
166
|
-
encoding: "utf8",
|
|
167
|
-
input: `${JSON.stringify({ id: piRpcRequestId, type: "get_state" })}\n`,
|
|
168
|
-
maxBuffer: 1024 * 1024,
|
|
169
|
-
shell: process.platform === "win32",
|
|
170
|
-
timeout: 20000
|
|
171
|
-
});
|
|
172
|
-
} catch {
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
if (result?.error || result?.status !== 0) return undefined;
|
|
176
|
-
return parsePiRpcEffectiveModel(result.stdout);
|
|
177
|
-
}
|
|
11
|
+
const piProviderId = "abelworkflow";
|
|
178
12
|
|
|
179
13
|
function parsePiModelIds(value) {
|
|
180
14
|
return [...new Set(String(value || "")
|
|
@@ -184,43 +18,20 @@ function parsePiModelIds(value) {
|
|
|
184
18
|
}
|
|
185
19
|
|
|
186
20
|
function getOpenAiUrlPathname(value) {
|
|
187
|
-
const input = String(value || "").trim();
|
|
188
21
|
try {
|
|
189
|
-
return new URL(
|
|
22
|
+
return new URL(String(value || "").trim()).pathname.replace(/\/+$/u, "");
|
|
190
23
|
} catch {
|
|
191
|
-
return
|
|
24
|
+
return "";
|
|
192
25
|
}
|
|
193
26
|
}
|
|
194
27
|
|
|
195
28
|
function inferPiApiFromBaseUrl(value) {
|
|
196
29
|
const pathname = getOpenAiUrlPathname(value);
|
|
197
|
-
if (pathname.endsWith("/v1/chat/completions"))
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
if (pathname.endsWith("/v1/responses")) {
|
|
201
|
-
return "openai-responses";
|
|
202
|
-
}
|
|
30
|
+
if (pathname.endsWith("/v1/chat/completions")) return "openai-completions";
|
|
31
|
+
if (pathname.endsWith("/v1/responses")) return "openai-responses";
|
|
203
32
|
return null;
|
|
204
33
|
}
|
|
205
34
|
|
|
206
|
-
function normalizeOpenAiBaseUrl(value) {
|
|
207
|
-
const input = String(value || "").trim();
|
|
208
|
-
try {
|
|
209
|
-
const url = new URL(input);
|
|
210
|
-
let pathname = url.pathname.replace(/\/+$/u, "")
|
|
211
|
-
.replace(/\/v1\/(?:chat\/completions|responses)$/u, "/v1");
|
|
212
|
-
if (!pathname.endsWith("/v1")) {
|
|
213
|
-
pathname = `${pathname}/v1`;
|
|
214
|
-
}
|
|
215
|
-
url.pathname = pathname;
|
|
216
|
-
return url.toString();
|
|
217
|
-
} catch {
|
|
218
|
-
const baseUrl = input.replace(/\/+$/u, "")
|
|
219
|
-
.replace(/\/v1\/(?:chat\/completions|responses)$/u, "/v1");
|
|
220
|
-
return baseUrl.endsWith("/v1") ? baseUrl : `${baseUrl}/v1`;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
35
|
function getPiApiPromptOptions() {
|
|
225
36
|
return [
|
|
226
37
|
{ value: "openai-completions", label: "OpenAI Chat Completions(推荐)" },
|
|
@@ -228,310 +39,392 @@ function getPiApiPromptOptions() {
|
|
|
228
39
|
];
|
|
229
40
|
}
|
|
230
41
|
|
|
231
|
-
function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
&&
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const defaultModel = effectiveProviderId && effectiveModelId
|
|
249
|
-
? effectiveModelId
|
|
250
|
-
: savedTargetExists ? savedModelId : "";
|
|
251
|
-
const provider = providerId
|
|
252
|
-
&& modelsConfig.providers?.[providerId]
|
|
253
|
-
&& typeof modelsConfig.providers[providerId] === "object"
|
|
254
|
-
? modelsConfig.providers[providerId]
|
|
255
|
-
: {};
|
|
256
|
-
const credential = providerId && auth[providerId] && typeof auth[providerId] === "object"
|
|
257
|
-
? auth[providerId]
|
|
258
|
-
: {};
|
|
259
|
-
const authApiKey = credential.type === "api_key" && typeof credential.key === "string"
|
|
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
|
|
56
|
+
: "";
|
|
57
|
+
const credential = auth[piProviderId];
|
|
58
|
+
const authApiKey = credential?.type === "api_key" && typeof credential.key === "string"
|
|
260
59
|
? credential.key
|
|
261
60
|
: "";
|
|
262
|
-
const models = Array.isArray(provider.models) ? provider.models.filter((model) => model?.id) : [];
|
|
263
|
-
const model = models.find((item) => item.id === defaultModel) || {};
|
|
264
|
-
const runtimeMatches = Boolean(effectiveProviderId
|
|
265
|
-
&& effectiveModelId
|
|
266
|
-
&& effectiveProviderId === providerId
|
|
267
|
-
&& effectiveModelId === defaultModel);
|
|
268
|
-
return {
|
|
269
|
-
providerId,
|
|
270
|
-
baseUrl: runtimeMatches && typeof effectiveModel.baseUrl === "string"
|
|
271
|
-
? effectiveModel.baseUrl
|
|
272
|
-
: typeof model.baseUrl === "string" ? model.baseUrl
|
|
273
|
-
: typeof provider.baseUrl === "string" ? provider.baseUrl : "",
|
|
274
|
-
api: runtimeMatches && typeof effectiveModel.api === "string"
|
|
275
|
-
? effectiveModel.api
|
|
276
|
-
: typeof model.api === "string" ? model.api
|
|
277
|
-
: typeof provider.api === "string" ? provider.api : "",
|
|
278
|
-
apiKey: authApiKey || (typeof provider.apiKey === "string" ? provider.apiKey : ""),
|
|
279
|
-
modelIds: models.map((model) => model.id),
|
|
280
|
-
defaultModel
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function assertConfigurablePiProvider(modelsConfig = {}, configuration = {}) {
|
|
285
|
-
const { providerId, defaultModel, api } = configuration;
|
|
286
|
-
const provider = modelsConfig.providers?.[providerId];
|
|
287
|
-
const models = Array.isArray(provider?.models) ? provider.models : [];
|
|
288
|
-
if (!provider || typeof provider !== "object" || !models.some((model) => model?.id === defaultModel)) {
|
|
289
|
-
throw new Error(`Pi 当前有效 Provider ${providerId || "未知"} 不是 models.json 中的自定义 Provider;为避免覆盖内置模型,已停止配置。`);
|
|
290
|
-
}
|
|
291
|
-
const supportedApis = new Set(getPiApiPromptOptions().map((option) => option.value));
|
|
292
|
-
if (api && !supportedApis.has(api)) {
|
|
293
|
-
throw new Error(`Pi 当前有效 Provider ${providerId} 使用不受支持的 API 类型 ${api};此配置器仅支持 OpenAI-compatible API。`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function buildPiModelConfig(modelId, existingModel = {}) {
|
|
298
|
-
const model = { ...existingModel };
|
|
299
|
-
delete model.baseUrl;
|
|
300
|
-
delete model.api;
|
|
301
61
|
return {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
contextWindow: model.contextWindow ?? 262144,
|
|
308
|
-
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] || ""
|
|
309
67
|
};
|
|
310
68
|
}
|
|
311
69
|
|
|
312
|
-
function buildPiModelsConfig(modelsConfig = {}, {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const currentProvider = providers[targetProviderId] && typeof providers[targetProviderId] === "object"
|
|
321
|
-
? providers[targetProviderId]
|
|
322
|
-
: {};
|
|
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}`);
|
|
323
78
|
const existingModels = new Map(
|
|
324
79
|
(Array.isArray(currentProvider.models) ? currentProvider.models : [])
|
|
325
80
|
.filter((model) => model?.id)
|
|
326
81
|
.map((model) => [model.id, model])
|
|
327
82
|
);
|
|
328
|
-
|
|
83
|
+
const responses = api === "openai-responses";
|
|
329
84
|
const provider = {
|
|
330
|
-
|
|
331
|
-
baseUrl,
|
|
85
|
+
baseUrl: normalizeOpenAiBaseUrl(baseUrl),
|
|
332
86
|
api,
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
supportsDeveloperRole: false
|
|
336
|
-
},
|
|
337
|
-
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
|
+
})
|
|
338
111
|
};
|
|
339
|
-
delete provider.apiKey;
|
|
340
112
|
|
|
341
113
|
return {
|
|
342
114
|
...modelsConfig,
|
|
343
115
|
providers: {
|
|
344
116
|
...providers,
|
|
345
|
-
[
|
|
117
|
+
[piProviderId]: provider
|
|
346
118
|
}
|
|
347
119
|
};
|
|
348
120
|
}
|
|
349
121
|
|
|
350
|
-
function buildPiAuthConfig(auth = {},
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
};
|
|
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;
|
|
363
134
|
}
|
|
364
135
|
|
|
365
|
-
function buildPiSettingsConfig(settings = {},
|
|
136
|
+
function buildPiSettingsConfig(settings = {}, defaultModel) {
|
|
137
|
+
assertPlainObject(settings, "Pi settings");
|
|
366
138
|
return {
|
|
367
139
|
...settings,
|
|
368
|
-
defaultProvider:
|
|
369
|
-
defaultModel
|
|
370
|
-
defaultThinkingLevel: settings.defaultThinkingLevel || "high",
|
|
371
|
-
enableSkillCommands: settings.enableSkillCommands ?? true
|
|
140
|
+
defaultProvider: piProviderId,
|
|
141
|
+
defaultModel
|
|
372
142
|
};
|
|
373
143
|
}
|
|
374
144
|
|
|
375
145
|
async function readExistingPiConfiguration(paths, operations = {}) {
|
|
376
|
-
const readAuth = operations.readAuth ?? ((path) => readJsonFileSafe(path, {}, {
|
|
377
|
-
|
|
378
|
-
|
|
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
|
+
}));
|
|
379
157
|
const auth = await readAuth(paths.piAuthPath);
|
|
380
158
|
const models = await readModels(paths.piModelsPath);
|
|
381
159
|
const settings = await readSettings(paths.piSettingsPath);
|
|
160
|
+
assertPlainObject(auth, "Pi auth");
|
|
161
|
+
assertPlainObject(models, "Pi models");
|
|
162
|
+
assertPlainObject(settings, "Pi settings");
|
|
382
163
|
return { auth, models, settings };
|
|
383
164
|
}
|
|
384
165
|
|
|
385
|
-
function
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
api,
|
|
402
|
-
modelIds
|
|
403
|
-
}),
|
|
404
|
-
settings: buildPiSettingsConfig(settings, targetProviderId, defaultModel)
|
|
405
|
-
};
|
|
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;
|
|
406
182
|
}
|
|
407
183
|
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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");
|
|
419
200
|
}
|
|
420
201
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
+
}
|
|
428
230
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
} = await readExistingPiConfiguration(paths);
|
|
440
|
-
const detectionSpinner = p.spinner();
|
|
441
|
-
detectionSpinner.start("正在识别 Pi 当前有效模型");
|
|
442
|
-
let effectiveModel;
|
|
443
|
-
try {
|
|
444
|
-
effectiveModel = await (runtime.getPiEffectiveModel ?? detectPiEffectiveModel)();
|
|
445
|
-
} finally {
|
|
446
|
-
detectionSpinner.stop(effectiveModel
|
|
447
|
-
? `已识别 ${effectiveModel.provider}/${effectiveModel.id}`
|
|
448
|
-
: "未识别到 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
|
+
}
|
|
449
241
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
p.log.warn(`Pi 保存的默认模型 ${settings.defaultProvider || "未知"}/${settings.defaultModel || "未知"} 与当前有效模型 ${existing.providerId}/${existing.defaultModel} 不同;将配置当前有效模型。`);
|
|
242
|
+
while (index < content.length
|
|
243
|
+
&& !/[\s,\]}]/u.test(content[index])
|
|
244
|
+
&& !content.startsWith("//", index)
|
|
245
|
+
&& !content.startsWith("/*", index)) {
|
|
246
|
+
index += 1;
|
|
456
247
|
}
|
|
457
|
-
|
|
248
|
+
if (index === start) throw new SyntaxError("Invalid JSONC value");
|
|
249
|
+
return { end: index, start, type: "scalar" };
|
|
250
|
+
}
|
|
458
251
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
});
|
|
464
|
-
assertNotCancelled(baseUrlInput);
|
|
465
|
-
const inferredApi = inferPiApiFromBaseUrl(baseUrlInput);
|
|
466
|
-
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
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
|
+
}
|
|
467
256
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
: undefined;
|
|
472
|
-
const api = inferredApi || await selectOrCancel({
|
|
473
|
-
message: `${providerLabel} API 类型`,
|
|
474
|
-
options: piApiOptions,
|
|
475
|
-
...(initialApi ? { initialValue: initialApi } : {})
|
|
476
|
-
});
|
|
257
|
+
function formatJsoncValue(value, indent, lineEnding) {
|
|
258
|
+
return JSON.stringify(value, null, 2).replace(/\n/gu, `${lineEnding}${indent}`);
|
|
259
|
+
}
|
|
477
260
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
});
|
|
261
|
+
function findJsoncProperty(objectNode, key) {
|
|
262
|
+
return objectNode.properties.find((property) => property.key.value === key);
|
|
263
|
+
}
|
|
482
264
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
validate: (value) => parsePiModelIds(value).length ? undefined : "至少需要一个模型 ID"
|
|
489
|
-
});
|
|
490
|
-
assertNotCancelled(modelIdsText);
|
|
491
|
-
const modelIds = parsePiModelIds(modelIdsText);
|
|
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
|
+
}
|
|
492
270
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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)}`;
|
|
277
|
+
}
|
|
499
278
|
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
}
|
|
294
|
+
|
|
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
|
+
}
|
|
308
|
+
|
|
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
|
+
});
|
|
508
343
|
});
|
|
509
|
-
|
|
510
|
-
|
|
344
|
+
}
|
|
345
|
+
|
|
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
|
+
});
|
|
511
390
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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);
|
|
406
|
+
|
|
407
|
+
await guard();
|
|
408
|
+
await persistPiConfiguration(paths, {
|
|
409
|
+
apiKey,
|
|
410
|
+
baseUrl,
|
|
411
|
+
api,
|
|
412
|
+
modelIds,
|
|
413
|
+
defaultModel: String(defaultModel).trim()
|
|
414
|
+
});
|
|
415
|
+
});
|
|
516
416
|
}
|
|
517
417
|
|
|
518
418
|
export {
|
|
519
|
-
assertConfigurablePiProvider,
|
|
520
|
-
assertSupportedPiVersion,
|
|
521
419
|
buildPiAuthConfig,
|
|
522
|
-
buildPiConfiguration,
|
|
523
420
|
buildPiModelsConfig,
|
|
524
421
|
buildPiSettingsConfig,
|
|
525
422
|
configurePiApi,
|
|
526
|
-
detectPiEffectiveModel,
|
|
527
423
|
getPiApiPromptOptions,
|
|
528
424
|
inferPiApiFromBaseUrl,
|
|
529
425
|
normalizeOpenAiBaseUrl,
|
|
530
|
-
parsePiRpcEffectiveModel,
|
|
531
426
|
parsePiModelIds,
|
|
532
427
|
persistPiConfiguration,
|
|
533
428
|
readExistingPiConfiguration,
|
|
534
|
-
resolveExistingPiApiConfig
|
|
535
|
-
runPiRpcCommand,
|
|
536
|
-
updatePiAuthFile
|
|
429
|
+
resolveExistingPiApiConfig
|
|
537
430
|
};
|