@standardagents/builder 0.10.1-next.bbd142a → 0.11.0-next.99fb790

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.
@@ -1,3 +1,4 @@
1
+ import '@standardagents/spec';
1
2
  import { z } from 'zod';
2
3
 
3
4
  // ../../node_modules/.pnpm/rou3@0.7.10/node_modules/rou3/dist/index.mjs
@@ -77,8 +78,6 @@ function getParamRegexp(segment) {
77
78
  const regex = segment.replace(/:(\w+)/g, (_, id) => `(?<${id}>[^/]+)`).replace(/\./g, "\\.");
78
79
  return /* @__PURE__ */ new RegExp(`^${regex}$`);
79
80
  }
80
-
81
- // src/router/index.ts
82
81
  function defineController(controller) {
83
82
  return controller;
84
83
  }
@@ -145,8 +144,8 @@ var agents_get_default = defineController(async ({ agents, agentNames, prompts,
145
144
  side_a_stop_on_response: definition.sideA?.stopOnResponse !== void 0 ? definition.sideA.stopOnResponse : true,
146
145
  side_a_stop_tool: definition.sideA?.stopTool || null,
147
146
  side_a_stop_tool_response_property: definition.sideA?.stopToolResponseProperty || null,
148
- side_a_max_turns: definition.sideA?.maxTurns || null,
149
- side_a_end_conversation_tool: definition.sideA?.endConversationTool || null,
147
+ side_a_max_steps: definition.sideA?.maxSteps || null,
148
+ side_a_end_session_tool: definition.sideA?.endSessionTool || null,
150
149
  side_a_manual_stop_condition: definition.sideA?.manualStopCondition || false,
151
150
  // Side B configuration (if dual_ai)
152
151
  side_b_label: definition.sideB?.label || null,
@@ -155,16 +154,14 @@ var agents_get_default = defineController(async ({ agents, agentNames, prompts,
155
154
  side_b_stop_on_response: definition.sideB?.stopOnResponse !== void 0 ? definition.sideB.stopOnResponse : true,
156
155
  side_b_stop_tool: definition.sideB?.stopTool || null,
157
156
  side_b_stop_tool_response_property: definition.sideB?.stopToolResponseProperty || null,
158
- side_b_max_turns: definition.sideB?.maxTurns || null,
159
- side_b_end_conversation_tool: definition.sideB?.endConversationTool || null,
157
+ side_b_max_steps: definition.sideB?.maxSteps || null,
158
+ side_b_end_session_tool: definition.sideB?.endSessionTool || null,
160
159
  side_b_manual_stop_condition: definition.sideB?.manualStopCondition || false,
161
160
  // Session configuration
162
161
  max_session_turns: definition.maxSessionTurns || null,
163
162
  // Tool exposure
164
163
  expose_as_tool: definition.exposeAsTool || false,
165
164
  tool_description: definition.toolDescription || null,
166
- // Tags
167
- tags: definition.tags || [],
168
165
  created_at: Math.floor(Date.now() / 1e3)
169
166
  };
170
167
  } catch (error) {
@@ -733,10 +730,7 @@ var prompts_get_default = defineController(async ({ prompts, promptNames, models
733
730
  include_past_tools: definition.includePastTools || false,
734
731
  parallel_tool_calls: definition.parallelToolCalls || false,
735
732
  tool_choice: definition.toolChoice || "auto",
736
- before_tool: definition.beforeTool || null,
737
- after_tool: definition.afterTool || null,
738
733
  tools,
739
- prompts: definition.handoffAgents || [],
740
734
  reasoning: definition.reasoning || null,
741
735
  created_at: Math.floor(Date.now() / 1e3)
742
736
  };
@@ -777,6 +771,11 @@ var providers_get_default = defineController(async ({ env }) => {
777
771
 
778
772
  // src/api/threads/index.post.ts
779
773
  var index_post_default3 = defineController(async ({ req, env }) => {
774
+ const authResult = await requireAuth(req, env);
775
+ if (authResult instanceof Response) {
776
+ return authResult;
777
+ }
778
+ const auth = authResult;
780
779
  let body;
781
780
  try {
782
781
  body = await req.json();
@@ -786,7 +785,8 @@ var index_post_default3 = defineController(async ({ req, env }) => {
786
785
  { status: 400 }
787
786
  );
788
787
  }
789
- const { agent_id, user_id, initial_messages, data, tags } = body;
788
+ const { agent_id, initial_messages, data, tags } = body;
789
+ const user_id = auth.authType === "super_admin" ? null : auth.user.id;
790
790
  if (!agent_id) {
791
791
  return Response.json(
792
792
  { error: "Missing required field: agent_id" },
@@ -828,7 +828,7 @@ var index_post_default3 = defineController(async ({ req, env }) => {
828
828
  try {
829
829
  const thread = await agentBuilder.createThread({
830
830
  agent_name: agent_id,
831
- user_id,
831
+ user_id: user_id ?? void 0,
832
832
  tags
833
833
  });
834
834
  return Response.json({
@@ -848,16 +848,32 @@ var index_post_default3 = defineController(async ({ req, env }) => {
848
848
  });
849
849
 
850
850
  // src/api/threads/index.ts
851
+ function resolveIconUrl(icon, origin) {
852
+ if (!icon) return void 0;
853
+ if (icon.startsWith("http://") || icon.startsWith("https://")) {
854
+ return icon;
855
+ }
856
+ if (icon.startsWith("/")) {
857
+ return `${origin}${icon}`;
858
+ }
859
+ return icon;
860
+ }
851
861
  var threads_default = defineController(async ({ req, env }) => {
852
862
  if (req.method !== "GET") {
853
863
  return new Response("Method Not Allowed", { status: 405 });
854
864
  }
865
+ const authResult = await requireAuth(req, env);
866
+ if (authResult instanceof Response) {
867
+ return authResult;
868
+ }
869
+ const auth = authResult;
855
870
  try {
856
871
  const url = new URL(req.url);
872
+ const origin = url.origin;
857
873
  const limit = parseInt(url.searchParams.get("limit") || "50", 10);
858
874
  const offset = parseInt(url.searchParams.get("offset") || "0", 10);
859
875
  const agentName = url.searchParams.get("agent_id");
860
- const userId = url.searchParams.get("user_id");
876
+ const userId = auth.user.role === "admin" ? void 0 : auth.user.id;
861
877
  const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
862
878
  const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
863
879
  const result = await agentBuilder.listThreads({
@@ -872,11 +888,14 @@ var threads_default = defineController(async ({ req, env }) => {
872
888
  try {
873
889
  const agentDef = await agentBuilder.loadAgent(thread.agent_name);
874
890
  agentDetails = {
875
- title: agentDef.title,
876
- type: agentDef.type || "ai_human"
891
+ name: thread.agent_name,
892
+ title: agentDef.title || thread.agent_name,
893
+ type: agentDef.type || "ai_human",
894
+ description: agentDef.description,
895
+ icon: resolveIconUrl(agentDef.icon, origin)
877
896
  };
878
897
  } catch {
879
- agentDetails = { title: thread.agent_name, type: "unknown" };
898
+ agentDetails = { name: thread.agent_name, title: thread.agent_name, type: "unknown" };
880
899
  }
881
900
  return {
882
901
  id: thread.id,
@@ -1065,6 +1084,7 @@ var index_get_default2 = defineController(async ({ req, env }) => {
1065
1084
  });
1066
1085
 
1067
1086
  // src/api/users/index.post.ts
1087
+ var VALID_ROLES = ["admin", "user"];
1068
1088
  var index_post_default4 = defineController(async ({ req, env }) => {
1069
1089
  try {
1070
1090
  const authResult = await requireAdmin(req, env);
@@ -1072,7 +1092,7 @@ var index_post_default4 = defineController(async ({ req, env }) => {
1072
1092
  return authResult;
1073
1093
  }
1074
1094
  const body = await req.json();
1075
- const { username, password, role = "admin" } = body;
1095
+ const { username, password, role = "user" } = body;
1076
1096
  if (!username || !password) {
1077
1097
  return Response.json(
1078
1098
  { error: "Username and password are required" },
@@ -1097,6 +1117,12 @@ var index_post_default4 = defineController(async ({ req, env }) => {
1097
1117
  { status: 400 }
1098
1118
  );
1099
1119
  }
1120
+ if (!VALID_ROLES.includes(role)) {
1121
+ return Response.json(
1122
+ { error: `Invalid role. Must be one of: ${VALID_ROLES.join(", ")}` },
1123
+ { status: 400 }
1124
+ );
1125
+ }
1100
1126
  const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
1101
1127
  const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
1102
1128
  const existingUser = await agentBuilder.getUserByUsername(username);
@@ -1168,8 +1194,8 @@ var name_get_default = defineController(async ({ params, agents, prompts, prompt
1168
1194
  side_a_stop_on_response: definition.sideA?.stopOnResponse !== void 0 ? definition.sideA.stopOnResponse : true,
1169
1195
  side_a_stop_tool: definition.sideA?.stopTool || null,
1170
1196
  side_a_stop_tool_response_property: definition.sideA?.stopToolResponseProperty || null,
1171
- side_a_max_turns: definition.sideA?.maxTurns || null,
1172
- side_a_end_conversation_tool: definition.sideA?.endConversationTool || null,
1197
+ side_a_max_steps: definition.sideA?.maxSteps || null,
1198
+ side_a_end_session_tool: definition.sideA?.endSessionTool || null,
1173
1199
  side_a_manual_stop_condition: definition.sideA?.manualStopCondition || false,
1174
1200
  // Side B configuration (if dual_ai)
1175
1201
  side_b_label: definition.sideB?.label || null,
@@ -1178,16 +1204,14 @@ var name_get_default = defineController(async ({ params, agents, prompts, prompt
1178
1204
  side_b_stop_on_response: definition.sideB?.stopOnResponse !== void 0 ? definition.sideB.stopOnResponse : true,
1179
1205
  side_b_stop_tool: definition.sideB?.stopTool || null,
1180
1206
  side_b_stop_tool_response_property: definition.sideB?.stopToolResponseProperty || null,
1181
- side_b_max_turns: definition.sideB?.maxTurns || null,
1182
- side_b_end_conversation_tool: definition.sideB?.endConversationTool || null,
1207
+ side_b_max_steps: definition.sideB?.maxSteps || null,
1208
+ side_b_end_session_tool: definition.sideB?.endSessionTool || null,
1183
1209
  side_b_manual_stop_condition: definition.sideB?.manualStopCondition || false,
1184
1210
  // Session configuration
1185
1211
  max_session_turns: definition.maxSessionTurns || null,
1186
1212
  // Tool exposure
1187
1213
  expose_as_tool: definition.exposeAsTool || false,
1188
1214
  tool_description: definition.toolDescription || null,
1189
- // Tags
1190
- tags: definition.tags || [],
1191
1215
  created_at: Math.floor(Date.now() / 1e3)
1192
1216
  };
1193
1217
  return Response.json({ agent });
@@ -1300,10 +1324,7 @@ var name_get_default2 = defineController(async ({ params, prompts, models, model
1300
1324
  include_past_tools: definition.includePastTools || false,
1301
1325
  parallel_tool_calls: definition.parallelToolCalls || false,
1302
1326
  tool_choice: definition.toolChoice || "auto",
1303
- before_tool: definition.beforeTool || null,
1304
- after_tool: definition.afterTool || null,
1305
1327
  tools,
1306
- prompts: definition.handoffAgents || [],
1307
1328
  reasoning: definition.reasoning || null,
1308
1329
  created_at: Math.floor(Date.now() / 1e3)
1309
1330
  };
@@ -1317,8 +1338,24 @@ var name_get_default2 = defineController(async ({ params, prompts, models, model
1317
1338
  }
1318
1339
  });
1319
1340
 
1341
+ // src/utils/permissions.ts
1342
+ function canAccessThread(auth, thread) {
1343
+ if (auth.user.role === "admin") {
1344
+ return true;
1345
+ }
1346
+ if (thread.user_id === null) {
1347
+ return false;
1348
+ }
1349
+ return thread.user_id === auth.user.id;
1350
+ }
1351
+
1320
1352
  // src/api/threads/[id].delete.ts
1321
1353
  var id_delete_default2 = defineController(async ({ req, env, params }) => {
1354
+ const authResult = await requireAuth(req, env);
1355
+ if (authResult instanceof Response) {
1356
+ return authResult;
1357
+ }
1358
+ const auth = authResult;
1322
1359
  const threadId = params.id;
1323
1360
  if (!threadId) {
1324
1361
  return Response.json({ error: "Missing thread ID" }, { status: 400 });
@@ -1332,6 +1369,12 @@ var id_delete_default2 = defineController(async ({ req, env, params }) => {
1332
1369
  { status: 404 }
1333
1370
  );
1334
1371
  }
1372
+ if (!canAccessThread(auth, thread)) {
1373
+ return Response.json(
1374
+ { error: "Forbidden: You don't have access to this thread" },
1375
+ { status: 403 }
1376
+ );
1377
+ }
1335
1378
  try {
1336
1379
  await agentBuilder.deleteThread(threadId);
1337
1380
  const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
@@ -1359,6 +1402,11 @@ var id_patch_default = defineController(async ({ req, env, params }) => {
1359
1402
  if (req.method !== "PATCH") {
1360
1403
  return new Response("Method Not Allowed", { status: 405 });
1361
1404
  }
1405
+ const authResult = await requireAuth(req, env);
1406
+ if (authResult instanceof Response) {
1407
+ return authResult;
1408
+ }
1409
+ const auth = authResult;
1362
1410
  const threadId = params?.id;
1363
1411
  if (!threadId) {
1364
1412
  return Response.json({ error: "Thread ID required" }, { status: 400 });
@@ -1375,6 +1423,12 @@ var id_patch_default = defineController(async ({ req, env, params }) => {
1375
1423
  { status: 404 }
1376
1424
  );
1377
1425
  }
1426
+ if (!canAccessThread(auth, existingThread)) {
1427
+ return Response.json(
1428
+ { error: "Forbidden: You don't have access to this thread" },
1429
+ { status: 403 }
1430
+ );
1431
+ }
1378
1432
  const updates = {};
1379
1433
  if (tags !== void 0) {
1380
1434
  if (!Array.isArray(tags)) {
@@ -1447,12 +1501,42 @@ var id_patch_default = defineController(async ({ req, env, params }) => {
1447
1501
  });
1448
1502
 
1449
1503
  // src/api/threads/[id].ts
1504
+ function resolveIconUrl2(icon, origin) {
1505
+ if (!icon) return void 0;
1506
+ if (icon.startsWith("http://") || icon.startsWith("https://")) {
1507
+ return icon;
1508
+ }
1509
+ if (icon.startsWith("/")) {
1510
+ return `${origin}${icon}`;
1511
+ }
1512
+ return icon;
1513
+ }
1450
1514
  var id_default = defineController(async ({ req, params, env }) => {
1515
+ const authResult = await requireAuth(req, env);
1516
+ if (authResult instanceof Response) {
1517
+ return authResult;
1518
+ }
1519
+ const auth = authResult;
1451
1520
  const threadId = params.id;
1452
1521
  if (!threadId) {
1453
1522
  return Response.json({ error: "Thread ID required" }, { status: 400 });
1454
1523
  }
1455
1524
  try {
1525
+ const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
1526
+ const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
1527
+ const thread = await agentBuilder.getThread(threadId);
1528
+ if (!thread) {
1529
+ return Response.json(
1530
+ { error: `Thread not found: ${threadId}` },
1531
+ { status: 404 }
1532
+ );
1533
+ }
1534
+ if (!canAccessThread(auth, thread)) {
1535
+ return Response.json(
1536
+ { error: "Forbidden: You don't have access to this thread" },
1537
+ { status: 403 }
1538
+ );
1539
+ }
1456
1540
  const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
1457
1541
  const stub = env.AGENT_BUILDER_THREAD.get(durableId);
1458
1542
  const doData = await stub.getThreadMeta(threadId);
@@ -1462,6 +1546,10 @@ var id_default = defineController(async ({ req, params, env }) => {
1462
1546
  { status: 404 }
1463
1547
  );
1464
1548
  }
1549
+ if (doData.agent?.icon) {
1550
+ const origin = new URL(req.url).origin;
1551
+ doData.agent.icon = resolveIconUrl2(doData.agent.icon, origin);
1552
+ }
1465
1553
  return Response.json(doData);
1466
1554
  } catch (error) {
1467
1555
  console.error(`Error fetching thread ${threadId}:`, error);
@@ -1624,7 +1712,7 @@ var login_post_default = defineController(async ({ req, env }) => {
1624
1712
  { status: 400 }
1625
1713
  );
1626
1714
  }
1627
- if (env.SUPER_ADMIN_PASSWORD && password === env.SUPER_ADMIN_PASSWORD) {
1715
+ if (env.SUPER_ADMIN_PASSWORD && username === "admin" && password === env.SUPER_ADMIN_PASSWORD) {
1628
1716
  if (!env.ENCRYPTION_KEY) {
1629
1717
  return Response.json(
1630
1718
  { error: "Server misconfigured: ENCRYPTION_KEY required for super admin" },
@@ -2077,7 +2165,48 @@ var logs_get_default = defineController(async ({ req, params, env }) => {
2077
2165
  }
2078
2166
  });
2079
2167
 
2080
- // src/api/threads/[id]/message.post.ts
2168
+ // src/api/threads/[id]/messages.get.ts
2169
+ var messages_get_default = defineController(async ({ req, params, env }) => {
2170
+ const url = new URL(req.url);
2171
+ const threadId = params.id;
2172
+ if (!threadId) {
2173
+ return Response.json({ error: "Thread ID required" }, { status: 400 });
2174
+ }
2175
+ try {
2176
+ const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
2177
+ const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
2178
+ const thread = await agentBuilder.getThread(threadId);
2179
+ if (!thread) {
2180
+ return Response.json(
2181
+ { error: `Thread not found: ${threadId}` },
2182
+ { status: 404 }
2183
+ );
2184
+ }
2185
+ const limit = parseInt(url.searchParams.get("limit") || "100", 10);
2186
+ const offset = parseInt(url.searchParams.get("offset") || "0", 10);
2187
+ const order = url.searchParams.get("order") === "asc" ? "ASC" : "DESC";
2188
+ const includeSilent = url.searchParams.get("includeSilent") === "true";
2189
+ const maxDepth = parseInt(url.searchParams.get("depth") || "0", 10);
2190
+ const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
2191
+ const stub = env.AGENT_BUILDER_THREAD.get(durableId);
2192
+ const result = await stub.getMessages(
2193
+ limit,
2194
+ offset,
2195
+ order,
2196
+ includeSilent,
2197
+ maxDepth
2198
+ );
2199
+ return Response.json(result);
2200
+ } catch (error) {
2201
+ console.error(`Error fetching messages for thread ${threadId}:`, error);
2202
+ return Response.json(
2203
+ { error: error.message || "Failed to fetch messages" },
2204
+ { status: 500 }
2205
+ );
2206
+ }
2207
+ });
2208
+
2209
+ // src/api/threads/[id]/messages.post.ts
2081
2210
  function needsProcessing(base64Data, mimeType) {
2082
2211
  const binarySize = Math.ceil(base64Data.length * 3 / 4);
2083
2212
  const MAX_SIZE = 2 * 1024 * 1024;
@@ -2089,7 +2218,7 @@ function needsProcessing(base64Data, mimeType) {
2089
2218
  }
2090
2219
  return false;
2091
2220
  }
2092
- var message_post_default = defineController(async ({ req, params, env }) => {
2221
+ var messages_post_default = defineController(async ({ req, params, env }) => {
2093
2222
  const threadId = params.id;
2094
2223
  if (!threadId) {
2095
2224
  return Response.json({ error: "Thread ID required" }, { status: 400 });
@@ -2148,9 +2277,6 @@ var message_post_default = defineController(async ({ req, params, env }) => {
2148
2277
  }
2149
2278
  const ext = mimeType === "image/png" ? "png" : mimeType === "image/jpeg" ? "jpg" : attachment.name.split(".").pop() || "bin";
2150
2279
  const path = `/attachments/${timestamp}-${attachmentId}.${ext}`;
2151
- const metadata = {};
2152
- if (width) metadata.width = width;
2153
- if (height) metadata.height = height;
2154
2280
  const base64Size = fileData.length;
2155
2281
  const binarySize = Math.ceil(base64Size * 3 / 4);
2156
2282
  console.log(`[writeFile] Storing ${attachment.name}: ${(binarySize / 1024 / 1024).toFixed(2)}MB binary (${(base64Size / 1024 / 1024).toFixed(2)}MB base64) to ${path}`);
@@ -2159,8 +2285,9 @@ var message_post_default = defineController(async ({ req, params, env }) => {
2159
2285
  fileData,
2160
2286
  mimeType,
2161
2287
  {
2162
- metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
2163
- thumbnail: attachment.thumbnail
2288
+ thumbnail: attachment.thumbnail,
2289
+ width,
2290
+ height
2164
2291
  }
2165
2292
  );
2166
2293
  console.log(`[writeFile] Result:`, result.success ? "success" : result.error);
@@ -2191,47 +2318,6 @@ var message_post_default = defineController(async ({ req, params, env }) => {
2191
2318
  }
2192
2319
  });
2193
2320
 
2194
- // src/api/threads/[id]/messages.get.ts
2195
- var messages_get_default = defineController(async ({ req, params, env }) => {
2196
- const url = new URL(req.url);
2197
- const threadId = params.id;
2198
- if (!threadId) {
2199
- return Response.json({ error: "Thread ID required" }, { status: 400 });
2200
- }
2201
- try {
2202
- const agentBuilderId = env.AGENT_BUILDER.idFromName("singleton");
2203
- const agentBuilder = env.AGENT_BUILDER.get(agentBuilderId);
2204
- const thread = await agentBuilder.getThread(threadId);
2205
- if (!thread) {
2206
- return Response.json(
2207
- { error: `Thread not found: ${threadId}` },
2208
- { status: 404 }
2209
- );
2210
- }
2211
- const limit = parseInt(url.searchParams.get("limit") || "100", 10);
2212
- const offset = parseInt(url.searchParams.get("offset") || "0", 10);
2213
- const order = url.searchParams.get("order") === "asc" ? "ASC" : "DESC";
2214
- const includeSilent = url.searchParams.get("includeSilent") === "true";
2215
- const maxDepth = parseInt(url.searchParams.get("depth") || "0", 10);
2216
- const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
2217
- const stub = env.AGENT_BUILDER_THREAD.get(durableId);
2218
- const result = await stub.getMessages(
2219
- limit,
2220
- offset,
2221
- order,
2222
- includeSilent,
2223
- maxDepth
2224
- );
2225
- return Response.json(result);
2226
- } catch (error) {
2227
- console.error(`Error fetching messages for thread ${threadId}:`, error);
2228
- return Response.json(
2229
- { error: error.message || "Failed to fetch messages" },
2230
- { status: 500 }
2231
- );
2232
- }
2233
- });
2234
-
2235
2321
  // src/api/threads/[id]/rpc.post.ts
2236
2322
  var rpc_post_default = defineController(async ({ req, params, env }) => {
2237
2323
  const threadId = params.id;
@@ -2323,6 +2409,23 @@ var stream_default = defineController(async ({ req, params, env }) => {
2323
2409
  });
2324
2410
 
2325
2411
  // src/api/threads/[id]/fs/[...path].ts
2412
+ var CHUNK_SIZE = 1.75 * 1024 * 1024;
2413
+ function toAttachmentRef(file) {
2414
+ const metadata = file.metadata;
2415
+ const width = file.width ?? metadata?.width;
2416
+ const height = file.height ?? metadata?.height;
2417
+ return {
2418
+ id: file.path,
2419
+ // Use path as unique ID
2420
+ type: "file",
2421
+ path: file.path,
2422
+ name: file.name,
2423
+ mimeType: file.mimeType,
2424
+ size: file.size,
2425
+ ...width && { width },
2426
+ ...height && { height }
2427
+ };
2428
+ }
2326
2429
  var path_default = defineController(async ({ req, params, env }) => {
2327
2430
  console.log("[fs] params received:", JSON.stringify(params));
2328
2431
  const threadId = params.id;
@@ -2336,11 +2439,30 @@ var path_default = defineController(async ({ req, params, env }) => {
2336
2439
  try {
2337
2440
  const durableId = env.AGENT_BUILDER_THREAD.idFromName(threadId);
2338
2441
  const stub = env.AGENT_BUILDER_THREAD.get(durableId);
2442
+ const uploadMatch = path.match(/^(.+)\/upload\/(start|complete|chunk\/(\d+))$/);
2443
+ if (uploadMatch) {
2444
+ const filePath = uploadMatch[1];
2445
+ const action = uploadMatch[2];
2446
+ const chunkIndex = uploadMatch[3] ? parseInt(uploadMatch[3], 10) : void 0;
2447
+ if (action === "start" && req.method === "POST") {
2448
+ return handleChunkedUploadStart(stub, filePath, req);
2449
+ }
2450
+ if (action === "complete" && req.method === "POST") {
2451
+ return handleChunkedUploadComplete(stub, filePath, req);
2452
+ }
2453
+ if (action.startsWith("chunk/") && req.method === "PUT" && chunkIndex !== void 0) {
2454
+ return handleChunkedUploadChunk(stub, filePath, chunkIndex, req);
2455
+ }
2456
+ return Response.json(
2457
+ { error: `Invalid upload action: ${action} with method ${req.method}` },
2458
+ { status: 400 }
2459
+ );
2460
+ }
2339
2461
  switch (req.method) {
2340
2462
  case "GET":
2341
2463
  return handleGet(stub, path, url);
2342
- case "PUT":
2343
- return handlePut(stub, path, req);
2464
+ case "POST":
2465
+ return handlePost(stub, path, req);
2344
2466
  case "DELETE":
2345
2467
  return handleDelete(stub, path);
2346
2468
  default:
@@ -2407,6 +2529,9 @@ async function handleGet(stub, path, url) {
2407
2529
  if (acceptHeader === "json") {
2408
2530
  return Response.json({ file });
2409
2531
  }
2532
+ if (file.isChunked && file.chunkCount) {
2533
+ return streamChunkedFile(stub, path, file);
2534
+ }
2410
2535
  const result = await stub.readFile(path);
2411
2536
  if (!result.success) {
2412
2537
  return Response.json({ error: result.error }, { status: 404 });
@@ -2424,7 +2549,40 @@ async function handleGet(stub, path, url) {
2424
2549
  }
2425
2550
  });
2426
2551
  }
2427
- async function handlePut(stub, path, req) {
2552
+ function streamChunkedFile(stub, path, file) {
2553
+ const { readable, writable } = new TransformStream();
2554
+ const writer = writable.getWriter();
2555
+ (async () => {
2556
+ try {
2557
+ for (let i = 0; i < file.chunkCount; i++) {
2558
+ const result = await stub.readFileChunk(path, i);
2559
+ if (!result.success) {
2560
+ console.error(`Failed to read chunk ${i}:`, result.error);
2561
+ break;
2562
+ }
2563
+ const binary = atob(result.data);
2564
+ const bytes = new Uint8Array(binary.length);
2565
+ for (let j = 0; j < binary.length; j++) {
2566
+ bytes[j] = binary.charCodeAt(j);
2567
+ }
2568
+ await writer.write(bytes);
2569
+ }
2570
+ } catch (error) {
2571
+ console.error("Error streaming chunked file:", error);
2572
+ } finally {
2573
+ await writer.close();
2574
+ }
2575
+ })();
2576
+ return new Response(readable, {
2577
+ headers: {
2578
+ "Content-Type": file.mimeType,
2579
+ "Content-Length": String(file.size),
2580
+ "Content-Disposition": `inline; filename="${file.name}"`
2581
+ // Note: We set Content-Length, but also use chunked transfer internally
2582
+ }
2583
+ });
2584
+ }
2585
+ async function handlePost(stub, path, req) {
2428
2586
  const contentType = req.headers.get("Content-Type") || "";
2429
2587
  if (contentType.includes("application/json")) {
2430
2588
  const body = await req.json();
@@ -2444,7 +2602,7 @@ async function handlePut(stub, path, req) {
2444
2602
  if (!result2.success) {
2445
2603
  return Response.json({ error: result2.error }, { status: 400 });
2446
2604
  }
2447
- return Response.json({ file: result2.file }, { status: 201 });
2605
+ return Response.json(toAttachmentRef(result2.file), { status: 201 });
2448
2606
  }
2449
2607
  if (body.data) {
2450
2608
  const result2 = await stub.writeFile(path, body.data, body.mimeType || "application/octet-stream", {
@@ -2454,12 +2612,16 @@ async function handlePut(stub, path, req) {
2454
2612
  if (!result2.success) {
2455
2613
  return Response.json({ error: result2.error }, { status: 400 });
2456
2614
  }
2457
- return Response.json({ file: result2.file }, { status: 201 });
2615
+ return Response.json(toAttachmentRef(result2.file), { status: 201 });
2458
2616
  }
2459
2617
  return Response.json({ error: "Invalid request body" }, { status: 400 });
2460
2618
  }
2461
- const data = await req.arrayBuffer();
2462
2619
  const mimeType = contentType.split(";")[0].trim() || "application/octet-stream";
2620
+ const contentLength = parseInt(req.headers.get("Content-Length") || "0", 10);
2621
+ if (contentLength > CHUNK_SIZE && req.body) {
2622
+ return handleStreamingUpload(stub, path, mimeType, contentLength, req);
2623
+ }
2624
+ const data = await req.arrayBuffer();
2463
2625
  const bytes = new Uint8Array(data);
2464
2626
  let binary = "";
2465
2627
  for (let i = 0; i < bytes.length; i++) {
@@ -2470,7 +2632,65 @@ async function handlePut(stub, path, req) {
2470
2632
  if (!result.success) {
2471
2633
  return Response.json({ error: result.error }, { status: 400 });
2472
2634
  }
2473
- return Response.json({ file: result.file }, { status: 201 });
2635
+ return Response.json(toAttachmentRef(result.file), { status: 201 });
2636
+ }
2637
+ async function handleStreamingUpload(stub, path, mimeType, totalSize, req) {
2638
+ const startResult = await stub.startChunkedUpload(path, totalSize, mimeType, {});
2639
+ if (!startResult.success) {
2640
+ return Response.json({ error: startResult.error }, { status: 400 });
2641
+ }
2642
+ const reader = req.body.getReader();
2643
+ let chunkIndex = 0;
2644
+ let buffer = new Uint8Array(0);
2645
+ try {
2646
+ while (true) {
2647
+ const { done, value } = await reader.read();
2648
+ if (value) {
2649
+ const newBuffer = new Uint8Array(buffer.length + value.length);
2650
+ newBuffer.set(buffer);
2651
+ newBuffer.set(value, buffer.length);
2652
+ buffer = newBuffer;
2653
+ while (buffer.length >= CHUNK_SIZE) {
2654
+ const chunk = buffer.slice(0, CHUNK_SIZE);
2655
+ const base64 = arrayBufferToBase64(chunk);
2656
+ const chunkResult = await stub.writeFileChunk(path, chunkIndex, base64);
2657
+ if (!chunkResult.success) {
2658
+ return Response.json({ error: chunkResult.error }, { status: 400 });
2659
+ }
2660
+ chunkIndex++;
2661
+ buffer = buffer.slice(CHUNK_SIZE);
2662
+ }
2663
+ }
2664
+ if (done) {
2665
+ if (buffer.length > 0) {
2666
+ const base64 = arrayBufferToBase64(buffer);
2667
+ const chunkResult = await stub.writeFileChunk(path, chunkIndex, base64);
2668
+ if (!chunkResult.success) {
2669
+ return Response.json({ error: chunkResult.error }, { status: 400 });
2670
+ }
2671
+ chunkIndex++;
2672
+ }
2673
+ break;
2674
+ }
2675
+ }
2676
+ const completeResult = await stub.completeChunkedUpload(path, chunkIndex, {});
2677
+ if (!completeResult.success) {
2678
+ return Response.json({ error: completeResult.error }, { status: 400 });
2679
+ }
2680
+ return Response.json(toAttachmentRef(completeResult.file), { status: 201 });
2681
+ } catch (error) {
2682
+ console.error("Streaming upload failed:", error);
2683
+ return Response.json({ error: error.message || "Upload failed" }, { status: 500 });
2684
+ }
2685
+ }
2686
+ function arrayBufferToBase64(bytes) {
2687
+ const BATCH_SIZE = 32768;
2688
+ let binary = "";
2689
+ for (let i = 0; i < bytes.length; i += BATCH_SIZE) {
2690
+ const slice = bytes.subarray(i, Math.min(i + BATCH_SIZE, bytes.length));
2691
+ binary += String.fromCharCode.apply(null, slice);
2692
+ }
2693
+ return btoa(binary);
2474
2694
  }
2475
2695
  async function handleDelete(stub, path) {
2476
2696
  const statResult = await stub.statFile(path);
@@ -2491,6 +2711,61 @@ async function handleDelete(stub, path) {
2491
2711
  }
2492
2712
  return new Response(null, { status: 204 });
2493
2713
  }
2714
+ async function handleChunkedUploadStart(stub, filePath, req) {
2715
+ const body = await req.json();
2716
+ if (!body.totalSize || !body.mimeType) {
2717
+ return Response.json(
2718
+ { error: "totalSize and mimeType are required" },
2719
+ { status: 400 }
2720
+ );
2721
+ }
2722
+ const result = await stub.startChunkedUpload(filePath, body.totalSize, body.mimeType, {
2723
+ metadata: body.metadata,
2724
+ width: body.width,
2725
+ height: body.height
2726
+ });
2727
+ if (!result.success) {
2728
+ return Response.json({ error: result.error }, { status: 400 });
2729
+ }
2730
+ return Response.json({
2731
+ chunkSize: result.chunkSize,
2732
+ expectedChunks: result.expectedChunks,
2733
+ path: filePath
2734
+ });
2735
+ }
2736
+ async function handleChunkedUploadChunk(stub, filePath, chunkIndex, req) {
2737
+ const data = await req.arrayBuffer();
2738
+ const bytes = new Uint8Array(data);
2739
+ let binary = "";
2740
+ for (let i = 0; i < bytes.length; i++) {
2741
+ binary += String.fromCharCode(bytes[i]);
2742
+ }
2743
+ const base64 = btoa(binary);
2744
+ const result = await stub.writeFileChunk(filePath, chunkIndex, base64);
2745
+ if (!result.success) {
2746
+ return Response.json({ error: result.error }, { status: 400 });
2747
+ }
2748
+ return Response.json({
2749
+ chunkIndex,
2750
+ received: data.byteLength
2751
+ });
2752
+ }
2753
+ async function handleChunkedUploadComplete(stub, filePath, req) {
2754
+ const body = await req.json();
2755
+ if (typeof body.expectedChunks !== "number") {
2756
+ return Response.json(
2757
+ { error: "expectedChunks is required" },
2758
+ { status: 400 }
2759
+ );
2760
+ }
2761
+ const result = await stub.completeChunkedUpload(filePath, body.expectedChunks, {
2762
+ thumbnail: body.thumbnail
2763
+ });
2764
+ if (!result.success) {
2765
+ return Response.json({ error: result.error }, { status: 400 });
2766
+ }
2767
+ return Response.json(toAttachmentRef(result.file), { status: 201 });
2768
+ }
2494
2769
 
2495
2770
  // src/api/threads/[id]/logs/[logId].get.ts
2496
2771
  var logId_get_default = defineController(async ({ req, params, env }) => {
@@ -2654,11 +2929,13 @@ var routeHandlers = {
2654
2929
  "GET:/threads/:id/cost": cost_get_default,
2655
2930
  "GET:/threads/:id/fs": fs_default,
2656
2931
  "GET:/threads/:id/logs": logs_get_default,
2657
- "POST:/threads/:id/message": message_post_default,
2658
2932
  "GET:/threads/:id/messages": messages_get_default,
2933
+ "POST:/threads/:id/messages": messages_post_default,
2659
2934
  "POST:/threads/:id/rpc": rpc_post_default,
2660
2935
  "POST:/threads/:id/stop": stop_post_default,
2661
2936
  "GET:/threads/:id/stream": stream_default,
2937
+ "DELETE:/threads/:id/fs/**": path_default,
2938
+ "POST:/threads/:id/fs/**": path_default,
2662
2939
  "GET:/threads/:id/fs/**": path_default,
2663
2940
  "GET:/threads/:id/logs/:logId": logId_get_default,
2664
2941
  "DELETE:/threads/:id/messages/:messageId": messageId_delete_default,