noumen 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +767 -51
  2. package/dist/a2a/index.d.ts +148 -0
  3. package/dist/a2a/index.js +579 -0
  4. package/dist/a2a/index.js.map +1 -0
  5. package/dist/acp/index.d.ts +129 -0
  6. package/dist/acp/index.js +498 -0
  7. package/dist/acp/index.js.map +1 -0
  8. package/dist/agent-BrkbZyOT.d.ts +1028 -0
  9. package/dist/cache-DVqaCX8v.d.ts +38 -0
  10. package/dist/chunk-2ZTGQLYK.js +356 -0
  11. package/dist/chunk-2ZTGQLYK.js.map +1 -0
  12. package/dist/chunk-42PHHZUA.js +132 -0
  13. package/dist/chunk-42PHHZUA.js.map +1 -0
  14. package/dist/chunk-4SQA2UCV.js +26 -0
  15. package/dist/chunk-4SQA2UCV.js.map +1 -0
  16. package/dist/chunk-5GEX6ZSB.js +179 -0
  17. package/dist/chunk-5GEX6ZSB.js.map +1 -0
  18. package/dist/chunk-7ZMN7XJE.js +94 -0
  19. package/dist/chunk-7ZMN7XJE.js.map +1 -0
  20. package/dist/chunk-AMYIJSAZ.js +57 -0
  21. package/dist/chunk-AMYIJSAZ.js.map +1 -0
  22. package/dist/chunk-BGG2E6JD.js +10 -0
  23. package/dist/chunk-BGG2E6JD.js.map +1 -0
  24. package/dist/chunk-BZSFUEWM.js +43 -0
  25. package/dist/chunk-BZSFUEWM.js.map +1 -0
  26. package/dist/chunk-CPFHEPW4.js +139 -0
  27. package/dist/chunk-CPFHEPW4.js.map +1 -0
  28. package/dist/chunk-D43BWEZA.js +346 -0
  29. package/dist/chunk-D43BWEZA.js.map +1 -0
  30. package/dist/chunk-DGUM43GV.js +11 -0
  31. package/dist/chunk-DGUM43GV.js.map +1 -0
  32. package/dist/chunk-JACGEMTF.js +43 -0
  33. package/dist/chunk-JACGEMTF.js.map +1 -0
  34. package/dist/chunk-JX7CLUCV.js +21 -0
  35. package/dist/chunk-JX7CLUCV.js.map +1 -0
  36. package/dist/chunk-KXDB56YW.js +39 -0
  37. package/dist/chunk-KXDB56YW.js.map +1 -0
  38. package/dist/chunk-KY6ZPWHO.js +112 -0
  39. package/dist/chunk-KY6ZPWHO.js.map +1 -0
  40. package/dist/chunk-NBDFQYUZ.js +7992 -0
  41. package/dist/chunk-NBDFQYUZ.js.map +1 -0
  42. package/dist/chunk-OGXNFXFA.js +196 -0
  43. package/dist/chunk-OGXNFXFA.js.map +1 -0
  44. package/dist/chunk-QTJ7VTJY.js +1994 -0
  45. package/dist/chunk-QTJ7VTJY.js.map +1 -0
  46. package/dist/chunk-UVSSQBDY.js +192 -0
  47. package/dist/chunk-UVSSQBDY.js.map +1 -0
  48. package/dist/chunk-Y45R3PQL.js +684 -0
  49. package/dist/chunk-Y45R3PQL.js.map +1 -0
  50. package/dist/cli/index.d.ts +1 -0
  51. package/dist/cli/index.js +868 -0
  52. package/dist/cli/index.js.map +1 -0
  53. package/dist/client/index.d.ts +64 -0
  54. package/dist/client/index.js +409 -0
  55. package/dist/client/index.js.map +1 -0
  56. package/dist/client-CRRO2376.js +10 -0
  57. package/dist/client-CRRO2376.js.map +1 -0
  58. package/dist/headless-Q7XHHZIW.js +143 -0
  59. package/dist/headless-Q7XHHZIW.js.map +1 -0
  60. package/dist/history-snip-64GYP4ZL.js +12 -0
  61. package/dist/history-snip-64GYP4ZL.js.map +1 -0
  62. package/dist/index.d.ts +1305 -418
  63. package/dist/index.js +384 -1757
  64. package/dist/index.js.map +1 -1
  65. package/dist/jsonrpc/index.d.ts +54 -0
  66. package/dist/jsonrpc/index.js +34 -0
  67. package/dist/jsonrpc/index.js.map +1 -0
  68. package/dist/lsp/index.d.ts +36 -0
  69. package/dist/lsp/index.js +16 -0
  70. package/dist/lsp/index.js.map +1 -0
  71. package/dist/lsp-PS3BWIHC.js +8 -0
  72. package/dist/lsp-PS3BWIHC.js.map +1 -0
  73. package/dist/manager-DLXK63XC.js +8 -0
  74. package/dist/manager-DLXK63XC.js.map +1 -0
  75. package/dist/mcp/index.d.ts +111 -0
  76. package/dist/mcp/index.js +104 -0
  77. package/dist/mcp/index.js.map +1 -0
  78. package/dist/mcp-auth-AEI2R4ZC.js +9 -0
  79. package/dist/mcp-auth-AEI2R4ZC.js.map +1 -0
  80. package/dist/ollama-YNXAYP3R.js +18 -0
  81. package/dist/ollama-YNXAYP3R.js.map +1 -0
  82. package/dist/provider-factory-34MSWJZ3.js +20 -0
  83. package/dist/provider-factory-34MSWJZ3.js.map +1 -0
  84. package/dist/providers/anthropic.d.ts +19 -0
  85. package/dist/providers/anthropic.js +33 -0
  86. package/dist/providers/anthropic.js.map +1 -0
  87. package/dist/providers/bedrock.d.ts +39 -0
  88. package/dist/providers/bedrock.js +54 -0
  89. package/dist/providers/bedrock.js.map +1 -0
  90. package/dist/providers/gemini.d.ts +16 -0
  91. package/dist/providers/gemini.js +224 -0
  92. package/dist/providers/gemini.js.map +1 -0
  93. package/dist/providers/openai.d.ts +18 -0
  94. package/dist/providers/openai.js +8 -0
  95. package/dist/providers/openai.js.map +1 -0
  96. package/dist/providers/openrouter.d.ts +16 -0
  97. package/dist/providers/openrouter.js +23 -0
  98. package/dist/providers/openrouter.js.map +1 -0
  99. package/dist/providers/vertex.d.ts +40 -0
  100. package/dist/providers/vertex.js +64 -0
  101. package/dist/providers/vertex.js.map +1 -0
  102. package/dist/render-GRN4ZSSW.js +14 -0
  103. package/dist/render-GRN4ZSSW.js.map +1 -0
  104. package/dist/resolve-XM52G7YE.js +14 -0
  105. package/dist/resolve-XM52G7YE.js.map +1 -0
  106. package/dist/server/index.d.ts +128 -0
  107. package/dist/server/index.js +626 -0
  108. package/dist/server/index.js.map +1 -0
  109. package/dist/server-Cg1yWGaV.d.ts +96 -0
  110. package/dist/spinner-OJNR6NFO.js +8 -0
  111. package/dist/spinner-OJNR6NFO.js.map +1 -0
  112. package/dist/types-2kTLUCnD.d.ts +107 -0
  113. package/dist/types-3c88cRKH.d.ts +547 -0
  114. package/dist/types-CwKKucOF.d.ts +620 -0
  115. package/dist/types-DwdzmXfs.d.ts +107 -0
  116. package/dist/types-NIyVwQ4h.d.ts +109 -0
  117. package/dist/types-QwfylltH.d.ts +71 -0
  118. package/package.json +134 -6
