Skip to content

Commit f4390cd

Browse files
committed
feat: migrate legacy mux-gateway model format to canonical form
Add forward compatibility for users who have directly specified mux-gateway models in their config: - migrateGatewayModel(): converts 'mux-gateway:provider/model' to 'provider:model' and auto-enables gateway toggle for that model - Applied in useSendMessageOptions, getSendOptionsFromStorage - useModelLRU: migrates LRU list and default model on load - Malformed gateway entries are filtered out Example: 'mux-gateway:anthropic/claude-opus-4-5' becomes 'anthropic:claude-opus-4-5' with gateway toggle enabled
1 parent 94840ff commit f4390cd

File tree

4 files changed

+67
-7
lines changed

4 files changed

+67
-7
lines changed

src/browser/hooks/useGatewayModels.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,50 @@ export function isGatewayEnabled(modelId: string): boolean {
3030
return gatewayModels.includes(modelId);
3131
}
3232

33+
/**
34+
* Check if a model string is in mux-gateway format.
35+
* @param modelId Model string to check
36+
* @returns true if model is "mux-gateway:provider/model" format
37+
*/
38+
export function isGatewayFormat(modelId: string): boolean {
39+
return modelId.startsWith("mux-gateway:");
40+
}
41+
42+
/**
43+
* Migrate a mux-gateway model to canonical format and enable gateway toggle.
44+
* Converts "mux-gateway:provider/model" to "provider:model" and marks it for gateway routing.
45+
*
46+
* This provides forward compatibility for users who have directly specified
47+
* mux-gateway models in their config.
48+
*
49+
* @param modelId Model string that may be in gateway format
50+
* @returns Canonical model ID (e.g., "anthropic:claude-opus-4-5")
51+
*/
52+
export function migrateGatewayModel(modelId: string): string {
53+
if (!isGatewayFormat(modelId)) {
54+
return modelId;
55+
}
56+
57+
// mux-gateway:anthropic/claude-opus-4-5 → anthropic:claude-opus-4-5
58+
const inner = modelId.slice("mux-gateway:".length);
59+
const slashIndex = inner.indexOf("/");
60+
if (slashIndex === -1) {
61+
return modelId; // Malformed, return as-is
62+
}
63+
64+
const provider = inner.slice(0, slashIndex);
65+
const model = inner.slice(slashIndex + 1);
66+
const canonicalId = `${provider}:${model}`;
67+
68+
// Auto-enable gateway for this model (one-time migration)
69+
const gatewayModels = readPersistedState<string[]>(GATEWAY_MODELS_KEY, []);
70+
if (!gatewayModels.includes(canonicalId)) {
71+
updatePersistedState(GATEWAY_MODELS_KEY, [...gatewayModels, canonicalId]);
72+
}
73+
74+
return canonicalId;
75+
}
76+
3377
/**
3478
* Check if the gateway provider is available (has coupon code configured)
3579
*/

src/browser/hooks/useModelLRU.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { MODEL_ABBREVIATIONS } from "@/browser/utils/slashCommands/registry";
44
import { defaultModel } from "@/common/utils/ai/models";
55
import { WORKSPACE_DEFAULTS } from "@/constants/workspaceDefaults";
66
import { useAPI } from "@/browser/contexts/API";
7+
import { migrateGatewayModel, isGatewayFormat } from "./useGatewayModels";
78

