@opengeni/react 7.0.0 → 7.1.0-canary.35855175602001
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.
- package/README.md +34 -0
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts.js +17 -17
- package/dist/{browser-viewer-LGH4IP47.js → browser-viewer-MN7MOYQT.js} +5 -5
- package/dist/{chunk-YS24CFDI.js → chunk-2O2HXRGP.js} +7 -7
- package/dist/{chunk-IP4BUBKH.js → chunk-3LL47F4U.js} +28 -17
- package/dist/chunk-3LL47F4U.js.map +1 -0
- package/dist/{chunk-UXE5SSNN.js → chunk-4ACTWOOW.js} +8 -8
- package/dist/{chunk-IYUPN32E.js → chunk-DBPVUGQT.js} +6 -1
- package/dist/{chunk-IYUPN32E.js.map → chunk-DBPVUGQT.js.map} +1 -1
- package/dist/{chunk-5GAL23FF.js → chunk-IOLPHOF4.js} +2 -5
- package/dist/chunk-IOLPHOF4.js.map +1 -0
- package/dist/{chunk-JPHDWKZE.js → chunk-PFDNB3U7.js} +15 -10
- package/dist/chunk-PFDNB3U7.js.map +1 -0
- package/dist/{chunk-D7GAPJ3V.js → chunk-SHRCATV6.js} +67 -34
- package/dist/chunk-SHRCATV6.js.map +1 -0
- package/dist/{chunk-H5YGYNXS.js → chunk-TV5PP2PE.js} +11 -11
- package/dist/chunk-TV5PP2PE.js.map +1 -0
- package/dist/{chunk-PNAVEK2U.js → chunk-UQS7BGNQ.js} +12 -12
- package/dist/{chunk-JKLGJQO5.js → chunk-VI7HELBI.js} +40 -20
- package/dist/chunk-VI7HELBI.js.map +1 -0
- package/dist/{chunk-HJ5VJ4JB.js → chunk-WKFFRFJF.js} +73 -15
- package/dist/chunk-WKFFRFJF.js.map +1 -0
- package/dist/{chunk-D5NOQPLR.js → chunk-ZC2DK5SC.js} +75 -65
- package/dist/chunk-ZC2DK5SC.js.map +1 -0
- package/dist/components/markdown.d.ts +2 -0
- package/dist/components/model-policy-picker.d.ts +17 -1
- package/dist/components/session-conversation.d.ts +3 -0
- package/dist/composer.d.ts +1 -1
- package/dist/composer.js +7 -7
- package/dist/{computer-viewer-CTFCJVS7.js → computer-viewer-FX4WOZMX.js} +4 -4
- package/dist/connect.js +5 -5
- package/dist/hooks/use-session-events.d.ts +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +112 -115
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +5 -5
- package/dist/machines.js +2 -2
- package/dist/{model-policy-picker-menu-RVOQ4ZZG.js → model-policy-picker-menu-AGXXJZ2B.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/realtime.js +5 -5
- package/dist/session-ui.js +12 -12
- package/dist/session.js +3 -3
- package/dist/timeline/tool-display-name.d.ts +2 -1
- package/dist/timeline/types.d.ts +2 -1
- package/package.json +3 -3
- package/src/approvals.ts +8 -1
- package/src/components/approval-surface.tsx +12 -2
- package/src/components/machine-card.tsx +5 -5
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machines/machine-detail.tsx +5 -2
- package/src/components/markdown.tsx +101 -55
- package/src/components/model-policy-picker-menu.tsx +131 -33
- package/src/components/model-policy-picker.tsx +61 -12
- package/src/components/session-commands-panel.tsx +8 -3
- package/src/components/session-conversation.tsx +5 -1
- package/src/composer.ts +1 -0
- package/src/hooks/use-last-started-turn-policy.ts +3 -7
- package/src/hooks/use-session-events.ts +32 -7
- package/src/index.ts +1 -0
- package/src/model-policy.ts +1 -4
- package/src/timeline/projection.ts +4 -0
- package/src/timeline/rolling-activity.tsx +4 -2
- package/src/timeline/tool-display-name.ts +8 -6
- package/src/timeline/tool-renderers.tsx +5 -3
- package/src/timeline/types.ts +2 -0
- package/styles/compiled.css +15 -0
- package/dist/chunk-5GAL23FF.js.map +0 -1
- package/dist/chunk-D5NOQPLR.js.map +0 -1
- package/dist/chunk-D7GAPJ3V.js.map +0 -1
- package/dist/chunk-H5YGYNXS.js.map +0 -1
- package/dist/chunk-HJ5VJ4JB.js.map +0 -1
- package/dist/chunk-IP4BUBKH.js.map +0 -1
- package/dist/chunk-JKLGJQO5.js.map +0 -1
- package/dist/chunk-JPHDWKZE.js.map +0 -1
- /package/dist/{browser-viewer-LGH4IP47.js.map → browser-viewer-MN7MOYQT.js.map} +0 -0
- /package/dist/{chunk-YS24CFDI.js.map → chunk-2O2HXRGP.js.map} +0 -0
- /package/dist/{chunk-UXE5SSNN.js.map → chunk-4ACTWOOW.js.map} +0 -0
- /package/dist/{chunk-PNAVEK2U.js.map → chunk-UQS7BGNQ.js.map} +0 -0
- /package/dist/{computer-viewer-CTFCJVS7.js.map → computer-viewer-FX4WOZMX.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-RVOQ4ZZG.js.map → model-policy-picker-menu-AGXXJZ2B.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ClientModel, ReasoningEffort } from "@opengeni/sdk";
|
|
2
|
-
import { CheckIcon, SearchIcon, ZapIcon } from "lucide-react";
|
|
2
|
+
import { ArrowRightIcon, CheckIcon, SearchIcon, ZapIcon } from "lucide-react";
|
|
3
3
|
import { useRef, useState, type RefObject, type CSSProperties } from "react";
|
|
4
4
|
import { Popover, RadioGroup } from "radix-ui";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
@@ -30,13 +30,26 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
30
30
|
const selected = findPickerRow(rows, props.model);
|
|
31
31
|
const words = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
32
32
|
const filtered = rows.filter((row) => {
|
|
33
|
+
const description = props.groupPresentation?.[row.billingClass]?.description;
|
|
33
34
|
const text =
|
|
34
|
-
`${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${payerSummaryForModel(row.catalog)}`.toLowerCase();
|
|
35
|
+
`${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${description === undefined ? payerSummaryForModel(row.catalog) : (description ?? "")}`.toLowerCase();
|
|
35
36
|
return words.every((word) => text.includes(word));
|
|
36
37
|
});
|
|
37
38
|
const matchingIds = new Set(filtered.map((row) => row.id));
|
|
38
39
|
const groups = groupPickerRowsByBillingClass(rows, { codexOnly: props.codexOnly === true })
|
|
39
|
-
.map((group) =>
|
|
40
|
+
.map((group) => {
|
|
41
|
+
const override = props.groupPresentation?.[group.billingClass]?.description;
|
|
42
|
+
return {
|
|
43
|
+
...group,
|
|
44
|
+
rows: group.rows.filter((row) => matchingIds.has(row.id)),
|
|
45
|
+
description:
|
|
46
|
+
override === undefined
|
|
47
|
+
? group.billingClass === "opengeni_credits"
|
|
48
|
+
? null
|
|
49
|
+
: messages.billingHints[group.billingClass]
|
|
50
|
+
: override,
|
|
51
|
+
};
|
|
52
|
+
})
|
|
40
53
|
.filter((group) => group.rows.length > 0);
|
|
41
54
|
const choose = (row: ClientPickerModelRow) => {
|
|
42
55
|
if (!row.selectable || props.disabled) return;
|
|
@@ -59,7 +72,7 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
59
72
|
hint={row.unavailableReason ?? undefined}
|
|
60
73
|
disabled={props.disabled || !row.selectable}
|
|
61
74
|
title={[row.label, row.unavailableReason].filter(Boolean).join(" · ")}
|
|
62
|
-
active={row.id === props.model}
|
|
75
|
+
active={row.selectable && row.id === props.model}
|
|
63
76
|
showChevron={false}
|
|
64
77
|
trailing={
|
|
65
78
|
row.catalog.cost === "free" || row.id === props.model ? (
|
|
@@ -106,28 +119,30 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
106
119
|
buttons[next]?.focus();
|
|
107
120
|
}}
|
|
108
121
|
>
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
event.
|
|
121
|
-
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
{rows.some((row) => row.selectable) ? (
|
|
123
|
+
<div className="border-b border-og-border py-1">
|
|
124
|
+
<label className="og-model-policy-search flex items-center gap-2 rounded-og-sm px-2 py-1 focus-within:outline-2 focus-within:-outline-offset-2 focus-within:outline-og-accent/40">
|
|
125
|
+
<SearchIcon className="size-4 shrink-0 text-og-fg-subtle" aria-hidden />
|
|
126
|
+
<input
|
|
127
|
+
aria-label={messages.searchLabel}
|
|
128
|
+
placeholder={messages.searchPlaceholder}
|
|
129
|
+
value={query}
|
|
130
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
131
|
+
onKeyDown={(event) => {
|
|
132
|
+
if (event.key !== "Escape" && event.key !== "Tab") event.stopPropagation();
|
|
133
|
+
if (event.key === "ArrowDown") {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
event.currentTarget
|
|
136
|
+
.closest('[data-testid="model-picker-menu"]')
|
|
137
|
+
?.querySelector<HTMLButtonElement>("button:not(:disabled)")
|
|
138
|
+
?.focus();
|
|
139
|
+
}
|
|
140
|
+
}}
|
|
141
|
+
className="og-model-policy-search-input h-8 min-w-0 flex-1 bg-transparent text-og-menu text-og-fg outline-hidden placeholder:text-og-fg-subtle"
|
|
142
|
+
/>
|
|
143
|
+
</label>
|
|
144
|
+
</div>
|
|
145
|
+
) : null}
|
|
131
146
|
{props.error ? (
|
|
132
147
|
<p className="px-2 py-2 text-og-control text-og-status-failed" role="alert">
|
|
133
148
|
{props.error}
|
|
@@ -139,7 +154,7 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
139
154
|
>
|
|
140
155
|
{props.loading ? (
|
|
141
156
|
<p className="px-2 py-3 text-og-control text-og-fg-subtle">{messages.loading}</p>
|
|
142
|
-
) : (
|
|
157
|
+
) : rows.some((row) => row.selectable) ? (
|
|
143
158
|
<>
|
|
144
159
|
{groups.map((group) => (
|
|
145
160
|
<section
|
|
@@ -148,12 +163,16 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
148
163
|
className="py-2.5 first:pt-1 [&+section]:border-t [&+section]:border-og-border"
|
|
149
164
|
>
|
|
150
165
|
<div className="flex items-center gap-2 px-2.5 py-1.5 text-og-control font-semibold text-og-fg">
|
|
151
|
-
<BillingClassMark
|
|
152
|
-
|
|
166
|
+
<BillingClassMark
|
|
167
|
+
billingClass={group.billingClass}
|
|
168
|
+
presentation={props.groupPresentation?.[group.billingClass]}
|
|
169
|
+
aria-label=""
|
|
170
|
+
/>
|
|
171
|
+
<span className="min-w-0 break-words">{group.label}</span>
|
|
153
172
|
</div>
|
|
154
|
-
{group.
|
|
155
|
-
<p className="px-2.5 pb-2 text-og-control text-og-fg-subtle">
|
|
156
|
-
{
|
|
173
|
+
{group.description ? (
|
|
174
|
+
<p className="break-words px-2.5 pb-2 text-og-control text-og-fg-subtle">
|
|
175
|
+
{group.description}
|
|
157
176
|
</p>
|
|
158
177
|
) : null}
|
|
159
178
|
{group.rows.map(modelRow)}
|
|
@@ -165,9 +184,14 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
165
184
|
</p>
|
|
166
185
|
) : null}
|
|
167
186
|
</>
|
|
187
|
+
) : props.connectModelsHref || rows.length > 0 ? (
|
|
188
|
+
<ConnectModelsPanel href={props.connectModelsHref} messages={messages} />
|
|
189
|
+
) : (
|
|
190
|
+
<p className="px-2 py-4 text-og-control text-og-fg-subtle">{messages.noModels}</p>
|
|
168
191
|
)}
|
|
169
192
|
</div>
|
|
170
|
-
{
|
|
193
|
+
{rows.some((row) => row.selectable) &&
|
|
194
|
+
selected &&
|
|
171
195
|
((props.hasImageAttachments &&
|
|
172
196
|
selected.catalog.capabilities?.inputModalities.includes("image") === false) ||
|
|
173
197
|
(effortOptionsForModel(selected.catalog).length > 1 &&
|
|
@@ -188,6 +212,80 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
188
212
|
);
|
|
189
213
|
}
|
|
190
214
|
|
|
215
|
+
const CONNECT_MODEL_OPTIONS: Array<{
|
|
216
|
+
billingClass: "codex_subscription" | "supergrok_subscription" | "byok" | "external";
|
|
217
|
+
title: string;
|
|
218
|
+
detail: string;
|
|
219
|
+
}> = [
|
|
220
|
+
{ billingClass: "codex_subscription", title: "Codex", detail: "ChatGPT" },
|
|
221
|
+
{ billingClass: "supergrok_subscription", title: "SuperGrok", detail: "xAI" },
|
|
222
|
+
{ billingClass: "byok", title: "AI Gateway", detail: "Vercel" },
|
|
223
|
+
{ billingClass: "external", title: "OpenRouter", detail: "API key" },
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
function ConnectModelsPanel(props: {
|
|
227
|
+
href: string | undefined;
|
|
228
|
+
messages: typeof defaultModelPolicyPickerMessages;
|
|
229
|
+
}) {
|
|
230
|
+
return (
|
|
231
|
+
<div className="px-1.5 pb-1.5 pt-2" data-testid="model-picker-connect">
|
|
232
|
+
<div className="px-1.5 pb-3">
|
|
233
|
+
<p className="text-og-menu font-medium tracking-tight text-og-fg">
|
|
234
|
+
{props.messages.connectTitle}
|
|
235
|
+
</p>
|
|
236
|
+
<p className="mt-1 text-og-control leading-snug text-og-fg-subtle">
|
|
237
|
+
{props.messages.connectBody}
|
|
238
|
+
</p>
|
|
239
|
+
</div>
|
|
240
|
+
<div className="grid grid-cols-2 gap-1">
|
|
241
|
+
{CONNECT_MODEL_OPTIONS.map((option) => {
|
|
242
|
+
const content = (
|
|
243
|
+
<>
|
|
244
|
+
<span className="flex size-8 shrink-0 items-center justify-center rounded-og-md bg-og-surface-2 text-og-fg">
|
|
245
|
+
<BillingClassMark
|
|
246
|
+
billingClass={option.billingClass}
|
|
247
|
+
aria-label=""
|
|
248
|
+
className="size-4 text-og-fg"
|
|
249
|
+
/>
|
|
250
|
+
</span>
|
|
251
|
+
<span className="min-w-0">
|
|
252
|
+
<span className="block truncate text-og-control font-medium text-og-fg">
|
|
253
|
+
{option.title}
|
|
254
|
+
</span>
|
|
255
|
+
<span className="block truncate text-og-control text-og-fg-subtle">
|
|
256
|
+
{option.detail}
|
|
257
|
+
</span>
|
|
258
|
+
</span>
|
|
259
|
+
</>
|
|
260
|
+
);
|
|
261
|
+
const className =
|
|
262
|
+
"flex min-w-0 items-center gap-2 rounded-og-md px-1.5 py-1.5 outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40";
|
|
263
|
+
return props.href ? (
|
|
264
|
+
<a key={option.title} href={props.href} className={className}>
|
|
265
|
+
{content}
|
|
266
|
+
</a>
|
|
267
|
+
) : (
|
|
268
|
+
<div key={option.title} className={className}>
|
|
269
|
+
{content}
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
})}
|
|
273
|
+
</div>
|
|
274
|
+
{props.href ? (
|
|
275
|
+
<a
|
|
276
|
+
href={props.href}
|
|
277
|
+
className="mt-2 flex h-9 w-full items-center justify-center gap-1.5 rounded-og-md bg-og-fg text-og-control font-medium text-og-surface-1 outline-hidden transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-og-accent/40"
|
|
278
|
+
>
|
|
279
|
+
{props.messages.connectAction}
|
|
280
|
+
<ArrowRightIcon className="size-3.5" aria-hidden />
|
|
281
|
+
</a>
|
|
282
|
+
) : (
|
|
283
|
+
<p className="px-1.5 pt-3 text-og-control text-og-fg-subtle">{props.messages.noModels}</p>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
191
289
|
function ModelThinkingControls(props: ModelPolicyPickerProps) {
|
|
192
290
|
const selected = findPickerRow(effectiveRows(props), props.model);
|
|
193
291
|
if (!selected) return null;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ChevronRightIcon,
|
|
6
6
|
Globe2Icon,
|
|
7
7
|
KeyRoundIcon,
|
|
8
|
+
SparklesIcon,
|
|
8
9
|
ZapIcon,
|
|
9
10
|
} from "lucide-react";
|
|
10
11
|
import { motion, useReducedMotion } from "motion/react";
|
|
@@ -37,10 +38,27 @@ const LazyModelPolicyPickerMenu = lazy(() =>
|
|
|
37
38
|
|
|
38
39
|
type ClientPickerModelRow = PickerModelRow<ClientModel>;
|
|
39
40
|
|
|
41
|
+
/** Presentation only; never changes model identity, billing or availability. */
|
|
42
|
+
export type ModelPolicyPickerGroupPresentation = Partial<
|
|
43
|
+
Record<
|
|
44
|
+
PickerBillingClass,
|
|
45
|
+
{
|
|
46
|
+
label?: string | undefined;
|
|
47
|
+
/** Omit to preserve the default; null hides the supporting text. */
|
|
48
|
+
description?: string | null | undefined;
|
|
49
|
+
/** Decorative, non-interactive content. Omit for the default; null hides it. */
|
|
50
|
+
icon?: ReactNode | undefined;
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
>;
|
|
54
|
+
|
|
40
55
|
export type ModelPolicyPickerMessages = {
|
|
41
56
|
label: string;
|
|
42
57
|
loading: string;
|
|
43
58
|
noModels: string;
|
|
59
|
+
connectTitle: string;
|
|
60
|
+
connectBody: string;
|
|
61
|
+
connectAction: string;
|
|
44
62
|
thinking: string;
|
|
45
63
|
fast: string;
|
|
46
64
|
fastRateHint: string;
|
|
@@ -61,6 +79,9 @@ export const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {
|
|
|
61
79
|
label: "Model and effort",
|
|
62
80
|
loading: "Loading model catalog…",
|
|
63
81
|
noModels: "No models available.",
|
|
82
|
+
connectTitle: "Connect a model",
|
|
83
|
+
connectBody: "Use a subscription or a provider key you already have.",
|
|
84
|
+
connectAction: "Open Models",
|
|
64
85
|
thinking: "Thinking",
|
|
65
86
|
fast: "Fast",
|
|
66
87
|
fastRateHint: "2× rate",
|
|
@@ -85,6 +106,8 @@ export const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {
|
|
|
85
106
|
};
|
|
86
107
|
|
|
87
108
|
export type ModelPolicyPickerProps = {
|
|
109
|
+
/** Host branding for payment-source groups, shared by menu and trigger. */
|
|
110
|
+
groupPresentation?: ModelPolicyPickerGroupPresentation | undefined;
|
|
88
111
|
/** Lightweight deployment models. Catalog rows take precedence when supplied. */
|
|
89
112
|
models?: ClientModel[] | undefined;
|
|
90
113
|
/** Warn only when the draft actually contains images this model cannot view. */
|
|
@@ -110,6 +133,8 @@ export type ModelPolicyPickerProps = {
|
|
|
110
133
|
menuSide?: "top" | "bottom" | undefined;
|
|
111
134
|
/** Hide latency controls on surfaces whose saved policy does not include latency. */
|
|
112
135
|
allowLatencyMode?: boolean | undefined;
|
|
136
|
+
/** Settings → Models. Shown when the catalog has no model that can run. */
|
|
137
|
+
connectModelsHref?: string | undefined;
|
|
113
138
|
/** Classes for the portalled menu surface. Prefer --og-* tokens for theming. */
|
|
114
139
|
contentClassName?: string | undefined;
|
|
115
140
|
/** Inline styles for the portalled menu, applied after inherited --og-* tokens. */
|
|
@@ -157,6 +182,7 @@ function XaiMark(props: SVGProps<SVGSVGElement>) {
|
|
|
157
182
|
|
|
158
183
|
export function BillingClassMark(props: {
|
|
159
184
|
billingClass: PickerBillingClass;
|
|
185
|
+
presentation?: ModelPolicyPickerGroupPresentation[PickerBillingClass] | undefined;
|
|
160
186
|
className?: string | undefined;
|
|
161
187
|
"aria-label"?: string | undefined;
|
|
162
188
|
}) {
|
|
@@ -168,13 +194,14 @@ export function BillingClassMark(props: {
|
|
|
168
194
|
byok: "Workspace provider account",
|
|
169
195
|
organization_byok: "Organization provider account",
|
|
170
196
|
};
|
|
171
|
-
|
|
197
|
+
if (props.presentation?.icon === null) return null;
|
|
198
|
+
const label = props["aria-label"] ?? props.presentation?.label ?? labels[props.billingClass];
|
|
172
199
|
const accessibility =
|
|
173
200
|
label.length === 0
|
|
174
201
|
? { "aria-hidden": true as const }
|
|
175
202
|
: { role: "img" as const, "aria-label": label };
|
|
176
203
|
const shell = cn(
|
|
177
|
-
"inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle",
|
|
204
|
+
"inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle [&>svg]:size-full [&>img]:size-full",
|
|
178
205
|
props.className,
|
|
179
206
|
);
|
|
180
207
|
const mark = "size-3.5";
|
|
@@ -184,7 +211,9 @@ export function BillingClassMark(props: {
|
|
|
184
211
|
data-testid={`billing-class-icon-${props.billingClass}`}
|
|
185
212
|
{...accessibility}
|
|
186
213
|
>
|
|
187
|
-
{props.
|
|
214
|
+
{props.presentation?.icon !== undefined ? (
|
|
215
|
+
props.presentation.icon
|
|
216
|
+
) : props.billingClass === "opengeni_credits" ? (
|
|
188
217
|
<OpenGeniMark className={mark} />
|
|
189
218
|
) : props.billingClass === "external" ? (
|
|
190
219
|
<Globe2Icon className={mark} aria-hidden />
|
|
@@ -235,7 +264,10 @@ function applyCodexOnly(
|
|
|
235
264
|
export function effectiveRows(props: ModelPolicyPickerProps): ClientPickerModelRow[] {
|
|
236
265
|
const rows = props.rows !== undefined ? props.rows : projectClientModelRows(props.models ?? []);
|
|
237
266
|
const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
|
|
238
|
-
return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly)
|
|
267
|
+
return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly).map((row) => {
|
|
268
|
+
const label = props.groupPresentation?.[row.billingClass]?.label;
|
|
269
|
+
return label === undefined ? row : { ...row, billingClassLabel: label };
|
|
270
|
+
});
|
|
239
271
|
}
|
|
240
272
|
|
|
241
273
|
export function PickerNavRow(props: {
|
|
@@ -346,6 +378,8 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
|
|
|
346
378
|
);
|
|
347
379
|
const { open, setOpen, rows } = useModelPolicyPickerState(props);
|
|
348
380
|
const selected = findPickerRow(rows, props.model);
|
|
381
|
+
const needsModel =
|
|
382
|
+
!rows.some((row) => row.selectable) && (rows.length > 0 || Boolean(props.connectModelsHref));
|
|
349
383
|
|
|
350
384
|
if (props.loading) {
|
|
351
385
|
return (
|
|
@@ -372,21 +406,36 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
|
|
|
372
406
|
disabled={props.disabled}
|
|
373
407
|
aria-label={messages.label}
|
|
374
408
|
className={cn(
|
|
375
|
-
"og-root og-model-policy-trigger inline-flex h-[var(--og-model-picker-trigger-height)] min-w-0 max-w-64 items-center gap-1 rounded-full border
|
|
409
|
+
"og-root og-model-policy-trigger inline-flex h-[var(--og-model-picker-trigger-height)] min-w-0 max-w-64 items-center gap-1 rounded-full border px-2.5 text-og-control outline-hidden transition-colors focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-11 max-sm:max-w-[7.5rem] max-sm:px-2",
|
|
410
|
+
needsModel
|
|
411
|
+
? "border-og-border bg-og-surface-2 text-og-fg hover:bg-og-surface-3"
|
|
412
|
+
: "border-transparent text-og-fg-muted hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg",
|
|
376
413
|
props.className,
|
|
377
414
|
)}
|
|
378
415
|
>
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
416
|
+
{needsModel ? (
|
|
417
|
+
<SparklesIcon className="size-3.5 shrink-0" aria-hidden />
|
|
418
|
+
) : (
|
|
419
|
+
<BillingClassMark
|
|
420
|
+
billingClass={selected?.billingClass ?? billingClassForMissingSelection(props.model)}
|
|
421
|
+
presentation={
|
|
422
|
+
props.groupPresentation?.[
|
|
423
|
+
selected?.billingClass ?? billingClassForMissingSelection(props.model)
|
|
424
|
+
]
|
|
425
|
+
}
|
|
426
|
+
className="text-og-fg"
|
|
427
|
+
/>
|
|
428
|
+
)}
|
|
383
429
|
<span className="og-model-policy-label-full min-w-0 truncate font-medium text-og-fg max-sm:hidden @max-[20rem]/model-controls:hidden">
|
|
384
|
-
{selected?.label ?? props.model}
|
|
430
|
+
{needsModel ? messages.connectTitle : (selected?.label ?? props.model)}
|
|
385
431
|
</span>
|
|
386
432
|
<span className="og-model-policy-label-short min-w-0 truncate font-medium text-og-fg sm:hidden @max-[20rem]/model-controls:block">
|
|
387
|
-
{
|
|
433
|
+
{needsModel
|
|
434
|
+
? messages.connectTitle
|
|
435
|
+
: (selected?.shortLabel ?? selected?.label ?? props.model)}
|
|
388
436
|
</span>
|
|
389
437
|
{selected &&
|
|
438
|
+
!needsModel &&
|
|
390
439
|
effortOptionsForModel(selected.catalog).length > 1 &&
|
|
391
440
|
selected.catalog.capabilities?.reasoning.runnable !== false ? (
|
|
392
441
|
<span
|
|
@@ -396,7 +445,7 @@ export function ModelPolicyPicker(props: ModelPolicyPickerProps) {
|
|
|
396
445
|
{labelReasoningEffort(props.effort)}
|
|
397
446
|
</span>
|
|
398
447
|
) : null}
|
|
399
|
-
{props.latencyMode === "fast" ? (
|
|
448
|
+
{props.latencyMode === "fast" && !needsModel ? (
|
|
400
449
|
<ZapIcon
|
|
401
450
|
className="size-3.5 shrink-0 fill-current stroke-current text-og-fg"
|
|
402
451
|
aria-label={messages.fast}
|
|
@@ -62,13 +62,18 @@ export function SessionCommandsPanel({
|
|
|
62
62
|
title="Expand command"
|
|
63
63
|
>
|
|
64
64
|
<ChevronRightIcon className="size-3 shrink-0 text-og-fg-subtle transition-transform group-open/command:rotate-90" />
|
|
65
|
-
<span
|
|
66
|
-
|
|
65
|
+
<span
|
|
66
|
+
className="min-w-0 truncate"
|
|
67
|
+
title={
|
|
68
|
+
command.commandText ?? (command.commandPreview || "Background command")
|
|
69
|
+
}
|
|
70
|
+
>
|
|
71
|
+
{command.commandText ?? (command.commandPreview || "Background command")}
|
|
67
72
|
</span>
|
|
68
73
|
</summary>
|
|
69
74
|
<div className="mt-2 space-y-1">
|
|
70
75
|
<p className="whitespace-pre-wrap break-all font-mono text-og-fg-muted">
|
|
71
|
-
{command.commandPreview || "Background command"}
|
|
76
|
+
{command.commandText ?? (command.commandPreview || "Background command")}
|
|
72
77
|
</p>
|
|
73
78
|
<time
|
|
74
79
|
className="block text-og-fg-subtle"
|
|
@@ -10,13 +10,15 @@ import { useHumanInputRequests } from "../hooks/use-human-input";
|
|
|
10
10
|
import { ChatComposer, type ChatComposerProps } from "./chat-composer";
|
|
11
11
|
import { SessionChrome } from "./session-chrome";
|
|
12
12
|
import { HumanInputSurface, type HumanInputSurfaceProps } from "./human-input-surface";
|
|
13
|
-
import { MessageTimeline } from "./message-timeline";
|
|
13
|
+
import { MessageTimeline, type MessageTimelineProps } from "./message-timeline";
|
|
14
14
|
import type { UserMessageDisclosureLabels } from "./user-message-body";
|
|
15
15
|
import { conversationTimeline } from "../conversation-timeline";
|
|
16
16
|
import { cn } from "../lib/cn";
|
|
17
17
|
|
|
18
18
|
export type SessionConversationProps = ClientOverride & {
|
|
19
19
|
sessionId: string;
|
|
20
|
+
/** Host-owned artifact links, previews and other message presentation. */
|
|
21
|
+
renderMessageText?: MessageTimelineProps["renderMessageText"];
|
|
20
22
|
/** Localized actions for already-sent user-message disclosure. */
|
|
21
23
|
userMessageDisclosureLabels?: UserMessageDisclosureLabels | undefined;
|
|
22
24
|
loadSkillReview?: HumanInputSurfaceProps["loadSkillReview"];
|
|
@@ -35,6 +37,7 @@ export function SessionConversation(props: SessionConversationProps) {
|
|
|
35
37
|
|
|
36
38
|
function Conversation({
|
|
37
39
|
sessionId,
|
|
40
|
+
renderMessageText,
|
|
38
41
|
userMessageDisclosureLabels,
|
|
39
42
|
loadSkillReview,
|
|
40
43
|
client,
|
|
@@ -75,6 +78,7 @@ function Conversation({
|
|
|
75
78
|
>
|
|
76
79
|
{error && <p role="alert">{error.message}</p>}
|
|
77
80
|
<MessageTimeline
|
|
81
|
+
renderMessageText={renderMessageText}
|
|
78
82
|
userMessageDisclosureLabels={userMessageDisclosureLabels}
|
|
79
83
|
className="min-h-0 flex-1"
|
|
80
84
|
items={conversationTimeline(feed.timeline, queue, composer)}
|
package/src/composer.ts
CHANGED
|
@@ -47,8 +47,7 @@ export function useLastStartedTurnPolicy(
|
|
|
47
47
|
sessionId: string | null | undefined,
|
|
48
48
|
options: UseLastStartedTurnPolicyOptions = {},
|
|
49
49
|
): UseLastStartedTurnPolicyResult {
|
|
50
|
-
const { client, workspaceId,
|
|
51
|
-
useOpenGeni(options);
|
|
50
|
+
const { client, workspaceId, registerSessionReconciler } = useOpenGeni(options);
|
|
52
51
|
const enabled = (options.enabled ?? true) && Boolean(sessionId);
|
|
53
52
|
|
|
54
53
|
const load = useCallback(
|
|
@@ -68,10 +67,6 @@ export function useLastStartedTurnPolicy(
|
|
|
68
67
|
enabled,
|
|
69
68
|
});
|
|
70
69
|
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (enabled && workspaceControlEvent) void refresh();
|
|
73
|
-
}, [enabled, refresh, workspaceControlEvent]);
|
|
74
|
-
|
|
75
70
|
useEffect(() => {
|
|
76
71
|
if (!sessionId || !enabled) return;
|
|
77
72
|
return registerSessionReconciler(sessionId, "last-started-turn-policy", refresh);
|
|
@@ -79,7 +74,8 @@ export function useLastStartedTurnPolicy(
|
|
|
79
74
|
|
|
80
75
|
// Shared-feed only: the session header lives outside the route event log and
|
|
81
76
|
// must not open a second SSE. Without `events`, rely on the initial fetch +
|
|
82
|
-
// optional poll /
|
|
77
|
+
// optional poll / reconciler. Pause/resume controls do not change the last
|
|
78
|
+
// admitted turn; its own turn.started event supplies that invalidation.
|
|
83
79
|
useSessionEventTrigger(
|
|
84
80
|
client,
|
|
85
81
|
workspaceId,
|
|
@@ -105,8 +105,8 @@ const FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
|
|
|
105
105
|
const FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
|
|
106
106
|
const EMPTY_EVENTS: SessionEvent[] = [];
|
|
107
107
|
const encoder = new TextEncoder();
|
|
108
|
-
export const SESSION_EVENT_BROWSER_MAX_BYTES =
|
|
109
|
-
export const SESSION_EVENT_BROWSER_MAX_COUNT =
|
|
108
|
+
export const SESSION_EVENT_BROWSER_MAX_BYTES = 160 * 1024 * 1024;
|
|
109
|
+
export const SESSION_EVENT_BROWSER_MAX_COUNT = 200_000;
|
|
110
110
|
export const SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
|
|
111
111
|
export const SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
|
|
112
112
|
|
|
@@ -325,7 +325,7 @@ export function useSessionEvents(
|
|
|
325
325
|
setSessionStatusProjection(status);
|
|
326
326
|
}
|
|
327
327
|
const current = eventWindowRef.current;
|
|
328
|
-
const next =
|
|
328
|
+
const next = appendBrowserSessionEventWindow(current, batch);
|
|
329
329
|
const retained = {
|
|
330
330
|
...next,
|
|
331
331
|
truncated: current.truncated || next.truncated,
|
|
@@ -366,7 +366,7 @@ export function useSessionEvents(
|
|
|
366
366
|
const current = eventWindowRef.current;
|
|
367
367
|
assertAppendOrder(current.events, plan.events);
|
|
368
368
|
const status = observeSessionStatus(plan.events, sessionStatusRef);
|
|
369
|
-
const next =
|
|
369
|
+
const next = appendBrowserSessionEventWindow(current, plan.events);
|
|
370
370
|
const retained = {
|
|
371
371
|
...next,
|
|
372
372
|
truncated: current.truncated || next.truncated,
|
|
@@ -764,9 +764,7 @@ export function useSessionEvents(
|
|
|
764
764
|
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
765
765
|
const previousOldest = current.events[0]?.sequence ?? null;
|
|
766
766
|
// Moving forward through history: keep the newest suffix of the merge.
|
|
767
|
-
const next =
|
|
768
|
-
direction: "newest",
|
|
769
|
-
});
|
|
767
|
+
const next = appendBrowserSessionEventWindow(current, window.events);
|
|
770
768
|
const retained = {
|
|
771
769
|
...next,
|
|
772
770
|
truncated: current.truncated || next.truncated || window.hasNewer,
|
|
@@ -1049,6 +1047,33 @@ export function boundBrowserSessionEventWindow(
|
|
|
1049
1047
|
};
|
|
1050
1048
|
}
|
|
1051
1049
|
|
|
1050
|
+
/** @internal Append immutable events without serializing the retained history again. */
|
|
1051
|
+
export function appendBrowserSessionEventWindow(
|
|
1052
|
+
current: BrowserSessionEventWindow,
|
|
1053
|
+
batch: readonly SessionEvent[],
|
|
1054
|
+
options: { maxBytes?: number; maxCount?: number } = {},
|
|
1055
|
+
): BrowserSessionEventWindow {
|
|
1056
|
+
const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
|
|
1057
|
+
const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
|
|
1058
|
+
const events = [...current.events, ...batch];
|
|
1059
|
+
let bytes = current.bytes;
|
|
1060
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
1061
|
+
bytes += browserJsonBytes(batch[index]) + (current.events.length + index > 0 ? 1 : 0);
|
|
1062
|
+
}
|
|
1063
|
+
let start = 0;
|
|
1064
|
+
// Preserve the newest event even when it alone exceeds the byte target,
|
|
1065
|
+
// matching the full-window reducer and retaining exact cursor progress.
|
|
1066
|
+
while (events.length - start > 1 && (bytes > maxBytes || events.length - start > maxCount)) {
|
|
1067
|
+
bytes -= browserJsonBytes(events[start]) + 1;
|
|
1068
|
+
start += 1;
|
|
1069
|
+
}
|
|
1070
|
+
return {
|
|
1071
|
+
events: start === 0 ? events : events.slice(start),
|
|
1072
|
+
bytes,
|
|
1073
|
+
truncated: current.truncated || start > 0,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1052
1077
|
function browserJsonBytes(value: unknown): number {
|
|
1053
1078
|
return encoder.encode(JSON.stringify(value)).byteLength;
|
|
1054
1079
|
}
|
package/src/index.ts
CHANGED
|
@@ -504,6 +504,7 @@ export { ModelPolicyPickerMenu } from "./components/model-policy-picker-menu";
|
|
|
504
504
|
export type {
|
|
505
505
|
ModelPolicyPickerMessages,
|
|
506
506
|
ModelPolicyPickerProps,
|
|
507
|
+
ModelPolicyPickerGroupPresentation,
|
|
507
508
|
} from "./components/model-policy-picker";
|
|
508
509
|
export {
|
|
509
510
|
advancedSourceSummary,
|
package/src/model-policy.ts
CHANGED
|
@@ -217,10 +217,7 @@ function organizationProviderPayerSummary(model: ClientModel): string {
|
|
|
217
217
|
|
|
218
218
|
export function projectPickerRows(models: WorkspaceModelCatalogModel[]): PickerModelRow[] {
|
|
219
219
|
return models
|
|
220
|
-
.filter((catalog) =>
|
|
221
|
-
const billingClass = billingClassForModel(catalog);
|
|
222
|
-
return billingClass === "opengeni_credits" || catalog.credentialReadiness.status === "ready";
|
|
223
|
-
})
|
|
220
|
+
.filter((catalog) => catalog.credentialReadiness.status === "ready")
|
|
224
221
|
.map((catalog) => {
|
|
225
222
|
const billingClass = billingClassForModel(catalog);
|
|
226
223
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseMediaGenerationResult,
|
|
3
|
+
parseToolDisplayMetadata,
|
|
3
4
|
type HumanInputAnswer,
|
|
4
5
|
type HumanInputQuestion,
|
|
5
6
|
type HumanInputResponse,
|
|
@@ -610,6 +611,7 @@ export function buildTimeline(
|
|
|
610
611
|
|
|
611
612
|
case "agent.toolCall.created": {
|
|
612
613
|
const name = typeof payload.name === "string" ? payload.name : "tool";
|
|
614
|
+
const display = parseToolDisplayMetadata(payload.display);
|
|
613
615
|
const callId = typeof payload.id === "string" ? payload.id : null;
|
|
614
616
|
const args = payload.arguments ?? null;
|
|
615
617
|
closeStreamingTail();
|
|
@@ -651,6 +653,7 @@ export function buildTimeline(
|
|
|
651
653
|
(item): item is ToolCallItem => item.kind === "tool-call" && item.callId === callId,
|
|
652
654
|
);
|
|
653
655
|
if (existing) {
|
|
656
|
+
if (display) existing.display = display;
|
|
654
657
|
if (args != null) {
|
|
655
658
|
existing.arguments = args;
|
|
656
659
|
}
|
|
@@ -667,6 +670,7 @@ export function buildTimeline(
|
|
|
667
670
|
turnId,
|
|
668
671
|
callId,
|
|
669
672
|
name,
|
|
673
|
+
...(display ? { display } : {}),
|
|
670
674
|
arguments: args,
|
|
671
675
|
output: undefined,
|
|
672
676
|
truncation: null,
|
|
@@ -36,7 +36,9 @@ export function RollingActivity({
|
|
|
36
36
|
const fallback = (
|
|
37
37
|
<span className="og-rolling-label">
|
|
38
38
|
<WrenchIcon className="size-3.5" />
|
|
39
|
-
<span>
|
|
39
|
+
<span>
|
|
40
|
+
{item.kind === "tool-call" ? toolDisplayName(item.name, item.display) : "Working"}
|
|
41
|
+
</span>
|
|
40
42
|
</span>
|
|
41
43
|
);
|
|
42
44
|
return (
|
|
@@ -46,7 +48,7 @@ export function RollingActivity({
|
|
|
46
48
|
>
|
|
47
49
|
<span className="sr-only">
|
|
48
50
|
{item.kind === "tool-call"
|
|
49
|
-
? toolDisplayName(item.name)
|
|
51
|
+
? toolDisplayName(item.name, item.display)
|
|
50
52
|
: item.kind === "reasoning"
|
|
51
53
|
? "Thinking"
|
|
52
54
|
: "Working"}
|