@opengeni/sdk 0.57.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
 
package/src/types.ts CHANGED
@@ -125,6 +125,8 @@ export type SessionRealtimeInboundEntry = {
125
125
  delegationItemId?: string | null | undefined;
126
126
  text?: string | null | undefined;
127
127
  payload?: Record<string, unknown> | undefined;
128
+ /** Model-visible application context attached to this exact realtime message. */
129
+ modelContext?: string | undefined;
128
130
  };
129
131
 
130
132
  export type SyncSessionRealtimeLedgerRequest = {
@@ -743,8 +745,9 @@ export type GoogleDriveConnectionMetadata = {
743
745
  googleEmail: string;
744
746
  googleDisplayName: string | null;
745
747
  verifiedAt: string;
746
- accessMode: "metadata_readonly" | "readonly";
748
+ accessMode: "file_only" | "metadata_readonly" | "readonly";
747
749
  lifecycle?: GoogleDriveConnectionLifecycle | undefined;
750
+ outputDestination?: GoogleDriveOutputDestination | undefined;
748
751
  documentDestination?: ConnectorDocumentDestination | undefined;
749
752
  selectedSources?: GoogleDriveSelectedSource[] | undefined;
750
753
  /** @deprecated Read selectedSources; retained while existing connections migrate. */
@@ -752,8 +755,17 @@ export type GoogleDriveConnectionMetadata = {
752
755
  [key: string]: unknown;
753
756
  };
754
757
 
758
+ export type GoogleDriveOutputDestination = {
759
+ folderId: string;
760
+ folderName: string;
761
+ driveId: string | null;
762
+ location: "my_drive" | "shared_drive";
763
+ selectedAt: string;
764
+ };
765
+
755
766
  export type GoogleDriveOAuthStartRequest = {
756
767
  connectionId?: string | undefined;
768
+ capability?: "source_read" | "publish" | undefined;
757
769
  };
758
770
 
759
771
  export type GoogleDriveOAuthStartResponse = {
@@ -2270,8 +2282,8 @@ export type CreateSessionRequest = {
2270
2282
  initialMessage?: string | undefined;
2271
2283
  /** Create an idle session shell so realtime voice can be the first interaction. */
2272
2284
  startMode?: "realtime" | undefined;
2273
- /** System instructions scoped to the initial turn; never visible timeline text. */
2274
- turnInstructions?: string | undefined;
2285
+ /** Model-visible application context attached to the initial user message; omitted by standard timeline rendering. */
2286
+ modelContext?: string | undefined;
2275
2287
  // Per-session agent persona/system instructions (org-visible metadata, not a
2276
2288
  // secret). Delivered system-level, composed AFTER the per-workspace persona —
2277
2289
  // how a host supplies per-agent-type prompts without leaking them into the
@@ -2984,7 +2996,7 @@ export type ClientAuthConfig =
2984
2996
 
2985
2997
  // Kept value-identical to @opengeni/contracts and pinned by the SDK contract
2986
2998
  // parity suite. The SDK has no runtime dependency on the Zod contracts package.
2987
- export const OPENGENI_API_CONTRACT_REVISION = "2026-08-social-provider-tools-v1" as const;
2999
+ export const OPENGENI_API_CONTRACT_REVISION = "2026-08-model-context-v1" as const;
2988
3000
  export const OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract" as const;
2989
3001
  /** Bounded request/response identifier shared by browser, ingress, and API diagnostics. */
2990
3002
  export const OPENGENI_CORRELATION_HEADER = "x-opengeni-correlation-id" as const;
@@ -6006,7 +6018,7 @@ export type UserMessageEventInput = {
6006
6018
  payload: {
6007
6019
  text: string;
6008
6020
  annotations?: SubmittedTimelineAnnotation[] | undefined;
6009
- turnInstructions?: string | undefined;
6021
+ modelContext?: string | undefined;
6010
6022
  resources?: ResourceRef[] | undefined;
6011
6023
  model?: string | undefined;
6012
6024
  reasoningEffort?: ReasoningEffort | undefined;