@opengeni/db 0.27.11 → 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.
- package/dist/{chunk-JYQPJ6Y5.js → chunk-P6CUHXQZ.js} +2967 -2272
- package/dist/chunk-P6CUHXQZ.js.map +1 -0
- package/dist/{chunk-RPHPNVWW.js → chunk-VHBFHMPC.js} +15 -1
- package/dist/chunk-VHBFHMPC.js.map +1 -0
- package/dist/environment-crypto.d.ts +8 -4
- package/dist/index.d.ts +272 -26
- package/dist/index.js +7798 -5008
- package/dist/index.js.map +1 -1
- package/dist/lossless-columns.d.ts +58 -0
- package/dist/lossless-json.d.ts +39 -0
- package/dist/memory-domain.d.ts +3 -6
- package/dist/persistence-errors.d.ts +7 -14
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +1420 -271
- package/dist/schema.js +25 -1
- package/dist/session-realtime-terminal.d.ts +7 -0
- package/dist/transcription-recordings-schema.d.ts +1264 -0
- package/dist/transcription-recordings.d.ts +228 -0
- package/drizzle/0065_enrollment_credential_generation.sql +10 -0
- package/drizzle/0140_retained_screenshot_artifacts.sql +192 -0
- package/drizzle/0170_resumable_transcription_recordings.sql +440 -0
- package/drizzle/0175_resumable_transcription_provider_deadline.sql +24 -0
- package/drizzle/0176_lossless_canonical_json.sql +975 -0
- package/drizzle/0177_session_events_workspace_turn_type_index.sql +5 -0
- package/drizzle/0178_permissioned_secret_reads.sql +14 -0
- package/drizzle/0179_slack_private_shortcut_delivery_gate.sql +85 -0
- package/drizzle/0180_retained_screenshot_lifecycle_fences.sql +273 -0
- package/drizzle/0181_connected_machine_removal.sql +52 -0
- package/drizzle/0182_connected_machine_remove_session_default.sql +77 -0
- package/package.json +4 -4
- package/src/database.ts +7 -1
- package/src/environment-crypto.ts +22 -9
- package/src/index.ts +4368 -1678
- package/src/lossless-columns.ts +35 -0
- package/src/lossless-json.ts +322 -0
- package/src/memory-domain.ts +5 -44
- package/src/persistence-errors.ts +29 -34
- package/src/runtime-posture.ts +14 -0
- package/src/schema.ts +264 -42
- package/src/session-control.ts +125 -76
- package/src/session-queue-commands.ts +325 -234
- package/src/session-realtime-context.ts +18 -5
- package/src/session-realtime-ledger.ts +29 -7
- package/src/session-realtime-mirror.ts +4 -2
- package/src/session-realtime-terminal.ts +89 -21
- package/src/session-realtime.ts +3 -2
- package/src/session-tool-call-settlement.ts +29 -15
- package/src/transcription-recordings-schema.ts +253 -0
- package/src/transcription-recordings.ts +1538 -0
- package/dist/chunk-JYQPJ6Y5.js.map +0 -1
- package/dist/chunk-RPHPNVWW.js.map +0 -1
- package/dist/event-payload-sanitizer.d.ts +0 -32
- 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
|
|
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:
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
1030
|
-
JSON.stringify(canonicalJsonValue(
|
|
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 }) =>
|
|
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 =
|
|
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 =
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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
|
})
|
package/src/session-realtime.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
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
|
|
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 =
|
|
189
|
+
const existingCall = decodedHistory.find(
|
|
177
190
|
({ item }) => historyItemType(item) === call.callType && historyCallId(item) === call.callId,
|
|
178
191
|
);
|
|
179
192
|
const existingResult = resultType
|
|
180
|
-
?
|
|
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 =
|
|
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
|
-
?
|
|
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:
|
|
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:
|
|
259
|
-
|
|
260
|
-
|
|
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:
|
|
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
|
}
|