@opengeni/sdk 0.57.0 → 2.2.0-canary.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 (94) hide show
  1. package/README.md +123 -2
  2. package/dist/artifacts.js +5 -5
  3. package/dist/automations-client.d.ts +17 -0
  4. package/dist/automations.d.ts +3 -0
  5. package/dist/automations.js +78 -0
  6. package/dist/automations.js.map +1 -0
  7. package/dist/{chunk-JDLDDRNE.js → chunk-C2H7HBNP.js} +290 -33
  8. package/dist/chunk-C2H7HBNP.js.map +1 -0
  9. package/dist/{chunk-GU6JF75T.js → chunk-C4DJPZRU.js} +1 -1
  10. package/dist/{chunk-GU6JF75T.js.map → chunk-C4DJPZRU.js.map} +1 -1
  11. package/dist/{chunk-JRPFWI6T.js → chunk-G6LBVWKT.js} +2 -2
  12. package/dist/{chunk-HJ3HHUT5.js → chunk-GLDV5QY7.js} +16 -2
  13. package/dist/chunk-GLDV5QY7.js.map +1 -0
  14. package/dist/{chunk-ST5DDKJP.js → chunk-GW3EJ2GP.js} +52 -1
  15. package/dist/chunk-GW3EJ2GP.js.map +1 -0
  16. package/dist/{chunk-VWE2QIVO.js → chunk-XNG7YGR3.js} +23 -6
  17. package/dist/chunk-XNG7YGR3.js.map +1 -0
  18. package/dist/{chunk-PKQ377ED.js → chunk-YBQTKNTL.js} +696 -57
  19. package/dist/chunk-YBQTKNTL.js.map +1 -0
  20. package/dist/client.d.ts +151 -26
  21. package/dist/codex-realtime-controller.d.ts +3 -0
  22. package/dist/codex-realtime-controller.js +2 -2
  23. package/dist/codex-realtime-v3.d.ts +13 -2
  24. package/dist/company-brain.d.ts +32 -0
  25. package/dist/company-profile.d.ts +1 -1
  26. package/dist/core.js +4 -4
  27. package/dist/editable-artifacts/browser-session.d.ts +7 -3
  28. package/dist/editable-artifacts/controller.d.ts +4 -1
  29. package/dist/editable-artifacts/errors.d.ts +1 -1
  30. package/dist/editable-artifacts/http-live-transport.d.ts +6 -2
  31. package/dist/editable-artifacts/index.d.ts +2 -0
  32. package/dist/editable-artifacts/open-failure.d.ts +9 -0
  33. package/dist/editable-artifacts/types.d.ts +4 -3
  34. package/dist/editable-artifacts/worker/kernel-adapter.d.ts +1 -1
  35. package/dist/editable-artifacts-worker.js +44 -17
  36. package/dist/editable-artifacts-worker.js.map +1 -1
  37. package/dist/editable-artifacts.js +222 -119
  38. package/dist/editable-artifacts.js.map +1 -1
  39. package/dist/errors.d.ts +1 -1
  40. package/dist/index.d.ts +7 -4
  41. package/dist/index.js +61 -12
  42. package/dist/index.js.map +1 -1
  43. package/dist/interaction.d.ts +23 -0
  44. package/dist/interaction.js +4 -1
  45. package/dist/organization-private-session-settings.d.ts +6 -0
  46. package/dist/organization-private-session-settings.js +22 -0
  47. package/dist/organization-private-session-settings.js.map +1 -0
  48. package/dist/pr-review-client.d.ts +15 -0
  49. package/dist/pr-review.d.ts +4 -0
  50. package/dist/pr-review.js +56 -0
  51. package/dist/pr-review.js.map +1 -0
  52. package/dist/preference-registry.d.ts +8 -0
  53. package/dist/realtime.js +2 -2
  54. package/dist/retained-artifacts.d.ts +5 -1
  55. package/dist/types.d.ts +976 -17
  56. package/dist/workspace-instruction-policies.d.ts +22 -0
  57. package/dist/workspace-learning.d.ts +124 -0
  58. package/package.json +14 -2
  59. package/src/automations-client.ts +118 -0
  60. package/src/automations.ts +15 -0
  61. package/src/client.ts +974 -71
  62. package/src/codex-realtime-controller.ts +235 -5
  63. package/src/codex-realtime-v3.ts +162 -31
  64. package/src/company-brain.ts +99 -0
  65. package/src/company-profile.ts +4 -1
  66. package/src/editable-artifacts/browser-session.ts +36 -22
  67. package/src/editable-artifacts/controller.ts +66 -45
  68. package/src/editable-artifacts/errors.ts +2 -0
  69. package/src/editable-artifacts/http-live-transport.ts +62 -37
  70. package/src/editable-artifacts/index.ts +6 -0
  71. package/src/editable-artifacts/open-failure.ts +79 -0
  72. package/src/editable-artifacts/storage.ts +31 -40
  73. package/src/editable-artifacts/types.ts +4 -3
  74. package/src/editable-artifacts/worker/kernel-adapter.ts +23 -7
  75. package/src/editable-artifacts/worker/runtime.ts +26 -13
  76. package/src/editable-artifacts/worker/wire-codec.ts +23 -5
  77. package/src/errors.ts +1 -1
  78. package/src/index.ts +131 -0
  79. package/src/interaction.ts +95 -0
  80. package/src/organization-private-session-settings.ts +33 -0
  81. package/src/pr-review-client.ts +84 -0
  82. package/src/pr-review.ts +15 -0
  83. package/src/preference-registry.ts +9 -0
  84. package/src/retained-artifacts.ts +101 -0
  85. package/src/stream.ts +13 -2
  86. package/src/types.ts +1117 -16
  87. package/src/workspace-instruction-policies.ts +24 -0
  88. package/src/workspace-learning.ts +138 -0
  89. package/dist/chunk-HJ3HHUT5.js.map +0 -1
  90. package/dist/chunk-JDLDDRNE.js.map +0 -1
  91. package/dist/chunk-PKQ377ED.js.map +0 -1
  92. package/dist/chunk-ST5DDKJP.js.map +0 -1
  93. package/dist/chunk-VWE2QIVO.js.map +0 -1
  94. /package/dist/{chunk-JRPFWI6T.js.map → chunk-G6LBVWKT.js.map} +0 -0