@@ -0,0 +1,626 @@
1
+ import "../chunk-DGUM43GV.js";
2
+
3
+ // src/server/index.ts
4
+ import { createServer as createHttpServer } from "http";
5
+ import { randomUUID } from "crypto";
6
+ var SSE_KEEPALIVE_INTERVAL_MS = 15e3;
7
+ var MAX_BODY_BYTES = 1048576;
8
+ var MAX_EVENT_BUFFER = 1e3;
9
+ var DEFAULT_PENDING_TIMEOUT_MS = 12e4;
10
+ var WS_PING_INTERVAL_MS = 3e4;
11
+ var SHUTDOWN_DRAIN_MS = 500;
12
+ var NoumenServer = class {
13
+ code;
14
+ options;
15
+ httpServer = null;
16
+ wss = null;
17
+ sessions = /* @__PURE__ */ new Map();
18
+ idleTimer = null;
19
+ constructor(code, options) {
20
+ this.code = code;
21
+ this.options = options;
22
+ }
23
+ async start() {
24
+ this.httpServer = createHttpServer((req, res) => this.handleRequest(req, res));
25
+ if (this.options.ws !== false) {
26
+ await this.initWebSocket();
27
+ }
28
+ this.ensureIdleReaper();
29
+ return new Promise((resolve, reject) => {
30
+ const host = this.options.host ?? "0.0.0.0";
31
+ this.httpServer.listen(this.options.port, host, () => resolve());
32
+ this.httpServer.once("error", reject);
33
+ });
34
+ }
35
+ async stop() {
36
+ if (this.idleTimer) {
37
+ clearInterval(this.idleTimer);
38
+ this.idleTimer = null;
39
+ }
40
+ for (const session of this.sessions.values()) {
41
+ session.abortController.abort();
42
+ }
43
+ await new Promise((resolve) => setTimeout(resolve, SHUTDOWN_DRAIN_MS));
44
+ for (const session of this.sessions.values()) {
45
+ this.destroySession(session);
46
+ }
47
+ if (this.wss) {
48
+ await new Promise((resolve) => this.wss.close(() => resolve()));
49
+ this.wss = null;
50
+ }
51
+ if (this.httpServer) {
52
+ if (typeof this.httpServer.closeAllConnections === "function") {
53
+ this.httpServer.closeAllConnections();
54
+ }
55
+ await new Promise(
56
+ (resolve, reject) => this.httpServer.close((err) => err ? reject(err) : resolve())
57
+ );
58
+ this.httpServer = null;
59
+ }
60
+ }
61
+ getActiveSessions() {
62
+ const result = /* @__PURE__ */ new Map();
63
+ for (const [id, s] of this.sessions) {
64
+ result.set(id, { id: s.id, lastActivity: s.lastActivity, done: s.done });
65
+ }
66
+ return result;
67
+ }
68
+ // -------------------------------------------------------------------------
69
+ // WebSocket setup
70
+ // -------------------------------------------------------------------------
71
+ async initWebSocket() {
72
+ let WsServerCtor;
73
+ try {
74
+ const ws = await import("ws");
75
+ WsServerCtor = ws.WebSocketServer ?? ws.default?.WebSocketServer;
76
+ } catch {
77
+ throw new Error(
78
+ "noumen/server: WebSocket support requires the 'ws' package. Install it with: npm install ws\nOr disable WebSocket with { ws: false } in ServerOptions."
79
+ );
80
+ }
81
+ this.wss = new WsServerCtor({ server: this.httpServer });
82
+ this.wss.on("connection", (ws, req) => {
83
+ this.handleWsConnection(ws, req).catch(
84
+ (err) => this.options.onError?.(err instanceof Error ? err : new Error(String(err)))
85
+ );
86
+ });
87
+ }
88
+ /**
89
+ * Handle an HTTP request. Used internally by `start()` and exposed for
90
+ * `createRequestHandler()` so the same logic can be mounted on an
91
+ * external Express / Fastify / Hono server.
92
+ */
93
+ async handleRequest(req, res) {
94
+ this.ensureIdleReaper();
95
+ return this.handleHttp(req, res).catch((err) => {
96
+ this.options.onError?.(err instanceof Error ? err : new Error(String(err)));
97
+ if (!res.headersSent) jsonResponse(res, 500, { error: "Internal server error" });
98
+ });
99
+ }
100
+ idleReaperStarted = false;
101
+ ensureIdleReaper() {
102
+ if (this.idleReaperStarted || !this.options.idleTimeoutMs) return;
103
+ this.idleReaperStarted = true;
104
+ const interval = Math.max(this.options.idleTimeoutMs / 2, 1e3);
105
+ this.idleTimer = setInterval(() => this.reapIdleSessions(), interval);
106
+ this.idleTimer.unref();
107
+ }
108
+ // -------------------------------------------------------------------------
109
+ // HTTP routing
110
+ // -------------------------------------------------------------------------
111
+ async handleHttp(req, res) {
112
+ if (this.options.cors !== false) {
113
+ res.setHeader("Access-Control-Allow-Origin", "*");
114
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
115
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Last-Event-ID");
116
+ }
117
+ const method = req.method ?? "GET";
118
+ if (method === "OPTIONS") {
119
+ res.writeHead(204);
120
+ res.end();
121
+ return;
122
+ }
123
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
124
+ const path = url.pathname;
125
+ if (path === "/health" && method === "GET") {
126
+ return jsonResponse(res, 200, { status: "ok", sessions: this.sessions.size });
127
+ }
128
+ if (this.options.auth) {
129
+ const authResult = await this.authenticate(req);
130
+ if (!authResult) {
131
+ return jsonResponse(res, 401, { error: "Unauthorized" });
132
+ }
133
+ }
134
+ if (path === "/sessions" && method === "POST") {
135
+ return this.handleCreateSession(req, res);
136
+ }
137
+ if (path === "/sessions" && method === "GET") {
138
+ return this.handleListSessions(res);
139
+ }
140
+ const sessionMatch = path.match(/^\/sessions\/([^/]+)(?:\/(.+))?$/);
141
+ if (sessionMatch) {
142
+ const sessionId = sessionMatch[1];
143
+ const sub = sessionMatch[2] ?? "";
144
+ if (sub === "events" && method === "GET") return this.handleSseStream(sessionId, req, res);
145
+ if (sub === "permissions" && method === "POST") return this.handlePermissionResponse(sessionId, req, res);
146
+ if (sub === "input" && method === "POST") return this.handleInputResponse(sessionId, req, res);
147
+ if (sub === "messages" && method === "POST") return this.handleSendMessage(sessionId, req, res);
148
+ if (sub === "" && method === "DELETE") return this.handleDeleteSession(sessionId, res);
149
+ }
150
+ jsonResponse(res, 404, { error: "Not found" });
151
+ }
152
+ // -------------------------------------------------------------------------
153
+ // Auth
154
+ // -------------------------------------------------------------------------
155
+ async authenticate(req) {
156
+ const auth = this.options.auth;
157
+ if (!auth) return {};
158
+ if (auth.type === "bearer") {
159
+ const header = req.headers.authorization;
160
+ if (header === `Bearer ${auth.token}`) return {};
161
+ return null;
162
+ }
163
+ return auth.verify(req);
164
+ }
165
+ // -------------------------------------------------------------------------
166
+ // REST handlers
167
+ // -------------------------------------------------------------------------
168
+ async handleCreateSession(req, res) {
169
+ const body = await readBody(req);
170
+ const { prompt, sessionId: requestedId } = body;
171
+ if (!prompt || typeof prompt !== "string") {
172
+ return jsonResponse(res, 400, { error: "Missing required field: prompt" });
173
+ }
174
+ if (this.options.maxSessions && this.sessions.size >= this.options.maxSessions) {
175
+ return jsonResponse(res, 429, { error: "Maximum sessions reached" });
176
+ }
177
+ const overrides = await this.resolveConnectionOverrides(req);
178
+ const session = this.createSessionState(requestedId, overrides);
179
+ this.runAgentSse(session, prompt, false);
180
+ jsonResponse(res, 201, {
181
+ sessionId: session.id,
182
+ eventsUrl: `/sessions/${session.id}/events`
183
+ });
184
+ }
185
+ handleListSessions(res) {
186
+ const sessions = Array.from(this.sessions.values()).map((s) => ({
187
+ id: s.id,
188
+ lastActivity: s.lastActivity,
189
+ done: s.done
190
+ }));
191
+ jsonResponse(res, 200, sessions);
192
+ }
193
+ handleSseStream(sessionId, req, res) {
194
+ const session = this.sessions.get(sessionId);
195
+ if (!session) return jsonResponse(res, 404, { error: "Session not found" });
196
+ if (session.sseResponse) {
197
+ const oldRes = session.sseResponse;
198
+ writeSseEventRaw(oldRes, session.sequenceNum + 1, { type: "subscriber_replaced" });
199
+ oldRes.end();
200
+ this.clearSseKeepalive(session);
201
+ session.sseResponse = null;
202
+ }
203
+ res.writeHead(200, {
204
+ "Content-Type": "text/event-stream",
205
+ "Cache-Control": "no-cache",
206
+ "Connection": "keep-alive",
207
+ "X-Accel-Buffering": "no"
208
+ });
209
+ const lastEventId = req.headers["last-event-id"];
210
+ const resumeAfterSeq = lastEventId ? parseInt(lastEventId, 10) : 0;
211
+ for (const buffered of session.eventBuffer) {
212
+ if (resumeAfterSeq && buffered.seq <= resumeAfterSeq) continue;
213
+ writeSseEventRaw(res, buffered.seq, serializeEvent(buffered.event));
214
+ }
215
+ session.eventBuffer = [];
216
+ session.sseResponse = res;
217
+ this.startSseKeepalive(session);
218
+ res.on("close", () => {
219
+ if (session.sseResponse === res) {
220
+ this.clearSseKeepalive(session);
221
+ session.sseResponse = null;
222
+ }
223
+ });
224
+ }
225
+ async handlePermissionResponse(sessionId, req, res) {
226
+ const session = this.sessions.get(sessionId);
227
+ if (!session) return jsonResponse(res, 404, { error: "Session not found" });
228
+ if (!session.pendingPermission) return jsonResponse(res, 409, { error: "No pending permission request" });
229
+ const body = await readBody(req);
230
+ this.clearPendingPermissionTimer(session);
231
+ session.pendingPermission.resolve(body);
232
+ session.pendingPermission = null;
233
+ jsonResponse(res, 200, { ok: true });
234
+ }
235
+ async handleInputResponse(sessionId, req, res) {
236
+ const session = this.sessions.get(sessionId);
237
+ if (!session) return jsonResponse(res, 404, { error: "Session not found" });
238
+ if (!session.pendingInput) return jsonResponse(res, 409, { error: "No pending input request" });
239
+ const body = await readBody(req);
240
+ if (typeof body.answer !== "string") {
241
+ return jsonResponse(res, 400, { error: "Missing required field: answer" });
242
+ }
243
+ this.clearPendingInputTimer(session);
244
+ session.pendingInput.resolve(body.answer);
245
+ session.pendingInput = null;
246
+ jsonResponse(res, 200, { ok: true });
247
+ }
248
+ async handleSendMessage(sessionId, req, res) {
249
+ const session = this.sessions.get(sessionId);
250
+ if (!session) return jsonResponse(res, 404, { error: "Session not found" });
251
+ if (!session.done) return jsonResponse(res, 409, { error: "Session is still running" });
252
+ const body = await readBody(req);
253
+ if (!body.prompt || typeof body.prompt !== "string") {
254
+ return jsonResponse(res, 400, { error: "Missing required field: prompt" });
255
+ }
256
+ session.done = false;
257
+ session.abortController = new AbortController();
258
+ this.runAgentSse(session, body.prompt, true);
259
+ jsonResponse(res, 200, { ok: true });
260
+ }
261
+ handleDeleteSession(sessionId, res) {
262
+ const session = this.sessions.get(sessionId);
263
+ if (!session) return jsonResponse(res, 404, { error: "Session not found" });
264
+ this.destroySession(session);
265
+ jsonResponse(res, 200, { ok: true });
266
+ }
267
+ // -------------------------------------------------------------------------
268
+ // WebSocket handling
269
+ // -------------------------------------------------------------------------
270
+ async handleWsConnection(ws, req) {
271
+ if (this.options.auth) {
272
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
273
+ const tokenParam = url.searchParams.get("token");
274
+ if (tokenParam && this.options.auth.type === "bearer") {
275
+ if (tokenParam !== this.options.auth.token) {
276
+ ws.close();
277
+ return;
278
+ }
279
+ } else {
280
+ const authResult = await this.authenticate(req);
281
+ if (!authResult) {
282
+ ws.close();
283
+ return;
284
+ }
285
+ }
286
+ }
287
+ const wsSessions = /* @__PURE__ */ new Set();
288
+ let pongReceived = true;
289
+ const pingTimer = setInterval(() => {
290
+ if (!pongReceived) {
291
+ ws.close();
292
+ return;
293
+ }
294
+ pongReceived = false;
295
+ try {
296
+ ws.ping();
297
+ } catch {
298
+ }
299
+ }, WS_PING_INTERVAL_MS);
300
+ ws.on("pong", () => {
301
+ pongReceived = true;
302
+ });
303
+ ws.on("message", async (raw) => {
304
+ try {
305
+ const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString());
306
+ await this.handleWsMessage(ws, msg, wsSessions, req);
307
+ } catch (err) {
308
+ wsSend(ws, { type: "error", error: String(err) });
309
+ }
310
+ });
311
+ ws.on("close", () => {
312
+ clearInterval(pingTimer);
313
+ for (const sid of wsSessions) {
314
+ const session = this.sessions.get(sid);
315
+ if (session) this.destroySession(session);
316
+ }
317
+ });
318
+ ws.on("error", () => {
319
+ clearInterval(pingTimer);
320
+ });
321
+ }
322
+ async handleWsMessage(ws, msg, wsSessions, req) {
323
+ const msgType = msg.type;
324
+ if (msgType === "run") {
325
+ if (this.options.maxSessions && this.sessions.size >= this.options.maxSessions) {
326
+ wsSend(ws, { type: "error", error: "Maximum sessions reached" });
327
+ return;
328
+ }
329
+ const overrides = await this.resolveConnectionOverrides(req);
330
+ const session = this.createSessionState(msg.sessionId, overrides);
331
+ wsSessions.add(session.id);
332
+ wsSend(ws, { type: "session_created", sessionId: session.id });
333
+ this.runAgentWs(session, msg.prompt, ws, false);
334
+ return;
335
+ }
336
+ if (msgType === "message") {
337
+ const session = this.sessions.get(msg.sessionId);
338
+ if (!session) {
339
+ wsSend(ws, { type: "error", error: "Session not found" });
340
+ return;
341
+ }
342
+ if (!session.done) {
343
+ wsSend(ws, { type: "error", error: "Session is still running" });
344
+ return;
345
+ }
346
+ session.done = false;
347
+ session.abortController = new AbortController();
348
+ this.runAgentWs(session, msg.prompt, ws, true);
349
+ return;
350
+ }
351
+ if (msgType === "permission_response") {
352
+ const session = this.sessions.get(msg.sessionId);
353
+ if (!session?.pendingPermission) return;
354
+ this.clearPendingPermissionTimer(session);
355
+ const { sessionId: _sid, type: _type, ...response } = msg;
356
+ session.pendingPermission.resolve(response);
357
+ session.pendingPermission = null;
358
+ return;
359
+ }
360
+ if (msgType === "input_response") {
361
+ const session = this.sessions.get(msg.sessionId);
362
+ if (!session?.pendingInput) return;
363
+ this.clearPendingInputTimer(session);
364
+ session.pendingInput.resolve(msg.answer ?? "");
365
+ session.pendingInput = null;
366
+ return;
367
+ }
368
+ if (msgType === "abort") {
369
+ const session = this.sessions.get(msg.sessionId);
370
+ if (session) this.destroySession(session);
371
+ }
372
+ }
373
+ // -------------------------------------------------------------------------
374
+ // Session management
375
+ // -------------------------------------------------------------------------
376
+ createSessionState(requestedId, overrides) {
377
+ const sessionId = requestedId ?? randomUUID();
378
+ const session = {
379
+ id: sessionId,
380
+ abortController: new AbortController(),
381
+ pendingPermission: null,
382
+ pendingInput: null,
383
+ pendingPermissionTimer: null,
384
+ pendingInputTimer: null,
385
+ lastActivity: Date.now(),
386
+ sseResponse: null,
387
+ sseKeepaliveTimer: null,
388
+ eventBuffer: [],
389
+ sequenceNum: 0,
390
+ done: false,
391
+ cwd: overrides.cwd
392
+ };
393
+ this.sessions.set(sessionId, session);
394
+ return session;
395
+ }
396
+ makeThread(session, resume) {
397
+ const handlers = {
398
+ cwd: session.cwd,
399
+ permissionHandler: (req) => this.bridgePermission(session.id, req),
400
+ userInputHandler: (q) => this.bridgeUserInput(session.id, q)
401
+ };
402
+ return resume ? this.code.resumeThread(session.id, handlers) : this.code.createThread({ sessionId: session.id, ...handlers });
403
+ }
404
+ runAgentSse(session, prompt, resume) {
405
+ const run = async () => {
406
+ try {
407
+ const thread = this.makeThread(session, resume);
408
+ for await (const event of thread.run(prompt, { signal: session.abortController.signal })) {
409
+ this.emitSseEvent(session, event);
410
+ session.lastActivity = Date.now();
411
+ }
412
+ } catch (err) {
413
+ if (err.name !== "AbortError") {
414
+ this.emitSseEvent(session, {
415
+ type: "error",
416
+ error: err instanceof Error ? err : new Error(String(err))
417
+ });
418
+ }
419
+ } finally {
420
+ session.done = true;
421
+ }
422
+ };
423
+ run().catch((err) => this.options.onError?.(err instanceof Error ? err : new Error(String(err))));
424
+ }
425
+ runAgentWs(session, prompt, ws, resume) {
426
+ const run = async () => {
427
+ try {
428
+ const thread = this.makeThread(session, resume);
429
+ for await (const event of thread.run(prompt, { signal: session.abortController.signal })) {
430
+ session.sequenceNum++;
431
+ wsSend(ws, { ...serializeEvent(event), sessionId: session.id, seq: session.sequenceNum });
432
+ session.lastActivity = Date.now();
433
+ }
434
+ } catch (err) {
435
+ if (err.name !== "AbortError") {
436
+ wsSend(ws, { type: "error", sessionId: session.id, error: String(err) });
437
+ }
438
+ } finally {
439
+ session.done = true;
440
+ }
441
+ };
442
+ run().catch((err) => this.options.onError?.(err instanceof Error ? err : new Error(String(err))));
443
+ }
444
+ emitSseEvent(session, event) {
445
+ session.sequenceNum++;
446
+ const seq = session.sequenceNum;
447
+ if (session.sseResponse) {
448
+ writeSseEventRaw(session.sseResponse, seq, serializeEvent(event));
449
+ } else {
450
+ if (session.eventBuffer.length >= MAX_EVENT_BUFFER) {
451
+ session.eventBuffer.shift();
452
+ }
453
+ session.eventBuffer.push({ seq, event });
454
+ }
455
+ }
456
+ bridgePermission(sessionId, _request) {
457
+ const session = this.sessions.get(sessionId);
458
+ if (!session) return Promise.reject(new Error("Session not found"));
459
+ const timeoutMs = this.options.pendingTimeoutMs ?? DEFAULT_PENDING_TIMEOUT_MS;
460
+ return new Promise((resolve, reject) => {
461
+ session.pendingPermission = { resolve, reject };
462
+ session.pendingPermissionTimer = setTimeout(() => {
463
+ session.pendingPermissionTimer = null;
464
+ if (session.pendingPermission) {
465
+ session.pendingPermission.reject(new Error("Permission request timed out"));
466
+ session.pendingPermission = null;
467
+ }
468
+ }, timeoutMs);
469
+ });
470
+ }
471
+ bridgeUserInput(sessionId, _question) {
472
+ const session = this.sessions.get(sessionId);
473
+ if (!session) return Promise.reject(new Error("Session not found"));
474
+ const timeoutMs = this.options.pendingTimeoutMs ?? DEFAULT_PENDING_TIMEOUT_MS;
475
+ return new Promise((resolve, reject) => {
476
+ session.pendingInput = { resolve, reject };
477
+ session.pendingInputTimer = setTimeout(() => {
478
+ session.pendingInputTimer = null;
479
+ if (session.pendingInput) {
480
+ session.pendingInput.reject(new Error("User input request timed out"));
481
+ session.pendingInput = null;
482
+ }
483
+ }, timeoutMs);
484
+ });
485
+ }
486
+ startSseKeepalive(session) {
487
+ this.clearSseKeepalive(session);
488
+ session.sseKeepaliveTimer = setInterval(() => {
489
+ if (session.sseResponse && !session.sseResponse.destroyed) {
490
+ session.sseResponse.write(":keepalive\n\n");
491
+ }
492
+ }, SSE_KEEPALIVE_INTERVAL_MS);
493
+ session.sseKeepaliveTimer.unref();
494
+ }
495
+ clearSseKeepalive(session) {
496
+ if (session.sseKeepaliveTimer) {
497
+ clearInterval(session.sseKeepaliveTimer);
498
+ session.sseKeepaliveTimer = null;
499
+ }
500
+ }
501
+ clearPendingPermissionTimer(session) {
502
+ if (session.pendingPermissionTimer) {
503
+ clearTimeout(session.pendingPermissionTimer);
504
+ session.pendingPermissionTimer = null;
505
+ }
506
+ }
507
+ clearPendingInputTimer(session) {
508
+ if (session.pendingInputTimer) {
509
+ clearTimeout(session.pendingInputTimer);
510
+ session.pendingInputTimer = null;
511
+ }
512
+ }
513
+ destroySession(session) {
514
+ session.abortController.abort();
515
+ this.clearSseKeepalive(session);
516
+ this.clearPendingPermissionTimer(session);
517
+ this.clearPendingInputTimer(session);
518
+ if (session.pendingPermission) {
519
+ session.pendingPermission.reject(new Error("Session aborted"));
520
+ session.pendingPermission = null;
521
+ }
522
+ if (session.pendingInput) {
523
+ session.pendingInput.reject(new Error("Session aborted"));
524
+ session.pendingInput = null;
525
+ }
526
+ if (session.sseResponse) {
527
+ session.sseResponse.end();
528
+ session.sseResponse = null;
529
+ }
530
+ this.sessions.delete(session.id);
531
+ }
532
+ reapIdleSessions() {
533
+ const timeout = this.options.idleTimeoutMs;
534
+ if (!timeout) return;
535
+ const now = Date.now();
536
+ for (const session of this.sessions.values()) {
537
+ if (now - session.lastActivity > timeout) {
538
+ this.destroySession(session);
539
+ }
540
+ }
541
+ }
542
+ async resolveConnectionOverrides(req) {
543
+ if (!this.options.onConnection) return {};
544
+ const auth = await this.authenticate(req) ?? {};
545
+ return this.options.onConnection({ auth, remoteAddress: req.socket.remoteAddress });
546
+ }
547
+ };
548
+ function createServer(code, options) {
549
+ return new NoumenServer(code, options);
550
+ }
551
+ function createRequestHandler(code, options) {
552
+ const serverOpts = {
553
+ port: 0,
554
+ ws: false,
555
+ ...options
556
+ };
557
+ const server = new NoumenServer(code, serverOpts);
558
+ return (req, res) => {
559
+ server.handleRequest(req, res);
560
+ };
561
+ }
562
+ function jsonResponse(res, status, body) {
563
+ const json = JSON.stringify(body);
564
+ res.writeHead(status, {
565
+ "Content-Type": "application/json",
566
+ "Content-Length": Buffer.byteLength(json)
567
+ });
568
+ res.end(json);
569
+ }
570
+ function serializeEvent(event) {
571
+ if (event.type === "error") {
572
+ return { type: "error", error: { message: event.error.message, name: event.error.name } };
573
+ }
574
+ if (event.type === "retry_exhausted") {
575
+ return { ...event, error: { message: event.error.message, name: event.error.name } };
576
+ }
577
+ if (event.type === "retry_attempt") {
578
+ return { ...event, error: { message: event.error.message, name: event.error.name } };
579
+ }
580
+ return event;
581
+ }
582
+ function writeSseEventRaw(res, seq, data) {
583
+ res.write(`id: ${seq}
584
+ data: ${JSON.stringify(data)}
585
+
586
+ `);
587
+ }
588
+ function readBody(req) {
589
+ return new Promise((resolve, reject) => {
590
+ let totalBytes = 0;
591
+ let rejected = false;
592
+ const chunks = [];
593
+ req.on("data", (chunk) => {
594
+ if (rejected) return;
595
+ totalBytes += chunk.length;
596
+ if (totalBytes > MAX_BODY_BYTES) {
597
+ rejected = true;
598
+ req.destroy();
599
+ reject(new Error("Request body too large"));
600
+ return;
601
+ }
602
+ chunks.push(chunk);
603
+ });
604
+ req.on("end", () => {
605
+ if (rejected) return;
606
+ try {
607
+ const raw = Buffer.concat(chunks).toString();
608
+ resolve(raw ? JSON.parse(raw) : {});
609
+ } catch (err) {
610
+ reject(err);
611
+ }
612
+ });
613
+ req.on("error", (err) => {
614
+ if (!rejected) reject(err);
615
+ });
616
+ });
617
+ }
618
+ function wsSend(ws, data) {
619
+ if (ws.readyState === 1) ws.send(JSON.stringify(data));
620
+ }
621
+ export {
622
+ NoumenServer,
623
+ createRequestHandler,
624
+ createServer
625
+ };
626
+ //# sourceMappingURL=index.js.map