@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,433 @@
1
+ import {
2
+ coerceReasoningEffortForModel,
3
+ effortOptionsForModel,
4
+ findPickerRow,
5
+ groupPickerRowsByBillingClass,
6
+ labelReasoningEffort,
7
+ projectClientModelRows,
8
+ runnableLatencyModesForModel
9
+ } from "./chunk-SJKT4TKW.js";
10
+ import {
11
+ cn
12
+ } from "./chunk-TK7G6XLT.js";
13
+
14
+ // src/components/model-policy-picker.tsx
15
+ import {
16
+ CheckIcon,
17
+ ChevronDownIcon,
18
+ ChevronLeftIcon,
19
+ ChevronRightIcon,
20
+ KeyRoundIcon,
21
+ ZapIcon
22
+ } from "lucide-react";
23
+ import { motion, useReducedMotion } from "motion/react";
24
+ import { DropdownMenu } from "radix-ui";
25
+ import { useEffect, useMemo, useState } from "react";
26
+ import { jsx, jsxs } from "react/jsx-runtime";
27
+ var defaultModelPolicyPickerMessages = {
28
+ label: "Model and effort",
29
+ loading: "Loading model catalog\u2026",
30
+ noModels: "No models available.",
31
+ thinking: "Thinking",
32
+ fast: "Fast",
33
+ fastRateHint: "2\xD7 rate",
34
+ codexOnly: "Codex-only session",
35
+ billingHints: {
36
+ opengeni_credits: "Will use credits",
37
+ codex_subscription: "ChatGPT / Codex plan",
38
+ byok: "Billed to your AI Gateway"
39
+ }
40
+ };
41
+ var SLIDE_EASE = [0.22, 1, 0.36, 1];
42
+ function OpenGeniMark(props) {
43
+ return /* @__PURE__ */ jsx(
44
+ "svg",
45
+ {
46
+ viewBox: "0 0 140 133",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ strokeWidth: 1.2,
50
+ strokeLinejoin: "round",
51
+ "aria-hidden": "true",
52
+ ...props,
53
+ children: /* @__PURE__ */ jsx("g", { transform: "translate(-17.5,-20.999893188476562) scale(1.75)", children: /* @__PURE__ */ jsx("g", { transform: "translate(0,-952.36218)", children: /* @__PURE__ */ jsx("path", { d: "m 60.7828,964.36215 27.1809,0.8834 -27.1809,25.9958 z m -1.9745,1.4513 0,26.7845 -25.2681,0 c 8.6166,-8.7334 16.8796,-17.8103 25.2681,-26.7845 z m 27.7053,3.628 3.4864,1.1989 -12.5877,7.4768 z m -68.1835,2.9656 5.5226,0 12.8654,14.0705 -5.9854,6.1204 -12.4026,0 c 9e-4,-6.7347 0,-13.4597 0,-20.1909 z m -1.9746,1.2304 0,5.8364 -6.3555,0 z m 3.363,20.9796 38.627,0 -10.7675,29.43465 z m 39.0898,4.54286 0,41.20229 -12.5878,-6.8775 c 4.1972,-11.443 8.3886,-22.879 12.5878,-34.32479 z" }) }) })
54
+ }
55
+ );
56
+ }
57
+ function ChatGptMark(props) {
58
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", ...props, children: /* @__PURE__ */ jsx("path", { d: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3653-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8414 3.3698-2.02 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.783-2.7622a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" }) });
59
+ }
60
+ function BillingClassMark(props) {
61
+ const labels = {
62
+ opengeni_credits: "OpenGeni",
63
+ codex_subscription: "Codex",
64
+ byok: "Bring your own key"
65
+ };
66
+ const label = props["aria-label"] ?? labels[props.billingClass];
67
+ const accessibility = label.length === 0 ? { "aria-hidden": true } : { role: "img", "aria-label": label };
68
+ const shell = cn(
69
+ "inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle",
70
+ props.className
71
+ );
72
+ const mark = "size-3.5";
73
+ return /* @__PURE__ */ jsx(
74
+ "span",
75
+ {
76
+ className: shell,
77
+ "data-testid": `billing-class-icon-${props.billingClass}`,
78
+ ...accessibility,
79
+ children: props.billingClass === "opengeni_credits" ? /* @__PURE__ */ jsx(OpenGeniMark, { className: mark }) : props.billingClass === "codex_subscription" ? /* @__PURE__ */ jsx(ChatGptMark, { className: mark }) : /* @__PURE__ */ jsx(KeyRoundIcon, { className: mark, "aria-hidden": true })
80
+ }
81
+ );
82
+ }
83
+ function isCodexModel(model) {
84
+ return model.id.startsWith("codex/") || model.source === "codex";
85
+ }
86
+ function applyCodexOnly(rows, codexOnly, unavailableReason) {
87
+ if (!codexOnly) return rows;
88
+ return rows.map(
89
+ (row) => isCodexModel(row.catalog) ? row : {
90
+ ...row,
91
+ selectable: false,
92
+ unavailableReason: row.unavailableReason ?? unavailableReason
93
+ }
94
+ );
95
+ }
96
+ function effectiveRows(props) {
97
+ const rows = props.rows !== void 0 ? props.rows : projectClientModelRows(props.models ?? []);
98
+ const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
99
+ return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly);
100
+ }
101
+ function defaultNavState(rows, modelId) {
102
+ const selected = findPickerRow(rows, modelId);
103
+ if (!selected) {
104
+ const rails = new Set(rows.map((row) => row.billingClass));
105
+ const onlyRail = rails.size === 1 ? rows[0]?.billingClass : null;
106
+ return onlyRail ? { level: "models", rail: onlyRail, modelId: null } : { level: "providers", rail: null, modelId: null };
107
+ }
108
+ return { level: "thinking", rail: selected.billingClass, modelId: selected.id };
109
+ }
110
+ function usePickerNavState(sessionKey, rows, model) {
111
+ const [state, setState] = useState(() => defaultNavState(rows, model));
112
+ useEffect(() => {
113
+ setState(defaultNavState(rows, model));
114
+ }, [sessionKey]);
115
+ return [state, setState];
116
+ }
117
+ function PickerNavRow(props) {
118
+ return /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ disabled: props.disabled,
123
+ title: props.title,
124
+ onClick: props.onClick,
125
+ "data-testid": props.testId,
126
+ className: cn(
127
+ "flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-2.5 py-2 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40",
128
+ props.disabled && "cursor-not-allowed opacity-50"
129
+ ),
130
+ children: [
131
+ props.icon,
132
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
133
+ /* @__PURE__ */ jsx("span", { className: cn("block truncate text-sm", props.active && "font-medium"), children: props.label }),
134
+ props.hint ? /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-xs text-og-fg-subtle", children: props.hint }) : null
135
+ ] }),
136
+ props.trailing ? /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0", children: props.trailing }) : null,
137
+ props.showChevron === false ? null : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle" })
138
+ ]
139
+ }
140
+ );
141
+ }
142
+ function PickerBackHeader(props) {
143
+ return /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center gap-0.5 border-b border-og-border/70 px-0.5 pb-1.5", children: [
144
+ /* @__PURE__ */ jsxs(
145
+ "button",
146
+ {
147
+ type: "button",
148
+ onClick: props.onBack,
149
+ "data-testid": "model-picker-back",
150
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40",
151
+ children: [
152
+ /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5 shrink-0 text-og-fg-subtle" }),
153
+ props.icon,
154
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: props.label })
155
+ ]
156
+ }
157
+ ),
158
+ props.trailing ? /* @__PURE__ */ jsx("div", { className: "relative z-10 shrink-0", children: props.trailing }) : null
159
+ ] });
160
+ }
161
+ function PickerAnimatedPage(props) {
162
+ const reduceMotion = useReducedMotion();
163
+ return /* @__PURE__ */ jsx(
164
+ motion.div,
165
+ {
166
+ initial: reduceMotion ? false : { x: props.direction * 14, opacity: 0 },
167
+ animate: { x: 0, opacity: 1 },
168
+ transition: { duration: reduceMotion ? 0 : 0.18, ease: SLIDE_EASE },
169
+ className: "w-full",
170
+ children: props.children
171
+ },
172
+ props.pageKey
173
+ );
174
+ }
175
+ function ModelPolicyPickerMenu(props) {
176
+ const messages = useMemo(
177
+ () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),
178
+ [props.messages]
179
+ );
180
+ const rows = effectiveRows(props);
181
+ const groups = useMemo(() => groupPickerRowsByBillingClass(rows), [rows]);
182
+ const [localNav, setLocalNav] = usePickerNavState(
183
+ props.nav === void 0 ? props.sessionKey : void 0,
184
+ rows,
185
+ props.model
186
+ );
187
+ const nav = props.nav ?? localNav;
188
+ const setNav = props.onNavChange ?? setLocalNav;
189
+ const [direction, setDirection] = useState(1);
190
+ const effectiveNav = nav.level === "providers" && groups.length === 1 ? { level: "models", rail: groups[0].billingClass, modelId: null } : nav;
191
+ const activeGroup = effectiveNav.rail === null ? void 0 : groups.find((group) => group.billingClass === effectiveNav.rail);
192
+ const focusModel = effectiveNav.modelId === null ? void 0 : findPickerRow(rows, effectiveNav.modelId);
193
+ const selectedRow = findPickerRow(rows, props.model);
194
+ const { latencyMode, loading, onLatencyModeChange } = props;
195
+ useEffect(() => {
196
+ if (loading || !selectedRow?.selectable) return;
197
+ const supportsCurrentMode = runnableLatencyModesForModel(selectedRow.catalog).includes(
198
+ latencyMode
199
+ );
200
+ if (!supportsCurrentMode && latencyMode !== "standard") {
201
+ onLatencyModeChange("standard");
202
+ }
203
+ }, [latencyMode, loading, onLatencyModeChange, selectedRow]);
204
+ const go = (next, nextDirection) => {
205
+ setDirection(nextDirection);
206
+ setNav(next);
207
+ };
208
+ const selectEffort = (row, effort) => {
209
+ if (!row.selectable) return;
210
+ if (row.id !== props.model) props.onModelChange(row.id);
211
+ props.onEffortChange(coerceReasoningEffortForModel(row.catalog, effort));
212
+ };
213
+ const pageKey = effectiveNav.level === "providers" ? "providers" : effectiveNav.level === "models" ? `models:${effectiveNav.rail ?? ""}` : `thinking:${effectiveNav.modelId ?? ""}`;
214
+ let body;
215
+ if (props.loading) {
216
+ body = /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-og-fg-subtle", children: messages.loading });
217
+ } else if (groups.length === 0) {
218
+ body = /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-xs text-og-fg-subtle", children: messages.noModels });
219
+ } else if (effectiveNav.level === "providers") {
220
+ body = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "model-picker-providers", children: groups.map((group) => /* @__PURE__ */ jsx(
221
+ PickerNavRow,
222
+ {
223
+ label: group.label,
224
+ hint: messages.billingHints[group.billingClass],
225
+ icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: group.billingClass }),
226
+ active: selectedRow?.billingClass === group.billingClass,
227
+ testId: `model-picker-rail-${group.billingClass}`,
228
+ onClick: () => go({ level: "models", rail: group.billingClass, modelId: null }, 1)
229
+ },
230
+ group.billingClass
231
+ )) });
232
+ } else if (effectiveNav.level === "models" && activeGroup) {
233
+ body = /* @__PURE__ */ jsxs("div", { "data-testid": "model-picker-models", children: [
234
+ groups.length > 1 ? /* @__PURE__ */ jsx(
235
+ PickerBackHeader,
236
+ {
237
+ label: activeGroup.label,
238
+ icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: activeGroup.billingClass }),
239
+ onBack: () => go({ level: "providers", rail: null, modelId: null }, -1)
240
+ }
241
+ ) : null,
242
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: activeGroup.rows.map((row) => /* @__PURE__ */ jsx(
243
+ PickerNavRow,
244
+ {
245
+ label: row.label,
246
+ hint: row.unavailableReason ?? void 0,
247
+ disabled: !row.selectable,
248
+ title: row.unavailableReason ?? void 0,
249
+ active: row.id === props.model,
250
+ testId: `model-picker-choice-${row.id}`,
251
+ onClick: () => {
252
+ if (row.selectable) {
253
+ go({ level: "thinking", rail: row.billingClass, modelId: row.id }, 1);
254
+ }
255
+ }
256
+ },
257
+ `${row.billingClass}:${row.id}`
258
+ )) })
259
+ ] });
260
+ } else if (effectiveNav.level === "thinking" && focusModel) {
261
+ const activeModel = focusModel.id === props.model;
262
+ const supportsFast = runnableLatencyModesForModel(focusModel.catalog).includes("fast");
263
+ body = /* @__PURE__ */ jsxs("div", { "data-testid": "model-picker-reasoning", children: [
264
+ /* @__PURE__ */ jsx(
265
+ PickerBackHeader,
266
+ {
267
+ label: focusModel.label,
268
+ icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: focusModel.billingClass }),
269
+ onBack: () => go({ level: "models", rail: focusModel.billingClass, modelId: null }, -1),
270
+ trailing: supportsFast ? /* @__PURE__ */ jsx(
271
+ "button",
272
+ {
273
+ type: "button",
274
+ disabled: !focusModel.selectable,
275
+ "aria-pressed": activeModel && props.latencyMode === "fast",
276
+ "aria-label": activeModel && props.latencyMode === "fast" ? `Disable ${messages.fast}` : `Enable ${messages.fast}`,
277
+ title: `${messages.fast} \xB7 ${messages.fastRateHint}`,
278
+ "data-testid": "model-picker-fast",
279
+ onClick: () => {
280
+ if (!activeModel) {
281
+ props.onModelChange(focusModel.id);
282
+ props.onEffortChange(
283
+ coerceReasoningEffortForModel(focusModel.catalog, props.effort)
284
+ );
285
+ }
286
+ props.onLatencyModeChange(
287
+ activeModel && props.latencyMode === "fast" ? "standard" : "fast"
288
+ );
289
+ },
290
+ className: cn(
291
+ "flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
292
+ activeModel && props.latencyMode === "fast" && "text-og-fg"
293
+ ),
294
+ children: /* @__PURE__ */ jsx(
295
+ ZapIcon,
296
+ {
297
+ className: cn(
298
+ "size-3.5",
299
+ activeModel && props.latencyMode === "fast" && "fill-current"
300
+ )
301
+ }
302
+ )
303
+ }
304
+ ) : null
305
+ }
306
+ ),
307
+ /* @__PURE__ */ jsx("p", { className: "px-2.5 pt-1 pb-1 text-xs font-medium tracking-wide text-og-fg-subtle uppercase", children: messages.thinking }),
308
+ focusModel.catalog.capabilities?.inputModalities.includes("image") === false ? /* @__PURE__ */ jsx("p", { className: "px-2.5 pb-1.5 text-xs leading-relaxed text-og-fg-subtle", children: "Unsupported attachments stay in the session but are hidden from this model." }) : null,
309
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", children: effortOptionsForModel(focusModel.catalog).map((effort) => {
310
+ const selected = activeModel && effort === props.effort;
311
+ return /* @__PURE__ */ jsxs(
312
+ "button",
313
+ {
314
+ type: "button",
315
+ disabled: !focusModel.selectable,
316
+ onClick: () => selectEffort(focusModel, effort),
317
+ className: cn(
318
+ "flex h-8 w-full cursor-pointer items-center rounded-og-sm px-2.5 text-left text-sm text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50",
319
+ selected && "bg-og-surface-2"
320
+ ),
321
+ children: [
322
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: labelReasoningEffort(effort) }),
323
+ selected ? /* @__PURE__ */ jsx(
324
+ CheckIcon,
325
+ {
326
+ className: "ml-auto size-3.5 shrink-0",
327
+ "data-testid": "model-picker-effort-check"
328
+ }
329
+ ) : null
330
+ ]
331
+ },
332
+ effort
333
+ );
334
+ }) })
335
+ ] });
336
+ } else {
337
+ body = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "model-picker-providers", children: groups.map((group) => /* @__PURE__ */ jsx(
338
+ PickerNavRow,
339
+ {
340
+ label: group.label,
341
+ hint: messages.billingHints[group.billingClass],
342
+ icon: /* @__PURE__ */ jsx(BillingClassMark, { billingClass: group.billingClass }),
343
+ testId: `model-picker-rail-${group.billingClass}`,
344
+ onClick: () => go({ level: "models", rail: group.billingClass, modelId: null }, 1)
345
+ },
346
+ group.billingClass
347
+ )) });
348
+ }
349
+ return /* @__PURE__ */ jsxs("div", { "data-testid": "model-picker-menu", className: "relative overflow-hidden", children: [
350
+ props.error ? /* @__PURE__ */ jsx("p", { className: "px-2 py-1 text-xs text-og-status-failed", role: "alert", children: props.error }) : null,
351
+ /* @__PURE__ */ jsx(PickerAnimatedPage, { pageKey, direction, children: body })
352
+ ] });
353
+ }
354
+ function ModelPolicyPicker(props) {
355
+ const [open, setOpen] = useState(false);
356
+ const messages = useMemo(
357
+ () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),
358
+ [props.messages]
359
+ );
360
+ const rows = effectiveRows(props);
361
+ const [nav, setNav] = usePickerNavState(props.sessionKey, rows, props.model);
362
+ const selected = findPickerRow(rows, props.model);
363
+ if (props.loading) {
364
+ return /* @__PURE__ */ jsx(
365
+ "span",
366
+ {
367
+ className: cn(
368
+ "inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2",
369
+ props.className
370
+ ),
371
+ "aria-label": messages.loading,
372
+ "data-testid": "model-picker-loading"
373
+ }
374
+ );
375
+ }
376
+ return /* @__PURE__ */ jsxs(DropdownMenu.Root, { open, onOpenChange: setOpen, children: [
377
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
378
+ "button",
379
+ {
380
+ type: "button",
381
+ disabled: props.disabled,
382
+ "aria-label": messages.label,
383
+ className: cn(
384
+ "inline-flex h-8 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-xs text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-7 max-sm:max-w-[9rem] max-sm:px-2",
385
+ props.className
386
+ ),
387
+ children: [
388
+ /* @__PURE__ */ jsx(
389
+ BillingClassMark,
390
+ {
391
+ billingClass: selected?.billingClass ?? (props.model.startsWith("codex/") ? "codex_subscription" : "opengeni_credits"),
392
+ className: "text-og-fg"
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium text-og-fg", children: selected?.label ?? props.model }),
396
+ /* @__PURE__ */ jsx("span", { className: "max-sm:hidden", children: labelReasoningEffort(props.effort) }),
397
+ props.latencyMode === "fast" ? /* @__PURE__ */ jsx(
398
+ ZapIcon,
399
+ {
400
+ className: "size-3.5 shrink-0 fill-current stroke-current text-og-fg",
401
+ "aria-label": messages.fast,
402
+ "data-testid": "model-picker-fast-icon"
403
+ }
404
+ ) : null,
405
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0" })
406
+ ]
407
+ }
408
+ ) }),
409
+ /* @__PURE__ */ jsx(DropdownMenu.Portal, { children: /* @__PURE__ */ jsx(
410
+ DropdownMenu.Content,
411
+ {
412
+ align: "start",
413
+ side: props.menuSide ?? "bottom",
414
+ sideOffset: 8,
415
+ collisionPadding: 12,
416
+ onCloseAutoFocus: (event) => event.preventDefault(),
417
+ className: "z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-72 max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-1.5 text-og-fg shadow-og-lg",
418
+ children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overscroll-contain", children: /* @__PURE__ */ jsx(ModelPolicyPickerMenu, { ...props, nav, onNavChange: setNav }) })
419
+ }
420
+ ) })
421
+ ] });
422
+ }
423
+
424
+ export {
425
+ defaultModelPolicyPickerMessages,
426
+ BillingClassMark,
427
+ PickerNavRow,
428
+ PickerBackHeader,
429
+ PickerAnimatedPage,
430
+ ModelPolicyPickerMenu,
431
+ ModelPolicyPicker
432
+ };
433
+ //# sourceMappingURL=chunk-WZT5G5OR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/model-policy-picker.tsx"],"sourcesContent":["import type { ClientModel, LatencyMode, ReasoningEffort } from \"@opengeni/sdk\";\nimport {\n CheckIcon,\n ChevronDownIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n KeyRoundIcon,\n ZapIcon,\n} from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { DropdownMenu } from \"radix-ui\";\nimport { useEffect, useMemo, useState, type ReactNode, type SVGProps } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n coerceReasoningEffortForModel,\n effortOptionsForModel,\n findPickerRow,\n groupPickerRowsByBillingClass,\n labelReasoningEffort,\n projectClientModelRows,\n runnableLatencyModesForModel,\n type PickerBillingClass,\n type PickerModelRow,\n} from \"../model-policy\";\n\ntype ClientPickerModelRow = PickerModelRow<ClientModel>;\n\ntype NavLevel = \"providers\" | \"models\" | \"thinking\";\n\ntype PickerNavState = {\n level: NavLevel;\n rail: PickerBillingClass | null;\n modelId: string | null;\n};\n\ntype NavUpdater = PickerNavState | ((previous: PickerNavState) => PickerNavState);\n\nexport type ModelPolicyPickerMessages = {\n label: string;\n loading: string;\n noModels: string;\n thinking: string;\n fast: string;\n fastRateHint: string;\n codexOnly: string;\n billingHints: Record<PickerBillingClass, string>;\n};\n\nexport const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {\n label: \"Model and effort\",\n loading: \"Loading model catalog…\",\n noModels: \"No models available.\",\n thinking: \"Thinking\",\n fast: \"Fast\",\n fastRateHint: \"2× rate\",\n codexOnly: \"Codex-only session\",\n billingHints: {\n opengeni_credits: \"Will use credits\",\n codex_subscription: \"ChatGPT / Codex plan\",\n byok: \"Billed to your AI Gateway\",\n },\n};\n\nexport type ModelPolicyPickerProps = {\n /** Lightweight deployment models. Catalog rows take precedence when supplied. */\n models?: ClientModel[] | undefined;\n /** Catalog-backed rows with availability and billing-class truth. */\n rows?: PickerModelRow[] | undefined;\n model: string;\n effort: ReasoningEffort;\n latencyMode: LatencyMode;\n disabled?: boolean | undefined;\n loading?: boolean | undefined;\n error?: string | null | undefined;\n /** Non-Codex models stay visible but cannot be selected. */\n codexOnly?: boolean | undefined;\n /** Resets drill-down navigation when the session scope changes. */\n sessionKey?: string | undefined;\n /** Prefer bottom on new-chat surfaces and top for bottom-docked composers. */\n menuSide?: \"top\" | \"bottom\" | undefined;\n className?: string | undefined;\n messages?: Partial<ModelPolicyPickerMessages> | undefined;\n onModelChange: (modelId: string) => void;\n onEffortChange: (effort: ReasoningEffort) => void;\n onLatencyModeChange: (latencyMode: LatencyMode) => void;\n};\n\nconst SLIDE_EASE = [0.22, 1, 0.36, 1] as const;\n\nfunction OpenGeniMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg\n viewBox=\"0 0 140 133\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.2}\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n {...props}\n >\n <g transform=\"translate(-17.5,-20.999893188476562) scale(1.75)\">\n <g transform=\"translate(0,-952.36218)\">\n <path d=\"m 60.7828,964.36215 27.1809,0.8834 -27.1809,25.9958 z m -1.9745,1.4513 0,26.7845 -25.2681,0 c 8.6166,-8.7334 16.8796,-17.8103 25.2681,-26.7845 z m 27.7053,3.628 3.4864,1.1989 -12.5877,7.4768 z m -68.1835,2.9656 5.5226,0 12.8654,14.0705 -5.9854,6.1204 -12.4026,0 c 9e-4,-6.7347 0,-13.4597 0,-20.1909 z m -1.9746,1.2304 0,5.8364 -6.3555,0 z m 3.363,20.9796 38.627,0 -10.7675,29.43465 z m 39.0898,4.54286 0,41.20229 -12.5878,-6.8775 c 4.1972,-11.443 8.3886,-22.879 12.5878,-34.32479 z\" />\n </g>\n </g>\n </svg>\n );\n}\n\nfunction ChatGptMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n <path d=\"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3653-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8414 3.3698-2.02 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.783-2.7622a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z\" />\n </svg>\n );\n}\n\nexport function BillingClassMark(props: {\n billingClass: PickerBillingClass;\n className?: string | undefined;\n \"aria-label\"?: string | undefined;\n}) {\n const labels: Record<PickerBillingClass, string> = {\n opengeni_credits: \"OpenGeni\",\n codex_subscription: \"Codex\",\n byok: \"Bring your own key\",\n };\n const label = props[\"aria-label\"] ?? labels[props.billingClass];\n const accessibility =\n label.length === 0\n ? { \"aria-hidden\": true as const }\n : { role: \"img\" as const, \"aria-label\": label };\n const shell = cn(\n \"inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle\",\n props.className,\n );\n const mark = \"size-3.5\";\n return (\n <span\n className={shell}\n data-testid={`billing-class-icon-${props.billingClass}`}\n {...accessibility}\n >\n {props.billingClass === \"opengeni_credits\" ? (\n <OpenGeniMark className={mark} />\n ) : props.billingClass === \"codex_subscription\" ? (\n <ChatGptMark className={mark} />\n ) : (\n <KeyRoundIcon className={mark} aria-hidden />\n )}\n </span>\n );\n}\n\nfunction isCodexModel(model: ClientModel): boolean {\n return model.id.startsWith(\"codex/\") || model.source === \"codex\";\n}\n\nfunction applyCodexOnly(\n rows: ClientPickerModelRow[],\n codexOnly: boolean,\n unavailableReason: string,\n): ClientPickerModelRow[] {\n if (!codexOnly) return rows;\n return rows.map((row) =>\n isCodexModel(row.catalog)\n ? row\n : {\n ...row,\n selectable: false,\n unavailableReason: row.unavailableReason ?? unavailableReason,\n },\n );\n}\n\nfunction effectiveRows(props: ModelPolicyPickerProps): ClientPickerModelRow[] {\n const rows = props.rows !== undefined ? props.rows : projectClientModelRows(props.models ?? []);\n const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };\n return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly);\n}\n\nfunction defaultNavState(rows: ClientPickerModelRow[], modelId: string): PickerNavState {\n const selected = findPickerRow(rows, modelId);\n if (!selected) {\n const rails = new Set(rows.map((row) => row.billingClass));\n const onlyRail = rails.size === 1 ? rows[0]?.billingClass : null;\n return onlyRail\n ? { level: \"models\", rail: onlyRail, modelId: null }\n : { level: \"providers\", rail: null, modelId: null };\n }\n return { level: \"thinking\", rail: selected.billingClass, modelId: selected.id };\n}\n\nfunction usePickerNavState(\n sessionKey: string | undefined,\n rows: ClientPickerModelRow[],\n model: string,\n): [PickerNavState, (next: NavUpdater) => void] {\n const [state, setState] = useState<PickerNavState>(() => defaultNavState(rows, model));\n useEffect(() => {\n setState(defaultNavState(rows, model));\n // The menu deliberately preserves its drill-down while the same session is mounted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sessionKey]);\n return [state, setState];\n}\n\nexport function PickerNavRow(props: {\n label: string;\n hint?: string | undefined;\n icon?: ReactNode;\n trailing?: ReactNode;\n showChevron?: boolean | undefined;\n disabled?: boolean | undefined;\n title?: string | undefined;\n active?: boolean | undefined;\n testId?: string | undefined;\n onClick: () => void;\n}) {\n return (\n <button\n type=\"button\"\n disabled={props.disabled}\n title={props.title}\n onClick={props.onClick}\n data-testid={props.testId}\n className={cn(\n \"flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-2.5 py-2 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40\",\n props.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {props.icon}\n <span className=\"min-w-0 flex-1\">\n <span className={cn(\"block truncate text-sm\", props.active && \"font-medium\")}>\n {props.label}\n </span>\n {props.hint ? (\n <span className=\"mt-0.5 block truncate text-xs text-og-fg-subtle\">{props.hint}</span>\n ) : null}\n </span>\n {props.trailing ? <span className=\"ml-auto shrink-0\">{props.trailing}</span> : null}\n {props.showChevron === false ? null : (\n <ChevronRightIcon className=\"size-3.5 shrink-0 text-og-fg-subtle\" />\n )}\n </button>\n );\n}\n\nexport function PickerBackHeader(props: {\n label: string;\n icon?: ReactNode;\n onBack: () => void;\n trailing?: ReactNode;\n}) {\n return (\n <div className=\"mb-1 flex items-center gap-0.5 border-b border-og-border/70 px-0.5 pb-1.5\">\n <button\n type=\"button\"\n onClick={props.onBack}\n data-testid=\"model-picker-back\"\n className=\"flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40\"\n >\n <ChevronLeftIcon className=\"size-3.5 shrink-0 text-og-fg-subtle\" />\n {props.icon}\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">{props.label}</span>\n </button>\n {props.trailing ? <div className=\"relative z-10 shrink-0\">{props.trailing}</div> : null}\n </div>\n );\n}\n\nexport function PickerAnimatedPage(props: {\n pageKey: string;\n direction: 1 | -1;\n children: ReactNode;\n}) {\n const reduceMotion = useReducedMotion();\n return (\n <motion.div\n key={props.pageKey}\n initial={reduceMotion ? false : { x: props.direction * 14, opacity: 0 }}\n animate={{ x: 0, opacity: 1 }}\n transition={{ duration: reduceMotion ? 0 : 0.18, ease: SLIDE_EASE }}\n className=\"w-full\"\n >\n {props.children}\n </motion.div>\n );\n}\n\nexport function ModelPolicyPickerMenu(\n props: ModelPolicyPickerProps & {\n nav?: PickerNavState | undefined;\n onNavChange?: ((next: NavUpdater) => void) | undefined;\n },\n) {\n const messages = useMemo(\n () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),\n [props.messages],\n );\n const rows = effectiveRows(props);\n const groups = useMemo(() => groupPickerRowsByBillingClass(rows), [rows]);\n const [localNav, setLocalNav] = usePickerNavState(\n props.nav === undefined ? props.sessionKey : undefined,\n rows,\n props.model,\n );\n const nav = props.nav ?? localNav;\n const setNav = props.onNavChange ?? setLocalNav;\n const [direction, setDirection] = useState<1 | -1>(1);\n\n const effectiveNav =\n nav.level === \"providers\" && groups.length === 1\n ? { level: \"models\" as const, rail: groups[0]!.billingClass, modelId: null }\n : nav;\n const activeGroup =\n effectiveNav.rail === null\n ? undefined\n : groups.find((group) => group.billingClass === effectiveNav.rail);\n const focusModel =\n effectiveNav.modelId === null ? undefined : findPickerRow(rows, effectiveNav.modelId);\n const selectedRow = findPickerRow(rows, props.model);\n const { latencyMode, loading, onLatencyModeChange } = props;\n\n useEffect(() => {\n if (loading || !selectedRow?.selectable) return;\n const supportsCurrentMode = runnableLatencyModesForModel(selectedRow.catalog).includes(\n latencyMode,\n );\n if (!supportsCurrentMode && latencyMode !== \"standard\") {\n onLatencyModeChange(\"standard\");\n }\n }, [latencyMode, loading, onLatencyModeChange, selectedRow]);\n\n const go = (next: PickerNavState, nextDirection: 1 | -1) => {\n setDirection(nextDirection);\n setNav(next);\n };\n\n const selectEffort = (row: ClientPickerModelRow, effort: ReasoningEffort) => {\n if (!row.selectable) return;\n if (row.id !== props.model) props.onModelChange(row.id);\n props.onEffortChange(coerceReasoningEffortForModel(row.catalog, effort));\n };\n\n const pageKey =\n effectiveNav.level === \"providers\"\n ? \"providers\"\n : effectiveNav.level === \"models\"\n ? `models:${effectiveNav.rail ?? \"\"}`\n : `thinking:${effectiveNav.modelId ?? \"\"}`;\n\n let body: ReactNode;\n if (props.loading) {\n body = <p className=\"px-2 py-3 text-xs text-og-fg-subtle\">{messages.loading}</p>;\n } else if (groups.length === 0) {\n body = <p className=\"px-2 py-3 text-xs text-og-fg-subtle\">{messages.noModels}</p>;\n } else if (effectiveNav.level === \"providers\") {\n body = (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"model-picker-providers\">\n {groups.map((group) => (\n <PickerNavRow\n key={group.billingClass}\n label={group.label}\n hint={messages.billingHints[group.billingClass]}\n icon={<BillingClassMark billingClass={group.billingClass} />}\n active={selectedRow?.billingClass === group.billingClass}\n testId={`model-picker-rail-${group.billingClass}`}\n onClick={() => go({ level: \"models\", rail: group.billingClass, modelId: null }, 1)}\n />\n ))}\n </div>\n );\n } else if (effectiveNav.level === \"models\" && activeGroup) {\n body = (\n <div data-testid=\"model-picker-models\">\n {groups.length > 1 ? (\n <PickerBackHeader\n label={activeGroup.label}\n icon={<BillingClassMark billingClass={activeGroup.billingClass} />}\n onBack={() => go({ level: \"providers\", rail: null, modelId: null }, -1)}\n />\n ) : null}\n <div className=\"flex flex-col gap-0.5\">\n {activeGroup.rows.map((row) => (\n <PickerNavRow\n key={`${row.billingClass}:${row.id}`}\n label={row.label}\n hint={row.unavailableReason ?? undefined}\n disabled={!row.selectable}\n title={row.unavailableReason ?? undefined}\n active={row.id === props.model}\n testId={`model-picker-choice-${row.id}`}\n onClick={() => {\n if (row.selectable) {\n go({ level: \"thinking\", rail: row.billingClass, modelId: row.id }, 1);\n }\n }}\n />\n ))}\n </div>\n </div>\n );\n } else if (effectiveNav.level === \"thinking\" && focusModel) {\n const activeModel = focusModel.id === props.model;\n const supportsFast = runnableLatencyModesForModel(focusModel.catalog).includes(\"fast\");\n body = (\n <div data-testid=\"model-picker-reasoning\">\n <PickerBackHeader\n label={focusModel.label}\n icon={<BillingClassMark billingClass={focusModel.billingClass} />}\n onBack={() => go({ level: \"models\", rail: focusModel.billingClass, modelId: null }, -1)}\n trailing={\n supportsFast ? (\n <button\n type=\"button\"\n disabled={!focusModel.selectable}\n aria-pressed={activeModel && props.latencyMode === \"fast\"}\n aria-label={\n activeModel && props.latencyMode === \"fast\"\n ? `Disable ${messages.fast}`\n : `Enable ${messages.fast}`\n }\n title={`${messages.fast} · ${messages.fastRateHint}`}\n data-testid=\"model-picker-fast\"\n onClick={() => {\n if (!activeModel) {\n props.onModelChange(focusModel.id);\n props.onEffortChange(\n coerceReasoningEffortForModel(focusModel.catalog, props.effort),\n );\n }\n props.onLatencyModeChange(\n activeModel && props.latencyMode === \"fast\" ? \"standard\" : \"fast\",\n );\n }}\n className={cn(\n \"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50\",\n activeModel && props.latencyMode === \"fast\" && \"text-og-fg\",\n )}\n >\n <ZapIcon\n className={cn(\n \"size-3.5\",\n activeModel && props.latencyMode === \"fast\" && \"fill-current\",\n )}\n />\n </button>\n ) : null\n }\n />\n <p className=\"px-2.5 pt-1 pb-1 text-xs font-medium tracking-wide text-og-fg-subtle uppercase\">\n {messages.thinking}\n </p>\n {focusModel.catalog.capabilities?.inputModalities.includes(\"image\") === false ? (\n <p className=\"px-2.5 pb-1.5 text-xs leading-relaxed text-og-fg-subtle\">\n Unsupported attachments stay in the session but are hidden from this model.\n </p>\n ) : null}\n <div className=\"flex flex-col gap-0.5\">\n {effortOptionsForModel(focusModel.catalog).map((effort) => {\n const selected = activeModel && effort === props.effort;\n return (\n <button\n key={effort}\n type=\"button\"\n disabled={!focusModel.selectable}\n onClick={() => selectEffort(focusModel, effort)}\n className={cn(\n \"flex h-8 w-full cursor-pointer items-center rounded-og-sm px-2.5 text-left text-sm text-og-fg outline-none transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50\",\n selected && \"bg-og-surface-2\",\n )}\n >\n <span className=\"min-w-0 flex-1 truncate\">{labelReasoningEffort(effort)}</span>\n {selected ? (\n <CheckIcon\n className=\"ml-auto size-3.5 shrink-0\"\n data-testid=\"model-picker-effort-check\"\n />\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n );\n } else {\n body = (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"model-picker-providers\">\n {groups.map((group) => (\n <PickerNavRow\n key={group.billingClass}\n label={group.label}\n hint={messages.billingHints[group.billingClass]}\n icon={<BillingClassMark billingClass={group.billingClass} />}\n testId={`model-picker-rail-${group.billingClass}`}\n onClick={() => go({ level: \"models\", rail: group.billingClass, modelId: null }, 1)}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div data-testid=\"model-picker-menu\" className=\"relative overflow-hidden\">\n {props.error ? (\n <p className=\"px-2 py-1 text-xs text-og-status-failed\" role=\"alert\">\n {props.error}\n </p>\n ) : null}\n <PickerAnimatedPage pageKey={pageKey} direction={direction}>\n {body}\n </PickerAnimatedPage>\n </div>\n );\n}\n\nexport function ModelPolicyPicker(props: ModelPolicyPickerProps) {\n const [open, setOpen] = useState(false);\n const messages = useMemo(\n () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),\n [props.messages],\n );\n const rows = effectiveRows(props);\n const [nav, setNav] = usePickerNavState(props.sessionKey, rows, props.model);\n const selected = findPickerRow(rows, props.model);\n\n if (props.loading) {\n return (\n <span\n className={cn(\n \"inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2\",\n props.className,\n )}\n aria-label={messages.loading}\n data-testid=\"model-picker-loading\"\n />\n );\n }\n return (\n <DropdownMenu.Root open={open} onOpenChange={setOpen}>\n <DropdownMenu.Trigger asChild>\n <button\n type=\"button\"\n disabled={props.disabled}\n aria-label={messages.label}\n className={cn(\n \"inline-flex h-8 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-xs text-og-fg-muted outline-none transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-7 max-sm:max-w-[9rem] max-sm:px-2\",\n props.className,\n )}\n >\n <BillingClassMark\n billingClass={\n selected?.billingClass ??\n (props.model.startsWith(\"codex/\") ? \"codex_subscription\" : \"opengeni_credits\")\n }\n className=\"text-og-fg\"\n />\n <span className=\"truncate font-medium text-og-fg\">{selected?.label ?? props.model}</span>\n <span className=\"max-sm:hidden\">{labelReasoningEffort(props.effort)}</span>\n {props.latencyMode === \"fast\" ? (\n <ZapIcon\n className=\"size-3.5 shrink-0 fill-current stroke-current text-og-fg\"\n aria-label={messages.fast}\n data-testid=\"model-picker-fast-icon\"\n />\n ) : null}\n <ChevronDownIcon className=\"size-3 shrink-0\" />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content\n align=\"start\"\n side={props.menuSide ?? \"bottom\"}\n sideOffset={8}\n collisionPadding={12}\n onCloseAutoFocus={(event) => event.preventDefault()}\n className=\"z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-72 max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-1.5 text-og-fg shadow-og-lg\"\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n <ModelPolicyPickerMenu {...props} nav={nav} onNavChange={setNav} />\n </div>\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n"],"mappings":"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","names":[]}
package/dist/client.d.ts CHANGED
@@ -31,3 +31,5 @@ export type EmbeddedFileAttachmentClientLike = Pick<OpenGeniClient, "uploadFile"
31
31
  export type EmbeddedHumanInputSessionClientLike = EmbeddedSessionEventClientLike & Pick<OpenGeniClient, "listHumanInputRequests" | "submitHumanInputResponse">;
