authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 22:13:35-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
log116c8c5e8e0db2fe7fad070c2f67738a62f93546
treeec6b3428610593479a94c4d56c0cd4d354e63e62
parentf8886e874d43fe591fa50d28e9a8983632ba7e45

rm some aliases


6 files changed, 40 insertions(+), 70 deletions(-)

apps/web/src/components/ChatView.tsx+12-17
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import {1import {
2 type ApprovalRequestId,2 type ApprovalRequestId,
3 DEFAULT_MODEL,3 DEFAULT_MODEL_BY_PROVIDER,
4 EDITORS,4 EDITORS,
5 type EditorId,5 type EditorId,
6 type KeybindingCommand,6 type KeybindingCommand,
...@@ -575,7 +575,9 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -575,7 +575,9 @@ export default function ChatView({ threadId }: ChatViewProps) {
575 const setComposerDraftProvider = useComposerDraftStore((store) => store.setProvider);575 const setComposerDraftProvider = useComposerDraftStore((store) => store.setProvider);
576 const setComposerDraftModel = useComposerDraftStore((store) => store.setModel);576 const setComposerDraftModel = useComposerDraftStore((store) => store.setModel);
577 const setComposerDraftRuntimeMode = useComposerDraftStore((store) => store.setRuntimeMode);577 const setComposerDraftRuntimeMode = useComposerDraftStore((store) => store.setRuntimeMode);
578 const setComposerDraftInteractionMode = useComposerDraftStore((store) => store.setInteractionMode);578 const setComposerDraftInteractionMode = useComposerDraftStore(
579 (store) => store.setInteractionMode,
580 );
579 const setComposerDraftEffort = useComposerDraftStore((store) => store.setEffort);581 const setComposerDraftEffort = useComposerDraftStore((store) => store.setEffort);
580 const setComposerDraftCodexFastMode = useComposerDraftStore((store) => store.setCodexFastMode);582 const setComposerDraftCodexFastMode = useComposerDraftStore((store) => store.setCodexFastMode);
581 const addComposerDraftImage = useComposerDraftStore((store) => store.addImage);583 const addComposerDraftImage = useComposerDraftStore((store) => store.addImage);
...@@ -703,14 +705,15 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -703,14 +705,15 @@ export default function ChatView({ threadId }: ChatViewProps) {
703 ? buildLocalDraftThread(705 ? buildLocalDraftThread(
704 threadId,706 threadId,
705 draftThread,707 draftThread,
706 fallbackDraftProject?.model ?? DEFAULT_MODEL,708 fallbackDraftProject?.model ?? DEFAULT_MODEL_BY_PROVIDER.codex,
707 localDraftError,709 localDraftError,
708 )710 )
709 : undefined,711 : undefined,
710 [draftThread, fallbackDraftProject?.model, localDraftError, threadId],712 [draftThread, fallbackDraftProject?.model, localDraftError, threadId],
711 );713 );
712 const activeThread = serverThread ?? localDraftThread;714 const activeThread = serverThread ?? localDraftThread;
713 const runtimeMode = composerDraft.runtimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE;715 const runtimeMode =
716 composerDraft.runtimeMode ?? activeThread?.runtimeMode ?? DEFAULT_RUNTIME_MODE;
714 const interactionMode =717 const interactionMode =
715 composerDraft.interactionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;718 composerDraft.interactionMode ?? activeThread?.interactionMode ?? DEFAULT_INTERACTION_MODE;
716 const isServerThread = serverThread !== undefined;719 const isServerThread = serverThread !== undefined;
...@@ -757,10 +760,7 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -757,10 +760,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
757 const selectedProvider: ProviderKind = lockedProvider ?? selectedProviderByThreadId ?? "codex";760 const selectedProvider: ProviderKind = lockedProvider ?? selectedProviderByThreadId ?? "codex";
758 const baseThreadModel = resolveModelSlugForProvider(761 const baseThreadModel = resolveModelSlugForProvider(
759 selectedProvider,762 selectedProvider,
760 activeThread?.model ??763 activeThread?.model ?? activeProject?.model ?? getDefaultModel(selectedProvider),
761 activeProject?.model ??
762 getDefaultModel(selectedProvider) ??
763 DEFAULT_MODEL,
764 );764 );
765 const selectedModel = useMemo(() => {765 const selectedModel = useMemo(() => {
766 const draftModel = composerDraft.model;766 const draftModel = composerDraft.model;
...@@ -2470,7 +2470,7 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -2470,7 +2470,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
2470 }2470 }
2471 const title = truncateTitle(titleSeed);2471 const title = truncateTitle(titleSeed);
2472 let threadCreateModel: ModelSlug =2472 let threadCreateModel: ModelSlug =
2473 selectedModel || (activeProject.model as ModelSlug) || DEFAULT_MODEL;2473 selectedModel || (activeProject.model as ModelSlug) || DEFAULT_MODEL_BY_PROVIDER.codex;
24742474
2475 if (isLocalDraftThread) {2475 if (isLocalDraftThread) {
2476 await api.orchestration.dispatchCommand({2476 await api.orchestration.dispatchCommand({
...@@ -2881,7 +2881,7 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -2881,7 +2881,7 @@ export default function ChatView({ threadId }: ChatViewProps) {
2881 selectedModel ||2881 selectedModel ||
2882 (activeThread.model as ModelSlug) ||2882 (activeThread.model as ModelSlug) ||
2883 (activeProject.model as ModelSlug) ||2883 (activeProject.model as ModelSlug) ||
2884 DEFAULT_MODEL;2884 DEFAULT_MODEL_BY_PROVIDER.codex;
28852885
2886 sendInFlightRef.current = true;2886 sendInFlightRef.current = true;
2887 setSendPhase("sending-turn");2887 setSendPhase("sending-turn");
...@@ -5127,9 +5127,7 @@ const MessagesTimeline = memo(function MessagesTimeline({...@@ -5127,9 +5127,7 @@ const MessagesTimeline = memo(function MessagesTimeline({
5127 );5127 );
5128});5128});
51295129
5130function isAvailableProviderOption(5130function isAvailableProviderOption(option: (typeof PROVIDER_OPTIONS)[number]): option is {
5131 option: (typeof PROVIDER_OPTIONS)[number],
5132): option is {
5133 value: ProviderKind;5131 value: ProviderKind;
5134 label: string;5132 label: string;
5135 available: true;5133 available: true;
...@@ -5227,10 +5225,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {...@@ -5227,10 +5225,7 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
5227 }5225 }
5228 >5226 >
5229 <span className="flex min-w-0 items-center gap-2">5227 <span className="flex min-w-0 items-center gap-2">
5230 <ProviderIcon5228 <ProviderIcon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground/70" />
5231 aria-hidden="true"
5232 className="size-4 shrink-0 text-muted-foreground/70"
5233 />
5234 <span className="truncate">{selectedModelLabel}</span>5229 <span className="truncate">{selectedModelLabel}</span>
5235 <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" />5230 <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" />
5236 </span>5231 </span>
apps/web/src/components/Sidebar.tsx+3-3
...@@ -9,7 +9,7 @@ import {...@@ -9,7 +9,7 @@ import {
9import { useCallback, useEffect, useMemo, useRef, useState } from "react";9import { useCallback, useEffect, useMemo, useRef, useState } from "react";
10import {10import {
11 DEFAULT_RUNTIME_MODE,11 DEFAULT_RUNTIME_MODE,
12 DEFAULT_MODEL,12 DEFAULT_MODEL_BY_PROVIDER,
13 type DesktopUpdateState,13 type DesktopUpdateState,
14 ProjectId,14 ProjectId,
15 ThreadId,15 ThreadId,
...@@ -506,7 +506,7 @@ export default function Sidebar() {...@@ -506,7 +506,7 @@ export default function Sidebar() {
506 projectId,506 projectId,
507 title,507 title,
508 workspaceRoot: cwd,508 workspaceRoot: cwd,
509 defaultModel: DEFAULT_MODEL,509 defaultModel: DEFAULT_MODEL_BY_PROVIDER.codex,
510 createdAt,510 createdAt,
511 })511 })
512 .then(() => true)512 .then(() => true)
...@@ -885,7 +885,7 @@ export default function Sidebar() {...@@ -885,7 +885,7 @@ export default function Sidebar() {
885 ? "text-sky-400"885 ? "text-sky-400"
886 : shouldHighlightDesktopUpdateError(desktopUpdateState)886 : shouldHighlightDesktopUpdateError(desktopUpdateState)
887 ? "text-rose-500 animate-pulse"887 ? "text-rose-500 animate-pulse"
888 : "text-amber-500 animate-pulse";888 : "text-amber-500 animate-pulse";
889 const newThreadShortcutLabel = useMemo(889 const newThreadShortcutLabel = useMemo(
890 () =>890 () =>
891 shortcutLabelForCommand(keybindings, "chat.newLocal") ??891 shortcutLabelForCommand(keybindings, "chat.newLocal") ??
apps/web/src/store.ts+14-35
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1import { Fragment, type ReactNode, createElement, useEffect } from "react";1import { Fragment, type ReactNode, createElement, useEffect } from "react";
2import {2import {
3 DEFAULT_MODEL,3 DEFAULT_MODEL_BY_PROVIDER,
4 type ProviderKind,4 type ProviderKind,
5 ThreadId,5 ThreadId,
6 type OrchestrationReadModel,6 type OrchestrationReadModel,
...@@ -13,11 +13,7 @@ import {...@@ -13,11 +13,7 @@ import {
13 resolveModelSlugForProvider,13 resolveModelSlugForProvider,
14} from "@t3tools/shared/model";14} from "@t3tools/shared/model";
15import { create } from "zustand";15import { create } from "zustand";
16import {16import { type ChatMessage, type Project, type Thread } from "./types";
17 type ChatMessage,
18 type Project,
19 type Thread,
20} from "./types";
2117
22// ── State ────────────────────────────────────────────────────────────18// ── State ────────────────────────────────────────────────────────────
2319
...@@ -114,7 +110,9 @@ function mapProjectsFromReadModel(...@@ -114,7 +110,9 @@ function mapProjectsFromReadModel(
114 id: project.id,110 id: project.id,
115 name: project.title,111 name: project.title,
116 cwd: project.workspaceRoot,112 cwd: project.workspaceRoot,
117 model: existing?.model ?? resolveModelSlug(project.defaultModel ?? DEFAULT_MODEL),113 model:
114 existing?.model ??
115 resolveModelSlug(project.defaultModel ?? DEFAULT_MODEL_BY_PROVIDER.codex),
118 expanded:116 expanded:
119 existing?.expanded ??117 existing?.expanded ??
120 (persistedExpandedProjectCwds.size > 0118 (persistedExpandedProjectCwds.size > 0
...@@ -181,11 +179,7 @@ function resolveWsHttpOrigin(): string {...@@ -181,11 +179,7 @@ function resolveWsHttpOrigin(): string {
181 try {179 try {
182 const wsUrl = new URL(wsCandidate);180 const wsUrl = new URL(wsCandidate);
183 const protocol =181 const protocol =
184 wsUrl.protocol === "wss:"182 wsUrl.protocol === "wss:" ? "https:" : wsUrl.protocol === "ws:" ? "http:" : wsUrl.protocol;
185 ? "https:"
186 : wsUrl.protocol === "ws:"
187 ? "http:"
188 : wsUrl.protocol;
189 return `${protocol}//${wsUrl.host}`;183 return `${protocol}//${wsUrl.host}`;
190 } catch {184 } catch {
191 return window.location.origin;185 return window.location.origin;
...@@ -205,17 +199,12 @@ function attachmentPreviewRoutePath(attachmentId: string): string {...@@ -205,17 +199,12 @@ function attachmentPreviewRoutePath(attachmentId: string): string {
205199
206// ── Pure state transition functions ────────────────────────────────────200// ── Pure state transition functions ────────────────────────────────────
207201
208export function syncServerReadModel(202export function syncServerReadModel(state: AppState, readModel: OrchestrationReadModel): AppState {
209 state: AppState,
210 readModel: OrchestrationReadModel,
211): AppState {
212 const projects = mapProjectsFromReadModel(203 const projects = mapProjectsFromReadModel(
213 readModel.projects.filter((project) => project.deletedAt === null),204 readModel.projects.filter((project) => project.deletedAt === null),
214 state.projects,205 state.projects,
215 );206 );
216 const existingThreadById = new Map(207 const existingThreadById = new Map(state.threads.map((thread) => [thread.id, thread] as const));
217 state.threads.map((thread) => [thread.id, thread] as const),
218 );
219 const threads = readModel.threads208 const threads = readModel.threads
220 .filter((thread) => thread.deletedAt === null)209 .filter((thread) => thread.deletedAt === null)
221 .map((thread) => {210 .map((thread) => {
...@@ -334,9 +323,7 @@ export function markThreadUnread(state: AppState, threadId: ThreadId): AppState...@@ -334,9 +323,7 @@ export function markThreadUnread(state: AppState, threadId: ThreadId): AppState
334export function toggleProject(state: AppState, projectId: Project["id"]): AppState {323export function toggleProject(state: AppState, projectId: Project["id"]): AppState {
335 return {324 return {
336 ...state,325 ...state,
337 projects: state.projects.map((p) =>326 projects: state.projects.map((p) => (p.id === projectId ? { ...p, expanded: !p.expanded } : p)),
338 p.id === projectId ? { ...p, expanded: !p.expanded } : p,
339 ),
340 };327 };
341}328}
342329
...@@ -390,27 +377,19 @@ interface AppStore extends AppState {...@@ -390,27 +377,19 @@ interface AppStore extends AppState {
390 toggleProject: (projectId: Project["id"]) => void;377 toggleProject: (projectId: Project["id"]) => void;
391 setProjectExpanded: (projectId: Project["id"], expanded: boolean) => void;378 setProjectExpanded: (projectId: Project["id"], expanded: boolean) => void;
392 setError: (threadId: ThreadId, error: string | null) => void;379 setError: (threadId: ThreadId, error: string | null) => void;
393 setThreadBranch: (380 setThreadBranch: (threadId: ThreadId, branch: string | null, worktreePath: string | null) => void;
394 threadId: ThreadId,
395 branch: string | null,
396 worktreePath: string | null,
397 ) => void;
398}381}
399382
400export const useStore = create<AppStore>((set) => ({383export const useStore = create<AppStore>((set) => ({
401 ...readPersistedState(),384 ...readPersistedState(),
402 syncServerReadModel: (readModel) =>385 syncServerReadModel: (readModel) => set((state) => syncServerReadModel(state, readModel)),
403 set((state) => syncServerReadModel(state, readModel)),
404 markThreadVisited: (threadId, visitedAt) =>386 markThreadVisited: (threadId, visitedAt) =>
405 set((state) => markThreadVisited(state, threadId, visitedAt)),387 set((state) => markThreadVisited(state, threadId, visitedAt)),
406 markThreadUnread: (threadId) =>388 markThreadUnread: (threadId) => set((state) => markThreadUnread(state, threadId)),
407 set((state) => markThreadUnread(state, threadId)),389 toggleProject: (projectId) => set((state) => toggleProject(state, projectId)),
408 toggleProject: (projectId) =>
409 set((state) => toggleProject(state, projectId)),
410 setProjectExpanded: (projectId, expanded) =>390 setProjectExpanded: (projectId, expanded) =>
411 set((state) => setProjectExpanded(state, projectId, expanded)),391 set((state) => setProjectExpanded(state, projectId, expanded)),
412 setError: (threadId, error) =>392 setError: (threadId, error) => set((state) => setError(state, threadId, error)),
413 set((state) => setError(state, threadId, error)),
414 setThreadBranch: (threadId, branch, worktreePath) =>393 setThreadBranch: (threadId, branch, worktreePath) =>
415 set((state) => setThreadBranch(state, threadId, branch, worktreePath)),394 set((state) => setThreadBranch(state, threadId, branch, worktreePath)),
416}));395}));
packages/contracts/src/model.ts+2-5
...@@ -29,18 +29,15 @@ export const MODEL_OPTIONS_BY_PROVIDER = {...@@ -29,18 +29,15 @@ export const MODEL_OPTIONS_BY_PROVIDER = {
29 { slug: "gpt-5.2", name: "GPT-5.2" },29 { slug: "gpt-5.2", name: "GPT-5.2" },
30 ],30 ],
31} as const satisfies Record<ProviderKind, readonly ModelOption[]>;31} as const satisfies Record<ProviderKind, readonly ModelOption[]>;
32export type ModelOptionsByProvider = typeof MODEL_OPTIONS_BY_PROVIDER;
3233
33type BuiltInModelSlug = (typeof MODEL_OPTIONS_BY_PROVIDER)[ProviderKind][number]["slug"];34type BuiltInModelSlug = ModelOptionsByProvider[ProviderKind][number]["slug"];
34export type ModelSlug = BuiltInModelSlug | (string & {});35export type ModelSlug = BuiltInModelSlug | (string & {});
3536
36export const DEFAULT_MODEL_BY_PROVIDER = {37export const DEFAULT_MODEL_BY_PROVIDER = {
37 codex: "gpt-5.4",38 codex: "gpt-5.4",
38} as const satisfies Record<ProviderKind, ModelSlug>;39} as const satisfies Record<ProviderKind, ModelSlug>;
3940
40// Backward compatibility for existing Codex-only call sites.
41export const MODEL_OPTIONS = MODEL_OPTIONS_BY_PROVIDER.codex;
42export const DEFAULT_MODEL = DEFAULT_MODEL_BY_PROVIDER.codex;
43
44export const MODEL_SLUG_ALIASES_BY_PROVIDER = {41export const MODEL_SLUG_ALIASES_BY_PROVIDER = {
45 codex: {42 codex: {
46 "5.4": "gpt-5.4",43 "5.4": "gpt-5.4",
packages/shared/src/model.test.ts+8-8
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1import { describe, expect, it } from "vitest";1import { describe, expect, it } from "vitest";
2import { DEFAULT_MODEL, MODEL_OPTIONS } from "@t3tools/contracts";2import { DEFAULT_MODEL_BY_PROVIDER, MODEL_OPTIONS_BY_PROVIDER } from "@t3tools/contracts";
33
4import {4import {
5 getDefaultModel,5 getDefaultModel,
...@@ -31,23 +31,23 @@ describe("normalizeModelSlug", () => {...@@ -31,23 +31,23 @@ describe("normalizeModelSlug", () => {
3131
32describe("resolveModelSlug", () => {32describe("resolveModelSlug", () => {
33 it("returns default only when the model is missing", () => {33 it("returns default only when the model is missing", () => {
34 expect(resolveModelSlug(undefined)).toBe(DEFAULT_MODEL);34 expect(resolveModelSlug(undefined)).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
35 expect(resolveModelSlug(null)).toBe(DEFAULT_MODEL);35 expect(resolveModelSlug(null)).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
36 });36 });
3737
38 it("preserves unknown custom models", () => {38 it("preserves unknown custom models", () => {
39 expect(resolveModelSlug("gpt-4.1")).toBe(DEFAULT_MODEL);39 expect(resolveModelSlug("gpt-4.1")).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
40 expect(resolveModelSlug("custom/internal-model")).toBe(DEFAULT_MODEL);40 expect(resolveModelSlug("custom/internal-model")).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
41 });41 });
4242
43 it("resolves only supported model options", () => {43 it("resolves only supported model options", () => {
44 for (const model of MODEL_OPTIONS) {44 for (const model of MODEL_OPTIONS_BY_PROVIDER.codex) {
45 expect(resolveModelSlug(model.slug)).toBe(model.slug);45 expect(resolveModelSlug(model.slug)).toBe(model.slug);
46 }46 }
47 });47 });
48 it("keeps codex defaults for backward compatibility", () => {48 it("keeps codex defaults for backward compatibility", () => {
49 expect(getDefaultModel()).toBe(DEFAULT_MODEL);49 expect(getDefaultModel()).toBe(DEFAULT_MODEL_BY_PROVIDER.codex);
50 expect(getModelOptions()).toEqual(MODEL_OPTIONS);50 expect(getModelOptions()).toEqual(MODEL_OPTIONS_BY_PROVIDER.codex);
51 });51 });
52});52});
5353
packages/shared/src/model.ts+1-2
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
1import {1import {
2 CODEX_REASONING_EFFORT_OPTIONS,2 CODEX_REASONING_EFFORT_OPTIONS,
3 DEFAULT_MODEL,
4 DEFAULT_MODEL_BY_PROVIDER,3 DEFAULT_MODEL_BY_PROVIDER,
5 MODEL_OPTIONS_BY_PROVIDER,4 MODEL_OPTIONS_BY_PROVIDER,
6 MODEL_SLUG_ALIASES_BY_PROVIDER,5 MODEL_SLUG_ALIASES_BY_PROVIDER,
...@@ -20,7 +19,7 @@ export function getModelOptions(provider: ProviderKind = "codex") {...@@ -20,7 +19,7 @@ export function getModelOptions(provider: ProviderKind = "codex") {
20}19}
2120
22export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug {21export function getDefaultModel(provider: ProviderKind = "codex"): ModelSlug {
23 return DEFAULT_MODEL_BY_PROVIDER[provider] ?? DEFAULT_MODEL;22 return DEFAULT_MODEL_BY_PROVIDER[provider];
24}23}
2524
26export function normalizeModelSlug(25export function normalizeModelSlug(