@standardagents/builder 0.9.17 → 0.10.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.
@@ -1979,6 +1979,66 @@ var cost_get_default = defineController(async ({ req, params, env }) => {
1979
1979
  }
1980
1980
  });
1981
1981
 
1982
+ // src/api/threads/[id]/fs/index.ts
1983
+ var fs_default = defineController(async ({ req, params, env }) => {
1984
+ const threadId = params.id;
1985
+ if (!threadId) {
1986
+ return Response.json({ error: "Thread ID required" }, { status: 400 });
1987
+ }
1988
+ if (req.method !== "GET") {
1989
+ return Response.json(
1990
+ { error: `Method ${req.method} not allowed` },
1991
+ { status: 405 }
1992
+ );
1993
+ }
1994
+ const url = new URL(req.url);
1995
+ try {
1996
+ const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
1997
+ const stub = env.AGENT_BUILDER_THREAD.get(durableId);
1998
+ if (url.searchParams.has("stats")) {
1999
+ const result2 = await stub.getFileStats();
2000
+ if (!result2.success) {
2001
+ return Response.json({ error: result2.error }, { status: 500 });
2002
+ }
2003
+ return Response.json(result2.stats);
2004
+ }
2005
+ const grepPattern = url.searchParams.get("grep");
2006
+ if (grepPattern) {
2007
+ const path = url.searchParams.get("path") || void 0;
2008
+ const limit = parseInt(url.searchParams.get("limit") || "100", 10);
2009
+ const result2 = await stub.grepFiles(grepPattern, { path, limit });
2010
+ if (!result2.success) {
2011
+ return Response.json({ error: result2.error }, { status: 500 });
2012
+ }
2013
+ return Response.json({ results: result2.results, pattern: grepPattern });
2014
+ }
2015
+ const findPattern = url.searchParams.get("find");
2016
+ if (findPattern) {
2017
+ const type = url.searchParams.get("type");
2018
+ const limit = parseInt(url.searchParams.get("limit") || "100", 10);
2019
+ const result2 = await stub.findFiles(findPattern, {
2020
+ type: type || "all",
2021
+ limit
2022
+ });
2023
+ if (!result2.success) {
2024
+ return Response.json({ error: result2.error }, { status: 500 });
2025
+ }
2026
+ return Response.json({ files: result2.files, pattern: findPattern });
2027
+ }
2028
+ const result = await stub.readdirFile("/");
2029
+ if (!result.success) {
2030
+ return Response.json({ error: result.error }, { status: 500 });
2031
+ }
2032
+ return Response.json({ files: result.files, path: "/" });
2033
+ } catch (error) {
2034
+ console.error(`Error in fs root API for thread ${threadId}:`, error);
2035
+ return Response.json(
2036
+ { error: error.message || "File operation failed" },
2037
+ { status: 500 }
2038
+ );
2039
+ }
2040
+ });
2041
+
1982
2042
  // src/api/threads/[id]/logs.get.ts
