feat(settings): unify routing rules and model aliases controls

Move model routing management into Settings and add a unified
model alias editor for exact and wildcard remaps.

Sync combo defaults with global routing strategy settings, add
localized combo onboarding copy, and expand routing i18n across
supported locales.

Also fix supporting routing behavior by accepting all strategy
values in settings schemas, preserving connection ids in combo
tests, honoring non-stream JSON requests for CC-compatible
providers, and handling hashed external package subpaths.
This commit is contained in:
diegosouzapw
2026-04-12 18:43:19 -03:00
parent 9944d136e4
commit 25bd04e400
61 changed files with 2036 additions and 492 deletions

View File

@@ -16,5 +16,6 @@
"javascript:S3776": {
"level": "off"
}
}
},
"git.ignoreLimitWarning": true
}

View File

@@ -67,10 +67,10 @@ const nextConfig = {
//
// We use two strategies:
// 1. Exact-name externals for all known server-side packages.
// 2. Hash-strip catch-all: any require('<name>-<16hexchars>' strips the
// suffix and falls through to the real package name.
// 2. Hash-strip catch-all: any require('<name>-<16hexchars>[/subpath]')
// strips the hash suffix and falls through to the real package name.
//
const HASH_PATTERN = /^(.+)-[0-9a-f]{16}$/;
const HASH_PATTERN = /^(.+)-[0-9a-f]{16}(\/.*)?$/;
const KNOWN_EXTERNALS = new Set([
"better-sqlite3",
@@ -102,13 +102,15 @@ const nextConfig = {
if (KNOWN_EXTERNALS.has(request)) {
return callback(null, `commonjs ${request}`);
}
// Case 2: Hash-suffixed name — strip hash, use base name
// Case 2: Hash-suffixed name — strip hash, preserve subpath
// e.g. "better-sqlite3-90e2652d1716b047" → "better-sqlite3"
// "zod-dcb22c6336e0bc69" → "zod"
// "zod-dcb22c6336e0bc69/v3" → "zod/v3"
// "zod-dcb22c6336e0bc69/v4-mini" → "zod/v4-mini"
const hashMatch = request?.match?.(HASH_PATTERN);
if (hashMatch) {
const baseName = hashMatch[1];
return callback(null, `commonjs ${baseName}`);
const resolved = hashMatch[2] ? `${hashMatch[1]}${hashMatch[2]}` : hashMatch[1];
return callback(null, `commonjs ${resolved}`);
}
callback();
},

View File

@@ -206,9 +206,7 @@ export class BaseExecutor {
headers["Authorization"] = `Bearer ${effectiveKey}`;
}
if (stream) {
headers["Accept"] = "text/event-stream";
}
headers["Accept"] = stream ? "text/event-stream" : "application/json";
return headers;
}

View File

@@ -179,7 +179,7 @@ export class DefaultExecutor extends BaseExecutor {
}
}
if (stream) headers["Accept"] = "text/event-stream";
headers["Accept"] = stream ? "text/event-stream" : "application/json";
// Qwen header cleanup: Remove X-Dashscope-* headers if using an API key (DashScope compatible mode).
// If using OAuth (Qwen Code), we MUST keep them for portal.qwen.ai to accept the request.

View File

@@ -957,7 +957,10 @@ export async function handleChatCore({
let translatedBody = body;
const isClaudePassthrough = sourceFormat === FORMATS.CLAUDE && targetFormat === FORMATS.CLAUDE;
const isClaudeCodeCompatible = isClaudeCodeCompatibleProvider(provider);
const upstreamStream = stream || isClaudeCodeCompatible;
// Respect the client's explicit non-streaming intent for CC-compatible providers.
// Most upstreams can answer JSON directly; the SSE->JSON fallback remains as a
// compatibility path when an upstream still responds with event-stream.
const upstreamStream = stream;
let ccSessionId: string | null = null;
// Determine if we should preserve client-side cache_control headers

View File

@@ -1730,22 +1730,21 @@ async function handleRoundRobinCombo({
});
recordedAttempts++;
if (provider) {
import("../../src/lib/localDb")
.then(({ setLKGP }) =>
Promise.all([
setLKGP(combo.name, target.executionKey, provider),
setLKGP(combo.name, combo.id || combo.name, provider),
])
)
.catch((err) =>
log.warn(
"COMBO-RR",
"Failed to record Last Known Good Provider. This is non-fatal.",
{
err,
}
)
try {
const { setLKGP } = await import("../../src/lib/localDb");
await Promise.all([
setLKGP(combo.name, target.executionKey, provider),
setLKGP(combo.name, combo.id || combo.name, provider),
]);
} catch (err) {
log.warn(
"COMBO-RR",
"Failed to record Last Known Good Provider. This is non-fatal.",
{
err,
}
);
}
}
return result;
}

View File

@@ -45,20 +45,6 @@ const ModelSelectModal = dynamic(() => import("@/shared/components/ModelSelectMo
const ProxyConfigModal = dynamic(() => import("@/shared/components/ProxyConfigModal"), {
ssr: false,
});
const ModelRoutingSection = dynamic(() => import("@/shared/components/ModelRoutingSection"), {
ssr: false,
loading: () => (
<div className="rounded-xl border border-black/10 dark:border-white/10 bg-white/50 dark:bg-white/[0.02] p-4">
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[18px]">route</span>
<div className="flex-1 min-w-0">
<div className="h-4 w-40 rounded bg-black/5 dark:bg-white/5" />
<div className="h-3 w-64 rounded bg-black/5 dark:bg-white/5 mt-2" />
</div>
</div>
</div>
),
});
// Validate combo name: letters, numbers, -, _, /, .
const VALID_NAME_REGEX = /^[a-zA-Z0-9_/.-]+$/;
@@ -883,6 +869,7 @@ export default function CombosPage() {
<ComboUsageGuide
onHide={() => setShowUsageGuide(false)}
onHideForever={handleHideUsageGuideForever}
onCreateCombo={() => setShowCreateModal(true)}
/>
)}
@@ -930,10 +917,6 @@ export default function CombosPage() {
</div>
</Card>
)}
{/* Model Routing Rules (#563) */}
<ModelRoutingSection combos={combos} />
<div className="flex flex-wrap items-center gap-2 rounded-xl border border-black/8 dark:border-white/8 bg-black/[0.02] dark:bg-white/[0.02] p-1">
{[
{
@@ -1116,9 +1099,35 @@ export default function CombosPage() {
);
}
function ComboUsageGuide({ onHide, onHideForever }) {
const COMBO_WIZARD_STEPS = [
{
step: 1,
icon: "badge",
titleKey: "wizardStep1Title",
descKey: "wizardStep1Desc",
},
{
step: 2,
icon: "hub",
titleKey: "wizardStep2Title",
descKey: "wizardStep2Desc",
},
{
step: 3,
icon: "route",
titleKey: "wizardStep3Title",
descKey: "wizardStep3Desc",
},
{
step: 4,
icon: "check_circle",
titleKey: "wizardStep4Title",
descKey: "wizardStep4Desc",
},
];
function ComboUsageGuide({ onHide, onHideForever, onCreateCombo }) {
const t = useTranslations("combos");
const guideStrategies = ["priority", "cost-optimized", "least-used"];
return (
<Card padding="sm">
@@ -1130,8 +1139,16 @@ function ComboUsageGuide({ onHide, onHideForever }) {
</span>
</div>
<div className="min-w-0">
<h2 className="text-sm font-semibold">{t("routingStrategy")}</h2>
<p className="text-xs text-text-muted mt-0.5">{t("description")}</p>
<h2 className="text-sm font-semibold">
{getI18nOrFallback(t, "wizardGuideTitle", "Getting Started with Combos")}
</h2>
<p className="text-xs text-text-muted mt-0.5">
{getI18nOrFallback(
t,
"wizardGuideDesc",
"Create model combos to route AI traffic intelligently"
)}
</p>
</div>
</div>
<div className="flex items-center gap-1 shrink-0">
@@ -1149,27 +1166,45 @@ function ComboUsageGuide({ onHide, onHideForever }) {
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 mt-3">
{guideStrategies.map((strategyValue) => {
const strategyMeta = getStrategyMeta(strategyValue);
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mt-3">
{COMBO_WIZARD_STEPS.map((step, index) => {
return (
<div
key={strategyValue}
className="rounded-lg border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.02] p-2.5"
key={step.step}
className="relative rounded-lg border border-black/10 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.02] p-2.5"
>
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1.5 mb-1.5">
<span className="inline-flex size-5 items-center justify-center rounded-full bg-primary/10 text-[10px] font-bold text-primary">
{step.step}
</span>
<span className="material-symbols-outlined text-[14px] text-primary">
{strategyMeta.icon}
{step.icon}
</span>
<span className="text-xs font-medium">{getStrategyLabel(t, strategyValue)}</span>
</div>
<p className="text-[11px] leading-4 text-text-muted mt-1.5">
{getStrategyDescription(t, strategyValue)}
<p className="text-xs font-medium">
{getI18nOrFallback(t, step.titleKey, step.titleKey)}
</p>
<p className="mt-1 text-[11px] leading-4 text-text-muted">
{getI18nOrFallback(t, step.descKey, step.descKey)}
</p>
{index < COMBO_WIZARD_STEPS.length - 1 && (
<span className="absolute -right-2.5 top-1/2 z-10 hidden -translate-y-1/2 text-text-muted md:block">
<span className="material-symbols-outlined text-[14px]">arrow_forward</span>
</span>
)}
</div>
);
})}
</div>
<div className="mt-3 flex items-center gap-2">
<Button size="sm" icon="add" onClick={onCreateCombo}>
{getI18nOrFallback(t, "createFirstCombo", "Create Your First Combo")}
</Button>
<span className="text-[10px] text-text-muted">
{getI18nOrFallback(t, "wizardGuideHint", "or click + Create Combo above")}
</span>
</div>
</Card>
);
}

View File

@@ -31,10 +31,15 @@ export default function ComboDefaultsTab() {
handoffThreshold: 0.85,
handoffModel: "",
maxMessagesForSummary: 30,
stickyRoundRobinLimit: 3,
});
const [providerOverrides, setProviderOverrides] = useState<any>({});
const [newOverrideProvider, setNewOverrideProvider] = useState("");
const [saving, setSaving] = useState(false);
const [status, setStatus] = useState<{ type: "success" | "error" | ""; message: string }>({
type: "",
message: "",
});
const t = useTranslations("settings");
const tc = useTranslations("common");
const strategyOptions = ROUTING_STRATEGIES.map((strategy) => ({
@@ -54,27 +59,75 @@ export default function ComboDefaultsTab() {
];
useEffect(() => {
fetch("/api/settings/combo-defaults")
.then((res) => res.json())
.then((data) => {
if (data.comboDefaults) {
setComboDefaults((prev) => ({ ...prev, ...data.comboDefaults }));
}
if (data.providerOverrides) setProviderOverrides(data.providerOverrides);
Promise.all([
fetch("/api/settings/combo-defaults").then((res) => res.json()),
fetch("/api/settings").then((res) => res.json()),
])
.then(([comboData, settingsData]) => {
setComboDefaults((prev) => ({
...prev,
...(comboData.comboDefaults || {}),
strategy:
settingsData.fallbackStrategy ?? comboData.comboDefaults?.strategy ?? prev.strategy,
stickyRoundRobinLimit:
settingsData.stickyRoundRobinLimit ??
comboData.comboDefaults?.stickyRoundRobinLimit ??
prev.stickyRoundRobinLimit,
}));
if (comboData.providerOverrides) setProviderOverrides(comboData.providerOverrides);
})
.catch((err) => console.error("Failed to fetch combo defaults:", err));
}, []);
const showStatus = (type: "success" | "error", message: string) => {
setStatus({ type, message });
setTimeout(() => setStatus({ type: "", message: "" }), 2500);
};
const syncGlobalRoutingSettings = async (patch: Record<string, unknown>) => {
const keys = Object.keys(patch);
if (keys.length === 0) return true;
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(patch),
});
if (!res.ok) {
throw new Error("Failed to sync global routing settings");
}
return true;
};
const saveComboDefaults = async () => {
setSaving(true);
try {
await fetch("/api/settings/combo-defaults", {
const { stickyRoundRobinLimit, ...comboDefaultsPayload } = comboDefaults;
const settingsPatch: Record<string, unknown> = {};
if (comboDefaults.strategy) {
settingsPatch.fallbackStrategy = comboDefaults.strategy;
}
if (comboDefaults.strategy === "round-robin" && stickyRoundRobinLimit !== undefined) {
settingsPatch.stickyRoundRobinLimit = stickyRoundRobinLimit;
}
const comboDefaultsRes = await fetch("/api/settings/combo-defaults", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ comboDefaults, providerOverrides }),
body: JSON.stringify({ comboDefaults: comboDefaultsPayload, providerOverrides }),
});
if (!comboDefaultsRes.ok) {
throw new Error("Failed to save combo defaults");
}
await syncGlobalRoutingSettings(settingsPatch);
showStatus("success", t("savedSuccessfully"));
} catch (err) {
console.error("Failed to save combo defaults:", err);
showStatus("error", t("errorOccurred"));
} finally {
setSaving(false);
}
@@ -103,8 +156,38 @@ export default function ComboDefaultsTab() {
tune
</span>
</div>
<h3 className="text-lg font-semibold">{t("comboDefaultsTitle")}</h3>
<h3 className="text-lg font-semibold">
{translateOrFallback(t, "comboDefaultsTitle", "Default Routing & Combo Settings")}
</h3>
<span className="text-xs text-text-muted ml-auto">{t("globalComboConfig")}</span>
{status.message && (
<span
className={`text-xs font-medium ml-2 ${
status.type === "success" ? "text-emerald-500" : "text-red-500"
}`}
>
{status.message}
</span>
)}
</div>
<div className="mb-4 rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
<p className="text-xs font-medium text-blue-700 dark:text-blue-300">
{translateOrFallback(t, "routingAdvancedGuideTitle", "Advanced routing guidance")}
</p>
<p className="text-xs text-text-muted mt-1">
{translateOrFallback(
t,
"routingAdvancedGuideHint1",
"Use Fill First for predictable priority, Round Robin for fairness, and P2C for latency resilience."
)}
</p>
<p className="text-xs text-text-muted">
{translateOrFallback(
t,
"routingAdvancedGuideHint2",
"If providers vary in quality or cost, start with Cost Opt for background work and Least Used for balanced wear."
)}
</p>
</div>
<div className="mb-4 rounded-lg border border-amber-500/20 bg-amber-500/5 p-3">
<p className="text-xs font-medium text-amber-700 dark:text-amber-300">
@@ -130,7 +213,15 @@ export default function ComboDefaultsTab() {
key={s.value}
role="tab"
aria-selected={comboDefaults.strategy === s.value}
onClick={() => setComboDefaults((prev) => ({ ...prev, strategy: s.value }))}
onClick={async () => {
setComboDefaults((prev) => ({ ...prev, strategy: s.value }));
try {
await syncGlobalRoutingSettings({ fallbackStrategy: s.value });
} catch (error) {
console.error("Failed to sync fallback strategy:", error);
showStatus("error", t("errorOccurred"));
}
}}
className={cn(
"px-2 py-1 rounded text-xs font-medium transition-all flex items-center justify-center gap-0.5",
comboDefaults.strategy === s.value
@@ -145,6 +236,35 @@ export default function ComboDefaultsTab() {
</div>
</div>
{comboDefaults.strategy === "round-robin" && (
<div className="flex items-center justify-between pt-3 border-t border-border/30">
<div>
<p className="text-sm font-medium">{t("stickyLimit")}</p>
<p className="text-xs text-text-muted">{t("stickyLimitDesc")}</p>
</div>
<Input
type="number"
min="1"
max="10"
value={comboDefaults.stickyRoundRobinLimit || 3}
onChange={async (e) => {
const nextLimit = parseInt(e.target.value) || 3;
setComboDefaults((prev) => ({
...prev,
stickyRoundRobinLimit: nextLimit,
}));
try {
await syncGlobalRoutingSettings({ stickyRoundRobinLimit: nextLimit });
} catch (error) {
console.error("Failed to sync sticky round robin limit:", error);
showStatus("error", t("errorOccurred"));
}
}}
className="w-20 text-center"
/>
</div>
)}
{/* Numeric settings */}
<div className="grid grid-cols-2 gap-3 pt-3 border-t border-border/50">
{numericSettings.map(({ key, label, min, max, step }) => (

View File

@@ -0,0 +1,366 @@
"use client";
import { useEffect, useState } from "react";
import { Button, Card, Input } from "@/shared/components";
import { useTranslations } from "next-intl";
interface WildcardAlias {
pattern: string;
target: string;
}
type AliasMode = "exact" | "wildcard";
function translateOrFallback(
t: ReturnType<typeof useTranslations>,
key: string,
fallback: string
): string {
return typeof t.has === "function" && t.has(key) ? t(key) : fallback;
}
export default function ModelAliasesUnified() {
const [wildcardAliases, setWildcardAliases] = useState<WildcardAlias[]>([]);
const [builtInAliases, setBuiltInAliases] = useState<Record<string, string>>({});
const [customAliases, setCustomAliases] = useState<Record<string, string>>({});
const [aliasMode, setAliasMode] = useState<AliasMode>("exact");
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [status, setStatus] = useState<{ type: "success" | "error" | ""; message: string }>({
type: "",
message: "",
});
const [fromValue, setFromValue] = useState("");
const [toValue, setToValue] = useState("");
const t = useTranslations("settings");
const builtInEntries = Object.entries(builtInAliases);
const customEntries = Object.entries(customAliases);
useEffect(() => {
const loadAliases = async () => {
try {
const [settingsRes, aliasesRes] = await Promise.all([
fetch("/api/settings"),
fetch("/api/settings/model-aliases"),
]);
const settingsData = settingsRes.ok ? await settingsRes.json() : {};
const aliasesData = aliasesRes.ok ? await aliasesRes.json() : {};
setWildcardAliases(settingsData.wildcardAliases || []);
setBuiltInAliases(aliasesData.builtIn || {});
setCustomAliases(aliasesData.custom || {});
} catch (error) {
console.error("Failed to load model aliases:", error);
} finally {
setLoading(false);
}
};
loadAliases();
}, []);
const showStatus = (type: "success" | "error", message: string) => {
setStatus({ type, message });
setTimeout(() => setStatus({ type: "", message: "" }), 2500);
};
const addExactAlias = async () => {
if (!fromValue.trim() || !toValue.trim()) return;
setSaving(true);
try {
const res = await fetch("/api/settings/model-aliases", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ from: fromValue.trim(), to: toValue.trim() }),
});
if (!res.ok) throw new Error("Failed to save exact alias");
const data = await res.json();
setCustomAliases(data.custom || {});
setFromValue("");
setToValue("");
showStatus("success", translateOrFallback(t, "saved", "Saved"));
} catch (error) {
console.error("Failed to save exact alias:", error);
showStatus("error", translateOrFallback(t, "errorOccurred", "An error occurred"));
} finally {
setSaving(false);
}
};
const removeExactAlias = async (from: string) => {
setSaving(true);
try {
const res = await fetch("/api/settings/model-aliases", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ from }),
});
if (!res.ok) throw new Error("Failed to remove exact alias");
const data = await res.json();
setCustomAliases(data.custom || {});
showStatus("success", translateOrFallback(t, "saved", "Saved"));
} catch (error) {
console.error("Failed to remove exact alias:", error);
showStatus("error", translateOrFallback(t, "errorOccurred", "An error occurred"));
} finally {
setSaving(false);
}
};
const addWildcardAlias = async () => {
if (!fromValue.trim() || !toValue.trim()) return;
setSaving(true);
try {
const updated = [...wildcardAliases, { pattern: fromValue.trim(), target: toValue.trim() }];
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ wildcardAliases: updated }),
});
if (!res.ok) throw new Error("Failed to save wildcard alias");
setWildcardAliases(updated);
setFromValue("");
setToValue("");
showStatus("success", translateOrFallback(t, "saved", "Saved"));
} catch (error) {
console.error("Failed to save wildcard alias:", error);
showStatus("error", translateOrFallback(t, "errorOccurred", "An error occurred"));
} finally {
setSaving(false);
}
};
const removeWildcardAlias = async (index: number) => {
setSaving(true);
try {
const updated = wildcardAliases.filter((_, currentIndex) => currentIndex !== index);
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ wildcardAliases: updated }),
});
if (!res.ok) throw new Error("Failed to remove wildcard alias");
setWildcardAliases(updated);
showStatus("success", translateOrFallback(t, "saved", "Saved"));
} catch (error) {
console.error("Failed to remove wildcard alias:", error);
showStatus("error", translateOrFallback(t, "errorOccurred", "An error occurred"));
} finally {
setSaving(false);
}
};
const handleAdd = async () => {
if (aliasMode === "wildcard") {
await addWildcardAlias();
return;
}
await addExactAlias();
};
return (
<Card>
<div className="flex items-center gap-3 mb-5">
<div className="p-2 rounded-lg bg-purple-500/10 text-purple-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
swap_horiz
</span>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold">
{translateOrFallback(t, "modelAliasesTitle", "Model Aliases")}
</h3>
<p className="text-sm text-text-muted">
{translateOrFallback(
t,
"modelAliasesDesc",
"Remap model names using exact matches or wildcard patterns."
)}
</p>
</div>
{status.message && (
<span
className={`text-xs font-medium flex items-center gap-1 ${
status.type === "success" ? "text-emerald-500" : "text-red-500"
}`}
>
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
{status.type === "success" ? "check_circle" : "error"}
</span>
{status.message}
</span>
)}
</div>
<div className="mb-5 rounded-lg border border-border/30 bg-surface/20 p-4">
<div className="flex flex-wrap gap-2 mb-3">
{[
{
value: "exact",
label: translateOrFallback(t, "exactMatchMode", "Exact Match"),
},
{
value: "wildcard",
label: translateOrFallback(t, "wildcardPatternMode", "Wildcard Pattern"),
},
].map((mode) => (
<button
key={mode.value}
type="button"
onClick={() => setAliasMode(mode.value as AliasMode)}
className={`rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${
aliasMode === mode.value
? "bg-white dark:bg-white/10 text-text-main shadow-sm"
: "bg-black/5 dark:bg-white/5 text-text-muted hover:text-text-main"
}`}
>
{mode.label}
</button>
))}
</div>
<p className="text-xs text-text-muted mb-3">
{aliasMode === "exact"
? translateOrFallback(
t,
"exactMatchModeDesc",
"Use exact aliases for deprecated or renamed model IDs."
)
: translateOrFallback(
t,
"wildcardPatternModeDesc",
"Use wildcard aliases with * and ? when a family of models should map to one target."
)}
</p>
<div className="grid grid-cols-1 md:grid-cols-[1fr_auto_1fr_auto] gap-2 items-end">
<Input
label={aliasMode === "exact" ? t("deprecatedModelId") : t("pattern")}
placeholder={
aliasMode === "exact" ? t("deprecatedModelId") : t("aliasPatternPlaceholder")
}
value={fromValue}
onChange={(e) => setFromValue(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && void handleAdd()}
disabled={loading || saving}
/>
<div className="hidden md:flex items-center justify-center pb-2 text-text-muted">
<span className="material-symbols-outlined text-[16px]" aria-hidden="true">
arrow_forward
</span>
</div>
<Input
label={aliasMode === "exact" ? t("newModelId") : t("targetModel")}
placeholder={aliasMode === "exact" ? t("newModelId") : t("aliasTargetPlaceholder")}
value={toValue}
onChange={(e) => setToValue(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && void handleAdd()}
disabled={loading || saving}
/>
<Button
variant="primary"
size="sm"
onClick={() => void handleAdd()}
disabled={loading || saving || !fromValue.trim() || !toValue.trim()}
className="md:mb-[2px]"
>
{t("add")}
</Button>
</div>
</div>
<div className="mb-4">
<p className="text-xs font-medium text-text-muted uppercase tracking-wider mb-2">
{translateOrFallback(t, "customAliases", "Custom Aliases")}
</p>
<div className="rounded-lg border border-border/30 divide-y divide-border/20">
{customEntries.length === 0 ? (
<div className="px-4 py-3 text-sm text-text-muted">
{translateOrFallback(
t,
"noExactAliasesConfigured",
"No exact-match aliases configured."
)}
</div>
) : (
customEntries.map(([from, to]) => (
<div key={from} className="flex items-center gap-3 px-4 py-2.5">
<code className="text-xs text-red-400/80 flex-1 truncate">{from}</code>
<span className="material-symbols-outlined text-[14px] text-text-muted">
arrow_forward
</span>
<code className="text-xs text-emerald-400/80 flex-1 truncate">{to}</code>
<button
type="button"
onClick={() => void removeExactAlias(from)}
disabled={saving}
className="p-1 rounded hover:bg-red-500/10 text-text-muted hover:text-red-400 transition-all"
>
<span className="material-symbols-outlined text-[16px]">close</span>
</button>
</div>
))
)}
</div>
</div>
<div className="mb-4">
<p className="text-xs font-medium text-text-muted uppercase tracking-wider mb-2">
{translateOrFallback(t, "wildcardRulesTitle", "Wildcard Rules")}
</p>
<div className="rounded-lg border border-border/30 divide-y divide-border/20">
{wildcardAliases.length === 0 ? (
<div className="px-4 py-3 text-sm text-text-muted">
{translateOrFallback(
t,
"noWildcardAliasesConfigured",
"No wildcard aliases configured."
)}
</div>
) : (
wildcardAliases.map((alias, index) => (
<div
key={`${alias.pattern}-${alias.target}-${index}`}
className="flex items-center gap-3 px-4 py-2.5"
>
<code className="text-xs text-purple-400 flex-1 truncate">{alias.pattern}</code>
<span className="material-symbols-outlined text-[14px] text-text-muted">
arrow_forward
</span>
<code className="text-xs text-emerald-400/80 flex-1 truncate">{alias.target}</code>
<button
type="button"
onClick={() => void removeWildcardAlias(index)}
disabled={saving}
className="p-1 rounded hover:bg-red-500/10 text-text-muted hover:text-red-400 transition-all"
>
<span className="material-symbols-outlined text-[16px]">close</span>
</button>
</div>
))
)}
</div>
</div>
<details className="group">
<summary className="text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer flex items-center gap-1 mb-2">
<span className="material-symbols-outlined text-[14px] group-open:rotate-90 transition-transform">
chevron_right
</span>
{translateOrFallback(t, "builtInAliases", "Built-in Aliases")} ({builtInEntries.length})
</summary>
<div className="rounded-lg border border-border/30 divide-y divide-border/20 max-h-60 overflow-y-auto">
{builtInEntries.map(([from, to]) => (
<div key={from} className="flex items-center gap-3 px-4 py-2 opacity-60">
<code className="text-xs text-red-400/60 flex-1 truncate">{from}</code>
<span className="material-symbols-outlined text-[14px] text-text-muted">
arrow_forward
</span>
<code className="text-xs text-emerald-400/60 flex-1 truncate">{to}</code>
<span className="material-symbols-outlined text-[14px] text-text-muted">lock</span>
</div>
))}
</div>
</details>
</Card>
);
}

View File

@@ -1,46 +1,24 @@
"use client";
import { useState, useEffect } from "react";
import { Card, Input, Button } from "@/shared/components";
import FallbackChainsEditor from "./FallbackChainsEditor";
import {
ROUTING_STRATEGIES,
SETTINGS_FALLBACK_STRATEGY_VALUES,
} from "@/shared/constants/routingStrategies";
import { useEffect, useState } from "react";
import { Button, Card } from "@/shared/components";
import { useTranslations } from "next-intl";
const STRATEGIES = ROUTING_STRATEGIES.filter((strategy) =>
SETTINGS_FALLBACK_STRATEGY_VALUES.includes(strategy.value)
).map((strategy) => ({
value: strategy.value,
labelKey: strategy.labelKey,
descKey: strategy.settingsDescKey,
icon: strategy.icon,
}));
import FallbackChainsEditor from "./FallbackChainsEditor";
export default function RoutingTab() {
const [settings, setSettings] = useState<any>({
fallbackStrategy: "fill-first",
alwaysPreserveClientCache: "auto",
});
const [loading, setLoading] = useState(true);
const [aliases, setAliases] = useState([]);
const [lkgpCacheLoading, setLkgpCacheLoading] = useState(false);
const [lkgpCacheStatus, setLkgpCacheStatus] = useState({ type: "", message: "" });
const [newPattern, setNewPattern] = useState("");
const [newTarget, setNewTarget] = useState("");
const t = useTranslations("settings");
const strategyHintKeyByValue = STRATEGIES.reduce<Record<string, string>>((acc, strategy) => {
acc[strategy.value] = strategy.descKey;
return acc;
}, {});
useEffect(() => {
fetch("/api/settings")
.then((res) => res.json())
.then((data) => {
setSettings(data);
setAliases(data.wildcardAliases || []);
setLoading(false);
})
.catch(() => setLoading(false));
@@ -61,100 +39,8 @@ export default function RoutingTab() {
}
};
const addAlias = async () => {
if (!newPattern.trim() || !newTarget.trim()) return;
const updated = [...aliases, { pattern: newPattern.trim(), target: newTarget.trim() }];
await updateSetting({ wildcardAliases: updated });
setAliases(updated);
setNewPattern("");
setNewTarget("");
};
const removeAlias = async (idx) => {
const updated = aliases.filter((_, i) => i !== idx);
await updateSetting({ wildcardAliases: updated });
setAliases(updated);
};
return (
<div className="flex flex-col gap-6">
{/* Strategy Selection */}
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
route
</span>
</div>
<h3 className="text-lg font-semibold">{t("routingStrategy")}</h3>
</div>
<div className="mb-4 rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
<p className="text-xs font-medium text-blue-700 dark:text-blue-300">
{t("routingAdvancedGuideTitle")}
</p>
<p className="text-xs text-text-muted mt-1">{t("routingAdvancedGuideHint1")}</p>
<p className="text-xs text-text-muted">{t("routingAdvancedGuideHint2")}</p>
</div>
<div
className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-2 mb-4"
style={{ gridAutoRows: "1fr" }}
>
{STRATEGIES.map((s) => (
<button
key={s.value}
onClick={() => updateSetting({ fallbackStrategy: s.value })}
disabled={loading}
className={`flex flex-col items-center gap-2 p-4 rounded-lg border text-center transition-all ${
settings.fallbackStrategy === s.value
? "border-blue-500/50 bg-blue-500/5 ring-1 ring-blue-500/20"
: "border-border/50 hover:border-border hover:bg-surface/30"
}`}
>
<span
className={`material-symbols-outlined text-[24px] ${
settings.fallbackStrategy === s.value ? "text-blue-400" : "text-text-muted"
}`}
>
{s.icon}
</span>
<div>
<p
className={`text-sm font-medium ${settings.fallbackStrategy === s.value ? "text-blue-400" : ""}`}
>
{t(s.labelKey)}
</p>
<p className="text-xs text-text-muted mt-0.5">{t(s.descKey)}</p>
</div>
</button>
))}
</div>
{settings.fallbackStrategy === "round-robin" && (
<div className="flex items-center justify-between pt-3 border-t border-border/30">
<div>
<p className="text-sm font-medium">{t("stickyLimit")}</p>
<p className="text-xs text-text-muted">{t("stickyLimitDesc")}</p>
</div>
<Input
type="number"
min="1"
max="10"
value={settings.stickyRoundRobinLimit || 3}
onChange={(e) => updateSetting({ stickyRoundRobinLimit: parseInt(e.target.value) })}
disabled={loading}
className="w-20 text-center"
/>
</div>
)}
<p className="text-xs text-text-muted italic pt-3 border-t border-border/30 mt-3">
{t(strategyHintKeyByValue[settings.fallbackStrategy] || "fillFirstDesc")}
</p>
</Card>
{/* Adaptive Volume Routing */}
<Card>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
@@ -188,7 +74,6 @@ export default function RoutingTab() {
</div>
</Card>
{/* LKGP Toggle */}
<Card>
<div className="flex items-start justify-between gap-4">
<div className="flex gap-3">
@@ -268,77 +153,8 @@ export default function RoutingTab() {
</div>
</Card>
{/* Wildcard Aliases */}
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-purple-500/10 text-purple-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
alt_route
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("modelAliases")}</h3>
<p className="text-sm text-text-muted">{t("modelAliasesDesc")}</p>
</div>
</div>
{aliases.length > 0 && (
<div className="flex flex-col gap-1.5 mb-4">
{aliases.map((a, i) => (
<div
key={i}
className="flex items-center justify-between gap-2 px-3 py-2 rounded-lg bg-surface/30 border border-border/20"
>
<div className="flex min-w-0 items-center gap-2 text-sm">
<span className="font-mono text-purple-400 break-all">{a.pattern}</span>
<span className="material-symbols-outlined text-[14px] text-text-muted">
arrow_forward
</span>
<span className="font-mono text-text-main break-all">{a.target}</span>
</div>
<button
onClick={() => removeAlias(i)}
className="shrink-0 text-text-muted hover:text-red-400 transition-colors"
>
<span className="material-symbols-outlined text-[16px]">close</span>
</button>
</div>
))}
</div>
)}
<div className="flex flex-col sm:flex-row gap-2 items-stretch sm:items-end">
<div className="flex-1">
<Input
label={t("pattern")}
placeholder={t("aliasPatternPlaceholder")}
value={newPattern}
onChange={(e) => setNewPattern(e.target.value)}
/>
</div>
<div className="flex-1">
<Input
label={t("targetModel")}
placeholder={t("aliasTargetPlaceholder")}
value={newTarget}
onChange={(e) => setNewTarget(e.target.value)}
/>
</div>
<Button
size="sm"
variant="primary"
onClick={addAlias}
className="mb-[2px] sm:w-auto w-full"
>
{t("add")}
</Button>
</div>
</Card>
{/* Fallback Chains */}
<FallbackChainsEditor />
{/* Client Cache Control */}
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-green-500/10 text-green-500">

View File

@@ -14,13 +14,14 @@ import AppearanceTab from "./components/AppearanceTab";
import ThinkingBudgetTab from "./components/ThinkingBudgetTab";
import CodexServiceTierTab from "./components/CodexServiceTierTab";
import SystemPromptTab from "./components/SystemPromptTab";
import ModelAliasesTab from "./components/ModelAliasesTab";
import ModelAliasesUnified from "./components/ModelAliasesUnified";
import BackgroundDegradationTab from "./components/BackgroundDegradationTab";
import CacheSettingsTab from "./components/CacheSettingsTab";
import MemorySkillsTab from "./components/MemorySkillsTab";
import ModelsDevSyncTab from "./components/ModelsDevSyncTab";
import ResilienceTab from "./components/ResilienceTab";
import CliproxyapiSettingsTab from "./components/CliproxyapiSettingsTab";
import ModelRoutingSection from "@/shared/components/ModelRoutingSection";
const tabs = [
{ id: "general", labelKey: "general", icon: "settings" },
@@ -105,8 +106,9 @@ export default function SettingsPage() {
{activeTab === "routing" && (
<div className="flex flex-col gap-6">
<RoutingTab />
<ModelRoutingSection />
<ComboDefaultsTab />
<ModelAliasesTab />
<ModelAliasesUnified />
<BackgroundDegradationTab />
</div>
)}

View File

@@ -40,6 +40,7 @@ async function testComboTarget(target, internalUrl) {
// Force a fresh execution path so combo tests cannot be satisfied by
// OmniRoute's semantic cache or other request reuse layers.
"X-OmniRoute-No-Cache": "true",
...(target.connectionId ? { "X-OmniRoute-Connection": target.connectionId } : {}),
"X-Request-Id": `combo-test-${randomUUID()}`,
},
body: JSON.stringify(testBody),

View File

@@ -6,15 +6,12 @@
import { NextResponse } from "next/server";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import yaml from "js-yaml";
let cachedSpec: { data: any; mtime: number } | null = null;
const ROUTE_DIR = path.dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = path.resolve(ROUTE_DIR, "../../../../../");
const OPENAPI_SPEC_CANDIDATES = [
path.join(PROJECT_ROOT, "docs", "openapi.yaml"),
path.join(PROJECT_ROOT, "app", "docs", "openapi.yaml"),
path.join(process.cwd(), "docs", "openapi.yaml"),
path.join(process.cwd(), "app", "docs", "openapi.yaml"),
];
export async function GET() {

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "التكاليف",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "المكالمات لكل حساب قبل التبديل",
"modelAliases": "الأسماء المستعارة النموذجية",
"modelAliasesTitle": "أسماء بديلة للنماذج",
"modelAliasesDesc": "أنماط أحرف البدل لإعادة تعيين أسماء النماذج • استخدم * و؟",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "إضافة اسم بديل مخصص",
"deprecatedModelId": "معرف النموذج المهمل",
"newModelId": "معرف النموذج الجديد",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "مترجم",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Разходи",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Обаждания на акаунт преди превключване",
"modelAliases": "Псевдоними на модела",
"modelAliasesTitle": "Псевдоними на модели",
"modelAliasesDesc": "Шаблони за заместващи символи за пренасочване на имена на модели • Използвайте * и ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Добави потребителски псевдоним",
"deprecatedModelId": "Остарял ID на модел",
"newModelId": "Нов ID на модел",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Преводач",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Kombo",
"autoDesc": "Samoopravný inteligentní směrovací pool (Optimalizovaný výkon)",
"lkgp": "Režim LKGP",
"lkgpDesc": "Poslední známý dobrý poskytovatel (Predikovatelná odolnost)"
"lkgpDesc": "Poslední známý dobrý poskytovatel (Predikovatelná odolnost)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Náklady",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Volání účtem před přepnutím",
"modelAliases": "Aliasy modelů",
"modelAliasesTitle": "Aliasy modelů",
"modelAliasesDesc": "Vzory zástupných znaků pro přemapování názvů modelů • Použijte * a ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Přidat vlastní alias",
"deprecatedModelId": "ID zastupovaného modelu",
"newModelId": "Nové ID modelu",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Poslední známý dobrý poskytovatel (Predikovatelná odolnost)",
"maintenance": "Údržba",
"purgeExpiredLogs": "Vymazat expirované protokoly",
"purgeLogsFailed": "Nepodařilo se vymazat protokoly"
"purgeLogsFailed": "Nepodařilo se vymazat protokoly",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Překladatel",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Omkostninger",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Opkald pr. konto før skift",
"modelAliases": "Model aliaser",
"modelAliasesTitle": "Model Aliaser",
"modelAliasesDesc": "Wildcard-mønstre til omlægning af modelnavne • Brug * og ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Tilføj Tilpasset Alias",
"deprecatedModelId": "Forældet model-ID",
"newModelId": "Nyt model-ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Oversætter",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Kosten",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Anrufe pro Konto vor dem Wechsel",
"modelAliases": "Modell-Aliase",
"modelAliasesTitle": "Modell-Aliase",
"modelAliasesDesc": "Platzhaltermuster zum Neuzuordnen von Modellnamen • Verwenden Sie * und ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Benutzerdefinierten Alias hinzufügen",
"deprecatedModelId": "Veraltete Modell-ID",
"newModelId": "Neue Modell-ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Übersetzer",

View File

@@ -1050,7 +1050,19 @@
"auto": "Intelligent Auto",
"autoDesc": "Self-healing smart routing pool with multi-factor scoring",
"lkgp": "LKGP Mode",
"lkgpDesc": "Prioritizes the last provider that successfully completed a request"
"lkgpDesc": "Prioritizes the last provider that successfully completed a request",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Costs",
@@ -2101,7 +2113,7 @@
"stickyLimitDesc": "Calls per account before switching",
"modelAliases": "Model Aliases",
"modelAliasesTitle": "Model Aliases",
"modelAliasesDesc": "Wildcard patterns to remap model names • Use * and ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Add Custom Alias",
"deprecatedModelId": "Deprecated model ID",
"newModelId": "New model ID",
@@ -2354,7 +2366,28 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Translator",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Costos",
@@ -1947,7 +1959,7 @@
"stickyLimitDesc": "Llamadas por cuenta antes de cambiar",
"modelAliases": "Alias de modelo",
"modelAliasesTitle": "Aliases de Modelo",
"modelAliasesDesc": "Patrones comodín para reasignar nombres de modelos • Utilice * y ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Agregar Alias Personalizado",
"deprecatedModelId": "ID del modelo obsoleto",
"newModelId": "Nuevo ID del modelo",
@@ -2236,7 +2248,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Traductor",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Kustannukset",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Puhelut tilikohtaisesti ennen vaihtamista",
"modelAliases": "Mallin aliakset",
"modelAliasesTitle": "Mallialias",
"modelAliasesDesc": "Jokerimerkkikuviot mallien nimien yhdistämiseen • Käytä * ja ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Lisää Mukautettu Alias",
"deprecatedModelId": "Vanhentunut malli-ID",
"newModelId": "Uusi malli-ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Kääntäjä",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Coûts",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Appels par compte avant de changer",
"modelAliases": "Alias de modèle",
"modelAliasesTitle": "Alias de Modèles",
"modelAliasesDesc": "Modèles génériques pour remapper les noms de modèles • Utilisez * et ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Ajouter un Alias Personnalisé",
"deprecatedModelId": "ID du modèle obsolète",
"newModelId": "Nouvel ID de modèle",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Traducteur",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "עלויות",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "שיחות לכל חשבון לפני המעבר",
"modelAliases": "כינויי דגם",
"modelAliasesTitle": "כינויי מודלים",
"modelAliasesDesc": "תבניות תווים כלליים למיפוי מחדש של שמות מודלים • השתמש ב-* וב-?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "הוסף כינוי מותאם אישית",
"deprecatedModelId": "מזהה מודל מיושן",
"newModelId": "מזהה מודל חדש",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "מתרגם",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "लागत",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "स्विच करने से पहले प्रति खाता कॉल",
"modelAliases": "मॉडल उपनाम",
"modelAliasesTitle": "Model Alias",
"modelAliasesDesc": "मॉडल नामों को रीमैप करने के लिए वाइल्डकार्ड पैटर्न • * और ? का उपयोग करें",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Kustom Alias Tambahkan",
"deprecatedModelId": "ID model yang tidak digunakan lagi",
"newModelId": "ID model baru",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "अनुवादक",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Költségek",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Hívások fiókonként váltás előtt",
"modelAliases": "Modell álnevek",
"modelAliasesTitle": "Modell aliasok",
"modelAliasesDesc": "Helyettesítő karakter minták a modellnevek újratervezéséhez • Használja a * és a ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Egyéni Alias Hozzáadása",
"deprecatedModelId": "Elavult modell ID",
"newModelId": "Új modell ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Fordító",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Biaya",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Panggilan per akun sebelum beralih",
"modelAliases": "Alias Model",
"modelAliasesTitle": "Alias Model",
"modelAliasesDesc": "Pola karakter pengganti untuk memetakan ulang nama model • Gunakan * dan ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Tambah Alias Kustom",
"deprecatedModelId": "ID model yang sudah usang",
"newModelId": "ID model baru",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Penerjemah",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Costi",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Chiamate per account prima del cambio",
"modelAliases": "Alias del modello",
"modelAliasesTitle": "Alias dei Modelli",
"modelAliasesDesc": "Modelli di caratteri jolly per rimappare i nomi dei modelli • Utilizzare * e ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Aggiungi Alias Personalizzato",
"deprecatedModelId": "ID modello deprecato",
"newModelId": "Nuovo ID modello",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Traduttore",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "コスト",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "切り替える前のアカウントごとの通話数",
"modelAliases": "モデルのエイリアス",
"modelAliasesTitle": "モデルエイリアス",
"modelAliasesDesc": "モデル名を再マッピングするワイルドカード パターン • * と ? を使用します。",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "カスタムエイリアスを追加",
"deprecatedModelId": "非推奨モデルID",
"newModelId": "新しいモデルID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "翻訳者",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "비용",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "전환 전 계정당 통화",
"modelAliases": "모델 별칭",
"modelAliasesTitle": "모델 별칭",
"modelAliasesDesc": "모델 이름을 다시 매핑하는 와일드카드 패턴 • * 및 ?를 사용합니다.",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "사용자 지정 별칭 추가",
"deprecatedModelId": "사용 중단된 모델 ID",
"newModelId": "새 모델 ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "번역기",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Kos",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Panggilan setiap akaun sebelum bertukar",
"modelAliases": "Alias Model",
"modelAliasesTitle": "Alias Model",
"modelAliasesDesc": "Corak kad liar untuk memetakan semula nama model • Gunakan * dan ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Tambah Alias Tersuai",
"deprecatedModelId": "ID model yang ditamatkan",
"newModelId": "ID model baharu",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Penterjemah",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Kosten",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Gesprekken per account voordat u overstapt",
"modelAliases": "Modelaliassen",
"modelAliasesTitle": "Model Aliases",
"modelAliasesDesc": "Jokertekenpatronen om modelnamen opnieuw toe te wijzen • Gebruik * en ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Aangepast Alias Toevoegen",
"deprecatedModelId": "Verouderd model-ID",
"newModelId": "Nieuw model-ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Vertaler",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Kostnader",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Anrop per konto før bytte",
"modelAliases": "Modellaliaser",
"modelAliasesTitle": "Modellaliaser",
"modelAliasesDesc": "Jokertegnmønstre for å tilordne modellnavn på nytt • Bruk * og ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Legg til Tilpasset Alias",
"deprecatedModelId": "Utdatert modell-ID",
"newModelId": "Ny modell-ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Oversetter",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Mga gastos",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Mga tawag sa bawat account bago lumipat",
"modelAliases": "Mga Alyas ng Modelo",
"modelAliasesTitle": "Mga Alias ng Model",
"modelAliasesDesc": "Mga pattern ng wildcard para i-remap ang mga pangalan ng modelo • Gamitin ang * at ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Magdagdag ng Custom na Alias",
"deprecatedModelId": "Deprecated na Model ID",
"newModelId": "Bagong Model ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Tagasalin",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Koszty",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Połączenia na konto przed zmianą",
"modelAliases": "Aliasy modeli",
"modelAliasesTitle": "Aliasy modeli",
"modelAliasesDesc": "Wzorce wieloznaczne do zmiany nazw modeli • Użyj * i ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Dodaj niestandardowy alias",
"deprecatedModelId": "Przestarzały ID modelu",
"newModelId": "Nowy ID modelu",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Tłumacz",

View File

@@ -1006,7 +1006,19 @@
"auto": "Auto Combo",
"autoDesc": "Pool de roteamento inteligente (Otimizado)",
"lkgp": "Modo LKGP",
"lkgpDesc": "Último Provedor Bom Conhecido (Resiliência previsível)"
"lkgpDesc": "Último Provedor Bom Conhecido (Resiliência previsível)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Custos",
@@ -2006,7 +2018,7 @@
"stickyLimitDesc": "Chamadas por conta antes de trocar",
"modelAliases": "Aliases de Modelo",
"modelAliasesTitle": "Aliases de Modelo",
"modelAliasesDesc": "Padrões coringa para remapear nomes de modelos • Use * e ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Adicionar Alias Personalizado",
"deprecatedModelId": "ID do modelo depreciado",
"newModelId": "Novo ID do modelo",
@@ -2301,7 +2313,28 @@
"clearLkgpCache": "Clear LKGP Cache",
"lkgpCacheCleared": "LKGP cache cleared successfully",
"lkgpCacheClearFailed": "Failed to clear LKGP cache",
"maintenance": "Maintenance"
"maintenance": "Maintenance",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Tradutor",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Pool de roteamento inteligente (Otimizado)",
"lkgp": "Modo LKGP",
"lkgpDesc": "Último Provedor Bom Conhecido (Resiliência previsível)"
"lkgpDesc": "Último Provedor Bom Conhecido (Resiliência previsível)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Custos",
@@ -1997,7 +2009,7 @@
"stickyLimitDesc": "Chamadas por conta antes de mudar",
"modelAliases": "Aliases de modelo",
"modelAliasesTitle": "Aliases de Modelo",
"modelAliasesDesc": "Padrões curinga para remapear nomes de modelos • Use * e ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Adicionar Alias Personalizado",
"deprecatedModelId": "ID do modelo depreciado",
"newModelId": "Novo ID do modelo",
@@ -2286,7 +2298,30 @@
"clearLkgpCache": "Clear LKGP Cache",
"lkgpCacheCleared": "LKGP cache cleared successfully",
"lkgpCacheClearFailed": "Failed to clear LKGP cache",
"maintenance": "Maintenance"
"maintenance": "Maintenance",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Tradutor",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Costuri",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Apeluri pe cont înainte de a comuta",
"modelAliases": "Aliasuri de model",
"modelAliasesTitle": "Aliasuri de model",
"modelAliasesDesc": "Modele wildcard pentru a remapa numele modelelor • Utilizați * și ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Adaugă Alias Personalizat",
"deprecatedModelId": "ID model depreciat",
"newModelId": "ID model nou",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Traducător",

View File

@@ -1021,7 +1021,19 @@
"auto": "Авто",
"autoDesc": "Автоматическая стратегия - система сама выбирает подходящее поведение.",
"lkgp": "LKGP",
"lkgpDesc": "Маршрутизация по последней известной хорошей политике"
"lkgpDesc": "Маршрутизация по последней известной хорошей политике",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Затраты",
@@ -1985,7 +1997,7 @@
"stickyLimitDesc": "Звонки на аккаунт до переключения",
"modelAliases": "Псевдонимы моделей",
"modelAliasesTitle": "Псевдонимы моделей",
"modelAliasesDesc": "Шаблоны подстановочных знаков для переназначения названий моделей • Используйте * и ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Добавить пользовательский псевдоним",
"deprecatedModelId": "Устаревший ID модели",
"newModelId": "Новый ID модели",
@@ -2260,7 +2272,30 @@
"skillsComingSoon": "Маркетплейс навыков скоро появится.",
"memorySkillsTitle": "Память и навыки",
"memorySkillsDesc": "Постоянный контекст и возможности",
"days": "Дни"
"days": "Дни",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Переводчик",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "náklady",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Hovory na účet pred prepnutím",
"modelAliases": "Aliasy modelov",
"modelAliasesTitle": "Aliasy modelov",
"modelAliasesDesc": "Vzory zástupných znakov na premapovanie názvov modelov • Použite * a ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Pridať vlastný alias",
"deprecatedModelId": "Zastaraný ID modelu",
"newModelId": "Nový ID modelu",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Prekladateľ",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Kostnader",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Samtal per konto innan byte",
"modelAliases": "Modellalias",
"modelAliasesTitle": "Modellalias",
"modelAliasesDesc": "Jokerteckenmönster för att mappa om modellnamn • Använd * och ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Lägg Till Anpassat Alias",
"deprecatedModelId": "Föråldrat modell-ID",
"newModelId": "Nytt modell-ID",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Översättare",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "ค่าใช้จ่าย",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "โทรต่อบัญชีก่อนที่จะเปลี่ยน",
"modelAliases": "นามแฝงของโมเดล",
"modelAliasesTitle": "นามแฝงโมเดล",
"modelAliasesDesc": "รูปแบบไวด์การ์ดเพื่อทำการแมปชื่อโมเดลใหม่ • ใช้ * และ ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "เพิ่มนามแฝงที่กำหนดเอง",
"deprecatedModelId": "ID โมเดลที่เลิกใช้",
"newModelId": "ID โมเดลใหม่",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "นักแปล",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Maliyetler",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Geçişten önce hesap başına çağrı sayısı",
"modelAliases": "Model Takma Adları",
"modelAliasesTitle": "Model Takma Adları",
"modelAliasesDesc": "Model adlarını yeniden eşlemek için joker karakter desenleri • * ve ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Özel Takma Ad Ekle",
"deprecatedModelId": "Kullanımdan kaldırılan model kimliği",
"newModelId": "Yeni model kimliği",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Çeviri",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Витрати",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Дзвінки на обліковий запис перед переходом",
"modelAliases": "Псевдоніми моделі",
"modelAliasesTitle": "Псевдоніми моделей",
"modelAliasesDesc": "Шаблони шаблонів узагальнення для зміни назв моделей • Використовуйте * та ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Додати власний псевдонім",
"deprecatedModelId": "Застарілий ID моделі",
"newModelId": "Новий ID моделі",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Перекладач",

View File

@@ -997,7 +997,19 @@
"auto": "Auto Combo",
"autoDesc": "Self-healing smart routing pool (Performance optimized)",
"lkgp": "LKGP Mode",
"lkgpDesc": "Last Known Good Provider (Predictable resilience)"
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "Chi phí",
@@ -1942,7 +1954,7 @@
"stickyLimitDesc": "Cuộc gọi trên mỗi tài khoản trước khi chuyển đổi",
"modelAliases": "Bí danh mẫu",
"modelAliasesTitle": "Bí danh mô hình",
"modelAliasesDesc": "Các mẫu ký tự đại diện để ánh xạ lại tên mẫu máy • Sử dụng * và ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "Thêm Bí Danh Tùy Chỉnh",
"deprecatedModelId": "ID mô hình ngừng sử dụng",
"newModelId": "ID mô hình mới",
@@ -2231,7 +2243,30 @@
"lkgpDesc": "Last Known Good Provider (Predictable resilience)",
"maintenance": "Maintenance",
"purgeExpiredLogs": "Purge Expired Logs",
"purgeLogsFailed": "Failed to purge logs"
"purgeLogsFailed": "Failed to purge logs",
"contextRelay": "Context Relay",
"contextRelayDesc": "Hands off context between providers with automatic summarization",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "Người phiên dịch",

View File

@@ -1006,7 +1006,19 @@
"auto": "自动组合",
"autoDesc": "自愈型智能路由池(性能优化)",
"lkgp": "LKGP 模式",
"lkgpDesc": "最后已知良好提供商(可预测的弹性)"
"lkgpDesc": "最后已知良好提供商(可预测的弹性)",
"wizardGuideTitle": "Getting Started with Combos",
"wizardGuideDesc": "Create model combos to route AI traffic intelligently",
"wizardGuideHint": "or click + Create Combo above",
"createFirstCombo": "Create Your First Combo",
"wizardStep1Title": "Name Your Combo",
"wizardStep1Desc": "Give your combo a unique name to identify it in routing rules",
"wizardStep2Title": "Add Models",
"wizardStep2Desc": "Select AI models and arrange their fallback priority order",
"wizardStep3Title": "Choose Strategy",
"wizardStep3Desc": "Pick how requests are distributed across your models - 13 strategies available",
"wizardStep4Title": "Review & Save",
"wizardStep4Desc": "Review your configuration and activate the combo"
},
"costs": {
"title": "成本",
@@ -2000,7 +2012,7 @@
"stickyLimitDesc": "切换前每个账户连续处理的请求次数",
"modelAliases": "模型别名",
"modelAliasesTitle": "模型别名",
"modelAliasesDesc": "重新映射模型名称的通配符模式 • 使用 * 和 ?",
"modelAliasesDesc": "Remap model names using exact matches or wildcard patterns.",
"addCustomAlias": "添加自定义别名",
"deprecatedModelId": "已弃用的模型 ID",
"newModelId": "新模型 ID",
@@ -2253,7 +2265,28 @@
"lkgpDesc": "最后已知良好提供商(可预测的弹性)",
"maintenance": "维护",
"purgeExpiredLogs": "清理过期日志",
"purgeLogsFailed": "清理日志失败"
"purgeLogsFailed": "清理日志失败",
"contextOpt": "Context Optimized",
"contextOptDesc": "Routes based on context window requirements and conversation length",
"priorityDesc": "Sequential fallback - tries provider 1 first, then provider 2, and so on",
"weightedDesc": "Distributes traffic by percentage weights across providers",
"modelRoutingTitle": "Model Routing Rules",
"modelRoutingDesc": "Automatically route models to specific combos using glob patterns",
"addRule": "Add Rule",
"routeToCombo": "Route to Combo",
"selectCombo": "Select combo...",
"priorityHint": "Higher = checked first. Use 10+ for specific patterns.",
"patternHint": "Use * for any chars, ? for single char. Case-insensitive.",
"noRoutingRules": "No routing rules configured. Requests use the global combo by default.",
"routingRuleHint": "Add a rule like claude-opus* -> frontier-combo to automatically route requests.",
"deleteRoutingRule": "Delete this model routing rule?",
"exactMatchMode": "Exact Match",
"wildcardPatternMode": "Wildcard Pattern",
"exactMatchModeDesc": "Use exact aliases for deprecated or renamed model IDs.",
"wildcardPatternModeDesc": "Use wildcard aliases with * and ? when a family of models should map to one target.",
"noExactAliasesConfigured": "No exact-match aliases configured.",
"wildcardRulesTitle": "Wildcard Rules",
"noWildcardAliasesConfigured": "No wildcard aliases configured."
},
"translator": {
"title": "翻译器",

View File

@@ -1,6 +1,7 @@
"use client";
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
export interface ModelMapping {
id: string;
@@ -17,11 +18,14 @@ interface Combo {
name: string;
}
export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[] }) {
export default function ModelRoutingSection({ combos: externalCombos }: { combos?: Combo[] } = {}) {
const t = useTranslations("settings");
const [mappings, setMappings] = useState<ModelMapping[]>([]);
const [internalCombos, setInternalCombos] = useState<Combo[]>([]);
const [loading, setLoading] = useState(true);
const [adding, setAdding] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const combos = externalCombos || internalCombos;
// Form state
const [pattern, setPattern] = useState("");
@@ -53,6 +57,22 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
};
}, []);
useEffect(() => {
if (externalCombos !== undefined) return;
let cancelled = false;
fetch("/api/combos")
.then((res) => (res.ok ? res.json() : { combos: [] }))
.then((data) => {
if (!cancelled) {
setInternalCombos(Array.isArray(data?.combos) ? data.combos : []);
}
})
.catch(() => {});
return () => {
cancelled = true;
};
}, [externalCombos]);
const refetchMappings = async () => {
const data = await loadMappings();
setMappings(data);
@@ -100,7 +120,7 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
};
const handleDelete = async (id: string) => {
if (!confirm("Delete this model routing rule?")) return;
if (!confirm(t("deleteRoutingRule"))) return;
try {
await fetch(`/api/model-combo-mappings/${id}`, { method: "DELETE" });
setMappings((prev) => prev.filter((m) => m.id !== id));
@@ -126,10 +146,8 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
<div className="flex items-center gap-2">
<span className="material-symbols-outlined text-primary text-[18px]">route</span>
<div>
<h3 className="text-sm font-semibold">Model Routing Rules</h3>
<p className="text-[11px] text-text-muted">
Automatically route models to specific combos using glob patterns
</p>
<h3 className="text-sm font-semibold">{t("modelRoutingTitle")}</h3>
<p className="text-[11px] text-text-muted">{t("modelRoutingDesc")}</p>
</div>
</div>
{!adding && (
@@ -139,7 +157,7 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
bg-primary/10 text-primary hover:bg-primary/20 transition-colors"
>
<span className="material-symbols-outlined text-[14px]">add</span>
Add Rule
{t("addRule")}
</button>
)}
</div>
@@ -150,7 +168,7 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Pattern
{t("pattern")}
</label>
<input
value={pattern}
@@ -159,13 +177,11 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
className="w-full mt-0.5 px-2.5 py-1.5 text-xs rounded-lg border border-black/10 dark:border-white/10
bg-white dark:bg-black/20 focus:outline-none focus:ring-1 focus:ring-primary"
/>
<p className="text-[9px] text-text-muted mt-0.5">
Use * for any chars, ? for single char. Case-insensitive.
</p>
<p className="text-[9px] text-text-muted mt-0.5">{t("patternHint")}</p>
</div>
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Route to Combo
{t("routeToCombo")}
</label>
<select
value={comboId}
@@ -173,7 +189,7 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
className="w-full mt-0.5 px-2.5 py-1.5 text-xs rounded-lg border border-black/10 dark:border-white/10
bg-white dark:bg-black/20 focus:outline-none focus:ring-1 focus:ring-primary"
>
<option value="">Select combo...</option>
<option value="">{t("selectCombo")}</option>
{combos.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
@@ -183,7 +199,7 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
</div>
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Priority
{t("priority")}
</label>
<input
type="number"
@@ -192,13 +208,11 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
className="w-full mt-0.5 px-2.5 py-1.5 text-xs rounded-lg border border-black/10 dark:border-white/10
bg-white dark:bg-black/20 focus:outline-none focus:ring-1 focus:ring-primary"
/>
<p className="text-[9px] text-text-muted mt-0.5">
Higher = checked first. Use 10+ for specific patterns.
</p>
<p className="text-[9px] text-text-muted mt-0.5">{t("priorityHint")}</p>
</div>
<div>
<label className="text-[10px] font-medium text-text-muted uppercase tracking-wider">
Description
{t("description")}
</label>
<input
value={description}
@@ -216,14 +230,14 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
className="px-3 py-1 text-xs font-medium rounded-lg bg-primary text-white
hover:bg-primary/90 disabled:opacity-40 transition-colors"
>
{editingId ? "Update" : "Save"}
{editingId ? t("update") : t("save")}
</button>
<button
onClick={resetForm}
className="px-3 py-1 text-xs font-medium rounded-lg
bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
>
Cancel
{t("cancel")}
</button>
</div>
</div>
@@ -231,18 +245,11 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
{/* Mappings list */}
{loading ? (
<div className="mt-3 text-xs text-text-muted">Loading...</div>
<div className="mt-3 text-xs text-text-muted">{t("loading")}</div>
) : mappings.length === 0 ? (
<div className="mt-3 text-center py-4">
<p className="text-xs text-text-muted">
No routing rules configured. Requests use the global combo by default.
</p>
<p className="text-[10px] text-text-muted mt-1">
Add a rule like{" "}
<code className="px-1 py-0.5 rounded bg-black/5 dark:bg-white/5">claude-opus*</code>
{" → "} <span className="font-medium">frontier-combo</span> to automatically route
requests.
</p>
<p className="text-xs text-text-muted">{t("noRoutingRules")}</p>
<p className="text-[10px] text-text-muted mt-1">{t("routingRuleHint")}</p>
</div>
) : (
<div className="mt-3 flex flex-col gap-1.5">
@@ -277,7 +284,7 @@ export default function ModelRoutingSection({ combos = [] }: { combos?: Combo[]
<button
onClick={() => handleToggle(m)}
className="p-1 rounded hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
title={m.enabled ? "Disable" : "Enable"}
title={m.enabled ? t("disable") : t("enable")}
>
<span
className={`material-symbols-outlined text-[14px] ${m.enabled ? "text-emerald-500" : "text-text-muted"}`}

View File

@@ -1,5 +1,5 @@
import { execSync, execFileSync } from "child_process";
import { existsSync } from "fs";
import { execFileSync, execSync } from "child_process";
import { existsSync, readFileSync } from "fs";
/**
* Get raw machine ID using OS-specific methods.
@@ -59,12 +59,7 @@ function getMachineIdRaw(): string {
try {
for (const filePath of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
try {
const content = execFileSync("cat", [filePath], {
encoding: "utf8",
timeout: 5000,
})
.trim()
.toLowerCase();
const content = readFileSync(filePath, "utf8").trim().toLowerCase();
if (content.length > 8) return content;
} catch {
// Try the next candidate file

View File

@@ -1,12 +1,14 @@
/**
* maskEmail — Privacy display utility for email addresses.
*
* Masks the username and domain name portions of an email address
* to prevent identity exposure in dashboards and logs.
* Masks both username and domain portions of an email address.
* - Username: keep the first `visibleChars`, mask the rest
* - Domain: mask everything except the final `visibleChars`
*
* @example
* maskEmail("diego.souza@gmail.com") // "die********@gmail.com"
* maskEmail("a@b.com") // "a@b.com" (too short to mask)
* maskEmail("diego.souza@outlook.com.br") // "die********@***********.br"
* maskEmail("user@gmail.com") // "use*@******.com"
* maskEmail("a@b.com") // "a@b.com" (too short to mask)
*/
export function maskEmail(email: string | null | undefined, visibleChars = 3): string {
if (!email) return "";
@@ -14,21 +16,20 @@ export function maskEmail(email: string | null | undefined, visibleChars = 3): s
const atIndex = email.lastIndexOf("@");
const username = email.slice(0, atIndex);
const rest = email.slice(atIndex + 1); // "gmail.com", "co.uk", etc.
const dotIndex = rest.indexOf(".");
const domainName = dotIndex !== -1 ? rest.slice(0, dotIndex) : rest;
const tld = dotIndex !== -1 ? rest.slice(dotIndex) : ""; // ".com", ".co.uk"
const domain = email.slice(atIndex + 1);
// If username is too short to mask meaningfully, return as-is
if (username.length <= visibleChars) return email;
const maskedUser = username.slice(0, visibleChars) + "*".repeat(username.length - visibleChars);
if (domain.length <= visibleChars) {
return `${maskedUser}@${domain}`;
}
// Preserve the full domain name to maintain clear provider account differentiation
const maskedDomain = domainName;
const maskedDomain =
"*".repeat(domain.length - visibleChars) + domain.slice(domain.length - visibleChars);
return `${maskedUser}@${maskedDomain}${tld}`;
return `${maskedUser}@${maskedDomain}`;
}
/**

View File

@@ -183,6 +183,22 @@ export const createComboSchema = z.object({
// ──── Settings Schemas ────
// FASE-01: Removed .passthrough() — only explicitly listed fields are accepted
const settingsFallbackStrategySchema = z.enum([
"priority",
"weighted",
"round-robin",
"context-relay",
"fill-first",
"p2c",
"random",
"least-used",
"cost-optimized",
"strict-random",
"auto",
"context-optimized",
"lkgp",
]);
export const updateSettingsSchema = z.object({
newPassword: z.string().min(1).max(200).optional(),
currentPassword: z.string().max(200).optional(),
@@ -200,17 +216,7 @@ export const updateSettingsSchema = z.object({
hideHealthCheckLogs: z.boolean().optional(),
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
// Routing settings (#134)
fallbackStrategy: z
.enum([
"fill-first",
"round-robin",
"p2c",
"random",
"least-used",
"cost-optimized",
"strict-random",
])
.optional(),
fallbackStrategy: settingsFallbackStrategySchema.optional(),
wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),
stickyRoundRobinLimit: z.number().int().min(0).max(1000).optional(),
// Auto intent classifier settings (multilingual routing)

View File

@@ -8,6 +8,22 @@
import { z } from "zod";
import { HIDEABLE_SIDEBAR_ITEM_IDS } from "@/shared/constants/sidebarVisibility";
const fallbackStrategyValues = [
"priority",
"weighted",
"round-robin",
"context-relay",
"fill-first",
"p2c",
"random",
"least-used",
"cost-optimized",
"strict-random",
"auto",
"context-optimized",
"lkgp",
] as const;
export const updateSettingsSchema = z.object({
newPassword: z.string().min(1).max(200).optional(),
currentPassword: z.string().max(200).optional(),
@@ -30,9 +46,7 @@ export const updateSettingsSchema = z.object({
debugMode: z.boolean().optional(),
hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
// Routing settings (#134)
fallbackStrategy: z
.enum(["fill-first", "round-robin", "p2c", "random", "least-used", "cost-optimized"])
.optional(),
fallbackStrategy: z.enum(fallbackStrategyValues).optional(),
wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),
stickyRoundRobinLimit: z.number().int().min(0).max(1000).optional(),
// Auto intent classifier settings (multilingual routing)

View File

@@ -61,7 +61,10 @@ import {
isFallbackDecision,
shouldUseFallback,
} from "@omniroute/open-sse/services/emergencyFallback.ts";
import { registerCodexQuotaFetcher } from "@omniroute/open-sse/services/codexQuotaFetcher.ts";
import {
registerCodexConnection,
registerCodexQuotaFetcher,
} from "@omniroute/open-sse/services/codexQuotaFetcher.ts";
// Register Codex quota fetcher at module load (once per server start).
// This hooks into the quotaPreflight + quotaMonitor systems so that combos
@@ -178,6 +181,7 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
// T04: client-provided external session header has priority over generated fingerprint.
const externalSessionId = extractExternalSessionId(request.headers);
const sessionId = externalSessionId || generateStableSessionId(body);
const requestedConnectionId = request.headers.get("x-omniroute-connection")?.trim() || null;
if (sessionId) {
touchSession(sessionId);
}
@@ -393,7 +397,11 @@ export async function handleChat(request: any, clientRawRequest: any = null) {
null,
apiKeyInfo,
telemetry,
{ sessionId, forceLiveComboTest: isComboLiveTest },
{
sessionId,
forceLiveComboTest: isComboLiveTest,
forcedConnectionId: requestedConnectionId,
},
null,
false
);
@@ -541,6 +549,20 @@ async function handleSingleModelChat(
}
}
const refreshedCredentials = await checkAndRefreshToken(provider, credentials);
if (provider === "codex" && refreshedCredentials?.accessToken && credentials.connectionId) {
const workspaceId =
typeof refreshedCredentials?.providerSpecificData?.workspaceId === "string" &&
refreshedCredentials.providerSpecificData.workspaceId.trim().length > 0
? refreshedCredentials.providerSpecificData.workspaceId
: typeof credentials?.providerSpecificData?.workspaceId === "string" &&
credentials.providerSpecificData.workspaceId.trim().length > 0
? credentials.providerSpecificData.workspaceId
: undefined;
registerCodexConnection(credentials.connectionId, {
accessToken: refreshedCredentials.accessToken,
...(workspaceId ? { workspaceId } : {}),
});
}
if (runtimeOptions.sessionId && body?._omnirouteInternalRequest !== "context-handoff") {
touchSession(runtimeOptions.sessionId, credentials.connectionId);
startQuotaMonitor(

View File

@@ -182,8 +182,8 @@ test.describe("Combo Unification", () => {
await page.getByTestId("strategy-option-auto").click();
await page.getByTestId("combo-builder-next").click();
await expect(page.getByText("Candidate Pool")).toBeVisible();
await expect(page.getByText("Mode Pack")).toBeVisible();
await expect(page.getByText("Exploration Rate")).toBeVisible();
await expect(page.getByText("Candidate Pool", { exact: true })).toBeVisible();
await expect(page.getByText("Mode Pack", { exact: true })).toBeVisible();
await expect(page.getByText("Exploration Rate", { exact: true })).toBeVisible();
});
});

View File

@@ -443,7 +443,7 @@ test("validateProviderApiKey uses CC skeleton request after /models fallback", a
assert.equal(calls[1].headers.Accept, "text/event-stream");
});
test("handleChatCore forces upstream streaming for CC compatible while returning JSON to non-stream clients", async () => {
test("handleChatCore respects non-streaming upstream requests for CC compatible providers", async () => {
const calls = [];
globalThis.fetch = async (url, init = {}) => {
calls.push({
@@ -518,8 +518,8 @@ test("handleChatCore forces upstream streaming for CC compatible while returning
assert.equal(result.success, true);
assert.equal(calls.length, 1);
assert.equal(calls[0].headers.Accept, "text/event-stream");
assert.equal(calls[0].body.stream, true);
assert.equal(calls[0].headers.Accept, "application/json");
assert.equal(calls[0].body.stream, undefined);
assert.equal(
calls[0].body.system.some((block) => block.cache_control !== undefined),
false

View File

@@ -457,8 +457,8 @@ test("chatCore builds Claude Code-compatible upstream requests for CC providers"
});
assert.equal(result.success, true);
assert.equal(call.headers.Accept ?? call.headers.accept, "text/event-stream");
assert.equal(call.body.stream, true);
assert.equal(call.headers.Accept ?? call.headers.accept, "application/json");
assert.equal(call.body.stream, undefined);
assert.equal(call.body.context_management.edits[0].type, "clear_thinking_20251015");
assert.equal(typeof call.body.metadata.user_id, "string");
assert.equal(call.body.messages[0].role, "user");

View File

@@ -6,6 +6,7 @@ const { CircuitBreaker, getCircuitBreaker, getAllCircuitBreakerStatuses, STATE }
await import("../../src/shared/utils/circuitBreaker.ts");
const { handleComboChat, getComboFromData } = await import("../../open-sse/services/combo.ts");
const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts");
const { PROVIDER_PROFILES } = await import("../../open-sse/config/constants.ts");
@@ -38,25 +39,31 @@ function mockHandler(statusSequence) {
};
}
function getComboTargetBreakerKey(combo, index = 0) {
const step = normalizeComboStep(combo.models[index], {
comboName: combo.name,
index,
});
return `combo:${combo.name}:${step.id}`;
}
// ─── Circuit Breaker Integration Tests ──────────────────────────────────────
// NOTE: combo.ts uses the full model string (e.g. "combo:groq/llama-3.3-70b")
// as the circuit breaker key, not just the provider prefix.
test("handleComboChat: circuit breaker opens after repeated 502 errors", async () => {
// breaker key mirrors what combo.ts uses: "combo:<full-model-string>"
const breakerKey = "combo:groq/llama-3.3-70b";
const breaker = getCircuitBreaker(breakerKey, {
failureThreshold: 3,
resetTimeout: 60000,
});
breaker.reset();
const combo = {
name: "test-combo",
models: [{ model: "groq/llama-3.3-70b", weight: 0 }],
strategy: "priority",
config: { maxRetries: 0 },
};
const breakerKey = getComboTargetBreakerKey(combo);
const breaker = getCircuitBreaker(breakerKey, {
failureThreshold: 3,
resetTimeout: 60000,
});
breaker.reset();
const log = mockLog();
@@ -82,7 +89,15 @@ test("handleComboChat: circuit breaker opens after repeated 502 errors", async (
test("handleComboChat: skips models with open circuit breaker", async () => {
// Set up: groq breaker is OPEN, fireworks breaker is CLOSED
const groqBreakerKey = "combo:groq/llama-3.3-70b";
const combo = {
name: "test-skip-combo",
models: [
{ model: "groq/llama-3.3-70b", weight: 0 },
{ model: "fireworks/deepseek-v3p1", weight: 0 },
],
strategy: "priority",
};
const groqBreakerKey = getComboTargetBreakerKey(combo, 0);
const groqBreaker = getCircuitBreaker(groqBreakerKey, {
failureThreshold: 3,
resetTimeout: 60000,
@@ -94,22 +109,13 @@ test("handleComboChat: skips models with open circuit breaker", async () => {
groqBreaker._onFailure();
assert.equal(groqBreaker.getStatus().state, STATE.OPEN);
const fireworksBreakerKey = "combo:fireworks/deepseek-v3p1";
const fireworksBreakerKey = getComboTargetBreakerKey(combo, 1);
const fireworksBreaker = getCircuitBreaker(fireworksBreakerKey, {
failureThreshold: 5,
resetTimeout: 30000,
});
fireworksBreaker.reset();
const combo = {
name: "test-skip-combo",
models: [
{ model: "groq/llama-3.3-70b", weight: 0 },
{ model: "fireworks/deepseek-v3p1", weight: 0 },
],
strategy: "priority",
};
const log = mockLog();
const result = await handleComboChat({
@@ -132,17 +138,6 @@ test("handleComboChat: skips models with open circuit breaker", async () => {
});
test("handleComboChat: returns 503 when all breakers are open", async () => {
// Open both breakers using the full model string keys
const groqBreaker = getCircuitBreaker("combo:groq/llama-3.3-70b");
groqBreaker.reset();
groqBreaker._onFailure();
groqBreaker._onFailure();
groqBreaker._onFailure();
const fireworksBreaker = getCircuitBreaker("combo:fireworks/deepseek-v3p1");
fireworksBreaker.reset();
for (let i = 0; i < 5; i++) fireworksBreaker._onFailure();
const combo = {
name: "test-all-open",
models: [
@@ -152,6 +147,25 @@ test("handleComboChat: returns 503 when all breakers are open", async () => {
strategy: "priority",
};
// Open both breakers explicitly before invoking the combo.
const groqBreaker = getCircuitBreaker(getComboTargetBreakerKey(combo, 0), {
failureThreshold: 3,
resetTimeout: 60000,
});
groqBreaker.reset();
groqBreaker._onFailure();
groqBreaker._onFailure();
groqBreaker._onFailure();
assert.equal(groqBreaker.getStatus().state, STATE.OPEN);
const fireworksBreaker = getCircuitBreaker(getComboTargetBreakerKey(combo, 1), {
failureThreshold: 5,
resetTimeout: 30000,
});
fireworksBreaker.reset();
for (let i = 0; i < 5; i++) fireworksBreaker._onFailure();
assert.equal(fireworksBreaker.getStatus().state, STATE.OPEN);
const log = mockLog();
const result = await handleComboChat({
@@ -170,18 +184,17 @@ test("handleComboChat: returns 503 when all breakers are open", async () => {
});
test("handleComboChat: 429 errors also trigger circuit breaker", async () => {
const breakerKey = "combo:cerebras/llama-3.3-70b";
const breaker = getCircuitBreaker(breakerKey, {
failureThreshold: 5,
resetTimeout: 30000,
});
breaker.reset();
const combo = {
name: "test-429",
models: [{ model: "cerebras/llama-3.3-70b", weight: 0 }],
strategy: "priority",
};
const breakerKey = getComboTargetBreakerKey(combo);
const breaker = getCircuitBreaker(breakerKey, {
failureThreshold: 5,
resetTimeout: 30000,
});
breaker.reset();
const log = mockLog();

View File

@@ -18,6 +18,7 @@ const { resetAllCircuitBreakers, getCircuitBreaker } =
const { resetAll: resetAllSemaphores } =
await import("../../open-sse/services/rateLimitSemaphore.ts");
const { _resetAllDecks } = await import("../../src/shared/utils/shuffleDeck.ts");
const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts");
const originalFetch = globalThis.fetch;
@@ -161,7 +162,17 @@ test("handleComboChat context-relay skips unavailable models and falls through t
});
test("handleComboChat context-relay skips models with an open circuit breaker", async () => {
const breaker = getCircuitBreaker("combo:codex/gpt-5.4", {
const combo = {
name: "relay-breaker",
strategy: "context-relay",
models: ["codex/gpt-5.4", "openai/gpt-4o-mini"],
config: { maxRetries: 0 },
};
const firstStep = normalizeComboStep(combo.models[0], {
comboName: combo.name,
index: 0,
});
const breaker = getCircuitBreaker(`combo:${combo.name}:${firstStep.id}`, {
failureThreshold: 1,
resetTimeout: 60000,
});
@@ -174,12 +185,7 @@ test("handleComboChat context-relay skips models with an open circuit breaker",
body: {
messages: [{ role: "user", content: "Breaker" }],
},
combo: {
name: "relay-breaker",
strategy: "context-relay",
models: ["codex/gpt-5.4", "openai/gpt-4o-mini"],
config: { maxRetries: 0 },
},
combo,
handleSingleModel: async (_body, modelStr) => {
calls.push(modelStr);
return okResponse();

View File

@@ -351,6 +351,8 @@ test("combo test route preserves structured step metadata for repeated model/acc
fetchCalls.map(({ init }) => JSON.parse(init.body).model),
["openai/gpt-4o-mini", "openai/gpt-4o-mini"]
);
assert.equal(fetchCalls[0].init.headers["X-OmniRoute-Connection"], "conn-openai-a");
assert.equal(fetchCalls[1].init.headers["X-OmniRoute-Connection"], "conn-openai-b");
assert.equal(body.results[0].connectionId, "conn-openai-a");
assert.equal(body.results[0].label, "Account A");
assert.equal(body.results[1].connectionId, "conn-openai-b");

View File

@@ -302,6 +302,13 @@ test("DefaultExecutor.buildHeaders rotates extra API keys and builds Claude Code
},
true
);
const ccJsonHeaders = cc.buildHeaders(
{
apiKey: "cc-key",
providerSpecificData: { ccSessionId: "session-1" },
},
false
);
assert.equal(first.Authorization, "Bearer primary");
assert.equal(second.Authorization, "Bearer extra-1");
@@ -309,6 +316,7 @@ test("DefaultExecutor.buildHeaders rotates extra API keys and builds Claude Code
assert.equal(ccHeaders["anthropic-version"], CLAUDE_CODE_COMPATIBLE_ANTHROPIC_VERSION);
assert.equal(ccHeaders["X-Claude-Code-Session-Id"], "session-1");
assert.equal(ccHeaders.Accept, "text/event-stream");
assert.equal(ccJsonHeaders.Accept, "application/json");
});
test("DefaultExecutor.transformRequest is a passthrough and preserves model ids with slashes", () => {

View File

@@ -16,6 +16,7 @@ const originalWindir = process.env.windir;
const originalExecSync = childProcess.execSync;
const originalExecFileSync = childProcess.execFileSync;
const originalExistsSync = fs.existsSync;
const originalReadFileSync = fs.readFileSync;
function setPlatform(value) {
Object.defineProperty(process, "platform", {
@@ -32,6 +33,7 @@ test.afterEach(() => {
childProcess.execSync = originalExecSync;
childProcess.execFileSync = originalExecFileSync;
fs.existsSync = originalExistsSync;
fs.readFileSync = originalReadFileSync;
if (originalPlatformDescriptor) {
Object.defineProperty(process, "platform", originalPlatformDescriptor);
@@ -82,10 +84,9 @@ test("machineId: falls back to Linux machine-id files before hostname", async ()
setPlatform("linux");
fs.existsSync = () => false;
childProcess.execFileSync = (command, args, options) => {
assert.equal(command, "cat");
assert.deepEqual(args, ["/etc/machine-id"]);
assert.equal(options.encoding, "utf8");
fs.readFileSync = (filePath, encoding) => {
assert.equal(filePath, "/etc/machine-id");
assert.equal(encoding, "utf8");
return "LINUX-MACHINE-ID\n";
};
childProcess.execSync = () => {

View File

@@ -8,7 +8,7 @@ import {
describe("maskEmail", () => {
it("masks standard email correctly", () => {
assert.equal(maskEmail("diego.souza@gmail.com"), "die********@gmail.com");
assert.equal(maskEmail("diego.souza@gmail.com"), "die********@******com");
});
it("masks email with short username (exactly visibleChars)", () => {
@@ -18,7 +18,7 @@ describe("maskEmail", () => {
it("masks email with longer username", () => {
const result = maskEmail("hello@example.com");
assert.equal(result, "hel**@example.com");
assert.equal(result, "hel**@********com");
});
it("returns empty string for null", () => {
@@ -38,14 +38,12 @@ describe("maskEmail", () => {
});
it("handles multi-part TLDs correctly", () => {
const result = maskEmail("user@company.co.uk");
assert.ok(result.endsWith(".co.uk"), `Expected .co.uk suffix, got: ${result}`);
assert.ok(result.includes("@"), "Should contain @");
assert.equal(maskEmail("diego.souza@outlook.com.br"), "die********@***********.br");
assert.equal(maskEmail("evelyn@outlook.com.br"), "eve***@***********.br");
});
it("handles single-char domain name", () => {
const result = maskEmail("user@x.com");
assert.ok(result.includes("@x.com"), `Expected @x.com in: ${result}`);
assert.equal(maskEmail("user@x.com"), "use*@**com");
});
it("allows customizing visibleChars", () => {
@@ -54,14 +52,14 @@ describe("maskEmail", () => {
});
it("masks email-like values stored in generic labels", () => {
assert.equal(maskEmailLikeValue("person@example.com"), "per***@example.com");
assert.equal(maskEmailLikeValue("person@example.com"), "per***@********com");
assert.equal(maskEmailLikeValue("Work Account"), "Work Account");
});
it("picks the first non-empty masked display value", () => {
assert.equal(
pickMaskedDisplayValue(["", "person@example.com", "fallback"], "fallback"),
"per***@example.com"
"per***@********com"
);
assert.equal(pickMaskedDisplayValue([null, "Workspace"], "fallback"), "Workspace");
});

View File

@@ -0,0 +1,17 @@
import test from "node:test";
import assert from "node:assert/strict";
import { ROUTING_STRATEGIES } from "@/shared/constants/routingStrategies";
import { updateSettingsSchema as settingsRouteSchema } from "@/shared/validation/settingsSchemas";
import { updateSettingsSchema as sharedSettingsSchema } from "@/shared/validation/schemas";
for (const strategy of ROUTING_STRATEGIES) {
test(`settings route schema accepts fallbackStrategy=${strategy.value}`, () => {
const parsed = settingsRouteSchema.parse({ fallbackStrategy: strategy.value });
assert.equal(parsed.fallbackStrategy, strategy.value);
});
test(`shared settings schema accepts fallbackStrategy=${strategy.value}`, () => {
const parsed = sharedSettingsSchema.parse({ fallbackStrategy: strategy.value });
assert.equal(parsed.fallbackStrategy, strategy.value);
});
}