larkin 0.4.29 → 0.5.1

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +5 -9
  3. package/artifacts/release/THIRD_PARTY_NOTICES.txt +751 -4425
  4. package/dist/app/agent-cli.mjs +14 -2
  5. package/dist/app/agent-config.mjs +26 -86
  6. package/dist/app/binary-entry.mjs +0 -20
  7. package/dist/app/cli.mjs +6 -22
  8. package/dist/app/internal-command.cjs +0 -2
  9. package/dist/app/internal-command.mjs +0 -2
  10. package/dist/app/runtime-model-directory.mjs +2 -16
  11. package/dist/app/runtime-process.mjs +0 -1
  12. package/dist/app/setup.mjs +52 -57
  13. package/dist/dashboard/dashboard-config-controller.mjs +43 -109
  14. package/dist/dashboard/dashboard-view-model.mjs +4 -7
  15. package/dist/dashboard/web/assets/dashboard.css +1 -1
  16. package/dist/dashboard/web/assets/dashboard.js +15 -15
  17. package/dist/feishu/host-shell.cjs +1 -2
  18. package/dist/feishu/host-shell.mjs +1 -2
  19. package/dist/platform/config.cjs +168 -133
  20. package/dist/platform/config.mjs +168 -133
  21. package/dist/runtime/pi-artifact-provenance.mjs +3 -3
  22. package/dist/runtime/pi-bash-timeout-extension.mjs +4 -5
  23. package/dist/runtime/pi-compaction-recovery.cjs +45 -15
  24. package/dist/runtime/pi-compaction-recovery.mjs +43 -14
  25. package/dist/runtime/pi-extension-api.mjs +1 -0
  26. package/dist/runtime/pi-model-catalog.cjs +7 -8
  27. package/dist/runtime/pi-model-catalog.mjs +7 -8
  28. package/dist/runtime/pi-subagent-injection.mjs +17 -4
  29. package/dist/runtime/runtime-adapters.mjs +26 -43
  30. package/dist/runtime/runtime-host.cjs +21 -43
  31. package/dist/runtime/runtime-host.mjs +22 -44
  32. package/dist/runtime/runtime-model-catalog.cjs +1 -1
  33. package/dist/runtime/runtime-model-catalog.mjs +1 -1
  34. package/dist/runtime/runtime-readiness.cjs +62 -98
  35. package/dist/runtime/runtime-readiness.mjs +60 -97
  36. package/dist/runtime/user-runtime.cjs +7 -24
  37. package/dist/runtime/user-runtime.mjs +7 -23
  38. package/dist/setup/bot-register.mjs +15 -123
  39. package/dist/setup/setup-agent-choice.mjs +48 -74
  40. package/dist/setup/setup-bind.mjs +51 -151
  41. package/dist/setup/setup-binding.mjs +6 -9
  42. package/package.json +3 -2
  43. package/dist/app/pi-auth-cli.mjs +0 -277
  44. package/dist/platform/process-boundary-trace.cjs +0 -108
  45. package/dist/runtime/builtin-pi-assets.cjs +0 -117
  46. package/dist/runtime/builtin-pi-assets.mjs +0 -105
  47. package/dist/runtime/pi-artifact-provenance.cjs +0 -147
  48. package/dist/runtime/pi-distribution-label.cjs +0 -6
  49. package/dist/runtime/pi-distribution-label.mjs +0 -3
  50. package/dist/runtime/pi-inline-extensions.mjs +0 -78
  51. package/dist/runtime/pi-official-auth.cjs +0 -594
  52. package/dist/runtime/pi-official-auth.mjs +0 -544
  53. package/dist/runtime/pi-profile-migration.cjs +0 -764
  54. package/dist/runtime/pi-profile-migration.mjs +0 -719
  55. package/dist/runtime/pi-provider-config.cjs +0 -354
  56. package/dist/runtime/pi-provider-config.mjs +0 -334
  57. package/dist/runtime/pi-provider-login.mjs +0 -240
