Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cbd60c853e | |||
| 2d8091340f | |||
| 2025c16c82 | |||
| 811fb7f9b2 |
@@ -4,52 +4,73 @@ description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35)
|
||||
|
||||
# Deploy to VPS Workflow
|
||||
|
||||
Deploy OmniRoute to the production VPS using Node.js + PM2 (no Docker).
|
||||
Deploy OmniRoute to the production VPS using `npm install -g` + PM2.
|
||||
|
||||
**VPS:** `69.164.221.35` (Akamai, Ubuntu 24.04, 1GB RAM + 2.5GB swap)
|
||||
**App path:** `/opt/omniroute-app`
|
||||
**Local VPS:** `192.168.0.15` (same setup)
|
||||
**Process manager:** PM2 (`omniroute`)
|
||||
**Port:** `20128`
|
||||
|
||||
> [!IMPORTANT]
|
||||
> PM2 runs from the global npm package at `/usr/lib/node_modules/omniroute`.
|
||||
> **DO NOT** use git clone or local copies. The `npm install -g` command handles
|
||||
> building, publishing, and installing the standalone app in one step.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Push to GitHub
|
||||
### 1. Publish to npm
|
||||
|
||||
Ensure all changes are committed and pushed:
|
||||
Ensure the version in `package.json` is bumped and the package is published:
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
npm publish
|
||||
```
|
||||
|
||||
### 2. SSH into VPS, pull latest code, rebuild, and restart
|
||||
### 2. Install on VPS and restart PM2
|
||||
|
||||
// turbo-all
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "
|
||||
cd /opt/omniroute-app &&
|
||||
git fetch origin &&
|
||||
git reset --hard origin/main &&
|
||||
export NODE_OPTIONS='--max-old-space-size=1536' &&
|
||||
npm install --no-audit --no-fund &&
|
||||
npm run build &&
|
||||
pm2 restart omniroute &&
|
||||
pm2 save &&
|
||||
echo '✅ Deploy complete!'
|
||||
"
|
||||
ssh root@69.164.221.35 "npm install -g omniroute@latest && pm2 restart omniroute && pm2 save && echo '✅ Deploy complete!'"
|
||||
```
|
||||
|
||||
For the local VPS:
|
||||
|
||||
```bash
|
||||
ssh root@192.168.0.15 "npm install -g omniroute@latest && pm2 restart omniroute && pm2 save && echo '✅ Deploy complete!'"
|
||||
```
|
||||
|
||||
### 3. Verify the deployment
|
||||
|
||||
```bash
|
||||
ssh root@69.164.221.35 "pm2 list && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:20128/"
|
||||
ssh root@69.164.221.35 "pm2 list && cat \$(npm root -g)/omniroute/package.json | grep version | head -1 && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:20128/"
|
||||
```
|
||||
|
||||
Expected: PM2 shows `online`, HTTP returns `307` (redirect to login).
|
||||
Expected: PM2 shows `online`, version matches published, HTTP returns `307` (redirect to login).
|
||||
|
||||
## How it works
|
||||
|
||||
1. `npm publish` builds Next.js standalone + bundles everything into the npm package
|
||||
2. `npm install -g omniroute@latest` downloads and installs to `/usr/lib/node_modules/omniroute/`
|
||||
3. PM2 is registered to run `npm start` from that directory (cwd: `/usr/lib/node_modules/omniroute`)
|
||||
4. `pm2 restart omniroute` picks up the new code immediately
|
||||
|
||||
## PM2 Setup (one-time)
|
||||
|
||||
If PM2 needs to be reconfigured from scratch:
|
||||
|
||||
```bash
|
||||
ssh root@<VPS> "
|
||||
cd /usr/lib/node_modules/omniroute &&
|
||||
PORT=20128 pm2 start app/server.js --name omniroute --env PORT=20128 &&
|
||||
pm2 save &&
|
||||
pm2 startup
|
||||
"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The VPS has only 1GB RAM. `NODE_OPTIONS='--max-old-space-size=1536'` uses swap for the build.
|
||||
- The `.env` file is at `/usr/lib/node_modules/omniroute/.env`. Back it up before major npm updates.
|
||||
- PM2 is configured with `pm2 startup` to auto-restart on reboot.
|
||||
- The `.env` file is at `/opt/omniroute-app/.env` (copied from the old Docker setup at `/opt/omniroute/.env`).
|
||||
- Nginx proxies `omniroute.online` → `localhost:20128`.
|
||||
- The VPS has only 1GB RAM — builds happen locally via `npm publish`, not on the VPS.
|
||||
|
||||
@@ -222,16 +222,42 @@ export function applyFingerprint(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime cache for CLI compat providers set via Settings UI.
|
||||
* Updated by the settings API when users toggle providers.
|
||||
*/
|
||||
let _cliCompatProviders: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* Update the runtime cache of CLI-compat-enabled providers.
|
||||
* Called from the settings API when cliCompatProviders is updated.
|
||||
*/
|
||||
export function setCliCompatProviders(providers: string[]): void {
|
||||
_cliCompatProviders = new Set((providers || []).map((p) => p.toLowerCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current list of CLI-compat-enabled providers.
|
||||
*/
|
||||
export function getCliCompatProviders(): string[] {
|
||||
return Array.from(_cliCompatProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if CLI compatibility mode is enabled for a provider.
|
||||
* This reads from the settings database or environment variable.
|
||||
* Reads from: 1) Runtime cache (Settings UI), 2) Environment variables.
|
||||
*/
|
||||
export function isCliCompatEnabled(provider: string): boolean {
|
||||
// Check environment variable first: CLI_COMPAT_<PROVIDER>=1
|
||||
const envKey = `CLI_COMPAT_${provider?.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
||||
const key = provider?.toLowerCase().replace(/[^a-z0-9]/g, "_");
|
||||
|
||||
// 1. Check runtime cache (set via Settings UI)
|
||||
if (_cliCompatProviders.has(provider?.toLowerCase())) return true;
|
||||
|
||||
// 2. Check environment variable: CLI_COMPAT_<PROVIDER>=1
|
||||
const envKey = `CLI_COMPAT_${key?.toUpperCase()}`;
|
||||
if (process.env[envKey] === "1" || process.env[envKey] === "true") return true;
|
||||
|
||||
// Global enable: CLI_COMPAT_ALL=1
|
||||
// 3. Global enable: CLI_COMPAT_ALL=1
|
||||
if (process.env.CLI_COMPAT_ALL === "1" || process.env.CLI_COMPAT_ALL === "true") return true;
|
||||
|
||||
return false;
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "2.0.9",
|
||||
"version": "2.0.10",
|
||||
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
||||
@@ -248,6 +248,76 @@ export default function SecurityTab() {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* CLI Fingerprint Matching */}
|
||||
<Card>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
||||
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
|
||||
fingerprint
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold">{t("cliFingerprint")}</h3>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-text-muted">{t("cliFingerprintDesc")}</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{(["codex", "claude", "github", "antigravity"] as const).map((providerId) => {
|
||||
const providerMeta = Object.values(AI_PROVIDERS).find(
|
||||
(p: any) => p.id === providerId
|
||||
) as any;
|
||||
const isEnabled = (settings.cliCompatProviders || []).includes(providerId);
|
||||
const displayName = providerMeta?.name || providerId;
|
||||
const icon = providerMeta?.icon || "terminal";
|
||||
const color = providerMeta?.color || "#888";
|
||||
return (
|
||||
<button
|
||||
key={providerId}
|
||||
onClick={() => {
|
||||
const current: string[] = settings.cliCompatProviders || [];
|
||||
const updated = current.includes(providerId)
|
||||
? current.filter((p) => p !== providerId)
|
||||
: [...current, providerId];
|
||||
updateSetting("cliCompatProviders", updated);
|
||||
}}
|
||||
disabled={loading}
|
||||
className={`inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all border ${
|
||||
isEnabled
|
||||
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"
|
||||
: "bg-black/[0.02] dark:bg-white/[0.02] border-transparent text-text-muted hover:bg-black/[0.05] dark:hover:bg-white/[0.05]"
|
||||
}`}
|
||||
title={
|
||||
isEnabled
|
||||
? t("disableFingerprintTitle", { provider: displayName })
|
||||
: t("enableFingerprintTitle", { provider: displayName })
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="material-symbols-outlined text-[14px]"
|
||||
style={{ color: isEnabled ? undefined : color }}
|
||||
>
|
||||
{isEnabled ? "fingerprint" : icon}
|
||||
</span>
|
||||
{displayName}
|
||||
{isEnabled && (
|
||||
<span className="material-symbols-outlined text-[12px] text-emerald-500">
|
||||
check
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{(settings.cliCompatProviders || []).length > 0 && (
|
||||
<p className="text-xs text-emerald-600 dark:text-emerald-400 mt-1 flex items-center gap-1">
|
||||
<span className="material-symbols-outlined text-[14px]">verified</span>
|
||||
{t("cliFingerprintEnabled", {
|
||||
count: (settings.cliCompatProviders || []).length,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<SessionInfoCard />
|
||||
<IPFilterSection />
|
||||
</div>
|
||||
|
||||
@@ -5,12 +5,18 @@ import bcrypt from "bcryptjs";
|
||||
import { getRuntimePorts } from "@/lib/runtime/ports";
|
||||
import { updateSettingsSchema } from "@/shared/validation/settingsSchemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { setCliCompatProviders } from "../../../../open-sse/config/cliFingerprints";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
const { password, ...safeSettings } = settings;
|
||||
|
||||
// Sync CLI fingerprint providers to runtime cache on load
|
||||
if (settings.cliCompatProviders) {
|
||||
setCliCompatProviders(settings.cliCompatProviders as string[]);
|
||||
}
|
||||
|
||||
const enableRequestLogs = process.env.ENABLE_REQUEST_LOGS === "true";
|
||||
const runtimePorts = getRuntimePorts();
|
||||
|
||||
@@ -74,6 +80,11 @@ export async function PATCH(request) {
|
||||
clearHealthCheckLogCache();
|
||||
}
|
||||
|
||||
// Sync CLI fingerprint providers to runtime cache
|
||||
if ("cliCompatProviders" in body) {
|
||||
setCliCompatProviders(body.cliCompatProviders || []);
|
||||
}
|
||||
|
||||
const { password, ...safeSettings } = settings;
|
||||
return NextResponse.json(safeSettings);
|
||||
} catch (error) {
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "بنفسجي ساحر",
|
||||
"themeOrange": "أورانج (Orange)",
|
||||
"themeCyan": "كيان",
|
||||
"endpoints": "نقاط النهاية"
|
||||
"endpoints": "نقاط النهاية",
|
||||
"playground": "ملعب النماذج"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "المواضيع",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Виолетово",
|
||||
"themeOrange": "Оранжево",
|
||||
"themeCyan": "Циан",
|
||||
"endpoints": "Крайни точки"
|
||||
"endpoints": "Крайни точки",
|
||||
"playground": "Площадка"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Теми",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Turkis",
|
||||
"endpoints": "Endpoints"
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Legeplads"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temaer",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violett",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpunkte"
|
||||
"endpoints": "Endpunkte",
|
||||
"playground": "Spielwiese"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themen",
|
||||
|
||||
@@ -1493,6 +1493,11 @@
|
||||
"providersBlocked": "{count} provider(s) blocked from /models",
|
||||
"blockProviderTitle": "Block {provider}",
|
||||
"unblockProviderTitle": "Unblock {provider}",
|
||||
"cliFingerprint": "CLI Fingerprint Matching",
|
||||
"cliFingerprintDesc": "Match native CLI binary signatures when proxying requests. Reorders headers and body fields to look identical to the official CLI tools. Your proxy IP is preserved.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) with CLI fingerprint active",
|
||||
"enableFingerprintTitle": "Enable fingerprint for {provider}",
|
||||
"disableFingerprintTitle": "Disable fingerprint for {provider}",
|
||||
"routingStrategy": "Routing Strategy",
|
||||
"fillFirst": "Fill First",
|
||||
"fillFirstDesc": "Use accounts in priority order",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violeta",
|
||||
"themeOrange": "Naranja",
|
||||
"themeCyan": "cian",
|
||||
"endpoints": "Endpoints"
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Temas",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violetti",
|
||||
"themeOrange": "Oranssi",
|
||||
"themeCyan": "Syaani",
|
||||
"endpoints": "Päätepisteet"
|
||||
"endpoints": "Päätepisteet",
|
||||
"playground": "Leikkipaikka"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Teemat",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violette",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Points d'accès"
|
||||
"endpoints": "Points d'accès",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Thèmes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "נקודות קצה"
|
||||
"endpoints": "נקודות קצה",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "narancssárga",
|
||||
"themeCyan": "Cián",
|
||||
"endpoints": "Végpontok"
|
||||
"endpoints": "Végpontok",
|
||||
"playground": "Játszótér"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Témák",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint"
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint"
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoint"
|
||||
"endpoints": "Endpoint",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "エンドポイント"
|
||||
"endpoints": "エンドポイント",
|
||||
"playground": "プレイグラウンド"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "엔드포인트"
|
||||
"endpoints": "엔드포인트",
|
||||
"playground": "플레이그라운드"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Titik Akhir"
|
||||
"endpoints": "Titik Akhir",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Eindpunten"
|
||||
"endpoints": "Eindpunten",
|
||||
"playground": "Speeltuin"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endepunkter"
|
||||
"endpoints": "Endepunkter",
|
||||
"playground": "Lekeplass"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Mga Endpoint"
|
||||
"endpoints": "Mga Endpoint",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Punkty końcowe"
|
||||
"endpoints": "Punkty końcowe",
|
||||
"playground": "Plac zabaw"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Endpoints"
|
||||
"endpoints": "Endpoints",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
@@ -1480,6 +1481,11 @@
|
||||
"providersBlocked": "{count} provedor(es) bloqueado(s) do /models",
|
||||
"blockProviderTitle": "Bloquear {provider}",
|
||||
"unblockProviderTitle": "Desbloquear {provider}",
|
||||
"cliFingerprint": "Assinatura Digital CLI",
|
||||
"cliFingerprintDesc": "Reproduz a assinatura digital dos CLIs nativos ao fazer proxy. Reorganiza headers e campos do body para parecer idêntico às ferramentas CLI oficiais. O IP do proxy é preservado.",
|
||||
"cliFingerprintEnabled": "{count} provider(s) com fingerprint CLI ativo",
|
||||
"enableFingerprintTitle": "Ativar fingerprint para {provider}",
|
||||
"disableFingerprintTitle": "Desativar fingerprint para {provider}",
|
||||
"routingStrategy": "Estratégia de Roteamento",
|
||||
"fillFirst": "Preencher Primeiro",
|
||||
"fillFirstDesc": "Usar contas em ordem de prioridade",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeGreen": "Verde",
|
||||
"themeViolet": "Violeta",
|
||||
"themeOrange": "Laranja",
|
||||
"themeCyan": "Ciano"
|
||||
"themeCyan": "Ciano",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Puncte finale"
|
||||
"endpoints": "Puncte finale",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Фиолетовый",
|
||||
"themeOrange": "Оранжевый",
|
||||
"themeCyan": "Голубой",
|
||||
"endpoints": "Конечные точки"
|
||||
"endpoints": "Конечные точки",
|
||||
"playground": "Площадка"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Темы",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Koncové body"
|
||||
"endpoints": "Koncové body",
|
||||
"playground": "Ihrisko"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Ändpunkter"
|
||||
"endpoints": "Ändpunkter",
|
||||
"playground": "Lekplats"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "จุดปลายทาง"
|
||||
"endpoints": "จุดปลายทาง",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "ธีมส์",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Кінцеві точки"
|
||||
"endpoints": "Кінцеві точки",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "Điểm cuối"
|
||||
"endpoints": "Điểm cuối",
|
||||
"playground": "Playground"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"themeViolet": "Violet",
|
||||
"themeOrange": "Orange",
|
||||
"themeCyan": "Cyan",
|
||||
"endpoints": "端点"
|
||||
"endpoints": "端点",
|
||||
"playground": "模型试验场"
|
||||
},
|
||||
"themesPage": {
|
||||
"title": "Themes",
|
||||
|
||||
@@ -34,4 +34,6 @@ export const updateSettingsSchema = z.object({
|
||||
mcpEnabled: z.boolean().optional(),
|
||||
mcpTransport: z.enum(["stdio", "sse", "streamable-http"]).optional(),
|
||||
a2aEnabled: z.boolean().optional(),
|
||||
// CLI Fingerprint compatibility (per-provider)
|
||||
cliCompatProviders: z.array(z.string().max(100)).optional(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user