Merge branch 'review-pr-983' into release/v3.5.1

This commit is contained in:
diegosouzapw
2026-04-04 09:22:41 -03:00
45 changed files with 4343 additions and 36 deletions

View File

@@ -0,0 +1 @@
[ 32698ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:20130/dashboard/usage?_rsc=18t7j:0

View File

@@ -0,0 +1,158 @@
- generic [active] [ref=e1]:
- link "Skip to content" [ref=e2] [cursor=pointer]:
- /url: "#main-content"
- generic [ref=e3]:
- complementary [ref=e5]:
- link "Skip to content" [ref=e6] [cursor=pointer]:
- /url: "#main-content"
- link "OmniRoute v3.5.0" [ref=e12] [cursor=pointer]:
- /url: /dashboard
- img [ref=e14]
- generic [ref=e26]:
- heading "OmniRoute" [level=1] [ref=e27]
- generic [ref=e28]: v3.5.0
- navigation "Main navigation" [ref=e29]:
- generic [ref=e30]:
- link "home Home" [ref=e31] [cursor=pointer]:
- /url: /dashboard
- generic [ref=e32]: home
- generic [ref=e33]: Home
- link "api Endpoints" [ref=e34] [cursor=pointer]:
- /url: /dashboard/endpoint
- generic [ref=e35]: api
- generic [ref=e36]: Endpoints
- link "vpn_key API Manager" [ref=e37] [cursor=pointer]:
- /url: /dashboard/api-manager
- generic [ref=e38]: vpn_key
- generic [ref=e39]: API Manager
- link "dns Providers" [ref=e40] [cursor=pointer]:
- /url: /dashboard/providers
- generic [ref=e41]: dns
- generic [ref=e42]: Providers
- link "layers Combos" [ref=e43] [cursor=pointer]:
- /url: /dashboard/combos
- generic [ref=e44]: layers
- generic [ref=e45]: Combos
- link "auto_awesome Auto Combo" [ref=e46] [cursor=pointer]:
- /url: /dashboard/auto-combo
- generic [ref=e47]: auto_awesome
- generic [ref=e48]: Auto Combo
- link "account_balance_wallet Costs" [ref=e49] [cursor=pointer]:
- /url: /dashboard/costs
- generic [ref=e50]: account_balance_wallet
- generic [ref=e51]: Costs
- link "analytics Analytics" [ref=e52] [cursor=pointer]:
- /url: /dashboard/analytics
- generic [ref=e53]: analytics
- generic [ref=e54]: Analytics
- link "tune Limits & Quotas" [ref=e55] [cursor=pointer]:
- /url: /dashboard/limits
- generic [ref=e56]: tune
- generic [ref=e57]: Limits & Quotas
- link "cached Cache" [ref=e58] [cursor=pointer]:
- /url: /dashboard/cache
- generic [ref=e59]: cached
- generic [ref=e60]: Cache
- link "perm_media Media" [ref=e61] [cursor=pointer]:
- /url: /dashboard/cache/media
- generic [ref=e62]: perm_media
- generic [ref=e63]: Media
- generic [ref=e64]:
- paragraph [ref=e65]: CLI
- link "terminal Tools" [ref=e66] [cursor=pointer]:
- /url: /dashboard/cli-tools
- generic [ref=e67]: terminal
- generic [ref=e68]: Tools
- link "smart_toy Agents" [ref=e69] [cursor=pointer]:
- /url: /dashboard/agents
- generic [ref=e70]: smart_toy
- generic [ref=e71]: Agents
- link "psychology Memory" [ref=e72] [cursor=pointer]:
- /url: /dashboard/memory
- generic [ref=e73]: psychology
- generic [ref=e74]: Memory
- link "auto_fix_high Skills" [ref=e75] [cursor=pointer]:
- /url: /dashboard/skills
- generic [ref=e76]: auto_fix_high
- generic [ref=e77]: Skills
- generic [ref=e78]:
- paragraph [ref=e79]: System
- link "health_and_safety Health" [ref=e80] [cursor=pointer]:
- /url: /dashboard/health
- generic [ref=e81]: health_and_safety
- generic [ref=e82]: Health
- link "description Logs" [ref=e83] [cursor=pointer]:
- /url: /dashboard/logs
- generic [ref=e84]: description
- generic [ref=e85]: Logs
- link "history Audit Log" [ref=e86] [cursor=pointer]:
- /url: /dashboard/audit
- generic [ref=e87]: history
- generic [ref=e88]: Audit Log
- link "settings Settings" [ref=e89] [cursor=pointer]:
- /url: /dashboard/settings
- generic [ref=e90]: settings
- generic [ref=e91]: Settings
- generic [ref=e92]:
- paragraph [ref=e93]: Help
- link "menu_book Docs" [ref=e94] [cursor=pointer]:
- /url: /docs
- generic [ref=e95]: menu_book
- generic [ref=e96]: Docs
- link "bug_report Issues" [ref=e97] [cursor=pointer]:
- /url: https://github.com/diegosouzapw/OmniRoute/issues
- generic [ref=e98]: bug_report
- generic [ref=e99]: Issues
- generic [ref=e100]:
- button "restart_alt Restart" [ref=e101]:
- generic: restart_alt
- text: Restart
- button "power_settings_new Shutdown" [ref=e102]:
- generic: power_settings_new
- text: Shutdown
- main [ref=e103]:
- generic [ref=e104]:
- button "menu" [ref=e106]:
- generic: menu
- generic [ref=e107]:
- button "🇺🇸 EN expand_more" [ref=e109]:
- generic [ref=e110]: 🇺🇸
- generic [ref=e111]: EN
- generic: expand_more
- button "Switch to dark mode" [ref=e112]:
- generic: dark_mode
- button "logout" [ref=e113]:
- generic: logout
- generic [ref=e115]:
- navigation "Breadcrumb" [ref=e116]:
- link "Dashboard" [ref=e118] [cursor=pointer]:
- /url: /dashboard
- generic [ref=e119]:
- generic [ref=e120]:
- generic [ref=e121]: Onboarding
- generic [ref=e123]:
- generic [ref=e124]:
- generic [ref=e126]: "1"
- generic [ref=e129]: "2"
- generic [ref=e132]: "3"
- generic [ref=e135]: "4"
- generic [ref=e138]: "5"
- generic [ref=e139]:
- generic [ref=e140]:
- generic [ref=e141]: waving_hand
- heading "Welcome" [level=2] [ref=e142]
- generic [ref=e144]:
- paragraph [ref=e145]: OmniRoute is your local AI API proxy. It routes requests to multiple AI providers with load balancing, failover, and usage tracking.
- generic [ref=e146]:
- generic [ref=e147]:
- generic [ref=e148]: swap_horiz
- text: Multi-Provider
- generic [ref=e149]:
- generic [ref=e150]: monitoring
- text: Usage Tracking
- generic [ref=e151]:
- generic [ref=e152]: shield
- text: API Key Mgmt
- button "Get Started" [ref=e155] [cursor=pointer]
- button "Skip wizard entirely" [ref=e157] [cursor=pointer]
- alert [ref=e158]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,132 @@
- generic [active] [ref=e1]:
- link "Skip to content" [ref=e2] [cursor=pointer]:
- /url: "#main-content"
- generic [ref=e3]:
- complementary [ref=e5]:
- link "Skip to content" [ref=e6] [cursor=pointer]:
- /url: "#main-content"
- link "OmniRoute v3.5.0" [ref=e12] [cursor=pointer]:
- /url: /dashboard
- img [ref=e14]
- generic [ref=e26]:
- heading "OmniRoute" [level=1] [ref=e27]
- generic [ref=e28]: v3.5.0
- navigation "Main navigation" [ref=e29]:
- generic [ref=e30]:
- link "home Home" [ref=e31] [cursor=pointer]:
- /url: /dashboard
- generic [ref=e32]: home
- generic [ref=e33]: Home
- link "api Endpoints" [ref=e34] [cursor=pointer]:
- /url: /dashboard/endpoint
- generic [ref=e35]: api
- generic [ref=e36]: Endpoints
- link "vpn_key API Manager" [ref=e37] [cursor=pointer]:
- /url: /dashboard/api-manager
- generic [ref=e38]: vpn_key
- generic [ref=e39]: API Manager
- link "dns Providers" [ref=e40] [cursor=pointer]:
- /url: /dashboard/providers
- generic [ref=e41]: dns
- generic [ref=e42]: Providers
- link "layers Combos" [ref=e43] [cursor=pointer]:
- /url: /dashboard/combos
- generic [ref=e44]: layers
- generic [ref=e45]: Combos
- link "auto_awesome Auto Combo" [ref=e46] [cursor=pointer]:
- /url: /dashboard/auto-combo
- generic [ref=e47]: auto_awesome
- generic [ref=e48]: Auto Combo
- link "account_balance_wallet Costs" [ref=e49] [cursor=pointer]:
- /url: /dashboard/costs
- generic [ref=e50]: account_balance_wallet
- generic [ref=e51]: Costs
- link "analytics Analytics" [ref=e52] [cursor=pointer]:
- /url: /dashboard/analytics
- generic [ref=e53]: analytics
- generic [ref=e54]: Analytics
- link "tune Limits & Quotas" [ref=e55] [cursor=pointer]:
- /url: /dashboard/limits
- generic [ref=e56]: tune
- generic [ref=e57]: Limits & Quotas
- link "cached Cache" [ref=e58] [cursor=pointer]:
- /url: /dashboard/cache
- generic [ref=e59]: cached
- generic [ref=e60]: Cache
- link "perm_media Media" [ref=e61] [cursor=pointer]:
- /url: /dashboard/cache/media
- generic [ref=e62]: perm_media
- generic [ref=e63]: Media
- generic [ref=e64]:
- paragraph [ref=e65]: CLI
- link "terminal Tools" [ref=e66] [cursor=pointer]:
- /url: /dashboard/cli-tools
- generic [ref=e67]: terminal
- generic [ref=e68]: Tools
- link "smart_toy Agents" [ref=e69] [cursor=pointer]:
- /url: /dashboard/agents
- generic [ref=e70]: smart_toy
- generic [ref=e71]: Agents
- link "psychology Memory" [ref=e72] [cursor=pointer]:
- /url: /dashboard/memory
- generic [ref=e73]: psychology
- generic [ref=e74]: Memory
- link "auto_fix_high Skills" [ref=e75] [cursor=pointer]:
- /url: /dashboard/skills
- generic [ref=e76]: auto_fix_high
- generic [ref=e77]: Skills
- generic [ref=e78]:
- paragraph [ref=e79]: System
- link "health_and_safety Health" [ref=e80] [cursor=pointer]:
- /url: /dashboard/health
- generic [ref=e81]: health_and_safety
- generic [ref=e82]: Health
- link "description Logs" [ref=e83] [cursor=pointer]:
- /url: /dashboard/logs
- generic [ref=e84]: description
- generic [ref=e85]: Logs
- link "history Audit Log" [ref=e86] [cursor=pointer]:
- /url: /dashboard/audit
- generic [ref=e87]: history
- generic [ref=e88]: Audit Log
- link "settings Settings" [ref=e89] [cursor=pointer]:
- /url: /dashboard/settings
- generic [ref=e90]: settings
- generic [ref=e91]: Settings
- generic [ref=e92]:
- paragraph [ref=e93]: Help
- link "menu_book Docs" [ref=e94] [cursor=pointer]:
- /url: /docs
- generic [ref=e95]: menu_book
- generic [ref=e96]: Docs
- link "bug_report Issues" [ref=e97] [cursor=pointer]:
- /url: https://github.com/diegosouzapw/OmniRoute/issues
- generic [ref=e98]: bug_report
- generic [ref=e99]: Issues
- generic [ref=e100]:
- button "restart_alt Restart" [ref=e101]:
- generic: restart_alt
- text: Restart
- button "power_settings_new Shutdown" [ref=e102]:
- generic: power_settings_new
- text: Shutdown
- main [ref=e103]:
- generic [ref=e104]:
- button "menu" [ref=e106]:
- generic: menu
- generic [ref=e107]:
- button "🇺🇸 EN expand_more" [ref=e109]:
- generic [ref=e110]: 🇺🇸
- generic [ref=e111]: EN
- generic: expand_more
- button "Switch to dark mode" [ref=e112]:
- generic: dark_mode
- button "logout" [ref=e113]:
- generic: logout
- navigation "Breadcrumb" [ref=e116]:
- link "Dashboard" [ref=e118] [cursor=pointer]:
- /url: /dashboard
- generic [ref=e119]:
- generic [ref=e120]:
- generic [ref=e121]: Providers
- alert [ref=e135]

View File

@@ -0,0 +1,346 @@
"use client";
import { useState, useEffect, useCallback } from "react";
import { Card } from "@/shared/components";
import { useTranslations } from "next-intl";
interface ModelsDevStatus {
enabled: boolean;
lastSync: string | null;
lastSyncModelCount: number;
lastSyncCapabilityCount: number;
nextSync: string | null;
intervalMs: number;
providerCount: number;
modelCount: number;
capabilityCount: number;
}
interface SyncResult {
success: boolean;
modelCount: number;
providerCount: number;
capabilityCount: number;
error?: string;
}
export default function ModelsDevSyncTab() {
const t = useTranslations("settings");
const [status, setStatus] = useState<ModelsDevStatus | null>(null);
const [loading, setLoading] = useState(true);
const [syncing, setSyncing] = useState(false);
const [saving, setSaving] = useState(false);
const [enabled, setEnabled] = useState(false);
const [intervalHours, setIntervalHours] = useState(24);
const [feedback, setFeedback] = useState<{ type: "success" | "error"; message: string } | null>(
null
);
const fetchStatus = useCallback(async () => {
try {
const res = await fetch("/api/settings/models-dev?action=status");
if (res.ok) {
const data = await res.json();
setStatus(data);
}
} catch {
// Silently fail — sync may not be initialized yet
}
setLoading(false);
}, []);
useEffect(() => {
Promise.all([fetchStatus(), fetch("/api/settings").then((r) => (r.ok ? r.json() : null))])
.then(([, settingsData]) => {
if (settingsData) {
setEnabled(settingsData.modelsDevSyncEnabled === true);
const intervalMs = settingsData.modelsDevSyncInterval || 86400000;
setIntervalHours(Math.round(intervalMs / 3600000));
}
})
.catch((err) => {
console.error("Failed to fetch models.dev settings:", err);
setFeedback({ type: "error", message: "Failed to load settings" });
});
}, [fetchStatus]);
const triggerSync = async () => {
setSyncing(true);
setFeedback(null);
try {
const res = await fetch("/api/settings/models-dev", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "sync" }),
});
if (res.ok) {
const result: SyncResult = await res.json();
if (result.success) {
setFeedback({
type: "success",
message: `Synced ${result.modelCount.toLocaleString()} pricing entries, ${result.capabilityCount.toLocaleString()} capabilities`,
});
fetchStatus();
} else {
setFeedback({ type: "error", message: result.error || "Sync failed" });
}
} else {
setFeedback({ type: "error", message: "Sync request failed" });
}
} catch {
setFeedback({ type: "error", message: "Network error" });
} finally {
setSyncing(false);
setTimeout(() => setFeedback(null), 5000);
}
};
const toggleEnabled = async () => {
const newVal = !enabled;
setEnabled(newVal);
setSaving(true);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ modelsDevSyncEnabled: newVal }),
});
if (!res.ok) {
setEnabled(!newVal);
setFeedback({ type: "error", message: t("enableSyncError") || "Failed to update" });
} else {
setFeedback({ type: "success", message: "Settings saved" });
}
} catch {
setEnabled(!newVal);
setFeedback({ type: "error", message: "Network error" });
} finally {
setSaving(false);
setTimeout(() => setFeedback(null), 3000);
}
};
const updateInterval = async (hours: number) => {
const oldInterval = intervalHours;
setIntervalHours(hours);
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ modelsDevSyncInterval: hours * 3600000 }),
});
if (!res.ok) {
setIntervalHours(oldInterval);
setFeedback({ type: "error", message: t("enableSyncError") || "Failed to update" });
} else {
setFeedback({ type: "success", message: "Interval updated" });
}
} catch {
setIntervalHours(oldInterval);
setFeedback({ type: "error", message: "Network error" });
} finally {
setTimeout(() => setFeedback(null), 3000);
}
};
if (loading) {
return (
<Card>
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
sync
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("modelsDevTitle")}</h3>
<p className="text-sm text-text-muted">{t("modelsDevDesc")}</p>
</div>
</div>
<div className="mt-4 text-sm text-text-muted">{t("loading")}...</div>
</Card>
);
}
const formatLastSync = (iso: string | null) => {
if (!iso) return t("never");
const d = new Date(iso);
const now = new Date();
const diffMs = now.getTime() - d.getTime();
const diffMin = Math.floor(diffMs / 60000);
if (diffMin < 1) return t("justNow");
if (diffMin < 60) return `${diffMin}m ago`;
const diffHr = Math.floor(diffMin / 60);
if (diffHr < 24) return `${diffHr}h ago`;
return d.toLocaleDateString();
};
return (
<div className="flex flex-col gap-6">
{/* Main sync card */}
<Card>
<div className="flex items-center gap-3 mb-5">
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
database
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("modelsDevTitle")}</h3>
<p className="text-sm text-text-muted">{t("modelsDevDesc")}</p>
</div>
{feedback && (
<span
className={`ml-auto text-xs font-medium flex items-center gap-1 ${
feedback.type === "success" ? "text-emerald-500" : "text-red-500"
}`}
>
<span className="material-symbols-outlined text-[14px]">
{feedback.type === "success" ? "check_circle" : "error"}
</span>{" "}
{feedback.message}
</span>
)}
</div>
{/* Enable toggle */}
<div className="flex items-center justify-between p-4 rounded-lg bg-surface/30 border border-border/30 mb-4">
<div>
<p className="text-sm font-medium">{t("modelsDevEnabled")}</p>
<p className="text-xs text-text-muted mt-0.5">{t("modelsDevEnabledDesc")}</p>
</div>
<button
onClick={toggleEnabled}
disabled={saving}
className={`relative w-11 h-6 rounded-full transition-colors ${
enabled ? "bg-blue-500" : "bg-border"
}`}
role="switch"
aria-checked={enabled}
>
<span
className={`absolute top-1 left-1 w-4 h-4 bg-white rounded-full transition-transform ${
enabled ? "translate-x-5" : "translate-x-0"
}`}
/>
</button>
</div>
{/* Sync interval */}
{enabled && (
<div className="p-4 rounded-lg bg-surface/30 border border-border/30 mb-4">
<div className="flex items-center justify-between mb-3">
<p className="text-sm font-medium">{t("modelsDevInterval")}</p>
<span className="text-sm font-mono tabular-nums text-blue-400">{intervalHours}h</span>
</div>
<input
type="range"
min="1"
max="168"
step="1"
value={intervalHours}
onChange={(e) => updateInterval(parseInt(e.target.value))}
className="w-full accent-blue-500"
/>
<div className="flex justify-between text-xs text-text-muted mt-1">
<span>1h</span>
<span>6h</span>
<span>24h</span>
<span>7d</span>
</div>
</div>
)}
{/* Manual sync button */}
<div className="flex items-center gap-3">
<button
onClick={triggerSync}
disabled={syncing}
className="px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 transition-colors flex items-center gap-2"
>
<span
className={`material-symbols-outlined text-[16px] ${syncing ? "animate-spin" : ""}`}
>
sync
</span>
{syncing ? t("syncing") : t("syncNow")}
</button>
{status?.lastSync && (
<span className="text-xs text-text-muted">
{t("lastSync")}: {formatLastSync(status.lastSync)}
</span>
)}
</div>
</Card>
{/* Stats card */}
{status && (status.providerCount > 0 || status.modelCount > 0) && (
<Card>
<div className="flex items-center gap-3 mb-5">
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
bar_chart
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("modelsDevStats")}</h3>
<p className="text-sm text-text-muted">{t("modelsDevStatsDesc")}</p>
</div>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<div className="p-4 rounded-lg bg-surface/30 border border-border/30 text-center">
<p className="text-2xl font-bold text-blue-400">
{status.providerCount.toLocaleString()}
</p>
<p className="text-xs text-text-muted mt-1">{t("providers")}</p>
</div>
<div className="p-4 rounded-lg bg-surface/30 border border-border/30 text-center">
<p className="text-2xl font-bold text-emerald-400">
{status.modelCount.toLocaleString()}
</p>
<p className="text-xs text-text-muted mt-1">{t("modelsWithPricing")}</p>
</div>
<div className="p-4 rounded-lg bg-surface/30 border border-border/30 text-center">
<p className="text-2xl font-bold text-violet-400">
{status.capabilityCount.toLocaleString()}
</p>
<p className="text-xs text-text-muted mt-1">{t("capabilities")}</p>
</div>
<div className="p-4 rounded-lg bg-surface/30 border border-border/30 text-center">
<p className="text-2xl font-bold text-amber-400">
{status.lastSyncModelCount > 0 ? status.lastSyncModelCount.toLocaleString() : "—"}
</p>
<p className="text-xs text-text-muted mt-1">{t("lastSyncCount")}</p>
</div>
</div>
{status.lastSync && (
<div className="mt-4 text-xs text-text-muted text-center">
{t("lastSyncFull")}: {new Date(status.lastSync).toLocaleString()}
</div>
)}
</Card>
)}
{/* Info card */}
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-amber-500/10 text-amber-500">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
info
</span>
</div>
<div>
<h3 className="text-lg font-semibold">{t("modelsDevInfo")}</h3>
</div>
</div>
<div className="text-sm text-text-muted space-y-2">
<p>{t("modelsDevInfoDesc")}</p>
<p>{t("modelsDevInfoResolution")}</p>
<p className="text-xs font-mono bg-surface/50 p-2 rounded">{t("modelsDevInfoOrder")}</p>
</div>
</Card>
</div>
);
}

