@opengeni/db 0.28.9 → 0.31.1

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 (163) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +91 -0
  4. package/dist/browser-identities.d.ts +128 -0
  5. package/dist/browser-sessions.d.ts +272 -0
  6. package/dist/browser-state-artifacts.d.ts +17 -0
  7. package/dist/capability-components.d.ts +37 -0
  8. package/dist/capability-integrations.d.ts +192 -0
  9. package/dist/chunk-F5FT7BEZ.js +3535 -0
  10. package/dist/chunk-F5FT7BEZ.js.map +1 -0
  11. package/dist/chunk-ISI2TIUG.js +639 -0
  12. package/dist/chunk-ISI2TIUG.js.map +1 -0
  13. package/dist/chunk-MWDVXQOL.js +11639 -0
  14. package/dist/chunk-MWDVXQOL.js.map +1 -0
  15. package/dist/chunk-NBE6CHRI.js +955 -0
  16. package/dist/chunk-NBE6CHRI.js.map +1 -0
  17. package/dist/{chunk-VHBFHMPC.js → chunk-YHZXQ7HP.js} +754 -11
  18. package/dist/chunk-YHZXQ7HP.js.map +1 -0
  19. package/dist/codemode-operations.d.ts +117 -0
  20. package/dist/codex-token-resolver.d.ts +2 -2
  21. package/dist/company-profile-schema.d.ts +798 -0
  22. package/dist/company-profile.d.ts +268 -0
  23. package/dist/computer-sessions.d.ts +109 -0
  24. package/dist/connection-token-resolver.d.ts +9 -4
  25. package/dist/database.d.ts +52 -3
  26. package/dist/editable-artifact-durable-export.d.ts +153 -0
  27. package/dist/editable-artifact-durable-export.js +788 -0
  28. package/dist/editable-artifact-durable-export.js.map +1 -0
  29. package/dist/editable-artifact-materialization.d.ts +123 -0
  30. package/dist/editable-artifacts-schema.d.ts +4152 -0
  31. package/dist/editable-artifacts.d.ts +610 -0
  32. package/dist/editable-artifacts.js +26 -0
  33. package/dist/editable-artifacts.js.map +1 -0
  34. package/dist/generated-images.d.ts +139 -0
  35. package/dist/index.d.ts +698 -111
  36. package/dist/index.js +27573 -10322
  37. package/dist/index.js.map +1 -1
  38. package/dist/insights.d.ts +51 -4
  39. package/dist/integration-bindings.d.ts +70 -0
  40. package/dist/integration-features.d.ts +99 -0
  41. package/dist/interaction-revisions.d.ts +3 -0
  42. package/dist/interaction-schema.d.ts +5372 -0
  43. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  44. package/dist/knowledge-source-sync.d.ts +274 -0
  45. package/dist/memory-governance.d.ts +2 -2
  46. package/dist/memory-slack-delivery.d.ts +101 -0
  47. package/dist/new-session-drafts.d.ts +2 -2
  48. package/dist/pack-components.d.ts +76 -0
  49. package/dist/pack-installations.d.ts +86 -0
  50. package/dist/persistence-errors.d.ts +3 -3
  51. package/dist/plugin-packages.d.ts +126 -0
  52. package/dist/preference-registry.d.ts +1 -1
  53. package/dist/provision-roles.d.ts +9 -1
  54. package/dist/provision-roles.js +1 -1
  55. package/dist/runtime-posture.d.ts +18 -6
  56. package/dist/schema.d.ts +18342 -9099
  57. package/dist/schema.js +165 -1
  58. package/dist/scoped-knowledge.d.ts +97 -1
  59. package/dist/session-control.d.ts +4 -4
  60. package/dist/session-queue-commands.d.ts +19 -11
  61. package/dist/session-realtime-context.d.ts +2 -2
  62. package/dist/session-realtime-ledger.d.ts +4 -4
  63. package/dist/session-realtime-mirror.d.ts +2 -2
  64. package/dist/session-realtime-state.d.ts +1 -1
  65. package/dist/session-realtime-terminal.d.ts +1 -1
  66. package/dist/session-realtime.d.ts +7 -7
  67. package/dist/session-tool-call-settlement.d.ts +1 -1
  68. package/dist/slack-user-link-access.d.ts +54 -0
  69. package/dist/transcription-recordings.d.ts +2 -2
  70. package/dist/turn-initiator.d.ts +2 -2
  71. package/dist/video-generation.d.ts +216 -0
  72. package/dist/video-generation.js +66 -0
  73. package/dist/video-generation.js.map +1 -0
  74. package/dist/workspace-artifacts.d.ts +1 -1
  75. package/dist/workspace-instruction-policies.d.ts +1 -1
  76. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  77. package/dist/workspace-learning-policy.d.ts +161 -0
  78. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  79. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  80. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  81. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  82. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  83. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  84. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  85. package/drizzle/0190_timeline_annotations.sql +31 -0
  86. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  87. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  88. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  89. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  90. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  91. package/drizzle/0196_rig_provider_images.sql +152 -0
  92. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  93. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  94. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  95. package/drizzle/0201_company_profile_authority.sql +849 -0
  96. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  97. package/drizzle/0203_durable_video_generation.sql +353 -0
  98. package/drizzle/0204_video_generation_funding.sql +44 -0
  99. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  100. package/drizzle/0206_browser_sessions.sql +646 -0
  101. package/drizzle/0207_browser_identities.sql +379 -0
  102. package/drizzle/0208_attached_browser_devices.sql +165 -0
  103. package/drizzle/0209_computer_sessions.sql +575 -0
  104. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  105. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  106. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  107. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  108. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  109. package/drizzle/0215_capabilities_platform.sql +980 -0
  110. package/drizzle/0216_pack_component_ownership.sql +235 -0
  111. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  112. package/package.json +18 -5
  113. package/src/attached-browser-devices.ts +371 -0
  114. package/src/attempt-tool-catalogs.ts +131 -0
  115. package/src/browser-auth.ts +1896 -0
  116. package/src/browser-identities.ts +1168 -0
  117. package/src/browser-sessions.ts +2005 -0
  118. package/src/browser-state-artifacts.ts +59 -0
  119. package/src/capability-components.ts +166 -0
  120. package/src/capability-integrations.ts +1663 -0
  121. package/src/codemode-operations.ts +674 -0
  122. package/src/company-profile-schema.ts +143 -0
  123. package/src/company-profile.ts +968 -0
  124. package/src/computer-sessions.ts +1130 -0
  125. package/src/connection-token-resolver.ts +270 -25
  126. package/src/database.ts +427 -23
  127. package/src/editable-artifact-durable-export.ts +1201 -0
  128. package/src/editable-artifact-materialization.ts +893 -0
  129. package/src/editable-artifacts-schema.ts +879 -0
  130. package/src/editable-artifacts.ts +5404 -0
  131. package/src/generated-images.ts +740 -0
  132. package/src/index.ts +5917 -1182
  133. package/src/insights.ts +160 -13
  134. package/src/integration-bindings.ts +455 -0
  135. package/src/integration-features.ts +859 -0
  136. package/src/interaction-revisions.ts +40 -0
  137. package/src/interaction-schema.ts +1303 -0
  138. package/src/knowledge-source-sync-schema.ts +192 -0
  139. package/src/knowledge-source-sync.ts +1922 -0
  140. package/src/memory-slack-delivery.ts +801 -0
  141. package/src/pack-components.ts +954 -0
  142. package/src/pack-installations.ts +929 -0
  143. package/src/persistence-errors.ts +4 -7
  144. package/src/plugin-packages.ts +1186 -0
  145. package/src/provision-roles.ts +302 -1
  146. package/src/runtime-posture-cli.ts +9 -0
  147. package/src/runtime-posture.ts +508 -5
  148. package/src/schema.ts +5117 -3119
  149. package/src/scoped-knowledge-schema.ts +1 -1
  150. package/src/scoped-knowledge.ts +655 -3
  151. package/src/session-control.ts +8 -5
  152. package/src/session-queue-commands.ts +187 -14
  153. package/src/session-realtime-context.ts +2 -2
  154. package/src/session-realtime-ledger.ts +3 -3
  155. package/src/session-realtime.ts +7 -7
  156. package/src/session-tool-call-settlement.ts +9 -0
  157. package/src/slack-user-link-access.ts +702 -0
  158. package/src/video-generation.ts +1452 -0
  159. package/src/workspace-learning-policy-schema.ts +139 -0
  160. package/src/workspace-learning-policy.ts +595 -0
  161. package/dist/chunk-P6CUHXQZ.js +0 -7248
  162. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  163. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -0,0 +1,740 @@
