@opengeni/react 0.36.0 → 0.37.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 (45) hide show
  1. package/README.md +4 -0
  2. package/dist/{chunk-J2RVJ6JX.js → chunk-4IJCL7YO.js} +13 -2
  3. package/dist/chunk-4IJCL7YO.js.map +1 -0
  4. package/dist/{chunk-OKKMZDQF.js → chunk-FSPDND3P.js} +2 -2
  5. package/dist/{chunk-FT2TXNGZ.js → chunk-HJ4OQVGW.js} +174 -14
  6. package/dist/chunk-HJ4OQVGW.js.map +1 -0
  7. package/dist/{chunk-PVW56EVR.js → chunk-IP22SLO6.js} +447 -13
  8. package/dist/chunk-IP22SLO6.js.map +1 -0
  9. package/dist/{chunk-U255M5EZ.js → chunk-SJKT4TKW.js} +39 -6
  10. package/dist/chunk-SJKT4TKW.js.map +1 -0
  11. package/dist/{chunk-XT7JF2EH.js → chunk-UCZPNXV3.js} +96 -17
  12. package/dist/chunk-UCZPNXV3.js.map +1 -0
  13. package/dist/components/chat-composer.d.ts +3 -1
  14. package/dist/components/model-policy-picker.d.ts +78 -0
  15. package/dist/components/session-chrome.d.ts +1 -1
  16. package/dist/composer.d.ts +2 -0
  17. package/dist/composer.js +17 -3
  18. package/dist/hooks/use-composer.d.ts +9 -0
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.js +30 -9
  21. package/dist/index.js.map +1 -1
  22. package/dist/model-policy.d.ts +12 -4
  23. package/dist/model-policy.js +5 -1
  24. package/dist/session-ui.js +2 -2
  25. package/dist/session.js +3 -3
  26. package/dist/timeline/types.d.ts +5 -0
  27. package/package.json +2 -2
  28. package/src/components/chat-composer.tsx +8 -3
  29. package/src/components/composer.tsx +17 -1
  30. package/src/components/copy-button.tsx +13 -6
  31. package/src/components/model-picker.tsx +2 -2
  32. package/src/components/model-policy-picker.tsx +582 -0
  33. package/src/components/session-chrome.tsx +138 -14
  34. package/src/composer.ts +13 -0
  35. package/src/hooks/use-composer.ts +213 -16
  36. package/src/index.ts +15 -0
  37. package/src/model-policy.ts +69 -14
  38. package/src/timeline/projection.ts +21 -1
  39. package/src/timeline/types.ts +5 -0
  40. package/dist/chunk-FT2TXNGZ.js.map +0 -1
  41. package/dist/chunk-J2RVJ6JX.js.map +0 -1
  42. package/dist/chunk-PVW56EVR.js.map +0 -1
  43. package/dist/chunk-U255M5EZ.js.map +0 -1
  44. package/dist/chunk-XT7JF2EH.js.map +0 -1
  45. /package/dist/{chunk-OKKMZDQF.js.map → chunk-FSPDND3P.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from "@opengeni/sdk";
2
2
  export type PickerBillingClass = "opengeni_credits" | "codex_subscription" | "byok";
3
- export type PickerModelRow = {
3
+ export type PickerModelRow<TCatalog extends ClientModel = WorkspaceModelCatalogModel> = {
4
4
  id: string;
5
5
  label: string;
6
6
  billingClass: PickerBillingClass;
@@ -9,7 +9,7 @@ export type PickerModelRow = {
9
9
  unavailableReason: string | null;
10
10
  provider: string;
11
11
  providerLabel: string;
12
- catalog: WorkspaceModelCatalogModel;
12
+ catalog: TCatalog;
13
13
  };
14
14
  export type LatencyModeId = "standard" | "priority" | "fast";
15
15
  export declare function billingClassForModel(model: ClientModel): PickerBillingClass;
@@ -20,13 +20,21 @@ export declare function defaultEffortForModel(model: ClientModel): ReasoningEffo
20
20
  export declare function coerceReasoningEffortForModel(model: ClientModel, effort: ReasoningEffort): ReasoningEffort;
21
21
  export declare function runnableLatencyModesForModel(model: ClientModel): LatencyModeId[];
22
22
  export declare function labelLatencyMode(mode: LatencyModeId): string;
23
+ export declare function labelReasoningEffort(effort: ReasoningEffort): string;
23
24
  export declare function payerSummaryForModel(model: ClientModel): string;
24
25
  export declare function advancedSourceSummary(model: ClientModel): string | null;
25
26
  export declare function projectPickerRows(models: WorkspaceModelCatalogModel[]): PickerModelRow[];
26
- export declare function sortPickerRows(rows: PickerModelRow[]): PickerModelRow[];
27
- export declare function findPickerRow(rows: PickerModelRow[], modelId: string): PickerModelRow | null;
27
+ /** Project the lightweight client-config model list into the same picker contract. */
28
+ export declare function projectClientModelRows(models: ClientModel[]): PickerModelRow<ClientModel>[];
29
+ export declare function sortPickerRows<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[]): PickerModelRow<TCatalog>[];
30
+ export declare function findPickerRow<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[], modelId: string): PickerModelRow<TCatalog> | null;
28
31
  export declare function groupPickerRowsByBillingClass(rows: PickerModelRow[]): Array<{
29
32
  billingClass: PickerBillingClass;
30
33
  label: string;
31
34
  rows: PickerModelRow[];
32
35
  }>;
36
+ export declare function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(rows: PickerModelRow<TCatalog>[]): Array<{
37
+ billingClass: PickerBillingClass;
38
+ label: string;
39
+ rows: PickerModelRow<TCatalog>[];
40
+ }>;
@@ -9,11 +9,13 @@ import {
9
9
  findPickerRow,
10
10
  groupPickerRowsByBillingClass,
11
11
  labelLatencyMode,
12
+ labelReasoningEffort,
12
13
  payerSummaryForModel,
14
+ projectClientModelRows,
13
15
  projectPickerRows,
14
16
  runnableLatencyModesForModel,
15
17
  sortPickerRows
16
- } from "./chunk-U255M5EZ.js";
18
+ } from "./chunk-SJKT4TKW.js";
17
19
  export {
18
20
  advancedSourceSummary,
19
21
  availabilityReasonLabel,
@@ -25,7 +27,9 @@ export {
25
27
  findPickerRow,
26
28
  groupPickerRowsByBillingClass,
27
29
  labelLatencyMode,
30
+ labelReasoningEffort,
28
31
  payerSummaryForModel,
32
+ projectClientModelRows,
29
33
  projectPickerRows,
30
34
  runnableLatencyModesForModel,
31
35
  sortPickerRows
@@ -6,8 +6,8 @@ import {
6
6
  SessionChrome,
7
7
  TimelineRow,
8
8
  sessionChromeGoalPillState
9
- } from "./chunk-XT7JF2EH.js";
10
- import "./chunk-J2RVJ6JX.js";
9
+ } from "./chunk-UCZPNXV3.js";
10
+ import "./chunk-4IJCL7YO.js";
11
11
  import "./chunk-ATSTR5P3.js";
12
12
  import "./chunk-TK7G6XLT.js";
13
13
  import "./chunk-LAKLF4PA.js";
package/dist/session.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  useSessionLineage,
19
19
  useSessionMcpApprovalPolicy,
20
20
  useTurnQueue
21
- } from "./chunk-OKKMZDQF.js";
21
+ } from "./chunk-FSPDND3P.js";
22
22
  import {
23
23
  buildTimeline,
24
24
  creditExhaustedFromEvents,
@@ -26,14 +26,14 @@ import {
26
26
  groupTimeline,
27
27
  sessionStatusFromEvents,
28
28
  toolDisplayName
29
- } from "./chunk-J2RVJ6JX.js";
29
+ } from "./chunk-4IJCL7YO.js";
30
30
  import {
31
31
  FILE_ONLY_MESSAGE_TEXT,
32
32
  composeSendInput,
33
33
  shouldSteerOnKey,
34
34
  shouldSubmitOnKey,
35
35
  useComposer
36
- } from "./chunk-FT2TXNGZ.js";
36
+ } from "./chunk-HJ4OQVGW.js";
37
37
  import "./chunk-7CJOAW3V.js";