View File

@@ -18,6 +18,7 @@ import ModelAliasesTab from "./components/ModelAliasesTab";
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";
@@ -95,6 +96,7 @@ export default function SettingsPage() {
<SystemPromptTab />
<CacheSettingsTab />
<MemorySkillsTab />
<ModelsDevSyncTab />
</div>
)}

View File

@@ -0,0 +1,92 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { isAuthenticated } from "@/shared/utils/apiAuth";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import {
syncModelsDev,
getModelsDevPricing,
getSyncedCapabilities,
getSyncStatus,
startPeriodicSync,
stopPeriodicSync,
} from "@/lib/modelsDevSync";
const modelsDevActionSchema = z.object({
action: z.enum(["sync", "start", "stop"]),
dryRun: z.boolean().optional(),
syncCapabilities: z.boolean().optional(),
});
export async function GET(request: NextRequest) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const action = searchParams.get("action");
if (action === "status") {
const status = getSyncStatus();
const pricing = getModelsDevPricing();
const caps = getSyncedCapabilities();
const providerCount = Object.keys(pricing).length;
const modelCount = Object.values(pricing).reduce(
(sum, models) => sum + Object.keys(models).length,
0
);
const capabilityCount = Object.values(caps).reduce(
(sum, models) => sum + Object.keys(models).length,
0
);
return NextResponse.json({
...status,
providerCount,
modelCount,
capabilityCount,
});
}
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
}
export async function POST(request: NextRequest) {
if (!(await isAuthenticated(request))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
let rawBody: unknown;
try {
rawBody = await request.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
const validation = validateBody(modelsDevActionSchema, rawBody);
if (isValidationFailure(validation)) {
return validation.response;
}
const { action, dryRun, syncCapabilities } = validation.data;
if (action === "sync") {
const result = await syncModelsDev({
dryRun: dryRun ?? false,
syncCapabilities: syncCapabilities !== false,
});
return NextResponse.json(result);
}
if (action === "start") {
startPeriodicSync();
return NextResponse.json({ success: true, message: "Periodic sync started" });
}
if (action === "stop") {
stopPeriodicSync();
return NextResponse.json({ success: true, message: "Periodic sync stopped" });
}
return NextResponse.json({ error: "Unknown action" }, { status: 400 });
}

View File

@@ -44,6 +44,12 @@ export async function PATCH(request) {
try {
const rawBody = await request.json();
// Capture old settings BEFORE update for comparison (needed for models.dev sync toggle)
const oldSettings =
"modelsDevSyncEnabled" in rawBody || "modelsDevSyncInterval" in rawBody
? await getSettings()
: null;
// Zod validation
const validation = validateBody(updateSettingsSchema, rawBody);
if (isValidationFailure(validation)) {
@@ -149,6 +155,26 @@ export async function PATCH(request) {
invalidateCacheControlSettingsCache();
}
// Sync models.dev sync settings (compare old vs new state)
if (oldSettings && ("modelsDevSyncEnabled" in body || "modelsDevSyncInterval" in body)) {
const { stopPeriodicSync, startPeriodicSync } = await import("@/lib/modelsDevSync");
const wasEnabled = (oldSettings as Record<string, unknown>).modelsDevSyncEnabled === true;
const isEnabled = settings.modelsDevSyncEnabled === true;
const oldInterval = (oldSettings as Record<string, unknown>).modelsDevSyncInterval as
| number
| undefined;
const newInterval = settings.modelsDevSyncInterval as number | undefined;
if (wasEnabled && !isEnabled) {
stopPeriodicSync();
} else if (!wasEnabled && isEnabled) {
startPeriodicSync(newInterval);
} else if (isEnabled && oldInterval !== newInterval) {
stopPeriodicSync();
startPeriodicSync(newInterval);
}
}
const { password, ...safeSettings } = settings;
return NextResponse.json(safeSettings);
} catch (error) {

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "يمكنك تجاوز التسعير الافتراضي لنماذج محددة. تحظى التجاوزات المخصصة بالأولوية على الأسعار التي يتم اكتشافها تلقائيًا.",
"editPricing": "تحرير التسعير",
"viewFullDetails": "عرض التفاصيل الكاملة",
"themeCoral": "مرجاني"
"themeCoral": "مرجاني",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "مترجم",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Можете да замените цените по подразбиране за конкретни модели. Персонализираните замени имат приоритет пред автоматично разпознатото ценообразуване.",
"editPricing": "Редактиране на цените",
"viewFullDetails": "Вижте пълните подробности",
"themeCoral": "Корал"
"themeCoral": "Корал",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Преводач",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Výchozí ceny pro konkrétní modely můžete přepsat. Vlastní přepsání má přednost před automaticky zjištěnými cenami.",
"editPricing": "Upravit ceny",
"viewFullDetails": "Zobrazit všechny podrobnosti",
"themeCoral": "Korál"
"themeCoral": "Korál",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Překladatel",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Du kan tilsidesætte standardpriser for specifikke modeller. Tilpassede tilsidesættelser har prioritet frem for automatisk registrerede priser.",
"editPricing": "Rediger prissætning",
"viewFullDetails": "Se alle detaljer",
"themeCoral": "Koral"
"themeCoral": "Koral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Oversætter",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Sie können die Standardpreise für bestimmte Modelle überschreiben. Benutzerdefinierte Überschreibungen haben Vorrang vor automatisch erkannten Preisen.",
"editPricing": "Preise bearbeiten",
"viewFullDetails": "Vollständige Details anzeigen",
"themeCoral": "Koralle"
"themeCoral": "Koralle",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Übersetzer",

View File

@@ -1827,6 +1827,27 @@
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"justNow": "Just now",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"providers": "Providers",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"systemTheme": "System Theme",
"debugToggle": "Enable Debug Mode",
"sidebarVisibilityToggle": "Show Sidebar Items",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Puede anular los precios predeterminados para modelos específicos. Las anulaciones personalizadas tienen prioridad sobre los precios detectados automáticamente.",
"editPricing": "Editar precios",
"viewFullDetails": "Ver todos los detalles",
"themeCoral": "Coral"
"themeCoral": "Coral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Traductor",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Voit ohittaa tiettyjen mallien oletushinnoittelun. Mukautetut ohitukset ovat etusijalla automaattisesti tunnistettuihin hinnoitteluun nähden.",
"editPricing": "Muokkaa hinnoittelua",
"viewFullDetails": "Näytä täydelliset tiedot",
"themeCoral": "Koralli"
"themeCoral": "Koralli",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Kääntäjä",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Vous pouvez remplacer le prix par défaut pour des modèles spécifiques. Les remplacements personnalisés ont la priorité sur les prix détectés automatiquement.",
"editPricing": "Modifier le prix",
"viewFullDetails": "Afficher tous les détails",
"themeCoral": "Corail"
"themeCoral": "Corail",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Traducteur",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "אתה יכול לעקוף את תמחור ברירת המחדל עבור דגמים ספציפיים. עקיפות מותאמות אישית מקבלות עדיפות על פני תמחור שזוהה אוטומטית.",
"editPricing": "ערוך תמחור",
"viewFullDetails": "צפה בפרטים המלאים",
"themeCoral": "אלמוג"
"themeCoral": "אלמוג",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "מתרגם",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "आप विशिष्ट मॉडलों के लिए डिफ़ॉल्ट मूल्य निर्धारण को ओवरराइड कर सकते हैं। कस्टम ओवरराइड्स को स्वतः-पता लगाए गए मूल्य-निर्धारण पर प्राथमिकता दी जाती है।",
"editPricing": "मूल्य निर्धारण संपादित करें",
"viewFullDetails": "पूर्ण विवरण देखें",
"themeCoral": "कोरल"
"themeCoral": "कोरल",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "अनुवादक",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Egyes modelleknél felülbírálhatja az alapértelmezett árazást. Az egyéni felülbírálások elsőbbséget élveznek az automatikusan észlelt árképzéssel szemben.",
"editPricing": "Árak szerkesztése",
"viewFullDetails": "Teljes részletek megtekintése",
"themeCoral": "Korall"
"themeCoral": "Korall",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Fordító",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Anda dapat mengganti harga default untuk model tertentu. Penggantian khusus lebih diprioritaskan dibandingkan harga yang terdeteksi otomatis.",
"editPricing": "Sunting Harga",
"viewFullDetails": "Lihat Detail Lengkap",
"themeCoral": "Koral"
"themeCoral": "Koral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Penerjemah",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Puoi sostituire i prezzi predefiniti per modelli specifici. Le sostituzioni personalizzate hanno la priorità sui prezzi rilevati automaticamente.",
"editPricing": "Modifica prezzi",
"viewFullDetails": "Visualizza i dettagli completi",
"themeCoral": "Corallo"
"themeCoral": "Corallo",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Traduttore",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "特定のモデルのデフォルトの価格をオーバーライドできます。カスタム オーバーライドは、自動検出された価格設定よりも優先されます。",
"editPricing": "価格の編集",
"viewFullDetails": "詳細を表示",
"themeCoral": "コーラル"
"themeCoral": "コーラル",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "翻訳者",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "특정 모델의 기본 가격을 재정의할 수 있습니다. 맞춤 재정의는 자동 감지된 가격보다 우선 적용됩니다.",
"editPricing": "가격 편집",
"viewFullDetails": "전체 세부정보 보기",
"themeCoral": "코랄"
"themeCoral": "코랄",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "번역기",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Anda boleh mengatasi harga lalai untuk model tertentu. Penggantian tersuai diutamakan berbanding harga yang dikesan secara automatik.",
"editPricing": "Edit Harga",
"viewFullDetails": "Lihat Butiran Penuh",
"themeCoral": "Koral"
"themeCoral": "Koral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Penterjemah",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "U kunt de standaardprijzen voor specifieke modellen overschrijven. Aangepaste overschrijvingen hebben voorrang op automatisch gedetecteerde prijzen.",
"editPricing": "Prijzen bewerken",
"viewFullDetails": "Bekijk volledige details",
"themeCoral": "Koraal"
"themeCoral": "Koraal",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Vertaler",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Du kan overstyre standardpriser for spesifikke modeller. Egendefinerte overstyringer prioriteres fremfor automatisk oppdagede priser.",
"editPricing": "Rediger priser",
"viewFullDetails": "Se alle detaljer",
"themeCoral": "Korall"
"themeCoral": "Korall",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Oversetter",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Maaari mong i-override ang default na pagpepresyo para sa mga partikular na modelo. Mas inuuna ang mga custom na override kaysa sa awtomatikong natukoy na pagpepresyo.",
"editPricing": "I-edit ang Pagpepresyo",
"viewFullDetails": "Tingnan ang Buong Detalye",
"themeCoral": "Coral"
"themeCoral": "Coral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Tagasalin",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Możesz zastąpić domyślne ceny dla określonych modeli. Zastąpienia niestandardowe mają pierwszeństwo przed automatycznie wykrytymi cenami.",
"editPricing": "Edytuj ceny",
"viewFullDetails": "Zobacz pełne szczegóły",
"themeCoral": "Koral"
"themeCoral": "Koral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Tłumacz",

View File

@@ -2250,7 +2250,26 @@
"memorySkillsTitle": "Memória e Skills",
"memorySkillsDesc": "Contexto persistente e capacidades A2A",
"purgeExpiredLogs": "Purgar Logs Expirados",
"purgeLogsFailed": "Falha ao purgar logs"
"purgeLogsFailed": "Falha ao purgar logs",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default"
},
"translator": {
"title": "Tradutor",

View File

@@ -2250,7 +2250,26 @@
"memorySkillsTitle": "Memória e Skills",
"memorySkillsDesc": "Contexto persistente e capacidades A2A",
"purgeExpiredLogs": "Purgar Logs Expirados",
"purgeLogsFailed": "Falha ao purgar logs"
"purgeLogsFailed": "Falha ao purgar logs",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default"
},
"translator": {
"title": "Tradutor",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Puteți suprascrie prețurile implicite pentru anumite modele. Anulările personalizate au prioritate față de prețurile detectate automat.",
"editPricing": "Editați prețul",
"viewFullDetails": "Vezi detalii complete",
"themeCoral": "Coral"
"themeCoral": "Coral",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Traducător",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Вы можете переопределить цены по умолчанию для определенных моделей. Пользовательские переопределения имеют приоритет над ценами, определяемыми автоматически.",
"editPricing": "Изменить цену",
"viewFullDetails": "Посмотреть полную информацию",
"themeCoral": "Коралл"
"themeCoral": "Коралл",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Переводчик",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Predvolené ceny pre konkrétne modely môžete prepísať. Vlastné prepísania majú prednosť pred automaticky zistenými cenami.",
"editPricing": "Upraviť ceny",
"viewFullDetails": "Zobraziť úplné podrobnosti",
"themeCoral": "Korál"
"themeCoral": "Korál",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Prekladateľ",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Du kan åsidosätta standardpriser för specifika modeller. Anpassade åsidosättningar har prioritet framför automatiskt identifierade priser.",
"editPricing": "Redigera prissättning",
"viewFullDetails": "Visa fullständiga detaljer",
"themeCoral": "Korall"
"themeCoral": "Korall",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Översättare",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "คุณสามารถแทนที่ราคาเริ่มต้นสำหรับรุ่นเฉพาะได้ การแทนที่แบบกำหนดเองจะมีลำดับความสำคัญมากกว่าการกำหนดราคาที่ตรวจพบอัตโนมัติ",
"editPricing": "แก้ไขราคา",
"viewFullDetails": "ดูรายละเอียดทั้งหมด",
"themeCoral": "คอรัล"
"themeCoral": "คอรัล",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "นักแปล",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Belirli modeller için varsayılan fiyatlandırmayı geçersiz kılabilirsiniz. Özel geçersiz kılmalar, otomatik algılanan fiyatlandırmaya göre öncelik kazanır.",
"editPricing": "Fiyatlandırmayı Düzenle",
"viewFullDetails": "Tüm Ayrıntıları Görüntüle",
"themeCoral": "Mercan"
"themeCoral": "Mercan",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Çeviri",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Ви можете змінити ціни за умовчанням для певних моделей. Спеціальні зміни мають пріоритет над автоматично визначеними цінами.",
"editPricing": "Редагувати ціни",
"viewFullDetails": "Переглянути повну інформацію",
"themeCoral": "Корал"
"themeCoral": "Корал",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Перекладач",

View File

@@ -2164,7 +2164,46 @@
"customPricingNote": "Bạn có thể ghi đè giá mặc định cho các kiểu máy cụ thể. Ghi đè tùy chỉnh được ưu tiên hơn giá được tự động phát hiện.",
"editPricing": "Chỉnh sửa giá",
"viewFullDetails": "Xem chi tiết đầy đủ",
"themeCoral": "San hô"
"themeCoral": "San hô",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "Người phiên dịch",

View File

@@ -2176,7 +2176,46 @@
"lkgpCacheClearFailed": "清除 LKGP 缓存失败",
"maintenance": "维护",
"purgeExpiredLogs": "清理过期日志",
"purgeLogsFailed": "清理日志失败"
"purgeLogsFailed": "清理日志失败",
"modelsDevTitle": "Model Database",
"modelsDevDesc": "Auto-sync pricing, capabilities & specs from models.dev",
"modelsDevEnabled": "Enable models.dev Sync",
"modelsDevEnabledDesc": "Fetch model pricing, capabilities, and specs from the open-source models.dev database",
"modelsDevInterval": "Sync Interval",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"lastSync": "Last sync",
"never": "Never",
"modelsDevStats": "Sync Statistics",
"modelsDevStatsDesc": "Current models.dev data coverage",
"modelsWithPricing": "Models with Pricing",
"capabilities": "Capabilities",
"lastSyncCount": "Last Sync Count",
"lastSyncFull": "Last full sync",
"modelsDevInfo": "How It Works",
"modelsDevInfoDesc": "models.dev is an open-source database of AI model specifications maintained by the SST/OpenCode team. It provides pricing, capabilities, context limits, and modalities for 4,000+ models across 100+ providers.",
"modelsDevInfoResolution": "Pricing resolution order (highest priority first):",
"modelsDevInfoOrder": "User Override → models.dev → LiteLLM → Hardcoded Default",
"memoryTitle": "Memory",
"memoryDesc": "Persistent cross-session conversational memory",
"memoryEnabled": "Enable Memory",
"memoryEnabledDesc": "When enabled, OmniRoute will inject relevant past context.",
"maxTokens": "Max Tokens",
"retentionDays": "Retention",
"recent": "Recent",
"recentDesc": "Chronological window",
"semantic": "Semantic",
"semanticDesc": "Vector search",
"hybrid": "Hybrid",
"hybridDesc": "Recent + Semantic",
"skillsTitle": "Skills",
"skillsDesc": "Tools for models",
"skillsEnabled": "Enable Skills",
"skillsEnabledDesc": "Allows agents to trigger functions.",
"skillsComingSoon": "Skills marketplace coming soon.",
"memorySkillsTitle": "Memory & Skills",
"memorySkillsDesc": "Persistent context & capabilities",
"days": "Days"
},
"translator": {
"title": "翻译者",

View File

@@ -103,7 +103,7 @@ export async function getPricing() {
const { getDefaultPricing } = await import("@/shared/constants/pricing");
const defaultPricing = getDefaultPricing();
// Layer 2: Synced external pricing (middle priority)
// Layer 2: Synced external pricing from LiteLLM (middle-low priority)
const syncedRows = db
.prepare("SELECT key, value FROM key_value WHERE namespace = 'pricing_synced'")
.all();
@@ -116,7 +116,24 @@ export async function getPricing() {
syncedPricing[key] = toRecord(JSON.parse(rawValue)) as PricingModels;
}
// Layer 3: User overrides (highest priority)
// Layer 3: Synced pricing from models.dev (middle-high priority)
const modelsDevRows = db
.prepare("SELECT key, value FROM key_value WHERE namespace = 'models_dev_pricing'")
.all();
const modelsDevPricing: PricingByProvider = {};
for (const row of modelsDevRows) {
const record = toRecord(row);
const key = typeof record.key === "string" ? record.key : null;
const rawValue = typeof record.value === "string" ? record.value : null;
if (!key || rawValue === null) continue;
try {
modelsDevPricing[key] = JSON.parse(rawValue) as PricingModels;
} catch {
// Corrupted data — skip silently, fallback to lower layers
}
}
// Layer 4: User overrides (highest priority)
const rows = db.prepare("SELECT key, value FROM key_value WHERE namespace = 'pricing'").all();
const userPricing: PricingByProvider = {};
for (const row of rows) {
@@ -127,7 +144,7 @@ export async function getPricing() {
userPricing[key] = toRecord(JSON.parse(rawValue)) as PricingModels;
}
// Merge: defaults → synced → user (each layer overrides the previous)
// Merge: defaults → LiteLLM → models.dev → user (each layer overrides the previous)
const mergedPricing: PricingByProvider = {};
// Start with defaults
@@ -135,8 +152,8 @@ export async function getPricing() {
mergedPricing[provider] = { ...(toRecord(models) as PricingModels) };
}
// Layer synced then user on top (each higher-priority layer overrides)
for (const layer of [syncedPricing, userPricing]) {
// Layer synced (LiteLLM), then models.dev, then user on top
for (const layer of [syncedPricing, modelsDevPricing, userPricing]) {
for (const [provider, models] of Object.entries(layer)) {
if (!mergedPricing[provider]) {
mergedPricing[provider] = { ...models };

694
src/lib/modelsDevSync.ts Normal file
View File

@@ -0,0 +1,694 @@
/**
* modelsDevSync.ts — Fetch model specs, pricing, and capabilities from models.dev
*
* models.dev (https://github.com/anomalyco/models.dev) is an open-source database
* of AI model specifications maintained by the SST/OpenCode team (MIT license).
*
* API: https://models.dev/api.json
* - 109 providers, 4,146+ models
* - Data: pricing, capabilities, limits, modalities, metadata
*
* Resolution order (highest → lowest):
* 1. User overrides (`pricing` namespace)
* 2. models.dev sync (`models_dev_pricing` namespace)
* 3. LiteLLM sync (`pricing_synced` namespace)
* 4. Hardcoded defaults (`pricing.ts`)
*
* Opt-in via MODELS_DEV_SYNC_ENABLED=true (default: false).
*/
import { getDbInstance } from "./db/core";
import { invalidateDbCache } from "./db/readCache";
import { backupDbFile } from "./db/backup";
// ─── Types ───────────────────────────────────────────────
type PricingEntry = {
input: number;
output: number;
cached?: number;
cache_creation?: number;
reasoning?: number;
};
type PricingModels = Record<string, PricingEntry>;
type PricingByProvider = Record<string, PricingModels>;
interface CapabilityEntry {
tool_call: boolean | null;
reasoning: boolean | null;
attachment: boolean | null;
structured_output: boolean | null;
temperature: boolean | null;
modalities_input: string; // JSON array
modalities_output: string; // JSON array
knowledge_cutoff: string | null;
release_date: string | null;
last_updated: string | null;
status: string | null;
family: string | null;
open_weights: boolean | null;
limit_context: number | null;
limit_input: number | null;
limit_output: number | null;
interleaved_field: string | null;
}
type CapabilitiesByProvider = Record<string, Record<string, CapabilityEntry>>;
interface SyncStatus {
enabled: boolean;
lastSync: string | null;
lastSyncModelCount: number;
lastSyncCapabilityCount: number;
nextSync: string | null;
intervalMs: number;
}
interface SyncResult {
success: boolean;
modelCount: number;
providerCount: number;
capabilityCount: number;
dryRun: boolean;
data?: { pricing: PricingByProvider; capabilities: CapabilitiesByProvider };
error?: string;
}
// ─── models.dev API types (raw) ──────────────────────────
interface ModelsDevCost {
input?: number;
output?: number;
reasoning?: number;
cache_read?: number;
cache_write?: number;
input_audio?: number;
output_audio?: number;
}
interface ModelsDevLimit {
context?: number;
input?: number;
output?: number;
}
interface ModelsDevModalities {
input?: string[];
output?: string[];
}
interface ModelsDevInterleaved {
field?: string;
}
interface ModelsDevModel {
id: string;
name: string;
family?: string;
attachment?: boolean;
reasoning?: boolean;
tool_call?: boolean;
structured_output?: boolean;
temperature?: boolean;
knowledge?: string;
release_date?: string;
last_updated?: string;
open_weights?: boolean;
status?: string;
cost?: ModelsDevCost;
limit?: ModelsDevLimit;
modalities?: ModelsDevModalities;
interleaved?: ModelsDevInterleaved | boolean;
}
interface ModelsDevProvider {
id: string;
name?: string;
env?: string[];
npm?: string;
api?: string;
doc?: string;
models: Record<string, ModelsDevModel>;
}
type ModelsDevData = Record<string, ModelsDevProvider>;
// ─── Configuration ───────────────────────────────────────
const MODELS_DEV_API_URL = "https://models.dev/api.json";
const parsedInterval = parseInt(process.env.MODELS_DEV_SYNC_INTERVAL || "86400", 10);
const SYNC_INTERVAL_MS =
Number.isFinite(parsedInterval) && parsedInterval > 0 ? parsedInterval * 1000 : 86400 * 1000;
// ─── Provider mapping: models.dev provider ID → OmniRoute provider IDs/aliases ──
//
// models.dev uses canonical provider IDs (e.g. "openai", "anthropic", "google").
// OmniRoute uses both full IDs and short aliases (e.g. "cc" for claude, "cx" for codex).
// We map each models.dev provider to ALL OmniRoute identifiers that should receive
// its pricing/capability data.
const MODELS_DEV_PROVIDER_MAP: Record<string, string[]> = {
// Major providers
openai: ["openai", "cx"], // cx = Codex (uses OpenAI models)
anthropic: ["anthropic", "cc"], // cc = Claude Code
google: ["gemini", "gemini-cli"],
vertex_ai: ["gemini", "vertex"],
deepseek: ["deepseek", "if"], // if = Qoder (routes through DeepSeek)
groq: ["groq"],
xai: ["xai"],
mistral: ["mistral"],
together_ai: ["together", "openrouter"],
fireworks: ["fireworks"],
cerebras: ["cerebras"],
cohere: ["cohere"],
nvidia: ["nvidia"],
nebius: ["nebius"],
siliconflow: ["siliconflow"],
hyperbolic: ["hyperbolic"],
huggingface: ["hf", "huggingface"],
openrouter: ["openrouter"],
perplexity: ["pplx", "perplexity"],
// OAuth / special providers
bedrock: ["kiro", "kr"], // kr = Kiro (AWS Bedrock)
// Additional providers that may overlap with OmniRoute
alibaba: ["ali", "alibaba", "bcp", "alicode", "alicode-intl"],
zai: ["zai", "glm"], // GLM models via Z.AI
moonshot: ["kimi", "kimi-coding", "kmc", "kmca"],
minimax: ["minimax", "minimax-cn"],
longcat: ["lc", "longcat"],
pollinations: ["pol", "pollinations"],
puter: ["pu", "puter"],
cloudflare: ["cf"],
scaleway: ["scw"],
ollama: ["ollamacloud", "ollama-cloud"],
blackbox: ["bb", "blackbox"],
kilocode: ["kc", "kilocode"],
cline: ["cl", "cline"],
cursor: ["cu", "cursor"],
github: ["gh", "github"],
// Fallback: if no mapping exists, use the models.dev ID as-is
};
/**
* Map a models.dev provider ID to OmniRoute provider IDs.
* Returns array of provider identifiers (may include aliases).
*/
export function mapProviderId(modelsDevProviderId: string): string[] {
return MODELS_DEV_PROVIDER_MAP[modelsDevProviderId] || [modelsDevProviderId];
}
// ─── Periodic sync state ─────────────────────────────────
let syncTimer: ReturnType<typeof setInterval> | null = null;
let lastSyncTime: string | null = null;
let lastSyncModelCount = 0;
let lastSyncCapabilityCount = 0;
let activeSyncIntervalMs = SYNC_INTERVAL_MS;
let cachedData: ModelsDevData | null = null;
let cacheTime = 0;
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
// ─── Core: Fetch ─────────────────────────────────────────
/**
* Fetch raw data from models.dev API.
* Uses in-memory cache with 24h TTL to avoid repeated fetches.
*/
export async function fetchModelsDev(): Promise<ModelsDevData> {
// Return cached data if still fresh
if (cachedData && Date.now() - cacheTime < CACHE_TTL_MS) {
return cachedData;
}
const response = await fetch(MODELS_DEV_API_URL, {
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
throw new Error(`models.dev fetch failed [${response.status}]: ${response.statusText}`);
}
const text = await response.text();
try {
const data = JSON.parse(text) as ModelsDevData;
cachedData = data;
cacheTime = Date.now();
return data;
} catch {
throw new Error(`models.dev returned invalid JSON (${text.slice(0, 100)}...)`);
}
}
// ─── Transform: Pricing ──────────────────────────────────
/**
* Transform models.dev raw data → OmniRoute PricingByProvider format.
*
* models.dev costs are already in $/1M tokens (same as OmniRoute format).
* Maps: cache_read → cached, cache_write → cache_creation.
*/
export function transformModelsDevToPricing(raw: ModelsDevData): PricingByProvider {
const result: PricingByProvider = {};
for (const [providerId, providerData] of Object.entries(raw)) {
const omniRouteProviders = mapProviderId(providerId);
for (const [modelId, model] of Object.entries(providerData.models || {})) {
if (!model.cost) continue;
// Must have at least input pricing
if (model.cost.input == null) continue;
const entry: PricingEntry = {
input: model.cost.input,
output: model.cost.output ?? 0,
};
if (model.cost.cache_read != null) {
entry.cached = model.cost.cache_read;
}
if (model.cost.cache_write != null) {
entry.cache_creation = model.cost.cache_write;
}
if (model.cost.reasoning != null) {
entry.reasoning = model.cost.reasoning;
}
// Write to ALL mapped OmniRoute providers
for (const omniProvider of omniRouteProviders) {
if (!result[omniProvider]) result[omniProvider] = {};
result[omniProvider][modelId] = entry;
}
}
}
return result;
}
// ─── Transform: Capabilities ─────────────────────────────
/**
* Transform models.dev raw data → CapabilitiesByProvider format.
*/
export function transformModelsDevToCapabilities(raw: ModelsDevData): CapabilitiesByProvider {
const result: CapabilitiesByProvider = {};
for (const [providerId, providerData] of Object.entries(raw)) {
const omniRouteProviders = mapProviderId(providerId);
for (const [modelId, model] of Object.entries(providerData.models || {})) {
const cap: CapabilityEntry = {
tool_call: model.tool_call ?? null,
reasoning: model.reasoning ?? null,
attachment: model.attachment ?? null,
structured_output: model.structured_output ?? null,
temperature: model.temperature ?? null,
modalities_input: JSON.stringify(model.modalities?.input ?? []),
modalities_output: JSON.stringify(model.modalities?.output ?? []),
knowledge_cutoff: model.knowledge ?? null,
release_date: model.release_date ?? null,
last_updated: model.last_updated ?? null,
status: model.status ?? null,
family: model.family ?? null,
open_weights: model.open_weights ?? null,
limit_context: model.limit?.context ?? null,
limit_input: model.limit?.input ?? null,
limit_output: model.limit?.output ?? null,
interleaved_field:
typeof model.interleaved === "object" && model.interleaved?.field
? model.interleaved.field
: model.interleaved === true
? "reasoning_content"
: null,
};
for (const omniProvider of omniRouteProviders) {
if (!result[omniProvider]) result[omniProvider] = {};
result[omniProvider][modelId] = cap;
}
}
}
return result;
}
// ─── DB: models.dev pricing namespace ────────────────────
function toRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
}
/**
* Read synced pricing from `models_dev_pricing` namespace.
*/
export function getModelsDevPricing(): PricingByProvider {
const db = getDbInstance();
const rows = db
.prepare("SELECT key, value FROM key_value WHERE namespace = 'models_dev_pricing'")
.all();
const synced: PricingByProvider = {};
for (const row of rows) {
const record = toRecord(row);
const key = typeof record.key === "string" ? record.key : null;
const rawValue = typeof record.value === "string" ? record.value : null;
if (!key || rawValue === null) continue;
try {
synced[key] = JSON.parse(rawValue) as PricingModels;
} catch {
console.warn(`[MODELS_DEV] Corrupted pricing data for provider "${key}", skipping`);
}
}
return synced;
}
/**
* Save synced pricing to `models_dev_pricing` namespace (full replace).
*/
export function saveModelsDevPricing(data: PricingByProvider): void {
const db = getDbInstance();
const del = db.prepare("DELETE FROM key_value WHERE namespace = 'models_dev_pricing'");
const insert = db.prepare(
"INSERT INTO key_value (namespace, key, value) VALUES ('models_dev_pricing', ?, ?)"
);
const tx = db.transaction(() => {
del.run();
for (const [provider, models] of Object.entries(data)) {
insert.run(provider, JSON.stringify(models));
}
});
tx();
backupDbFile("pre-write");
invalidateDbCache("pricing");
}
/**
* Clear all models.dev synced pricing data.
*/
export function clearModelsDevPricing(): void {
const db = getDbInstance();
db.prepare("DELETE FROM key_value WHERE namespace = 'models_dev_pricing'").run();
backupDbFile("pre-write");
invalidateDbCache("pricing");
}
// ─── DB: model_capabilities table ────────────────────────
/**
* Ensure the model_capabilities table exists.
* Call this before any capability operations.
*/
export function ensureCapabilitiesTable(): void {
const db = getDbInstance();
db.exec(`
CREATE TABLE IF NOT EXISTS model_capabilities (
provider TEXT NOT NULL,
model_id TEXT NOT NULL,
tool_call BOOLEAN,
reasoning BOOLEAN,
attachment BOOLEAN,
structured_output BOOLEAN,
temperature BOOLEAN,
modalities_input TEXT,
modalities_output TEXT,
knowledge_cutoff TEXT,
release_date TEXT,
last_updated TEXT,
status TEXT,
family TEXT,
open_weights BOOLEAN,
limit_context INTEGER,
limit_input INTEGER,
limit_output INTEGER,
interleaved_field TEXT,
last_synced TEXT,
PRIMARY KEY (provider, model_id)
)
`);
}
/**
* Read synced capabilities from `model_capabilities` table.
*/
export function getSyncedCapabilities(
provider?: string,
modelId?: string
): Record<string, Record<string, CapabilityEntry>> {
const db = getDbInstance();
ensureCapabilitiesTable();
let query = "SELECT * FROM model_capabilities";
const params: (string | number)[] = [];
if (provider) {
query += " WHERE provider = ?";
params.push(provider);
if (modelId) {
query += " AND model_id = ?";
params.push(modelId);
}
}
const rows = db.prepare(query).all(...params);
const result: Record<string, Record<string, CapabilityEntry>> = {};
for (const row of rows) {
const record = toRecord(row);
const prov = typeof record.provider === "string" ? record.provider : null;
const mid = typeof record.model_id === "string" ? record.model_id : null;
if (!prov || !mid) continue;
if (!result[prov]) result[prov] = {};
result[prov][mid] = {
tool_call: record.tool_call === 1 ? true : record.tool_call === 0 ? false : null,
reasoning: record.reasoning === 1 ? true : record.reasoning === 0 ? false : null,
attachment: record.attachment === 1 ? true : record.attachment === 0 ? false : null,
structured_output:
record.structured_output === 1 ? true : record.structured_output === 0 ? false : null,
temperature: record.temperature === 1 ? true : record.temperature === 0 ? false : null,
modalities_input:
typeof record.modalities_input === "string" ? record.modalities_input : "[]",
modalities_output:
typeof record.modalities_output === "string" ? record.modalities_output : "[]",
knowledge_cutoff:
typeof record.knowledge_cutoff === "string" ? record.knowledge_cutoff : null,
release_date: typeof record.release_date === "string" ? record.release_date : null,
last_updated: typeof record.last_updated === "string" ? record.last_updated : null,
status: typeof record.status === "string" ? record.status : null,
family: typeof record.family === "string" ? record.family : null,
open_weights: record.open_weights === 1 ? true : record.open_weights === 0 ? false : null,
limit_context: typeof record.limit_context === "number" ? record.limit_context : null,
limit_input: typeof record.limit_input === "number" ? record.limit_input : null,
limit_output: typeof record.limit_output === "number" ? record.limit_output : null,
interleaved_field:
typeof record.interleaved_field === "string" ? record.interleaved_field : null,
};
}
return result;
}
/**
* Save synced capabilities to `model_capabilities` table (full replace).
*/
export function saveModelsDevCapabilities(data: CapabilitiesByProvider): void {
const db = getDbInstance();
ensureCapabilitiesTable();
const del = db.prepare("DELETE FROM model_capabilities");
const insert = db.prepare(`
INSERT INTO model_capabilities (
provider, model_id, tool_call, reasoning, attachment, structured_output,
temperature, modalities_input, modalities_output, knowledge_cutoff,
release_date, last_updated, status, family, open_weights,
limit_context, limit_input, limit_output, interleaved_field, last_synced
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const now = new Date().toISOString();
const tx = db.transaction(() => {
del.run();
for (const [provider, models] of Object.entries(data)) {
for (const [modelId, cap] of Object.entries(models)) {
insert.run(
provider,
modelId,
cap.tool_call === null ? null : cap.tool_call ? 1 : 0,
cap.reasoning === null ? null : cap.reasoning ? 1 : 0,
cap.attachment === null ? null : cap.attachment ? 1 : 0,
cap.structured_output === null ? null : cap.structured_output ? 1 : 0,
cap.temperature === null ? null : cap.temperature ? 1 : 0,
cap.modalities_input,
cap.modalities_output,
cap.knowledge_cutoff,
cap.release_date,
cap.last_updated,
cap.status,
cap.family,
cap.open_weights === null ? null : cap.open_weights ? 1 : 0,
cap.limit_context,
cap.limit_input,
cap.limit_output,
cap.interleaved_field,
now
);
}
}
});
tx();
backupDbFile("pre-write");
}
/**
* Clear all synced capability data.
*/
export function clearModelsDevCapabilities(): void {
const db = getDbInstance();
ensureCapabilitiesTable();
db.prepare("DELETE FROM model_capabilities").run();
backupDbFile("pre-write");
}
// ─── Main sync function ──────────────────────────────────
/**
* Fetch, transform, and save pricing + capabilities from models.dev.
*/
export async function syncModelsDev(opts?: {
dryRun?: boolean;
syncCapabilities?: boolean;
}): Promise<SyncResult> {
const dryRun = opts?.dryRun ?? false;
const syncCapabilities = opts?.syncCapabilities ?? true;
try {
const raw = await fetchModelsDev();
const pricing = transformModelsDevToPricing(raw);
const capabilities = syncCapabilities ? transformModelsDevToCapabilities(raw) : {};
const modelCount = Object.values(pricing).reduce(
(sum, models) => sum + Object.keys(models).length,
0
);
const providerCount = Object.keys(pricing).length;
const capabilityCount = syncCapabilities
? Object.values(capabilities).reduce((sum, models) => sum + Object.keys(models).length, 0)
: 0;
if (!dryRun) {
saveModelsDevPricing(pricing);
if (syncCapabilities) {
ensureCapabilitiesTable();
saveModelsDevCapabilities(capabilities);
}
lastSyncTime = new Date().toISOString();
lastSyncModelCount = modelCount;
lastSyncCapabilityCount = capabilityCount;
}
return {
success: true,
modelCount,
providerCount,
capabilityCount,
dryRun,
...(dryRun ? { data: { pricing, capabilities } } : {}),
};
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
console.warn("[MODELS_DEV] Sync failed:", message);
return {
success: false,
modelCount: 0,
providerCount: 0,
capabilityCount: 0,
dryRun,
error: message,
};
}
}
// ─── Periodic sync ───────────────────────────────────────
/**
* Start periodic models.dev sync (non-blocking).
*/
export function startPeriodicSync(intervalMs?: number): void {
if (syncTimer) return; // Already running
const interval = intervalMs ?? SYNC_INTERVAL_MS;
activeSyncIntervalMs = interval;
console.log(`[MODELS_DEV] Starting periodic sync every ${interval / 1000}s`);
// Initial sync (non-blocking)
syncModelsDev()
.then((result) => {
if (result.success) {
console.log(
`[MODELS_DEV] Initial sync complete: ${result.modelCount} pricing entries, ${result.capabilityCount} capabilities from ${result.providerCount} providers`
);
}
})
.catch((err) => {
console.warn("[MODELS_DEV] Initial sync error:", err instanceof Error ? err.message : err);
});
syncTimer = setInterval(() => {
syncModelsDev()
.then((result) => {
if (result.success) {
console.log(`[MODELS_DEV] Periodic sync complete: ${result.modelCount} pricing entries`);
}
})
.catch((err) => {
console.warn("[MODELS_DEV] Periodic sync error:", err instanceof Error ? err.message : err);
});
}, interval);
}
/**
* Stop periodic sync and cleanup timer.
*/
export function stopPeriodicSync(): void {
if (syncTimer) {
clearInterval(syncTimer);
syncTimer = null;
console.log("[MODELS_DEV] Periodic sync stopped");
}
}
/**
* Get current sync status.
*/
export function getSyncStatus(): SyncStatus {
// If the sync timer is active, it's enabled.
const enabled = syncTimer !== null;
return {
enabled,
lastSync: lastSyncTime,
lastSyncModelCount,
lastSyncCapabilityCount,
nextSync:
syncTimer && lastSyncTime
? new Date(new Date(lastSyncTime).getTime() + activeSyncIntervalMs).toISOString()
: null,
intervalMs: activeSyncIntervalMs,
};
}
// ─── Init (called from server-init.ts) ───────────────────
/**
* Initialize models.dev sync if enabled.
*/
export async function initModelsDevSync(): Promise<void> {
const { getSettings } = await import("./localDb");
const settings = await getSettings();
if (settings.modelsDevSyncEnabled !== true) {
console.log("[MODELS_DEV] Disabled (enable via Settings > AI)");
return;
}
const interval = settings.modelsDevSyncInterval as number | undefined;
startPeriodicSync(interval);
}

View File

@@ -72,4 +72,7 @@ export const updateSettingsSchema = z.object({
.optional(),
// SkillsMP marketplace API key
skillsmpApiKey: z.string().max(200).optional(),
// models.dev sync settings
modelsDevSyncEnabled: z.boolean().optional(),
modelsDevSyncInterval: z.number().int().min(3600).max(604800).optional(),
});

View File

@@ -0,0 +1,186 @@
/**
* Integration tests for models.dev sync: live fetch → DB save → retrieve → resolution order.
*
* Uses a test DB file in /tmp to avoid touching production data.
*/
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { unlinkSync, existsSync } from "node:fs";
// Point DB at a temp file
const TEST_DB = "/tmp/omniroute-test-modelsdev.sqlite";
before(() => {
if (existsSync(TEST_DB)) unlinkSync(TEST_DB);
process.env.DATA_DIR = "/tmp";
process.env.DB_FILE = TEST_DB;
});
after(() => {
if (existsSync(TEST_DB)) unlinkSync(TEST_DB);
});
describe("modelsDevSync — integration: live fetch → DB → retrieve", () => {
it("fetches, saves pricing, and retrieves from DB", async () => {
const {
syncModelsDev,
getModelsDevPricing,
getSyncedCapabilities,
clearModelsDevPricing,
clearModelsDevCapabilities,
} = await import("../../src/lib/modelsDevSync.ts");
// 1. Sync from live API
const result = await syncModelsDev({ dryRun: false, syncCapabilities: true });
assert.equal(result.success, true, `sync should succeed: ${result.error || ""}`);
assert.ok(result.modelCount > 0, `should have pricing entries, got ${result.modelCount}`);
assert.ok(result.providerCount > 0, `should have providers, got ${result.providerCount}`);
assert.ok(
result.capabilityCount > 0,
`should have capabilities, got ${result.capabilityCount}`
);
// 2. Retrieve pricing from DB
const pricing = getModelsDevPricing();
assert.ok(Object.keys(pricing).length > 0, "should have pricing in DB");
// 3. Verify specific known models
assert.ok(pricing.openai, "openai pricing should exist");
assert.ok(pricing.openai["gpt-4o"], "gpt-4o pricing should exist in openai");
assert.ok(pricing.openai["gpt-4o"].input > 0, "gpt-4o input price should be > 0");
assert.ok(pricing.openai["gpt-4o"].output > 0, "gpt-4o output price should be > 0");
assert.ok(pricing.anthropic, "anthropic pricing should exist");
const claudeModels = Object.keys(pricing.anthropic).filter((m) => m.includes("claude"));
assert.ok(claudeModels.length > 0, "should have claude pricing entries");
assert.ok(pricing.deepseek, "deepseek pricing should exist");
assert.ok(pricing.if, "if (Qoder) pricing should exist (mapped from deepseek)");
// 4. Retrieve capabilities from DB
const caps = getSyncedCapabilities();
assert.ok(Object.keys(caps).length > 0, "should have capabilities in DB");
assert.ok(caps.openai, "openai capabilities should exist");
assert.ok(caps.openai["gpt-4o"], "gpt-4o capabilities should exist");
assert.equal(caps.openai["gpt-4o"].tool_call, true);
assert.equal(caps.openai["gpt-4o"].reasoning, false);
assert.equal(caps.openai["gpt-4o"].attachment, true);
// 5. Query specific capability
const specificCaps = getSyncedCapabilities("openai", "gpt-4o");
assert.ok(specificCaps.openai, "specific query should return openai");
assert.ok(specificCaps.openai["gpt-4o"], "specific query should return gpt-4o");
assert.equal(specificCaps.openai["gpt-4o"].limit_context, 128000);
// 6. Cleanup
clearModelsDevPricing();
clearModelsDevCapabilities();
const afterPricing = getModelsDevPricing();
assert.equal(Object.keys(afterPricing).length, 0, "pricing should be cleared");
const afterCaps = getSyncedCapabilities();
assert.equal(Object.keys(afterCaps).length, 0, "capabilities should be cleared");
});
it("dryRun returns data without saving to DB", async () => {
const { syncModelsDev, getModelsDevPricing, clearModelsDevPricing } =
await import("../../src/lib/modelsDevSync.ts");
// Clear any existing data first
clearModelsDevPricing();
const result = await syncModelsDev({ dryRun: true, syncCapabilities: false });
assert.equal(result.success, true);
assert.ok(result.data, "dryRun should return data");
assert.ok(result.data.pricing, "dryRun should return pricing data");
// Verify nothing was saved
const pricing = getModelsDevPricing();
assert.equal(Object.keys(pricing).length, 0, "dryRun should not save to DB");
});
it("sync without capabilities only saves pricing", async () => {
const {
syncModelsDev,
getModelsDevPricing,
getSyncedCapabilities,
clearModelsDevPricing,
clearModelsDevCapabilities,
} = await import("../../src/lib/modelsDevSync.ts");
clearModelsDevPricing();
clearModelsDevCapabilities();
const result = await syncModelsDev({ dryRun: false, syncCapabilities: false });
assert.equal(result.success, true);
assert.ok(result.modelCount > 0, "should have pricing entries");
assert.equal(result.capabilityCount, 0, "capabilityCount should be 0 when disabled");
// Pricing should be saved
const pricing = getModelsDevPricing();
assert.ok(Object.keys(pricing).length > 0, "pricing should be saved");
// Capabilities should NOT be saved
const caps = getSyncedCapabilities();
assert.equal(Object.keys(caps).length, 0, "capabilities should not be saved");
clearModelsDevPricing();
});
});
describe("modelsDevSync — resolution order: user > models.dev > LiteLLM > default", () => {
it("returns models.dev pricing when no user override exists", async () => {
const { syncModelsDev, getModelsDevPricing, clearModelsDevPricing } =
await import("../../src/lib/modelsDevSync.ts");
clearModelsDevPricing();
await syncModelsDev({ dryRun: false, syncCapabilities: false });
const pricing = getModelsDevPricing();
assert.ok(pricing.openai, "openai pricing should exist");
assert.ok(pricing.openai["gpt-4o"], "gpt-4o should have models.dev pricing");
clearModelsDevPricing();
});
it("getPricing() merges all layers correctly", async () => {
const { getPricing } = await import("../../src/lib/db/settings.ts");
const { syncModelsDev, clearModelsDevPricing, saveModelsDevPricing } =
await import("../../src/lib/modelsDevSync.ts");
const { updatePricing, resetPricing } = await import("../../src/lib/db/settings.ts");
// Clear all synced data first
clearModelsDevPricing();
// Sync models.dev pricing
await syncModelsDev({ dryRun: false, syncCapabilities: false });
// Get merged pricing
const merged = await getPricing();
// Should have openai pricing (either from models.dev or hardcoded defaults)
assert.ok(merged.openai || merged.cx, "should have openai/cx pricing in merged result");
// Add a user override
await updatePricing({
openai: {
"gpt-4o": { input: 999, output: 999 },
},
});
// Verify user override wins
const mergedWithOverride = await getPricing();
assert.equal(
mergedWithOverride.openai?.["gpt-4o"]?.input,
999,
"user override should win over models.dev"
);
// Reset the override
await resetPricing("openai", "gpt-4o");
clearModelsDevPricing();
});
});

View File

@@ -0,0 +1,449 @@
/**
* Unit tests for modelsDevSync.ts
*
* Tests: fetch, transform (pricing + capabilities), provider mapping,
* DB save/retrieve, and resolution order.
*/
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import {
transformModelsDevToPricing,
transformModelsDevToCapabilities,
mapProviderId,
fetchModelsDev,
} from "../../src/lib/modelsDevSync.ts";
// ─── Mock data ───────────────────────────────────────────
const MOCK_MODELS_DEV_DATA = {
openai: {
id: "openai",
name: "OpenAI",
npm: "@ai-sdk/openai",
env: ["OPENAI_API_KEY"],
doc: "https://platform.openai.com/docs/models",
models: {
"gpt-4o": {
id: "gpt-4o",
name: "GPT-4o",
family: "gpt-4",
attachment: true,
reasoning: false,
tool_call: true,
structured_output: true,
temperature: true,
knowledge: "2024-10",
release_date: "2024-05-13",
last_updated: "2024-10-01",
open_weights: false,
cost: {
input: 2.5,
output: 10.0,
cache_read: 1.25,
cache_write: 2.5,
},
limit: {
context: 128000,
input: 128000,
output: 16384,
},
modalities: {
input: ["text", "image"],
output: ["text"],
},
},
"o3-mini": {
id: "o3-mini",
name: "o3-mini",
family: "o3",
attachment: false,
reasoning: true,
tool_call: true,
structured_output: true,
temperature: false,
release_date: "2025-01-31",
last_updated: "2025-01-31",
open_weights: false,
cost: {
input: 1.1,
output: 4.4,
reasoning: 4.4,
},
limit: {
context: 200000,
output: 100000,
},
modalities: {
input: ["text"],
output: ["text"],
},
},
},
},
anthropic: {
id: "anthropic",
name: "Anthropic",
npm: "@ai-sdk/anthropic",
env: ["ANTHROPIC_API_KEY"],
doc: "https://docs.anthropic.com/en/docs/about-claud/models",
models: {
"claude-sonnet-4-20250514": {
id: "claude-sonnet-4-20250514",
name: "Claude Sonnet 4",
family: "claude-4",
attachment: true,
reasoning: false,
tool_call: true,
structured_output: true,
temperature: true,
release_date: "2025-05-14",
last_updated: "2025-05-14",
open_weights: false,
cost: {
input: 3.0,
output: 15.0,
cache_read: 0.3,
cache_write: 3.75,
},
limit: {
context: 200000,
output: 64000,
},
modalities: {
input: ["text", "image"],
output: ["text"],
},
},
},
},
deepseek: {
id: "deepseek",
name: "DeepSeek",
npm: "@ai-sdk/openai-compatible",
api: "https://api.deepseek.com/v1",
env: ["DEEPSEEK_API_KEY"],
doc: "https://api-docs.deepseek.com/",
models: {
"deepseek-chat": {
id: "deepseek-chat",
name: "DeepSeek V3",
family: "deepseek-v3",
attachment: false,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2024-12-26",
last_updated: "2025-01-20",
open_weights: true,
cost: {
input: 0.27,
output: 1.1,
cache_read: 0.07,
},
limit: {
context: 64000,
output: 8192,
},
modalities: {
input: ["text"],
output: ["text"],
},
},
},
},
"unknown-provider": {
id: "unknown-provider",
name: "Unknown",
npm: "@ai-sdk/openai-compatible",
api: "https://api.unknown.com/v1",
env: ["UNKNOWN_API_KEY"],
models: {
"some-model": {
id: "some-model",
name: "Some Model",
attachment: false,
reasoning: false,
tool_call: false,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
cost: {
input: 1.0,
output: 2.0,
},
limit: {
context: 32000,
output: 4096,
},
modalities: {
input: ["text"],
output: ["text"],
},
},
},
},
};
// ─── Tests ───────────────────────────────────────────────
describe("modelsDevSync — transformModelsDevToPricing", () => {
it("transforms pricing data correctly for openai provider", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
// openai maps to ["openai", "cx"]
assert.ok(pricing.openai, "openai provider should exist");
assert.ok(pricing.openai["gpt-4o"], "gpt-4o pricing should exist");
assert.equal(pricing.openai["gpt-4o"].input, 2.5);
assert.equal(pricing.openai["gpt-4o"].output, 10.0);
assert.equal(pricing.openai["gpt-4o"].cached, 1.25);
assert.equal(pricing.openai["gpt-4o"].cache_creation, 2.5);
// cx (Codex alias) should also have the same pricing
assert.ok(pricing.cx, "cx provider should exist");
assert.ok(pricing.cx["gpt-4o"], "gpt-4o pricing in cx should exist");
assert.equal(pricing.cx["gpt-4o"].input, 2.5);
});
it("maps reasoning cost correctly", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
assert.ok(pricing.openai["o3-mini"], "o3-mini pricing should exist");
assert.equal(pricing.openai["o3-mini"].input, 1.1);
assert.equal(pricing.openai["o3-mini"].output, 4.4);
assert.equal(pricing.openai["o3-mini"].reasoning, 4.4);
// o3-mini has no cache_read/cache_write
assert.equal(pricing.openai["o3-mini"].cached, undefined);
assert.equal(pricing.openai["o3-mini"].cache_creation, undefined);
});
it("maps deepseek to both deepseek and if (Qoder) providers", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
assert.ok(pricing.deepseek, "deepseek provider should exist");
assert.ok(pricing.if, "if (Qoder) provider should exist");
assert.equal(pricing.deepseek["deepseek-chat"].input, 0.27);
assert.equal(pricing.deepseek["deepseek-chat"].output, 1.1);
assert.equal(pricing.deepseek["deepseek-chat"].cached, 0.07);
// Should be same in if provider
assert.equal(pricing.if["deepseek-chat"].input, 0.27);
});
it("handles unknown providers by using the models.dev ID as-is", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
assert.ok(pricing["unknown-provider"], "unknown-provider should exist");
assert.equal(pricing["unknown-provider"]["some-model"].input, 1.0);
assert.equal(pricing["unknown-provider"]["some-model"].output, 2.0);
});
it("skips models without cost data", () => {
const dataWithNoCost = {
testprovider: {
id: "testprovider",
models: {
"no-cost-model": {
id: "no-cost-model",
name: "No Cost Model",
attachment: false,
reasoning: false,
tool_call: false,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
// No cost field
limit: { context: 4096, output: 2048 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const pricing = transformModelsDevToPricing(dataWithNoCost);
assert.equal(pricing.testprovider, undefined, "provider without cost should not exist");
});
it("counts pricing entries correctly", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
const totalModels = Object.values(pricing).reduce(
(sum, models) => sum + Object.keys(models).length,
0
);
// openai: 2 models × 2 providers (openai, cx) = 4
// anthropic: 1 model × 2 providers (anthropic, cc) = 2
// deepseek: 1 model × 2 providers (deepseek, if) = 2
// unknown-provider: 1 model × 1 provider = 1
assert.equal(totalModels, 9);
});
});
describe("modelsDevSync — transformModelsDevToCapabilities", () => {
it("transforms capability data correctly", () => {
const caps = transformModelsDevToCapabilities(MOCK_MODELS_DEV_DATA);
assert.ok(caps.openai, "openai capabilities should exist");
assert.ok(caps.openai["gpt-4o"], "gpt-4o capabilities should exist");
const gpt4o = caps.openai["gpt-4o"];
assert.equal(gpt4o.tool_call, true);
assert.equal(gpt4o.reasoning, false);
assert.equal(gpt4o.attachment, true);
assert.equal(gpt4o.structured_output, true);
assert.equal(gpt4o.temperature, true);
assert.equal(gpt4o.limit_context, 128000);
assert.equal(gpt4o.limit_output, 16384);
assert.equal(gpt4o.status, null); // not set in mock
assert.equal(gpt4o.family, "gpt-4");
assert.equal(gpt4o.open_weights, false);
assert.equal(JSON.parse(gpt4o.modalities_input).length, 2);
assert.equal(JSON.parse(gpt4o.modalities_output).length, 1);
});
it("handles interleaved reasoning field", () => {
const dataWithInterleaved = {
testprovider: {
id: "testprovider",
models: {
"reasoning-model": {
id: "reasoning-model",
name: "Reasoning Model",
attachment: false,
reasoning: true,
tool_call: true,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: true,
interleaved: { field: "reasoning_content" },
limit: { context: 200000, output: 64000 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const caps = transformModelsDevToCapabilities(dataWithInterleaved);
assert.equal(caps.testprovider["reasoning-model"].interleaved_field, "reasoning_content");
});
it("handles interleaved as boolean true", () => {
const dataWithBoolInterleaved = {
testprovider: {
id: "testprovider",
models: {
"bool-model": {
id: "bool-model",
name: "Bool Model",
attachment: false,
reasoning: true,
tool_call: true,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
interleaved: true,
limit: { context: 100000, output: 32000 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const caps = transformModelsDevToCapabilities(dataWithBoolInterleaved);
assert.equal(caps.testprovider["bool-model"].interleaved_field, "reasoning_content");
});
it("handles null/missing optional fields", () => {
const dataWithMinimal = {
minimal: {
id: "minimal",
models: {
"minimal-model": {
id: "minimal-model",
name: "Minimal",
attachment: false,
reasoning: false,
tool_call: false,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
// No cost, no interleaved, no status, no family
limit: { context: 4096, output: 2048 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const caps = transformModelsDevToCapabilities(dataWithMinimal);
const m = caps.minimal["minimal-model"];
assert.equal(m.tool_call, false);
assert.equal(m.reasoning, false);
assert.equal(m.attachment, false);
assert.equal(m.structured_output, null);
assert.equal(m.temperature, null);
assert.equal(m.knowledge_cutoff, null);
assert.equal(m.status, null);
assert.equal(m.family, null);
assert.equal(m.interleaved_field, null);
assert.equal(m.limit_context, 4096);
});
});
describe("modelsDevSync — mapProviderId", () => {
it("maps openai to [openai, cx]", () => {
assert.deepEqual(mapProviderId("openai"), ["openai", "cx"]);
});
it("maps anthropic to [anthropic, cc]", () => {
assert.deepEqual(mapProviderId("anthropic"), ["anthropic", "cc"]);
});
it("maps google to [gemini, gemini-cli]", () => {
assert.deepEqual(mapProviderId("google"), ["gemini", "gemini-cli"]);
});
it("maps deepseek to [deepseek, if]", () => {
assert.deepEqual(mapProviderId("deepseek"), ["deepseek", "if"]);
});
it("falls back to original ID for unmapped providers", () => {
assert.deepEqual(mapProviderId("some-new-provider"), ["some-new-provider"]);
});
it("maps bedrock to [kiro, kr]", () => {
assert.deepEqual(mapProviderId("bedrock"), ["kiro", "kr"]);
});
});
describe("modelsDevSync — fetchModelsDev (live API)", () => {
it("fetches data from models.dev API", async () => {
const data = await fetchModelsDev();
assert.ok(typeof data === "object", "data should be an object");
const providerCount = Object.keys(data).length;
assert.ok(providerCount >= 100, `should have 100+ providers, got ${providerCount}`);
let modelCount = 0;
for (const provider of Object.values(data)) {
const p = provider;
if (p.models) {
modelCount += Object.keys(p.models).length;
}
}
assert.ok(modelCount >= 4000, `should have 4000+ models, got ${modelCount}`);
});
it("returns cached data on second call", async () => {
const data1 = await fetchModelsDev();
const data2 = await fetchModelsDev();
assert.strictEqual(data1, data2, "should return same cached reference");
});
it("has openai provider with gpt-4o model", async () => {
const data = await fetchModelsDev();
assert.ok(data.openai, "openai provider should exist");
assert.ok(data.openai.models["gpt-4o"], "gpt-4o model should exist");
});
it("has anthropic provider with claude models", async () => {
const data = await fetchModelsDev();
assert.ok(data.anthropic, "anthropic provider should exist");
const claudeModels = Object.keys(data.anthropic.models).filter((m) => m.includes("claude"));
assert.ok(claudeModels.length > 0, "should have claude models");
});
});