@standardagents/builder 0.9.17 → 0.10.1-dev.616ec2e

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/index.js CHANGED
@@ -1,10 +1,16 @@
1
+ import { probe, sip } from '@standardagents/sip';
2
+ import { decode as decode$1, encode } from '@jsquash/png';
3
+ import { decode } from '@jsquash/avif';
1
4
  import fs2 from 'fs';
2
5
  import path3 from 'path';
3
6
  import { fileURLToPath } from 'url';
7
+ import { createRequire } from 'module';
4
8
  import { DurableObject } from 'cloudflare:workers';
5
9
 
6
10
  var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
14
  var __esm = (fn, res) => function __init() {
9
15
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
16
  };
@@ -12,6 +18,15 @@ var __export = (target, all) => {
12
18
  for (var name in all)
13
19
  __defProp(target, name, { get: all[name], enumerable: true });
14
20
  };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
30
 
16
31
  // src/agents/types.ts
17
32
  var MAX_TURNS, MAX_RETRIES_PER_MODEL, TIMESTAMP_MULTIPLIER, STREAM_COOLDOWN, BACKOFF_BASE;
@@ -250,9 +265,27 @@ var init_BaseProvider = __esm({
250
265
  /**
251
266
  * Helper: Update log with actual request body before making API call
252
267
  * This ensures we have the complete request even if the call fails
268
+ *
269
+ * NOTE: Only updates if request_body is NULL - LLMRequest may have already
270
+ * set a transformed request_body (with image paths instead of base64 data URLs)
271
+ *
272
+ * We check if request_body is already set BEFORE serializing to avoid
273
+ * processing megabytes of base64 image data unnecessarily.
253
274
  */
254
275
  async logActualRequest(request, logId, state) {
255
276
  try {
277
+ let needsUpdate = false;
278
+ await state.stream.waitFor(async () => {
279
+ const result = await state.storage.sql.exec(
280
+ `SELECT COUNT(*) as cnt FROM logs WHERE id = ?1 AND request_body IS NULL`,
281
+ logId
282
+ );
283
+ const rows = result.toArray();
284
+ needsUpdate = rows.length > 0 && rows[0].cnt > 0;
285
+ });
286
+ if (!needsUpdate) {
287
+ return;
288
+ }
256
289
  const requestBody = JSON.stringify(request);
257
290
  await state.stream.waitFor(async () => {
258
291
  await state.storage.sql.exec(
@@ -324,6 +357,81 @@ ${errorStack}` : ""}`,
324
357
  console.error(`[${this.name}] Failed to log error:`, logError);
325
358
  }
326
359
  }
360
+ /**
361
+ * Helper: Check if a MIME type is an image
362
+ */
363
+ isImageMimeType(mimeType) {
364
+ return mimeType.startsWith("image/");
365
+ }
366
+ /**
367
+ * Helper: Create image content part for OpenAI/OpenRouter format
368
+ * Converts base64 data to a data URL
369
+ */
370
+ createImageContentPart(base64Data, mimeType, detail = "auto") {
371
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
372
+ return {
373
+ type: "image_url",
374
+ image_url: {
375
+ url: dataUrl,
376
+ detail
377
+ }
378
+ };
379
+ }
380
+ /**
381
+ * Helper: Create text content part
382
+ */
383
+ createTextContentPart(text) {
384
+ return {
385
+ type: "text",
386
+ text
387
+ };
388
+ }
389
+ /**
390
+ * Helper: Convert message content and attachments to multimodal format
391
+ * Returns multimodal content array if there are images, otherwise returns string
392
+ *
393
+ * @param textContent The text content of the message
394
+ * @param attachments Array of attachment references with resolved base64 data
395
+ * @returns MessageContent - either string or multimodal array
396
+ */
397
+ buildMultimodalContent(textContent, attachments) {
398
+ if (!attachments || attachments.length === 0) {
399
+ return textContent || "";
400
+ }
401
+ const imageAttachments = attachments.filter(
402
+ (a) => this.isImageMimeType(a.ref.mimeType)
403
+ );
404
+ if (imageAttachments.length === 0) {
405
+ return textContent || "";
406
+ }
407
+ const parts = [];
408
+ if (textContent) {
409
+ parts.push(this.createTextContentPart(textContent));
410
+ }
411
+ for (const attachment of imageAttachments) {
412
+ parts.push(
413
+ this.createImageContentPart(attachment.base64, attachment.ref.mimeType)
414
+ );
415
+ }
416
+ return parts;
417
+ }
418
+ /**
419
+ * Helper: Convert MessageContent to string for logging/display
420
+ * Extracts text from multimodal content, describes images
421
+ */
422
+ contentToString(content) {
423
+ if (!content) return "";
424
+ if (typeof content === "string") return content;
425
+ const parts = [];
426
+ for (const part of content) {
427
+ if (part.type === "text") {
428
+ parts.push(part.text);
429
+ } else if (part.type === "image_url") {
430
+ parts.push("[Image]");
431
+ }
432
+ }
433
+ return parts.join(" ");
434
+ }
327
435
  };
328
436
  }
329
437
  });
@@ -762,6 +870,11 @@ var init_TestScript = __esm({
762
870
  });
763
871
 
764
872
  // src/agents/providers/TestProvider.ts
873
+ function contentToString(content) {
874
+ if (!content) return "";
875
+ if (typeof content === "string") return content;
876
+ return content.filter((part) => part.type === "text").map((part) => part.text).join(" ");
877
+ }
765
878
  var TestProvider;
766
879
  var init_TestProvider = __esm({
767
880
  "src/agents/providers/TestProvider.ts"() {
@@ -804,8 +917,9 @@ var init_TestProvider = __esm({
804
917
  const responses = this.script.getResponses();
805
918
  if (this.responseIndex >= responses.length) {
806
919
  const lastMessage = context.messages.slice(-1)[0];
920
+ const lastContent = contentToString(lastMessage?.content);
807
921
  throw new Error(
808
- `TestProvider: Script exhausted after ${this.responseIndex} responses. Expected ${responses.length} total requests. Received request with ${context.messages.length} messages. Last message role: "${lastMessage?.role}", content: "${lastMessage?.content?.substring(0, 100)}..."`
922
+ `TestProvider: Script exhausted after ${this.responseIndex} responses. Expected ${responses.length} total requests. Received request with ${context.messages.length} messages. Last message role: "${lastMessage?.role}", content: "${lastContent.substring(0, 100)}..."`
809
923
  );
810
924
  }
811
925
  const scripted = responses[this.responseIndex];
@@ -882,8 +996,9 @@ var init_TestProvider = __esm({
882
996
  if (expectations.containsMessage) {
883
997
  const pattern = expectations.containsMessage;
884
998
  const found = context.messages.some((m) => {
885
- if (!m.content) return false;
886
- return typeof pattern === "string" ? m.content.includes(pattern) : pattern.test(m.content);
999
+ const content = contentToString(m.content);
1000
+ if (!content) return false;
1001
+ return typeof pattern === "string" ? content.includes(pattern) : pattern.test(content);
887
1002
  });
888
1003
  if (!found) {
889
1004
  throw new Error(
@@ -895,8 +1010,9 @@ var init_TestProvider = __esm({
895
1010
  const { toolName, resultContains } = expectations.containsToolResult;
896
1011
  const found = context.messages.some((m) => {
897
1012
  if (m.role !== "tool") return false;
898
- if (!m.content) return false;
899
- return !resultContains || m.content.includes(resultContains);
1013
+ const content = contentToString(m.content);
1014
+ if (!content) return false;
1015
+ return !resultContains || content.includes(resultContains);
900
1016
  });
901
1017
  if (!found) {
902
1018
  throw new Error(
@@ -907,10 +1023,11 @@ var init_TestProvider = __esm({
907
1023
  if (expectations.systemPromptContains) {
908
1024
  const pattern = expectations.systemPromptContains;
909
1025
  const systemMessage = context.messages.find((m) => m.role === "system");
910
- if (!systemMessage?.content) {
1026
+ const systemContent = contentToString(systemMessage?.content);
1027
+ if (!systemContent) {
911
1028
  throw new Error(`TestProvider: No system message found`);
912
1029
  }
913
- const matches = typeof pattern === "string" ? systemMessage.content.includes(pattern) : pattern.test(systemMessage.content);
1030
+ const matches = typeof pattern === "string" ? systemContent.includes(pattern) : pattern.test(systemContent);
914
1031
  if (!matches) {
915
1032
  throw new Error(
916
1033
  `TestProvider: System prompt does not contain "${pattern}"`
@@ -923,7 +1040,7 @@ var init_TestProvider = __esm({
923
1040
  */
924
1041
  estimateTokens(messages) {
925
1042
  return messages.reduce((sum, m) => {
926
- const content = m.content || "";
1043
+ const content = contentToString(m.content);
927
1044
  return sum + Math.ceil(content.length / 4);
928
1045
  }, 0);
929
1046
  }
@@ -1131,6 +1248,37 @@ var init_providers = __esm({
1131
1248
  });
1132
1249
 
1133
1250
  // src/agents/LLMRequest.ts
1251
+ function transformMessagesForLog(messages, imagePathMap) {
1252
+ console.log("[DEBUG] transformMessagesForLog called, imagePathMap size:", imagePathMap?.size ?? 0);
1253
+ if (!imagePathMap || imagePathMap.size === 0) {
1254
+ console.log("[DEBUG] No imagePathMap, returning original");
1255
+ return messages;
1256
+ }
1257
+ const result = messages.map((msg) => {
1258
+ if (!Array.isArray(msg.content)) {
1259
+ return msg;
1260
+ }
1261
+ return {
1262
+ ...msg,
1263
+ content: msg.content.map((part) => {
1264
+ if (typeof part === "object" && part !== null && "type" in part && part.type === "image_url" && "image_url" in part && part.image_url?.url) {
1265
+ const path4 = imagePathMap.get(part.image_url.url);
1266
+ console.log("[DEBUG] Found image_url, lookup result:", path4 ? path4 : "NOT FOUND");
1267
+ if (path4) {
1268
+ return {
1269
+ ...part,
1270
+ image_url: { ...part.image_url, url: path4 }
1271
+ };
1272
+ }
1273
+ }
1274
+ return part;
1275
+ })
1276
+ };
1277
+ });
1278
+ const hasDataUrl = JSON.stringify(result).includes("data:image");
1279
+ console.log("[DEBUG] After transform, still has data URL:", hasDataUrl);
1280
+ return result;
1281
+ }
1134
1282
  var LLMRequest;
1135
1283
  var init_LLMRequest = __esm({
1136
1284
  "src/agents/LLMRequest.ts"() {
@@ -1278,6 +1426,23 @@ var init_LLMRequest = __esm({
1278
1426
  } catch (err) {
1279
1427
  console.error("Failed to fetch model name:", err);
1280
1428
  }
1429
+ const messagesForLog = transformMessagesForLog(context.messages, context.imagePathMap);
1430
+ const requestBodyObj = {
1431
+ model: modelName || modelId,
1432
+ messages: messagesForLog,
1433
+ tools: context.tools,
1434
+ stream: context.stream ?? true
1435
+ };
1436
+ const requestBodyStr = JSON.stringify(requestBodyObj);
1437
+ const hasDataUrlInFinal = requestBodyStr.includes("data:image");
1438
+ console.log("[DEBUG] request_body has data URL:", hasDataUrlInFinal);
1439
+ if (hasDataUrlInFinal) {
1440
+ console.log("[DEBUG] PROBLEM! Data URL still in request_body after transform");
1441
+ console.log(
1442
+ "[DEBUG] messagesForLog first user msg content type:",
1443
+ messagesForLog.find((m) => m.role === "user")?.content
1444
+ );
1445
+ }
1281
1446
  const logData = {
1282
1447
  id,
1283
1448
  message_id: state.rootMessageId || crypto.randomUUID(),
@@ -1285,13 +1450,7 @@ var init_LLMRequest = __esm({
1285
1450
  model: modelId,
1286
1451
  model_name: modelName ?? void 0,
1287
1452
  endpoint: "chat.completions",
1288
- request_body: JSON.stringify({
1289
- model: modelName || modelId,
1290
- // Use actual model name sent to API, fallback to ID
1291
- messages: context.messages,
1292
- tools: context.tools,
1293
- stream: context.stream ?? true
1294
- }),
1453
+ request_body: requestBodyStr,
1295
1454
  tools_available: context.tools ? context.tools.length : 0,
1296
1455
  message_history_length: context.messages.length,
1297
1456
  prompt_name: context.promptName ?? void 0,
@@ -2543,6 +2702,112 @@ ${errorStack}` : ""}`;
2543
2702
  }
2544
2703
  });
2545
2704
 
2705
+ // src/agents/context.ts
2706
+ function hasImageAttachments(message) {
2707
+ if (!message.attachments) return false;
2708
+ try {
2709
+ const attachments = typeof message.attachments === "string" ? JSON.parse(message.attachments) : message.attachments;
2710
+ return attachments.some((att) => att.mimeType.startsWith("image/"));
2711
+ } catch {
2712
+ return false;
2713
+ }
2714
+ }
2715
+ function getUnsummarizedImageAttachments(message) {
2716
+ if (!message.attachments) return [];
2717
+ try {
2718
+ const attachments = typeof message.attachments === "string" ? JSON.parse(message.attachments) : message.attachments;
2719
+ return attachments.filter(
2720
+ (att) => att.mimeType.startsWith("image/") && !att.description
2721
+ );
2722
+ } catch {
2723
+ return [];
2724
+ }
2725
+ }
2726
+ function getMessagesToSummarize(messages, config = DEFAULT_CONFIG) {
2727
+ const indices = [];
2728
+ const threshold = messages.length - config.recentMessageThreshold;
2729
+ for (let i = 0; i < threshold; i++) {
2730
+ const msg = messages[i];
2731
+ if (msg.role === "user" && hasImageAttachments(msg)) {
2732
+ indices.push(i);
2733
+ }
2734
+ }
2735
+ return indices;
2736
+ }
2737
+ function buildImageDescription(attachment) {
2738
+ if (attachment.description) {
2739
+ return `[Image: ${attachment.name}] ${attachment.description}`;
2740
+ }
2741
+ const dimensions = attachment.width && attachment.height ? ` (${attachment.width}x${attachment.height})` : "";
2742
+ return `[Image: ${attachment.name}${dimensions}]`;
2743
+ }
2744
+ function replaceImagesWithDescriptions(content, attachments) {
2745
+ if (typeof content === "string") {
2746
+ const imageDescriptions2 = attachments.filter((att) => att.mimeType.startsWith("image/")).map(buildImageDescription).join("\n");
2747
+ if (!imageDescriptions2) return content;
2748
+ if (!content) return imageDescriptions2;
2749
+ return `${imageDescriptions2}
2750
+
2751
+ ${content}`;
2752
+ }
2753
+ const textParts = content.filter((part) => part.type === "text");
2754
+ const imageAttachments = attachments.filter(
2755
+ (att) => att.mimeType.startsWith("image/")
2756
+ );
2757
+ if (imageAttachments.length === 0) {
2758
+ return content;
2759
+ }
2760
+ const imageDescriptions = imageAttachments.map(buildImageDescription).join("\n");
2761
+ if (textParts.length > 0) {
2762
+ const existingText = textParts.map((p) => p.text).join(" ");
2763
+ return `${imageDescriptions}
2764
+
2765
+ ${existingText}`;
2766
+ }
2767
+ return imageDescriptions;
2768
+ }
2769
+ function optimizeImageContext(messages, config = DEFAULT_CONFIG) {
2770
+ const messagesToSummarize = getMessagesToSummarize(messages, config);
2771
+ if (messagesToSummarize.length === 0) {
2772
+ return messages;
2773
+ }
2774
+ return messages.map((msg, index) => {
2775
+ if (!messagesToSummarize.includes(index)) {
2776
+ return msg;
2777
+ }
2778
+ let attachments;
2779
+ try {
2780
+ attachments = typeof msg.attachments === "string" ? JSON.parse(msg.attachments) : msg.attachments || [];
2781
+ } catch {
2782
+ return msg;
2783
+ }
2784
+ const optimizedContent = replaceImagesWithDescriptions(
2785
+ msg.content || "",
2786
+ attachments
2787
+ );
2788
+ return {
2789
+ ...msg,
2790
+ content: typeof optimizedContent === "string" ? optimizedContent : JSON.stringify(optimizedContent),
2791
+ // Clear attachments since we've inlined descriptions
2792
+ attachments: JSON.stringify(
2793
+ attachments.filter((att) => !att.mimeType.startsWith("image/"))
2794
+ )
2795
+ };
2796
+ });
2797
+ }
2798
+ async function generateImageDescription(_imageBase64, _mimeType, _state) {
2799
+ return null;
2800
+ }
2801
+ var DEFAULT_CONFIG;
2802
+ var init_context = __esm({
2803
+ "src/agents/context.ts"() {
2804
+ DEFAULT_CONFIG = {
2805
+ recentMessageThreshold: 10,
2806
+ descriptionPrompt: "Describe this image concisely in 1-2 sentences, focusing on the key visual elements and any text visible."
2807
+ };
2808
+ }
2809
+ });
2810
+
2546
2811
  // src/agents/FlowEngine.ts
2547
2812
  var FlowEngine_exports = {};
2548
2813
  __export(FlowEngine_exports, {
@@ -2555,6 +2820,7 @@ var init_FlowEngine = __esm({
2555
2820
  init_StreamManager();
2556
2821
  init_LLMRequest();
2557
2822
  init_ToolExecutor();
2823
+ init_context();
2558
2824
  FlowEngine = class {
2559
2825
  /**
2560
2826
  * Main execution entry point
@@ -3014,6 +3280,7 @@ var init_FlowEngine = __esm({
3014
3280
  */
3015
3281
  static async assembleContext(state) {
3016
3282
  const messages = [];
3283
+ const imagePathMap = /* @__PURE__ */ new Map();
3017
3284
  const model = state.prompt.model;
3018
3285
  const promptName = state.prompt.name;
3019
3286
  const parallelToolCalls = state.prompt.parallel_tool_calls;
@@ -3042,6 +3309,9 @@ var init_FlowEngine = __esm({
3042
3309
  toolCallsWithResponses.add(msg.tool_call_id);
3043
3310
  }
3044
3311
  }
3312
+ const recentMessageThreshold = state.prompt.recentImageThreshold ?? 10;
3313
+ const oldMessageThreshold = completedMessages.length - recentMessageThreshold;
3314
+ let messageIndex = 0;
3045
3315
  for (const msg of completedMessages) {
3046
3316
  if (!includePastTools && msg.role === "tool") {
3047
3317
  continue;
@@ -3071,7 +3341,36 @@ var init_FlowEngine = __esm({
3071
3341
  role = state.currentSide === "a" ? "user" : "assistant";
3072
3342
  }
3073
3343
  }
3074
- const messageContent = msg.content || void 0;
3344
+ let messageContent;
3345
+ const hasAttachments = msg.attachments && msg.attachments !== "[]";
3346
+ const isOldMessage = messageIndex < oldMessageThreshold;
3347
+ if (hasAttachments && msg.role === "user") {
3348
+ const attachments = JSON.parse(msg.attachments);
3349
+ const imageAttachments = attachments.filter(
3350
+ (a) => a.mimeType.startsWith("image/")
3351
+ );
3352
+ if (isOldMessage && imageAttachments.length > 0) {
3353
+ const imageDescriptions = imageAttachments.map(buildImageDescription).join("\n");
3354
+ const nonImageFiles = attachments.filter((a) => !a.mimeType.startsWith("image/")).map((a) => a.name);
3355
+ const nonImageList = nonImageFiles.length > 0 ? `
3356
+ [Other files: ${nonImageFiles.join(", ")}]` : "";
3357
+ messageContent = msg.content ? `${imageDescriptions}${nonImageList}
3358
+
3359
+ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
3360
+ } else {
3361
+ if (imageAttachments.length > 0) {
3362
+ messageContent = await this.resolveAttachmentsToContent(msg, state, imagePathMap);
3363
+ } else {
3364
+ const fileList = attachments.map((a) => a.name).join(", ");
3365
+ messageContent = msg.content ? `${msg.content}
3366
+
3367
+ [Attached files: ${fileList}]` : `[Attached files: ${fileList}]`;
3368
+ }
3369
+ }
3370
+ } else {
3371
+ messageContent = msg.content || "";
3372
+ }
3373
+ messageIndex++;
3075
3374
  const messageToAdd = {
3076
3375
  role,
3077
3376
  content: messageContent,
@@ -3170,7 +3469,8 @@ var init_FlowEngine = __esm({
3170
3469
  // Retries now apply to all prompts (top-level and sub-prompts), only for provider errors
3171
3470
  parallel_tool_calls: parallelToolCalls,
3172
3471
  tool_choice: finalToolChoice,
3173
- reasoning
3472
+ reasoning,
3473
+ imagePathMap: imagePathMap.size > 0 ? imagePathMap : void 0
3174
3474
  };
3175
3475
  }
3176
3476
  /**
@@ -4039,6 +4339,7 @@ var init_FlowEngine = __esm({
4039
4339
  "role",
4040
4340
  "content",
4041
4341
  "name",
4342
+ "attachments",
4042
4343
  ...includeToolCalls ? ["tool_calls", "tool_call_id", "tool_status"] : [],
4043
4344
  "log_id",
4044
4345
  "created_at",
@@ -4070,6 +4371,7 @@ var init_FlowEngine = __esm({
4070
4371
  role: row.role,
4071
4372
  content: row.content,
4072
4373
  name: row.name,
4374
+ attachments: row.attachments,
4073
4375
  tool_calls: row.tool_calls,
4074
4376
  tool_call_id: row.tool_call_id,
4075
4377
  log_id: row.log_id,
@@ -4148,8 +4450,691 @@ ${errorStack}` : ""}`,
4148
4450
  console.error("Failed to log error to database:", logError);
4149
4451
  }
4150
4452
  }
4453
+ /**
4454
+ * Check if a MIME type is an image
4455
+ */
4456
+ static isImageMimeType(mimeType) {
4457
+ return mimeType.startsWith("image/");
4458
+ }
4459
+ /**
4460
+ * Resolve attachments from a message and build multimodal content
4461
+ * Returns the message content (string or multimodal array)
4462
+ *
4463
+ * @param msg - Message with potential attachments
4464
+ * @param state - Flow state with storage access
4465
+ * @returns MessageContent - either string or multimodal array
4466
+ */
4467
+ static async resolveAttachmentsToContent(msg, state, imagePathMap) {
4468
+ const textContent = msg.content || "";
4469
+ if (!msg.attachments) {
4470
+ return textContent;
4471
+ }
4472
+ let attachments;
4473
+ try {
4474
+ attachments = JSON.parse(msg.attachments);
4475
+ } catch (e) {
4476
+ console.error(`Failed to parse attachments for message ${msg.id}:`, e);
4477
+ return textContent;
4478
+ }
4479
+ if (!attachments || attachments.length === 0) {
4480
+ return textContent;
4481
+ }
4482
+ const imageAttachments = attachments.filter(
4483
+ (a) => this.isImageMimeType(a.mimeType)
4484
+ );
4485
+ if (imageAttachments.length === 0) {
4486
+ const nonImageFiles2 = attachments.filter(
4487
+ (a) => !this.isImageMimeType(a.mimeType)
4488
+ );
4489
+ if (nonImageFiles2.length > 0) {
4490
+ const fileList = nonImageFiles2.map((a) => a.name).join(", ");
4491
+ return `${textContent}
4492
+
4493
+ [Attached files: ${fileList}]`;
4494
+ }
4495
+ return textContent;
4496
+ }
4497
+ const multimodalParts = [];
4498
+ if (textContent) {
4499
+ multimodalParts.push({
4500
+ type: "text",
4501
+ text: textContent
4502
+ });
4503
+ }
4504
+ for (const attachment of imageAttachments) {
4505
+ try {
4506
+ const result = await state.thread.instance.readFile(
4507
+ attachment.path
4508
+ );
4509
+ if (result.success && result.data) {
4510
+ const dataUrl = `data:${attachment.mimeType};base64,${result.data}`;
4511
+ if (imagePathMap) {
4512
+ imagePathMap.set(dataUrl, attachment.path);
4513
+ }
4514
+ multimodalParts.push({
4515
+ type: "image_url",
4516
+ image_url: {
4517
+ url: dataUrl,
4518
+ detail: "auto"
4519
+ }
4520
+ });
4521
+ } else {
4522
+ console.warn(
4523
+ `Failed to load image attachment ${attachment.path}:`,
4524
+ result.error
4525
+ );
4526
+ multimodalParts.push({
4527
+ type: "text",
4528
+ text: `[Image not available: ${attachment.name}]`
4529
+ });
4530
+ }
4531
+ } catch (error) {
4532
+ console.error(`Error loading image attachment ${attachment.path}:`, error);
4533
+ multimodalParts.push({
4534
+ type: "text",
4535
+ text: `[Error loading image: ${attachment.name}]`
4536
+ });
4537
+ }
4538
+ }
4539
+ const nonImageFiles = attachments.filter(
4540
+ (a) => !this.isImageMimeType(a.mimeType)
4541
+ );
4542
+ if (nonImageFiles.length > 0) {
4543
+ const fileList = nonImageFiles.map((a) => a.name).join(", ");
4544
+ multimodalParts.push({
4545
+ type: "text",
4546
+ text: `
4547
+
4548
+ [Attached files: ${fileList}]`
4549
+ });
4550
+ }
4551
+ return multimodalParts;
4552
+ }
4553
+ };
4554
+ }
4555
+ });
4556
+
4557
+ // src/durable-objects/files.ts
4558
+ var files_exports = {};
4559
+ __export(files_exports, {
4560
+ FileStorage: () => FileStorage,
4561
+ basename: () => basename,
4562
+ detectStorageBackend: () => detectStorageBackend,
4563
+ dirname: () => dirname,
4564
+ isTextMimeType: () => isTextMimeType,
4565
+ normalizePath: () => normalizePath,
4566
+ rowToFileRecord: () => rowToFileRecord
4567
+ });
4568
+ function isTextMimeType(mimeType) {
4569
+ return TEXT_MIME_TYPES.some((prefix) => mimeType.startsWith(prefix));
4570
+ }
4571
+ function detectStorageBackend(location) {
4572
+ if (location.startsWith("s3://")) return "s3";
4573
+ if (location.startsWith("r2://")) return "r2";
4574
+ if (location.startsWith("http://") || location.startsWith("https://"))
4575
+ return "url";
4576
+ return "local";
4577
+ }
4578
+ function basename(path4) {
4579
+ const parts = path4.split("/").filter(Boolean);
4580
+ return parts[parts.length - 1] || "";
4581
+ }
4582
+ function normalizePath(path4) {
4583
+ let normalized = path4.replace(/\/+/g, "/");
4584
+ if (!normalized.startsWith("/")) normalized = "/" + normalized;
4585
+ if (normalized.length > 1 && normalized.endsWith("/")) {
4586
+ normalized = normalized.slice(0, -1);
4587
+ }
4588
+ return normalized;
4589
+ }
4590
+ function dirname(path4) {
4591
+ const normalized = normalizePath(path4);
4592
+ const lastSlash = normalized.lastIndexOf("/");
4593
+ if (lastSlash <= 0) return "/";
4594
+ return normalized.slice(0, lastSlash);
4595
+ }
4596
+ function rowToFileRecord(row) {
4597
+ return {
4598
+ path: row.path,
4599
+ name: row.name,
4600
+ mimeType: row.mime_type,
4601
+ storage: row.storage,
4602
+ location: row.location,
4603
+ size: row.size,
4604
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
4605
+ isDirectory: row.is_directory === 1,
4606
+ createdAt: row.created_at
4607
+ };
4608
+ }
4609
+ function inferMimeType(filename) {
4610
+ const ext = filename.split(".").pop()?.toLowerCase();
4611
+ const mimeTypes = {
4612
+ // Text
4613
+ txt: "text/plain",
4614
+ md: "text/markdown",
4615
+ html: "text/html",
4616
+ css: "text/css",
4617
+ csv: "text/csv",
4618
+ // Code
4619
+ js: "application/javascript",
4620
+ ts: "application/typescript",
4621
+ json: "application/json",
4622
+ xml: "application/xml",
4623
+ yaml: "application/yaml",
4624
+ yml: "application/yaml",
4625
+ // Images
4626
+ jpg: "image/jpeg",
4627
+ jpeg: "image/jpeg",
4628
+ png: "image/png",
4629
+ gif: "image/gif",
4630
+ webp: "image/webp",
4631
+ svg: "image/svg+xml",
4632
+ ico: "image/x-icon",
4633
+ // Documents
4634
+ pdf: "application/pdf",
4635
+ // Archives
4636
+ zip: "application/zip",
4637
+ tar: "application/x-tar",
4638
+ gz: "application/gzip"
4639
+ };
4640
+ return mimeTypes[ext || ""] || "application/octet-stream";
4641
+ }
4642
+ var TEXT_MIME_TYPES, FileStorage;
4643
+ var init_files = __esm({
4644
+ "src/durable-objects/files.ts"() {
4645
+ TEXT_MIME_TYPES = [
4646
+ "text/",
4647
+ "application/json",
4648
+ "application/javascript",
4649
+ "application/xml",
4650
+ "application/x-yaml",
4651
+ "application/yaml"
4652
+ ];
4653
+ FileStorage = class {
4654
+ constructor(sql) {
4655
+ this.sql = sql;
4656
+ }
4657
+ /**
4658
+ * Write a file to storage
4659
+ */
4660
+ async writeFile(path4, data, mimeType, options) {
4661
+ const normalizedPath = normalizePath(path4);
4662
+ const name = basename(normalizedPath);
4663
+ const isText = isTextMimeType(mimeType);
4664
+ const now = Date.now();
4665
+ let content = null;
4666
+ let blobData = null;
4667
+ let size;
4668
+ if (isText) {
4669
+ content = typeof data === "string" ? data : new TextDecoder().decode(data);
4670
+ size = new TextEncoder().encode(content).length;
4671
+ } else {
4672
+ blobData = typeof data === "string" ? new TextEncoder().encode(data) : data;
4673
+ size = blobData.byteLength;
4674
+ }
4675
+ const metadataJson = options?.metadata ? JSON.stringify(options.metadata) : null;
4676
+ await this.sql.exec(
4677
+ `INSERT OR REPLACE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
4678
+ VALUES (?, ?, ?, 'local', NULL, ?, ?, ?, ?, ?, 0, ?)`,
4679
+ normalizedPath,
4680
+ name,
4681
+ mimeType,
4682
+ blobData,
4683
+ content,
4684
+ size,
4685
+ metadataJson,
4686
+ options?.thumbnail || null,
4687
+ now
4688
+ );
4689
+ await this.updateStats();
4690
+ return {
4691
+ path: normalizedPath,
4692
+ name,
4693
+ mimeType,
4694
+ storage: "local",
4695
+ location: null,
4696
+ size,
4697
+ metadata: options?.metadata || null,
4698
+ isDirectory: false,
4699
+ createdAt: now
4700
+ };
4701
+ }
4702
+ /**
4703
+ * Link to an external file (URL, S3, R2)
4704
+ */
4705
+ async linkFile(path4, location, options) {
4706
+ const normalizedPath = normalizePath(path4);
4707
+ const name = basename(normalizedPath);
4708
+ const storage = detectStorageBackend(location);
4709
+ const now = Date.now();
4710
+ const mimeType = options?.mimeType || inferMimeType(name);
4711
+ const metadataJson = options?.metadata ? JSON.stringify(options.metadata) : null;
4712
+ await this.sql.exec(
4713
+ `INSERT OR REPLACE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
4714
+ VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, NULL, 0, ?)`,
4715
+ normalizedPath,
4716
+ name,
4717
+ mimeType,
4718
+ storage,
4719
+ location,
4720
+ options?.size || 0,
4721
+ metadataJson,
4722
+ now
4723
+ );
4724
+ await this.updateStats();
4725
+ return {
4726
+ path: normalizedPath,
4727
+ name,
4728
+ mimeType,
4729
+ storage,
4730
+ location,
4731
+ size: options?.size || 0,
4732
+ metadata: options?.metadata || null,
4733
+ isDirectory: false,
4734
+ createdAt: now
4735
+ };
4736
+ }
4737
+ /**
4738
+ * Read file data from storage
4739
+ * Returns null if file doesn't exist or is external
4740
+ */
4741
+ async readFile(path4) {
4742
+ const normalizedPath = normalizePath(path4);
4743
+ const cursor = await this.sql.exec(
4744
+ `SELECT data, content, storage FROM files WHERE path = ? AND is_directory = 0`,
4745
+ normalizedPath
4746
+ );
4747
+ const rows = cursor.toArray();
4748
+ if (rows.length === 0) return null;
4749
+ const row = rows[0];
4750
+ if (row.storage !== "local") return null;
4751
+ if (row.content !== null) {
4752
+ return new TextEncoder().encode(row.content);
4753
+ }
4754
+ return row.data;
4755
+ }
4756
+ /**
4757
+ * Read text file content
4758
+ */
4759
+ async readTextFile(path4) {
4760
+ const normalizedPath = normalizePath(path4);
4761
+ const cursor = await this.sql.exec(
4762
+ `SELECT content, data, storage FROM files WHERE path = ? AND is_directory = 0`,
4763
+ normalizedPath
4764
+ );
4765
+ const rows = cursor.toArray();
4766
+ if (rows.length === 0) return null;
4767
+ const row = rows[0];
4768
+ if (row.storage !== "local") return null;
4769
+ if (row.content !== null) return row.content;
4770
+ if (row.data !== null) {
4771
+ return new TextDecoder().decode(row.data);
4772
+ }
4773
+ return null;
4774
+ }
4775
+ /**
4776
+ * Get file metadata (stat)
4777
+ */
4778
+ async stat(path4) {
4779
+ const normalizedPath = normalizePath(path4);
4780
+ const cursor = await this.sql.exec(
4781
+ `SELECT path, name, mime_type, storage, location, size, metadata, is_directory, created_at
4782
+ FROM files WHERE path = ?`,
4783
+ normalizedPath
4784
+ );
4785
+ const rows = cursor.toArray();
4786
+ if (rows.length === 0) return null;
4787
+ return rowToFileRecord(rows[0]);
4788
+ }
4789
+ /**
4790
+ * Check if file or directory exists
4791
+ */
4792
+ async exists(path4) {
4793
+ const normalizedPath = normalizePath(path4);
4794
+ const cursor = await this.sql.exec(
4795
+ `SELECT COUNT(*) as count FROM files WHERE path = ?`,
4796
+ normalizedPath
4797
+ );
4798
+ return cursor.one().count > 0;
4799
+ }
4800
+ /**
4801
+ * Delete a file
4802
+ */
4803
+ async unlink(path4) {
4804
+ const normalizedPath = normalizePath(path4);
4805
+ await this.sql.exec(
4806
+ `DELETE FROM files WHERE path = ? AND is_directory = 0`,
4807
+ normalizedPath
4808
+ );
4809
+ await this.updateStats();
4810
+ }
4811
+ /**
4812
+ * Create a directory marker
4813
+ */
4814
+ async mkdir(path4) {
4815
+ const normalizedPath = normalizePath(path4);
4816
+ const name = basename(normalizedPath);
4817
+ const now = Date.now();
4818
+ await this.sql.exec(
4819
+ `INSERT OR IGNORE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
4820
+ VALUES (?, ?, 'inode/directory', 'local', NULL, NULL, NULL, 0, NULL, NULL, 1, ?)`,
4821
+ normalizedPath,
4822
+ name,
4823
+ now
4824
+ );
4825
+ return {
4826
+ path: normalizedPath,
4827
+ name,
4828
+ mimeType: "inode/directory",
4829
+ storage: "local",
4830
+ location: null,
4831
+ size: 0,
4832
+ metadata: null,
4833
+ isDirectory: true,
4834
+ createdAt: now
4835
+ };
4836
+ }
4837
+ /**
4838
+ * List directory contents
4839
+ */
4840
+ async readdir(path4) {
4841
+ const normalizedPath = normalizePath(path4);
4842
+ const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
4843
+ const cursor = await this.sql.exec(
4844
+ `SELECT path, name, mime_type, storage, location, size, metadata, is_directory, created_at
4845
+ FROM files
4846
+ WHERE path LIKE ? || '%'
4847
+ AND path != ?
4848
+ AND SUBSTR(path, LENGTH(?) + 1) NOT LIKE '%/%'
4849
+ ORDER BY is_directory DESC, name ASC`,
4850
+ prefix,
4851
+ normalizedPath,
4852
+ prefix
4853
+ );
4854
+ return cursor.toArray().map(rowToFileRecord);
4855
+ }
4856
+ /**
4857
+ * Remove empty directory
4858
+ */
4859
+ async rmdir(path4) {
4860
+ const normalizedPath = normalizePath(path4);
4861
+ const prefix = normalizedPath + "/";
4862
+ const cursor = await this.sql.exec(
4863
+ `SELECT COUNT(*) as count FROM files WHERE path LIKE ? || '%'`,
4864
+ prefix
4865
+ );
4866
+ if (cursor.one().count > 0) {
4867
+ throw new Error("Directory not empty");
4868
+ }
4869
+ await this.sql.exec(
4870
+ `DELETE FROM files WHERE path = ? AND is_directory = 1`,
4871
+ normalizedPath
4872
+ );
4873
+ }
4874
+ /**
4875
+ * Get storage statistics
4876
+ */
4877
+ async getFileStats() {
4878
+ const cursor = await this.sql.exec(`SELECT total_size, file_count FROM file_stats WHERE id = 1`);
4879
+ const rows = cursor.toArray();
4880
+ if (rows.length === 0) {
4881
+ return { totalSize: 0, fileCount: 0 };
4882
+ }
4883
+ return {
4884
+ totalSize: rows[0].total_size,
4885
+ fileCount: rows[0].file_count
4886
+ };
4887
+ }
4888
+ /**
4889
+ * Update file statistics (call after writes/deletes)
4890
+ */
4891
+ async updateStats() {
4892
+ await this.sql.exec(`
4893
+ UPDATE file_stats SET
4894
+ total_size = (SELECT COALESCE(SUM(size), 0) FROM files WHERE is_directory = 0),
4895
+ file_count = (SELECT COUNT(*) FROM files WHERE is_directory = 0)
4896
+ WHERE id = 1
4897
+ `);
4898
+ }
4899
+ /**
4900
+ * Get thumbnail for an image
4901
+ */
4902
+ async getThumbnail(path4) {
4903
+ const normalizedPath = normalizePath(path4);
4904
+ const cursor = await this.sql.exec(
4905
+ `SELECT thumbnail FROM files WHERE path = ? AND is_directory = 0`,
4906
+ normalizedPath
4907
+ );
4908
+ const rows = cursor.toArray();
4909
+ if (rows.length === 0) return null;
4910
+ return rows[0].thumbnail;
4911
+ }
4912
+ /**
4913
+ * Search file contents using FTS5
4914
+ */
4915
+ async grep(pattern, options) {
4916
+ const limit = options?.limit || 100;
4917
+ let query = `
4918
+ SELECT files.path, files.name, snippet(files_fts, 2, '<mark>', '</mark>', '...', 32) as snippet
4919
+ FROM files_fts
4920
+ JOIN files ON files.rowid = files_fts.rowid
4921
+ WHERE files_fts MATCH ?
4922
+ `;
4923
+ const params = [pattern];
4924
+ if (options?.path) {
4925
+ query += ` AND files.path LIKE ? || '%'`;
4926
+ params.push(normalizePath(options.path));
4927
+ }
4928
+ query += ` LIMIT ?`;
4929
+ params.push(limit);
4930
+ const cursor = await this.sql.exec(query, ...params);
4931
+ return cursor.toArray();
4932
+ }
4933
+ /**
4934
+ * Find files by path pattern (glob-like)
4935
+ */
4936
+ async find(pattern, options) {
4937
+ const limit = options?.limit || 100;
4938
+ const type = options?.type || "all";
4939
+ let sqlPattern = pattern.replace(/\*\*/g, "%").replace(/\*/g, "%").replace(/\?/g, "_");
4940
+ if (!sqlPattern.startsWith("/")) {
4941
+ sqlPattern = "%" + sqlPattern;
4942
+ }
4943
+ let typeClause = "";
4944
+ if (type === "file") typeClause = " AND is_directory = 0";
4945
+ if (type === "directory") typeClause = " AND is_directory = 1";
4946
+ const cursor = await this.sql.exec(
4947
+ `SELECT path, name, mime_type, storage, location, size, metadata, is_directory, created_at
4948
+ FROM files
4949
+ WHERE path LIKE ?${typeClause}
4950
+ ORDER BY path
4951
+ LIMIT ?`,
4952
+ sqlPattern,
4953
+ limit
4954
+ );
4955
+ return cursor.toArray().map(rowToFileRecord);
4956
+ }
4957
+ };
4958
+ }
4959
+ });
4960
+
4961
+ // src/image-processing/index.ts
4962
+ var image_processing_exports = {};
4963
+ __export(image_processing_exports, {
4964
+ arrayBufferToBase64: () => arrayBufferToBase64,
4965
+ base64ToArrayBuffer: () => base64ToArrayBuffer,
4966
+ needsProcessing: () => needsProcessing,
4967
+ processImage: () => processImage
4968
+ });
4969
+ async function processImage(input, inputMimeType) {
4970
+ if (input.byteLength > MAX_INPUT_SIZE) {
4971
+ throw new Error(`Image too large: ${input.byteLength} bytes exceeds ${MAX_INPUT_SIZE} byte limit`);
4972
+ }
4973
+ const probeResult = probe(input);
4974
+ const format = probeResult.format !== "unknown" ? probeResult.format : detectFormat(input, inputMimeType);
4975
+ const hasAlpha = probeResult.hasAlpha;
4976
+ if (hasAlpha && (format === "png" || format === "avif" || format === "webp")) {
4977
+ return await processPngWithAlpha(input, format);
4978
+ }
4979
+ try {
4980
+ const result = await sip.process(input, {
4981
+ maxWidth: MAX_DIMENSION,
4982
+ maxHeight: MAX_DIMENSION,
4983
+ maxBytes: MAX_SIZE,
4984
+ quality: 85
4985
+ });
4986
+ return {
4987
+ data: result.data,
4988
+ mimeType: "image/jpeg",
4989
+ width: result.width,
4990
+ height: result.height
4991
+ };
4992
+ } catch (err) {
4993
+ console.error("[sip] Processing failed, falling back:", err);
4994
+ throw err;
4995
+ }
4996
+ }
4997
+ async function processPngWithAlpha(input, format) {
4998
+ let imageData;
4999
+ if (format === "avif") {
5000
+ imageData = await decode(input);
5001
+ } else {
5002
+ imageData = await decode$1(input);
5003
+ }
5004
+ const originalWidth = imageData.width;
5005
+ const originalHeight = imageData.height;
5006
+ let encoded = await encode(imageData);
5007
+ if (encoded.byteLength <= MAX_SIZE) {
5008
+ return {
5009
+ data: encoded,
5010
+ mimeType: "image/png",
5011
+ width: originalWidth,
5012
+ height: originalHeight
4151
5013
  };
4152
5014
  }
5015
+ let scale = 0.9;
5016
+ while (encoded.byteLength > MAX_SIZE && scale > 0.15) {
5017
+ const newWidth = Math.floor(originalWidth * scale);
5018
+ const newHeight = Math.floor(originalHeight * scale);
5019
+ if (newWidth < 100 || newHeight < 100) {
5020
+ scale *= 0.9;
5021
+ continue;
5022
+ }
5023
+ const resized = resizeRgba(
5024
+ new Uint8ClampedArray(imageData.data),
5025
+ originalWidth,
5026
+ originalHeight,
5027
+ newWidth,
5028
+ newHeight
5029
+ );
5030
+ const resizedImageData = { data: resized, width: newWidth, height: newHeight };
5031
+ encoded = await encode(resizedImageData);
5032
+ if (encoded.byteLength <= MAX_SIZE) {
5033
+ return {
5034
+ data: encoded,
5035
+ mimeType: "image/png",
5036
+ width: newWidth,
5037
+ height: newHeight
5038
+ };
5039
+ }
5040
+ scale *= 0.9;
5041
+ }
5042
+ const finalWidth = Math.floor(originalWidth * 0.15);
5043
+ const finalHeight = Math.floor(originalHeight * 0.15);
5044
+ const finalResized = resizeRgba(
5045
+ new Uint8ClampedArray(imageData.data),
5046
+ originalWidth,
5047
+ originalHeight,
5048
+ finalWidth,
5049
+ finalHeight
5050
+ );
5051
+ encoded = await encode({ data: finalResized, width: finalWidth, height: finalHeight });
5052
+ return {
5053
+ data: encoded,
5054
+ mimeType: "image/png",
5055
+ width: finalWidth,
5056
+ height: finalHeight
5057
+ };
5058
+ }
5059
+ function resizeRgba(src, srcWidth, srcHeight, dstWidth, dstHeight) {
5060
+ const dst = new Uint8ClampedArray(dstWidth * dstHeight * 4);
5061
+ const xScale = srcWidth / dstWidth;
5062
+ const yScale = srcHeight / dstHeight;
5063
+ for (let dstY = 0; dstY < dstHeight; dstY++) {
5064
+ for (let dstX = 0; dstX < dstWidth; dstX++) {
5065
+ const srcXFloat = dstX * xScale;
5066
+ const srcYFloat = dstY * yScale;
5067
+ const srcX0 = Math.floor(srcXFloat);
5068
+ const srcY0 = Math.floor(srcYFloat);
5069
+ const srcX1 = Math.min(srcX0 + 1, srcWidth - 1);
5070
+ const srcY1 = Math.min(srcY0 + 1, srcHeight - 1);
5071
+ const tx = srcXFloat - srcX0;
5072
+ const ty = srcYFloat - srcY0;
5073
+ const idx00 = (srcY0 * srcWidth + srcX0) * 4;
5074
+ const idx10 = (srcY0 * srcWidth + srcX1) * 4;
5075
+ const idx01 = (srcY1 * srcWidth + srcX0) * 4;
5076
+ const idx11 = (srcY1 * srcWidth + srcX1) * 4;
5077
+ const dstIdx = (dstY * dstWidth + dstX) * 4;
5078
+ for (let c = 0; c < 4; c++) {
5079
+ const v00 = src[idx00 + c];
5080
+ const v10 = src[idx10 + c];
5081
+ const v01 = src[idx01 + c];
5082
+ const v11 = src[idx11 + c];
5083
+ const top = v00 * (1 - tx) + v10 * tx;
5084
+ const bottom = v01 * (1 - tx) + v11 * tx;
5085
+ dst[dstIdx + c] = Math.round(top * (1 - ty) + bottom * ty);
5086
+ }
5087
+ }
5088
+ }
5089
+ return dst;
5090
+ }
5091
+ function detectFormat(data, mimeType) {
5092
+ const bytes = new Uint8Array(data.slice(0, 12));
5093
+ if (bytes[4] === 102 && bytes[5] === 116 && bytes[6] === 121 && bytes[7] === 112) {
5094
+ const brand = String.fromCharCode(...bytes.slice(8, 12));
5095
+ if (brand === "avif" || brand === "avis") return "avif";
5096
+ }
5097
+ if (bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71) {
5098
+ return "png";
5099
+ }
5100
+ if (bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
5101
+ return "jpeg";
5102
+ }
5103
+ if (bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) {
5104
+ return "webp";
5105
+ }
5106
+ if (mimeType.includes("png")) return "png";
5107
+ if (mimeType.includes("webp")) return "webp";
5108
+ if (mimeType.includes("avif")) return "avif";
5109
+ return "jpeg";
5110
+ }
5111
+ function needsProcessing(data, mimeType) {
5112
+ const binaryLength = Math.ceil(data.length * 3 / 4);
5113
+ return binaryLength > MAX_SIZE || mimeType.includes("avif") || mimeType.includes("webp");
5114
+ }
5115
+ function base64ToArrayBuffer(base64) {
5116
+ const binaryString = atob(base64);
5117
+ const bytes = new Uint8Array(binaryString.length);
5118
+ for (let i = 0; i < binaryString.length; i++) {
5119
+ bytes[i] = binaryString.charCodeAt(i);
5120
+ }
5121
+ return bytes.buffer;
5122
+ }
5123
+ function arrayBufferToBase64(buffer) {
5124
+ const bytes = new Uint8Array(buffer);
5125
+ let binary = "";
5126
+ for (let i = 0; i < bytes.length; i++) {
5127
+ binary += String.fromCharCode(bytes[i]);
5128
+ }
5129
+ return btoa(binary);
5130
+ }
5131
+ var MAX_SIZE, MAX_DIMENSION, MAX_INPUT_SIZE;
5132
+ var init_image_processing = __esm({
5133
+ "src/image-processing/index.ts"() {
5134
+ MAX_SIZE = 1.5 * 1024 * 1024;
5135
+ MAX_DIMENSION = 4096;
5136
+ MAX_INPUT_SIZE = 20 * 1024 * 1024;
5137
+ }
4153
5138
  });
4154
5139
  var TSCONFIG_CONTENT = `{
4155
5140
  "compilerOptions": {
@@ -5134,7 +6119,7 @@ function generateAgentFile(data) {
5134
6119
  if (data.type && data.type !== "ai_human") {
5135
6120
  lines.push(` type: '${data.type}',`);
5136
6121
  }
5137
- if (data.maxSessionTurns !== void 0) {
6122
+ if (data.maxSessionTurns !== void 0 && data.maxSessionTurns !== null) {
5138
6123
  lines.push(` maxSessionTurns: ${data.maxSessionTurns},`);
5139
6124
  }
5140
6125
  lines.push(` sideA: ${formatSideConfig(data.sideA)},`);
@@ -5777,7 +6762,7 @@ function validateAgentData(data) {
5777
6762
  if (data.exposeAsTool && !data.toolDescription) {
5778
6763
  return "toolDescription is required when exposeAsTool is true";
5779
6764
  }
5780
- if (data.maxSessionTurns !== void 0) {
6765
+ if (data.maxSessionTurns !== void 0 && data.maxSessionTurns !== null) {
5781
6766
  if (typeof data.maxSessionTurns !== "number" || data.maxSessionTurns <= 0) {
5782
6767
  return "maxSessionTurns must be a positive number";
5783
6768
  }
@@ -5796,6 +6781,7 @@ function validateAgentData(data) {
5796
6781
  }
5797
6782
 
5798
6783
  // src/plugin.ts
6784
+ createRequire(import.meta.url);
5799
6785
  var VIRTUAL_TOOLS_ID = "virtual:@standardagents-tools";
5800
6786
  var RESOLVED_VIRTUAL_TOOLS_ID = "\0" + VIRTUAL_TOOLS_ID;
5801
6787
  var VIRTUAL_ROUTES_ID = "virtual:@standardagents-routes";
@@ -6204,13 +7190,28 @@ function agentbuilder(options = {}) {
6204
7190
  const depsToExclude = [
6205
7191
  "@standardagents/builder",
6206
7192
  "@standardagents/builder/runtime",
6207
- "@standardagents/builder/built-in-routes"
7193
+ "@standardagents/builder/built-in-routes",
7194
+ "@standardagents/builder/image-processing",
7195
+ // WASM image processing deps - must be excluded to avoid pre-bundle cache issues
7196
+ "@cf-wasm/photon",
7197
+ "@cf-wasm/photon/workerd",
7198
+ "@jsquash/avif",
7199
+ "@jsquash/jpeg",
7200
+ "@jsquash/png",
7201
+ "@jsquash/webp",
7202
+ "@standardagents/sip"
7203
+ ];
7204
+ const depsToInclude = [
7205
+ "zod",
7206
+ "openai"
6208
7207
  ];
6209
7208
  return {
6210
7209
  optimizeDeps: {
6211
7210
  // Exclude our packages from pre-bundling - they contain cloudflare:workers imports
6212
7211
  // that cannot be resolved during dependency optimization
6213
- exclude: depsToExclude
7212
+ exclude: depsToExclude,
7213
+ // Include common deps upfront to prevent re-optimization during dev
7214
+ include: depsToInclude
6214
7215
  },
6215
7216
  ssr: {
6216
7217
  // noExternal ensures Vite transforms these instead of leaving as external
@@ -6223,18 +7224,35 @@ function agentbuilder(options = {}) {
6223
7224
  }
6224
7225
  };
6225
7226
  },
6226
- // Apply exclusions to ALL environments including Cloudflare worker
7227
+ // Apply exclusions and inclusions to ALL environments including Cloudflare worker
6227
7228
  configEnvironment(name, config) {
6228
7229
  const depsToExclude = [
6229
7230
  "@standardagents/builder",
6230
7231
  "@standardagents/builder/runtime",
6231
- "@standardagents/builder/built-in-routes"
7232
+ "@standardagents/builder/built-in-routes",
7233
+ "@standardagents/builder/image-processing",
7234
+ // WASM image processing deps
7235
+ "@cf-wasm/photon",
7236
+ "@cf-wasm/photon/workerd",
7237
+ "@jsquash/avif",
7238
+ "@jsquash/jpeg",
7239
+ "@jsquash/png",
7240
+ "@jsquash/webp",
7241
+ "@standardagents/sip"
7242
+ ];
7243
+ const depsToInclude = [
7244
+ "zod",
7245
+ "openai"
6232
7246
  ];
6233
7247
  config.optimizeDeps = config.optimizeDeps || {};
6234
7248
  config.optimizeDeps.exclude = [
6235
7249
  ...config.optimizeDeps.exclude || [],
6236
7250
  ...depsToExclude.filter((dep) => !config.optimizeDeps?.exclude?.includes(dep))
6237
7251
  ];
7252
+ config.optimizeDeps.include = [
7253
+ ...config.optimizeDeps.include || [],
7254
+ ...depsToInclude.filter((dep) => !config.optimizeDeps?.include?.includes(dep))
7255
+ ];
6238
7256
  },
6239
7257
  resolveId(id) {
6240
7258
  if (id === VIRTUAL_TOOLS_ID) {
@@ -6672,6 +7690,11 @@ export const agentNames = ${JSON.stringify(agents.filter((a) => !a.error).map((a
6672
7690
  import { DurableThread as _BaseDurableThread } from '@standardagents/builder/runtime';
6673
7691
  import { DurableAgentBuilder as _BaseDurableAgentBuilder } from '@standardagents/builder/runtime';
6674
7692
 
7693
+ // Import sip WASM module and initializer
7694
+ // Static import allows workerd to pre-compile the WASM at bundle time
7695
+ import _sipWasm from '@standardagents/sip/dist/sip.wasm';
7696
+ import { initWithWasmModule as _initSipWasm } from '@standardagents/sip';
7697
+
6675
7698
  // Re-export router from virtual:@standardagents-routes
6676
7699
  export { router } from 'virtual:@standardagents-routes';
6677
7700
 
@@ -6701,6 +7724,16 @@ ${agentsCode}
6701
7724
  * Simply extend this class in your agents/Thread.ts file.
6702
7725
  */
6703
7726
  export class DurableThread extends _BaseDurableThread {
7727
+ constructor(ctx, env) {
7728
+ super(ctx, env);
7729
+ // Initialize sip WASM in DO constructor
7730
+ // blockConcurrencyWhile ensures WASM is ready before handling any requests
7731
+ // Pass the statically imported WASM module for workerd to pre-compile
7732
+ ctx.blockConcurrencyWhile(async () => {
7733
+ await _initSipWasm(_sipWasm);
7734
+ });
7735
+ }
7736
+
6704
7737
  tools() {
6705
7738
  return _tools;
6706
7739
  }
@@ -8116,8 +9149,88 @@ var migration16 = {
8116
9149
  }
8117
9150
  };
8118
9151
 
9152
+ // src/durable-objects/migrations/017_add_files.ts
9153
+ var migration17 = {
9154
+ version: 17,
9155
+ async up(sql) {
9156
+ await sql.exec(`
9157
+ CREATE TABLE files (
9158
+ path TEXT PRIMARY KEY,
9159
+ name TEXT NOT NULL,
9160
+ mime_type TEXT NOT NULL,
9161
+
9162
+ -- Storage backend: 'local' | 'url' | 's3' | 'r2'
9163
+ storage TEXT NOT NULL DEFAULT 'local',
9164
+ -- External reference: URL, s3://bucket/key, r2://bucket/key
9165
+ location TEXT,
9166
+
9167
+ -- Local storage (NULL if external)
9168
+ data BLOB,
9169
+ content TEXT,
9170
+
9171
+ size INTEGER NOT NULL DEFAULT 0,
9172
+ metadata TEXT,
9173
+ thumbnail BLOB,
9174
+ is_directory INTEGER NOT NULL DEFAULT 0,
9175
+ created_at INTEGER NOT NULL
9176
+ )
9177
+ `);
9178
+ await sql.exec(`
9179
+ CREATE VIRTUAL TABLE files_fts USING fts5(
9180
+ path,
9181
+ name,
9182
+ content,
9183
+ content='files',
9184
+ content_rowid='rowid'
9185
+ )
9186
+ `);
9187
+ await sql.exec(`
9188
+ CREATE TRIGGER files_ai AFTER INSERT ON files BEGIN
9189
+ INSERT INTO files_fts(rowid, path, name, content)
9190
+ VALUES (new.rowid, new.path, new.name, new.content);
9191
+ END
9192
+ `);
9193
+ await sql.exec(`
9194
+ CREATE TRIGGER files_ad AFTER DELETE ON files BEGIN
9195
+ INSERT INTO files_fts(files_fts, rowid, path, name, content)
9196
+ VALUES('delete', old.rowid, old.path, old.name, old.content);
9197
+ END
9198
+ `);
9199
+ await sql.exec(`
9200
+ CREATE TRIGGER files_au AFTER UPDATE ON files BEGIN
9201
+ INSERT INTO files_fts(files_fts, rowid, path, name, content)
9202
+ VALUES('delete', old.rowid, old.path, old.name, old.content);
9203
+ INSERT INTO files_fts(rowid, path, name, content)
9204
+ VALUES (new.rowid, new.path, new.name, new.content);
9205
+ END
9206
+ `);
9207
+ await sql.exec(`
9208
+ CREATE TABLE file_stats (
9209
+ id INTEGER PRIMARY KEY CHECK (id = 1),
9210
+ total_size INTEGER DEFAULT 0,
9211
+ file_count INTEGER DEFAULT 0
9212
+ )
9213
+ `);
9214
+ await sql.exec(`
9215
+ INSERT INTO file_stats (id, total_size, file_count) VALUES (1, 0, 0)
9216
+ `);
9217
+ await sql.exec(`
9218
+ ALTER TABLE messages ADD COLUMN attachments TEXT
9219
+ `);
9220
+ await sql.exec(`
9221
+ CREATE INDEX idx_files_created_at ON files(created_at)
9222
+ `);
9223
+ await sql.exec(`
9224
+ CREATE INDEX idx_files_mime_type ON files(mime_type)
9225
+ `);
9226
+ await sql.exec(`
9227
+ UPDATE _metadata SET value = '17' WHERE key = 'schema_version'
9228
+ `);
9229
+ }
9230
+ };
9231
+
8119
9232
  // src/durable-objects/migrations/index.ts
8120
- var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16];
9233
+ var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16, migration17];
8121
9234
  var LATEST_SCHEMA_VERSION = migrations.length;
8122
9235
 
8123
9236
  // src/durable-objects/DurableThread.ts
@@ -8327,7 +9440,7 @@ var DurableThread = class extends DurableObject {
8327
9440
  );
8328
9441
  break;
8329
9442
  case "processMessage":
8330
- await this.processMessage(args.threadId, args.content, args.role);
9443
+ await this.processMessage(args.threadId, args.content, args.role, args.attachments);
8331
9444
  break;
8332
9445
  case "testOperation":
8333
9446
  await this.testOperation(args.id, args.label, args.expectedOrder);
@@ -8518,9 +9631,9 @@ var DurableThread = class extends DurableObject {
8518
9631
  * Each migration is run in order, starting from the current version + 1.
8519
9632
  */
8520
9633
  async runMigrations(fromVersion) {
8521
- for (const migration18 of migrations) {
8522
- if (migration18.version > fromVersion) {
8523
- await migration18.up(this.ctx.storage.sql);
9634
+ for (const migration19 of migrations) {
9635
+ if (migration19.version > fromVersion) {
9636
+ await migration19.up(this.ctx.storage.sql);
8524
9637
  }
8525
9638
  }
8526
9639
  }
@@ -8578,7 +9691,7 @@ var DurableThread = class extends DurableObject {
8578
9691
  * Send a new message to the thread (RPC method)
8579
9692
  * Enqueues the message processing to be handled by the alarm handler
8580
9693
  */
8581
- async sendMessage(threadId, content, role = "user") {
9694
+ async sendMessage(threadId, content, role = "user", attachments) {
8582
9695
  await this.ensureMigrated();
8583
9696
  try {
8584
9697
  const queueId = await this.alarmQueue.enqueue(
@@ -8586,7 +9699,8 @@ var DurableThread = class extends DurableObject {
8586
9699
  {
8587
9700
  threadId,
8588
9701
  content,
8589
- role
9702
+ role,
9703
+ attachments
8590
9704
  },
8591
9705
  1
8592
9706
  // Execute almost immediately (1ms)
@@ -8797,7 +9911,7 @@ var DurableThread = class extends DurableObject {
8797
9911
  const total = countResult.one().total;
8798
9912
  const result = await this.ctx.storage.sql.exec(
8799
9913
  `
8800
- SELECT id, role, content, name, tool_calls, tool_call_id, tool_status, log_id, created_at, silent, parent_id, depth, status, reasoning_content, reasoning_details
9914
+ SELECT id, role, content, name, tool_calls, tool_call_id, tool_status, log_id, created_at, silent, parent_id, depth, status, reasoning_content, reasoning_details, attachments
8801
9915
  FROM messages
8802
9916
  ${whereClause}
8803
9917
  ORDER BY created_at ${order === "ASC" ? "ASC" : "DESC"}
@@ -8821,7 +9935,8 @@ var DurableThread = class extends DurableObject {
8821
9935
  depth: row.depth,
8822
9936
  status: row.status,
8823
9937
  reasoning_content: row.reasoning_content,
8824
- reasoning_details: row.reasoning_details
9938
+ reasoning_details: row.reasoning_details,
9939
+ attachments: row.attachments ? JSON.parse(row.attachments) : null
8825
9940
  }));
8826
9941
  if (order === "DESC") {
8827
9942
  messages = messages.reverse();
@@ -9104,6 +10219,8 @@ var DurableThread = class extends DurableObject {
9104
10219
  id: threadMetadata.agent_name,
9105
10220
  title: agentDef.title || threadMetadata.agent_name,
9106
10221
  type: agentDef.type,
10222
+ description: agentDef.description,
10223
+ icon: agentDef.icon,
9107
10224
  side_a_label: agentDef.sideA?.label,
9108
10225
  side_b_label: agentDef.sideB?.label
9109
10226
  };
@@ -9526,7 +10643,7 @@ var DurableThread = class extends DurableObject {
9526
10643
  * Internal method: Process a message (called by alarm queue)
9527
10644
  * This is the actual message processing logic, separate from the public sendMessage() RPC method
9528
10645
  */
9529
- async processMessage(threadId, content, role = "user") {
10646
+ async processMessage(threadId, content, role = "user", attachments) {
9530
10647
  if (role === "user") {
9531
10648
  await this.ctx.storage.sql.exec(
9532
10649
  `UPDATE execution_state SET value = 'false' WHERE key = 'stopped'`
@@ -9576,6 +10693,7 @@ var DurableThread = class extends DurableObject {
9576
10693
  id: messageId,
9577
10694
  role,
9578
10695
  content,
10696
+ attachments: attachments || null,
9579
10697
  created_at: timestamp
9580
10698
  };
9581
10699
  const state = {
@@ -9591,16 +10709,18 @@ var DurableThread = class extends DurableObject {
9591
10709
  const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
9592
10710
  message = await FlowEngine2.runBeforeCreateMessageHook(state, message);
9593
10711
  await this.ctx.storage.sql.exec(
9594
- `INSERT INTO messages (id, role, content, created_at) VALUES (?, ?, ?, ?)`,
10712
+ `INSERT INTO messages (id, role, content, attachments, created_at) VALUES (?, ?, ?, ?, ?)`,
9595
10713
  message.id,
9596
10714
  message.role,
9597
10715
  message.content,
10716
+ message.attachments,
9598
10717
  message.created_at
9599
10718
  );
9600
10719
  this.broadcastMessage({
9601
10720
  id: message.id,
9602
10721
  role: message.role,
9603
10722
  content: message.content,
10723
+ attachments: message.attachments,
9604
10724
  created_at: message.created_at
9605
10725
  });
9606
10726
  const nextSide = role === "assistant" ? "b" : "a";
@@ -9769,10 +10889,297 @@ var DurableThread = class extends DurableObject {
9769
10889
  now
9770
10890
  );
9771
10891
  }
10892
+ // ============================================================
10893
+ // File Storage Methods (RPC)
10894
+ // ============================================================
10895
+ /**
10896
+ * Get file storage helper instance
10897
+ */
10898
+ getFileStorage() {
10899
+ const { FileStorage: FileStorage2 } = (init_files(), __toCommonJS(files_exports));
10900
+ return new FileStorage2(this.ctx.storage.sql);
10901
+ }
10902
+ /**
10903
+ * Write a file to storage (RPC method)
10904
+ */
10905
+ async writeFile(path4, data, mimeType, options) {
10906
+ await this.ensureMigrated();
10907
+ try {
10908
+ const fs4 = this.getFileStorage();
10909
+ const binaryString = atob(data);
10910
+ const dataBuffer = new Uint8Array(binaryString.length);
10911
+ for (let i = 0; i < binaryString.length; i++) {
10912
+ dataBuffer[i] = binaryString.charCodeAt(i);
10913
+ }
10914
+ let thumbnailBuffer;
10915
+ if (options?.thumbnail) {
10916
+ const thumbBinary = atob(options.thumbnail);
10917
+ thumbnailBuffer = new Uint8Array(thumbBinary.length);
10918
+ for (let i = 0; i < thumbBinary.length; i++) {
10919
+ thumbnailBuffer[i] = thumbBinary.charCodeAt(i);
10920
+ }
10921
+ }
10922
+ const record = await fs4.writeFile(path4, dataBuffer, mimeType, {
10923
+ metadata: options?.metadata,
10924
+ thumbnail: thumbnailBuffer
10925
+ });
10926
+ return { success: true, file: record };
10927
+ } catch (error) {
10928
+ console.error("Error in writeFile:", error);
10929
+ return { success: false, error: error.message };
10930
+ }
10931
+ }
10932
+ /**
10933
+ * Write a text file to storage (RPC method)
10934
+ */
10935
+ async writeTextFile(path4, content, mimeType = "text/plain", options) {
10936
+ await this.ensureMigrated();
10937
+ try {
10938
+ const fs4 = this.getFileStorage();
10939
+ const record = await fs4.writeFile(path4, content, mimeType, options);
10940
+ return { success: true, file: record };
10941
+ } catch (error) {
10942
+ console.error("Error in writeTextFile:", error);
10943
+ return { success: false, error: error.message };
10944
+ }
10945
+ }
10946
+ /**
10947
+ * Process an image using sip (WASM-based image processing)
10948
+ * This runs inside the DO where WASM is properly initialized.
10949
+ *
10950
+ * @param data - base64 encoded image data
10951
+ * @param mimeType - MIME type of the input image
10952
+ * @returns Processed image data, dimensions, and mimeType
10953
+ */
10954
+ async processImage(data, mimeType) {
10955
+ try {
10956
+ const { processImage: processImage2, base64ToArrayBuffer: base64ToArrayBuffer2, arrayBufferToBase64: arrayBufferToBase642 } = await Promise.resolve().then(() => (init_image_processing(), image_processing_exports));
10957
+ const buffer = base64ToArrayBuffer2(data);
10958
+ const processed = await processImage2(buffer, mimeType);
10959
+ return {
10960
+ success: true,
10961
+ data: arrayBufferToBase642(processed.data),
10962
+ mimeType: processed.mimeType,
10963
+ width: processed.width,
10964
+ height: processed.height
10965
+ };
10966
+ } catch (error) {
10967
+ console.error("[DurableThread.processImage] Error:", error);
10968
+ return { success: false, error: error.message };
10969
+ }
10970
+ }
10971
+ /**
10972
+ * Link to an external file (RPC method)
10973
+ */
10974
+ async linkFile(path4, location, options) {
10975
+ await this.ensureMigrated();
10976
+ try {
10977
+ const fs4 = this.getFileStorage();
10978
+ const record = await fs4.linkFile(path4, location, options);
10979
+ return { success: true, file: record };
10980
+ } catch (error) {
10981
+ console.error("Error in linkFile:", error);
10982
+ return { success: false, error: error.message };
10983
+ }
10984
+ }
10985
+ /**
10986
+ * Read a file from storage (RPC method)
10987
+ * Returns base64-encoded data
10988
+ */
10989
+ async readFile(path4) {
10990
+ await this.ensureMigrated();
10991
+ try {
10992
+ const fs4 = this.getFileStorage();
10993
+ const data = await fs4.readFile(path4);
10994
+ if (data === null) {
10995
+ return { success: false, error: "File not found or external" };
10996
+ }
10997
+ const bytes = new Uint8Array(data);
10998
+ let binary = "";
10999
+ for (let i = 0; i < bytes.length; i++) {
11000
+ binary += String.fromCharCode(bytes[i]);
11001
+ }
11002
+ const base64 = btoa(binary);
11003
+ return { success: true, data: base64 };
11004
+ } catch (error) {
11005
+ console.error("Error in readFile:", error);
11006
+ return { success: false, error: error.message };
11007
+ }
11008
+ }
11009
+ /**
11010
+ * Read a text file from storage (RPC method)
11011
+ */
11012
+ async readTextFile(path4) {
11013
+ await this.ensureMigrated();
11014
+ try {
11015
+ const fs4 = this.getFileStorage();
11016
+ const content = await fs4.readTextFile(path4);
11017
+ if (content === null) {
11018
+ return { success: false, error: "File not found or external" };
11019
+ }
11020
+ return { success: true, content };
11021
+ } catch (error) {
11022
+ console.error("Error in readTextFile:", error);
11023
+ return { success: false, error: error.message };
11024
+ }
11025
+ }
11026
+ /**
11027
+ * Get file metadata (RPC method)
11028
+ */
11029
+ async statFile(path4) {
11030
+ await this.ensureMigrated();
11031
+ try {
11032
+ const fs4 = this.getFileStorage();
11033
+ const record = await fs4.stat(path4);
11034
+ if (record === null) {
11035
+ return { success: false, error: "File not found" };
11036
+ }
11037
+ return { success: true, file: record };
11038
+ } catch (error) {
11039
+ console.error("Error in statFile:", error);
11040
+ return { success: false, error: error.message };
11041
+ }
11042
+ }
11043
+ /**
11044
+ * Check if file exists (RPC method)
11045
+ */
11046
+ async fileExists(path4) {
11047
+ await this.ensureMigrated();
11048
+ try {
11049
+ const fs4 = this.getFileStorage();
11050
+ const exists2 = await fs4.exists(path4);
11051
+ return { success: true, exists: exists2 };
11052
+ } catch (error) {
11053
+ console.error("Error in fileExists:", error);
11054
+ return { success: false, error: error.message };
11055
+ }
11056
+ }
11057
+ /**
11058
+ * Delete a file (RPC method)
11059
+ */
11060
+ async unlinkFile(path4) {
11061
+ await this.ensureMigrated();
11062
+ try {
11063
+ const fs4 = this.getFileStorage();
11064
+ await fs4.unlink(path4);
11065
+ return { success: true };
11066
+ } catch (error) {
11067
+ console.error("Error in unlinkFile:", error);
11068
+ return { success: false, error: error.message };
11069
+ }
11070
+ }
11071
+ /**
11072
+ * Create a directory (RPC method)
11073
+ */
11074
+ async mkdirFile(path4) {
11075
+ await this.ensureMigrated();
11076
+ try {
11077
+ const fs4 = this.getFileStorage();
11078
+ const record = await fs4.mkdir(path4);
11079
+ return { success: true, directory: record };
11080
+ } catch (error) {
11081
+ console.error("Error in mkdirFile:", error);
11082
+ return { success: false, error: error.message };
11083
+ }
11084
+ }
11085
+ /**
11086
+ * List directory contents (RPC method)
11087
+ */
11088
+ async readdirFile(path4) {
11089
+ await this.ensureMigrated();
11090
+ try {
11091
+ const fs4 = this.getFileStorage();
11092
+ const files = await fs4.readdir(path4);
11093
+ return { success: true, files };
11094
+ } catch (error) {
11095
+ console.error("Error in readdirFile:", error);
11096
+ return { success: false, error: error.message };
11097
+ }
11098
+ }
11099
+ /**
11100
+ * Remove empty directory (RPC method)
11101
+ */
11102
+ async rmdirFile(path4) {
11103
+ await this.ensureMigrated();
11104
+ try {
11105
+ const fs4 = this.getFileStorage();
11106
+ await fs4.rmdir(path4);
11107
+ return { success: true };
11108
+ } catch (error) {
11109
+ console.error("Error in rmdirFile:", error);
11110
+ return { success: false, error: error.message };
11111
+ }
11112
+ }
11113
+ /**
11114
+ * Get file storage statistics (RPC method)
11115
+ */
11116
+ async getFileStats() {
11117
+ await this.ensureMigrated();
11118
+ try {
11119
+ const fs4 = this.getFileStorage();
11120
+ const stats = await fs4.getFileStats();
11121
+ return { success: true, stats };
11122
+ } catch (error) {
11123
+ console.error("Error in getFileStats:", error);
11124
+ return { success: false, error: error.message };
11125
+ }
11126
+ }
11127
+ /**
11128
+ * Get thumbnail for an image (RPC method)
11129
+ * Returns base64-encoded data
11130
+ */
11131
+ async getFileThumbnail(path4) {
11132
+ await this.ensureMigrated();
11133
+ try {
11134
+ const fs4 = this.getFileStorage();
11135
+ const data = await fs4.getThumbnail(path4);
11136
+ if (data === null) {
11137
+ return { success: false, error: "Thumbnail not found" };
11138
+ }
11139
+ const bytes = new Uint8Array(data);
11140
+ let binary = "";
11141
+ for (let i = 0; i < bytes.length; i++) {
11142
+ binary += String.fromCharCode(bytes[i]);
11143
+ }
11144
+ const base64 = btoa(binary);
11145
+ return { success: true, data: base64 };
11146
+ } catch (error) {
11147
+ console.error("Error in getFileThumbnail:", error);
11148
+ return { success: false, error: error.message };
11149
+ }
11150
+ }
11151
+ /**
11152
+ * Search file contents using FTS5 (RPC method)
11153
+ */
11154
+ async grepFiles(pattern, options) {
11155
+ await this.ensureMigrated();
11156
+ try {
11157
+ const fs4 = this.getFileStorage();
11158
+ const results = await fs4.grep(pattern, options);
11159
+ return { success: true, results };
11160
+ } catch (error) {
11161
+ console.error("Error in grepFiles:", error);
11162
+ return { success: false, error: error.message };
11163
+ }
11164
+ }
11165
+ /**
11166
+ * Find files by path pattern (RPC method)
11167
+ */
11168
+ async findFiles(pattern, options) {
11169
+ await this.ensureMigrated();
11170
+ try {
11171
+ const fs4 = this.getFileStorage();
11172
+ const files = await fs4.find(pattern, options);
11173
+ return { success: true, files };
11174
+ } catch (error) {
11175
+ console.error("Error in findFiles:", error);
11176
+ return { success: false, error: error.message };
11177
+ }
11178
+ }
9772
11179
  };
9773
11180
 
9774
11181
  // src/durable-objects/agentbuilder-migrations/0001_initial.ts
9775
- var migration17 = {
11182
+ var migration18 = {
9776
11183
  version: 1,
9777
11184
  async up(sql) {
9778
11185
  sql.exec(`
@@ -9871,7 +11278,7 @@ var migration17 = {
9871
11278
  };
9872
11279
 
9873
11280
  // src/durable-objects/agentbuilder-migrations/index.ts
9874
- var migrations2 = [migration17];
11281
+ var migrations2 = [migration18];
9875
11282
  var LATEST_SCHEMA_VERSION2 = 1;
9876
11283
 
9877
11284
  // src/durable-objects/DurableAgentBuilder.ts
@@ -9961,9 +11368,9 @@ var DurableAgentBuilder = class extends DurableObject {
9961
11368
  }
9962
11369
  }
9963
11370
  async runMigrations(fromVersion) {
9964
- for (const migration18 of migrations2) {
9965
- if (migration18.version > fromVersion) {
9966
- await migration18.up(this.ctx.storage.sql);
11371
+ for (const migration19 of migrations2) {
11372
+ if (migration19.version > fromVersion) {
11373
+ await migration19.up(this.ctx.storage.sql);
9967
11374
  }
9968
11375
  }
9969
11376
  }
@@ -10731,6 +12138,12 @@ function definePrompt(options) {
10731
12138
  `Invalid reasoning.effort '${options.reasoning.effort}'. Must be one of: low, medium, high`
10732
12139
  );
10733
12140
  }
12141
+ if (options.maxImagePixels !== void 0 && (options.maxImagePixels <= 0 || !Number.isInteger(options.maxImagePixels))) {
12142
+ throw new Error("maxImagePixels must be a positive integer");
12143
+ }
12144
+ if (options.recentImageThreshold !== void 0 && (options.recentImageThreshold <= 0 || !Number.isInteger(options.recentImageThreshold))) {
12145
+ throw new Error("recentImageThreshold must be a positive integer");
12146
+ }
10734
12147
  return options;
10735
12148
  }
10736
12149
 
@@ -10769,7 +12182,7 @@ function defineAgent(options) {
10769
12182
  if (options.sideB?.maxTurns !== void 0 && options.sideB.maxTurns <= 0) {
10770
12183
  throw new Error("sideB.maxTurns must be a positive number");
10771
12184
  }
10772
- if (options.maxSessionTurns !== void 0 && options.maxSessionTurns <= 0) {
12185
+ if (options.maxSessionTurns !== void 0 && options.maxSessionTurns !== null && options.maxSessionTurns <= 0) {
10773
12186
  throw new Error("maxSessionTurns must be a positive number");
10774
12187
  }
10775
12188
  if (!["ai_human", "dual_ai"].includes(type)) {
@@ -10923,6 +12336,165 @@ async function updateThread(thread, params) {
10923
12336
  }
10924
12337
  return result.thread;
10925
12338
  }
12339
+ async function writeFile(flow, path4, data, mimeType, options) {
12340
+ const instance = flow.thread.instance;
12341
+ let base64Data;
12342
+ if (typeof data === "string") {
12343
+ base64Data = btoa(data);
12344
+ } else {
12345
+ const bytes = new Uint8Array(data);
12346
+ let binary = "";
12347
+ for (let i = 0; i < bytes.length; i++) {
12348
+ binary += String.fromCharCode(bytes[i]);
12349
+ }
12350
+ base64Data = btoa(binary);
12351
+ }
12352
+ let base64Thumbnail;
12353
+ if (options?.thumbnail) {
12354
+ const bytes = new Uint8Array(options.thumbnail);
12355
+ let binary = "";
12356
+ for (let i = 0; i < bytes.length; i++) {
12357
+ binary += String.fromCharCode(bytes[i]);
12358
+ }
12359
+ base64Thumbnail = btoa(binary);
12360
+ }
12361
+ const result = await instance.writeFile(path4, base64Data, mimeType, {
12362
+ metadata: options?.metadata,
12363
+ thumbnail: base64Thumbnail
12364
+ });
12365
+ if (!result.success) {
12366
+ throw new Error(result.error || "Failed to write file");
12367
+ }
12368
+ return result.file;
12369
+ }
12370
+ async function writeImage(flow, path4, data, mimeType, options) {
12371
+ return writeFile(flow, path4, data, mimeType, options);
12372
+ }
12373
+ async function linkFile(flow, path4, location, options) {
12374
+ const instance = flow.thread.instance;
12375
+ const result = await instance.linkFile(path4, location, options);
12376
+ if (!result.success) {
12377
+ throw new Error(result.error || "Failed to link file");
12378
+ }
12379
+ return result.file;
12380
+ }
12381
+ async function readFile(flow, path4) {
12382
+ const instance = flow.thread.instance;
12383
+ const result = await instance.readFile(path4);
12384
+ if (!result.success) {
12385
+ return null;
12386
+ }
12387
+ const binary = atob(result.data);
12388
+ const bytes = new Uint8Array(binary.length);
12389
+ for (let i = 0; i < binary.length; i++) {
12390
+ bytes[i] = binary.charCodeAt(i);
12391
+ }
12392
+ return bytes.buffer;
12393
+ }
12394
+ async function stat(flow, path4) {
12395
+ const instance = flow.thread.instance;
12396
+ const result = await instance.statFile(path4);
12397
+ if (!result.success) {
12398
+ return null;
12399
+ }
12400
+ return result.file;
12401
+ }
12402
+ async function exists(flow, path4) {
12403
+ const instance = flow.thread.instance;
12404
+ const result = await instance.fileExists(path4);
12405
+ return result.success && result.exists;
12406
+ }
12407
+ async function unlink(flow, path4) {
12408
+ const instance = flow.thread.instance;
12409
+ const result = await instance.unlinkFile(path4);
12410
+ if (!result.success) {
12411
+ throw new Error(result.error || "Failed to delete file");
12412
+ }
12413
+ }
12414
+ async function mkdir(flow, path4) {
12415
+ const instance = flow.thread.instance;
12416
+ const result = await instance.mkdirFile(path4);
12417
+ if (!result.success) {
12418
+ throw new Error(result.error || "Failed to create directory");
12419
+ }
12420
+ return result.directory;
12421
+ }
12422
+ async function readdir(flow, path4) {
12423
+ const instance = flow.thread.instance;
12424
+ const result = await instance.readdirFile(path4);
12425
+ if (!result.success) {
12426
+ throw new Error(result.error || "Failed to read directory");
12427
+ }
12428
+ return result.files;
12429
+ }
12430
+ async function rmdir(flow, path4) {
12431
+ const instance = flow.thread.instance;
12432
+ const result = await instance.rmdirFile(path4);
12433
+ if (!result.success) {
12434
+ throw new Error(result.error || "Failed to remove directory");
12435
+ }
12436
+ }
12437
+ async function getFileStats(flow) {
12438
+ const instance = flow.thread.instance;
12439
+ const result = await instance.getFileStats();
12440
+ if (!result.success) {
12441
+ throw new Error(result.error || "Failed to get file stats");
12442
+ }
12443
+ return result.stats;
12444
+ }
12445
+ async function getThumbnail(flow, path4) {
12446
+ const instance = flow.thread.instance;
12447
+ const result = await instance.getFileThumbnail(path4);
12448
+ if (!result.success) {
12449
+ return null;
12450
+ }
12451
+ const binary = atob(result.data);
12452
+ const bytes = new Uint8Array(binary.length);
12453
+ for (let i = 0; i < binary.length; i++) {
12454
+ bytes[i] = binary.charCodeAt(i);
12455
+ }
12456
+ return bytes.buffer;
12457
+ }
12458
+ async function cat(flow, path4) {
12459
+ const instance = flow.thread.instance;
12460
+ const result = await instance.readTextFile(path4);
12461
+ if (!result.success) {
12462
+ return null;
12463
+ }
12464
+ return result.content;
12465
+ }
12466
+ async function head(flow, path4, lines = 10) {
12467
+ const content = await cat(flow, path4);
12468
+ if (content === null) return null;
12469
+ const allLines = content.split("\n");
12470
+ return allLines.slice(0, lines).join("\n");
12471
+ }
12472
+ async function tail(flow, path4, lines = 10) {
12473
+ const content = await cat(flow, path4);
12474
+ if (content === null) return null;
12475
+ const allLines = content.split("\n");
12476
+ return allLines.slice(-lines).join("\n");
12477
+ }
12478
+ async function grep(flow, pattern, options) {
12479
+ const instance = flow.thread.instance;
12480
+ const result = await instance.grepFiles(pattern, options);
12481
+ if (!result.success) {
12482
+ throw new Error(result.error || "Search failed");
12483
+ }
12484
+ return result.results.map((r) => ({
12485
+ path: r.path,
12486
+ name: r.name,
12487
+ matches: [r.snippet]
12488
+ }));
12489
+ }
12490
+ async function find(flow, pattern, options) {
12491
+ const instance = flow.thread.instance;
12492
+ const result = await instance.findFiles(pattern, options);
12493
+ if (!result.success) {
12494
+ throw new Error(result.error || "Find failed");
12495
+ }
12496
+ return result.files;
12497
+ }
10926
12498
 
10927
12499
  // src/agents/FlowStateSdk.ts
10928
12500
  init_types();
@@ -11257,6 +12829,9 @@ function enhanceFlowState(flow) {
11257
12829
  return enhanced;
11258
12830
  }
11259
12831
 
12832
+ // src/index.ts
12833
+ init_context();
12834
+
11260
12835
  // src/github/GitHubClient.ts
11261
12836
  var GITHUB_API_BASE = "https://api.github.com";
11262
12837
  var GitHubClient = class _GitHubClient {
@@ -11494,6 +13069,6 @@ var GitHubApiError = class extends Error {
11494
13069
  }
11495
13070
  };
11496
13071
 
11497
- export { DurableAgentBuilder, DurableThread, FlowStateSdk, GitHubApiError, GitHubClient, agentbuilder, authenticate, defineAgent, defineController, defineHook, defineModel, definePrompt, defineThreadEndpoint, defineTool, emitThreadEvent, enhanceFlowState, forceTurn, generateAgentFile, generateModelFile, generatePromptFile, getMessages, injectMessage, queueTool, reloadHistory, requireAdmin, requireAuth, updateThread };
13072
+ export { DurableAgentBuilder, DurableThread, FlowStateSdk, GitHubApiError, GitHubClient, agentbuilder, authenticate, buildImageDescription, cat, defineAgent, defineController, defineHook, defineModel, definePrompt, defineThreadEndpoint, defineTool, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateAgentFile, generateImageDescription, generateModelFile, generatePromptFile, getFileStats, getMessages, getMessagesToSummarize, getThumbnail, getUnsummarizedImageAttachments, grep, hasImageAttachments, head, injectMessage, linkFile, mkdir, optimizeImageContext, queueTool, readFile, readdir, reloadHistory, replaceImagesWithDescriptions, requireAdmin, requireAuth, rmdir, stat, tail, unlink, updateThread, writeFile, writeImage };
11498
13073
  //# sourceMappingURL=index.js.map
11499
13074
  //# sourceMappingURL=index.js.map