@stigmer/react 3.6.0 → 3.8.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.
- package/billing/CreditLedgerTable.d.ts.map +1 -1
- package/billing/CreditLedgerTable.js +2 -7
- package/billing/CreditLedgerTable.js.map +1 -1
- package/channel/AgentChannelsPanel.d.ts.map +1 -1
- package/channel/AgentChannelsPanel.js +21 -2
- package/channel/AgentChannelsPanel.js.map +1 -1
- package/channel/ChannelConversationsDialog.d.ts +12 -4
- package/channel/ChannelConversationsDialog.d.ts.map +1 -1
- package/channel/ChannelConversationsDialog.js +18 -24
- package/channel/ChannelConversationsDialog.js.map +1 -1
- package/channel/providers.d.ts +23 -0
- package/channel/providers.d.ts.map +1 -1
- package/channel/providers.js +4 -0
- package/channel/providers.js.map +1 -1
- package/channel/useSaveAgentChannel.d.ts +5 -4
- package/channel/useSaveAgentChannel.d.ts.map +1 -1
- package/channel/useSaveAgentChannel.js +18 -4
- package/channel/useSaveAgentChannel.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -2
- package/composer/SessionComposer.js.map +1 -1
- package/conversation/ConversationAttentionBanner.d.ts +22 -0
- package/conversation/ConversationAttentionBanner.d.ts.map +1 -0
- package/conversation/ConversationAttentionBanner.js +25 -0
- package/conversation/ConversationAttentionBanner.js.map +1 -0
- package/conversation/ConversationComposer.d.ts +41 -0
- package/conversation/ConversationComposer.d.ts.map +1 -0
- package/conversation/ConversationComposer.js +57 -0
- package/conversation/ConversationComposer.js.map +1 -0
- package/conversation/ConversationControlBanner.d.ts +52 -0
- package/conversation/ConversationControlBanner.d.ts.map +1 -0
- package/conversation/ConversationControlBanner.js +59 -0
- package/conversation/ConversationControlBanner.js.map +1 -0
- package/conversation/ConversationListPane.d.ts +68 -0
- package/conversation/ConversationListPane.d.ts.map +1 -0
- package/conversation/ConversationListPane.js +120 -0
- package/conversation/ConversationListPane.js.map +1 -0
- package/conversation/ConversationTimelineView.d.ts +46 -0
- package/conversation/ConversationTimelineView.d.ts.map +1 -0
- package/conversation/ConversationTimelineView.js +166 -0
- package/conversation/ConversationTimelineView.js.map +1 -0
- package/conversation/ConversationsWorkbench.d.ts +63 -0
- package/conversation/ConversationsWorkbench.d.ts.map +1 -0
- package/conversation/ConversationsWorkbench.js +165 -0
- package/conversation/ConversationsWorkbench.js.map +1 -0
- package/conversation/conversationPresentation.d.ts +137 -0
- package/conversation/conversationPresentation.d.ts.map +1 -0
- package/conversation/conversationPresentation.js +230 -0
- package/conversation/conversationPresentation.js.map +1 -0
- package/conversation/index.d.ts +26 -0
- package/conversation/index.d.ts.map +1 -0
- package/conversation/index.js +14 -0
- package/conversation/index.js.map +1 -0
- package/conversation/polling.d.ts +39 -0
- package/conversation/polling.d.ts.map +1 -0
- package/conversation/polling.js +39 -0
- package/conversation/polling.js.map +1 -0
- package/conversation/useConversation.d.ts +61 -0
- package/conversation/useConversation.d.ts.map +1 -0
- package/conversation/useConversation.js +153 -0
- package/conversation/useConversation.js.map +1 -0
- package/conversation/useConversationList.d.ts +98 -0
- package/conversation/useConversationList.d.ts.map +1 -0
- package/conversation/useConversationList.js +200 -0
- package/conversation/useConversationList.js.map +1 -0
- package/conversation/useConversationParticipation.d.ts +63 -0
- package/conversation/useConversationParticipation.d.ts.map +1 -0
- package/conversation/useConversationParticipation.js +84 -0
- package/conversation/useConversationParticipation.js.map +1 -0
- package/conversation/useConversationTimeline.d.ts +71 -0
- package/conversation/useConversationTimeline.d.ts.map +1 -0
- package/conversation/useConversationTimeline.js +152 -0
- package/conversation/useConversationTimeline.js.map +1 -0
- package/conversation/useConversationsWantsHumanCount.d.ts +42 -0
- package/conversation/useConversationsWantsHumanCount.d.ts.map +1 -0
- package/conversation/useConversationsWantsHumanCount.js +40 -0
- package/conversation/useConversationsWantsHumanCount.js.map +1 -0
- package/execution/useCreateAgentExecution.d.ts +14 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +6 -2
- package/index.d.ts.map +1 -1
- package/index.js +14 -5
- package/index.js.map +1 -1
- package/internal/Pagination.d.ts +15 -0
- package/internal/Pagination.d.ts.map +1 -0
- package/internal/Pagination.js +20 -0
- package/internal/Pagination.js.map +1 -0
- package/internal/tooltip.d.ts.map +1 -1
- package/internal/tooltip.js +7 -1
- package/internal/tooltip.js.map +1 -1
- package/internal/useAutoScroll.d.ts +25 -4
- package/internal/useAutoScroll.d.ts.map +1 -1
- package/internal/useAutoScroll.js +55 -15
- package/internal/useAutoScroll.js.map +1 -1
- package/internal/useFetch.d.ts.map +1 -1
- package/internal/useFetch.js +14 -4
- package/internal/useFetch.js.map +1 -1
- package/models/ModelSelector.d.ts +45 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +49 -6
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +11 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +3 -0
- package/models/registry.js.map +1 -1
- package/models/service-tier.d.ts +27 -0
- package/models/service-tier.d.ts.map +1 -0
- package/models/service-tier.js +34 -0
- package/models/service-tier.js.map +1 -0
- package/package.json +4 -4
- package/schedule/ScheduleDetailView.d.ts +31 -8
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +441 -29
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts +7 -4
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +94 -8
- package/schedule/ScheduleForm.js.map +1 -1
- package/schedule/ScheduleRunsTable.d.ts +72 -0
- package/schedule/ScheduleRunsTable.d.ts.map +1 -0
- package/schedule/ScheduleRunsTable.js +161 -0
- package/schedule/ScheduleRunsTable.js.map +1 -0
- package/schedule/index.d.ts +6 -0
- package/schedule/index.d.ts.map +1 -1
- package/schedule/index.js +3 -0
- package/schedule/index.js.map +1 -1
- package/schedule/useScheduleRuns.d.ts +44 -0
- package/schedule/useScheduleRuns.d.ts.map +1 -0
- package/schedule/useScheduleRuns.js +48 -0
- package/schedule/useScheduleRuns.js.map +1 -0
- package/schedule/useTriggerSchedule.d.ts +23 -10
- package/schedule/useTriggerSchedule.d.ts.map +1 -1
- package/schedule/useTriggerSchedule.js +28 -9
- package/schedule/useTriggerSchedule.js.map +1 -1
- package/schedule/useUpdateScheduleSpec.d.ts +49 -0
- package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
- package/schedule/useUpdateScheduleSpec.js +70 -0
- package/schedule/useUpdateScheduleSpec.js.map +1 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +1 -0
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +11 -0
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +1 -0
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/sharing/useSaveAgentShare.d.ts +10 -1
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +9 -0
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/sidebar/WorkspaceSidebar.d.ts +12 -4
- package/sidebar/WorkspaceSidebar.d.ts.map +1 -1
- package/sidebar/WorkspaceSidebar.js +19 -7
- package/sidebar/WorkspaceSidebar.js.map +1 -1
- package/src/billing/CreditLedgerTable.tsx +3 -42
- package/src/channel/AgentChannelsPanel.tsx +33 -2
- package/src/channel/ChannelConversationsDialog.tsx +22 -25
- package/src/channel/__tests__/AgentChannelsPanel.test.tsx +36 -5
- package/src/channel/__tests__/ChannelConversationsDialog.test.tsx +38 -1
- package/src/channel/__tests__/useSaveAgentChannel.test.tsx +26 -0
- package/src/channel/providers.ts +27 -0
- package/src/channel/useSaveAgentChannel.ts +18 -4
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +24 -1
- package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +157 -0
- package/src/conversation/ConversationAttentionBanner.tsx +77 -0
- package/src/conversation/ConversationComposer.tsx +156 -0
- package/src/conversation/ConversationControlBanner.tsx +193 -0
- package/src/conversation/ConversationListPane.tsx +459 -0
- package/src/conversation/ConversationTimelineView.tsx +435 -0
- package/src/conversation/ConversationsWorkbench.tsx +378 -0
- package/src/conversation/__tests__/ConversationAttentionBanner.test.tsx +82 -0
- package/src/conversation/__tests__/ConversationComposer.test.tsx +160 -0
- package/src/conversation/__tests__/ConversationControlBanner.test.tsx +190 -0
- package/src/conversation/__tests__/ConversationListPane.test.tsx +372 -0
- package/src/conversation/__tests__/ConversationTimelineView.layout.test.tsx +160 -0
- package/src/conversation/__tests__/ConversationTimelineView.test.tsx +405 -0
- package/src/conversation/__tests__/ConversationsWorkbench.test.tsx +860 -0
- package/src/conversation/__tests__/a11y/conversation-surfaces.a11y.test.tsx +235 -0
- package/src/conversation/__tests__/a11y/harness.tsx +100 -0
- package/src/conversation/__tests__/conversation-tooltips.layout.test.tsx +167 -0
- package/src/conversation/__tests__/conversationPollingComposition.test.tsx +138 -0
- package/src/conversation/__tests__/conversationPresentation.test.ts +289 -0
- package/src/conversation/__tests__/useConversation.test.tsx +243 -0
- package/src/conversation/__tests__/useConversationList.test.tsx +492 -0
- package/src/conversation/__tests__/useConversationParticipation.test.tsx +190 -0
- package/src/conversation/__tests__/useConversationTimeline.test.tsx +258 -0
- package/src/conversation/__tests__/useConversationsWantsHumanCount.test.tsx +102 -0
- package/src/conversation/conversationPresentation.ts +305 -0
- package/src/conversation/index.ts +80 -0
- package/src/conversation/polling.ts +41 -0
- package/src/conversation/useConversation.ts +221 -0
- package/src/conversation/useConversationList.ts +328 -0
- package/src/conversation/useConversationParticipation.ts +178 -0
- package/src/conversation/useConversationTimeline.ts +244 -0
- package/src/conversation/useConversationsWantsHumanCount.ts +86 -0
- package/src/execution/useCreateAgentExecution.ts +18 -0
- package/src/index.ts +76 -4
- package/src/internal/Pagination.tsx +72 -0
- package/src/internal/__tests__/fake-timer-slices.ts +29 -0
- package/src/internal/__tests__/useAutoScroll.layout.test.tsx +143 -0
- package/src/internal/__tests__/useAutoScroll.test.tsx +62 -5
- package/src/internal/__tests__/useFetch-poll-under-render-pressure.test.tsx +79 -0
- package/src/internal/tooltip.tsx +6 -1
- package/src/internal/useAutoScroll.ts +66 -20
- package/src/internal/useFetch.ts +15 -4
- package/src/models/ModelSelector.tsx +133 -4
- package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +256 -0
- package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +17 -0
- package/src/models/service-tier.ts +49 -0
- package/src/schedule/ScheduleDetailView.tsx +1179 -111
- package/src/schedule/ScheduleForm.tsx +218 -7
- package/src/schedule/ScheduleRunsTable.tsx +424 -0
- package/src/schedule/__tests__/ScheduleDetailView.test.tsx +431 -8
- package/src/schedule/__tests__/scheduleCreation.test.tsx +197 -0
- package/src/schedule/__tests__/scheduleHooks.test.tsx +94 -7
- package/src/schedule/index.ts +12 -0
- package/src/schedule/useScheduleRuns.ts +93 -0
- package/src/schedule/useTriggerSchedule.ts +39 -14
- package/src/schedule/useUpdateScheduleSpec.ts +108 -0
- package/src/session/useNewSessionFlow.ts +1 -0
- package/src/session/useSessionConversation.ts +12 -0
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +55 -1
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +1 -0
- package/src/sharing/useSaveAgentShare.ts +19 -1
- package/src/sidebar/WorkspaceSidebar.tsx +62 -3
- package/src/sidebar/__tests__/WorkspaceSidebar.test.tsx +56 -1
- package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
- package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
- package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
- package/src/workflow/starter-workflow-yaml.ts +2 -1
- package/styles.css +1 -1
- package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
- package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
- package/workflow/inspector/forms/AgentCallForm.js +87 -9
- package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.js +31 -5
- package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
- package/workflow/starter-workflow-yaml.d.ts +1 -1
- package/workflow/starter-workflow-yaml.d.ts.map +1 -1
- package/workflow/starter-workflow-yaml.js +2 -1
- package/workflow/starter-workflow-yaml.js.map +1 -1
|
@@ -4,9 +4,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
4
4
|
import { Popover } from "@base-ui/react/popover";
|
|
5
5
|
import { cn } from "@stigmer/theme";
|
|
6
6
|
import { useStigmerPortalContainer } from "../portal-container.js";
|
|
7
|
+
import { Switch } from "../switch/Switch.js";
|
|
7
8
|
import { useModelRegistry } from "./useModelRegistry.js";
|
|
8
9
|
import type { ModelInfo, CostTier, SpeedTier } from "./registry.js";
|
|
9
10
|
import { HARNESS_META, HARNESS_OPTIONS, type HarnessOption } from "./harness.js";
|
|
11
|
+
import { FAST_SERVICE_TIER, type ServiceTierOption } from "./service-tier.js";
|
|
10
12
|
|
|
11
13
|
const COST_TIER_LABEL: Record<CostTier, string> = {
|
|
12
14
|
economy: "$",
|
|
@@ -61,6 +63,37 @@ export interface ModelSelectorProps {
|
|
|
61
63
|
readonly className?: string;
|
|
62
64
|
/** When true, disables the selector. */
|
|
63
65
|
readonly disabled?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Trigger label shown while `value` is empty, instead of falling back
|
|
68
|
+
* to the registry's default model. Lets a form distinguish "nothing
|
|
69
|
+
* pinned — the platform default applies" from an actual selection
|
|
70
|
+
* (the schedule form's contract: an unset model inherits the
|
|
71
|
+
* surface's platform default). The harness prefix is hidden while
|
|
72
|
+
* the placeholder shows, since no engine is pinned either.
|
|
73
|
+
*/
|
|
74
|
+
readonly placeholderLabel?: string;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Current service tier for the selected model (stigmer/stigmer#357).
|
|
78
|
+
* Defaults to "standard". Only meaningful together with
|
|
79
|
+
* {@link onServiceTierChange}.
|
|
80
|
+
*/
|
|
81
|
+
readonly serviceTier?: ServiceTierOption;
|
|
82
|
+
/**
|
|
83
|
+
* Called when the user toggles the fast tier. Providing this enables the
|
|
84
|
+
* "Fast tier" switch in the popover's options area (top of the popover,
|
|
85
|
+
* beside the harness row — the Cursor options-panel convention), which
|
|
86
|
+
* renders ONLY while the selected model prices a fast variant
|
|
87
|
+
* ({@link ModelInfo.serviceTiers}) — no dead controls.
|
|
88
|
+
*
|
|
89
|
+
* An active fast tier persists across switches between fast-capable
|
|
90
|
+
* models (the tier is always visible: trigger badge + options switch),
|
|
91
|
+
* so "fast, but on that other model" is one action, not a re-toggle.
|
|
92
|
+
* Selecting a model without a fast tier resets to "standard" through
|
|
93
|
+
* this callback — an unsupported combination can never be submitted
|
|
94
|
+
* (#357, refused at create time).
|
|
95
|
+
*/
|
|
96
|
+
readonly onServiceTierChange?: (tier: ServiceTierOption) => void;
|
|
64
97
|
|
|
65
98
|
/**
|
|
66
99
|
* @deprecated Use {@link onHarnessChange} instead.
|
|
@@ -79,6 +112,19 @@ export interface ModelSelectorProps {
|
|
|
79
112
|
* The trigger button displays the current selection in compact format:
|
|
80
113
|
* `Harness · Model Name ▾` (or just `Model Name ▾` when harness is locked).
|
|
81
114
|
*
|
|
115
|
+
* **Service tier (#357).** When the consumer opts in via
|
|
116
|
+
* {@link ModelSelectorProps.onServiceTierChange} and the selected model
|
|
117
|
+
* prices a fast variant, a "Fast tier" switch renders in the options area
|
|
118
|
+
* at the TOP of the popover — the first thing visible on open, mirroring
|
|
119
|
+
* Cursor's own options panel (Jakob's Law: our users already know that
|
|
120
|
+
* layout). Deliberately not a per-row affordance: row-level chips crowd
|
|
121
|
+
* the rows and collide with the "Fast/Fastest" speed badge, and an
|
|
122
|
+
* interactive element inside `role="option"` is an ARIA authoring error
|
|
123
|
+
* (option children are presentational). An active fast tier persists
|
|
124
|
+
* across switches between fast-capable models — the visible badge and
|
|
125
|
+
* switch keep the state honest — and resets only for models with no fast
|
|
126
|
+
* variant, where it would be refused at create time.
|
|
127
|
+
*
|
|
82
128
|
* @example
|
|
83
129
|
* ```tsx
|
|
84
130
|
* <ModelSelector
|
|
@@ -104,6 +150,9 @@ export function ModelSelector({
|
|
|
104
150
|
compact = false,
|
|
105
151
|
className,
|
|
106
152
|
disabled,
|
|
153
|
+
placeholderLabel,
|
|
154
|
+
serviceTier = "standard",
|
|
155
|
+
onServiceTierChange,
|
|
107
156
|
}: ModelSelectorProps) {
|
|
108
157
|
const portalContainer = useStigmerPortalContainer();
|
|
109
158
|
|
|
@@ -216,9 +265,21 @@ export function ModelSelector({
|
|
|
216
265
|
const selectModel = useCallback(
|
|
217
266
|
(model: ModelInfo) => {
|
|
218
267
|
onValueChange(model.modelId);
|
|
268
|
+
// An active fast tier survives a switch between fast-capable models —
|
|
269
|
+
// resetting it forced users to re-toggle on every model change, and
|
|
270
|
+
// the tier is never silent (trigger badge + options switch both show
|
|
271
|
+
// it). It resets ONLY when the new model prices no fast variant: a
|
|
272
|
+
// stale tier there would be refused at create time (#357).
|
|
273
|
+
if (
|
|
274
|
+
serviceTier === "fast"
|
|
275
|
+
&& onServiceTierChange
|
|
276
|
+
&& !model.serviceTiers.includes(FAST_SERVICE_TIER)
|
|
277
|
+
) {
|
|
278
|
+
onServiceTierChange("standard");
|
|
279
|
+
}
|
|
219
280
|
setOpen(false);
|
|
220
281
|
},
|
|
221
|
-
[onValueChange],
|
|
282
|
+
[onValueChange, serviceTier, onServiceTierChange],
|
|
222
283
|
);
|
|
223
284
|
|
|
224
285
|
const scrollHighlightIntoView = useCallback((idx: number) => {
|
|
@@ -229,7 +290,7 @@ export function ModelSelector({
|
|
|
229
290
|
}, []);
|
|
230
291
|
|
|
231
292
|
const handleKeyDown = useCallback(
|
|
232
|
-
(e: React.KeyboardEvent) => {
|
|
293
|
+
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
233
294
|
const len = visibleModels.length;
|
|
234
295
|
if (len === 0) return;
|
|
235
296
|
|
|
@@ -265,8 +326,27 @@ export function ModelSelector({
|
|
|
265
326
|
|
|
266
327
|
const showShowAllButton = !isSearching && !showAll && featuredModels.length > 0 && featuredModels.length < models.length;
|
|
267
328
|
|
|
268
|
-
|
|
269
|
-
|
|
329
|
+
// The options-area switch exists only where it applies: a consumer opted
|
|
330
|
+
// in AND the selected model prices a fast variant (recognition over
|
|
331
|
+
// recall — never a dead control on models with no tier choice).
|
|
332
|
+
const showServiceTierToggle =
|
|
333
|
+
onServiceTierChange !== undefined
|
|
334
|
+
&& selectedModel !== undefined
|
|
335
|
+
&& selectedModel.serviceTiers.includes(FAST_SERVICE_TIER);
|
|
336
|
+
// The trigger badge is display-only: fast is in effect iff the tier is
|
|
337
|
+
// fast AND the selected model actually prices the variant.
|
|
338
|
+
const fastActive =
|
|
339
|
+
serviceTier === "fast"
|
|
340
|
+
&& (selectedModel?.serviceTiers.includes(FAST_SERVICE_TIER) ?? false);
|
|
341
|
+
|
|
342
|
+
const usingPlaceholder = !value && placeholderLabel !== undefined;
|
|
343
|
+
const triggerLabel = usingPlaceholder
|
|
344
|
+
? placeholderLabel
|
|
345
|
+
: (selectedModel?.displayName ?? "Select model");
|
|
346
|
+
const triggerHarness =
|
|
347
|
+
!isHarnessLocked && !usingPlaceholder
|
|
348
|
+
? HARNESS_META[activeHarness].label
|
|
349
|
+
: undefined;
|
|
270
350
|
|
|
271
351
|
return (
|
|
272
352
|
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
@@ -288,6 +368,17 @@ export function ModelSelector({
|
|
|
288
368
|
<span className="shrink-0 text-border" aria-hidden>·</span>
|
|
289
369
|
)}
|
|
290
370
|
<span className="truncate">{triggerLabel}</span>
|
|
371
|
+
{fastActive && (
|
|
372
|
+
<span
|
|
373
|
+
className={cn(
|
|
374
|
+
"inline-flex shrink-0 items-center gap-0.5 rounded-full border border-primary px-1.5 py-px",
|
|
375
|
+
"text-[0.6rem] font-medium uppercase tracking-wider text-primary",
|
|
376
|
+
)}
|
|
377
|
+
>
|
|
378
|
+
<BoltIcon />
|
|
379
|
+
Fast
|
|
380
|
+
</span>
|
|
381
|
+
)}
|
|
291
382
|
<ChevronIcon />
|
|
292
383
|
</Popover.Trigger>
|
|
293
384
|
|
|
@@ -369,6 +460,29 @@ export function ModelSelector({
|
|
|
369
460
|
)}
|
|
370
461
|
</div>
|
|
371
462
|
|
|
463
|
+
{/* Options area — the Cursor options-panel convention: settings
|
|
464
|
+
of the CURRENT selection sit at the top of the popover, the
|
|
465
|
+
first thing visible on open, never buried under the list.
|
|
466
|
+
Rendered only while the selected model prices a fast
|
|
467
|
+
variant (#357). */}
|
|
468
|
+
{showServiceTierToggle && (
|
|
469
|
+
<div className="flex items-center justify-between border-b border-border px-3 py-2">
|
|
470
|
+
<div className="flex flex-col">
|
|
471
|
+
<span className="text-xs text-foreground">Fast tier</span>
|
|
472
|
+
<span className="text-[0.65rem] text-muted-foreground">
|
|
473
|
+
Faster responses at higher per-token rates
|
|
474
|
+
</span>
|
|
475
|
+
</div>
|
|
476
|
+
<Switch
|
|
477
|
+
checked={fastActive}
|
|
478
|
+
onCheckedChange={(next) =>
|
|
479
|
+
onServiceTierChange?.(next ? "fast" : "standard")
|
|
480
|
+
}
|
|
481
|
+
aria-label="Fast tier"
|
|
482
|
+
/>
|
|
483
|
+
</div>
|
|
484
|
+
)}
|
|
485
|
+
|
|
372
486
|
{/* Search input */}
|
|
373
487
|
<div className="border-b border-border px-3 py-1.5">
|
|
374
488
|
<input
|
|
@@ -568,3 +682,18 @@ function CheckIcon({ className }: { className?: string }) {
|
|
|
568
682
|
</svg>
|
|
569
683
|
);
|
|
570
684
|
}
|
|
685
|
+
|
|
686
|
+
function BoltIcon() {
|
|
687
|
+
return (
|
|
688
|
+
<svg
|
|
689
|
+
className="shrink-0"
|
|
690
|
+
width="8"
|
|
691
|
+
height="10"
|
|
692
|
+
viewBox="0 0 8 10"
|
|
693
|
+
fill="currentColor"
|
|
694
|
+
aria-hidden
|
|
695
|
+
>
|
|
696
|
+
<path d="M4.9 0.2L0.6 5.6h2.6L3.1 9.8l4.3-5.4H4.8L4.9 0.2z" />
|
|
697
|
+
</svg>
|
|
698
|
+
);
|
|
699
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach, beforeAll } from "vitest";
|
|
2
|
+
import { render, screen, cleanup, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { ModelSelector } from "../ModelSelector";
|
|
4
|
+
import { ModelRegistryContext } from "../ModelRegistryContext";
|
|
5
|
+
import type { ModelInfo } from "../registry";
|
|
6
|
+
|
|
7
|
+
// Portal into document.body so the popover mounts under happy-dom, and shim
|
|
8
|
+
// ResizeObserver for Base UI's positioner (the scheduleCreation.test.tsx
|
|
9
|
+
// pattern).
|
|
10
|
+
vi.mock("../../portal-container", () => ({
|
|
11
|
+
useStigmerPortalContainer: () => document.body,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
if (!("ResizeObserver" in globalThis)) {
|
|
16
|
+
(globalThis as unknown as { ResizeObserver: unknown }).ResizeObserver =
|
|
17
|
+
class {
|
|
18
|
+
observe() {}
|
|
19
|
+
unobserve() {}
|
|
20
|
+
disconnect() {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fast-tier switch in the popover's options area (stigmer/stigmer#357).
|
|
27
|
+
*
|
|
28
|
+
* The switch sits at the TOP of the popover (the Cursor options-panel
|
|
29
|
+
* convention) and is capability-gated: it renders only when the consumer
|
|
30
|
+
* opted in (onServiceTierChange) AND the selected model prices a fast
|
|
31
|
+
* variant (ModelInfo.serviceTiers) — no dead controls. An active fast
|
|
32
|
+
* tier persists across switches between fast-capable models (the trigger
|
|
33
|
+
* badge and the switch keep the state visible), and resets ONLY when the
|
|
34
|
+
* chosen model prices no fast variant — a stale tier there would be
|
|
35
|
+
* refused at create time.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const FAST_CAPABLE: ModelInfo = {
|
|
39
|
+
modelId: "composer-2.5",
|
|
40
|
+
provider: "cursor",
|
|
41
|
+
displayName: "Composer 2.5",
|
|
42
|
+
shortDescription: "Fast agentic model",
|
|
43
|
+
speedTier: "fastest",
|
|
44
|
+
costTier: "economy",
|
|
45
|
+
harness: "cursor",
|
|
46
|
+
featured: true,
|
|
47
|
+
serviceTiers: ["fast"],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const ALSO_FAST_CAPABLE: ModelInfo = {
|
|
51
|
+
modelId: "claude-opus-4-8",
|
|
52
|
+
provider: "anthropic",
|
|
53
|
+
displayName: "Claude 4.8 Opus",
|
|
54
|
+
shortDescription: "Frontier Opus via Cursor",
|
|
55
|
+
speedTier: "slow",
|
|
56
|
+
costTier: "premium",
|
|
57
|
+
harness: "cursor",
|
|
58
|
+
featured: true,
|
|
59
|
+
serviceTiers: ["fast"],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const NO_FAST_TIER: ModelInfo = {
|
|
63
|
+
modelId: "claude-haiku-4-5",
|
|
64
|
+
provider: "anthropic",
|
|
65
|
+
displayName: "Haiku 4.5",
|
|
66
|
+
shortDescription: "Small fast model",
|
|
67
|
+
speedTier: "fastest",
|
|
68
|
+
costTier: "economy",
|
|
69
|
+
harness: "cursor",
|
|
70
|
+
featured: true,
|
|
71
|
+
serviceTiers: [],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function renderSelector(
|
|
75
|
+
props?: Partial<React.ComponentProps<typeof ModelSelector>>,
|
|
76
|
+
) {
|
|
77
|
+
const onValueChange = vi.fn();
|
|
78
|
+
const onServiceTierChange = vi.fn();
|
|
79
|
+
|
|
80
|
+
const result = render(
|
|
81
|
+
<ModelRegistryContext.Provider
|
|
82
|
+
value={{
|
|
83
|
+
models: [FAST_CAPABLE, ALSO_FAST_CAPABLE, NO_FAST_TIER],
|
|
84
|
+
isLoading: false,
|
|
85
|
+
error: null,
|
|
86
|
+
refetch: vi.fn(),
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
<ModelSelector
|
|
90
|
+
value="composer-2.5"
|
|
91
|
+
onValueChange={onValueChange}
|
|
92
|
+
harness="cursor"
|
|
93
|
+
serviceTier="standard"
|
|
94
|
+
onServiceTierChange={onServiceTierChange}
|
|
95
|
+
{...props}
|
|
96
|
+
/>
|
|
97
|
+
</ModelRegistryContext.Provider>,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return { ...result, onValueChange, onServiceTierChange };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Open the popover via its trigger and wait for the portaled popup to
|
|
105
|
+
* mount (Base UI portals render asynchronously in jsdom). The search
|
|
106
|
+
* input is the popup's always-present landmark.
|
|
107
|
+
*/
|
|
108
|
+
async function openPopover(triggerLabel: string) {
|
|
109
|
+
fireEvent.click(screen.getByRole("button", { name: new RegExp(triggerLabel) }));
|
|
110
|
+
await screen.findByPlaceholderText("Search models…");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A model row inside the popup list (never matches the trigger). */
|
|
114
|
+
function modelOption(displayName: string): HTMLElement {
|
|
115
|
+
const option = screen
|
|
116
|
+
.getAllByText(displayName)
|
|
117
|
+
.map((el) => el.closest<HTMLElement>("[data-model-option]"))
|
|
118
|
+
.find((el) => el != null);
|
|
119
|
+
if (!option) throw new Error(`No popup option for "${displayName}"`);
|
|
120
|
+
return option;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
afterEach(cleanup);
|
|
124
|
+
|
|
125
|
+
describe("ModelSelector — fast tier switch in the options area", () => {
|
|
126
|
+
it("renders the switch for a fast-capable selected model", async () => {
|
|
127
|
+
renderSelector();
|
|
128
|
+
await openPopover("Composer 2.5");
|
|
129
|
+
|
|
130
|
+
const toggle = screen.getByRole("switch", { name: "Fast tier" });
|
|
131
|
+
expect(toggle.getAttribute("aria-checked")).toBe("false");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("places the switch in the options area above the model list", async () => {
|
|
135
|
+
renderSelector();
|
|
136
|
+
await openPopover("Composer 2.5");
|
|
137
|
+
|
|
138
|
+
const toggle = screen.getByRole("switch", { name: "Fast tier" });
|
|
139
|
+
const search = screen.getByRole("searchbox", { name: "Search models" });
|
|
140
|
+
// DOCUMENT_POSITION_FOLLOWING: the search input comes after the switch,
|
|
141
|
+
// i.e. the options area precedes search + list (never a footer).
|
|
142
|
+
expect(
|
|
143
|
+
toggle.compareDocumentPosition(search)
|
|
144
|
+
& Node.DOCUMENT_POSITION_FOLLOWING,
|
|
145
|
+
).toBeTruthy();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("does not render the switch for a model without a fast tier", async () => {
|
|
149
|
+
renderSelector({ value: "claude-haiku-4-5" });
|
|
150
|
+
await openPopover("Haiku 4.5");
|
|
151
|
+
|
|
152
|
+
expect(screen.queryByRole("switch", { name: "Fast tier" })).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("does not render the switch when the consumer did not opt in", async () => {
|
|
156
|
+
renderSelector({ onServiceTierChange: undefined });
|
|
157
|
+
await openPopover("Composer 2.5");
|
|
158
|
+
|
|
159
|
+
expect(screen.queryByRole("switch", { name: "Fast tier" })).toBeNull();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("toggling fires onServiceTierChange with the flipped tier", async () => {
|
|
163
|
+
const { onServiceTierChange } = renderSelector();
|
|
164
|
+
await openPopover("Composer 2.5");
|
|
165
|
+
|
|
166
|
+
fireEvent.click(screen.getByRole("switch", { name: "Fast tier" }));
|
|
167
|
+
expect(onServiceTierChange).toHaveBeenCalledOnce();
|
|
168
|
+
expect(onServiceTierChange).toHaveBeenCalledWith("fast");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("toggling off an active fast tier fires 'standard'", async () => {
|
|
172
|
+
const { onServiceTierChange } = renderSelector({ serviceTier: "fast" });
|
|
173
|
+
await openPopover("Composer 2.5");
|
|
174
|
+
|
|
175
|
+
fireEvent.click(screen.getByRole("switch", { name: "Fast tier" }));
|
|
176
|
+
expect(onServiceTierChange).toHaveBeenCalledOnce();
|
|
177
|
+
expect(onServiceTierChange).toHaveBeenCalledWith("standard");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("keeps the popover open across a toggle", async () => {
|
|
181
|
+
renderSelector();
|
|
182
|
+
await openPopover("Composer 2.5");
|
|
183
|
+
|
|
184
|
+
fireEvent.click(screen.getByRole("switch", { name: "Fast tier" }));
|
|
185
|
+
expect(screen.getByPlaceholderText("Search models…")).toBeTruthy();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("ModelSelector — trigger badge", () => {
|
|
190
|
+
it("shows the Fast badge on the trigger when the fast tier is active", () => {
|
|
191
|
+
renderSelector({ serviceTier: "fast" });
|
|
192
|
+
|
|
193
|
+
expect(screen.getByText("Fast")).toBeTruthy();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("hides the badge when the selected model has no fast tier", () => {
|
|
197
|
+
renderSelector({ serviceTier: "fast", value: "claude-haiku-4-5" });
|
|
198
|
+
|
|
199
|
+
expect(screen.queryByText("Fast")).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("ModelSelector — fast tier persistence across model switches", () => {
|
|
204
|
+
it("selecting a model while standard emits no tier change", async () => {
|
|
205
|
+
const { onValueChange, onServiceTierChange } = renderSelector();
|
|
206
|
+
await openPopover("Composer 2.5");
|
|
207
|
+
|
|
208
|
+
fireEvent.click(modelOption("Composer 2.5"));
|
|
209
|
+
|
|
210
|
+
expect(onValueChange).toHaveBeenCalledWith("composer-2.5");
|
|
211
|
+
expect(onServiceTierChange).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("keeps an active fast tier when selecting another fast-capable model", async () => {
|
|
215
|
+
// "Fast, but on that other model" is one action — resetting here made
|
|
216
|
+
// users re-toggle on every model change, and the tier is never silent
|
|
217
|
+
// (trigger badge + options switch both show it).
|
|
218
|
+
const { onValueChange, onServiceTierChange } = renderSelector({
|
|
219
|
+
serviceTier: "fast",
|
|
220
|
+
});
|
|
221
|
+
await openPopover("Composer 2.5");
|
|
222
|
+
|
|
223
|
+
fireEvent.click(modelOption("Claude 4.8 Opus"));
|
|
224
|
+
|
|
225
|
+
expect(onValueChange).toHaveBeenCalledWith("claude-opus-4-8");
|
|
226
|
+
expect(onServiceTierChange).not.toHaveBeenCalled();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("toggle-then-pick works in a single popover visit", async () => {
|
|
230
|
+
// Flip the switch first (popover stays open), then pick a fast-capable
|
|
231
|
+
// model: the toggle survives the selection.
|
|
232
|
+
const { onValueChange, onServiceTierChange } = renderSelector();
|
|
233
|
+
await openPopover("Composer 2.5");
|
|
234
|
+
|
|
235
|
+
fireEvent.click(screen.getByRole("switch", { name: "Fast tier" }));
|
|
236
|
+
fireEvent.click(modelOption("Claude 4.8 Opus"));
|
|
237
|
+
|
|
238
|
+
expect(onServiceTierChange).toHaveBeenCalledOnce();
|
|
239
|
+
expect(onServiceTierChange).toHaveBeenCalledWith("fast");
|
|
240
|
+
expect(onValueChange).toHaveBeenCalledWith("claude-opus-4-8");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("resets an active fast tier when selecting a model without one", async () => {
|
|
244
|
+
const { onValueChange, onServiceTierChange } = renderSelector({
|
|
245
|
+
serviceTier: "fast",
|
|
246
|
+
});
|
|
247
|
+
await openPopover("Composer 2.5");
|
|
248
|
+
|
|
249
|
+
fireEvent.click(modelOption("Haiku 4.5"));
|
|
250
|
+
|
|
251
|
+
expect(onValueChange).toHaveBeenCalledOnce();
|
|
252
|
+
expect(onValueChange).toHaveBeenCalledWith("claude-haiku-4-5");
|
|
253
|
+
expect(onServiceTierChange).toHaveBeenCalledOnce();
|
|
254
|
+
expect(onServiceTierChange).toHaveBeenCalledWith("standard");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -73,6 +73,9 @@ const TEST_REGISTRY_JSON = {
|
|
|
73
73
|
costTier: "standard",
|
|
74
74
|
featured: false,
|
|
75
75
|
pricing: { inputPricePerMillion: 3, outputPricePerMillion: 15, cacheWritePricePerMillion: 3.75, cacheReadPricePerMillion: 0.3 },
|
|
76
|
+
pricingVariants: {
|
|
77
|
+
fast: { inputPricePerMillion: 18, outputPricePerMillion: 90 },
|
|
78
|
+
},
|
|
76
79
|
},
|
|
77
80
|
{
|
|
78
81
|
id: "ollama-local",
|
|
@@ -103,6 +106,18 @@ function createWrapper(models: readonly ModelInfo[] = TEST_MODELS) {
|
|
|
103
106
|
};
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
describe("parseRegistryJson service tiers (#357)", () => {
|
|
110
|
+
it("surfaces pricingVariants keys as serviceTiers", () => {
|
|
111
|
+
const withVariants = TEST_MODELS.find((m) => m.modelId === "claude-4.6-sonnet");
|
|
112
|
+
expect(withVariants?.serviceTiers).toEqual(["fast"]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("yields an empty serviceTiers list for models without variants", () => {
|
|
116
|
+
const withoutVariants = TEST_MODELS.find((m) => m.modelId === "claude-sonnet-4.6");
|
|
117
|
+
expect(withoutVariants?.serviceTiers).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
106
121
|
describe("useModelRegistry", () => {
|
|
107
122
|
describe("unified mode (no harness)", () => {
|
|
108
123
|
it("excludes DISABLED_PROVIDERS from models", () => {
|
package/src/models/index.ts
CHANGED
|
@@ -11,3 +11,5 @@ export { HarnessSelector } from "./HarnessSelector.js";
|
|
|
11
11
|
export type { HarnessSelectorProps } from "./HarnessSelector.js";
|
|
12
12
|
export { DEFAULT_HARNESS, HARNESS_LABELS, HARNESS_META, HARNESS_OPTIONS, toProtoHarness, fromProtoHarness } from "./harness.js";
|
|
13
13
|
export type { HarnessOption, HarnessDisplayInfo } from "./harness.js";
|
|
14
|
+
export { FAST_SERVICE_TIER, toProtoServiceTier, fromProtoServiceTier } from "./service-tier.js";
|
|
15
|
+
export type { ServiceTierOption } from "./service-tier.js";
|
package/src/models/registry.ts
CHANGED
|
@@ -89,6 +89,17 @@ export interface ModelInfo {
|
|
|
89
89
|
* before "Show All Models" is expanded or search is used).
|
|
90
90
|
*/
|
|
91
91
|
readonly featured: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Pricing-variant keys the registry prices for this model (e.g.
|
|
94
|
+
* `["fast"]`). A priced variant is a *selectable* service tier
|
|
95
|
+
* (stigmer/stigmer#357): the fast-tier switch in the model popover's
|
|
96
|
+
* options area renders only while the selected model's `serviceTiers`
|
|
97
|
+
* includes it. Empty for models with no variants.
|
|
98
|
+
*
|
|
99
|
+
* Distinct from {@link speedTier}, which is a static latency badge,
|
|
100
|
+
* not a selectable option.
|
|
101
|
+
*/
|
|
102
|
+
readonly serviceTiers: readonly string[];
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
/**
|
|
@@ -153,6 +164,8 @@ interface RegistryJsonEntry {
|
|
|
153
164
|
cacheWritePricePerMillion: number;
|
|
154
165
|
cacheReadPricePerMillion: number;
|
|
155
166
|
};
|
|
167
|
+
/** Variant-key → variant pricing block; only the key set matters here. */
|
|
168
|
+
pricingVariants?: Record<string, unknown>;
|
|
156
169
|
$comment?: string;
|
|
157
170
|
}
|
|
158
171
|
|
|
@@ -192,6 +205,10 @@ export function parseRegistryJson(data: unknown): ModelInfo[] {
|
|
|
192
205
|
costTier: m.costTier as CostTier,
|
|
193
206
|
harness: m.harness as HarnessOption,
|
|
194
207
|
featured: m.featured ?? false,
|
|
208
|
+
serviceTiers:
|
|
209
|
+
m.pricingVariants && typeof m.pricingVariants === "object"
|
|
210
|
+
? Object.keys(m.pricingVariants).sort()
|
|
211
|
+
: [],
|
|
195
212
|
}));
|
|
196
213
|
}
|
|
197
214
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ServiceTier } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* String form of the service tier for component props and hook inputs
|
|
5
|
+
* (stigmer/stigmer#357). Mirrors {@link HarnessOption}: components speak
|
|
6
|
+
* strings, execution creation speaks proto.
|
|
7
|
+
*
|
|
8
|
+
* "standard" is the model's base-priced configuration; "fast" is the
|
|
9
|
+
* provider's fast variant at the registry's fast rates — selectable only
|
|
10
|
+
* for models whose {@link ModelInfo.serviceTiers} includes "fast".
|
|
11
|
+
*/
|
|
12
|
+
export type ServiceTierOption = "standard" | "fast";
|
|
13
|
+
|
|
14
|
+
/** The tier key a model must price for the fast-tier switch to render. */
|
|
15
|
+
export const FAST_SERVICE_TIER = "fast";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Convert a {@link ServiceTierOption} string to the proto enum.
|
|
19
|
+
*/
|
|
20
|
+
export function toProtoServiceTier(tier: ServiceTierOption): ServiceTier {
|
|
21
|
+
switch (tier) {
|
|
22
|
+
case "fast":
|
|
23
|
+
return ServiceTier.FAST;
|
|
24
|
+
case "standard":
|
|
25
|
+
default:
|
|
26
|
+
return ServiceTier.STANDARD;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert a proto {@link ServiceTier} enum to its string form.
|
|
32
|
+
*
|
|
33
|
+
* `UNSPECIFIED` (and any unknown value) maps to `undefined` so callers can
|
|
34
|
+
* decide their own default — matching the proto contract (`UNSPECIFIED`
|
|
35
|
+
* resolves to `SERVICE_TIER_STANDARD` in the runner, never the provider
|
|
36
|
+
* account default).
|
|
37
|
+
*/
|
|
38
|
+
export function fromProtoServiceTier(
|
|
39
|
+
tier: ServiceTier | undefined,
|
|
40
|
+
): ServiceTierOption | undefined {
|
|
41
|
+
switch (tier) {
|
|
42
|
+
case ServiceTier.STANDARD:
|
|
43
|
+
return "standard";
|
|
44
|
+
case ServiceTier.FAST:
|
|
45
|
+
return "fast";
|
|
46
|
+
default:
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|