@opengeni/db 0.28.1 → 0.28.9

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 (52) hide show
  1. package/dist/{chunk-HT5T62CC.js → chunk-P6CUHXQZ.js} +651 -206
  2. package/dist/chunk-P6CUHXQZ.js.map +1 -0
  3. package/dist/{chunk-DBS5HPEW.js → chunk-VHBFHMPC.js} +7 -1
  4. package/dist/chunk-VHBFHMPC.js.map +1 -0
  5. package/dist/environment-crypto.d.ts +8 -4
  6. package/dist/index.d.ts +265 -20
  7. package/dist/index.js +3966 -2185
  8. package/dist/index.js.map +1 -1
  9. package/dist/lossless-columns.d.ts +58 -0
  10. package/dist/lossless-json.d.ts +39 -0
  11. package/dist/memory-domain.d.ts +3 -6
  12. package/dist/persistence-errors.d.ts +7 -14
  13. package/dist/provision-roles.js +1 -1
  14. package/dist/runtime-posture.d.ts +2 -2
  15. package/dist/schema.d.ts +1419 -271
  16. package/dist/schema.js +9 -1
  17. package/dist/session-realtime-terminal.d.ts +7 -0
  18. package/dist/transcription-recordings-schema.d.ts +44 -6
  19. package/dist/transcription-recordings.d.ts +1 -1
  20. package/drizzle/0065_enrollment_credential_generation.sql +10 -0
  21. package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
  22. package/drizzle/0176_lossless_canonical_json.sql +975 -0
  23. package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
  24. package/drizzle/0178_permissioned_secret_reads.sql +14 -0
  25. package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
  26. package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
  27. package/drizzle/0181_connected_machine_removal.sql +52 -0
  28. package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
  29. package/package.json +4 -4
  30. package/src/database.ts +7 -1
  31. package/src/environment-crypto.ts +22 -9
  32. package/src/index.ts +4253 -1601
  33. package/src/lossless-columns.ts +35 -0
  34. package/src/lossless-json.ts +322 -0
  35. package/src/memory-domain.ts +5 -44
  36. package/src/persistence-errors.ts +29 -34
  37. package/src/runtime-posture.ts +6 -0
  38. package/src/schema.ts +260 -38
  39. package/src/session-control.ts +125 -76
  40. package/src/session-queue-commands.ts +325 -234
  41. package/src/session-realtime-context.ts +18 -5
  42. package/src/session-realtime-ledger.ts +29 -7
  43. package/src/session-realtime-mirror.ts +4 -2
  44. package/src/session-realtime-terminal.ts +89 -21
  45. package/src/session-realtime.ts +3 -2
  46. package/src/session-tool-call-settlement.ts +29 -15
  47. package/src/transcription-recordings-schema.ts +5 -2
  48. package/src/transcription-recordings.ts +70 -40
  49. package/dist/chunk-DBS5HPEW.js.map +0 -1
  50. package/dist/chunk-HT5T62CC.js.map +0 -1
  51. package/dist/event-payload-sanitizer.d.ts +0 -32
  52. package/src/event-payload-sanitizer.ts +0 -377
@@ -4,6 +4,7 @@ import { ReasoningEffort } from "@opengeni/contracts";
4
4
  import { and, asc, desc, eq, inArray, isNull, sql } from "drizzle-orm";
5
5
 
6
6
  import type { Database } from "./database";
7
+ import { fromPostgresLosslessJson, fromPostgresLosslessText } from "./lossless-json";
7
8
  import * as schema from "./schema";
8
9
  import { submitHumanPromptInTransaction } from "./session-queue-commands";
9
10
 
@@ -233,7 +234,9 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
233
234
  sequence: schema.sessionRealtimeEntries.sequence,
234
235
  role: schema.sessionRealtimeEntries.role,
235
236
  text: schema.sessionRealtimeEntries.text,
237
+ textCodecVersion: schema.sessionRealtimeEntries.textCodecVersion,
236
238
  payload: schema.sessionRealtimeEntries.payload,
239
+ payloadCodecVersion: schema.sessionRealtimeEntries.payloadCodecVersion,
237
240
  })
238
241
  .from(schema.sessionRealtimeEntries)
239
242
  .where(
@@ -248,7 +251,12 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
248
251
  ),
249
252
  )
