@ai-matrx/media 0.7.16 → 0.8.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/files.js ADDED
@@ -0,0 +1,1275 @@
1
+ // src/files/ref-types.ts
2
+ function toVisibility(raw) {
3
+ if (raw === "public") return "public";
4
+ if (raw === "link") return "link";
5
+ if (raw === "internal") return "internal";
6
+ return "personal";
7
+ }
8
+ function fileIdToMediaRef(fileId, mimeType) {
9
+ const ref = { file_id: fileId };
10
+ if (mimeType) ref.mime_type = mimeType;
11
+ return ref;
12
+ }
13
+ function urlToMediaRef(url, mimeType) {
14
+ const ref = { url };
15
+ if (mimeType) ref.mime_type = mimeType;
16
+ return ref;
17
+ }
18
+
19
+ // src/files/blocks/types.ts
20
+ function parseGenerationMetadata(metadata) {
21
+ if (!metadata) return null;
22
+ const raw = metadata.generation;
23
+ if (!raw || typeof raw !== "object") return null;
24
+ const w = raw;
25
+ if (typeof w.kind !== "string") return null;
26
+ if (typeof w.provider !== "string") return null;
27
+ if (typeof w.model !== "string") return null;
28
+ if (typeof w.prompt !== "string") return null;
29
+ return {
30
+ kind: w.kind,
31
+ provider: w.provider,
32
+ model: w.model,
33
+ prompt: w.prompt,
34
+ negativePrompt: w.negative_prompt ?? null,
35
+ revisedPrompt: w.revised_prompt ?? null,
36
+ width: w.width ?? null,
37
+ height: w.height ?? null,
38
+ aspectRatio: w.aspect_ratio ?? null,
39
+ durationSeconds: w.duration_seconds ?? null,
40
+ quality: w.quality ?? null,
41
+ style: w.style ?? null,
42
+ seed: w.seed ?? null,
43
+ steps: w.steps ?? null,
44
+ cfgScale: w.cfg_scale ?? null,
45
+ nRequested: w.n_requested ?? 1,
46
+ nReturned: w.n_returned ?? 1,
47
+ responseId: w.response_id ?? null,
48
+ durationMs: w.duration_ms ?? null,
49
+ costUsd: w.cost_usd ?? null,
50
+ finishReason: w.finish_reason ?? null,
51
+ safetyFlagged: w.safety_flagged ?? false,
52
+ providerExtras: w.provider_extras ?? {}
53
+ };
54
+ }
55
+
56
+ // src/files/blocks/guards.ts
57
+ function isObject(value) {
58
+ return value !== null && typeof value === "object";
59
+ }
60
+ function hasMediaShape(value) {
61
+ if (!isObject(value)) return false;
62
+ if (typeof value.kind !== "string") return false;
63
+ if (value.origin !== "matrx" && value.origin !== "external") return false;
64
+ return true;
65
+ }
66
+ function isMatrxMediaBlock(value) {
67
+ if (!hasMediaShape(value)) return false;
68
+ if (value.origin !== "matrx") return false;
69
+ return typeof value.fileId === "string";
70
+ }
71
+ function isExternalMediaBlock(value) {
72
+ if (!hasMediaShape(value)) return false;
73
+ if (value.origin !== "external") return false;
74
+ return typeof value.externalUrl === "string";
75
+ }
76
+ function isUnifiedMediaBlock(value) {
77
+ return isMatrxMediaBlock(value) || isExternalMediaBlock(value);
78
+ }
79
+ function isImageBlock(value) {
80
+ return isUnifiedMediaBlock(value) && value.kind === "image";
81
+ }
82
+ function isVideoBlock(value) {
83
+ return isUnifiedMediaBlock(value) && value.kind === "video";
84
+ }
85
+ function isAudioBlock(value) {
86
+ return isUnifiedMediaBlock(value) && value.kind === "audio";
87
+ }
88
+ function isDocumentBlock(value) {
89
+ return isUnifiedMediaBlock(value) && value.kind === "document";
90
+ }
91
+ function isYouTubeBlock(value) {
92
+ return isUnifiedMediaBlock(value) && value.kind === "youtube";
93
+ }
94
+ function isMatrxImageBlock(value) {
95
+ return isImageBlock(value) && value.origin === "matrx";
96
+ }
97
+ function isExternalImageBlock(value) {
98
+ return isImageBlock(value) && value.origin === "external";
99
+ }
100
+
101
+ // src/files/blocks/image/guards.ts
102
+ function isUnifiedImageBlock(value) {
103
+ return isImageBlock(value);
104
+ }
105
+
106
+ // src/files/blocks/adapters/from-cx-av-part.ts
107
+ function str(v) {
108
+ return typeof v === "string" && v.length > 0 ? v : null;
109
+ }
110
+ function liftUrls(part) {
111
+ const metadata = part.metadata ?? null;
112
+ const cdn = str(metadata?.cdn_url);
113
+ const download = str(metadata?.download_url);
114
+ return {
115
+ // The on-disk `url` is a save-time snapshot; forward it as the fallback
116
+ // the handler only uses when no identity is available.
117
+ url: str(part.url) ?? cdn ?? "",
118
+ file_id: str(part.file_id) ?? str(metadata?.file_id),
119
+ cdn_url: cdn,
120
+ download_url: download
121
+ };
122
+ }
123
+ function fromCxAudioPart(part) {
124
+ return {
125
+ type: "audio_output",
126
+ ...liftUrls(part),
127
+ mime_type: part.mime_type ?? "audio/*",
128
+ transcription_result: part.transcription_result ?? null
129
+ };
130
+ }
131
+ function fromCxVideoPart(part) {
132
+ return {
133
+ type: "video_output",
134
+ ...liftUrls(part),
135
+ mime_type: part.mime_type ?? "video/*",
136
+ // A video reference's role (extend / restyle) and its `@name` — carried so
137
+ // a reload redisplays them (see ReferenceRoleCaption).
138
+ reference_role: part.role ?? null,
139
+ reference_name: part.name ?? null
140
+ };
141
+ }
142
+
143
+ // src/files/blocks/adapters/from-media-block.ts
144
+ function pickStatus(s) {
145
+ return s === "streaming" || s === "error" ? s : "complete";
146
+ }
147
+ function pickVisibility(v) {
148
+ return toVisibility(v);
149
+ }
150
+ function commonFields(wire) {
151
+ return {
152
+ status: pickStatus(wire.status),
153
+ progress: wire.progress ?? null,
154
+ errorMessage: wire.error_message ?? null,
155
+ mimeType: wire.mime_type ?? null,
156
+ fileName: wire.file_name ?? null,
157
+ sizeBytes: wire.size_bytes ?? null,
158
+ base64: wire.base64 ?? null,
159
+ metadata: wire.metadata ?? null
160
+ };
161
+ }
162
+ function matrxFields(wire) {
163
+ if (wire.origin !== "matrx") return null;
164
+ if (!wire.file_id) return null;
165
+ return {
166
+ origin: "matrx",
167
+ fileId: wire.file_id,
168
+ visibility: pickVisibility(wire.visibility),
169
+ cdnUrl: wire.cdn_url ?? null,
170
+ downloadUrl: wire.download_url ?? null,
171
+ parentFileId: wire.parent_file_id ?? null,
172
+ derivationKind: wire.derivation_kind ?? null
173
+ };
174
+ }
175
+ function externalFields(wire) {
176
+ return {
177
+ origin: "external",
178
+ externalUrl: wire.external_url ?? "",
179
+ sourceLabel: wire.source_label ?? null
180
+ };
181
+ }
182
+ function liftImage(wire) {
183
+ const common = commonFields(wire);
184
+ const matrx = matrxFields(wire);
185
+ const imageBits = {
186
+ kind: "image",
187
+ width: wire.width ?? null,
188
+ height: wire.height ?? null,
189
+ visionClass: wire.vision_class ?? null
190
+ };
191
+ if (matrx) {
192
+ const block2 = { ...common, ...matrx, ...imageBits };
193
+ return block2;
194
+ }
195
+ const block = {
196
+ ...common,
197
+ ...externalFields(wire),
198
+ ...imageBits
199
+ };
200
+ return block;
201
+ }
202
+ function liftVideo(wire) {
203
+ const common = commonFields(wire);
204
+ const matrx = matrxFields(wire);
205
+ const videoBits = {
206
+ kind: "video",
207
+ width: wire.width ?? null,
208
+ height: wire.height ?? null,
209
+ durationMs: wire.duration_ms ?? null,
210
+ posterUrl: wire.poster_url ?? null
211
+ };
212
+ if (matrx) {
213
+ const block2 = { ...common, ...matrx, ...videoBits };
214
+ return block2;
215
+ }
216
+ const block = {
217
+ ...common,
218
+ ...externalFields(wire),
219
+ ...videoBits
220
+ };
221
+ return block;
222
+ }
223
+ function liftAudio(wire) {
224
+ const common = commonFields(wire);
225
+ const matrx = matrxFields(wire);
226
+ const audioBits = {
227
+ kind: "audio",
228
+ durationMs: wire.duration_ms ?? null,
229
+ transcript: wire.transcript ?? null
230
+ };
231
+ if (matrx) {
232
+ const block2 = { ...common, ...matrx, ...audioBits };
233
+ return block2;
234
+ }
235
+ const block = {
236
+ ...common,
237
+ ...externalFields(wire),
238
+ ...audioBits
239
+ };
240
+ return block;
241
+ }
242
+ function liftDocument(wire) {
243
+ const common = commonFields(wire);
244
+ const matrx = matrxFields(wire);
245
+ const docBits = {
246
+ kind: "document",
247
+ pageCount: wire.page_count ?? null,
248
+ page1Url: wire.page1_url ?? null
249
+ };
250
+ if (matrx) {
251
+ const block2 = { ...common, ...matrx, ...docBits };
252
+ return block2;
253
+ }
254
+ const block = {
255
+ ...common,
256
+ ...externalFields(wire),
257
+ ...docBits
258
+ };
259
+ return block;
260
+ }
261
+ function liftYouTube(wire) {
262
+ const common = commonFields(wire);
263
+ const block = {
264
+ ...common,
265
+ ...externalFields(wire),
266
+ kind: "youtube",
267
+ videoId: wire.video_id ?? null
268
+ };
269
+ return block;
270
+ }
271
+ function fromMediaBlock(wire) {
272
+ switch (wire.kind) {
273
+ case "image":
274
+ return liftImage(wire);
275
+ case "video":
276
+ return liftVideo(wire);
277
+ case "audio":
278
+ return liftAudio(wire);
279
+ case "document":
280
+ return liftDocument(wire);
281
+ case "youtube":
282
+ return liftYouTube(wire);
283
+ default: {
284
+ const base = wire;
285
+ const unknownKind = wire.kind;
286
+ const fallback = {
287
+ ...commonFields(base),
288
+ ...externalFields(base),
289
+ kind: "image",
290
+ width: null,
291
+ height: null,
292
+ visionClass: null,
293
+ metadata: {
294
+ ...base.metadata ?? {},
295
+ _unknown_media_kind: unknownKind
296
+ },
297
+ errorMessage: `Unsupported media kind: ${unknownKind}`,
298
+ status: "error"
299
+ };
300
+ return fallback;
301
+ }
302
+ }
303
+ }
304
+ function isMediaBlockData(value) {
305
+ if (value === null || typeof value !== "object") return false;
306
+ const v = value;
307
+ if (v.type !== "media_block") return false;
308
+ if (!v.block || typeof v.block !== "object") return false;
309
+ const b = v.block;
310
+ return typeof b.kind === "string" && typeof b.origin === "string";
311
+ }
312
+
313
+ // src/files/blocks/adapters/from-media-ref.ts
314
+ function commonBase(ref) {
315
+ return {
316
+ status: "complete",
317
+ progress: null,
318
+ errorMessage: null,
319
+ mimeType: ref.mime_type ?? null,
320
+ fileName: null,
321
+ sizeBytes: null,
322
+ base64: null,
323
+ metadata: null
324
+ };
325
+ }
326
+ function matrxBase(fileId) {
327
+ return {
328
+ origin: "matrx",
329
+ fileId,
330
+ visibility: "personal",
331
+ cdnUrl: null,
332
+ downloadUrl: null,
333
+ parentFileId: null,
334
+ derivationKind: null
335
+ };
336
+ }
337
+ function externalBase(url) {
338
+ return {
339
+ origin: "external",
340
+ externalUrl: url,
341
+ sourceLabel: null
342
+ };
343
+ }
344
+ function imageBlockFromMediaRef(ref) {
345
+ if (!ref) return null;
346
+ const common = commonBase(ref);
347
+ const imageBits = {
348
+ kind: "image",
349
+ width: null,
350
+ height: null,
351
+ visionClass: null
352
+ };
353
+ if (ref.file_id) {
354
+ const block2 = {
355
+ ...common,
356
+ ...matrxBase(ref.file_id),
357
+ ...imageBits
358
+ };
359
+ return block2;
360
+ }
361
+ const block = {
362
+ ...common,
363
+ ...externalBase(ref.url ?? ""),
364
+ ...imageBits
365
+ };
366
+ return block;
367
+ }
368
+ function videoBlockFromMediaRef(ref) {
369
+ if (!ref) return null;
370
+ const common = commonBase(ref);
371
+ const videoBits = {
372
+ kind: "video",
373
+ width: null,
374
+ height: null,
375
+ durationMs: null,
376
+ posterUrl: null
377
+ };
378
+ if (ref.file_id) {
379
+ const block2 = {
380
+ ...common,
381
+ ...matrxBase(ref.file_id),
382
+ ...videoBits
383
+ };
384
+ return block2;
385
+ }
386
+ const block = {
387
+ ...common,
388
+ ...externalBase(ref.url ?? ""),
389
+ ...videoBits
390
+ };
391
+ return block;
392
+ }
393
+ function blockFromMediaRef(ref, kind) {
394
+ return kind === "video" ? videoBlockFromMediaRef(ref) : imageBlockFromMediaRef(ref);
395
+ }
396
+
397
+ // src/files/blocks/image/helpers/extract-file-id-from-url.ts
398
+ import { isUuidShape } from "@ai-matrx/kit/uuid";
399
+ function extractFileIdFromUrl(url) {
400
+ if (!url) return null;
401
+ try {
402
+ const parts = new URL(url).pathname.split("/").filter(Boolean);
403
+ if (parts.length >= 2) {
404
+ const candidate = parts[1];
405
+ if (isUuidShape(candidate)) return candidate;
406
+ }
407
+ const last = parts[parts.length - 1] ?? "";
408
+ const stripped = last.replace(/\.[^.]+$/, "");
409
+ if (isUuidShape(stripped)) return stripped;
410
+ return null;
411
+ } catch {
412
+ return null;
413
+ }
414
+ }
415
+
416
+ // src/files/blocks/image/helpers/parse-filename-from-url.ts
417
+ var FILENAME_QUERY_KEYS = [
418
+ "response-content-disposition",
419
+ "content-disposition"
420
+ ];
421
+ function parseFilenameFromUrl(url) {
422
+ if (!url) return null;
423
+ let parsed;
424
+ try {
425
+ parsed = new URL(url);
426
+ } catch {
427
+ return null;
428
+ }
429
+ for (const key of FILENAME_QUERY_KEYS) {
430
+ const disposition = parsed.searchParams.get(key);
431
+ if (disposition) {
432
+ const name = parseContentDisposition(disposition);
433
+ if (name) return name;
434
+ }
435
+ }
436
+ return null;
437
+ }
438
+ function parseContentDisposition(value) {
439
+ const extMatch = /filename\*\s*=\s*([A-Za-z0-9_-]+)'(?:[A-Za-z0-9_-]*)'([^;]+)/i.exec(value);
440
+ if (extMatch) {
441
+ const charset = extMatch[1];
442
+ const encoded = extMatch[2].trim();
443
+ try {
444
+ const decoded = decodeURIComponent(encoded);
445
+ if (charset.toLowerCase() === "utf-8") return safeName(decoded);
446
+ return safeName(decoded);
447
+ } catch {
448
+ }
449
+ }
450
+ const basicMatch = /filename\s*=\s*(?:"([^"]+)"|([^;]+))/i.exec(value);
451
+ if (basicMatch) {
452
+ const raw = (basicMatch[1] ?? basicMatch[2] ?? "").trim();
453
+ if (raw) return safeName(raw);
454
+ }
455
+ return null;
456
+ }
457
+ function safeName(name) {
458
+ const leaf = name.split(/[/\\]/).pop()?.trim();
459
+ if (!leaf) return null;
460
+ if (/[\x00-\x1f]/.test(leaf)) return null;
461
+ return leaf;
462
+ }
463
+
464
+ // src/files/blocks/image/adapters/from-image-output-data.ts
465
+ import { isSignedUrl } from "@ai-matrx/data/files";
466
+ function metaString(metadata, key) {
467
+ if (!metadata) return null;
468
+ const value = metadata[key];
469
+ return typeof value === "string" ? value : null;
470
+ }
471
+ function metaNumber(metadata, key) {
472
+ if (!metadata) return null;
473
+ const value = metadata[key];
474
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
475
+ }
476
+ function metaVisibility(metadata) {
477
+ const value = metaString(metadata, "visibility");
478
+ if (value === "public" || value === "personal" || value === "internal" || value === "link")
479
+ return value;
480
+ if (value === "shared" || value === "private") return "personal";
481
+ return "personal";
482
+ }
483
+ function fromImageOutputData(data, carriedMetadata) {
484
+ const metadata = carriedMetadata ?? null;
485
+ const cdnUrl = data.cdn_url ?? null;
486
+ const downloadUrl = data.download_url ?? null;
487
+ const fallbackUrl = data.url;
488
+ const fallbackLooksSigned = isSignedUrl(fallbackUrl);
489
+ const finalCdnUrl = cdnUrl ?? (fallbackLooksSigned ? null : fallbackUrl ?? null);
490
+ const explicitFileId = data.file_id ?? null;
491
+ const inferredFileId = explicitFileId ?? extractFileIdFromUrl(finalCdnUrl) ?? extractFileIdFromUrl(fallbackUrl) ?? null;
492
+ const common = {
493
+ kind: "image",
494
+ base64: null,
495
+ mimeType: data.mime_type ?? null,
496
+ // Filename priority:
497
+ // 1. Explicit `file_name` in the event metadata (Python's intent).
498
+ // 2. The filename baked into the signed URL's
499
+ // `response-content-disposition` query param — Python sets this
500
+ // to the AI-chosen name on every signed-URL mint, and it's the
501
+ // ground truth for "what should the user see on download".
502
+ // 3. null — fall back to a generic `image.<ext>` at the callsite.
503
+ fileName: metaString(metadata, "file_name") ?? parseFilenameFromUrl(downloadUrl) ?? parseFilenameFromUrl(finalCdnUrl) ?? null,
504
+ width: metaNumber(metadata, "width"),
505
+ height: metaNumber(metadata, "height"),
506
+ // Phase 0 wire rename: prefer `size_bytes`, accept legacy `file_size`
507
+ // from in-flight services that haven't redeployed yet.
508
+ sizeBytes: metaNumber(metadata, "size_bytes") ?? metaNumber(metadata, "file_size"),
509
+ visionClass: metaString(metadata, "vision_class"),
510
+ status: "complete",
511
+ progress: null,
512
+ errorMessage: null,
513
+ metadata: metadata ?? null
514
+ };
515
+ if (inferredFileId) {
516
+ const matrx = {
517
+ ...common,
518
+ origin: "matrx",
519
+ fileId: inferredFileId,
520
+ visibility: metaVisibility(metadata),
521
+ cdnUrl: finalCdnUrl,
522
+ downloadUrl,
523
+ // Phase 1b: thumbnails removed from MatrxImageBlock — the canonical
524
+ // source is `Asset.variants["thumbnail_url"].url` via GET /assets/{id}.
525
+ // For top-level listings, `CloudFile.thumbnailUrl` (lifted from
526
+ // `FileRecord.thumbnail_url`) is the FE-side cache of that resolved URL.
527
+ parentFileId: metaString(metadata, "parent_file_id"),
528
+ derivationKind: metaString(metadata, "derivation_kind")
529
+ };
530
+ return matrx;
531
+ }
532
+ const externalUrl = fallbackUrl ?? finalCdnUrl ?? "";
533
+ if (!externalUrl) {
534
+ const broken = {
535
+ ...common,
536
+ origin: "external",
537
+ externalUrl: "",
538
+ sourceLabel: metaString(metadata, "source_label")
539
+ };
540
+ return broken;
541
+ }
542
+ const external = {
543
+ ...common,
544
+ origin: "external",
545
+ externalUrl,
546
+ sourceLabel: metaString(metadata, "source_label")
547
+ };
548
+ return external;
549
+ }
550
+
551
+ // src/files/blocks/image/adapters/from-cx-media-part.ts
552
+ function fromCxMediaPart(part) {
553
+ const metadata = part.metadata ?? null;
554
+ const cdnFromMeta = typeof metadata?.cdn_url === "string" ? metadata.cdn_url : null;
555
+ const downloadFromMeta = typeof metadata?.download_url === "string" ? metadata.download_url : null;
556
+ const fileId = (typeof part.file_id === "string" ? part.file_id : null) ?? (typeof metadata?.file_id === "string" ? metadata.file_id : null);
557
+ const fallbackUrl = part.url ?? cdnFromMeta ?? "";
558
+ const block = fromImageOutputData(
559
+ {
560
+ type: "image_output",
561
+ url: fallbackUrl,
562
+ mime_type: part.mime_type ?? "image/*",
563
+ file_id: fileId,
564
+ cdn_url: cdnFromMeta,
565
+ download_url: downloadFromMeta
566
+ },
567
+ {
568
+ // Pass through all metadata so the extension fields (visibility,
569
+ // thumbnail, dimensions, etc.) get lifted by the rich adapter.
570
+ // Stored parts from before the storage-location eradication may
571
+ ...metadata ?? {}
572
+ }
573
+ );
574
+ const withRole = {
575
+ ...block,
576
+ referenceRole: part.role ?? null,
577
+ referenceName: part.name ?? null
578
+ };
579
+ if (typeof metadata?.base64_data === "string") {
580
+ return { ...withRole, base64: metadata.base64_data };
581
+ }
582
+ return withRole;
583
+ }
584
+
585
+ // src/files/blocks/image/adapters/from-partial-image-data.ts
586
+ function fromPartialImageData(data, carriedMetadata) {
587
+ return {
588
+ kind: "image",
589
+ origin: "external",
590
+ base64: data.b64_json,
591
+ mimeType: data.mime_type ?? "image/png",
592
+ fileName: null,
593
+ width: null,
594
+ height: null,
595
+ sizeBytes: null,
596
+ visionClass: null,
597
+ status: "streaming",
598
+ progress: data.progress ?? null,
599
+ errorMessage: null,
600
+ metadata: carriedMetadata ?? null,
601
+ externalUrl: "",
602
+ sourceLabel: "Generating"
603
+ };
604
+ }
605
+
606
+ // src/files/blocks/image/adapters/from-render-block.ts
607
+ function fromRenderBlock(block) {
608
+ const data = block.data ?? {};
609
+ const src = typeof data.src === "string" ? data.src : "";
610
+ const mimeType = typeof data.mime_type === "string" ? data.mime_type : "image/*";
611
+ return fromImageOutputData(
612
+ {
613
+ type: "image_output",
614
+ url: src,
615
+ mime_type: mimeType
616
+ },
617
+ block.metadata ?? null
618
+ );
619
+ }
620
+
621
+ // src/files/blocks/image/adapters/to-cx-media-part.ts
622
+ function toCxMediaPart(block) {
623
+ const visibleUrl = block.origin === "matrx" ? block.cdnUrl ?? void 0 : block.externalUrl || void 0;
624
+ const callerMetadata = block.metadata ?? {};
625
+ const packed = {
626
+ ...callerMetadata,
627
+ kind: block.kind,
628
+ origin: block.origin,
629
+ mime_type: block.mimeType,
630
+ file_name: block.fileName,
631
+ width: block.width,
632
+ height: block.height,
633
+ size_bytes: block.sizeBytes,
634
+ vision_class: block.visionClass,
635
+ status: block.status,
636
+ progress: block.progress,
637
+ error_message: block.errorMessage
638
+ };
639
+ delete packed.signed_url;
640
+ delete packed.signed_url_expires_at;
641
+ if (block.origin === "matrx") {
642
+ packed.cdn_url = block.cdnUrl;
643
+ packed.download_url = block.downloadUrl;
644
+ packed.file_id = block.fileId;
645
+ packed.visibility = block.visibility;
646
+ packed.parent_file_id = block.parentFileId;
647
+ packed.derivation_kind = block.derivationKind;
648
+ } else {
649
+ packed.external_url = block.externalUrl;
650
+ packed.source_label = block.sourceLabel;
651
+ }
652
+ if (block.base64) {
653
+ packed.base64_data = block.base64;
654
+ }
655
+ const common = {
656
+ type: "media",
657
+ kind: "image",
658
+ ...block.mimeType !== null ? { mime_type: block.mimeType } : {},
659
+ ...block.sizeBytes !== null ? { size_bytes: block.sizeBytes } : {},
660
+ ...block.width !== null ? { width: block.width } : {},
661
+ ...block.height !== null ? { height: block.height } : {},
662
+ metadata: packed
663
+ };
664
+ if (block.origin === "matrx") {
665
+ return {
666
+ ...common,
667
+ origin: "matrx",
668
+ file_id: block.fileId,
669
+ ...visibleUrl ? { url: visibleUrl } : {}
670
+ };
671
+ }
672
+ const externalUrl = visibleUrl ?? (block.base64 ? `data:${block.mimeType ?? "image/*"};base64,${block.base64}` : null);
673
+ if (!externalUrl) {
674
+ throw new TypeError(
675
+ "Cannot project external image into chat.message.content: missing URL and base64 locator"
676
+ );
677
+ }
678
+ return {
679
+ ...common,
680
+ origin: "external",
681
+ url: externalUrl
682
+ };
683
+ }
684
+
685
+ // src/files/blocks/image/helpers/derive-viewer-url.ts
686
+ function deriveViewerUrl(block) {
687
+ if (block.origin === "external") return null;
688
+ return `/files/f/${block.fileId}`;
689
+ }
690
+
691
+ // src/react/cn.ts
692
+ import { twMerge } from "tailwind-merge";
693
+ function cn(...inputs) {
694
+ return twMerge(inputs.filter(Boolean).join(" "));
695
+ }
696
+
697
+ // src/files/media/video-date.ts
698
+ var COMPACT_VIDEO_DATE = new Intl.DateTimeFormat("en-US", {
699
+ month: "2-digit",
700
+ day: "2-digit",
701
+ year: "2-digit",
702
+ timeZone: "UTC"
703
+ });
704
+ var FULL_VIDEO_DATE = new Intl.DateTimeFormat("en-US", {
705
+ month: "long",
706
+ day: "numeric",
707
+ year: "numeric",
708
+ timeZone: "UTC"
709
+ });
710
+ function parseVideoDate(value) {
711
+ if (!value) return null;
712
+ const date = new Date(value);
713
+ return Number.isNaN(date.getTime()) ? null : date;
714
+ }
715
+ function formatVideoPublishDate(value) {
716
+ const date = parseVideoDate(value);
717
+ return date ? COMPACT_VIDEO_DATE.format(date) : "No Date";
718
+ }
719
+ function formatVideoPublishDateTitle(value) {
720
+ const date = parseVideoDate(value);
721
+ return date ? `Published ${FULL_VIDEO_DATE.format(date)}` : "Publish date unavailable";
722
+ }
723
+ var VIDEO_DATE_KEYS = [
724
+ "published_at",
725
+ "publishedAt",
726
+ "publish_date",
727
+ "publishDate",
728
+ "uploadDate",
729
+ "datePublished"
730
+ ];
731
+ var VIDEO_METADATA_CONTAINERS = [
732
+ "video_metadata",
733
+ "schema_org",
734
+ "metadata"
735
+ ];
736
+ function videoPublishDateFromMetadata(value) {
737
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
738
+ const record = value;
739
+ for (const key of VIDEO_DATE_KEYS) {
740
+ const candidate = record[key];
741
+ if (typeof candidate === "string" && parseVideoDate(candidate)) {
742
+ return candidate;
743
+ }
744
+ }
745
+ for (const key of VIDEO_METADATA_CONTAINERS) {
746
+ const nested = videoPublishDateFromMetadata(record[key]);
747
+ if (nested) return nested;
748
+ }
749
+ return null;
750
+ }
751
+
752
+ // src/files/blocks/video/VideoPublishDate.tsx
753
+ import { jsx } from "react/jsx-runtime";
754
+ function VideoPublishDate({
755
+ publishedAt,
756
+ className
757
+ }) {
758
+ const dateTime = publishedAt && !Number.isNaN(new Date(publishedAt).getTime()) ? publishedAt : void 0;
759
+ const content = formatVideoPublishDate(publishedAt);
760
+ const sharedProps = {
761
+ className: cn(
762
+ "shrink-0 whitespace-nowrap text-[10px] tabular-nums text-muted-foreground",
763
+ className
764
+ ),
765
+ title: formatVideoPublishDateTitle(publishedAt),
766
+ "aria-label": formatVideoPublishDateTitle(publishedAt)
767
+ };
768
+ return dateTime ? /* @__PURE__ */ jsx("time", { ...sharedProps, dateTime, children: content }) : /* @__PURE__ */ jsx("span", { ...sharedProps, children: content });
769
+ }
770
+
771
+ // src/files/handler/errors.ts
772
+ var FileHandlerError = class extends Error {
773
+ code;
774
+ fileId;
775
+ cause;
776
+ constructor(code, message, opts) {
777
+ super(message);
778
+ this.name = "FileHandlerError";
779
+ this.code = code;
780
+ this.fileId = opts?.fileId;
781
+ this.cause = opts?.cause;
782
+ }
783
+ };
784
+ var FileAccessDeniedError = class extends FileHandlerError {
785
+ constructor(message = "This file isn't available to you.", opts) {
786
+ super("access_denied", message, opts);
787
+ this.name = "FileAccessDeniedError";
788
+ }
789
+ };
790
+ var FileNotFoundError = class extends FileHandlerError {
791
+ // A 404 from the file server. It proves the server could not retrieve the
792
+ // file — NOT that the file was deleted (that is `FileDeletedError`, which
793
+ // has a `deleted_at` to point at) and not that it never existed.
794
+ constructor(message = "We couldn't retrieve this file.", opts) {
795
+ super("not_found", message, opts);
796
+ this.name = "FileNotFoundError";
797
+ }
798
+ };
799
+ var FileDeletedError = class extends FileHandlerError {
800
+ constructor(message = "File is in trash", opts) {
801
+ super("deleted", message, opts);
802
+ this.name = "FileDeletedError";
803
+ }
804
+ };
805
+ var ShareLinkInvalidError = class extends FileHandlerError {
806
+ constructor(message = "Share link is invalid, expired, or revoked") {
807
+ super("share_link_invalid", message);
808
+ this.name = "ShareLinkInvalidError";
809
+ }
810
+ };
811
+ var ExternalFetchError = class extends FileHandlerError {
812
+ constructor(message = "Failed to fetch external URL", opts) {
813
+ super("external_fetch_failed", message, opts);
814
+ this.name = "ExternalFetchError";
815
+ }
816
+ };
817
+ var FileUploadError = class extends FileHandlerError {
818
+ constructor(message, opts) {
819
+ super("upload_failed", message, opts);
820
+ this.name = "FileUploadError";
821
+ }
822
+ };
823
+ var UploadCancelledError = class extends FileHandlerError {
824
+ constructor(message = "Upload cancelled \u2014 no workspace was selected.") {
825
+ super("upload_cancelled", message);
826
+ this.name = "UploadCancelledError";
827
+ }
828
+ };
829
+ function isUploadCancelledError(error) {
830
+ return error instanceof Error && error.name === "UploadCancelledError";
831
+ }
832
+
833
+ // src/files/utils/folder-conventions.ts
834
+ var CloudFolders = {
835
+ // ── Visible / user-managed ────────────────────────────────────────────
836
+ /** Top-level "Images" drawer. */
837
+ IMAGES: "Images",
838
+ /** Images pasted/attached inside a chat. */
839
+ IMAGES_CHAT: "Images/Chat",
840
+ /** Screenshots dragged or pasted into the app. */
841
+ IMAGES_SCREENSHOTS: "Images/Screenshots",
842
+ /** User avatars / profile images. */
843
+ IMAGES_AVATARS: "Images/Avatars",
844
+ /** Images generated by AI tools. */
845
+ IMAGES_GENERATED: "Images/Generated",
846
+ /** Screenshots and images flattened with user-authored markup. */
847
+ IMAGES_ANNOTATED: "Images/Annotated",
848
+ /** Top-level "Audio" drawer. */
849
+ AUDIO: "Audio",
850
+ /** User-initiated audio recordings (voice-to-text, voice pad, etc.). */
851
+ AUDIO_RECORDINGS: "Audio/Recordings",
852
+ /** Podcast asset uploads. */
853
+ AUDIO_PODCASTS: "Audio/Podcasts",
854
+ /** Top-level "Captures" drawer — everything the in-browser media-capture
855
+ * system saves (features/media-capture). User-produced, so visible. */
856
+ CAPTURES: "Captures",
857
+ /** Photos taken with the in-app camera (Capture Studio / device fallback). */
858
+ CAPTURES_PHOTOS: "Captures/Photos",
859
+ /** Videos recorded with the in-app camera (media-capture Phase 7). */
860
+ CAPTURES_VIDEOS: "Captures/Videos",
861
+ /** Audio recorded through the Capture Studio (media-capture Phase 7). */
862
+ CAPTURES_AUDIO: "Captures/Audio",
863
+ /** Product-capture staging (features/product-capture) — per-item folders of
864
+ * photos/videos/voice notes shot ahead of listing creation. Org-namespaced
865
+ * via `folderForProductCaptureItem` (same server one-path-one-org rule as
866
+ * Captures). User-produced, so visible. */
867
+ PRODUCT_CAPTURES: "Product Captures",
868
+ /** Commerce intake (W4 capture app): per-asset media filed under
869
+ * `Commerce Intake/<orgId>/<batchId>/<asset-leaf>` via
870
+ * `folderForIntakeAsset` (same server one-path-one-org rule as Captures).
871
+ * User-produced, so visible. */
872
+ COMMERCE_INTAKE: "Commerce Intake",
873
+ /**
874
+ * Static app assets (sounds, hero images, model-card thumbnails, demo
875
+ * data) owned by an admin service account and rendered globally.
876
+ * Always public — CDN URL is the goal. Source of truth for the
877
+ * static-asset migration (see scripts/migrate-public-assets-to-cdn.ts).
878
+ */
879
+ APP_ASSETS: "App Assets",
880
+ /** Top-level documents (PDF, DOCX, etc.). */
881
+ DOCUMENTS: "Documents",
882
+ /** PDF-specific drawer. */
883
+ DOCUMENTS_PDFS: "Documents/PDFs",
884
+ /** Top-level code files (scripts, snippets). */
885
+ CODE: "Code",
886
+ /** Code saved via the code-editor window. */
887
+ CODE_EDITOR: "Code/Editor",
888
+ /** Generic AI-generated outputs. */
889
+ GENERATED: "Generated",
890
+ /** Files attached to agent apps, keyed by app id at runtime. */
891
+ AGENT_APPS: "Agent Apps",
892
+ /** Files attached to tasks, keyed by task id at runtime. */
893
+ TASK_ATTACHMENTS: "Task Attachments",
894
+ /** Files attached to War Room tiles, keyed by tile id at runtime. */
895
+ WAR_ROOM: "War Room",
896
+ /** Files dropped into a chat composer (pre-send or history). */
897
+ CHAT_ATTACHMENTS: "Chat Attachments",
898
+ /** Slack-imported files. */
899
+ SLACK_IMPORTS: "Slack Imports",
900
+ /** Web-scrape captures (HTML snapshots, exported markdown). */
901
+ SCRAPED_CONTENT: "Scraped Content",
902
+ // ── Shared assets / org-scoped public surfaces ─────────────────────────
903
+ /**
904
+ * Org-scoped shared assets — logos, hero images, anything an
905
+ * organization renders on a public surface. Keyed by org id at
906
+ * runtime (e.g. `Shared Assets/orgs/<org-id>`). Always public —
907
+ * the CDN URL is the rendering target.
908
+ */
909
+ SHARED_ASSETS_ORGS: "Shared Assets/orgs",
910
+ /** Public screenshot attachments submitted through the feedback window. */
911
+ FEEDBACK_IMAGES: "Shared Assets/feedback-images",
912
+ /**
913
+ * OG / social-preview covers for HTML pages the user publishes.
914
+ * Lives under Shared Assets so all org/public-surface previews
915
+ * share one CDN-backed bucket.
916
+ */
917
+ HTML_PAGES_OG: "Shared Assets/html-pages",
918
+ // ── App-asset subfolders ───────────────────────────────────────────────
919
+ /**
920
+ * Favicons rendered by prompt-app / agent-app standalone pages.
921
+ * Always public — favicon requests are anonymous.
922
+ */
923
+ PROMPT_APPS_FAVICONS: "App Assets/prompt-apps/favicons",
924
+ /**
925
+ * Per-block assets attached to agent blocks (icons, illustrations,
926
+ * inline media). Keyed by block id at runtime via
927
+ * `folderForAgentBlock(blockId)`.
928
+ */
929
+ AGENT_BLOCKS: "Agent Apps/blocks",
930
+ // ── Canvas / image-editor ──────────────────────────────────────────────
931
+ /**
932
+ * Cover images rendered on canvas social/share surfaces. Public so
933
+ * the share-card OG image is a stable CDN URL.
934
+ */
935
+ CANVAS_COVERS: "Images/Canvas/Covers",
936
+ /**
937
+ * Source images the user is editing in the image studio. Private —
938
+ * these are the user's working files, NOT public render targets.
939
+ * Outputs that the user explicitly publishes land elsewhere.
940
+ */
941
+ IMAGES_EDITED_SOURCES: "Images/Edited/Sources",
942
+ // ── Hidden / ephemeral ─────────────────────────────────────────────────
943
+ // All hidden roots live under ".matrx-tmp/" so the tree has a single
944
+ // ignorable folder rather than many. Callers that use these MUST delete
945
+ // the file when done.
946
+ /** Root for all ephemeral cloud-files work. Hidden in the tree. */
947
+ TMP: ".matrx-tmp",
948
+ /** Short-lived audio files staged for URL-based transcription. */
949
+ TMP_TRANSCRIPTS: ".matrx-tmp/transcripts",
950
+ /** Upload-in-progress staging (future use). */
951
+ TMP_UPLOADS: ".matrx-tmp/uploads",
952
+ // ── System / infrastructure ────────────────────────────────────────────
953
+ // Owned and populated by the Python backend (not the user). These rows
954
+ // exist in `cld_files` / `cld_folders` so they have asset IDs, but they
955
+ // must NEVER appear in the user-facing tree.
956
+ //
957
+ // The Phase 1b/1c "SOCIAL_BASELINE" backfill (2026-05-16) created
958
+ // `cld_files` rows under `system-files/variants/<file_id>/<og|thumb|tiny|page1_url>.jpg`
959
+ // — three to four rows per source file. The cascade was cleaned up
960
+ // server-side on 2026-05-16 (migrations 012/013); the tree RPCs now
961
+ // exclude `parent_file_id IS NOT NULL` and `file_path LIKE
962
+ // 'system-files/%'`. The FE keeps `isSystemPath` as a defensive guard
963
+ // at every tree boundary (loadUserFileTree, loadFolderContents,
964
+ // realtime middleware). See common-docs systems/files/file-service/WIRE_CONTRACT.md.
965
+ /** Root for backend-owned infrastructure files (variants, posters, etc.). */
966
+ SYSTEM_FILES: "system-files",
967
+ /** SOCIAL_BASELINE variant storage: og.jpg / thumb.jpg / tiny.jpg / page1_url.jpg. */
968
+ SYSTEM_VARIANTS: "system-files/variants",
969
+ /**
970
+ * Backend "generations" registry root — every AI generation (image,
971
+ * video, audio/TTS) the server materializes lands under
972
+ * `generations/<images|video|audio>/...`. Backend-owned; mirrors the
973
+ * server's `is_system_path()` (which treats `system-files` AND
974
+ * `generations` as system). Never shown in the user tree.
975
+ */
976
+ GENERATIONS: "generations",
977
+ /**
978
+ * Recorded audio captured by the Transcripts / voice features. As of
979
+ * 2026-06-14 the backend relocates every `origin: "transcripts"` upload
980
+ * here, UNDER the hidden `system-files/` root — one
981
+ * `recording_<iso>_<rand>.webm` per capture (80+ for an active user).
982
+ * Because it's under `system-files/`, `isSystemPath` already hides it
983
+ * from the tree, folder views, AND Recents — no separate predicate
984
+ * needed. Managed exclusively via the Transcripts UI (by `cld_files.id`).
985
+ * See docs/files/transcript-recordings-system-relocation.md.
986
+ */
987
+ TRANSCRIPT_RECORDINGS: "system-files/transcripts/Recordings",
988
+ /**
989
+ * LEGACY user-namespace location for transcript recordings, before the
990
+ * 2026-06-14 backend relocation to `system-files/transcripts/...`. Kept
991
+ * ONLY as a defensive Recents guard: the backend relocates every
992
+ * `origin: "transcripts"` upload, but the presigned/TUS (chunked + large
993
+ * file) relocation path was genuinely missing and is committed but NOT yet
994
+ * in prod as of 2026-06-15.
995
+ *
996
+ * DO NOT DROP THIS GUARD until that backend fix has DEPLOYED to prod —
997
+ * dropping it earlier would let a chunked/large recording slip through
998
+ * unhidden. Sequence: backend deploys → then remove this constant from
999
+ * `isSystemManagedContentPath` (and delete it). See the FE-response /
1000
+ * "open items" in docs/files/transcript-recordings-system-relocation.md.
1001
+ */
1002
+ TRANSCRIPT_RECORDINGS_LEGACY: "Transcripts/Recordings",
1003
+ // ── System-managed user content (browsable, but NOT in Recents) ─────────
1004
+ // Lives under USER roots (so it stays visible in the Files browser), but
1005
+ // is machine-named, high-volume, and background-produced — so it must
1006
+ // never pollute the Recents stream (your "entire reality" on open).
1007
+ /**
1008
+ * Per-tool generated image variants — `tool-images/<id>/v/<name>.jpg`,
1009
+ * multiple sizes per source. Machine-produced output, excluded from
1010
+ * Recents (see `isSystemManagedContentPath`).
1011
+ */
1012
+ TOOL_IMAGES: "tool-images",
1013
+ /**
1014
+ * FastFire voice-drill recordings — full-session + per-card response clips.
1015
+ * Live under the hidden `system-files/fastfire/...` root (same class as
1016
+ * transcript recordings). Managed via Education / FastFire UI only.
1017
+ */
1018
+ SYSTEM_FASTFIRE: "system-files/fastfire",
1019
+ SYSTEM_FASTFIRE_SESSIONS: "system-files/fastfire/sessions",
1020
+ SYSTEM_FASTFIRE_RESPONSES: "system-files/fastfire/responses",
1021
+ /**
1022
+ * Spoken Practice (oral exam / interview / debate) recordings — full-session
1023
+ * clips + per-prompt spoken-answer clips. Same hidden `system-files/` class as
1024
+ * FastFire (so `isSystemPath` keeps them out of the Files browser + Recents).
1025
+ */
1026
+ SYSTEM_SPOKEN_PRACTICE: "system-files/spoken-practice",
1027
+ SYSTEM_SPOKEN_PRACTICE_SESSIONS: "system-files/spoken-practice/sessions",
1028
+ SYSTEM_SPOKEN_PRACTICE_RESPONSES: "system-files/spoken-practice/responses",
1029
+ /**
1030
+ * Handwritten / image-answer grading — photos of worked problems a learner
1031
+ * submits to the vision grader (assessment written-item photo answers +
1032
+ * the standalone "Grade my handwritten work" surface). Same hidden
1033
+ * `system-files/` class (kept out of the Files browser + Recents).
1034
+ */
1035
+ SYSTEM_IMAGE_GRADE: "system-files/image-grade",
1036
+ SYSTEM_IMAGE_GRADE_RESPONSES: "system-files/image-grade/responses",
1037
+ /**
1038
+ * LEGACY user-namespace FastFire paths (pre-2026-07 relocation). Kept as a
1039
+ * defensive tree + Recents guard until every row is backfilled under
1040
+ * `system-files/fastfire/`.
1041
+ */
1042
+ FASTFIRE_SESSIONS: "FastFire/sessions",
1043
+ FASTFIRE_RESPONSES: "FastFire/responses"
1044
+ };
1045
+ function folderForTask(taskId) {
1046
+ return `${CloudFolders.TASK_ATTACHMENTS}/${taskId}`;
1047
+ }
1048
+ function folderForWarRoomThread(threadId) {
1049
+ return `${CloudFolders.WAR_ROOM}/${threadId}`;
1050
+ }
1051
+ function folderForConversation(conversationId) {
1052
+ return `${CloudFolders.CHAT_ATTACHMENTS}/${conversationId}`;
1053
+ }
1054
+ function folderForAgentApp(appId) {
1055
+ return `${CloudFolders.AGENT_APPS}/${appId}`;
1056
+ }
1057
+ function folderForPodcast(podcastId) {
1058
+ return `${CloudFolders.AUDIO_PODCASTS}/${podcastId}`;
1059
+ }
1060
+ function folderForOrg(orgId) {
1061
+ return `${CloudFolders.SHARED_ASSETS_ORGS}/${orgId}`;
1062
+ }
1063
+ function folderForAgentBlock(blockId) {
1064
+ return `${CloudFolders.AGENT_BLOCKS}/${blockId}`;
1065
+ }
1066
+ function folderForCaptures(kind, orgId) {
1067
+ const leaf = kind === "photo" ? "Photos" : kind === "video" ? "Videos" : "Audio";
1068
+ const trimmed = typeof orgId === "string" ? orgId.trim() : "";
1069
+ if (trimmed) {
1070
+ return `${CloudFolders.CAPTURES}/${trimmed}/${leaf}`;
1071
+ }
1072
+ return kind === "photo" ? CloudFolders.CAPTURES_PHOTOS : kind === "video" ? CloudFolders.CAPTURES_VIDEOS : CloudFolders.CAPTURES_AUDIO;
1073
+ }
1074
+ function folderForProductCaptureItem(orgId, leaf) {
1075
+ const safe = leaf.replace(/[^A-Za-z0-9._@-]+/g, "-").replace(/^-+|-+$/g, "");
1076
+ return `${CloudFolders.PRODUCT_CAPTURES}/${orgId.trim()}/${safe}`;
1077
+ }
1078
+ function folderForIntakeAsset(orgId, batchId, leaf) {
1079
+ const safe = leaf.replace(/[^A-Za-z0-9._@-]+/g, "-").replace(/^-+|-+$/g, "");
1080
+ return `${CloudFolders.COMMERCE_INTAKE}/${orgId.trim()}/${batchId.trim()}/${safe || batchId.trim()}`;
1081
+ }
1082
+ function isHiddenFolder(folderPath) {
1083
+ return folderPath === CloudFolders.TMP || folderPath.startsWith(".matrx-tmp/");
1084
+ }
1085
+ function isSystemPath(path) {
1086
+ if (!path) return false;
1087
+ return path === CloudFolders.SYSTEM_FILES || path.startsWith(`${CloudFolders.SYSTEM_FILES}/`) || path === CloudFolders.GENERATIONS || path.startsWith(`${CloudFolders.GENERATIONS}/`) || path === CloudFolders.TMP || path.startsWith(`${CloudFolders.TMP}/`);
1088
+ }
1089
+ function isConventionalFolder(folderPath) {
1090
+ const all = Object.values(CloudFolders);
1091
+ return all.includes(folderPath);
1092
+ }
1093
+ var CloudFolderDescriptions = {
1094
+ [CloudFolders.IMAGES]: "All your images, organized.",
1095
+ [CloudFolders.IMAGES_CHAT]: "Images shared in chat.",
1096
+ [CloudFolders.IMAGES_SCREENSHOTS]: "Screenshots you pasted or dragged in.",
1097
+ [CloudFolders.IMAGES_AVATARS]: "Profile pictures and avatars.",
1098
+ [CloudFolders.IMAGES_GENERATED]: "AI-generated images.",
1099
+ [CloudFolders.AUDIO]: "Audio files.",
1100
+ [CloudFolders.AUDIO_RECORDINGS]: "Voice recordings.",
1101
+ [CloudFolders.AUDIO_PODCASTS]: "Podcast assets.",
1102
+ [CloudFolders.CAPTURES]: "Photos, videos, and audio captured in the app.",
1103
+ [CloudFolders.CAPTURES_PHOTOS]: "Photos taken with the in-app camera.",
1104
+ [CloudFolders.CAPTURES_VIDEOS]: "Videos recorded with the in-app camera.",
1105
+ [CloudFolders.CAPTURES_AUDIO]: "Audio recorded in the Capture Studio.",
1106
+ [CloudFolders.APP_ASSETS]: "Static app assets shared across the platform.",
1107
+ [CloudFolders.DOCUMENTS]: "Documents and reports.",
1108
+ [CloudFolders.DOCUMENTS_PDFS]: "PDFs.",
1109
+ [CloudFolders.CODE]: "Code files.",
1110
+ [CloudFolders.GENERATED]: "Files produced by AI tools.",
1111
+ [CloudFolders.AGENT_APPS]: "Assets attached to your agent apps.",
1112
+ [CloudFolders.TASK_ATTACHMENTS]: "Files attached to tasks.",
1113
+ [CloudFolders.CHAT_ATTACHMENTS]: "Files shared in conversations.",
1114
+ [CloudFolders.SLACK_IMPORTS]: "Files imported from Slack.",
1115
+ [CloudFolders.SCRAPED_CONTENT]: "Captures from the web scraper.",
1116
+ [CloudFolders.SHARED_ASSETS_ORGS]: "Organization-scoped shared assets.",
1117
+ [CloudFolders.HTML_PAGES_OG]: "OG covers for HTML pages.",
1118
+ [CloudFolders.PROMPT_APPS_FAVICONS]: "Favicons for prompt-app pages.",
1119
+ [CloudFolders.AGENT_BLOCKS]: "Per-block agent app assets.",
1120
+ [CloudFolders.CANVAS_COVERS]: "Canvas social cover images.",
1121
+ [CloudFolders.IMAGES_EDITED_SOURCES]: "Source images for the image studio.",
1122
+ [CloudFolders.TMP]: "Internal temporary workspace (hidden)."
1123
+ };
1124
+ var DEFAULT_VISIBILITY_RULES = [
1125
+ // Most-specific rules first — longest-prefix wins.
1126
+ { prefix: "Shared Assets/feedback-images", visibility: "public" },
1127
+ { prefix: CloudFolders.SHARED_ASSETS_ORGS, visibility: "public" },
1128
+ { prefix: CloudFolders.HTML_PAGES_OG, visibility: "public" },
1129
+ { prefix: CloudFolders.PROMPT_APPS_FAVICONS, visibility: "public" },
1130
+ { prefix: CloudFolders.AGENT_BLOCKS, visibility: "public" },
1131
+ { prefix: CloudFolders.CANVAS_COVERS, visibility: "public" },
1132
+ { prefix: CloudFolders.APP_ASSETS, visibility: "public" },
1133
+ { prefix: CloudFolders.IMAGES_AVATARS, visibility: "public" },
1134
+ { prefix: CloudFolders.AUDIO_PODCASTS, visibility: "public" },
1135
+ { prefix: CloudFolders.AGENT_APPS, visibility: "public" },
1136
+ { prefix: "Shared Assets", visibility: "public" },
1137
+ // IMAGES_EDITED_SOURCES is private — these are the user's working
1138
+ // source files for the image studio, not public render targets.
1139
+ // Captures are the user's own camera/mic output — never public render
1140
+ // targets. Explicit rule (not just the fallback) so the intent is recorded.
1141
+ { prefix: CloudFolders.CAPTURES, visibility: "personal" },
1142
+ { prefix: CloudFolders.IMAGES_EDITED_SOURCES, visibility: "personal" },
1143
+ { prefix: CloudFolders.IMAGES_GENERATED, visibility: "personal" },
1144
+ { prefix: CloudFolders.CHAT_ATTACHMENTS, visibility: "personal" },
1145
+ { prefix: CloudFolders.TASK_ATTACHMENTS, visibility: "personal" },
1146
+ { prefix: CloudFolders.WAR_ROOM, visibility: "personal" },
1147
+ { prefix: CloudFolders.SLACK_IMPORTS, visibility: "personal" },
1148
+ { prefix: CloudFolders.SCRAPED_CONTENT, visibility: "personal" },
1149
+ { prefix: "Private Assets", visibility: "personal" },
1150
+ { prefix: CloudFolders.TMP, visibility: "personal" }
1151
+ ];
1152
+ function resolveDefaultVisibility(folderPath) {
1153
+ const normalized = folderPath.replace(/^\/+|\/+$/g, "");
1154
+ let best = null;
1155
+ for (const rule of DEFAULT_VISIBILITY_RULES) {
1156
+ if (normalized === rule.prefix || normalized.startsWith(`${rule.prefix}/`)) {
1157
+ if (!best || rule.prefix.length > best.prefix.length) {
1158
+ best = rule;
1159
+ }
1160
+ }
1161
+ }
1162
+ return best?.visibility ?? "personal";
1163
+ }
1164
+
1165
+ // src/files/handler/utils/upload-folder-path.ts
1166
+ var TOP_LEVEL_FOLDERS = {
1167
+ userContent: "My Files",
1168
+ images: CloudFolders.IMAGES,
1169
+ Images: CloudFolders.IMAGES,
1170
+ audio: CloudFolders.AUDIO,
1171
+ Audio: CloudFolders.AUDIO,
1172
+ "audio-recordings": CloudFolders.AUDIO_RECORDINGS,
1173
+ documents: CloudFolders.DOCUMENTS,
1174
+ Documents: CloudFolders.DOCUMENTS,
1175
+ code: CloudFolders.CODE,
1176
+ Code: CloudFolders.CODE,
1177
+ "any-file": "Uploads",
1178
+ attachments: CloudFolders.CHAT_ATTACHMENTS
1179
+ };
1180
+ function composeUploadFolderPath(root = "Uploads", path) {
1181
+ const top = (TOP_LEVEL_FOLDERS[root] ?? root).replace(/^\/+|\/+$/g, "");
1182
+ const sub = (path ?? "").replace(/^\/+|\/+$/g, "");
1183
+ return sub ? `${top}/${sub}` : top;
1184
+ }
1185
+
1186
+ // src/files/media/object-url-registry.ts
1187
+ var MAX_LIVE = 256;
1188
+ var live = /* @__PURE__ */ new Map();
1189
+ function createTrackedObjectUrl(blob) {
1190
+ const url = URL.createObjectURL(blob);
1191
+ live.set(url, true);
1192
+ while (live.size > MAX_LIVE) {
1193
+ const oldest = live.keys().next().value;
1194
+ if (!oldest) break;
1195
+ live.delete(oldest);
1196
+ try {
1197
+ URL.revokeObjectURL(oldest);
1198
+ } catch {
1199
+ }
1200
+ }
1201
+ return url;
1202
+ }
1203
+ function revokeTrackedObjectUrl(url) {
1204
+ if (!url || !live.has(url)) return;
1205
+ live.delete(url);
1206
+ try {
1207
+ URL.revokeObjectURL(url);
1208
+ } catch {
1209
+ }
1210
+ }
1211
+ export {
1212
+ CloudFolderDescriptions,
1213
+ CloudFolders,
1214
+ ExternalFetchError,
1215
+ FileAccessDeniedError,
1216
+ FileDeletedError,
1217
+ FileHandlerError,
1218
+ FileNotFoundError,
1219
+ FileUploadError,
1220
+ ShareLinkInvalidError,
1221
+ UploadCancelledError,
1222
+ VideoPublishDate,
1223
+ blockFromMediaRef,
1224
+ composeUploadFolderPath,
1225
+ createTrackedObjectUrl,
1226
+ deriveViewerUrl,
1227
+ extractFileIdFromUrl,
1228
+ fileIdToMediaRef,
1229
+ folderForAgentApp,
1230
+ folderForAgentBlock,
1231
+ folderForCaptures,
1232
+ folderForConversation,
1233
+ folderForIntakeAsset,
1234
+ folderForOrg,
1235
+ folderForPodcast,
1236
+ folderForProductCaptureItem,
1237
+ folderForTask,
1238
+ folderForWarRoomThread,
1239
+ formatVideoPublishDate,
1240
+ formatVideoPublishDateTitle,
1241
+ fromCxAudioPart,
1242
+ fromCxMediaPart,
1243
+ fromCxVideoPart,
1244
+ fromImageOutputData,
1245
+ fromMediaBlock,
1246
+ fromPartialImageData,
1247
+ fromRenderBlock,
1248
+ imageBlockFromMediaRef,
1249
+ isAudioBlock,
1250
+ isConventionalFolder,
1251
+ isDocumentBlock,
1252
+ isExternalImageBlock,
1253
+ isExternalMediaBlock,
1254
+ isHiddenFolder,
1255
+ isImageBlock,
1256
+ isMatrxImageBlock,
1257
+ isMatrxMediaBlock,
1258
+ isMediaBlockData,
1259
+ isSystemPath,
1260
+ isUnifiedImageBlock,
1261
+ isUnifiedMediaBlock,
1262
+ isUploadCancelledError,
1263
+ isVideoBlock,
1264
+ isYouTubeBlock,
1265
+ parseFilenameFromUrl,
1266
+ parseGenerationMetadata,
1267
+ resolveDefaultVisibility,
1268
+ revokeTrackedObjectUrl,
1269
+ toCxMediaPart,
1270
+ toVisibility,
1271
+ urlToMediaRef,
1272
+ videoBlockFromMediaRef,
1273
+ videoPublishDateFromMetadata
1274
+ };
1275
+ //# sourceMappingURL=files.js.map