@@ -1,354 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PI_PROVIDER_PRESETS = exports.MAX_BUILTIN_PI_API_KEY_LENGTH = exports.BUNDLED_PI_VERSION = exports.piDistributionLabel = void 0;
7
- exports.isPiLoopbackHostname = isPiLoopbackHostname;
8
- exports.presetIdForOfficialProvider = presetIdForOfficialProvider;
9
- exports.builtinPiProviderRecoveryNextAction = builtinPiProviderRecoveryNextAction;
10
- exports.validatePiBaseUrl = validatePiBaseUrl;
11
- exports.listProviderModels = listProviderModels;
12
- exports.resolveBuiltinPiProviderSetupSelection = resolveBuiltinPiProviderSetupSelection;
13
- exports.validateBuiltinPiProviderSelection = validateBuiltinPiProviderSelection;
14
- exports.piAgentDirectory = piAgentDirectory;
15
- exports.ownedPiCatalogAgentDir = ownedPiCatalogAgentDir;
16
- exports.piCatalogCommandSpec = piCatalogCommandSpec;
17
- exports.assertBuiltinPiAgentDirectory = assertBuiltinPiAgentDirectory;
18
- exports.configureBuiltinPiProviderModel = configureBuiltinPiProviderModel;
19
- exports.stageBuiltinPiProvider = stageBuiltinPiProvider;
20
- const node_crypto_1 = __importDefault(require("node:crypto"));
21
- const node_fs_1 = __importDefault(require("node:fs"));
22
- const node_path_1 = __importDefault(require("node:path"));
23
- const internal_command_js_1 = require("../app/internal-command.cjs");
24
- const secure_metadata_js_1 = require("../platform/secure-metadata.cjs");
25
- var pi_distribution_label_js_1 = require("./pi-distribution-label.cjs");
26
- Object.defineProperty(exports, "piDistributionLabel", { enumerable: true, get: function () { return pi_distribution_label_js_1.piDistributionLabel; } });
27
- exports.BUNDLED_PI_VERSION = "0.84.2";
28
- /** API Key 字符上限;stdin 必须在读入任意多余输入前按同一上限截断。 */
29
- exports.MAX_BUILTIN_PI_API_KEY_LENGTH = 16_384;
30
- // These values mirror the catalog shipped by the pinned official Pi distribution.
31
- // Keeping the user-facing defaults here makes endpoint changes reviewable in Larkin.
32
- exports.PI_PROVIDER_PRESETS = Object.freeze([
33
- { id: "deepseek", name: "DeepSeek(推荐)", provider: "deepseek", baseUrl: "https://api.deepseek.com", defaultModel: "deepseek-v4-pro", api: "openai-completions" },
34
- { id: "kimi", name: "Kimi / Moonshot(中国)", provider: "moonshotai-cn", baseUrl: "https://api.moonshot.cn/v1", defaultModel: "kimi-k2.6", api: "openai-completions" },
35
- { id: "minimax", name: "MiniMax(中国)", provider: "minimax-cn", baseUrl: "https://api.minimaxi.com/anthropic", defaultModel: "MiniMax-M2.7", api: "anthropic-messages" },
36
- { id: "zhipu", name: "智谱 / BigModel", provider: "zai-coding-cn", baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", defaultModel: "glm-5.2", api: "openai-completions" },
37
- { id: "openai", name: "OpenAI", provider: "openai", baseUrl: "https://api.openai.com/v1", defaultModel: "gpt-5.5", api: "openai-completions" },
38
- { id: "anthropic", name: "Anthropic Claude", provider: "anthropic", baseUrl: "https://api.anthropic.com", defaultModel: "claude-opus-4-8", api: "anthropic-messages" },
39
- { id: "gemini", name: "Google Gemini", provider: "google", baseUrl: "https://generativelanguage.googleapis.com/v1beta", defaultModel: "gemini-3.1-pro-preview", api: "openai-completions" },
40
- { id: "groq", name: "Groq", provider: "groq", baseUrl: "https://api.groq.com/openai/v1", defaultModel: "openai/gpt-oss-120b", api: "openai-completions" },
41
- { id: "cerebras", name: "Cerebras", provider: "cerebras", baseUrl: "https://api.cerebras.ai/v1", defaultModel: "zai-glm-4.7", api: "openai-completions" },
42
- { id: "xai", name: "xAI Grok", provider: "xai", baseUrl: "https://api.x.ai/v1", defaultModel: "grok-4.5", api: "openai-completions" },
43
- { id: "fireworks", name: "Fireworks AI", provider: "fireworks", baseUrl: "https://api.fireworks.ai/inference", defaultModel: "accounts/fireworks/models/kimi-k2p6", api: "openai-completions" },
44
- { id: "together", name: "Together AI", provider: "together", baseUrl: "https://api.together.ai/v1", defaultModel: "moonshotai/Kimi-K2.6", api: "openai-completions" },
45
- { id: "mistral", name: "Mistral", provider: "mistral", baseUrl: "https://api.mistral.ai", defaultModel: "devstral-medium-latest", api: "openai-completions" },
46
- { id: "openrouter", name: "OpenRouter", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", defaultModel: "moonshotai/kimi-k2.6", api: "openai-completions" },
47
- { id: "kimi-coding", name: "Kimi For Coding", provider: "kimi-coding", baseUrl: "https://api.kimi.com/coding", defaultModel: "kimi-for-coding", api: "openai-completions" },
48
- { id: "qwen-cn", name: "通义千问 Token Plan(中国)", provider: "qwen-token-plan-cn", baseUrl: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", defaultModel: "qwen3.7-max", api: "openai-completions" },
49
- { id: "opencode-go", name: "OpenCode Go", provider: "opencode-go", baseUrl: "https://opencode.ai/zen/go/v1", defaultModel: "kimi-k2.6", api: "openai-completions" },
50
- { id: "ant-ling", name: "Ant Ling", provider: "ant-ling", baseUrl: "https://api.ant-ling.com/v1", defaultModel: "Ring-2.6-1T", api: "openai-completions" },
51
- { id: "nvidia", name: "NVIDIA NIM", provider: "nvidia", baseUrl: "https://integrate.api.nvidia.com/v1", defaultModel: "google/gemma-3-12b-it", api: "openai-completions" },
52
- { id: "huggingface", name: "Hugging Face", provider: "huggingface", baseUrl: "https://router.huggingface.co/v1", defaultModel: "MiniMaxAI/MiniMax-M2", api: "openai-completions" },
53
- { id: "minimax-global", name: "MiniMax(全球)", provider: "minimax", baseUrl: "https://api.minimax.io/anthropic", defaultModel: "MiniMax-M2.7", api: "anthropic-messages" },
54
- { id: "moonshotai", name: "Moonshot(全球)", provider: "moonshotai", baseUrl: "https://api.moonshot.ai/v1", defaultModel: "kimi-k2-0711-preview", api: "openai-completions" },
55
- { id: "opencode", name: "OpenCode Zen", provider: "opencode", baseUrl: "https://opencode.ai/zen", defaultModel: "claude-fable-5", api: "openai-completions" },
56
- { id: "qwen-token-plan", name: "通义千问 Token Plan(全球)", provider: "qwen-token-plan", baseUrl: "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1", defaultModel: "qwen3.7-max", api: "openai-completions" },
57
- { id: "vercel-ai-gateway", name: "Vercel AI Gateway", provider: "vercel-ai-gateway", baseUrl: "https://ai-gateway.vercel.sh", defaultModel: "zai/glm-5.1", api: "anthropic-messages" },
58
- { id: "xiaomi", name: "Xiaomi MiMo", provider: "xiaomi", baseUrl: "https://api.xiaomimimo.com/v1", defaultModel: "mimo-v2-flash", api: "openai-completions" },
59
- { id: "xiaomi-token-plan-cn", name: "Xiaomi MiMo Token Plan(中国)", provider: "xiaomi-token-plan-cn", baseUrl: "https://token-plan-cn.xiaomimimo.com/v1", defaultModel: "mimo-v2.5-pro", api: "openai-completions" },
60
- { id: "xiaomi-token-plan-ams", name: "Xiaomi MiMo Token Plan(阿姆斯特丹)", provider: "xiaomi-token-plan-ams", baseUrl: "https://token-plan-ams.xiaomimimo.com/v1", defaultModel: "mimo-v2.5-pro", api: "openai-completions" },
61
- { id: "xiaomi-token-plan-sgp", name: "Xiaomi MiMo Token Plan(新加坡)", provider: "xiaomi-token-plan-sgp", baseUrl: "https://token-plan-sgp.xiaomimimo.com/v1", defaultModel: "mimo-v2.5-pro", api: "openai-completions" },
62
- { id: "zai", name: "ZAI Coding Plan(全球)", provider: "zai", baseUrl: "https://api.z.ai/api/coding/paas/v4", defaultModel: "glm-4.5-air", api: "openai-completions" },
63
- ]);
64
- const APP_ID = /^cli_[A-Za-z0-9]+$/;
65
- const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:@+\/-]{0,255}$/;
66
- /** Node 的 URL.hostname 对 IPv6 是 [::1],不是 ::1。 */
67
- function isPiLoopbackHostname(hostname) {
68
- const host = hostname.trim().replace(/^\[|\]$/g, "").toLowerCase();
69
- return host === "localhost" || host === "127.0.0.1" || host === "::1";
70
- }
71
- /** 官方 provider ID(如 zai-coding-cn)映射到 CLI/Dashboard 接受的 preset ID(如 zhipu)。 */
72
- function presetIdForOfficialProvider(providerId) {
73
- const id = providerId.trim();
74
- if (id === "larkin-custom")
75
- return "custom";
76
- return exports.PI_PROVIDER_PRESETS.find((preset) => preset.provider === id)?.id ?? null;
77
- }
78
- /** Agent 范围的缺凭证恢复路径:只用 preset,不指向 setup / import / 全局 ~/.pi。 */
79
- function builtinPiProviderRecoveryNextAction(options = {}) {
80
- const preset = options.providerId ? presetIdForOfficialProvider(options.providerId) ?? "<preset|custom>" : "<preset|custom>";
81
- const agent = options.agentId && /^cli_[A-Za-z0-9]+$/.test(options.agentId) ? options.agentId : "<App ID>";
82
- return `在 Dashboard 的 Provider Credentials 中为该 Agent 配置 Provider 与 API Key,或运行 larkin pi-auth login ${preset} --agent ${agent}。`;
83
- }
84
- function validatePiBaseUrl(raw) {
85
- const input = raw.trim();
86
- if (!input || /[\0\r\n\t]/.test(input))
87
- throw new Error("Base URL 不能为空或包含控制字符");
88
- let url;
89
- try {
90
- url = new URL(input);
91
- }
92
- catch {
93
- throw new Error("Base URL 不是合法 URL");
94
- }
95
- if (url.username || url.password)
96
- throw new Error("Base URL 不允许包含用户名或凭证");
97
- if (url.search || url.hash)
98
- throw new Error("Base URL 不允许 query 或 fragment");
99
- const local = isPiLoopbackHostname(url.hostname);
100
- if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) {
101
- throw new Error("Base URL 必须使用 https;仅 localhost/loopback 开发端点允许 http");
102
- }
103
- if (/\/(?:chat\/completions|responses|models)\/?$/i.test(url.pathname)) {
104
- throw new Error("Base URL 应填写 API 根路径,不要包含 chat/completions、responses 或 models");
105
- }
106
- return url.toString().replace(/\/$/, "");
107
- }
108
- /**
109
- * 查询 OpenAI 兼容 provider 的 /models 列表(setup 时校验 --model 是否真实可用)。
110
- * 网络超时/不可达返回 null(不阻塞 setup——运行时对账会严格兜底);
111
- * 列表拿到但模型不在其中时抛错。
112
- */
113
- async function listProviderModels(baseUrl, apiKey, fetchImpl = fetch) {
114
- try {
115
- const url = `${validatePiBaseUrl(baseUrl)}/models`;
116
- const response = await fetchImpl(url, {
117
- headers: { Authorization: `Bearer ${apiKey}` },
118
- signal: AbortSignal.timeout(30_000),
119
- });
120
- if (!response.ok)
121
- throw new Error(`provider /models 查询失败:HTTP ${response.status}`);
122
- const payload = await response.json();
123
- if (!payload || !Array.isArray(payload.data))
124
- throw new Error("provider /models 响应格式非法");
125
- return payload.data.map((entry) => String(entry.id ?? "")).filter(Boolean);
126
- }
127
- catch (error) {
128
- if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError"
129
- || /fetch failed|network|ETIMEDOUT|ECONNREFUSED|ConnectionRefused|Unable to connect/i.test(error.message))) {
130
- return null;
131
- }
132
- throw error;
133
- }
134
- }
135
- function resolveBuiltinPiProviderSetupSelection(input) {
136
- if (input.distribution !== "builtin")
137
- throw new Error("provider 凭证只能用于内置 Pi");
138
- const model = input.model.trim();
139
- if (!MODEL_ID.test(model))
140
- throw new Error("模型 ID 格式不安全");
141
- if (input.preset === "custom") {
142
- const provider = "larkin-custom";
143
- return {
144
- ...input,
145
- model: model.startsWith(`${provider}/`) ? model : `${provider}/${model}`,
146
- provider,
147
- baseUrl: validatePiBaseUrl(input.baseUrl || ""),
148
- api: "openai-completions",
149
- };
150
- }
151
- const preset = exports.PI_PROVIDER_PRESETS.find((candidate) => candidate.id === input.preset);
152
- if (!preset)
153
- throw new Error(`未知 Pi provider preset: ${String(input.preset)}`);
154
- return { ...input, model: model.startsWith(`${preset.provider}/`) ? model : `${preset.provider}/${model}`,
155
- provider: preset.provider, baseUrl: preset.baseUrl, api: preset.api };
156
- }
157
- function validateBuiltinPiProviderSelection(input) {
158
- const key = input.apiKey.trim();
159
- if (!key || key.length > exports.MAX_BUILTIN_PI_API_KEY_LENGTH || /[\0\r\n]/.test(key))
160
- throw new Error("API Key 不能为空或包含控制字符");
161
- return { ...resolveBuiltinPiProviderSetupSelection(input), apiKey: key };
162
- }
163
- function piAgentDirectory(configDir, agentId) {
164
- if (!APP_ID.test(agentId))
165
- throw new Error("Pi Agent ID 格式无效");
166
- return node_path_1.default.join(node_path_1.default.resolve(configDir), "providers", "pi", agentId);
167
- }
168
- /** Catalog/CLI/dashboard must use the Agent-owned provider dir, never host ~/.pi. */
169
- function ownedPiCatalogAgentDir(configDir, agentId) {
170
- return piAgentDirectory(configDir, agentId);
171
- }
172
- function piCatalogCommandSpec(distribution, env = process.env) {
173
- if (distribution === "builtin") {
174
- const spec = (0, internal_command_js_1.internalCommandSpec)("pi-rpc", [], env);
175
- return { command: spec.command, commandArgs: spec.args };
176
- }
177
- return {
178
- command: env.LARKIN_PI_COMMAND ?? process.env.LARKIN_PI_COMMAND ?? "pi",
179
- commandArgs: [],
180
- };
181
- }
182
- function assertPrivateDirectory(directory) {
183
- if (secure_metadata_js_1.isWindows) {
184
- // Windows 无 POSIX 权限位:icacls 收紧为「当前用户 + SYSTEM」并回读校验。
185
- (0, secure_metadata_js_1.secureWindowsDirectoryAcl)(directory, { label: "Pi provider 凭证目录" });
186
- return;
187
- }
188
- const stat = node_fs_1.default.lstatSync(directory);
189
- if (!stat.isDirectory() || stat.isSymbolicLink()
190
- || (typeof process.getuid === "function" && stat.uid !== process.getuid())
191
- || (stat.mode & 0o777) !== 0o700) {
192
- throw new Error("Pi provider 凭证目录必须是当前用户拥有、非 symlink、权限精确 0700 的目录");
193
- }
194
- }
195
- function assertBuiltinPiAgentDirectory(directory) {
196
- assertPrivateDirectory(directory);
197
- const authFile = node_path_1.default.join(directory, "auth.json");
198
- const auth = readSnapshot(authFile);
199
- if (!auth)
200
- throw new Error("内置 Pi 尚未配置 API Key");
201
- try {
202
- const value = JSON.parse(auth.toString("utf8"));
203
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length === 0)
204
- throw new Error("empty");
205
- }
206
- catch {
207
- throw new Error("内置 Pi auth.json 格式无效");
208
- }
209
- }
210
- function ensurePrivateDirectory(directory) {
211
- node_fs_1.default.mkdirSync(node_path_1.default.dirname(directory), { recursive: true, mode: 0o700 });
212
- node_fs_1.default.chmodSync(node_path_1.default.dirname(directory), 0o700);
213
- node_fs_1.default.mkdirSync(directory, { recursive: true, mode: 0o700 });
214
- node_fs_1.default.chmodSync(directory, 0o700);
215
- assertPrivateDirectory(directory);
216
- }
217
- function readSnapshot(file) {
218
- try {
219
- const stat = node_fs_1.default.lstatSync(file);
220
- if (!stat.isFile() || stat.isSymbolicLink()
221
- || (typeof process.getuid === "function" && stat.uid !== process.getuid())
222
- || (!secure_metadata_js_1.isWindows && (stat.mode & 0o777) !== 0o600))
223
- throw new Error(`Pi provider 文件不安全: ${node_path_1.default.basename(file)}`);
224
- return node_fs_1.default.readFileSync(file);
225
- }
226
- catch (error) {
227
- if (error.code === "ENOENT")
228
- return null;
229
- throw error;
230
- }
231
- }
232
- function atomicPrivateWrite(file, content) {
233
- const directory = node_path_1.default.dirname(file);
234
- assertPrivateDirectory(directory);
235
- const temporary = node_path_1.default.join(directory, `.${node_path_1.default.basename(file)}.${process.pid}.${node_crypto_1.default.randomUUID()}.tmp`);
236
- try {
237
- node_fs_1.default.writeFileSync(temporary, content, { mode: 0o600, flag: "wx" });
238
- node_fs_1.default.renameSync(temporary, file);
239
- node_fs_1.default.chmodSync(file, 0o600);
240
- }
241
- finally {
242
- try {
243
- node_fs_1.default.unlinkSync(temporary);
244
- }
245
- catch { /* renamed or absent */ }
246
- }
247
- }
248
- function restore(file, snapshot) {
249
- if (snapshot === null) {
250
- try {
251
- node_fs_1.default.unlinkSync(file);
252
- }
253
- catch (error) {
254
- if (error.code !== "ENOENT")
255
- throw error;
256
- }
257
- }
258
- else
259
- atomicPrivateWrite(file, snapshot.toString("utf8"));
260
- }
261
- function configureBuiltinPiProviderModel(configDir, agentId, raw) {
262
- const selection = resolveBuiltinPiProviderSetupSelection(raw);
263
- const directory = piAgentDirectory(configDir, agentId);
264
- ensurePrivateDirectory(directory);
265
- if (selection.preset !== "custom")
266
- return selection;
267
- const modelsFile = node_path_1.default.join(directory, "models.json");
268
- const beforeModels = readSnapshot(modelsFile);
269
- const currentModels = beforeModels ? JSON.parse(beforeModels.toString("utf8")) : {};
270
- if (!currentModels || typeof currentModels !== "object" || Array.isArray(currentModels))
271
- throw new Error("现有 Pi models.json 格式无效");
272
- const currentProviders = currentModels.providers === undefined ? {} : currentModels.providers;
273
- if (!currentProviders || typeof currentProviders !== "object" || Array.isArray(currentProviders))
274
- throw new Error("现有 Pi models.json providers 格式无效");
275
- const modelId = selection.model.slice(`${selection.provider}/`.length);
276
- atomicPrivateWrite(modelsFile, `${JSON.stringify({
277
- ...currentModels,
278
- providers: {
279
- ...currentProviders,
280
- [selection.provider]: {
281
- baseUrl: selection.baseUrl,
282
- api: selection.api,
283
- models: [{
284
- id: modelId, name: modelId, reasoning: false, input: ["text"], contextWindow: 272_000, maxTokens: 16_384,
285
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
286
- }],
287
- },
288
- },
289
- }, null, 2)}\n`);
290
- return selection;
291
- }
292
- function stageBuiltinPiProvider(configDir, agentId, raw) {
293
- const selection = validateBuiltinPiProviderSelection(raw);
294
- const directory = piAgentDirectory(configDir, agentId);
295
- ensurePrivateDirectory(directory);
296
- const authFile = node_path_1.default.join(directory, "auth.json");
297
- const modelsFile = node_path_1.default.join(directory, "models.json");
298
- const beforeAuth = readSnapshot(authFile);
299
- const beforeModels = readSnapshot(modelsFile);
300
- const currentAuth = beforeAuth ? JSON.parse(beforeAuth.toString("utf8")) : {};
301
- if (!currentAuth || typeof currentAuth !== "object" || Array.isArray(currentAuth))
302
- throw new Error("现有 Pi auth.json 格式无效");
303
- const auth = { ...currentAuth, [selection.provider]: { type: "api_key", key: selection.apiKey } };
304
- const currentModels = beforeModels ? JSON.parse(beforeModels.toString("utf8")) : {};
305
- if (!currentModels || typeof currentModels !== "object" || Array.isArray(currentModels))
306
- throw new Error("现有 Pi models.json 格式无效");
307
- const currentProviders = currentModels.providers === undefined ? {} : currentModels.providers;
308
- if (!currentProviders || typeof currentProviders !== "object" || Array.isArray(currentProviders))
309
- throw new Error("现有 Pi models.json providers 格式无效");
310
- const customModels = selection.preset === "custom" ? {
311
- ...currentModels,
312
- providers: {
313
- ...currentProviders,
314
- [selection.provider]: {
315
- baseUrl: selection.baseUrl,
316
- api: selection.api,
317
- models: [{
318
- id: selection.model.slice(`${selection.provider}/`.length),
319
- name: selection.model.slice(`${selection.provider}/`.length),
320
- reasoning: false,
321
- input: ["text"],
322
- contextWindow: 272_000,
323
- maxTokens: 16_384,
324
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
325
- }],
326
- },
327
- },
328
- } : null;
329
- let active = true;
330
- try {
331
- atomicPrivateWrite(authFile, `${JSON.stringify(auth, null, 2)}\n`);
332
- if (customModels)
333
- atomicPrivateWrite(modelsFile, `${JSON.stringify(customModels, null, 2)}\n`);
334
- }
335
- catch (error) {
336
- try {
337
- restore(authFile, beforeAuth);
338
- restore(modelsFile, beforeModels);
339
- }
340
- catch { /* original error remains primary */ }
341
- throw error;
342
- }
343
- return {
344
- directory,
345
- commit() { active = false; },
346
- rollback() {
347
- if (!active)
348
- return;
349
- restore(authFile, beforeAuth);
350
- restore(modelsFile, beforeModels);
351
- active = false;
352
- },
353
- };
354
- }