@@ -33,6 +33,7 @@ import type {
33
33
  GatewayRealtimeConnectResponse,
34
34
  EndSessionRealtimeRequest,
35
35
  RenewSessionRealtimeRequest,
36
+ SessionRealtimeInboundEntry,
36
37
  SessionRealtimeMode,
37
38
  SessionRealtimeModel,
38
39
  SessionRealtimeMutationResponse,
@@ -49,7 +50,29 @@ export const CODEX_REALTIME_NEGOTIATION_TIMEOUT_MS = 20_000;
49
50
  // OpenGeni policy: rotate conservatively without asserting an upstream lifetime.
50
51
  const DEFAULT_CONNECTION_ROTATION_INTERVAL_MS = 15 * 60_000;
51
52
  const DEFAULT_RECONNECT_BACKOFF_MS = [250, 1_000, 2_000, 5_000] as const;
53
+ const MAX_BROWSER_TIMEOUT_MS = 2_147_483_647;
52
54
  const OWNER_RECORD_VERSION = 1;
55
+ const OWNER_DELEGATION_REPLAY_VERSION = 1;
56
+ const OWNER_DELEGATION_REPLAY_MAX_CALLS = 4_096;
57
+ const OWNER_DELEGATION_REPLAY_MAX_BYTES = 4 * 1024 * 1024;
58
+ const SESSION_REALTIME_INBOUND_ENTRY_KEYS = new Set([
59
+ "operationId",
60
+ "kind",
61
+ "role",
62
+ "providerEventId",
63
+ "delegationItemId",
64
+ "text",
65
+ "payload",
66
+ "modelContext",
67
+ ]);
68
+ const SESSION_REALTIME_INBOUND_KINDS = new Set([
69
+ "user_transcript",
70
+ "assistant_transcript",
71
+ "delegation_call",
72
+ "interruption",
73
+ "error",
74
+ ]);
75
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
53
76
 
54
77
  export type CodexRealtimeControllerStatus =
55
78
  | "idle"
@@ -202,6 +225,7 @@ export type CreateCodexRealtimeControllerOptions = {
202
225
  createPeerConnection?: (() => RTCPeerConnection) | undefined;
203
226
  getUserMedia?: ((constraints: MediaStreamConstraints) => Promise<MediaStream>) | undefined;
204
227
  randomUUID?: (() => string) | undefined;
228
+ now?: (() => Date) | undefined;
205
229
  setInterval?: ((callback: () => void, delayMs: number) => unknown) | undefined;
206
230
  clearInterval?: ((handle: unknown) => void) | undefined;
207
231
  setTimeout?: ((callback: () => void, delayMs: number) => unknown) | undefined;
@@ -213,6 +237,8 @@ export type CreateCodexRealtimeControllerOptions = {
213
237
  model?: SessionRealtimeModel | undefined;
214
238
  ownerStorageNamespace?: string | undefined;
215
239
  startTransport?: RealtimeControllerTransportStarter | undefined;
240
+ /** Model-visible application context captured with each durable realtime message. */
241
+ getModelContext?: (() => string | undefined) | undefined;
216
242
  };
217
243
 
218
244
  export type RealtimeControllerTransportStarter = (input: {
@@ -258,6 +284,13 @@ type OwnerRecord = {
258
284
  operationId: string;
259
285
  browserInstanceId: string;
260
286
  ownerKey: string;
287
+ delegationReplay?: OwnerDelegationReplay | undefined;
288
+ };
289
+
290
+ type OwnerDelegationReplay = {
291
+ version: typeof OWNER_DELEGATION_REPLAY_VERSION;
292
+ acceptedDelegationItemIds: string[];
293
+ pendingDelegations: SessionRealtimeInboundEntry[];
261
294
  };
262
295
 
263
296
  type ConnectionRuntime = {
@@ -285,6 +318,7 @@ export function createCodexRealtimeController(
285
318
  );
286
319
  const model = options.model ?? "gpt-live-1-boulder-alpha";
287
320
  const randomUUID = options.randomUUID ?? defaultRandomUUID;
321
+ const now = options.now ?? (() => new Date());
288
322
  const scheduleInterval =
289
323
  options.setInterval ?? ((callback, delay) => globalThis.setInterval(callback, delay));
290
324
  const unscheduleInterval =
@@ -331,6 +365,7 @@ export function createCodexRealtimeController(
331
365
  let rotationTimer: unknown = null;
332
366
  let reconnectTimer: unknown = null;
333
367
  let negotiationTimer: unknown = null;
368
+ let lostOwnerExpiryTimer: unknown = null;
334
369
  let closed = false;
335
370
  let stopping = false;
336
371
  let generation = 0;
@@ -338,6 +373,59 @@ export function createCodexRealtimeController(
338
373
  let recoveryTerminal = false;
339
374
  let mutationTail = Promise.resolve();
340
375
  let connectionTask: Promise<void> | null = null;
376
+ const acceptedDelegationItemIds = new Set(
377
+ owner?.delegationReplay?.acceptedDelegationItemIds ?? [],
378
+ );
379
+ const pendingDelegations = new Map(
380
+ (owner?.delegationReplay?.pendingDelegations ?? []).flatMap((entry) =>
381
+ entry.delegationItemId ? [[entry.delegationItemId, entry] as const] : [],
382
+ ),
383
+ );
384
+
385
+ const invalidateStoredOwner = (): void => {
386
+ try {
387
+ storage?.removeItem(storageKey);
388
+ } catch {
389
+ // Best effort only. The bridge still fails closed and retains the exact
390
+ // in-memory snapshot for this controller's bounded recovery attempts.
391
+ }
392
+ };
393
+
394
+ const persistDelegationReplay = (input: {
395
+ acceptedDelegationItemIds: ReadonlySet<string>;
396
+ pendingDelegations: ReadonlyMap<string, SessionRealtimeInboundEntry>;
397
+ }): void => {
398
+ if (!owner || !storage) return;
399
+ const delegationReplay: OwnerDelegationReplay = {
400
+ version: OWNER_DELEGATION_REPLAY_VERSION,
401
+ acceptedDelegationItemIds: [...input.acceptedDelegationItemIds],
402
+ pendingDelegations: [...input.pendingDelegations.values()],
403
+ };
404
+ if (
405
+ delegationReplay.pendingDelegations.length > CODEX_REALTIME_V3_PENDING_MAX_ENTRIES ||
406
+ delegationReplay.acceptedDelegationItemIds.length +
407
+ delegationReplay.pendingDelegations.length >
408
+ OWNER_DELEGATION_REPLAY_MAX_CALLS
409
+ ) {
410
+ invalidateStoredOwner();
411
+ throw new Error("Realtime delegation replay journal exceeded its call limit");
412
+ }
413
+ const next: OwnerRecord = { ...owner, delegationReplay };
414
+ const serialized = JSON.stringify(next);
415
+ if (new TextEncoder().encode(serialized).byteLength > OWNER_DELEGATION_REPLAY_MAX_BYTES) {
416
+ invalidateStoredOwner();
417
+ throw new Error("Realtime delegation replay journal exceeded its byte limit");
418
+ }
419
+ try {
420
+ storage.setItem(storageKey, serialized);
421
+ } catch (error) {
422
+ // Never leave older ownership proof reloadable without the delegation
423
+ // snapshot that the active bridge just froze.
424
+ invalidateStoredOwner();
425
+ throw error;
426
+ }
427
+ owner = next;
428
+ };
341
429
 
342
430
  const publish = (patch: Partial<CodexRealtimeControllerSnapshot>): void => {
343
431
  state = { ...state, ...patch };
@@ -372,11 +460,13 @@ export function createCodexRealtimeController(
372
460
  if (rotationTimer !== null) unscheduleTimeout(rotationTimer);
373
461
  if (reconnectTimer !== null) unscheduleTimeout(reconnectTimer);
374
462
  if (negotiationTimer !== null) unscheduleTimeout(negotiationTimer);
463
+ if (lostOwnerExpiryTimer !== null) unscheduleTimeout(lostOwnerExpiryTimer);
375
464
  heartbeatTimer = null;
376
465
  syncTimer = null;
377
466
  rotationTimer = null;
378
467
  reconnectTimer = null;
379
468
  negotiationTimer = null;
469
+ lostOwnerExpiryTimer = null;
380
470
  };
381
471
 
382
472
  const stopNegotiationTimers = (): void => {
@@ -422,6 +512,8 @@ export function createCodexRealtimeController(
422
512
  const clearOwner = (): void => {
423
513
  owner = null;
424
514
  storage?.removeItem(storageKey);
515
+ acceptedDelegationItemIds.clear();
516
+ pendingDelegations.clear();
425
517
  };
426
518
 
427
519
  const transitionEnded = (message = "Realtime mode ended"): void => {
@@ -524,7 +616,10 @@ export function createCodexRealtimeController(
524
616
  });
525
617
  return;
526
618
  }
527
- publish({ audibleOutput: next, ...(next === "audible" ? { error: null } : {}) });
619
+ publish({
620
+ audibleOutput: next,
621
+ ...(next === "audible" ? { error: null } : {}),
622
+ });
528
623
  };
529
624
 
530
625
  const startActiveIntervals = (): void => {
@@ -911,6 +1006,10 @@ export function createCodexRealtimeController(
911
1006
  sync: async (request) =>
912
1007
  await syncForGeneration(targetGeneration, activated.mode.id, request),
913
1008
  randomUUID,
1009
+ ...(options.getModelContext ? { getModelContext: options.getModelContext } : {}),
1010
+ acceptedDelegationItemIds,
1011
+ pendingDelegations,
1012
+ onDelegationReplayStateChange: persistDelegationReplay,
914
1013
  onSnapshot: (nextBridge) => {
915
1014
  if (active?.generation === targetGeneration) publish({ bridge: nextBridge });
916
1015
  },
@@ -1097,7 +1196,12 @@ export function createCodexRealtimeController(
1097
1196
  await handleConnectionFailure(error, "reconnect", false);
1098
1197
  } else {
1099
1198
  clearOwner();
1100
- publish({ status: "error", realtimeId: null, mode: null, error: safeError(error) });
1199
+ publish({
1200
+ status: "error",
1201
+ realtimeId: null,
1202
+ mode: null,
1203
+ error: safeError(error),
1204
+ });
1101
1205
  }
1102
1206
  throw error;
1103
1207
  }
@@ -1157,6 +1261,12 @@ export function createCodexRealtimeController(
1157
1261
  if (!record || record.operationId !== lifecycle.operationId) {
1158
1262
  closeBrowserResources();
1159
1263
  owner = null;
1264
+ const leaseExpiresAt = Date.parse(lifecycle.leaseExpiresAt);
1265
+ const remainingLeaseMs = leaseExpiresAt - now().getTime();
1266
+ if (Number.isFinite(remainingLeaseMs) && remainingLeaseMs <= 0) {
1267
+ transitionEnded("Realtime lease expired");
1268
+ return;
1269
+ }
1160
1270
  const message =
1161
1271
  "Realtime is active in another browser owner. It can resume after that owner stops or its lease expires.";
1162
1272
  publish({
@@ -1167,13 +1277,26 @@ export function createCodexRealtimeController(
1167
1277
  diagnostic: diagnostic("lost_owner", message, false),
1168
1278
  error: message,
1169
1279
  });
1280
+ if (Number.isFinite(remainingLeaseMs) && remainingLeaseMs <= MAX_BROWSER_TIMEOUT_MS) {
1281
+ const observedRealtimeId = lifecycle.realtimeId;
1282
+ lostOwnerExpiryTimer = scheduleTimeout(() => {
1283
+ lostOwnerExpiryTimer = null;
1284
+ if (state.status === "lost_owner" && state.realtimeId === observedRealtimeId) {
1285
+ transitionEnded("Realtime lease expired");
1286
+ }
1287
+ }, remainingLeaseMs);
1288
+ }
1170
1289
  return;
1171
1290
  }
1172
1291
  if (connectionTask || state.status === "starting") return;
1173
1292
  closed = false;
1174
1293
  stopping = false;
1175
1294
  owner = record;
1176
- publish({ status: "recovering", realtimeId: lifecycle.realtimeId, error: null });
1295
+ publish({
1296
+ status: "recovering",
1297
+ realtimeId: lifecycle.realtimeId,
1298
+ error: null,
1299
+ });
1177
1300
  connectionTask = begin(record, true)
1178
1301
  .then(() => undefined)
1179
1302
  .catch(async (error) => {
@@ -1377,8 +1500,13 @@ function readOwnerRecord(
1377
1500
  ): OwnerRecord | null {
1378
1501
  const raw = storage?.getItem(key);
1379
1502
  if (!raw) return null;
1503
+ if (new TextEncoder().encode(raw).byteLength > OWNER_DELEGATION_REPLAY_MAX_BYTES) {
1504
+ storage?.removeItem(key);
1505
+ return null;
1506
+ }
1380
1507
  try {
1381
1508
  const parsed = recordValue(JSON.parse(raw));
1509
+ const delegationReplay = readOwnerDelegationReplay(parsed?.delegationReplay);
1382
1510
  if (
1383
1511
  parsed?.version !== OWNER_RECORD_VERSION ||
1384
1512
  parsed.workspaceId !== scope.workspaceId ||
@@ -1386,18 +1514,120 @@ function readOwnerRecord(
1386
1514
  !stringValue(parsed.operationId) ||
1387
1515
  !stringValue(parsed.browserInstanceId) ||
1388
1516
  !stringValue(parsed.ownerKey) ||
1389
- String(parsed.ownerKey).length < 32
1517
+ String(parsed.ownerKey).length < 32 ||
1518
+ delegationReplay === null
1390
1519
  ) {
1391
1520
  storage?.removeItem(key);
1392
1521
  return null;
1393
1522
  }
1394
- return parsed as OwnerRecord;
1523
+ return {
1524
+ ...(parsed as Omit<OwnerRecord, "delegationReplay">),
1525
+ ...(delegationReplay ? { delegationReplay } : {}),
1526
+ };
1395
1527
  } catch {
1396
1528
  storage?.removeItem(key);
1397
1529
  return null;
1398
1530
  }
1399
1531
  }
1400
1532
 
1533
+ function readOwnerDelegationReplay(value: unknown): OwnerDelegationReplay | undefined | null {
1534
+ if (value === undefined) return undefined;
1535
+ const record = recordValue(value);
1536
+ if (
1537
+ record?.version !== OWNER_DELEGATION_REPLAY_VERSION ||
1538
+ !Array.isArray(record.acceptedDelegationItemIds) ||
1539
+ !Array.isArray(record.pendingDelegations) ||
1540
+ record.acceptedDelegationItemIds.length + record.pendingDelegations.length >
1541
+ OWNER_DELEGATION_REPLAY_MAX_CALLS ||
1542
+ record.pendingDelegations.length > CODEX_REALTIME_V3_PENDING_MAX_ENTRIES
1543
+ ) {
1544
+ return null;
1545
+ }
1546
+ const acceptedDelegationItemIds: string[] = [];
1547
+ const accepted = new Set<string>();
1548
+ for (const candidate of record.acceptedDelegationItemIds) {
1549
+ if (typeof candidate !== "string" || candidate.length < 1 || candidate.length > 1_024) {
1550
+ return null;
1551
+ }
1552
+ if (!accepted.has(candidate)) {
1553
+ accepted.add(candidate);
1554
+ acceptedDelegationItemIds.push(candidate);
1555
+ }
1556
+ }
1557
+ const pendingDelegations: SessionRealtimeInboundEntry[] = [];
1558
+ const pendingIds = new Set<string>();
1559
+ for (const candidate of record.pendingDelegations) {
1560
+ const parsed = readSessionRealtimeInboundEntry(candidate);
1561
+ if (
1562
+ !parsed ||
1563
+ parsed.kind !== "delegation_call" ||
1564
+ !parsed.delegationItemId ||
1565
+ accepted.has(parsed.delegationItemId) ||
1566
+ pendingIds.has(parsed.delegationItemId)
1567
+ ) {
1568
+ return null;
1569
+ }
1570
+ pendingIds.add(parsed.delegationItemId);
1571
+ pendingDelegations.push(parsed);
1572
+ }
1573
+ const replay: OwnerDelegationReplay = {
1574
+ version: OWNER_DELEGATION_REPLAY_VERSION,
1575
+ acceptedDelegationItemIds,
1576
+ pendingDelegations,
1577
+ };
1578
+ if (
1579
+ new TextEncoder().encode(JSON.stringify(replay)).byteLength > OWNER_DELEGATION_REPLAY_MAX_BYTES
1580
+ ) {
1581
+ return null;
1582
+ }
1583
+ return replay;
1584
+ }
1585
+
1586
+ function readSessionRealtimeInboundEntry(value: unknown): SessionRealtimeInboundEntry | null {
1587
+ const record = recordValue(value);
1588
+ if (
1589
+ !record ||
1590
+ Object.keys(record).some((key) => !SESSION_REALTIME_INBOUND_ENTRY_KEYS.has(key)) ||
1591
+ typeof record.operationId !== "string" ||
1592
+ !UUID_PATTERN.test(record.operationId) ||
1593
+ typeof record.kind !== "string" ||
1594
+ !SESSION_REALTIME_INBOUND_KINDS.has(record.kind) ||
1595
+ !optionalNullableEnum(record.role, ["user", "assistant"]) ||
1596
+ !optionalNullableBoundedString(record.providerEventId, 1_024) ||
1597
+ !optionalNullableBoundedString(record.delegationItemId, 1_024) ||
1598
+ !optionalNullableBoundedString(record.text, 131_072) ||
1599
+ (record.payload !== undefined && recordValue(record.payload) === null) ||
1600
+ !optionalModelContext(record.modelContext)
1601
+ ) {
1602
+ return null;
1603
+ }
1604
+ return record as SessionRealtimeInboundEntry;
1605
+ }
1606
+
1607
+ function optionalNullableEnum(value: unknown, allowed: readonly string[]): boolean {
1608
+ return (
1609
+ value === undefined || value === null || (typeof value === "string" && allowed.includes(value))
1610
+ );
1611
+ }
1612
+
1613
+ function optionalNullableBoundedString(value: unknown, maxLength: number): boolean {
1614
+ return (
1615
+ value === undefined ||
1616
+ value === null ||
1617
+ (typeof value === "string" && value.length <= maxLength)
1618
+ );
1619
+ }
1620
+
1621
+ function optionalModelContext(value: unknown): boolean {
1622
+ return (
1623
+ value === undefined ||
1624
+ (typeof value === "string" &&
1625
+ value.length >= 1 &&
1626
+ value.length <= 32_768 &&
1627
+ value.trim() === value)
1628
+ );
1629
+ }
1630
+
1401
1631
  function defaultStorage(): CodexRealtimeOwnerStorage | undefined {
1402
1632
  return typeof sessionStorage === "undefined" ? undefined : sessionStorage;
1403
1633
  }
@@ -35,9 +35,10 @@ export const CODEX_REALTIME_V3_PENDING_MAX_ENTRIES = 256;
35
35
  export const CODEX_REALTIME_V3_PENDING_MAX_BYTES = 16 * 1024 * 1024;
36
36
  const REALTIME_DELEGATION_TRANSCRIPT_MAX_BYTES = 65_536;
37
37
  const REALTIME_DELEGATION_INPUT_MAX_BYTES = 65_536;
38
+ const REALTIME_MODEL_CONTEXT_MAX_CHARACTERS = 32_768;
38
39
 
39
40
  export type CodexRealtimeV3BridgeFatal = {
40
- code: "pending_overflow";
41
+ code: "pending_overflow" | "replay_journal_failed";
41
42
  message: string;
42
43
  };
43
44
 
@@ -72,6 +73,19 @@ export type CodexRealtimeV3BridgeOptions = {
72
73
  >;
73
74
  sync(request: SyncSessionRealtimeLedgerRequest): Promise<SyncSessionRealtimeLedgerResponse>;
74
75
  randomUUID?: (() => string) | undefined;
76
+ /** Model-visible application context captured once for each durable message-bearing entry. */
77
+ getModelContext?: (() => string | undefined) | undefined;
78
+ /** Controller-lifetime delegation identities shared across provider connection rotations. */
79
+ acceptedDelegationItemIds?: Set<string> | undefined;
80
+ /** Unsynced delegation snapshots retained exactly across provider connection rotations. */
81
+ pendingDelegations?: Map<string, SessionRealtimeInboundEntry> | undefined;
82
+ /** Persist exact delegation replay state before it becomes browser-reload-sensitive. */
83
+ onDelegationReplayStateChange?:
84
+ | ((state: {
85
+ acceptedDelegationItemIds: ReadonlySet<string>;
86
+ pendingDelegations: ReadonlyMap<string, SessionRealtimeInboundEntry>;
87
+ }) => void)
88
+ | undefined;
75
89
  /** The controller installs its activation FIFO first, then enables this listener synchronously. */
76
90
  listen?: boolean | undefined;
77
91
  onSnapshot?: ((snapshot: CodexRealtimeV3BridgeSnapshot) => void) | undefined;
@@ -125,9 +139,32 @@ export function createCodexRealtimeV3Bridge(
125
139
  const clientReceivedSequences = new Set<number>();
126
140
  const sentSequences = new Set<number>();
127
141
  const finalizedTurnIds = new Set<string>();
142
+ const acceptedDelegationItemIds = options.acceptedDelegationItemIds ?? new Set<string>();
143
+ const pendingDelegations = options.pendingDelegations ?? new Map();
144
+ const locallyQueuedDelegationItemIds = new Set<string>();
128
145
  let transcriptSinceDelegation: FinalizedTranscript[] = [];
129
- let pendingDelegationUserTranscript: { delegationItemId: string; text: string } | null = null;
146
+ let pendingDelegationUserTranscript: {
147
+ delegationItemId: string;
148
+ text: string;
149
+ } | null = null;
130
150
  const randomUUID = options.randomUUID ?? defaultRandomUUID;
151
+ const currentModelContext = (): string | undefined => {
152
+ let context: string | undefined;
153
+ try {
154
+ context = options.getModelContext?.()?.trim();
155
+ } catch {
156
+ lastError =
157
+ "Realtime model context callback failed; the provider message continued without application context";
158
+ return undefined;
159
+ }
160
+ if (!context) return undefined;
161
+ if (context.length > REALTIME_MODEL_CONTEXT_MAX_CHARACTERS) {
162
+ lastError =
163
+ "Realtime model context exceeded the 32768-character limit; the provider message continued without application context";
164
+ return undefined;
165
+ }
166
+ return context;
167
+ };
131
168
 
132
169
  const snapshot = (): CodexRealtimeV3BridgeSnapshot => ({
133
170
  connectionId: options.connectionId,
@@ -148,9 +185,9 @@ export function createCodexRealtimeV3Bridge(
148
185
  });
149
186
  const publish = (): void => options.onSnapshot?.(snapshot());
150
187
 
151
- const triggerFatal = (message: string): void => {
188
+ const triggerFatalCode = (code: CodexRealtimeV3BridgeFatal["code"], message: string): void => {
152
189
  if (closed || fatal) return;
153
- fatal = { code: "pending_overflow", message };
190
+ fatal = { code, message };
154
191
  lastError = message;
155
192
  publish();
156
193
  try {
@@ -161,6 +198,10 @@ export function createCodexRealtimeV3Bridge(
161
198
  }
162
199
  };
163
200
 
201
+ const triggerFatal = (message: string): void => {
202
+ triggerFatalCode("pending_overflow", message);
203
+ };
204
+
164
205
  const enqueue = (entry: SessionRealtimeInboundEntry): boolean => {
165
206
  if (closed || sealed || fatal) return false;
166
207
  const bytes = utf8ByteLength(JSON.stringify(entry));
@@ -177,6 +218,14 @@ export function createCodexRealtimeV3Bridge(
177
218
  return true;
178
219
  };
179
220
 
221
+ // Same-browser reload reconstructs this exact map from the persisted owner
222
+ // journal. Queue those first-frozen calls before listening to the replacement
223
+ // provider connection; startup proof or a duplicate call drives the normal
224
+ // flush path without resampling application context.
225
+ for (const [delegationItemId, entry] of pendingDelegations) {
226
+ if (enqueue(entry)) locallyQueuedDelegationItemIds.add(delegationItemId);
227
+ }
228
+
180
229
  const hasWork = (): boolean =>
181
230
  pendingInbound.length > 0 ||
182
231
  (!providerStartedAccepted && providerStarted !== undefined) ||
@@ -213,7 +262,46 @@ export function createCodexRealtimeV3Bridge(
213
262
  throw error;
214
263
  }
215
264
 
265
+ const acceptedAfterSync = new Set(acceptedDelegationItemIds);
266
+ const pendingAfterSync = new Map(pendingDelegations);
267
+ let delegationReplayChanged = false;
268
+ for (const item of batch) {
269
+ if (item.entry.kind === "delegation_call" && item.entry.delegationItemId) {
270
+ delegationReplayChanged = true;
271
+ acceptedAfterSync.add(item.entry.delegationItemId);
272
+ if (pendingAfterSync.get(item.entry.delegationItemId) === item.entry) {
273
+ pendingAfterSync.delete(item.entry.delegationItemId);
274
+ }
275
+ }
276
+ }
277
+ if (delegationReplayChanged) {
278
+ try {
279
+ options.onDelegationReplayStateChange?.({
280
+ acceptedDelegationItemIds: acceptedAfterSync,
281
+ pendingDelegations: pendingAfterSync,
282
+ });
283
+ } catch (error) {
284
+ // The server may already have admitted this exact batch. Keep it
285
+ // queued with the original operation identity and stop this bridge.
286
+ // Recovery can safely replay it because the prior pending journal
287
+ // state remains authoritative until the accepted transition writes.
288
+ pendingInbound = [...batch, ...pendingInbound];
289
+ triggerFatalCode(
290
+ "replay_journal_failed",
291
+ `Codex realtime delegation replay journal failed: ${safeError(error)}`,
292
+ );
293
+ return;
294
+ }
295
+ }
296
+
216
297
  for (const item of batch) {
298
+ if (item.entry.kind === "delegation_call" && item.entry.delegationItemId) {
299
+ acceptedDelegationItemIds.add(item.entry.delegationItemId);
300
+ if (pendingDelegations.get(item.entry.delegationItemId) === item.entry) {
301
+ pendingDelegations.delete(item.entry.delegationItemId);
302
+ }
303
+ locallyQueuedDelegationItemIds.delete(item.entry.delegationItemId);
304
+ }
217
305
  pendingInboundCount -= 1;
218
306
  pendingInboundBytes -= item.bytes;
219
307
  }
@@ -345,31 +433,67 @@ export function createCodexRealtimeV3Bridge(
345
433
  // These events are provider UI deltas. `turn.done` is the single
346
434
  // authoritative finalized transcript persisted below.
347
435
  } else if (event.type === "delegation.created") {
348
- activeDelegationId = event.delegationItemId;
349
- const transcript = delegationTranscript(transcriptSinceDelegation, event.inputTranscript);
350
- const coveredTurnIds = transcriptSinceDelegation.map((entry) => entry.turnId);
351
- durable = enqueue({
352
- operationId: randomUUID(),
353
- kind: "delegation_call",
354
- providerEventId: event.providerEventId,
355
- delegationItemId: event.delegationItemId,
356
- text: renderRealtimeDelegationInput(event.inputTranscript, transcript),
357
- payload: {
358
- offsetMs: event.offsetMs,
359
- inputTranscript: event.inputTranscript,
360
- transcriptFenceTurnIds: coveredTurnIds,
361
- },
362
- });
363
- if (durable) {
364
- const alreadyFinalized = transcriptSinceDelegation.some(
365
- (entry) =>
366
- entry.role === "user" &&
367
- normalizedTranscript(entry.text) === normalizedTranscript(event.inputTranscript),
368
- );
369
- pendingDelegationUserTranscript = alreadyFinalized
370
- ? null
371
- : { delegationItemId: event.delegationItemId, text: event.inputTranscript };
372
- transcriptSinceDelegation = [];
436
+ if (acceptedDelegationItemIds.has(event.delegationItemId)) {
437
+ ignoredEventCount += 1;
438
+ lastIgnoredEventType = event.type;
439
+ } else if (locallyQueuedDelegationItemIds.has(event.delegationItemId)) {
440
+ ignoredEventCount += 1;
441
+ lastIgnoredEventType = event.type;
442
+ durable = true;
443
+ } else {
444
+ let entry = pendingDelegations.get(event.delegationItemId);
445
+ if (!entry) {
446
+ const transcript = delegationTranscript(transcriptSinceDelegation, event.inputTranscript);
447
+ const coveredTurnIds = transcriptSinceDelegation.map((item) => item.turnId);
448
+ const modelContext = currentModelContext();
449
+ entry = {
450
+ operationId: randomUUID(),
451
+ kind: "delegation_call",
452
+ providerEventId: event.providerEventId,
453
+ delegationItemId: event.delegationItemId,
454
+ text: renderRealtimeDelegationInput(event.inputTranscript, transcript),
455
+ payload: {
456
+ offsetMs: event.offsetMs,
457
+ inputTranscript: event.inputTranscript,
458
+ transcriptFenceTurnIds: coveredTurnIds,
459
+ },
460
+ ...(modelContext ? { modelContext } : {}),
461
+ };
462
+ pendingDelegations.set(event.delegationItemId, entry);
463
+ }
464
+ try {
465
+ options.onDelegationReplayStateChange?.({
466
+ acceptedDelegationItemIds,
467
+ pendingDelegations,
468
+ });
469
+ } catch (error) {
470
+ triggerFatalCode(
471
+ "replay_journal_failed",
472
+ `Codex realtime delegation replay journal failed: ${safeError(error)}`,
473
+ );
474
+ return Promise.resolve();
475
+ }
476
+ durable = enqueue(entry);
477
+ if (durable) {
478
+ locallyQueuedDelegationItemIds.add(event.delegationItemId);
479
+ activeDelegationId = event.delegationItemId;
480
+ const frozenInputTranscript =
481
+ typeof entry.payload.inputTranscript === "string"
482
+ ? entry.payload.inputTranscript
483
+ : event.inputTranscript;
484
+ const alreadyFinalized = transcriptSinceDelegation.some(
485
+ (item) =>
486
+ item.role === "user" &&
487
+ normalizedTranscript(item.text) === normalizedTranscript(frozenInputTranscript),
488
+ );
489
+ pendingDelegationUserTranscript = alreadyFinalized
490
+ ? null
491
+ : {
492
+ delegationItemId: event.delegationItemId,
493
+ text: frozenInputTranscript,
494
+ };
495
+ transcriptSinceDelegation = [];
496
+ }
373
497
  }
374
498
  } else if (event.type === "output_audio.delta") {
375
499
  speaking = true;
@@ -383,7 +507,9 @@ export function createCodexRealtimeV3Bridge(
383
507
  normalizedTranscript(pendingDelegationUserTranscript.text)
384
508
  ? pendingDelegationUserTranscript.delegationItemId
385
509
  : null;
386
- durable = enqueue(finalTranscript(randomUUID, event, coveredByDelegationItemId));
510
+ durable = enqueue(
511
+ finalTranscript(randomUUID, event, coveredByDelegationItemId, currentModelContext()),
512
+ );
387
513
  if (durable) {
388
514
  finalizedTurnIds.add(event.turnId);
389
515
  if (coveredByDelegationItemId) {
@@ -446,6 +572,7 @@ function finalTranscript(
446
572
  randomUUID: () => string,
447
573
  event: Extract<CodexRealtimeV3Event, { type: "turn.done" }>,
448
574
  coveredByDelegationItemId: string | null,
575
+ modelContext: string | undefined,
449
576
  ): SessionRealtimeInboundEntry {
450
577
  return {
451
578
  operationId: randomUUID(),
@@ -456,6 +583,7 @@ function finalTranscript(
456
583
  turnId: event.turnId,
457
584
  ...(coveredByDelegationItemId ? { coveredByDelegationItemId } : {}),
458
585
  },
586
+ ...(modelContext ? { modelContext } : {}),
459
587
  };
460
588
  }
461
589
 
@@ -539,7 +667,10 @@ function sendOutbound(events: RTCDataChannel, entry: SessionRealtimeLedgerEntry)
539
667
  text,
540
668
  channel: payloadChannel ?? "speakable",
541
669
  })
542
- : encodeCodexRealtimeV3SessionContextAppend({ text, channel: payloadChannel });
670
+ : encodeCodexRealtimeV3SessionContextAppend({
671
+ text,
672
+ channel: payloadChannel,
673
+ });
543
674
  for (const message of messages) events.send(JSON.stringify(message));
544
675
  }
545
676