@tangle-network/agent-app 0.45.59 → 0.45.61
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/chat-routes/attachment-store.d.ts +7 -4
- package/dist/chat-routes/index.js +61 -34
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-routes/promote-file-part.d.ts +2 -0
- package/dist/{chunk-CDC5HFKL.js → chunk-MH74DY2I.js} +13 -3
- package/dist/chunk-MH74DY2I.js.map +1 -0
- package/dist/redact/index.js +1 -1
- package/package.json +18 -18
- package/dist/chunk-CDC5HFKL.js.map +0 -1
|
@@ -50,13 +50,16 @@ export type ReadAttachmentFn = (scopeId: string, path: string) => Promise<Attach
|
|
|
50
50
|
* routes create a unique path for each attempt, and the product adapter stores
|
|
51
51
|
* this id with it. */
|
|
52
52
|
export interface AttachmentWriteOwnership {
|
|
53
|
-
id: string;
|
|
54
|
-
path: string;
|
|
53
|
+
readonly id: string;
|
|
54
|
+
readonly path: string;
|
|
55
55
|
}
|
|
56
56
|
/** Public-safe outcome for a storage outage. Backend details belong in logs. */
|
|
57
57
|
export declare const ATTACHMENT_STORAGE_FAILURE_MESSAGE = "Attachment storage is temporarily unavailable. Please try again.";
|
|
58
|
-
/**
|
|
59
|
-
|
|
58
|
+
/** Stable client error code when compensating cleanup did not complete. */
|
|
59
|
+
export declare const ATTACHMENT_ROLLBACK_FAILURE_CODE = "rollback_failed";
|
|
60
|
+
/** Public-safe outcome when compensating cleanup did not complete. */
|
|
61
|
+
export declare const ATTACHMENT_ROLLBACK_FAILURE_MESSAGE = "Attachment cleanup failed. Please try again.";
|
|
62
|
+
/** Add a path-safe ownership id to a logical path and return an immutable store key. */
|
|
60
63
|
export declare function immutableAttachmentPath(logicalPath: string, ownershipId: string): string;
|
|
61
64
|
/** Compensation for one attachment write. The adapter MUST compare the stored
|
|
62
65
|
* ownership id before deleting, so an older rollback cannot delete a newer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
redactErrorMessage
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-MH74DY2I.js";
|
|
4
4
|
import {
|
|
5
5
|
createSandboxFileIndexRoute
|
|
6
6
|
} from "../chunk-QY4BRKRJ.js";
|
|
@@ -2267,6 +2267,8 @@ function createUploadRoute(options) {
|
|
|
2267
2267
|
|
|
2268
2268
|
// src/chat-routes/attachment-store.ts
|
|
2269
2269
|
var ATTACHMENT_STORAGE_FAILURE_MESSAGE = "Attachment storage is temporarily unavailable. Please try again.";
|
|
2270
|
+
var ATTACHMENT_ROLLBACK_FAILURE_CODE = "rollback_failed";
|
|
2271
|
+
var ATTACHMENT_ROLLBACK_FAILURE_MESSAGE = "Attachment cleanup failed. Please try again.";
|
|
2270
2272
|
function immutableAttachmentPath(logicalPath, ownershipId) {
|
|
2271
2273
|
if (!/^[A-Za-z0-9_-]{1,128}$/.test(ownershipId)) {
|
|
2272
2274
|
throw new Error("attachment ownership id must be a path-safe identifier");
|
|
@@ -2462,27 +2464,27 @@ function base64ToBytes(base64) {
|
|
|
2462
2464
|
return bytes;
|
|
2463
2465
|
}
|
|
2464
2466
|
function parseDataUrl(url) {
|
|
2465
|
-
const match = /^data:[^,]*,([\s\S]*)
|
|
2467
|
+
const match = /^data:[^,]*,([\s\S]*)$/i.exec(url);
|
|
2466
2468
|
if (!match) return null;
|
|
2467
2469
|
return { base64: /;base64,/i.test(url), data: match[1] ?? "" };
|
|
2468
2470
|
}
|
|
2469
2471
|
function dataUrlMime(url) {
|
|
2470
2472
|
if (!url) return void 0;
|
|
2471
|
-
const match = /^data:([^;,]+)[;,]
|
|
2473
|
+
const match = /^data:([^;,]+)[;,]/i.exec(url);
|
|
2472
2474
|
return match ? match[1] : void 0;
|
|
2473
2475
|
}
|
|
2474
2476
|
function basenameFromUrl(url) {
|
|
2475
|
-
if (!url ||
|
|
2477
|
+
if (!url || /^data:/i.test(url)) return void 0;
|
|
2476
2478
|
const withoutQuery = url.split(/[?#]/)[0] ?? url;
|
|
2477
2479
|
const segments = withoutQuery.split("/").filter(Boolean);
|
|
2478
2480
|
return segments[segments.length - 1] || void 0;
|
|
2479
2481
|
}
|
|
2480
2482
|
function resolveFileUrlPath(url) {
|
|
2481
|
-
const withoutScheme =
|
|
2483
|
+
const withoutScheme = /^file:\/\//i.test(url) ? url.slice("file://".length) : url;
|
|
2482
2484
|
try {
|
|
2483
2485
|
return { succeeded: true, path: decodeURIComponent(withoutScheme) };
|
|
2484
2486
|
} catch (err) {
|
|
2485
|
-
return { succeeded: false, reason: `malformed file path: ${
|
|
2487
|
+
return { succeeded: false, reason: `malformed file path: ${redactErrorMessage(err)}` };
|
|
2486
2488
|
}
|
|
2487
2489
|
}
|
|
2488
2490
|
function oversizeReason(filename, actual, limit) {
|
|
@@ -2495,7 +2497,7 @@ function resolveDataUrlBytes(url, filename, maxBytes) {
|
|
|
2495
2497
|
try {
|
|
2496
2498
|
bytes = parsed.base64 ? base64ToBytes(parsed.data) : new TextEncoder().encode(decodeURIComponent(parsed.data));
|
|
2497
2499
|
} catch (err) {
|
|
2498
|
-
return { succeeded: false, reason: `failed to decode data URI: ${
|
|
2500
|
+
return { succeeded: false, reason: `failed to decode data URI: ${redactErrorMessage(err)}` };
|
|
2499
2501
|
}
|
|
2500
2502
|
if (bytes.byteLength > maxBytes) {
|
|
2501
2503
|
return { succeeded: false, reason: oversizeReason(filename, bytes.byteLength, maxBytes) };
|
|
@@ -2505,23 +2507,26 @@ function resolveDataUrlBytes(url, filename, maxBytes) {
|
|
|
2505
2507
|
async function resolveSandboxFileBytes(input) {
|
|
2506
2508
|
const stat = await statSandboxFileSize(input.box, input.path, { sessionId: input.sessionId });
|
|
2507
2509
|
if (!stat.succeeded) {
|
|
2508
|
-
return { succeeded: false, reason: `could not stat agent file: ${stat.error}` };
|
|
2510
|
+
return { succeeded: false, reason: `could not stat agent file: ${redactErrorMessage(stat.error)}` };
|
|
2509
2511
|
}
|
|
2510
2512
|
if (stat.value > input.maxBytes) {
|
|
2511
2513
|
return { succeeded: false, reason: oversizeReason(input.filename, stat.value, input.maxBytes) };
|
|
2512
2514
|
}
|
|
2513
2515
|
const read = await readSandboxBinaryBytes(input.box, input.path, stat.value, { sessionId: input.sessionId });
|
|
2514
2516
|
if (!read.succeeded) {
|
|
2515
|
-
return { succeeded: false, reason: `could not read agent file: ${read.error}` };
|
|
2517
|
+
return { succeeded: false, reason: `could not read agent file: ${redactErrorMessage(read.error)}` };
|
|
2516
2518
|
}
|
|
2517
2519
|
return { succeeded: true, bytes: read.value.bytes };
|
|
2518
2520
|
}
|
|
2519
2521
|
async function resolveBytes(input) {
|
|
2520
2522
|
const url = input.raw.url;
|
|
2521
2523
|
if (!url) return { succeeded: false, reason: "the file part carries no url" };
|
|
2522
|
-
if (
|
|
2523
|
-
const isSandboxPath =
|
|
2524
|
-
if (!isSandboxPath)
|
|
2524
|
+
if (/^data:/i.test(url)) return resolveDataUrlBytes(url, input.filename, input.maxBytes);
|
|
2525
|
+
const isSandboxPath = /^file:\/\//i.test(url) || url.startsWith("/");
|
|
2526
|
+
if (!isSandboxPath) {
|
|
2527
|
+
const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(url)?.[1]?.toLowerCase() ?? "unknown";
|
|
2528
|
+
return { succeeded: false, reason: `unsupported file URL scheme: ${scheme}` };
|
|
2529
|
+
}
|
|
2525
2530
|
if (!input.box) return { succeeded: false, reason: "no sandbox to read agent file" };
|
|
2526
2531
|
const resolvedPath = resolveFileUrlPath(url);
|
|
2527
2532
|
if (!resolvedPath.succeeded) return resolvedPath;
|
|
@@ -2545,7 +2550,13 @@ function defaultBuildAttachmentPath(args) {
|
|
|
2545
2550
|
}
|
|
2546
2551
|
function logPromotionStorageError(logger, message, fields) {
|
|
2547
2552
|
try {
|
|
2548
|
-
|
|
2553
|
+
const safeFields = Object.fromEntries(
|
|
2554
|
+
Object.entries(fields).map(([key, value]) => [
|
|
2555
|
+
key,
|
|
2556
|
+
typeof value === "string" ? redactErrorMessage(value) : value
|
|
2557
|
+
])
|
|
2558
|
+
);
|
|
2559
|
+
logger.error(message, safeFields);
|
|
2549
2560
|
} catch {
|
|
2550
2561
|
}
|
|
2551
2562
|
}
|
|
@@ -2589,11 +2600,21 @@ async function promoteAgentFilePart(options) {
|
|
|
2589
2600
|
maxBytes
|
|
2590
2601
|
});
|
|
2591
2602
|
if (!resolved.succeeded) return { succeeded: false, filename, reason: resolved.reason };
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2603
|
+
let mediaType;
|
|
2604
|
+
let kind;
|
|
2605
|
+
let logicalPath;
|
|
2606
|
+
try {
|
|
2607
|
+
mediaType = (options.raw.mediaType ?? options.raw.mime ?? dataUrlMime(options.raw.url) ?? sniffMime(filename)).toLowerCase();
|
|
2608
|
+
kind = attachmentKindForMime(mediaType);
|
|
2609
|
+
const digest = await hash8(options.raw.id ?? options.raw.url ?? filename);
|
|
2610
|
+
const date = now().toISOString().split("T")[0] ?? "";
|
|
2611
|
+
logicalPath = buildAttachmentPath({ filename, hash8: digest, date, mediaType, kind });
|
|
2612
|
+
} catch (error) {
|
|
2613
|
+
logPromotionStorageError(logger, "[promote-file-part] path planning failed", {
|
|
2614
|
+
error: redactErrorMessage(error)
|
|
2615
|
+
});
|
|
2616
|
+
return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
|
|
2617
|
+
}
|
|
2597
2618
|
if (!atomic) {
|
|
2598
2619
|
let written2;
|
|
2599
2620
|
try {
|
|
@@ -2649,7 +2670,7 @@ async function promoteAgentFilePart(options) {
|
|
|
2649
2670
|
});
|
|
2650
2671
|
return { succeeded: false, filename, reason: ATTACHMENT_STORAGE_FAILURE_MESSAGE };
|
|
2651
2672
|
}
|
|
2652
|
-
const ownership = { id: ownershipId, path };
|
|
2673
|
+
const ownership = Object.freeze({ id: ownershipId, path });
|
|
2653
2674
|
let written;
|
|
2654
2675
|
try {
|
|
2655
2676
|
written = await options.attachmentWriter.write(options.scopeId, path, resolved.bytes, {
|
|
@@ -2720,7 +2741,13 @@ async function promoteAgentFilePart(options) {
|
|
|
2720
2741
|
// src/chat-routes/attachment-upload.ts
|
|
2721
2742
|
function logAttachmentUploadError(logger, message, fields) {
|
|
2722
2743
|
try {
|
|
2723
|
-
|
|
2744
|
+
const safeFields = Object.fromEntries(
|
|
2745
|
+
Object.entries(fields).map(([key, value]) => [
|
|
2746
|
+
key,
|
|
2747
|
+
typeof value === "string" ? redactErrorMessage(value) : value
|
|
2748
|
+
])
|
|
2749
|
+
);
|
|
2750
|
+
logger.error(message, safeFields);
|
|
2724
2751
|
} catch {
|
|
2725
2752
|
}
|
|
2726
2753
|
}
|
|
@@ -2730,6 +2757,14 @@ function attachmentUploadError(status, code, message, path) {
|
|
|
2730
2757
|
{ status }
|
|
2731
2758
|
);
|
|
2732
2759
|
}
|
|
2760
|
+
function atomicAttachmentFailure(path, cleanupFailures = 0) {
|
|
2761
|
+
return attachmentUploadError(
|
|
2762
|
+
503,
|
|
2763
|
+
cleanupFailures > 0 ? ATTACHMENT_ROLLBACK_FAILURE_CODE : "attachment_store_unavailable",
|
|
2764
|
+
cleanupFailures > 0 ? ATTACHMENT_ROLLBACK_FAILURE_MESSAGE : ATTACHMENT_STORAGE_FAILURE_MESSAGE,
|
|
2765
|
+
path
|
|
2766
|
+
);
|
|
2767
|
+
}
|
|
2733
2768
|
async function rollbackSuccessfulWrites(writes, scopeId, writer, logger) {
|
|
2734
2769
|
let failures = 0;
|
|
2735
2770
|
for (let index = writes.length - 1; index >= 0; index -= 1) {
|
|
@@ -2955,7 +2990,7 @@ function createAttachmentUploadRoute(options) {
|
|
|
2955
2990
|
if (!ownershipId) {
|
|
2956
2991
|
return attachmentUploadError(503, "attachment_store_unavailable", ATTACHMENT_STORAGE_FAILURE_MESSAGE, input.path);
|
|
2957
2992
|
}
|
|
2958
|
-
const ownership = { id: ownershipId, path: input.path };
|
|
2993
|
+
const ownership = Object.freeze({ id: ownershipId, path: input.path });
|
|
2959
2994
|
let written;
|
|
2960
2995
|
try {
|
|
2961
2996
|
written = await atomicWriter.write(auth.scopeId, input.path, input.bytes, {
|
|
@@ -2973,12 +3008,7 @@ function createAttachmentUploadRoute(options) {
|
|
|
2973
3008
|
cleanupFailures,
|
|
2974
3009
|
error: redactErrorMessage(error)
|
|
2975
3010
|
});
|
|
2976
|
-
return
|
|
2977
|
-
503,
|
|
2978
|
-
"attachment_store_unavailable",
|
|
2979
|
-
ATTACHMENT_STORAGE_FAILURE_MESSAGE,
|
|
2980
|
-
input.path
|
|
2981
|
-
);
|
|
3011
|
+
return atomicAttachmentFailure(input.path, cleanupFailures);
|
|
2982
3012
|
}
|
|
2983
3013
|
const result = inspectAtomicAttachmentWriteResult(written, ownership);
|
|
2984
3014
|
if (!result) {
|
|
@@ -2988,7 +3018,7 @@ function createAttachmentUploadRoute(options) {
|
|
|
2988
3018
|
ownershipId: ownership.id,
|
|
2989
3019
|
cleanupFailures
|
|
2990
3020
|
});
|
|
2991
|
-
return
|
|
3021
|
+
return atomicAttachmentFailure(input.path, cleanupFailures);
|
|
2992
3022
|
}
|
|
2993
3023
|
if (!result.ok) {
|
|
2994
3024
|
const cleanupFailures = await rollbackSuccessfulWrites([
|
|
@@ -3005,12 +3035,7 @@ function createAttachmentUploadRoute(options) {
|
|
|
3005
3035
|
cleanupFailures,
|
|
3006
3036
|
error: redactErrorMessage(result.reason)
|
|
3007
3037
|
});
|
|
3008
|
-
return
|
|
3009
|
-
503,
|
|
3010
|
-
"attachment_store_unavailable",
|
|
3011
|
-
ATTACHMENT_STORAGE_FAILURE_MESSAGE,
|
|
3012
|
-
input.path
|
|
3013
|
-
);
|
|
3038
|
+
return atomicAttachmentFailure(input.path, cleanupFailures);
|
|
3014
3039
|
}
|
|
3015
3040
|
successfulWrites.push({ path: input.path, ownership, receipt: result.receipt });
|
|
3016
3041
|
uploaded.push({
|
|
@@ -3245,6 +3270,8 @@ export {
|
|
|
3245
3270
|
ALLOWED_ATTACHMENT_SNIFFED_MIMES,
|
|
3246
3271
|
ATTACHMENT_ACCEPT,
|
|
3247
3272
|
ATTACHMENT_MAX_COUNT,
|
|
3273
|
+
ATTACHMENT_ROLLBACK_FAILURE_CODE,
|
|
3274
|
+
ATTACHMENT_ROLLBACK_FAILURE_MESSAGE,
|
|
3248
3275
|
ATTACHMENT_STORAGE_FAILURE_MESSAGE,
|
|
3249
3276
|
ChatTurnInputError,
|
|
3250
3277
|
DEFAULT_MODEL_FIRST_RESPONSE_TIMEOUT_MS,
|