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?.() ?? '';