mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
Add Claude Code routing preference for unprefixed Claude models (#3540)
Integrated into release/v3.8.22 — page.tsx conflict resolved (re-applied toggle to ProviderDetailPageClient) + disable-test updated for catalog drift in review.
This commit is contained in:
@@ -292,6 +292,12 @@ ALLOW_API_KEY_REVEAL=false
|
||||
# Default: 5000 | Minimum: 1000
|
||||
# OMNIROUTE_PAYLOAD_RULES_RELOAD_MS=5000
|
||||
|
||||
# Prefer Claude Code OAuth for unprefixed Claude-family model IDs such as
|
||||
# claude-sonnet-4-6 or newly released IDs like claude-fable-5.
|
||||
# Used by: open-sse/services/model.ts. Explicit provider prefixes still win.
|
||||
# Default: false
|
||||
# OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS=false
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
# 7. URLS & CLOUD SYNC
|
||||
# ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -1519,4 +1525,3 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# OpenCode-style API key (sk-...) for the regenerated opencode.json. Used by:
|
||||
# scripts/ad-hoc/regen-opencode-config.ts. Falls back to OMNIROUTE_KEY.
|
||||
# OPENCODE_API_KEY=
|
||||
|
||||
|
||||
@@ -272,6 +272,40 @@ async function getActiveProviderSet() {
|
||||
}
|
||||
}
|
||||
|
||||
function isTruthyEnv(value: string | undefined) {
|
||||
return typeof value === "string" && /^(1|true|yes|on)$/i.test(value.trim());
|
||||
}
|
||||
|
||||
async function getPreferClaudeCodeForUnprefixedClaudeModels() {
|
||||
try {
|
||||
const { getCachedSettings } = await import("@/lib/localDb");
|
||||
const settings = (await getCachedSettings()) as Record<string, unknown>;
|
||||
if (typeof settings.preferClaudeCodeForUnprefixedClaudeModels === "boolean") {
|
||||
return settings.preferClaudeCodeForUnprefixedClaudeModels;
|
||||
}
|
||||
} catch {
|
||||
// Standalone open-sse usage may not have the app DB layer available.
|
||||
}
|
||||
|
||||
return isTruthyEnv(process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS);
|
||||
}
|
||||
|
||||
function shouldPreferClaudeCodeForUnprefixedClaudeModel(
|
||||
modelId: string,
|
||||
activeProviders: Set<string> | null,
|
||||
preferClaudeCode: boolean
|
||||
) {
|
||||
if (!preferClaudeCode || !/^claude-/i.test(modelId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If DB/provider state is unavailable in a lightweight runtime, honor the
|
||||
// explicit operator flag and let the normal credential path report any missing
|
||||
// Claude Code account. When state is available, avoid stealing traffic from
|
||||
// other Claude-family providers unless Claude Code is actually active.
|
||||
return activeProviders === null || activeProviders.size === 0 || activeProviders.has("claude");
|
||||
}
|
||||
|
||||
function shouldTreatAsExactModelId(modelStr: string | null) {
|
||||
if (!modelStr || typeof modelStr !== "string" || !modelStr.includes("/")) return false;
|
||||
if (!KNOWN_MODEL_IDS.has(modelStr)) return false;
|
||||
@@ -448,7 +482,10 @@ async function resolveModelByProviderInference(modelId: string, extendedContext:
|
||||
};
|
||||
}
|
||||
|
||||
const activeProviders = await getActiveProviderSet();
|
||||
const [activeProviders, preferClaudeCodeForUnprefixedClaudeModels] = await Promise.all([
|
||||
getActiveProviderSet(),
|
||||
getPreferClaudeCodeForUnprefixedClaudeModels(),
|
||||
]);
|
||||
|
||||
// Preserve historical behavior: OpenAI stays default when model exists there.
|
||||
// Connection availability must not make unprefixed OpenAI models resolve to a
|
||||
@@ -485,6 +522,21 @@ async function resolveModelByProviderInference(modelId: string, extendedContext:
|
||||
|
||||
const candidatesToUse = nonOpenAIProviders;
|
||||
|
||||
if (
|
||||
candidatesToUse.includes("claude") &&
|
||||
shouldPreferClaudeCodeForUnprefixedClaudeModel(
|
||||
modelId,
|
||||
activeProviders,
|
||||
preferClaudeCodeForUnprefixedClaudeModels
|
||||
)
|
||||
) {
|
||||
return {
|
||||
provider: "claude",
|
||||
model: resolveInferredProviderModel("claude", modelId),
|
||||
extendedContext,
|
||||
};
|
||||
}
|
||||
|
||||
if (candidatesToUse.length === 1) {
|
||||
const provider = candidatesToUse[0];
|
||||
const canonicalModel = resolveInferredProviderModel(provider, modelId);
|
||||
@@ -510,6 +562,15 @@ async function resolveModelByProviderInference(modelId: string, extendedContext:
|
||||
// FIX #73: Models like claude-haiku-4-5-20251001 sent without provider prefix
|
||||
// would incorrectly route to OpenAI. Use heuristic prefix detection first.
|
||||
if (/^claude-/i.test(modelId)) {
|
||||
if (
|
||||
shouldPreferClaudeCodeForUnprefixedClaudeModel(
|
||||
modelId,
|
||||
activeProviders,
|
||||
preferClaudeCodeForUnprefixedClaudeModels
|
||||
)
|
||||
) {
|
||||
return { provider: "claude", model: modelId, extendedContext };
|
||||
}
|
||||
// Claude models → Anthropic provider (canonical source for Claude models)
|
||||
return { provider: "anthropic", model: modelId, extendedContext };
|
||||
}
|
||||
|
||||
@@ -1222,6 +1222,15 @@ export default function ProviderDetailPageClient() {
|
||||
const [codexSettingsLoaded, setCodexSettingsLoaded] = useState(false);
|
||||
const [codexSettingsLoadError, setCodexSettingsLoadError] = useState<string | null>(null);
|
||||
const [savingCodexGlobalServiceMode, setSavingCodexGlobalServiceMode] = useState(false);
|
||||
const [
|
||||
preferClaudeCodeForUnprefixedClaudeModels,
|
||||
setPreferClaudeCodeForUnprefixedClaudeModels,
|
||||
] = useState(false);
|
||||
const [claudeRoutingSettingsLoaded, setClaudeRoutingSettingsLoaded] = useState(false);
|
||||
const [claudeRoutingSettingsLoadError, setClaudeRoutingSettingsLoadError] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [savingClaudeRoutingPreference, setSavingClaudeRoutingPreference] = useState(false);
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
const [batchDeleting, setBatchDeleting] = useState(false);
|
||||
const [batchUpdating, setBatchUpdating] = useState<"activate" | "deactivate" | null>(null);
|
||||
@@ -1630,6 +1639,41 @@ export default function ProviderDetailPageClient() {
|
||||
void loadCodexSettings();
|
||||
}, [loadCodexSettings]);
|
||||
|
||||
const loadClaudeRoutingSettings = useCallback(async () => {
|
||||
if (providerId !== "claude") {
|
||||
setClaudeRoutingSettingsLoaded(false);
|
||||
setClaudeRoutingSettingsLoadError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setClaudeRoutingSettingsLoaded(false);
|
||||
setClaudeRoutingSettingsLoadError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/settings", { cache: "no-store" });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Settings request failed with HTTP ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
if (!data || typeof data !== "object") {
|
||||
throw new Error("Settings response was empty");
|
||||
}
|
||||
setPreferClaudeCodeForUnprefixedClaudeModels(
|
||||
data.preferClaudeCodeForUnprefixedClaudeModels === true
|
||||
);
|
||||
setClaudeRoutingSettingsLoaded(true);
|
||||
} catch (error) {
|
||||
setClaudeRoutingSettingsLoaded(false);
|
||||
setClaudeRoutingSettingsLoadError(
|
||||
error instanceof Error ? error.message : "Failed to load settings"
|
||||
);
|
||||
}
|
||||
}, [providerId]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadClaudeRoutingSettings();
|
||||
}, [loadClaudeRoutingSettings]);
|
||||
|
||||
const loadConnProxies = useCallback(async (conns: { id?: string }[]) => {
|
||||
if (!conns.length) return;
|
||||
try {
|
||||
@@ -2676,6 +2720,48 @@ export default function ProviderDetailPageClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleClaudeRoutingPreference = async (enabled: boolean) => {
|
||||
if (savingClaudeRoutingPreference || !claudeRoutingSettingsLoaded) return;
|
||||
setSavingClaudeRoutingPreference(true);
|
||||
const previous = preferClaudeCodeForUnprefixedClaudeModels;
|
||||
setPreferClaudeCodeForUnprefixedClaudeModels(enabled);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
preferClaudeCodeForUnprefixedClaudeModels: enabled,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
setPreferClaudeCodeForUnprefixedClaudeModels(previous);
|
||||
notify.error(data.error || "Failed to update Claude Code routing preference");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json().catch(() => null);
|
||||
if (data && typeof data === "object") {
|
||||
setPreferClaudeCodeForUnprefixedClaudeModels(
|
||||
data.preferClaudeCodeForUnprefixedClaudeModels === true
|
||||
);
|
||||
}
|
||||
notify.success(
|
||||
enabled
|
||||
? "Unprefixed Claude models now prefer Claude Code"
|
||||
: "Unprefixed Claude models no longer prefer Claude Code"
|
||||
);
|
||||
} catch (error) {
|
||||
setPreferClaudeCodeForUnprefixedClaudeModels(previous);
|
||||
console.error("Error updating Claude Code routing preference:", error);
|
||||
notify.error("Failed to update Claude Code routing preference");
|
||||
} finally {
|
||||
setSavingClaudeRoutingPreference(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeCodexGlobalServiceMode = async (mode: CodexGlobalServiceMode) => {
|
||||
if (savingCodexGlobalServiceMode || !codexSettingsLoaded) return;
|
||||
setSavingCodexGlobalServiceMode(true);
|
||||
@@ -4157,6 +4243,66 @@ export default function ProviderDetailPageClient() {
|
||||
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<h2 className="text-lg font-semibold">{t("connections")}</h2>
|
||||
{providerId === "claude" && (
|
||||
<div
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-orange-500/20 bg-orange-500/5 px-2 py-1 text-xs font-medium text-text-muted"
|
||||
title={providerText(
|
||||
t,
|
||||
"preferClaudeCodeForUnprefixedClaudeModelsTooltip",
|
||||
"Route bare claude-* model IDs from Claude Code clients through the Claude Code account instead of asking for a provider prefix."
|
||||
)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[14px] text-orange-500">
|
||||
alt_route
|
||||
</span>
|
||||
<span>
|
||||
{providerText(
|
||||
t,
|
||||
"preferClaudeCodeForUnprefixedClaudeModelsLabel",
|
||||
"Claude Code default"
|
||||
)}
|
||||
</span>
|
||||
<Toggle
|
||||
size="sm"
|
||||
checked={preferClaudeCodeForUnprefixedClaudeModels}
|
||||
onChange={handleToggleClaudeRoutingPreference}
|
||||
disabled={savingClaudeRoutingPreference || !claudeRoutingSettingsLoaded}
|
||||
ariaLabel={providerText(
|
||||
t,
|
||||
"preferClaudeCodeForUnprefixedClaudeModelsAria",
|
||||
"Prefer Claude Code for unprefixed Claude models"
|
||||
)}
|
||||
title={
|
||||
preferClaudeCodeForUnprefixedClaudeModels
|
||||
? providerText(
|
||||
t,
|
||||
"preferClaudeCodeForUnprefixedClaudeModelsDisable",
|
||||
"Disable Claude Code preference for bare claude-* model IDs"
|
||||
)
|
||||
: providerText(
|
||||
t,
|
||||
"preferClaudeCodeForUnprefixedClaudeModelsEnable",
|
||||
"Enable Claude Code preference for bare claude-* model IDs"
|
||||
)
|
||||
}
|
||||
/>
|
||||
<span className="text-[11px] text-text-muted/70">
|
||||
{preferClaudeCodeForUnprefixedClaudeModels
|
||||
? providerText(t, "toggleOnShort", "On")
|
||||
: providerText(t, "toggleOffShort", "Off")}
|
||||
</span>
|
||||
{claudeRoutingSettingsLoadError ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void loadClaudeRoutingSettings()}
|
||||
className="rounded border border-orange-500/30 px-2 py-0.5 text-[11px] font-medium text-orange-600 hover:bg-orange-500/10 dark:text-orange-300"
|
||||
title={claudeRoutingSettingsLoadError}
|
||||
>
|
||||
{providerText(t, "retry", "Retry")}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
{providerId === "codex" && (
|
||||
<div
|
||||
className="inline-flex items-center gap-2 rounded-lg border border-sky-500/20 bg-sky-500/5 px-2 py-1 text-xs font-medium text-text-muted"
|
||||
|
||||
@@ -31,6 +31,10 @@ type ProxyResolutionCacheEntry = {
|
||||
|
||||
const PROXY_RESOLUTION_CACHE_MAX_ENTRIES = 100;
|
||||
|
||||
function isTruthyEnvFlag(value: string | undefined): boolean {
|
||||
return typeof value === "string" && /^(1|true|yes|on)$/i.test(value.trim());
|
||||
}
|
||||
|
||||
let proxyConfigGeneration = 0;
|
||||
const proxyResolutionCache = new Map<string, ProxyResolutionCacheEntry>();
|
||||
|
||||
@@ -100,6 +104,9 @@ export async function getSettings() {
|
||||
hideEndpointCloudflaredTunnel: false,
|
||||
hideEndpointTailscaleFunnel: false,
|
||||
hideEndpointNgrokTunnel: false,
|
||||
preferClaudeCodeForUnprefixedClaudeModels: isTruthyEnvFlag(
|
||||
process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS
|
||||
),
|
||||
autoRefreshProviderQuota: false,
|
||||
autoRefreshProviderQuotaInterval: 180,
|
||||
comboConfigMode: "guided",
|
||||
|
||||
@@ -748,6 +748,7 @@ export const updateSettingsSchema = z.object({
|
||||
hideEndpointCloudflaredTunnel: z.boolean().optional(),
|
||||
hideEndpointTailscaleFunnel: z.boolean().optional(),
|
||||
hideEndpointNgrokTunnel: z.boolean().optional(),
|
||||
preferClaudeCodeForUnprefixedClaudeModels: z.boolean().optional(),
|
||||
pinProviderQuotaToHome: z.boolean().optional(),
|
||||
showQuickStartOnHome: z.boolean().optional(),
|
||||
showProviderTopologyOnHome: z.boolean().optional(),
|
||||
|
||||
@@ -36,6 +36,7 @@ export const updateSettingsSchema = z.object({
|
||||
hideEndpointCloudflaredTunnel: z.boolean().optional(),
|
||||
hideEndpointTailscaleFunnel: z.boolean().optional(),
|
||||
hideEndpointNgrokTunnel: z.boolean().optional(),
|
||||
preferClaudeCodeForUnprefixedClaudeModels: z.boolean().optional(),
|
||||
autoRefreshProviderQuota: z.boolean().optional(),
|
||||
autoRefreshProviderQuotaInterval: z.number().int().min(10).max(3600).optional(),
|
||||
pinProviderQuotaToHome: z.boolean().optional(),
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface Settings {
|
||||
hideEndpointCloudflaredTunnel?: boolean;
|
||||
hideEndpointTailscaleFunnel?: boolean;
|
||||
hideEndpointNgrokTunnel?: boolean;
|
||||
preferClaudeCodeForUnprefixedClaudeModels?: boolean;
|
||||
autoRefreshProviderQuota?: boolean;
|
||||
autoRefreshProviderQuotaInterval?: number;
|
||||
pinProviderQuotaToHome?: boolean;
|
||||
|
||||
@@ -1,8 +1,51 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const previousDataDir = process.env.DATA_DIR;
|
||||
const modelResolverDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-model-resolver-"));
|
||||
process.env.DATA_DIR = modelResolverDataDir;
|
||||
|
||||
const model = await import("../../open-sse/services/model.ts");
|
||||
|
||||
function withEnv(name: string, value: string | undefined, fn: () => Promise<void>) {
|
||||
return async () => {
|
||||
const { invalidateDbCache } = await import("../../src/lib/db/readCache.ts");
|
||||
const previous = process.env[name];
|
||||
if (value === undefined) {
|
||||
delete process.env[name];
|
||||
} else {
|
||||
process.env[name] = value;
|
||||
}
|
||||
invalidateDbCache("settings");
|
||||
try {
|
||||
await fn();
|
||||
} finally {
|
||||
if (previous === undefined) {
|
||||
delete process.env[name];
|
||||
} else {
|
||||
process.env[name] = previous;
|
||||
}
|
||||
invalidateDbCache("settings");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
const core = await import("../../src/lib/db/core.ts");
|
||||
const { invalidateDbCache } = await import("../../src/lib/db/readCache.ts");
|
||||
core.resetDbInstance();
|
||||
invalidateDbCache();
|
||||
fs.rmSync(modelResolverDataDir, { recursive: true, force: true });
|
||||
if (previousDataDir === undefined) {
|
||||
delete process.env.DATA_DIR;
|
||||
} else {
|
||||
process.env.DATA_DIR = previousDataDir;
|
||||
}
|
||||
});
|
||||
|
||||
test("resolveProviderAlias returns null for null/undefined", () => {
|
||||
assert.equal(model.resolveProviderAlias(null), null);
|
||||
assert.equal(model.resolveProviderAlias(undefined), null);
|
||||
@@ -116,3 +159,84 @@ test("getModelInfoCore handles null", async () => {
|
||||
assert.ok(result);
|
||||
assert.ok(typeof result === "object");
|
||||
});
|
||||
|
||||
test(
|
||||
"getModelInfoCore routes newly released unprefixed Claude models to Claude Code when enabled",
|
||||
withEnv("OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS", "true", async () => {
|
||||
const result = await model.getModelInfoCore("claude-fable-5", {});
|
||||
assert.deepEqual(result, {
|
||||
provider: "claude",
|
||||
model: "claude-fable-5",
|
||||
extendedContext: false,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
"getModelInfoCore resolves ambiguous unprefixed Claude catalog models to Claude Code when enabled",
|
||||
withEnv("OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS", "true", async () => {
|
||||
const result = await model.getModelInfoCore("claude-haiku-4-5-20251001", {});
|
||||
assert.deepEqual(result, {
|
||||
provider: "claude",
|
||||
model: "claude-haiku-4-5-20251001",
|
||||
extendedContext: false,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
test("getModelInfoCore routes unprefixed Claude models to Claude Code from settings toggle", async () => {
|
||||
const previousEnvFlag =
|
||||
process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS;
|
||||
delete process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS;
|
||||
|
||||
try {
|
||||
const { updateSettings } = await import("../../src/lib/db/settings.ts");
|
||||
await updateSettings({ preferClaudeCodeForUnprefixedClaudeModels: true });
|
||||
|
||||
const result = await model.getModelInfoCore("claude-fable-5", {});
|
||||
assert.deepEqual(result, {
|
||||
provider: "claude",
|
||||
model: "claude-fable-5",
|
||||
extendedContext: false,
|
||||
});
|
||||
|
||||
const extendedResult = await model.getModelInfoCore("claude-fable-5[1m]", {});
|
||||
assert.deepEqual(extendedResult, {
|
||||
provider: "claude",
|
||||
model: "claude-fable-5",
|
||||
extendedContext: true,
|
||||
});
|
||||
} finally {
|
||||
if (previousEnvFlag === undefined) {
|
||||
delete process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS;
|
||||
} else {
|
||||
process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS = previousEnvFlag;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("getModelInfoCore lets settings toggle disable Claude Code preference", async () => {
|
||||
const previousEnvFlag =
|
||||
process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS;
|
||||
process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS = "true";
|
||||
|
||||
try {
|
||||
const { updateSettings } = await import("../../src/lib/db/settings.ts");
|
||||
await updateSettings({ preferClaudeCodeForUnprefixedClaudeModels: false });
|
||||
|
||||
// With the toggle OFF, a bare `claude-*` id must NOT be auto-preferred to the
|
||||
// Claude Code ("claude") provider — it falls back to normal inference. The exact
|
||||
// fallback target depends on the live catalog (an id present in a single provider
|
||||
// resolves there; one present in several is reported ambiguous and requires a
|
||||
// provider prefix), so assert the toggle's effect rather than a catalog-dependent
|
||||
// target: the result is simply not routed to Claude Code.
|
||||
const result = await model.getModelInfoCore("claude-fable-5", {});
|
||||
assert.notEqual(result.provider, "claude");
|
||||
} finally {
|
||||
if (previousEnvFlag === undefined) {
|
||||
delete process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS;
|
||||
} else {
|
||||
process.env.OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS = previousEnvFlag;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -58,6 +58,18 @@ test("settings schemas accept wsAuth toggle", () => {
|
||||
assert.equal(sharedParsed.wsAuth, false);
|
||||
});
|
||||
|
||||
test("settings schemas accept Claude Code unprefixed model routing toggle", () => {
|
||||
const routeParsed = settingsRouteSchema.parse({
|
||||
preferClaudeCodeForUnprefixedClaudeModels: true,
|
||||
});
|
||||
const sharedParsed = sharedSettingsSchema.parse({
|
||||
preferClaudeCodeForUnprefixedClaudeModels: false,
|
||||
});
|
||||
|
||||
assert.equal(routeParsed.preferClaudeCodeForUnprefixedClaudeModels, true);
|
||||
assert.equal(sharedParsed.preferClaudeCodeForUnprefixedClaudeModels, false);
|
||||
});
|
||||
|
||||
test("settings schemas accept combo configuration modes", () => {
|
||||
const routeParsed = settingsRouteSchema.parse({ comboConfigMode: "expert" });
|
||||
const sharedParsed = sharedSettingsSchema.parse({ comboConfigMode: "guided" });
|
||||
|
||||
Reference in New Issue
Block a user