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.
- package/LICENSE +21 -0
- package/README.md +48 -4
- package/bin/pi-forge.mjs +37 -0
- package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js +34 -0
- package/dist/client/assets/CodeMirrorEditor-BqaaP1EE.js.map +1 -0
- package/dist/client/assets/index-B-529kgJ.css +32 -0
- package/dist/client/assets/index-BzKzxXFs.js +392 -0
- package/dist/client/assets/index-BzKzxXFs.js.map +1 -0
- package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js +3 -0
- package/dist/client/assets/workbox-window.prod.es5-BBnX5xw4.js.map +1 -0
- package/dist/client/icons/icon-192.png +0 -0
- package/dist/client/icons/icon-512.png +0 -0
- package/dist/client/icons/icon-maskable-512.png +0 -0
- package/dist/client/icons/icon.svg +9 -0
- package/dist/client/index.html +24 -0
- package/dist/client/manifest.webmanifest +1 -0
- package/dist/client/offline.html +142 -0
- package/dist/client/sw.js +3 -0
- package/dist/client/sw.js.map +1 -0
- package/dist/client/workbox-6d7155ed.js +3 -0
- package/dist/client/workbox-6d7155ed.js.map +1 -0
- package/dist/server/agent-resource-loader.js +126 -0
- package/dist/server/agent-resource-loader.js.map +1 -0
- package/dist/server/attachment-converters.js +96 -0
- package/dist/server/attachment-converters.js.map +1 -0
- package/dist/server/auth.js +209 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/compaction-history.js +106 -0
- package/dist/server/compaction-history.js.map +1 -0
- package/dist/server/concurrency.js +49 -0
- package/dist/server/concurrency.js.map +1 -0
- package/dist/server/config-export.js +220 -0
- package/dist/server/config-export.js.map +1 -0
- package/dist/server/config-manager.js +528 -0
- package/dist/server/config-manager.js.map +1 -0
- package/dist/server/config.js +326 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/conversion-worker.mjs +90 -0
- package/dist/server/diagnostics.js +137 -0
- package/dist/server/diagnostics.js.map +1 -0
- package/dist/server/extensions-discovery.js +147 -0
- package/dist/server/extensions-discovery.js.map +1 -0
- package/dist/server/file-manager.js +734 -0
- package/dist/server/file-manager.js.map +1 -0
- package/dist/server/file-references.js +215 -0
- package/dist/server/file-references.js.map +1 -0
- package/dist/server/file-searcher.js +385 -0
- package/dist/server/file-searcher.js.map +1 -0
- package/dist/server/git-runner.js +684 -0
- package/dist/server/git-runner.js.map +1 -0
- package/dist/server/index.js +468 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/mcp/config.js +133 -0
- package/dist/server/mcp/config.js.map +1 -0
- package/dist/server/mcp/manager.js +351 -0
- package/dist/server/mcp/manager.js.map +1 -0
- package/dist/server/mcp/tool-bridge.js +173 -0
- package/dist/server/mcp/tool-bridge.js.map +1 -0
- package/dist/server/project-manager.js +301 -0
- package/dist/server/project-manager.js.map +1 -0
- package/dist/server/pty-manager.js +354 -0
- package/dist/server/pty-manager.js.map +1 -0
- package/dist/server/routes/_schemas.js +73 -0
- package/dist/server/routes/_schemas.js.map +1 -0
- package/dist/server/routes/auth.js +164 -0
- package/dist/server/routes/auth.js.map +1 -0
- package/dist/server/routes/config.js +1163 -0
- package/dist/server/routes/config.js.map +1 -0
- package/dist/server/routes/control.js +464 -0
- package/dist/server/routes/control.js.map +1 -0
- package/dist/server/routes/exec.js +217 -0
- package/dist/server/routes/exec.js.map +1 -0
- package/dist/server/routes/files.js +847 -0
- package/dist/server/routes/files.js.map +1 -0
- package/dist/server/routes/git.js +837 -0
- package/dist/server/routes/git.js.map +1 -0
- package/dist/server/routes/health.js +97 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/mcp.js +300 -0
- package/dist/server/routes/mcp.js.map +1 -0
- package/dist/server/routes/projects.js +259 -0
- package/dist/server/routes/projects.js.map +1 -0
- package/dist/server/routes/prompt.js +496 -0
- package/dist/server/routes/prompt.js.map +1 -0
- package/dist/server/routes/sessions.js +783 -0
- package/dist/server/routes/sessions.js.map +1 -0
- package/dist/server/routes/stream.js +69 -0
- package/dist/server/routes/stream.js.map +1 -0
- package/dist/server/routes/terminal.js +335 -0
- package/dist/server/routes/terminal.js.map +1 -0
- package/dist/server/session-registry.js +1197 -0
- package/dist/server/session-registry.js.map +1 -0
- package/dist/server/skill-overrides.js +151 -0
- package/dist/server/skill-overrides.js.map +1 -0
- package/dist/server/skills-export.js +257 -0
- package/dist/server/skills-export.js.map +1 -0
- package/dist/server/sse-bridge.js +220 -0
- package/dist/server/sse-bridge.js.map +1 -0
- package/dist/server/tool-overrides.js +277 -0
- package/dist/server/tool-overrides.js.map +1 -0
- package/dist/server/turn-diff-builder.js +280 -0
- package/dist/server/turn-diff-builder.js.map +1 -0
- 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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"}
|