38
38
  import "./chunk-LAKLF4PA.js";
39
39
  export {
@@ -3,6 +3,11 @@ export type UserMessageItem = {
3
3
  kind: "user-message";
4
4
  id: string;
5
5
  text: string;
6
+ presentation?: {
7
+ kind: "realtime_voice" | "realtime_voice_handoff";
8
+ /** Full bounded execution context sent with this visible voice request. */
9
+ context: string;
10
+ };
6
11
  /** Resources attached to this message (file uploads, repositories). */
7
12
  resources: ResourceRef[];
8
13
  /** Tools requested for the turn this message starts. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -70,7 +70,7 @@
70
70
  "@dnd-kit/utilities": "3.2.2",
71
71
  "@fontsource-variable/noto-sans-arabic": "5.2.10",
72
72
  "@fontsource-variable/noto-sans-jp": "5.2.10",
73
- "@opengeni/sdk": "^0.36.0",
73
+ "@opengeni/sdk": "^0.37.0",
74
74
  "clsx": "^2.1.1",
75
75
  "lucide-react": "^1.8.0",
76
76
  "motion": "^12.0.0",
@@ -53,6 +53,8 @@ export type ChatComposerProps = {
53
53
  hint?: string | undefined;
54
54
  /** App controls in the footer row, replacing the hint. */
55
55
  controlsStart?: ReactNode | undefined;
56
+ /** App actions beside Pause/Send, ordered before the built-in actions. */
57
+ actionsStart?: ReactNode | undefined;
56
58
  /** Provider-neutral speech capability. Provider configuration stays in workspace settings. */
57
59
  transcription?: ComposerTranscriptionControlProps | undefined;
58
60
  /** Content rendered above the textarea, inside the field chrome. */
@@ -89,6 +91,7 @@ export function ChatComposer({
89
91
  autoFocus,
90
92
  hint,
91
93
  controlsStart,
94
+ actionsStart,
92
95
  transcription,
93
96
  header,
94
97
  onPaste,
@@ -119,6 +122,7 @@ export function ChatComposer({
119
122
  messages,
120
123
  });
121
124
  const hasControls = Boolean(attachments || models || controlsStart || transcription);
125
+ const stackActions = hasControls && Boolean(actionsStart);
122
126
 
123
127
  return (
124
128
  <Root controller={controller} className={className}>
@@ -133,9 +137,9 @@ export function ChatComposer({
133
137
  {controller.confirmState ? (
134
138
  <Confirmation />
135
139
  ) : (
136
- <Footer>
140
+ <Footer className={stackActions ? "flex-wrap" : undefined}>
137
141
  {hasControls ? (
138
- <Controls>
142
+ <Controls className={stackActions ? "w-full sm:w-auto" : undefined}>
139
143
  <AttachButton />
140
144
  {transcription ? <ComposerTranscriptionControl {...transcription} /> : null}
141
145
  {models ? (
@@ -146,7 +150,8 @@ export function ChatComposer({
146
150
  ) : (
147
151
  <Hint>{hint}</Hint>
148
152
  )}
149
- <Actions>
153
+ <Actions className={stackActions ? "w-full justify-end sm:w-auto" : undefined}>
154
+ {actionsStart}
150
155
  <PauseButton />
151
156
  <SendButton />
152
157
  </Actions>
@@ -851,12 +851,27 @@ type OwnedInputProps =
851
851
  export type ComposerInputProps = Omit<ComponentPropsWithoutRef<"textarea">, OwnedInputProps>;
852
852
 
853
853
  export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(function ComposerInput(
854
- { rows = 1, placeholder, className, "aria-label": ariaLabel, ...props },
854
+ { rows = 1, placeholder, className, "aria-label": ariaLabel, autoFocus = false, ...props },
855
855
  forwardedRef,
856
856
  ) {
857
857
  const controller = useComposerController();
858
+ const autoFocusedRef = useRef(false);
858
859
  const paletteOpen =
859
860
  controller.paletteEnabled && controller.paletteMounted && controller.palette.open;
861
+
862
+ // Native autoFocus loses when the textarea mounts disabled (create-session draft
863
+ // hydrate). Retry once the controller becomes interactive.
864
+ useEffect(() => {
865
+ if (!autoFocus || controller.disabled || autoFocusedRef.current) return;
866
+ const frame = window.requestAnimationFrame(() => {
867
+ const textarea = controller.textareaRef.current;
868
+ if (!textarea || textarea.disabled) return;
869
+ autoFocusedRef.current = true;
870
+ textarea.focus();
871
+ });
872
+ return () => window.cancelAnimationFrame(frame);
873
+ }, [autoFocus, controller.disabled, controller.textareaRef]);
874
+
860
875
  return (
861
876
  <textarea
862
877
  {...props}
@@ -872,6 +887,7 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
872
887
  : (placeholder ?? controller.messages.messagePlaceholder)
873
888
  }
874
889
  disabled={controller.disabled}
890
+ autoFocus={autoFocus && !controller.disabled}
875
891
  aria-label={ariaLabel ?? controller.messages.inputLabel}
876
892
  aria-keyshortcuts="Enter Meta+Enter Control+Enter Shift+Enter"
877
893
  aria-autocomplete={
@@ -44,6 +44,11 @@ export function CopyButton({
44
44
  const onClick = async (event: MouseEvent<HTMLButtonElement>) => {
45
45
  event.preventDefault();
46
46
  event.stopPropagation();
47
+ // Capture before any await: React nulls `currentTarget` after the
48
+ // synchronous handler turn. Blurring via the stale target throws and used
49
+ // to skip the copied-reset timer, leaving the tip stuck on "Copied".
50
+ const button = event.currentTarget;
51
+ const pointerActivated = event.detail > 0;
47
52
  const value = typeof text === "function" ? text() : text;
48
53
  const ok = await copyTextToClipboard(value);
49
54
  if (!ok) {
@@ -51,20 +56,22 @@ export function CopyButton({
51
56
  }
52
57
  setCopied(true);
53
58
  setTipOpen(true);
54
- // Pointer activation focuses the button and leaves `group-focus-within`
55
- // stuck after the cursor leaves — chrome never "unhovers". Keyboard
56
- // activation (`detail === 0`) keeps focus for a11y.
57
- if (event.detail > 0) {
58
- event.currentTarget.blur();
59
- }
60
59
  if (timerRef.current !== null) {
61
60
  clearTimeout(timerRef.current);
62
61
  }
62
+ // Always arm the reset before blur — a throw here must not leave the
63
+ // forced "Copied" tip open forever.
63
64
  timerRef.current = setTimeout(() => {
64
65
  timerRef.current = null;
65
66
  setCopied(false);
66
67
  setTipOpen(false);
67
68
  }, COPIED_MS);
69
+ // Pointer activation focuses the button and leaves `group-focus-within`
70
+ // stuck after the cursor leaves — chrome never "unhovers". Keyboard
71
+ // activation (`detail === 0`) keeps focus for a11y.
72
+ if (pointerActivated) {
73
+ button.blur();
74
+ }
68
75
  };
69
76
 
70
77
  const tip = copied ? "Copied" : label;
@@ -24,11 +24,11 @@ export type ModelPickerProps = {
24
24
  const EMPTY_CLIENT_MODELS: ClientModel[] = [];
25
25
 
26
26
  function isCodexClientModel(model: ClientModel): boolean {
27
- return model.id.startsWith("codex/") || model.provider === "codex-subscription";
27
+ return model.id.startsWith("codex/") || model.source === "codex";
28
28
  }
29
29
 
30
30
  function isCodexPickerRow(row: PickerModelRow): boolean {
31
- return row.id.startsWith("codex/") || row.provider === "codex-subscription";
31
+ return row.id.startsWith("codex/") || row.catalog.source === "codex";
32
32
  }
33
33
 
34
34
  function applyCodexOnlyRows(rows: PickerModelRow[], codexOnly: boolean): PickerModelRow[] {