@opengeni/db 5.0.1 → 6.0.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 (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  nestedPostgresSqlState,
3
3
  rawRows
4
- } from "./chunk-DPONPYJD.js";
4
+ } from "./chunk-CJI6G254.js";
5
5
 
6
6
  // src/managed-auth-session-sets.ts
7
7
  import {
@@ -296,4 +296,4 @@ export {
296
296
  reapManagedAuthIsolatedSessions,
297
297
  reapExpiredManagedAuthSessionSets
298
298
  };
299
- //# sourceMappingURL=chunk-MWMXWHJD.js.map
299
+ //# sourceMappingURL=chunk-7JCDSMX2.js.map
@@ -0,0 +1,219 @@
1
+ import {
2
+ rawRows,
3
+ withWorkspaceSubjectRls
4
+ } from "./chunk-CJI6G254.js";
5
+
6
+ // src/connect-attempts.ts
7
+ import { createHash } from "crypto";
8
+ import { sql } from "drizzle-orm";
9
+ import { ConnectAttempt } from "@opengeni/contracts/connect";
10
+ import { ExternalActorContinuation } from "@opengeni/contracts/external-identities";
11
+ import { stableJson } from "@opengeni/contracts";
12
+ var ConnectAttemptConflictError = class extends Error {
13
+ code = "CONNECT_ATTEMPT_CONFLICT";
14
+ constructor(message = "Connect attempt changed; reload its current state") {
15
+ super(message);
16
+ }
17
+ };
18
+ var ConnectAttemptNotFoundError = class extends Error {
19
+ code = "CONNECT_ATTEMPT_NOT_FOUND";
20
+ constructor() {
21
+ super("Connect attempt not found");
22
+ }
23
+ };
24
+ function projection(row, scope) {
25
+ const value = ConnectAttempt.parse(row.projection);
26
+ if (row.account_id !== scope.accountId || row.workspace_id !== scope.workspaceId || row.subject_id !== scope.subjectId || value.id !== row.id || value.workspaceId !== scope.workspaceId) {
27
+ throw new ConnectAttemptNotFoundError();
28
+ }
29
+ return value;
30
+ }
31
+ function scoped(db, scope, run) {
32
+ return withWorkspaceSubjectRls(db, scope.workspaceId, scope.subjectId, run);
33
+ }
34
+ async function lockAttempt(tx, scope, id) {
35
+ const [row] = await rawRows(
36
+ tx,
37
+ sql`select *, expires_at <= clock_timestamp() as expired from connect_attempts
38
+ where id = ${id}::uuid and account_id = ${scope.accountId}::uuid
39
+ and workspace_id = ${scope.workspaceId}::uuid and subject_id = ${scope.subjectId}
40
+ for update`
41
+ );
42
+ if (!row) throw new ConnectAttemptNotFoundError();
43
+ return row;
44
+ }
45
+ function receiptFor(row, operationId, digest, scope) {
46
+ if (!Object.hasOwn(row.receipts, operationId)) return null;
47
+ const receipt = row.receipts[operationId];
48
+ if (receipt.digest !== digest)
49
+ throw new ConnectAttemptConflictError("Connect operation ID was reused");
50
+ return projection({ ...row, projection: receipt.result }, scope);
51
+ }
52
+ function validateOperation(id, digest) {
53
+ if (!id || new TextEncoder().encode(id).byteLength > 512 || !/^[0-9a-f]{64}$/.test(digest)) {
54
+ throw new ConnectAttemptConflictError("Invalid Connect operation identity");
55
+ }
56
+ }
57
+ async function getConnectBeginReplay(db, scope, input) {
58
+ validateOperation(input.idempotencyKey, input.requestDigest);
59
+ const origin = input.externalContinuation ? ExternalActorContinuation.parse(input.externalContinuation) : null;
60
+ const key = createHash("sha256").update(input.idempotencyKey).digest("hex");
61
+ return scoped(db, scope, async (tx) => {
62
+ const [existing] = await rawRows(
63
+ tx,
64
+ sql`select * from connect_attempts
65
+ where workspace_id = ${scope.workspaceId}::uuid and subject_id = ${scope.subjectId}
66
+ and idempotency_key_hash = ${key} and account_id = ${scope.accountId}::uuid`
67
+ );
68
+ if (!existing) return null;
69
+ if (existing.request_digest !== input.requestDigest || existing.return_url !== input.returnUrl || stableJson(existing.external_continuation ?? null) !== stableJson(origin))
70
+ throw new ConnectAttemptConflictError(
71
+ "Connect idempotency key was reused with different input"
72
+ );
73
+ return projection(existing, scope);
74
+ });
75
+ }
76
+ async function beginConnectAttempt(db, scope, input) {
77
+ const value = ConnectAttempt.parse(input.attempt);
78
+ const origin = input.externalContinuation ? ExternalActorContinuation.parse(input.externalContinuation) : null;
79
+ validateOperation(input.idempotencyKey, input.requestDigest);
80
+ if (value.workspaceId !== scope.workspaceId || value.revision !== 1)
81
+ throw new ConnectAttemptConflictError();
82
+ const key = createHash("sha256").update(input.idempotencyKey).digest("hex");
83
+ return scoped(db, scope, async (tx) => {
84
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(
85
+ ${`connect:${scope.workspaceId}:${scope.subjectId}`}, 0))`);
86
+ await tx.execute(sql`with expired as (
87
+ select id from connect_attempts where workspace_id = ${scope.workspaceId}::uuid
88
+ and account_id = ${scope.accountId}::uuid and subject_id = ${scope.subjectId}
89
+ and expires_at < now() - interval '30 days'
90
+ order by expires_at, id limit 100 for update skip locked
91
+ ) delete from connect_attempts where id in (select id from expired)`);
92
+ const existing = await getConnectBeginReplay(tx, scope, input);
93
+ if (existing) return existing;
94
+ await input.authorizeAcquisition?.(tx);
95
+ const [count] = await rawRows(
96
+ tx,
97
+ sql`select count(*)::int as count from connect_attempts
98
+ where workspace_id = ${scope.workspaceId}::uuid and subject_id = ${scope.subjectId}
99
+ and (expires_at > now() or operation_id is not null)
100
+ and projection->>'state' not in ('complete','cancelled','expired')`
101
+ );
102
+ if ((count?.count ?? 0) >= 32)
103
+ throw new ConnectAttemptConflictError("Too many pending Connect attempts");
104
+ const [row] = await rawRows(
105
+ tx,
106
+ sql`insert into connect_attempts
107
+ (id, account_id, workspace_id, subject_id, idempotency_key_hash, request_digest, return_url, projection, expires_at, external_continuation)
108
+ values (${value.id}::uuid, ${scope.accountId}::uuid, ${scope.workspaceId}::uuid, ${scope.subjectId},
109
+ ${key}, ${input.requestDigest}, ${input.returnUrl}, ${JSON.stringify(value)}::jsonb, ${value.expiresAt}::timestamptz, ${origin ? JSON.stringify(origin) : null}::jsonb)
110
+ returning *`
111
+ );
112
+ if (!row) throw new Error("Connect attempt insert returned no result");
113
+ return projection(row, scope);
114
+ });
115
+ }
116
+ async function getConnectAttempt(db, scope, id) {
117
+ return scoped(db, scope, async (tx) => {
118
+ const row = await lockAttempt(tx, scope, id);
119
+ const current = projection(row, scope);
120
+ if (row.expired && !["complete", "cancelled", "expired", "uncertain"].includes(current.state)) {
121
+ const expired = ConnectAttempt.parse({
122
+ ...current,
123
+ revision: current.revision + 1,
124
+ state: row.operation_id ? "uncertain" : "expired",
125
+ nextAction: { type: "none" }
126
+ });
127
+ await tx.execute(sql`update connect_attempts set projection = ${JSON.stringify(expired)}::jsonb,
128
+ updated_at = now() where id = ${row.id}::uuid`);
129
+ row.projection = expired;
130
+ }
131
+ return {
132
+ attempt: projection(row, scope),
133
+ returnUrl: row.return_url,
134
+ operationInFlight: row.operation_id !== null
135
+ };
136
+ });
137
+ }
138
+ async function listPendingConnectAttempts(db, scope) {
139
+ return scoped(db, scope, async (tx) => {
140
+ const rows = await rawRows(
141
+ tx,
142
+ sql`select * from connect_attempts
143
+ where workspace_id = ${scope.workspaceId}::uuid and account_id = ${scope.accountId}::uuid
144
+ and subject_id = ${scope.subjectId} and (expires_at > now() or operation_id is not null)
145
+ and projection->>'state' not in ('complete','cancelled','expired')
146
+ order by created_at desc, id desc limit 32`
147
+ );
148
+ return rows.map((row) => projection(row, scope));
149
+ });
150
+ }
151
+ async function claimConnectOperation(db, scope, input) {
152
+ validateOperation(input.operationId, input.inputDigest);
153
+ return scoped(db, scope, async (tx) => {
154
+ const row = await lockAttempt(tx, scope, input.attemptId);
155
+ const current = projection(row, scope);
156
+ await input.authorize?.(
157
+ tx,
158
+ structuredClone(current),
159
+ row.external_continuation ? ExternalActorContinuation.parse(row.external_continuation) : null
160
+ );
161
+ const receipt = receiptFor(row, input.operationId, input.inputDigest, scope);
162
+ if (receipt) {
163
+ return { status: "replayed", attempt: receipt };
164
+ }
165
+ await input.authorizeAcquisition?.(tx, structuredClone(current));
166
+ if (row.operation_id)
167
+ throw new ConnectAttemptConflictError(
168
+ "Connect operation is in flight or uncertain; reconcile before retrying"
169
+ );
170
+ if (Object.keys(row.receipts).length >= 64 || current.revision !== input.expectedRevision || row.expired || ["complete", "cancelled", "expired"].includes(current.state)) {
171
+ throw new ConnectAttemptConflictError();
172
+ }
173
+ await tx.execute(sql`update connect_attempts set operation_id = ${input.operationId},
174
+ operation_digest = ${input.inputDigest}, updated_at = now() where id = ${row.id}::uuid`);
175
+ return { status: "claimed", attempt: current };
176
+ });
177
+ }
178
+ async function finishConnectOperation(db, scope, input) {
179
+ validateOperation(input.operationId, input.inputDigest);
180
+ return scoped(db, scope, async (tx) => {
181
+ const row = await lockAttempt(tx, scope, input.attemptId);
182
+ const current = projection(row, scope);
183
+ await input.authorize?.(
184
+ tx,
185
+ structuredClone(current),
186
+ row.external_continuation ? ExternalActorContinuation.parse(row.external_continuation) : null
187
+ );
188
+ const receipt = receiptFor(row, input.operationId, input.inputDigest, scope);
189
+ if (receipt) {
190
+ return receipt;
191
+ }
192
+ await input.authorizeAcquisition?.(tx, structuredClone(current));
193
+ if (row.operation_id !== input.operationId || row.operation_digest !== input.inputDigest)
194
+ throw new ConnectAttemptConflictError();
195
+ const next = ConnectAttempt.parse(await input.commit(tx, structuredClone(current)));
196
+ if (next.id !== current.id || next.workspaceId !== current.workspaceId || next.providerId !== current.providerId || next.ownership !== current.ownership || next.completionRequirement !== current.completionRequirement || stableJson(next.installationTarget ?? null) !== stableJson(current.installationTarget ?? null) || current.source !== void 0 && stableJson(next.source) !== stableJson(current.source) || next.expiresAt !== current.expiresAt || next.revision !== current.revision + 1 || current.credentialsCommitted && !next.credentialsCommitted || current.integrationInstalled && !next.integrationInstalled)
197
+ throw new ConnectAttemptConflictError();
198
+ const receipts = {
199
+ ...row.receipts,
200
+ [input.operationId]: { digest: input.inputDigest, result: next }
201
+ };
202
+ await tx.execute(sql`update connect_attempts set projection = ${JSON.stringify(next)}::jsonb,
203
+ receipts = ${JSON.stringify(receipts)}::jsonb, operation_id = null, operation_digest = null,
204
+ updated_at = now() where id = ${row.id}::uuid`);
205
+ return next;
206
+ });
207
+ }
208
+
209
+ export {
210
+ ConnectAttemptConflictError,
211
+ ConnectAttemptNotFoundError,
212
+ getConnectBeginReplay,
213
+ beginConnectAttempt,
214
+ getConnectAttempt,
215
+ listPendingConnectAttempts,
216
+ claimConnectOperation,
217
+ finishConnectOperation
218
+ };
219
+ //# sourceMappingURL=chunk-AJA2ZIUW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connect-attempts.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { sql } from \"drizzle-orm\";\nimport { ConnectAttempt, type ConnectAttempt as Attempt } from \"@opengeni/contracts/connect\";\nimport { ExternalActorContinuation } from \"@opengeni/contracts/external-identities\";\nimport { stableJson } from \"@opengeni/contracts\";\nimport { rawRows, withWorkspaceSubjectRls, type Database } from \"./database\";\n\nexport type ConnectActorScope = { accountId: string; workspaceId: string; subjectId: string };\n/** Live admission under the attempt transaction. Implementations must acquire\n * their key/membership/link fences in a consistent order and hold them through\n * commit. No network I/O. The attempt argument is an isolated copy. */\nexport type ConnectOperationAuthorization = (\n tx: Database,\n attempt: Attempt,\n origin: ExternalActorContinuation | null,\n) => Promise<void>;\ntype Receipt = { digest: string; result: Attempt };\ntype Row = {\n id: string;\n account_id: string;\n workspace_id: string;\n subject_id: string;\n request_digest: string;\n return_url: string;\n projection: unknown;\n operation_id: string | null;\n operation_digest: string | null;\n receipts: Record<string, Receipt>;\n external_continuation: unknown;\n};\n\nexport class ConnectAttemptConflictError extends Error {\n readonly code = \"CONNECT_ATTEMPT_CONFLICT\";\n constructor(message = \"Connect attempt changed; reload its current state\") {\n super(message);\n }\n}\nexport class ConnectAttemptNotFoundError extends Error {\n readonly code = \"CONNECT_ATTEMPT_NOT_FOUND\";\n constructor() {\n super(\"Connect attempt not found\");\n }\n}\n\nfunction projection(row: Row, scope: ConnectActorScope): Attempt {\n const value = ConnectAttempt.parse(row.projection);\n if (\n row.account_id !== scope.accountId ||\n row.workspace_id !== scope.workspaceId ||\n row.subject_id !== scope.subjectId ||\n value.id !== row.id ||\n value.workspaceId !== scope.workspaceId\n ) {\n throw new ConnectAttemptNotFoundError();\n }\n return value;\n}\nfunction scoped<T>(\n db: Database,\n scope: ConnectActorScope,\n run: (tx: Database) => Promise<T>,\n): Promise<T> {\n return withWorkspaceSubjectRls(db, scope.workspaceId, scope.subjectId, run);\n}\nasync function lockAttempt(\n tx: Database,\n scope: ConnectActorScope,\n id: string,\n): Promise<Row & { expired: boolean }> {\n const [row] = await rawRows<Row & { expired: boolean }>(\n tx,\n sql`select *, expires_at <= clock_timestamp() as expired from connect_attempts\n where id = ${id}::uuid and account_id = ${scope.accountId}::uuid\n and workspace_id = ${scope.workspaceId}::uuid and subject_id = ${scope.subjectId}\n for update`,\n );\n if (!row) throw new ConnectAttemptNotFoundError();\n return row;\n}\n\nfunction receiptFor(\n row: Row,\n operationId: string,\n digest: string,\n scope: ConnectActorScope,\n): Attempt | null {\n if (!Object.hasOwn(row.receipts, operationId)) return null;\n const receipt = row.receipts[operationId]!;\n if (receipt.digest !== digest)\n throw new ConnectAttemptConflictError(\"Connect operation ID was reused\");\n return projection({ ...row, projection: receipt.result }, scope);\n}\nfunction validateOperation(id: string, digest: string): void {\n if (!id || new TextEncoder().encode(id).byteLength > 512 || !/^[0-9a-f]{64}$/.test(digest)) {\n throw new ConnectAttemptConflictError(\"Invalid Connect operation identity\");\n }\n}\n\n/** Caller must establish live workspace/actor authority before this storage\n * seam. RLS scope is a consistency boundary, not proof of authentication. */\ntype ConnectBeginIdentity = {\n idempotencyKey: string;\n requestDigest: string;\n returnUrl: string;\n externalContinuation?: ExternalActorContinuation;\n};\n\n/** Recover only an exact saved begin request, under the caller's live actor\n * admission. This performs no provider work or acquisition. */\nexport async function getConnectBeginReplay(\n db: Database,\n scope: ConnectActorScope,\n input: ConnectBeginIdentity,\n): Promise<Attempt | null> {\n validateOperation(input.idempotencyKey, input.requestDigest);\n const origin = input.externalContinuation\n ? ExternalActorContinuation.parse(input.externalContinuation)\n : null;\n const key = createHash(\"sha256\").update(input.idempotencyKey).digest(\"hex\");\n return scoped(db, scope, async (tx) => {\n const [existing] = await rawRows<Row>(\n tx,\n sql`select * from connect_attempts\n where workspace_id = ${scope.workspaceId}::uuid and subject_id = ${scope.subjectId}\n and idempotency_key_hash = ${key} and account_id = ${scope.accountId}::uuid`,\n );\n if (!existing) return null;\n if (\n existing.request_digest !== input.requestDigest ||\n existing.return_url !== input.returnUrl ||\n stableJson(existing.external_continuation ?? null) !== stableJson(origin)\n )\n throw new ConnectAttemptConflictError(\n \"Connect idempotency key was reused with different input\",\n );\n return projection(existing, scope);\n });\n}\n\nexport async function beginConnectAttempt(\n db: Database,\n scope: ConnectActorScope,\n input: {\n idempotencyKey: string;\n requestDigest: string;\n returnUrl: string;\n attempt: Attempt;\n externalContinuation?: ExternalActorContinuation;\n authorizeAcquisition?: (tx: Database) => Promise<void>;\n },\n): Promise<Attempt> {\n const value = ConnectAttempt.parse(input.attempt);\n const origin = input.externalContinuation\n ? ExternalActorContinuation.parse(input.externalContinuation)\n : null;\n validateOperation(input.idempotencyKey, input.requestDigest);\n if (value.workspaceId !== scope.workspaceId || value.revision !== 1)\n throw new ConnectAttemptConflictError();\n const key = createHash(\"sha256\").update(input.idempotencyKey).digest(\"hex\");\n return scoped(db, scope, async (tx) => {\n // Serialize per-actor creation and quota, not the provider's remote request.\n await tx.execute(sql`select pg_advisory_xact_lock(hashtextextended(\n ${`connect:${scope.workspaceId}:${scope.subjectId}`}, 0))`);\n // Bounded actor-local cleanup; no Connection credentials are deleted.\n await tx.execute(sql`with expired as (\n select id from connect_attempts where workspace_id = ${scope.workspaceId}::uuid\n and account_id = ${scope.accountId}::uuid and subject_id = ${scope.subjectId}\n and expires_at < now() - interval '30 days'\n order by expires_at, id limit 100 for update skip locked\n ) delete from connect_attempts where id in (select id from expired)`);\n const existing = await getConnectBeginReplay(tx, scope, input);\n if (existing) return existing;\n await input.authorizeAcquisition?.(tx);\n const [count] = await rawRows<{ count: number }>(\n tx,\n sql`select count(*)::int as count from connect_attempts\n where workspace_id = ${scope.workspaceId}::uuid and subject_id = ${scope.subjectId}\n and (expires_at > now() or operation_id is not null)\n and projection->>'state' not in ('complete','cancelled','expired')`,\n );\n if ((count?.count ?? 0) >= 32)\n throw new ConnectAttemptConflictError(\"Too many pending Connect attempts\");\n const [row] = await rawRows<Row>(\n tx,\n sql`insert into connect_attempts\n (id, account_id, workspace_id, subject_id, idempotency_key_hash, request_digest, return_url, projection, expires_at, external_continuation)\n values (${value.id}::uuid, ${scope.accountId}::uuid, ${scope.workspaceId}::uuid, ${scope.subjectId},\n ${key}, ${input.requestDigest}, ${input.returnUrl}, ${JSON.stringify(value)}::jsonb, ${value.expiresAt}::timestamptz, ${origin ? JSON.stringify(origin) : null}::jsonb)\n returning *`,\n );\n if (!row) throw new Error(\"Connect attempt insert returned no result\");\n return projection(row, scope);\n });\n}\n\nexport async function getConnectAttempt(\n db: Database,\n scope: ConnectActorScope,\n id: string,\n): Promise<{\n attempt: Attempt;\n returnUrl: string;\n operationInFlight: boolean;\n}> {\n return scoped(db, scope, async (tx) => {\n const row = await lockAttempt(tx, scope, id);\n const current = projection(row, scope);\n if (row.expired && ![\"complete\", \"cancelled\", \"expired\", \"uncertain\"].includes(current.state)) {\n const expired = ConnectAttempt.parse({\n ...current,\n revision: current.revision + 1,\n state: row.operation_id ? \"uncertain\" : \"expired\",\n nextAction: { type: \"none\" },\n });\n await tx.execute(sql`update connect_attempts set projection = ${JSON.stringify(expired)}::jsonb,\n updated_at = now() where id = ${row.id}::uuid`);\n row.projection = expired;\n }\n return {\n attempt: projection(row, scope),\n returnUrl: row.return_url,\n operationInFlight: row.operation_id !== null,\n };\n });\n}\n\nexport async function listPendingConnectAttempts(\n db: Database,\n scope: ConnectActorScope,\n): Promise<Attempt[]> {\n return scoped(db, scope, async (tx) => {\n const rows = await rawRows<Row>(\n tx,\n sql`select * from connect_attempts\n where workspace_id = ${scope.workspaceId}::uuid and account_id = ${scope.accountId}::uuid\n and subject_id = ${scope.subjectId} and (expires_at > now() or operation_id is not null)\n and projection->>'state' not in ('complete','cancelled','expired')\n order by created_at desc, id desc limit 32`,\n );\n return rows.map((row) => projection(row, scope));\n });\n}\n\n/** A claim is committed before remote effects. Never expire/reclaim it merely\n * because the caller timed out: the provider may have accepted the operation. */\nexport async function claimConnectOperation(\n db: Database,\n scope: ConnectActorScope,\n input: {\n attemptId: string;\n expectedRevision: number;\n operationId: string;\n inputDigest: string;\n authorize?: ConnectOperationAuthorization;\n /** Additional acquisition admission, only after exact no-effect receipt replay. */\n authorizeAcquisition?: (tx: Database, attempt: Attempt) => Promise<void>;\n },\n): Promise<{ status: \"claimed\"; attempt: Attempt } | { status: \"replayed\"; attempt: Attempt }> {\n validateOperation(input.operationId, input.inputDigest);\n return scoped(db, scope, async (tx) => {\n const row = await lockAttempt(tx, scope, input.attemptId);\n const current = projection(row, scope);\n await input.authorize?.(\n tx,\n structuredClone(current),\n row.external_continuation ? ExternalActorContinuation.parse(row.external_continuation) : null,\n );\n const receipt = receiptFor(row, input.operationId, input.inputDigest, scope);\n if (receipt) {\n return { status: \"replayed\", attempt: receipt };\n }\n await input.authorizeAcquisition?.(tx, structuredClone(current));\n if (row.operation_id)\n throw new ConnectAttemptConflictError(\n \"Connect operation is in flight or uncertain; reconcile before retrying\",\n );\n if (\n Object.keys(row.receipts).length >= 64 ||\n current.revision !== input.expectedRevision ||\n row.expired ||\n [\"complete\", \"cancelled\", \"expired\"].includes(current.state)\n ) {\n throw new ConnectAttemptConflictError();\n }\n await tx.execute(sql`update connect_attempts set operation_id = ${input.operationId},\n operation_digest = ${input.inputDigest}, updated_at = now() where id = ${row.id}::uuid`);\n return { status: \"claimed\", attempt: current };\n });\n}\n\n/** Pass the same transaction to credential/installation persistence through\n * commit so its receipt and the public setup result become visible atomically.\n * Do not perform network effects inside commit. */\nexport async function finishConnectOperation(\n db: Database,\n scope: ConnectActorScope,\n input: {\n attemptId: string;\n operationId: string;\n inputDigest: string;\n commit: (tx: Database, current: Attempt) => Promise<Attempt>;\n authorize?: ConnectOperationAuthorization;\n /** Additional acquisition admission, only after exact no-effect receipt replay. */\n authorizeAcquisition?: (tx: Database, attempt: Attempt) => Promise<void>;\n },\n): Promise<Attempt> {\n validateOperation(input.operationId, input.inputDigest);\n return scoped(db, scope, async (tx) => {\n const row = await lockAttempt(tx, scope, input.attemptId);\n const current = projection(row, scope);\n await input.authorize?.(\n tx,\n structuredClone(current),\n row.external_continuation ? ExternalActorContinuation.parse(row.external_continuation) : null,\n );\n const receipt = receiptFor(row, input.operationId, input.inputDigest, scope);\n if (receipt) {\n return receipt;\n }\n await input.authorizeAcquisition?.(tx, structuredClone(current));\n if (row.operation_id !== input.operationId || row.operation_digest !== input.inputDigest)\n throw new ConnectAttemptConflictError();\n const next = ConnectAttempt.parse(await input.commit(tx, structuredClone(current)));\n if (\n next.id !== current.id ||\n next.workspaceId !== current.workspaceId ||\n next.providerId !== current.providerId ||\n next.ownership !== current.ownership ||\n next.completionRequirement !== current.completionRequirement ||\n stableJson(next.installationTarget ?? null) !==\n stableJson(current.installationTarget ?? null) ||\n (current.source !== undefined && stableJson(next.source) !== stableJson(current.source)) ||\n next.expiresAt !== current.expiresAt ||\n next.revision !== current.revision + 1 ||\n (current.credentialsCommitted && !next.credentialsCommitted) ||\n (current.integrationInstalled && !next.integrationInstalled)\n )\n throw new ConnectAttemptConflictError();\n const receipts = {\n ...row.receipts,\n [input.operationId]: { digest: input.inputDigest, result: next },\n };\n await tx.execute(sql`update connect_attempts set projection = ${JSON.stringify(next)}::jsonb,\n receipts = ${JSON.stringify(receipts)}::jsonb, operation_id = null, operation_digest = null,\n updated_at = now() where id = ${row.id}::uuid`);\n return next;\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,247 @@
1
+ import {
2
+ rawRows,
3
+ withWorkspaceRls
4
+ } from "./chunk-CJI6G254.js";
5
+ import {
6
+ sessionEvents,
7
+ sessionTurns
8
+ } from "./chunk-MN5PZMEK.js";
9
+ import {
10
+ LOSSLESS_JSON_STRING_PREFIX,
11
+ fromPostgresLosslessJson
12
+ } from "./chunk-S2TZ3F4L.js";
13
+
14
+ // src/session-event-slices.ts
15
+ import { and, asc, desc, eq, gt, inArray, lt, notInArray, sql } from "drizzle-orm";
16
+ import { resolveSessionEventTypeFilters } from "@opengeni/contracts";
17
+ var WINDOW = 8192;
18
+ var STRUCTURED_BYTES = 1024 * 1024;
19
+ async function readSessionMessageSliceInScope(db, workspaceId, sessionId, identity, offset, windows = 1) {
20
+ if (!Number.isInteger(windows) || windows < 1 || windows > 4)
21
+ throw new Error("Invalid search scalar window budget");
22
+ const page = await listSessionEventSlicesInternal(
23
+ db,
24
+ workspaceId,
25
+ sessionId,
26
+ { sourceSequence: identity.sequence, sourceOffset: offset, view: "conversation" },
27
+ void 0,
28
+ identity,
29
+ WINDOW * windows
30
+ );
31
+ return page.slices?.[identity.sequence];
32
+ }
33
+ async function listSessionEventSlices(db, workspaceId, sessionId, options, legacyRead) {
34
+ return listSessionEventSlicesInternal(db, workspaceId, sessionId, options, legacyRead);
35
+ }
36
+ async function listSessionEventSlicesInternal(db, workspaceId, sessionId, options, legacyRead, selectedIdentity, scalarWindow = WINDOW) {
37
+ if (options.legacyUtf16) {
38
+ if (!legacyRead) throw new Error("Legacy continuation requires its original bounded reader");
39
+ return legacyRead({
40
+ ...options,
41
+ limit: 1,
42
+ maxBytes: 1024 * 1024
43
+ });
44
+ }
45
+ const offset = options.sourceOffset ?? 0;
46
+ if (!Number.isSafeInteger(offset) || offset < 0 || offset > 2147483647)
47
+ throw new Error("Invalid message continuation offset");
48
+ const direction = options.direction ?? (options.before === void 0 ? "after" : "before");
49
+ const limit = options.sourceSequence ? 1 : Math.max(1, Math.min(options.limit ?? 8, 8));
50
+ const filters = resolveSessionEventTypeFilters(options);
51
+ const predicates = [
52
+ eq(sessionEvents.workspaceId, workspaceId),
53
+ eq(sessionEvents.sessionId, sessionId),
54
+ gt(sessionEvents.sequence, Math.min(options.after ?? 0, 2147483647)),
55
+ // Same indexed exclusion as the audit/discovery reader. Do not substitute
56
+ // turn status: terminal-before-claim prompts must remain excluded too.
57
+ sql`(${sessionEvents.type} <> 'user.message' or not exists (
58
+ select 1 from ${sessionTurns} unclaimed_prompt_turn
59
+ where unclaimed_prompt_turn.workspace_id = ${workspaceId}
60
+ and unclaimed_prompt_turn.workspace_id = ${sessionEvents.workspaceId}
61
+ and unclaimed_prompt_turn.session_id = ${sessionEvents.sessionId}
62
+ and unclaimed_prompt_turn.trigger_event_id = ${sessionEvents.id}
63
+ and unclaimed_prompt_turn.source in ('user', 'api')
64
+ and unclaimed_prompt_turn.started_at is null
65
+ ))`
66
+ ];
67
+ if (options.before !== void 0 && options.before <= 2147483647)
68
+ predicates.push(lt(sessionEvents.sequence, options.before));
69
+ if (filters.includeTypes.length)
70
+ predicates.push(inArray(sessionEvents.type, filters.includeTypes));
71
+ if (filters.excludeTypes.length)
72
+ predicates.push(notInArray(sessionEvents.type, filters.excludeTypes));
73
+ const identities = selectedIdentity ? [selectedIdentity] : await withWorkspaceRls(
74
+ db,
75
+ workspaceId,
76
+ (tx) => tx.select({
77
+ id: sessionEvents.id,
78
+ sequence: sessionEvents.sequence,
79
+ type: sql`case when octet_length(${sessionEvents.type}) <= 256 then ${sessionEvents.type} else 'session.event.envelope_omitted' end`,
80
+ turnId: sessionEvents.turnId,
81
+ turnAssociation: sql`case when ${sessionEvents.turnAssociation} is null then null when ${sessionEvents.turnAssociation} = 'current' then 'current' else 'late_rejected' end`,
82
+ duplicateOfEventId: sessionEvents.duplicateOfEventId
83
+ }).from(sessionEvents).where(and(...predicates)).orderBy(
84
+ direction === "before" ? desc(sessionEvents.sequence) : asc(sessionEvents.sequence)
85
+ ).limit(limit + 1)
86
+ );
87
+ const ordered = identities.slice(0, limit).map(
88
+ (identity) => ({
89
+ ...identity,
90
+ workspaceId,
91
+ sessionId,
92
+ payload: {},
93
+ occurredAt: ""
94
+ })
95
+ );
96
+ const page = {
97
+ events: [],
98
+ hasMore: identities.length > limit,
99
+ bytes: 2,
100
+ fullPayloadsExact: true,
101
+ direction,
102
+ coveredSequence: null,
103
+ nextAfter: null,
104
+ nextBefore: null,
105
+ truncatedBy: identities.length > limit ? "count" : null
106
+ };
107
+ const slices = {};
108
+ const events = [];
109
+ let consumed = 0;
110
+ const readScalars = async (tx) => {
111
+ for (const event of ordered) {
112
+ consumed++;
113
+ if (event.duplicateOfEventId || event.turnAssociation && event.turnAssociation !== "current")
114
+ continue;
115
+ const p = sessionEvents.payload;
116
+ const output = event.type === "agent.toolCall.output";
117
+ let value = sql`${p}->'text'`;
118
+ if (options.view === "tools")
119
+ value = output ? sql`${p}->'output'` : sql`coalesce(nullif(${p}->'arguments', 'null'::jsonb), ${p}->'args')`;
120
+ else if (options.view === "results")
121
+ value = event.type === "turn.completed" ? sql`coalesce(nullif(${p}->'output', 'null'::jsonb), ${p}->'result')` : sql`${p}`;
122
+ const include = options.view !== "tools" || (output ? options.includeOutput : options.includeArguments);
123
+ if (!include) value = sql`null::jsonb`;
124
+ const start = options.sourceSequence === event.sequence ? offset : 0;
125
+ const prefix = LOSSLESS_JSON_STRING_PREFIX;
126
+ const raw = sql`raw`;
127
+ const encoded = sql`encoded`;
128
+ const tagged = sql`case when version = 1
129
+ and left(${raw}, ${prefix.length}) = ${prefix}
130
+ and length(${encoded}) > 0
131
+ and ${encoded} ~ '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'
132
+ then (length(${encoded}) / 4 * 3 - case when right(${encoded}, 2) = '==' then 2 when right(${encoded}, 1) = '=' then 1 else 0 end) % 2 = 0
133
+ and encode(decode(right(${encoded}, 4), 'base64'), 'base64') = right(${encoded}, 4)
134
+ else false end`;
135
+ const group = Math.floor(Math.max(0, start - 1) / 3);
136
+ const safeMetadata = (key) => sql`case when octet_length((${p}->${key})::text) <= 4096 then ${p}->${key} else null end`;
137
+ const callId = sql`coalesce(nullif(${p}->'callId', 'null'::jsonb), nullif(${p}->'call_id', 'null'::jsonb), ${p}->'id')`;
138
+ const metadataSql = options.view === "tools" ? sql`jsonb_strip_nulls(jsonb_build_object(
139
+ 'callId', case when octet_length((${callId})::text) <= 4096 then ${callId} else null end,
140
+ 'identityOmitted', case when octet_length((${callId})::text) > 4096 or octet_length((${p}->'name')::text) > 4096 then true else null end,
141
+ 'name', ${safeMetadata("name")}, 'isError', case when ${p}->'isError' = 'true'::jsonb or (${output} and ${p}->'output'->'isError' = 'true'::jsonb) then true else null end,
142
+ 'maintenance', case when ${p} ? 'maintenance' then true else null end,
143
+ 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))` : options.view === "results" && event.type === "turn.completed" ? sql`jsonb_strip_nulls(jsonb_build_object(
144
+ 'maintenance', case when ${p} ? 'maintenance' then true else null end,
145
+ 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))` : sql`'{}'::jsonb`;
146
+ const [row] = await rawRows(
147
+ tx,
148
+ sql`with source as (
149
+ select ${value} as value, ${metadataSql} as metadata,
150
+ ${sessionEvents.payloadCodecVersion} as version
151
+ from ${sessionEvents}
152
+ where ${sessionEvents.workspaceId} = ${workspaceId}
153
+ and ${sessionEvents.sessionId} = ${sessionId}
154
+ and ${sessionEvents.id} = ${event.id}
155
+ and (${sessionEvents.turnAssociation} is null or ${sessionEvents.turnAssociation} = 'current')
156
+ and ${sessionEvents.duplicateOfEventId} is null
157
+ limit 1
158
+ ), scalar as (
159
+ select *, jsonb_typeof(value) as kind,
160
+ case when jsonb_typeof(value) = 'string' then value #>> '{}' else null end as raw,
161
+ case when jsonb_typeof(value) <> 'string' then octet_length(value::text) else 0 end as structured_bytes
162
+ from source offset 0
163
+ ), encoding as (
164
+ select *, case when version = 1 and left(raw, ${prefix.length}) = ${prefix}
165
+ then substring(raw from ${prefix.length + 1}::integer) else null end as encoded
166
+ from scalar offset 0
167
+ ), validated as (
168
+ select *, ${tagged} as tagged from encoding offset 0
169
+ )
170
+ select metadata, version, kind, tagged,
171
+ case when kind = 'string' then
172
+ case when tagged then (length(encoded) / 4 * 3 - case when right(encoded, 2) = '==' then 2 when right(encoded, 1) = '=' then 1 else 0 end) / 2
173
+ else length(raw) end else 0 end as total,
174
+ case when kind = 'string' then
175
+ case when tagged then substring(encoded from ${Math.min(group * 8 + 1, 2147483647)}::integer for ${Math.ceil((scalarWindow + 6) / 3) * 8}::integer)
176
+ else substring(raw from ${Math.min(start + 1, 2147483647)}::integer for ${scalarWindow}::integer) end else null end as window,
177
+ case when kind <> 'string' and structured_bytes <= ${STRUCTURED_BYTES} then value else null end as structured,
178
+ coalesce(kind <> 'string' and structured_bytes > ${STRUCTURED_BYTES}, false) as omitted
179
+ from validated`
180
+ );
181
+ if (!row) continue;
182
+ const metadata = fromPostgresLosslessJson(row.metadata, row.version);
183
+ for (const [key, budget] of [
184
+ ["callId", 4096],
185
+ ["name", 1024]
186
+ ]) {
187
+ if (metadata[key] !== void 0 && Buffer.byteLength(JSON.stringify(metadata[key]), "utf8") > budget) {
188
+ delete metadata[key];
189
+ metadata.identityOmitted = true;
190
+ }
191
+ }
192
+ let text = row.window ?? "";
193
+ if (row.tagged) {
194
+ text = fromPostgresLosslessJson(prefix + text, 1);
195
+ const local = start - group * 3;
196
+ if (local > 0 && /[\uD800-\uDBFF]/.test(text[local - 1]) && /[\uDC00-\uDFFF]/.test(text[local] ?? ""))
197
+ throw new Error("Invalid message continuation offset: splits a surrogate pair");
198
+ text = text.slice(local, local + scalarWindow);
199
+ if (start + text.length < row.total && /[\uD800-\uDBFF]/.test(text.at(-1)))
200
+ text = text.slice(0, -1);
201
+ }
202
+ if (start > row.total && row.kind === "string")
203
+ throw new Error("Invalid message continuation offset");
204
+ const logical = row.omitted ? {} : row.kind === "string" ? text : fromPostgresLosslessJson(row.structured, row.version);
205
+ let payload = { ...metadata };
206
+ if (options.view === "tools") {
207
+ if (row.kind !== null) payload[output ? "output" : "arguments"] = logical;
208
+ } else if (options.view === "results") {
209
+ if (event.type === "turn.completed") payload.output = logical;
210
+ else payload = logical ?? {};
211
+ } else payload.text = logical;
212
+ if (row.omitted) payload = { ...payload, sourceOmitted: true };
213
+ if (row.kind === "string" || row.omitted)
214
+ slices[event.sequence] = {
215
+ offset: start,
216
+ total: row.total,
217
+ unit: row.tagged ? "utf16" : "codepoint",
218
+ text,
219
+ omitted: row.omitted
220
+ };
221
+ events.push({ ...event, payload });
222
+ if (row.kind !== "string" && Buffer.byteLength(JSON.stringify(payload)) > WINDOW) break;
223
+ if (row.kind === "string" && start + (row.tagged ? text.length : Array.from(text).length) < row.total)
224
+ break;
225
+ }
226
+ };
227
+ if (selectedIdentity) await readScalars(db);
228
+ else await withWorkspaceRls(db, workspaceId, readScalars);
229
+ return {
230
+ ...page,
231
+ events: events.sort((a, b) => a.sequence - b.sequence),
232
+ hasMore: page.hasMore || consumed < ordered.length,
233
+ coveredSequence: consumed ? {
234
+ first: Math.min(ordered[0].sequence, ordered[consumed - 1].sequence),
235
+ last: Math.max(ordered[0].sequence, ordered[consumed - 1].sequence)
236
+ } : null,
237
+ bytes: Buffer.byteLength(JSON.stringify(events), "utf8"),
238
+ fullPayloadsExact: !Object.values(slices).some((slice) => slice.omitted),
239
+ slices
240
+ };
241
+ }
242
+
243
+ export {
244
+ readSessionMessageSliceInScope,
245
+ listSessionEventSlices
246
+ };
247
+ //# sourceMappingURL=chunk-CDAT6EKW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/session-event-slices.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, inArray, lt, notInArray, sql, type SQL } from \"drizzle-orm\";\nimport { resolveSessionEventTypeFilters, type SessionEvent } from \"@opengeni/contracts\";\nimport { rawRows, withWorkspaceRls, type Database } from \"./database\";\ntype ListSessionEventPageOptions = import(\"./index\").ListSessionEventPageOptions;\ntype SessionEventPage = import(\"./index\").SessionEventPage;\nimport { fromPostgresLosslessJson, LOSSLESS_JSON_STRING_PREFIX } from \"./lossless-json\";\nimport * as schema from \"./schema\";\n\nexport type SessionEventSliceOptions = ListSessionEventPageOptions & {\n sourceSequence?: number;\n sourceOffset?: number;\n view?: \"conversation\" | \"results\" | \"tools\";\n includeArguments?: boolean;\n includeOutput?: boolean;\n legacyUtf16?: boolean;\n};\nexport type SessionEventSlice = {\n offset: number;\n total: number;\n unit: \"codepoint\" | \"utf16\";\n text: string;\n omitted: boolean;\n};\nexport type SessionEventSlicePage = SessionEventPage & {\n slices?: Record<number, SessionEventSlice>;\n};\nconst WINDOW = 8192;\nconst STRUCTURED_BYTES = 1024 * 1024;\n\ntype SliceIdentity = Pick<\n SessionEvent,\n \"id\" | \"sequence\" | \"type\" | \"turnId\" | \"turnAssociation\" | \"duplicateOfEventId\"\n>;\n\n/** Internal search projection, NOT authorization. The scanner has selected this\n * exact identity under its subject RLS/list filters and still holds that same\n * transaction and tenancy/membership fences. Reuse them rather than creating\n * two nested scopes and rediscovering the identity for every scalar window.\n * The scalar query still checks the exact workspace/session/event and RLS.\n * Keep the codec/window implementation shared with the conversation reader.\n */\nexport async function readSessionMessageSliceInScope(\n db: Database,\n workspaceId: string,\n sessionId: string,\n identity: SliceIdentity,\n offset: number,\n windows = 1,\n): Promise<SessionEventSlice | undefined> {\n if (!Number.isInteger(windows) || windows < 1 || windows > 4)\n throw new Error(\"Invalid search scalar window budget\");\n const page = await listSessionEventSlicesInternal(\n db,\n workspaceId,\n sessionId,\n { sourceSequence: identity.sequence, sourceOffset: offset, view: \"conversation\" },\n undefined,\n identity,\n WINDOW * windows,\n );\n return page.slices?.[identity.sequence];\n}\n\n/** Bounded transfer over retained scalar truth. JSONB extraction/detoasting still\n * costs PostgreSQL work proportional to the individual source value, not history.\n * This is a read projection, not authorization: the MCP caller must authorize the\n * target on every request. Both metadata and scalar reads retain workspace RLS.\n */\nexport async function listSessionEventSlices(\n db: Database,\n workspaceId: string,\n sessionId: string,\n options: SessionEventSliceOptions,\n legacyRead?: (options: ListSessionEventPageOptions) => Promise<SessionEventPage>,\n): Promise<SessionEventSlicePage> {\n return listSessionEventSlicesInternal(db, workspaceId, sessionId, options, legacyRead);\n}\n\nasync function listSessionEventSlicesInternal(\n db: Database,\n workspaceId: string,\n sessionId: string,\n options: SessionEventSliceOptions,\n legacyRead?: (options: ListSessionEventPageOptions) => Promise<SessionEventPage>,\n selectedIdentity?: SliceIdentity,\n scalarWindow = WINDOW,\n): Promise<SessionEventSlicePage> {\n // Finish an already-issued UTF-16 cursor using its original bounded reader.\n // New messages use source slices; no cursor offset is silently reinterpreted.\n if (options.legacyUtf16) {\n if (!legacyRead) throw new Error(\"Legacy continuation requires its original bounded reader\");\n return legacyRead({\n ...options,\n limit: 1,\n maxBytes: 1024 * 1024,\n });\n }\n const offset = options.sourceOffset ?? 0;\n if (!Number.isSafeInteger(offset) || offset < 0 || offset > 2_147_483_647)\n throw new Error(\"Invalid message continuation offset\");\n const direction = options.direction ?? (options.before === undefined ? \"after\" : \"before\");\n const limit = options.sourceSequence ? 1 : Math.max(1, Math.min(options.limit ?? 8, 8));\n const filters = resolveSessionEventTypeFilters(options);\n const predicates: SQL[] = [\n eq(schema.sessionEvents.workspaceId, workspaceId),\n eq(schema.sessionEvents.sessionId, sessionId),\n gt(schema.sessionEvents.sequence, Math.min(options.after ?? 0, 2_147_483_647)),\n // Same indexed exclusion as the audit/discovery reader. Do not substitute\n // turn status: terminal-before-claim prompts must remain excluded too.\n sql`(${schema.sessionEvents.type} <> 'user.message' or not exists (\n select 1 from ${schema.sessionTurns} unclaimed_prompt_turn\n where unclaimed_prompt_turn.workspace_id = ${workspaceId}\n and unclaimed_prompt_turn.workspace_id = ${schema.sessionEvents.workspaceId}\n and unclaimed_prompt_turn.session_id = ${schema.sessionEvents.sessionId}\n and unclaimed_prompt_turn.trigger_event_id = ${schema.sessionEvents.id}\n and unclaimed_prompt_turn.source in ('user', 'api')\n and unclaimed_prompt_turn.started_at is null\n ))`,\n ];\n if (options.before !== undefined && options.before <= 2_147_483_647)\n predicates.push(lt(schema.sessionEvents.sequence, options.before));\n if (filters.includeTypes.length)\n predicates.push(inArray(schema.sessionEvents.type, filters.includeTypes));\n if (filters.excludeTypes.length)\n predicates.push(notInArray(schema.sessionEvents.type, filters.excludeTypes));\n // Unlike audit `none` mode, this identity query does not compute a payload\n // projection merely to report its size. Only the selected scalar is extracted.\n const identities = selectedIdentity\n ? [selectedIdentity]\n : await withWorkspaceRls(db, workspaceId, (tx) =>\n tx\n .select({\n id: schema.sessionEvents.id,\n sequence: schema.sessionEvents.sequence,\n type: sql<string>`case when octet_length(${schema.sessionEvents.type}) <= 256 then ${schema.sessionEvents.type} else 'session.event.envelope_omitted' end`,\n turnId: schema.sessionEvents.turnId,\n turnAssociation: sql<\n string | null\n >`case when ${schema.sessionEvents.turnAssociation} is null then null when ${schema.sessionEvents.turnAssociation} = 'current' then 'current' else 'late_rejected' end`,\n duplicateOfEventId: schema.sessionEvents.duplicateOfEventId,\n })\n .from(schema.sessionEvents)\n .where(and(...predicates))\n .orderBy(\n direction === \"before\"\n ? desc(schema.sessionEvents.sequence)\n : asc(schema.sessionEvents.sequence),\n )\n .limit(limit + 1),\n );\n const ordered = identities.slice(0, limit).map(\n (identity) =>\n ({\n ...identity,\n workspaceId,\n sessionId,\n payload: {},\n occurredAt: \"\",\n }) as SessionEvent,\n );\n const page: SessionEventPage = {\n events: [],\n hasMore: identities.length > limit,\n bytes: 2,\n fullPayloadsExact: true,\n direction,\n coveredSequence: null,\n nextAfter: null,\n nextBefore: null,\n truncatedBy: identities.length > limit ? \"count\" : null,\n };\n const slices: Record<number, SessionEventSlice> = {};\n const events: SessionEvent[] = [];\n let consumed = 0;\n const readScalars = async (tx: Database) => {\n for (const event of ordered) {\n consumed++;\n if (\n event.duplicateOfEventId ||\n (event.turnAssociation && event.turnAssociation !== \"current\")\n )\n continue;\n const p = schema.sessionEvents.payload;\n const output = event.type === \"agent.toolCall.output\";\n let value: SQL = sql`${p}->'text'`;\n if (options.view === \"tools\")\n value = output\n ? sql`${p}->'output'`\n : sql`coalesce(nullif(${p}->'arguments', 'null'::jsonb), ${p}->'args')`;\n else if (options.view === \"results\")\n value =\n event.type === \"turn.completed\"\n ? sql`coalesce(nullif(${p}->'output', 'null'::jsonb), ${p}->'result')`\n : sql`${p}`;\n const include =\n options.view !== \"tools\" || (output ? options.includeOutput : options.includeArguments);\n if (!include) value = sql`null::jsonb`;\n const start = options.sourceSequence === event.sequence ? offset : 0;\n const prefix = LOSSLESS_JSON_STRING_PREFIX;\n const raw = sql`raw`;\n const encoded = sql`encoded`;\n // Match exactly the JS codec's canonical-base64/even-byte acceptance.\n // A malformed active marker remains literal, just like the shared codec.\n const tagged = sql`case when version = 1\n and left(${raw}, ${prefix.length}) = ${prefix}\n and length(${encoded}) > 0\n and ${encoded} ~ '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'\n then (length(${encoded}) / 4 * 3 - case when right(${encoded}, 2) = '==' then 2 when right(${encoded}, 1) = '=' then 1 else 0 end) % 2 = 0\n and encode(decode(right(${encoded}, 4), 'base64'), 'base64') = right(${encoded}, 4)\n else false end`;\n // Aligned base64 windows represent groups of three UTF-16 units. Include\n // one preceding and one following unit to validate/avoid pair splits.\n const group = Math.floor(Math.max(0, start - 1) / 3);\n const safeMetadata = (key: string) =>\n sql`case when octet_length((${p}->${key})::text) <= 4096 then ${p}->${key} else null end`;\n const callId = sql`coalesce(nullif(${p}->'callId', 'null'::jsonb), nullif(${p}->'call_id', 'null'::jsonb), ${p}->'id')`;\n const metadataSql =\n options.view === \"tools\"\n ? sql`jsonb_strip_nulls(jsonb_build_object(\n 'callId', case when octet_length((${callId})::text) <= 4096 then ${callId} else null end,\n 'identityOmitted', case when octet_length((${callId})::text) > 4096 or octet_length((${p}->'name')::text) > 4096 then true else null end,\n 'name', ${safeMetadata(\"name\")}, 'isError', case when ${p}->'isError' = 'true'::jsonb or (${output} and ${p}->'output'->'isError' = 'true'::jsonb) then true else null end,\n 'maintenance', case when ${p} ? 'maintenance' then true else null end,\n 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))`\n : options.view === \"results\" && event.type === \"turn.completed\"\n ? sql`jsonb_strip_nulls(jsonb_build_object(\n 'maintenance', case when ${p} ? 'maintenance' then true else null end,\n 'segmentLimit', case when ${p} ? 'segmentLimit' then true else null end))`\n : sql`'{}'::jsonb`;\n const [row] = await rawRows<{\n metadata: unknown;\n version: number | null;\n kind: string | null;\n tagged: boolean;\n total: number;\n window: string | null;\n structured: unknown;\n omitted: boolean;\n }>(\n tx,\n sql`with source as (\n select ${value} as value, ${metadataSql} as metadata,\n ${schema.sessionEvents.payloadCodecVersion} as version\n from ${schema.sessionEvents}\n where ${schema.sessionEvents.workspaceId} = ${workspaceId}\n and ${schema.sessionEvents.sessionId} = ${sessionId}\n and ${schema.sessionEvents.id} = ${event.id}\n and (${schema.sessionEvents.turnAssociation} is null or ${schema.sessionEvents.turnAssociation} = 'current')\n and ${schema.sessionEvents.duplicateOfEventId} is null\n limit 1\n ), scalar as (\n select *, jsonb_typeof(value) as kind,\n case when jsonb_typeof(value) = 'string' then value #>> '{}' else null end as raw,\n case when jsonb_typeof(value) <> 'string' then octet_length(value::text) else 0 end as structured_bytes\n from source offset 0\n ), encoding as (\n select *, case when version = 1 and left(raw, ${prefix.length}) = ${prefix}\n then substring(raw from ${prefix.length + 1}::integer) else null end as encoded\n from scalar offset 0\n ), validated as (\n select *, ${tagged} as tagged from encoding offset 0\n )\n select metadata, version, kind, tagged,\n case when kind = 'string' then\n case when tagged then (length(encoded) / 4 * 3 - case when right(encoded, 2) = '==' then 2 when right(encoded, 1) = '=' then 1 else 0 end) / 2\n else length(raw) end else 0 end as total,\n case when kind = 'string' then\n case when tagged then substring(encoded from ${Math.min(group * 8 + 1, 2_147_483_647)}::integer for ${Math.ceil((scalarWindow + 6) / 3) * 8}::integer)\n else substring(raw from ${Math.min(start + 1, 2_147_483_647)}::integer for ${scalarWindow}::integer) end else null end as window,\n case when kind <> 'string' and structured_bytes <= ${STRUCTURED_BYTES} then value else null end as structured,\n coalesce(kind <> 'string' and structured_bytes > ${STRUCTURED_BYTES}, false) as omitted\n from validated`,\n );\n if (!row) continue;\n const metadata = fromPostgresLosslessJson(row.metadata, row.version) as Record<\n string,\n unknown\n >;\n // Codec decoding can expand JSON escaping (notably NUL/lone surrogates).\n // Keep identities bounded too, without falling back to a different alias.\n for (const [key, budget] of [\n [\"callId\", 4096],\n [\"name\", 1024],\n ] as const) {\n if (\n metadata[key] !== undefined &&\n Buffer.byteLength(JSON.stringify(metadata[key]), \"utf8\") > budget\n ) {\n delete metadata[key];\n metadata.identityOmitted = true;\n }\n }\n let text = row.window ?? \"\";\n if (row.tagged) {\n // Window lengths are multiples of eight, so each partial block is also\n // a complete canonical encoding accepted by the existing lossless codec.\n text = fromPostgresLosslessJson(prefix + text, 1);\n const local = start - group * 3;\n if (\n local > 0 &&\n /[\\uD800-\\uDBFF]/.test(text[local - 1]!) &&\n /[\\uDC00-\\uDFFF]/.test(text[local] ?? \"\")\n )\n throw new Error(\"Invalid message continuation offset: splits a surrogate pair\");\n text = text.slice(local, local + scalarWindow);\n if (start + text.length < row.total && /[\\uD800-\\uDBFF]/.test(text.at(-1)!))\n text = text.slice(0, -1);\n }\n if (start > row.total && row.kind === \"string\")\n throw new Error(\"Invalid message continuation offset\");\n const logical = row.omitted\n ? {}\n : row.kind === \"string\"\n ? text\n : fromPostgresLosslessJson(row.structured, row.version);\n let payload: Record<string, unknown> = { ...metadata };\n if (options.view === \"tools\") {\n if (row.kind !== null) payload[output ? \"output\" : \"arguments\"] = logical;\n } else if (options.view === \"results\") {\n if (event.type === \"turn.completed\") payload.output = logical;\n else payload = (logical as Record<string, unknown>) ?? {};\n } else payload.text = logical;\n if (row.omitted) payload = { ...payload, sourceOmitted: true };\n if (row.kind === \"string\" || row.omitted)\n slices[event.sequence] = {\n offset: start,\n total: row.total,\n unit: row.tagged ? \"utf16\" : \"codepoint\",\n text,\n omitted: row.omitted,\n };\n events.push({ ...event, payload });\n // One large structured value preserves the previous bounded JSON path,\n // without accumulating several megabyte-sized values in one source page.\n if (row.kind !== \"string\" && Buffer.byteLength(JSON.stringify(payload)) > WINDOW) break;\n if (\n row.kind === \"string\" &&\n start + (row.tagged ? text.length : Array.from(text).length) < row.total\n )\n break;\n }\n };\n if (selectedIdentity) await readScalars(db);\n else await withWorkspaceRls(db, workspaceId, readScalars);\n // Preserve scan advancement even when every identity was excluded above.\n return {\n ...page,\n events: events.sort((a, b) => a.sequence - b.sequence),\n hasMore: page.hasMore || consumed < ordered.length,\n coveredSequence: consumed\n ? {\n first: Math.min(ordered[0]!.sequence, ordered[consumed - 1]!.sequence),\n last: Math.max(ordered[0]!.sequence, ordered[consumed - 1]!.sequence),\n }\n : null,\n bytes: Buffer.byteLength(JSON.stringify(events), \"utf8\"),\n fullPayloadsExact: !Object.values(slices).some((slice) => slice.omitted),\n slices,\n };\n}\n"],"mappings":"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","names":[]}