@opengeni/react 0.36.1 → 0.40.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 (85) hide show
  1. package/README.md +92 -2
  2. package/dist/{chunk-J2RVJ6JX.js → chunk-4IJCL7YO.js} +13 -2
  3. package/dist/chunk-4IJCL7YO.js.map +1 -0
  4. package/dist/{chunk-7CJOAW3V.js → chunk-EB67J347.js} +1 -135
  5. package/dist/chunk-EB67J347.js.map +1 -0
  6. package/dist/chunk-EKZH6IDG.js +141 -0
  7. package/dist/chunk-EKZH6IDG.js.map +1 -0
  8. package/dist/{chunk-OKKMZDQF.js → chunk-JALF5FI3.js} +9 -7
  9. package/dist/{chunk-OKKMZDQF.js.map → chunk-JALF5FI3.js.map} +1 -1
  10. package/dist/{chunk-Z5FV355Z.js → chunk-KR2SK5GJ.js} +1382 -724
  11. package/dist/chunk-KR2SK5GJ.js.map +1 -0
  12. package/dist/{chunk-N4XEBE23.js → chunk-OMCFRWHL.js} +22 -13
  13. package/dist/{chunk-N4XEBE23.js.map → chunk-OMCFRWHL.js.map} +1 -1
  14. package/dist/{chunk-FT2TXNGZ.js → chunk-Q2NCKWTK.js} +183 -18
  15. package/dist/chunk-Q2NCKWTK.js.map +1 -0
  16. package/dist/{chunk-22RM4GMO.js → chunk-SJKT4TKW.js} +26 -2
  17. package/dist/chunk-SJKT4TKW.js.map +1 -0
  18. package/dist/{chunk-ALA3UGWB.js → chunk-SXIQK54Z.js} +8 -6
  19. package/dist/{chunk-ALA3UGWB.js.map → chunk-SXIQK54Z.js.map} +1 -1
  20. package/dist/chunk-WZT5G5OR.js +433 -0
  21. package/dist/chunk-WZT5G5OR.js.map +1 -0
  22. package/dist/client.d.ts +2 -0
  23. package/dist/components/chat-composer.d.ts +7 -1
  24. package/dist/components/human-input-form.d.ts +16 -6
  25. package/dist/components/human-input-surface.d.ts +19 -0
  26. package/dist/components/machine-input-display.d.ts +12 -0
  27. package/dist/components/message-timeline.d.ts +6 -1
  28. package/dist/components/model-policy-picker.d.ts +78 -0
  29. package/dist/components/session-chrome.d.ts +2 -1
  30. package/dist/components/tip-follow.d.ts +30 -0
  31. package/dist/composer.d.ts +2 -0
  32. package/dist/composer.js +22 -5
  33. package/dist/hooks/use-composer.d.ts +10 -1
  34. package/dist/index.d.ts +7 -1
  35. package/dist/index.js +2261 -2230
  36. package/dist/index.js.map +1 -1
  37. package/dist/machines.js +4 -3
  38. package/dist/model-policy.d.ts +12 -4
  39. package/dist/model-policy.js +5 -1
  40. package/dist/realtime/dropdown-menu.d.ts +16 -0
  41. package/dist/realtime/realtime-control.d.ts +126 -0
  42. package/dist/realtime.d.ts +11 -0
  43. package/dist/realtime.js +1023 -0
  44. package/dist/realtime.js.map +1 -0
  45. package/dist/session-context.d.ts +7 -1
  46. package/dist/session-ui.d.ts +2 -0
  47. package/dist/session-ui.js +6 -4
  48. package/dist/session.js +5 -4
  49. package/dist/timeline/compute-label.d.ts +2 -0
  50. package/dist/timeline/index.d.ts +1 -0
  51. package/dist/timeline/types.d.ts +5 -0
  52. package/package.json +7 -2
  53. package/src/client.ts +13 -0
  54. package/src/components/chat-composer.tsx +19 -5
  55. package/src/components/composer-transcription-control.tsx +2 -2
  56. package/src/components/composer.tsx +14 -5
  57. package/src/components/human-input-form.tsx +506 -186
  58. package/src/components/human-input-surface.tsx +80 -0
  59. package/src/components/machine-input-display.ts +83 -0
  60. package/src/components/message-timeline.tsx +348 -308
  61. package/src/components/model-policy-picker.tsx +587 -0
  62. package/src/components/sandbox-files.tsx +1 -1
  63. package/src/components/session-chrome.tsx +141 -15
  64. package/src/components/tip-follow.ts +127 -6
  65. package/src/composer.ts +13 -0
  66. package/src/hooks/use-composer.ts +229 -29
  67. package/src/index.ts +19 -0
  68. package/src/model-policy.ts +51 -7
  69. package/src/realtime/dropdown-menu.tsx +123 -0
  70. package/src/realtime/realtime-control.tsx +1178 -0
  71. package/src/realtime.ts +27 -0
  72. package/src/session-context.ts +16 -0
  73. package/src/session-ui.ts +2 -0
  74. package/src/timeline/compute-label.tsx +18 -0
  75. package/src/timeline/index.ts +3 -0
  76. package/src/timeline/projection.ts +21 -1
  77. package/src/timeline/tool-renderers.tsx +232 -43
  78. package/src/timeline/types.ts +5 -0
  79. package/styles/index.css +61 -0
  80. package/styles/tokens.css +2 -0
  81. package/dist/chunk-22RM4GMO.js.map +0 -1
  82. package/dist/chunk-7CJOAW3V.js.map +0 -1
  83. package/dist/chunk-FT2TXNGZ.js.map +0 -1
  84. package/dist/chunk-J2RVJ6JX.js.map +0 -1
  85. package/dist/chunk-Z5FV355Z.js.map +0 -1
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Batteries-included realtime voice controls and hooks.
3
+ *
4
+ * Import this subpath lazily when the composer action slot is rendered. The
5
+ * base `@opengeni/react` entry remains SSR/browser safe and does not eagerly
6
+ * load microphone, WebRTC, Gateway, or realtime UI code.
7
+ */
8
+ export {
9
+ CodexRealtimeControl,
10
+ NewSessionRealtimeControl,
11
+ RealtimeModelPickerMenu,
12
+ RealtimeVoiceControl,
13
+ SessionCodexRealtimeControl,
14
+ SessionRealtimeControl,
15
+ codexRealtimeAdmissionAllowed,
16
+ codexRealtimeAdmissionBlocker,
17
+ useRealtimeModelSelection,
18
+ useSessionCodexRealtime,
19
+ useSessionRealtime,
20
+ useWorkspaceRealtimeModelSelection,
21
+ } from "./realtime/realtime-control";
22
+ export type {
23
+ RealtimeModelOption,
24
+ SessionRealtimeControllerFactory,
25
+ } from "./realtime/realtime-control";
26
+ export type { EmbeddedRealtimeSessionClientLike } from "./client";
27
+ export type { EmbeddedRealtimeSessionClientOverride } from "./session-context";
@@ -4,6 +4,7 @@ import type {
4
4
  EmbeddedFileAttachmentClientLike,
5
5
  EmbeddedGoalClientLike,
6
6
  EmbeddedHumanInputSessionClientLike,
7
+ EmbeddedRealtimeSessionClientLike,
7
8
  EmbeddedSessionClientLike,
8
9
  EmbeddedSessionLineageClientLike,
9
10
  EmbeddedSessionMcpApprovalPolicyClientLike,
@@ -55,6 +56,11 @@ export type EmbeddedSessionReadClientOverride = {
55
56
  workspaceId?: string | undefined;
56
57
  };
57
58
 
59
+ export type EmbeddedRealtimeSessionClientOverride = {
60
+ client?: EmbeddedRealtimeSessionClientLike | undefined;
61
+ workspaceId?: string | undefined;
62
+ };
63
+
58
64
  export type EmbeddedGoalClientOverride = {
59
65
  client?: EmbeddedGoalClientLike | undefined;
60
66
  workspaceId?: string | undefined;
@@ -240,6 +246,16 @@ export function useEmbeddedSessionMcpApprovalPolicy(
240
246
  );
241
247
  }
242
248
 
249
+ /** Resolve the exact browser/API surface required by `@opengeni/react/realtime`. */
250
+ export function useEmbeddedRealtimeSession(
251
+ override: EmbeddedRealtimeSessionClientOverride = {},
252
+ ): EmbeddedClientContextValue<EmbeddedRealtimeSessionClientLike> {
253
+ // Preserve the existing lazy capability behavior: proxy/test clients may
254
+ // implement only the methods reached by their selected model and lifecycle.
255
+ // The exported structural type remains the complete embedding contract.
256
+ return useEmbeddedClientRefinement(override, [], "realtime hooks");
257
+ }
258
+
243
259
  /**
244
260
  * Resolve the client only — for hooks that are not workspace-scoped
245
261
  * (`useWorkspaces`, `useBillingUsage`).
package/src/session-ui.ts CHANGED
@@ -7,6 +7,8 @@ export type {
7
7
  HumanInputFormMessages,
8
8
  HumanInputFormProps,
9
9
  } from "./components/human-input-form";
10
+ export { HumanInputSurface } from "./components/human-input-surface";
11
+ export type { HumanInputSurfaceProps } from "./components/human-input-surface";
10
12
  export { MessageTimeline, TimelineRow } from "./components/message-timeline";
11
13
  export type { MessageTimelineProps } from "./components/message-timeline";
12
14
  export { BUILT_IN_TURN_SUMMARY_FACET_IDS } from "./timeline/turn-summary";
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /* ----------------------------------------------------------------------------
4
+ Session compute label
5
+
6
+ Optional host-supplied display name for the session's active compute target
7
+ (e.g. "Cloud sandbox" or a Connected Machine name). Used by exec_command
8
+ collapsed previews. The host owns resolution; this package never calls the
9
+ machines API.
10
+ -------------------------------------------------------------------------- */
11
+
12
+ const TimelineComputeLabelContext = createContext<string | null>(null);
13
+
14
+ export const TimelineComputeLabelProvider = TimelineComputeLabelContext.Provider;
15
+
16
+ export function useTimelineComputeLabel(): string | null {
17
+ return useContext(TimelineComputeLabelContext);
18
+ }
@@ -95,6 +95,9 @@ export type { FoldRestingState } from "./fold-memory";
95
95
  // seen activity ids (live tool enter across rail remounts)
96
96
  export { SeenActivityIdsProvider, useSeenActivityIds } from "./seen-activity-ids";
97
97
 
98
+ // session compute label (host-supplied active machine / cloud sandbox name)
99
+ export { TimelineComputeLabelProvider, useTimelineComputeLabel } from "./compute-label";
100
+
98
101
  // turn-collapse summary chip
99
102
  export { BUILT_IN_TURN_SUMMARY_FACET_IDS, TurnSummary, useTurnSettleOpen } from "./turn-summary";
100
103
  export type {
@@ -124,10 +124,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
124
124
  });
