@opengeni/react 1.4.2 → 1.4.5-canary.37458084490001
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 +17 -6
- package/dist/artifacts.d.ts +1 -0
- package/dist/artifacts.js +22 -15
- package/dist/artifacts.js.map +1 -1
- package/dist/{browser-viewer-WGJ43YGI.js → browser-viewer-CV23VIHB.js} +8 -6
- package/dist/chunk-3ZC6QPOM.js +174 -0
- package/dist/chunk-3ZC6QPOM.js.map +1 -0
- package/dist/{chunk-YVFRBXST.js → chunk-4BCVCJSU.js} +2 -2
- package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
- package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
- package/dist/{chunk-MDIXFKA3.js → chunk-6FOTSVWU.js} +303 -109
- package/dist/chunk-6FOTSVWU.js.map +1 -0
- package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
- package/dist/chunk-7RRBX5EM.js.map +1 -0
- package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
- package/dist/chunk-7ZB7I3KP.js.map +1 -0
- package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
- package/dist/chunk-ATIRUHBZ.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/{chunk-VCSERYXH.js → chunk-CESBPQP5.js} +309 -162
- package/dist/chunk-CESBPQP5.js.map +1 -0
- package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
- package/dist/chunk-GHJU7XG7.js.map +1 -0
- package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
- package/dist/chunk-HNVWMDNN.js.map +1 -0
- package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
- package/dist/chunk-I5EGRXXG.js.map +1 -0
- package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
- package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
- package/dist/chunk-IX3K4THH.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-JDJJMBB3.js +1883 -0
- package/dist/chunk-JDJJMBB3.js.map +1 -0
- package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
- package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
- package/dist/chunk-LHF6BVM4.js +83 -0
- package/dist/chunk-LHF6BVM4.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-R5R5RQQD.js +51 -0
- package/dist/chunk-R5R5RQQD.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/{chunk-CREMJMVN.js → chunk-TZQSHCZN.js} +692 -1996
- package/dist/chunk-TZQSHCZN.js.map +1 -0
- package/dist/chunk-ZIB4H5LN.js +101 -0
- package/dist/chunk-ZIB4H5LN.js.map +1 -0
- package/dist/client.d.ts +9 -3
- package/dist/clipboard.js +1 -1
- package/dist/components/browser-viewer.d.ts +6 -0
- package/dist/components/composer.d.ts +3 -69
- package/dist/components/human-input-form.d.ts +3 -32
- package/dist/components/markdown.d.ts +8 -0
- package/dist/components/model-policy-picker.d.ts +4 -37
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/provider-recovery-notice.d.ts +15 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-workspace.d.ts +13 -0
- package/dist/components/session-chrome.d.ts +5 -3
- package/dist/components/session-conversation.d.ts +15 -3
- package/dist/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer.js +15 -10
- package/dist/{computer-viewer-46UIN7F3.js → computer-viewer-K4CKYRX6.js} +4 -4
- package/dist/connect-setup.d.ts +2 -0
- package/dist/connect.d.ts +1 -1
- package/dist/connect.js +21 -6
- package/dist/connect.js.map +1 -1
- package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-3ZFWWJ6O.js} +6 -4
- package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.js.map} +1 -1
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/use-client-config-flags.d.ts +6 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/human-input-model.d.ts +40 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +116 -73
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +5 -5
- package/dist/{latest-question-2POFPYNB.js → latest-question-SWC6QEVN.js} +3 -3
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/provider-recovery.d.ts +49 -0
- package/dist/machines.js +2 -2
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
- package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
- package/dist/model-policy.d.ts +10 -0
- package/dist/model-policy.js +5 -1
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
- package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
- package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
- package/dist/realtime.js +5 -3
- package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
- package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +23 -0
- package/dist/sandbox-label-model.js +28 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +46 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +96 -0
- package/dist/session-list-model.js +219 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-status-model.d.ts +19 -0
- package/dist/session-ui.js +26 -17
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +8 -6
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +303 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +11 -0
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/notice-presentation.d.ts +7 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +7 -0
- package/dist/timeline/presented-image.d.ts +6 -1
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +10 -1
- package/dist/timeline/retained-loaders.d.ts +1 -1
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +90 -0
- package/dist/timeline/turn-summary.d.ts +4 -74
- package/dist/timeline/work-presentation.d.ts +42 -0
- package/dist/timeline-model.d.ts +24 -0
- package/dist/timeline-model.js +152 -0
- package/dist/timeline-model.js.map +1 -0
- package/package.json +47 -3
- package/src/artifacts.ts +1 -0
- package/src/client.ts +9 -3
- package/src/components/browser-viewer.tsx +23 -2
- package/src/components/claude-mark.tsx +2 -1
- package/src/components/composer.tsx +4 -144
- package/src/components/computer-viewer.tsx +139 -30
- package/src/components/grok-mark.tsx +2 -1
- package/src/components/human-input-form.tsx +13 -124
- package/src/components/markdown.tsx +19 -1
- package/src/components/message-timeline.tsx +45 -99
- package/src/components/model-mark.tsx +2 -1
- package/src/components/model-policy-picker-menu.tsx +30 -85
- package/src/components/model-policy-picker.tsx +15 -148
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/provider-recovery-notice.tsx +41 -0
- package/src/components/queue-surface-implementation.tsx +20 -349
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-workspace.tsx +17 -1
- package/src/components/session-chrome.tsx +114 -99
- package/src/components/session-conversation.tsx +55 -6
- package/src/components/session-list.tsx +1 -1
- package/src/components/session-status.tsx +17 -16
- package/src/composer-messages.ts +150 -0
- package/src/connect-setup.tsx +45 -11
- package/src/connect.ts +1 -1
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/use-browser-session.ts +445 -168
- package/src/hooks/use-client-config-flags.ts +8 -0
- package/src/hooks/use-computer-session.ts +334 -98
- package/src/hooks/use-goal.ts +66 -10
- package/src/human-input-model.ts +147 -0
- package/src/index.ts +16 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/format.ts +7 -0
- package/src/lib/provider-recovery.ts +202 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +43 -2
- package/src/organization-model.ts +118 -0
- package/src/queue-presentation-model.ts +464 -0
- package/src/sandbox-label-model.ts +41 -0
- package/src/session-attention-model.ts +59 -0
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +327 -0
- package/src/session-status-model.ts +62 -0
- package/src/testing/session-scenarios.ts +326 -0
- package/src/timeline/activity-rail.tsx +5 -17
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +13 -18
- package/src/timeline/index.ts +1 -0
- package/src/timeline/notice-presentation.ts +22 -0
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +122 -0
- package/src/timeline/platform-activity-row.tsx +6 -96
- package/src/timeline/presented-image.ts +16 -2
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +11 -1
- package/src/timeline/retained-loaders.ts +9 -4
- package/src/timeline/rolling-activity.tsx +7 -19
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +226 -1025
- package/src/timeline/turn-summary-model.ts +338 -0
- package/src/timeline/turn-summary.tsx +21 -326
- package/src/timeline/work-presentation.ts +139 -0
- package/src/timeline-model.ts +108 -0
- package/styles/compiled.css +8 -0
- package/styles/connect.css +4 -0
- package/dist/chunk-CREMJMVN.js.map +0 -1
- package/dist/chunk-CVYCLFMH.js.map +0 -1
- package/dist/chunk-G6YRCIYJ.js.map +0 -1
- package/dist/chunk-HXYOSSQO.js +0 -117
- package/dist/chunk-HXYOSSQO.js.map +0 -1
- package/dist/chunk-IUHHPMAX.js.map +0 -1
- package/dist/chunk-IYQCWGU5.js.map +0 -1
- package/dist/chunk-JH7V627O.js.map +0 -1
- package/dist/chunk-L2J3PU7A.js.map +0 -1
- package/dist/chunk-LVS3MH5T.js.map +0 -1
- package/dist/chunk-MDIXFKA3.js.map +0 -1
- package/dist/chunk-PHBFJCEU.js.map +0 -1
- package/dist/chunk-VCSERYXH.js.map +0 -1
- package/dist/chunk-XS67YG5R.js.map +0 -1
- package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
- package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
- /package/dist/{browser-viewer-WGJ43YGI.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
- /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
- /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
- /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
- /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
- /package/dist/{computer-viewer-46UIN7F3.js.map → composer-messages.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
- /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
- /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model picker's presentation logic, free of any renderer: which rows show,
|
|
3
|
+
* how they group and filter, and which thinking/latency controls apply. The web
|
|
4
|
+
* menu and native sheets both render from this.
|
|
5
|
+
*/
|
|
6
|
+
import type { ClientModel, LatencyMode, ReasoningEffort } from "@opengeni/sdk";
|
|
7
|
+
import {
|
|
8
|
+
coerceReasoningEffortForModel,
|
|
9
|
+
effortOptionsForModel,
|
|
10
|
+
findPickerRow,
|
|
11
|
+
groupPickerRowsByBillingClass,
|
|
12
|
+
payerSummaryForModel,
|
|
13
|
+
projectClientModelRows,
|
|
14
|
+
runnableLatencyModesForModel,
|
|
15
|
+
scopedBillingClassLabel,
|
|
16
|
+
type PickerBillingClass,
|
|
17
|
+
type PickerModelRow,
|
|
18
|
+
} from "./model-policy";
|
|
19
|
+
|
|
20
|
+
type ClientPickerModelRow = PickerModelRow<ClientModel>;
|
|
21
|
+
|
|
22
|
+
export type ModelPolicyPickerMessages = {
|
|
23
|
+
label: string;
|
|
24
|
+
loading: string;
|
|
25
|
+
noModels: string;
|
|
26
|
+
connectTitle: string;
|
|
27
|
+
connectBody: string;
|
|
28
|
+
connectAction: string;
|
|
29
|
+
thinking: string;
|
|
30
|
+
fast: string;
|
|
31
|
+
fastRateHint: string;
|
|
32
|
+
codexOnly: string;
|
|
33
|
+
searchLabel?: string;
|
|
34
|
+
searchPlaceholder?: string;
|
|
35
|
+
currentModel?: string;
|
|
36
|
+
noMatches?: string;
|
|
37
|
+
unsupportedAttachments?: string;
|
|
38
|
+
thinkingEffort?: string;
|
|
39
|
+
selected?: string;
|
|
40
|
+
free?: string;
|
|
41
|
+
|
|
42
|
+
billingHints: Record<PickerBillingClass, string>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {
|
|
46
|
+
label: "Model and effort",
|
|
47
|
+
loading: "Loading model catalog…",
|
|
48
|
+
noModels: "No models available.",
|
|
49
|
+
connectTitle: "Connect a model",
|
|
50
|
+
connectBody: "Use a subscription or a provider key you already have.",
|
|
51
|
+
connectAction: "Open Models",
|
|
52
|
+
thinking: "Thinking",
|
|
53
|
+
fast: "Fast",
|
|
54
|
+
fastRateHint: "2× rate",
|
|
55
|
+
codexOnly: "Codex-only session",
|
|
56
|
+
searchLabel: "Search models or providers",
|
|
57
|
+
searchPlaceholder: "Search models or providers…",
|
|
58
|
+
currentModel: "Current model",
|
|
59
|
+
noMatches: "No matching models. Try a model or provider name.",
|
|
60
|
+
unsupportedAttachments: "This model cannot view the attached images.",
|
|
61
|
+
thinkingEffort: "Thinking effort",
|
|
62
|
+
selected: "Selected",
|
|
63
|
+
free: "Free",
|
|
64
|
+
|
|
65
|
+
billingHints: {
|
|
66
|
+
opengeni_credits: "Provided by Opengeni",
|
|
67
|
+
external: "Provider terms and limits apply",
|
|
68
|
+
codex_subscription: "ChatGPT / Codex plan",
|
|
69
|
+
supergrok_subscription: "SuperGrok / xAI plan",
|
|
70
|
+
claude_subscription: "Claude plan",
|
|
71
|
+
byok: "Billed to the connected provider account",
|
|
72
|
+
organization_byok: "Billed to the connected provider account",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Host branding for one payment group; the icon type belongs to the renderer. */
|
|
77
|
+
export type ModelPickerGroupBranding<TIcon = unknown> = {
|
|
78
|
+
label?: string | undefined;
|
|
79
|
+
/** Omit to preserve the default; null hides the supporting text. */
|
|
80
|
+
description?: string | null | undefined;
|
|
81
|
+
/** Decorative, non-interactive content. Omit for the default; null hides it. */
|
|
82
|
+
icon?: TIcon | undefined;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type ModelPickerGroupPresentation<TIcon = unknown> = Partial<
|
|
86
|
+
Record<PickerBillingClass, ModelPickerGroupBranding<TIcon>>
|
|
87
|
+
>;
|
|
88
|
+
|
|
89
|
+
/** The renderer-neutral inputs every picker surface shares. */
|
|
90
|
+
export type ModelPickerInput<TIcon = unknown> = {
|
|
91
|
+
groupPresentation?: ModelPickerGroupPresentation<TIcon> | undefined;
|
|
92
|
+
models?: ClientModel[] | undefined;
|
|
93
|
+
rows?: PickerModelRow[] | undefined;
|
|
94
|
+
hasImageAttachments?: boolean | undefined;
|
|
95
|
+
model: string;
|
|
96
|
+
effort: ReasoningEffort;
|
|
97
|
+
latencyMode: LatencyMode;
|
|
98
|
+
codexOnly?: boolean | undefined;
|
|
99
|
+
collapseScopes?: boolean | undefined;
|
|
100
|
+
allowLatencyMode?: boolean | undefined;
|
|
101
|
+
messages?: Partial<ModelPolicyPickerMessages> | undefined;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Models settings keeps naming who is billed for a key. */
|
|
105
|
+
export const SCOPED_BILLING_HINTS: Partial<Record<PickerBillingClass, string>> = {
|
|
106
|
+
byok: "Billed to the workspace provider account",
|
|
107
|
+
organization_byok: "Billed to the organization provider account",
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export function isCodexPickerModel(model: ClientModel): boolean {
|
|
111
|
+
return model.id.startsWith("codex/") || model.source === "codex";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The payment group a selection the catalog no longer lists most likely belonged to. */
|
|
115
|
+
export function billingClassForMissingSelection(modelId: string): PickerBillingClass {
|
|
116
|
+
if (modelId.startsWith("workspace-claude-subscription/")) return "claude_subscription";
|
|
117
|
+
if (modelId.startsWith("workspace-anthropic/")) return "byok";
|
|
118
|
+
if (modelId.startsWith("organization-claude-subscription/")) return "claude_subscription";
|
|
119
|
+
if (modelId.startsWith("organization-anthropic/")) return "organization_byok";
|
|
120
|
+
if (modelId.startsWith("workspace-gateway/")) return "byok";
|
|
121
|
+
if (modelId.startsWith("workspace-openrouter/")) return "byok";
|
|
122
|
+
if (modelId.startsWith("workspace-opper/")) return "byok";
|
|
123
|
+
// A deployment OpenRouter or Opper ID does not encode its workspace-facing cost.
|
|
124
|
+
// Missing rows therefore use the credits-safe rail instead of falsely
|
|
125
|
+
// claiming that an unknown former selection was externally funded.
|
|
126
|
+
if (modelId.startsWith("openrouter/")) return "opengeni_credits";
|
|
127
|
+
if (modelId.startsWith("opper/")) return "opengeni_credits";
|
|
128
|
+
if (modelId.startsWith("codex/")) return "codex_subscription";
|
|
129
|
+
if (modelId.startsWith("supergrok/")) return "supergrok_subscription";
|
|
130
|
+
return "opengeni_credits";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function applyCodexOnly(
|
|
134
|
+
rows: ClientPickerModelRow[],
|
|
135
|
+
codexOnly: boolean,
|
|
136
|
+
unavailableReason: string,
|
|
137
|
+
): ClientPickerModelRow[] {
|
|
138
|
+
if (!codexOnly) return rows;
|
|
139
|
+
return rows.map((row) =>
|
|
140
|
+
isCodexPickerModel(row.catalog)
|
|
141
|
+
? row
|
|
142
|
+
: {
|
|
143
|
+
...row,
|
|
144
|
+
selectable: false,
|
|
145
|
+
unavailableReason: row.unavailableReason ?? unavailableReason,
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Host branding for a payment group. While scopes are collapsed, API keys are
|
|
152
|
+
* one group, so either API-key presentation serves both.
|
|
153
|
+
*/
|
|
154
|
+
export function groupPresentationFor<TIcon>(
|
|
155
|
+
props: {
|
|
156
|
+
groupPresentation?: ModelPickerGroupPresentation<TIcon> | undefined;
|
|
157
|
+
collapseScopes?: boolean | undefined;
|
|
158
|
+
},
|
|
159
|
+
billingClass: PickerBillingClass,
|
|
160
|
+
): ModelPickerGroupBranding<TIcon> | undefined {
|
|
161
|
+
const own = props.groupPresentation?.[billingClass];
|
|
162
|
+
if (own !== undefined || props.collapseScopes === false) return own;
|
|
163
|
+
if (billingClass === "byok") return props.groupPresentation?.organization_byok;
|
|
164
|
+
if (billingClass === "organization_byok") return props.groupPresentation?.byok;
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function effectiveRows<TIcon>(props: ModelPickerInput<TIcon>): ClientPickerModelRow[] {
|
|
169
|
+
const rows = props.rows !== undefined ? props.rows : projectClientModelRows(props.models ?? []);
|
|
170
|
+
const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
|
|
171
|
+
return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly).map((row) => {
|
|
172
|
+
const scoped =
|
|
173
|
+
props.collapseScopes === false
|
|
174
|
+
? {
|
|
175
|
+
...row,
|
|
176
|
+
label: row.catalog.label,
|
|
177
|
+
billingClassLabel: scopedBillingClassLabel(row.billingClass),
|
|
178
|
+
}
|
|
179
|
+
: row;
|
|
180
|
+
if (props.collapseScopes === false) {
|
|
181
|
+
if (row.catalog.shortLabel) scoped.shortLabel = row.catalog.shortLabel;
|
|
182
|
+
else delete scoped.shortLabel;
|
|
183
|
+
}
|
|
184
|
+
const label = groupPresentationFor(props, row.billingClass)?.label;
|
|
185
|
+
return label === undefined ? scoped : { ...scoped, billingClassLabel: label };
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type ModelPickerMenuGroup = {
|
|
190
|
+
billingClass: PickerBillingClass;
|
|
191
|
+
label: string;
|
|
192
|
+
description: string | null;
|
|
193
|
+
rows: ClientPickerModelRow[];
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export type ModelPickerThinking = {
|
|
197
|
+
efforts: ReasoningEffort[];
|
|
198
|
+
value: ReasoningEffort;
|
|
199
|
+
showThinking: boolean;
|
|
200
|
+
showFast: boolean;
|
|
201
|
+
/** The selected row cannot run, so its controls are read-only. */
|
|
202
|
+
disabled: boolean;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export type ModelPickerMenuView = {
|
|
206
|
+
messages: ModelPolicyPickerMessages;
|
|
207
|
+
rows: ClientPickerModelRow[];
|
|
208
|
+
selected: ClientPickerModelRow | null;
|
|
209
|
+
/** Any row can run; otherwise the surface offers to connect models instead. */
|
|
210
|
+
anySelectable: boolean;
|
|
211
|
+
/** Whether the query is non-empty (decides "no matches" versus "no models"). */
|
|
212
|
+
searching: boolean;
|
|
213
|
+
groups: ModelPickerMenuGroup[];
|
|
214
|
+
matchCount: number;
|
|
215
|
+
/** Thinking and Fast controls for the selected model, or null when it has none. */
|
|
216
|
+
thinking: ModelPickerThinking | null;
|
|
217
|
+
/** The selected model cannot view images the draft carries. */
|
|
218
|
+
unsupportedAttachments: boolean;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/** Everything a picker menu shows for this input and search query. */
|
|
222
|
+
export function modelPickerMenuView<TIcon>(
|
|
223
|
+
props: ModelPickerInput<TIcon>,
|
|
224
|
+
query: string,
|
|
225
|
+
): ModelPickerMenuView {
|
|
226
|
+
const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };
|
|
227
|
+
const rows = effectiveRows(props);
|
|
228
|
+
const selected = findPickerRow(rows, props.model);
|
|
229
|
+
const words = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
|
230
|
+
const filtered = rows.filter((row) => {
|
|
231
|
+
const description = groupPresentationFor(props, row.billingClass)?.description;
|
|
232
|
+
const text =
|
|
233
|
+
`${row.label} ${row.id} ${row.providerLabel} ${row.billingClassLabel} ${description === undefined ? payerSummaryForModel(row.catalog) : (description ?? "")}`.toLowerCase();
|
|
234
|
+
return words.every((word) => text.includes(word));
|
|
235
|
+
});
|
|
236
|
+
const matchingIds = new Set(filtered.map((row) => row.id));
|
|
237
|
+
const groups = groupPickerRowsByBillingClass(rows, {
|
|
238
|
+
codexOnly: props.codexOnly === true,
|
|
239
|
+
selectedId: props.model,
|
|
240
|
+
collapseScopes: props.collapseScopes !== false,
|
|
241
|
+
})
|
|
242
|
+
.map((group) => {
|
|
243
|
+
const presentation = groupPresentationFor(props, group.billingClass);
|
|
244
|
+
const override = presentation?.description;
|
|
245
|
+
const defaultHint =
|
|
246
|
+
props.collapseScopes === false &&
|
|
247
|
+
messages.billingHints[group.billingClass] ===
|
|
248
|
+
defaultModelPolicyPickerMessages.billingHints[group.billingClass]
|
|
249
|
+
? (SCOPED_BILLING_HINTS[group.billingClass] ?? messages.billingHints[group.billingClass])
|
|
250
|
+
: messages.billingHints[group.billingClass];
|
|
251
|
+
return {
|
|
252
|
+
billingClass: group.billingClass,
|
|
253
|
+
label: presentation?.label ?? group.label,
|
|
254
|
+
rows: group.rows.filter((row) => matchingIds.has(row.id)),
|
|
255
|
+
description:
|
|
256
|
+
override === undefined
|
|
257
|
+
? group.billingClass === "opengeni_credits"
|
|
258
|
+
? null
|
|
259
|
+
: defaultHint
|
|
260
|
+
: override,
|
|
261
|
+
};
|
|
262
|
+
})
|
|
263
|
+
.filter((group) => group.rows.length > 0);
|
|
264
|
+
const anySelectable = rows.some((row) => row.selectable);
|
|
265
|
+
const unsupportedAttachments =
|
|
266
|
+
props.hasImageAttachments === true &&
|
|
267
|
+
selected?.catalog.capabilities?.inputModalities.includes("image") === false;
|
|
268
|
+
return {
|
|
269
|
+
messages,
|
|
270
|
+
rows,
|
|
271
|
+
selected,
|
|
272
|
+
anySelectable,
|
|
273
|
+
searching: words.length > 0,
|
|
274
|
+
groups,
|
|
275
|
+
matchCount: filtered.length,
|
|
276
|
+
thinking: selected ? modelPickerThinking(props, selected) : null,
|
|
277
|
+
unsupportedAttachments,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function modelPickerThinking(
|
|
282
|
+
props: Pick<ModelPickerInput, "effort" | "allowLatencyMode">,
|
|
283
|
+
selected: ClientPickerModelRow,
|
|
284
|
+
): ModelPickerThinking | null {
|
|
285
|
+
const efforts = effortOptionsForModel(selected.catalog);
|
|
286
|
+
const showThinking =
|
|
287
|
+
efforts.length > 1 && selected.catalog.capabilities?.reasoning.runnable !== false;
|
|
288
|
+
const showFast =
|
|
289
|
+
props.allowLatencyMode !== false &&
|
|
290
|
+
runnableLatencyModesForModel(selected.catalog).includes("fast");
|
|
291
|
+
if (!showThinking && !showFast) return null;
|
|
292
|
+
return {
|
|
293
|
+
efforts,
|
|
294
|
+
value: coerceReasoningEffortForModel(selected.catalog, props.effort),
|
|
295
|
+
showThinking,
|
|
296
|
+
showFast,
|
|
297
|
+
disabled: !selected.selectable,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export type ModelPickerChoice = {
|
|
302
|
+
/** The new model, or null when the row is already selected. */
|
|
303
|
+
model: string | null;
|
|
304
|
+
/** Always reported, so hosts can commit model and effort together. */
|
|
305
|
+
effort: ReasoningEffort;
|
|
306
|
+
/** Fast falls back to standard when the new model cannot run it. */
|
|
307
|
+
latencyMode: LatencyMode | null;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
/** What choosing a row changes, or null when the row cannot be chosen. */
|
|
311
|
+
export function modelPickerChoice(
|
|
312
|
+
props: Pick<ModelPickerInput, "model" | "effort" | "latencyMode">,
|
|
313
|
+
row: ClientPickerModelRow,
|
|
314
|
+
): ModelPickerChoice | null {
|
|
315
|
+
if (!row.selectable) return null;
|
|
316
|
+
return {
|
|
317
|
+
model: row.id === props.model ? null : row.id,
|
|
318
|
+
effort: coerceReasoningEffortForModel(row.catalog, props.effort),
|
|
319
|
+
latencyMode:
|
|
320
|
+
props.latencyMode !== "standard" &&
|
|
321
|
+
!runnableLatencyModesForModel(row.catalog).includes(props.latencyMode)
|
|
322
|
+
? "standard"
|
|
323
|
+
: null,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** The web segmented control's short labels ("X-high" fits a phone row). */
|
|
328
|
+
export function compactEffortLabel(effort: ReasoningEffort, label: string): string {
|
|
329
|
+
return effort === "xhigh" ? "X-high" : label;
|
|
330
|
+
}
|
package/src/model-policy.ts
CHANGED
|
@@ -55,7 +55,11 @@ const AVAILABILITY_REASON_LABELS: Record<string, string> = {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
export function billingClassForModel(model: ClientModel): PickerBillingClass {
|
|
58
|
-
if (
|
|
58
|
+
if (
|
|
59
|
+
model.provider === "organization-gateway" ||
|
|
60
|
+
model.provider === "organization-openrouter" ||
|
|
61
|
+
model.provider === "organization-opper"
|
|
62
|
+
) {
|
|
59
63
|
return "organization_byok";
|
|
60
64
|
}
|
|
61
65
|
if (
|
|
@@ -63,7 +67,11 @@ export function billingClassForModel(model: ClientModel): PickerBillingClass {
|
|
|
63
67
|
model.provider?.startsWith("workspace-azure-openai-")
|
|
64
68
|
)
|
|
65
69
|
return "byok";
|
|
66
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
model.provider === "workspace-gateway" ||
|
|
72
|
+
model.provider === "workspace-openrouter" ||
|
|
73
|
+
model.provider === "workspace-opper"
|
|
74
|
+
) {
|
|
67
75
|
return "byok";
|
|
68
76
|
}
|
|
69
77
|
return modelPickerBillingClassFor(model);
|
|
@@ -116,6 +124,30 @@ export function coerceReasoningEffortForModel(
|
|
|
116
124
|
return defaultEffortForModel(model);
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
/** Whether the composer offers (and therefore labels) a reasoning-effort choice. */
|
|
128
|
+
export function modelOffersEffortChoice(model: ClientModel): boolean {
|
|
129
|
+
return (
|
|
130
|
+
effortOptionsForModel(model).length > 1 && model.capabilities?.reasoning.runnable !== false
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The composer model pill at phone width: the catalog's compact name (falling
|
|
136
|
+
* back to the display name) and the effort label when effort is a choice.
|
|
137
|
+
*/
|
|
138
|
+
export function compactModelPill(
|
|
139
|
+
models: readonly ClientModel[],
|
|
140
|
+
modelId: string,
|
|
141
|
+
effort: ReasoningEffort | null | undefined,
|
|
142
|
+
): { name: string; effort: string | null } {
|
|
143
|
+
const row = findPickerRow(projectClientModelRows([...models]), modelId);
|
|
144
|
+
return {
|
|
145
|
+
name: row?.shortLabel ?? row?.label ?? modelDisplayName(modelId),
|
|
146
|
+
effort:
|
|
147
|
+
row && effort && modelOffersEffortChoice(row.catalog) ? labelReasoningEffort(effort) : null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
export function runnableLatencyModesForModel(model: ClientModel): LatencyModeId[] {
|
|
120
152
|
const modes = model.capabilities?.latencyModes ?? [];
|
|
121
153
|
return modes.filter((mode) => mode.runnable).map((mode) => mode.id);
|
|
@@ -212,6 +244,9 @@ export function advancedSourceSummary(model: ClientModel): string | null {
|
|
|
212
244
|
if (model.provider === "workspace-openrouter") {
|
|
213
245
|
return "Workspace OpenRouter connection";
|
|
214
246
|
}
|
|
247
|
+
if (model.provider === "workspace-opper") {
|
|
248
|
+
return "Workspace Opper connection";
|
|
249
|
+
}
|
|
215
250
|
if (model.provider === "workspace-gateway" || model.source === "workspace_gateway") {
|
|
216
251
|
return "Workspace Vercel AI Gateway";
|
|
217
252
|
}
|
|
@@ -233,6 +268,9 @@ function workspaceProviderPayerSummary(model: ClientModel): string {
|
|
|
233
268
|
if (model.provider === "workspace-openrouter") {
|
|
234
269
|
return "Billed to the workspace OpenRouter account";
|
|
235
270
|
}
|
|
271
|
+
if (model.provider === "workspace-opper") {
|
|
272
|
+
return "Billed to the workspace Opper account";
|
|
273
|
+
}
|
|
236
274
|
if (model.provider === "workspace-gateway" || model.source === "workspace_gateway") {
|
|
237
275
|
return "Billed to the workspace Vercel account";
|
|
238
276
|
}
|
|
@@ -247,6 +285,9 @@ function organizationProviderPayerSummary(model: ClientModel): string {
|
|
|
247
285
|
if (model.provider === "organization-openrouter") {
|
|
248
286
|
return "Billed to the organization OpenRouter account";
|
|
249
287
|
}
|
|
288
|
+
if (model.provider === "organization-opper") {
|
|
289
|
+
return "Billed to the organization Opper account";
|
|
290
|
+
}
|
|
250
291
|
if (model.provider === "organization-gateway") {
|
|
251
292
|
return "Billed to the organization Vercel account";
|
|
252
293
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Pure organization and workspace selection rules shared by the web rail's
|
|
2
|
+
// switcher and native renderers. The wire model has no account *name*, so a
|
|
3
|
+
// display label is derived from the access grant — preferring the account
|
|
4
|
+
// name, falling back to a short, stable id fragment. No DOM, no React.
|
|
5
|
+
import type { AccessContext, AccountGrant, Workspace } from "@opengeni/sdk";
|
|
6
|
+
|
|
7
|
+
export type OrgOption = {
|
|
8
|
+
accountId: string;
|
|
9
|
+
label: string;
|
|
10
|
+
/** Whether the subject has the owner/admin authority required by organization settings. */
|
|
11
|
+
canManage: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Organizations the subject administers (owner or admin), sorted by id. */
|
|
15
|
+
export function organizationAdministrationAccountIds(accessContext: AccessContext): string[] {
|
|
16
|
+
return accessContext.accountGrants
|
|
17
|
+
.filter(
|
|
18
|
+
(grant) =>
|
|
19
|
+
grant.subjectId === accessContext.subjectId &&
|
|
20
|
+
(grant.role === "owner" || grant.role === "admin"),
|
|
21
|
+
)
|
|
22
|
+
.map((grant) => grant.accountId)
|
|
23
|
+
.sort();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A short, stable id fragment for an account that has no human name. */
|
|
27
|
+
export function shortAccountId(accountId: string): string {
|
|
28
|
+
return accountId.length > 8 ? accountId.slice(0, 8) : accountId;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The organization's real name, or null when the API sent none. */
|
|
32
|
+
export function orgName(accountId: string, grants: AccountGrant[]): string | null {
|
|
33
|
+
const grant = grants.find((candidate) => candidate.accountId === accountId);
|
|
34
|
+
const label =
|
|
35
|
+
grant?.metadata && typeof grant.metadata.accountName === "string"
|
|
36
|
+
? grant.metadata.accountName
|
|
37
|
+
: undefined;
|
|
38
|
+
return label?.trim() || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Human label for an organization: the grant's subjectLabel, else a short id. */
|
|
42
|
+
export function orgLabel(accountId: string, grants: AccountGrant[]): string {
|
|
43
|
+
return orgName(accountId, grants) ?? `Org ${shortAccountId(accountId)}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The organizations the subject belongs to, in a stable order (default org
|
|
48
|
+
* first, then by name). Derived from account grants, unioned with the accounts
|
|
49
|
+
* that own the accessible workspaces so an org always shows even without an
|
|
50
|
+
* explicit grant.
|
|
51
|
+
*/
|
|
52
|
+
export function organizationsForSubject(
|
|
53
|
+
context: AccessContext,
|
|
54
|
+
workspaces: Workspace[],
|
|
55
|
+
): OrgOption[] {
|
|
56
|
+
const administeredIds = new Set(organizationAdministrationAccountIds(context));
|
|
57
|
+
const ids = new Set<string>();
|
|
58
|
+
for (const grant of context.accountGrants) {
|
|
59
|
+
ids.add(grant.accountId);
|
|
60
|
+
}
|
|
61
|
+
for (const workspace of workspaces) {
|
|
62
|
+
ids.add(workspace.accountId);
|
|
63
|
+
}
|
|
64
|
+
const options = [...ids].map((accountId) => ({
|
|
65
|
+
accountId,
|
|
66
|
+
label: orgLabel(accountId, context.accountGrants),
|
|
67
|
+
canManage: administeredIds.has(accountId),
|
|
68
|
+
}));
|
|
69
|
+
return options.sort((a, b) => {
|
|
70
|
+
if (a.accountId === context.defaultAccountId) {
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
if (b.accountId === context.defaultAccountId) {
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
return a.label.localeCompare(b.label) || a.accountId.localeCompare(b.accountId);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Workspaces that belong to a given organization, ordered by name. */
|
|
81
|
+
export function workspacesInOrg(workspaces: Workspace[], accountId: string): Workspace[] {
|
|
82
|
+
return workspaces
|
|
83
|
+
.filter((workspace) => workspace.accountId === accountId)
|
|
84
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Organization settings remain anchored to an accessible workspace in that organization. */
|
|
88
|
+
export function organizationSettingsWorkspaceId(
|
|
89
|
+
workspaces: Workspace[],
|
|
90
|
+
accountId: string,
|
|
91
|
+
activeWorkspaceId: string,
|
|
92
|
+
): string | null {
|
|
93
|
+
const candidates = workspacesInOrg(workspaces, accountId);
|
|
94
|
+
return (
|
|
95
|
+
candidates.find((workspace) => workspace.id === activeWorkspaceId)?.id ??
|
|
96
|
+
candidates[0]?.id ??
|
|
97
|
+
null
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Where switching to an organization lands: the workspace last used there
|
|
103
|
+
* while it is still open to the person, else its first shared workspace, else
|
|
104
|
+
* their Personal workspace there. Null when nothing in it is open to them.
|
|
105
|
+
*/
|
|
106
|
+
export function organizationLandingWorkspaceId(
|
|
107
|
+
workspaces: Workspace[],
|
|
108
|
+
accountId: string,
|
|
109
|
+
rememberedWorkspaceId?: string | null,
|
|
110
|
+
): string | null {
|
|
111
|
+
const candidates = workspacesInOrg(workspaces, accountId);
|
|
112
|
+
return (
|
|
113
|
+
candidates.find((workspace) => workspace.id === rememberedWorkspaceId)?.id ??
|
|
114
|
+
candidates.find((workspace) => workspace.kind !== "personal")?.id ??
|
|
115
|
+
candidates[0]?.id ??
|
|
116
|
+
null
|
|
117
|
+
);
|
|
118
|
+
}
|