32
32
  /** Exact client surface required by MCP approval-policy hooks. */
33
33
  export type EmbeddedSessionMcpApprovalPolicyClientLike = EmbeddedSessionEventClientLike & Pick<OpenGeniClient, "updateSessionMcpApprovalPolicy">;
34
+ /** Exact client surface required by the public realtime React subpath. */
35
+ export type EmbeddedRealtimeSessionClientLike = Pick<OpenGeniClient, "getWorkspaceRealtimeModelCatalog" | "beginSessionRealtime" | "heartbeatSessionRealtime" | "negotiateCodexRealtimeWebrtc" | "negotiateGatewayRealtime" | "activateCodexRealtimeConnection" | "syncSessionRealtimeLedger" | "endSessionRealtime">;
@@ -23,8 +23,14 @@ export type ChatComposerProps = {
23
23
  autoFocus?: boolean | undefined;
24
24
  /** Replaces the default keyboard hint under the field. */
25
25
  hint?: string | undefined;
26
+ /** App controls ahead of attach (e.g. mobile “+” overflow). */
27
+ controlsLeading?: ReactNode | undefined;
26
28
  /** App controls in the footer row, replacing the hint. */
27
29
  controlsStart?: ReactNode | undefined;
30
+ /** App actions beside Pause/Send, ordered before the built-in actions. */
31
+ actionsStart?: ReactNode | undefined;
32
+ /** Extra classes for the built-in attach control (e.g. `max-sm:hidden`). */
33
+ attachButtonClassName?: string | undefined;
28
34
  /** Provider-neutral speech capability. Provider configuration stays in workspace settings. */
29
35
  transcription?: ComposerTranscriptionControlProps | undefined;
30
36
  /** Content rendered above the textarea, inside the field chrome. */
@@ -49,4 +55,4 @@ export type ChatComposerProps = {
49
55
  * the public controller and compound primitives exported by the composer
50
56
  * subpath, so custom and default layouts share one behavioral implementation.
51
57
  */
52
- export declare function ChatComposer({ composer, effectiveControl, queuedAheadCount, canControlWorkspace, controlLinks, placeholder, disabled, autoFocus, hint, controlsStart, transcription, header, onPaste, attachments, models, selectedModel, onSelectModel, className, commands, commandContext, onClearView, messages, }: ChatComposerProps): import("react/jsx-runtime").JSX.Element;
58
+ export declare function ChatComposer({ composer, effectiveControl, queuedAheadCount, canControlWorkspace, controlLinks, placeholder, disabled, autoFocus, hint, controlsLeading, controlsStart, actionsStart, attachButtonClassName, transcription, header, onPaste, attachments, models, selectedModel, onSelectModel, className, commands, commandContext, onClearView, messages, }: ChatComposerProps): import("react/jsx-runtime").JSX.Element;
@@ -15,11 +15,16 @@ export type HumanInputFormMessages = {
15
15
  deadlineLabel: string;
16
16
  formatDeadline: (value: string) => string;
17
17
  required: string;
18
- minLength: (count: number) => string;
19
- maxLength: (count: number) => string;
20
18
  otherRequired: string;
21
19
  minSelections: (count: number) => string;
22
20
  maxSelections: (count: number) => string;
21
+ optional: string;
22
+ /** Shown when the question list overflows the card and more content is below. */
23
+ moreBelow: string;
24
+ questionCount: (count: number) => string;
25
+ collapse: string;
26
+ expand: string;
27
+ selectionHint: (min: number | null | undefined, max: number | null | undefined) => string | null;
23
28
  };
24
29
  export declare const defaultHumanInputFormMessages: HumanInputFormMessages;
25
30
  export type HumanInputFormProps = {
@@ -29,18 +34,23 @@ export type HumanInputFormProps = {
29
34
  error?: string | null | undefined;
30
35
  title?: ReactNode;
31
36
  description?: ReactNode;
37
+ /** e.g. "1 of 2" when a host is stepping through parallel requests. */
38
+ progressLabel?: ReactNode;
32
39
  submitLabel?: string | undefined;
33
40
  skipLabel?: string | undefined;
34
41
  messages?: Partial<HumanInputFormMessages> | undefined;
35
42
  autoFocus?: boolean | undefined;
43
+ /** Start collapsed to a compact bar (drafts still retained). */
44
+ defaultCollapsed?: boolean | undefined;
36
45
  className?: string | undefined;
37
46
  };
38
47
  /**
39
- * Styled but host-neutral renderer for one structured request. Hosts can use
40
- * the headless `useHumanInputRequests` hook instead, or replace the title and
41
- * description while retaining accessible field semantics and validation.
48
+ * Styled but host-neutral renderer for one structured request. Matches the
49
+ * waiting-tone decision language of ApprovalSurface: question-first, compact
50
+ * options, sticky ask/submit chrome. Hosts can replace title/description or
51
+ * use `useHumanInputRequests` headlessly.
42
52
  */
43
- export declare function HumanInputForm({ request, onSubmit, submitting, error, title, description, submitLabel, skipLabel, messages: messageOverrides, autoFocus, className, }: HumanInputFormProps): import("react/jsx-runtime").JSX.Element;
53
+ export declare function HumanInputForm({ request, onSubmit, submitting, error, title, description, progressLabel, submitLabel, skipLabel, messages: messageOverrides, autoFocus, defaultCollapsed, className, }: HumanInputFormProps): import("react/jsx-runtime").JSX.Element;
44
54
  export declare function answersFromDrafts(questions: HumanInputQuestion[], drafts: Record<string, HumanInputAnswerDraft>, messageOverrides?: Partial<HumanInputFormMessages>): {
45
55
  answers: HumanInputAnswer[];
46
56
  errors: Record<string, string>;
@@ -0,0 +1,19 @@
1
+ import type { SessionHumanInputRequest, SubmitHumanInputResponseRequest } from "@opengeni/sdk";
2
+ import { type HumanInputFormMessages } from "./human-input-form";
3
+ export type HumanInputSurfaceProps = {
4
+ requests: SessionHumanInputRequest[];
5
+ onSubmit: (requestId: string, response: SubmitHumanInputResponseRequest) => void | Promise<void>;
6
+ respondingRequestId?: string | null | undefined;
7
+ error?: string | null | undefined;
8
+ messages?: Partial<HumanInputFormMessages> | undefined;
9
+ className?: string | undefined;
10
+ /** Forwarded to the active form. Defaults true. */
11
+ autoFocus?: boolean | undefined;
12
+ };
13
+ /**
14
+ * One decision shell for pending structured human-input requests. Parallel
15
+ * freezes are shown one at a time (oldest first) as “N of M”; Continue/Skip
16
+ * settles the active request and the next remaining set advances automatically
17
+ * when the authoritative pending list updates.
18
+ */
19
+ export declare function HumanInputSurface({ requests, onSubmit, respondingRequestId, error, messages, className, autoFocus, }: HumanInputSurfaceProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ import type { MachineInputMember } from "../timeline/types";
2
+ export declare const MACHINE_INPUT_META: Record<MachineInputMember["kind"], string>;
3
+ /**
4
+ * Collapsed landmark label for a coalesced machine-input batch.
5
+ * Same-kind batches get a natural plural; mixed kinds stay short.
6
+ */
7
+ export declare function machineInputBatchLabel(members: readonly MachineInputMember[]): string;
8
+ /** Strip protocol prefixes and worker/session UUIDs from display summaries. */
9
+ export declare function cleanMachineInputSummary(summary: string): string;
10
+ export declare function readableMachineInputSource(sourceId: string): string | null;
11
+ /** True when a cleaned single-member summary adds meaning beyond the pill label. */
12
+ export declare function machineInputSummaryIsUseful(kind: MachineInputMember["kind"], cleanedSummary: string): boolean;
@@ -43,6 +43,11 @@ export type MessageTimelineProps = {
43
43
  * `createDefaultToolRegistry({ entries })` to add custom tool renderers.
44
44
  */
45
45
  toolRegistry?: ToolRegistry | undefined;
46
+ /**
47
+ * Display name of the session's active compute target (Connected Machine or
48
+ * cloud sandbox). When set, exec_command collapsed previews prefix `on {label}`.
49
+ */
50
+ computeLabel?: string | null | undefined;
46
51
  /** Customize collapsed turn facets for this timeline instance. */
47
52
  turnSummary?: TurnSummaryOptions | undefined;
48
53
  /** Follow new events when pinned to the bottom. Defaults to true. */
@@ -77,7 +82,7 @@ export type MessageTimelineProps = {
77
82
  * cards, goal markers, and status transitions. Owns stick-to-bottom scrolling
78
83
  * with a "jump to latest" affordance when the reader scrolls back.
79
84
  */
80
- export declare function MessageTimeline({ events, items, status: _status, renderMessageText, onOpenSession, onMemoryClick, onReconnect, resolveProviderLogo, toolRegistry, turnSummary, autoFollow, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, emptyState, className, }: MessageTimelineProps): import("react/jsx-runtime").JSX.Element;
85
+ export declare function MessageTimeline({ events, items, status: _status, renderMessageText, onOpenSession, onMemoryClick, onReconnect, resolveProviderLogo, toolRegistry, computeLabel, turnSummary, autoFollow, hasOlder, loadingOlder, onLoadOlder, onJumpToStart, loadingOldest, hasNewer, loadingNewer, onLoadNewer, onJumpToLatest, emptyState, className, }: MessageTimelineProps): import("react/jsx-runtime").JSX.Element;
81
86
  /**
82
87
  * Settled turns lift the final agent answer out as a sibling group. Include it
83
88
  * in "Copy turn" when present so the chip copies the full assistant reply.