sparkecoder 0.1.21 → 0.1.22
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/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +53 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +397 -46
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/{index-BzedNBK-.d.ts → index-CNwLFGiZ.d.ts} +24 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +392 -41
- package/dist/index.js.map +1 -1
- package/dist/{schema-CkrIadxa.d.ts → schema-Df7MU3nM.d.ts} +26 -3
- package/dist/server/index.js +392 -41
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1d78db71._.js → 2374f_387a1437._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_378282b1._.js → 2374f_5f58fd73._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_30f9df13._.js → 2374f_65fcfd95._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8825dcc9._.js → 2374f_741f6b67._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9bf3c7f3._.js → 2374f_814be2c9._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5de336d2._.js → 2374f_84859a94._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bbc99511._.js → 2374f_cfd0137a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d94c2b70._.js → 2374f_f1038f7c._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__a984d933._.js → [root-of-the-server]__3ec22171._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_c7618534._.js +8 -0
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/{a86053f0894587f2.js → 3bb454ca848ec78e.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{a86053f0894587f2.js → 3bb454ca848ec78e.js} +3 -3
- package/web/.next/{static/chunks/5ec82ce8f3aabaf0.js → standalone/web/.next/static/static/chunks/5e5b485d77ac0d8f.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +1 -0
- package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +89 -36
- package/web/.next/standalone/web/src/components/chat-interface.tsx +353 -37
- package/web/.next/standalone/web/src/lib/api.ts +133 -2
- package/web/.next/static/chunks/{a86053f0894587f2.js → 3bb454ca848ec78e.js} +3 -3
- package/web/.next/{standalone/web/.next/static/static/chunks/5ec82ce8f3aabaf0.js → static/chunks/5e5b485d77ac0d8f.js} +1 -1
- package/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_19b6934c._.js +0 -8
- package/web/.next/standalone/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/d0a69c59b1c0d99c.css +0 -1
- package/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
- /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
- /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
- /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -3260,11 +3260,12 @@ ${this.summary}`
|
|
|
3260
3260
|
}
|
|
3261
3261
|
/**
|
|
3262
3262
|
* Add a user message to the context
|
|
3263
|
+
* Content can be a string or an array of content parts (for messages with images/files)
|
|
3263
3264
|
*/
|
|
3264
|
-
addUserMessage(
|
|
3265
|
+
addUserMessage(content) {
|
|
3265
3266
|
const userMessage = {
|
|
3266
3267
|
role: "user",
|
|
3267
|
-
content
|
|
3268
|
+
content
|
|
3268
3269
|
};
|
|
3269
3270
|
messageQueries.create(this.sessionId, userMessage);
|
|
3270
3271
|
}
|
|
@@ -3364,13 +3365,62 @@ var Agent = class _Agent {
|
|
|
3364
3365
|
getSession() {
|
|
3365
3366
|
return this.session;
|
|
3366
3367
|
}
|
|
3368
|
+
/**
|
|
3369
|
+
* Build user message content from prompt and attachments
|
|
3370
|
+
*/
|
|
3371
|
+
buildUserMessageContent(prompt, attachments) {
|
|
3372
|
+
if (!attachments || attachments.length === 0) {
|
|
3373
|
+
return prompt;
|
|
3374
|
+
}
|
|
3375
|
+
const contentParts = [];
|
|
3376
|
+
const attachmentDescriptions = attachments.map((a, i) => {
|
|
3377
|
+
const name = a.filename || `attachment_${i + 1}`;
|
|
3378
|
+
const typeLabel = a.type === "image" ? "Image" : "File";
|
|
3379
|
+
const location = a.savedPath || "(path unknown)";
|
|
3380
|
+
return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
|
|
3381
|
+
}).join("\n");
|
|
3382
|
+
contentParts.push({
|
|
3383
|
+
type: "text",
|
|
3384
|
+
text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
|
|
3385
|
+
${attachmentDescriptions}
|
|
3386
|
+
|
|
3387
|
+
You can reference these files by their paths above. The file contents are also shown inline below.`
|
|
3388
|
+
});
|
|
3389
|
+
if (prompt) {
|
|
3390
|
+
contentParts.push({ type: "text", text: `
|
|
3391
|
+
[USER MESSAGE]
|
|
3392
|
+
${prompt}` });
|
|
3393
|
+
}
|
|
3394
|
+
for (const attachment of attachments) {
|
|
3395
|
+
if (attachment.type === "image") {
|
|
3396
|
+
contentParts.push({
|
|
3397
|
+
type: "image",
|
|
3398
|
+
image: attachment.data,
|
|
3399
|
+
// base64 data URL or raw base64
|
|
3400
|
+
mediaType: attachment.mediaType,
|
|
3401
|
+
filename: attachment.filename,
|
|
3402
|
+
savedPath: attachment.savedPath
|
|
3403
|
+
});
|
|
3404
|
+
} else {
|
|
3405
|
+
contentParts.push({
|
|
3406
|
+
type: "file",
|
|
3407
|
+
data: attachment.data,
|
|
3408
|
+
mediaType: attachment.mediaType || "application/octet-stream",
|
|
3409
|
+
filename: attachment.filename,
|
|
3410
|
+
savedPath: attachment.savedPath
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
return contentParts;
|
|
3415
|
+
}
|
|
3367
3416
|
/**
|
|
3368
3417
|
* Run the agent with a prompt (streaming)
|
|
3369
3418
|
*/
|
|
3370
3419
|
async stream(options) {
|
|
3371
3420
|
const config = getConfig();
|
|
3421
|
+
const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
|
|
3372
3422
|
if (!options.skipSaveUserMessage) {
|
|
3373
|
-
this.context.addUserMessage(
|
|
3423
|
+
this.context.addUserMessage(userContent);
|
|
3374
3424
|
}
|
|
3375
3425
|
sessionQueries.updateStatus(this.session.id, "active");
|
|
3376
3426
|
const systemPrompt = await buildSystemPrompt({
|
|
@@ -3584,8 +3634,8 @@ import { Hono as Hono5 } from "hono";
|
|
|
3584
3634
|
import { serve } from "@hono/node-server";
|
|
3585
3635
|
import { cors } from "hono/cors";
|
|
3586
3636
|
import { logger } from "hono/logger";
|
|
3587
|
-
import { existsSync as
|
|
3588
|
-
import { resolve as resolve8, dirname as dirname6, join as
|
|
3637
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
3638
|
+
import { resolve as resolve8, dirname as dirname6, join as join5 } from "path";
|
|
3589
3639
|
import { spawn as spawn2 } from "child_process";
|
|
3590
3640
|
import { createServer as createNetServer } from "net";
|
|
3591
3641
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -3594,6 +3644,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
3594
3644
|
import { Hono } from "hono";
|
|
3595
3645
|
import { zValidator } from "@hono/zod-validator";
|
|
3596
3646
|
import { z as z9 } from "zod";
|
|
3647
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
|
|
3648
|
+
import { join as join3, basename as basename2, extname as extname5 } from "path";
|
|
3649
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
3597
3650
|
var sessions2 = new Hono();
|
|
3598
3651
|
var createSessionSchema = z9.object({
|
|
3599
3652
|
name: z9.string().optional(),
|
|
@@ -3945,11 +3998,138 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
3945
3998
|
currentContent: fileDiff.currentContent
|
|
3946
3999
|
});
|
|
3947
4000
|
});
|
|
4001
|
+
function getAttachmentsDir(sessionId) {
|
|
4002
|
+
const appDataDir = getAppDataDirectory();
|
|
4003
|
+
return join3(appDataDir, "attachments", sessionId);
|
|
4004
|
+
}
|
|
4005
|
+
function ensureAttachmentsDir(sessionId) {
|
|
4006
|
+
const dir = getAttachmentsDir(sessionId);
|
|
4007
|
+
if (!existsSync10(dir)) {
|
|
4008
|
+
mkdirSync3(dir, { recursive: true });
|
|
4009
|
+
}
|
|
4010
|
+
return dir;
|
|
4011
|
+
}
|
|
4012
|
+
sessions2.get("/:id/attachments", async (c) => {
|
|
4013
|
+
const sessionId = c.req.param("id");
|
|
4014
|
+
const session = sessionQueries.getById(sessionId);
|
|
4015
|
+
if (!session) {
|
|
4016
|
+
return c.json({ error: "Session not found" }, 404);
|
|
4017
|
+
}
|
|
4018
|
+
const dir = getAttachmentsDir(sessionId);
|
|
4019
|
+
if (!existsSync10(dir)) {
|
|
4020
|
+
return c.json({ sessionId, attachments: [], count: 0 });
|
|
4021
|
+
}
|
|
4022
|
+
const files = readdirSync(dir);
|
|
4023
|
+
const attachments = files.map((filename) => {
|
|
4024
|
+
const filePath = join3(dir, filename);
|
|
4025
|
+
const stats = statSync(filePath);
|
|
4026
|
+
return {
|
|
4027
|
+
id: filename.split("_")[0],
|
|
4028
|
+
// Extract the nanoid prefix
|
|
4029
|
+
filename,
|
|
4030
|
+
path: filePath,
|
|
4031
|
+
size: stats.size,
|
|
4032
|
+
createdAt: stats.birthtime.toISOString()
|
|
4033
|
+
};
|
|
4034
|
+
});
|
|
4035
|
+
return c.json({
|
|
4036
|
+
sessionId,
|
|
4037
|
+
attachments,
|
|
4038
|
+
count: attachments.length
|
|
4039
|
+
});
|
|
4040
|
+
});
|
|
4041
|
+
sessions2.post("/:id/attachments", async (c) => {
|
|
4042
|
+
const sessionId = c.req.param("id");
|
|
4043
|
+
const session = sessionQueries.getById(sessionId);
|
|
4044
|
+
if (!session) {
|
|
4045
|
+
return c.json({ error: "Session not found" }, 404);
|
|
4046
|
+
}
|
|
4047
|
+
const contentType = c.req.header("content-type") || "";
|
|
4048
|
+
if (contentType.includes("multipart/form-data")) {
|
|
4049
|
+
try {
|
|
4050
|
+
const formData = await c.req.formData();
|
|
4051
|
+
const file = formData.get("file");
|
|
4052
|
+
if (!file || !(file instanceof File)) {
|
|
4053
|
+
return c.json({ error: "No file provided" }, 400);
|
|
4054
|
+
}
|
|
4055
|
+
const dir = ensureAttachmentsDir(sessionId);
|
|
4056
|
+
const id = nanoid4(10);
|
|
4057
|
+
const ext = extname5(file.name) || "";
|
|
4058
|
+
const safeFilename = `${id}_${basename2(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
4059
|
+
const filePath = join3(dir, safeFilename);
|
|
4060
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
4061
|
+
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
4062
|
+
return c.json({
|
|
4063
|
+
id,
|
|
4064
|
+
filename: file.name,
|
|
4065
|
+
storedAs: safeFilename,
|
|
4066
|
+
path: filePath,
|
|
4067
|
+
size: file.size,
|
|
4068
|
+
mediaType: file.type,
|
|
4069
|
+
sessionId
|
|
4070
|
+
}, 201);
|
|
4071
|
+
} catch (err) {
|
|
4072
|
+
console.error("Failed to upload attachment:", err);
|
|
4073
|
+
return c.json({ error: "Failed to upload file" }, 500);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
try {
|
|
4077
|
+
const body = await c.req.json();
|
|
4078
|
+
if (!body.filename || !body.data) {
|
|
4079
|
+
return c.json({ error: "Missing filename or data" }, 400);
|
|
4080
|
+
}
|
|
4081
|
+
const dir = ensureAttachmentsDir(sessionId);
|
|
4082
|
+
const id = nanoid4(10);
|
|
4083
|
+
const ext = extname5(body.filename) || "";
|
|
4084
|
+
const safeFilename = `${id}_${basename2(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
4085
|
+
const filePath = join3(dir, safeFilename);
|
|
4086
|
+
let base64Data = body.data;
|
|
4087
|
+
if (base64Data.includes(",")) {
|
|
4088
|
+
base64Data = base64Data.split(",")[1];
|
|
4089
|
+
}
|
|
4090
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
4091
|
+
writeFileSync2(filePath, buffer);
|
|
4092
|
+
return c.json({
|
|
4093
|
+
id,
|
|
4094
|
+
filename: body.filename,
|
|
4095
|
+
storedAs: safeFilename,
|
|
4096
|
+
path: filePath,
|
|
4097
|
+
size: buffer.length,
|
|
4098
|
+
mediaType: body.mediaType,
|
|
4099
|
+
sessionId
|
|
4100
|
+
}, 201);
|
|
4101
|
+
} catch (err) {
|
|
4102
|
+
console.error("Failed to upload attachment:", err);
|
|
4103
|
+
return c.json({ error: "Failed to upload file" }, 500);
|
|
4104
|
+
}
|
|
4105
|
+
});
|
|
4106
|
+
sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
4107
|
+
const sessionId = c.req.param("id");
|
|
4108
|
+
const attachmentId = c.req.param("attachmentId");
|
|
4109
|
+
const session = sessionQueries.getById(sessionId);
|
|
4110
|
+
if (!session) {
|
|
4111
|
+
return c.json({ error: "Session not found" }, 404);
|
|
4112
|
+
}
|
|
4113
|
+
const dir = getAttachmentsDir(sessionId);
|
|
4114
|
+
if (!existsSync10(dir)) {
|
|
4115
|
+
return c.json({ error: "Attachment not found" }, 404);
|
|
4116
|
+
}
|
|
4117
|
+
const files = readdirSync(dir);
|
|
4118
|
+
const file = files.find((f) => f.startsWith(attachmentId + "_"));
|
|
4119
|
+
if (!file) {
|
|
4120
|
+
return c.json({ error: "Attachment not found" }, 404);
|
|
4121
|
+
}
|
|
4122
|
+
const filePath = join3(dir, file);
|
|
4123
|
+
unlinkSync(filePath);
|
|
4124
|
+
return c.json({ success: true, id: attachmentId });
|
|
4125
|
+
});
|
|
3948
4126
|
|
|
3949
4127
|
// src/server/routes/agents.ts
|
|
3950
4128
|
import { Hono as Hono2 } from "hono";
|
|
3951
4129
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
3952
4130
|
import { z as z10 } from "zod";
|
|
4131
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
4132
|
+
import { join as join4 } from "path";
|
|
3953
4133
|
|
|
3954
4134
|
// src/server/resumable-stream.ts
|
|
3955
4135
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -4024,11 +4204,23 @@ var streamContext = createResumableStreamContext({
|
|
|
4024
4204
|
});
|
|
4025
4205
|
|
|
4026
4206
|
// src/server/routes/agents.ts
|
|
4027
|
-
import { nanoid as
|
|
4207
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
4028
4208
|
var agents = new Hono2();
|
|
4029
|
-
var
|
|
4030
|
-
|
|
4209
|
+
var attachmentSchema = z10.object({
|
|
4210
|
+
type: z10.enum(["image", "file"]),
|
|
4211
|
+
data: z10.string(),
|
|
4212
|
+
// base64 data URL or raw base64
|
|
4213
|
+
mediaType: z10.string().optional(),
|
|
4214
|
+
filename: z10.string().optional()
|
|
4031
4215
|
});
|
|
4216
|
+
var runPromptSchema = z10.object({
|
|
4217
|
+
prompt: z10.string(),
|
|
4218
|
+
// Can be empty if attachments are provided
|
|
4219
|
+
attachments: z10.array(attachmentSchema).optional()
|
|
4220
|
+
}).refine(
|
|
4221
|
+
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
4222
|
+
{ message: "Either prompt or attachments must be provided" }
|
|
4223
|
+
);
|
|
4032
4224
|
var quickStartSchema = z10.object({
|
|
4033
4225
|
prompt: z10.string().min(1),
|
|
4034
4226
|
name: z10.string().optional(),
|
|
@@ -4040,7 +4232,53 @@ var rejectSchema = z10.object({
|
|
|
4040
4232
|
reason: z10.string().optional()
|
|
4041
4233
|
}).optional();
|
|
4042
4234
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
4043
|
-
function
|
|
4235
|
+
function getAttachmentsDirectory(sessionId) {
|
|
4236
|
+
const appDataDir = getAppDataDirectory();
|
|
4237
|
+
return join4(appDataDir, "attachments", sessionId);
|
|
4238
|
+
}
|
|
4239
|
+
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
4240
|
+
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
4241
|
+
if (!existsSync11(attachmentsDir)) {
|
|
4242
|
+
mkdirSync4(attachmentsDir, { recursive: true });
|
|
4243
|
+
}
|
|
4244
|
+
let filename = attachment.filename;
|
|
4245
|
+
if (!filename) {
|
|
4246
|
+
const ext = getExtensionFromMediaType(attachment.mediaType, attachment.type);
|
|
4247
|
+
filename = `attachment_${index + 1}${ext}`;
|
|
4248
|
+
}
|
|
4249
|
+
let base64Data = attachment.data;
|
|
4250
|
+
if (base64Data.includes(",")) {
|
|
4251
|
+
base64Data = base64Data.split(",")[1];
|
|
4252
|
+
}
|
|
4253
|
+
const filePath = join4(attachmentsDir, filename);
|
|
4254
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
4255
|
+
writeFileSync3(filePath, buffer);
|
|
4256
|
+
return filePath;
|
|
4257
|
+
}
|
|
4258
|
+
function getExtensionFromMediaType(mediaType, type) {
|
|
4259
|
+
if (!mediaType) {
|
|
4260
|
+
return type === "image" ? ".png" : ".bin";
|
|
4261
|
+
}
|
|
4262
|
+
const mimeToExt = {
|
|
4263
|
+
"image/png": ".png",
|
|
4264
|
+
"image/jpeg": ".jpg",
|
|
4265
|
+
"image/jpg": ".jpg",
|
|
4266
|
+
"image/gif": ".gif",
|
|
4267
|
+
"image/webp": ".webp",
|
|
4268
|
+
"image/svg+xml": ".svg",
|
|
4269
|
+
"application/pdf": ".pdf",
|
|
4270
|
+
"text/plain": ".txt",
|
|
4271
|
+
"text/markdown": ".md",
|
|
4272
|
+
"application/json": ".json",
|
|
4273
|
+
"application/javascript": ".js",
|
|
4274
|
+
"text/javascript": ".js",
|
|
4275
|
+
"text/typescript": ".ts",
|
|
4276
|
+
"text/html": ".html",
|
|
4277
|
+
"text/css": ".css"
|
|
4278
|
+
};
|
|
4279
|
+
return mimeToExt[mediaType] || ".bin";
|
|
4280
|
+
}
|
|
4281
|
+
function createAgentStreamProducer(sessionId, prompt, streamId, attachments) {
|
|
4044
4282
|
return () => {
|
|
4045
4283
|
const { readable, writable } = new TransformStream();
|
|
4046
4284
|
const writer = writable.getWriter();
|
|
@@ -4073,9 +4311,53 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
|
|
|
4073
4311
|
try {
|
|
4074
4312
|
const agent = await Agent.create({ sessionId });
|
|
4075
4313
|
await writeSSE(JSON.stringify({ type: "data-stream-id", streamId }));
|
|
4314
|
+
let broadcastContent;
|
|
4315
|
+
if (attachments && attachments.length > 0) {
|
|
4316
|
+
const contentParts = [];
|
|
4317
|
+
const attachmentDescriptions = attachments.map((a, i) => {
|
|
4318
|
+
const name = a.filename || `attachment_${i + 1}`;
|
|
4319
|
+
const typeLabel = a.type === "image" ? "Image" : "File";
|
|
4320
|
+
const location = a.savedPath || "(path unknown)";
|
|
4321
|
+
return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
|
|
4322
|
+
}).join("\n");
|
|
4323
|
+
contentParts.push({
|
|
4324
|
+
type: "text",
|
|
4325
|
+
text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
|
|
4326
|
+
${attachmentDescriptions}
|
|
4327
|
+
|
|
4328
|
+
You can reference these files by their paths above. The file contents are also shown inline below.`
|
|
4329
|
+
});
|
|
4330
|
+
if (prompt) {
|
|
4331
|
+
contentParts.push({ type: "text", text: `
|
|
4332
|
+
[USER MESSAGE]
|
|
4333
|
+
${prompt}` });
|
|
4334
|
+
}
|
|
4335
|
+
for (const attachment of attachments) {
|
|
4336
|
+
if (attachment.type === "image") {
|
|
4337
|
+
contentParts.push({
|
|
4338
|
+
type: "image",
|
|
4339
|
+
image: attachment.data,
|
|
4340
|
+
mediaType: attachment.mediaType,
|
|
4341
|
+
filename: attachment.filename,
|
|
4342
|
+
savedPath: attachment.savedPath
|
|
4343
|
+
});
|
|
4344
|
+
} else {
|
|
4345
|
+
contentParts.push({
|
|
4346
|
+
type: "file",
|
|
4347
|
+
data: attachment.data,
|
|
4348
|
+
mediaType: attachment.mediaType || "application/octet-stream",
|
|
4349
|
+
filename: attachment.filename,
|
|
4350
|
+
savedPath: attachment.savedPath
|
|
4351
|
+
});
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
broadcastContent = contentParts;
|
|
4355
|
+
} else {
|
|
4356
|
+
broadcastContent = prompt;
|
|
4357
|
+
}
|
|
4076
4358
|
await writeSSE(JSON.stringify({
|
|
4077
4359
|
type: "data-user-message",
|
|
4078
|
-
data: { id: `user_${Date.now()}`, content:
|
|
4360
|
+
data: { id: `user_${Date.now()}`, content: broadcastContent }
|
|
4079
4361
|
}));
|
|
4080
4362
|
const messageId = `msg_${Date.now()}`;
|
|
4081
4363
|
await writeSSE(JSON.stringify({ type: "start", messageId }));
|
|
@@ -4083,6 +4365,8 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
|
|
|
4083
4365
|
let textStarted = false;
|
|
4084
4366
|
const result = await agent.stream({
|
|
4085
4367
|
prompt,
|
|
4368
|
+
attachments,
|
|
4369
|
+
// Pass attachments to agent
|
|
4086
4370
|
abortSignal: abortController.signal,
|
|
4087
4371
|
// Use our managed abort controller, NOT client signal
|
|
4088
4372
|
skipSaveUserMessage: true,
|
|
@@ -4192,14 +4476,20 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
|
|
|
4192
4476
|
} else {
|
|
4193
4477
|
await writeSSE(JSON.stringify({ type: "finish" }));
|
|
4194
4478
|
}
|
|
4195
|
-
|
|
4479
|
+
try {
|
|
4480
|
+
activeStreamQueries.finish(streamId);
|
|
4481
|
+
} catch {
|
|
4482
|
+
}
|
|
4196
4483
|
} catch (error) {
|
|
4197
4484
|
if (error.name === "AbortError" || error.message?.includes("aborted")) {
|
|
4198
4485
|
await writeSSE(JSON.stringify({ type: "abort" }));
|
|
4199
4486
|
} else {
|
|
4200
4487
|
console.error("Agent error:", error);
|
|
4201
4488
|
await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
|
|
4202
|
-
|
|
4489
|
+
try {
|
|
4490
|
+
activeStreamQueries.markError(streamId);
|
|
4491
|
+
} catch {
|
|
4492
|
+
}
|
|
4203
4493
|
}
|
|
4204
4494
|
} finally {
|
|
4205
4495
|
cleanupAbortController();
|
|
@@ -4215,19 +4505,74 @@ agents.post(
|
|
|
4215
4505
|
zValidator2("json", runPromptSchema),
|
|
4216
4506
|
async (c) => {
|
|
4217
4507
|
const id = c.req.param("id");
|
|
4218
|
-
const { prompt } = c.req.valid("json");
|
|
4508
|
+
const { prompt, attachments } = c.req.valid("json");
|
|
4219
4509
|
const session = sessionQueries.getById(id);
|
|
4220
4510
|
if (!session) {
|
|
4221
4511
|
return c.json({ error: "Session not found" }, 404);
|
|
4222
4512
|
}
|
|
4223
4513
|
const nextSequence = messageQueries.getNextSequence(id);
|
|
4224
4514
|
await createCheckpoint(id, session.workingDirectory, nextSequence);
|
|
4225
|
-
|
|
4226
|
-
const
|
|
4515
|
+
let userMessageContent;
|
|
4516
|
+
const streamAttachments = attachments;
|
|
4517
|
+
if (streamAttachments && streamAttachments.length > 0) {
|
|
4518
|
+
for (let i = 0; i < streamAttachments.length; i++) {
|
|
4519
|
+
const attachment = streamAttachments[i];
|
|
4520
|
+
try {
|
|
4521
|
+
const savedPath = saveAttachmentToDisk(id, attachment, i);
|
|
4522
|
+
attachment.savedPath = savedPath;
|
|
4523
|
+
} catch (err) {
|
|
4524
|
+
console.error(`Failed to save attachment ${i}:`, err);
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
const contentParts = [];
|
|
4528
|
+
const attachmentDescriptions = streamAttachments.map((a, i) => {
|
|
4529
|
+
const name = a.filename || `attachment_${i + 1}`;
|
|
4530
|
+
const typeLabel = a.type === "image" ? "Image" : "File";
|
|
4531
|
+
const location = a.savedPath || "(path unknown)";
|
|
4532
|
+
return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
|
|
4533
|
+
}).join("\n");
|
|
4534
|
+
contentParts.push({
|
|
4535
|
+
type: "text",
|
|
4536
|
+
text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
|
|
4537
|
+
${attachmentDescriptions}
|
|
4538
|
+
|
|
4539
|
+
You can reference these files by their paths above. The file contents are also shown inline below.`
|
|
4540
|
+
});
|
|
4541
|
+
if (prompt) {
|
|
4542
|
+
contentParts.push({ type: "text", text: `
|
|
4543
|
+
[USER MESSAGE]
|
|
4544
|
+
${prompt}` });
|
|
4545
|
+
}
|
|
4546
|
+
for (const attachment of streamAttachments) {
|
|
4547
|
+
if (attachment.type === "image") {
|
|
4548
|
+
contentParts.push({
|
|
4549
|
+
type: "image",
|
|
4550
|
+
image: attachment.data,
|
|
4551
|
+
// base64 data URL or raw base64
|
|
4552
|
+
mediaType: attachment.mediaType,
|
|
4553
|
+
filename: attachment.filename,
|
|
4554
|
+
savedPath: attachment.savedPath
|
|
4555
|
+
});
|
|
4556
|
+
} else {
|
|
4557
|
+
contentParts.push({
|
|
4558
|
+
type: "file",
|
|
4559
|
+
data: attachment.data,
|
|
4560
|
+
mediaType: attachment.mediaType || "application/octet-stream",
|
|
4561
|
+
filename: attachment.filename,
|
|
4562
|
+
savedPath: attachment.savedPath
|
|
4563
|
+
});
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
userMessageContent = contentParts;
|
|
4567
|
+
} else {
|
|
4568
|
+
userMessageContent = prompt;
|
|
4569
|
+
}
|
|
4570
|
+
messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
4571
|
+
const streamId = `stream_${id}_${nanoid5(10)}`;
|
|
4227
4572
|
activeStreamQueries.create(id, streamId);
|
|
4228
4573
|
const stream = await streamContext.resumableStream(
|
|
4229
4574
|
streamId,
|
|
4230
|
-
createAgentStreamProducer(id, prompt, streamId)
|
|
4575
|
+
createAgentStreamProducer(id, prompt, streamId, streamAttachments)
|
|
4231
4576
|
);
|
|
4232
4577
|
if (!stream) {
|
|
4233
4578
|
return c.json({ error: "Failed to create stream" }, 500);
|
|
@@ -4420,7 +4765,7 @@ agents.post(
|
|
|
4420
4765
|
sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
|
|
4421
4766
|
});
|
|
4422
4767
|
const session = agent.getSession();
|
|
4423
|
-
const streamId = `stream_${session.id}_${
|
|
4768
|
+
const streamId = `stream_${session.id}_${nanoid5(10)}`;
|
|
4424
4769
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
4425
4770
|
activeStreamQueries.create(session.id, streamId);
|
|
4426
4771
|
const createQuickStreamProducer = () => {
|
|
@@ -5079,11 +5424,11 @@ function getWebDirectory() {
|
|
|
5079
5424
|
try {
|
|
5080
5425
|
const currentDir = dirname6(fileURLToPath2(import.meta.url));
|
|
5081
5426
|
const webDir = resolve8(currentDir, "..", "web");
|
|
5082
|
-
if (
|
|
5427
|
+
if (existsSync12(webDir) && existsSync12(join5(webDir, "package.json"))) {
|
|
5083
5428
|
return webDir;
|
|
5084
5429
|
}
|
|
5085
5430
|
const altWebDir = resolve8(currentDir, "..", "..", "web");
|
|
5086
|
-
if (
|
|
5431
|
+
if (existsSync12(altWebDir) && existsSync12(join5(altWebDir, "package.json"))) {
|
|
5087
5432
|
return altWebDir;
|
|
5088
5433
|
}
|
|
5089
5434
|
return null;
|
|
@@ -5141,23 +5486,23 @@ async function findWebPort(preferredPort) {
|
|
|
5141
5486
|
return { port: preferredPort, alreadyRunning: false };
|
|
5142
5487
|
}
|
|
5143
5488
|
function hasProductionBuild(webDir) {
|
|
5144
|
-
const buildIdPath =
|
|
5145
|
-
return
|
|
5489
|
+
const buildIdPath = join5(webDir, ".next", "BUILD_ID");
|
|
5490
|
+
return existsSync12(buildIdPath);
|
|
5146
5491
|
}
|
|
5147
5492
|
function hasSourceFiles(webDir) {
|
|
5148
|
-
const appDir =
|
|
5149
|
-
const pagesDir =
|
|
5150
|
-
const rootAppDir =
|
|
5151
|
-
const rootPagesDir =
|
|
5152
|
-
return
|
|
5493
|
+
const appDir = join5(webDir, "src", "app");
|
|
5494
|
+
const pagesDir = join5(webDir, "src", "pages");
|
|
5495
|
+
const rootAppDir = join5(webDir, "app");
|
|
5496
|
+
const rootPagesDir = join5(webDir, "pages");
|
|
5497
|
+
return existsSync12(appDir) || existsSync12(pagesDir) || existsSync12(rootAppDir) || existsSync12(rootPagesDir);
|
|
5153
5498
|
}
|
|
5154
5499
|
function getStandaloneServerPath(webDir) {
|
|
5155
5500
|
const possiblePaths = [
|
|
5156
|
-
|
|
5157
|
-
|
|
5501
|
+
join5(webDir, ".next", "standalone", "server.js"),
|
|
5502
|
+
join5(webDir, ".next", "standalone", "web", "server.js")
|
|
5158
5503
|
];
|
|
5159
5504
|
for (const serverPath of possiblePaths) {
|
|
5160
|
-
if (
|
|
5505
|
+
if (existsSync12(serverPath)) {
|
|
5161
5506
|
return serverPath;
|
|
5162
5507
|
}
|
|
5163
5508
|
}
|
|
@@ -5197,15 +5542,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5197
5542
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
5198
5543
|
return { process: null, port: actualPort };
|
|
5199
5544
|
}
|
|
5200
|
-
const usePnpm =
|
|
5201
|
-
const useNpm = !usePnpm &&
|
|
5545
|
+
const usePnpm = existsSync12(join5(webDir, "pnpm-lock.yaml"));
|
|
5546
|
+
const useNpm = !usePnpm && existsSync12(join5(webDir, "package-lock.json"));
|
|
5202
5547
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
5203
5548
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
5204
5549
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
5205
5550
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
5206
|
-
const runtimeConfigPath =
|
|
5551
|
+
const runtimeConfigPath = join5(webDir, "runtime-config.json");
|
|
5207
5552
|
try {
|
|
5208
|
-
|
|
5553
|
+
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
5209
5554
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
5210
5555
|
} catch (err) {
|
|
5211
5556
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -5231,7 +5576,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5231
5576
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
5232
5577
|
} else if (hasBuild && (isProduction || !hasSource)) {
|
|
5233
5578
|
command = pkgManager;
|
|
5234
|
-
args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"
|
|
5579
|
+
args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
|
|
5235
5580
|
} else if (hasSource) {
|
|
5236
5581
|
if (isProduction && !hasBuild) {
|
|
5237
5582
|
if (!quiet) console.log(" \u{1F4E6} Building Web UI for production...");
|
|
@@ -5243,10 +5588,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5243
5588
|
}
|
|
5244
5589
|
if (!quiet) console.log(" \u2713 Web UI build complete");
|
|
5245
5590
|
command = pkgManager;
|
|
5246
|
-
args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"
|
|
5591
|
+
args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
|
|
5247
5592
|
} else {
|
|
5248
5593
|
command = pkgManager;
|
|
5249
|
-
args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev"
|
|
5594
|
+
args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev"];
|
|
5250
5595
|
}
|
|
5251
5596
|
} else {
|
|
5252
5597
|
if (!quiet) {
|
|
@@ -5274,6 +5619,12 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5274
5619
|
}, startupTimeout);
|
|
5275
5620
|
child.stdout?.on("data", (data) => {
|
|
5276
5621
|
const output = data.toString();
|
|
5622
|
+
if (!quiet) {
|
|
5623
|
+
const lines = output.trim().split("\n").filter((l) => l.trim());
|
|
5624
|
+
for (const line of lines) {
|
|
5625
|
+
console.log(` Web UI: ${line}`);
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5277
5628
|
if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
5278
5629
|
started = true;
|
|
5279
5630
|
clearTimeout(timeout);
|
|
@@ -5281,9 +5632,9 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5281
5632
|
}
|
|
5282
5633
|
});
|
|
5283
5634
|
child.stderr?.on("data", (data) => {
|
|
5284
|
-
const output = data.toString();
|
|
5285
|
-
if (output
|
|
5286
|
-
|
|
5635
|
+
const output = data.toString().trim();
|
|
5636
|
+
if (!quiet && output) {
|
|
5637
|
+
console.error(` Web UI: ${output.slice(0, 500)}`);
|
|
5287
5638
|
}
|
|
5288
5639
|
});
|
|
5289
5640
|
child.on("error", (err) => {
|
|
@@ -5389,8 +5740,8 @@ async function startServer(options = {}) {
|
|
|
5389
5740
|
if (options.workingDirectory) {
|
|
5390
5741
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
5391
5742
|
}
|
|
5392
|
-
if (!
|
|
5393
|
-
|
|
5743
|
+
if (!existsSync12(config.resolvedWorkingDirectory)) {
|
|
5744
|
+
mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
|
|
5394
5745
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
5395
5746
|
}
|
|
5396
5747
|
initDatabase(config.resolvedDatabasePath);
|