125
125
  break;
126
126
  }
127
+ const voiceMessage = realtimeVoiceMessage(payload);
127
128
  items.push({
128
129
  kind: "user-message",
129
130
  id: event.id,
130
- text: stringValue(payload.text),
131
+ text: voiceMessage?.text ?? stringValue(payload.text),
132
+ ...(voiceMessage ? { presentation: voiceMessage.presentation } : {}),
131
133
  resources: resourceRefs(payload.resources),
132
134
  tools: toolRefs(payload.tools),
133
135
  occurredAt: event.occurredAt,
@@ -687,6 +689,24 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
687
689
  return items;
688
690
  }
689
691
 
692
+ function realtimeVoiceMessage(payload: Record<string, unknown>): {
693
+ text: string;
694
+ presentation: {
695
+ kind: "realtime_voice" | "realtime_voice_handoff";
696
+ context: string;
697
+ };
698
+ } | null {
699
+ const presentation = asRecord(payload.presentation);
700
+ const visibleText = stringValue(payload.text);
701
+ if (presentation.kind === "realtime_voice" || presentation.kind === "realtime_voice_handoff") {
702
+ const context = stringValue(presentation.context);
703
+ return visibleText && context
704
+ ? { text: visibleText, presentation: { kind: presentation.kind, context } }
705
+ : null;
706
+ }
707
+ return null;
708
+ }
709
+
690
710
  function providerNativeToolStatus(rawValue: unknown): ToolCallItem["status"] {
691
711
  const raw = asRecord(rawValue);
692
712
  if (raw.type !== "hosted_tool_call") {
@@ -13,6 +13,7 @@ import {
13
13
  KeyboardIcon,
14
14
  KeyRoundIcon,
15
15
  LockIcon,
16
+ MessageCircleQuestionIcon,
16
17
  MessagesSquareIcon,
17
18
  MessageSquareIcon,
18
19
  MousePointer2Icon,
@@ -28,6 +29,7 @@ import {
28
29
  } from "lucide-react";
29
30
  import type { ReactNode } from "react";
30
31
  import { stringifyPayload, tryParseJson } from "../lib/format";
32
+ import { useTimelineComputeLabel } from "./compute-label";
31
33
  import {
32
34
  applyPatchOpsFromToolItem,
33
35
  controlCaret,
@@ -94,6 +96,14 @@ function RunningPreview({ children }: { children: ReactNode }) {
94
96
  );
95
97
  }
96
98
 
99
+ /** Prefix a collapsed preview with the host-supplied active compute label. */
100
+ function withComputePreview(label: string | null, preview: string): string {
101
+ if (!label) {
102
+ return preview;
103
+ }
104
+ return `on ${label} · ${preview}`;
105
+ }
106
+
97
107
  /* ---- exec_command ---------------------------------------------------------- */
98
108
 
99
109
  function ExecRenderer({ item }: ToolRendererProps) {
@@ -103,6 +113,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
103
113
  const running = item.status === "running";
104
114
  const out = item.output;
105
115
  const title = `$ ${cmd}`;
116
+ const computeLabel = useTimelineComputeLabel();
106
117
 
107
118
  // No output event ever arrived (item.output stays undefined from creation):
108
119
  // the turn failed before the output insert — most likely a NUL byte in the
@@ -116,7 +127,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
116
127
  title={title}
117
128
  titleMono
118
129
  chip={{ tone: "bad", text: "failed" }}
119
- preview="output lost — NUL byte could not be stored"
130
+ preview={withComputePreview(computeLabel, "output lost — NUL byte could not be stored")}
120
131
  >
121
132
  <BodyNote tone="error">
122
133
  output contained a NUL byte and could not be stored; the turn failed on this tool&apos;s
@@ -137,7 +148,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
137
148
  title={title}
138
149
  titleMono
139
150
  chip={{ tone: "bad", text: "failed" }}
140
- preview="tool call failed"
151
+ preview={withComputePreview(computeLabel, "tool call failed")}
141
152
  >
142
153
  <BodyNote tone="error">the tool call failed with no output.</BodyNote>
143
154
  </ActivityDisclosure>
@@ -146,6 +157,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
146
157
 
147
158
  if (running) {
148
159
  const streamed = typeof out === "string" ? stripExecBanner(out) : "";
160
+ const runningPreview = streamed ? `${streamed.split("\n").length} lines` : "running…";
149
161
  return (
150
162
  <ActivityDisclosure
151
163
  icon={<TerminalIcon className={ICON_SIZE} />}
@@ -154,9 +166,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
154
166
  titleMono
155
167
  running
156
168
  preview={
157
- <RunningPreview>
158
- {streamed ? `${streamed.split("\n").length} lines` : "running…"}
159
- </RunningPreview>
169
+ <RunningPreview>{withComputePreview(computeLabel, runningPreview)}</RunningPreview>
160
170
  }
161
171
  >
162
172
  {/* The row title is already `$ ${cmd}`; the TermBlock header drops the
@@ -184,8 +194,9 @@ function ExecRenderer({ item }: ToolRendererProps) {
184
194
  iconTone = "failed";
185
195
  }
186
196
 
187
- const preview = binary ? "binary output" : tailPeek(stripped) || "(no output)";
197
+ const peek = binary ? "binary output" : tailPeek(stripped) || "(no output)";
188
198
  const truncated = execTruncated(text);
199
+ const preview = withComputePreview(computeLabel, truncated ? `⋯ truncated · ${peek}` : peek);
189
200
  // Hand TermBlock the FULL stripped output; it owns the tail/show-more slicing.
190
201
  const body = binary ? "(binary output suppressed)" : stripped;
191
202
 
@@ -198,7 +209,7 @@ function ExecRenderer({ item }: ToolRendererProps) {
198
209
  {...(chip ? { chip } : {})}
199
210
  failed={item.status === "failed"}
200
211
  cancelled={item.status === "cancelled"}
201
- preview={truncated ? `⋯ truncated · ${preview}` : preview}
212
+ preview={preview}
202
213
  >
203
214
  <TermBlock
204
215
  command={null}
@@ -1254,6 +1265,179 @@ function MemoryProposeRenderer({ item }: ToolRendererProps) {
1254
1265
  );
1255
1266
  }
1256
1267
 
1268
+ /* ---- request_human_input --------------------------------------------------- */
1269
+
1270
+ function askToolPreview(args: unknown): string | null {
1271
+ const record = args && typeof args === "object" ? (args as Record<string, unknown>) : null;
1272
+ const questions = Array.isArray(record?.questions) ? record.questions : null;
1273
+ if (!questions || questions.length === 0) {
1274
+ return null;
1275
+ }
1276
+ const first = questions[0];
1277
+ if (!first || typeof first !== "object") {
1278
+ return null;
1279
+ }
1280
+ const q = first as Record<string, unknown>;
1281
+ const text =
1282
+ typeof q.label === "string" && q.label.trim()
1283
+ ? q.label.trim()
1284
+ : typeof q.prompt === "string" && q.prompt.trim()
1285
+ ? q.prompt.trim()
1286
+ : null;
1287
+ if (!text) {
1288
+ return null;
1289
+ }
1290
+ const preview = truncatePreview(text, 90);
1291
+ return questions.length > 1 ? `${preview} · ${questions.length} questions` : preview;
1292
+ }
1293
+
1294
+ function AskRenderer({ item }: ToolRendererProps) {
1295
+ const args = redactSecrets(parseToolArgs(item.arguments));
1296
+ const preview = askToolPreview(args);
1297
+ const icon = <MessageCircleQuestionIcon className={ICON_SIZE} />;
1298
+ const title = "Ask";
1299
+
1300
+ if (item.status === "running") {
1301
+ return (
1302
+ <ActivityDisclosure
1303
+ icon={icon}
1304
+ iconTone="running"
1305
+ title={title}
1306
+ running
1307
+ preview={
1308
+ preview ? (
1309
+ <RunningPreview>{preview}</RunningPreview>
1310
+ ) : (
1311
+ <RunningPreview>Waiting…</RunningPreview>
1312
+ )
1313
+ }
1314
+ >
1315
+ <PayloadBlock label="Arguments" value={args} />
1316
+ </ActivityDisclosure>
1317
+ );
1318
+ }
1319
+
1320
+ const { text: outText, isError } = unwrapMcpOutput(item.output);
1321
+ if ((isError || item.status === "failed") && item.status !== "cancelled") {
1322
+ return (
1323
+ <ActivityDisclosure
1324
+ icon={icon}
1325
+ iconTone="failed"
1326
+ title={title}
1327
+ chip={{ tone: "bad", text: "error" }}
1328
+ preview={truncatePreview(outText, 80) || preview || "Error"}
1329
+ >
1330
+ <PayloadBlock label="Arguments" value={args} />
1331
+ <PayloadBlock label="Error" value={outText} failed />
1332
+ </ActivityDisclosure>
1333
+ );
1334
+ }
1335
+
1336
+ return (
1337
+ <ActivityDisclosure
1338
+ icon={icon}
1339
+ iconTone="muted"
1340
+ title={title}
1341
+ cancelled={item.status === "cancelled"}
1342
+ preview={item.status === "cancelled" ? undefined : (preview ?? undefined)}
1343
+ >
1344
+ <PayloadBlock label="Arguments" value={args} />
1345
+ {outText ? <PayloadBlock label="Result" value={outText} /> : null}
1346
+ </ActivityDisclosure>
1347
+ );
1348
+ }
1349
+
1350
+ /* ---- run_on ---------------------------------------------------------------- */
1351
+
1352
+ function runOnTargetName(output: unknown): string | null {
1353
+ const { text } = unwrapMcpOutput(output);
1354
+ const parsed = tryParseJson(text);
1355
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1356
+ const name = (parsed as { targetName?: unknown }).targetName;
1357
+ if (typeof name === "string" && name.trim()) {
1358
+ return name.trim();
1359
+ }
1360
+ }
1361
+ return null;
1362
+ }
1363
+
1364
+ function runOnOpPreview(args: Record<string, unknown>): string | null {
1365
+ const op = args.op;
1366
+ if (!op || typeof op !== "object" || Array.isArray(op)) {
1367
+ return null;
1368
+ }
1369
+ const record = op as Record<string, unknown>;
1370
+ if (record.kind === "exec" && typeof record.cmd === "string" && record.cmd.trim()) {
1371
+ return `$ ${record.cmd.trim()}`;
1372
+ }
1373
+ if (
1374
+ (record.kind === "read" || record.kind === "write") &&
1375
+ typeof record.path === "string" &&
1376
+ record.path.trim()
1377
+ ) {
1378
+ return truncatePreview(record.path.trim(), 72);
1379
+ }
1380
+ return null;
1381
+ }
1382
+
1383
+ function RunOnRenderer({ item }: ToolRendererProps) {
1384
+ const parsedArgs = parseToolArgs(item.arguments);
1385
+ const args = redactSecrets(parsedArgs);
1386
+ const targetName = runOnTargetName(item.output);
1387
+ const title = targetName ? `Run on ${targetName}` : "Run on";
1388
+ const opPreview = runOnOpPreview(parsedArgs);
1389
+ const icon = <ServerIcon className={ICON_SIZE} />;
1390
+
1391
+ if (item.status === "running") {
1392
+ return (
1393
+ <ActivityDisclosure
1394
+ icon={icon}
1395
+ iconTone="running"
1396
+ title={title}
1397
+ running
1398
+ preview={
1399
+ opPreview ? (
1400
+ <RunningPreview>{opPreview}</RunningPreview>
1401
+ ) : (
1402
+ <RunningPreview>Running…</RunningPreview>
1403
+ )
1404
+ }
1405
+ >
1406
+ <PayloadBlock label="Arguments" value={args} />
1407
+ </ActivityDisclosure>
1408
+ );
1409
+ }
1410
+
1411
+ const { text: outText, isError } = unwrapMcpOutput(item.output);
1412
+ if ((isError || item.status === "failed") && item.status !== "cancelled") {
1413
+ return (
1414
+ <ActivityDisclosure
1415
+ icon={icon}
1416
+ iconTone="failed"
1417
+ title={title}
1418
+ chip={{ tone: "bad", text: "error" }}
1419
+ preview={truncatePreview(outText, 80) || opPreview || "Error"}
1420
+ >
1421
+ <PayloadBlock label="Arguments" value={args} />
1422
+ <PayloadBlock label="Error" value={outText} failed />
1423
+ </ActivityDisclosure>
1424
+ );
1425
+ }
1426
+
1427
+ return (
1428
+ <ActivityDisclosure
1429
+ icon={icon}
1430
+ iconTone="muted"
1431
+ title={title}
1432
+ cancelled={item.status === "cancelled"}
1433
+ preview={item.status === "cancelled" ? undefined : (opPreview ?? undefined)}
1434
+ >
1435
+ <PayloadBlock label="Arguments" value={args} />
1436
+ {outText ? <PayloadBlock label="Result" value={outText} /> : null}
1437
+ </ActivityDisclosure>
1438
+ );
1439
+ }
1440
+
1257
1441
  /* ---- generic fallback (first-party MCP, external MCP, unknown) ------------- */
1258
1442
 
1259
1443
  /**
@@ -1355,42 +1539,45 @@ function truncatePreview(text: string, max: number): string {
1355
1539
  /** Explicit first-party leaf/prefix → canonical product icons (match nav/pages). */
1356
1540
  function GenericToolIcon({ name }: { name: string }) {
1357
1541
  const leaf = mcpToolLeaf(name);
1358
- const Icon = leaf.startsWith("goal_")
1359
- ? TargetIcon
1360
- : leaf.startsWith("memory_") ||
1361
- leaf === "preference_registry_summary" ||
1362
- leaf === "preference_registry_get"
1363
- ? BrainCircuitIcon
1364
- : leaf.startsWith("session_") ||
1365
- leaf === "sessions_list" ||
1366
- leaf === "set_session_title" ||
1367
- leaf === "set_other_session_title"
1368
- ? MessagesSquareIcon
1369
- : leaf.startsWith("sandbox") || leaf === "sandboxes_list" || leaf === "run_on"
1370
- ? ServerIcon
1371
- : leaf.startsWith("rig_")
1372
- ? ServerCogIcon
1373
- : leaf.startsWith("scheduled_")
1374
- ? CalendarClockIcon
1375
- : leaf.startsWith("artifacts_")
1376
- ? PanelsTopLeftIcon
1377
- : leaf.startsWith("social_")
1378
- ? Share2Icon
1379
- : leaf.startsWith("slack_")
1380
- ? MessageSquareIcon
1381
- : leaf.startsWith("github_")
1382
- ? FolderGitIcon
1383
- : leaf.startsWith("variable_")
1384
- ? BoxIcon
1385
- : leaf.startsWith("environment_")
1386
- ? KeyRoundIcon
1387
- : leaf.includes("document") ||
1388
- leaf.includes("knowledge") ||
1389
- leaf === "list_document_bases"
1390
- ? FileSearchIcon
1391
- : leaf === "tool_search" || leaf === "load_skill"
1392
- ? PlugIcon
1393
- : WrenchIcon;
1542
+ const Icon =
1543
+ leaf === "request_human_input"
1544
+ ? MessageCircleQuestionIcon
1545
+ : leaf.startsWith("goal_")
1546
+ ? TargetIcon
1547
+ : leaf.startsWith("memory_") ||
1548
+ leaf === "preference_registry_summary" ||
1549
+ leaf === "preference_registry_get"
1550
+ ? BrainCircuitIcon
1551
+ : leaf.startsWith("session_") ||
1552
+ leaf === "sessions_list" ||
1553
+ leaf === "set_session_title" ||
1554
+ leaf === "set_other_session_title"
1555
+ ? MessagesSquareIcon
1556
+ : leaf.startsWith("sandbox") || leaf === "sandboxes_list" || leaf === "run_on"
1557
+ ? ServerIcon
1558
+ : leaf.startsWith("rig_")
1559
+ ? ServerCogIcon
1560
+ : leaf.startsWith("scheduled_")
1561
+ ? CalendarClockIcon
1562
+ : leaf.startsWith("artifacts_")
1563
+ ? PanelsTopLeftIcon
1564
+ : leaf.startsWith("social_")
1565
+ ? Share2Icon
1566
+ : leaf.startsWith("slack_")
1567
+ ? MessageSquareIcon
1568
+ : leaf.startsWith("github_")
1569
+ ? FolderGitIcon
1570
+ : leaf.startsWith("variable_")
1571
+ ? BoxIcon
1572
+ : leaf.startsWith("environment_")
1573
+ ? KeyRoundIcon
1574
+ : leaf.includes("document") ||
1575
+ leaf.includes("knowledge") ||
1576
+ leaf === "list_document_bases"
1577
+ ? FileSearchIcon
1578
+ : leaf === "tool_search" || leaf === "load_skill"
1579
+ ? PlugIcon
1580
+ : WrenchIcon;
1394
1581
  return <Icon className={ICON_SIZE} />;
1395
1582
  }
1396
1583
 
@@ -1404,6 +1591,8 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
1404
1591
  { match: "rawType", type: "hosted_tool_call", render: WebSearchRenderer },
1405
1592
  // First-party sandbox + MCP tools resolve by name (exact or MCP leaf).
1406
1593
  { match: "name", name: "exec_command", render: ExecRenderer },
1594
+ { match: "name", name: "request_human_input", render: AskRenderer },
1595
+ { match: "name", name: "run_on", render: RunOnRenderer },
1407
1596
  { match: "name", name: "write_stdin", render: WriteStdinRenderer },
1408
1597
  { match: "name", name: "apply_patch_call", render: ApplyPatchRenderer },
1409
1598
  { match: "name", name: "apply_patch", render: ApplyPatchRenderer },
@@ -13,6 +13,11 @@ export type UserMessageItem = {
13
13
  kind: "user-message";
14
14
  id: string;
15
15
  text: string;
16
+ presentation?: {
17
+ kind: "realtime_voice" | "realtime_voice_handoff";
18
+ /** Full bounded execution context sent with this visible voice request. */
19
+ context: string;
20
+ };
16
21
  /** Resources attached to this message (file uploads, repositories). */
17
22
  resources: ResourceRef[];
18
23
  /** Tools requested for the turn this message starts. */
package/styles/index.css CHANGED
@@ -19,6 +19,11 @@
19
19
  @theme inline {
20
20
  --font-og-sans: var(--og-font-sans);
21
21
  --font-og-mono: var(--og-font-mono);
22
+ /* Compatibility aliases still used by published components and the exact
23
+ dropdown primitives shared with the web app. Keep the public stylesheet
24
+ self-contained instead of requiring consumers to copy the app theme. */
25
+ --font-sans: var(--og-font-sans);
26
+ --font-mono: var(--og-font-mono);
22
27
 
23
28
  /* Type ramp — a real 4-step scale (no half-pixel hand-tuning). Every
24
29
  timeline string maps to one of these via `text-og-{xs,sm,base,md}`;
@@ -31,6 +36,8 @@
31
36
  --text-og-base--line-height: 1.55;
32
37
  --text-og-md: 15px;
33
38
  --text-og-md--line-height: 1.6;
39
+ --text-2xs: 0.6875rem;
40
+ --text-2xs--line-height: 1rem;
34
41
 
35
42
  --color-og-bg: var(--og-color-bg);
36
43
  --color-og-surface-1: var(--og-color-surface-1);
@@ -44,6 +51,7 @@
44
51
 
45
52
  --color-og-accent: var(--og-color-accent);
46
53
  --color-og-accent-strong: var(--og-color-accent-strong);
54
+ --color-og-accent-deep: var(--og-color-accent-deep);
47
55
  --color-og-accent-fg: var(--og-color-accent-fg);
48
56
  --color-og-accent-soft: var(--og-color-accent-soft);
49
57
 
@@ -55,11 +63,64 @@
55
63
  --color-og-status-cancelled: var(--og-color-status-cancelled);
56
64
  --color-og-danger: var(--og-color-danger);
57
65
 
66
+ --color-bg: var(--og-color-bg);
67
+ --color-surface: var(--og-color-surface-1);
68
+ --color-surface-2: var(--og-color-surface-2);
69
+ --color-surface-3: var(--og-color-surface-3);
70
+ --color-border: var(--og-color-border);
71
+ --color-border-strong: var(--og-color-border-strong);
72
+ --color-fg: var(--og-color-fg);
73
+ --color-fg-muted: var(--og-color-fg-muted);
74
+ --color-fg-subtle: var(--og-color-fg-subtle);
75
+ --color-brand: var(--og-color-accent);
76
+ --color-brand-strong: var(--og-color-accent-deep);
77
+ --color-brand-fg: var(--og-color-accent-fg);
78
+ --color-status-queued: var(--og-color-status-queued);
79
+ --color-status-running: var(--og-color-status-running);
80
+ --color-status-idle: var(--og-color-status-idle);
81
+ --color-status-waiting: var(--og-color-status-waiting);
82
+ --color-status-success: var(--og-color-status-idle);
83
+ --color-status-failed: var(--og-color-status-failed);
84
+ --color-status-cancelled: var(--og-color-status-cancelled);
85
+ --color-danger: var(--og-color-danger);
86
+
87
+ --color-background: var(--color-bg);
88
+ --color-foreground: var(--color-fg);
89
+ --color-card: var(--color-surface);
90
+ --color-card-foreground: var(--color-fg);
91
+ --color-popover: var(--color-surface-3);
92
+ --color-popover-foreground: var(--color-fg);
93
+ --color-primary: var(--color-brand-strong);
94
+ --color-primary-foreground: var(--color-brand-fg);
95
+ --color-secondary: var(--color-surface-2);
96
+ --color-secondary-foreground: var(--color-fg);
97
+ --color-muted: var(--color-surface-2);
98
+ --color-muted-foreground: var(--color-fg-muted);
99
+ --color-accent: var(--color-surface-2);
100
+ --color-accent-foreground: var(--color-fg);
101
+ --color-destructive: var(--og-color-danger);
102
+ --color-destructive-filled: color-mix(in oklch, var(--og-color-danger) 70%, black);
103
+ --color-destructive-foreground: var(--og-color-accent-fg);
104
+ --color-input: var(--color-surface-2);
105
+ --color-ring: var(--color-brand);
106
+ --color-sidebar: var(--color-surface);
107
+ --color-sidebar-foreground: var(--color-fg);
108
+ --color-sidebar-primary: var(--color-brand-strong);
109
+ --color-sidebar-primary-foreground: var(--color-brand-fg);
110
+ --color-sidebar-accent: var(--color-surface-2);
111
+ --color-sidebar-accent-foreground: var(--color-fg);
112
+ --color-sidebar-border: var(--color-border);
113
+ --color-sidebar-ring: var(--color-brand);
114
+
58
115
  --radius-og-xs: var(--og-radius-xs);
59
116
  --radius-og-sm: var(--og-radius-sm);
60
117
  --radius-og-md: var(--og-radius-md);
61
118
  --radius-og-lg: var(--og-radius-lg);
62
119
  --radius-og-xl: var(--og-radius-xl);
120
+ --radius-sm: var(--og-radius-sm);
121
+ --radius-md: var(--og-radius-md);
122
+ --radius-lg: var(--og-radius-lg);
123
+ --radius-xl: var(--og-radius-xl);
63
124
 
64
125
  --shadow-og-sm: var(--og-shadow-sm);
65
126
  --shadow-og-md: var(--og-shadow-md);
package/styles/tokens.css CHANGED
@@ -107,6 +107,8 @@
107
107
  --og-session-chrome-rail-pad: 0.2rem;
108
108
  --og-session-chrome-panel-pad-x: 0.5rem;
109
109
  --og-session-chrome-panel-pad-y: 0.4rem;
110
+ /* Expanded agents/queue/inbox/goal body — scroll inside, never grow unbound. */
111
+ --og-session-chrome-panel-max-height: min(18rem, 40dvh);
110
112
  --og-session-chrome-duration: 220ms;
111
113
  --og-session-chrome-crossfade-duration: 180ms;
112
114
  --og-session-chrome-ease: var(--og-ease-out);
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/model-policy.ts"],"sourcesContent":["import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from \"@opengeni/sdk\";\n\nexport type PickerBillingClass = \"opengeni_credits\" | \"codex_subscription\" | \"byok\";\n\nexport type PickerModelRow = {\n id: string;\n label: string;\n billingClass: PickerBillingClass;\n billingClassLabel: string;\n selectable: boolean;\n unavailableReason: string | null;\n provider: string;\n providerLabel: string;\n catalog: WorkspaceModelCatalogModel;\n};\n\nexport type LatencyModeId = \"standard\" | \"priority\" | \"fast\";\n\nconst BILLING_CLASS_ORDER: PickerBillingClass[] = [\n \"opengeni_credits\",\n \"codex_subscription\",\n \"byok\",\n];\n\nconst BILLING_CLASS_LABELS: Record<PickerBillingClass, string> = {\n opengeni_credits: \"OpenGeni\",\n codex_subscription: \"Codex\",\n byok: \"Your Gateway\",\n};\n\nconst AVAILABILITY_REASON_LABELS: Record<string, string> = {\n missing_credential: \"Credentials required\",\n needs_reauth: \"Reconnect required\",\n credential_not_ready: \"Credential not ready\",\n not_entitled: \"Not entitled\",\n provider_unhealthy: \"Provider unavailable\",\n policy_blocked: \"Blocked by workspace policy\",\n unsupported: \"Unsupported\",\n};\n\nexport function billingClassForModel(model: ClientModel): PickerBillingClass {\n if (model.source === \"codex\") {\n return \"codex_subscription\";\n }\n if (model.source === \"workspace_gateway\") {\n return \"byok\";\n }\n if (model.credentialSource?.kind === \"connected_subscription\") {\n return \"codex_subscription\";\n }\n if (model.credentialSource?.kind === \"workspace_connection\") {\n return \"byok\";\n }\n if (model.billing?.upstreamPayer === \"connected_subscription\") {\n return \"codex_subscription\";\n }\n if (model.billing?.upstreamPayer === \"workspace\") {\n return \"byok\";\n }\n return \"opengeni_credits\";\n}\n\nexport function billingClassLabel(billingClass: PickerBillingClass): string {\n return BILLING_CLASS_LABELS[billingClass];\n}\n\nexport function availabilityReasonLabel(\n reason: WorkspaceModelCatalogModel[\"availability\"][\"reason\"],\n): string | null {\n if (!reason) {\n return null;\n }\n return AVAILABILITY_REASON_LABELS[reason] ?? \"Unavailable\";\n}\n\nexport function effortOptionsForModel(model: ClientModel): ReasoningEffort[] {\n const efforts = model.capabilities?.reasoning.efforts;\n if (!efforts || efforts.length === 0) {\n return [\"low\"];\n }\n const order: ReasoningEffort[] = [\"none\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\"];\n return order.filter((effort) => efforts.includes(effort));\n}\n\nexport function defaultEffortForModel(model: ClientModel): ReasoningEffort {\n const options = effortOptionsForModel(model);\n const configured = model.capabilities?.reasoning.defaultEffort;\n if (configured && options.includes(configured)) {\n return configured;\n }\n return options[0] ?? \"low\";\n}\n\nexport function coerceReasoningEffortForModel(\n model: ClientModel,\n effort: ReasoningEffort,\n): ReasoningEffort {\n const options = effortOptionsForModel(model);\n if (options.includes(effort)) {\n return effort;\n }\n return defaultEffortForModel(model);\n}\n\nexport function runnableLatencyModesForModel(model: ClientModel): LatencyModeId[] {\n const modes = model.capabilities?.latencyModes ?? [];\n return modes.filter((mode) => mode.runnable).map((mode) => mode.id);\n}\n\nexport function labelLatencyMode(mode: LatencyModeId): string {\n if (mode === \"fast\") {\n return \"Fast\";\n }\n if (mode === \"priority\") {\n return \"Priority\";\n }\n return \"Standard\";\n}\n\nexport function payerSummaryForModel(model: ClientModel): string {\n const billing = model.billing;\n if (!billing) {\n return \"Route unknown\";\n }\n if (billing.metering === \"opengeni_credits\") {\n return \"OpenGeni credits · automatic managed route\";\n }\n if (billing.upstreamPayer === \"connected_subscription\") {\n return \"Codex subscription · external billing\";\n }\n if (billing.upstreamPayer === \"workspace\") {\n return \"Billed to your AI Gateway\";\n }\n return \"Deployment route · external billing\";\n}\n\nexport function advancedSourceSummary(model: ClientModel): string | null {\n const source = model.credentialSource;\n if (!source) {\n return null;\n }\n if (source.kind === \"connected_subscription\") {\n return \"Connected Codex subscription\";\n }\n if (source.kind === \"workspace_connection\") {\n return \"Workspace AI Gateway\";\n }\n if (source.kind === \"deployment\") {\n return source.mechanism === \"azure_ad_bearer\"\n ? \"Deployment Azure identity\"\n : \"Deployment API key\";\n }\n return null;\n}\n\nexport function projectPickerRows(models: WorkspaceModelCatalogModel[]): PickerModelRow[] {\n return models\n .filter((catalog) => {\n const billingClass = billingClassForModel(catalog);\n return billingClass === \"opengeni_credits\" || catalog.credentialReadiness.status === \"ready\";\n })\n .map((catalog) => {\n const billingClass = billingClassForModel(catalog);\n return {\n id: catalog.id,\n label: catalog.label,\n billingClass,\n billingClassLabel: billingClassLabel(billingClass),\n selectable: catalog.availability.selectable,\n unavailableReason: catalog.availability.selectable\n ? null\n : availabilityReasonLabel(catalog.availability.reason),\n provider: catalog.provider,\n providerLabel: catalog.providerLabel,\n catalog,\n };\n });\n}\n\nexport function sortPickerRows(rows: PickerModelRow[]): PickerModelRow[] {\n return [...rows].sort((left, right) => {\n const classDelta =\n BILLING_CLASS_ORDER.indexOf(left.billingClass) -\n BILLING_CLASS_ORDER.indexOf(right.billingClass);\n if (classDelta !== 0) {\n return classDelta;\n }\n if (left.selectable !== right.selectable) {\n return left.selectable ? -1 : 1;\n }\n return left.label.localeCompare(right.label);\n });\n}\n\nexport function findPickerRow(rows: PickerModelRow[], modelId: string): PickerModelRow | null {\n return rows.find((row) => row.id === modelId) ?? null;\n}\n\nexport function groupPickerRowsByBillingClass(\n rows: PickerModelRow[],\n): Array<{ billingClass: PickerBillingClass; label: string; rows: PickerModelRow[] }> {\n const sorted = sortPickerRows(rows);\n const groups: Array<{\n billingClass: PickerBillingClass;\n label: string;\n rows: PickerModelRow[];\n }> = [];\n for (const row of sorted) {\n const existing = groups.find((group) => group.billingClass === row.billingClass);\n if (existing) {\n existing.rows.push(row);\n continue;\n }\n groups.push({\n billingClass: row.billingClass,\n label: row.billingClassLabel,\n rows: [row],\n });\n }\n return groups;\n}\n"],"mappings":"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","names":[]}