@ai-matrx/media 0.8.0 → 0.9.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/files/engine/api/assets.d.ts +223 -0
  3. package/dist/files/engine/api/assets.js +124 -0
  4. package/dist/files/engine/api/assets.js.map +1 -0
  5. package/dist/files/engine/api/direct.d.ts +43 -0
  6. package/dist/files/engine/api/direct.js +73 -0
  7. package/dist/files/engine/api/direct.js.map +1 -0
  8. package/dist/files/engine/api/fileOrganization.d.ts +34 -0
  9. package/dist/files/engine/api/fileOrganization.js +38 -0
  10. package/dist/files/engine/api/fileOrganization.js.map +1 -0
  11. package/dist/files/engine/api/files.d.ts +249 -0
  12. package/dist/files/engine/api/files.js +231 -0
  13. package/dist/files/engine/api/files.js.map +1 -0
  14. package/dist/files/engine/api/folders.d.ts +63 -0
  15. package/dist/files/engine/api/folders.js +56 -0
  16. package/dist/files/engine/api/folders.js.map +1 -0
  17. package/dist/files/engine/api/office.d.ts +27 -0
  18. package/dist/files/engine/api/office.js +19 -0
  19. package/dist/files/engine/api/office.js.map +1 -0
  20. package/dist/files/engine/api/permissions.d.ts +37 -0
  21. package/dist/files/engine/api/permissions.js +109 -0
  22. package/dist/files/engine/api/permissions.js.map +1 -0
  23. package/dist/files/engine/api/versions.d.ts +26 -0
  24. package/dist/files/engine/api/versions.js +37 -0
  25. package/dist/files/engine/api/versions.js.map +1 -0
  26. package/dist/files/engine/cache/idb-store.d.ts +95 -0
  27. package/dist/files/engine/cache/idb-store.js +172 -0
  28. package/dist/files/engine/cache/idb-store.js.map +1 -0
  29. package/dist/files/engine/cache/policy.d.ts +14 -0
  30. package/dist/files/engine/cache/policy.js +40 -0
  31. package/dist/files/engine/cache/policy.js.map +1 -0
  32. package/dist/files/engine/cache/register-service-worker.d.ts +57 -0
  33. package/dist/files/engine/cache/register-service-worker.js +80 -0
  34. package/dist/files/engine/cache/register-service-worker.js.map +1 -0
  35. package/dist/files/engine/db-types.d.ts +48865 -0
  36. package/dist/files/engine/db-types.js +1 -0
  37. package/dist/files/engine/db-types.js.map +1 -0
  38. package/dist/files/engine/filesDb.d.ts +1913 -0
  39. package/dist/files/engine/filesDb.js +28 -0
  40. package/dist/files/engine/filesDb.js.map +1 -0
  41. package/dist/files/engine/handler/handler.d.ts +82 -0
  42. package/dist/files/engine/handler/handler.js +124 -0
  43. package/dist/files/engine/handler/handler.js.map +1 -0
  44. package/dist/files/engine/handler/hooks/useFile.d.ts +17 -0
  45. package/dist/files/engine/handler/hooks/useFile.js +75 -0
  46. package/dist/files/engine/handler/hooks/useFile.js.map +1 -0
  47. package/dist/files/engine/handler/hooks/useFileUpload.d.ts +76 -0
  48. package/dist/files/engine/handler/hooks/useFileUpload.js +67 -0
  49. package/dist/files/engine/handler/hooks/useFileUpload.js.map +1 -0
  50. package/dist/files/engine/handler/input/normalize.d.ts +14 -0
  51. package/dist/files/engine/handler/input/normalize.js +365 -0
  52. package/dist/files/engine/handler/input/normalize.js.map +1 -0
  53. package/dist/files/engine/handler/intelligence/access.d.ts +35 -0
  54. package/dist/files/engine/handler/intelligence/access.js +85 -0
  55. package/dist/files/engine/handler/intelligence/access.js.map +1 -0
  56. package/dist/files/engine/handler/intelligence/magic-bytes.d.ts +21 -0
  57. package/dist/files/engine/handler/intelligence/magic-bytes.js +67 -0
  58. package/dist/files/engine/handler/intelligence/magic-bytes.js.map +1 -0
  59. package/dist/files/engine/handler/output/target.d.ts +19 -0
  60. package/dist/files/engine/handler/output/target.js +222 -0
  61. package/dist/files/engine/handler/output/target.js.map +1 -0
  62. package/dist/files/engine/handler/resolver.d.ts +34 -0
  63. package/dist/files/engine/handler/resolver.js +133 -0
  64. package/dist/files/engine/handler/resolver.js.map +1 -0
  65. package/dist/files/engine/handler/types.d.ts +367 -0
  66. package/dist/files/engine/handler/types.js +1 -0
  67. package/dist/files/engine/handler/types.js.map +1 -0
  68. package/dist/files/engine/handler/upload.d.ts +32 -0
  69. package/dist/files/engine/handler/upload.js +295 -0
  70. package/dist/files/engine/handler/upload.js.map +1 -0
  71. package/dist/files/engine/handler/utils/classify.d.ts +14 -0
  72. package/dist/files/engine/handler/utils/classify.js +27 -0
  73. package/dist/files/engine/handler/utils/classify.js.map +1 -0
  74. package/dist/files/engine/handler/utils/prefer-locator.d.ts +32 -0
  75. package/dist/files/engine/handler/utils/prefer-locator.js +34 -0
  76. package/dist/files/engine/handler/utils/prefer-locator.js.map +1 -0
  77. package/dist/files/engine/handler/utils/python-base.d.ts +129 -0
  78. package/dist/files/engine/handler/utils/python-base.js +60 -0
  79. package/dist/files/engine/handler/utils/python-base.js.map +1 -0
  80. package/dist/files/engine/hooks/blob-cache.d.ts +130 -0
  81. package/dist/files/engine/hooks/blob-cache.js +162 -0
  82. package/dist/files/engine/hooks/blob-cache.js.map +1 -0
  83. package/dist/files/engine/hooks/office-extraction-cache.d.ts +28 -0
  84. package/dist/files/engine/hooks/office-extraction-cache.js +67 -0
  85. package/dist/files/engine/hooks/office-extraction-cache.js.map +1 -0
  86. package/dist/files/engine/host/configure.d.ts +166 -0
  87. package/dist/files/engine/host/configure.js +30 -0
  88. package/dist/files/engine/host/configure.js.map +1 -0
  89. package/dist/files/engine/host/org.d.ts +8 -0
  90. package/dist/files/engine/host/org.js +16 -0
  91. package/dist/files/engine/host/org.js.map +1 -0
  92. package/dist/files/engine/host/python-client.d.ts +60 -0
  93. package/dist/files/engine/host/python-client.js +46 -0
  94. package/dist/files/engine/host/python-client.js.map +1 -0
  95. package/dist/files/engine/host/share-links.d.ts +20 -0
  96. package/dist/files/engine/host/share-links.js +32 -0
  97. package/dist/files/engine/host/share-links.js.map +1 -0
  98. package/dist/files/engine/host/store.d.ts +24 -0
  99. package/dist/files/engine/host/store.js +23 -0
  100. package/dist/files/engine/host/store.js.map +1 -0
  101. package/dist/files/engine/host/supabase.d.ts +3808 -0
  102. package/dist/files/engine/host/supabase.js +33 -0
  103. package/dist/files/engine/host/supabase.js.map +1 -0
  104. package/dist/files/engine/host/toast.d.ts +14 -0
  105. package/dist/files/engine/host/toast.js +20 -0
  106. package/dist/files/engine/host/toast.js.map +1 -0
  107. package/dist/files/engine/host/typed-client.d.ts +136 -0
  108. package/dist/files/engine/host/typed-client.js +66 -0
  109. package/dist/files/engine/host/typed-client.js.map +1 -0
  110. package/dist/files/engine/index.d.ts +22 -0
  111. package/dist/files/engine/index.js +49 -0
  112. package/dist/files/engine/index.js.map +1 -0
  113. package/dist/files/engine/media/our-file-sources.d.ts +39 -0
  114. package/dist/files/engine/media/our-file-sources.js +83 -0
  115. package/dist/files/engine/media/our-file-sources.js.map +1 -0
  116. package/dist/files/engine/media/signed-url.d.ts +1 -0
  117. package/dist/files/engine/media/signed-url.js +6 -0
  118. package/dist/files/engine/media/signed-url.js.map +1 -0
  119. package/dist/files/engine/redux/converters.d.ts +73 -0
  120. package/dist/files/engine/redux/converters.js +286 -0
  121. package/dist/files/engine/redux/converters.js.map +1 -0
  122. package/dist/files/engine/redux/file-hydration.d.ts +15 -0
  123. package/dist/files/engine/redux/file-hydration.js +46 -0
  124. package/dist/files/engine/redux/file-hydration.js.map +1 -0
  125. package/dist/files/engine/redux/file-tree-auth-boundary.d.ts +5 -0
  126. package/dist/files/engine/redux/file-tree-auth-boundary.js +28 -0
  127. package/dist/files/engine/redux/file-tree-auth-boundary.js.map +1 -0
  128. package/dist/files/engine/redux/file-tree-timeout.d.ts +32 -0
  129. package/dist/files/engine/redux/file-tree-timeout.js +73 -0
  130. package/dist/files/engine/redux/file-tree-timeout.js.map +1 -0
  131. package/dist/files/engine/redux/mutation-toast-middleware.d.ts +27 -0
  132. package/dist/files/engine/redux/mutation-toast-middleware.js +103 -0
  133. package/dist/files/engine/redux/mutation-toast-middleware.js.map +1 -0
  134. package/dist/files/engine/redux/realtime-middleware.d.ts +61 -0
  135. package/dist/files/engine/redux/realtime-middleware.js +431 -0
  136. package/dist/files/engine/redux/realtime-middleware.js.map +1 -0
  137. package/dist/files/engine/redux/request-ledger.d.ts +68 -0
  138. package/dist/files/engine/redux/request-ledger.js +66 -0
  139. package/dist/files/engine/redux/request-ledger.js.map +1 -0
  140. package/dist/files/engine/redux/selectors.d.ts +3254 -0
  141. package/dist/files/engine/redux/selectors.js +428 -0
  142. package/dist/files/engine/redux/selectors.js.map +1 -0
  143. package/dist/files/engine/redux/slice.d.ts +157 -0
  144. package/dist/files/engine/redux/slice.js +836 -0
  145. package/dist/files/engine/redux/slice.js.map +1 -0
  146. package/dist/files/engine/redux/thunks.d.ts +449 -0
  147. package/dist/files/engine/redux/thunks.js +1684 -0
  148. package/dist/files/engine/redux/thunks.js.map +1 -0
  149. package/dist/files/engine/redux/tree-utils.d.ts +90 -0
  150. package/dist/files/engine/redux/tree-utils.js +200 -0
  151. package/dist/files/engine/redux/tree-utils.js.map +1 -0
  152. package/dist/files/engine/support/claimsUser.d.ts +77 -0
  153. package/dist/files/engine/support/claimsUser.js +48 -0
  154. package/dist/files/engine/support/claimsUser.js.map +1 -0
  155. package/dist/files/engine/support/datetime.d.ts +6 -0
  156. package/dist/files/engine/support/datetime.js +15 -0
  157. package/dist/files/engine/support/datetime.js.map +1 -0
  158. package/dist/files/engine/support/document-visibility.d.ts +14 -0
  159. package/dist/files/engine/support/document-visibility.js +28 -0
  160. package/dist/files/engine/support/document-visibility.js.map +1 -0
  161. package/dist/files/engine/support/logger.d.ts +28 -0
  162. package/dist/files/engine/support/logger.js +36 -0
  163. package/dist/files/engine/support/logger.js.map +1 -0
  164. package/dist/files/engine/types.d.ts +1081 -0
  165. package/dist/files/engine/types.js +30 -0
  166. package/dist/files/engine/types.js.map +1 -0
  167. package/dist/files/engine/upload/cloudUpload.d.ts +182 -0
  168. package/dist/files/engine/upload/cloudUpload.js +418 -0
  169. package/dist/files/engine/upload/cloudUpload.js.map +1 -0
  170. package/dist/files/engine/upload/tusUpload.d.ts +100 -0
  171. package/dist/files/engine/upload/tusUpload.js +258 -0
  172. package/dist/files/engine/upload/tusUpload.js.map +1 -0
  173. package/dist/files/engine/upload/uploadDedupGuard.d.ts +58 -0
  174. package/dist/files/engine/upload/uploadDedupGuard.js +41 -0
  175. package/dist/files/engine/upload/uploadDedupGuard.js.map +1 -0
  176. package/dist/files/engine/upload/uploadGuardOpeners.d.ts +70 -0
  177. package/dist/files/engine/upload/uploadGuardOpeners.js +54 -0
  178. package/dist/files/engine/upload/uploadGuardOpeners.js.map +1 -0
  179. package/dist/files/engine/utils/file-types.d.ts +243 -0
  180. package/dist/files/engine/utils/file-types.js +1722 -0
  181. package/dist/files/engine/utils/file-types.js.map +1 -0
  182. package/dist/files/engine/utils/user-visible.d.ts +133 -0
  183. package/dist/files/engine/utils/user-visible.js +115 -0
  184. package/dist/files/engine/utils/user-visible.js.map +1 -0
  185. package/package.json +393 -9