250
253
  .orderBy(asc(schema.sessionRealtimeEntries.sequence), asc(schema.sessionRealtimeEntries.id));
251
- const rendered = renderSessionRealtimeTail(rows);
254
+ const decodedRows = rows.map((row) => ({
255
+ ...row,
256
+ text: row.text === null ? null : fromPostgresLosslessText(row.text, row.textCodecVersion),
257
+ payload: fromPostgresLosslessJson(row.payload, row.payloadCodecVersion),
258
+ }));
259
+ const rendered = renderSessionRealtimeTail(decodedRows);
252
260
  if (!rendered.context) return null;
253
261
 
254
262
  const [session] = await db
@@ -290,7 +298,7 @@ export async function flushSessionRealtimeTranscriptTailInTransaction(
290
298
  source: SESSION_REALTIME_TAIL_SOURCE,
291
299
  realtimeId: mode.id,
292
300
  lastDelegationItemId: latestDelegation?.delegationItemId ?? null,
293
- sourceEntryIds: rows.map((entry) => entry.id),
301
+ sourceEntryIds: decodedRows.map((entry) => entry.id),
294
302
  },
295
303
  },
296
304
  source: "api",
@@ -346,7 +354,9 @@ export async function listSessionRealtimeContinuityEntriesInTransaction(
346
354
  sequence: schema.sessionRealtimeEntries.sequence,
347
355
  role: schema.sessionRealtimeEntries.role,
348
356
  text: schema.sessionRealtimeEntries.text,
357
+ textCodecVersion: schema.sessionRealtimeEntries.textCodecVersion,
349
358
  payload: schema.sessionRealtimeEntries.payload,
359
+ payloadCodecVersion: schema.sessionRealtimeEntries.payloadCodecVersion,
350
360
  createdAt: schema.sessionRealtimeEntries.createdAt,
351
361
  modeStartedAt: schema.sessionRealtimeModes.startedAt,
352
362
  })
@@ -371,10 +381,13 @@ export async function listSessionRealtimeContinuityEntriesInTransaction(
371
381
  )
372
382
  .limit(maximumEntries);
