From 77f1777f9859c8430280320b5d017ca44f176fce Mon Sep 17 00:00:00 2001 From: SeifAlmotaz Date: Tue, 17 Mar 2026 22:14:23 +0200 Subject: [PATCH 1/7] feat: add configurable model for git text generation - Add gitTextGenerationModel setting to AppSettingsSchema with gpt-5.4-mini as default - Add UI in settings page to select git text generation model - Propagate model parameter from settings through to CodexTextGeneration layer - Add DEFAULT_GIT_TEXT_GENERATION_MODEL constant (gpt-5.4-mini) - Add gpt-5.4-mini to MODEL_OPTIONS_BY_PROVIDER.codex --- .../src/git/Layers/CodexTextGeneration.ts | 9 ++- apps/server/src/git/Layers/GitManager.ts | 13 +++- .../server/src/git/Services/TextGeneration.ts | 6 ++ .../Layers/ProviderCommandReactor.ts | 2 + apps/web/src/appSettings.ts | 1 + apps/web/src/components/GitActionsControl.tsx | 7 +- apps/web/src/lib/gitReactQuery.ts | 2 + apps/web/src/routes/_chat.settings.tsx | 69 ++++++++++++++++++- packages/contracts/src/git.ts | 1 + packages/contracts/src/model.ts | 3 + packages/shared/src/model.ts | 3 + 11 files changed, 110 insertions(+), 6 deletions(-) diff --git a/apps/server/src/git/Layers/CodexTextGeneration.ts b/apps/server/src/git/Layers/CodexTextGeneration.ts index 9a8d1d93f0..a444627c3f 100644 --- a/apps/server/src/git/Layers/CodexTextGeneration.ts +++ b/apps/server/src/git/Layers/CodexTextGeneration.ts @@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto"; import { Effect, FileSystem, Layer, Option, Path, Schema, Stream } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; +import { DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/contracts"; import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; @@ -17,7 +18,6 @@ import { TextGeneration, } from "../Services/TextGeneration.ts"; -const CODEX_MODEL = "gpt-5.3-codex"; const CODEX_REASONING_EFFORT = "low"; const CODEX_TIMEOUT_MS = 180_000; @@ -187,6 +187,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { outputSchemaJson, imagePaths = [], cleanupPaths = [], + model, }: { operation: "generateCommitMessage" | "generatePrContent" | "generateBranchName"; cwd: string; @@ -194,6 +195,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { outputSchemaJson: S; imagePaths?: ReadonlyArray; cleanupPaths?: ReadonlyArray; + model?: string; }): Effect.Effect => Effect.gen(function* () { const schemaPath = yield* writeTempFile( @@ -212,7 +214,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { "-s", "read-only", "--model", - CODEX_MODEL, + model ?? DEFAULT_GIT_TEXT_GENERATION_MODEL, "--config", `model_reasoning_effort="${CODEX_REASONING_EFFORT}"`, "--output-schema", @@ -353,6 +355,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { cwd: input.cwd, prompt, outputSchemaJson, + ...(input.model ? { model: input.model } : {}), }).pipe( Effect.map( (generated) => @@ -398,6 +401,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { title: Schema.String, body: Schema.String, }), + ...(input.model ? { model: input.model } : {}), }).pipe( Effect.map( (generated) => @@ -449,6 +453,7 @@ const makeCodexTextGeneration = Effect.gen(function* () { branch: Schema.String, }), imagePaths, + ...(input.model ? { model: input.model } : {}), }); return { diff --git a/apps/server/src/git/Layers/GitManager.ts b/apps/server/src/git/Layers/GitManager.ts index 8357795173..5745ea76c8 100644 --- a/apps/server/src/git/Layers/GitManager.ts +++ b/apps/server/src/git/Layers/GitManager.ts @@ -639,6 +639,7 @@ export const makeGitManager = Effect.gen(function* () { /** When true, also produce a semantic feature branch name. */ includeBranch?: boolean; filePaths?: readonly string[]; + model?: string; }) => Effect.gen(function* () { const context = yield* gitCore.prepareCommitContext(input.cwd, input.filePaths); @@ -665,6 +666,7 @@ export const makeGitManager = Effect.gen(function* () { stagedSummary: limitContext(context.stagedSummary, 8_000), stagedPatch: limitContext(context.stagedPatch, 50_000), ...(input.includeBranch ? { includeBranch: true } : {}), + ...(input.model ? { model: input.model } : {}), }) .pipe(Effect.map((result) => sanitizeCommitMessage(result))); @@ -682,6 +684,7 @@ export const makeGitManager = Effect.gen(function* () { commitMessage?: string, preResolvedSuggestion?: CommitAndBranchSuggestion, filePaths?: readonly string[], + model?: string, ) => Effect.gen(function* () { const suggestion = @@ -691,6 +694,7 @@ export const makeGitManager = Effect.gen(function* () { branch, ...(commitMessage ? { commitMessage } : {}), ...(filePaths ? { filePaths } : {}), + ...(model ? { model } : {}), })); if (!suggestion) { return { status: "skipped_no_changes" as const }; @@ -704,7 +708,7 @@ export const makeGitManager = Effect.gen(function* () { }; }); - const runPrStep = (cwd: string, fallbackBranch: string | null) => + const runPrStep = (cwd: string, fallbackBranch: string | null, model?: string) => Effect.gen(function* () { const details = yield* gitCore.statusDetails(cwd); const branch = details.branch ?? fallbackBranch; @@ -748,6 +752,7 @@ export const makeGitManager = Effect.gen(function* () { commitSummary: limitContext(rangeContext.commitSummary, 20_000), diffSummary: limitContext(rangeContext.diffSummary, 20_000), diffPatch: limitContext(rangeContext.diffPatch, 60_000), + ...(model ? { model } : {}), }); const bodyFile = path.join(tempDir, `t3code-pr-body-${process.pid}-${randomUUID()}.md`); @@ -972,6 +977,7 @@ export const makeGitManager = Effect.gen(function* () { branch: string | null, commitMessage?: string, filePaths?: readonly string[], + model?: string, ) => Effect.gen(function* () { const suggestion = yield* resolveCommitAndBranchSuggestion({ @@ -980,6 +986,7 @@ export const makeGitManager = Effect.gen(function* () { ...(commitMessage ? { commitMessage } : {}), ...(filePaths ? { filePaths } : {}), includeBranch: true, + ...(model ? { model } : {}), }); if (!suggestion) { return yield* gitManagerError( @@ -1028,6 +1035,7 @@ export const makeGitManager = Effect.gen(function* () { initialStatus.branch, input.commitMessage, input.filePaths, + input.model, ); branchStep = result.branchStep; commitMessageForStep = result.resolvedCommitMessage; @@ -1044,6 +1052,7 @@ export const makeGitManager = Effect.gen(function* () { commitMessageForStep, preResolvedCommitSuggestion, input.filePaths, + input.model, ); const push = wantsPush @@ -1051,7 +1060,7 @@ export const makeGitManager = Effect.gen(function* () { : { status: "skipped_not_requested" as const }; const pr = wantsPr - ? yield* runPrStep(input.cwd, currentBranch) + ? yield* runPrStep(input.cwd, currentBranch, input.model) : { status: "skipped_not_requested" as const }; return { diff --git a/apps/server/src/git/Services/TextGeneration.ts b/apps/server/src/git/Services/TextGeneration.ts index daae27fe66..b4650ed570 100644 --- a/apps/server/src/git/Services/TextGeneration.ts +++ b/apps/server/src/git/Services/TextGeneration.ts @@ -19,6 +19,8 @@ export interface CommitMessageGenerationInput { stagedPatch: string; /** When true, the model also returns a semantic branch name for the change. */ includeBranch?: boolean; + /** Model to use for generation. Defaults to gpt-5.4-mini if not specified. */ + model?: string; } export interface CommitMessageGenerationResult { @@ -35,6 +37,8 @@ export interface PrContentGenerationInput { commitSummary: string; diffSummary: string; diffPatch: string; + /** Model to use for generation. Defaults to gpt-5.4-mini if not specified. */ + model?: string; } export interface PrContentGenerationResult { @@ -46,6 +50,8 @@ export interface BranchNameGenerationInput { cwd: string; message: string; attachments?: ReadonlyArray | undefined; + /** Model to use for generation. Defaults to gpt-5.4-mini if not specified. */ + model?: string; } export interface BranchNameGenerationResult { diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index fe02188450..d8203bd93d 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -1,6 +1,7 @@ import { type ChatAttachment, CommandId, + DEFAULT_GIT_TEXT_GENERATION_MODEL, EventId, type OrchestrationEvent, type ProviderModelOptions, @@ -391,6 +392,7 @@ const make = Effect.gen(function* () { cwd, message: input.messageText, ...(attachments.length > 0 ? { attachments } : {}), + model: DEFAULT_GIT_TEXT_GENERATION_MODEL, }) .pipe( Effect.catch((error) => diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 18e76d2f92..eff751067d 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -34,6 +34,7 @@ const AppSettingsSchema = Schema.Struct({ customCodexModels: Schema.Array(Schema.String).pipe( Schema.withConstructorDefault(() => Option.some([])), ), + gitTextGenerationModel: Schema.String.pipe(Schema.withConstructorDefault(() => Option.some(""))), }); export type AppSettings = typeof AppSettingsSchema.Type; export interface AppModelOption { diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index e4fad02af2..8d349cdd92 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -1,4 +1,5 @@ import type { GitStackedAction, GitStatusResult, ThreadId } from "@t3tools/contracts"; +import { DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/shared/model"; import { useIsMutating, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ChevronDownIcon, CloudUploadIcon, GitCommitIcon, InfoIcon } from "lucide-react"; @@ -15,6 +16,7 @@ import { resolveQuickAction, summarizeGitResult, } from "./GitActionsControl.logic"; +import { useAppSettings } from "~/appSettings"; import { Button } from "~/components/ui/button"; import { Checkbox } from "~/components/ui/checkbox"; import { @@ -154,6 +156,9 @@ function GitQuickActionIcon({ quickAction }: { quickAction: GitQuickAction }) { } export default function GitActionsControl({ gitCwd, activeThreadId }: GitActionsControlProps) { + const { settings } = useAppSettings(); + const gitTextGenerationModel = + settings.gitTextGenerationModel || DEFAULT_GIT_TEXT_GENERATION_MODEL; const threadToastData = useMemo( () => (activeThreadId ? { threadId: activeThreadId } : undefined), [activeThreadId], @@ -191,7 +196,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions const initMutation = useMutation(gitInitMutationOptions({ cwd: gitCwd, queryClient })); const runImmediateGitActionMutation = useMutation( - gitRunStackedActionMutationOptions({ cwd: gitCwd, queryClient }), + gitRunStackedActionMutationOptions({ cwd: gitCwd, queryClient, model: gitTextGenerationModel }), ); const pullMutation = useMutation(gitPullMutationOptions({ cwd: gitCwd, queryClient })); diff --git a/apps/web/src/lib/gitReactQuery.ts b/apps/web/src/lib/gitReactQuery.ts index 9b5fe7731f..b1cfa8ce9c 100644 --- a/apps/web/src/lib/gitReactQuery.ts +++ b/apps/web/src/lib/gitReactQuery.ts @@ -112,6 +112,7 @@ export function gitCheckoutMutationOptions(input: { export function gitRunStackedActionMutationOptions(input: { cwd: string | null; queryClient: QueryClient; + model?: string; }) { return mutationOptions({ mutationKey: gitMutationKeys.runStackedAction(input.cwd), @@ -134,6 +135,7 @@ export function gitRunStackedActionMutationOptions(input: { ...(commitMessage ? { commitMessage } : {}), ...(featureBranch ? { featureBranch } : {}), ...(filePaths ? { filePaths } : {}), + ...(input.model ? { model: input.model } : {}), }); }, onSettled: async () => { diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index b4afcdefa1..db9f853ba5 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -2,7 +2,11 @@ import { createFileRoute } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { useCallback, useState } from "react"; import { type ProviderKind } from "@t3tools/contracts"; -import { getModelOptions, normalizeModelSlug } from "@t3tools/shared/model"; +import { + DEFAULT_GIT_TEXT_GENERATION_MODEL, + getModelOptions, + normalizeModelSlug, +} from "@t3tools/shared/model"; import { MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { isElectron } from "../env"; @@ -502,6 +506,69 @@ function SettingsRouteView() { +
+
+

Git

+

+ Configure the model used for generating commit messages, PR titles, and branch + names. +

+
+ +
+
+

Text generation model

+

+ Model used for auto-generated git content. Faster models are recommended. + Default: {DEFAULT_GIT_TEXT_GENERATION_MODEL} +

+
+ +
+ + {settings.gitTextGenerationModel !== defaults.gitTextGenerationModel ? ( +
+ +
+ ) : null} +
+

Threads

diff --git a/packages/contracts/src/git.ts b/packages/contracts/src/git.ts index 081b4d0d82..ed7d93ebd5 100644 --- a/packages/contracts/src/git.ts +++ b/packages/contracts/src/git.ts @@ -64,6 +64,7 @@ export const GitRunStackedActionInput = Schema.Struct({ filePaths: Schema.optional( Schema.Array(TrimmedNonEmptyStringSchema).check(Schema.isMinLength(1)), ), + model: Schema.optional(Schema.String), }); export type GitRunStackedActionInput = typeof GitRunStackedActionInput.Type; diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts index 189fbf09dc..2b1174e237 100644 --- a/packages/contracts/src/model.ts +++ b/packages/contracts/src/model.ts @@ -23,6 +23,7 @@ type ModelOption = { export const MODEL_OPTIONS_BY_PROVIDER = { codex: [ { slug: "gpt-5.4", name: "GPT-5.4" }, + { slug: "gpt-5.4-mini", name: "GPT-5.4 Mini" }, { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, { slug: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark" }, { slug: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, @@ -38,6 +39,8 @@ export const DEFAULT_MODEL_BY_PROVIDER = { codex: "gpt-5.4", } as const satisfies Record; +export const DEFAULT_GIT_TEXT_GENERATION_MODEL = "gpt-5.4-mini" as const; + export const MODEL_SLUG_ALIASES_BY_PROVIDER = { codex: { "5.4": "gpt-5.4", diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 592e2dfb9f..527d804050 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -1,5 +1,6 @@ import { CODEX_REASONING_EFFORT_OPTIONS, + DEFAULT_GIT_TEXT_GENERATION_MODEL, DEFAULT_MODEL_BY_PROVIDER, MODEL_OPTIONS_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, @@ -8,6 +9,8 @@ import { type ProviderKind, } from "@t3tools/contracts"; +export { DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/contracts"; + type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER; const MODEL_SLUG_SET_BY_PROVIDER: Record> = { From bebef05ee54c26155bc81734c485e88f1df70ef8 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 17 Mar 2026 15:07:33 -0700 Subject: [PATCH 2/7] Fix git text model selector labels and custom model options - Build git text generation model options via `getAppModelOptions` - Show the selected model's display name in the select trigger - Improve settings row layout for better responsiveness --- apps/web/src/routes/_chat.settings.tsx | 37 +++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index db9f853ba5..6f539ae372 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -7,7 +7,7 @@ import { getModelOptions, normalizeModelSlug, } from "@t3tools/shared/model"; -import { MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings"; +import { getAppModelOptions, MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { isElectron } from "../env"; import { useTheme } from "../hooks/useTheme"; @@ -115,6 +115,16 @@ function SettingsRouteView() { const codexHomePath = settings.codexHomePath; const keybindingsConfigPath = serverConfigQuery.data?.keybindingsConfigPath ?? null; const availableEditors = serverConfigQuery.data?.availableEditors; + const selectedGitTextGenerationModel = + settings.gitTextGenerationModel || DEFAULT_GIT_TEXT_GENERATION_MODEL; + const gitTextGenerationModelOptions = getAppModelOptions( + "codex", + settings.customCodexModels, + selectedGitTextGenerationModel, + ); + const selectedGitTextGenerationModelLabel = + gitTextGenerationModelOptions.find((option) => option.slug === selectedGitTextGenerationModel) + ?.name ?? selectedGitTextGenerationModel; const openKeybindingsFile = useCallback(() => { if (!keybindingsConfigPath) return; @@ -515,16 +525,15 @@ function SettingsRouteView() {

-
-
+
+

Text generation model

- Model used for auto-generated git content. Faster models are recommended. - Default: {DEFAULT_GIT_TEXT_GENERATION_MODEL} + Model used for auto-generated git content.

From b44a6fe637c88289524a004c2aaae1b8c8d95884 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 17 Mar 2026 15:09:25 -0700 Subject: [PATCH 3/7] remove reexport --- apps/web/src/components/GitActionsControl.tsx | 2 +- apps/web/src/routes/_chat.settings.tsx | 8 ++------ packages/shared/src/model.ts | 3 --- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 8d349cdd92..f321ede4d3 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -1,5 +1,5 @@ import type { GitStackedAction, GitStatusResult, ThreadId } from "@t3tools/contracts"; -import { DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/shared/model"; +import { DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/contracts"; import { useIsMutating, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ChevronDownIcon, CloudUploadIcon, GitCommitIcon, InfoIcon } from "lucide-react"; diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index 6f539ae372..65eed355da 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -1,12 +1,8 @@ import { createFileRoute } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { useCallback, useState } from "react"; -import { type ProviderKind } from "@t3tools/contracts"; -import { - DEFAULT_GIT_TEXT_GENERATION_MODEL, - getModelOptions, - normalizeModelSlug, -} from "@t3tools/shared/model"; +import { type ProviderKind, DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/contracts"; +import { getModelOptions, normalizeModelSlug } from "@t3tools/shared/model"; import { getAppModelOptions, MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { isElectron } from "../env"; diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 527d804050..592e2dfb9f 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -1,6 +1,5 @@ import { CODEX_REASONING_EFFORT_OPTIONS, - DEFAULT_GIT_TEXT_GENERATION_MODEL, DEFAULT_MODEL_BY_PROVIDER, MODEL_OPTIONS_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, @@ -9,8 +8,6 @@ import { type ProviderKind, } from "@t3tools/contracts"; -export { DEFAULT_GIT_TEXT_GENERATION_MODEL } from "@t3tools/contracts"; - type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER; const MODEL_SLUG_SET_BY_PROVIDER: Record> = { From 6027c96137c8d08515905d811779e27741ace1ae Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 17 Mar 2026 15:15:30 -0700 Subject: [PATCH 4/7] handle default server side --- apps/server/src/git/Layers/GitManager.ts | 6 ++-- apps/web/src/appSettings.ts | 4 +-- apps/web/src/components/GitActionsControl.tsx | 8 ++++-- apps/web/src/lib/gitReactQuery.ts | 2 +- apps/web/src/routes/_chat.settings.tsx | 28 +++++++++++-------- packages/contracts/src/git.ts | 7 +++-- 6 files changed, 32 insertions(+), 23 deletions(-) diff --git a/apps/server/src/git/Layers/GitManager.ts b/apps/server/src/git/Layers/GitManager.ts index 5745ea76c8..1a3cf2bb35 100644 --- a/apps/server/src/git/Layers/GitManager.ts +++ b/apps/server/src/git/Layers/GitManager.ts @@ -1035,7 +1035,7 @@ export const makeGitManager = Effect.gen(function* () { initialStatus.branch, input.commitMessage, input.filePaths, - input.model, + input.textGenerationModel, ); branchStep = result.branchStep; commitMessageForStep = result.resolvedCommitMessage; @@ -1052,7 +1052,7 @@ export const makeGitManager = Effect.gen(function* () { commitMessageForStep, preResolvedCommitSuggestion, input.filePaths, - input.model, + input.textGenerationModel, ); const push = wantsPush @@ -1060,7 +1060,7 @@ export const makeGitManager = Effect.gen(function* () { : { status: "skipped_not_requested" as const }; const pr = wantsPr - ? yield* runPrStep(input.cwd, currentBranch, input.model) + ? yield* runPrStep(input.cwd, currentBranch, input.textGenerationModel) : { status: "skipped_not_requested" as const }; return { diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index eff751067d..d060c2ef06 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -1,6 +1,6 @@ import { useCallback } from "react"; import { Option, Schema } from "effect"; -import { type ProviderKind } from "@t3tools/contracts"; +import { TrimmedNonEmptyString, type ProviderKind } from "@t3tools/contracts"; import { getDefaultModel, getModelOptions, normalizeModelSlug } from "@t3tools/shared/model"; import { useLocalStorage } from "./hooks/useLocalStorage"; @@ -34,7 +34,7 @@ const AppSettingsSchema = Schema.Struct({ customCodexModels: Schema.Array(Schema.String).pipe( Schema.withConstructorDefault(() => Option.some([])), ), - gitTextGenerationModel: Schema.String.pipe(Schema.withConstructorDefault(() => Option.some(""))), + textGenerationModel: Schema.optional(TrimmedNonEmptyString), }); export type AppSettings = typeof AppSettingsSchema.Type; export interface AppModelOption { diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index f321ede4d3..5fdd739a0a 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -157,8 +157,6 @@ function GitQuickActionIcon({ quickAction }: { quickAction: GitQuickAction }) { export default function GitActionsControl({ gitCwd, activeThreadId }: GitActionsControlProps) { const { settings } = useAppSettings(); - const gitTextGenerationModel = - settings.gitTextGenerationModel || DEFAULT_GIT_TEXT_GENERATION_MODEL; const threadToastData = useMemo( () => (activeThreadId ? { threadId: activeThreadId } : undefined), [activeThreadId], @@ -196,7 +194,11 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions const initMutation = useMutation(gitInitMutationOptions({ cwd: gitCwd, queryClient })); const runImmediateGitActionMutation = useMutation( - gitRunStackedActionMutationOptions({ cwd: gitCwd, queryClient, model: gitTextGenerationModel }), + gitRunStackedActionMutationOptions({ + cwd: gitCwd, + queryClient, + model: settings.textGenerationModel ?? null, + }), ); const pullMutation = useMutation(gitPullMutationOptions({ cwd: gitCwd, queryClient })); diff --git a/apps/web/src/lib/gitReactQuery.ts b/apps/web/src/lib/gitReactQuery.ts index b1cfa8ce9c..d520f73c1c 100644 --- a/apps/web/src/lib/gitReactQuery.ts +++ b/apps/web/src/lib/gitReactQuery.ts @@ -112,7 +112,7 @@ export function gitCheckoutMutationOptions(input: { export function gitRunStackedActionMutationOptions(input: { cwd: string | null; queryClient: QueryClient; - model?: string; + model?: string | null; }) { return mutationOptions({ mutationKey: gitMutationKeys.runStackedAction(input.cwd), diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index 65eed355da..efe0301e48 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -111,16 +111,15 @@ function SettingsRouteView() { const codexHomePath = settings.codexHomePath; const keybindingsConfigPath = serverConfigQuery.data?.keybindingsConfigPath ?? null; const availableEditors = serverConfigQuery.data?.availableEditors; - const selectedGitTextGenerationModel = - settings.gitTextGenerationModel || DEFAULT_GIT_TEXT_GENERATION_MODEL; + const gitTextGenerationModelOptions = getAppModelOptions( "codex", settings.customCodexModels, - selectedGitTextGenerationModel, + settings.textGenerationModel, ); const selectedGitTextGenerationModelLabel = - gitTextGenerationModelOptions.find((option) => option.slug === selectedGitTextGenerationModel) - ?.name ?? selectedGitTextGenerationModel; + gitTextGenerationModelOptions.find((option) => option.slug === settings.textGenerationModel) + ?.name ?? settings.textGenerationModel; const openKeybindingsFile = useCallback(() => { if (!keybindingsConfigPath) return; @@ -529,12 +528,17 @@ function SettingsRouteView() {

{ - if (value && value !== DEFAULT_GIT_TEXT_GENERATION_MODEL) { + if (value) { updateSettings({ textGenerationModel: value, }); - } else if (value === DEFAULT_GIT_TEXT_GENERATION_MODEL) { - updateSettings({ - textGenerationModel: undefined, - }); } }} > From bea44b5db2c028910499e1f452ab56e50eba544d Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 17 Mar 2026 15:22:46 -0700 Subject: [PATCH 7/7] add to test --- apps/web/src/appSettings.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/appSettings.test.ts b/apps/web/src/appSettings.test.ts index 326bceaacf..15579809a4 100644 --- a/apps/web/src/appSettings.test.ts +++ b/apps/web/src/appSettings.test.ts @@ -28,6 +28,7 @@ describe("getAppModelOptions", () => { expect(options.map((option) => option.slug)).toEqual([ "gpt-5.4", + "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.3-codex-spark", "gpt-5.2-codex",