1
+ import type { FileAsset, FileStatus, FileUploadStatus } from "@opengeni/contracts";
2
+ import { and, eq, sql } from "drizzle-orm";
3
+ import type { Database } from "./database";
4
+ import { withRlsContext, withWorkspaceRls } from "./database";
5
+ import * as schema from "./schema";
6
+
7
+ export type GeneratedImageSourceStrategy = "native_hosted" | "provider_adapter";
8
+ export type GeneratedImageArtifactStatus = "pending" | "ready";
9
+ export type ImageGenerationOperationStatus =
10
+ | "prepared"
11
+ | "provider_started"
12
+ | "completed"
13
+ | "outcome_unknown"
14
+ | "retention_failed";
15
+
16
+ export type ImageGenerationOperation = {
17
+ id: string;
18
+ accountId: string;
19
+ workspaceId: string;
20
+ sessionId: string | null;
21
+ turnId: string | null;
22
+ attemptId: string | null;
23
+ operationKey: string;
24
+ toolCallId: string;
25
+ providerId: string;
26
+ providerBindingHash: string;
27
+ modelId: string;
28
+ requestDigest: string;
29
+ expectedArtifactId: string;
30
+ status: ImageGenerationOperationStatus;
31
+ providerStartedAt: Date | null;
32
+ completedAt: Date | null;
33
+ lastError: string | null;
34
+ createdAt: Date;
35
+ updatedAt: Date;
36
+ };
37
+
38
+ export type GeneratedImageArtifact = {
39
+ artifactId: string;
40
+ accountId: string;
41
+ workspaceId: string;
42
+ sessionId: string | null;
43
+ turnId: string | null;
44
+ attemptId: string | null;
45
+ uploadId: string | null;
46
+ settlementKey: string;
47
+ toolCallId: string;
48
+ sourceStrategy: GeneratedImageSourceStrategy;
49
+ providerId: string;
50
+ providerBindingHash: string;
51
+ providerItemId: string | null;
52
+ status: GeneratedImageArtifactStatus;
53
+ mediaType: string;
54
+ sizeBytes: number;
55
+ sha256: string;
56
+ width: number;
57
+ height: number;
58
+ sandboxPath: string;
59
+ readyAt: Date | null;
60
+ lastError: string | null;
61
+ createdAt: Date;
62
+ updatedAt: Date;
63
+ uploadStatus: FileUploadStatus | null;
64
+ file: FileAsset;
65
+ };
66
+
67
+ export type PrepareGeneratedImageArtifactInput = {
68
+ artifactId: string;
69
+ uploadId: string;
70
+ accountId: string;
71
+ workspaceId: string;
72
+ sessionId: string;
73
+ turnId: string;
74
+ attemptId: string;
75
+ settlementKey: string;
76
+ toolCallId: string;
77
+ sourceStrategy: GeneratedImageSourceStrategy;
78
+ providerId: string;
79
+ providerBindingHash: string;
80
+ providerItemId: string | null;
81
+ mediaType: string;
82
+ sizeBytes: number;
83
+ sha256: string;
84
+ width: number;
85
+ height: number;
86
+ sandboxPath: string;
87
+ filename: string;
88
+ safeFilename: string;
89
+ bucket: string;
90
+ objectKey: string;
91
+ uploadExpiresAt: Date;
92
+ };
93
+
94
+ export type PrepareImageGenerationOperationInput = Omit<
95
+ ImageGenerationOperation,
96
+ "status" | "providerStartedAt" | "completedAt" | "lastError" | "createdAt" | "updatedAt"
97
+ >;
98
+
99
+ export async function prepareImageGenerationOperation(
100
+ db: Database,
101
+ input: PrepareImageGenerationOperationInput,
102
+ ): Promise<{ operation: ImageGenerationOperation; created: boolean }> {
103
+ return await withRlsContext(
104
+ db,
105
+ { accountId: input.accountId, workspaceId: input.workspaceId },
106
+ async (scopedDb) =>
107
+ await scopedDb.transaction(async (tx) => {
108
+ await tx.execute(
109
+ sql`select pg_advisory_xact_lock(hashtextextended(${`image-generation-operation:${input.operationKey}`}, 0))`,
110
+ );
111
+ const existing = await imageGenerationOperationByKeyTx(
112
+ tx,
113
+ input.workspaceId,
114
+ input.operationKey,
115
+ );
116
+ if (existing) {
117
+ assertImageGenerationOperationMatches(existing, input);
118
+ return { operation: existing, created: false };
119
+ }
120
+ const [row] = await tx
121
+ .insert(schema.imageGenerationOperations)
122
+ .values({ ...input, status: "prepared" })
123
+ .returning();
124
+ if (!row) throw new Error("Failed to prepare image generation operation");
125
+ return { operation: mapImageGenerationOperation(row), created: true };
126
+ }),
127
+ );
128
+ }
129
+
130
+ export async function getImageGenerationOperation(
131
+ db: Database,
132
+ workspaceId: string,
133
+ operationId: string,
134
+ ): Promise<ImageGenerationOperation | null> {
135
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
136
+ const [row] = await scopedDb
137
+ .select()
138
+ .from(schema.imageGenerationOperations)
139
+ .where(
140
+ and(
141
+ eq(schema.imageGenerationOperations.workspaceId, workspaceId),
142
+ eq(schema.imageGenerationOperations.id, operationId),
143
+ ),
144
+ )
145
+ .limit(1);
146
+ return row ? mapImageGenerationOperation(row) : null;
147
+ });
148
+ }
149
+
150
+ export async function beginImageGenerationOperation(
151
+ db: Database,
152
+ input: {
153
+ accountId: string;
154
+ workspaceId: string;
155
+ operationId: string;
156
+ operationKey: string;
157
+ },
158
+ ): Promise<{ operation: ImageGenerationOperation; started: boolean }> {
159
+ let started = false;
160
+ const operation = await mutateImageGenerationOperation(db, input, async (tx, current) => {
161
+ if (current.status !== "prepared") return current;
162
+ const now = new Date();
163
+ const [row] = await tx
164
+ .update(schema.imageGenerationOperations)
165
+ .set({
166
+ status: "provider_started",
167
+ providerStartedAt: now,
168
+ updatedAt: now,
169
+ })
170
+ .where(eq(schema.imageGenerationOperations.id, current.id))
171
+ .returning();
172
+ if (!row) throw new Error("Failed to begin image generation operation");
173
+ started = true;
174
+ return mapImageGenerationOperation(row);
175
+ });
176
+ return { operation, started };
177
+ }
178
+
179
+ export async function markImageGenerationOperationOutcomeUnknown(
180
+ db: Database,
181
+ input: {
182
+ accountId: string;
183
+ workspaceId: string;
184
+ operationId: string;
185
+ operationKey: string;
186
+ error: string;
187
+ },
188
+ ): Promise<ImageGenerationOperation> {
189
+ return await mutateImageGenerationOperation(db, input, async (tx, current) => {
190
+ if (current.status !== "provider_started") return current;
191
+ const [row] = await tx
192
+ .update(schema.imageGenerationOperations)
193
+ .set({
194
+ status: "outcome_unknown",
195
+ lastError: input.error.slice(0, 4_096),
196
+ updatedAt: new Date(),
197
+ })
198
+ .where(eq(schema.imageGenerationOperations.id, current.id))
199
+ .returning();
200
+ if (!row) throw new Error("Failed to fence ambiguous image generation operation");
201
+ return mapImageGenerationOperation(row);
202
+ });
203
+ }
204
+
205
+ /** Provider completed, but its bytes could not be durably retained. Never replay the paid call. */
206
+ export async function markImageGenerationOperationRetentionFailed(
207
+ db: Database,
208
+ input: {
209
+ accountId: string;
210
+ workspaceId: string;
211
+ operationId: string;
212
+ operationKey: string;
213
+ error: string;
214
+ },
215
+ ): Promise<ImageGenerationOperation> {
216
+ return await mutateImageGenerationOperation(db, input, async (tx, current) => {
217
+ if (current.status !== "provider_started") return current;
218
+ const [row] = await tx
219
+ .update(schema.imageGenerationOperations)
220
+ .set({
221
+ status: "retention_failed",
222
+ lastError: input.error.slice(0, 4_096),
223
+ updatedAt: new Date(),
224
+ })
225
+ .where(eq(schema.imageGenerationOperations.id, current.id))
226
+ .returning();
227
+ if (!row) throw new Error("Failed to record generated image retention failure");
228
+ return mapImageGenerationOperation(row);
229
+ });
230
+ }
231
+
232
+ export async function completeImageGenerationOperation(
233
+ db: Database,
234
+ input: {
235
+ accountId: string;
236
+ workspaceId: string;
237
+ operationId: string;
238
+ operationKey: string;
239
+ },
240
+ ): Promise<ImageGenerationOperation> {
241
+ return await mutateImageGenerationOperation(db, input, async (tx, current) => {
242
+ if (current.status === "completed") return current;
243
+ if (
244
+ current.status !== "provider_started" &&
245
+ current.status !== "outcome_unknown" &&
246
+ current.status !== "retention_failed"
247
+ ) {
248
+ throw new Error("Image generation operation did not reach the provider");
249
+ }
250
+ const [artifact] = await tx
251
+ .select({
252
+ artifact: schema.generatedImageArtifacts,
253
+ file: schema.files,
254
+ })
255
+ .from(schema.generatedImageArtifacts)
256
+ .innerJoin(schema.files, eq(schema.files.id, schema.generatedImageArtifacts.artifactId))
257
+ .where(
258
+ and(
259
+ eq(schema.generatedImageArtifacts.workspaceId, input.workspaceId),
260
+ eq(schema.generatedImageArtifacts.artifactId, current.expectedArtifactId),
261
+ ),
262
+ )
263
+ .limit(1);
264
+ if (
265
+ !artifact ||
266
+ artifact.artifact.status !== "ready" ||
267
+ artifact.file.status !== "ready" ||
268
+ artifact.artifact.sourceStrategy !== "provider_adapter" ||
269
+ artifact.artifact.sessionId !== current.sessionId ||
270
+ artifact.artifact.turnId !== current.turnId ||
271
+ artifact.artifact.toolCallId !== current.toolCallId ||
272
+ artifact.artifact.providerId !== current.providerId ||
273
+ artifact.artifact.providerBindingHash !== current.providerBindingHash ||
274
+ artifact.artifact.providerItemId !== null
275
+ ) {
276
+ throw new Error("Image generation operation has no durable ready artifact");
277
+ }
278
+ const now = new Date();
279
+ const [row] = await tx
280
+ .update(schema.imageGenerationOperations)
281
+ .set({
282
+ status: "completed",
283
+ completedAt: now,
284
+ lastError: null,
285
+ updatedAt: now,
286
+ })
287
+ .where(eq(schema.imageGenerationOperations.id, current.id))
288
+ .returning();
289
+ if (!row) throw new Error("Failed to complete image generation operation");
290
+ return mapImageGenerationOperation(row);
291
+ });
292
+ }
293
+
294
+ /**
295
+ * Reserve one deterministic file/upload/correlation triple. Concurrent retries
296
+ * converge on the same identities and must agree on every immutable byte fact.
297
+ */
298
+ export async function prepareGeneratedImageArtifact(
299
+ db: Database,
300
+ input: PrepareGeneratedImageArtifactInput,
301
+ ): Promise<{ artifact: GeneratedImageArtifact; created: boolean }> {
302
+ return await withRlsContext(
303
+ db,
304
+ { accountId: input.accountId, workspaceId: input.workspaceId },
305
+ async (scopedDb) =>
306
+ await scopedDb.transaction(async (tx) => {
307
+ // `settlementKey` is the durable provider-operation identity. Serializing
308
+ // only that key makes concurrent Temporal/activity retries converge
309
+ // without relying on unique-violation recovery across three tables.
310
+ await tx.execute(
311
+ sql`select pg_advisory_xact_lock(hashtextextended(${`generated-image:${input.settlementKey}`}, 0))`,
312
+ );
313
+ const existing = await generatedImageArtifactBySettlementTx(
314
+ tx,
315
+ input.workspaceId,
316
+ input.settlementKey,
317
+ );
318
+ if (existing) {
319
+ assertPreparedArtifactMatches(existing, input);
320
+ return { artifact: existing, created: false };
321
+ }
322
+
323
+ const [file] = await tx
324
+ .insert(schema.files)
325
+ .values({
326
+ id: input.artifactId,
327
+ accountId: input.accountId,
328
+ workspaceId: input.workspaceId,
329
+ filename: input.filename,
330
+ safeFilename: input.safeFilename,
331
+ contentType: input.mediaType,
332
+ sizeBytes: input.sizeBytes,
333
+ sha256: input.sha256,
334
+ bucket: input.bucket,
335
+ objectKey: input.objectKey,
336
+ status: "pending_upload",
337
+ })
338
+ .returning();
339
+ if (!file) throw new Error("Failed to create generated image file");
340
+
341
+ const [upload] = await tx
342
+ .insert(schema.fileUploads)
343
+ .values({
344
+ id: input.uploadId,
345
+ accountId: input.accountId,
346
+ workspaceId: input.workspaceId,
347
+ fileId: input.artifactId,
348
+ status: "pending",
349
+ expiresAt: input.uploadExpiresAt,
350
+ })
351
+ .returning();
352
+ if (!upload) throw new Error("Failed to create generated image upload");
353
+
354
+ const [artifactRow] = await tx
355
+ .insert(schema.generatedImageArtifacts)
356
+ .values({
357
+ artifactId: input.artifactId,
358
+ accountId: input.accountId,
359
+ workspaceId: input.workspaceId,
360
+ sessionId: input.sessionId,
361
+ turnId: input.turnId,
362
+ attemptId: input.attemptId,
363
+ uploadId: input.uploadId,
364
+ settlementKey: input.settlementKey,
365
+ toolCallId: input.toolCallId,
366
+ sourceStrategy: input.sourceStrategy,
367
+ providerId: input.providerId,
368
+ providerBindingHash: input.providerBindingHash,
369
+ providerItemId: input.providerItemId,
370
+ status: "pending",
371
+ mediaType: input.mediaType,
372
+ sizeBytes: input.sizeBytes,
373
+ sha256: input.sha256,
374
+ width: input.width,
375
+ height: input.height,
376
+ sandboxPath: input.sandboxPath,
377
+ })
378
+ .returning();
379
+ if (!artifactRow) throw new Error("Failed to create generated image artifact");
380
+ return {
381
+ artifact: mapGeneratedImageArtifact(artifactRow, file, upload.status),
382
+ created: true,
383
+ };
384
+ }),
385
+ );
386
+ }
387
+
388
+ export async function getGeneratedImageArtifact(
389
+ db: Database,
390
+ workspaceId: string,
391
+ artifactId: string,
392
+ ): Promise<GeneratedImageArtifact | null> {
393
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
394
+ return await generatedImageArtifactByIdTx(scopedDb, workspaceId, artifactId);
395
+ });
396
+ }
397
+
398
+ export async function settleGeneratedImageArtifactReady(
399
+ db: Database,
400
+ input: {
401
+ accountId: string;
402
+ workspaceId: string;
403
+ artifactId: string;
404
+ settlementKey: string;
405
+ },
406
+ ): Promise<GeneratedImageArtifact> {
407
+ return await withRlsContext(
408
+ db,
409
+ { accountId: input.accountId, workspaceId: input.workspaceId },
410
+ async (scopedDb) =>
411
+ await scopedDb.transaction(async (tx) => {
412
+ const [locked] = await tx
413
+ .select()
414
+ .from(schema.generatedImageArtifacts)
415
+ .where(
416
+ and(
417
+ eq(schema.generatedImageArtifacts.workspaceId, input.workspaceId),
418
+ eq(schema.generatedImageArtifacts.artifactId, input.artifactId),
419
+ ),
420
+ )
421
+ .for("update")
422
+ .limit(1);
423
+ if (!locked || locked.settlementKey !== input.settlementKey) {
424
+ throw new Error("Generated image settlement identity does not match");
425
+ }
426
+ const existing = await generatedImageArtifactByIdTx(
427
+ tx,
428
+ input.workspaceId,
429
+ input.artifactId,
430
+ );
431
+ if (!existing) throw new Error("Generated image settlement file is missing");
432
+ if (existing.status === "ready") return existing;
433
+ if (existing.file.status !== "ready" || existing.uploadStatus !== "completed") {
434
+ throw new Error("Generated image file is not durably ready");
435
+ }
436
+ const now = new Date();
437
+ await tx
438
+ .update(schema.generatedImageArtifacts)
439
+ .set({
440
+ status: "ready",
441
+ readyAt: now,
442
+ lastError: null,
443
+ updatedAt: now,
444
+ })
445
+ .where(
446
+ and(
447
+ eq(schema.generatedImageArtifacts.workspaceId, input.workspaceId),
448
+ eq(schema.generatedImageArtifacts.artifactId, input.artifactId),
449
+ eq(schema.generatedImageArtifacts.status, "pending"),
450
+ ),
451
+ );
452
+ const settled = await generatedImageArtifactByIdTx(tx, input.workspaceId, input.artifactId);
453
+ if (!settled || settled.status !== "ready") {
454
+ throw new Error("Generated image settlement was superseded");
455
+ }
456
+ return settled;
457
+ }),
458
+ );
459
+ }
460
+
461
+ export async function recordGeneratedImageArtifactError(
462
+ db: Database,
463
+ input: {
464
+ accountId: string;
465
+ workspaceId: string;
466
+ artifactId: string;
467
+ settlementKey: string;
468
+ error: string;
469
+ },
470
+ ): Promise<void> {
471
+ await withRlsContext(
472
+ db,
473
+ { accountId: input.accountId, workspaceId: input.workspaceId },
474
+ async (scopedDb) => {
475
+ await scopedDb
476
+ .update(schema.generatedImageArtifacts)
477
+ .set({ lastError: input.error.slice(0, 4_096), updatedAt: new Date() })
478
+ .where(
479
+ and(
480
+ eq(schema.generatedImageArtifacts.workspaceId, input.workspaceId),
481
+ eq(schema.generatedImageArtifacts.artifactId, input.artifactId),
482
+ eq(schema.generatedImageArtifacts.settlementKey, input.settlementKey),
483
+ eq(schema.generatedImageArtifacts.status, "pending"),
484
+ ),
485
+ );
486
+ },
487
+ );
488
+ }
489
+
490
+ export async function listReadyGeneratedImageArtifactsForSession(
491
+ db: Database,
492
+ workspaceId: string,
493
+ sessionId: string,
494
+ ): Promise<GeneratedImageArtifact[]> {
495
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
496
+ const rows = await scopedDb
497
+ .select({
498
+ artifact: schema.generatedImageArtifacts,
499
+ file: schema.files,
500
+ uploadStatus: schema.fileUploads.status,
501
+ })
502
+ .from(schema.generatedImageArtifacts)
503
+ .innerJoin(
504
+ schema.files,
505
+ and(
506
+ eq(schema.files.workspaceId, schema.generatedImageArtifacts.workspaceId),
507
+ eq(schema.files.id, schema.generatedImageArtifacts.artifactId),
508
+ ),
509
+ )
510
+ .leftJoin(
511
+ schema.fileUploads,
512
+ eq(schema.fileUploads.id, schema.generatedImageArtifacts.uploadId),
513
+ )
514
+ .where(
515
+ and(
516
+ eq(schema.generatedImageArtifacts.workspaceId, workspaceId),
517
+ eq(schema.generatedImageArtifacts.sessionId, sessionId),
518
+ eq(schema.generatedImageArtifacts.status, "ready"),
519
+ ),
520
+ );
521
+ return rows.map((row) => mapGeneratedImageArtifact(row.artifact, row.file, row.uploadStatus));
522
+ });
523
+ }
524
+
525
+ async function generatedImageArtifactBySettlementTx(
526
+ tx: Database,
527
+ workspaceId: string,
528
+ settlementKey: string,
529
+ ): Promise<GeneratedImageArtifact | null> {
530
+ const [row] = await tx
531
+ .select({
532
+ artifact: schema.generatedImageArtifacts,
533
+ file: schema.files,
534
+ uploadStatus: schema.fileUploads.status,
535
+ })
536
+ .from(schema.generatedImageArtifacts)
537
+ .innerJoin(
538
+ schema.files,
539
+ and(
540
+ eq(schema.files.workspaceId, schema.generatedImageArtifacts.workspaceId),
541
+ eq(schema.files.id, schema.generatedImageArtifacts.artifactId),
542
+ ),
543
+ )
544
+ .leftJoin(
545
+ schema.fileUploads,
546
+ eq(schema.fileUploads.id, schema.generatedImageArtifacts.uploadId),
547
+ )
548
+ .where(
549
+ and(
550
+ eq(schema.generatedImageArtifacts.workspaceId, workspaceId),
551
+ eq(schema.generatedImageArtifacts.settlementKey, settlementKey),
552
+ ),
553
+ )
554
+ .limit(1);
555
+ return row ? mapGeneratedImageArtifact(row.artifact, row.file, row.uploadStatus) : null;
556
+ }
557
+
558
+ async function generatedImageArtifactByIdTx(
559
+ tx: Database,
560
+ workspaceId: string,
561
+ artifactId: string,
562
+ ): Promise<GeneratedImageArtifact | null> {
563
+ const [row] = await tx
564
+ .select({
565
+ artifact: schema.generatedImageArtifacts,
566
+ file: schema.files,
567
+ uploadStatus: schema.fileUploads.status,
568
+ })
569
+ .from(schema.generatedImageArtifacts)
570
+ .innerJoin(
571
+ schema.files,
572
+ and(
573
+ eq(schema.files.workspaceId, schema.generatedImageArtifacts.workspaceId),
574
+ eq(schema.files.id, schema.generatedImageArtifacts.artifactId),
575
+ ),
576
+ )
577
+ .leftJoin(
578
+ schema.fileUploads,
579
+ eq(schema.fileUploads.id, schema.generatedImageArtifacts.uploadId),
580
+ )
581
+ .where(
582
+ and(
583
+ eq(schema.generatedImageArtifacts.workspaceId, workspaceId),
584
+ eq(schema.generatedImageArtifacts.artifactId, artifactId),
585
+ ),
586
+ )
587
+ .limit(1);
588
+ return row ? mapGeneratedImageArtifact(row.artifact, row.file, row.uploadStatus) : null;
589
+ }
590
+
591
+ function mapGeneratedImageArtifact(
592
+ artifact: typeof schema.generatedImageArtifacts.$inferSelect,
593
+ file: typeof schema.files.$inferSelect,
594
+ uploadStatus: string | null,
595
+ ): GeneratedImageArtifact {
596
+ return {
597
+ ...artifact,
598
+ sourceStrategy: artifact.sourceStrategy as GeneratedImageSourceStrategy,
599
+ status: artifact.status as GeneratedImageArtifactStatus,
600
+ uploadStatus: uploadStatus as FileUploadStatus | null,
601
+ file: mapFile(file),
602
+ };
603
+ }
604
+
605
+ function mapFile(row: typeof schema.files.$inferSelect): FileAsset {
606
+ return {
607
+ id: row.id,
608
+ workspaceId: row.workspaceId,
609
+ status: row.status as FileStatus,
610
+ filename: row.filename,
611
+ safeFilename: row.safeFilename,
612
+ contentType: row.contentType,
613
+ sizeBytes: row.sizeBytes,
614
+ sha256: row.sha256,
615
+ bucket: row.bucket,
616
+ objectKey: row.objectKey,
617
+ createdAt: row.createdAt.toISOString(),
618
+ updatedAt: row.updatedAt.toISOString(),
619
+ };
620
+ }
621
+
622
+ function assertPreparedArtifactMatches(
623
+ artifact: GeneratedImageArtifact,
624
+ input: PrepareGeneratedImageArtifactInput,
625
+ ): void {
626
+ const adapterProvenanceMismatch =
627
+ artifact.sourceStrategy === "provider_adapter" &&
628
+ (artifact.sessionId !== input.sessionId ||
629
+ artifact.turnId !== input.turnId ||
630
+ artifact.toolCallId !== input.toolCallId);
631
+ const mismatched =
632
+ artifact.artifactId !== input.artifactId ||
633
+ artifact.accountId !== input.accountId ||
634
+ artifact.workspaceId !== input.workspaceId ||
635
+ artifact.uploadId !== input.uploadId ||
636
+ adapterProvenanceMismatch ||
637
+ artifact.sourceStrategy !== input.sourceStrategy ||
638
+ artifact.providerId !== input.providerId ||
639
+ artifact.providerBindingHash !== input.providerBindingHash ||
640
+ artifact.providerItemId !== input.providerItemId ||
641
+ artifact.mediaType !== input.mediaType ||
642
+ artifact.sizeBytes !== input.sizeBytes ||
643
+ artifact.sha256 !== input.sha256 ||
644
+ artifact.width !== input.width ||
645
+ artifact.height !== input.height ||
646
+ artifact.sandboxPath !== input.sandboxPath ||
647
+ artifact.file.filename !== input.filename ||
648
+ artifact.file.safeFilename !== input.safeFilename ||
649
+ artifact.file.contentType !== input.mediaType ||
650
+ artifact.file.sizeBytes !== input.sizeBytes ||
651
+ artifact.file.sha256 !== input.sha256 ||
652
+ artifact.file.bucket !== input.bucket ||
653
+ artifact.file.objectKey !== input.objectKey;
654
+ if (mismatched) {
655
+ throw new Error("Generated image retry does not match the reserved immutable artifact");
656
+ }
657
+ }
658
+
659
+ async function imageGenerationOperationByKeyTx(
660
+ tx: Database,
661
+ workspaceId: string,
662
+ operationKey: string,
663
+ ): Promise<ImageGenerationOperation | null> {
664
+ const [row] = await tx
665
+ .select()
666
+ .from(schema.imageGenerationOperations)
667
+ .where(
668
+ and(
669
+ eq(schema.imageGenerationOperations.workspaceId, workspaceId),
670
+ eq(schema.imageGenerationOperations.operationKey, operationKey),
671
+ ),
672
+ )
673
+ .limit(1);
674
+ return row ? mapImageGenerationOperation(row) : null;
675
+ }
676
+
677
+ async function mutateImageGenerationOperation(
678
+ db: Database,
679
+ input: {
680
+ accountId: string;
681
+ workspaceId: string;
682
+ operationId: string;
683
+ operationKey: string;
684
+ },
685
+ mutate: (tx: Database, current: ImageGenerationOperation) => Promise<ImageGenerationOperation>,
686
+ ): Promise<ImageGenerationOperation> {
687
+ return await withRlsContext(
688
+ db,
689
+ { accountId: input.accountId, workspaceId: input.workspaceId },
690
+ async (scopedDb) =>
691
+ await scopedDb.transaction(async (tx) => {
692
+ const [row] = await tx
693
+ .select()
694
+ .from(schema.imageGenerationOperations)
695
+ .where(
696
+ and(
697
+ eq(schema.imageGenerationOperations.workspaceId, input.workspaceId),
698
+ eq(schema.imageGenerationOperations.id, input.operationId),
699
+ ),
700
+ )
701
+ .for("update")
702
+ .limit(1);
703
+ if (!row || row.operationKey !== input.operationKey) {
704
+ throw new Error("Image generation operation identity does not match");
705
+ }
706
+ return await mutate(tx, mapImageGenerationOperation(row));
707
+ }),
708
+ );
709
+ }
710
+
711
+ function mapImageGenerationOperation(
712
+ row: typeof schema.imageGenerationOperations.$inferSelect,
713
+ ): ImageGenerationOperation {
714
+ return {
715
+ ...row,
716
+ status: row.status as ImageGenerationOperationStatus,
717
+ };
718
+ }
719
+
720
+ function assertImageGenerationOperationMatches(
721
+ operation: ImageGenerationOperation,
722
+ input: PrepareImageGenerationOperationInput,
723
+ ): void {
724
+ if (
725
+ operation.id !== input.id ||
726
+ operation.accountId !== input.accountId ||
727
+ operation.workspaceId !== input.workspaceId ||
728
+ operation.sessionId !== input.sessionId ||
729
+ operation.turnId !== input.turnId ||
730
+ operation.operationKey !== input.operationKey ||
731
+ operation.toolCallId !== input.toolCallId ||
732
+ operation.providerId !== input.providerId ||
733
+ operation.providerBindingHash !== input.providerBindingHash ||
734
+ operation.modelId !== input.modelId ||
735
+ operation.requestDigest !== input.requestDigest ||
736
+ operation.expectedArtifactId !== input.expectedArtifactId
737
+ ) {
738
+ throw new Error("Image generation retry does not match the reserved operation");
739
+ }
740
+ }