373
383
  return rows.reverse().flatMap((row) => {
374
- const turnId = row.payload.turnId;
384
+ const text =
385
+ row.text === null ? null : fromPostgresLosslessText(row.text, row.textCodecVersion);
386
+ const payload = fromPostgresLosslessJson(row.payload, row.payloadCodecVersion);
387
+ const turnId = payload.turnId;
375
388
  if (
376
389
  (row.role !== "user" && row.role !== "assistant") ||
377
- typeof row.text !== "string" ||
390
+ typeof text !== "string" ||
378
391
  typeof turnId !== "string"
379
392
  ) {
380
393
  return [];
@@ -384,7 +397,7 @@ export async function listSessionRealtimeContinuityEntriesInTransaction(
384
397
  realtimeId: row.realtimeId,
385
398
  sequence: row.sequence,
386
399
  role: row.role,
387
- text: takeUtf8Head(row.text, 16_000),
400
+ text: takeUtf8Head(text, 16_000),
388
401
  turnId,
389
402
  createdAt: row.createdAt.toISOString(),
390
403
  },
@@ -3,8 +3,12 @@ import { createHash } from "node:crypto";
3
3
  import { LatencyMode, ReasoningEffort, type SessionRealtimeMode } from "@opengeni/contracts";
4
4
  import { and, asc, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
5
5
 
6
- import { sanitizeEventPayload } from "./event-payload-sanitizer";
7
6
  import type { Database } from "./database";
7
+ import {
8
+ fromPostgresLosslessJson,
9
+ fromPostgresLosslessText,
10
+ LOSSLESS_CONTENT_CODEC_VERSION,
11
+ } from "./lossless-json";
8
12
  import * as schema from "./schema";
9
13
  import {
10
14
  assertSessionRealtimeOwnerInTransaction,
@@ -259,8 +263,8 @@ function mapEntry(row: EntryRow): SessionRealtimeLedgerEntry {
259
263
  sourceUpdateId: row.sourceUpdateId,
260
264
  historyItemId: row.historyItemId,
261
265
  turnId: row.turnId,
262
- text: row.text,
263
- payload: row.payload,
266
+ text: row.text === null ? null : fromPostgresLosslessText(row.text, row.textCodecVersion),
267
+ payload: fromPostgresLosslessJson(row.payload, row.payloadCodecVersion),
264
268
  clientAckedAt: row.clientAckedAt?.toISOString() ?? null,
265
269
  providerAckedAt: row.providerAckedAt?.toISOString() ?? null,
266
270
  createdAt: row.createdAt.toISOString(),
@@ -288,7 +292,7 @@ function assertBoundedString(
288
292
  }
289
293
 
290
294
  function boundedPayload(input: Record<string, unknown> | undefined): Record<string, unknown> {
291
- const payload = sanitizeEventPayload(input ?? {});
295
+ const payload = input ?? {};
292
296
  if (
293
297
  Buffer.byteLength(JSON.stringify(payload), "utf8") > SESSION_REALTIME_LEDGER_MAX_PAYLOAD_BYTES
294
298
  ) {
@@ -1019,6 +1023,9 @@ function inboundReplayMatches(
1019
1023
  text: string | null,
1020
1024
  payload: Record<string, unknown>,
1021
1025
  ): boolean {
1026
+ const storedText =
1027
+ row.text === null ? null : fromPostgresLosslessText(row.text, row.textCodecVersion);
1028
+ const storedPayload = fromPostgresLosslessJson(row.payload, row.payloadCodecVersion);
1022
1029
  return (
1023
1030
  row.direction === "provider_in" &&
1024
1031
  row.kind === input.kind &&
@@ -1026,8 +1033,9 @@ function inboundReplayMatches(
1026
1033
  row.providerEventId === (input.providerEventId ?? null) &&
1027
1034
  row.delegationItemId === (input.delegationItemId ?? null) &&
1028
1035
  row.sourceUpdateId === null &&
1029
- row.text === text &&
1030
- JSON.stringify(canonicalJsonValue(row.payload)) === JSON.stringify(canonicalJsonValue(payload))
1036
+ storedText === text &&
1037
+ JSON.stringify(canonicalJsonValue(storedPayload)) ===
1038
+ JSON.stringify(canonicalJsonValue(payload))
1031
1039
  );
1032
1040
  }
1033
1041
 
@@ -1055,10 +1063,12 @@ async function appendInvalidDelegationFailure(
1055
1063
  kind: "error",
1056
1064
  delegationItemId: incoming.delegationItemId ?? null,
1057
1065
  text: failure.message,
1066
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1058
1067
  payload: {
1059
1068
  code: failure.code,
1060
1069
  callOperationId: incoming.operationId,
1061
1070
  },
1071
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1062
1072
  createdAt: now,
1063
1073
  updatedAt: now,
1064
1074
  });
@@ -1189,7 +1199,11 @@ async function materializeRealtimeUpdates(
1189
1199
  )
1190
1200
  .orderBy(asc(schema.sessionSystemUpdates.createdAt), asc(schema.sessionSystemUpdates.id))
1191
1201
  .limit(SESSION_REALTIME_LEDGER_MAX_OUTBOUND);
1192
- const updates = rows.map(({ update }) => update);
1202
+ const updates = rows.map(({ update }) => ({
1203
+ ...update,
1204
+ summary: fromPostgresLosslessText(update.summary, update.summaryCodecVersion),
1205
+ payload: fromPostgresLosslessJson(update.payload, update.payloadCodecVersion),
1206
+ }));
1193
1207
  if (updates.length === 0) return;
1194
1208
  await db.insert(schema.sessionRealtimeEntries).values(
1195
1209
  updates.map((update) => ({
@@ -1204,6 +1218,7 @@ async function materializeRealtimeUpdates(
1204
1218
  kind: "session_update",
1205
1219
  sourceUpdateId: update.id,
1206
1220
  text: update.summary,
1221
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1207
1222
  payload: boundedPayload({
1208
1223
  updateId: update.id,
1209
1224
  kind: update.kind,
@@ -1213,6 +1228,7 @@ async function materializeRealtimeUpdates(
1213
1228
  payload: update.payload,
1214
1229
  lineage: update.lineage,
1215
1230
  }),
1231
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1216
1232
  createdAt: now,
1217
1233
  updatedAt: now,
1218
1234
  })),
@@ -1404,7 +1420,9 @@ export async function syncSessionRealtimeLedgerInTransaction(
1404
1420
  delegationItemId: incoming.delegationItemId ?? null,
1405
1421
  historyItemId: null,
1406
1422
  text,
1423
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1407
1424
  payload,
1425
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1408
1426
  createdAt: now,
1409
1427
  updatedAt: now,
1410
1428
  })
@@ -1612,7 +1630,9 @@ export async function appendSessionRealtimeOutboundInTransaction(
1612
1630
  kind: input.kind,
1613
1631
  delegationItemId: input.delegationItemId,
1614
1632
  text,
1633
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1615
1634
  payload,
1635
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1616
1636
  createdAt: now,
1617
1637
  updatedAt: now,
1618
1638
  })
@@ -1757,6 +1777,7 @@ export async function projectSessionRealtimeDelegationProgressInTransaction(
1757
1777
  delegationItemId: call.delegationItemId,
1758
1778
  turnId: input.turnId,
1759
1779
  text: chunk,
1780
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1760
1781
  payload: boundedPayload({
1761
1782
  route: "delegation_context",
1762
1783
  channel: "commentary",
@@ -1772,6 +1793,7 @@ export async function projectSessionRealtimeDelegationProgressInTransaction(
1772
1793
  chunkIndex,
1773
1794
  chunkCount: chunks.length,
1774
1795
  }),
1796
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
1775
1797
  createdAt: now,
1776
1798
  updatedAt: now,
1777
1799
  })),
@@ -3,8 +3,8 @@ import { createHash } from "node:crypto";
3
3
  import type { HumanInputQuestion, HumanInputResponse } from "@opengeni/contracts";
4
4
  import { and, desc, eq, gt, sql } from "drizzle-orm";
5
5
 
6
- import { sanitizeEventPayload } from "./event-payload-sanitizer";
7
6
  import type { Database } from "./database";
7
+ import { LOSSLESS_CONTENT_CODEC_VERSION } from "./lossless-json";
8
8
  import * as schema from "./schema";
9
9
 
10
10
  const SESSION_REALTIME_MIRROR_MAX_TEXT_BYTES = 131_072;
@@ -59,7 +59,7 @@ function boundedText(value: string): string {
59
59
  }
60
60
 
61
61
  function boundedPayload(value: Record<string, unknown>): Record<string, unknown> {
62
- const payload = sanitizeEventPayload(value);
62
+ const payload = value;
63
63
  if (
64
64
  Buffer.byteLength(JSON.stringify(payload), "utf8") > SESSION_REALTIME_MIRROR_MAX_PAYLOAD_BYTES
65
65
  ) {
@@ -137,7 +137,9 @@ export async function mirrorSessionRealtimeContextInTransaction(
137
137
  direction: "provider_out",
138
138
  kind: "session_update",
139
139
  text: boundedText(input.text),
140
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
140
141
  payload,
142
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
141
143
  createdAt: now,
142
144
  updatedAt: now,
143
145
  })
@@ -1,9 +1,18 @@
1
1
  import { createHash } from "node:crypto";
2
2
 
3
+ import {
4
+ retainedOutputUnavailable,
5
+ validateRetainedOutputEvidence,
6
+ type RetainedOutputEvidence,
7
+ } from "@opengeni/contracts";
3
8
  import { and, eq, inArray, sql } from "drizzle-orm";
4
9
 
5
- import { sanitizeEventPayload } from "./event-payload-sanitizer";
6
10
  import type { Database } from "./database";
11
+ import {
12
+ fromPostgresLosslessJson,
13
+ fromPostgresLosslessText,
14
+ LOSSLESS_CONTENT_CODEC_VERSION,
15
+ } from "./lossless-json";
7
16
  import { mirrorSessionRealtimeContextInTransaction } from "./session-realtime-mirror";
8
17
  import * as schema from "./schema";
9
18
 
@@ -17,9 +26,11 @@ export type ProjectSessionRealtimeDelegationTerminalInput = {
17
26
  turnId: string;
18
27
  turnStatus: "completed" | "failed" | "cancelled" | "superseded";
19
28
  terminalEvent: {
29
+ id: string | null;
20
30
  type: "turn.completed" | "turn.failed" | "turn.cancelled" | "turn.superseded";
21
31
  payload: Record<string, unknown>;
22
32
  };
33
+ retainedOutputEvidence?: unknown;
23
34
  now?: Date;
24
35
  };
25
36
 
@@ -71,20 +82,48 @@ function deterministicTerminalOperationId(turnId: string): string {
71
82
  }
72
83
 
73
84
  function boundedPayload(input: Record<string, unknown> | undefined): Record<string, unknown> {
74
- const payload = sanitizeEventPayload(input ?? {});
85
+ const payload = input ?? {};
75
86
  if (Buffer.byteLength(JSON.stringify(payload), "utf8") > MAX_PAYLOAD_BYTES) {
76
87
  throw new Error("Realtime payload exceeds the durable ledger limit");
77
88
  }
78
89
  return payload;
79
90
  }
80
91
 
81
- function assertBoundedText(value: string, label: string): void {
82
- if (Buffer.byteLength(value, "utf8") > MAX_TEXT_BYTES) {
83
- throw new Error(`${label} exceeds the durable realtime ledger limit`);
84
- }
92
+ function payloadBytes(value: Record<string, unknown>): number {
93
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
94
+ }
95
+
96
+ function fitsRealtimePayload(value: Record<string, unknown>): boolean {
97
+ return payloadBytes(value) <= MAX_PAYLOAD_BYTES;
98
+ }
99
+
100
+ function retainedEvidence(value: unknown): RetainedOutputEvidence {
101
+ return validateRetainedOutputEvidence(value) ?? retainedOutputUnavailable();
102
+ }
103
+
104
+ function terminalReference(
105
+ input: ProjectSessionRealtimeDelegationTerminalInput,
106
+ terminal: Record<string, unknown>,
107
+ ): Record<string, unknown> {
108
+ const output = typeof terminal.output === "string" ? terminal.output : null;
109
+ return {
110
+ canonicalEvent: {
111
+ id: input.terminalEvent.id,
112
+ type: input.terminalEvent.type,
113
+ },
114
+ truncation: {
115
+ truncated: true,
116
+ reason: "realtime_ledger_limit",
117
+ originalBytes: payloadBytes(terminal),
118
+ outputBytes: output === null ? null : Buffer.byteLength(output, "utf8"),
119
+ fullEvidence: retainedEvidence(input.retainedOutputEvidence),
120
+ },
121
+ };
85
122
  }
86
123
 
87
- function terminalProjection(input: ProjectSessionRealtimeDelegationTerminalInput): {
124
+ export function buildSessionRealtimeDelegationTerminalProjection(
125
+ input: ProjectSessionRealtimeDelegationTerminalInput,
126
+ ): {
88
127
  kind: "delegation_result" | "error";
89
128
  text: string;
90
129
  payload: Record<string, unknown>;
@@ -95,19 +134,33 @@ function terminalProjection(input: ProjectSessionRealtimeDelegationTerminalInput
95
134
  `Realtime delegation terminal event ${input.terminalEvent.type} does not match ${input.turnStatus}`,
96
135
  );
97
136
  }
98
- const terminal = boundedPayload(input.terminalEvent.payload);
137
+ const terminal = input.terminalEvent.payload;
99
138
  if (input.turnStatus === "completed") {
100
139
  const output =
101
140
  typeof terminal.output === "string" ? terminal.output : "Delegated turn completed.";
102
- assertBoundedText(output, "Delegation result");
141
+ const exactPayload = {
142
+ status: input.turnStatus,
143
+ turnId: input.turnId,
144
+ terminalEventId: input.terminalEvent.id,
145
+ terminalEventType: input.terminalEvent.type,
146
+ terminal,
147
+ };
148
+ if (Buffer.byteLength(output, "utf8") <= MAX_TEXT_BYTES && fitsRealtimePayload(exactPayload)) {
149
+ return {
150
+ kind: "delegation_result",
151
+ text: output,
152
+ payload: exactPayload,
153
+ };
154
+ }
103
155
  return {
104
156
  kind: "delegation_result",
105
- text: output,
157
+ text: "Delegated turn completed. The full result remains in the canonical session event.",
106
158
  payload: boundedPayload({
107
159
  status: input.turnStatus,
108
160
  turnId: input.turnId,
161
+ terminalEventId: input.terminalEvent.id,
109
162
  terminalEventType: input.terminalEvent.type,
110
- terminal,
163
+ terminal: terminalReference(input, terminal),
111
164
  }),
112
165
  };
113
166
  }
@@ -119,16 +172,31 @@ function terminalProjection(input: ProjectSessionRealtimeDelegationTerminalInput
119
172
  typeof terminal.error === "string" && terminal.error.length > 0
120
173
  ? terminal.error
121
174
  : `Delegated turn ${input.turnStatus}.`;
122
- assertBoundedText(message, "Delegation error");
175
+ const exactPayload = {
176
+ code,
177
+ status: input.turnStatus,
178
+ turnId: input.turnId,
179
+ terminalEventId: input.terminalEvent.id,
180
+ terminalEventType: input.terminalEvent.type,
181
+ terminal,
182
+ };
183
+ if (Buffer.byteLength(message, "utf8") <= MAX_TEXT_BYTES && fitsRealtimePayload(exactPayload)) {
184
+ return {
185
+ kind: "error",
186
+ text: message,
187
+ payload: exactPayload,
188
+ };
189
+ }
123
190
  return {
124
191
  kind: "error",
125
- text: message,
192
+ text: `Delegated turn ${input.turnStatus}. The full diagnostic remains in the canonical session event.`,
126
193
  payload: boundedPayload({
127
- code,
194
+ ...(Buffer.byteLength(code, "utf8") <= 256 ? { code } : {}),
128
195
  status: input.turnStatus,
129
196
  turnId: input.turnId,
197
+ terminalEventId: input.terminalEvent.id,
130
198
  terminalEventType: input.terminalEvent.type,
131
- terminal,
199
+ terminal: terminalReference(input, terminal),
132
200
  }),
133
201
  };
134
202
  }
@@ -150,8 +218,8 @@ function mapEntry(
150
218
  sourceUpdateId: row.sourceUpdateId,
151
219
  historyItemId: row.historyItemId,
152
220
  turnId: row.turnId,
153
- text: row.text,
154
- payload: row.payload,
221
+ text: row.text === null ? null : fromPostgresLosslessText(row.text, row.textCodecVersion),
222
+ payload: fromPostgresLosslessJson(row.payload, row.payloadCodecVersion),
155
223
  clientAckedAt: row.clientAckedAt?.toISOString() ?? null,
156
224
  providerAckedAt: row.providerAckedAt?.toISOString() ?? null,
157
225
  createdAt: row.createdAt.toISOString(),
@@ -187,7 +255,7 @@ export async function projectSessionRealtimeDelegationTerminalInTransaction(
187
255
  if (calls.length > 1) {
188
256
  throw new Error(`Delegation turn ${input.turnId} has multiple accepted realtime calls`);
189
257
  }
190
- const projected = terminalProjection(input);
258
+ const projected = buildSessionRealtimeDelegationTerminalProjection(input);
191
259
  const now = input.now ?? new Date();
192
260
  const call = calls[0] ?? null;
193
261
  const [mode] = call
@@ -225,10 +293,8 @@ export async function projectSessionRealtimeDelegationTerminalInTransaction(
225
293
  channel: "speakable",
226
294
  text: projected.text,
227
295
  payload: {
296
+ ...projected.payload,
228
297
  route: "session_context",
229
- status: input.turnStatus,
230
- terminalEventType: input.terminalEvent.type,
231
- terminal: boundedPayload(input.terminalEvent.payload),
232
298
  ...(call ? { priorRealtimeId: call.realtimeId } : {}),
233
299
  },
234
300
  now,
@@ -296,7 +362,9 @@ export async function projectSessionRealtimeDelegationTerminalInTransaction(
296
362
  delegationItemId: call.delegationItemId,
297
363
  turnId: input.turnId,
298
364
  text: projected.text,
365
+ textCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
299
366
  payload,
367
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
300
368
  createdAt: now,
301
369
  updatedAt: now,
302
370
  })
@@ -8,8 +8,8 @@ import type {
8
8
  } from "@opengeni/contracts";
9
9
  import { and, asc, eq, gt, inArray, isNull, or } from "drizzle-orm";
10
10
 
11
- import { sanitizeEventPayload } from "./event-payload-sanitizer";
12
11
  import type { Database } from "./database";
12
+ import { LOSSLESS_CONTENT_CODEC_VERSION } from "./lossless-json";
13
13
  import { flushSessionRealtimeTranscriptTailInTransaction } from "./session-realtime-context";
14
14
  import {
15
15
  evaluateSessionControl,
@@ -185,7 +185,8 @@ async function appendRealtimeLifecycleEvent(
185
185
  sessionId: session.id,
186
186
  sequence,
187
187
  type,
188
- payload: sanitizeEventPayload(payload),
188
+ payload: payload,
189
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
189
190
  occurredAt: now,
190
191
  })
191
192
  .returning();
@@ -2,7 +2,7 @@ import type { SessionEvent } from "@opengeni/contracts";
2
2
  import { boundModelToolOutputItem } from "@opengeni/codex";
3
3
  import { and, asc, eq, sql } from "drizzle-orm";
4
4
  import type { Database } from "./database";
5
- import { sanitizeEventPayload, sanitizeModelPayload } from "./event-payload-sanitizer";
5
+ import { fromPostgresLosslessJson, LOSSLESS_CONTENT_CODEC_VERSION } from "./lossless-json";
6
6
  import * as schema from "./schema";
7
7
 
8
8
  export const TOOL_RESULT_TYPE_BY_CALL_TYPE: Readonly<Record<string, string>> = {
@@ -93,7 +93,7 @@ function mapEvent(row: typeof schema.sessionEvents.$inferSelect): SessionEvent {
93
93
  sessionId: row.sessionId,
94
94
  sequence: row.sequence,
95
95
  type: row.type as SessionEvent["type"],
96
- payload: row.payload,
96
+ payload: fromPostgresLosslessJson(row.payload, row.payloadCodecVersion),
97
97
  occurredAt: row.occurredAt.toISOString(),
98
98
  clientEventId: row.clientEventId,
99
99
  turnId: row.turnId,
@@ -115,7 +115,7 @@ export async function closePendingSessionToolCallsInTransaction(
115
115
  tx: Database,
116
116
  input: ClosePendingSessionToolCallsInput,
117
117
  ): Promise<{ sequence: number; events: SessionEvent[]; closed: number }> {
118
- const pending = await tx
118
+ const pendingRows = await tx
119
119
  .select()
120
120
  .from(schema.sessionPendingToolCalls)
121
121
  .where(
@@ -127,12 +127,21 @@ export async function closePendingSessionToolCallsInTransaction(
127
127
  )
128
128
  .orderBy(asc(schema.sessionPendingToolCalls.createdAt), asc(schema.sessionPendingToolCalls.id))
129
129
  .for("update");
130
+ const pending = pendingRows.map((row) => ({
131
+ ...row,
132
+ callItem: fromPostgresLosslessJson(row.callItem, row.callItemCodecVersion),
133
+ resultItem:
134
+ row.resultItem === null
135
+ ? null
136
+ : fromPostgresLosslessJson(row.resultItem, row.resultItemCodecVersion),
137
+ }));
130
138
  if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
131
139
 
132
140
  const history = await tx
133
141
  .select({
134
142
  position: schema.sessionHistoryItems.position,
135
143
  item: schema.sessionHistoryItems.item,
144
+ itemCodecVersion: schema.sessionHistoryItems.itemCodecVersion,
136
145
  active: schema.sessionHistoryItems.active,
137
146
  })
138
147
  .from(schema.sessionHistoryItems)
@@ -144,6 +153,10 @@ export async function closePendingSessionToolCallsInTransaction(
144
153
  ),
145
154
  )
146
155
  .orderBy(asc(schema.sessionHistoryItems.position));
156
+ const decodedHistory = history.map((row) => ({
157
+ ...row,
158
+ item: fromPostgresLosslessJson(row.item, row.itemCodecVersion),
159
+ }));
147
160
  const [{ maxPosition } = { maxPosition: -1 }] = await tx
148
161
  .select({ maxPosition: sql<number>`coalesce(max(${schema.sessionHistoryItems.position}), -1)` })
149
162
  .from(schema.sessionHistoryItems)
@@ -173,23 +186,23 @@ export async function closePendingSessionToolCallsInTransaction(
173
186
  const eventValues: Array<typeof schema.sessionEvents.$inferInsert> = [];
174
187
  const resolutions = pending.map((call) => {
175
188
  const resultType = TOOL_RESULT_TYPE_BY_CALL_TYPE[call.callType];
176
- const existingCall = history.find(
189
+ const existingCall = decodedHistory.find(
177
190
  ({ item }) => historyItemType(item) === call.callType && historyCallId(item) === call.callId,
178
191
  );
179
192
  const existingResult = resultType
180
- ? history.find(
193
+ ? decodedHistory.find(
181
194
  ({ item, position }) =>
182
195
  position > (existingCall?.position ?? Number.MAX_SAFE_INTEGER) &&
183
196
  historyItemType(item) === resultType &&
184
197
  historyCallId(item) === call.callId,
185
198
  )
186
199
  : undefined;
187
- const activeCall = history.find(
200
+ const activeCall = decodedHistory.find(
188
201
  ({ item, active }) =>
189
202
  active && historyItemType(item) === call.callType && historyCallId(item) === call.callId,
190
203
  );
191
204
  const activeResult = resultType
192
- ? history.find(
205
+ ? decodedHistory.find(
193
206
  ({ item, active, position }) =>
194
207
  active &&
195
208
  position > (activeCall?.position ?? Number.MAX_SAFE_INTEGER) &&
@@ -232,7 +245,8 @@ export async function closePendingSessionToolCallsInTransaction(
232
245
  sessionId: input.sessionId,
233
246
  turnId: input.turnId,
234
247
  position: nextPosition++,
235
- item: sanitizeModelPayload(resolution.call.callItem),
248
+ item: resolution.call.callItem,
249
+ itemCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
236
250
  active: true,
237
251
  });
238
252
  }
@@ -255,12 +269,11 @@ export async function closePendingSessionToolCallsInTransaction(
255
269
  sessionId: input.sessionId,
256
270
  turnId: input.turnId,
257
271
  position: nextPosition++,
258
- item: sanitizeModelPayload(
259
- boundModelToolOutputItem(
260
- resolution.result,
261
- resolution.call.modelToolOutputTruncationTokens ?? undefined,
262
- ),
272
+ item: boundModelToolOutputItem(
273
+ resolution.result,
274
+ resolution.call.modelToolOutputTruncationTokens ?? undefined,
263
275
  ),
276
+ itemCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
264
277
  active: true,
265
278
  });
266
279
  }
@@ -280,7 +293,7 @@ export async function closePendingSessionToolCallsInTransaction(
280
293
  turnGeneration: resolution.call.executionGeneration,
281
294
  turnAttemptId: resolution.call.attemptId,
282
295
  turnAssociation: "current",
283
- payload: sanitizeEventPayload({
296
+ payload: {
284
297
  id: resolution.call.callId,
285
298
  output: resolution.interrupted
286
299
  ? {
@@ -302,7 +315,8 @@ export async function closePendingSessionToolCallsInTransaction(
302
315
  unsupportedCallShape:
303
316
  resolution.interrupted && (!resolution.result || !resolution.rawCallIsValid),
304
317
  },
305
- }),
318
+ },
319
+ payloadCodecVersion: LOSSLESS_CONTENT_CODEC_VERSION,
306
320
  occurredAt: input.now,
307
321
  });
308
322
  }
@@ -1,4 +1,5 @@
1
1
  import { sql } from "drizzle-orm";
2
+ import { losslessCodecVersion, losslessText } from "./lossless-columns";
2
3
  import {
3
4
  boolean,
4
5
  check,
@@ -51,7 +52,8 @@ export const transcriptionRecordings = pgTable(
51
52
  totalDurationMilliseconds: integer("total_duration_milliseconds").notNull().default(0),
52
53
  segmentCount: integer("segment_count").notNull().default(0),
53
54
  completedSegmentCount: integer("completed_segment_count").notNull().default(0),
54
- transcriptText: text("transcript_text"),
55
+ transcriptText: losslessText("transcript_text"),
56
+ transcriptTextCodecVersion: losslessCodecVersion("transcript_text_codec_version"),
55
57
  languages: jsonb("languages").$type<string[]>().notNull().default([]),
56
58
  errorCode: text("error_code"),
57
59
  retryable: boolean("retryable").notNull().default(false),
@@ -209,7 +211,8 @@ export const transcriptionRecordingSegments = pgTable(
209
211
  attemptId: uuid("attempt_id"),
210
212
  attemptStartedAt: timestamp("attempt_started_at", { withTimezone: true }),
211
213
  attemptDeadlineAt: timestamp("attempt_deadline_at", { withTimezone: true }),
212
- transcriptText: text("transcript_text"),
214
+ transcriptText: losslessText("transcript_text"),
215
+ transcriptTextCodecVersion: losslessCodecVersion("transcript_text_codec_version"),
213
216
  languages: jsonb("languages").$type<string[]>().notNull().default([]),
214
217
  providerId: text("provider_id"),
215
218
  errorCode: text("error_code"),