pi-forge 0.0.0 → 1.1.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -4
  3. package/bin/pi-forge.mjs +37 -0
  4. package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js +34 -0
  5. package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js.map +1 -0
  6. package/dist/client/assets/index-B-529kgJ.css +32 -0
  7. package/dist/client/assets/index-BzKzxXFs.js +392 -0
  8. package/dist/client/assets/index-BzKzxXFs.js.map +1 -0
  9. package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js +3 -0
  10. package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js.map +1 -0
  11. package/dist/client/icons/icon-192.png +0 -0
  12. package/dist/client/icons/icon-512.png +0 -0
  13. package/dist/client/icons/icon-maskable-512.png +0 -0
  14. package/dist/client/icons/icon.svg +9 -0
  15. package/dist/client/index.html +24 -0
  16. package/dist/client/manifest.webmanifest +1 -0
  17. package/dist/client/offline.html +142 -0
  18. package/dist/client/sw.js +3 -0
  19. package/dist/client/sw.js.map +1 -0
  20. package/dist/client/workbox-6d7155ed.js +3 -0
  21. package/dist/client/workbox-6d7155ed.js.map +1 -0
  22. package/dist/server/agent-resource-loader.js +126 -0
  23. package/dist/server/agent-resource-loader.js.map +1 -0
  24. package/dist/server/attachment-converters.js +96 -0
  25. package/dist/server/attachment-converters.js.map +1 -0
  26. package/dist/server/auth.js +209 -0
  27. package/dist/server/auth.js.map +1 -0
  28. package/dist/server/compaction-history.js +106 -0
  29. package/dist/server/compaction-history.js.map +1 -0
  30. package/dist/server/concurrency.js +49 -0
  31. package/dist/server/concurrency.js.map +1 -0
  32. package/dist/server/config-export.js +220 -0
  33. package/dist/server/config-export.js.map +1 -0
  34. package/dist/server/config-manager.js +528 -0
  35. package/dist/server/config-manager.js.map +1 -0
  36. package/dist/server/config.js +326 -0
  37. package/dist/server/config.js.map +1 -0
  38. package/dist/server/conversion-worker.mjs +90 -0
  39. package/dist/server/diagnostics.js +137 -0
  40. package/dist/server/diagnostics.js.map +1 -0
  41. package/dist/server/extensions-discovery.js +147 -0
  42. package/dist/server/extensions-discovery.js.map +1 -0
  43. package/dist/server/file-manager.js +734 -0
  44. package/dist/server/file-manager.js.map +1 -0
  45. package/dist/server/file-references.js +215 -0
  46. package/dist/server/file-references.js.map +1 -0
  47. package/dist/server/file-searcher.js +385 -0
  48. package/dist/server/file-searcher.js.map +1 -0
  49. package/dist/server/git-runner.js +684 -0
  50. package/dist/server/git-runner.js.map +1 -0
  51. package/dist/server/index.js +468 -0
  52. package/dist/server/index.js.map +1 -0
  53. package/dist/server/mcp/config.js +133 -0
  54. package/dist/server/mcp/config.js.map +1 -0
  55. package/dist/server/mcp/manager.js +351 -0
  56. package/dist/server/mcp/manager.js.map +1 -0
  57. package/dist/server/mcp/tool-bridge.js +173 -0
  58. package/dist/server/mcp/tool-bridge.js.map +1 -0
  59. package/dist/server/project-manager.js +301 -0
  60. package/dist/server/project-manager.js.map +1 -0
  61. package/dist/server/pty-manager.js +354 -0
  62. package/dist/server/pty-manager.js.map +1 -0
  63. package/dist/server/routes/_schemas.js +73 -0
  64. package/dist/server/routes/_schemas.js.map +1 -0
  65. package/dist/server/routes/auth.js +164 -0
  66. package/dist/server/routes/auth.js.map +1 -0
  67. package/dist/server/routes/config.js +1163 -0
  68. package/dist/server/routes/config.js.map +1 -0
  69. package/dist/server/routes/control.js +464 -0
  70. package/dist/server/routes/control.js.map +1 -0
  71. package/dist/server/routes/exec.js +217 -0
  72. package/dist/server/routes/exec.js.map +1 -0
  73. package/dist/server/routes/files.js +847 -0
  74. package/dist/server/routes/files.js.map +1 -0
  75. package/dist/server/routes/git.js +837 -0
  76. package/dist/server/routes/git.js.map +1 -0
  77. package/dist/server/routes/health.js +97 -0
  78. package/dist/server/routes/health.js.map +1 -0
  79. package/dist/server/routes/mcp.js +300 -0
  80. package/dist/server/routes/mcp.js.map +1 -0
  81. package/dist/server/routes/projects.js +259 -0
  82. package/dist/server/routes/projects.js.map +1 -0
  83. package/dist/server/routes/prompt.js +496 -0
  84. package/dist/server/routes/prompt.js.map +1 -0
  85. package/dist/server/routes/sessions.js +783 -0
  86. package/dist/server/routes/sessions.js.map +1 -0
  87. package/dist/server/routes/stream.js +69 -0
  88. package/dist/server/routes/stream.js.map +1 -0
  89. package/dist/server/routes/terminal.js +335 -0
  90. package/dist/server/routes/terminal.js.map +1 -0
  91. package/dist/server/session-registry.js +1197 -0
  92. package/dist/server/session-registry.js.map +1 -0
  93. package/dist/server/skill-overrides.js +151 -0
  94. package/dist/server/skill-overrides.js.map +1 -0
  95. package/dist/server/skills-export.js +257 -0
  96. package/dist/server/skills-export.js.map +1 -0
  97. package/dist/server/sse-bridge.js +220 -0
  98. package/dist/server/sse-bridge.js.map +1 -0
  99. package/dist/server/tool-overrides.js +277 -0
  100. package/dist/server/tool-overrides.js.map +1 -0
  101. package/dist/server/turn-diff-builder.js +280 -0
  102. package/dist/server/turn-diff-builder.js.map +1 -0
  103. package/package.json +53 -12
