From e9f446c9727fd973484ddcc634a61d0278a18882 Mon Sep 17 00:00:00 2001 From: Dafydd Thomas Date: Fri, 13 Mar 2026 09:26:55 +0000 Subject: [PATCH 1/4] feat: scaffold Claude Code provider with Anthropic/Bedrock config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds claudeCode as a second ProviderKind alongside codex. The provider appears in the model picker only once configured in Settings (binary path set, or AWS Bedrock selected as the API provider). Bedrock is explicitly a separate auth path from the Anthropic subscription — users choose between the two in a radio group, with AWS region/profile fields shown only when Bedrock is selected. Changes: - contracts: add claudeCode to ProviderKind, ClaudeCodeProviderStartOptions with apiProvider ("anthropic"|"bedrock"), awsRegion, awsProfile - contracts/model: add claudeCode model catalog, defaults, aliases, reasoning effort - appSettings: add claudeCodeBinaryPath, claudeCodeProvider, claudeCodeAwsRegion, claudeCodeAwsProfile, customClaudeCodeModels - session-logic: resolveProviderOptions() derives claudeCode availability from settings - ProviderModelPicker: consume resolveProviderOptions; show "Configure in Settings" hint instead of "Coming soon" for unconfigured claudeCode - settings page: Claude Code section with binary path, provider radio group, and conditional Bedrock fields (region, profile) This is a draft — no server-side adapter yet. Refs #991 and #759. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/appSettings.ts | 16 ++ .../components/chat/ProviderModelPicker.tsx | 27 +-- apps/web/src/routes/_chat.settings.tsx | 157 +++++++++++++++++- apps/web/src/session-logic.ts | 12 +- packages/contracts/src/model.ts | 13 ++ packages/contracts/src/orchestration.ts | 9 +- packages/contracts/src/provider.ts | 7 + 7 files changed, 227 insertions(+), 14 deletions(-) diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 18e76d2f9..1c3736104 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -12,6 +12,7 @@ export type TimestampFormat = (typeof TIMESTAMP_FORMAT_OPTIONS)[number]; export const DEFAULT_TIMESTAMP_FORMAT: TimestampFormat = "locale"; const BUILT_IN_MODEL_SLUGS_BY_PROVIDER: Record> = { codex: new Set(getModelOptions("codex").map((option) => option.slug)), + claudeCode: new Set(getModelOptions("claudeCode").map((option) => option.slug)), }; const AppSettingsSchema = Schema.Struct({ @@ -21,6 +22,18 @@ const AppSettingsSchema = Schema.Struct({ codexHomePath: Schema.String.check(Schema.isMaxLength(4096)).pipe( Schema.withConstructorDefault(() => Option.some("")), ), + claudeCodeBinaryPath: Schema.String.check(Schema.isMaxLength(4096)).pipe( + Schema.withConstructorDefault(() => Option.some("")), + ), + claudeCodeProvider: Schema.Literals(["anthropic", "bedrock"]).pipe( + Schema.withConstructorDefault(() => Option.some("anthropic")), + ), + claudeCodeAwsRegion: Schema.String.check(Schema.isMaxLength(64)).pipe( + Schema.withConstructorDefault(() => Option.some("")), + ), + claudeCodeAwsProfile: Schema.String.check(Schema.isMaxLength(256)).pipe( + Schema.withConstructorDefault(() => Option.some("")), + ), defaultThreadEnvMode: Schema.Literals(["local", "worktree"]).pipe( Schema.withConstructorDefault(() => Option.some("local")), ), @@ -34,6 +47,9 @@ const AppSettingsSchema = Schema.Struct({ customCodexModels: Schema.Array(Schema.String).pipe( Schema.withConstructorDefault(() => Option.some([])), ), + customClaudeCodeModels: Schema.Array(Schema.String).pipe( + Schema.withConstructorDefault(() => Option.some([])), + ), }); export type AppSettings = typeof AppSettingsSchema.Type; export interface AppModelOption { diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 9bc034991..b2ec5c0e0 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -1,7 +1,8 @@ import { type ModelSlug, type ProviderKind } from "@t3tools/contracts"; import { normalizeModelSlug } from "@t3tools/shared/model"; import { memo, useState } from "react"; -import { type ProviderPickerKind, PROVIDER_OPTIONS } from "../../session-logic"; +import { type ProviderPickerKind, resolveProviderOptions } from "../../session-logic"; +import { useAppSettings } from "../../appSettings"; import { ChevronDownIcon } from "lucide-react"; import { Button } from "../ui/button"; import { @@ -20,12 +21,12 @@ import { import { ClaudeAI, CursorIcon, Gemini, Icon, OpenAI, OpenCodeIcon } from "../Icons"; import { cn } from "~/lib/utils"; -function isAvailableProviderOption(option: (typeof PROVIDER_OPTIONS)[number]): option is { +function isAvailableProviderOption(option: ReturnType[number]): option is { value: ProviderKind; label: string; available: true; } { - return option.available && option.value !== "claudeCode"; + return option.available; } function resolveModelForProviderPicker( @@ -67,8 +68,7 @@ const PROVIDER_ICON_BY_PROVIDER: Record = { cursor: CursorIcon, }; -export const AVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter(isAvailableProviderOption); -const UNAVAILABLE_PROVIDER_OPTIONS = PROVIDER_OPTIONS.filter((option) => !option.available); +// Derived at runtime from settings — see ProviderModelPicker component below. const COMING_SOON_PROVIDER_OPTIONS = [ { id: "opencode", label: "OpenCode", icon: OpenCodeIcon }, { id: "gemini", label: "Gemini", icon: Gemini }, @@ -84,6 +84,13 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { onProviderModelChange: (provider: ProviderKind, model: ModelSlug) => void; }) { const [isMenuOpen, setIsMenuOpen] = useState(false); + const { settings } = useAppSettings(); + const claudeCodeConfigured = + settings.claudeCodeBinaryPath.trim() !== "" || + settings.claudeCodeProvider === "bedrock"; + const providerOptions = resolveProviderOptions(claudeCodeConfigured); + const availableProviderOptions = providerOptions.filter(isAvailableProviderOption); + const unavailableProviderOptions = providerOptions.filter((option) => !option.available); const selectedProviderOptions = props.modelOptionsByProvider[props.provider]; const selectedModelLabel = selectedProviderOptions.find((option) => option.slug === props.model)?.name ?? props.model; @@ -122,7 +129,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { - {AVAILABLE_PROVIDER_OPTIONS.map((option) => { + {availableProviderOptions.map((option) => { const OptionIcon = PROVIDER_ICON_BY_PROVIDER[option.value]; const isDisabledByProviderLock = props.lockedProvider !== null && props.lockedProvider !== option.value; @@ -168,8 +175,8 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { ); })} - {UNAVAILABLE_PROVIDER_OPTIONS.length > 0 && } - {UNAVAILABLE_PROVIDER_OPTIONS.map((option) => { + {unavailableProviderOptions.length > 0 && } + {unavailableProviderOptions.map((option) => { const OptionIcon = PROVIDER_ICON_BY_PROVIDER[option.value]; return ( @@ -182,12 +189,12 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { /> {option.label} - Coming soon + {option.value === "claudeCode" ? "Configure in Settings" : "Coming soon"} ); })} - {UNAVAILABLE_PROVIDER_OPTIONS.length === 0 && } + {unavailableProviderOptions.length === 0 && } {COMING_SOON_PROVIDER_OPTIONS.map((option) => { const OptionIcon = option.icon; return ( diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index b4afcdefa..b532acb26 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -3,7 +3,7 @@ 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 { MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings"; +import { MAX_CUSTOM_MODEL_LENGTH, useAppSettings, type AppSettings } from "../appSettings"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { isElectron } from "../env"; import { useTheme } from "../hooks/useTheme"; @@ -54,6 +54,13 @@ const MODEL_PROVIDER_SETTINGS: Array<{ placeholder: "your-codex-model-slug", example: "gpt-6.7-codex-ultra-preview", }, + { + provider: "claudeCode", + title: "Claude Code", + description: "Save additional Claude Code model slugs for the picker and `/model` command.", + placeholder: "your-claude-model-slug", + example: "claude-opus-4-6-preview", + }, ] as const; const TIMESTAMP_FORMAT_LABELS = { @@ -67,6 +74,8 @@ function getCustomModelsForProvider( provider: ProviderKind, ) { switch (provider) { + case "claudeCode": + return settings.customClaudeCodeModels; case "codex": default: return settings.customCodexModels; @@ -78,14 +87,18 @@ function getDefaultCustomModelsForProvider( provider: ProviderKind, ) { switch (provider) { + case "claudeCode": + return defaults.customClaudeCodeModels; case "codex": default: return defaults.customCodexModels; } } -function patchCustomModels(provider: ProviderKind, models: string[]) { +function patchCustomModels(provider: ProviderKind, models: string[]): Partial { switch (provider) { + case "claudeCode": + return { customClaudeCodeModels: models }; case "codex": default: return { customCodexModels: models }; @@ -102,6 +115,7 @@ function SettingsRouteView() { Record >({ codex: "", + claudeCode: "", }); const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState< Partial> @@ -369,6 +383,145 @@ function SettingsRouteView() { +
+
+

