@botconnector/bccli 0.4.16 → 0.5.0-rc.4

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 (185) hide show
  1. package/LICENSE +203 -21
  2. package/THIRD_PARTY_NOTICES.md +9 -0
  3. package/branding.mjs +347 -0
  4. package/cli.mjs +120 -0
  5. package/core.mjs +260 -0
  6. package/credentials.mjs +52 -0
  7. package/gateway.mjs +2 -0
  8. package/model-command.mjs +61 -0
  9. package/package.json +35 -59
  10. package/provider-cli.mjs +77 -0
  11. package/providers.mjs +166 -0
  12. package/proxy.mjs +242 -0
  13. package/startup-menu.mjs +120 -0
  14. package/README.en.md +0 -253
  15. package/README.md +0 -250
  16. package/dist/abort.d.ts +0 -5
  17. package/dist/acp-JZW3XHE5.js +0 -221
  18. package/dist/acp.d.ts +0 -44
  19. package/dist/agent.d.ts +0 -147
  20. package/dist/agents.d.ts +0 -4
  21. package/dist/args-XVO7TB4C.js +0 -13
  22. package/dist/args.d.ts +0 -59
  23. package/dist/budget.d.ts +0 -26
  24. package/dist/checkpoints.d.ts +0 -35
  25. package/dist/chunk-3CYQYHPK.js +0 -202
  26. package/dist/chunk-6JK4IBGK.js +0 -1075
  27. package/dist/chunk-6SDRWEPN.js +0 -50
  28. package/dist/chunk-7GM6NN5E.js +0 -341
  29. package/dist/chunk-7XD47KN5.js +0 -71
  30. package/dist/chunk-INMSTUB2.js +0 -56
  31. package/dist/chunk-ISOKH5WB.js +0 -256
  32. package/dist/chunk-JUC3ZOCV.js +0 -50
  33. package/dist/chunk-KDVL2NP6.js +0 -861
  34. package/dist/chunk-KEKDRROL.js +0 -216
  35. package/dist/chunk-KJIXA6MP.js +0 -159
  36. package/dist/chunk-OK2USAXX.js +0 -88
  37. package/dist/chunk-PSO7R5AD.js +0 -10
  38. package/dist/chunk-Q42ZGVAQ.js +0 -91
  39. package/dist/chunk-R754XIBJ.js +0 -523
  40. package/dist/chunk-S3QEZDKB.js +0 -270
  41. package/dist/chunk-SWNRA3EW.js +0 -78
  42. package/dist/chunk-TLCL6DC7.js +0 -378
  43. package/dist/chunk-V5D6PJQN.js +0 -8
  44. package/dist/chunk-YYTFJUO2.js +0 -148
  45. package/dist/chunk-ZJRZTMJF.js +0 -2669
  46. package/dist/cli.d.ts +0 -1
  47. package/dist/cli.js +0 -233
  48. package/dist/clipboardImage.d.ts +0 -17
  49. package/dist/commands.d.ts +0 -16
  50. package/dist/completion-Q7VHOPPV.js +0 -125
  51. package/dist/completion.d.ts +0 -14
  52. package/dist/config-T7MKGJVG.js +0 -34
  53. package/dist/config-YYEAEN2C.js +0 -25
  54. package/dist/config.d.ts +0 -99
  55. package/dist/context.d.ts +0 -13
  56. package/dist/contextWindow.d.ts +0 -3
  57. package/dist/diff.d.ts +0 -7
  58. package/dist/exit.d.ts +0 -9
  59. package/dist/extensions.d.ts +0 -34
  60. package/dist/ghBotCli-7VUGI63S.js +0 -680
  61. package/dist/ghBotCli.d.ts +0 -9
  62. package/dist/ghBotDispatch.d.ts +0 -68
  63. package/dist/ghBotRunner.d.ts +0 -82
  64. package/dist/ghBotServe.d.ts +0 -21
  65. package/dist/ghBotWebhook.d.ts +0 -45
  66. package/dist/hooks.d.ts +0 -27
  67. package/dist/i18n/id.d.ts +0 -1
  68. package/dist/i18n.d.ts +0 -16
  69. package/dist/integrationCli-FBRSTF5F.js +0 -876
  70. package/dist/integrationCli.d.ts +0 -9
  71. package/dist/keybinds.d.ts +0 -22
  72. package/dist/login-BSMUGW7L.js +0 -18
  73. package/dist/login.d.ts +0 -21
  74. package/dist/lsp.d.ts +0 -73
  75. package/dist/mcp/catalog.d.ts +0 -14
  76. package/dist/mcp/config.d.ts +0 -36
  77. package/dist/mcp/manager.d.ts +0 -52
  78. package/dist/mcp/oauth.d.ts +0 -82
  79. package/dist/mcpCli-ZT7CRYMP.js +0 -113
  80. package/dist/mcpCli.d.ts +0 -3
  81. package/dist/mcpServer/serve.d.ts +0 -8
  82. package/dist/mcpServer/server.d.ts +0 -19
  83. package/dist/models.d.ts +0 -13
  84. package/dist/modelsCli-2ZRW4CC4.js +0 -36
  85. package/dist/modelsCli.d.ts +0 -15
  86. package/dist/npmRunner-LU43GM3W.js +0 -46
  87. package/dist/npmRunner.d.ts +0 -21
  88. package/dist/oauthLogin-THSM6FE7.js +0 -210
  89. package/dist/oauthLogin.d.ts +0 -47
  90. package/dist/permissions.d.ts +0 -30
  91. package/dist/pluginSpec.d.ts +0 -25
  92. package/dist/plugins.d.ts +0 -45
  93. package/dist/pluginsCli-EKQI4CJW.js +0 -288
  94. package/dist/pluginsCli.d.ts +0 -16
  95. package/dist/prCli-FMQTOBIV.js +0 -94
  96. package/dist/prCli.d.ts +0 -46
  97. package/dist/presets.d.ts +0 -8
  98. package/dist/print.d.ts +0 -17
  99. package/dist/provider.d.ts +0 -145
  100. package/dist/providerCli-3TFEKV4C.js +0 -15
  101. package/dist/providerCli.d.ts +0 -17
  102. package/dist/providers.d.ts +0 -6
  103. package/dist/reasoning.d.ts +0 -12
  104. package/dist/repomap.d.ts +0 -10
  105. package/dist/sdk.d.ts +0 -61
  106. package/dist/sdk.js +0 -6494
  107. package/dist/serve-3BODPEPR.js +0 -532
  108. package/dist/serve-V6YVZTNR.js +0 -178
  109. package/dist/serve.d.ts +0 -37
  110. package/dist/serveHeartbeat.d.ts +0 -14
  111. package/dist/serveProtocol.d.ts +0 -47
  112. package/dist/session-LX4UNPCD.js +0 -12
  113. package/dist/session.d.ts +0 -33
  114. package/dist/setup-MFG3BWEU.js +0 -24
  115. package/dist/setup.d.ts +0 -79
  116. package/dist/slash/copy.d.ts +0 -1
  117. package/dist/slash/diff.d.ts +0 -1
  118. package/dist/slash/doctor.d.ts +0 -18
  119. package/dist/slash/export.d.ts +0 -2
  120. package/dist/slash/info.d.ts +0 -46
  121. package/dist/slash/memory.d.ts +0 -10
  122. package/dist/statsCli-7MIQHLVW.js +0 -353
  123. package/dist/statsCli.d.ts +0 -62
  124. package/dist/textToolCalls.d.ts +0 -6
  125. package/dist/thinking.d.ts +0 -17
  126. package/dist/tokenBudget.d.ts +0 -22
  127. package/dist/tools/bash.d.ts +0 -54
  128. package/dist/tools/diagnostics.d.ts +0 -13
  129. package/dist/tools/edit.d.ts +0 -12
  130. package/dist/tools/fetch.d.ts +0 -41
  131. package/dist/tools/glob.d.ts +0 -5
  132. package/dist/tools/globSafety.d.ts +0 -7
  133. package/dist/tools/grep.d.ts +0 -25
  134. package/dist/tools/ignore.d.ts +0 -2
  135. package/dist/tools/index.d.ts +0 -12
  136. package/dist/tools/memory.d.ts +0 -13
  137. package/dist/tools/paths.d.ts +0 -4
  138. package/dist/tools/plan.d.ts +0 -10
  139. package/dist/tools/read.d.ts +0 -16
  140. package/dist/tools/repomap.d.ts +0 -2
  141. package/dist/tools/sandbox.d.ts +0 -15
  142. package/dist/tools/shell.d.ts +0 -18
  143. package/dist/tools/skill.d.ts +0 -3
  144. package/dist/tools/task.d.ts +0 -28
  145. package/dist/tools/todo.d.ts +0 -13
  146. package/dist/tools/types.d.ts +0 -52
  147. package/dist/tools/websearch.d.ts +0 -9
  148. package/dist/tools/write.d.ts +0 -5
  149. package/dist/transferCli-SRSYQLSJ.js +0 -73
  150. package/dist/transferCli.d.ts +0 -23
  151. package/dist/ui/App.d.ts +0 -7
  152. package/dist/ui/DiffView.d.ts +0 -3
  153. package/dist/ui/EditableText.d.ts +0 -7
  154. package/dist/ui/LinePrompt.d.ts +0 -6
  155. package/dist/ui/ListPicker.d.ts +0 -11
  156. package/dist/ui/Markdown.d.ts +0 -8
  157. package/dist/ui/McpMenu.d.ts +0 -11
  158. package/dist/ui/ModelPicker.d.ts +0 -6
  159. package/dist/ui/PermissionPrompt.d.ts +0 -5
  160. package/dist/ui/PlanApproval.d.ts +0 -5
  161. package/dist/ui/PromptInput.d.ts +0 -20
  162. package/dist/ui/ProviderMenu.d.ts +0 -10
  163. package/dist/ui/Spinner.d.ts +0 -4
  164. package/dist/ui/StatusBar.d.ts +0 -9
  165. package/dist/ui/TodoList.d.ts +0 -4
  166. package/dist/ui/ToolBlock.d.ts +0 -14
  167. package/dist/ui/complete.d.ts +0 -8
  168. package/dist/ui/index.d.ts +0 -7
  169. package/dist/ui/lineEdit.d.ts +0 -42
  170. package/dist/ui/liveCap.d.ts +0 -9
  171. package/dist/ui/replay.d.ts +0 -4
  172. package/dist/ui/stdinFilter.d.ts +0 -18
  173. package/dist/ui/table.d.ts +0 -20
  174. package/dist/ui/theme.d.ts +0 -5
  175. package/dist/ui/thinkingMouse.d.ts +0 -38
  176. package/dist/ui/transcript.d.ts +0 -55
  177. package/dist/ui/tty.d.ts +0 -11
  178. package/dist/ui/vim.d.ts +0 -26
  179. package/dist/ui-LZ4JGF2V.js +0 -2913
  180. package/dist/update-P7MU4ZM7.js +0 -145
  181. package/dist/update.d.ts +0 -30
  182. package/dist/verify.d.ts +0 -16
  183. package/dist/version.d.ts +0 -1
  184. package/dist/webBudget.d.ts +0 -25
  185. package/dist/worktree.d.ts +0 -12
