diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
index ff9ef7600743ed6f7c89b380309847870f0d29f6..7c346c726c29dbb139c6cabd616b75a42f65520b 100644
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -1,6 +1,6 @@
import {
type ApprovalRequestId,
- DEFAULT_MODEL,
+ DEFAULT_MODEL_BY_PROVIDER,
EDITORS,
type EditorId,
type KeybindingCommand,
@@ -575,7 +575,9 @@ export default function ChatView({ threadId }: ChatViewProps) {
const setComposerDraftProvider = useComposerDraftStore((store) => store.setProvider);
const setComposerDraftModel = useComposerDraftStore((store) => store.setModel);
const setComposerDraftRuntimeMode = useComposerDraftStore((store) => store.setRuntimeMode);
- const setComposerDraftInteractionMode = useComposerDraftStore((store) => store.setInteractionMode);
+ const setComposerDraftInteractionMode = useComposerDraftStore(
+ (store) => store.setInteractionMode,
+ );
const setComposerDraftEffort = useComposerDraftStore((store) => store.setEffort);
const setComposerDraftCodexFastMode = useComposerDraftStore((store) => store.setCodexFastMode);
const addComposerDraftImage = useComposerDraftStore((store) => store.addImage);
@@ -703,14 +705,15 @@ export default function ChatView({ threadId }: ChatViewProps) {
? buildLocalDraftThread(
threadId,
draftThread,
- fallbackDraftProject?.model ?? DEFAULT_MODEL,
+ fallbackDraftProject?.model ?? DEFAULT_MODEL_BY_PROVIDER.codex,
localDraftError,
)
: undefined,
[draftThread, fallbackDraftProject?.model, localDraftError, threadId],
);
const activeThread = serverThread ?? localDraftThread;
- const runtimeMode = composerDraft.runtimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE;
+ const runtimeMode =
+ composerDraft.runtimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE;
const interactionMode =
composerDraft.interactionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;
const isServerThread = serverThread !== undefined;
@@ -757,10 +760,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
const selectedProvider: ProviderKind = lockedProvider ?? selectedProviderByThreadId ?? "codex";
const baseThreadModel = resolveModelSlugForProvider(
selectedProvider,
- activeThread?.model ??
- activeProject?.model ??
- getDefaultModel(selectedProvider) ??
- DEFAULT_MODEL,
+ activeThread?.model ?? activeProject?.model ?? getDefaultModel(selectedProvider),
);
const selectedModel = useMemo(() => {
const draftModel = composerDraft.model;
@@ -2470,7 +2470,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
}
const title = truncateTitle(titleSeed);
let threadCreateModel: ModelSlug =
- selectedModel || (activeProject.model as ModelSlug) || DEFAULT_MODEL;
+ selectedModel || (activeProject.model as ModelSlug) || DEFAULT_MODEL_BY_PROVIDER.codex;
if (isLocalDraftThread) {
await api.orchestration.dispatchCommand({
@@ -2881,7 +2881,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
selectedModel ||
(activeThread.model as ModelSlug) ||
(activeProject.model as ModelSlug) ||
- DEFAULT_MODEL;
+ DEFAULT_MODEL_BY_PROVIDER.codex;
sendInFlightRef.current = true;
setSendPhase("sending-turn");
@@ -5127,9 +5127,7 @@ const MessagesTimeline = memo(function MessagesTimeline({
);
});
-function isAvailableProviderOption(
- option: (typeof PROVIDER_OPTIONS)[number],
-): option is {
+function isAvailableProviderOption(option: (typeof PROVIDER_OPTIONS)[number]): option is {
value: ProviderKind;
label: string;
available: true;
@@ -5227,10 +5225,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
}
>
-
+
{selectedModelLabel}
diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx
index 0207f289b579a45a6a58c7a189566a8a6014084d..2b8b02ee59b7b35685e6991bff0ac9471e2588a8 100644
--- a/apps/web/src/components/Sidebar.tsx
+++ b/apps/web/src/components/Sidebar.tsx
@@ -9,7 +9,7 @@ import {
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
DEFAULT_RUNTIME_MODE,
- DEFAULT_MODEL,
+ DEFAULT_MODEL_BY_PROVIDER,
type DesktopUpdateState,
ProjectId,
ThreadId,
@@ -506,7 +506,7 @@ export default function Sidebar() {
projectId,
title,
workspaceRoot: cwd,
- defaultModel: DEFAULT_MODEL,
+ defaultModel: DEFAULT_MODEL_BY_PROVIDER.codex,
createdAt,
})
.then(() => true)
@@ -885,7 +885,7 @@ export default function Sidebar() {
? "text-sky-400"
: shouldHighlightDesktopUpdateError(desktopUpdateState)
? "text-rose-500 animate-pulse"
- : "text-amber-500 animate-pulse";
+ : "text-amber-500 animate-pulse";
const newThreadShortcutLabel = useMemo(
() =>
shortcutLabelForCommand(keybindings, "chat.newLocal") ??
diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts
index 8bea15ea7facf04a678145aab7a231749126e47a..65c96653788d17e7ce0243a94ef83dc680f255d3 100644
--- a/apps/web/src/store.ts
+++ b/apps/web/src/store.ts
@@ -1,6 +1,6 @@
import { Fragment, type ReactNode, createElement, useEffect } from "react";
import {
- DEFAULT_MODEL,
+ DEFAULT_MODEL_BY_PROVIDER,
type ProviderKind,
ThreadId,
type OrchestrationReadModel,
@@ -13,11 +13,7 @@ import {
resolveModelSlugForProvider,
} from "@t3tools/shared/model";
import { create } from "zustand";
-import {
- type ChatMessage,
- type Project,
- type Thread,
-} from "./types";
+import { type ChatMessage, type Project, type Thread } from "./types";
// ── State ────────────────────────────────────────────────────────────
@@ -114,7 +110,9 @@ function mapProjectsFromReadModel(
id: project.id,
name: project.title,
cwd: project.workspaceRoot,
- model: existing?.model ?? resolveModelSlug(project.defaultModel ?? DEFAULT_MODEL),
+ model:
+ existing?.model ??
+ resolveModelSlug(project.defaultModel ?? DEFAULT_MODEL_BY_PROVIDER.codex),
expanded:
existing?.expanded ??
(persistedExpandedProjectCwds.size > 0
@@ -181,11 +179,7 @@ function resolveWsHttpOrigin(): string {
try {
const wsUrl = new URL(wsCandidate);
const protocol =
- wsUrl.protocol === "wss:"
- ? "https:"
- : wsUrl.protocol === "ws:"
- ? "http:"
- : wsUrl.protocol;
+ wsUrl.protocol === "wss:" ? "https:" : wsUrl.protocol === "ws:" ? "http:" : wsUrl.protocol;
return `${protocol}//${wsUrl.host}`;
} catch {
return window.location.origin;
@@ -205,17 +199,12 @@ function attachmentPreviewRoutePath(attachmentId: string): string {
// ── Pure state transition functions ────────────────────────────────────
-export function syncServerReadModel(
- state: AppState,
- readModel: OrchestrationReadModel,
-): AppState {
+export function syncServerReadModel(state: AppState, readModel: OrchestrationReadModel): AppState {
const projects = mapProjectsFromReadModel(
readModel.projects.filter((project) => project.deletedAt === null),
state.projects,
);
- const existingThreadById = new Map(
- state.threads.map((thread) => [thread.id, thread] as const),
- );
+ const existingThreadById = new Map(state.threads.map((thread) => [thread.id, thread] as const));
const threads = readModel.threads
.filter((thread) => thread.deletedAt === null)
.map((thread) => {
@@ -334,9 +323,7 @@ export function markThreadUnread(state: AppState, threadId: ThreadId): AppState
export function toggleProject(state: AppState, projectId: Project["id"]): AppState {
return {
...state,
- projects: state.projects.map((p) =>
- p.id === projectId ? { ...p, expanded: !p.expanded } : p,
- ),
+ projects: state.projects.map((p) => (p.id === projectId ? { ...p, expanded: !p.expanded } : p)),
};
}
@@ -390,27 +377,19 @@ interface AppStore extends AppState {
toggleProject: (projectId: Project["id"]) => void;
setProjectExpanded: (projectId: Project["id"], expanded: boolean) => void;
setError: (threadId: ThreadId, error: string | null) => void;
- setThreadBranch: (
- threadId: ThreadId,
- branch: string | null,
- worktreePath: string | null,
- ) => void;
+ setThreadBranch: (threadId: ThreadId, branch: string | null, worktreePath: string | null) => void;
}
export const useStore = create((set) => ({
...readPersistedState(),
- syncServerReadModel: (readModel) =>
- set((state) => syncServerReadModel(state, readModel)),
+ syncServerReadModel: (readModel) => set((state) => syncServerReadModel(state, readModel)),
markThreadVisited: (threadId, visitedAt) =>
set((state) => markThreadVisited(state, threadId, visitedAt)),
- markThreadUnread: (threadId) =>
- set((state) => markThreadUnread(state, threadId)),
- toggleProject: (projectId) =>
- set((state) => toggleProject(state, projectId)),
+ markThreadUnread: (threadId) => set((state) => markThreadUnread(state, threadId)),
+ toggleProject: (projectId) => set((state) => toggleProject(state, projectId)),
setProjectExpanded: (projectId, expanded) =>
set((state) => setProjectExpanded(state, projectId, expanded)),
- setError: (threadId, error) =>
- set((state) => setError(state, threadId, error)),
+ setError: (threadId, error) => set((state) => setError(state, threadId, error)),
setThreadBranch: (threadId, branch, worktreePath) =>
set((state) => setThreadBranch(state, threadId, branch, worktreePath)),
}));
diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts
index ee2e2106ac71a21279f97c7be94d0375487d37b2..189fbf09dcd1e1aaa73e664c03c678c78b2965d3 100644
--- a/packages/contracts/src/model.ts
+++ b/packages/contracts/src/model.ts
@@ -29,18 +29,15 @@ export const MODEL_OPTIONS_BY_PROVIDER = {
{ slug: "gpt-5.2", name: "GPT-5.2" },
],
} as const satisfies Record;
+export type ModelOptionsByProvider = typeof MODEL_OPTIONS_BY_PROVIDER;
-type BuiltInModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)[ProviderKind][number]["slug"];
+type BuiltInModelSlug = ModelOptionsByProvider[ProviderKind][number]["slug"];
export type ModelSlug = BuiltInModelSlug | (string & {});
export const DEFAULT_MODEL_BY_PROVIDER = {
codex: "gpt-5.4",
} as const satisfies Record;
-// Backward compatibility for existing Codex-only call sites.
-export const MODEL_OPTIONS = MODEL_OPTIONS_BY_PROVIDER.codex;
-export const DEFAULT_MODEL = DEFAULT_MODEL_BY_PROVIDER.codex;
-
export const MODEL_SLUG_ALIASES_BY_PROVIDER = {
codex: {
"5.4": "gpt-5.4",
diff --git a/packages/shared/src/model.test.ts b/packages/shared/src/model.test.ts
index e3f3b6925b50f09b2da3935bebf73f0a66e3d19b..00fb0a77500587984f4af0e4760916b7d8fe6cf1 100644
--- a/packages/shared/src/model.test.ts
+++ b/packages/shared/src/model.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { DEFAULT_MODEL, MODEL_OPTIONS } from "@t3tools/contracts";
+import { DEFAULT_MODEL_BY_PROVIDER, MODEL_OPTIONS_BY_PROVIDER } from "@t3tools/contracts";
import {
getDefaultModel,
@@ -31,23 +31,23 @@ describe("normalizeModelSlug", () => {
describe("resolveModelSlug", () => {
it("returns default only when the model is missing", () => {
- expect(resolveModelSlug(undefined)).toBe(DEFAULT_MODEL);
- expect(resolveModelSlug(null)).toBe(DEFAULT_MODEL);
+ expect(resolveModelSlug(undefined)).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
+ expect(resolveModelSlug(null)).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
});
it("preserves unknown custom models", () => {
- expect(resolveModelSlug("gpt-4.1")).toBe(DEFAULT_MODEL);
- expect(resolveModelSlug("custom/internal-model")).toBe(DEFAULT_MODEL);
+ expect(resolveModelSlug("gpt-4.1")).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
+ expect(resolveModelSlug("custom/internal-model")).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
});
it("resolves only supported model options", () => {
- for (const model of MODEL_OPTIONS) {
+ for (const model of MODEL_OPTIONS_BY_PROVIDER.codex) {
expect(resolveModelSlug(model.slug)).toBe(model.slug);
}
});
it("keeps codex defaults for backward compatibility", () => {
- expect(getDefaultModel()).toBe(DEFAULT_MODEL);
- expect(getModelOptions()).toEqual(MODEL_OPTIONS);
+ expect(getDefaultModel()).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
+ expect(getModelOptions()).toEqual(MODEL_OPTIONS_BY_PROVIDER.codex);
});
});
diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts
index 9b80199574a8d642582f04a2a1e374c762b0af16..6f3c1a75ec21346dc04c258b908be4765f5bb12b 100644
--- a/packages/shared/src/model.ts
+++ b/packages/shared/src/model.ts
@@ -1,6 +1,5 @@
import {
CODEX_REASONING_EFFORT_OPTIONS,
- DEFAULT_MODEL,
DEFAULT_MODEL_BY_PROVIDER,
MODEL_OPTIONS_BY_PROVIDER,
MODEL_SLUG_ALIASES_BY_PROVIDER,
@@ -20,7 +19,7 @@ export function getModelOptions(provider: ProviderKind = "codex") {
}
export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug {
- return DEFAULT_MODEL_BY_PROVIDER[provider] ?? DEFAULT_MODEL;
+ return DEFAULT_MODEL_BY_PROVIDER[provider];
}
export function normalizeModelSlug(