89
const MAX_LRU_SIZE = 12;
910
const LRU_KEY = "model-lru";
@@ -36,7 +37,9 @@ export function evictModelFromLRU(model: string): void {
3637

3738
export function getDefaultModel(): string {
3839
const persisted = readPersistedState<string | null>(DEFAULT_MODEL_KEY, null);
39-
return persisted ?? FALLBACK_MODEL;
40+
if (!persisted) return FALLBACK_MODEL;
41+
// Migrate legacy mux-gateway format to canonical form
42+
return migrateGatewayModel(persisted);
4043
}
4144

4245
/**
@@ -60,10 +63,18 @@ export function useModelLRU() {
6063
{ listener: true }
6164
);
6265

63-
// Merge any new defaults from MODEL_ABBREVIATIONS (only once on mount)
66+
// Merge any new defaults from MODEL_ABBREVIATIONS and migrate legacy gateway models (only once on mount)
6467
useEffect(() => {
6568
setRecentModels((prev) => {
66-
const merged = [...prev];
69+
// Migrate any mux-gateway:provider/model entries to canonical form
70+
const migrated = prev.map((m) => migrateGatewayModel(m));
71+
// Remove any remaining mux-gateway entries that couldn't be migrated
72+
const filtered = migrated.filter((m) => !isGatewayFormat(m));
73+
// Deduplicate (migration might create duplicates)
74+
const deduped = [...new Set(filtered)];
75+
76+
// Merge defaults
77+
const merged = [...deduped];
6778
for (const defaultModel of DEFAULT_MODELS) {
6879
if (!merged.includes(defaultModel)) {
6980
merged.push(defaultModel);

src/browser/hooks/useSendMessageOptions.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useThinkingLevel } from "./useThinkingLevel";
22
import { useMode } from "@/browser/contexts/ModeContext";
33
import { usePersistedState } from "./usePersistedState";
44
import { getDefaultModel } from "./useModelLRU";
5-
import { toGatewayModel } from "./useGatewayModels";
5+
import { toGatewayModel, migrateGatewayModel } from "./useGatewayModels";
66
import { modeToToolPolicy, PLAN_MODE_INSTRUCTION } from "@/common/utils/ui/modeUtils";
77
import { getModelKey } from "@/common/constants/storage";
88
import type { SendMessageOptions } from "@/common/orpc/types";
@@ -27,9 +27,12 @@ function constructSendMessageOptions(
2727
const additionalSystemInstructions = mode === "plan" ? PLAN_MODE_INSTRUCTION : undefined;
2828

2929
// Ensure model is always a valid string (defensive against corrupted localStorage)
30-
const baseModel =
30+
const rawModel =
3131
typeof preferredModel === "string" && preferredModel ? preferredModel : fallbackModel;
3232

33+
// Migrate any legacy mux-gateway:provider/model format to canonical form
34+
const baseModel = migrateGatewayModel(rawModel);
35+
3336
// Transform to gateway format if gateway is enabled for this model
3437
const model = toGatewayModel(baseModel);
3538

src/browser/utils/messages/sendOptions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getModelKey, getThinkingLevelKey, getModeKey } from "@/common/constants
22
import { modeToToolPolicy, PLAN_MODE_INSTRUCTION } from "@/common/utils/ui/modeUtils";
33
import { readPersistedState } from "@/browser/hooks/usePersistedState";
44
import { getDefaultModel } from "@/browser/hooks/useModelLRU";
5-
import { toGatewayModel } from "@/browser/hooks/useGatewayModels";
5+
import { toGatewayModel, migrateGatewayModel } from "@/browser/hooks/useGatewayModels";
66
import type { SendMessageOptions } from "@/common/orpc/types";
77
import type { UIMode } from "@/common/types/mode";
88
import type { ThinkingLevel } from "@/common/types/thinking";
@@ -39,7 +39,9 @@ function getProviderOptions(): MuxProviderOptions {
3939
*/
4040
export function getSendOptionsFromStorage(workspaceId: string): SendMessageOptions {
4141
// Read model preference (workspace-specific), fallback to LRU default
42-
const baseModel = readPersistedState<string>(getModelKey(workspaceId), getDefaultModel());
42+
const rawModel = readPersistedState<string>(getModelKey(workspaceId), getDefaultModel());
43+
// Migrate any legacy mux-gateway:provider/model format to canonical form
44+
const baseModel = migrateGatewayModel(rawModel);
4345
// Transform to gateway format if gateway is enabled for this model
4446
const model = toGatewayModel(baseModel);
4547

0 commit comments

Comments
 (0)