@opengeni/react 0.52.1 → 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.
Files changed (132) hide show
  1. package/README.md +51 -1
  2. package/dist/artifacts.d.ts +1 -1
  3. package/dist/artifacts.js +43 -14
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{chunk-ROIRTM37.js → chunk-5UQPNLIO.js} +6 -6
  6. package/dist/{chunk-JR3QFY6B.js → chunk-6KLX2RH5.js} +49 -2
  7. package/dist/chunk-6KLX2RH5.js.map +1 -0
  8. package/dist/{chunk-FWS3KC3Z.js → chunk-B6WPZSTT.js} +104 -104
  9. package/dist/{chunk-FWS3KC3Z.js.map → chunk-B6WPZSTT.js.map} +1 -1
  10. package/dist/{chunk-HDBG7XGO.js → chunk-C5ZPSSS5.js} +50 -1
  11. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  12. package/dist/{chunk-X4S56Y2H.js → chunk-HA46CYQK.js} +135 -11
  13. package/dist/chunk-HA46CYQK.js.map +1 -0
  14. package/dist/{chunk-724BW4TW.js → chunk-IGC6A2ZI.js} +2 -2
  15. package/dist/chunk-LLOLUHVG.js +5937 -0
  16. package/dist/chunk-LLOLUHVG.js.map +1 -0
  17. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  18. package/dist/chunk-MKPIAGVL.js.map +1 -0
  19. package/dist/chunk-R3AZOAB5.js +933 -0
  20. package/dist/chunk-R3AZOAB5.js.map +1 -0
  21. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  22. package/dist/chunk-XBZWOM2K.js.map +1 -0
  23. package/dist/{chunk-JAGDUCZQ.js → chunk-Y7OEDUJ5.js} +17 -13
  24. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  25. package/dist/client.d.ts +6 -4
  26. package/dist/components/artifacts/index.d.ts +1 -0
  27. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  28. package/dist/components/browser-input.d.ts +7 -0
  29. package/dist/components/browser-viewer.d.ts +5 -4
  30. package/dist/components/computer-viewer.d.ts +2 -1
  31. package/dist/components/desktop-viewer.d.ts +26 -2
  32. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  33. package/dist/components/sandbox-terminal.d.ts +4 -1
  34. package/dist/components/sandbox-workspace.d.ts +3 -0
  35. package/dist/composer.js +6 -6
  36. package/dist/hooks/use-auth-runs.d.ts +32 -0
  37. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  38. package/dist/hooks/use-browser-identities.d.ts +5 -1
  39. package/dist/hooks/use-browser-session.d.ts +2 -1
  40. package/dist/hooks/use-browser-sessions.d.ts +1 -1
  41. package/dist/hooks/use-channels.d.ts +26 -0
  42. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  43. package/dist/hooks/use-computer-session.d.ts +2 -1
  44. package/dist/hooks/use-computer-sessions.d.ts +1 -1
  45. package/dist/hooks/use-desktop-stream.d.ts +4 -0
  46. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  47. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  48. package/dist/hooks/use-network-routes.d.ts +25 -0
  49. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  50. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  51. package/dist/hooks/use-terminal-stream.d.ts +3 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +1220 -1545
  54. package/dist/index.js.map +1 -1
  55. package/dist/interaction.d.ts +13 -3
  56. package/dist/interaction.js +520 -6
  57. package/dist/interaction.js.map +1 -1
  58. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  59. package/dist/lib/host-platform.d.ts +5 -0
  60. package/dist/lib/relay-wire.d.ts +20 -1
  61. package/dist/machines.js +3 -3
  62. package/dist/model-policy.d.ts +1 -1
  63. package/dist/model-policy.js +1 -1
  64. package/dist/provider.d.ts +3 -2
  65. package/dist/realtime/realtime-control.d.ts +6 -2
  66. package/dist/realtime.js +138 -24
  67. package/dist/realtime.js.map +1 -1
  68. package/dist/session-context.d.ts +10 -2
  69. package/dist/session-ui.js +3 -3
  70. package/dist/session.js +11 -11
  71. package/dist/timeline/types.d.ts +5 -0
  72. package/package.json +2 -2
  73. package/src/artifacts.ts +3 -0
  74. package/src/client.ts +73 -3
  75. package/src/components/artifacts/index.ts +5 -0
  76. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  77. package/src/components/browser-input.ts +65 -0
  78. package/src/components/browser-viewer.tsx +1287 -177
  79. package/src/components/computer-viewer.tsx +611 -114
  80. package/src/components/desktop-viewer.tsx +114 -1
  81. package/src/components/interaction-intervention-banner.tsx +99 -0
  82. package/src/components/machines/machine-detail.tsx +1 -1
  83. package/src/components/model-policy-picker.tsx +15 -0
  84. package/src/components/sandbox-terminal.tsx +24 -7
  85. package/src/components/sandbox-workspace.tsx +42 -4
  86. package/src/components/workspace-dock.tsx +59 -5
  87. package/src/hooks/use-attached-browsers.ts +13 -11
  88. package/src/hooks/use-auth-runs.ts +278 -0
  89. package/src/hooks/use-browser-downloads.ts +218 -0
  90. package/src/hooks/use-browser-frame-stream.ts +168 -59
  91. package/src/hooks/use-browser-identities.ts +47 -6
  92. package/src/hooks/use-browser-session.ts +94 -27
  93. package/src/hooks/use-browser-sessions.ts +19 -14
  94. package/src/hooks/use-channels.ts +99 -0
  95. package/src/hooks/use-computer-frame-stream.ts +184 -61
  96. package/src/hooks/use-computer-session.ts +22 -2
  97. package/src/hooks/use-computer-sessions.ts +19 -13
  98. package/src/hooks/use-desktop-stream.ts +39 -7
  99. package/src/hooks/use-interaction-interventions.ts +242 -0
  100. package/src/hooks/use-interaction-invalidation.ts +66 -0
  101. package/src/hooks/use-machine-chip.ts +13 -3
  102. package/src/hooks/use-network-routes.ts +214 -0
  103. package/src/hooks/use-site-auth-connections.ts +226 -0
  104. package/src/hooks/use-supergrok-accounts.ts +81 -0
  105. package/src/hooks/use-terminal-stream.ts +288 -55
  106. package/src/index.ts +8 -0
  107. package/src/interaction.ts +30 -0
  108. package/src/lib/host-platform.ts +10 -0
  109. package/src/lib/relay-wire.ts +43 -4
  110. package/src/lib/terminal-capability.ts +1 -1
  111. package/src/model-policy.ts +19 -4
  112. package/src/provider.tsx +133 -1
  113. package/src/realtime/realtime-control.tsx +181 -26
  114. package/src/session-context.ts +66 -1
  115. package/src/timeline/activity-rail.tsx +6 -0
  116. package/src/timeline/projection.ts +58 -0
  117. package/src/timeline/types.ts +2 -0
  118. package/styles/compiled.css +64 -6
  119. package/dist/chunk-23EJ676W.js.map +0 -1
  120. package/dist/chunk-6BBWWOOU.js +0 -82
  121. package/dist/chunk-6BBWWOOU.js.map +0 -1
  122. package/dist/chunk-7I2KB3OK.js +0 -3895
  123. package/dist/chunk-7I2KB3OK.js.map +0 -1
  124. package/dist/chunk-HDBG7XGO.js.map +0 -1
  125. package/dist/chunk-JAGDUCZQ.js.map +0 -1
  126. package/dist/chunk-JR3QFY6B.js.map +0 -1
  127. package/dist/chunk-WYA65Y3F.js.map +0 -1
  128. package/dist/chunk-X4S56Y2H.js.map +0 -1
  129. package/dist/interactive-workbench-viewers-KHNOHWQE.js +0 -13
  130. /package/dist/{chunk-ROIRTM37.js.map → chunk-5UQPNLIO.js.map} +0 -0
  131. /package/dist/{chunk-724BW4TW.js.map → chunk-IGC6A2ZI.js.map} +0 -0
  132. /package/dist/{interactive-workbench-viewers-KHNOHWQE.js.map → interactive-workbench-viewers-TOHBXJEE.js.map} +0 -0
