@standardagents/builder 0.9.16 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/built-in-routes.js +306 -4
- package/dist/built-in-routes.js.map +1 -1
- package/dist/client/assets/index.css +1 -1
- package/dist/client/index.js +19 -19
- package/dist/client/vendor.js +1 -1
- package/dist/client/vue.js +1 -1
- package/dist/image-processing.d.ts +41 -0
- package/dist/image-processing.js +198 -0
- package/dist/image-processing.js.map +1 -0
- package/dist/index.d.ts +758 -3
- package/dist/index.js +1342 -37
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +16 -2
- package/dist/plugin.js.map +1 -1
- package/package.json +11 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,9 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import { DurableObject } from 'cloudflare:workers';
|
|
5
5
|
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
10
|
var __esm = (fn, res) => function __init() {
|
|
9
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
12
|
};
|
|
@@ -12,6 +14,15 @@ var __export = (target, all) => {
|
|
|
12
14
|
for (var name in all)
|
|
13
15
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
16
|
};
|
|
17
|
+
var __copyProps = (to, from, except, desc) => {
|
|
18
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
|
+
for (let key of __getOwnPropNames(from))
|
|
20
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
21
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
26
|
|
|
16
27
|
// src/agents/types.ts
|
|
17
28
|
var MAX_TURNS, MAX_RETRIES_PER_MODEL, TIMESTAMP_MULTIPLIER, STREAM_COOLDOWN, BACKOFF_BASE;
|
|
@@ -250,13 +261,16 @@ var init_BaseProvider = __esm({
|
|
|
250
261
|
/**
|
|
251
262
|
* Helper: Update log with actual request body before making API call
|
|
252
263
|
* This ensures we have the complete request even if the call fails
|
|
264
|
+
*
|
|
265
|
+
* NOTE: Only updates if request_body is NULL - LLMRequest may have already
|
|
266
|
+
* set a transformed request_body (with image paths instead of base64 data URLs)
|
|
253
267
|
*/
|
|
254
268
|
async logActualRequest(request, logId, state) {
|
|
255
269
|
try {
|
|
256
270
|
const requestBody = JSON.stringify(request);
|
|
257
271
|
await state.stream.waitFor(async () => {
|
|
258
272
|
await state.storage.sql.exec(
|
|
259
|
-
`UPDATE logs SET request_body = ?1 WHERE id = ?2`,
|
|
273
|
+
`UPDATE logs SET request_body = ?1 WHERE id = ?2 AND request_body IS NULL`,
|
|
260
274
|
requestBody,
|
|
261
275
|
logId
|
|
262
276
|
);
|
|
@@ -324,6 +338,81 @@ ${errorStack}` : ""}`,
|
|
|
324
338
|
console.error(`[${this.name}] Failed to log error:`, logError);
|
|
325
339
|
}
|
|
326
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* Helper: Check if a MIME type is an image
|
|
343
|
+
*/
|
|
344
|
+
isImageMimeType(mimeType) {
|
|
345
|
+
return mimeType.startsWith("image/");
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Helper: Create image content part for OpenAI/OpenRouter format
|
|
349
|
+
* Converts base64 data to a data URL
|
|
350
|
+
*/
|
|
351
|
+
createImageContentPart(base64Data, mimeType, detail = "auto") {
|
|
352
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
353
|
+
return {
|
|
354
|
+
type: "image_url",
|
|
355
|
+
image_url: {
|
|
356
|
+
url: dataUrl,
|
|
357
|
+
detail
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Helper: Create text content part
|
|
363
|
+
*/
|
|
364
|
+
createTextContentPart(text) {
|
|
365
|
+
return {
|
|
366
|
+
type: "text",
|
|
367
|
+
text
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Helper: Convert message content and attachments to multimodal format
|
|
372
|
+
* Returns multimodal content array if there are images, otherwise returns string
|
|
373
|
+
*
|
|
374
|
+
* @param textContent The text content of the message
|
|
375
|
+
* @param attachments Array of attachment references with resolved base64 data
|
|
376
|
+
* @returns MessageContent - either string or multimodal array
|
|
377
|
+
*/
|
|
378
|
+
buildMultimodalContent(textContent, attachments) {
|
|
379
|
+
if (!attachments || attachments.length === 0) {
|
|
380
|
+
return textContent || "";
|
|
381
|
+
}
|
|
382
|
+
const imageAttachments = attachments.filter(
|
|
383
|
+
(a) => this.isImageMimeType(a.ref.mimeType)
|
|
384
|
+
);
|
|
385
|
+
if (imageAttachments.length === 0) {
|
|
386
|
+
return textContent || "";
|
|
387
|
+
}
|
|
388
|
+
const parts = [];
|
|
389
|
+
if (textContent) {
|
|
390
|
+
parts.push(this.createTextContentPart(textContent));
|
|
391
|
+
}
|
|
392
|
+
for (const attachment of imageAttachments) {
|
|
393
|
+
parts.push(
|
|
394
|
+
this.createImageContentPart(attachment.base64, attachment.ref.mimeType)
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return parts;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Helper: Convert MessageContent to string for logging/display
|
|
401
|
+
* Extracts text from multimodal content, describes images
|
|
402
|
+
*/
|
|
403
|
+
contentToString(content) {
|
|
404
|
+
if (!content) return "";
|
|
405
|
+
if (typeof content === "string") return content;
|
|
406
|
+
const parts = [];
|
|
407
|
+
for (const part of content) {
|
|
408
|
+
if (part.type === "text") {
|
|
409
|
+
parts.push(part.text);
|
|
410
|
+
} else if (part.type === "image_url") {
|
|
411
|
+
parts.push("[Image]");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return parts.join(" ");
|
|
415
|
+
}
|
|
327
416
|
};
|
|
328
417
|
}
|
|
329
418
|
});
|
|
@@ -762,6 +851,11 @@ var init_TestScript = __esm({
|
|
|
762
851
|
});
|
|
763
852
|
|
|
764
853
|
// src/agents/providers/TestProvider.ts
|
|
854
|
+
function contentToString(content) {
|
|
855
|
+
if (!content) return "";
|
|
856
|
+
if (typeof content === "string") return content;
|
|
857
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join(" ");
|
|
858
|
+
}
|
|
765
859
|
var TestProvider;
|
|
766
860
|
var init_TestProvider = __esm({
|
|
767
861
|
"src/agents/providers/TestProvider.ts"() {
|
|
@@ -804,8 +898,9 @@ var init_TestProvider = __esm({
|
|
|
804
898
|
const responses = this.script.getResponses();
|
|
805
899
|
if (this.responseIndex >= responses.length) {
|
|
806
900
|
const lastMessage = context.messages.slice(-1)[0];
|
|
901
|
+
const lastContent = contentToString(lastMessage?.content);
|
|
807
902
|
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: "${
|
|
903
|
+
`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
904
|
);
|
|
810
905
|
}
|
|
811
906
|
const scripted = responses[this.responseIndex];
|
|
@@ -882,8 +977,9 @@ var init_TestProvider = __esm({
|
|
|
882
977
|
if (expectations.containsMessage) {
|
|
883
978
|
const pattern = expectations.containsMessage;
|
|
884
979
|
const found = context.messages.some((m) => {
|
|
885
|
-
|
|
886
|
-
|
|
980
|
+
const content = contentToString(m.content);
|
|
981
|
+
if (!content) return false;
|
|
982
|
+
return typeof pattern === "string" ? content.includes(pattern) : pattern.test(content);
|
|
887
983
|
});
|
|
888
984
|
if (!found) {
|
|
889
985
|
throw new Error(
|
|
@@ -895,8 +991,9 @@ var init_TestProvider = __esm({
|
|
|
895
991
|
const { toolName, resultContains } = expectations.containsToolResult;
|
|
896
992
|
const found = context.messages.some((m) => {
|
|
897
993
|
if (m.role !== "tool") return false;
|
|
898
|
-
|
|
899
|
-
|
|
994
|
+
const content = contentToString(m.content);
|
|
995
|
+
if (!content) return false;
|
|
996
|
+
return !resultContains || content.includes(resultContains);
|
|
900
997
|
});
|
|
901
998
|
if (!found) {
|
|
902
999
|
throw new Error(
|
|
@@ -907,10 +1004,11 @@ var init_TestProvider = __esm({
|
|
|
907
1004
|
if (expectations.systemPromptContains) {
|
|
908
1005
|
const pattern = expectations.systemPromptContains;
|
|
909
1006
|
const systemMessage = context.messages.find((m) => m.role === "system");
|
|
910
|
-
|
|
1007
|
+
const systemContent = contentToString(systemMessage?.content);
|
|
1008
|
+
if (!systemContent) {
|
|
911
1009
|
throw new Error(`TestProvider: No system message found`);
|
|
912
1010
|
}
|
|
913
|
-
const matches = typeof pattern === "string" ?
|
|
1011
|
+
const matches = typeof pattern === "string" ? systemContent.includes(pattern) : pattern.test(systemContent);
|
|
914
1012
|
if (!matches) {
|
|
915
1013
|
throw new Error(
|
|
916
1014
|
`TestProvider: System prompt does not contain "${pattern}"`
|
|
@@ -923,7 +1021,7 @@ var init_TestProvider = __esm({
|
|
|
923
1021
|
*/
|
|
924
1022
|
estimateTokens(messages) {
|
|
925
1023
|
return messages.reduce((sum, m) => {
|
|
926
|
-
const content = m.content
|
|
1024
|
+
const content = contentToString(m.content);
|
|
927
1025
|
return sum + Math.ceil(content.length / 4);
|
|
928
1026
|
}, 0);
|
|
929
1027
|
}
|
|
@@ -1131,6 +1229,37 @@ var init_providers = __esm({
|
|
|
1131
1229
|
});
|
|
1132
1230
|
|
|
1133
1231
|
// src/agents/LLMRequest.ts
|
|
1232
|
+
function transformMessagesForLog(messages, imagePathMap) {
|
|
1233
|
+
console.log("[DEBUG] transformMessagesForLog called, imagePathMap size:", imagePathMap?.size ?? 0);
|
|
1234
|
+
if (!imagePathMap || imagePathMap.size === 0) {
|
|
1235
|
+
console.log("[DEBUG] No imagePathMap, returning original");
|
|
1236
|
+
return messages;
|
|
1237
|
+
}
|
|
1238
|
+
const result = messages.map((msg) => {
|
|
1239
|
+
if (!Array.isArray(msg.content)) {
|
|
1240
|
+
return msg;
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
...msg,
|
|
1244
|
+
content: msg.content.map((part) => {
|
|
1245
|
+
if (typeof part === "object" && part !== null && "type" in part && part.type === "image_url" && "image_url" in part && part.image_url?.url) {
|
|
1246
|
+
const path4 = imagePathMap.get(part.image_url.url);
|
|
1247
|
+
console.log("[DEBUG] Found image_url, lookup result:", path4 ? path4 : "NOT FOUND");
|
|
1248
|
+
if (path4) {
|
|
1249
|
+
return {
|
|
1250
|
+
...part,
|
|
1251
|
+
image_url: { ...part.image_url, url: path4 }
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return part;
|
|
1256
|
+
})
|
|
1257
|
+
};
|
|
1258
|
+
});
|
|
1259
|
+
const hasDataUrl = JSON.stringify(result).includes("data:image");
|
|
1260
|
+
console.log("[DEBUG] After transform, still has data URL:", hasDataUrl);
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
1134
1263
|
var LLMRequest;
|
|
1135
1264
|
var init_LLMRequest = __esm({
|
|
1136
1265
|
"src/agents/LLMRequest.ts"() {
|
|
@@ -1278,6 +1407,23 @@ var init_LLMRequest = __esm({
|
|
|
1278
1407
|
} catch (err) {
|
|
1279
1408
|
console.error("Failed to fetch model name:", err);
|
|
1280
1409
|
}
|
|
1410
|
+
const messagesForLog = transformMessagesForLog(context.messages, context.imagePathMap);
|
|
1411
|
+
const requestBodyObj = {
|
|
1412
|
+
model: modelName || modelId,
|
|
1413
|
+
messages: messagesForLog,
|
|
1414
|
+
tools: context.tools,
|
|
1415
|
+
stream: context.stream ?? true
|
|
1416
|
+
};
|
|
1417
|
+
const requestBodyStr = JSON.stringify(requestBodyObj);
|
|
1418
|
+
const hasDataUrlInFinal = requestBodyStr.includes("data:image");
|
|
1419
|
+
console.log("[DEBUG] request_body has data URL:", hasDataUrlInFinal);
|
|
1420
|
+
if (hasDataUrlInFinal) {
|
|
1421
|
+
console.log("[DEBUG] PROBLEM! Data URL still in request_body after transform");
|
|
1422
|
+
console.log(
|
|
1423
|
+
"[DEBUG] messagesForLog first user msg content type:",
|
|
1424
|
+
messagesForLog.find((m) => m.role === "user")?.content
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1281
1427
|
const logData = {
|
|
1282
1428
|
id,
|
|
1283
1429
|
message_id: state.rootMessageId || crypto.randomUUID(),
|
|
@@ -1285,13 +1431,7 @@ var init_LLMRequest = __esm({
|
|
|
1285
1431
|
model: modelId,
|
|
1286
1432
|
model_name: modelName ?? void 0,
|
|
1287
1433
|
endpoint: "chat.completions",
|
|
1288
|
-
request_body:
|
|
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
|
-
}),
|
|
1434
|
+
request_body: requestBodyStr,
|
|
1295
1435
|
tools_available: context.tools ? context.tools.length : 0,
|
|
1296
1436
|
message_history_length: context.messages.length,
|
|
1297
1437
|
prompt_name: context.promptName ?? void 0,
|
|
@@ -2543,6 +2683,112 @@ ${errorStack}` : ""}`;
|
|
|
2543
2683
|
}
|
|
2544
2684
|
});
|
|
2545
2685
|
|
|
2686
|
+
// src/agents/context.ts
|
|
2687
|
+
function hasImageAttachments(message) {
|
|
2688
|
+
if (!message.attachments) return false;
|
|
2689
|
+
try {
|
|
2690
|
+
const attachments = typeof message.attachments === "string" ? JSON.parse(message.attachments) : message.attachments;
|
|
2691
|
+
return attachments.some((att) => att.mimeType.startsWith("image/"));
|
|
2692
|
+
} catch {
|
|
2693
|
+
return false;
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
function getUnsummarizedImageAttachments(message) {
|
|
2697
|
+
if (!message.attachments) return [];
|
|
2698
|
+
try {
|
|
2699
|
+
const attachments = typeof message.attachments === "string" ? JSON.parse(message.attachments) : message.attachments;
|
|
2700
|
+
return attachments.filter(
|
|
2701
|
+
(att) => att.mimeType.startsWith("image/") && !att.description
|
|
2702
|
+
);
|
|
2703
|
+
} catch {
|
|
2704
|
+
return [];
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
function getMessagesToSummarize(messages, config = DEFAULT_CONFIG) {
|
|
2708
|
+
const indices = [];
|
|
2709
|
+
const threshold = messages.length - config.recentMessageThreshold;
|
|
2710
|
+
for (let i = 0; i < threshold; i++) {
|
|
2711
|
+
const msg = messages[i];
|
|
2712
|
+
if (msg.role === "user" && hasImageAttachments(msg)) {
|
|
2713
|
+
indices.push(i);
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
return indices;
|
|
2717
|
+
}
|
|
2718
|
+
function buildImageDescription(attachment) {
|
|
2719
|
+
if (attachment.description) {
|
|
2720
|
+
return `[Image: ${attachment.name}] ${attachment.description}`;
|
|
2721
|
+
}
|
|
2722
|
+
const dimensions = attachment.width && attachment.height ? ` (${attachment.width}x${attachment.height})` : "";
|
|
2723
|
+
return `[Image: ${attachment.name}${dimensions}]`;
|
|
2724
|
+
}
|
|
2725
|
+
function replaceImagesWithDescriptions(content, attachments) {
|
|
2726
|
+
if (typeof content === "string") {
|
|
2727
|
+
const imageDescriptions2 = attachments.filter((att) => att.mimeType.startsWith("image/")).map(buildImageDescription).join("\n");
|
|
2728
|
+
if (!imageDescriptions2) return content;
|
|
2729
|
+
if (!content) return imageDescriptions2;
|
|
2730
|
+
return `${imageDescriptions2}
|
|
2731
|
+
|
|
2732
|
+
${content}`;
|
|
2733
|
+
}
|
|
2734
|
+
const textParts = content.filter((part) => part.type === "text");
|
|
2735
|
+
const imageAttachments = attachments.filter(
|
|
2736
|
+
(att) => att.mimeType.startsWith("image/")
|
|
2737
|
+
);
|
|
2738
|
+
if (imageAttachments.length === 0) {
|
|
2739
|
+
return content;
|
|
2740
|
+
}
|
|
2741
|
+
const imageDescriptions = imageAttachments.map(buildImageDescription).join("\n");
|
|
2742
|
+
if (textParts.length > 0) {
|
|
2743
|
+
const existingText = textParts.map((p) => p.text).join(" ");
|
|
2744
|
+
return `${imageDescriptions}
|
|
2745
|
+
|
|
2746
|
+
${existingText}`;
|
|
2747
|
+
}
|
|
2748
|
+
return imageDescriptions;
|
|
2749
|
+
}
|
|
2750
|
+
function optimizeImageContext(messages, config = DEFAULT_CONFIG) {
|
|
2751
|
+
const messagesToSummarize = getMessagesToSummarize(messages, config);
|
|
2752
|
+
if (messagesToSummarize.length === 0) {
|
|
2753
|
+
return messages;
|
|
2754
|
+
}
|
|
2755
|
+
return messages.map((msg, index) => {
|
|
2756
|
+
if (!messagesToSummarize.includes(index)) {
|
|
2757
|
+
return msg;
|
|
2758
|
+
}
|
|
2759
|
+
let attachments;
|
|
2760
|
+
try {
|
|
2761
|
+
attachments = typeof msg.attachments === "string" ? JSON.parse(msg.attachments) : msg.attachments || [];
|
|
2762
|
+
} catch {
|
|
2763
|
+
return msg;
|
|
2764
|
+
}
|
|
2765
|
+
const optimizedContent = replaceImagesWithDescriptions(
|
|
2766
|
+
msg.content || "",
|
|
2767
|
+
attachments
|
|
2768
|
+
);
|
|
2769
|
+
return {
|
|
2770
|
+
...msg,
|
|
2771
|
+
content: typeof optimizedContent === "string" ? optimizedContent : JSON.stringify(optimizedContent),
|
|
2772
|
+
// Clear attachments since we've inlined descriptions
|
|
2773
|
+
attachments: JSON.stringify(
|
|
2774
|
+
attachments.filter((att) => !att.mimeType.startsWith("image/"))
|
|
2775
|
+
)
|
|
2776
|
+
};
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
async function generateImageDescription(_imageBase64, _mimeType, _state) {
|
|
2780
|
+
return null;
|
|
2781
|
+
}
|
|
2782
|
+
var DEFAULT_CONFIG;
|
|
2783
|
+
var init_context = __esm({
|
|
2784
|
+
"src/agents/context.ts"() {
|
|
2785
|
+
DEFAULT_CONFIG = {
|
|
2786
|
+
recentMessageThreshold: 10,
|
|
2787
|
+
descriptionPrompt: "Describe this image concisely in 1-2 sentences, focusing on the key visual elements and any text visible."
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2546
2792
|
// src/agents/FlowEngine.ts
|
|
2547
2793
|
var FlowEngine_exports = {};
|
|
2548
2794
|
__export(FlowEngine_exports, {
|
|
@@ -2555,6 +2801,7 @@ var init_FlowEngine = __esm({
|
|
|
2555
2801
|
init_StreamManager();
|
|
2556
2802
|
init_LLMRequest();
|
|
2557
2803
|
init_ToolExecutor();
|
|
2804
|
+
init_context();
|
|
2558
2805
|
FlowEngine = class {
|
|
2559
2806
|
/**
|
|
2560
2807
|
* Main execution entry point
|
|
@@ -3014,6 +3261,7 @@ var init_FlowEngine = __esm({
|
|
|
3014
3261
|
*/
|
|
3015
3262
|
static async assembleContext(state) {
|
|
3016
3263
|
const messages = [];
|
|
3264
|
+
const imagePathMap = /* @__PURE__ */ new Map();
|
|
3017
3265
|
const model = state.prompt.model;
|
|
3018
3266
|
const promptName = state.prompt.name;
|
|
3019
3267
|
const parallelToolCalls = state.prompt.parallel_tool_calls;
|
|
@@ -3042,6 +3290,9 @@ var init_FlowEngine = __esm({
|
|
|
3042
3290
|
toolCallsWithResponses.add(msg.tool_call_id);
|
|
3043
3291
|
}
|
|
3044
3292
|
}
|
|
3293
|
+
const recentMessageThreshold = state.prompt.recentImageThreshold ?? 10;
|
|
3294
|
+
const oldMessageThreshold = completedMessages.length - recentMessageThreshold;
|
|
3295
|
+
let messageIndex = 0;
|
|
3045
3296
|
for (const msg of completedMessages) {
|
|
3046
3297
|
if (!includePastTools && msg.role === "tool") {
|
|
3047
3298
|
continue;
|
|
@@ -3071,7 +3322,36 @@ var init_FlowEngine = __esm({
|
|
|
3071
3322
|
role = state.currentSide === "a" ? "user" : "assistant";
|
|
3072
3323
|
}
|
|
3073
3324
|
}
|
|
3074
|
-
|
|
3325
|
+
let messageContent;
|
|
3326
|
+
const hasAttachments = msg.attachments && msg.attachments !== "[]";
|
|
3327
|
+
const isOldMessage = messageIndex < oldMessageThreshold;
|
|
3328
|
+
if (hasAttachments && msg.role === "user") {
|
|
3329
|
+
const attachments = JSON.parse(msg.attachments);
|
|
3330
|
+
const imageAttachments = attachments.filter(
|
|
3331
|
+
(a) => a.mimeType.startsWith("image/")
|
|
3332
|
+
);
|
|
3333
|
+
if (isOldMessage && imageAttachments.length > 0) {
|
|
3334
|
+
const imageDescriptions = imageAttachments.map(buildImageDescription).join("\n");
|
|
3335
|
+
const nonImageFiles = attachments.filter((a) => !a.mimeType.startsWith("image/")).map((a) => a.name);
|
|
3336
|
+
const nonImageList = nonImageFiles.length > 0 ? `
|
|
3337
|
+
[Other files: ${nonImageFiles.join(", ")}]` : "";
|
|
3338
|
+
messageContent = msg.content ? `${imageDescriptions}${nonImageList}
|
|
3339
|
+
|
|
3340
|
+
${msg.content}` : `${imageDescriptions}${nonImageList}`;
|
|
3341
|
+
} else {
|
|
3342
|
+
if (imageAttachments.length > 0) {
|
|
3343
|
+
messageContent = await this.resolveAttachmentsToContent(msg, state, imagePathMap);
|
|
3344
|
+
} else {
|
|
3345
|
+
const fileList = attachments.map((a) => a.name).join(", ");
|
|
3346
|
+
messageContent = msg.content ? `${msg.content}
|
|
3347
|
+
|
|
3348
|
+
[Attached files: ${fileList}]` : `[Attached files: ${fileList}]`;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
} else {
|
|
3352
|
+
messageContent = msg.content || "";
|
|
3353
|
+
}
|
|
3354
|
+
messageIndex++;
|
|
3075
3355
|
const messageToAdd = {
|
|
3076
3356
|
role,
|
|
3077
3357
|
content: messageContent,
|
|
@@ -3170,7 +3450,8 @@ var init_FlowEngine = __esm({
|
|
|
3170
3450
|
// Retries now apply to all prompts (top-level and sub-prompts), only for provider errors
|
|
3171
3451
|
parallel_tool_calls: parallelToolCalls,
|
|
3172
3452
|
tool_choice: finalToolChoice,
|
|
3173
|
-
reasoning
|
|
3453
|
+
reasoning,
|
|
3454
|
+
imagePathMap: imagePathMap.size > 0 ? imagePathMap : void 0
|
|
3174
3455
|
};
|
|
3175
3456
|
}
|
|
3176
3457
|
/**
|
|
@@ -4039,6 +4320,7 @@ var init_FlowEngine = __esm({
|
|
|
4039
4320
|
"role",
|
|
4040
4321
|
"content",
|
|
4041
4322
|
"name",
|
|
4323
|
+
"attachments",
|
|
4042
4324
|
...includeToolCalls ? ["tool_calls", "tool_call_id", "tool_status"] : [],
|
|
4043
4325
|
"log_id",
|
|
4044
4326
|
"created_at",
|
|
@@ -4070,6 +4352,7 @@ var init_FlowEngine = __esm({
|
|
|
4070
4352
|
role: row.role,
|
|
4071
4353
|
content: row.content,
|
|
4072
4354
|
name: row.name,
|
|
4355
|
+
attachments: row.attachments,
|
|
4073
4356
|
tool_calls: row.tool_calls,
|
|
4074
4357
|
tool_call_id: row.tool_call_id,
|
|
4075
4358
|
log_id: row.log_id,
|
|
@@ -4148,6 +4431,510 @@ ${errorStack}` : ""}`,
|
|
|
4148
4431
|
console.error("Failed to log error to database:", logError);
|
|
4149
4432
|
}
|
|
4150
4433
|
}
|
|
4434
|
+
/**
|
|
4435
|
+
* Check if a MIME type is an image
|
|
4436
|
+
*/
|
|
4437
|
+
static isImageMimeType(mimeType) {
|
|
4438
|
+
return mimeType.startsWith("image/");
|
|
4439
|
+
}
|
|
4440
|
+
/**
|
|
4441
|
+
* Resolve attachments from a message and build multimodal content
|
|
4442
|
+
* Returns the message content (string or multimodal array)
|
|
4443
|
+
*
|
|
4444
|
+
* @param msg - Message with potential attachments
|
|
4445
|
+
* @param state - Flow state with storage access
|
|
4446
|
+
* @returns MessageContent - either string or multimodal array
|
|
4447
|
+
*/
|
|
4448
|
+
static async resolveAttachmentsToContent(msg, state, imagePathMap) {
|
|
4449
|
+
const textContent = msg.content || "";
|
|
4450
|
+
if (!msg.attachments) {
|
|
4451
|
+
return textContent;
|
|
4452
|
+
}
|
|
4453
|
+
let attachments;
|
|
4454
|
+
try {
|
|
4455
|
+
attachments = JSON.parse(msg.attachments);
|
|
4456
|
+
} catch (e) {
|
|
4457
|
+
console.error(`Failed to parse attachments for message ${msg.id}:`, e);
|
|
4458
|
+
return textContent;
|
|
4459
|
+
}
|
|
4460
|
+
if (!attachments || attachments.length === 0) {
|
|
4461
|
+
return textContent;
|
|
4462
|
+
}
|
|
4463
|
+
const imageAttachments = attachments.filter(
|
|
4464
|
+
(a) => this.isImageMimeType(a.mimeType)
|
|
4465
|
+
);
|
|
4466
|
+
if (imageAttachments.length === 0) {
|
|
4467
|
+
const nonImageFiles2 = attachments.filter(
|
|
4468
|
+
(a) => !this.isImageMimeType(a.mimeType)
|
|
4469
|
+
);
|
|
4470
|
+
if (nonImageFiles2.length > 0) {
|
|
4471
|
+
const fileList = nonImageFiles2.map((a) => a.name).join(", ");
|
|
4472
|
+
return `${textContent}
|
|
4473
|
+
|
|
4474
|
+
[Attached files: ${fileList}]`;
|
|
4475
|
+
}
|
|
4476
|
+
return textContent;
|
|
4477
|
+
}
|
|
4478
|
+
const multimodalParts = [];
|
|
4479
|
+
if (textContent) {
|
|
4480
|
+
multimodalParts.push({
|
|
4481
|
+
type: "text",
|
|
4482
|
+
text: textContent
|
|
4483
|
+
});
|
|
4484
|
+
}
|
|
4485
|
+
for (const attachment of imageAttachments) {
|
|
4486
|
+
try {
|
|
4487
|
+
const result = await state.thread.instance.readFile(
|
|
4488
|
+
attachment.path
|
|
4489
|
+
);
|
|
4490
|
+
if (result.success && result.data) {
|
|
4491
|
+
const dataUrl = `data:${attachment.mimeType};base64,${result.data}`;
|
|
4492
|
+
if (imagePathMap) {
|
|
4493
|
+
imagePathMap.set(dataUrl, attachment.path);
|
|
4494
|
+
}
|
|
4495
|
+
multimodalParts.push({
|
|
4496
|
+
type: "image_url",
|
|
4497
|
+
image_url: {
|
|
4498
|
+
url: dataUrl,
|
|
4499
|
+
detail: "auto"
|
|
4500
|
+
}
|
|
4501
|
+
});
|
|
4502
|
+
} else {
|
|
4503
|
+
console.warn(
|
|
4504
|
+
`Failed to load image attachment ${attachment.path}:`,
|
|
4505
|
+
result.error
|
|
4506
|
+
);
|
|
4507
|
+
multimodalParts.push({
|
|
4508
|
+
type: "text",
|
|
4509
|
+
text: `[Image not available: ${attachment.name}]`
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
} catch (error) {
|
|
4513
|
+
console.error(`Error loading image attachment ${attachment.path}:`, error);
|
|
4514
|
+
multimodalParts.push({
|
|
4515
|
+
type: "text",
|
|
4516
|
+
text: `[Error loading image: ${attachment.name}]`
|
|
4517
|
+
});
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
const nonImageFiles = attachments.filter(
|
|
4521
|
+
(a) => !this.isImageMimeType(a.mimeType)
|
|
4522
|
+
);
|
|
4523
|
+
if (nonImageFiles.length > 0) {
|
|
4524
|
+
const fileList = nonImageFiles.map((a) => a.name).join(", ");
|
|
4525
|
+
multimodalParts.push({
|
|
4526
|
+
type: "text",
|
|
4527
|
+
text: `
|
|
4528
|
+
|
|
4529
|
+
[Attached files: ${fileList}]`
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
return multimodalParts;
|
|
4533
|
+
}
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
});
|
|
4537
|
+
|
|
4538
|
+
// src/durable-objects/files.ts
|
|
4539
|
+
var files_exports = {};
|
|
4540
|
+
__export(files_exports, {
|
|
4541
|
+
FileStorage: () => FileStorage,
|
|
4542
|
+
basename: () => basename,
|
|
4543
|
+
detectStorageBackend: () => detectStorageBackend,
|
|
4544
|
+
dirname: () => dirname,
|
|
4545
|
+
isTextMimeType: () => isTextMimeType,
|
|
4546
|
+
normalizePath: () => normalizePath,
|
|
4547
|
+
rowToFileRecord: () => rowToFileRecord
|
|
4548
|
+
});
|
|
4549
|
+
function isTextMimeType(mimeType) {
|
|
4550
|
+
return TEXT_MIME_TYPES.some((prefix) => mimeType.startsWith(prefix));
|
|
4551
|
+
}
|
|
4552
|
+
function detectStorageBackend(location) {
|
|
4553
|
+
if (location.startsWith("s3://")) return "s3";
|
|
4554
|
+
if (location.startsWith("r2://")) return "r2";
|
|
4555
|
+
if (location.startsWith("http://") || location.startsWith("https://"))
|
|
4556
|
+
return "url";
|
|
4557
|
+
return "local";
|
|
4558
|
+
}
|
|
4559
|
+
function basename(path4) {
|
|
4560
|
+
const parts = path4.split("/").filter(Boolean);
|
|
4561
|
+
return parts[parts.length - 1] || "";
|
|
4562
|
+
}
|
|
4563
|
+
function normalizePath(path4) {
|
|
4564
|
+
let normalized = path4.replace(/\/+/g, "/");
|
|
4565
|
+
if (!normalized.startsWith("/")) normalized = "/" + normalized;
|
|
4566
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
4567
|
+
normalized = normalized.slice(0, -1);
|
|
4568
|
+
}
|
|
4569
|
+
return normalized;
|
|
4570
|
+
}
|
|
4571
|
+
function dirname(path4) {
|
|
4572
|
+
const normalized = normalizePath(path4);
|
|
4573
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
4574
|
+
if (lastSlash <= 0) return "/";
|
|
4575
|
+
return normalized.slice(0, lastSlash);
|
|
4576
|
+
}
|
|
4577
|
+
function rowToFileRecord(row) {
|
|
4578
|
+
return {
|
|
4579
|
+
path: row.path,
|
|
4580
|
+
name: row.name,
|
|
4581
|
+
mimeType: row.mime_type,
|
|
4582
|
+
storage: row.storage,
|
|
4583
|
+
location: row.location,
|
|
4584
|
+
size: row.size,
|
|
4585
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : null,
|
|
4586
|
+
isDirectory: row.is_directory === 1,
|
|
4587
|
+
createdAt: row.created_at
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
function inferMimeType(filename) {
|
|
4591
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
4592
|
+
const mimeTypes = {
|
|
4593
|
+
// Text
|
|
4594
|
+
txt: "text/plain",
|
|
4595
|
+
md: "text/markdown",
|
|
4596
|
+
html: "text/html",
|
|
4597
|
+
css: "text/css",
|
|
4598
|
+
csv: "text/csv",
|
|
4599
|
+
// Code
|
|
4600
|
+
js: "application/javascript",
|
|
4601
|
+
ts: "application/typescript",
|
|
4602
|
+
json: "application/json",
|
|
4603
|
+
xml: "application/xml",
|
|
4604
|
+
yaml: "application/yaml",
|
|
4605
|
+
yml: "application/yaml",
|
|
4606
|
+
// Images
|
|
4607
|
+
jpg: "image/jpeg",
|
|
4608
|
+
jpeg: "image/jpeg",
|
|
4609
|
+
png: "image/png",
|
|
4610
|
+
gif: "image/gif",
|
|
4611
|
+
webp: "image/webp",
|
|
4612
|
+
svg: "image/svg+xml",
|
|
4613
|
+
ico: "image/x-icon",
|
|
4614
|
+
// Documents
|
|
4615
|
+
pdf: "application/pdf",
|
|
4616
|
+
// Archives
|
|
4617
|
+
zip: "application/zip",
|
|
4618
|
+
tar: "application/x-tar",
|
|
4619
|
+
gz: "application/gzip"
|
|
4620
|
+
};
|
|
4621
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
4622
|
+
}
|
|
4623
|
+
var TEXT_MIME_TYPES, FileStorage;
|
|
4624
|
+
var init_files = __esm({
|
|
4625
|
+
"src/durable-objects/files.ts"() {
|
|
4626
|
+
TEXT_MIME_TYPES = [
|
|
4627
|
+
"text/",
|
|
4628
|
+
"application/json",
|
|
4629
|
+
"application/javascript",
|
|
4630
|
+
"application/xml",
|
|
4631
|
+
"application/x-yaml",
|
|
4632
|
+
"application/yaml"
|
|
4633
|
+
];
|
|
4634
|
+
FileStorage = class {
|
|
4635
|
+
constructor(sql) {
|
|
4636
|
+
this.sql = sql;
|
|
4637
|
+
}
|
|
4638
|
+
/**
|
|
4639
|
+
* Write a file to storage
|
|
4640
|
+
*/
|
|
4641
|
+
async writeFile(path4, data, mimeType, options) {
|
|
4642
|
+
const normalizedPath = normalizePath(path4);
|
|
4643
|
+
const name = basename(normalizedPath);
|
|
4644
|
+
const isText = isTextMimeType(mimeType);
|
|
4645
|
+
const now = Date.now();
|
|
4646
|
+
let content = null;
|
|
4647
|
+
let blobData = null;
|
|
4648
|
+
let size;
|
|
4649
|
+
if (isText) {
|
|
4650
|
+
content = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
4651
|
+
size = new TextEncoder().encode(content).length;
|
|
4652
|
+
} else {
|
|
4653
|
+
blobData = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
4654
|
+
size = blobData.byteLength;
|
|
4655
|
+
}
|
|
4656
|
+
const metadataJson = options?.metadata ? JSON.stringify(options.metadata) : null;
|
|
4657
|
+
await this.sql.exec(
|
|
4658
|
+
`INSERT OR REPLACE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
|
|
4659
|
+
VALUES (?, ?, ?, 'local', NULL, ?, ?, ?, ?, ?, 0, ?)`,
|
|
4660
|
+
normalizedPath,
|
|
4661
|
+
name,
|
|
4662
|
+
mimeType,
|
|
4663
|
+
blobData,
|
|
4664
|
+
content,
|
|
4665
|
+
size,
|
|
4666
|
+
metadataJson,
|
|
4667
|
+
options?.thumbnail || null,
|
|
4668
|
+
now
|
|
4669
|
+
);
|
|
4670
|
+
await this.updateStats();
|
|
4671
|
+
return {
|
|
4672
|
+
path: normalizedPath,
|
|
4673
|
+
name,
|
|
4674
|
+
mimeType,
|
|
4675
|
+
storage: "local",
|
|
4676
|
+
location: null,
|
|
4677
|
+
size,
|
|
4678
|
+
metadata: options?.metadata || null,
|
|
4679
|
+
isDirectory: false,
|
|
4680
|
+
createdAt: now
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
/**
|
|
4684
|
+
* Link to an external file (URL, S3, R2)
|
|
4685
|
+
*/
|
|
4686
|
+
async linkFile(path4, location, options) {
|
|
4687
|
+
const normalizedPath = normalizePath(path4);
|
|
4688
|
+
const name = basename(normalizedPath);
|
|
4689
|
+
const storage = detectStorageBackend(location);
|
|
4690
|
+
const now = Date.now();
|
|
4691
|
+
const mimeType = options?.mimeType || inferMimeType(name);
|
|
4692
|
+
const metadataJson = options?.metadata ? JSON.stringify(options.metadata) : null;
|
|
4693
|
+
await this.sql.exec(
|
|
4694
|
+
`INSERT OR REPLACE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
|
|
4695
|
+
VALUES (?, ?, ?, ?, ?, NULL, NULL, ?, ?, NULL, 0, ?)`,
|
|
4696
|
+
normalizedPath,
|
|
4697
|
+
name,
|
|
4698
|
+
mimeType,
|
|
4699
|
+
storage,
|
|
4700
|
+
location,
|
|
4701
|
+
options?.size || 0,
|
|
4702
|
+
metadataJson,
|
|
4703
|
+
now
|
|
4704
|
+
);
|
|
4705
|
+
await this.updateStats();
|
|
4706
|
+
return {
|
|
4707
|
+
path: normalizedPath,
|
|
4708
|
+
name,
|
|
4709
|
+
mimeType,
|
|
4710
|
+
storage,
|
|
4711
|
+
location,
|
|
4712
|
+
size: options?.size || 0,
|
|
4713
|
+
metadata: options?.metadata || null,
|
|
4714
|
+
isDirectory: false,
|
|
4715
|
+
createdAt: now
|
|
4716
|
+
};
|
|
4717
|
+
}
|
|
4718
|
+
/**
|
|
4719
|
+
* Read file data from storage
|
|
4720
|
+
* Returns null if file doesn't exist or is external
|
|
4721
|
+
*/
|
|
4722
|
+
async readFile(path4) {
|
|
4723
|
+
const normalizedPath = normalizePath(path4);
|
|
4724
|
+
const cursor = await this.sql.exec(
|
|
4725
|
+
`SELECT data, content, storage FROM files WHERE path = ? AND is_directory = 0`,
|
|
4726
|
+
normalizedPath
|
|
4727
|
+
);
|
|
4728
|
+
const rows = cursor.toArray();
|
|
4729
|
+
if (rows.length === 0) return null;
|
|
4730
|
+
const row = rows[0];
|
|
4731
|
+
if (row.storage !== "local") return null;
|
|
4732
|
+
if (row.content !== null) {
|
|
4733
|
+
return new TextEncoder().encode(row.content);
|
|
4734
|
+
}
|
|
4735
|
+
return row.data;
|
|
4736
|
+
}
|
|
4737
|
+
/**
|
|
4738
|
+
* Read text file content
|
|
4739
|
+
*/
|
|
4740
|
+
async readTextFile(path4) {
|
|
4741
|
+
const normalizedPath = normalizePath(path4);
|
|
4742
|
+
const cursor = await this.sql.exec(
|
|
4743
|
+
`SELECT content, data, storage FROM files WHERE path = ? AND is_directory = 0`,
|
|
4744
|
+
normalizedPath
|
|
4745
|
+
);
|
|
4746
|
+
const rows = cursor.toArray();
|
|
4747
|
+
if (rows.length === 0) return null;
|
|
4748
|
+
const row = rows[0];
|
|
4749
|
+
if (row.storage !== "local") return null;
|
|
4750
|
+
if (row.content !== null) return row.content;
|
|
4751
|
+
if (row.data !== null) {
|
|
4752
|
+
return new TextDecoder().decode(row.data);
|
|
4753
|
+
}
|
|
4754
|
+
return null;
|
|
4755
|
+
}
|
|
4756
|
+
/**
|
|
4757
|
+
* Get file metadata (stat)
|
|
4758
|
+
*/
|
|
4759
|
+
async stat(path4) {
|
|
4760
|
+
const normalizedPath = normalizePath(path4);
|
|
4761
|
+
const cursor = await this.sql.exec(
|
|
4762
|
+
`SELECT path, name, mime_type, storage, location, size, metadata, is_directory, created_at
|
|
4763
|
+
FROM files WHERE path = ?`,
|
|
4764
|
+
normalizedPath
|
|
4765
|
+
);
|
|
4766
|
+
const rows = cursor.toArray();
|
|
4767
|
+
if (rows.length === 0) return null;
|
|
4768
|
+
return rowToFileRecord(rows[0]);
|
|
4769
|
+
}
|
|
4770
|
+
/**
|
|
4771
|
+
* Check if file or directory exists
|
|
4772
|
+
*/
|
|
4773
|
+
async exists(path4) {
|
|
4774
|
+
const normalizedPath = normalizePath(path4);
|
|
4775
|
+
const cursor = await this.sql.exec(
|
|
4776
|
+
`SELECT COUNT(*) as count FROM files WHERE path = ?`,
|
|
4777
|
+
normalizedPath
|
|
4778
|
+
);
|
|
4779
|
+
return cursor.one().count > 0;
|
|
4780
|
+
}
|
|
4781
|
+
/**
|
|
4782
|
+
* Delete a file
|
|
4783
|
+
*/
|
|
4784
|
+
async unlink(path4) {
|
|
4785
|
+
const normalizedPath = normalizePath(path4);
|
|
4786
|
+
await this.sql.exec(
|
|
4787
|
+
`DELETE FROM files WHERE path = ? AND is_directory = 0`,
|
|
4788
|
+
normalizedPath
|
|
4789
|
+
);
|
|
4790
|
+
await this.updateStats();
|
|
4791
|
+
}
|
|
4792
|
+
/**
|
|
4793
|
+
* Create a directory marker
|
|
4794
|
+
*/
|
|
4795
|
+
async mkdir(path4) {
|
|
4796
|
+
const normalizedPath = normalizePath(path4);
|
|
4797
|
+
const name = basename(normalizedPath);
|
|
4798
|
+
const now = Date.now();
|
|
4799
|
+
await this.sql.exec(
|
|
4800
|
+
`INSERT OR IGNORE INTO files (path, name, mime_type, storage, location, data, content, size, metadata, thumbnail, is_directory, created_at)
|
|
4801
|
+
VALUES (?, ?, 'inode/directory', 'local', NULL, NULL, NULL, 0, NULL, NULL, 1, ?)`,
|
|
4802
|
+
normalizedPath,
|
|
4803
|
+
name,
|
|
4804
|
+
now
|
|
4805
|
+
);
|
|
4806
|
+
return {
|
|
4807
|
+
path: normalizedPath,
|
|
4808
|
+
name,
|
|
4809
|
+
mimeType: "inode/directory",
|
|
4810
|
+
storage: "local",
|
|
4811
|
+
location: null,
|
|
4812
|
+
size: 0,
|
|
4813
|
+
metadata: null,
|
|
4814
|
+
isDirectory: true,
|
|
4815
|
+
createdAt: now
|
|
4816
|
+
};
|
|
4817
|
+
}
|
|
4818
|
+
/**
|
|
4819
|
+
* List directory contents
|
|
4820
|
+
*/
|
|
4821
|
+
async readdir(path4) {
|
|
4822
|
+
const normalizedPath = normalizePath(path4);
|
|
4823
|
+
const prefix = normalizedPath === "/" ? "/" : normalizedPath + "/";
|
|
4824
|
+
const cursor = await this.sql.exec(
|
|
4825
|
+
`SELECT path, name, mime_type, storage, location, size, metadata, is_directory, created_at
|
|
4826
|
+
FROM files
|
|
4827
|
+
WHERE path LIKE ? || '%'
|
|
4828
|
+
AND path != ?
|
|
4829
|
+
AND SUBSTR(path, LENGTH(?) + 1) NOT LIKE '%/%'
|
|
4830
|
+
ORDER BY is_directory DESC, name ASC`,
|
|
4831
|
+
prefix,
|
|
4832
|
+
normalizedPath,
|
|
4833
|
+
prefix
|
|
4834
|
+
);
|
|
4835
|
+
return cursor.toArray().map(rowToFileRecord);
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* Remove empty directory
|
|
4839
|
+
*/
|
|
4840
|
+
async rmdir(path4) {
|
|
4841
|
+
const normalizedPath = normalizePath(path4);
|
|
4842
|
+
const prefix = normalizedPath + "/";
|
|
4843
|
+
const cursor = await this.sql.exec(
|
|
4844
|
+
`SELECT COUNT(*) as count FROM files WHERE path LIKE ? || '%'`,
|
|
4845
|
+
prefix
|
|
4846
|
+
);
|
|
4847
|
+
if (cursor.one().count > 0) {
|
|
4848
|
+
throw new Error("Directory not empty");
|
|
4849
|
+
}
|
|
4850
|
+
await this.sql.exec(
|
|
4851
|
+
`DELETE FROM files WHERE path = ? AND is_directory = 1`,
|
|
4852
|
+
normalizedPath
|
|
4853
|
+
);
|
|
4854
|
+
}
|
|
4855
|
+
/**
|
|
4856
|
+
* Get storage statistics
|
|
4857
|
+
*/
|
|
4858
|
+
async getFileStats() {
|
|
4859
|
+
const cursor = await this.sql.exec(`SELECT total_size, file_count FROM file_stats WHERE id = 1`);
|
|
4860
|
+
const rows = cursor.toArray();
|
|
4861
|
+
if (rows.length === 0) {
|
|
4862
|
+
return { totalSize: 0, fileCount: 0 };
|
|
4863
|
+
}
|
|
4864
|
+
return {
|
|
4865
|
+
totalSize: rows[0].total_size,
|
|
4866
|
+
fileCount: rows[0].file_count
|
|
4867
|
+
};
|
|
4868
|
+
}
|
|
4869
|
+
/**
|
|
4870
|
+
* Update file statistics (call after writes/deletes)
|
|
4871
|
+
*/
|
|
4872
|
+
async updateStats() {
|
|
4873
|
+
await this.sql.exec(`
|
|
4874
|
+
UPDATE file_stats SET
|
|
4875
|
+
total_size = (SELECT COALESCE(SUM(size), 0) FROM files WHERE is_directory = 0),
|
|
4876
|
+
file_count = (SELECT COUNT(*) FROM files WHERE is_directory = 0)
|
|
4877
|
+
WHERE id = 1
|
|
4878
|
+
`);
|
|
4879
|
+
}
|
|
4880
|
+
/**
|
|
4881
|
+
* Get thumbnail for an image
|
|
4882
|
+
*/
|
|
4883
|
+
async getThumbnail(path4) {
|
|
4884
|
+
const normalizedPath = normalizePath(path4);
|
|
4885
|
+
const cursor = await this.sql.exec(
|
|
4886
|
+
`SELECT thumbnail FROM files WHERE path = ? AND is_directory = 0`,
|
|
4887
|
+
normalizedPath
|
|
4888
|
+
);
|
|
4889
|
+
const rows = cursor.toArray();
|
|
4890
|
+
if (rows.length === 0) return null;
|
|
4891
|
+
return rows[0].thumbnail;
|
|
4892
|
+
}
|
|
4893
|
+
/**
|
|
4894
|
+
* Search file contents using FTS5
|
|
4895
|
+
*/
|
|
4896
|
+
async grep(pattern, options) {
|
|
4897
|
+
const limit = options?.limit || 100;
|
|
4898
|
+
let query = `
|
|
4899
|
+
SELECT files.path, files.name, snippet(files_fts, 2, '<mark>', '</mark>', '...', 32) as snippet
|
|
4900
|
+
FROM files_fts
|
|
4901
|
+
JOIN files ON files.rowid = files_fts.rowid
|
|
4902
|
+
WHERE files_fts MATCH ?
|
|
4903
|
+
`;
|
|
4904
|
+
const params = [pattern];
|
|
4905
|
+
if (options?.path) {
|
|
4906
|
+
query += ` AND files.path LIKE ? || '%'`;
|
|
4907
|
+
params.push(normalizePath(options.path));
|
|
4908
|
+
}
|
|
4909
|
+
query += ` LIMIT ?`;
|
|
4910
|
+
params.push(limit);
|
|
4911
|
+
const cursor = await this.sql.exec(query, ...params);
|
|
4912
|
+
return cursor.toArray();
|
|
4913
|
+
}
|
|
4914
|
+
/**
|
|
4915
|
+
* Find files by path pattern (glob-like)
|
|
4916
|
+
*/
|
|
4917
|
+
async find(pattern, options) {
|
|
4918
|
+
const limit = options?.limit || 100;
|
|
4919
|
+
const type = options?.type || "all";
|
|
4920
|
+
let sqlPattern = pattern.replace(/\*\*/g, "%").replace(/\*/g, "%").replace(/\?/g, "_");
|
|
4921
|
+
if (!sqlPattern.startsWith("/")) {
|
|
4922
|
+
sqlPattern = "%" + sqlPattern;
|
|
4923
|
+
}
|
|
4924
|
+
let typeClause = "";
|
|
4925
|
+
if (type === "file") typeClause = " AND is_directory = 0";
|
|
4926
|
+
if (type === "directory") typeClause = " AND is_directory = 1";
|
|
4927
|
+
const cursor = await this.sql.exec(
|
|
4928
|
+
`SELECT path, name, mime_type, storage, location, size, metadata, is_directory, created_at
|
|
4929
|
+
FROM files
|
|
4930
|
+
WHERE path LIKE ?${typeClause}
|
|
4931
|
+
ORDER BY path
|
|
4932
|
+
LIMIT ?`,
|
|
4933
|
+
sqlPattern,
|
|
4934
|
+
limit
|
|
4935
|
+
);
|
|
4936
|
+
return cursor.toArray().map(rowToFileRecord);
|
|
4937
|
+
}
|
|
4151
4938
|
};
|
|
4152
4939
|
}
|
|
4153
4940
|
});
|
|
@@ -6204,7 +6991,14 @@ function agentbuilder(options = {}) {
|
|
|
6204
6991
|
const depsToExclude = [
|
|
6205
6992
|
"@standardagents/builder",
|
|
6206
6993
|
"@standardagents/builder/runtime",
|
|
6207
|
-
"@standardagents/builder/built-in-routes"
|
|
6994
|
+
"@standardagents/builder/built-in-routes",
|
|
6995
|
+
"@standardagents/builder/image-processing",
|
|
6996
|
+
// WASM image processing deps - must be excluded to avoid pre-bundle cache issues
|
|
6997
|
+
"@cf-wasm/photon",
|
|
6998
|
+
"@cf-wasm/photon/workerd",
|
|
6999
|
+
"@jsquash/avif",
|
|
7000
|
+
"@jsquash/jpeg",
|
|
7001
|
+
"@jsquash/png"
|
|
6208
7002
|
];
|
|
6209
7003
|
return {
|
|
6210
7004
|
optimizeDeps: {
|
|
@@ -6228,7 +7022,14 @@ function agentbuilder(options = {}) {
|
|
|
6228
7022
|
const depsToExclude = [
|
|
6229
7023
|
"@standardagents/builder",
|
|
6230
7024
|
"@standardagents/builder/runtime",
|
|
6231
|
-
"@standardagents/builder/built-in-routes"
|
|
7025
|
+
"@standardagents/builder/built-in-routes",
|
|
7026
|
+
"@standardagents/builder/image-processing",
|
|
7027
|
+
// WASM image processing deps
|
|
7028
|
+
"@cf-wasm/photon",
|
|
7029
|
+
"@cf-wasm/photon/workerd",
|
|
7030
|
+
"@jsquash/avif",
|
|
7031
|
+
"@jsquash/jpeg",
|
|
7032
|
+
"@jsquash/png"
|
|
6232
7033
|
];
|
|
6233
7034
|
config.optimizeDeps = config.optimizeDeps || {};
|
|
6234
7035
|
config.optimizeDeps.exclude = [
|
|
@@ -8116,8 +8917,88 @@ var migration16 = {
|
|
|
8116
8917
|
}
|
|
8117
8918
|
};
|
|
8118
8919
|
|
|
8920
|
+
// src/durable-objects/migrations/017_add_files.ts
|
|
8921
|
+
var migration17 = {
|
|
8922
|
+
version: 17,
|
|
8923
|
+
async up(sql) {
|
|
8924
|
+
await sql.exec(`
|
|
8925
|
+
CREATE TABLE files (
|
|
8926
|
+
path TEXT PRIMARY KEY,
|
|
8927
|
+
name TEXT NOT NULL,
|
|
8928
|
+
mime_type TEXT NOT NULL,
|
|
8929
|
+
|
|
8930
|
+
-- Storage backend: 'local' | 'url' | 's3' | 'r2'
|
|
8931
|
+
storage TEXT NOT NULL DEFAULT 'local',
|
|
8932
|
+
-- External reference: URL, s3://bucket/key, r2://bucket/key
|
|
8933
|
+
location TEXT,
|
|
8934
|
+
|
|
8935
|
+
-- Local storage (NULL if external)
|
|
8936
|
+
data BLOB,
|
|
8937
|
+
content TEXT,
|
|
8938
|
+
|
|
8939
|
+
size INTEGER NOT NULL DEFAULT 0,
|
|
8940
|
+
metadata TEXT,
|
|
8941
|
+
thumbnail BLOB,
|
|
8942
|
+
is_directory INTEGER NOT NULL DEFAULT 0,
|
|
8943
|
+
created_at INTEGER NOT NULL
|
|
8944
|
+
)
|
|
8945
|
+
`);
|
|
8946
|
+
await sql.exec(`
|
|
8947
|
+
CREATE VIRTUAL TABLE files_fts USING fts5(
|
|
8948
|
+
path,
|
|
8949
|
+
name,
|
|
8950
|
+
content,
|
|
8951
|
+
content='files',
|
|
8952
|
+
content_rowid='rowid'
|
|
8953
|
+
)
|
|
8954
|
+
`);
|
|
8955
|
+
await sql.exec(`
|
|
8956
|
+
CREATE TRIGGER files_ai AFTER INSERT ON files BEGIN
|
|
8957
|
+
INSERT INTO files_fts(rowid, path, name, content)
|
|
8958
|
+
VALUES (new.rowid, new.path, new.name, new.content);
|
|
8959
|
+
END
|
|
8960
|
+
`);
|
|
8961
|
+
await sql.exec(`
|
|
8962
|
+
CREATE TRIGGER files_ad AFTER DELETE ON files BEGIN
|
|
8963
|
+
INSERT INTO files_fts(files_fts, rowid, path, name, content)
|
|
8964
|
+
VALUES('delete', old.rowid, old.path, old.name, old.content);
|
|
8965
|
+
END
|
|
8966
|
+
`);
|
|
8967
|
+
await sql.exec(`
|
|
8968
|
+
CREATE TRIGGER files_au AFTER UPDATE ON files BEGIN
|
|
8969
|
+
INSERT INTO files_fts(files_fts, rowid, path, name, content)
|
|
8970
|
+
VALUES('delete', old.rowid, old.path, old.name, old.content);
|
|
8971
|
+
INSERT INTO files_fts(rowid, path, name, content)
|
|
8972
|
+
VALUES (new.rowid, new.path, new.name, new.content);
|
|
8973
|
+
END
|
|
8974
|
+
`);
|
|
8975
|
+
await sql.exec(`
|
|
8976
|
+
CREATE TABLE file_stats (
|
|
8977
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
8978
|
+
total_size INTEGER DEFAULT 0,
|
|
8979
|
+
file_count INTEGER DEFAULT 0
|
|
8980
|
+
)
|
|
8981
|
+
`);
|
|
8982
|
+
await sql.exec(`
|
|
8983
|
+
INSERT INTO file_stats (id, total_size, file_count) VALUES (1, 0, 0)
|
|
8984
|
+
`);
|
|
8985
|
+
await sql.exec(`
|
|
8986
|
+
ALTER TABLE messages ADD COLUMN attachments TEXT
|
|
8987
|
+
`);
|
|
8988
|
+
await sql.exec(`
|
|
8989
|
+
CREATE INDEX idx_files_created_at ON files(created_at)
|
|
8990
|
+
`);
|
|
8991
|
+
await sql.exec(`
|
|
8992
|
+
CREATE INDEX idx_files_mime_type ON files(mime_type)
|
|
8993
|
+
`);
|
|
8994
|
+
await sql.exec(`
|
|
8995
|
+
UPDATE _metadata SET value = '17' WHERE key = 'schema_version'
|
|
8996
|
+
`);
|
|
8997
|
+
}
|
|
8998
|
+
};
|
|
8999
|
+
|
|
8119
9000
|
// 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];
|
|
9001
|
+
var migrations = [migration, migration2, migration3, migration4, migration5, migration6, migration7, migration8, migration9, migration10, migration11, migration12, migration13, migration14, migration15, migration16, migration17];
|
|
8121
9002
|
var LATEST_SCHEMA_VERSION = migrations.length;
|
|
8122
9003
|
|
|
8123
9004
|
// src/durable-objects/DurableThread.ts
|
|
@@ -8327,7 +9208,7 @@ var DurableThread = class extends DurableObject {
|
|
|
8327
9208
|
);
|
|
8328
9209
|
break;
|
|
8329
9210
|
case "processMessage":
|
|
8330
|
-
await this.processMessage(args.threadId, args.content, args.role);
|
|
9211
|
+
await this.processMessage(args.threadId, args.content, args.role, args.attachments);
|
|
8331
9212
|
break;
|
|
8332
9213
|
case "testOperation":
|
|
8333
9214
|
await this.testOperation(args.id, args.label, args.expectedOrder);
|
|
@@ -8518,9 +9399,9 @@ var DurableThread = class extends DurableObject {
|
|
|
8518
9399
|
* Each migration is run in order, starting from the current version + 1.
|
|
8519
9400
|
*/
|
|
8520
9401
|
async runMigrations(fromVersion) {
|
|
8521
|
-
for (const
|
|
8522
|
-
if (
|
|
8523
|
-
await
|
|
9402
|
+
for (const migration19 of migrations) {
|
|
9403
|
+
if (migration19.version > fromVersion) {
|
|
9404
|
+
await migration19.up(this.ctx.storage.sql);
|
|
8524
9405
|
}
|
|
8525
9406
|
}
|
|
8526
9407
|
}
|
|
@@ -8578,7 +9459,7 @@ var DurableThread = class extends DurableObject {
|
|
|
8578
9459
|
* Send a new message to the thread (RPC method)
|
|
8579
9460
|
* Enqueues the message processing to be handled by the alarm handler
|
|
8580
9461
|
*/
|
|
8581
|
-
async sendMessage(threadId, content, role = "user") {
|
|
9462
|
+
async sendMessage(threadId, content, role = "user", attachments) {
|
|
8582
9463
|
await this.ensureMigrated();
|
|
8583
9464
|
try {
|
|
8584
9465
|
const queueId = await this.alarmQueue.enqueue(
|
|
@@ -8586,7 +9467,8 @@ var DurableThread = class extends DurableObject {
|
|
|
8586
9467
|
{
|
|
8587
9468
|
threadId,
|
|
8588
9469
|
content,
|
|
8589
|
-
role
|
|
9470
|
+
role,
|
|
9471
|
+
attachments
|
|
8590
9472
|
},
|
|
8591
9473
|
1
|
|
8592
9474
|
// Execute almost immediately (1ms)
|
|
@@ -8797,7 +9679,7 @@ var DurableThread = class extends DurableObject {
|
|
|
8797
9679
|
const total = countResult.one().total;
|
|
8798
9680
|
const result = await this.ctx.storage.sql.exec(
|
|
8799
9681
|
`
|
|
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
|
|
9682
|
+
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
9683
|
FROM messages
|
|
8802
9684
|
${whereClause}
|
|
8803
9685
|
ORDER BY created_at ${order === "ASC" ? "ASC" : "DESC"}
|
|
@@ -8821,7 +9703,8 @@ var DurableThread = class extends DurableObject {
|
|
|
8821
9703
|
depth: row.depth,
|
|
8822
9704
|
status: row.status,
|
|
8823
9705
|
reasoning_content: row.reasoning_content,
|
|
8824
|
-
reasoning_details: row.reasoning_details
|
|
9706
|
+
reasoning_details: row.reasoning_details,
|
|
9707
|
+
attachments: row.attachments ? JSON.parse(row.attachments) : null
|
|
8825
9708
|
}));
|
|
8826
9709
|
if (order === "DESC") {
|
|
8827
9710
|
messages = messages.reverse();
|
|
@@ -9526,7 +10409,7 @@ var DurableThread = class extends DurableObject {
|
|
|
9526
10409
|
* Internal method: Process a message (called by alarm queue)
|
|
9527
10410
|
* This is the actual message processing logic, separate from the public sendMessage() RPC method
|
|
9528
10411
|
*/
|
|
9529
|
-
async processMessage(threadId, content, role = "user") {
|
|
10412
|
+
async processMessage(threadId, content, role = "user", attachments) {
|
|
9530
10413
|
if (role === "user") {
|
|
9531
10414
|
await this.ctx.storage.sql.exec(
|
|
9532
10415
|
`UPDATE execution_state SET value = 'false' WHERE key = 'stopped'`
|
|
@@ -9576,6 +10459,7 @@ var DurableThread = class extends DurableObject {
|
|
|
9576
10459
|
id: messageId,
|
|
9577
10460
|
role,
|
|
9578
10461
|
content,
|
|
10462
|
+
attachments: attachments || null,
|
|
9579
10463
|
created_at: timestamp
|
|
9580
10464
|
};
|
|
9581
10465
|
const state = {
|
|
@@ -9591,16 +10475,18 @@ var DurableThread = class extends DurableObject {
|
|
|
9591
10475
|
const { FlowEngine: FlowEngine2 } = await Promise.resolve().then(() => (init_FlowEngine(), FlowEngine_exports));
|
|
9592
10476
|
message = await FlowEngine2.runBeforeCreateMessageHook(state, message);
|
|
9593
10477
|
await this.ctx.storage.sql.exec(
|
|
9594
|
-
`INSERT INTO messages (id, role, content, created_at) VALUES (?, ?, ?, ?)`,
|
|
10478
|
+
`INSERT INTO messages (id, role, content, attachments, created_at) VALUES (?, ?, ?, ?, ?)`,
|
|
9595
10479
|
message.id,
|
|
9596
10480
|
message.role,
|
|
9597
10481
|
message.content,
|
|
10482
|
+
message.attachments,
|
|
9598
10483
|
message.created_at
|
|
9599
10484
|
);
|
|
9600
10485
|
this.broadcastMessage({
|
|
9601
10486
|
id: message.id,
|
|
9602
10487
|
role: message.role,
|
|
9603
10488
|
content: message.content,
|
|
10489
|
+
attachments: message.attachments,
|
|
9604
10490
|
created_at: message.created_at
|
|
9605
10491
|
});
|
|
9606
10492
|
const nextSide = role === "assistant" ? "b" : "a";
|
|
@@ -9769,10 +10655,261 @@ var DurableThread = class extends DurableObject {
|
|
|
9769
10655
|
now
|
|
9770
10656
|
);
|
|
9771
10657
|
}
|
|
10658
|
+
// ============================================================
|
|
10659
|
+
// File Storage Methods (RPC)
|
|
10660
|
+
// ============================================================
|
|
10661
|
+
/**
|
|
10662
|
+
* Get file storage helper instance
|
|
10663
|
+
*/
|
|
10664
|
+
getFileStorage() {
|
|
10665
|
+
const { FileStorage: FileStorage2 } = (init_files(), __toCommonJS(files_exports));
|
|
10666
|
+
return new FileStorage2(this.ctx.storage.sql);
|
|
10667
|
+
}
|
|
10668
|
+
/**
|
|
10669
|
+
* Write a file to storage (RPC method)
|
|
10670
|
+
*/
|
|
10671
|
+
async writeFile(path4, data, mimeType, options) {
|
|
10672
|
+
await this.ensureMigrated();
|
|
10673
|
+
try {
|
|
10674
|
+
const fs4 = this.getFileStorage();
|
|
10675
|
+
const dataBuffer = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
|
|
10676
|
+
const thumbnailBuffer = options?.thumbnail ? Uint8Array.from(atob(options.thumbnail), (c) => c.charCodeAt(0)) : void 0;
|
|
10677
|
+
const record = await fs4.writeFile(path4, dataBuffer, mimeType, {
|
|
10678
|
+
metadata: options?.metadata,
|
|
10679
|
+
thumbnail: thumbnailBuffer
|
|
10680
|
+
});
|
|
10681
|
+
return { success: true, file: record };
|
|
10682
|
+
} catch (error) {
|
|
10683
|
+
console.error("Error in writeFile:", error);
|
|
10684
|
+
return { success: false, error: error.message };
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
/**
|
|
10688
|
+
* Write a text file to storage (RPC method)
|
|
10689
|
+
*/
|
|
10690
|
+
async writeTextFile(path4, content, mimeType = "text/plain", options) {
|
|
10691
|
+
await this.ensureMigrated();
|
|
10692
|
+
try {
|
|
10693
|
+
const fs4 = this.getFileStorage();
|
|
10694
|
+
const record = await fs4.writeFile(path4, content, mimeType, options);
|
|
10695
|
+
return { success: true, file: record };
|
|
10696
|
+
} catch (error) {
|
|
10697
|
+
console.error("Error in writeTextFile:", error);
|
|
10698
|
+
return { success: false, error: error.message };
|
|
10699
|
+
}
|
|
10700
|
+
}
|
|
10701
|
+
/**
|
|
10702
|
+
* Link to an external file (RPC method)
|
|
10703
|
+
*/
|
|
10704
|
+
async linkFile(path4, location, options) {
|
|
10705
|
+
await this.ensureMigrated();
|
|
10706
|
+
try {
|
|
10707
|
+
const fs4 = this.getFileStorage();
|
|
10708
|
+
const record = await fs4.linkFile(path4, location, options);
|
|
10709
|
+
return { success: true, file: record };
|
|
10710
|
+
} catch (error) {
|
|
10711
|
+
console.error("Error in linkFile:", error);
|
|
10712
|
+
return { success: false, error: error.message };
|
|
10713
|
+
}
|
|
10714
|
+
}
|
|
10715
|
+
/**
|
|
10716
|
+
* Read a file from storage (RPC method)
|
|
10717
|
+
* Returns base64-encoded data
|
|
10718
|
+
*/
|
|
10719
|
+
async readFile(path4) {
|
|
10720
|
+
await this.ensureMigrated();
|
|
10721
|
+
try {
|
|
10722
|
+
const fs4 = this.getFileStorage();
|
|
10723
|
+
const data = await fs4.readFile(path4);
|
|
10724
|
+
if (data === null) {
|
|
10725
|
+
return { success: false, error: "File not found or external" };
|
|
10726
|
+
}
|
|
10727
|
+
const bytes = new Uint8Array(data);
|
|
10728
|
+
let binary = "";
|
|
10729
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
10730
|
+
binary += String.fromCharCode(bytes[i]);
|
|
10731
|
+
}
|
|
10732
|
+
const base64 = btoa(binary);
|
|
10733
|
+
return { success: true, data: base64 };
|
|
10734
|
+
} catch (error) {
|
|
10735
|
+
console.error("Error in readFile:", error);
|
|
10736
|
+
return { success: false, error: error.message };
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
/**
|
|
10740
|
+
* Read a text file from storage (RPC method)
|
|
10741
|
+
*/
|
|
10742
|
+
async readTextFile(path4) {
|
|
10743
|
+
await this.ensureMigrated();
|
|
10744
|
+
try {
|
|
10745
|
+
const fs4 = this.getFileStorage();
|
|
10746
|
+
const content = await fs4.readTextFile(path4);
|
|
10747
|
+
if (content === null) {
|
|
10748
|
+
return { success: false, error: "File not found or external" };
|
|
10749
|
+
}
|
|
10750
|
+
return { success: true, content };
|
|
10751
|
+
} catch (error) {
|
|
10752
|
+
console.error("Error in readTextFile:", error);
|
|
10753
|
+
return { success: false, error: error.message };
|
|
10754
|
+
}
|
|
10755
|
+
}
|
|
10756
|
+
/**
|
|
10757
|
+
* Get file metadata (RPC method)
|
|
10758
|
+
*/
|
|
10759
|
+
async statFile(path4) {
|
|
10760
|
+
await this.ensureMigrated();
|
|
10761
|
+
try {
|
|
10762
|
+
const fs4 = this.getFileStorage();
|
|
10763
|
+
const record = await fs4.stat(path4);
|
|
10764
|
+
if (record === null) {
|
|
10765
|
+
return { success: false, error: "File not found" };
|
|
10766
|
+
}
|
|
10767
|
+
return { success: true, file: record };
|
|
10768
|
+
} catch (error) {
|
|
10769
|
+
console.error("Error in statFile:", error);
|
|
10770
|
+
return { success: false, error: error.message };
|
|
10771
|
+
}
|
|
10772
|
+
}
|
|
10773
|
+
/**
|
|
10774
|
+
* Check if file exists (RPC method)
|
|
10775
|
+
*/
|
|
10776
|
+
async fileExists(path4) {
|
|
10777
|
+
await this.ensureMigrated();
|
|
10778
|
+
try {
|
|
10779
|
+
const fs4 = this.getFileStorage();
|
|
10780
|
+
const exists2 = await fs4.exists(path4);
|
|
10781
|
+
return { success: true, exists: exists2 };
|
|
10782
|
+
} catch (error) {
|
|
10783
|
+
console.error("Error in fileExists:", error);
|
|
10784
|
+
return { success: false, error: error.message };
|
|
10785
|
+
}
|
|
10786
|
+
}
|
|
10787
|
+
/**
|
|
10788
|
+
* Delete a file (RPC method)
|
|
10789
|
+
*/
|
|
10790
|
+
async unlinkFile(path4) {
|
|
10791
|
+
await this.ensureMigrated();
|
|
10792
|
+
try {
|
|
10793
|
+
const fs4 = this.getFileStorage();
|
|
10794
|
+
await fs4.unlink(path4);
|
|
10795
|
+
return { success: true };
|
|
10796
|
+
} catch (error) {
|
|
10797
|
+
console.error("Error in unlinkFile:", error);
|
|
10798
|
+
return { success: false, error: error.message };
|
|
10799
|
+
}
|
|
10800
|
+
}
|
|
10801
|
+
/**
|
|
10802
|
+
* Create a directory (RPC method)
|
|
10803
|
+
*/
|
|
10804
|
+
async mkdirFile(path4) {
|
|
10805
|
+
await this.ensureMigrated();
|
|
10806
|
+
try {
|
|
10807
|
+
const fs4 = this.getFileStorage();
|
|
10808
|
+
const record = await fs4.mkdir(path4);
|
|
10809
|
+
return { success: true, directory: record };
|
|
10810
|
+
} catch (error) {
|
|
10811
|
+
console.error("Error in mkdirFile:", error);
|
|
10812
|
+
return { success: false, error: error.message };
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10815
|
+
/**
|
|
10816
|
+
* List directory contents (RPC method)
|
|
10817
|
+
*/
|
|
10818
|
+
async readdirFile(path4) {
|
|
10819
|
+
await this.ensureMigrated();
|
|
10820
|
+
try {
|
|
10821
|
+
const fs4 = this.getFileStorage();
|
|
10822
|
+
const files = await fs4.readdir(path4);
|
|
10823
|
+
return { success: true, files };
|
|
10824
|
+
} catch (error) {
|
|
10825
|
+
console.error("Error in readdirFile:", error);
|
|
10826
|
+
return { success: false, error: error.message };
|
|
10827
|
+
}
|
|
10828
|
+
}
|
|
10829
|
+
/**
|
|
10830
|
+
* Remove empty directory (RPC method)
|
|
10831
|
+
*/
|
|
10832
|
+
async rmdirFile(path4) {
|
|
10833
|
+
await this.ensureMigrated();
|
|
10834
|
+
try {
|
|
10835
|
+
const fs4 = this.getFileStorage();
|
|
10836
|
+
await fs4.rmdir(path4);
|
|
10837
|
+
return { success: true };
|
|
10838
|
+
} catch (error) {
|
|
10839
|
+
console.error("Error in rmdirFile:", error);
|
|
10840
|
+
return { success: false, error: error.message };
|
|
10841
|
+
}
|
|
10842
|
+
}
|
|
10843
|
+
/**
|
|
10844
|
+
* Get file storage statistics (RPC method)
|
|
10845
|
+
*/
|
|
10846
|
+
async getFileStats() {
|
|
10847
|
+
await this.ensureMigrated();
|
|
10848
|
+
try {
|
|
10849
|
+
const fs4 = this.getFileStorage();
|
|
10850
|
+
const stats = await fs4.getFileStats();
|
|
10851
|
+
return { success: true, stats };
|
|
10852
|
+
} catch (error) {
|
|
10853
|
+
console.error("Error in getFileStats:", error);
|
|
10854
|
+
return { success: false, error: error.message };
|
|
10855
|
+
}
|
|
10856
|
+
}
|
|
10857
|
+
/**
|
|
10858
|
+
* Get thumbnail for an image (RPC method)
|
|
10859
|
+
* Returns base64-encoded data
|
|
10860
|
+
*/
|
|
10861
|
+
async getFileThumbnail(path4) {
|
|
10862
|
+
await this.ensureMigrated();
|
|
10863
|
+
try {
|
|
10864
|
+
const fs4 = this.getFileStorage();
|
|
10865
|
+
const data = await fs4.getThumbnail(path4);
|
|
10866
|
+
if (data === null) {
|
|
10867
|
+
return { success: false, error: "Thumbnail not found" };
|
|
10868
|
+
}
|
|
10869
|
+
const bytes = new Uint8Array(data);
|
|
10870
|
+
let binary = "";
|
|
10871
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
10872
|
+
binary += String.fromCharCode(bytes[i]);
|
|
10873
|
+
}
|
|
10874
|
+
const base64 = btoa(binary);
|
|
10875
|
+
return { success: true, data: base64 };
|
|
10876
|
+
} catch (error) {
|
|
10877
|
+
console.error("Error in getFileThumbnail:", error);
|
|
10878
|
+
return { success: false, error: error.message };
|
|
10879
|
+
}
|
|
10880
|
+
}
|
|
10881
|
+
/**
|
|
10882
|
+
* Search file contents using FTS5 (RPC method)
|
|
10883
|
+
*/
|
|
10884
|
+
async grepFiles(pattern, options) {
|
|
10885
|
+
await this.ensureMigrated();
|
|
10886
|
+
try {
|
|
10887
|
+
const fs4 = this.getFileStorage();
|
|
10888
|
+
const results = await fs4.grep(pattern, options);
|
|
10889
|
+
return { success: true, results };
|
|
10890
|
+
} catch (error) {
|
|
10891
|
+
console.error("Error in grepFiles:", error);
|
|
10892
|
+
return { success: false, error: error.message };
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10895
|
+
/**
|
|
10896
|
+
* Find files by path pattern (RPC method)
|
|
10897
|
+
*/
|
|
10898
|
+
async findFiles(pattern, options) {
|
|
10899
|
+
await this.ensureMigrated();
|
|
10900
|
+
try {
|
|
10901
|
+
const fs4 = this.getFileStorage();
|
|
10902
|
+
const files = await fs4.find(pattern, options);
|
|
10903
|
+
return { success: true, files };
|
|
10904
|
+
} catch (error) {
|
|
10905
|
+
console.error("Error in findFiles:", error);
|
|
10906
|
+
return { success: false, error: error.message };
|
|
10907
|
+
}
|
|
10908
|
+
}
|
|
9772
10909
|
};
|
|
9773
10910
|
|
|
9774
10911
|
// src/durable-objects/agentbuilder-migrations/0001_initial.ts
|
|
9775
|
-
var
|
|
10912
|
+
var migration18 = {
|
|
9776
10913
|
version: 1,
|
|
9777
10914
|
async up(sql) {
|
|
9778
10915
|
sql.exec(`
|
|
@@ -9871,7 +11008,7 @@ var migration17 = {
|
|
|
9871
11008
|
};
|
|
9872
11009
|
|
|
9873
11010
|
// src/durable-objects/agentbuilder-migrations/index.ts
|
|
9874
|
-
var migrations2 = [
|
|
11011
|
+
var migrations2 = [migration18];
|
|
9875
11012
|
var LATEST_SCHEMA_VERSION2 = 1;
|
|
9876
11013
|
|
|
9877
11014
|
// src/durable-objects/DurableAgentBuilder.ts
|
|
@@ -9961,9 +11098,9 @@ var DurableAgentBuilder = class extends DurableObject {
|
|
|
9961
11098
|
}
|
|
9962
11099
|
}
|
|
9963
11100
|
async runMigrations(fromVersion) {
|
|
9964
|
-
for (const
|
|
9965
|
-
if (
|
|
9966
|
-
await
|
|
11101
|
+
for (const migration19 of migrations2) {
|
|
11102
|
+
if (migration19.version > fromVersion) {
|
|
11103
|
+
await migration19.up(this.ctx.storage.sql);
|
|
9967
11104
|
}
|
|
9968
11105
|
}
|
|
9969
11106
|
}
|
|
@@ -10731,6 +11868,12 @@ function definePrompt(options) {
|
|
|
10731
11868
|
`Invalid reasoning.effort '${options.reasoning.effort}'. Must be one of: low, medium, high`
|
|
10732
11869
|
);
|
|
10733
11870
|
}
|
|
11871
|
+
if (options.maxImagePixels !== void 0 && (options.maxImagePixels <= 0 || !Number.isInteger(options.maxImagePixels))) {
|
|
11872
|
+
throw new Error("maxImagePixels must be a positive integer");
|
|
11873
|
+
}
|
|
11874
|
+
if (options.recentImageThreshold !== void 0 && (options.recentImageThreshold <= 0 || !Number.isInteger(options.recentImageThreshold))) {
|
|
11875
|
+
throw new Error("recentImageThreshold must be a positive integer");
|
|
11876
|
+
}
|
|
10734
11877
|
return options;
|
|
10735
11878
|
}
|
|
10736
11879
|
|
|
@@ -10923,6 +12066,165 @@ async function updateThread(thread, params) {
|
|
|
10923
12066
|
}
|
|
10924
12067
|
return result.thread;
|
|
10925
12068
|
}
|
|
12069
|
+
async function writeFile(flow, path4, data, mimeType, options) {
|
|
12070
|
+
const instance = flow.thread.instance;
|
|
12071
|
+
let base64Data;
|
|
12072
|
+
if (typeof data === "string") {
|
|
12073
|
+
base64Data = btoa(data);
|
|
12074
|
+
} else {
|
|
12075
|
+
const bytes = new Uint8Array(data);
|
|
12076
|
+
let binary = "";
|
|
12077
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
12078
|
+
binary += String.fromCharCode(bytes[i]);
|
|
12079
|
+
}
|
|
12080
|
+
base64Data = btoa(binary);
|
|
12081
|
+
}
|
|
12082
|
+
let base64Thumbnail;
|
|
12083
|
+
if (options?.thumbnail) {
|
|
12084
|
+
const bytes = new Uint8Array(options.thumbnail);
|
|
12085
|
+
let binary = "";
|
|
12086
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
12087
|
+
binary += String.fromCharCode(bytes[i]);
|
|
12088
|
+
}
|
|
12089
|
+
base64Thumbnail = btoa(binary);
|
|
12090
|
+
}
|
|
12091
|
+
const result = await instance.writeFile(path4, base64Data, mimeType, {
|
|
12092
|
+
metadata: options?.metadata,
|
|
12093
|
+
thumbnail: base64Thumbnail
|
|
12094
|
+
});
|
|
12095
|
+
if (!result.success) {
|
|
12096
|
+
throw new Error(result.error || "Failed to write file");
|
|
12097
|
+
}
|
|
12098
|
+
return result.file;
|
|
12099
|
+
}
|
|
12100
|
+
async function writeImage(flow, path4, data, mimeType, options) {
|
|
12101
|
+
return writeFile(flow, path4, data, mimeType, options);
|
|
12102
|
+
}
|
|
12103
|
+
async function linkFile(flow, path4, location, options) {
|
|
12104
|
+
const instance = flow.thread.instance;
|
|
12105
|
+
const result = await instance.linkFile(path4, location, options);
|
|
12106
|
+
if (!result.success) {
|
|
12107
|
+
throw new Error(result.error || "Failed to link file");
|
|
12108
|
+
}
|
|
12109
|
+
return result.file;
|
|
12110
|
+
}
|
|
12111
|
+
async function readFile(flow, path4) {
|
|
12112
|
+
const instance = flow.thread.instance;
|
|
12113
|
+
const result = await instance.readFile(path4);
|
|
12114
|
+
if (!result.success) {
|
|
12115
|
+
return null;
|
|
12116
|
+
}
|
|
12117
|
+
const binary = atob(result.data);
|
|
12118
|
+
const bytes = new Uint8Array(binary.length);
|
|
12119
|
+
for (let i = 0; i < binary.length; i++) {
|
|
12120
|
+
bytes[i] = binary.charCodeAt(i);
|
|
12121
|
+
}
|
|
12122
|
+
return bytes.buffer;
|
|
12123
|
+
}
|
|
12124
|
+
async function stat(flow, path4) {
|
|
12125
|
+
const instance = flow.thread.instance;
|
|
12126
|
+
const result = await instance.statFile(path4);
|
|
12127
|
+
if (!result.success) {
|
|
12128
|
+
return null;
|
|
12129
|
+
}
|
|
12130
|
+
return result.file;
|
|
12131
|
+
}
|
|
12132
|
+
async function exists(flow, path4) {
|
|
12133
|
+
const instance = flow.thread.instance;
|
|
12134
|
+
const result = await instance.fileExists(path4);
|
|
12135
|
+
return result.success && result.exists;
|
|
12136
|
+
}
|
|
12137
|
+
async function unlink(flow, path4) {
|
|
12138
|
+
const instance = flow.thread.instance;
|
|
12139
|
+
const result = await instance.unlinkFile(path4);
|
|
12140
|
+
if (!result.success) {
|
|
12141
|
+
throw new Error(result.error || "Failed to delete file");
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
async function mkdir(flow, path4) {
|
|
12145
|
+
const instance = flow.thread.instance;
|
|
12146
|
+
const result = await instance.mkdirFile(path4);
|
|
12147
|
+
if (!result.success) {
|
|
12148
|
+
throw new Error(result.error || "Failed to create directory");
|
|
12149
|
+
}
|
|
12150
|
+
return result.directory;
|
|
12151
|
+
}
|
|
12152
|
+
async function readdir(flow, path4) {
|
|
12153
|
+
const instance = flow.thread.instance;
|
|
12154
|
+
const result = await instance.readdirFile(path4);
|
|
12155
|
+
if (!result.success) {
|
|
12156
|
+
throw new Error(result.error || "Failed to read directory");
|
|
12157
|
+
}
|
|
12158
|
+
return result.files;
|
|
12159
|
+
}
|
|
12160
|
+
async function rmdir(flow, path4) {
|
|
12161
|
+
const instance = flow.thread.instance;
|
|
12162
|
+
const result = await instance.rmdirFile(path4);
|
|
12163
|
+
if (!result.success) {
|
|
12164
|
+
throw new Error(result.error || "Failed to remove directory");
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
async function getFileStats(flow) {
|
|
12168
|
+
const instance = flow.thread.instance;
|
|
12169
|
+
const result = await instance.getFileStats();
|
|
12170
|
+
if (!result.success) {
|
|
12171
|
+
throw new Error(result.error || "Failed to get file stats");
|
|
12172
|
+
}
|
|
12173
|
+
return result.stats;
|
|
12174
|
+
}
|
|
12175
|
+
async function getThumbnail(flow, path4) {
|
|
12176
|
+
const instance = flow.thread.instance;
|
|
12177
|
+
const result = await instance.getFileThumbnail(path4);
|
|
12178
|
+
if (!result.success) {
|
|
12179
|
+
return null;
|
|
12180
|
+
}
|
|
12181
|
+
const binary = atob(result.data);
|
|
12182
|
+
const bytes = new Uint8Array(binary.length);
|
|
12183
|
+
for (let i = 0; i < binary.length; i++) {
|
|
12184
|
+
bytes[i] = binary.charCodeAt(i);
|
|
12185
|
+
}
|
|
12186
|
+
return bytes.buffer;
|
|
12187
|
+
}
|
|
12188
|
+
async function cat(flow, path4) {
|
|
12189
|
+
const instance = flow.thread.instance;
|
|
12190
|
+
const result = await instance.readTextFile(path4);
|
|
12191
|
+
if (!result.success) {
|
|
12192
|
+
return null;
|
|
12193
|
+
}
|
|
12194
|
+
return result.content;
|
|
12195
|
+
}
|
|
12196
|
+
async function head(flow, path4, lines = 10) {
|
|
12197
|
+
const content = await cat(flow, path4);
|
|
12198
|
+
if (content === null) return null;
|
|
12199
|
+
const allLines = content.split("\n");
|
|
12200
|
+
return allLines.slice(0, lines).join("\n");
|
|
12201
|
+
}
|
|
12202
|
+
async function tail(flow, path4, lines = 10) {
|
|
12203
|
+
const content = await cat(flow, path4);
|
|
12204
|
+
if (content === null) return null;
|
|
12205
|
+
const allLines = content.split("\n");
|
|
12206
|
+
return allLines.slice(-lines).join("\n");
|
|
12207
|
+
}
|
|
12208
|
+
async function grep(flow, pattern, options) {
|
|
12209
|
+
const instance = flow.thread.instance;
|
|
12210
|
+
const result = await instance.grepFiles(pattern, options);
|
|
12211
|
+
if (!result.success) {
|
|
12212
|
+
throw new Error(result.error || "Search failed");
|
|
12213
|
+
}
|
|
12214
|
+
return result.results.map((r) => ({
|
|
12215
|
+
path: r.path,
|
|
12216
|
+
name: r.name,
|
|
12217
|
+
matches: [r.snippet]
|
|
12218
|
+
}));
|
|
12219
|
+
}
|
|
12220
|
+
async function find(flow, pattern, options) {
|
|
12221
|
+
const instance = flow.thread.instance;
|
|
12222
|
+
const result = await instance.findFiles(pattern, options);
|
|
12223
|
+
if (!result.success) {
|
|
12224
|
+
throw new Error(result.error || "Find failed");
|
|
12225
|
+
}
|
|
12226
|
+
return result.files;
|
|
12227
|
+
}
|
|
10926
12228
|
|
|
10927
12229
|
// src/agents/FlowStateSdk.ts
|
|
10928
12230
|
init_types();
|
|
@@ -11257,6 +12559,9 @@ function enhanceFlowState(flow) {
|
|
|
11257
12559
|
return enhanced;
|
|
11258
12560
|
}
|
|
11259
12561
|
|
|
12562
|
+
// src/index.ts
|
|
12563
|
+
init_context();
|
|
12564
|
+
|
|
11260
12565
|
// src/github/GitHubClient.ts
|
|
11261
12566
|
var GITHUB_API_BASE = "https://api.github.com";
|
|
11262
12567
|
var GitHubClient = class _GitHubClient {
|
|
@@ -11494,6 +12799,6 @@ var GitHubApiError = class extends Error {
|
|
|
11494
12799
|
}
|
|
11495
12800
|
};
|
|
11496
12801
|
|
|
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 };
|
|
12802
|
+
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
12803
|
//# sourceMappingURL=index.js.map
|
|
11499
12804
|
//# sourceMappingURL=index.js.map
|