@@ -0,0 +1,496 @@
1
+ import { ConversionError, convertAttachment, pickConverter } from "../attachment-converters.js";
2
+ import { config } from "../config.js";
3
+ import { formatErrorChain } from "../diagnostics.js";
4
+ import { expandFileReferences, languageHintForPath } from "../file-references.js";
5
+ import { getSession } from "../session-registry.js";
6
+ import { errorSchema } from "./_schemas.js";
7
+ /**
8
+ * Prompt route. Per CLAUDE.md "Pi SDK Key Facts": session.prompt() is async
9
+ * but only resolves after the entire agent run finishes (including retries
10
+ * and compaction). Routes MUST NOT await it — call without await and return
11
+ * 202 immediately. Output streams over SSE.
12
+ *
13
+ * Phase 14: also accepts multipart/form-data with attachments. The route's
14
+ * declared `body` schema covers ONLY the JSON path; the multipart path is
15
+ * detected by content-type and parsed inline below (Fastify's schema
16
+ * validation is skipped for multipart — there is no JSON body to validate).
17
+ */
18
+ const IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
19
+ const MAX_IMAGES_PER_PROMPT = 4;
20
+ const MAX_TEXT_FILES_PER_PROMPT = 4;
21
+ const MAX_FILE_BYTES = 20 * 1024 * 1024;
22
+ /**
23
+ * MIME types we'll trust as text without sniffing (the browser said
24
+ * so explicitly). `text/*` covers the obvious cases; the
25
+ * `application/*` allowlist below covers code/data formats whose
26
+ * canonical MIME starts with `application/` even though the bytes
27
+ * are UTF-8 text.
28
+ */
29
+ const TEXT_APPLICATION_MIME_TYPES = new Set([
30
+ "application/json",
31
+ "application/xml",
32
+ "application/yaml",
33
+ "application/x-yaml",
34
+ "application/javascript",
35
+ "application/typescript",
36
+ "application/sql",
37
+ "application/x-sh",
38
+ "application/toml",
39
+ "application/x-httpd-php",
40
+ ]);
41
+ function looksLikeTextMime(mime) {
42
+ if (mime.startsWith("text/"))
43
+ return true;
44
+ if (TEXT_APPLICATION_MIME_TYPES.has(mime))
45
+ return true;
46
+ // `+json` / `+xml` suffixes (RFC 6839): application/foo+json, etc.
47
+ if (mime.endsWith("+json") || mime.endsWith("+xml") || mime.endsWith("+yaml"))
48
+ return true;
49
+ return false;
50
+ }
51
+ /**
52
+ * Heuristic binary sniff: scan the first chunk of the buffer for NUL
53
+ * bytes. UTF-8 text files essentially never contain `\x00`; binary
54
+ * formats (PDFs, Office docs, Visio, images, executables) almost
55
+ * always do, usually within the first few hundred bytes.
56
+ *
57
+ * 8 KB sample is enough to reliably catch every real-world binary
58
+ * format we care about and cheap enough to run on every upload.
59
+ */
60
+ function looksBinary(buf) {
61
+ const limit = Math.min(buf.byteLength, 8 * 1024);
62
+ for (let i = 0; i < limit; i++) {
63
+ if (buf[i] === 0)
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ /**
69
+ * Read a multipart upload off the request, validating limits inline.
70
+ * Returns either the parsed shape or the error code+message the caller
71
+ * should send as 400.
72
+ */
73
+ async function parseMultipart(req) {
74
+ let text;
75
+ let streamingBehavior;
76
+ const images = [];
77
+ const textFiles = [];
78
+ // No-op drain helper. The previous implementation called
79
+ // `req.raw.destroy()` to abort the upload on early-error paths and
80
+ // free in-flight buffers. That's harmful on HTTP/1.1 keep-alive (the
81
+ // dev server's default): browsers commonly reuse one TCP connection
82
+ // for multiple requests including the long-lived SSE stream.
83
+ // Destroying the raw socket killed the SSE alongside the POST,
84
+ // producing a "Reconnecting" banner in chat right after submitting a
85
+ // prompt with an attachment. Fastify will close the request normally
86
+ // once we send the response; the worst-case bandwidth waste from
87
+ // not pre-aborting is bounded by the multipart `bodyLimit`.
88
+ const drain = () => {
89
+ // intentionally no-op — see comment above
90
+ };
91
+ for await (const part of req.parts()) {
92
+ if (part.type === "field") {
93
+ if (part.fieldname === "text") {
94
+ text = typeof part.value === "string" ? part.value : "";
95
+ }
96
+ else if (part.fieldname === "streamingBehavior") {
97
+ if (part.value === "steer" || part.value === "followUp") {
98
+ streamingBehavior = part.value;
99
+ }
100
+ }
101
+ // Unknown fields are ignored (forwards-compatible).
102
+ continue;
103
+ }
104
+ // File part. Reading the buffer counts against the multipart
105
+ // size limit; @fastify/multipart's `truncated` flag flips when
106
+ // the file exceeded `limits.fileSize`.
107
+ const file = part;
108
+ const buf = await file.toBuffer();
109
+ if (file.file.truncated) {
110
+ drain();
111
+ return {
112
+ error: "attachment_too_large",
113
+ message: `Attachment "${file.filename}" exceeds the ${MAX_FILE_BYTES / (1024 * 1024)} MB per-file limit.`,
114
+ };
115
+ }
116
+ const mime = (file.mimetype ?? "application/octet-stream").toLowerCase();
117
+ if (IMAGE_MIME_TYPES.has(mime)) {
118
+ if (images.length >= MAX_IMAGES_PER_PROMPT) {
119
+ drain();
120
+ return {
121
+ error: "too_many_images",
122
+ message: `Up to ${MAX_IMAGES_PER_PROMPT} images per prompt; got more.`,
123
+ };
124
+ }
125
+ images.push({ base64: buf.toString("base64"), mimeType: mime });
126
+ continue;
127
+ }
128
+ // Anything that isn't an image: decide between "text we can inline
129
+ // as a fenced block" and "binary we can't do anything useful with."
130
+ //
131
+ // Two-step decision:
132
+ // 1. If the browser declared a text-shaped MIME, trust it and
133
+ // treat as text.
134
+ // 2. Otherwise (typically `application/octet-stream` for files
135
+ // with unknown extensions like `.tsx` from some browsers),
136
+ // sniff for NUL bytes. No NUL in the first 8 KB → text;
137
+ // NUL present → binary, reject with a clear error so the
138
+ // user knows their PDF/Visio/Word/etc. didn't go anywhere.
139
+ //
140
+ // Without this check, a binary upload was being best-effort UTF-8
141
+ // decoded into garbled noise + U+FFFD replacement chars and
142
+ // prepended to the prompt, which broke the session (model errors,
143
+ // hallucinations, blown context budget).
144
+ const filename = file.filename ?? "attachment";
145
+ // Office-format conversion. PDF / DOCX / XLSX dispatch to a pure-JS
146
+ // converter that yields plain text the model can read. Done before
147
+ // the binary-reject branch so these formats land here even though
148
+ // their bytes contain NUL.
149
+ const converter = pickConverter(filename, mime);
150
+ if (converter !== undefined) {
151
+ if (textFiles.length >= MAX_TEXT_FILES_PER_PROMPT) {
152
+ drain();
153
+ return {
154
+ error: "too_many_text_files",
155
+ message: `Up to ${MAX_TEXT_FILES_PER_PROMPT} text attachments per prompt; got more.`,
156
+ };
157
+ }
158
+ try {
159
+ const content = await convertAttachment(converter, filename, buf);
160
+ textFiles.push({ filename, content });
161
+ }
162
+ catch (err) {
163
+ drain();
164
+ const message = err instanceof ConversionError
165
+ ? err.message
166
+ : `Failed to convert "${filename}": ${err.message}`;
167
+ return { error: "conversion_failed", message };
168
+ }
169
+ continue;
170
+ }
171
+ if (!looksLikeTextMime(mime) && looksBinary(buf)) {
172
+ drain();
173
+ return {
174
+ error: "unsupported_attachment_type",
175
+ message: `Attachment "${filename}" appears to be binary (${mime}). Only text files and images (PNG/JPEG/GIF/WebP) are supported — convert it first or attach a text/markdown export.`,
176
+ };
177
+ }
178
+ if (textFiles.length >= MAX_TEXT_FILES_PER_PROMPT) {
179
+ drain();
180
+ return {
181
+ error: "too_many_text_files",
182
+ message: `Up to ${MAX_TEXT_FILES_PER_PROMPT} text attachments per prompt; got more.`,
183
+ };
184
+ }
185
+ // Whole file goes into the prompt. The per-file size cap
186
+ // (MAX_FILE_BYTES) is the only upper bound — it exists for
187
+ // memory-pressure reasons during multipart parsing, not LLM
188
+ // context. If the composed prompt exceeds the model's context
189
+ // window, the provider returns a clean error and the user sees
190
+ // it in their chat — no value in pre-truncating to a guess of
191
+ // what "fits."
192
+ textFiles.push({
193
+ filename,
194
+ content: buf.toString("utf8"),
195
+ });
196
+ }
197
+ if (text === undefined || text.length === 0) {
198
+ return { error: "missing_text", message: "Prompt text is required." };
199
+ }
200
+ const result = { text, images, textFiles };
201
+ if (streamingBehavior !== undefined)
202
+ result.streamingBehavior = streamingBehavior;
203
+ return result;
204
+ }
205
+ /**
206
+ * Compose the final prompt text by prepending each text-file
207
+ * attachment as a fenced code block. Mirrors what most agent UIs
208
+ * produce when a file is dragged in: the LLM sees the filename as
209
+ * the language hint and the content directly below.
210
+ *
211
+ * Fence-break safety: a file whose content contains ``` would
212
+ * terminate a 3-backtick fence early and let its bytes leak out as
213
+ * "user prompt", which a hostile shared file could exploit to inject
214
+ * arbitrary instructions to the LLM. The CommonMark fenced-block
215
+ * rule says the closing fence must be at least as long as the
216
+ * opener, so we pick a fence one backtick longer than the longest
217
+ * run inside the content. Filename is also sanitized — any
218
+ * backtick/newline in it is collapsed so the opening fence line
219
+ * itself can't be hijacked.
220
+ */
221
+ function pickFence(content) {
222
+ // Longest run of consecutive backticks in `content` — we need to
223
+ // open with one MORE than that.
224
+ let max = 0;
225
+ let cur = 0;
226
+ for (const ch of content) {
227
+ if (ch === "`") {
228
+ cur += 1;
229
+ if (cur > max)
230
+ max = cur;
231
+ }
232
+ else {
233
+ cur = 0;
234
+ }
235
+ }
236
+ return "`".repeat(Math.max(3, max + 1));
237
+ }
238
+ function sanitizeFilename(name) {
239
+ // Strip backticks and newlines so neither breaks the opener line.
240
+ // Leave the rest intact — the LLM uses this as a language hint /
241
+ // identifier, so extension + path readability matter.
242
+ return name.replace(/[`\r\n]/g, "_");
243
+ }
244
+ function composePromptText(parsed) {
245
+ if (parsed.textFiles.length === 0)
246
+ return parsed.text;
247
+ // Match the format `expandFileReferences` uses for `@<path>` blocks
248
+ // — `\`\`\`<lang> file: <name>\n...\n\`\`\`` — so the chat-bubble
249
+ // renderer's `extractFileRefs` regex picks both up the same way and
250
+ // both render as collapsible badges instead of raw fenced text.
251
+ const blocks = parsed.textFiles.map((f) => {
252
+ const fence = pickFence(f.content);
253
+ const safeName = sanitizeFilename(f.filename);
254
+ const lang = languageHintForPath(safeName);
255
+ return `${fence}${lang} file: ${safeName}\n${f.content}\n${fence}`;
256
+ });
257
+ return blocks.join("\n\n") + "\n\n" + parsed.text;
258
+ }
259
+ /**
260
+ * Pre-flight checks shared by the JSON + multipart paths. On a check
261
+ * failure, sends the 4xx via `reply` AND returns undefined — caller
262
+ * MUST `return reply;` immediately to avoid double-send. The reply
263
+ * sends are awaited for clean ordering of any onSend hooks (security
264
+ * headers etc.) before the route handler proceeds.
265
+ */
266
+ async function preflight(req, reply) {
267
+ const live = getSession(req.params.id);
268
+ if (live === undefined) {
269
+ await reply
270
+ .code(404)
271
+ .send({ error: "session_not_found", message: "no live session with that id" });
272
+ return undefined;
273
+ }
274
+ const model = live.session.model;
275
+ if (model === undefined) {
276
+ await reply.code(400).send({
277
+ error: "no_model_configured",
278
+ message: "no model is configured for this session",
279
+ });
280
+ return undefined;
281
+ }
282
+ if (!live.session.modelRegistry.hasConfiguredAuth(model)) {
283
+ await reply.code(400).send({
284
+ error: "no_api_key",
285
+ message: `No API key configured for provider "${model.provider}". Add one via PUT /api/v1/config/auth/${model.provider}.`,
286
+ });
287
+ return undefined;
288
+ }
289
+ return live;
290
+ }
291
+ export const promptRoutes = async (fastify) => {
292
+ fastify.post("/sessions/:id/prompt", {
293
+ config: {
294
+ // Cost cap: each prompt costs LLM tokens. The default of 60/min is
295
+ // far above interactive use; a leaked-token loop hits the cap fast.
296
+ // Tune via RATE_LIMIT_PROMPT_*.
297
+ rateLimit: {
298
+ max: config.rateLimits.promptMax,
299
+ timeWindow: config.rateLimits.promptWindowMs,
300
+ },
301
+ },
302
+ schema: {
303
+ description: "Send a prompt to the session. Returns 202 immediately; the agent " +
304
+ "response streams over GET /sessions/:id/stream.\n\n" +
305
+ "Two body shapes:\n" +
306
+ " - application/json: { text, streamingBehavior? }\n" +
307
+ " - multipart/form-data: `text` field, optional `streamingBehavior` field, " +
308
+ " `attachments[]` files. Image attachments (PNG/JPEG/GIF/WEBP, max 4) " +
309
+ " pass into model context; non-image text files are prepended to the " +
310
+ " prompt as fenced code blocks. 20 MB per-file cap. Whole-file content " +
311
+ " is sent — if the assembled prompt exceeds the model's context window, " +
312
+ " the provider returns a clean error.",
313
+ tags: ["sessions"],
314
+ consumes: ["application/json", "multipart/form-data"],
315
+ params: {
316
+ type: "object",
317
+ required: ["id"],
318
+ properties: { id: { type: "string" } },
319
+ },
320
+ body: {
321
+ type: "object",
322
+ required: ["text"],
323
+ additionalProperties: false,
324
+ properties: {
325
+ text: { type: "string", minLength: 1 },
326
+ streamingBehavior: { type: "string", enum: ["steer", "followUp"] },
327
+ },
328
+ },
329
+ response: {
330
+ 202: {
331
+ type: "object",
332
+ required: ["accepted"],
333
+ properties: { accepted: { type: "boolean", const: true } },
334
+ },
335
+ 400: errorSchema,
336
+ 404: errorSchema,
337
+ },
338
+ },
339
+ // The framework's body parser bypasses fastify schema validation
340
+ // when the request is multipart, so this is purely informational
341
+ // for OpenAPI consumers. Multipart parsing happens inside the
342
+ // handler via `req.parts()`.
343
+ attachValidation: true,
344
+ }, async (req, reply) => {
345
+ const isMultipart = req.isMultipart();
346
+ // Plain JSON path: validation already ran via schema.body.
347
+ // Surface validation errors as 400 with the standard shape.
348
+ if (!isMultipart && req.validationError !== undefined) {
349
+ return reply.code(400).send({
350
+ error: "invalid_body",
351
+ message: req.validationError.message,
352
+ });
353
+ }
354
+ const live = await preflight(req, reply);
355
+ if (live === undefined)
356
+ return reply;
357
+ let promptText;
358
+ let streamingBehavior;
359
+ let images = [];
360
+ if (isMultipart) {
361
+ let parsed;
362
+ try {
363
+ parsed = await parseMultipart(req);
364
+ }
365
+ catch (err) {
366
+ // @fastify/multipart throws typed errors when the multipart
367
+ // limits we registered are exceeded (FilesLimitError,
368
+ // FieldsLimitError, PartsLimitError, RequestFileTooLargeError
369
+ // when throwFileSizeLimit:true). Map them to 400 with a
370
+ // typed code instead of letting Fastify render generic
371
+ // 500/413 responses.
372
+ const errCode = err.code ?? "";
373
+ if (errCode === "FST_FILES_LIMIT") {
374
+ return reply.code(400).send({
375
+ error: "too_many_files",
376
+ message: "Too many attachments in one request.",
377
+ });
378
+ }
379
+ if (errCode === "FST_REQ_FILE_TOO_LARGE") {
380
+ return reply.code(400).send({
381
+ error: "attachment_too_large",
382
+ message: `Attachment exceeds the ${MAX_FILE_BYTES / (1024 * 1024)} MB per-file limit.`,
383
+ });
384
+ }
385
+ if (errCode === "FST_FIELDS_LIMIT" || errCode === "FST_PARTS_LIMIT") {
386
+ return reply.code(400).send({
387
+ error: "too_many_parts",
388
+ message: "Too many fields or parts in the multipart request.",
389
+ });
390
+ }
391
+ throw err;
392
+ }
393
+ if ("error" in parsed) {
394
+ return reply.code(400).send(parsed);
395
+ }
396
+ promptText = composePromptText(parsed);
397
+ streamingBehavior = parsed.streamingBehavior;
398
+ images = parsed.images;
399
+ }
400
+ else {
401
+ promptText = req.body.text;
402
+ streamingBehavior = req.body.streamingBehavior;
403
+ }
404
+ // Expand `@<path>` file references inline (the chat input's
405
+ // `@`-autocomplete inserts these markers; server-side
406
+ // expansion keeps the LLM context as the source of truth and
407
+ // matches how attachments work). A path that doesn't resolve
408
+ // to a real file inside the workspace passes through untouched
409
+ // — see file-references.ts for the rules.
410
+ promptText = await expandFileReferences(promptText, live.workspacePath);
411
+ // No app-level composed-prompt cap: per-file size limits
412
+ // already prevent runaway memory pressure during multipart
413
+ // parsing, and if the assembled prompt genuinely exceeds the
414
+ // model's context window the provider returns a clean error
415
+ // that surfaces to the client over SSE — no value in pre-
416
+ // rejecting based on a guess of what "fits."
417
+ const promptBytes = Buffer.byteLength(promptText, "utf8");
418
+ const opts = {};
419
+ if (streamingBehavior !== undefined)
420
+ opts.streamingBehavior = streamingBehavior;
421
+ if (images.length > 0) {
422
+ // The SDK's ImageContent.data is RAW base64 (no `data:` prefix);
423
+ // each provider builds its own data URL via
424
+ // `data:${mimeType};base64,${data}` when needed. Don't pre-
425
+ // build the URL or providers double-prefix and break.
426
+ opts.images = images.map((img) => ({
427
+ type: "image",
428
+ data: img.base64,
429
+ mimeType: img.mimeType,
430
+ }));
431
+ }
432
+ // Fire-and-forget. Pre-flight already covered the common synchronous
433
+ // failure modes; remaining rejections are LLM/network errors that
434
+ // surface to the client as agent_end with errorMessage over SSE.
435
+ //
436
+ // The SDK's normal flow emits `agent_end` itself when a turn
437
+ // completes (success or SDK-tracked error). But certain failure
438
+ // modes — e.g. provider rejected the request synchronously,
439
+ // network down, malformed prompt — reject session.prompt() WITHOUT
440
+ // ever firing agent_start / agent_end. Connected SSE clients then
441
+ // sit on a "thinking…" spinner forever and the chat input stays
442
+ // disabled. To recover, we synthesize a terminal `agent_end`
443
+ // (with errorMessage) into the live session's fan-out so the
444
+ // browser releases the spinner and surfaces the error in chat.
445
+ const synthesizeFailureEvent = (err) => {
446
+ const errorMessage = err instanceof Error ? err.message : String(err);
447
+ for (const client of live.clients) {
448
+ try {
449
+ client.send({
450
+ type: "agent_end",
451
+ sessionId: req.params.id,
452
+ errorMessage,
453
+ });
454
+ }
455
+ catch {
456
+ // a single client send-failure shouldn't stop fan-out
457
+ }
458
+ }
459
+ };
460
+ // Operator-visible breadcrumb. Without this, a hung prompt
461
+ // (where the SDK is silently retrying or the LLM provider is
462
+ // not responding) is invisible to operators reading
463
+ // `docker logs`. Pino redacts the prompt body itself; the byte
464
+ // count is the safe diagnostic to emit.
465
+ process.stderr.write(`${JSON.stringify({
466
+ level: "info",
467
+ time: new Date().toISOString(),
468
+ msg: "session.prompt invoked",
469
+ sessionId: req.params.id,
470
+ promptBytes,
471
+ imageCount: images.length,
472
+ streamingBehavior,
473
+ })}\n`);
474
+ try {
475
+ live.session.prompt(promptText, opts).catch((err) => {
476
+ const f = formatErrorChain(err);
477
+ process.stderr.write(`${JSON.stringify({
478
+ level: "warn",
479
+ time: new Date().toISOString(),
480
+ msg: "session.prompt rejected",
481
+ sessionId: req.params.id,
482
+ error: f.message,
483
+ chain: f.chain,
484
+ stack: f.stack,
485
+ })}\n`);
486
+ synthesizeFailureEvent(err);
487
+ });
488
+ }
489
+ catch (err) {
490
+ fastify.log.warn({ err: err instanceof Error ? err.message : String(err), sessionId: req.params.id }, "session.prompt threw synchronously");
491
+ synthesizeFailureEvent(err);
492
+ }
493
+ return reply.code(202).send({ accepted: true });
494
+ });
495
+ };
496
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/routes/prompt.ts"],"names":[],"mappings":"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"}