@@ -0,0 +1,5 @@
1
+ export type InteractionHostPlatform = "mac" | "other";
2
+ /** Physical client platform. Remote input adapters use this only to identify
3
+ * the user's primary shortcut modifier; the target platform is resolved
4
+ * separately. */
5
+ export declare function interactionHostPlatform(): InteractionHostPlatform;
@@ -1,11 +1,15 @@
1
1
  /** `StreamKind.STREAM_KIND_DESKTOP` (agent-proto enum value). */
2
2
  export declare const STREAM_KIND_DESKTOP = 2;
3
+ /** `StreamKind.STREAM_KIND_PTY` (agent-proto enum value). */
4
+ export declare const STREAM_KIND_PTY = 1;
3
5
  /** `StreamKind.STREAM_KIND_BROWSER` (agent-proto enum value). */
4
6
  export declare const STREAM_KIND_BROWSER = 3;
5
7
  /** `StreamKind.STREAM_KIND_COMPUTER` (agent-proto enum value). */
6
8
  export declare const STREAM_KIND_COMPUTER = 4;
7
9
  /** `StreamRole.STREAM_ROLE_CLIENT` (agent-proto enum value). */
8
10
  export declare const STREAM_ROLE_CLIENT = 2;
11
+ /** `StreamCloseReason.STREAM_CLOSE_REASON_NORMAL`. */
12
+ export declare const STREAM_CLOSE_REASON_NORMAL = 1;
9
13
  export interface RelayChannel {
10
14
  channelId: string;
11
15
  workspaceId: string;
@@ -21,6 +25,20 @@ export interface RelayStreamOpen {
21
25
  }
22
26
  /** Encode a `StreamOpen`, byte-compatible with `StreamOpen.encode(...).finish()`. */
23
27
  export declare function encodeStreamOpen(message: RelayStreamOpen): Uint8Array;
28
+ /** Encode an intentional viewer detach. Transport loss omits this message so
29
+ * reconnect/resume remains available. */
30
+ export declare function encodeStreamClose(message: {
31
+ channelId: string;
32
+ reason: number;
33
+ message: string;
34
+ }): Uint8Array;
35
+ /** Encode a viewer→agent PTY input `StreamFrame`. */
36
+ export declare function encodeStreamFrame(message: {
37
+ channelId: string;
38
+ seq: string;
39
+ data: Uint8Array;
40
+ producedAtMs: string;
41
+ }): Uint8Array;
24
42
  /** Decode a `StreamOpenAck` (we need `accepted` + the error message). */
25
43
  export declare function decodeStreamOpenAck(bytes: Uint8Array): {
26
44
  accepted: boolean;
@@ -28,7 +46,8 @@ export declare function decodeStreamOpenAck(bytes: Uint8Array): {
28
46
  message?: string;
29
47
  };
30
48
  };
31
- /** Decode a `StreamFrame`, extracting only the `data` (framebuffer PNG) bytes. */
49
+ /** Decode the sequence plus `data` from a `StreamFrame`. */
32
50
  export declare function decodeStreamFrame(bytes: Uint8Array): {
51
+ seq: string;
33
52
  data: Uint8Array;
34
53
  };
package/dist/machines.js CHANGED
@@ -24,10 +24,10 @@ import {
24
24
  healthPulses,
25
25
  pointsFor,
26
26
  useMachines
27
- } from "./chunk-X4S56Y2H.js";
28
- import "./chunk-LAKLF4PA.js";
27
+ } from "./chunk-HA46CYQK.js";
29
28
  import "./chunk-KZ73RL76.js";