@@ -1,532 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createRuntime
4
- } from "./chunk-ZJRZTMJF.js";
5
- import "./chunk-OK2USAXX.js";
6
- import "./chunk-KDVL2NP6.js";
7
- import "./chunk-7GM6NN5E.js";
8
- import "./chunk-PSO7R5AD.js";
9
- import {
10
- VERSION
11
- } from "./chunk-V5D6PJQN.js";
12
- import {
13
- LOOPBACK_BIND,
14
- MAX_FRAME_BYTES,
15
- bearerOk,
16
- parseCliArgs,
17
- parseClientFrame,
18
- randomToken,
19
- trustFence
20
- } from "./chunk-ISOKH5WB.js";
21
- import "./chunk-YYTFJUO2.js";
22
- import "./chunk-SWNRA3EW.js";
23
- import {
24
- listAllModels
25
- } from "./chunk-INMSTUB2.js";
26
- import "./chunk-6SDRWEPN.js";
27
- import "./chunk-3CYQYHPK.js";
28
- import {
29
- Session
30
- } from "./chunk-KEKDRROL.js";
31
- import "./chunk-6JK4IBGK.js";
32
- import "./chunk-7XD47KN5.js";
33
- import "./chunk-Q42ZGVAQ.js";
34
- import {
35
- bccliHome,
36
- loadConfig
37
- } from "./chunk-TLCL6DC7.js";
38
- import "./chunk-R754XIBJ.js";
39
-
40
- // src/serve.ts
41
- import { randomBytes } from "crypto";
42
- import { createServer } from "http";
43
- import { basename } from "path";
44
- import { WebSocket, WebSocketServer } from "ws";
45
-
46
- // src/serveHeartbeat.ts
47
- function createHeartbeat(intervalMs, ping, terminate) {
48
- let awaitingPong = false;
49
- let stopped = false;
50
- let timer = null;
51
- const tick = () => {
52
- if (stopped) return;
53
- if (awaitingPong) {
54
- terminate();
55
- timer = null;
56
- return;
57
- }
58
- awaitingPong = true;
59
- ping();
60
- timer = setTimeout(tick, intervalMs);
61
- };
62
- return {
63
- start() {
64
- if (stopped || timer) return;
65
- awaitingPong = true;
66
- ping();
67
- timer = setTimeout(tick, intervalMs);
68
- },
69
- pong() {
70
- awaitingPong = false;
71
- },
72
- stop() {
73
- stopped = true;
74
- if (timer) clearTimeout(timer);
75
- timer = null;
76
- }
77
- };
78
- }
79
-
80
- // src/serve.ts
81
- var MAX_BODY_BYTES = 1024 * 1024;
82
- var DISPLAY_MAX = 1e5;
83
- function json(res, status, value) {
84
- const body = JSON.stringify(value);
85
- res.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(body) });
86
- res.end(body);
87
- }
88
- function readBody(req) {
89
- return new Promise((resolve, reject) => {
90
- let body = "";
91
- let size = 0;
92
- let tooLarge = false;
93
- req.on("data", (c) => {
94
- size += c.length;
95
- if (size > MAX_BODY_BYTES) tooLarge = true;
96
- else body += c;
97
- });
98
- req.on("end", () => resolve(tooLarge ? null : body));
99
- req.on("error", reject);
100
- });
101
- }
102
- function sendFrame(ws, frame) {
103
- if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(frame));
104
- }
105
- function serveBanner(handle, opts = {}) {
106
- const supplied = opts.token !== void 0 || (opts.env ?? process.env).BCCLI_SERVE_TOKEN !== void 0;
107
- return `bccli serve ${handle.url}${supplied ? "" : ` \u2014 Authorization: Bearer ${handle.token}`}
108
- `;
109
- }
110
- async function startServe(opts = {}) {
111
- const env = opts.env ?? process.env;
112
- const cwd = opts.cwd ?? process.cwd();
113
- const host = opts.host ?? "127.0.0.1";
114
- if (!LOOPBACK_BIND.has(host)) throw new Error(`serve refuses non-loopback --host ${host} (loopback only)`);
115
- const token = opts.token ?? env.BCCLI_SERVE_TOKEN ?? randomToken();
116
- const home = bccliHome(env);
117
- const sessions = /* @__PURE__ */ new Map();
118
- const allSockets = /* @__PURE__ */ new Set();
119
- const server = createServer((req, res) => {
120
- void handleHttp(req, res);
121
- });
122
- const wss = new WebSocketServer({ noServer: true });
123
- server.on("upgrade", (req, socket, head) => {
124
- const address2 = server.address();
125
- const port2 = typeof address2 === "object" && address2 ? address2.port : 0;
126
- const reject = (status, reason) => {
127
- socket.write(`HTTP/1.1 ${status} ${reason}\r
128
- Connection: close\r
129
- \r
130
- `);
131
- socket.destroy();
132
- };
133
- const fence = trustFence(req.headers, port2);
134
- if (fence) return reject(403, "Forbidden");
135
- const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
136
- if (url.pathname !== "/v1/mux") return reject(404, "Not Found");
137
- if (!bearerOk(req.headers.authorization, token)) return reject(401, "Unauthorized");
138
- wss.handleUpgrade(req, socket, head, (ws) => wss.emit("connection", ws, req));
139
- });
140
- wss.on("connection", (ws) => {
141
- allSockets.add(ws);
142
- const heartbeat = createHeartbeat(opts.heartbeatMs ?? 2e3, () => ws.ping(), () => ws.terminate());
143
- ws.on("pong", () => heartbeat.pong());
144
- ws.on("close", () => heartbeat.stop());
145
- heartbeat.start();
146
- const connStreams = /* @__PURE__ */ new Map();
147
- ws.on("close", () => {
148
- allSockets.delete(ws);
149
- for (const stream of connStreams.values()) stream.session.streams.delete(stream);
150
- connStreams.clear();
151
- });
152
- ws.on("message", (data) => {
153
- const rawFrame = String(data);
154
- if (rawFrame.length > MAX_FRAME_BYTES) {
155
- sendFrame(ws, { type: "error", streamId: "", message: "frame exceeds 256 KB" });
156
- return;
157
- }
158
- const frame = parseClientFrame(rawFrame);
159
- if ("error" in frame) {
160
- sendFrame(ws, { type: "error", streamId: "", message: frame.error });
161
- return;
162
- }
163
- if (frame.type === "cancel") {
164
- const stream2 = connStreams.get(frame.streamId);
165
- if (!stream2) return;
166
- connStreams.delete(frame.streamId);
167
- stream2.session.streams.delete(stream2);
168
- sendFrame(ws, { type: "end", streamId: frame.streamId });
169
- return;
170
- }
171
- if (connStreams.has(frame.streamId)) {
172
- sendFrame(ws, { type: "error", streamId: frame.streamId, message: "duplicate open" });
173
- return;
174
- }
175
- const match = frame.target.match(/^session:(.+)$/);
176
- const sessionId = match ? decodeURIComponent(match[1]) : "";
177
- const session = sessionId ? sessions.get(sessionId) ?? attachSession(sessionId, cwd) : void 0;
178
- if (!session) {
179
- sendFrame(ws, { type: "error", streamId: frame.streamId, message: `unknown target ${frame.target}` });
180
- return;
181
- }
182
- const stream = { id: frame.streamId, ws, session };
183
- connStreams.set(frame.streamId, stream);
184
- session.streams.add(stream);
185
- sendFrame(ws, { type: "item", streamId: frame.streamId, value: { type: "ready", sessionId: session.id, host: { home, version: VERSION } } });
186
- for (const [requestId, p] of session.pending) {
187
- sendFrame(ws, {
188
- type: "item",
189
- streamId: frame.streamId,
190
- value: { type: "approval_request", requestId, tool: p.req.tool, kind: p.req.kind, target: p.req.target, preview: p.req.preview }
191
- });
192
- }
193
- });
194
- });
195
- function broadcast(s, value) {
196
- for (const stream of s.streams) sendFrame(stream.ws, { type: "item", streamId: stream.id, value });
197
- }
198
- async function handleHttp(req, res) {
199
- const address2 = server.address();
200
- const port2 = typeof address2 === "object" && address2 ? address2.port : 0;
201
- const fence = trustFence(req.headers, port2);
202
- if (fence) {
203
- jsonError(res, 403, fence);
204
- return;
205
- }
206
- const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
207
- if (url.pathname !== "/health" && !bearerOk(req.headers.authorization, token)) {
208
- jsonError(res, 401, "missing or invalid bearer token");
209
- return;
210
- }
211
- await route(req, res, url);
212
- }
213
- function jsonError(res, status, message) {
214
- json(res, status, { error: { message } });
215
- }
216
- async function route(req, res, url) {
217
- const method = req.method ?? "GET";
218
- if (method === "GET" && url.pathname === "/health") {
219
- json(res, 200, { ok: true, version: VERSION });
220
- return;
221
- }
222
- if (method === "GET" && url.pathname === "/v1/models") {
223
- try {
224
- const groups = opts.provider ? [{ providerId: "injected", providerName: "injected", models: await opts.provider.listModels() }] : await listAllModels(loadConfig(cwd, env), env, { fetch: opts.fetch });
225
- json(res, 200, groups);
226
- } catch (error) {
227
- jsonError(res, 500, error.message);
228
- }
229
- return;
230
- }
231
- if (url.pathname === "/v1/sessions" && method === "POST") {
232
- const raw = await readBody(req);
233
- if (raw === null) {
234
- jsonError(res, 413, "request body exceeds 1 MB");
235
- return;
236
- }
237
- let body = {};
238
- if (raw) {
239
- try {
240
- body = JSON.parse(raw);
241
- } catch {
242
- jsonError(res, 400, "invalid JSON body");
243
- return;
244
- }
245
- }
246
- if (body.permissionMode !== void 0 && body.permissionMode !== "ask" && body.permissionMode !== "bypassPermissions") {
247
- jsonError(res, 400, "permissionMode must be 'ask' or 'bypassPermissions'");
248
- return;
249
- }
250
- try {
251
- const s = openSession(body.cwd ?? cwd, body.permissionMode ?? "ask", body.model);
252
- json(res, 201, { id: s.id, cwd: s.cwd, permissionMode: s.permissionMode });
253
- } catch (error) {
254
- jsonError(res, 500, error.message);
255
- }
256
- return;
257
- }
258
- if (url.pathname === "/v1/sessions" && method === "GET") {
259
- const listCwd = url.searchParams.get("cwd") ?? cwd;
260
- const byId = /* @__PURE__ */ new Map();
261
- for (const { session, mtime, preview } of Session.list(home, listCwd)) {
262
- const id = basename(session.file, ".jsonl");
263
- byId.set(id, { id, mtime: mtime.toISOString(), preview });
264
- }
265
- for (const s of sessions.values()) {
266
- if (s.cwd === listCwd && !byId.has(s.id)) {
267
- byId.set(s.id, { id: s.id, mtime: (/* @__PURE__ */ new Date()).toISOString(), preview: "" });
268
- }
269
- }
270
- json(res, 200, [...byId.values()]);
271
- return;
272
- }
273
- const one = url.pathname.match(/^\/v1\/sessions\/([^/]+)$/);
274
- if (one && method === "GET") {
275
- const id = decodeURIComponent(one[1]);
276
- const live = sessions.get(id);
277
- if (live) {
278
- json(res, 200, { id, cwd: live.cwd, permissionMode: live.permissionMode, messages: live.rt.agent.messages });
279
- return;
280
- }
281
- const found = findSessionFile(id, url.searchParams.get("cwd") ?? cwd);
282
- if (!found) {
283
- jsonError(res, 404, `unknown session ${id}`);
284
- return;
285
- }
286
- json(res, 200, { id, cwd: found.cwd, permissionMode: "ask", messages: found.session.load() });
287
- return;
288
- }
289
- const messages = url.pathname.match(/^\/v1\/sessions\/([^/]+)\/messages$/);
290
- if (messages && method === "POST") {
291
- const raw = await readBody(req);
292
- if (raw === null) {
293
- jsonError(res, 413, "request body exceeds 1 MB");
294
- return;
295
- }
296
- let body = {};
297
- if (raw) {
298
- try {
299
- body = JSON.parse(raw);
300
- } catch {
301
- jsonError(res, 400, "invalid JSON body");
302
- return;
303
- }
304
- }
305
- const id = decodeURIComponent(messages[1]);
306
- let s = sessions.get(id);
307
- if (!s) s = attachSession(id, url.searchParams.get("cwd") ?? cwd);
308
- if (!s) {
309
- jsonError(res, 404, `unknown session ${id}`);
310
- return;
311
- }
312
- if (typeof body.prompt !== "string" || body.prompt === "") {
313
- jsonError(res, 400, "prompt must be a non-empty string");
314
- return;
315
- }
316
- if (s.busy) {
317
- jsonError(res, 409, `session ${id} is busy with turn ${s.busy.turnId}`);
318
- return;
319
- }
320
- const turnId = randomBytes(6).toString("hex");
321
- const controller = new AbortController();
322
- s.busy = { turnId, controller };
323
- void runTurn(s, body.prompt, controller);
324
- json(res, 202, { turnId });
325
- return;
326
- }
327
- const approvals = url.pathname.match(/^\/v1\/sessions\/([^/]+)\/approvals$/);
328
- if (approvals && method === "POST") {
329
- const raw = await readBody(req);
330
- if (raw === null) {
331
- jsonError(res, 413, "request body exceeds 1 MB");
332
- return;
333
- }
334
- let body = {};
335
- if (raw) {
336
- try {
337
- body = JSON.parse(raw);
338
- } catch {
339
- jsonError(res, 400, "invalid JSON body");
340
- return;
341
- }
342
- }
343
- const answers = ["yes", "no", "session", "all"];
344
- if (typeof body.requestId !== "string" || !answers.includes(body.answer)) {
345
- jsonError(res, 400, "requestId must be a string and answer one of 'yes'|'no'|'session'|'all'");
346
- return;
347
- }
348
- const s = sessions.get(decodeURIComponent(approvals[1]));
349
- if (!s) {
350
- jsonError(res, 404, `unknown session ${decodeURIComponent(approvals[1])}`);
351
- return;
352
- }
353
- const pending = s.pending.get(body.requestId);
354
- if (!pending) {
355
- jsonError(res, 404, `unknown or already answered requestId ${body.requestId}`);
356
- return;
357
- }
358
- s.pending.delete(body.requestId);
359
- pending.resolve(body.answer);
360
- json(res, 200, { accepted: true });
361
- return;
362
- }
363
- const cancel = url.pathname.match(/^\/v1\/sessions\/([^/]+)\/cancel$/);
364
- if (cancel && method === "POST") {
365
- const s = sessions.get(decodeURIComponent(cancel[1]));
366
- if (!s) {
367
- jsonError(res, 404, `unknown session ${decodeURIComponent(cancel[1])}`);
368
- return;
369
- }
370
- if (s.busy) s.busy.controller.abort();
371
- failPending(s);
372
- json(res, 200, { cancelled: true });
373
- return;
374
- }
375
- jsonError(res, 404, `no route ${method} ${url.pathname}`);
376
- }
377
- async function runTurn(s, prompt, controller) {
378
- let text = "";
379
- let stopReason = "done";
380
- const toolCalls = [];
381
- const byId = /* @__PURE__ */ new Map();
382
- let usage = { inputTokens: 0, outputTokens: 0 };
383
- const map = (event) => {
384
- switch (event.type) {
385
- case "text":
386
- text += event.delta;
387
- broadcast(s, { type: "text", delta: event.delta });
388
- break;
389
- case "textReplace":
390
- text = event.text;
391
- break;
392
- case "thinking":
393
- broadcast(s, { type: "thinking", delta: event.delta });
394
- break;
395
- case "toolStart": {
396
- const call = { name: event.tool, target: event.target };
397
- byId.set(event.id, call);
398
- toolCalls.push(call);
399
- broadcast(s, { type: "tool_use", tool: event.tool, target: event.target });
400
- break;
401
- }
402
- case "toolEnd": {
403
- const call = byId.get(event.id);
404
- if (call) {
405
- call.output = event.output;
406
- call.isError = event.isError;
407
- }
408
- const item = {
409
- type: "tool_result",
410
- tool: event.tool,
411
- output: event.output,
412
- isError: event.isError
413
- };
414
- if (typeof event.display === "string") {
415
- item.display = event.display.length > DISPLAY_MAX ? `${event.display.slice(0, DISPLAY_MAX)}
416
- [truncated]` : event.display;
417
- }
418
- broadcast(s, item);
419
- break;
420
- }
421
- case "usage":
422
- usage = { inputTokens: event.inputTokens, outputTokens: event.outputTokens };
423
- broadcast(s, { type: "usage", ...usage });
424
- break;
425
- case "stepLimit":
426
- stopReason = "stepLimit";
427
- break;
428
- case "budgetExceeded":
429
- stopReason = "budgetExceeded";
430
- break;
431
- case "aborted":
432
- stopReason = "aborted";
433
- break;
434
- case "error":
435
- broadcast(s, { type: "error", message: event.message });
436
- break;
437
- default:
438
- break;
439
- }
440
- };
441
- s.rt.agent.onEvent = map;
442
- try {
443
- await s.rt.agent.run(prompt, controller.signal);
444
- } catch {
445
- stopReason = controller.signal.aborted ? "aborted" : stopReason;
446
- } finally {
447
- s.rt.agent.onEvent = () => {
448
- };
449
- s.busy = null;
450
- failPending(s);
451
- broadcast(s, { type: "result", text, toolCalls, usage, stopReason });
452
- }
453
- }
454
- function findSessionFile(id, forCwd) {
455
- const hit = Session.list(home, forCwd).find(({ session }) => basename(session.file, ".jsonl") === id);
456
- return hit ? { session: hit.session, cwd: forCwd } : void 0;
457
- }
458
- function buildRuntime(forCwd, permissionMode, model) {
459
- return createRuntime({
460
- cwd: forCwd,
461
- args: { ...parseCliArgs([]), allowAll: permissionMode === "bypassPermissions", model },
462
- env,
463
- provider: opts.provider,
464
- fetch: opts.fetch
465
- });
466
- }
467
- function openSession(forCwd, permissionMode, model) {
468
- return registerSession(buildRuntime(forCwd, permissionMode, model), forCwd, permissionMode);
469
- }
470
- function attachSession(id, forCwd) {
471
- const found = findSessionFile(id, forCwd);
472
- if (!found) return void 0;
473
- const rt = buildRuntime(found.cwd, "ask", found.session.lastModel());
474
- rt.resume(found.session);
475
- return registerSession(rt, found.cwd, "ask", id);
476
- }
477
- function registerSession(rt, forCwd, permissionMode, id) {
478
- const sessionId = id ?? basename(rt.session.file, ".jsonl");
479
- const s = {
480
- id: sessionId,
481
- cwd: forCwd,
482
- rt,
483
- permissionMode,
484
- busy: null,
485
- streams: /* @__PURE__ */ new Set(),
486
- pending: /* @__PURE__ */ new Map()
487
- };
488
- if (permissionMode === "bypassPermissions") {
489
- rt.agent.askPermission = async () => "yes";
490
- } else {
491
- rt.agent.askPermission = (req) => new Promise((resolve) => {
492
- const requestId = randomBytes(6).toString("hex");
493
- s.pending.set(requestId, { resolve, req });
494
- broadcast(s, {
495
- type: "approval_request",
496
- requestId,
497
- tool: req.tool,
498
- kind: req.kind,
499
- target: req.target,
500
- preview: req.preview
501
- });
502
- });
503
- }
504
- sessions.set(sessionId, s);
505
- return s;
506
- }
507
- function failPending(s) {
508
- for (const [, p] of s.pending) p.resolve("no");
509
- s.pending.clear();
510
- }
511
- await new Promise(
512
- (resolve) => server.listen(opts.port ?? 8787, host === "localhost" ? "127.0.0.1" : host, resolve)
513
- );
514
- const address = server.address();
515
- const port = typeof address === "object" && address ? address.port : 0;
516
- return {
517
- port,
518
- token,
519
- url: `http://127.0.0.1:${port}`,
520
- close: () => new Promise((resolve) => {
521
- for (const s of sessions.values()) failPending(s);
522
- for (const ws of allSockets) ws.terminate();
523
- allSockets.clear();
524
- server.closeAllConnections();
525
- server.close(() => resolve());
526
- })
527
- };
528
- }
529
- export {
530
- serveBanner,
531
- startServe
532
- };