authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-09 09:13:52-07:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-09 09:47:21-07:00
log9fdb45be1efb0e0b542a0dd78d868de6f71d7d4a
treee8e861c56557446f870d00987394b96ce5eb43ff
parentb946f00f1a31f16a51f9bd1f579baf40c8eaed83

Remove service tier setting from Codex flow and contracts

- Drop `serviceTier` from orchestration/provider contracts and server dispatch paths - Remove service-tier app setting/UI and related resolver logic - Keep fast badge behavior tied to explicit fast-mode boolean

9 files changed, 26 insertions(+), 130 deletions(-)

apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+7-10
......@@ -5,7 +5,6 @@ import {
55 type OrchestrationEvent,
66 type ProviderModelOptions,
77 type ProviderKind,
8 type ProviderServiceTier,
98 type ProviderStartOptions,
109 type OrchestrationSession,
1110 ThreadId,
......@@ -205,7 +204,6 @@ const make = Effect.gen(function* () {
205204 readonly provider?: ProviderKind;
206205 readonly model?: string;
207206 readonly modelOptions?: ProviderModelOptions;
208 readonly serviceTier?: ProviderServiceTier | null;
209207 readonly providerOptions?: ProviderStartOptions;
210208 },
211209 ) {
......@@ -241,7 +239,6 @@ const make = Effect.gen(function* () {
241239 : {}),
242240 ...(effectiveCwd ? { cwd: effectiveCwd } : {}),
243241 ...(desiredModel ? { model: desiredModel } : {}),
244 ...(options?.serviceTier !== undefined ? { serviceTier: options.serviceTier } : {}),
245242 ...(options?.modelOptions !== undefined ? { modelOptions: options.modelOptions } : {}),
246243 ...(options?.providerOptions !== undefined ? { providerOptions: options.providerOptions } : {}),
247244 ...(input?.resumeCursor !== undefined ? { resumeCursor: input.resumeCursor } : {}),
......@@ -328,7 +325,6 @@ const make = Effect.gen(function* () {
328325 readonly attachments?: ReadonlyArray<ChatAttachment>;
329326 readonly provider?: ProviderKind;
330327 readonly model?: string;
331 readonly serviceTier?: ProviderServiceTier | null;
332328 readonly modelOptions?: ProviderModelOptions;
333329 readonly providerOptions?: ProviderStartOptions;
334330 readonly interactionMode?: "default" | "plan";
......@@ -344,7 +340,6 @@ const make = Effect.gen(function* () {
344340 yield* ensureSessionForThread(input.threadId, input.createdAt, {
345341 ...(input.provider !== undefined ? { provider: input.provider } : {}),
346342 ...(input.model !== undefined ? { model: input.model } : {}),
347 ...(input.serviceTier !== undefined ? { serviceTier: input.serviceTier } : {}),
348343 ...(input.modelOptions !== undefined ? { modelOptions: input.modelOptions } : {}),
349344 ...(input.providerOptions !== undefined ? { providerOptions: input.providerOptions } : {}),
350345 });
......@@ -365,7 +360,6 @@ const make = Effect.gen(function* () {
365360 ...(normalizedInput ? { input: normalizedInput } : {}),
366361 ...(normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {}),
367362 ...(modelForTurn !== undefined ? { model: modelForTurn } : {}),
368 ...(input.serviceTier !== undefined ? { serviceTier: input.serviceTier } : {}),
369363 ...(input.modelOptions !== undefined ? { modelOptions: input.modelOptions } : {}),
370364 ...(input.interactionMode !== undefined ? { interactionMode: input.interactionMode } : {}),
371365 });
......@@ -480,7 +474,6 @@ const make = Effect.gen(function* () {
480474 ...(message.attachments !== undefined ? { attachments: message.attachments } : {}),
481475 ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}),
482476 ...(event.payload.model !== undefined ? { model: event.payload.model } : {}),
483 ...(event.payload.serviceTier !== undefined ? { serviceTier: event.payload.serviceTier } : {}),
484477 ...(event.payload.modelOptions !== undefined ? { modelOptions: event.payload.modelOptions } : {}),
485478 ...(event.payload.providerOptions !== undefined ? { providerOptions: event.payload.providerOptions } : {}),
486479 interactionMode: event.payload.interactionMode,
......@@ -639,9 +632,13 @@ const make = Effect.gen(function* () {
639632 return;
640633 }
641634 const cachedProviderOptions = threadProviderOptions.get(event.payload.threadId);
642 yield* ensureSessionForThread(event.payload.threadId, event.occurredAt, {
643 ...(cachedProviderOptions !== undefined ? { providerOptions: cachedProviderOptions } : {}),
644 });
635 yield* ensureSessionForThread(
636 event.payload.threadId,
637 event.occurredAt,
638 cachedProviderOptions !== undefined
639 ? { providerOptions: cachedProviderOptions }
640 : undefined,
641 );
645642 return;
646643 }
647644 case "thread.turn-start-requested":
apps/server/src/orchestration/decider.ts-1
......@@ -301,7 +301,6 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
301301 messageId: command.message.messageId,
302302 ...(command.provider !== undefined ? { provider: command.provider } : {}),
303303 ...(command.model !== undefined ? { model: command.model } : {}),
304 ...(command.serviceTier !== undefined ? { serviceTier: command.serviceTier } : {}),
305304 ...(command.modelOptions !== undefined ? { modelOptions: command.modelOptions } : {}),
306305 ...(command.providerOptions !== undefined ? { providerOptions: command.providerOptions } : {}),
307306 assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE,
apps/server/src/provider/Layers/CodexAdapter.ts-1
......@@ -1354,7 +1354,6 @@ const makeCodexAdapter = (options?: CodexAdapterLiveOptions) =>
13541354 threadId: input.threadId,
13551355 ...(input.input !== undefined ? { input: input.input } : {}),
13561356 ...(input.model !== undefined ? { model: input.model } : {}),
1357 ...(input.serviceTier !== undefined ? { serviceTier: input.serviceTier } : {}),
13581357 ...(input.modelOptions?.codex?.reasoningEffort !== undefined
13591358 ? { effort: input.modelOptions.codex.reasoningEffort }
13601359 : {}),
apps/web/src/appSettings.test.ts+4-16
......@@ -4,7 +4,6 @@ import {
44 getAppModelOptions,
55 getSlashModelOptions,
66 normalizeCustomModelSlugs,
7 resolveAppServiceTier,
87 shouldShowFastTierIcon,
98 resolveAppModelSelection,
109} from "./appSettings";
......@@ -85,21 +84,10 @@ describe("getSlashModelOptions", () => {
8584 });
8685});
8786
88describe("resolveAppServiceTier", () => {
89 it("maps automatic to no override", () => {
90 expect(resolveAppServiceTier("auto")).toBeNull();
91 });
92
93 it("preserves explicit service tier overrides", () => {
94 expect(resolveAppServiceTier("fast")).toBe("fast");
95 expect(resolveAppServiceTier("flex")).toBe("flex");
96 });
97});
98
9987describe("shouldShowFastTierIcon", () => {
100 it("shows the fast-tier icon only for gpt-5.4 on fast tier", () => {
101 expect(shouldShowFastTierIcon("gpt-5.4", "fast")).toBe(true);
102 expect(shouldShowFastTierIcon("gpt-5.4", "auto")).toBe(false);
103 expect(shouldShowFastTierIcon("gpt-5.3-codex", "fast")).toBe(false);
88 it("shows the fast-tier icon only for gpt-5.4 when fast mode is enabled", () => {
89 expect(shouldShowFastTierIcon("gpt-5.4", true)).toBe(true);
90 expect(shouldShowFastTierIcon("gpt-5.4", false)).toBe(false);
91 expect(shouldShowFastTierIcon("gpt-5.3-codex", true)).toBe(false);
10492 });
10593});
apps/web/src/appSettings.ts+3-27
......@@ -1,30 +1,11 @@
11import { useCallback, useSyncExternalStore } from "react";
22import { Option, Schema } from "effect";
3import { type ProviderKind, type ProviderServiceTier } from "@t3tools/contracts";
3import { type ProviderKind } from "@t3tools/contracts";
44import { getDefaultModel, getModelOptions, normalizeModelSlug } from "@t3tools/shared/model";
55
66const APP_SETTINGS_STORAGE_KEY = "t3code:app-settings:v1";
77const MAX_CUSTOM_MODEL_COUNT = 32;
88export const MAX_CUSTOM_MODEL_LENGTH = 256;
9export const APP_SERVICE_TIER_OPTIONS = [
10 {
11 value: "auto",
12 label: "Automatic",
13 description: "Use Codex defaults without forcing a service tier.",
14 },
15 {
16 value: "fast",
17 label: "Fast",
18 description: "Request the fast service tier when the model supports it.",
19 },
20 {
21 value: "flex",
22 label: "Flex",
23 description: "Request the flex service tier when the model supports it.",
24 },
25] as const;
26export type AppServiceTier = (typeof APP_SERVICE_TIER_OPTIONS)[number]["value"];
27const AppServiceTierSchema = Schema.Literals(["auto", "fast", "flex"]);
289const MODELS_WITH_FAST_SUPPORT = new Set(["gpt-5.4"]);
2910const BUILT_IN_MODEL_SLUGS_BY_PROVIDER: Record<ProviderKind, ReadonlySet<string>> = {
3011 codex: new Set(getModelOptions("codex").map((option) => option.slug)),
......@@ -41,7 +22,6 @@ const AppSettingsSchema = Schema.Struct({
4122 enableAssistantStreaming: Schema.Boolean.pipe(
4223 Schema.withConstructorDefault(() => Option.some(false)),
4324 ),
44 codexServiceTier: AppServiceTierSchema.pipe(Schema.withConstructorDefault(() => Option.some("auto"))),
4525 customCodexModels: Schema.Array(Schema.String).pipe(
4626 Schema.withConstructorDefault(() => Option.some([])),
4727 ),
......@@ -53,17 +33,13 @@ export interface AppModelOption {
5333 isCustom: boolean;
5434}
5535
56export function resolveAppServiceTier(serviceTier: AppServiceTier): ProviderServiceTier | null {
57 return serviceTier === "auto" ? null : serviceTier;
58}
59
6036export function shouldShowFastTierIcon(
6137 model: string | null | undefined,
62 serviceTier: AppServiceTier,
38 fastModeEnabled: boolean,
6339): boolean {
6440 const normalizedModel = normalizeModelSlug(model);
6541 return (
66 resolveAppServiceTier(serviceTier) === "fast" &&
42 fastModeEnabled &&
6743 normalizedModel !== null &&
6844 MODELS_WITH_FAST_SUPPORT.has(normalizedModel)
6945 );
apps/web/src/components/ChatView.tsx+7-12
......@@ -205,9 +205,7 @@ import { readNativeApi } from "~/nativeApi";
205205import {
206206 getAppModelOptions,
207207 resolveAppModelSelection,
208 resolveAppServiceTier,
209208 shouldShowFastTierIcon,
210 type AppServiceTier,
211209 useAppSettings,
212210} from "../appSettings";
213211import {
......@@ -785,8 +783,6 @@ export default function ChatView({ threadId }: ChatViewProps) {
785783 activeThread.messages.length > 0 ||
786784 activeThread.session !== null),
787785 );
788 const selectedServiceTierSetting = settings.codexServiceTier;
789 const selectedServiceTier = resolveAppServiceTier(selectedServiceTierSetting);
790786 const lockedProvider: ProviderKind | null = hasThreadStarted
791787 ? (sessionProvider ?? selectedProviderByThreadId ?? null)
792788 : null;
......@@ -1255,9 +1251,9 @@ export default function ChatView({ threadId }: ChatViewProps) {
12551251 label: name,
12561252 description: `${providerLabel} · ${slug}`,
12571253 showFastBadge:
1258 provider === "codex" && shouldShowFastTierIcon(slug, selectedServiceTierSetting),
1254 provider === "codex" && shouldShowFastTierIcon(slug, selectedCodexFastModeEnabled),
12591255 }));
1260 }, [composerTrigger, searchableModelOptions, selectedServiceTierSetting, workspaceEntries]);
1256 }, [composerTrigger, searchableModelOptions, selectedCodexFastModeEnabled, workspaceEntries]);
12611257 const composerMenuOpen = Boolean(composerTrigger);
12621258 const activeComposerMenuItem = useMemo(
12631259 () =>
......@@ -2665,7 +2661,6 @@ export default function ChatView({ threadId }: ChatViewProps) {
26652661 attachments: turnAttachments,
26662662 },
26672663 model: selectedModel || undefined,
2668 serviceTier: selectedServiceTier,
26692664 ...(selectedModelOptionsForDispatch
26702665 ? { modelOptions: selectedModelOptionsForDispatch }
26712666 : {}),
......@@ -3700,7 +3695,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
37003695 model={selectedModelForPickerWithCustomFallback}
37013696 lockedProvider={lockedProvider}
37023697 modelOptionsByProvider={modelOptionsByProvider}
3703 serviceTierSetting={selectedServiceTierSetting}
3698 fastModeEnabled={selectedCodexFastModeEnabled}
37043699 onProviderModelChange={onProviderModelSelect}
37053700 />
37063701
......@@ -5548,7 +5543,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
55485543 model: ModelSlug;
55495544 lockedProvider: ProviderKind | null;
55505545 modelOptionsByProvider: Record<ProviderKind, ReadonlyArray<{ slug: string; name: string }>>;
5551 serviceTierSetting: AppServiceTier;
5546 fastModeEnabled: boolean;
55525547 disabled?: boolean;
55535548 onProviderModelChange: (provider: ProviderKind, model: ModelSlug) => void;
55545549}) {
......@@ -5578,10 +5573,10 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
55785573 disabled={props.disabled}
55795574 />
55805575 }
5581 >
5576 >
55825577 <span className="flex min-w-0 items-center gap-2">
55835578 <ProviderIcon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground/70" />
5584 {props.provider === "codex" && shouldShowFastTierIcon(props.model, props.serviceTierSetting) ? (
5579 {props.provider === "codex" && shouldShowFastTierIcon(props.model, props.fastModeEnabled) ? (
55855580 <ZapIcon className="size-3.5 shrink-0 text-amber-500" />
55865581 ) : null}
55875582 <span className="truncate">{selectedModelLabel}</span>
......@@ -5627,7 +5622,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
56275622 onClick={() => setIsMenuOpen(false)}
56285623 >
56295624 {option.value === "codex" &&
5630 shouldShowFastTierIcon(modelOption.slug, props.serviceTierSetting) ? (
5625 shouldShowFastTierIcon(modelOption.slug, props.fastModeEnabled) ? (
56315626 <ZapIcon className="size-3.5 shrink-0 text-amber-500" />
56325627 ) : null}
56335628 {modelOption.name}
apps/web/src/routes/_chat.settings.tsx+5-55
......@@ -3,14 +3,8 @@ import { useQuery } from "@tanstack/react-query";
33import { useCallback, useState } from "react";
44import { type ProviderKind } from "@t3tools/contracts";
55import { getModelOptions, normalizeModelSlug } from "@t3tools/shared/model";
6import { ZapIcon } from "lucide-react";
7
8import {
9 APP_SERVICE_TIER_OPTIONS,
10 MAX_CUSTOM_MODEL_LENGTH,
11 shouldShowFastTierIcon,
12 useAppSettings,
13} from "../appSettings";
6
7import { MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings";
148import { isElectron } from "../env";
159import { useTheme } from "../hooks/useTheme";
1610import { serverConfigQueryOptions } from "../lib/serverReactQuery";
......@@ -18,7 +12,6 @@ import { ensureNativeApi } from "../nativeApi";
1812import { preferredTerminalEditor } from "../terminal-links";
1913import { Button } from "../components/ui/button";
2014import { Input } from "../components/ui/input";
21import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../components/ui/select";
2215import { Switch } from "../components/ui/switch";
2316import { SidebarInset } from "~/components/ui/sidebar";
2417
......@@ -103,7 +96,6 @@ function SettingsRouteView() {
10396
10497 const codexBinaryPath = settings.codexBinaryPath;
10598 const codexHomePath = settings.codexHomePath;
106 const codexServiceTier = settings.codexServiceTier;
10799 const keybindingsConfigPath = serverConfigQuery.data?.keybindingsConfigPath ?? null;
108100
109101 const openKeybindingsFile = useCallback(() => {
......@@ -310,43 +302,6 @@ function SettingsRouteView() {
310302 </div>
311303
312304 <div className="space-y-5">
313 <label className="block space-y-1">
314 <span className="text-xs font-medium text-foreground">Default service tier</span>
315 <Select
316 items={APP_SERVICE_TIER_OPTIONS.map((option) => ({
317 label: option.label,
318 value: option.value,
319 }))}
320 value={codexServiceTier}
321 onValueChange={(value) => {
322 if (!value) return;
323 updateSettings({ codexServiceTier: value });
324 }}
325 >
326 <SelectTrigger>
327 <SelectValue />
328 </SelectTrigger>
329 <SelectPopup alignItemWithTrigger={false}>
330 {APP_SERVICE_TIER_OPTIONS.map((option) => (
331 <SelectItem key={option.value} value={option.value}>
332 <div className="flex min-w-0 items-center gap-2">
333 {option.value === "fast" ? (
334 <ZapIcon className="size-3.5 text-amber-500" />
335 ) : (
336 <span className="size-3.5 shrink-0" aria-hidden="true" />
337 )}
338 <span className="truncate">{option.label}</span>
339 </div>
340 </SelectItem>
341 ))}
342 </SelectPopup>
343 </Select>
344 <span className="text-xs text-muted-foreground">
345 {APP_SERVICE_TIER_OPTIONS.find((option) => option.value === codexServiceTier)
346 ?.description ?? "Use Codex defaults without forcing a service tier."}
347 </span>
348 </label>
349
350305 {MODEL_PROVIDER_SETTINGS.map((providerSettings) => {
351306 const provider = providerSettings.provider;
352307 const customModels = getCustomModelsForProvider(settings, provider);
......@@ -445,14 +400,9 @@ function SettingsRouteView() {
445400 key={`${provider}:${slug}`}
446401 className="flex items-center justify-between gap-3 rounded-lg border border-border bg-background px-3 py-2"
447402 >
448 <div className="flex min-w-0 flex-1 items-center gap-2">
449 {provider === "codex" && shouldShowFastTierIcon(slug, codexServiceTier) ? (
450 <ZapIcon className="size-3.5 shrink-0 text-amber-500" />
451 ) : null}
452 <code className="min-w-0 flex-1 truncate text-xs text-foreground">
453 {slug}
454 </code>
455 </div>
403 <code className="min-w-0 flex-1 truncate text-xs text-foreground">
404 {slug}
405 </code>
456406 <Button
457407 size="xs"
458408 variant="ghost"
packages/contracts/src/orchestration.ts-5
......@@ -42,8 +42,6 @@ export const ProviderSandboxMode = Schema.Literals([
4242 "danger-full-access",
4343]);
4444export type ProviderSandboxMode = typeof ProviderSandboxMode.Type;
45export const ProviderServiceTier = Schema.Literals(["fast", "flex"]);
46export type ProviderServiceTier = typeof ProviderServiceTier.Type;
4745export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex";
4846const CodexProviderStartOptions = Schema.Struct({
4947 binaryPath: Schema.optional(TrimmedNonEmptyString),
......@@ -371,7 +369,6 @@ export const ThreadTurnStartCommand = Schema.Struct({
371369 }),
372370 provider: Schema.optional(ProviderKind),
373371 model: Schema.optional(TrimmedNonEmptyString),
374 serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
375372 modelOptions: Schema.optional(ProviderModelOptions),
376373 providerOptions: Schema.optional(ProviderStartOptions),
377374 assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
......@@ -394,7 +391,6 @@ const ClientThreadTurnStartCommand = Schema.Struct({
394391 }),
395392 provider: Schema.optional(ProviderKind),
396393 model: Schema.optional(TrimmedNonEmptyString),
397 serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
398394 modelOptions: Schema.optional(ProviderModelOptions),
399395 providerOptions: Schema.optional(ProviderStartOptions),
400396 assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
......@@ -675,7 +671,6 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({
675671 messageId: MessageId,
676672 provider: Schema.optional(ProviderKind),
677673 model: Schema.optional(TrimmedNonEmptyString),
678 serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
679674 modelOptions: Schema.optional(ProviderModelOptions),
680675 providerOptions: Schema.optional(ProviderStartOptions),
681676 assistantDeliveryMode: Schema.optional(AssistantDeliveryMode),
packages/contracts/src/provider.ts-3
......@@ -19,7 +19,6 @@ import {
1919 ProviderKind,
2020 ProviderRequestKind,
2121 ProviderSandboxMode,
22 ProviderServiceTier,
2322 ProviderUserInputAnswers,
2423 RuntimeMode,
2524} from "./orchestration";
......@@ -65,7 +64,6 @@ export const ProviderSessionStartInput = Schema.Struct({
6564 model: Schema.optional(TrimmedNonEmptyStringSchema),
6665 modelOptions: Schema.optional(ProviderModelOptions),
6766 resumeCursor: Schema.optional(Schema.Unknown),
68 serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
6967 approvalPolicy: Schema.optional(ProviderApprovalPolicy),
7068 sandboxMode: Schema.optional(ProviderSandboxMode),
7169 providerOptions: Schema.optional(ProviderStartOptions),
......@@ -82,7 +80,6 @@ export const ProviderSendTurnInput = Schema.Struct({
8280 Schema.Array(ChatAttachment).check(Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_ATTACHMENTS)),
8381 ),
8482 model: Schema.optional(TrimmedNonEmptyStringSchema),
85 serviceTier: Schema.optional(Schema.NullOr(ProviderServiceTier)),
8683 modelOptions: Schema.optional(ProviderModelOptions),
8784 interactionMode: Schema.optional(ProviderInteractionMode),
8885});