pockcode 0.0.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.
@@ -0,0 +1,1736 @@
1
+ import { c as readWorkspaceTree, d as readRecordField, f as readStringField, l as HttpError, o as listWorkspaceDirectory, s as readWorkspaceResource, u as readBooleanField } from "./socket.server-7dC-9Fnw.js";
2
+ import { A as listProviderAdapters, B as readNumber, C as createAccount, D as readConnectedAccountLimits, E as listAccounts, F as resolveCodexAccountHome, H as ensureDatabase, I as startCodexMcpOauthLogin, L as updateCodexInstructions, N as readCodexInstructions, P as reloadCodexMcpServerConfig, R as asJsonObject, S as authenticateAccount, T as listAccountModels, U as prisma, V as readString, a as executeMessage, b as updateChat, c as interruptChatRun, f as refreshChat, g as reviewChat, h as respondToServerRequest, i as deleteQueuedChatRun, j as listCodexMcpServerStatuses, k as updateAccount, l as listChats, m as reorderQueuedChatRuns, n as compactChat, o as forkChat, r as createChat, t as archiveChat, u as listMessages, v as steerQueuedChatRun, w as deleteAccount, x as updateQueuedChatRun, z as readBoolean } from "./chats.service-H6m23Fna.js";
3
+ import { a as listMessageSchedules, c as updateMessageSchedule, i as listMessageScheduleRuns, n as createMessageSchedule, r as getMessageSchedule, t as archiveMessageSchedule } from "./message-schedules.service-Be9t4LDg.js";
4
+ import { a as readPluginSetting, c as listPluginRegistrations, d as deleteWorkspaceHistory, f as listWorkspaceHistory, i as restartPlugin, l as readPluginRegistration, n as getPluginStatus, o as readPluginState, p as saveWorkspaceHistory, s as writePluginSetting, t as createPluginContext, u as verifyTelegramBotToken } from "./manager.server-Cru4ZxHo.js";
5
+ import { realpathSync } from "node:fs";
6
+ import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
7
+ import path, { join } from "node:path";
8
+ import { randomUUID } from "node:crypto";
9
+ import { Prisma } from "@prisma/client";
10
+ import { homedir } from "node:os";
11
+ import { execFile, spawn } from "node:child_process";
12
+ import { promisify } from "node:util";
13
+ import { parse, stringify } from "smol-toml";
14
+ //#region app/server/cloudflared.service.ts
15
+ var execFileAsync$1 = promisify(execFile);
16
+ var temporaryTunnelReadyTimeoutMs = 35e3;
17
+ var maxLogLines = 80;
18
+ var tryCloudflareUrlPattern = /https:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)*\.trycloudflare\.com/iu;
19
+ var temporaryTunnels = /* @__PURE__ */ new Map();
20
+ async function readCloudflaredStatus() {
21
+ const installation = await readCloudflaredInstallation();
22
+ if (!installation.installed) return {
23
+ installed: false,
24
+ message: installation.message,
25
+ namedTunnels: [],
26
+ temporaryTunnels: listTemporaryTunnels()
27
+ };
28
+ try {
29
+ return {
30
+ installed: true,
31
+ namedTunnels: await listNamedTunnels(),
32
+ temporaryTunnels: listTemporaryTunnels(),
33
+ version: installation.version
34
+ };
35
+ } catch (error) {
36
+ const namedTunnelsAuthRequired = isMissingOriginCertError(error);
37
+ return {
38
+ installed: true,
39
+ namedTunnels: [],
40
+ namedTunnelsAuthRequired,
41
+ namedTunnelsError: namedTunnelsAuthRequired ? "Named tunnels require a cloudflared origin certificate. Run `cloudflared tunnel login` or set TUNNEL_ORIGIN_CERT, then refresh. Temporary tunnels still work without named-tunnel login." : cleanCommandError(error, "Unable to list cloudflared tunnels."),
42
+ temporaryTunnels: listTemporaryTunnels(),
43
+ version: installation.version
44
+ };
45
+ }
46
+ }
47
+ async function startTemporaryCloudflaredTunnel(originUrl) {
48
+ const normalizedOriginUrl = normalizeOriginUrl(originUrl);
49
+ await ensureCloudflaredInstalled();
50
+ const record = {
51
+ child: null,
52
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
53
+ id: randomUUID(),
54
+ logs: [],
55
+ originUrl: normalizedOriginUrl,
56
+ status: "starting",
57
+ stopRequested: false
58
+ };
59
+ temporaryTunnels.set(record.id, record);
60
+ const child = spawn("cloudflared", [
61
+ "tunnel",
62
+ "--url",
63
+ normalizedOriginUrl
64
+ ], { stdio: [
65
+ "ignore",
66
+ "pipe",
67
+ "pipe"
68
+ ] });
69
+ record.child = child;
70
+ attachTemporaryTunnelListeners(record, child);
71
+ await waitForTemporaryTunnelUrl(record);
72
+ return readCloudflaredStatus();
73
+ }
74
+ async function stopTemporaryCloudflaredTunnel(id) {
75
+ const record = temporaryTunnels.get(id);
76
+ if (!record) throw new HttpError(404, "Temporary tunnel was not found.");
77
+ record.stopRequested = true;
78
+ record.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
79
+ record.status = "stopped";
80
+ appendLog(record, "Stopped by Pockcode.");
81
+ if (record.child && !record.child.killed) {
82
+ record.child.kill("SIGTERM");
83
+ setTimeout(() => {
84
+ if (record.child && !record.child.killed) record.child.kill("SIGKILL");
85
+ }, 2e3).unref();
86
+ } else {
87
+ record.status = "stopped";
88
+ record.child = null;
89
+ }
90
+ return readCloudflaredStatus();
91
+ }
92
+ async function deleteNamedCloudflaredTunnel(identifier) {
93
+ const tunnelIdentifier = normalizeTunnelIdentifier(identifier);
94
+ await ensureCloudflaredInstalled();
95
+ await runCloudflared([
96
+ "tunnel",
97
+ "delete",
98
+ "-f",
99
+ tunnelIdentifier
100
+ ], 6e4);
101
+ return readCloudflaredStatus();
102
+ }
103
+ async function readCloudflaredInstallation() {
104
+ try {
105
+ return {
106
+ installed: true,
107
+ version: parseCloudflaredVersion((await runCloudflared(["--version"], 1e4)).stdout)
108
+ };
109
+ } catch (error) {
110
+ return {
111
+ installed: false,
112
+ message: cleanCommandError(error, "cloudflared is not installed or not available on PATH.")
113
+ };
114
+ }
115
+ }
116
+ async function ensureCloudflaredInstalled() {
117
+ const installation = await readCloudflaredInstallation();
118
+ if (!installation.installed) throw new HttpError(400, installation.message);
119
+ }
120
+ async function listNamedTunnels() {
121
+ const result = await runCloudflared([
122
+ "tunnel",
123
+ "list",
124
+ "--output",
125
+ "json"
126
+ ], 3e4);
127
+ const parsed = JSON.parse(result.stdout.trim() || "[]");
128
+ return (Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.tunnels) ? parsed.tunnels : []).map(readNamedTunnel).filter((tunnel) => Boolean(tunnel));
129
+ }
130
+ function readNamedTunnel(value) {
131
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
132
+ const record = value;
133
+ const id = readRecordString(record, "id") ?? readRecordString(record, "uuid");
134
+ if (!id) return null;
135
+ const name = readRecordString(record, "name") ?? id;
136
+ const createdAt = readRecordString(record, "createdAt") ?? readRecordString(record, "created_at");
137
+ const connections = Array.isArray(record.connections) ? record.connections : [];
138
+ const rawStatus = readRecordString(record, "status");
139
+ const status = rawStatus === "active" || rawStatus === "inactive" ? rawStatus : connections.length ? "active" : "inactive";
140
+ return {
141
+ connectionCount: connections.length,
142
+ createdAt,
143
+ id,
144
+ name,
145
+ status
146
+ };
147
+ }
148
+ function attachTemporaryTunnelListeners(record, child) {
149
+ const handleOutput = (chunk) => {
150
+ const text = chunk.toString("utf8");
151
+ appendLog(record, text);
152
+ const match = text.match(tryCloudflareUrlPattern) ?? record.logs.join("\n").match(tryCloudflareUrlPattern);
153
+ if (match?.[0] && !record.publicUrl) {
154
+ record.publicUrl = match[0];
155
+ record.status = "running";
156
+ }
157
+ };
158
+ child.stdout.on("data", handleOutput);
159
+ child.stderr.on("data", handleOutput);
160
+ child.once("error", (error) => {
161
+ appendLog(record, cleanCommandError(error, "cloudflared failed to start."));
162
+ record.child = null;
163
+ record.exitCode = null;
164
+ record.status = "exited";
165
+ record.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
166
+ });
167
+ child.once("exit", (code, signal) => {
168
+ record.child = null;
169
+ record.exitCode = code;
170
+ record.signal = signal;
171
+ record.status = record.stopRequested ? "stopped" : "exited";
172
+ record.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
173
+ });
174
+ }
175
+ async function waitForTemporaryTunnelUrl(record) {
176
+ const startedAt = Date.now();
177
+ while (Date.now() - startedAt < temporaryTunnelReadyTimeoutMs) {
178
+ if (record.publicUrl) return;
179
+ if (record.status === "exited" || record.status === "stopped") throw new HttpError(400, lastMeaningfulLog(record) ?? "cloudflared exited before creating a temporary tunnel.");
180
+ await delay(100);
181
+ }
182
+ await stopTemporaryCloudflaredTunnel(record.id);
183
+ throw new HttpError(504, "Timed out waiting for cloudflared to publish the temporary tunnel URL.");
184
+ }
185
+ async function runCloudflared(args, timeout) {
186
+ try {
187
+ return await execFileAsync$1("cloudflared", args, {
188
+ maxBuffer: 2 * 1024 * 1024,
189
+ timeout
190
+ });
191
+ } catch (error) {
192
+ throw new HttpError(400, cleanCommandError(error, "cloudflared command failed."));
193
+ }
194
+ }
195
+ function listTemporaryTunnels() {
196
+ return Array.from(temporaryTunnels.values()).map(serializeTemporaryTunnel).sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt));
197
+ }
198
+ function serializeTemporaryTunnel(record) {
199
+ return {
200
+ createdAt: record.createdAt,
201
+ exitCode: record.exitCode,
202
+ id: record.id,
203
+ logs: record.logs.slice(-12),
204
+ originUrl: record.originUrl,
205
+ publicUrl: record.publicUrl,
206
+ signal: record.signal,
207
+ status: record.status,
208
+ stoppedAt: record.stoppedAt
209
+ };
210
+ }
211
+ function appendLog(record, text) {
212
+ const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
213
+ record.logs.push(...lines);
214
+ if (record.logs.length > maxLogLines) record.logs.splice(0, record.logs.length - maxLogLines);
215
+ }
216
+ function lastMeaningfulLog(record) {
217
+ return record.logs.slice().reverse().find((line) => line.trim()) ?? null;
218
+ }
219
+ function normalizeOriginUrl(value) {
220
+ let parsed;
221
+ try {
222
+ parsed = new URL(value.trim());
223
+ } catch {
224
+ throw new HttpError(400, "url must be a valid http or https URL.");
225
+ }
226
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new HttpError(400, "url must use http or https.");
227
+ if (!parsed.hostname) throw new HttpError(400, "url must include a host.");
228
+ return parsed.toString();
229
+ }
230
+ function normalizeTunnelIdentifier(value) {
231
+ const identifier = value.trim();
232
+ if (!identifier) throw new HttpError(400, "Tunnel id is required.");
233
+ if (identifier.length > 200 || /[\r\n]/u.test(identifier)) throw new HttpError(400, "Tunnel id is invalid.");
234
+ return identifier;
235
+ }
236
+ function parseCloudflaredVersion(output) {
237
+ return output.trim().replace(/^cloudflared\s+version\s+/iu, "") || "cloudflared";
238
+ }
239
+ function readRecordString(record, key) {
240
+ const value = record[key];
241
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
242
+ }
243
+ function cleanCommandError(error, fallback) {
244
+ if ((error && typeof error === "object" ? error : {}).code === "ENOENT") return "cloudflared is not installed or not available on PATH.";
245
+ return formatCommandMessage(commandErrorText(error).replace(/^Command failed: cloudflared[^\n]*\n?/iu, "").trim()) || fallback;
246
+ }
247
+ function isMissingOriginCertError(error) {
248
+ const text = commandErrorText(error).toLowerCase();
249
+ return text.includes("origin certificate") || text.includes("origincert") || text.includes("tunnel_origin_cert");
250
+ }
251
+ function commandErrorText(error) {
252
+ const record = error && typeof error === "object" ? error : {};
253
+ const stderr = typeof record.stderr === "string" ? record.stderr : "";
254
+ const stdout = typeof record.stdout === "string" ? record.stdout : "";
255
+ const message = error instanceof Error ? error.message : "";
256
+ return stderr || stdout || message;
257
+ }
258
+ function formatCommandMessage(text) {
259
+ const messages = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).map((line) => {
260
+ try {
261
+ const parsed = JSON.parse(line);
262
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
263
+ const message = parsed.message;
264
+ if (typeof message === "string" && message.trim()) return message.trim();
265
+ }
266
+ } catch {
267
+ return line;
268
+ }
269
+ return line;
270
+ });
271
+ return messages.filter((line, index) => messages.indexOf(line) === index).join("\n");
272
+ }
273
+ function delay(ms) {
274
+ return new Promise((resolve) => setTimeout(resolve, ms));
275
+ }
276
+ //#endregion
277
+ //#region app/server/git.service.ts
278
+ var execFileAsync = promisify(execFile);
279
+ var workspaceRoot = realpathSync(homedir());
280
+ async function readGitStatus(inputPath) {
281
+ const cwd = await resolveWorkspacePath(inputPath);
282
+ if (!await isGitRepository(cwd)) return {
283
+ ahead: 0,
284
+ behind: 0,
285
+ branch: "No repository",
286
+ changes: [],
287
+ commits: [],
288
+ isRepository: false,
289
+ message: "This workspace is not initialized as a Git repository."
290
+ };
291
+ const status = await runGit(cwd, [
292
+ "status",
293
+ "--short",
294
+ "--branch"
295
+ ]);
296
+ const commits = await readGitLog(cwd);
297
+ return {
298
+ ...parseGitStatus(status.stdout),
299
+ commits,
300
+ isRepository: true
301
+ };
302
+ }
303
+ async function initGitRepository(inputPath) {
304
+ const cwd = await resolveWorkspacePath(inputPath);
305
+ await runGit(cwd, ["init"]);
306
+ return readGitStatus(cwd);
307
+ }
308
+ async function stageGitPaths(inputPath, paths) {
309
+ const cwd = await resolveWorkspacePath(inputPath);
310
+ await ensureGitRepository(cwd);
311
+ await runGit(cwd, [
312
+ "add",
313
+ "--",
314
+ ...paths.length ? paths : ["."]
315
+ ]);
316
+ return readGitStatus(cwd);
317
+ }
318
+ async function unstageGitPaths(inputPath, paths) {
319
+ const cwd = await resolveWorkspacePath(inputPath);
320
+ await ensureGitRepository(cwd);
321
+ await runGit(cwd, [
322
+ "restore",
323
+ "--staged",
324
+ "--",
325
+ ...paths.length ? paths : ["."]
326
+ ]);
327
+ return readGitStatus(cwd);
328
+ }
329
+ async function discardGitPaths(inputPath, paths) {
330
+ const cwd = await resolveWorkspacePath(inputPath);
331
+ await ensureGitRepository(cwd);
332
+ const targets = paths.length ? paths : ["."];
333
+ await runGit(cwd, [
334
+ "restore",
335
+ "--staged",
336
+ "--worktree",
337
+ "--",
338
+ ...targets
339
+ ]).catch(() => void 0);
340
+ await runGit(cwd, [
341
+ "clean",
342
+ "-f",
343
+ "--",
344
+ ...targets
345
+ ]).catch(() => void 0);
346
+ return readGitStatus(cwd);
347
+ }
348
+ async function commitGitChanges(inputPath, message) {
349
+ const cwd = await resolveWorkspacePath(inputPath);
350
+ await ensureGitRepository(cwd);
351
+ if (!message.trim()) throw new HttpError(400, "Commit message is required.");
352
+ await runGit(cwd, [
353
+ "commit",
354
+ "-m",
355
+ message.trim()
356
+ ]);
357
+ return readGitStatus(cwd);
358
+ }
359
+ async function pullGitRepository(inputPath) {
360
+ const cwd = await resolveWorkspacePath(inputPath);
361
+ await ensureGitRepository(cwd);
362
+ await runGit(cwd, ["pull", "--ff-only"]);
363
+ return readGitStatus(cwd);
364
+ }
365
+ async function pushGitRepository(inputPath) {
366
+ const cwd = await resolveWorkspacePath(inputPath);
367
+ await ensureGitRepository(cwd);
368
+ await runGit(cwd, ["push"]);
369
+ return readGitStatus(cwd);
370
+ }
371
+ async function ensureGitRepository(cwd) {
372
+ if (!await isGitRepository(cwd)) throw new HttpError(400, "This workspace is not initialized as a Git repository.");
373
+ }
374
+ async function isGitRepository(cwd) {
375
+ try {
376
+ return (await runGit(cwd, ["rev-parse", "--is-inside-work-tree"])).stdout.trim() === "true";
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+ function parseGitStatus(output) {
382
+ const lines = output.split(/\r?\n/).filter(Boolean);
383
+ return {
384
+ ...parseBranchLine(lines[0]?.startsWith("## ") ? lines.shift() ?? "" : ""),
385
+ changes: lines.map(parseStatusLine).filter((change) => Boolean(change))
386
+ };
387
+ }
388
+ function parseBranchLine(line) {
389
+ const [left, tracking = ""] = line.replace(/^##\s+/, "").split("...");
390
+ const branch = left || "HEAD";
391
+ const upstream = tracking.replace(/\s+\[.*\]$/, "") || void 0;
392
+ return {
393
+ ahead: Number.parseInt(tracking.match(/ahead (\d+)/)?.[1] ?? "0", 10),
394
+ behind: Number.parseInt(tracking.match(/behind (\d+)/)?.[1] ?? "0", 10),
395
+ branch,
396
+ upstream
397
+ };
398
+ }
399
+ function parseStatusLine(line) {
400
+ const indexStatus = line[0] ?? " ";
401
+ const workingTreeStatus = line[1] ?? " ";
402
+ const rawPath = line.slice(3);
403
+ if (!rawPath) return null;
404
+ const [originalPath, pathName] = rawPath.includes(" -> ") ? rawPath.split(" -> ", 2) : [void 0, rawPath];
405
+ return {
406
+ indexStatus,
407
+ originalPath,
408
+ path: pathName,
409
+ staged: indexStatus !== " " && indexStatus !== "?",
410
+ status: statusFromCode(indexStatus !== " " && indexStatus !== "?" ? indexStatus : workingTreeStatus),
411
+ workingTreeStatus
412
+ };
413
+ }
414
+ function statusFromCode(code) {
415
+ if (code === "?") return "untracked";
416
+ if (code === "A") return "added";
417
+ if (code === "D") return "deleted";
418
+ if (code === "R") return "renamed";
419
+ return "modified";
420
+ }
421
+ async function readGitLog(cwd) {
422
+ try {
423
+ return (await runGit(cwd, [
424
+ "log",
425
+ "--date-order",
426
+ "--decorate=short",
427
+ "--pretty=format:%h%x09%D%x09%s%x09%an",
428
+ "-n",
429
+ "16"
430
+ ])).stdout.split(/\r?\n/).filter(Boolean).map((line) => {
431
+ const [hash = "", refs = "", subject = "", author = ""] = line.split(" ");
432
+ return {
433
+ author,
434
+ hash,
435
+ refs,
436
+ subject
437
+ };
438
+ });
439
+ } catch {
440
+ return [];
441
+ }
442
+ }
443
+ async function runGit(cwd, args) {
444
+ try {
445
+ return await execFileAsync("git", args, {
446
+ cwd,
447
+ maxBuffer: 1024 * 1024,
448
+ timeout: 12e4
449
+ });
450
+ } catch (error) {
451
+ throw new HttpError(400, (error instanceof Error ? error.message : "Git command failed.").replace(/^Command failed: git [^\n]+\n?/, "").trim() || "Git command failed.");
452
+ }
453
+ }
454
+ async function resolveWorkspacePath(inputPath) {
455
+ const requested = inputPath?.trim();
456
+ if (!requested) throw new HttpError(400, "path is required.");
457
+ const resolvedPath = await resolveRealPath(resolveUserPath(requested));
458
+ if (!isPathWithinWorkspaceRoot(resolvedPath)) throw new HttpError(403, "Path is outside the file browser home.");
459
+ if (!(await stat(resolvedPath)).isDirectory()) throw new HttpError(400, "Path is not a directory.");
460
+ return resolvedPath;
461
+ }
462
+ async function resolveRealPath(inputPath) {
463
+ try {
464
+ return await realpath(path.resolve(inputPath));
465
+ } catch {
466
+ throw new HttpError(400, "Directory path does not exist.");
467
+ }
468
+ }
469
+ function resolveUserPath(inputPath) {
470
+ if (inputPath === "~") return workspaceRoot;
471
+ if (inputPath.startsWith("~/")) return path.join(workspaceRoot, inputPath.slice(2));
472
+ if (path.isAbsolute(inputPath)) return inputPath;
473
+ return path.join(workspaceRoot, inputPath);
474
+ }
475
+ function isPathWithinWorkspaceRoot(inputPath) {
476
+ const relativePath = path.relative(workspaceRoot, inputPath);
477
+ return relativePath === "" || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
478
+ }
479
+ //#endregion
480
+ //#region app/server/mcp.service.ts
481
+ var codexProviderId = "codex";
482
+ var serverNamePattern = /^[A-Za-z0-9_-]+$/;
483
+ var maxStatusErrorLength = 2e3;
484
+ async function listMcpServers() {
485
+ await ensureDatabase();
486
+ return (await prisma.mcpServer.findMany({
487
+ include: { installations: true },
488
+ orderBy: { name: "asc" }
489
+ })).map(serializeMcpServer);
490
+ }
491
+ async function getMcpServer(serverId) {
492
+ return serializeMcpServer(await getMcpServerRecord(serverId));
493
+ }
494
+ async function createMcpServer(dto) {
495
+ await ensureDatabase();
496
+ const draft = readMcpServerDraft(dto, { requireTransport: true });
497
+ const name = draft.name;
498
+ const transport = draft.transport;
499
+ if (!name) throw new HttpError(400, "name is required.");
500
+ if (!transport) throw new HttpError(400, "transport is required.");
501
+ const created = await prisma.mcpServer.create({
502
+ data: {
503
+ adapterSettings: draft.adapterSettings,
504
+ config: draft.config ?? {},
505
+ displayName: draft.displayName ?? null,
506
+ enabled: draft.enabled,
507
+ name,
508
+ required: draft.required,
509
+ startupTimeoutSec: draft.startupTimeoutSec,
510
+ toolPolicy: draft.toolPolicy,
511
+ toolTimeoutSec: draft.toolTimeoutSec,
512
+ transport
513
+ },
514
+ include: { installations: true }
515
+ }).catch((error) => {
516
+ if (isUniqueConstraintError(error)) throw new HttpError(409, "An MCP server with this name already exists.");
517
+ throw error;
518
+ });
519
+ await replaceMcpServerInstallations(created.id, dto.accountIds);
520
+ return getMcpServer(created.id);
521
+ }
522
+ async function updateMcpServer(serverId, dto) {
523
+ await ensureDatabase();
524
+ await getMcpServerRecord(serverId);
525
+ const draft = readMcpServerDraft(dto, { requireTransport: false });
526
+ await prisma.mcpServer.update({
527
+ where: { id: serverId },
528
+ data: {
529
+ adapterSettings: draft.adapterSettings === void 0 ? void 0 : draft.adapterSettings,
530
+ config: draft.config === void 0 ? void 0 : draft.config,
531
+ displayName: draft.displayName,
532
+ enabled: draft.enabled,
533
+ name: draft.name,
534
+ required: draft.required,
535
+ startupTimeoutSec: draft.startupTimeoutSec,
536
+ toolPolicy: draft.toolPolicy === void 0 ? void 0 : draft.toolPolicy,
537
+ toolTimeoutSec: draft.toolTimeoutSec,
538
+ transport: draft.transport
539
+ }
540
+ }).catch((error) => {
541
+ if (isUniqueConstraintError(error)) throw new HttpError(409, "An MCP server with this name already exists.");
542
+ throw error;
543
+ });
544
+ await replaceMcpServerInstallations(serverId, dto.accountIds);
545
+ return getMcpServer(serverId);
546
+ }
547
+ async function deleteMcpServer(serverId) {
548
+ await ensureDatabase();
549
+ const server = await getMcpServerRecord(serverId);
550
+ const accountIds = server.installations.map((installation) => installation.accountId);
551
+ const response = serializeMcpServer(server);
552
+ await prisma.mcpServer.delete({ where: { id: serverId } });
553
+ await Promise.allSettled([...new Set(accountIds)].map((accountId) => syncCodexAccountMcpConfig(accountId)));
554
+ return response;
555
+ }
556
+ async function syncMcpServer(serverId, dto = {}) {
557
+ await ensureDatabase();
558
+ const previousAccountIds = (await getMcpServerRecord(serverId)).installations.map((installation) => installation.accountId);
559
+ await replaceMcpServerInstallations(serverId, dto.accountIds);
560
+ const server = await getMcpServerRecord(serverId);
561
+ const targetAccountIds = dto.accountIds !== void 0 ? uniqueStrings([...previousAccountIds, ...dto.accountIds]) : uniqueStrings(server.installations.map((installation) => installation.accountId));
562
+ const failed = (await Promise.all(targetAccountIds.map((accountId) => syncCodexAccountMcpConfig(accountId)))).find((result) => result.error);
563
+ if (failed) throw new HttpError(502, failed.error ?? "Unable to sync MCP server.");
564
+ return getMcpServer(serverId);
565
+ }
566
+ async function listMcpServerStatuses(accountId) {
567
+ await ensureDatabase();
568
+ const account = await getCodexAccount(accountId);
569
+ const installations = await prisma.mcpServerInstallation.findMany({
570
+ include: { server: true },
571
+ orderBy: { server: { name: "asc" } },
572
+ where: {
573
+ accountId,
574
+ providerId: codexProviderId
575
+ }
576
+ });
577
+ let rawStatuses = [];
578
+ try {
579
+ rawStatuses = await listCodexMcpServerStatuses(account);
580
+ } catch (error) {
581
+ const message = errorMessage(error);
582
+ return {
583
+ accountId,
584
+ data: installations.map((installation) => statusItemFromInstallation(installation, message))
585
+ };
586
+ }
587
+ const statusByName = /* @__PURE__ */ new Map();
588
+ for (const rawStatus of rawStatuses) {
589
+ const name = readString(asJsonObject(rawStatus)?.name);
590
+ if (name) statusByName.set(name, rawStatus);
591
+ }
592
+ const items = installations.map((installation) => {
593
+ const rawStatus = statusByName.get(installation.server.name);
594
+ return rawStatus ? statusItemFromRaw(accountId, installation.server.id, rawStatus, installation.lastError) : statusItemFromInstallation(installation, installation.lastError);
595
+ });
596
+ for (const rawStatus of rawStatuses) {
597
+ const name = readString(asJsonObject(rawStatus)?.name);
598
+ if (name && !installations.some((installation) => installation.server.name === name)) items.push(statusItemFromRaw(accountId, null, rawStatus, null));
599
+ }
600
+ return {
601
+ accountId,
602
+ data: items
603
+ };
604
+ }
605
+ async function startMcpServerOauthLogin(serverId, dto) {
606
+ await ensureDatabase();
607
+ const server = await getMcpServerRecord(serverId);
608
+ const transport = serializeMcpTransport(server);
609
+ if (transport.type !== "streamable_http") throw new HttpError(400, "OAuth login is only available for HTTP MCP servers.");
610
+ const account = await getCodexAccount(dto.accountId);
611
+ if (!await prisma.mcpServerInstallation.findFirst({ where: {
612
+ accountId: account.id,
613
+ providerId: codexProviderId,
614
+ serverId
615
+ } })) throw new HttpError(400, "Install this MCP server to the selected account before starting OAuth login.");
616
+ const sync = await syncCodexAccountMcpConfig(account.id);
617
+ if (sync.error) throw new HttpError(502, sync.error);
618
+ const scopes = dto.scopes?.length ? dto.scopes : transport.scopes;
619
+ return startCodexMcpOauthLogin(account, server.name, scopes);
620
+ }
621
+ async function getMcpServerRecord(serverId) {
622
+ await ensureDatabase();
623
+ const server = await prisma.mcpServer.findUnique({
624
+ include: { installations: true },
625
+ where: { id: serverId }
626
+ });
627
+ if (!server) throw new HttpError(404, "MCP server not found.");
628
+ return server;
629
+ }
630
+ async function getCodexAccount(accountId) {
631
+ const account = await prisma.providerAccount.findUnique({ where: { id: accountId } });
632
+ if (!account) throw new HttpError(404, "Provider account not found.");
633
+ if (account.providerId !== codexProviderId) throw new HttpError(400, "MCP sync currently supports Codex accounts only.");
634
+ return account;
635
+ }
636
+ async function replaceMcpServerInstallations(serverId, accountIds) {
637
+ if (accountIds === void 0) return;
638
+ const uniqueAccountIds = uniqueStrings(accountIds);
639
+ if (!uniqueAccountIds.length) {
640
+ await prisma.mcpServerInstallation.deleteMany({ where: {
641
+ providerId: codexProviderId,
642
+ serverId
643
+ } });
644
+ return;
645
+ }
646
+ const accounts = await prisma.providerAccount.findMany({ where: {
647
+ id: { in: uniqueAccountIds },
648
+ providerId: codexProviderId
649
+ } });
650
+ const foundIds = new Set(accounts.map((account) => account.id));
651
+ const missing = uniqueAccountIds.find((accountId) => !foundIds.has(accountId));
652
+ if (missing) throw new HttpError(400, `Codex account ${missing} was not found.`);
653
+ await prisma.mcpServerInstallation.deleteMany({ where: {
654
+ accountId: { notIn: uniqueAccountIds },
655
+ providerId: codexProviderId,
656
+ serverId
657
+ } });
658
+ await Promise.all(uniqueAccountIds.map((accountId) => prisma.mcpServerInstallation.upsert({
659
+ create: {
660
+ accountId,
661
+ providerId: codexProviderId,
662
+ serverId
663
+ },
664
+ update: { enabled: true },
665
+ where: { serverId_providerId_accountId: {
666
+ accountId,
667
+ providerId: codexProviderId,
668
+ serverId
669
+ } }
670
+ })));
671
+ }
672
+ async function syncCodexAccountMcpConfig(accountId) {
673
+ const account = await getCodexAccount(accountId);
674
+ const installations = await prisma.mcpServerInstallation.findMany({
675
+ include: { server: true },
676
+ orderBy: { server: { name: "asc" } },
677
+ where: {
678
+ accountId,
679
+ providerId: codexProviderId
680
+ }
681
+ });
682
+ const startedAt = /* @__PURE__ */ new Date();
683
+ try {
684
+ await writeCodexMcpConfig(account, installations);
685
+ } catch (error) {
686
+ const message = errorMessage(error);
687
+ await markInstallationsSynced(accountId, startedAt, "ERROR", message);
688
+ return {
689
+ accountId,
690
+ error: message,
691
+ status: "ERROR"
692
+ };
693
+ }
694
+ try {
695
+ await reloadCodexMcpServerConfig(account);
696
+ } catch (error) {
697
+ const message = errorMessage(error);
698
+ await markInstallationsSynced(accountId, startedAt, "ERROR", message);
699
+ return {
700
+ accountId,
701
+ error: message,
702
+ status: "ERROR"
703
+ };
704
+ }
705
+ await markInstallationsSynced(accountId, startedAt, "SYNCED", null);
706
+ return {
707
+ accountId,
708
+ error: null,
709
+ status: "SYNCED"
710
+ };
711
+ }
712
+ async function markInstallationsSynced(accountId, syncedAt, status, error) {
713
+ await prisma.mcpServerInstallation.updateMany({
714
+ data: {
715
+ lastError: error ? truncateError(error) : null,
716
+ lastStatus: status,
717
+ lastSyncAt: syncedAt
718
+ },
719
+ where: {
720
+ accountId,
721
+ providerId: codexProviderId
722
+ }
723
+ });
724
+ }
725
+ async function writeCodexMcpConfig(account, installations) {
726
+ const codexHome = resolveCodexAccountHome(account);
727
+ await mkdir(codexHome, {
728
+ mode: 448,
729
+ recursive: true
730
+ });
731
+ const configPath = join(codexHome, "config.toml");
732
+ const currentConfig = await readCodexTomlConfig(configPath);
733
+ const mcpServers = {};
734
+ for (const installation of installations) mcpServers[installation.server.name] = codexConfigForServer(installation.server, installation);
735
+ if (Object.keys(mcpServers).length) currentConfig.mcp_servers = mcpServers;
736
+ else delete currentConfig.mcp_servers;
737
+ await writeFile(configPath, stringify(currentConfig), "utf8");
738
+ }
739
+ async function readCodexTomlConfig(configPath) {
740
+ const source = await readFile(configPath, "utf8").catch((error) => {
741
+ if (error.code === "ENOENT") return "";
742
+ throw error;
743
+ });
744
+ if (!source.trim()) return {};
745
+ const parsed = parse(source);
746
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Codex config.toml must contain a TOML table.");
747
+ return parsed;
748
+ }
749
+ function codexConfigForServer(server, installation) {
750
+ const transport = serializeMcpTransport(server);
751
+ const toolPolicy = serializeToolPolicy(server.toolPolicy);
752
+ const entry = {
753
+ enabled: server.enabled && installation.enabled,
754
+ required: server.required
755
+ };
756
+ if (transport.type === "stdio") {
757
+ entry.command = transport.command;
758
+ if (transport.args.length) entry.args = transport.args;
759
+ if (Object.keys(transport.env).length) entry.env = transport.env;
760
+ if (transport.envVars.length) entry.env_vars = transport.envVars.map(envVarName);
761
+ if (transport.cwd) entry.cwd = transport.cwd;
762
+ } else {
763
+ entry.url = transport.url;
764
+ if (transport.bearerTokenEnvVar) entry.bearer_token_env_var = transport.bearerTokenEnvVar;
765
+ if (Object.keys(transport.httpHeaders).length) entry.http_headers = transport.httpHeaders;
766
+ if (Object.keys(transport.envHttpHeaders).length) entry.env_http_headers = transport.envHttpHeaders;
767
+ if (transport.oauthClientId) entry.oauth = { client_id: transport.oauthClientId };
768
+ if (transport.oauthResource) entry.oauth_resource = transport.oauthResource;
769
+ if (transport.scopes.length) entry.scopes = transport.scopes;
770
+ }
771
+ if (server.startupTimeoutSec !== null) entry.startup_timeout_sec = server.startupTimeoutSec;
772
+ if (server.toolTimeoutSec !== null) entry.tool_timeout_sec = server.toolTimeoutSec;
773
+ if (toolPolicy.defaultToolsApprovalMode) entry.default_tools_approval_mode = toolPolicy.defaultToolsApprovalMode;
774
+ if (toolPolicy.enabledTools?.length) entry.enabled_tools = toolPolicy.enabledTools;
775
+ if (toolPolicy.disabledTools?.length) entry.disabled_tools = toolPolicy.disabledTools;
776
+ if (toolPolicy.tools && Object.keys(toolPolicy.tools).length) {
777
+ const tools = {};
778
+ for (const [toolName, override] of Object.entries(toolPolicy.tools)) tools[toolName] = override.approvalMode ? { approval_mode: override.approvalMode } : {};
779
+ entry.tools = tools;
780
+ }
781
+ return entry;
782
+ }
783
+ function readMcpServerDraft(dto, options) {
784
+ const transport = readTransportConfig(dto.transport, options.requireTransport);
785
+ const split = transport ? splitTransportConfig(transport) : null;
786
+ return {
787
+ adapterSettings: dto.adapterSettings === void 0 ? void 0 : readJsonObject(dto.adapterSettings, "adapterSettings"),
788
+ config: split?.config,
789
+ displayName: dto.displayName === void 0 ? void 0 : readOptionalString(dto.displayName, "displayName", 160),
790
+ enabled: dto.enabled === void 0 ? void 0 : readBooleanValue(dto.enabled, "enabled"),
791
+ name: dto.name === void 0 ? void 0 : readMcpServerName(dto.name),
792
+ required: dto.required === void 0 ? void 0 : readBooleanValue(dto.required, "required"),
793
+ startupTimeoutSec: dto.startupTimeoutSec === void 0 ? void 0 : readOptionalPositiveNumber(dto.startupTimeoutSec, "startupTimeoutSec"),
794
+ toolPolicy: dto.toolPolicy === void 0 ? void 0 : readToolPolicy(dto.toolPolicy),
795
+ toolTimeoutSec: dto.toolTimeoutSec === void 0 ? void 0 : readOptionalPositiveNumber(dto.toolTimeoutSec, "toolTimeoutSec"),
796
+ transport: split?.transport
797
+ };
798
+ }
799
+ function readTransportConfig(value, required) {
800
+ if (value === void 0) {
801
+ if (required) throw new HttpError(400, "transport is required.");
802
+ return;
803
+ }
804
+ const record = readObject(value, "transport");
805
+ const rawType = readString(record.type);
806
+ const type = rawType === "http" ? "streamable_http" : rawType;
807
+ if (type === "stdio") return {
808
+ args: readStringArray(record.args, "transport.args"),
809
+ command: readRequiredString(record.command, "transport.command", 500),
810
+ cwd: readOptionalString(record.cwd, "transport.cwd", 1e3),
811
+ env: readStringRecord(record.env, "transport.env"),
812
+ envVars: readEnvVarRefs(record.envVars ?? record.env_vars),
813
+ type
814
+ };
815
+ if (type === "streamable_http") return {
816
+ bearerTokenEnvVar: readOptionalString(record.bearerTokenEnvVar ?? record.bearer_token_env_var, "transport.bearerTokenEnvVar", 200),
817
+ envHttpHeaders: readStringRecord(record.envHttpHeaders ?? record.env_http_headers, "transport.envHttpHeaders"),
818
+ httpHeaders: readStringRecord(record.httpHeaders ?? record.http_headers, "transport.httpHeaders"),
819
+ oauthClientId: readOptionalString(record.oauthClientId ?? asJsonObject(record.oauth)?.client_id, "transport.oauthClientId", 500),
820
+ oauthResource: readOptionalString(record.oauthResource ?? record.oauth_resource, "transport.oauthResource", 1e3),
821
+ scopes: readStringArray(record.scopes, "transport.scopes"),
822
+ type,
823
+ url: readHttpUrl(record.url, "transport.url")
824
+ };
825
+ throw new HttpError(400, "transport.type must be stdio or streamable_http.");
826
+ }
827
+ function splitTransportConfig(transport) {
828
+ const { type, ...config } = transport;
829
+ return {
830
+ config,
831
+ transport: type
832
+ };
833
+ }
834
+ function serializeMcpServer(server) {
835
+ return {
836
+ adapterSettings: serializeJsonObject(server.adapterSettings),
837
+ createdAt: server.createdAt.toISOString(),
838
+ displayName: server.displayName,
839
+ enabled: server.enabled,
840
+ id: server.id,
841
+ installations: server.installations.map(serializeMcpInstallation),
842
+ name: server.name,
843
+ required: server.required,
844
+ startupTimeoutSec: server.startupTimeoutSec,
845
+ toolPolicy: serializeToolPolicy(server.toolPolicy),
846
+ toolTimeoutSec: server.toolTimeoutSec,
847
+ transport: serializeMcpTransport(server),
848
+ updatedAt: server.updatedAt.toISOString()
849
+ };
850
+ }
851
+ function serializeMcpInstallation(installation) {
852
+ return {
853
+ accountId: installation.accountId,
854
+ createdAt: installation.createdAt.toISOString(),
855
+ enabled: installation.enabled,
856
+ id: installation.id,
857
+ lastError: installation.lastError,
858
+ lastStatus: installation.lastStatus,
859
+ lastSyncAt: installation.lastSyncAt?.toISOString() ?? null,
860
+ providerId: installation.providerId,
861
+ serverId: installation.serverId,
862
+ updatedAt: installation.updatedAt.toISOString()
863
+ };
864
+ }
865
+ function serializeMcpTransport(server) {
866
+ const config = serializeJsonObject(server.config);
867
+ if (server.transport === "stdio") return readTransportConfig({
868
+ ...config,
869
+ type: "stdio"
870
+ }, true);
871
+ return readTransportConfig({
872
+ ...config,
873
+ type: "streamable_http"
874
+ }, true);
875
+ }
876
+ function readToolPolicy(value) {
877
+ const record = readJsonObject(value, "toolPolicy");
878
+ return {
879
+ defaultToolsApprovalMode: readApprovalMode(record.defaultToolsApprovalMode ?? record.default_tools_approval_mode, "toolPolicy.defaultToolsApprovalMode"),
880
+ disabledTools: record.disabledTools === void 0 && record.disabled_tools === void 0 ? void 0 : readStringArray(record.disabledTools ?? record.disabled_tools, "toolPolicy.disabledTools"),
881
+ enabledTools: record.enabledTools === void 0 && record.enabled_tools === void 0 ? void 0 : readStringArray(record.enabledTools ?? record.enabled_tools, "toolPolicy.enabledTools"),
882
+ tools: readToolOverrides(record.tools)
883
+ };
884
+ }
885
+ function serializeToolPolicy(value) {
886
+ return readToolPolicy(serializeJsonObject(value));
887
+ }
888
+ function readToolOverrides(value) {
889
+ if (value === void 0) return;
890
+ const record = readObject(value, "toolPolicy.tools");
891
+ const tools = {};
892
+ for (const [toolName, override] of Object.entries(record)) {
893
+ if (!toolName.trim()) throw new HttpError(400, "Tool override names cannot be empty.");
894
+ const overrideRecord = readObject(override, `toolPolicy.tools.${toolName}`);
895
+ tools[toolName] = { approvalMode: readApprovalMode(overrideRecord.approvalMode ?? overrideRecord.approval_mode, `toolPolicy.tools.${toolName}.approvalMode`) };
896
+ }
897
+ return tools;
898
+ }
899
+ function statusItemFromInstallation(installation, error) {
900
+ return {
901
+ accountId: installation.accountId,
902
+ authStatus: "unknown",
903
+ error: error ?? null,
904
+ lastError: installation.lastError,
905
+ name: installation.server.name,
906
+ resourceCount: 0,
907
+ resourceTemplateCount: 0,
908
+ serverId: installation.serverId,
909
+ serverInfo: null,
910
+ toolCount: 0,
911
+ tools: []
912
+ };
913
+ }
914
+ function statusItemFromRaw(accountId, serverId, rawStatus, lastError) {
915
+ const record = asJsonObject(rawStatus) ?? {};
916
+ const tools = readStatusNames(record.tools);
917
+ const resources = readStatusNames(record.resources);
918
+ const resourceTemplates = readStatusNames(record.resourceTemplates ?? record.resource_templates);
919
+ return {
920
+ accountId,
921
+ authStatus: readAuthStatus(record.authStatus ?? record.auth_status),
922
+ error: readString(record.error ?? record.startupError ?? record.startup_error) ?? null,
923
+ lastError: lastError ?? null,
924
+ name: readString(record.name) ?? "unknown",
925
+ raw: isJsonSerializable(rawStatus) ? rawStatus : void 0,
926
+ resourceCount: resources.length,
927
+ resourceTemplateCount: resourceTemplates.length,
928
+ serverId,
929
+ serverInfo: asJsonObject(record.serverInfo ?? record.server_info) ?? null,
930
+ toolCount: tools.length,
931
+ tools
932
+ };
933
+ }
934
+ function readStatusNames(value) {
935
+ if (Array.isArray(value)) return value.map((item) => typeof item === "string" ? item : readString(asJsonObject(item)?.name)).filter((name) => Boolean(name));
936
+ const record = asJsonObject(value);
937
+ return record ? Object.keys(record) : [];
938
+ }
939
+ function readAuthStatus(value) {
940
+ if (value === "unsupported" || value === "notLoggedIn" || value === "bearerToken" || value === "oAuth") return value;
941
+ if (value === "not_logged_in") return "notLoggedIn";
942
+ if (value === "bearer_token") return "bearerToken";
943
+ if (value === "oauth" || value === "o_auth") return "oAuth";
944
+ return "unknown";
945
+ }
946
+ function readMcpServerName(value) {
947
+ const name = readRequiredString(value, "name", 80);
948
+ if (!serverNamePattern.test(name)) throw new HttpError(400, "MCP server names can only contain letters, numbers, -, and _.");
949
+ return name;
950
+ }
951
+ function readHttpUrl(value, field) {
952
+ const url = readRequiredString(value, field, 2e3);
953
+ let parsed;
954
+ try {
955
+ parsed = new URL(url);
956
+ } catch {
957
+ throw new HttpError(400, `${field} must be a valid URL.`);
958
+ }
959
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new HttpError(400, `${field} must use http or https.`);
960
+ return url;
961
+ }
962
+ function readObject(value, field) {
963
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new HttpError(400, `${field} must be an object.`);
964
+ return value;
965
+ }
966
+ function readJsonObject(value, field) {
967
+ return readObject(value, field);
968
+ }
969
+ function readRequiredString(value, field, maxLength) {
970
+ if (typeof value !== "string" || !value.trim()) throw new HttpError(400, `${field} is required.`);
971
+ const trimmed = value.trim();
972
+ if (trimmed.length > maxLength) throw new HttpError(400, `${field} must be ${maxLength} characters or fewer.`);
973
+ return trimmed;
974
+ }
975
+ function readOptionalString(value, field, maxLength) {
976
+ if (value === void 0 || value === null || value === "") return null;
977
+ return readRequiredString(value, field, maxLength) || null;
978
+ }
979
+ function readBooleanValue(value, field) {
980
+ const booleanValue = readBoolean(value);
981
+ if (booleanValue === void 0) throw new HttpError(400, `${field} must be a boolean.`);
982
+ return booleanValue;
983
+ }
984
+ function readOptionalPositiveNumber(value, field) {
985
+ if (value === null || value === "") return null;
986
+ const numberValue = readNumber(value);
987
+ if (numberValue === void 0 || numberValue < 0) throw new HttpError(400, `${field} must be a positive number.`);
988
+ return numberValue;
989
+ }
990
+ function readStringArray(value, field) {
991
+ if (value === void 0 || value === null) return [];
992
+ if (!Array.isArray(value)) throw new HttpError(400, `${field} must be an array.`);
993
+ return value.map((item, index) => readRequiredString(item, `${field}[${index}]`, 500)).filter((item, index, values) => values.indexOf(item) === index);
994
+ }
995
+ function readStringRecord(value, field) {
996
+ if (value === void 0 || value === null) return {};
997
+ const record = readObject(value, field);
998
+ return Object.fromEntries(Object.entries(record).map(([key, item]) => [readRequiredString(key, `${field} key`, 200), readRequiredString(item, `${field}.${key}`, 2e3)]));
999
+ }
1000
+ function readEnvVarRefs(value) {
1001
+ if (value === void 0 || value === null) return [];
1002
+ if (!Array.isArray(value)) throw new HttpError(400, "transport.envVars must be an array.");
1003
+ return value.map((item, index) => {
1004
+ if (typeof item === "string") return readRequiredString(item, `transport.envVars[${index}]`, 200);
1005
+ const record = readObject(item, `transport.envVars[${index}]`);
1006
+ const name = readRequiredString(record.name, `transport.envVars[${index}].name`, 200);
1007
+ const source = record.source === "local" || record.source === "remote" ? record.source : void 0;
1008
+ return source ? {
1009
+ name,
1010
+ source
1011
+ } : { name };
1012
+ });
1013
+ }
1014
+ function envVarName(value) {
1015
+ return typeof value === "string" ? value : value.name;
1016
+ }
1017
+ function readApprovalMode(value, field) {
1018
+ if (value === void 0 || value === null || value === "") return null;
1019
+ if (value === "auto" || value === "prompt" || value === "approve") return value;
1020
+ throw new HttpError(400, `${field} must be auto, prompt, or approve.`);
1021
+ }
1022
+ function serializeJsonObject(value) {
1023
+ return asJsonObject(value) ?? {};
1024
+ }
1025
+ function uniqueStrings(values) {
1026
+ return values.map((value) => value.trim()).filter((value, index, next) => Boolean(value) && next.indexOf(value) === index);
1027
+ }
1028
+ function isUniqueConstraintError(error) {
1029
+ return error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2002";
1030
+ }
1031
+ function errorMessage(error) {
1032
+ return error instanceof Error ? error.message : "Request failed.";
1033
+ }
1034
+ function truncateError(error) {
1035
+ return error.length > maxStatusErrorLength ? `${error.slice(0, maxStatusErrorLength - 3)}...` : error;
1036
+ }
1037
+ function isJsonSerializable(value) {
1038
+ try {
1039
+ JSON.stringify(value);
1040
+ return true;
1041
+ } catch {
1042
+ return false;
1043
+ }
1044
+ }
1045
+ //#endregion
1046
+ //#region app/server/providers/types.server.ts
1047
+ function serializeProviderDefinition(definition) {
1048
+ return definition;
1049
+ }
1050
+ //#endregion
1051
+ //#region app/server/providers.service.ts
1052
+ async function listProviders() {
1053
+ await ensureDatabase();
1054
+ return listProviderAdapters().map((adapter) => serializeProviderDefinition(adapter.definition));
1055
+ }
1056
+ //#endregion
1057
+ //#region app/server/plugins.service.ts
1058
+ async function listPlugins() {
1059
+ return Promise.all(listPluginRegistrations().map((registration) => serializePlugin(registration.definition.id)));
1060
+ }
1061
+ async function updatePlugin(pluginId, dto) {
1062
+ const registration = readKnownPlugin(pluginId);
1063
+ const current = await readPluginSetting(pluginId, registration.definition.defaultSettings);
1064
+ const settings = dto.settings === void 0 ? current.settings : filterSettings(dto.settings, registration.definition.settingsFields.map((field) => field.key), current.settings);
1065
+ const secrets = dto.secrets === void 0 ? current.secrets : filterSecrets(dto.secrets, registration.definition.secretFields.map((field) => field.key), current.secrets);
1066
+ const enabled = dto.enabled ?? current.enabled;
1067
+ if (pluginId === "telegram" && enabled) await verifyTelegramBotToken(readSecret(secrets, "botToken"));
1068
+ await writePluginSetting({
1069
+ enabled,
1070
+ pluginId,
1071
+ secrets,
1072
+ settings
1073
+ });
1074
+ await restartPlugin(pluginId);
1075
+ return serializePlugin(pluginId);
1076
+ }
1077
+ async function runPluginAction(pluginId, action) {
1078
+ const registration = readKnownPlugin(pluginId);
1079
+ const handler = registration.actions?.[action];
1080
+ if (!handler) throw new HttpError(404, "Plugin action not found.");
1081
+ const result = await handler(createPluginContext(pluginId, await readPluginSetting(pluginId, registration.definition.defaultSettings)));
1082
+ await restartPlugin(pluginId);
1083
+ return {
1084
+ message: result?.message,
1085
+ plugin: await serializePlugin(pluginId)
1086
+ };
1087
+ }
1088
+ async function serializePlugin(pluginId) {
1089
+ const registration = readKnownPlugin(pluginId);
1090
+ const setting = await readPluginSetting(pluginId, registration.definition.defaultSettings);
1091
+ const state = await readPluginState(pluginId);
1092
+ return {
1093
+ description: registration.definition.description,
1094
+ enabled: setting.enabled,
1095
+ icon: registration.definition.icon,
1096
+ id: registration.definition.id,
1097
+ label: registration.definition.label,
1098
+ secretConfigured: Object.fromEntries(registration.definition.secretFields.map((field) => [field.key, Boolean(readSecret(setting.secrets, field.key))])),
1099
+ secrets: setting.secrets,
1100
+ secretFields: registration.definition.secretFields,
1101
+ settings: setting.settings,
1102
+ settingsFields: registration.definition.settingsFields,
1103
+ stateSummary: registration.summarizeState?.(state) ?? {},
1104
+ status: setting.enabled ? getPluginStatus(pluginId) : {
1105
+ state: "disabled",
1106
+ message: null,
1107
+ updatedAt: null
1108
+ }
1109
+ };
1110
+ }
1111
+ function readKnownPlugin(pluginId) {
1112
+ try {
1113
+ return readPluginRegistration(pluginId);
1114
+ } catch {
1115
+ throw new HttpError(404, "Plugin not found.");
1116
+ }
1117
+ }
1118
+ function filterSettings(value, allowedKeys, current) {
1119
+ const next = { ...current };
1120
+ for (const key of allowedKeys) if (value[key] !== void 0) next[key] = value[key];
1121
+ return next;
1122
+ }
1123
+ function filterSecrets(value, allowedKeys, current) {
1124
+ const next = { ...current };
1125
+ for (const key of allowedKeys) {
1126
+ const secret = value[key];
1127
+ if (typeof secret === "string" && secret.trim()) next[key] = secret.trim();
1128
+ if (secret === null) delete next[key];
1129
+ }
1130
+ return next;
1131
+ }
1132
+ function readSecret(secrets, key) {
1133
+ const value = secrets[key];
1134
+ return typeof value === "string" ? value : "";
1135
+ }
1136
+ //#endregion
1137
+ //#region app/server/api.server.ts
1138
+ function installApiServer(middlewares) {
1139
+ middlewares.use((req, res, next) => {
1140
+ const url = new URL(req.url ?? "/", "http://localhost");
1141
+ if (!url.pathname.startsWith("/api/")) {
1142
+ next();
1143
+ return;
1144
+ }
1145
+ handleApiRequest(req, res, url).catch((error) => {
1146
+ sendRouteError(res, error);
1147
+ });
1148
+ });
1149
+ }
1150
+ async function handleApiRequest(req, res, url) {
1151
+ const method = req.method ?? "GET";
1152
+ if (url.pathname === "/api/providers") {
1153
+ requireMethod(method, ["GET"]);
1154
+ sendJson(res, await listProviders());
1155
+ return;
1156
+ }
1157
+ if (url.pathname === "/api/plugins") {
1158
+ requireMethod(method, ["GET"]);
1159
+ sendJson(res, await listPlugins());
1160
+ return;
1161
+ }
1162
+ const pluginActionMatch = url.pathname.match(/^\/api\/plugins\/([^/]+)\/actions\/([^/]+)$/);
1163
+ if (pluginActionMatch) {
1164
+ requireMethod(method, ["POST"]);
1165
+ sendJson(res, await runPluginAction(decodeURIComponent(pluginActionMatch[1]), decodeURIComponent(pluginActionMatch[2])));
1166
+ return;
1167
+ }
1168
+ const pluginMatch = url.pathname.match(/^\/api\/plugins\/([^/]+)$/);
1169
+ if (pluginMatch) {
1170
+ requireMethod(method, ["PATCH"]);
1171
+ sendJson(res, await updatePlugin(decodeURIComponent(pluginMatch[1]), readPluginSettingsUpdateRequest(await readNodeJsonBody(req))));
1172
+ return;
1173
+ }
1174
+ if (url.pathname === "/api/providers/codex/instructions") {
1175
+ requireMethod(method, ["GET", "PUT"]);
1176
+ if (method === "PUT") {
1177
+ sendJson(res, await updateCodexInstructions(readCodexInstructionsRequest(await readNodeJsonBody(req)).instructions));
1178
+ return;
1179
+ }
1180
+ sendJson(res, await readCodexInstructions());
1181
+ return;
1182
+ }
1183
+ if (url.pathname === "/api/mcp-servers") {
1184
+ requireMethod(method, ["GET", "POST"]);
1185
+ if (method === "POST") {
1186
+ sendJson(res, await createMcpServer(await readNodeJsonBody(req)), 201);
1187
+ return;
1188
+ }
1189
+ sendJson(res, await listMcpServers());
1190
+ return;
1191
+ }
1192
+ if (url.pathname === "/api/mcp-servers/status") {
1193
+ requireMethod(method, ["GET"]);
1194
+ sendJson(res, await listMcpServerStatuses(readStringField(url.searchParams.get("accountId"), "accountId", { required: true })));
1195
+ return;
1196
+ }
1197
+ const mcpServerSyncMatch = url.pathname.match(/^\/api\/mcp-servers\/([^/]+)\/sync$/);
1198
+ if (mcpServerSyncMatch) {
1199
+ requireMethod(method, ["POST"]);
1200
+ sendJson(res, await syncMcpServer(decodeURIComponent(mcpServerSyncMatch[1]), await readNodeJsonBody(req)));
1201
+ return;
1202
+ }
1203
+ const mcpServerOauthLoginMatch = url.pathname.match(/^\/api\/mcp-servers\/([^/]+)\/oauth-login$/);
1204
+ if (mcpServerOauthLoginMatch) {
1205
+ requireMethod(method, ["POST"]);
1206
+ sendJson(res, await startMcpServerOauthLogin(decodeURIComponent(mcpServerOauthLoginMatch[1]), await readNodeJsonBody(req)));
1207
+ return;
1208
+ }
1209
+ const mcpServerMatch = url.pathname.match(/^\/api\/mcp-servers\/([^/]+)$/);
1210
+ if (mcpServerMatch) {
1211
+ const serverId = decodeURIComponent(mcpServerMatch[1]);
1212
+ requireMethod(method, [
1213
+ "DELETE",
1214
+ "GET",
1215
+ "PATCH"
1216
+ ]);
1217
+ if (method === "DELETE") {
1218
+ sendJson(res, await deleteMcpServer(serverId));
1219
+ return;
1220
+ }
1221
+ if (method === "PATCH") {
1222
+ sendJson(res, await updateMcpServer(serverId, await readNodeJsonBody(req)));
1223
+ return;
1224
+ }
1225
+ sendJson(res, await getMcpServer(serverId));
1226
+ return;
1227
+ }
1228
+ if (url.pathname === "/api/provider-accounts") {
1229
+ requireMethod(method, ["GET", "POST"]);
1230
+ if (method === "POST") {
1231
+ sendJson(res, await createAccount(readCreateAccountRequest(await readNodeJsonBody(req))), 201);
1232
+ return;
1233
+ }
1234
+ sendJson(res, await listAccounts());
1235
+ return;
1236
+ }
1237
+ if (url.pathname === "/api/provider-accounts/limits") {
1238
+ requireMethod(method, ["GET"]);
1239
+ sendJson(res, await readConnectedAccountLimits());
1240
+ return;
1241
+ }
1242
+ if (url.pathname === "/api/chats") {
1243
+ requireMethod(method, ["GET", "POST"]);
1244
+ if (method === "POST") {
1245
+ sendJson(res, await createChat(readCreateChatRequest(await readNodeJsonBody(req))), 201);
1246
+ return;
1247
+ }
1248
+ sendJson(res, await listChats(url.searchParams.get("workingDirectory")));
1249
+ return;
1250
+ }
1251
+ if (url.pathname === "/api/schedules") {
1252
+ requireMethod(method, ["GET", "POST"]);
1253
+ if (method === "POST") {
1254
+ sendJson(res, await createMessageSchedule(readCreateMessageScheduleRequest(await readNodeJsonBody(req))), 201);
1255
+ return;
1256
+ }
1257
+ sendJson(res, await listMessageSchedules(url.searchParams.get("workingDirectory")));
1258
+ return;
1259
+ }
1260
+ const scheduleRunsMatch = url.pathname.match(/^\/api\/schedules\/([^/]+)\/runs$/);
1261
+ if (scheduleRunsMatch) {
1262
+ requireMethod(method, ["GET"]);
1263
+ sendJson(res, await listMessageScheduleRuns(decodeURIComponent(scheduleRunsMatch[1])));
1264
+ return;
1265
+ }
1266
+ const scheduleMatch = url.pathname.match(/^\/api\/schedules\/([^/]+)$/);
1267
+ if (scheduleMatch) {
1268
+ const scheduleId = decodeURIComponent(scheduleMatch[1]);
1269
+ requireMethod(method, [
1270
+ "DELETE",
1271
+ "GET",
1272
+ "PATCH"
1273
+ ]);
1274
+ if (method === "DELETE") {
1275
+ sendJson(res, await archiveMessageSchedule(scheduleId));
1276
+ return;
1277
+ }
1278
+ if (method === "PATCH") {
1279
+ sendJson(res, await updateMessageSchedule(scheduleId, readUpdateMessageScheduleRequest(await readNodeJsonBody(req))));
1280
+ return;
1281
+ }
1282
+ sendJson(res, await getMessageSchedule(scheduleId));
1283
+ return;
1284
+ }
1285
+ const chatMessagesMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/messages$/);
1286
+ if (chatMessagesMatch) {
1287
+ const chatId = decodeURIComponent(chatMessagesMatch[1]);
1288
+ requireMethod(method, ["GET", "POST"]);
1289
+ if (method === "POST") {
1290
+ sendJson(res, await executeMessage(chatId, readExecuteChatRequest(await readNodeJsonBody(req))), 202);
1291
+ return;
1292
+ }
1293
+ sendJson(res, await listMessages(chatId, readIntegerParam(url.searchParams.get("limit"), 1e3)));
1294
+ return;
1295
+ }
1296
+ const chatInterruptMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/interrupt$/);
1297
+ if (chatInterruptMatch) {
1298
+ requireMethod(method, ["POST"]);
1299
+ sendJson(res, await interruptChatRun(decodeURIComponent(chatInterruptMatch[1])));
1300
+ return;
1301
+ }
1302
+ const chatForkMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/fork$/);
1303
+ if (chatForkMatch) {
1304
+ requireMethod(method, ["POST"]);
1305
+ sendJson(res, await forkChat(decodeURIComponent(chatForkMatch[1]), readForkChatRequest(await readNodeJsonBody(req))), 201);
1306
+ return;
1307
+ }
1308
+ const chatCompactMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/compact$/);
1309
+ if (chatCompactMatch) {
1310
+ requireMethod(method, ["POST"]);
1311
+ sendJson(res, await compactChat(decodeURIComponent(chatCompactMatch[1]), readCompactChatRequest(await readNodeJsonBody(req))));
1312
+ return;
1313
+ }
1314
+ const chatReviewMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/review$/);
1315
+ if (chatReviewMatch) {
1316
+ requireMethod(method, ["POST"]);
1317
+ sendJson(res, await reviewChat(decodeURIComponent(chatReviewMatch[1]), readReviewChatRequest(await readNodeJsonBody(req))));
1318
+ return;
1319
+ }
1320
+ const chatRefreshMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/refresh$/);
1321
+ if (chatRefreshMatch) {
1322
+ requireMethod(method, ["POST"]);
1323
+ sendJson(res, await refreshChat(decodeURIComponent(chatRefreshMatch[1])));
1324
+ return;
1325
+ }
1326
+ const serverRequestMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/server-requests\/([^/]+)$/);
1327
+ if (serverRequestMatch) {
1328
+ requireMethod(method, ["POST"]);
1329
+ sendJson(res, await respondToServerRequest(decodeURIComponent(serverRequestMatch[1]), decodeURIComponent(serverRequestMatch[2]), readServerRequestResponseRequest(await readNodeJsonBody(req))));
1330
+ return;
1331
+ }
1332
+ const queuedRunsReorderMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/runs\/reorder$/);
1333
+ if (queuedRunsReorderMatch) {
1334
+ requireMethod(method, ["POST"]);
1335
+ sendJson(res, await reorderQueuedChatRuns(decodeURIComponent(queuedRunsReorderMatch[1]), readReorderQueuedChatRunsRequest(await readNodeJsonBody(req))));
1336
+ return;
1337
+ }
1338
+ const queuedRunSteerMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/runs\/([^/]+)\/steer$/);
1339
+ if (queuedRunSteerMatch) {
1340
+ requireMethod(method, ["POST"]);
1341
+ sendJson(res, await steerQueuedChatRun(decodeURIComponent(queuedRunSteerMatch[1]), decodeURIComponent(queuedRunSteerMatch[2])));
1342
+ return;
1343
+ }
1344
+ const queuedRunMatch = url.pathname.match(/^\/api\/chats\/([^/]+)\/runs\/([^/]+)$/);
1345
+ if (queuedRunMatch) {
1346
+ const chatId = decodeURIComponent(queuedRunMatch[1]);
1347
+ const runId = decodeURIComponent(queuedRunMatch[2]);
1348
+ requireMethod(method, ["DELETE", "PATCH"]);
1349
+ if (method === "DELETE") {
1350
+ sendJson(res, await deleteQueuedChatRun(chatId, runId));
1351
+ return;
1352
+ }
1353
+ sendJson(res, await updateQueuedChatRun(chatId, runId, readUpdateQueuedChatRunRequest(await readNodeJsonBody(req))));
1354
+ return;
1355
+ }
1356
+ const chatMatch = url.pathname.match(/^\/api\/chats\/([^/]+)$/);
1357
+ if (chatMatch) {
1358
+ const chatId = decodeURIComponent(chatMatch[1]);
1359
+ requireMethod(method, ["DELETE", "PATCH"]);
1360
+ if (method === "DELETE") {
1361
+ sendJson(res, await archiveChat(chatId));
1362
+ return;
1363
+ }
1364
+ sendJson(res, await updateChat(chatId, readUpdateChatRequest(await readNodeJsonBody(req))));
1365
+ return;
1366
+ }
1367
+ const accountAuthMatch = url.pathname.match(/^\/api\/provider-accounts\/([^/]+)\/authenticate$/);
1368
+ if (accountAuthMatch) {
1369
+ requireMethod(method, ["POST"]);
1370
+ const body = await readNodeJsonBody(req);
1371
+ sendJson(res, await authenticateAccount(decodeURIComponent(accountAuthMatch[1]), readAuthMode(body.mode)));
1372
+ return;
1373
+ }
1374
+ const accountModelsMatch = url.pathname.match(/^\/api\/provider-accounts\/([^/]+)\/models$/);
1375
+ if (accountModelsMatch) {
1376
+ requireMethod(method, ["GET"]);
1377
+ sendJson(res, await listAccountModels(decodeURIComponent(accountModelsMatch[1])));
1378
+ return;
1379
+ }
1380
+ const accountMatch = url.pathname.match(/^\/api\/provider-accounts\/([^/]+)$/);
1381
+ if (accountMatch) {
1382
+ requireMethod(method, ["DELETE", "PATCH"]);
1383
+ const accountId = decodeURIComponent(accountMatch[1]);
1384
+ if (method === "DELETE") {
1385
+ sendJson(res, await deleteAccount(accountId));
1386
+ return;
1387
+ }
1388
+ sendJson(res, await updateAccount(accountId, readUpdateAccountRequest(await readNodeJsonBody(req))));
1389
+ return;
1390
+ }
1391
+ if (url.pathname === "/api/workspaces") {
1392
+ requireMethod(method, [
1393
+ "DELETE",
1394
+ "GET",
1395
+ "POST"
1396
+ ]);
1397
+ if (method === "POST") {
1398
+ sendJson(res, await saveWorkspaceHistory(readStringField((await readNodeJsonBody(req)).path, "path", { required: true })), 201);
1399
+ return;
1400
+ }
1401
+ if (method === "DELETE") {
1402
+ sendJson(res, await deleteWorkspaceHistory(readStringField(url.searchParams.get("path"), "path", { required: true })));
1403
+ return;
1404
+ }
1405
+ sendJson(res, await listWorkspaceHistory());
1406
+ return;
1407
+ }
1408
+ if (url.pathname === "/api/workspaces/directories") {
1409
+ requireMethod(method, ["GET"]);
1410
+ sendJson(res, await listWorkspaceDirectory(url.searchParams.get("path"), url.searchParams.get("hidden") === "1"));
1411
+ return;
1412
+ }
1413
+ if (url.pathname === "/api/workspaces/tree") {
1414
+ requireMethod(method, ["GET"]);
1415
+ sendJson(res, await readWorkspaceTree(url.searchParams.get("path"), url.searchParams.get("hidden") === "1"));
1416
+ return;
1417
+ }
1418
+ if (url.pathname === "/api/workspaces/resource") {
1419
+ requireMethod(method, ["GET"]);
1420
+ sendJson(res, await readWorkspaceResource(url.searchParams.get("path")));
1421
+ return;
1422
+ }
1423
+ if (url.pathname === "/api/cloudflared/status") {
1424
+ requireMethod(method, ["GET"]);
1425
+ sendJson(res, await readCloudflaredStatus());
1426
+ return;
1427
+ }
1428
+ if (url.pathname === "/api/cloudflared/temporary") {
1429
+ requireMethod(method, ["POST"]);
1430
+ sendJson(res, await startTemporaryCloudflaredTunnel(readStringField((await readNodeJsonBody(req)).url, "url", {
1431
+ required: true,
1432
+ maxLength: 2e3
1433
+ })), 201);
1434
+ return;
1435
+ }
1436
+ const cloudflaredTemporaryMatch = url.pathname.match(/^\/api\/cloudflared\/temporary\/([^/]+)$/);
1437
+ if (cloudflaredTemporaryMatch) {
1438
+ requireMethod(method, ["DELETE"]);
1439
+ sendJson(res, await stopTemporaryCloudflaredTunnel(decodeURIComponent(cloudflaredTemporaryMatch[1])));
1440
+ return;
1441
+ }
1442
+ const cloudflaredTunnelMatch = url.pathname.match(/^\/api\/cloudflared\/tunnels\/([^/]+)$/);
1443
+ if (cloudflaredTunnelMatch) {
1444
+ requireMethod(method, ["DELETE"]);
1445
+ sendJson(res, await deleteNamedCloudflaredTunnel(decodeURIComponent(cloudflaredTunnelMatch[1])));
1446
+ return;
1447
+ }
1448
+ if (url.pathname === "/api/git/status") {
1449
+ requireMethod(method, ["GET"]);
1450
+ sendJson(res, await readGitStatus(url.searchParams.get("path") ?? void 0));
1451
+ return;
1452
+ }
1453
+ if (url.pathname === "/api/git/init") {
1454
+ requireMethod(method, ["POST"]);
1455
+ sendJson(res, await initGitRepository(readStringField((await readNodeJsonBody(req)).path, "path", { required: true })), 201);
1456
+ return;
1457
+ }
1458
+ if (url.pathname === "/api/git/stage") {
1459
+ requireMethod(method, ["POST"]);
1460
+ const body = await readNodeJsonBody(req);
1461
+ sendJson(res, await stageGitPaths(readStringField(body.path, "path", { required: true }), readStringArrayField(body.paths, "paths")));
1462
+ return;
1463
+ }
1464
+ if (url.pathname === "/api/git/unstage") {
1465
+ requireMethod(method, ["POST"]);
1466
+ const body = await readNodeJsonBody(req);
1467
+ sendJson(res, await unstageGitPaths(readStringField(body.path, "path", { required: true }), readStringArrayField(body.paths, "paths")));
1468
+ return;
1469
+ }
1470
+ if (url.pathname === "/api/git/discard") {
1471
+ requireMethod(method, ["POST"]);
1472
+ const body = await readNodeJsonBody(req);
1473
+ sendJson(res, await discardGitPaths(readStringField(body.path, "path", { required: true }), readStringArrayField(body.paths, "paths")));
1474
+ return;
1475
+ }
1476
+ if (url.pathname === "/api/git/commit") {
1477
+ requireMethod(method, ["POST"]);
1478
+ const body = await readNodeJsonBody(req);
1479
+ sendJson(res, await commitGitChanges(readStringField(body.path, "path", { required: true }), readStringField(body.message, "message", {
1480
+ required: true,
1481
+ maxLength: 500
1482
+ })));
1483
+ return;
1484
+ }
1485
+ if (url.pathname === "/api/git/pull") {
1486
+ requireMethod(method, ["POST"]);
1487
+ sendJson(res, await pullGitRepository(readStringField((await readNodeJsonBody(req)).path, "path", { required: true })));
1488
+ return;
1489
+ }
1490
+ if (url.pathname === "/api/git/push") {
1491
+ requireMethod(method, ["POST"]);
1492
+ sendJson(res, await pushGitRepository(readStringField((await readNodeJsonBody(req)).path, "path", { required: true })));
1493
+ return;
1494
+ }
1495
+ throw new HttpError(404, "API route not found.");
1496
+ }
1497
+ function readCreateAccountRequest(body) {
1498
+ return {
1499
+ displayName: readStringField(body.displayName, "displayName", { maxLength: 100 }),
1500
+ providerId: readStringField(body.providerId, "providerId", { required: true }),
1501
+ runtimeDefaults: readRecordField(body.runtimeDefaults, "runtimeDefaults"),
1502
+ settings: readRecordField(body.settings, "settings")
1503
+ };
1504
+ }
1505
+ function readPluginSettingsUpdateRequest(body) {
1506
+ return {
1507
+ enabled: readBooleanField(body.enabled, "enabled"),
1508
+ secrets: readRecordField(body.secrets, "secrets"),
1509
+ settings: readRecordField(body.settings, "settings")
1510
+ };
1511
+ }
1512
+ function readCodexInstructionsRequest(body) {
1513
+ if (body.instructions === void 0) return { instructions: "" };
1514
+ if (typeof body.instructions !== "string") throw new HttpError(400, "instructions must be a string.");
1515
+ if (body.instructions.length > 1e5) throw new HttpError(400, "instructions must be 100000 characters or fewer.");
1516
+ return { instructions: body.instructions };
1517
+ }
1518
+ function readCreateChatRequest(body) {
1519
+ return {
1520
+ accountId: readStringField(body.accountId, "accountId", { required: true }),
1521
+ autoRotateAccount: readBooleanField(body.autoRotateAccount, "autoRotateAccount"),
1522
+ collaborationMode: readStringField(body.collaborationMode, "collaborationMode"),
1523
+ model: readStringField(body.model, "model"),
1524
+ permissionMode: readStringField(body.permissionMode, "permissionMode"),
1525
+ providerId: readStringField(body.providerId, "providerId"),
1526
+ reasoningEffort: readStringField(body.reasoningEffort, "reasoningEffort"),
1527
+ serviceTier: readStringField(body.serviceTier, "serviceTier"),
1528
+ title: readStringField(body.title, "title", { maxLength: 160 }),
1529
+ workingDirectory: readStringField(body.workingDirectory, "workingDirectory", { required: true })
1530
+ };
1531
+ }
1532
+ function readCreateMessageScheduleRequest(body) {
1533
+ return {
1534
+ accountId: readStringField(body.accountId, "accountId", { required: true }),
1535
+ collaborationMode: readNullableStringField(body.collaborationMode, "collaborationMode"),
1536
+ firstRunAt: readStringField(body.firstRunAt, "firstRunAt", { required: true }),
1537
+ goalObjective: readNullableStringField(body.goalObjective, "goalObjective"),
1538
+ message: readStringField(body.message, "message", {
1539
+ required: true,
1540
+ maxLength: 2e4
1541
+ }),
1542
+ model: readNullableStringField(body.model, "model"),
1543
+ permissionMode: readNullableStringField(body.permissionMode, "permissionMode"),
1544
+ reasoningEffort: readNullableStringField(body.reasoningEffort, "reasoningEffort"),
1545
+ recurrence: readScheduleRecurrence(body.recurrence),
1546
+ serviceTier: readNullableStringField(body.serviceTier, "serviceTier"),
1547
+ status: readCreateScheduleStatus(body.status),
1548
+ title: readStringField(body.title, "title", { maxLength: 160 }),
1549
+ workingDirectory: readStringField(body.workingDirectory, "workingDirectory", { required: true })
1550
+ };
1551
+ }
1552
+ function readUpdateMessageScheduleRequest(body) {
1553
+ return {
1554
+ accountId: readNullableStringField(body.accountId, "accountId"),
1555
+ collaborationMode: readNullableStringField(body.collaborationMode, "collaborationMode"),
1556
+ firstRunAt: readNullableStringField(body.firstRunAt, "firstRunAt"),
1557
+ goalObjective: readNullableStringField(body.goalObjective, "goalObjective"),
1558
+ message: readStringField(body.message, "message", { maxLength: 2e4 }),
1559
+ model: readNullableStringField(body.model, "model"),
1560
+ permissionMode: readNullableStringField(body.permissionMode, "permissionMode"),
1561
+ reasoningEffort: readNullableStringField(body.reasoningEffort, "reasoningEffort"),
1562
+ recurrence: readScheduleRecurrence(body.recurrence),
1563
+ serviceTier: readNullableStringField(body.serviceTier, "serviceTier"),
1564
+ status: readScheduleStatus(body.status),
1565
+ title: readStringField(body.title, "title", { maxLength: 160 })
1566
+ };
1567
+ }
1568
+ function readUpdateChatRequest(body) {
1569
+ return {
1570
+ accountId: readNullableStringField(body.accountId, "accountId"),
1571
+ autoRotateAccount: readBooleanField(body.autoRotateAccount, "autoRotateAccount"),
1572
+ collaborationMode: readNullableStringField(body.collaborationMode, "collaborationMode"),
1573
+ model: readNullableStringField(body.model, "model"),
1574
+ permissionMode: readNullableStringField(body.permissionMode, "permissionMode"),
1575
+ reasoningEffort: readNullableStringField(body.reasoningEffort, "reasoningEffort"),
1576
+ serviceTier: readNullableStringField(body.serviceTier, "serviceTier"),
1577
+ title: readStringField(body.title, "title", { maxLength: 160 }),
1578
+ workingDirectory: readStringField(body.workingDirectory, "workingDirectory")
1579
+ };
1580
+ }
1581
+ function readForkChatRequest(body) {
1582
+ return { lastTurnId: readNullableStringField(body.lastTurnId, "lastTurnId") };
1583
+ }
1584
+ function readCompactChatRequest(_body) {
1585
+ return {};
1586
+ }
1587
+ function readReviewChatRequest(body) {
1588
+ const target = body.target;
1589
+ if (target !== void 0 && target !== null && target !== "uncommittedChanges" && target !== "baseBranch" && target !== "commit" && target !== "custom") throw new HttpError(400, "target must be uncommittedChanges, baseBranch, commit, or custom.");
1590
+ const delivery = body.delivery;
1591
+ if (delivery !== void 0 && delivery !== null && delivery !== "inline" && delivery !== "detached") throw new HttpError(400, "delivery must be inline or detached.");
1592
+ return {
1593
+ baseBranch: readNullableStringField(body.baseBranch, "baseBranch"),
1594
+ commitSha: readNullableStringField(body.commitSha, "commitSha"),
1595
+ commitTitle: readNullableStringField(body.commitTitle, "commitTitle"),
1596
+ delivery,
1597
+ instructions: readNullableStringField(body.instructions, "instructions"),
1598
+ target
1599
+ };
1600
+ }
1601
+ function readExecuteChatRequest(body) {
1602
+ return {
1603
+ accountId: readStringField(body.accountId, "accountId"),
1604
+ attachments: readChatAttachments(body.attachments),
1605
+ collaborationMode: readNullableStringField(body.collaborationMode, "collaborationMode"),
1606
+ content: readStringField(body.content, "content", { required: true }),
1607
+ delivery: body.delivery === "queue" || body.delivery === "steer" ? body.delivery : void 0,
1608
+ goalObjective: readNullableStringField(body.goalObjective, "goalObjective"),
1609
+ metadata: readRecordField(body.metadata, "metadata"),
1610
+ permissionMode: readNullableStringField(body.permissionMode, "permissionMode")
1611
+ };
1612
+ }
1613
+ function readUpdateQueuedChatRunRequest(body) {
1614
+ return { content: readStringField(body.content, "content", { required: true }) };
1615
+ }
1616
+ function readReorderQueuedChatRunsRequest(body) {
1617
+ if (!Array.isArray(body.runIds)) throw new HttpError(400, "runIds must be an array.");
1618
+ return { runIds: body.runIds.map((runId) => readStringField(runId, "runIds[]", { required: true })).filter((runId, index, runIds) => runIds.indexOf(runId) === index) };
1619
+ }
1620
+ function readServerRequestResponseRequest(body) {
1621
+ const kind = body.kind;
1622
+ if (kind !== "approval" && kind !== "permissions" && kind !== "userInput") throw new HttpError(400, "kind must be approval, permissions, or userInput.");
1623
+ return {
1624
+ decision: body.decision,
1625
+ kind,
1626
+ result: body.result
1627
+ };
1628
+ }
1629
+ function readChatAttachments(value) {
1630
+ if (value === void 0) return;
1631
+ if (!Array.isArray(value)) throw new HttpError(400, "attachments must be an array.");
1632
+ return value.slice(0, 20).map((item, index) => {
1633
+ if (!item || typeof item !== "object" || Array.isArray(item)) throw new HttpError(400, `attachments[${index}] must be an object.`);
1634
+ const record = item;
1635
+ const kind = record.kind;
1636
+ if (kind !== "file" && kind !== "folder" && kind !== "image") throw new HttpError(400, `attachments[${index}].kind is invalid.`);
1637
+ const size = record.size;
1638
+ return {
1639
+ dataUrl: readStringField(record.dataUrl, `attachments[${index}].dataUrl`),
1640
+ kind,
1641
+ mimeType: readStringField(record.mimeType, `attachments[${index}].mimeType`),
1642
+ name: readStringField(record.name, `attachments[${index}].name`, {
1643
+ required: true,
1644
+ maxLength: 240
1645
+ }),
1646
+ path: readStringField(record.path, `attachments[${index}].path`, { maxLength: 1e3 }),
1647
+ size: typeof size === "number" && Number.isFinite(size) ? size : void 0
1648
+ };
1649
+ });
1650
+ }
1651
+ function readUpdateAccountRequest(body) {
1652
+ return {
1653
+ displayName: readStringField(body.displayName, "displayName", { maxLength: 100 }),
1654
+ runtimeDefaults: readRecordField(body.runtimeDefaults, "runtimeDefaults"),
1655
+ settings: readRecordField(body.settings, "settings")
1656
+ };
1657
+ }
1658
+ function readScheduleRecurrence(value) {
1659
+ if (value === void 0) return;
1660
+ const record = readRecordField(value, "recurrence") ?? {};
1661
+ const frequency = record.frequency;
1662
+ if (frequency !== void 0 && frequency !== "none" && frequency !== "daily" && frequency !== "weekly" && frequency !== "monthly") throw new HttpError(400, "recurrence.frequency must be none, daily, weekly, or monthly.");
1663
+ return {
1664
+ anchorDay: readOptionalNumber(record.anchorDay, "recurrence.anchorDay") ?? void 0,
1665
+ endAt: readNullableStringField(record.endAt, "recurrence.endAt"),
1666
+ frequency,
1667
+ interval: readOptionalNumber(record.interval, "recurrence.interval") ?? void 0,
1668
+ maxRuns: readOptionalNumber(record.maxRuns, "recurrence.maxRuns")
1669
+ };
1670
+ }
1671
+ function readCreateScheduleStatus(value) {
1672
+ if (value === void 0) return;
1673
+ if (value === "ACTIVE" || value === "PAUSED") return value;
1674
+ throw new HttpError(400, "status must be ACTIVE or PAUSED.");
1675
+ }
1676
+ function readScheduleStatus(value) {
1677
+ if (value === void 0) return;
1678
+ if (value === "ACTIVE" || value === "PAUSED" || value === "COMPLETED" || value === "ARCHIVED") return value;
1679
+ throw new HttpError(400, "status must be ACTIVE, PAUSED, COMPLETED, or ARCHIVED.");
1680
+ }
1681
+ function readOptionalNumber(value, field) {
1682
+ if (value === void 0) return;
1683
+ if (value === null || value === "") return null;
1684
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new HttpError(400, `${field} must be a number.`);
1685
+ return value;
1686
+ }
1687
+ function readNullableStringField(value, field) {
1688
+ if (value === null) return null;
1689
+ return readStringField(value, field);
1690
+ }
1691
+ function readStringArrayField(value, field) {
1692
+ if (value === void 0) return [];
1693
+ if (!Array.isArray(value)) throw new HttpError(400, `${field} must be an array.`);
1694
+ return value.map((item, index) => readStringField(item, `${field}[${index}]`, { required: true }));
1695
+ }
1696
+ function readAuthMode(value) {
1697
+ if (value === void 0 || value === null) return "browser";
1698
+ if (value === "browser" || value === "device" || value === "local") return value;
1699
+ throw new HttpError(400, "mode must be browser, device, or local.");
1700
+ }
1701
+ function readIntegerParam(value, fallback) {
1702
+ if (!value) return fallback;
1703
+ const parsed = Number.parseInt(value, 10);
1704
+ return Number.isFinite(parsed) ? parsed : fallback;
1705
+ }
1706
+ async function readNodeJsonBody(req) {
1707
+ const chunks = [];
1708
+ for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1709
+ const text = Buffer.concat(chunks).toString("utf8");
1710
+ if (!text.trim()) return {};
1711
+ try {
1712
+ const value = JSON.parse(text);
1713
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new HttpError(400, "JSON body must be an object.");
1714
+ return value;
1715
+ } catch (error) {
1716
+ if (error instanceof HttpError) throw error;
1717
+ throw new HttpError(400, "Request body must be valid JSON.");
1718
+ }
1719
+ }
1720
+ function requireMethod(method, allowed) {
1721
+ if (!allowed.includes(method)) throw new HttpError(405, `${method} is not supported for this route.`);
1722
+ }
1723
+ function sendJson(res, data, status = 200) {
1724
+ res.statusCode = status;
1725
+ res.setHeader("Content-Type", "application/json");
1726
+ res.end(JSON.stringify(data));
1727
+ }
1728
+ function sendRouteError(res, error) {
1729
+ if (error instanceof HttpError) {
1730
+ sendJson(res, { error: error.message }, error.status);
1731
+ return;
1732
+ }
1733
+ sendJson(res, { error: error instanceof Error ? error.message : "Request failed." }, 500);
1734
+ }
1735
+ //#endregion
1736
+ export { handleApiRequest, installApiServer, sendRouteError };