Unified · Split
| @@ -1,50 +0,0 @@ | ||
| 1 | −/** | |
| 2 | − * Client-only settings - persisted in localStorage. | |
| 3 | − * | |
| 4 | − * These settings affect only the frontend UX and never need to be read by | |
| 5 | − * the server. Server-authoritative settings live in `ServerSettings` | |
| 6 | − * (defined in `@t3tools/contracts/server`). | |
| 7 | − */ | |
| 8 | −import { Option, Schema } from "effect"; | |
| 9 | − | |
| 10 | −export const CLIENT_SETTINGS_STORAGE_KEY = "t3code:client-settings:v1"; | |
| 11 | − | |
| 12 | −export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"]); | |
| 13 | −export type TimestampFormat = typeof TimestampFormat.Type; | |
| 14 | −export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale"; | |
| 15 | − | |
| 16 | −export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]); | |
| 17 | −export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type; | |
| 18 | −export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at"; | |
| 19 | − | |
| 20 | −export const SidebarThreadSortOrder = Schema.Literals(["updated_at", "created_at"]); | |
| 21 | −export type SidebarThreadSortOrder = typeof SidebarThreadSortOrder.Type; | |
| 22 | −export const DEFAULT_SIDEBAR_THREAD_SORT_ORDER: SidebarThreadSortOrder = "updated_at"; | |
| 23 | − | |
| 24 | −const withDefaults = | |
| 25 | − < | |
| 26 | − S extends Schema.Top & Schema.WithoutConstructorDefault, | |
| 27 | − D extends S["~type.make.in"] & S["Encoded"], | |
| 28 | − >( | |
| 29 | − fallback: () => D, | |
| 30 | − ) => | |
| 31 | − (schema: S) => | |
| 32 | − schema.pipe( | |
| 33 | − Schema.withConstructorDefault(() => Option.some(fallback())), | |
| 34 | − Schema.withDecodingDefault(() => fallback()), | |
| 35 | − ); | |
| 36 | − | |
| 37 | −export const ClientSettingsSchema = Schema.Struct({ | |
| 38 | − confirmThreadDelete: Schema.Boolean.pipe(withDefaults(() => true)), | |
| 39 | − diffWordWrap: Schema.Boolean.pipe(withDefaults(() => false)), | |
| 40 | − sidebarProjectSortOrder: SidebarProjectSortOrder.pipe( | |
| 41 | − withDefaults(() => DEFAULT_SIDEBAR_PROJECT_SORT_ORDER), | |
| 42 | − ), | |
| 43 | − sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( | |
| 44 | − withDefaults(() => DEFAULT_SIDEBAR_THREAD_SORT_ORDER), | |
| 45 | − ), | |
| 46 | − timestampFormat: TimestampFormat.pipe(withDefaults(() => DEFAULT_TIMESTAMP_FORMAT)), | |
| 47 | −}); | |
| 48 | −export type ClientSettings = typeof ClientSettingsSchema.Type; | |
| 49 | − | |
| 50 | −export const DEFAULT_CLIENT_SETTINGS: ClientSettings = ClientSettingsSchema.makeUnsafe({}); | |
| @@ -31,7 +31,7 @@ import { isMacPlatform } from "../lib/utils"; · 30 unmodified lines | ||
| 31 | 31 | import { getRouter } from "../router"; |
| 32 | 32 | import { useStore } from "../store"; |
| 33 | 33 | import { estimateTimelineMessageHeight } from "./timelineHeight"; |
| 34 | −import { DEFAULT_CLIENT_SETTINGS } from "~/clientSettings"; | |
| 34 | +import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; | |
| 35 | 35 | |
| 36 | 36 | const THREAD_ID = "thread-browser-test" as ThreadId; |
| 37 | 37 | const UUID_ROUTE_RE = /^\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; |
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { memo, useState, useCallback } from "react"; |
| 2 | −import { type TimestampFormat } from "../clientSettings"; | |
| 2 | +import { type TimestampFormat } from "@t3tools/contracts/settings"; | |
| 3 | 3 | import { Badge } from "./ui/badge"; |
| 4 | 4 | import { Button } from "./ui/button"; |
| 5 | 5 | import { ScrollArea } from "./ui/scroll-area"; |
| @@ -1,4 +1,4 @@ | ||
| 1 | −import type { SidebarProjectSortOrder, SidebarThreadSortOrder } from "../clientSettings"; | |
| 1 | +import type { SidebarProjectSortOrder, SidebarThreadSortOrder } from "@t3tools/contracts/settings"; | |
| 2 | 2 | import type { Thread } from "../types"; |
| 3 | 3 | import { cn } from "../lib/utils"; |
| 4 | 4 | import { |
| @@ -38,7 +38,10 @@ import { · 37 unmodified lines | ||
| 38 | 38 | } from "@t3tools/contracts"; |
| 39 | 39 | import { useMutation, useQueries, useQuery, useQueryClient } from "@tanstack/react-query"; |
| 40 | 40 | import { useLocation, useNavigate, useParams } from "@tanstack/react-router"; |
| 41 | −import { type SidebarProjectSortOrder, type SidebarThreadSortOrder } from "../clientSettings"; | |
| 41 | +import { | |
| 42 | + type SidebarProjectSortOrder, | |
| 43 | + type SidebarThreadSortOrder, | |
| 44 | +} from "@t3tools/contracts/settings"; | |
| 42 | 45 | import { isElectron } from "../env"; |
| 43 | 46 | import { APP_STAGE_LABEL, APP_VERSION } from "../branding"; |
| 44 | 47 | import { isLinuxPlatform, isMacPlatform, newCommandId, newProjectId } from "../lib/utils"; |
| @@ -48,7 +48,7 @@ import { · 47 unmodified lines | ||
| 48 | 48 | type ParsedTerminalContextEntry, |
| 49 | 49 | } from "~/lib/terminalContext"; |
| 50 | 50 | import { cn } from "~/lib/utils"; |
| 51 | −import { type TimestampFormat } from "../../clientSettings"; | |
| 51 | +import { type TimestampFormat } from "@t3tools/contracts/settings"; | |
| 52 | 52 | import { formatTimestamp } from "../../timestampFormat"; |
| 53 | 53 | import { |
| 54 | 54 | buildInlineTerminalContextText, |
| @@ -22,7 +22,7 @@ import { · 21 unmodified lines | ||
| 22 | 22 | useComposerThreadDraft, |
| 23 | 23 | useEffectiveComposerModelState, |
| 24 | 24 | } from "../../composerDraftStore"; |
| 25 | −import { DEFAULT_CLIENT_SETTINGS } from "~/clientSettings"; | |
| 25 | +import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; | |
| 26 | 26 | |
| 27 | 27 | // ── Claude TraitsPicker tests ───────────────────────────────────────── |
| 28 | 28 | |
| @@ -19,16 +19,19 @@ import { · 18 unmodified lines | ||
| 19 | 19 | ThreadEnvMode, |
| 20 | 20 | } from "@t3tools/contracts"; |
| 21 | 21 | import { DEFAULT_SERVER_SETTINGS } from "@t3tools/contracts"; |
| 22 | +import { | |
| 23 | + type ClientSettings, | |
| 24 | + ClientSettingsSchema, | |
| 25 | + CLIENT_SETTINGS_STORAGE_KEY, | |
| 26 | + DEFAULT_CLIENT_SETTINGS, | |
| 27 | + SidebarProjectSortOrder, | |
| 28 | + SidebarThreadSortOrder, | |
| 29 | + TimestampFormat, | |
| 30 | +} from "@t3tools/contracts/settings"; | |
| 22 | 31 | import { serverConfigQueryOptions, serverQueryKeys } from "~/lib/serverReactQuery"; |
| 23 | 32 | import { ensureNativeApi } from "~/nativeApi"; |
| 24 | 33 | import { useLocalStorage } from "./useLocalStorage"; |
| 25 | 34 | import { normalizeCustomModelSlugs } from "~/modelSelection"; |
| 26 | −import { | |
| 27 | − ClientSettingsSchema, | |
| 28 | − type ClientSettings, | |
| 29 | − DEFAULT_CLIENT_SETTINGS, | |
| 30 | − CLIENT_SETTINGS_STORAGE_KEY, | |
| 31 | −} from "~/clientSettings"; | |
| 32 | 35 | import { Predicate, Schema, Struct } from "effect"; |
| 33 | 36 | import { DeepMutable } from "effect/Types"; |
| 34 | 37 | import { deepMerge } from "@t3tools/shared/Struct"; |
| @@ -199,9 +202,38 @@ export function buildLegacyServerSettingsMigrationPatch(legacySettings: Record<s · 164 unmodified lines | ||
| 199 | 202 | return patch; |
| 200 | 203 | } |
| 201 | 204 | |
| 205 | +export function buildLegacyClientSettingsMigrationPatch( | |
| 206 | + legacySettings: Record<string, unknown>, | |
| 207 | +): Partial<DeepMutable<ClientSettings>> { | |
| 208 | + const patch: Partial<DeepMutable<ClientSettings>> = {}; | |
| 209 | + | |
| 210 | + if (Predicate.isBoolean(legacySettings.confirmThreadDelete)) { | |
| 211 | + patch.confirmThreadDelete = legacySettings.confirmThreadDelete; | |
| 212 | + } | |
| 213 | + | |
| 214 | + if (Predicate.isBoolean(legacySettings.diffWordWrap)) { | |
| 215 | + patch.diffWordWrap = legacySettings.diffWordWrap; | |
| 216 | + } | |
| 217 | + | |
| 218 | + if (Schema.is(SidebarProjectSortOrder)(legacySettings.sidebarProjectSortOrder)) { | |
| 219 | + patch.sidebarProjectSortOrder = legacySettings.sidebarProjectSortOrder; | |
| 220 | + } | |
| 221 | + | |
| 222 | + if (Schema.is(SidebarThreadSortOrder)(legacySettings.sidebarThreadSortOrder)) { | |
| 223 | + patch.sidebarThreadSortOrder = legacySettings.sidebarThreadSortOrder; | |
| 224 | + } | |
| 225 | + | |
| 226 | + if (Schema.is(TimestampFormat)(legacySettings.timestampFormat)) { | |
| 227 | + patch.timestampFormat = legacySettings.timestampFormat; | |
| 228 | + } | |
| 229 | + | |
| 230 | + return patch; | |
| 231 | +} | |
| 232 | + | |
| 202 | 233 | /** |
| 203 | − * Call once on app startup. Migrates server-relevant settings from the | |
| 204 | − * old localStorage key to the server. Idempotent. | |
| 234 | + * Call once on app startup. Migrates settings from the old unified | |
| 235 | + * localStorage key to the server (server-relevant keys) and to the new | |
| 236 | + * client-settings localStorage key (client-only keys). Idempotent. | |
| 205 | 237 | */ |
| 206 | 238 | export function migrateLocalSettingsToServer(): void { |
| 207 | 239 | if (typeof window === "undefined") return; |
| @@ -217,19 +249,27 @@ export function migrateLocalSettingsToServer(): void { · 9 unmodified lines | ||
| 217 | 249 | const old = JSON.parse(raw); |
| 218 | 250 | if (!Predicate.isObject(old)) return; |
| 219 | 251 | |
| 220 | − const api = ensureNativeApi(); | |
| 221 | − | |
| 252 | + // Migrate server-relevant keys via RPC | |
| 222 | 253 | const serverPatch = buildLegacyServerSettingsMigrationPatch(old); |
| 254 | + if (Object.keys(serverPatch).length > 0) { | |
| 255 | + const api = ensureNativeApi(); | |
| 256 | + void api.server.updateSettings(serverPatch); | |
| 257 | + } | |
| 223 | 258 | |
| 224 | − if (Object.keys(serverPatch).length === 0) { | |
| 225 | − localStorage.setItem(MIGRATION_FLAG_KEY, "true"); | |
| 226 | − return; | |
| 259 | + // Migrate client-only keys to the new localStorage key | |
| 260 | + const clientPatch = buildLegacyClientSettingsMigrationPatch(old); | |
| 261 | + if (Object.keys(clientPatch).length > 0) { | |
| 262 | + const existing = localStorage.getItem(CLIENT_SETTINGS_STORAGE_KEY); | |
| 263 | + const current = existing ? (JSON.parse(existing) as Record<string, unknown>) : {}; | |
| 264 | + localStorage.setItem( | |
| 265 | + CLIENT_SETTINGS_STORAGE_KEY, | |
| 266 | + JSON.stringify({ ...current, ...clientPatch }), | |
| 267 | + ); | |
| 227 | 268 | } |
| 228 | 269 | |
| 229 | − void api.server.updateSettings(serverPatch); | |
| 230 | 270 | localStorage.setItem(MIGRATION_FLAG_KEY, "true"); |
| 231 | 271 | } catch (error) { |
| 232 | − console.error("[MIGRATION] Error migrating local settings to server:", error); | |
| 272 | + console.error("[MIGRATION] Error migrating local settings:", error); | |
| 233 | 273 | } finally { |
| 234 | 274 | // Mark as migrated no matter what to avoid retrying |
| 235 | 275 | localStorage.setItem(MIGRATION_FLAG_KEY, "true"); |
| @@ -1,4 +1,4 @@ | ||
| 1 | −import { type TimestampFormat } from "./clientSettings"; | |
| 1 | +import { type TimestampFormat } from "@t3tools/contracts/settings"; | |
| 2 | 2 | |
| 3 | 3 | export function getTimestampFormatOptions( |
| 4 | 4 | timestampFormat: TimestampFormat, |
| @@ -10,6 +10,11 @@ · 9 unmodified lines | ||
| 10 | 10 | "module": "./dist/index.mjs", |
| 11 | 11 | "types": "./src/index.ts", |
| 12 | 12 | "exports": { |
| 13 | + "./settings": { | |
| 14 | + "types": "./src/settings.ts", | |
| 15 | + "import": "./src/settings.ts", | |
| 16 | + "require": "./src/settings.ts" | |
| 17 | + }, | |
| 13 | 18 | ".": { |
| 14 | 19 | "types": "./src/index.ts", |
| 15 | 20 | "import": "./src/index.ts", |
| @@ -2,100 +2,24 @@ import { Schema } from "effect"; · 1 unmodified line | ||
| 2 | 2 | import { IsoDateTime, TrimmedNonEmptyString } from "./baseSchemas"; |
| 3 | 3 | import { KeybindingRule, ResolvedKeybindingsConfig } from "./keybindings"; |
| 4 | 4 | import { EditorId } from "./editor"; |
| 5 | +import { ProviderKind } from "./orchestration"; | |
| 5 | 6 | import { |
| 6 | − ClaudeModelOptions, | |
| 7 | − CodexModelOptions, | |
| 8 | − DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER, | |
| 9 | −} from "./model"; | |
| 10 | −import { ModelSelection, ProviderKind } from "./orchestration"; | |
| 11 | − | |
| 12 | −// ── Server Settings (server-authoritative) ─────────────────────────── | |
| 13 | − | |
| 14 | −export const ThreadEnvMode = Schema.Literals(["local", "worktree"]); | |
| 15 | −export type ThreadEnvMode = typeof ThreadEnvMode.Type; | |
| 16 | − | |
| 17 | −export const CodexSettings = Schema.Struct({ | |
| 18 | − binaryPath: Schema.String.pipe(Schema.withDecodingDefault(() => "")), | |
| 19 | − homePath: Schema.String.pipe(Schema.withDecodingDefault(() => "")), | |
| 20 | − customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(() => [])), | |
| 21 | −}); | |
| 22 | −export type CodexSettings = typeof CodexSettings.Type; | |
| 23 | − | |
| 24 | −export const ClaudeSettings = Schema.Struct({ | |
| 25 | − binaryPath: Schema.String.pipe(Schema.withDecodingDefault(() => "")), | |
| 26 | − customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(() => [])), | |
| 27 | −}); | |
| 28 | −export type ClaudeSettings = typeof ClaudeSettings.Type; | |
| 29 | − | |
| 30 | −export const ServerSettings = Schema.Struct({ | |
| 31 | − enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(() => false)), | |
| 32 | − defaultThreadEnvMode: ThreadEnvMode.pipe( | |
| 33 | − Schema.withDecodingDefault(() => "local" as const satisfies ThreadEnvMode), | |
| 34 | − ), | |
| 35 | − textGenerationModelSelection: ModelSelection.pipe( | |
| 36 | − Schema.withDecodingDefault(() => ({ | |
| 37 | − provider: "codex" as const, | |
| 38 | − model: DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER.codex, | |
| 39 | − })), | |
| 40 | − ), | |
| 41 | − | |
| 42 | − // Provider specific settings | |
| 43 | − providers: Schema.Struct({ | |
| 44 | − codex: CodexSettings.pipe(Schema.withDecodingDefault(() => ({}))), | |
| 45 | − claudeAgent: ClaudeSettings.pipe(Schema.withDecodingDefault(() => ({}))), | |
| 46 | − }).pipe(Schema.withDecodingDefault(() => ({}))), | |
| 47 | −}); | |
| 48 | −export type ServerSettings = typeof ServerSettings.Type; | |
| 49 | − | |
| 50 | −export const DEFAULT_SERVER_SETTINGS: ServerSettings = Schema.decodeUnknownSync(ServerSettings)({}); | |
| 51 | − | |
| 52 | −const CodexModelOptionsPatch = Schema.Struct({ | |
| 53 | − reasoningEffort: Schema.optionalKey(CodexModelOptions.fields.reasoningEffort), | |
| 54 | − fastMode: Schema.optionalKey(CodexModelOptions.fields.fastMode), | |
| 55 | −}); | |
| 56 | − | |
| 57 | −const ClaudeModelOptionsPatch = Schema.Struct({ | |
| 58 | − thinking: Schema.optionalKey(ClaudeModelOptions.fields.thinking), | |
| 59 | − effort: Schema.optionalKey(ClaudeModelOptions.fields.effort), | |
| 60 | − fastMode: Schema.optionalKey(ClaudeModelOptions.fields.fastMode), | |
| 61 | −}); | |
| 62 | − | |
| 63 | −const ModelSelectionPatch = Schema.Union([ | |
| 64 | − Schema.Struct({ | |
| 65 | − provider: Schema.optionalKey(Schema.Literal("codex")), | |
| 66 | − model: Schema.optionalKey(TrimmedNonEmptyString), | |
| 67 | − options: Schema.optionalKey(CodexModelOptionsPatch), | |
| 68 | − }), | |
| 69 | − Schema.Struct({ | |
| 70 | − provider: Schema.optionalKey(Schema.Literal("claudeAgent")), | |
| 71 | − model: Schema.optionalKey(TrimmedNonEmptyString), | |
| 72 | − options: Schema.optionalKey(ClaudeModelOptionsPatch), | |
| 73 | − }), | |
| 74 | −]); | |
| 75 | − | |
| 76 | −const CodexSettingsPatch = Schema.Struct({ | |
| 77 | − binaryPath: Schema.optionalKey(Schema.String), | |
| 78 | − homePath: Schema.optionalKey(Schema.String), | |
| 79 | − customModels: Schema.optionalKey(Schema.Array(Schema.String)), | |
| 80 | −}); | |
| 81 | − | |
| 82 | −const ClaudeSettingsPatch = Schema.Struct({ | |
| 83 | − binaryPath: Schema.optionalKey(Schema.String), | |
| 84 | − customModels: Schema.optionalKey(Schema.Array(Schema.String)), | |
| 85 | −}); | |
| 86 | − | |
| 87 | −export const ServerSettingsPatch = Schema.Struct({ | |
| 88 | − enableAssistantStreaming: Schema.optionalKey(Schema.Boolean), | |
| 89 | − defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode), | |
| 90 | − textGenerationModelSelection: Schema.optionalKey(ModelSelectionPatch), | |
| 91 | − providers: Schema.optionalKey( | |
| 92 | − Schema.Struct({ | |
| 93 | − codex: Schema.optionalKey(CodexSettingsPatch), | |
| 94 | − claudeAgent: Schema.optionalKey(ClaudeSettingsPatch), | |
| 95 | − }), | |
| 96 | − ), | |
| 97 | −}); | |
| 98 | −export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; | |
| 7 | + DEFAULT_SERVER_SETTINGS, | |
| 8 | + CodexSettings, | |
| 9 | + ClaudeSettings, | |
| 10 | + ServerSettings, | |
| 11 | + ServerSettingsPatch, | |
| 12 | + ThreadEnvMode, | |
| 13 | +} from "./settings"; | |
| 14 | + | |
| 15 | +export { | |
| 16 | + DEFAULT_SERVER_SETTINGS, | |
| 17 | + ServerSettings, | |
| 18 | + ServerSettingsPatch, | |
| 19 | + ThreadEnvMode, | |
| 20 | + CodexSettings, | |
| 21 | + ClaudeSettings, | |
| 22 | +}; | |
| 99 | 23 | |
| 100 | 24 | const KeybindingsMalformedConfigIssue = Schema.Struct({ |
| 101 | 25 | kind: Schema.Literal("keybindings.malformed-config"), |
| @@ -0,0 +1,129 @@ | ||
| 1 | +import * as Schema from "effect/Schema"; | |
| 2 | +import { TrimmedNonEmptyString } from "./baseSchemas"; | |
| 3 | +import { | |
| 4 | + ClaudeModelOptions, | |
| 5 | + CodexModelOptions, | |
| 6 | + DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER, | |
| 7 | +} from "./model"; | |
| 8 | +import { ModelSelection } from "./orchestration"; | |
| 9 | + | |
| 10 | +// ── Client Settings (local-only) ─────────────────────────────── | |
| 11 | + | |
| 12 | +export const CLIENT_SETTINGS_STORAGE_KEY = "t3code:client-settings:v1"; | |
| 13 | + | |
| 14 | +export const TimestampFormat = Schema.Literals(["locale", "12-hour", "24-hour"]); | |
| 15 | +export type TimestampFormat = typeof TimestampFormat.Type; | |
| 16 | +export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale"; | |
| 17 | + | |
| 18 | +export const SidebarProjectSortOrder = Schema.Literals(["updated_at", "created_at", "manual"]); | |
| 19 | +export type SidebarProjectSortOrder = typeof SidebarProjectSortOrder.Type; | |
| 20 | +export const DEFAULT_SIDEBAR_PROJECT_SORT_ORDER: SidebarProjectSortOrder = "updated_at"; | |
| 21 | + | |
| 22 | +export const SidebarThreadSortOrder = Schema.Literals(["updated_at", "created_at"]); | |
| 23 | +export type SidebarThreadSortOrder = typeof SidebarThreadSortOrder.Type; | |
| 24 | +export const DEFAULT_SIDEBAR_THREAD_SORT_ORDER: SidebarThreadSortOrder = "updated_at"; | |
| 25 | + | |
| 26 | +export const ClientSettingsSchema = Schema.Struct({ | |
| 27 | + confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(() => true)), | |
| 28 | + diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(() => false)), | |
| 29 | + sidebarProjectSortOrder: SidebarProjectSortOrder.pipe( | |
| 30 | + Schema.withDecodingDefault(() => DEFAULT_SIDEBAR_PROJECT_SORT_ORDER), | |
| 31 | + ), | |
| 32 | + sidebarThreadSortOrder: SidebarThreadSortOrder.pipe( | |
| 33 | + Schema.withDecodingDefault(() => DEFAULT_SIDEBAR_THREAD_SORT_ORDER), | |
| 34 | + ), | |
| 35 | + timestampFormat: TimestampFormat.pipe(Schema.withDecodingDefault(() => DEFAULT_TIMESTAMP_FORMAT)), | |
| 36 | +}); | |
| 37 | +export type ClientSettings = typeof ClientSettingsSchema.Type; | |
| 38 | + | |
| 39 | +export const DEFAULT_CLIENT_SETTINGS: ClientSettings = Schema.decodeSync(ClientSettingsSchema)({}); | |
| 40 | + | |
| 41 | +// ── Server Settings (server-authoritative) ──────────────────── | |
| 42 | + | |
| 43 | +export const ThreadEnvMode = Schema.Literals(["local", "worktree"]); | |
| 44 | +export type ThreadEnvMode = typeof ThreadEnvMode.Type; | |
| 45 | + | |
| 46 | +export const CodexSettings = Schema.Struct({ | |
| 47 | + binaryPath: Schema.String.pipe(Schema.withDecodingDefault(() => "")), | |
| 48 | + homePath: Schema.String.pipe(Schema.withDecodingDefault(() => "")), | |
| 49 | + customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(() => [])), | |
| 50 | +}); | |
| 51 | +export type CodexSettings = typeof CodexSettings.Type; | |
| 52 | + | |
| 53 | +export const ClaudeSettings = Schema.Struct({ | |
| 54 | + binaryPath: Schema.String.pipe(Schema.withDecodingDefault(() => "")), | |
| 55 | + customModels: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(() => [])), | |
| 56 | +}); | |
| 57 | +export type ClaudeSettings = typeof ClaudeSettings.Type; | |
| 58 | + | |
| 59 | +export const ServerSettings = Schema.Struct({ | |
| 60 | + enableAssistantStreaming: Schema.Boolean.pipe(Schema.withDecodingDefault(() => false)), | |
| 61 | + defaultThreadEnvMode: ThreadEnvMode.pipe( | |
| 62 | + Schema.withDecodingDefault(() => "local" as const satisfies ThreadEnvMode), | |
| 63 | + ), | |
| 64 | + textGenerationModelSelection: ModelSelection.pipe( | |
| 65 | + Schema.withDecodingDefault(() => ({ | |
| 66 | + provider: "codex" as const, | |
| 67 | + model: DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER.codex, | |
| 68 | + })), | |
| 69 | + ), | |
| 70 | + | |
| 71 | + // Provider specific settings | |
| 72 | + providers: Schema.Struct({ | |
| 73 | + codex: CodexSettings.pipe(Schema.withDecodingDefault(() => ({}))), | |
| 74 | + claudeAgent: ClaudeSettings.pipe(Schema.withDecodingDefault(() => ({}))), | |
| 75 | + }).pipe(Schema.withDecodingDefault(() => ({}))), | |
| 76 | +}); | |
| 77 | +export type ServerSettings = typeof ServerSettings.Type; | |
| 78 | + | |
| 79 | +export const DEFAULT_SERVER_SETTINGS: ServerSettings = Schema.decodeSync(ServerSettings)({}); | |
| 80 | + | |
| 81 | +// ── Server Settings Patch (replace with a Schema.deepPartial if available) ────────────────────────────────────────── | |
| 82 | + | |
| 83 | +const CodexModelOptionsPatch = Schema.Struct({ | |
| 84 | + reasoningEffort: Schema.optionalKey(CodexModelOptions.fields.reasoningEffort), | |
| 85 | + fastMode: Schema.optionalKey(CodexModelOptions.fields.fastMode), | |
| 86 | +}); | |
| 87 | + | |
| 88 | +const ClaudeModelOptionsPatch = Schema.Struct({ | |
| 89 | + thinking: Schema.optionalKey(ClaudeModelOptions.fields.thinking), | |
| 90 | + effort: Schema.optionalKey(ClaudeModelOptions.fields.effort), | |
| 91 | + fastMode: Schema.optionalKey(ClaudeModelOptions.fields.fastMode), | |
| 92 | +}); | |
| 93 | + | |
| 94 | +const ModelSelectionPatch = Schema.Union([ | |
| 95 | + Schema.Struct({ | |
| 96 | + provider: Schema.optionalKey(Schema.Literal("codex")), | |
| 97 | + model: Schema.optionalKey(TrimmedNonEmptyString), | |
| 98 | + options: Schema.optionalKey(CodexModelOptionsPatch), | |
| 99 | + }), | |
| 100 | + Schema.Struct({ | |
| 101 | + provider: Schema.optionalKey(Schema.Literal("claudeAgent")), | |
| 102 | + model: Schema.optionalKey(TrimmedNonEmptyString), | |
| 103 | + options: Schema.optionalKey(ClaudeModelOptionsPatch), | |
| 104 | + }), | |
| 105 | +]); | |
| 106 | + | |
| 107 | +const CodexSettingsPatch = Schema.Struct({ | |
| 108 | + binaryPath: Schema.optionalKey(Schema.String), | |
| 109 | + homePath: Schema.optionalKey(Schema.String), | |
| 110 | + customModels: Schema.optionalKey(Schema.Array(Schema.String)), | |
| 111 | +}); | |
| 112 | + | |
| 113 | +const ClaudeSettingsPatch = Schema.Struct({ | |
| 114 | + binaryPath: Schema.optionalKey(Schema.String), | |
| 115 | + customModels: Schema.optionalKey(Schema.Array(Schema.String)), | |
| 116 | +}); | |
| 117 | + | |
| 118 | +export const ServerSettingsPatch = Schema.Struct({ | |
| 119 | + enableAssistantStreaming: Schema.optionalKey(Schema.Boolean), | |
| 120 | + defaultThreadEnvMode: Schema.optionalKey(ThreadEnvMode), | |
| 121 | + textGenerationModelSelection: Schema.optionalKey(ModelSelectionPatch), | |
| 122 | + providers: Schema.optionalKey( | |
| 123 | + Schema.Struct({ | |
| 124 | + codex: Schema.optionalKey(CodexSettingsPatch), | |
| 125 | + claudeAgent: Schema.optionalKey(ClaudeSettingsPatch), | |
| 126 | + }), | |
| 127 | + ), | |
| 128 | +}); | |
| 129 | +export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; | |