diff --git a/packages/ui/src/components/sections/agents/AgentsPage.tsx b/packages/ui/src/components/sections/agents/AgentsPage.tsx index 62b52048..092270fb 100644 --- a/packages/ui/src/components/sections/agents/AgentsPage.tsx +++ b/packages/ui/src/components/sections/agents/AgentsPage.tsx @@ -16,6 +16,7 @@ import { ModelSelector } from './ModelSelector'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { useI18n } from '@/lib/i18n'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; import { Select, SelectContent, @@ -761,8 +762,8 @@ export const AgentsPage: React.FC = () => {
{ if (providerId && modelId) { setModel(`${providerId}/${modelId}`); diff --git a/packages/ui/src/components/sections/commands/CommandsPage.tsx b/packages/ui/src/components/sections/commands/CommandsPage.tsx index dfa7475d..a87cdc3a 100644 --- a/packages/ui/src/components/sections/commands/CommandsPage.tsx +++ b/packages/ui/src/components/sections/commands/CommandsPage.tsx @@ -17,6 +17,7 @@ import { SelectValue, } from '@/components/ui/select'; import { useI18n } from '@/lib/i18n'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; export const CommandsPage: React.FC = () => { const { t } = useI18n(); @@ -295,8 +296,8 @@ export const CommandsPage: React.FC = () => {
{ if (providerId && modelId) { setModel(`${providerId}/${modelId}`); diff --git a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx index 1b71abfc..76d86864 100644 --- a/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/DefaultsSettings.tsx @@ -9,15 +9,14 @@ import { useUIStore } from '@/stores/useUIStore'; import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; const getDisplayModel = ( storedModel: string | undefined ): { providerId: string; modelId: string } => { - if (storedModel) { - const parts = storedModel.split('/'); - if (parts.length === 2 && parts[0] && parts[1]) { - return { providerId: parts[0], modelId: parts[1] }; - } + const parsed = parseModelIdentifier(storedModel); + if (parsed) { + return parsed; } return { providerId: '', modelId: '' }; diff --git a/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx b/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx index 08203abf..8b9e2b19 100644 --- a/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx +++ b/packages/ui/src/components/session/GitHubIssuePickerDialog.tsx @@ -31,6 +31,7 @@ import { useUIStore } from '@/stores/useUIStore'; import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore'; import { opencodeClient } from '@/lib/opencode/client'; import { renderMagicPrompt } from '@/lib/magicPrompts'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; import { createWorktreeSessionForNewBranch } from '@/lib/worktreeSessionCreator'; import { generateBranchSlug } from '@/lib/git/branchNameGenerator'; import type { GitHubIssue, GitHubIssueComment, GitHubIssuesListResult, GitHubIssueSummary, GitHubRepoSelector } from '@/lib/api/types'; @@ -234,14 +235,11 @@ export function GitHubIssuePickerDialog({ return null; } - const parts = settingsDefaultModel.split('/'); - if (parts.length !== 2) { - return null; - } - const [providerID, modelID] = parts; - if (!providerID || !modelID) { + const parsed = parseModelIdentifier(settingsDefaultModel); + if (!parsed) { return null; } + const { providerId: providerID, modelId: modelID } = parsed; const modelMetadata = configState.getModelMetadata(providerID, modelID); if (!modelMetadata) { diff --git a/packages/ui/src/components/session/NewWorktreeDialog.tsx b/packages/ui/src/components/session/NewWorktreeDialog.tsx index 922fbdef..90f271d5 100644 --- a/packages/ui/src/components/session/NewWorktreeDialog.tsx +++ b/packages/ui/src/components/session/NewWorktreeDialog.tsx @@ -51,6 +51,7 @@ import { getRootBranch } from '@/lib/worktrees/worktreeStatus'; import { generateBranchSlug } from '@/lib/git/branchNameGenerator'; import { opencodeClient } from '@/lib/opencode/client'; import { renderMagicPrompt } from '@/lib/magicPrompts'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; import { rankBranchesForQuery } from '@/lib/worktrees/branchSearch'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useGitBranches, useGitStore, useGitLoadingBranches } from '@/stores/useGitStore'; @@ -428,10 +429,9 @@ export function NewWorktreeDialog({ const settingsDefaultModel = configState.settingsDefaultModel; if (!settingsDefaultModel) return null; - const parts = settingsDefaultModel.split('/'); - if (parts.length !== 2) return null; - const [providerID, modelID] = parts; - if (!providerID || !modelID) return null; + const parsed = parseModelIdentifier(settingsDefaultModel); + if (!parsed) return null; + const { providerId: providerID, modelId: modelID } = parsed; const modelMetadata = configState.getModelMetadata(providerID, modelID); if (!modelMetadata) return null; diff --git a/packages/ui/src/lib/modelIdentifier.ts b/packages/ui/src/lib/modelIdentifier.ts new file mode 100644 index 00000000..c90da21a --- /dev/null +++ b/packages/ui/src/lib/modelIdentifier.ts @@ -0,0 +1,20 @@ +export interface ParsedModelIdentifier { + providerId: string; + modelId: string; +} + +export const parseModelIdentifier = (value: string | undefined): ParsedModelIdentifier | null => { + if (!value) { + return null; + } + + const separatorIndex = value.indexOf('/'); + if (separatorIndex <= 0 || separatorIndex >= value.length - 1) { + return null; + } + + return { + providerId: value.slice(0, separatorIndex), + modelId: value.slice(separatorIndex + 1), + }; +}; diff --git a/packages/ui/src/lib/worktreeSessionCreator.ts b/packages/ui/src/lib/worktreeSessionCreator.ts index 303e161d..a89a6962 100644 --- a/packages/ui/src/lib/worktreeSessionCreator.ts +++ b/packages/ui/src/lib/worktreeSessionCreator.ts @@ -12,6 +12,7 @@ import { useContextStore } from '@/stores/contextStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { checkIsGitRepository, previewGitWorktree } from '@/lib/gitApi'; import { generateBranchName } from '@/lib/git/branchNameGenerator'; +import { parseModelIdentifier } from '@/lib/modelIdentifier'; import { getRootBranch } from '@/lib/worktrees/worktreeStatus'; import { getWorktreeSetupCommands } from '@/lib/openchamberConfig'; import { @@ -87,12 +88,12 @@ const applyDefaultAgentAndModelSelection = (sessionId: string, configState = use return; } - const parts = settingsDefaultModel.split('/'); - if (parts.length !== 2) { + const parsed = parseModelIdentifier(settingsDefaultModel); + if (!parsed) { return; } - const [providerId, modelId] = parts; + const { providerId, modelId } = parsed; const modelMetadata = configState.getModelMetadata(providerId, modelId); if (!modelMetadata) { return; diff --git a/packages/ui/src/stores/useConfigStore.ts b/packages/ui/src/stores/useConfigStore.ts index a02eb676..14865b64 100644 --- a/packages/ui/src/stores/useConfigStore.ts +++ b/packages/ui/src/stores/useConfigStore.ts @@ -13,6 +13,7 @@ import { getRegisteredRuntimeAPIs } from "@/contexts/runtimeAPIRegistry"; import { updateDesktopSettings } from "@/lib/persistence"; import { useDirectoryStore } from "@/stores/useDirectoryStore"; import { streamDebugEnabled } from "@/stores/utils/streamDebug"; +import { parseModelIdentifier } from "@/lib/modelIdentifier"; const MODELS_DEV_API_URL = "https://models.dev/api.json"; const MODELS_DEV_PROXY_URL = "/api/openchamber/models-metadata"; @@ -105,14 +106,7 @@ const fetchOpenChamberDefaults = async (): Promise => { }; const parseModelString = (modelString: string): { providerId: string; modelId: string } | null => { - if (!modelString || typeof modelString !== 'string') { - return null; - } - const parts = modelString.split('/'); - if (parts.length !== 2 || !parts[0] || !parts[1]) { - return null; - } - return { providerId: parts[0], modelId: parts[1] }; + return parseModelIdentifier(modelString); }; const normalizeProviderId = (value: string) => value?.toLowerCase?.() ?? '';