1983
2043
  var logs_get_default = defineController(async ({ req, params, env }) => {
1984
2044
  const url = new URL(req.url);
@@ -2018,6 +2078,13 @@ var logs_get_default = defineController(async ({ req, params, env }) => {
2018
2078
  });
2019
2079
 
2020
2080
  // src/api/threads/[id]/message.post.ts
2081
+ var imageProcessingModule = null;
2082
+ async function getImageProcessing() {
2083
+ if (!imageProcessingModule) {
2084
+ imageProcessingModule = await import('@standardagents/builder/image-processing');
2085
+ }
2086
+ return imageProcessingModule;
2087
+ }
2021
2088
  var message_post_default = defineController(async ({ req, params, env }) => {
2022
2089
  const threadId = params.id;
2023
2090
  if (!threadId) {
@@ -2034,16 +2101,79 @@ var message_post_default = defineController(async ({ req, params, env }) => {
2034
2101
  );
2035
2102
  }
2036
2103
  const body = await req.json();
2037
- const { content, role } = body;
2038
- if (!content || typeof content !== "string") {
2104
+ const { content = "", role, attachments, silent } = body;
2105
+ const hasContent = content && typeof content === "string" && content.trim().length > 0;
2106
+ const hasAttachments = attachments && Array.isArray(attachments) && attachments.length > 0;
2107
+ if (!hasContent && !hasAttachments) {
2039
2108
  return Response.json(
2040
- { error: "Message content is required" },
2109
+ { error: "Message content or attachments required" },
2041
2110
  { status: 400 }
2042
2111
  );
2043
2112
  }
2044
2113
  const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
2045
2114
  const stub = env.AGENT_BUILDER_THREAD.get(durableId);
2046
- return await stub.sendMessage(threadId, content, role);
2115
+ let attachmentRefs;
2116
+ if (attachments && attachments.length > 0) {
2117
+ attachmentRefs = [];
2118
+ for (const attachment of attachments) {
2119
+ const attachmentId = crypto.randomUUID();
2120
+ const timestamp = Date.now();
2121
+ let fileData = attachment.data;
2122
+ let mimeType = attachment.mimeType;
2123
+ let width = attachment.width;
2124
+ let height = attachment.height;
2125
+ const imgProc = await getImageProcessing();
2126
+ if (mimeType.startsWith("image/") && imgProc.needsProcessing(fileData, mimeType)) {
2127
+ try {
2128
+ const buffer = imgProc.base64ToArrayBuffer(fileData);
2129
+ const processed = await imgProc.processImage(buffer, mimeType);
2130
+ fileData = imgProc.arrayBufferToBase64(processed.data);
2131
+ mimeType = processed.mimeType;
2132
+ width = processed.width;
2133
+ height = processed.height;
2134
+ console.log(
2135
+ `[image-processing] Processed ${attachment.name}: ${(buffer.byteLength / 1024 / 1024).toFixed(2)}MB \u2192 ${(processed.data.byteLength / 1024 / 1024).toFixed(2)}MB, ${processed.width}x${processed.height}, ${processed.mimeType}`
2136
+ );
2137
+ } catch (err) {
2138
+ console.error(`[image-processing] Failed to process ${attachment.name}:`, err);
2139
+ }
2140
+ }
2141
+ const ext = mimeType === "image/png" ? "png" : mimeType === "image/jpeg" ? "jpg" : attachment.name.split(".").pop() || "bin";
2142
+ const path = `/attachments/${timestamp}-${attachmentId}.${ext}`;
2143
+ const metadata = {};
2144
+ if (width) metadata.width = width;
2145
+ if (height) metadata.height = height;
2146
+ const base64Size = fileData.length;
2147
+ const binarySize = Math.ceil(base64Size * 3 / 4);
2148
+ console.log(`[writeFile] Storing ${attachment.name}: ${(binarySize / 1024 / 1024).toFixed(2)}MB binary (${(base64Size / 1024 / 1024).toFixed(2)}MB base64) to ${path}`);
2149
+ const result = await stub.writeFile(
2150
+ path,
2151
+ fileData,
2152
+ mimeType,
2153
+ {
2154
+ metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
2155
+ thumbnail: attachment.thumbnail
2156
+ }
2157
+ );
2158
+ console.log(`[writeFile] Result:`, result.success ? "success" : result.error);
2159
+ if (!result.success) {
2160
+ console.error(`Failed to store attachment ${attachment.name}:`, result.error);
2161
+ continue;
2162
+ }
2163
+ const ref = {
2164
+ id: attachmentId,
2165
+ type: "file",
2166
+ path,
2167
+ name: attachment.name,
2168
+ mimeType
2169
+ };
2170
+ if (width) ref.width = width;
2171
+ if (height) ref.height = height;
2172
+ attachmentRefs.push(ref);
2173
+ }
2174
+ }
2175
+ const attachmentsJson = attachmentRefs && attachmentRefs.length > 0 ? JSON.stringify(attachmentRefs) : void 0;
2176
+ return await stub.sendMessage(threadId, content, role, attachmentsJson);
2047
2177
  } catch (error) {
2048
2178
  console.error(`Error sending message to thread ${threadId}:`, error);
2049
2179
  return Response.json(
@@ -2184,6 +2314,176 @@ var stream_default = defineController(async ({ req, params, env }) => {
2184
2314
  }
2185
2315
  });
2186
2316
 
2317
+ // src/api/threads/[id]/fs/[...path].ts
2318
+ var path_default = defineController(async ({ req, params, env }) => {
2319
+ console.log("[fs] params received:", JSON.stringify(params));
2320
+ const threadId = params.id;
2321
+ const pathParam = params._ || params.path || params["*"] || "";
2322
+ if (!threadId) {
2323
+ return Response.json({ error: "Thread ID required" }, { status: 400 });
2324
+ }
2325
+ console.log("[fs] threadId:", threadId, "pathParam:", pathParam);
2326
+ const path = pathParam ? `/${pathParam}` : "/";
2327
+ const url = new URL(req.url);
2328
+ try {
2329
+ const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
2330
+ const stub = env.AGENT_BUILDER_THREAD.get(durableId);
2331
+ switch (req.method) {
2332
+ case "GET":
2333
+ return handleGet(stub, path, url);
2334
+ case "PUT":
2335
+ return handlePut(stub, path, req);
2336
+ case "DELETE":
2337
+ return handleDelete(stub, path);
2338
+ default:
2339
+ return Response.json(
2340
+ { error: `Method ${req.method} not allowed` },
2341
+ { status: 405 }
2342
+ );
2343
+ }
2344
+ } catch (error) {
2345
+ console.error(`Error in fs API for thread ${threadId}:`, error);
2346
+ return Response.json(
2347
+ { error: error.message || "File operation failed" },
2348
+ { status: 500 }
2349
+ );
2350
+ }
2351
+ });
2352
+ async function handleGet(stub, path, url) {
2353
+ if (url.searchParams.has("stats")) {
2354
+ const result2 = await stub.getFileStats();
2355
+ if (!result2.success) {
2356
+ return Response.json({ error: result2.error }, { status: 500 });
2357
+ }
2358
+ return Response.json(result2.stats);
2359
+ }
2360
+ if (url.searchParams.has("thumbnail")) {
2361
+ const result2 = await stub.getFileThumbnail(path);
2362
+ if (!result2.success) {
2363
+ return Response.json({ error: result2.error }, { status: 404 });
2364
+ }
2365
+ const binary2 = atob(result2.data);
2366
+ const bytes2 = new Uint8Array(binary2.length);
2367
+ for (let i = 0; i < binary2.length; i++) {
2368
+ bytes2[i] = binary2.charCodeAt(i);
2369
+ }
2370
+ return new Response(bytes2, {
2371
+ headers: {
2372
+ "Content-Type": "image/jpeg",
2373
+ // Thumbnails are typically JPEG
2374
+ "Cache-Control": "public, max-age=31536000"
2375
+ // Cache for 1 year
2376
+ }
2377
+ });
2378
+ }
2379
+ const statResult = await stub.statFile(path);
2380
+ if (!statResult.success) {
2381
+ return Response.json({ error: "Not found" }, { status: 404 });
2382
+ }
2383
+ const file = statResult.file;
2384
+ if (file.isDirectory) {
2385
+ const result2 = await stub.readdirFile(path);
2386
+ if (!result2.success) {
2387
+ return Response.json({ error: result2.error }, { status: 500 });
2388
+ }
2389
+ return Response.json({ files: result2.files, path });
2390
+ }
2391
+ if (file.storage !== "local") {
2392
+ return Response.json({
2393
+ file,
2394
+ external: true,
2395
+ location: file.location
2396
+ });
2397
+ }
2398
+ const acceptHeader = url.searchParams.get("format") || "auto";
2399
+ if (acceptHeader === "json") {
2400
+ return Response.json({ file });
2401
+ }
2402
+ const result = await stub.readFile(path);
2403
+ if (!result.success) {
2404
+ return Response.json({ error: result.error }, { status: 404 });
2405
+ }
2406
+ const binary = atob(result.data);
2407
+ const bytes = new Uint8Array(binary.length);
2408
+ for (let i = 0; i < binary.length; i++) {
2409
+ bytes[i] = binary.charCodeAt(i);
2410
+ }
2411
+ return new Response(bytes, {
2412
+ headers: {
2413
+ "Content-Type": file.mimeType,
2414
+ "Content-Length": String(file.size),
2415
+ "Content-Disposition": `inline; filename="${file.name}"`
2416
+ }
2417
+ });
2418
+ }
2419
+ async function handlePut(stub, path, req) {
2420
+ const contentType = req.headers.get("Content-Type") || "";
2421
+ if (contentType.includes("application/json")) {
2422
+ const body = await req.json();
2423
+ if (body.type === "directory") {
2424
+ const result2 = await stub.mkdirFile(path);
2425
+ if (!result2.success) {
2426
+ return Response.json({ error: result2.error }, { status: 400 });
2427
+ }
2428
+ return Response.json({ directory: result2.directory }, { status: 201 });
2429
+ }
2430
+ if (body.location) {
2431
+ const result2 = await stub.linkFile(path, body.location, {
2432
+ mimeType: body.mimeType,
2433
+ size: body.size,
2434
+ metadata: body.metadata
2435
+ });
2436
+ if (!result2.success) {
2437
+ return Response.json({ error: result2.error }, { status: 400 });
2438
+ }
2439
+ return Response.json({ file: result2.file }, { status: 201 });
2440
+ }
2441
+ if (body.data) {
2442
+ const result2 = await stub.writeFile(path, body.data, body.mimeType || "application/octet-stream", {
2443
+ metadata: body.metadata,
2444
+ thumbnail: body.thumbnail
2445
+ });
2446
+ if (!result2.success) {
2447
+ return Response.json({ error: result2.error }, { status: 400 });
2448
+ }
2449
+ return Response.json({ file: result2.file }, { status: 201 });
2450
+ }
2451
+ return Response.json({ error: "Invalid request body" }, { status: 400 });
2452
+ }
2453
+ const data = await req.arrayBuffer();
2454
+ const mimeType = contentType.split(";")[0].trim() || "application/octet-stream";
2455
+ const bytes = new Uint8Array(data);
2456
+ let binary = "";
2457
+ for (let i = 0; i < bytes.length; i++) {
2458
+ binary += String.fromCharCode(bytes[i]);
2459
+ }
2460
+ const base64 = btoa(binary);
2461
+ const result = await stub.writeFile(path, base64, mimeType, {});
2462
+ if (!result.success) {
2463
+ return Response.json({ error: result.error }, { status: 400 });
2464
+ }
2465
+ return Response.json({ file: result.file }, { status: 201 });
2466
+ }
2467
+ async function handleDelete(stub, path) {
2468
+ const statResult = await stub.statFile(path);
2469
+ if (!statResult.success) {
2470
+ return Response.json({ error: "Not found" }, { status: 404 });
2471
+ }
2472
+ const file = statResult.file;
2473
+ if (file.isDirectory) {
2474
+ const result = await stub.rmdirFile(path);
2475
+ if (!result.success) {
2476
+ return Response.json({ error: result.error }, { status: 400 });
2477
+ }
2478
+ } else {
2479
+ const result = await stub.unlinkFile(path);
2480
+ if (!result.success) {
2481
+ return Response.json({ error: result.error }, { status: 400 });
2482
+ }
2483
+ }
2484
+ return new Response(null, { status: 204 });
2485
+ }
2486
+
2187
2487
  // src/api/threads/[id]/logs/[logId].get.ts
2188
2488
  var logId_get_default = defineController(async ({ req, params, env }) => {
2189
2489
  const threadId = params.id;
@@ -2344,12 +2644,14 @@ var routeHandlers = {
2344
2644
  "POST:/models/available": available_post_default,
2345
2645
  "POST:/models/endpoints": endpoints_post_default,
2346
2646
  "GET:/threads/:id/cost": cost_get_default,
2647
+ "GET:/threads/:id/fs": fs_default,
2347
2648
  "GET:/threads/:id/logs": logs_get_default,
2348
2649
  "POST:/threads/:id/message": message_post_default,
2349
2650
  "GET:/threads/:id/messages": messages_get_default,
2350
2651
  "POST:/threads/:id/rpc": rpc_post_default,
2351
2652
  "POST:/threads/:id/stop": stop_post_default,
2352
2653
  "GET:/threads/:id/stream": stream_default,
2654
+ "GET:/threads/:id/fs/**": path_default,
2353
2655
  "GET:/threads/:id/logs/:logId": logId_get_default,
2354
2656
  "DELETE:/threads/:id/messages/:messageId": messageId_delete_default,
2355
2657
  "PATCH:/threads/:id/messages/:messageId": messageId_patch_default