30
- import "./chunk-HDBG7XGO.js";
29
+ import "./chunk-C5ZPSSS5.js";
30
+ import "./chunk-LAKLF4PA.js";
31
31
  import "./chunk-22KP6LR5.js";
32
32
  export {
33
33
  CONNECTION_STATUS_META,
@@ -1,5 +1,5 @@
1
1
  import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from "@opengeni/sdk";
2
- export type PickerBillingClass = "opengeni_credits" | "codex_subscription" | "byok";
2
+ export type PickerBillingClass = "opengeni_credits" | "codex_subscription" | "supergrok_subscription" | "byok";
3
3
  export type PickerModelRow<TCatalog extends ClientModel = WorkspaceModelCatalogModel> = {
4
4
  id: string;
5
5
  label: string;
@@ -15,7 +15,7 @@ import {
15
15
  projectPickerRows,
16
16
  runnableLatencyModesForModel,
17
17
  sortPickerRows
18
- } from "./chunk-23EJ676W.js";
18
+ } from "./chunk-XBZWOM2K.js";
19
19
  export {
20
20
  advancedSourceSummary,
21
21
  availabilityReasonLabel,
@@ -1,4 +1,4 @@
1
- import { type WorkspaceControlEvent } from "@opengeni/sdk";
1
+ import { type WorkspaceControlEvent, type WorkspaceInteractionRevisionEvent } from "@opengeni/sdk";
2
2
  import { type ReactNode } from "react";
3
3
  import type { SessionClientLike } from "./client.js";
4
4
  export { useOpenGeni, useOpenGeniClient } from "./session-context.js";
@@ -7,10 +7,11 @@ export type OpenGeniProviderProps = {
7
7
  client: SessionClientLike;
8
8
  workspaceId: string;
9
9
  onWorkspaceControlEvent?: ((event: WorkspaceControlEvent) => void) | undefined;
10
+ onWorkspaceInteractionEvent?: ((event: WorkspaceInteractionRevisionEvent) => void) | undefined;
10
11
  children?: ReactNode;
11
12
  };
12
13
  /**
13
14
  * Supplies the OpenGeni client + workspace to all hooks below it. Hooks also
14
15
  * accept `{ client, workspaceId }` overrides per call for multi-workspace UIs.
15
16
  */
16
- export declare function OpenGeniProvider({ client, workspaceId, onWorkspaceControlEvent, children, }: OpenGeniProviderProps): import("react").JSX.Element;
17
+ export declare function OpenGeniProvider({ client, workspaceId, onWorkspaceControlEvent, onWorkspaceInteractionEvent, children, }: OpenGeniProviderProps): import("react").JSX.Element;
@@ -5,13 +5,13 @@ import type { EmbeddedRealtimeSessionClientLike } from "../client.js";
5
5
  export type RealtimeModelOption = {
6
6
  id: SessionRealtimeModel;
7
7
  label: string;
8
- provider: "OpenGeni" | "Connected Codex" | "Your Gateway";
8
+ provider: "OpenGeni" | "Connected Codex" | "Connected SuperGrok" | "Your Gateway";
9
9
  description: string;
10
10
  available: boolean;
11
11
  unavailableReason: string | null;
12
12
  recommended: boolean;
13
13
  };
14
- declare const REALTIME_MODEL_PROVIDERS: readonly ["OpenGeni", "Connected Codex", "Your Gateway"];
14
+ declare const REALTIME_MODEL_PROVIDERS: readonly ["OpenGeni", "Connected Codex", "Connected SuperGrok", "Your Gateway"];
15
15
  type RealtimeModelProvider = (typeof REALTIME_MODEL_PROVIDERS)[number];
16
16
  type RealtimeControllerClient = EmbeddedRealtimeSessionClientLike & SessionRealtimeClientLike;
17
17
  export type SessionRealtimeControllerFactory = (options: CreateSessionRealtimeControllerOptions) => SessionRealtimeController;
@@ -38,6 +38,8 @@ export declare function useSessionRealtime(options: {
38
38
  model?: SessionRealtimeModel | undefined;
39
39
  modelAvailable?: boolean | undefined;
40
40
  modelUnavailableReason?: string | null | undefined;
41
+ /** Model-visible application context captured with each durable realtime message. */
42
+ getModelContext?: (() => string | undefined) | undefined;
41
43
  /** Deterministic browser-test/demo seam. Production hosts should use the SDK default. */
42
44
  controllerFactory?: SessionRealtimeControllerFactory | undefined;
43
45
  }): {
@@ -63,6 +65,8 @@ export declare function SessionRealtimeControl(props: {
63
65
  events: SessionEvent[];
64
66
  eventsReady: boolean;
65
67
  codexConnected: boolean;
68
+ /** Model-visible application context captured with each durable realtime message. */
69
+ getModelContext?: (() => string | undefined) | undefined;
66
70
  realtimeAutostartModel?: SessionRealtimeModel | undefined;
67
71
  onRealtimeAutostartConsumed?: (() => void) | undefined;
68
72
  /** Host hides dictate (and can choreograph layout) while realtime voice is live. */
package/dist/realtime.js CHANGED
@@ -3,15 +3,15 @@ import {
3
3
  PickerAnimatedPage,
4
4
  PickerBackHeader,
5
5
  PickerNavRow
6
- } from "./chunk-WYA65Y3F.js";
7
- import "./chunk-23EJ676W.js";
6
+ } from "./chunk-MKPIAGVL.js";
7
+ import "./chunk-XBZWOM2K.js";
8
8
  import {
9
9
  usePortalTokenSource,
10
10
  usePortalTokenStyle
11
11
  } from "./chunk-VZTQ73XT.js";
12
12
  import {
13
13
  useEmbeddedRealtimeSession
14
- } from "./chunk-HDBG7XGO.js";
14
+ } from "./chunk-C5ZPSSS5.js";
15
15
  import {
16
16
  cn
17
17
  } from "./chunk-22KP6LR5.js";
@@ -187,16 +187,91 @@ var CODEX_LIVE_MODEL = {
187
187
  unavailableReason: "Connect Codex to use this voice model",
188
188
  recommended: false
189
189
  };
190
- var REALTIME_MODEL_PROVIDERS = ["OpenGeni", "Connected Codex", "Your Gateway"];
190
+ var REALTIME_MODEL_PROVIDERS = [
191
+ "OpenGeni",
192
+ "Connected Codex",
193
+ "Connected SuperGrok",
194
+ "Your Gateway"
195
+ ];
191
196
  var REALTIME_PROVIDER_META = {
192
197
  OpenGeni: { billingClass: "opengeni_credits", hint: "Will use credits" },
193
198
  "Connected Codex": {
194
199
  billingClass: "codex_subscription",
195
200
  hint: "ChatGPT / Codex plan"
196
201
  },
202
+ "Connected SuperGrok": {
203
+ billingClass: "supergrok_subscription",
204
+ hint: "SuperGrok / xAI plan"
205
+ },
197
206
  "Your Gateway": { billingClass: "byok", hint: "Billed to your AI Gateway" }
198
207
  };
199
208
  var REALTIME_MODEL_STORAGE_PREFIX = "opengeni:realtime-model";
209
+ var REALTIME_MODEL_CATALOG_CACHE_TTL_MS = 6e4;
210
+ var REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES = 64;
211
+ var realtimeModelCatalogCache = /* @__PURE__ */ new WeakMap();
212
+ function realtimeModelFallback(codexConnected) {
213
+ return [
214
+ {
215
+ ...CODEX_LIVE_MODEL,
216
+ available: codexConnected,
217
+ unavailableReason: codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason
218
+ }
219
+ ];
220
+ }
221
+ function realtimeModelCatalogCacheFor(client) {
222
+ const existing = realtimeModelCatalogCache.get(client);
223
+ if (existing) return existing;
224
+ const created = /* @__PURE__ */ new Map();
225
+ realtimeModelCatalogCache.set(client, created);
226
+ return created;
227
+ }
228
+ function readCachedRealtimeModelCatalog(client, workspaceId, now = Date.now()) {
229
+ const cache = realtimeModelCatalogCache.get(client);
230
+ const entry = cache?.get(workspaceId);
231
+ if (!entry || entry.state === "loading") return null;
232
+ if (entry.expiresAt > now) return entry.models;
233
+ cache?.delete(workspaceId);
234
+ return null;
235
+ }
236
+ function pruneRealtimeModelCatalogCache(cache, now) {
237
+ for (const [workspaceId, entry] of cache) {
238
+ if (entry.state === "ready" && entry.expiresAt <= now) cache.delete(workspaceId);
239
+ }
240
+ while (cache.size >= REALTIME_MODEL_CATALOG_CACHE_MAX_WORKSPACES) {
241
+ const oldestWorkspaceId = cache.keys().next().value;
242
+ if (oldestWorkspaceId === void 0) return;
243
+ cache.delete(oldestWorkspaceId);
244
+ }
245
+ }
246
+ function loadRealtimeModelCatalog(client, workspaceId) {
247
+ const load = client.getWorkspaceRealtimeModelCatalog;
248
+ if (!load) return null;
249
+ const now = Date.now();
250
+ const cache = realtimeModelCatalogCacheFor(client);
251
+ const entry = cache.get(workspaceId);
252
+ if (entry?.state === "loading") return entry.promise;
253
+ if (entry?.state === "ready" && entry.expiresAt > now) return Promise.resolve(entry.models);
254
+ if (entry) cache.delete(workspaceId);
255
+ pruneRealtimeModelCatalogCache(cache, now);
256
+ const promise = load.call(client, workspaceId).then((response) => response.models.map(toRealtimeModelOption)).then((models) => {
257
+ const current = cache.get(workspaceId);
258
+ if (current?.state === "loading" && current.promise === promise) {
259
+ cache.delete(workspaceId);
260
+ cache.set(workspaceId, {
261
+ state: "ready",
262
+ models,
263
+ expiresAt: Date.now() + REALTIME_MODEL_CATALOG_CACHE_TTL_MS
264
+ });
265
+ }
266
+ return models;
267
+ }).catch((error) => {
268
+ const current = cache.get(workspaceId);
269
+ if (current?.state === "loading" && current.promise === promise) cache.delete(workspaceId);
270
+ throw error;
271
+ });
272
+ cache.set(workspaceId, { state: "loading", promise });
273
+ return promise;
274
+ }
200
275
  function codexRealtimeAdmissionAllowed(input) {
201
276
  return codexRealtimeAdmissionBlocker(input) === null;
202
277
  }
@@ -217,8 +292,14 @@ function useSessionRealtime(options) {
217
292
  const audioRef = useRef(null);
218
293
  const controllerRef = useRef(null);
219
294
  const controllerModelRef = useRef(null);
295
+ const modelContextProviderRef = useRef(options.getModelContext);
296
+ modelContextProviderRef.current = options.getModelContext;
220
297
  const [snapshot, setSnapshot] = useState(() => ({
221
- status: hasStoredSessionRealtimeOwnerProof({ workspaceId, sessionId: options.sessionId, model }) ? "recovering" : "idle",
298
+ status: hasStoredSessionRealtimeOwnerProof({
299
+ workspaceId,
300
+ sessionId: options.sessionId,
301
+ model
302
+ }) ? "recovering" : "idle",
222
303
  realtimeId: null,
223
304
  mode: null,
224
305
  bridge: null,
@@ -252,7 +333,8 @@ function useSessionRealtime(options) {
252
333
  workspaceId,
253
334
  sessionId: options.sessionId,
254
335
  ...audioRef.current ? { remoteAudio: audioRef.current } : {},
255
- model
336
+ model,
337
+ getModelContext: () => modelContextProviderRef.current?.()
256
338
  });
257
339
  controllerRef.current = controller;
258
340
  controllerModelRef.current = model;
@@ -424,28 +506,46 @@ function useRealtimeModelSelection(options) {
424
506
  workspaceId: options.workspaceId
425
507
  });
426
508
  const activeModel = options.activeModel;
427
- const [catalog, setCatalog] = useState(() => [
428
- {
429
- ...CODEX_LIVE_MODEL,
430
- available: options.codexConnected,
431
- unavailableReason: options.codexConnected ? null : CODEX_LIVE_MODEL.unavailableReason
432
- }
433
- ]);
434
- const [selectedModelId, setSelectedModelId] = useState(
435
- () => readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id
509
+ const initialCachedCatalog = readCachedRealtimeModelCatalog(client, workspaceId);
510
+ const [catalog, setCatalog] = useState(
511
+ () => initialCachedCatalog ?? realtimeModelFallback(options.codexConnected)
436
512
  );
513
+ const catalogScopeRef = useRef({
514
+ client,
515
+ workspaceId,
516
+ source: initialCachedCatalog ? "cache" : "fallback"
517
+ });
518
+ const [selectedModelId, setSelectedModelId] = useState(() => {
519
+ const preferred = readRealtimeModelPreference(workspaceId) ?? CODEX_LIVE_MODEL.id;
520
+ if (!initialCachedCatalog) return preferred;
521
+ if (initialCachedCatalog.some((model) => model.id === preferred && model.available)) {
522
+ return preferred;
523
+ }
524
+ return initialCachedCatalog.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
525
+ });
437
526
  useEffect(() => {
438
527
  let disposed = false;
439
- const load = client.getWorkspaceRealtimeModelCatalog;
440
- if (!load) return;
441
- void load.call(client, workspaceId).then((response) => {
442
- if (disposed) return;
443
- const models = response.models.map(toRealtimeModelOption);
528
+ const applyCatalog = (models) => {
529
+ catalogScopeRef.current = { client, workspaceId, source: "cache" };
444
530
  setCatalog(models);
445
531
  setSelectedModelId((current) => {
446
532
  if (models.some((model) => model.id === current && model.available)) return current;
447
533
  return models.find((model) => model.available)?.id ?? CODEX_LIVE_MODEL.id;
448
534
  });
535
+ };
536
+ const cached = readCachedRealtimeModelCatalog(client, workspaceId);
537
+ if (cached) {
538
+ const scope = catalogScopeRef.current;
539
+ if (scope.client !== client || scope.workspaceId !== workspaceId || scope.source !== "cache") {
540
+ applyCatalog(cached);
541
+ }
542
+ return;
543
+ }
544
+ const loading = loadRealtimeModelCatalog(client, workspaceId);
545
+ if (!loading) return;
546
+ void loading.then((models) => {
547
+ if (disposed) return;
548
+ applyCatalog(models);
449
549
  }).catch(() => void 0);
450
550
  return () => {
451
551
  disposed = true;
@@ -615,7 +715,13 @@ function RealtimeVoiceControl(props) {
615
715
  animate: {
616
716
  x: 0,
617
717
  opacity: 1,
618
- transition: reduceMotion ? { duration: 0 } : { delay: 0.32, type: "spring", stiffness: 320, damping: 30, mass: 0.8 }
718
+ transition: reduceMotion ? { duration: 0 } : {
719
+ delay: 0.32,
720
+ type: "spring",
721
+ stiffness: 320,
722
+ damping: 30,
723
+ mass: 0.8
724
+ }
619
725
  },
620
726
  ...reduceMotion ? {} : {
621
727
  exit: {
@@ -1017,7 +1123,11 @@ function statusContent(snapshot, modelAvailable, canStart, admissionBlocker, mod
1017
1123
  };
1018
1124
  }
1019
1125
  if (!canStart && admissionBlocker) {
1020
- return { phase: "unavailable", label: "Voice unavailable", detail: admissionBlocker };
1126
+ return {
1127
+ phase: "unavailable",
1128
+ label: "Voice unavailable",
1129
+ detail: admissionBlocker
1130
+ };
1021
1131
  }
1022
1132
  return {
1023
1133
  phase: "idle",
@@ -1094,7 +1204,11 @@ function RealtimeStatusDot(props) {
1094
1204
  {
1095
1205
  className: "absolute inset-0 rounded-full bg-og-status-running",
1096
1206
  animate: { opacity: [0.45, 0], scale: [1, 2.2] },
1097
- transition: { duration: 1.4, ease: "easeOut", repeat: Number.POSITIVE_INFINITY }
1207
+ transition: {
1208
+ duration: 1.4,
1209
+ ease: "easeOut",
1210
+ repeat: Number.POSITIVE_INFINITY
1211
+ }
1098
1212
  }
1099
1213
  ) : null,
1100
1214
  /* @__PURE__ */ jsx2(
@@ -1152,7 +1266,7 @@ function writeRealtimeModelPreference(workspaceId, model) {
1152
1266
  }
1153
1267
  }
1154
1268
  function isRealtimeModel(value) {
1155
- return value === "gpt-live-1-boulder-alpha" || value === "opengeni-gateway/openai/gpt-realtime-2.1" || value === "opengeni-gateway/openai/gpt-realtime-mini" || value === "opengeni-gateway/xai/grok-voice-think-fast-2.0" || value === "workspace-gateway/openai/gpt-realtime-2.1" || value === "workspace-gateway/openai/gpt-realtime-mini" || value === "workspace-gateway/xai/grok-voice-think-fast-2.0";
1269
+ return value === "gpt-live-1-boulder-alpha" || value === "opengeni-gateway/openai/gpt-realtime-2.1" || value === "opengeni-gateway/openai/gpt-realtime-mini" || value === "opengeni-gateway/xai/grok-voice-think-fast-2.0" || value === "supergrok/grok-voice-think-fast-2.0" || value === "workspace-gateway/openai/gpt-realtime-2.1" || value === "workspace-gateway/openai/gpt-realtime-mini" || value === "workspace-gateway/xai/grok-voice-think-fast-2.0";
1156
1270
  }
1157
1271
  var useSessionCodexRealtime = useSessionRealtime;
1158
1272
  var useWorkspaceRealtimeModelSelection = useRealtimeModelSelection;