@@ -0,0 +1,367 @@
1
+ /**
2
+ * features/files/handler/types.ts
3
+ *
4
+ * The universal file handler. ONE shape goes in (FileSource), ONE shape
5
+ * comes out (FileTarget). Between them, every file in the app — owned
6
+ * cld_files row, blob from a paste, base64 string, external URL — funnels
7
+ * through the same NormalizedFile representation so the core never
8
+ * branches on origin.
9
+ *
10
+ * Every callsite that touches a file MUST go through this feature. Direct
11
+ * construction of ImageBlock/AudioBlock/VideoBlock/DocumentBlock literals
12
+ * and direct object-store SDK calls are banned by ESLint (see .eslintrc).
13
+ */
14
+ import type { Asset, AssetPreset, CloudFile, FileUploadResponse, MediaRef, PermissionLevel, Visibility } from "../types.js";
15
+ import type { CustomVariantSpec } from "../api/assets.js";
16
+ import type { FileCategory, PreviewKind, ThumbnailStrategy } from "../utils/file-types.js";
17
+ import type { AudioBlock, DocumentBlock, ImageBlock, VideoBlock, YouTubeVideoBlock } from "@ai-matrx/agents/message-parts";
18
+ import type { MessagePart } from "@ai-matrx/agents/generated/stream-events";
19
+ /**
20
+ * Discriminated union of every file shape that can enter the handler.
21
+ *
22
+ * Resolution priority enforced by the resolver: owned identifiers
23
+ * (`cloud_file`, `file_id`) beat opaque URLs (`share_link`, `public_cdn`)
24
+ * beat raw bytes (`blob`, `file`, `data_uri`, ...). Two locators on the
25
+ * same source are tolerated; the resolver picks the best.
26
+ */
27
+ export type FileSource = BlobSource | FileObjectSource | BufferSource_ | StreamSource | DataUriSource | Base64Source | ExternalUrlSource | YouTubeSource | CloudFileSource | FileIdSource | ShareLinkSource | PublicCdnSource | UploadResultSource | StreamEventSource;
28
+ export interface BlobSource {
29
+ kind: "blob";
30
+ blob: Blob;
31
+ fileName?: string;
32
+ mime?: string;
33
+ }
34
+ export interface FileObjectSource {
35
+ kind: "file";
36
+ file: File;
37
+ }
38
+ export interface BufferSource_ {
39
+ kind: "buffer";
40
+ buffer: ArrayBuffer | Uint8Array;
41
+ mime: string;
42
+ fileName?: string;
43
+ }
44
+ export interface StreamSource {
45
+ kind: "stream";
46
+ stream: ReadableStream<Uint8Array>;
47
+ mime: string;
48
+ fileName?: string;
49
+ }
50
+ export interface DataUriSource {
51
+ kind: "data_uri";
52
+ dataUri: string;
53
+ }
54
+ export interface Base64Source {
55
+ kind: "base64";
56
+ base64: string;
57
+ mime: string;
58
+ fileName?: string;
59
+ }
60
+ export interface ExternalUrlSource {
61
+ kind: "external_url";
62
+ url: string;
63
+ mime?: string;
64
+ }
65
+ export interface YouTubeSource {
66
+ kind: "youtube";
67
+ url: string;
68
+ }
69
+ export interface CloudFileSource {
70
+ kind: "cloud_file";
71
+ cloudFile: CloudFile;
72
+ }
73
+ export interface FileIdSource {
74
+ kind: "file_id";
75
+ fileId: string;
76
+ mime?: string;
77
+ }
78
+ export interface ShareLinkSource {
79
+ kind: "share_link";
80
+ token: string;
81
+ mime?: string;
82
+ }
83
+ export interface PublicCdnSource {
84
+ kind: "public_cdn";
85
+ url: string;
86
+ fileId?: string;
87
+ mime?: string;
88
+ }
89
+ /** Legacy-compat shape — `{ fileId, url, ... }` payload emitted by older upload pathways. */
90
+ export interface UploadResultSource {
91
+ kind: "upload_result";
92
+ uploadResult: {
93
+ fileId?: string;
94
+ url: string;
95
+ pageUrl?: string;
96
+ type?: string;
97
+ metadata?: Record<string, unknown>;
98
+ };
99
+ }
100
+ /**
101
+ * Stream event payload. The handler unwraps RenderBlock/data/tool-output
102
+ * envelopes and produces a NormalizedFile. The shape mirrors the
103
+ * `TypedDataPayload` discriminator for image_output/audio_output/video_output
104
+ * plus the structured render-block payload for type=image/video/document.
105
+ */
106
+ export interface StreamEventSource {
107
+ kind: "stream_event";
108
+ payload: unknown;
109
+ }
110
+ /**
111
+ * The canonical internal shape. After normalization, every code path past
112
+ * the resolver sees only this. There is no second internal shape — adding
113
+ * one would defeat the entire purpose of the handler.
114
+ */
115
+ export interface NormalizedFile {
116
+ /** cld_files UUID — set whenever known. */
117
+ fileId?: string;
118
+ /**
119
+ * User-facing logical path for a persisted cloud file. This is never an
120
+ * object-store key or transport identity; it is retained so canonical
121
+ * version writers can target the existing row after resolving by fileId.
122
+ */
123
+ filePath?: string;
124
+ /** Any URL safe to hand to the browser. */
125
+ url?: string;
126
+ /** Inline base64. Last resort. Always include `meta.mime`. */
127
+ base64?: string;
128
+ /** Resolved share-link token, when the source was a share link. */
129
+ shareToken?: string;
130
+ /** YouTube videos are first-class — backend has dedicated handling. */
131
+ youtubeUrl?: string;
132
+ origin: FileOrigin;
133
+ capabilities: FileCapabilities;
134
+ meta: FileMeta;
135
+ lifecycle: FileLifecycle;
136
+ scope: FileScope;
137
+ derivedFrom?: {
138
+ fileId: string;
139
+ kind: string;
140
+ };
141
+ /**
142
+ * Full {@link Asset} envelope — populated when the file was uploaded
143
+ * through the `/assets` preset pipeline (or when callers explicitly
144
+ * resolve to one). Carries every rendered variant URL keyed by
145
+ * canonical variant key (e.g. `og_url`, `thumbnail_url`). Read this
146
+ * instead of making a second round-trip for variants.
147
+ */
148
+ asset?: Asset;
149
+ /**
150
+ * Original input — kept so we can re-resolve, refresh, or diagnose.
151
+ * Never read this from a render path; use the normalized fields.
152
+ */
153
+ __source: FileSource;
154
+ }
155
+ /**
156
+ * Result of a successful canonical upload. Unlike a general NormalizedFile,
157
+ * a newly-persisted upload must always carry its durable cld_files identity.
158
+ */
159
+ export interface UploadedNormalizedFile extends NormalizedFile {
160
+ fileId: string;
161
+ }
162
+ export type FileOrigin = "owned" | "shared" | "public" | "external" | "ephemeral" | "anonymous_public";
163
+ export interface FileCapabilities {
164
+ canRead: boolean;
165
+ canEdit: boolean;
166
+ canShare: boolean;
167
+ canDelete: boolean;
168
+ /** True when the consumer needs to hold a JWT to open this URL. */
169
+ requiresAuth: boolean;
170
+ /**
171
+ * True when the URL can be `fetch()`ed from the browser — durable
172
+ * `/files/{id}/download` route URLs qualify (the python-client attaches
173
+ * Authorization headers). False for opaque third-party URLs that may be
174
+ * CORS-blocked; output adapters that need bytes route through the
175
+ * backend instead when this is false.
176
+ */
177
+ transportSafeForFetch: boolean;
178
+ }
179
+ export interface FileMeta {
180
+ fileName?: string;
181
+ mime?: string;
182
+ sizeBytes?: number;
183
+ checksum?: string;
184
+ width?: number;
185
+ height?: number;
186
+ durationSec?: number;
187
+ pageCount?: number;
188
+ category: FileCategory;
189
+ previewKind: PreviewKind;
190
+ thumbnailStrategy: ThumbnailStrategy;
191
+ }
192
+ export interface FileLifecycle {
193
+ /** ms epoch — only set for time-bounded references. */
194
+ expiresAt?: number;
195
+ /** True if we have enough identity to re-mint a fresh URL. */
196
+ refreshable: boolean;
197
+ /** ms epoch of the last full re-validation. */
198
+ lastVerifiedAt?: number;
199
+ /** True once the file is durably persisted (vs in-flight upload). */
200
+ persisted: boolean;
201
+ }
202
+ export interface FileScope {
203
+ ownerId?: string;
204
+ organizationId?: string;
205
+ projectId?: string;
206
+ taskId?: string;
207
+ }
208
+ /**
209
+ * Discriminated union of every consumer surface. The output dispatcher picks
210
+ * the optimal locator on the NormalizedFile for each target — `<img src>`
211
+ * prefers a permanent CDN URL, `fetchable_url` prefers the same-origin proxy
212
+ * for CORS safety, AI media blocks prefer `file_id`.
213
+ */
214
+ export type FileTarget = {
215
+ kind: "media_block";
216
+ } | {
217
+ kind: "media_ref";
218
+ } | {
219
+ kind: "html_src";
220
+ } | {
221
+ kind: "fetchable_url";
222
+ } | {
223
+ kind: "blob";
224
+ } | {
225
+ kind: "data_uri";
226
+ } | {
227
+ kind: "form_data_part";
228
+ field: string;
229
+ } | {
230
+ kind: "anchor_download";
231
+ suggestedName?: string;
232
+ } | {
233
+ kind: "og_image";
234
+ } | {
235
+ kind: "jsonb_content_part";
236
+ } | {
237
+ kind: "rag_ingest_source";
238
+ };
239
+ export type RenderedFor<T extends FileTarget> = T extends {
240
+ kind: "media_block";
241
+ } ? MediaBlock : T extends {
242
+ kind: "media_ref";
243
+ } ? MediaRef : T extends {
244
+ kind: "html_src";
245
+ } ? string : T extends {
246
+ kind: "fetchable_url";
247
+ } ? string : T extends {
248
+ kind: "blob";
249
+ } ? Blob : T extends {
250
+ kind: "data_uri";
251
+ } ? string : T extends {
252
+ kind: "form_data_part";
253
+ } ? {
254
+ value: Blob;
255
+ filename: string;
256
+ } : T extends {
257
+ kind: "anchor_download";
258
+ } ? {
259
+ url: string;
260
+ filename: string;
261
+ } : T extends {
262
+ kind: "og_image";
263
+ } ? string : T extends {
264
+ kind: "jsonb_content_part";
265
+ } ? MessagePart : T extends {
266
+ kind: "rag_ingest_source";
267
+ } ? RagIngestSource : never;
268
+ /** All four media block types plus YouTube — what AI APIs accept. */
269
+ export type MediaBlock = ImageBlock | AudioBlock | VideoBlock | DocumentBlock | YouTubeVideoBlock;
270
+ export interface RagIngestSource {
271
+ source_kind: "cld_file" | "external_url" | "inline";
272
+ source_id?: string;
273
+ url?: string;
274
+ inline?: {
275
+ mime: string;
276
+ bytes: Uint8Array;
277
+ };
278
+ }
279
+ export interface UploadOpts {
280
+ /**
281
+ * Logical folder path. Backend creates intermediate folders atomically.
282
+ * If omitted, defaults to a per-feature inbox like "Inbox/Pasted".
283
+ */
284
+ folderPath?: string;
285
+ /**
286
+ * Full logical file path (folder + filename). Wins over `folderPath`
287
+ * when set. Use this to write a new version of an existing file by
288
+ * pointing at the same path the existing file occupies — the Python
289
+ * backend treats matching paths as version-increments on the existing
290
+ * cld_files row rather than creating a new sibling.
291
+ */
292
+ filePath?: string;
293
+ /**
294
+ * One-line note attached to the new version row (`cld_file_versions.change_summary`).
295
+ * Surfaces in the version history UI.
296
+ */
297
+ changeSummary?: string;
298
+ /** Visibility on the new cld_files row. Default "personal" (or "public" for preset uploads). */
299
+ visibility?: Visibility;
300
+ /** Initial sharing grants. */
301
+ shareWith?: string[];
302
+ shareLevel?: PermissionLevel;
303
+ /** Free-form metadata stored on the row. */
304
+ metadata?: Record<string, unknown>;
305
+ /**
306
+ * The organization that owns the uploaded bytes. When supplied, this wins
307
+ * over the ambient app context for every visibility and is written into
308
+ * `metadata.scope.organization_id` before the transport starts.
309
+ */
310
+ organizationId?: string;
311
+ /** Override filename when source has no inherent name. */
312
+ fileName?: string;
313
+ /**
314
+ * When true, scope columns (organization_id/project_id/task_id) are read
315
+ * from `appContext` and stamped into `metadata.scope` so the row carries
316
+ * its scope context. Defaults to false for personal uploads and true for
317
+ * public/shared uploads.
318
+ */
319
+ inheritActiveScope?: boolean;
320
+ /** Per-byte progress callback. */
321
+ onProgress?: (loaded: number, total: number) => void;
322
+ /** Abort the upload (buffered XHR abort / TUS abort). */
323
+ signal?: AbortSignal;
324
+ /**
325
+ * Transport override. Default policy: files ≥
326
+ * `TUS_TRANSPORT_THRESHOLD_BYTES` (features/files/upload/cloudUpload.ts)
327
+ * go resumable TUS; smaller files use the buffered multipart POST.
328
+ */
329
+ transport?: "buffered" | "tus";
330
+ /**
331
+ * If true, the handler also creates a permanent share link after upload.
332
+ * The returned NormalizedFile carries `shareToken` + `url` pointing at
333
+ * Python's `{BACKEND}/share/{token}` endpoint (no Next.js hop, no
334
+ * expiry — embed in `<img src>`, persist to DB columns, etc.).
335
+ */
336
+ createShareLink?: boolean;
337
+ /** Default "read". `admin` is not valid for share links. */
338
+ shareLinkPermissionLevel?: "viewer" | "editor";
339
+ shareLinkExpiresAt?: string | null;
340
+ shareLinkMaxUses?: number | null;
341
+ /**
342
+ * Override origin for the returned share URL. Defaults to
343
+ * `window.location.origin`. Rare — useful for SSR/worker contexts that
344
+ * synthesize their own origin.
345
+ */
346
+ appOrigin?: string;
347
+ /**
348
+ * Asset-pipeline preset. When set, the handler routes through
349
+ * `POST /assets` (which renders preset variants server-side) instead of
350
+ * the plain `POST /files/upload` write. The returned `NormalizedFile`
351
+ * carries the full {@link Asset} envelope on `asset` so consumers can
352
+ * read every rendered variant URL without a second round-trip.
353
+ *
354
+ * Set this for image uploads that need server-rendered variant sets
355
+ * (podcast covers, OG cards, org logos, avatars, favicons). Omit for
356
+ * generic file uploads.
357
+ */
358
+ preset?: AssetPreset;
359
+ /**
360
+ * Ad-hoc custom variant specs sent verbatim to the server in
361
+ * `custom_variants_json`. Use when the preset registry doesn't carry a
362
+ * key you need; otherwise prefer presets so downstream renderers
363
+ * recognize the resulting variant keys.
364
+ */
365
+ customVariants?: CustomVariantSpec[];
366
+ }
367
+ export type { FileUploadResponse };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * features/files/handler/upload.ts
3
+ *
4
+ * The single write path. Coerces a `FileSource` into a `File`, then runs
5
+ * it through `cloudUpload` (which dispatches optimistic Redux updates,
6
+ * calls Python's `/files/upload`, and optionally creates a share link in
7
+ * the same round-trip). The returned `NormalizedFile` reflects the new
8
+ * `cld_files` row and carries the share-link URL when requested.
9
+ *
10
+ * Org-scope routing is visibility-aware: public/shared uploads inherit
11
+ * active organization / project / task ids by default; personal uploads
12
+ * remain independent of ambient app scope unless explicitly opted in.
13
+ */
14
+ import type { FileSource, NormalizedFile, UploadedNormalizedFile, UploadOpts } from "./types.js";
15
+ /**
16
+ * Personal files belong to the individual, independent of whichever
17
+ * organization, project, or task happens to be active in the UI.
18
+ */
19
+ export declare function shouldInheritActiveScope(visibility: NonNullable<UploadOpts["visibility"]>, override?: boolean, folderPath?: string): boolean;
20
+ export declare function uploadInternal(source: FileSource, opts: UploadOpts): Promise<UploadedNormalizedFile>;
21
+ /**
22
+ * Merge metadata from a post-upload hydration without letting that secondary
23
+ * read erase or replace the identity acknowledged by the creation response.
24
+ * This is shared by ordinary and asset uploads because both are the same
25
+ * two-step contract: create → hydrate.
26
+ */
27
+ export declare function preserveUploadedIdentity(hydrated: NormalizedFile, authoritativeFileId: string, label?: string): UploadedNormalizedFile;
28
+ export declare function stampScope(metadata: Record<string, unknown>, inherit: boolean, organizationId?: string, activeScope?: {
29
+ organizationId?: string | null;
30
+ projectId?: string | null;
31
+ taskId?: string | null;
32
+ }): Record<string, unknown>;
@@ -0,0 +1,295 @@
1
+ import { getStoreSingleton } from "../host/store.js";
2
+ import {
3
+ cloudUpload,
4
+ isCloudUploadFailure
5
+ } from "../upload/cloudUpload.js";
6
+ import * as Files from "../api/files.js";
7
+ import {
8
+ uploadAsset,
9
+ uploadAssetWithProgress
10
+ } from "../api/assets.js";
11
+ import { pythonShareUrl } from "./utils/python-base.js";
12
+ import { apiFileRecordToCloudFile } from "../redux/converters.js";
13
+ import {
14
+ selectOrganizationId,
15
+ selectProjectId,
16
+ selectTaskId
17
+ } from "../host/store.js";
18
+ import { FileUploadError, UploadCancelledError } from "../../../files.js";
19
+ import { fromCloudFile } from "./input/normalize.js";
20
+ import { setCached } from "../hooks/blob-cache.js";
21
+ const DEFAULT_FOLDER = "Inbox";
22
+ function shouldInheritActiveScope(visibility, override, folderPath) {
23
+ if (override !== void 0) return override;
24
+ const normalizedFolder = folderPath?.replace(/^\/+|\/+$/g, "") ?? "";
25
+ if (normalizedFolder === "Shared Assets" || normalizedFolder.startsWith("Shared Assets/") || normalizedFolder === "Private Assets" || normalizedFolder.startsWith("Private Assets/")) {
26
+ return false;
27
+ }
28
+ return visibility !== "personal";
29
+ }
30
+ async function uploadInternal(source, opts) {
31
+ const file = await sourceToFile(source, opts.fileName);
32
+ if (!file) {
33
+ throw new FileUploadError(
34
+ "Cannot upload: source did not produce raw bytes (use the input shape that already has a fileId instead)"
35
+ );
36
+ }
37
+ const store = getStoreSingleton();
38
+ if (!store) {
39
+ throw new FileUploadError("Cannot upload: redux store not yet initialized");
40
+ }
41
+ const dispatch = store.dispatch;
42
+ const folderPath = opts.folderPath ?? defaultFolderForSource(source);
43
+ const visibility = opts.visibility ?? (opts.preset ? "public" : "personal");
44
+ const metadata = stampScope(
45
+ opts.metadata ?? {},
46
+ shouldInheritActiveScope(visibility, opts.inheritActiveScope, folderPath),
47
+ opts.organizationId
48
+ );
49
+ if (opts.preset) {
50
+ const params = {
51
+ file,
52
+ preset: opts.preset,
53
+ folder: folderPath.replace(/^\/+|\/+$/g, ""),
54
+ visibility,
55
+ customVariants: opts.customVariants,
56
+ shareWith: opts.shareWith,
57
+ shareLevel: opts.shareLevel,
58
+ metadata
59
+ };
60
+ const { data: asset } = opts.onProgress ? await uploadAssetWithProgress(
61
+ params,
62
+ (event) => opts.onProgress(event.loaded, event.total)
63
+ ) : await uploadAsset(params);
64
+ setCached(asset.file_id, file, URL.createObjectURL(file), {
65
+ mimeType: file.type
66
+ });
67
+ const { data: full2 } = await Files.getFile(asset.file_id);
68
+ const cloudFile2 = apiFileRecordToCloudFile(full2);
69
+ const normalized2 = preserveUploadedIdentity(
70
+ fromCloudFile(cloudFile2, source),
71
+ asset.file_id,
72
+ "asset upload"
73
+ );
74
+ return {
75
+ ...normalized2,
76
+ asset,
77
+ url: asset.primary_url ?? normalized2.url
78
+ };
79
+ }
80
+ const result = await cloudUpload(
81
+ file,
82
+ {
83
+ // filePath wins when set — used by version-replace callers (e.g. the
84
+ // image editor "Save" path that overwrites the existing cloud file).
85
+ ...opts.filePath ? { filePath: opts.filePath.replace(/^\/+/, "") } : { folderPath: folderPath.replace(/^\/+|\/+$/g, "") },
86
+ visibility,
87
+ shareWith: opts.shareWith,
88
+ shareLevel: opts.shareLevel,
89
+ changeSummary: opts.changeSummary,
90
+ metadata,
91
+ onProgress: opts.onProgress ? (event) => opts.onProgress(event.loaded, event.total) : void 0,
92
+ signal: opts.signal,
93
+ transport: opts.transport,
94
+ createShareLink: opts.createShareLink,
95
+ shareLinkPermissionLevel: opts.shareLinkPermissionLevel,
96
+ shareLinkExpiresAt: opts.shareLinkExpiresAt,
97
+ shareLinkMaxUses: opts.shareLinkMaxUses
98
+ },
99
+ dispatch
100
+ );
101
+ if (isCloudUploadFailure(result)) {
102
+ if (result.errorCode === "upload_cancelled") {
103
+ throw new UploadCancelledError(result.error);
104
+ }
105
+ throw new FileUploadError(result.error);
106
+ }
107
+ setCached(result.fileId, file, URL.createObjectURL(file), {
108
+ mimeType: file.type
109
+ });
110
+ const { data: full } = await Files.getFile(result.fileId);
111
+ const cloudFile = apiFileRecordToCloudFile(full);
112
+ const normalized = preserveUploadedIdentity(
113
+ fromCloudFile(cloudFile, source),
114
+ result.fileId
115
+ );
116
+ if (result.shareToken) {
117
+ const appShareUrl = opts.appOrigin ? `${opts.appOrigin.replace(/\/$/, "")}/s/${result.shareToken}` : void 0;
118
+ const publicUrl = cloudFile.visibility === "public" ? normalized.url : void 0;
119
+ const url = publicUrl || result.directUrl || appShareUrl || result.shareUrl || pythonShareUrl(result.shareToken);
120
+ return {
121
+ ...normalized,
122
+ shareToken: result.shareToken,
123
+ url
124
+ };
125
+ }
126
+ return normalized;
127
+ }
128
+ function preserveUploadedIdentity(hydrated, authoritativeFileId, label = "upload") {
129
+ if (hydrated.fileId !== authoritativeFileId) {
130
+ console.error(
131
+ `[file-handler] hydrated ${label} lost or changed its durable identity (upload=${authoritativeFileId}, hydrated=${hydrated.fileId ?? "<missing>"}); preserving the upload acknowledgement.`
132
+ );
133
+ }
134
+ const uploaded = {
135
+ ...hydrated,
136
+ fileId: authoritativeFileId
137
+ };
138
+ assertUploadedIdentity(uploaded);
139
+ return uploaded;
140
+ }
141
+ function assertUploadedIdentity(file) {
142
+ if (!file.fileId) {
143
+ throw new FileUploadError(
144
+ "Upload completed without a durable file identity; refusing to attach a temporary URL"
145
+ );
146
+ }
147
+ }
148
+ async function sourceToFile(source, overrideName) {
149
+ switch (source.kind) {
150
+ case "file":
151
+ return overrideName ? renameFile(source.file, overrideName) : source.file;
152
+ case "blob": {
153
+ const name = overrideName ?? source.fileName ?? guessFilename(source.blob.type);
154
+ return new File([source.blob], name, {
155
+ type: source.mime ?? source.blob.type ?? "application/octet-stream"
156
+ });
157
+ }
158
+ case "buffer": {
159
+ const blob = bufferToBlob(source.buffer, source.mime);
160
+ const name = overrideName ?? source.fileName ?? guessFilename(source.mime);
161
+ return new File([blob], name, { type: source.mime });
162
+ }
163
+ case "stream": {
164
+ const reader = source.stream.getReader();
165
+ const parts = [];
166
+ while (true) {
167
+ const { value, done } = await reader.read();
168
+ if (done) break;
169
+ if (value) {
170
+ const copy = new Uint8Array(value.byteLength);
171
+ copy.set(value);
172
+ parts.push(copy.buffer);
173
+ }
174
+ }
175
+ const blob = new Blob(parts, { type: source.mime });
176
+ const name = overrideName ?? source.fileName ?? guessFilename(source.mime);
177
+ return new File([blob], name, { type: source.mime });
178
+ }
179
+ case "data_uri":
180
+ case "base64": {
181
+ const dataUri = source.kind === "data_uri" ? source.dataUri : `data:${source.mime};base64,${source.base64}`;
182
+ const blob = await dataUriToBlob(dataUri);
183
+ const fallbackName = "fileName" in source && source.fileName || guessFilename(blob.type);
184
+ const name = overrideName ?? fallbackName;
185
+ return new File([blob], name, { type: blob.type });
186
+ }
187
+ case "external_url": {
188
+ const res = await fetch(source.url);
189
+ if (!res.ok) {
190
+ throw new FileUploadError(
191
+ `Failed to fetch external URL for upload (${res.status})`
192
+ );
193
+ }
194
+ const blob = await res.blob();
195
+ const name = overrideName ?? filenameFromUrl(source.url) ?? guessFilename(blob.type);
196
+ return new File([blob], name, { type: source.mime ?? blob.type });
197
+ }
198
+ case "youtube":
199
+ throw new FileUploadError(
200
+ "YouTube URLs cannot be uploaded \u2014 pass them as a source directly"
201
+ );
202
+ default:
203
+ return null;
204
+ }
205
+ }
206
+ function bufferToBlob(buffer, mime) {
207
+ if (buffer instanceof Uint8Array) {
208
+ const copy2 = new Uint8Array(buffer.byteLength);
209
+ copy2.set(buffer);
210
+ return new Blob([copy2.buffer], { type: mime });
211
+ }
212
+ if (buffer instanceof ArrayBuffer) return new Blob([buffer], { type: mime });
213
+ const view = new Uint8Array(buffer);
214
+ const copy = new Uint8Array(view.byteLength);
215
+ copy.set(view);
216
+ return new Blob([copy.buffer], { type: mime });
217
+ }
218
+ function dataUriToBlob(dataUri) {
219
+ return fetch(dataUri).then((r) => r.blob());
220
+ }
221
+ function renameFile(file, name) {
222
+ return new File([file], name, {
223
+ type: file.type,
224
+ lastModified: file.lastModified
225
+ });
226
+ }
227
+ function filenameFromUrl(url) {
228
+ try {
229
+ const path = new URL(url).pathname;
230
+ const last = path.split("/").pop();
231
+ return last && last.length > 0 ? last : void 0;
232
+ } catch {
233
+ return void 0;
234
+ }
235
+ }
236
+ function guessFilename(mime) {
237
+ if (!mime) return "upload.bin";
238
+ const ext = mime.split("/")[1]?.split(";")[0] ?? "bin";
239
+ return `upload-${Date.now()}.${ext}`;
240
+ }
241
+ function stampScope(metadata, inherit, organizationId, activeScope) {
242
+ if (!inherit && !organizationId) return metadata;
243
+ let resolvedActiveScope = activeScope;
244
+ if (inherit && !resolvedActiveScope) {
245
+ const store = getStoreSingleton();
246
+ if (store) {
247
+ const state = store.getState();
248
+ resolvedActiveScope = {
249
+ organizationId: selectOrganizationId(state),
250
+ projectId: selectProjectId(state),
251
+ taskId: selectTaskId(state)
252
+ };
253
+ }
254
+ }
255
+ const activeOrganizationId = resolvedActiveScope?.organizationId;
256
+ const projectId = resolvedActiveScope?.projectId;
257
+ const taskId = resolvedActiveScope?.taskId;
258
+ if (!organizationId && !activeOrganizationId && !projectId && !taskId) {
259
+ return metadata;
260
+ }
261
+ const existing = metadata.scope ?? {};
262
+ return {
263
+ ...metadata,
264
+ scope: {
265
+ ...existing,
266
+ ...activeOrganizationId ? { organization_id: activeOrganizationId } : {},
267
+ ...projectId ? { project_id: projectId } : {},
268
+ ...taskId ? { task_id: taskId } : {},
269
+ // An explicit owner is a record-bound write. It must never be replaced
270
+ // by whatever organization happens to be active in the shell.
271
+ ...organizationId ? { organization_id: organizationId } : {}
272
+ }
273
+ };
274
+ }
275
+ function defaultFolderForSource(source) {
276
+ switch (source.kind) {
277
+ case "file":
278
+ case "blob":
279
+ return DEFAULT_FOLDER;
280
+ case "data_uri":
281
+ case "base64":
282
+ return `${DEFAULT_FOLDER}/Pasted`;
283
+ case "external_url":
284
+ return `${DEFAULT_FOLDER}/Imported`;
285
+ default:
286
+ return DEFAULT_FOLDER;
287
+ }
288
+ }
289
+ export {
290
+ preserveUploadedIdentity,
291
+ shouldInheritActiveScope,
292
+ stampScope,
293
+ uploadInternal
294
+ };
295
+ //# sourceMappingURL=upload.js.map