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.
@@ -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
- async function updatePiAuthFile(path, apiKey) {
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 版本;配置自定义 Provider 需要 Pi 0.80.0 或更高版本。");
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;请升级到 Pi 0.80.0 或更高版本。`);
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 provider = modelsConfig.providers?.[piProviderId] && typeof modelsConfig.providers[piProviderId] === "object"
120
- ? modelsConfig.providers[piProviderId]
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[piProviderId] && typeof auth[piProviderId] === "object"
123
- ? auth[piProviderId]
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
- baseUrl: provider.baseUrl || piDefaultBaseUrl,
131
- api: provider.api || piDefaultApi,
132
- apiKey: authApiKey || provider.apiKey || "",
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: settings.defaultProvider === piProviderId && settings.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
- ...existingModel,
303
+ ...model,
143
304
  id: modelId,
144
- name: existingModel.name || modelId,
145
- reasoning: existingModel.reasoning ?? true,
146
- input: Array.isArray(existingModel.input) ? existingModel.input : ["text", "image"],
147
- contextWindow: existingModel.contextWindow ?? 262144,
148
- maxTokens: existingModel.maxTokens ?? 64000
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?.[piProviderId]?.headers;
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 = {}, { baseUrl, api, modelIds, insecureTls = false }) {
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[piProviderId] && typeof providers[piProviderId] === "object" ? providers[piProviderId] : {};
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
- [piProviderId]: provider
370
+ [targetProviderId]: provider
201
371
  }
202
372
  };
203
373
  }
204
374
 
205
- function buildPiAuthConfig(auth = {}, apiKey) {
206
- const credential = auth[piProviderId] && typeof auth[piProviderId] === "object" ? auth[piProviderId] : {};
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
- [piProviderId]: {
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: piProviderId,
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, { baseUrl, api, modelIds, insecureTls }),
249
- settings: buildPiSettingsConfig(settings, defaultModel)
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 existing = resolveExistingPiApiConfig(modelsConfig, settings, auth);
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: "Pi gpt Base URL",
285
- defaultValue: existing.baseUrl,
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: "是否仅为 Pi gpt 中转请求跳过 TLS 证书校验?仅证书无法修复时启用",
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: "Pi gpt API 类型",
508
+ message: `${providerLabel} API 类型`,
300
509
  options: piApiOptions,
301
- initialValue: piApiOptions.some((option) => option.value === existing.api) ? existing.api : piDefaultApi
510
+ ...(initialApi ? { initialValue: initialApi } : {})
302
511
  });
303
512
 
304
- const apiKey = await p.password({
305
- message: "Pi gpt API Key(输入 - 清除)",
306
- mask: "*",
307
- defaultValue: existing.apiKey || undefined,
308
- validate: requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
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: "Pi gpt 模型 ID(多个用逗号分隔)",
315
- defaultValue: (existing.modelIds.length ? existing.modelIds : [existing.defaultModel]).join(","),
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
- defaultValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
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)} (${piProviderId}, ${baseUrl})`);
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
  };
@@ -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, ensureWorkflowPresent = async () => {}, promptApi) {
21
- const { assertNotCancelled, confirmOrCancel, required, requiredUnlessExisting, resolvePasswordValue } = promptApi;
22
- await ensureWorkflowPresent(paths);
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
- defaultValue: existing.GROK_API_URL || "https://api.x.ai/v1",
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
- message: "Grok API Key(输入 - 清除)",
34
- mask: "*",
35
- defaultValue: existing.GROK_API_KEY || undefined,
36
- validate: requiredUnlessExisting(existing.GROK_API_KEY, "Grok API Key 不能为空")
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
- defaultValue: existing.GROK_MODEL || grokDefaults.model,
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
- message: "Tavily API Key(输入 - 清除)",
56
- mask: "*",
57
- defaultValue: existing.TAVILY_API_KEY || undefined,
58
- validate: requiredUnlessExisting(existing.TAVILY_API_KEY, "Tavily API Key 不能为空")
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, ensureWorkflowPresent = async () => {}, promptApi) {
76
- const { assertNotCancelled, resolvePasswordValue } = promptApi;
77
- await ensureWorkflowPresent(paths);
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
- message: "Context7 API Key (可选,输入 - 清除)",
82
- mask: "*",
83
- defaultValue: existing.CONTEXT7_API_KEY || undefined
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
  };