abelworkflow 1.0.0-rc.1 → 1.0.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/README.md +7 -8
- package/extensions/pi-gpt-responses-compat/index.ts +1 -1
- package/lib/cli/args.mjs +2 -2
- package/lib/cli/main.mjs +39 -41
- package/lib/cli/prompts.mjs +16 -29
- package/lib/installer/assets.mjs +13 -15
- package/lib/installer/install.mjs +18 -32
- package/lib/installer/state.mjs +0 -19
- package/lib/providers/claude.mjs +23 -214
- package/lib/providers/codex.mjs +7 -7
- package/lib/providers/pi.mjs +273 -58
- package/lib/providers/skills.mjs +29 -88
- package/lib/templates/workflow/AGENTS.md +10 -10
- package/lib/templates/workflow/commands/abel-design.md +175 -0
- package/lib/templates/workflow/commands/abel-implement.md +64 -46
- package/lib/templates/workflow/commands/abel-init.md +2 -4
- package/package.json +1 -7
- package/skills/grok-search/.env.example +3 -3
- package/skills/grok-search/SKILL.md +1 -1
- package/skills/grok-search/defaults.json +1 -1
- package/skills/prompt-enhancer/SKILL.md +2 -27
- package/lib/installer/render.mjs +0 -46
- package/lib/templates/workflow/commands/abel-plan.md +0 -82
- package/lib/templates/workflow/commands/abel-research.md +0 -126
- package/skills/prompt-enhancer/.env.example +0 -12
- package/skills/prompt-enhancer/ADVANCED.md +0 -103
- package/skills/prompt-enhancer/requirements.txt +0 -1
- package/skills/prompt-enhancer/scripts/_dotenv.py +0 -32
- package/skills/prompt-enhancer/scripts/enhance.py +0 -144
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -238
package/lib/providers/pi.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
4
|
import { piInsecureTlsHeader } from "../../extensions/pi-gpt-responses-compat/tls-fetch.mjs";
|
|
@@ -11,18 +11,33 @@ import {
|
|
|
11
11
|
} from "../config/store.mjs";
|
|
12
12
|
import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
|
|
13
13
|
|
|
14
|
-
const piProviderId = "gpt";
|
|
15
|
-
const piDefaultApi = "openai-completions";
|
|
16
|
-
const piDefaultBaseUrl = "https://api.openai.com/v1";
|
|
17
|
-
const piDefaultModel = "gpt-5.5";
|
|
18
14
|
const minimumPiVersion = [0, 80, 0];
|
|
15
|
+
const piRpcRequestId = "abelworkflow-provider";
|
|
16
|
+
const piRpcArgs = [
|
|
17
|
+
"--mode", "rpc",
|
|
18
|
+
"--no-session",
|
|
19
|
+
"--offline",
|
|
20
|
+
"--no-context-files",
|
|
21
|
+
"--no-skills",
|
|
22
|
+
"--no-prompt-templates",
|
|
23
|
+
"--no-themes"
|
|
24
|
+
];
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
function requirePiProviderId(value) {
|
|
27
|
+
const providerId = typeof value === "string" ? value.trim() : "";
|
|
28
|
+
if (!providerId) {
|
|
29
|
+
throw new Error("未检测到 Pi 当前有效 Provider;请先在 Pi 中配置可用模型。");
|
|
30
|
+
}
|
|
31
|
+
return providerId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function updatePiAuthFile(path, providerId, apiKey) {
|
|
35
|
+
const targetProviderId = requirePiProviderId(providerId);
|
|
21
36
|
return updateLockedJson(path, (auth) => {
|
|
22
37
|
if (!auth || typeof auth !== "object" || Array.isArray(auth)) {
|
|
23
38
|
throw new TypeError("Pi auth.json must contain a JSON object");
|
|
24
39
|
}
|
|
25
|
-
return buildPiAuthConfig(auth, apiKey);
|
|
40
|
+
return buildPiAuthConfig(auth, targetProviderId, apiKey);
|
|
26
41
|
}, {
|
|
27
42
|
sensitive: true,
|
|
28
43
|
retries: {
|
|
@@ -43,12 +58,12 @@ function parsePiVersion(value) {
|
|
|
43
58
|
function assertSupportedPiVersion(value) {
|
|
44
59
|
const version = parsePiVersion(value);
|
|
45
60
|
if (!version) {
|
|
46
|
-
throw new Error("无法检测 Pi
|
|
61
|
+
throw new Error("无法检测 Pi 版本(可能尚未安装);请先安装 Pi 0.80.0 或更高版本。");
|
|
47
62
|
}
|
|
48
63
|
for (let index = 0; index < minimumPiVersion.length; index += 1) {
|
|
49
64
|
if (version[index] > minimumPiVersion[index]) return version;
|
|
50
65
|
if (version[index] < minimumPiVersion[index]) {
|
|
51
|
-
throw new Error(`当前 Pi ${version.join(".")} 不支持 auth-only 自定义 Provider
|
|
66
|
+
throw new Error(`当前 Pi ${version.join(".")} 不支持 auth-only 自定义 Provider;请先升级到 Pi 0.80.0 或更高版本。`);
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
69
|
return version;
|
|
@@ -63,6 +78,105 @@ function detectPiVersion() {
|
|
|
63
78
|
return result.status === 0 ? `${result.stdout || ""} ${result.stderr || ""}`.trim() : undefined;
|
|
64
79
|
}
|
|
65
80
|
|
|
81
|
+
function parsePiRpcEffectiveModel(value) {
|
|
82
|
+
for (const line of String(value || "").split(/\r?\n/u)) {
|
|
83
|
+
let payload;
|
|
84
|
+
try {
|
|
85
|
+
payload = JSON.parse(line);
|
|
86
|
+
} catch {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const model = payload?.type === "response"
|
|
90
|
+
&& payload.command === "get_state"
|
|
91
|
+
&& payload.success === true
|
|
92
|
+
? payload.data?.model
|
|
93
|
+
: undefined;
|
|
94
|
+
const provider = typeof model?.provider === "string" ? model.provider.trim() : "";
|
|
95
|
+
const id = typeof model?.id === "string" ? model.id.trim() : "";
|
|
96
|
+
if (!provider || !id) continue;
|
|
97
|
+
return {
|
|
98
|
+
provider,
|
|
99
|
+
id,
|
|
100
|
+
api: typeof model.api === "string" ? model.api.trim() : "",
|
|
101
|
+
baseUrl: typeof model.baseUrl === "string" ? model.baseUrl.trim() : ""
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function runPiRpcCommand(command, args, {
|
|
107
|
+
input = "",
|
|
108
|
+
maxBuffer = 1024 * 1024,
|
|
109
|
+
shell = process.platform === "win32",
|
|
110
|
+
start = spawn,
|
|
111
|
+
timeout = 20000
|
|
112
|
+
} = {}) {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
let child;
|
|
115
|
+
let stdout = "";
|
|
116
|
+
let settled = false;
|
|
117
|
+
let timer;
|
|
118
|
+
const finish = (status) => {
|
|
119
|
+
if (settled) return;
|
|
120
|
+
settled = true;
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
child?.stdin?.destroy();
|
|
123
|
+
child?.stdout?.destroy();
|
|
124
|
+
child?.stderr?.destroy();
|
|
125
|
+
if (child?.exitCode === null && child.signalCode === null && !child.killed) child.kill();
|
|
126
|
+
child?.unref();
|
|
127
|
+
resolve({ status, stdout });
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const env = { ...process.env };
|
|
132
|
+
delete env.NODE_TEST_CONTEXT;
|
|
133
|
+
child = start(command, args, {
|
|
134
|
+
env,
|
|
135
|
+
shell,
|
|
136
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
137
|
+
windowsHide: true
|
|
138
|
+
});
|
|
139
|
+
} catch {
|
|
140
|
+
finish(null);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
timer = setTimeout(() => finish(null), timeout);
|
|
145
|
+
child.on("error", () => finish(null));
|
|
146
|
+
child.on("exit", (code) => finish(code));
|
|
147
|
+
child.stdout.setEncoding("utf8");
|
|
148
|
+
child.stdout.on("data", (chunk) => {
|
|
149
|
+
stdout += chunk;
|
|
150
|
+
if (Buffer.byteLength(stdout, "utf8") > maxBuffer) {
|
|
151
|
+
finish(null);
|
|
152
|
+
} else if (parsePiRpcEffectiveModel(stdout)) {
|
|
153
|
+
finish(0);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
child.stdin.on("error", () => finish(null));
|
|
157
|
+
child.stdin.write(input, (error) => {
|
|
158
|
+
if (error) finish(null);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function detectPiEffectiveModel(run = runPiRpcCommand) {
|
|
164
|
+
let result;
|
|
165
|
+
try {
|
|
166
|
+
result = await run("pi", piRpcArgs, {
|
|
167
|
+
encoding: "utf8",
|
|
168
|
+
input: `${JSON.stringify({ id: piRpcRequestId, type: "get_state" })}\n`,
|
|
169
|
+
maxBuffer: 1024 * 1024,
|
|
170
|
+
shell: process.platform === "win32",
|
|
171
|
+
timeout: 20000
|
|
172
|
+
});
|
|
173
|
+
} catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (result?.error || result?.status !== 0) return undefined;
|
|
177
|
+
return parsePiRpcEffectiveModel(result.stdout);
|
|
178
|
+
}
|
|
179
|
+
|
|
66
180
|
function parsePiModelIds(value) {
|
|
67
181
|
return [...new Set(String(value || "")
|
|
68
182
|
.split(/[\n,]+/u)
|
|
@@ -115,50 +229,106 @@ function getPiApiPromptOptions() {
|
|
|
115
229
|
];
|
|
116
230
|
}
|
|
117
231
|
|
|
118
|
-
function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {}) {
|
|
119
|
-
const
|
|
120
|
-
|
|
232
|
+
function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {}, effectiveModel) {
|
|
233
|
+
const savedProviderId = typeof settings.defaultProvider === "string" ? settings.defaultProvider.trim() : "";
|
|
234
|
+
const savedModelId = typeof settings.defaultModel === "string" ? settings.defaultModel.trim() : "";
|
|
235
|
+
const effectiveProviderId = typeof effectiveModel?.provider === "string" ? effectiveModel.provider.trim() : "";
|
|
236
|
+
const effectiveModelId = typeof effectiveModel?.id === "string" ? effectiveModel.id.trim() : "";
|
|
237
|
+
const savedProvider = savedProviderId
|
|
238
|
+
&& modelsConfig.providers?.[savedProviderId]
|
|
239
|
+
&& typeof modelsConfig.providers[savedProviderId] === "object"
|
|
240
|
+
? modelsConfig.providers[savedProviderId]
|
|
241
|
+
: undefined;
|
|
242
|
+
const savedModels = Array.isArray(savedProvider?.models) ? savedProvider.models : [];
|
|
243
|
+
const savedTargetExists = savedProviderId
|
|
244
|
+
&& savedModelId
|
|
245
|
+
&& savedModels.some((model) => model?.id === savedModelId);
|
|
246
|
+
const providerId = effectiveProviderId && effectiveModelId
|
|
247
|
+
? effectiveProviderId
|
|
248
|
+
: savedTargetExists ? savedProviderId : "";
|
|
249
|
+
const defaultModel = effectiveProviderId && effectiveModelId
|
|
250
|
+
? effectiveModelId
|
|
251
|
+
: savedTargetExists ? savedModelId : "";
|
|
252
|
+
const provider = providerId
|
|
253
|
+
&& modelsConfig.providers?.[providerId]
|
|
254
|
+
&& typeof modelsConfig.providers[providerId] === "object"
|
|
255
|
+
? modelsConfig.providers[providerId]
|
|
121
256
|
: {};
|
|
122
|
-
const credential = auth[
|
|
123
|
-
? auth[
|
|
257
|
+
const credential = providerId && auth[providerId] && typeof auth[providerId] === "object"
|
|
258
|
+
? auth[providerId]
|
|
124
259
|
: {};
|
|
125
260
|
const authApiKey = credential.type === "api_key" && typeof credential.key === "string"
|
|
126
261
|
? credential.key
|
|
127
262
|
: "";
|
|
128
263
|
const models = Array.isArray(provider.models) ? provider.models.filter((model) => model?.id) : [];
|
|
264
|
+
const model = models.find((item) => item.id === defaultModel) || {};
|
|
265
|
+
const runtimeMatches = Boolean(effectiveProviderId
|
|
266
|
+
&& effectiveModelId
|
|
267
|
+
&& effectiveProviderId === providerId
|
|
268
|
+
&& effectiveModelId === defaultModel);
|
|
129
269
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
270
|
+
providerId,
|
|
271
|
+
baseUrl: runtimeMatches && typeof effectiveModel.baseUrl === "string"
|
|
272
|
+
? effectiveModel.baseUrl
|
|
273
|
+
: typeof model.baseUrl === "string" ? model.baseUrl
|
|
274
|
+
: typeof provider.baseUrl === "string" ? provider.baseUrl : "",
|
|
275
|
+
api: runtimeMatches && typeof effectiveModel.api === "string"
|
|
276
|
+
? effectiveModel.api
|
|
277
|
+
: typeof model.api === "string" ? model.api
|
|
278
|
+
: typeof provider.api === "string" ? provider.api : "",
|
|
279
|
+
apiKey: authApiKey || (typeof provider.apiKey === "string" ? provider.apiKey : ""),
|
|
133
280
|
modelIds: models.map((model) => model.id),
|
|
134
|
-
defaultModel
|
|
135
|
-
? settings.defaultModel
|
|
136
|
-
: models[0]?.id || piDefaultModel
|
|
281
|
+
defaultModel
|
|
137
282
|
};
|
|
138
283
|
}
|
|
139
284
|
|
|
285
|
+
function assertConfigurablePiProvider(modelsConfig = {}, configuration = {}) {
|
|
286
|
+
const { providerId, defaultModel, api } = configuration;
|
|
287
|
+
const provider = modelsConfig.providers?.[providerId];
|
|
288
|
+
const models = Array.isArray(provider?.models) ? provider.models : [];
|
|
289
|
+
if (!provider || typeof provider !== "object" || !models.some((model) => model?.id === defaultModel)) {
|
|
290
|
+
throw new Error(`Pi 当前有效 Provider ${providerId || "未知"} 不是 models.json 中的自定义 Provider;为避免覆盖内置模型,已停止配置。`);
|
|
291
|
+
}
|
|
292
|
+
const supportedApis = new Set(getPiApiPromptOptions().map((option) => option.value));
|
|
293
|
+
if (api && !supportedApis.has(api)) {
|
|
294
|
+
throw new Error(`Pi 当前有效 Provider ${providerId} 使用不受支持的 API 类型 ${api};此配置器仅支持 OpenAI-compatible API。`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
140
298
|
function buildPiModelConfig(modelId, existingModel = {}) {
|
|
299
|
+
const model = { ...existingModel };
|
|
300
|
+
delete model.baseUrl;
|
|
301
|
+
delete model.api;
|
|
141
302
|
return {
|
|
142
|
-
...
|
|
303
|
+
...model,
|
|
143
304
|
id: modelId,
|
|
144
|
-
name:
|
|
145
|
-
reasoning:
|
|
146
|
-
input: Array.isArray(
|
|
147
|
-
contextWindow:
|
|
148
|
-
maxTokens:
|
|
305
|
+
name: model.name || modelId,
|
|
306
|
+
reasoning: model.reasoning ?? true,
|
|
307
|
+
input: Array.isArray(model.input) ? model.input : ["text", "image"],
|
|
308
|
+
contextWindow: model.contextWindow ?? 262144,
|
|
309
|
+
maxTokens: model.maxTokens ?? 64000
|
|
149
310
|
};
|
|
150
311
|
}
|
|
151
312
|
|
|
152
|
-
function hasPiInsecureTlsSetting(modelsConfig = {}) {
|
|
153
|
-
const headers = modelsConfig.providers?.[
|
|
313
|
+
function hasPiInsecureTlsSetting(modelsConfig = {}, providerId) {
|
|
314
|
+
const headers = modelsConfig.providers?.[requirePiProviderId(providerId)]?.headers;
|
|
154
315
|
return headers && typeof headers === "object"
|
|
155
316
|
? Object.keys(headers).some((key) => key.toLowerCase() === piInsecureTlsHeader)
|
|
156
317
|
: false;
|
|
157
318
|
}
|
|
158
319
|
|
|
159
|
-
function buildPiModelsConfig(modelsConfig = {}, {
|
|
320
|
+
function buildPiModelsConfig(modelsConfig = {}, {
|
|
321
|
+
providerId,
|
|
322
|
+
baseUrl,
|
|
323
|
+
api,
|
|
324
|
+
modelIds,
|
|
325
|
+
insecureTls = false
|
|
326
|
+
}) {
|
|
327
|
+
const targetProviderId = requirePiProviderId(providerId);
|
|
160
328
|
const providers = modelsConfig.providers && typeof modelsConfig.providers === "object" ? modelsConfig.providers : {};
|
|
161
|
-
const currentProvider = providers[
|
|
329
|
+
const currentProvider = providers[targetProviderId] && typeof providers[targetProviderId] === "object"
|
|
330
|
+
? providers[targetProviderId]
|
|
331
|
+
: {};
|
|
162
332
|
const headers = currentProvider.headers && typeof currentProvider.headers === "object"
|
|
163
333
|
? { ...currentProvider.headers }
|
|
164
334
|
: {};
|
|
@@ -197,16 +367,19 @@ function buildPiModelsConfig(modelsConfig = {}, { baseUrl, api, modelIds, insecu
|
|
|
197
367
|
...modelsConfig,
|
|
198
368
|
providers: {
|
|
199
369
|
...providers,
|
|
200
|
-
[
|
|
370
|
+
[targetProviderId]: provider
|
|
201
371
|
}
|
|
202
372
|
};
|
|
203
373
|
}
|
|
204
374
|
|
|
205
|
-
function buildPiAuthConfig(auth = {}, apiKey) {
|
|
206
|
-
const
|
|
375
|
+
function buildPiAuthConfig(auth = {}, providerId, apiKey) {
|
|
376
|
+
const targetProviderId = requirePiProviderId(providerId);
|
|
377
|
+
const credential = auth[targetProviderId] && typeof auth[targetProviderId] === "object"
|
|
378
|
+
? auth[targetProviderId]
|
|
379
|
+
: {};
|
|
207
380
|
return {
|
|
208
381
|
...auth,
|
|
209
|
-
[
|
|
382
|
+
[targetProviderId]: {
|
|
210
383
|
...credential,
|
|
211
384
|
type: "api_key",
|
|
212
385
|
key: apiKey
|
|
@@ -214,10 +387,10 @@ function buildPiAuthConfig(auth = {}, apiKey) {
|
|
|
214
387
|
};
|
|
215
388
|
}
|
|
216
389
|
|
|
217
|
-
function buildPiSettingsConfig(settings = {}, defaultModel) {
|
|
390
|
+
function buildPiSettingsConfig(settings = {}, providerId, defaultModel) {
|
|
218
391
|
return {
|
|
219
392
|
...settings,
|
|
220
|
-
defaultProvider:
|
|
393
|
+
defaultProvider: requirePiProviderId(providerId),
|
|
221
394
|
defaultModel,
|
|
222
395
|
defaultThinkingLevel: settings.defaultThinkingLevel || "high",
|
|
223
396
|
enableSkillCommands: settings.enableSkillCommands ?? true
|
|
@@ -235,6 +408,7 @@ async function readExistingPiConfiguration(paths, operations = {}) {
|
|
|
235
408
|
}
|
|
236
409
|
|
|
237
410
|
function buildPiConfiguration({ auth, models, settings }, {
|
|
411
|
+
providerId,
|
|
238
412
|
apiKey,
|
|
239
413
|
baseUrl,
|
|
240
414
|
api,
|
|
@@ -242,11 +416,19 @@ function buildPiConfiguration({ auth, models, settings }, {
|
|
|
242
416
|
insecureTls,
|
|
243
417
|
defaultModel
|
|
244
418
|
}) {
|
|
419
|
+
const targetProviderId = requirePiProviderId(providerId);
|
|
245
420
|
return {
|
|
421
|
+
providerId: targetProviderId,
|
|
246
422
|
apiKey,
|
|
247
|
-
auth: buildPiAuthConfig(auth, apiKey),
|
|
248
|
-
models: buildPiModelsConfig(models, {
|
|
249
|
-
|
|
423
|
+
auth: buildPiAuthConfig(auth, targetProviderId, apiKey),
|
|
424
|
+
models: buildPiModelsConfig(models, {
|
|
425
|
+
providerId: targetProviderId,
|
|
426
|
+
baseUrl,
|
|
427
|
+
api,
|
|
428
|
+
modelIds,
|
|
429
|
+
insecureTls
|
|
430
|
+
}),
|
|
431
|
+
settings: buildPiSettingsConfig(settings, targetProviderId, defaultModel)
|
|
250
432
|
};
|
|
251
433
|
}
|
|
252
434
|
|
|
@@ -258,31 +440,55 @@ async function persistPiConfiguration(paths, configuration, operations = {}) {
|
|
|
258
440
|
{ sensitive: true }
|
|
259
441
|
));
|
|
260
442
|
const writeSettings = operations.writeSettings ?? ((path, value) => writeJsonFileWithBackup(path, value));
|
|
261
|
-
await updateAuth(paths.piAuthPath, configuration.apiKey);
|
|
443
|
+
await updateAuth(paths.piAuthPath, configuration.providerId, configuration.apiKey);
|
|
262
444
|
await writeModels(paths.piModelsPath, configuration.models);
|
|
263
445
|
await writeSettings(paths.piSettingsPath, configuration.settings);
|
|
264
446
|
}
|
|
265
447
|
|
|
266
448
|
async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = async () => {}, promptApi, runtime = {}) {
|
|
449
|
+
const piVersion = await (runtime.getPiVersion ?? detectPiVersion)();
|
|
450
|
+
try {
|
|
451
|
+
assertSupportedPiVersion(piVersion);
|
|
452
|
+
} catch (error) {
|
|
453
|
+
(runtime.log ?? p.log).warn(error.message || String(error));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
267
456
|
const {
|
|
268
457
|
assertNotCancelled,
|
|
269
458
|
confirmOrCancel,
|
|
459
|
+
passwordPromptOptions,
|
|
270
460
|
required,
|
|
271
461
|
requiredUnlessExisting,
|
|
272
462
|
resolvePasswordValue,
|
|
273
463
|
selectOrCancel
|
|
274
464
|
} = promptApi;
|
|
275
|
-
assertSupportedPiVersion(await (runtime.getPiVersion ?? detectPiVersion)());
|
|
276
465
|
const {
|
|
277
466
|
auth,
|
|
278
467
|
models: modelsConfig,
|
|
279
468
|
settings
|
|
280
469
|
} = await readExistingPiConfiguration(paths);
|
|
281
|
-
const
|
|
470
|
+
const detectionSpinner = p.spinner();
|
|
471
|
+
detectionSpinner.start("正在识别 Pi 当前有效模型");
|
|
472
|
+
let effectiveModel;
|
|
473
|
+
try {
|
|
474
|
+
effectiveModel = await (runtime.getPiEffectiveModel ?? detectPiEffectiveModel)();
|
|
475
|
+
} finally {
|
|
476
|
+
detectionSpinner.stop(effectiveModel
|
|
477
|
+
? `已识别 ${effectiveModel.provider}/${effectiveModel.id}`
|
|
478
|
+
: "未识别到 Pi 当前有效模型");
|
|
479
|
+
}
|
|
480
|
+
const existing = resolveExistingPiApiConfig(modelsConfig, settings, auth, effectiveModel);
|
|
481
|
+
const providerId = requirePiProviderId(existing.providerId);
|
|
482
|
+
assertConfigurablePiProvider(modelsConfig, existing);
|
|
483
|
+
if (effectiveModel
|
|
484
|
+
&& (settings.defaultProvider !== existing.providerId || settings.defaultModel !== existing.defaultModel)) {
|
|
485
|
+
p.log.warn(`Pi 保存的默认模型 ${settings.defaultProvider || "未知"}/${settings.defaultModel || "未知"} 与当前有效模型 ${existing.providerId}/${existing.defaultModel} 不同;将配置当前有效模型。`);
|
|
486
|
+
}
|
|
487
|
+
const providerLabel = `Pi ${providerId}`;
|
|
282
488
|
|
|
283
489
|
const baseUrlInput = await p.text({
|
|
284
|
-
message:
|
|
285
|
-
|
|
490
|
+
message: `${providerLabel} Base URL`,
|
|
491
|
+
initialValue: existing.baseUrl,
|
|
286
492
|
validate: required()
|
|
287
493
|
});
|
|
288
494
|
assertNotCancelled(baseUrlInput);
|
|
@@ -290,29 +496,33 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
|
|
|
290
496
|
const baseUrl = normalizeOpenAiBaseUrl(baseUrlInput);
|
|
291
497
|
|
|
292
498
|
const insecureTls = await confirmOrCancel({
|
|
293
|
-
message:
|
|
294
|
-
initialValue: hasPiInsecureTlsSetting(modelsConfig)
|
|
499
|
+
message: `是否仅为 ${providerLabel} 中转请求跳过 TLS 证书校验?仅证书无法修复时启用`,
|
|
500
|
+
initialValue: hasPiInsecureTlsSetting(modelsConfig, providerId)
|
|
295
501
|
});
|
|
296
502
|
|
|
297
503
|
const piApiOptions = getPiApiPromptOptions();
|
|
504
|
+
const initialApi = piApiOptions.some((option) => option.value === existing.api)
|
|
505
|
+
? existing.api
|
|
506
|
+
: undefined;
|
|
298
507
|
const api = inferredApi || await selectOrCancel({
|
|
299
|
-
message:
|
|
508
|
+
message: `${providerLabel} API 类型`,
|
|
300
509
|
options: piApiOptions,
|
|
301
|
-
|
|
510
|
+
...(initialApi ? { initialValue: initialApi } : {})
|
|
302
511
|
});
|
|
303
512
|
|
|
304
|
-
const apiKey = await p.password(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
});
|
|
513
|
+
const apiKey = await p.password(passwordPromptOptions(
|
|
514
|
+
`${providerLabel} API Key`,
|
|
515
|
+
existing.apiKey,
|
|
516
|
+
requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
|
|
517
|
+
));
|
|
310
518
|
assertNotCancelled(apiKey);
|
|
311
519
|
const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
|
|
312
520
|
|
|
313
521
|
const modelIdsText = await p.text({
|
|
314
|
-
message:
|
|
315
|
-
|
|
522
|
+
message: `${providerLabel} 模型 ID(多个用逗号分隔)`,
|
|
523
|
+
initialValue: (existing.modelIds.length
|
|
524
|
+
? existing.modelIds
|
|
525
|
+
: existing.defaultModel ? [existing.defaultModel] : []).join(","),
|
|
316
526
|
validate: (value) => parsePiModelIds(value).length ? undefined : "至少需要一个模型 ID"
|
|
317
527
|
});
|
|
318
528
|
assertNotCancelled(modelIdsText);
|
|
@@ -320,13 +530,14 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
|
|
|
320
530
|
|
|
321
531
|
const defaultModel = await p.text({
|
|
322
532
|
message: "Pi 默认模型",
|
|
323
|
-
|
|
533
|
+
initialValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
|
|
324
534
|
validate: (value) => modelIds.includes(String(value || "").trim()) ? undefined : "默认模型必须在模型 ID 列表中"
|
|
325
535
|
});
|
|
326
536
|
assertNotCancelled(defaultModel);
|
|
327
537
|
|
|
328
538
|
const finalDefaultModel = String(defaultModel).trim();
|
|
329
539
|
const configuration = buildPiConfiguration({ auth, models: modelsConfig, settings }, {
|
|
540
|
+
providerId,
|
|
330
541
|
apiKey: finalApiKey,
|
|
331
542
|
baseUrl,
|
|
332
543
|
api,
|
|
@@ -337,26 +548,30 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
|
|
|
337
548
|
await ensurePiResourcesLinked(paths);
|
|
338
549
|
await persistPiConfiguration(paths, configuration);
|
|
339
550
|
|
|
340
|
-
p.log.step(`已更新 ${pathToLabel(paths.piModelsPath, paths.homeDir)} (${
|
|
551
|
+
p.log.step(`已更新 ${pathToLabel(paths.piModelsPath, paths.homeDir)} (${providerId}, ${baseUrl})`);
|
|
341
552
|
p.log.step(`已更新 ${pathToLabel(paths.piSettingsPath, paths.homeDir)} (默认模型: ${finalDefaultModel})`);
|
|
342
553
|
p.log.step(`已更新 ${pathToLabel(paths.piAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
|
|
343
554
|
p.log.step(`已链接 Pi 扩展到 ${pathToLabel(join(paths.piAgentDir, "extensions"), paths.homeDir)}`);
|
|
344
555
|
}
|
|
345
556
|
|
|
346
557
|
export {
|
|
558
|
+
assertConfigurablePiProvider,
|
|
347
559
|
assertSupportedPiVersion,
|
|
348
560
|
buildPiAuthConfig,
|
|
349
561
|
buildPiConfiguration,
|
|
350
562
|
buildPiModelsConfig,
|
|
351
563
|
buildPiSettingsConfig,
|
|
352
564
|
configurePiApi,
|
|
565
|
+
detectPiEffectiveModel,
|
|
353
566
|
getPiApiPromptOptions,
|
|
354
567
|
hasPiInsecureTlsSetting,
|
|
355
568
|
inferPiApiFromBaseUrl,
|
|
356
569
|
normalizeOpenAiBaseUrl,
|
|
570
|
+
parsePiRpcEffectiveModel,
|
|
357
571
|
parsePiModelIds,
|
|
358
572
|
persistPiConfiguration,
|
|
359
573
|
readExistingPiConfiguration,
|
|
360
574
|
resolveExistingPiApiConfig,
|
|
575
|
+
runPiRpcCommand,
|
|
361
576
|
updatePiAuthFile
|
|
362
577
|
};
|
package/lib/providers/skills.mjs
CHANGED
|
@@ -17,30 +17,36 @@ async function updateSkillEnvFile(path, updates) {
|
|
|
17
17
|
return updateDotenvFile(path, updates, { sensitive: true });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
async function configureGrokSearchEnv(paths,
|
|
21
|
-
const {
|
|
22
|
-
|
|
20
|
+
async function configureGrokSearchEnv(paths, ensureSkillPresent = async () => {}, promptApi) {
|
|
21
|
+
const {
|
|
22
|
+
assertNotCancelled,
|
|
23
|
+
confirmOrCancel,
|
|
24
|
+
passwordPromptOptions,
|
|
25
|
+
required,
|
|
26
|
+
requiredUnlessExisting,
|
|
27
|
+
resolvePasswordValue
|
|
28
|
+
} = promptApi;
|
|
29
|
+
await ensureSkillPresent(paths);
|
|
23
30
|
const envPath = join(paths.agentsDir, "skills", "grok-search", ".env");
|
|
24
31
|
const existing = await readSkillEnvFile(envPath);
|
|
25
32
|
const baseUrl = await p.text({
|
|
26
33
|
message: "Grok API URL",
|
|
27
|
-
|
|
34
|
+
initialValue: existing.GROK_API_URL || "https://api.x.ai/v1",
|
|
28
35
|
validate: required()
|
|
29
36
|
});
|
|
30
37
|
assertNotCancelled(baseUrl);
|
|
31
38
|
|
|
32
|
-
const apiKey = await p.password(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
});
|
|
39
|
+
const apiKey = await p.password(passwordPromptOptions(
|
|
40
|
+
"Grok API Key",
|
|
41
|
+
existing.GROK_API_KEY,
|
|
42
|
+
requiredUnlessExisting(existing.GROK_API_KEY, "Grok API Key 不能为空")
|
|
43
|
+
));
|
|
38
44
|
assertNotCancelled(apiKey);
|
|
39
45
|
const finalApiKey = resolvePasswordValue(apiKey, existing.GROK_API_KEY);
|
|
40
46
|
|
|
41
47
|
const model = await p.text({
|
|
42
48
|
message: "Grok 默认模型",
|
|
43
|
-
|
|
49
|
+
initialValue: existing.GROK_MODEL || grokDefaults.model,
|
|
44
50
|
validate: required()
|
|
45
51
|
});
|
|
46
52
|
assertNotCancelled(model);
|
|
@@ -51,12 +57,11 @@ async function configureGrokSearchEnv(paths, ensureWorkflowPresent = async () =>
|
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
const tavilyKey = useTavily
|
|
54
|
-
? await p.password(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
})
|
|
60
|
+
? await p.password(passwordPromptOptions(
|
|
61
|
+
"Tavily API Key",
|
|
62
|
+
existing.TAVILY_API_KEY,
|
|
63
|
+
requiredUnlessExisting(existing.TAVILY_API_KEY, "Tavily API Key 不能为空")
|
|
64
|
+
))
|
|
60
65
|
: "";
|
|
61
66
|
if (useTavily) assertNotCancelled(tavilyKey);
|
|
62
67
|
const finalTavilyKey = useTavily ? resolvePasswordValue(tavilyKey, existing.TAVILY_API_KEY) : null;
|
|
@@ -72,16 +77,15 @@ async function configureGrokSearchEnv(paths, ensureWorkflowPresent = async () =>
|
|
|
72
77
|
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
async function configureContext7Env(paths,
|
|
76
|
-
const { assertNotCancelled, resolvePasswordValue } = promptApi;
|
|
77
|
-
await
|
|
80
|
+
async function configureContext7Env(paths, ensureSkillPresent = async () => {}, promptApi) {
|
|
81
|
+
const { assertNotCancelled, passwordPromptOptions, resolvePasswordValue } = promptApi;
|
|
82
|
+
await ensureSkillPresent(paths);
|
|
78
83
|
const envPath = join(paths.agentsDir, "skills", "context7-auto-research", ".env");
|
|
79
84
|
const existing = await readSkillEnvFile(envPath);
|
|
80
|
-
const apiKey = await p.password(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
+
const apiKey = await p.password(passwordPromptOptions(
|
|
86
|
+
"Context7 API Key(可选)",
|
|
87
|
+
existing.CONTEXT7_API_KEY
|
|
88
|
+
));
|
|
85
89
|
assertNotCancelled(apiKey);
|
|
86
90
|
const finalApiKey = resolvePasswordValue(apiKey, existing.CONTEXT7_API_KEY);
|
|
87
91
|
|
|
@@ -92,73 +96,10 @@ async function configureContext7Env(paths, ensureWorkflowPresent = async () => {
|
|
|
92
96
|
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
function hasPromptEnhancerApiConfig(config) {
|
|
96
|
-
return [config.PE_API_URL, config.PE_API_KEY, config.PE_MODEL]
|
|
97
|
-
.every((value) => typeof value === "string" && value.trim() !== "");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function resolvePromptEnhancerMode(existing) {
|
|
101
|
-
return hasPromptEnhancerApiConfig(existing) ? "openai-compatible" : "agent";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function buildPromptEnhancerEnvUpdates(apiUrl = null, apiKey = null, model = null) {
|
|
105
|
-
return { PE_API_URL: apiUrl, PE_API_KEY: apiKey, PE_MODEL: model };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function configurePromptEnhancerEnv(paths, ensureWorkflowPresent = async () => {}, promptApi) {
|
|
109
|
-
const { assertNotCancelled, required, requiredUnlessExisting, resolvePasswordValue, selectOrCancel } = promptApi;
|
|
110
|
-
await ensureWorkflowPresent(paths);
|
|
111
|
-
const envPath = join(paths.agentsDir, "skills", "prompt-enhancer", ".env");
|
|
112
|
-
const existing = await readSkillEnvFile(envPath);
|
|
113
|
-
const mode = await selectOrCancel({
|
|
114
|
-
message: "请选择 prompt-enhancer 的运行方式",
|
|
115
|
-
options: [
|
|
116
|
-
{ value: "openai-compatible", label: "第三方 OpenAI 兼容接口" },
|
|
117
|
-
{ value: "agent", label: "直接使用当前 Agent" }
|
|
118
|
-
],
|
|
119
|
-
initialValue: resolvePromptEnhancerMode(existing)
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
if (mode === "openai-compatible") {
|
|
123
|
-
const apiUrl = await p.text({
|
|
124
|
-
message: "PE_API_URL",
|
|
125
|
-
defaultValue: existing.PE_API_URL || undefined,
|
|
126
|
-
validate: required()
|
|
127
|
-
});
|
|
128
|
-
assertNotCancelled(apiUrl);
|
|
129
|
-
|
|
130
|
-
const apiKey = await p.password({
|
|
131
|
-
message: "PE_API_KEY(输入 - 清除)",
|
|
132
|
-
mask: "*",
|
|
133
|
-
defaultValue: existing.PE_API_KEY || undefined,
|
|
134
|
-
validate: requiredUnlessExisting(existing.PE_API_KEY, "PE_API_KEY 不能为空")
|
|
135
|
-
});
|
|
136
|
-
assertNotCancelled(apiKey);
|
|
137
|
-
const finalApiKey = resolvePasswordValue(apiKey, existing.PE_API_KEY);
|
|
138
|
-
|
|
139
|
-
const model = await p.text({
|
|
140
|
-
message: "PE_MODEL",
|
|
141
|
-
defaultValue: existing.PE_MODEL || undefined,
|
|
142
|
-
validate: required()
|
|
143
|
-
});
|
|
144
|
-
assertNotCancelled(model);
|
|
145
|
-
|
|
146
|
-
await updateSkillEnvFile(envPath, buildPromptEnhancerEnvUpdates(apiUrl, finalApiKey, model));
|
|
147
|
-
} else {
|
|
148
|
-
await updateSkillEnvFile(envPath, buildPromptEnhancerEnvUpdates());
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
p.log.step(`已写入 ${pathToLabel(envPath)}`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
99
|
export {
|
|
155
|
-
buildPromptEnhancerEnvUpdates,
|
|
156
100
|
configureContext7Env,
|
|
157
101
|
configureGrokSearchEnv,
|
|
158
|
-
configurePromptEnhancerEnv,
|
|
159
102
|
grokDefaults,
|
|
160
|
-
hasPromptEnhancerApiConfig,
|
|
161
103
|
readSkillEnvFile,
|
|
162
|
-
resolvePromptEnhancerMode,
|
|
163
104
|
updateSkillEnvFile
|
|
164
105
|
};
|