Claude Code

+

+ Configure the Claude Code CLI. Once set, Claude Code will appear as an available + provider in the chat model picker. +

+
+ +
+ + +
+ API provider +

+ Choose how Claude Code authenticates. Select Anthropic to use + your Claude Code subscription, or AWS Bedrock to route requests + through your AWS account using the{" "} + CLAUDE_CODE_USE_BEDROCK environment variable. +

+
+ {( + [ + { + value: "anthropic", + label: "Anthropic", + description: "Use your Claude Code subscription via the Anthropic API.", + }, + { + value: "bedrock", + label: "AWS Bedrock", + description: + "Route requests through your AWS account. Requires AWS credentials and Bedrock model access.", + }, + ] as const + ).map((option) => { + const selected = settings.claudeCodeProvider === option.value; + return ( + + ); + })} +
+
+ + {settings.claudeCodeProvider === "bedrock" && ( +
+ + + +
+ )} + +
+ +
+
+
+

Models

diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index e389f10e2..c9dd1ecc4 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -18,7 +18,7 @@ import type { TurnDiffSummary, } from "./types"; -export type ProviderPickerKind = ProviderKind | "claudeCode" | "cursor"; +export type ProviderPickerKind = ProviderKind | "cursor"; export const PROVIDER_OPTIONS: Array<{ value: ProviderPickerKind; @@ -30,6 +30,16 @@ export const PROVIDER_OPTIONS: Array<{ { value: "cursor", label: "Cursor", available: false }, ]; +export function resolveProviderOptions( + claudeCodeConfigured: boolean, +): typeof PROVIDER_OPTIONS { + return PROVIDER_OPTIONS.map((option) => + option.value === "claudeCode" + ? { ...option, available: claudeCodeConfigured } + : option, + ); +} + export interface WorkLogEntry { id: string; createdAt: string; diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts index 189fbf09d..9fd50bffa 100644 --- a/packages/contracts/src/model.ts +++ b/packages/contracts/src/model.ts @@ -28,6 +28,11 @@ export const MODEL_OPTIONS_BY_PROVIDER = { { slug: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, { slug: "gpt-5.2", name: "GPT-5.2" }, ], + claudeCode: [ + { slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, + { slug: "claude-opus-4-6", name: "Claude Opus 4.6" }, + { slug: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5" }, + ], } as const satisfies Record; export type ModelOptionsByProvider = typeof MODEL_OPTIONS_BY_PROVIDER; @@ -36,6 +41,7 @@ export type ModelSlug = BuiltInModelSlug | (string & {}); export const DEFAULT_MODEL_BY_PROVIDER = { codex: "gpt-5.4", + claudeCode: "claude-sonnet-4-6", } as const satisfies Record; export const MODEL_SLUG_ALIASES_BY_PROVIDER = { @@ -46,12 +52,19 @@ export const MODEL_SLUG_ALIASES_BY_PROVIDER = { "5.3-spark": "gpt-5.3-codex-spark", "gpt-5.3-spark": "gpt-5.3-codex-spark", }, + claudeCode: { + sonnet: "claude-sonnet-4-6", + opus: "claude-opus-4-6", + haiku: "claude-haiku-4-5-20251001", + }, } as const satisfies Record>; export const REASONING_EFFORT_OPTIONS_BY_PROVIDER = { codex: CODEX_REASONING_EFFORT_OPTIONS, + claudeCode: [], } as const satisfies Record; export const DEFAULT_REASONING_EFFORT_BY_PROVIDER = { codex: "high", + claudeCode: null, } as const satisfies Record; diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index 17c5eb21d..8fc729e71 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -27,7 +27,7 @@ export const ORCHESTRATION_WS_CHANNELS = { domainEvent: "orchestration.domainEvent", } as const; -export const ProviderKind = Schema.Literal("codex"); +export const ProviderKind = Schema.Literals(["codex", "claudeCode"]); export type ProviderKind = typeof ProviderKind.Type; export const ProviderApprovalPolicy = Schema.Literals([ "untrusted", @@ -47,8 +47,15 @@ const CodexProviderStartOptions = Schema.Struct({ binaryPath: Schema.optional(TrimmedNonEmptyString), homePath: Schema.optional(TrimmedNonEmptyString), }); +const ClaudeCodeProviderStartOptions = Schema.Struct({ + binaryPath: Schema.optional(TrimmedNonEmptyString), + apiProvider: Schema.optional(Schema.Literals(["anthropic", "bedrock"])), + awsRegion: Schema.optional(TrimmedNonEmptyString), + awsProfile: Schema.optional(TrimmedNonEmptyString), +}); const ProviderStartOptions = Schema.Struct({ codex: Schema.optional(CodexProviderStartOptions), + claudeCode: Schema.optional(ClaudeCodeProviderStartOptions), }); export const RuntimeMode = Schema.Literals(["approval-required", "full-access"]); export type RuntimeMode = typeof RuntimeMode.Type; diff --git a/packages/contracts/src/provider.ts b/packages/contracts/src/provider.ts index 9d2a198b6..24ad5a700 100644 --- a/packages/contracts/src/provider.ts +++ b/packages/contracts/src/provider.ts @@ -51,9 +51,16 @@ const CodexProviderStartOptions = Schema.Struct({ binaryPath: Schema.optional(TrimmedNonEmptyStringSchema), homePath: Schema.optional(TrimmedNonEmptyStringSchema), }); +const ClaudeCodeProviderStartOptions = Schema.Struct({ + binaryPath: Schema.optional(TrimmedNonEmptyStringSchema), + apiProvider: Schema.optional(Schema.Literals(["anthropic", "bedrock"])), + awsRegion: Schema.optional(TrimmedNonEmptyStringSchema), + awsProfile: Schema.optional(TrimmedNonEmptyStringSchema), +}); export const ProviderStartOptions = Schema.Struct({ codex: Schema.optional(CodexProviderStartOptions), + claudeCode: Schema.optional(ClaudeCodeProviderStartOptions), }); export type ProviderStartOptions = typeof ProviderStartOptions.Type; From 889db145623afab761e2518fd91d53b891e8771c Mon Sep 17 00:00:00 2001 From: Dafydd Thomas Date: Fri, 13 Mar 2026 09:32:25 +0000 Subject: [PATCH 2/4] refactor: simplify Claude Code config to Bedrock-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the Anthropic subscription option — the team is already working on Claude Code support via the direct API. This PR focuses solely on AWS Bedrock as an auth path for Claude Code. - Drop apiProvider ("anthropic"|"bedrock") from ClaudeCodeProviderStartOptions - Replace claudeCodeProvider setting with a single claudeCodeUseBedrock boolean - Replace radio group UI with a Switch (consistent with existing settings patterns) - Bedrock fields (awsRegion, awsProfile) still shown conditionally when enabled - claudeCodeConfigured now: binary path set OR useBedrock toggled on Refs #1017, #991, #759 Co-Authored-By: Claude Opus 4.6 --- apps/web/src/appSettings.ts | 4 +- .../components/chat/ProviderModelPicker.tsx | 3 +- apps/web/src/routes/_chat.settings.tsx | 80 ++++++------------- packages/contracts/src/orchestration.ts | 1 - packages/contracts/src/provider.ts | 1 - 5 files changed, 26 insertions(+), 63 deletions(-) diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 1c3736104..48798e5c2 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -25,8 +25,8 @@ const AppSettingsSchema = Schema.Struct({ claudeCodeBinaryPath: Schema.String.check(Schema.isMaxLength(4096)).pipe( Schema.withConstructorDefault(() => Option.some("")), ), - claudeCodeProvider: Schema.Literals(["anthropic", "bedrock"]).pipe( - Schema.withConstructorDefault(() => Option.some("anthropic")), + claudeCodeUseBedrock: Schema.Boolean.pipe( + Schema.withConstructorDefault(() => Option.some(false)), ), claudeCodeAwsRegion: Schema.String.check(Schema.isMaxLength(64)).pipe( Schema.withConstructorDefault(() => Option.some("")), diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index b2ec5c0e0..24bfac589 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -86,8 +86,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { const [isMenuOpen, setIsMenuOpen] = useState(false); const { settings } = useAppSettings(); const claudeCodeConfigured = - settings.claudeCodeBinaryPath.trim() !== "" || - settings.claudeCodeProvider === "bedrock"; + settings.claudeCodeBinaryPath.trim() !== "" || settings.claudeCodeUseBedrock; const providerOptions = resolveProviderOptions(claudeCodeConfigured); const availableProviderOptions = providerOptions.filter(isAvailableProviderOption); const unavailableProviderOptions = providerOptions.filter((option) => !option.available); diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index b532acb26..508166089 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -3,7 +3,7 @@ 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 { MAX_CUSTOM_MODEL_LENGTH, useAppSettings, type AppSettings } from "../appSettings"; +import { MAX_CUSTOM_MODEL_LENGTH, useAppSettings } from "../appSettings"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { isElectron } from "../env"; import { useTheme } from "../hooks/useTheme"; @@ -95,7 +95,7 @@ function getDefaultCustomModelsForProvider( } } -function patchCustomModels(provider: ProviderKind, models: string[]): Partial { +function patchCustomModels(provider: ProviderKind, models: string[]) { switch (provider) { case "claudeCode": return { customClaudeCodeModels: models }; @@ -387,8 +387,8 @@ function SettingsRouteView() {

Claude Code

- Configure the Claude Code CLI. Once set, Claude Code will appear as an available - provider in the chat model picker. + Configure Claude Code for use with AWS Bedrock. Once enabled, Claude Code will + appear as an available provider in the chat model picker.

@@ -411,60 +411,25 @@ function SettingsRouteView() { -
- API provider -

- Choose how Claude Code authenticates. Select Anthropic to use - your Claude Code subscription, or AWS Bedrock to route requests - through your AWS account using the{" "} - CLAUDE_CODE_USE_BEDROCK environment variable. -

-
- {( - [ - { - value: "anthropic", - label: "Anthropic", - description: "Use your Claude Code subscription via the Anthropic API.", - }, - { - value: "bedrock", - label: "AWS Bedrock", - description: - "Route requests through your AWS account. Requires AWS credentials and Bedrock model access.", - }, - ] as const - ).map((option) => { - const selected = settings.claudeCodeProvider === option.value; - return ( - - ); - })} +
+
+

Use AWS Bedrock

+

+ Route Claude Code requests through your AWS account via{" "} + CLAUDE_CODE_USE_BEDROCK. Requires AWS credentials and Bedrock + model access. +

+ + updateSettings({ claudeCodeUseBedrock: Boolean(checked) }) + } + aria-label="Use AWS Bedrock for Claude Code" + />
- {settings.claudeCodeProvider === "bedrock" && ( + {settings.claudeCodeUseBedrock && (
)} @@ -479,6 +527,9 @@ function SettingsRouteView() { claudeCodeUseBedrock: defaults.claudeCodeUseBedrock, claudeCodeAwsRegion: defaults.claudeCodeAwsRegion, claudeCodeAwsProfile: defaults.claudeCodeAwsProfile, + claudeCodeBedrockArnHaiku: defaults.claudeCodeBedrockArnHaiku, + claudeCodeBedrockArnSonnet: defaults.claudeCodeBedrockArnSonnet, + claudeCodeBedrockArnOpus: defaults.claudeCodeBedrockArnOpus, }) } > diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index f2c3d75c5..cbe1167fb 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -51,6 +51,9 @@ const ClaudeCodeProviderStartOptions = Schema.Struct({ binaryPath: Schema.optional(TrimmedNonEmptyString), awsRegion: Schema.optional(TrimmedNonEmptyString), awsProfile: Schema.optional(TrimmedNonEmptyString), + bedrockModelOverrideHaiku: Schema.optional(TrimmedNonEmptyString), + bedrockModelOverrideSonnet: Schema.optional(TrimmedNonEmptyString), + bedrockModelOverrideOpus: Schema.optional(TrimmedNonEmptyString), }); const ProviderStartOptions = Schema.Struct({ codex: Schema.optional(CodexProviderStartOptions), diff --git a/packages/contracts/src/provider.ts b/packages/contracts/src/provider.ts index 95352bd7b..9c3688099 100644 --- a/packages/contracts/src/provider.ts +++ b/packages/contracts/src/provider.ts @@ -55,6 +55,9 @@ const ClaudeCodeProviderStartOptions = Schema.Struct({ binaryPath: Schema.optional(TrimmedNonEmptyStringSchema), awsRegion: Schema.optional(TrimmedNonEmptyStringSchema), awsProfile: Schema.optional(TrimmedNonEmptyStringSchema), + bedrockModelOverrideHaiku: Schema.optional(TrimmedNonEmptyStringSchema), + bedrockModelOverrideSonnet: Schema.optional(TrimmedNonEmptyStringSchema), + bedrockModelOverrideOpus: Schema.optional(TrimmedNonEmptyStringSchema), }); export const ProviderStartOptions = Schema.Struct({ From c55921e9e0a22b464df27d4fc9f3eda5c2f6da7d Mon Sep 17 00:00:00 2001 From: Dafydd Thomas Date: Fri, 13 Mar 2026 09:45:04 +0000 Subject: [PATCH 4/4] fix: resolve all typecheck errors across monorepo - shared/model: add claudeCode to MODEL_SLUG_SET_BY_PROVIDER so resolveModelSlug works correctly for claudeCode provider - session-logic: extract isAvailableProviderOption type guard as an export so ChatView and ProviderModelPicker can share it (narrows ProviderPickerKind to ProviderKind after filtering available options) - ProviderModelPicker: import isAvailableProviderOption from session-logic instead of re-declaring it locally - ChatView: import isAvailableProviderOption from session-logic; use it as the filter predicate for searchableModelOptions to correctly narrow types - ChatView.logic: keep claudeCode in getCustomModelOptionsByProvider return value (required by Record) but pass empty custom models since we're not adding a custom model UI for claudeCode in this PR - appSettings: restore BUILT_IN_MODEL_SLUGS_BY_PROVIDER claudeCode entry (required by Record); remove customClaudeCodeModels - settings: remove Claude Code from Models section; Bedrock-only scope - server test: update LegacyProviderRuntimeEvent.provider from "codex" literal to ProviderKind to satisfy the widened union type Co-Authored-By: Claude Opus 4.6 --- .../Layers/ProviderRuntimeIngestion.test.ts | 4 ++-- apps/web/src/appSettings.ts | 3 --- apps/web/src/components/ChatView.logic.ts | 3 +-- apps/web/src/components/ChatView.tsx | 3 ++- .../src/components/chat/ProviderModelPicker.tsx | 14 +++++--------- apps/web/src/routes/_chat.settings.tsx | 13 ------------- apps/web/src/session-logic.ts | 8 ++++++++ packages/shared/src/model.ts | 1 + 8 files changed, 19 insertions(+), 30 deletions(-) diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index b6b48c7ed..c97fd413b 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { OrchestrationReadModel, ProviderRuntimeEvent } from "@t3tools/contracts"; +import type { OrchestrationReadModel, ProviderKind, ProviderRuntimeEvent } from "@t3tools/contracts"; import { ApprovalRequestId, CommandId, @@ -45,7 +45,7 @@ const asTurnId = (value: string): TurnId => TurnId.makeUnsafe(value); type LegacyProviderRuntimeEvent = { readonly type: string; readonly eventId: EventId; - readonly provider: "codex"; + readonly provider: ProviderKind; readonly createdAt: string; readonly threadId: ThreadId; readonly turnId?: string | undefined; diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 86fc72f6f..8c1079f5c 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -56,9 +56,6 @@ const AppSettingsSchema = Schema.Struct({ customCodexModels: Schema.Array(Schema.String).pipe( Schema.withConstructorDefault(() => Option.some([])), ), - customClaudeCodeModels: Schema.Array(Schema.String).pipe( - Schema.withConstructorDefault(() => Option.some([])), - ), }); export type AppSettings = typeof AppSettingsSchema.Type; export interface AppModelOption { diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 1bec65b9b..d62f92a25 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -118,10 +118,9 @@ export function cloneComposerImageForRetry( export function getCustomModelOptionsByProvider(settings: { customCodexModels: readonly string[]; - customClaudeCodeModels: readonly string[]; }): Record> { return { codex: getAppModelOptions("codex", settings.customCodexModels), - claudeCode: getAppModelOptions("claudeCode", settings.customClaudeCodeModels), + claudeCode: getAppModelOptions("claudeCode", []), }; } diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 56f9df2bc..767e19d71 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -58,6 +58,7 @@ import { hasToolActivityForTurn, isLatestTurnSettled, formatElapsed, + isAvailableProviderOption, resolveProviderOptions, } from "../session-logic"; import { isScrollContainerNearBottom } from "../chat-scroll"; @@ -554,7 +555,7 @@ export default function ChatView({ threadId }: ChatViewProps) { const searchableModelOptions = useMemo( () => resolveProviderOptions(claudeCodeConfigured) - .filter((option) => option.available) + .filter(isAvailableProviderOption) .filter((option) => lockedProvider === null || option.value === lockedProvider) .flatMap((option) => modelOptionsByProvider[option.value].map(({ slug, name }) => ({ diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx index 24bfac589..6d8b62c8b 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.tsx @@ -1,7 +1,11 @@ import { type ModelSlug, type ProviderKind } from "@t3tools/contracts"; import { normalizeModelSlug } from "@t3tools/shared/model"; import { memo, useState } from "react"; -import { type ProviderPickerKind, resolveProviderOptions } from "../../session-logic"; +import { + type ProviderPickerKind, + isAvailableProviderOption, + resolveProviderOptions, +} from "../../session-logic"; import { useAppSettings } from "../../appSettings"; import { ChevronDownIcon } from "lucide-react"; import { Button } from "../ui/button"; @@ -21,14 +25,6 @@ import { import { ClaudeAI, CursorIcon, Gemini, Icon, OpenAI, OpenCodeIcon } from "../Icons"; import { cn } from "~/lib/utils"; -function isAvailableProviderOption(option: ReturnType[number]): option is { - value: ProviderKind; - label: string; - available: true; -} { - return option.available; -} - function resolveModelForProviderPicker( provider: ProviderKind, value: string, diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index b78871fde..60cde6ba7 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -54,13 +54,6 @@ const MODEL_PROVIDER_SETTINGS: Array<{ placeholder: "your-codex-model-slug", example: "gpt-6.7-codex-ultra-preview", }, - { - provider: "claudeCode", - title: "Claude Code", - description: "Save additional Claude Code model slugs for the picker and `/model` command.", - placeholder: "your-claude-model-slug", - example: "claude-opus-4-6-preview", - }, ] as const; const TIMESTAMP_FORMAT_LABELS = { @@ -74,8 +67,6 @@ function getCustomModelsForProvider( provider: ProviderKind, ) { switch (provider) { - case "claudeCode": - return settings.customClaudeCodeModels; case "codex": default: return settings.customCodexModels; @@ -87,8 +78,6 @@ function getDefaultCustomModelsForProvider( provider: ProviderKind, ) { switch (provider) { - case "claudeCode": - return defaults.customClaudeCodeModels; case "codex": default: return defaults.customCodexModels; @@ -97,8 +86,6 @@ function getDefaultCustomModelsForProvider( function patchCustomModels(provider: ProviderKind, models: string[]) { switch (provider) { - case "claudeCode": - return { customClaudeCodeModels: models }; case "codex": default: return { customCodexModels: models }; diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index c9dd1ecc4..7a093968d 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -30,6 +30,14 @@ export const PROVIDER_OPTIONS: Array<{ { value: "cursor", label: "Cursor", available: false }, ]; +export function isAvailableProviderOption(option: (typeof PROVIDER_OPTIONS)[number]): option is { + value: ProviderKind; + label: string; + available: true; +} { + return option.available; +} + export function resolveProviderOptions( claudeCodeConfigured: boolean, ): typeof PROVIDER_OPTIONS { diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 592e2dfb9..ec86e7601 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -12,6 +12,7 @@ type CatalogProvider = keyof typeof MODEL_OPTIONS_BY_PROVIDER; const MODEL_SLUG_SET_BY_PROVIDER: Record> = { codex: new Set(MODEL_OPTIONS_BY_PROVIDER.codex.map((option) => option.slug)), + claudeCode: new Set(MODEL_OPTIONS_BY_PROVIDER.claudeCode.map((option) => option.slug)), }; export function getModelOptions(provider: ProviderKind = "codex") {