From a7e09eda5c5d8465049774883d4c04acd57f1031 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 13:17:24 -0300 Subject: [PATCH 01/20] fix(dashboard): restore ProviderModelsModal map broken by #11228 (base-red #9985) (#11256) The squash merge of #11228 applied its endpoint-header hunk inside ProviderModelsModal, replacing the groupModels.map callback's return statement with the page-level guided header JSX. The file no longer parsed (Turbopack: 3 errors at line 2397; release-green reported the same defect as '1 ESLint error'), red-ing every Build App / Docker publish run since 8a42aeebb8. Surgical revert of that single hunk: the file is byte-identical to its pre-#11228 state. The rest of #11228 (health page verdict header, resilience reassurance, i18n keys) parses fine and stays. Validation: prettier parse OK; EndpointPageClient.test.tsx 4/4 (the pre-existing jsdom render suite imports the component, so it is the permanent regression guard); diff vs pre-#11228 empty. Refs #9985 Co-authored-by: Xiangzhe From b81f2b646a9f7a6eab43acce050ecdb728ff1b6d Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:15:14 -0300 Subject: [PATCH 02/20] fix(build): align pack-boot sql.js expectations with dependency-based packaging (#11242) (#11266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check:pack-artifact and check:pack-boot have been self-contradictory since 05/08, blocking the v3.8.50 publish in ci.yml (build:cli job) and npm-publish.yml: - check:pack-artifact FAILS any tarball path containing a node_modules segment (PACK_ARTIFACT_NEVER_ALLOWED_SEGMENTS; files[] also excludes "!**/node_modules/**"). - check:pack-boot REQUIRED sql.js under the vendored dist/node_modules/sql.js location — a path the tarball can never carry, so both gates could never be green at once. The packaging model is now dependency-based: sql.js and node-machine-id are declared `dependencies` (a clean install places them under /node_modules/), and better-sqlite3 is an optionalDependency installed natively per platform (^13.0.2 — which also covers the darwin-arm64 prebuild gap from #11242 by construction). The runtime already resolves the WASM at /node_modules/sql.js/dist/sql-wasm.wasm (src/lib/db/adapters/sqljsAdapter.ts). Changes: - scripts/check/check-pack-boot.mjs: REQUIRED_SQLJS_RUNTIME_FILES now points at node_modules/sql.js/{package.json,dist/sql-wasm.js, dist/sql-wasm.wasm} — the dependency-installed location the clean-prefix install actually produces. REQUIRED_MACHINE_TOKEN_RUNTIME_FILES was already correct and is unchanged. - bin/cli/runtime/sqliteRuntime.mjs: BETTER_SQLITE3_VERSION bumped ^12.10.1 -> ^13.0.2 to match optionalDependencies (the lazy runtime install was pulling the wrong major), and exported for the guard. - tests/unit/pack-boot-runtime-paths.test.ts (new, TDD: RED -> GREEN): pins that (a) no pack-boot required path references a never-publishable vendored dist/ location (driven by PACK_ARTIFACT_NEVER_ALLOWED_SEGMENTS), (b) sql.js/node-machine-id stay declared dependencies, (c) the lazy-install spec stays on the declared optionalDependency major. - tests/unit/check-pack-boot.test.ts: the sql.js contract test pinned the old vendored path; updated to node_modules/sql.js/dist/sql-wasm.wasm. This is alignment to the real new contract (vendoring ended), not masking — the same test still asserts the find-missing behavior. Electron is unaffected: the vendored dist/node_modules bundle still exists for Electron packaging (postinstall.mjs and assembleStandalone.mjs untouched). Refs #11242 Refs #10296 Co-authored-by: Xiangzhe --- bin/cli/runtime/sqliteRuntime.mjs | 4 +- scripts/check/check-pack-boot.mjs | 12 +++- tests/unit/check-pack-boot.test.ts | 7 +- tests/unit/pack-boot-runtime-paths.test.ts | 78 ++++++++++++++++++++++ 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 tests/unit/pack-boot-runtime-paths.test.ts diff --git a/bin/cli/runtime/sqliteRuntime.mjs b/bin/cli/runtime/sqliteRuntime.mjs index 506481a353..e8ca615bbf 100644 --- a/bin/cli/runtime/sqliteRuntime.mjs +++ b/bin/cli/runtime/sqliteRuntime.mjs @@ -6,7 +6,9 @@ import { pathToFileURL } from "node:url"; import { validateBinaryMagic, platformBinaryLabel } from "./magicBytes.mjs"; const RUNTIME_DIR = join(homedir(), ".omniroute", "runtime"); -const BETTER_SQLITE3_VERSION = "better-sqlite3@^12.10.1"; +// Exported so the packaging coherence guard (tests/unit/pack-boot-runtime-paths.test.ts) +// can assert this stays on the same major as optionalDependencies.better-sqlite3 (#11242). +export const BETTER_SQLITE3_VERSION = "better-sqlite3@^13.0.2"; let resolvedCached = null; diff --git a/scripts/check/check-pack-boot.mjs b/scripts/check/check-pack-boot.mjs index 673decdd21..2ca4097a0a 100644 --- a/scripts/check/check-pack-boot.mjs +++ b/scripts/check/check-pack-boot.mjs @@ -26,10 +26,16 @@ const MAX_SERVER_OUTPUT_CHARS = 1_000_000; const SQLJS_STARTUP_MARKER = "Pre-initializing sql.js WASM"; const DEFAULT_CLI_SALT = "omniroute-cli-auth-v1"; +// Dependency-based packaging (#11242): the tarball can never contain a node_modules +// path (files[] has "!**/node_modules/**" and check:pack-artifact fails on the +// segment), so sql.js must be required where a clean `npm install` of the declared +// `dependencies` places it — /node_modules/sql.js — NOT under the old +// vendored dist/node_modules location. The runtime resolves the WASM the same way +// (src/lib/db/adapters/sqljsAdapter.ts → /node_modules/sql.js/dist/sql-wasm.wasm). export const REQUIRED_SQLJS_RUNTIME_FILES = Object.freeze([ - "dist/node_modules/sql.js/package.json", - "dist/node_modules/sql.js/dist/sql-wasm.js", - "dist/node_modules/sql.js/dist/sql-wasm.wasm", + "node_modules/sql.js/package.json", + "node_modules/sql.js/dist/sql-wasm.js", + "node_modules/sql.js/dist/sql-wasm.wasm", ]); export const REQUIRED_MACHINE_TOKEN_RUNTIME_FILES = Object.freeze([ diff --git a/tests/unit/check-pack-boot.test.ts b/tests/unit/check-pack-boot.test.ts index 56abe03176..2a7e0edf07 100644 --- a/tests/unit/check-pack-boot.test.ts +++ b/tests/unit/check-pack-boot.test.ts @@ -71,10 +71,13 @@ test("installed package contract requires sql.js metadata, entrypoint, and WASM" [] ); - present.delete(path.join("/pkg", "dist/node_modules/sql.js/dist/sql-wasm.wasm")); + // Dependency-based packaging (#11242): sql.js is a declared dependency, so the + // contract path is the npm-installed /node_modules/sql.js location, + // never the old vendored dist/node_modules one (banned from the tarball). + present.delete(path.join("/pkg", "node_modules/sql.js/dist/sql-wasm.wasm")); assert.deepEqual( findMissingSqlJsRuntimeFiles("/pkg", (file) => present.has(file)), - ["dist/node_modules/sql.js/dist/sql-wasm.wasm"] + ["node_modules/sql.js/dist/sql-wasm.wasm"] ); }); diff --git a/tests/unit/pack-boot-runtime-paths.test.ts b/tests/unit/pack-boot-runtime-paths.test.ts new file mode 100644 index 0000000000..8b9016a245 --- /dev/null +++ b/tests/unit/pack-boot-runtime-paths.test.ts @@ -0,0 +1,78 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { + REQUIRED_MACHINE_TOKEN_RUNTIME_FILES, + REQUIRED_SQLJS_RUNTIME_FILES, +} from "../../scripts/check/check-pack-boot.mjs"; +import { PACK_ARTIFACT_NEVER_ALLOWED_SEGMENTS } from "../../scripts/build/pack-artifact-policy.ts"; +import * as sqliteRuntime from "../../bin/cli/runtime/sqliteRuntime.mjs"; + +// Coherence guard for the v3.8.50 publish blocker (#11242): check:pack-artifact +// FAILS any tarball path containing a node_modules segment (files[] excludes them +// via "!**/node_modules/**"), while check:pack-boot REQUIRED sql.js under the +// vendored dist/node_modules/ location — a path the tarball can never contain, +// so the two gates could never be green at the same time. The npm packaging +// model is now dependency-based: sql.js and node-machine-id are declared +// `dependencies` that a clean install places under /node_modules/, +// and better-sqlite3 is an optionalDependency installed natively per platform. +// These tests pin that contract so neither gate can drift back into conflict. + +const REPO_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); +const PKG = JSON.parse(readFileSync(path.join(REPO_ROOT, "package.json"), "utf8")) as { + dependencies?: Record; + optionalDependencies?: Record; +}; + +test("pack-boot required runtime files never reference a never-publishable vendored path", () => { + const requiredFiles = [...REQUIRED_SQLJS_RUNTIME_FILES, ...REQUIRED_MACHINE_TOKEN_RUNTIME_FILES]; + assert.ok(requiredFiles.length > 0, "pack-boot must require at least one runtime file"); + for (const requiredPath of requiredFiles) { + for (const segment of PACK_ARTIFACT_NEVER_ALLOWED_SEGMENTS) { + const vendoredPrefix = `dist/${segment}/`; + assert.ok( + !requiredPath.includes(vendoredPrefix), + `"${requiredPath}" lives under ${vendoredPrefix} — check:pack-artifact bans any ` + + `tarball path with a "${segment}" segment, so check:pack-boot must require the ` + + `dependency-installed location (node_modules/) instead (#11242)` + ); + } + } +}); + +test("sql.js and node-machine-id are declared runtime dependencies (npm installs them)", () => { + assert.ok( + PKG.dependencies?.["sql.js"], + "sql.js must stay in dependencies so a clean install provides node_modules/sql.js" + ); + assert.ok( + PKG.dependencies?.["node-machine-id"], + "node-machine-id must stay in dependencies so a clean install provides node_modules/node-machine-id" + ); +}); + +test("the lazy better-sqlite3 runtime install targets the declared optionalDependency major", () => { + const spec = (sqliteRuntime as Record).BETTER_SQLITE3_VERSION; + assert.equal( + typeof spec, + "string", + "bin/cli/runtime/sqliteRuntime.mjs must export BETTER_SQLITE3_VERSION" + ); + const declared = PKG.optionalDependencies?.["better-sqlite3"]; + assert.ok(declared, "package.json must declare better-sqlite3 as an optionalDependency"); + + const majorOf = (versionSpec: string): number => { + const match = versionSpec.match(/(\d+)\./); + assert.ok(match, `"${versionSpec}" must contain a semver major`); + return Number(match[1]); + }; + assert.equal( + majorOf(spec as string), + majorOf(declared), + `lazy runtime install "${spec}" drifted from optionalDependencies.better-sqlite3 ` + + `"${declared}" — the fallback install must track the same major (#11242)` + ); +}); From 7fdd2e0f2ab53d0758b949e0d349871357fba642 Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:21:27 +0200 Subject: [PATCH 03/20] fix(sse): learn accepted reasoning_effort sets and clamp downgrade-only (#11232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined 12-PR batch board (worktree off origin/release/v3.8.50 @ 8f390eff): focused node:test suites 183/184 (the single red was a load-induced pollUntil flake — 11/11 when re-run isolated, this file included), typecheck:core clean, file-size/changelog/complexity/cognitive gates all within baseline, mutation-coverage gate no-drift. Learned effort sets now cost at most one 400 per provider+model. Thank you @maxmad64bis! --- open-sse/executors/base.ts | 27 ++-- open-sse/executors/base/reasoningEffort.ts | 42 +++--- .../services/learnedReasoningEffortCaps.ts | 100 +++++++++----- .../learned-reasoning-effort-caps.test.ts | 94 +++++++++++-- .../reasoning-effort-clamp-and-retry.test.ts | 127 +++++++++++++++++- ...easoning-effort-learned-capability.test.ts | 64 +++++++++ 6 files changed, 373 insertions(+), 81 deletions(-) diff --git a/open-sse/executors/base.ts b/open-sse/executors/base.ts index 1c13442aff..53800c8bc7 100644 --- a/open-sse/executors/base.ts +++ b/open-sse/executors/base.ts @@ -1559,22 +1559,31 @@ export class BaseExecutor { if (acceptedValues) { reasoningEffortClamped = true; const learned = recordLearnedReasoningEffort(this.provider, model, acceptedValues); - if (learned) { + if (learned && learned.size > 0) { + const beforeRetry = JSON.stringify(transformedBody); transformedBody = sanitizeReasoningEffortForProvider( transformedBody, this.provider, model, log ); - let retryBody = JSON.stringify(transformedBody); - if (usesClaudeCodeProtocol || this.provider === "claude") { - retryBody = await signRequestBody(retryBody); + const afterRetry = JSON.stringify(transformedBody); + if (beforeRetry === afterRetry) { + log?.info?.( + "REASONING_SANITIZE", + `Upstream ${response.status} rejected reasoning_effort on ${url} — learned ${[...learned].join(",")} but clamp was no-op for ${this.provider}/${model}, not retrying` + ); + } else { + let retryBody = JSON.stringify(transformedBody); + if (usesClaudeCodeProtocol || this.provider === "claude") { + retryBody = await signRequestBody(retryBody); + } + log?.info?.( + "REASONING_SANITIZE", + `Upstream ${response.status} rejected reasoning_effort on ${url} — clamped to ${[...learned].join(",")} and retrying (learned for ${this.provider}/${model})` + ); + response = await fetchWithStartTimeout(url, { ...fetchOptions, body: retryBody }); } - log?.info?.( - "REASONING_SANITIZE", - `Upstream ${response.status} rejected reasoning_effort on ${url} — clamped to ${learned} and retrying (learned for ${this.provider}/${model})` - ); - response = await fetchWithStartTimeout(url, { ...fetchOptions, body: retryBody }); } } } diff --git a/open-sse/executors/base/reasoningEffort.ts b/open-sse/executors/base/reasoningEffort.ts index 8dd99904fd..d6ca00ba55 100644 --- a/open-sse/executors/base/reasoningEffort.ts +++ b/open-sse/executors/base/reasoningEffort.ts @@ -10,7 +10,7 @@ import { } from "../../config/providerModels.ts"; import { getLearnedReasoningEffort, - REASONING_EFFORT_ORDER, + clampToLearned, } from "../../services/learnedReasoningEffortCaps.ts"; /** @@ -340,26 +340,29 @@ export function sanitizeReasoningEffortForProvider( return body; } + // Generic learned clamp (downgrade-only: greatest accepted <= demand). + // Sits AFTER the per-provider early returns by design: deepseek/command-code/ + // ollama-cloud have deliberate static translations that take precedence; the + // learned set governs every other provider and all effort values, before the + // xhigh/max static fallbacks below. + const learnedSet = getLearnedReasoningEffort(provider, modelStr); + if (learnedSet && learnedSet.size > 0 && !learnedSet.has(effortStr)) { + const clamped = clampToLearned(effortStr, learnedSet); + if (clamped && clamped !== effortStr) { + log?.info?.( + "REASONING_SANITIZE", + `${provider}/${modelStr}: clamped reasoning_effort ${effortStr} → ${clamped} (learned)` + ); + return writeEffortValue(b, clamped, c); + } + } + const supportsXHigh = supportsXHighEffort(provider, modelStr); const supportsMax = supportsMaxEffortForProvider(provider, modelStr); - // Highest value we've actually seen this provider+model accept in a real - // upstream 4xx (learnedReasoningEffortCaps.ts) — takes priority over the - // static registry (which defaults to "supports everything" when there's no - // entry, e.g. custom OpenAI-compatible connections) and over the hardcoded - // "high" fallback below (which isn't always valid either). - const learnedCap = getLearnedReasoningEffort(provider, modelStr); - const learnedRank = learnedCap ? REASONING_EFFORT_ORDER.indexOf(learnedCap) : -1; // ── xhigh handling ────────────────────────────────────────────────────── // xhigh is OmniRoute-internal. Map it to the best effort the model accepts. if (effortStr === "xhigh") { - if (learnedCap && learnedRank < REASONING_EFFORT_ORDER.indexOf("xhigh")) { - log?.info?.( - "REASONING_SANITIZE", - `${provider}/${modelStr}: clamped reasoning_effort xhigh → ${learnedCap} (learned)` - ); - return writeEffortValue(b, learnedCap, c); - } if (supportsXHigh) return body; // model accepts xhigh natively if (supportsMax) { log?.info?.( @@ -384,13 +387,6 @@ export function sanitizeReasoningEffortForProvider( // upstream, and if it 400s the user gets a clear signal. This prevents // new models from being unusable for weeks until they're whitelisted (#8057). if (effortStr === "max") { - if (learnedCap && learnedRank < REASONING_EFFORT_ORDER.indexOf("max")) { - log?.info?.( - "REASONING_SANITIZE", - `${provider}/${modelStr}: clamped reasoning_effort max → ${learnedCap} (learned)` - ); - return writeEffortValue(b, learnedCap, c); - } if (supportsMax) return body; // explicitly known to accept max // A model that explicitly advertises its accepted tiers is safe to normalize. @@ -407,7 +403,7 @@ export function sanitizeReasoningEffortForProvider( )?.supportedThinkingEfforts; const maxFallback = Array.isArray(explicitEfforts) && !explicitEfforts.includes("max") - ? ["xhigh", "high", "medium", "low"].find((tier) => explicitEfforts.includes(tier)) + ? ["ultra", "xhigh", "high", "medium", "low"].find((tier) => explicitEfforts.includes(tier)) : undefined; if (maxFallback) { log?.info?.( diff --git a/open-sse/services/learnedReasoningEffortCaps.ts b/open-sse/services/learnedReasoningEffortCaps.ts index b0125d8683..ef7e7b5f32 100644 --- a/open-sse/services/learnedReasoningEffortCaps.ts +++ b/open-sse/services/learnedReasoningEffortCaps.ts @@ -6,12 +6,14 @@ * Same shape as `learnedThinkingCaps.ts` (thinking_budget), generalized from a * numeric budget to an ordinal reasoning_effort scale: on a 4xx whose body * enumerates the accepted values, `base.ts`'s executor calls - * `recordLearnedReasoningEffort`, which stores the highest recognized value in a - * module-level Map keyed "provider:model" (lowercased). Subsequent requests for - * the same provider+model read the cap via `getLearnedReasoningEffort` (consulted - * by `sanitizeReasoningEffortForProvider` in `executors/base/reasoningEffort.ts`) + * `recordLearnedReasoningEffort`, which stores the accepted set in a module-level + * Map keyed "provider:model" (lowercased). Subsequent requests for the same + * provider+model read the set via `getLearnedReasoningEffort` (consulted by + * `sanitizeReasoningEffortForProvider` in `executors/base/reasoningEffort.ts`) * so the 4xx→retry round-trip is paid at most once per process per provider+model. * + * `clampToLearned` implements downgrade-only clamping: greatest accepted <= demand. + * * In-memory only (same operator-accepted tradeoff as the thinking-budget cache): * restart resets, the first request after a restart may re-learn at the cost of * one upstream 4xx. @@ -25,10 +27,11 @@ export const REASONING_EFFORT_ORDER: readonly string[] = [ "high", "xhigh", "max", + "ultra", ]; -// key: `${provider}:${model}` lowercased → highest value known to be accepted. -const learnedCaps = new Map(); +// key: `${provider}:${model}` lowercased → accepted set. +const learnedCaps = new Map>(); function buildKey(provider: string | null | undefined, model: string | null | undefined): string { const p = typeof provider === "string" ? provider.trim().toLowerCase() : ""; @@ -41,61 +44,89 @@ function rankOf(value: string): number { return REASONING_EFFORT_ORDER.indexOf(value); } +function isSubset(a: Set, b: Set): boolean { + for (const v of a) if (!b.has(v)) return false; + return true; +} + /** - * Return the learned cap for provider+model, or null when nothing has been - * learned yet (no upstream 4xx recorded). Keyed case-insensitively. + * Return the learned accepted set for provider+model, or null when nothing has + * been learned yet (no upstream 4xx recorded). Keyed case-insensitively. */ export function getLearnedReasoningEffort( provider: string | null | undefined, model: string | null | undefined -): string | null { +): Set | null { const key = buildKey(provider, model); if (!key) return null; - return learnedCaps.get(key) ?? null; + const v = learnedCaps.get(key); + return v ? new Set(v) : null; } /** * Record that `acceptedValues` is the enum the upstream advertised for - * provider+model, and store the highest recognized value as the learned cap. - * Returns the stored value, or null when `acceptedValues` contained no token - * from `REASONING_EFFORT_ORDER` (nothing usable to learn) or the key is unusable. + * provider+model, and store the accepted set. Returns the stored set, or null + * when `acceptedValues` contained no token from `REASONING_EFFORT_ORDER`. * - * Always monotonically decreases: if a cap already stored ranks lower than the - * newly computed highest, the stored (lower) value wins and is returned - * unchanged. This keeps a later, laxer-looking response (or a race between - * concurrent requests) from ratcheting the cap back up. + * Monotonically non-expanding: if existing ⊆ newSet, keep existing (never + * re-expand); if newSet ⊂ existing, replace (more restrictive); if neither + * subset, keep existing. */ export function recordLearnedReasoningEffort( provider: string | null | undefined, model: string | null | undefined, acceptedValues: string[] -): string | null { +): Set | null { const key = buildKey(provider, model); if (!key) return null; - let best: string | null = null; - let bestRank = -1; + const newSet = new Set(); for (const raw of acceptedValues) { - const rank = rankOf(raw); - if (rank > bestRank) { - bestRank = rank; - best = raw; - } + const lowered = typeof raw === "string" ? raw.trim().toLowerCase() : ""; + if (lowered && REASONING_EFFORT_ORDER.includes(lowered)) newSet.add(lowered); } - if (best === null) return null; + if (newSet.size === 0) return null; const existing = learnedCaps.get(key); - if (existing !== undefined && rankOf(existing) <= bestRank) { - return existing; // already learned an equal-or-lower cap; keep it + if (existing !== undefined) { + // Defensive copies: never hand out the live cached Set. + if (isSubset(existing, newSet)) return new Set(existing); + if (isSubset(newSet, existing)) { + learnedCaps.set(key, newSet); + return new Set(newSet); + } + return new Set(existing); + } + learnedCaps.set(key, newSet); + return new Set(newSet); +} + +/** + * Return the greatest accepted value <= effortStr (downgrade only), or null + * if effortStr is already accepted, below the minimum, or not in ORDER. + */ +export function clampToLearned(effortStr: string, accepted: Set): string | null { + if (!effortStr || accepted.has(effortStr)) return null; + const rank = rankOf(effortStr); + if (rank === -1) return null; + const minRank = Math.min(...[...accepted].map((v) => rankOf(v))); + if (rank < minRank) return null; + let best: string | null = null; + let bestRank = -1; + for (const v of accepted) { + const r = rankOf(v); + if (r <= rank && r > bestRank) { + bestRank = r; + best = v; + } } - learnedCaps.set(key, best); return best; } -// Matches both prose shapes observed: OVH's `@ai-sdk/openai-compatible` -// deserializer ("expected one of `a`, `b`") and a generic vendor prose form -// ("Supported types are a, b, and c"). -const LIST_INTRO = /(?:expected one of|supported (?:types|values) are)[:\s]*([^.]+)/i; +// Matches prose shapes: OVH's "@ai-sdk/openai-compatible" deserializer +// ("expected one of `a`, `b`"), generic ("Supported types are a, b, and c"), +// and "please use a, b, or c". +const LIST_INTRO = /(?:expected one of|supported (?:types|values) are|please use)[:\s]*([^.]+)/i; /** * Extract the upstream-advertised accepted reasoning_effort values from a 4xx @@ -108,13 +139,14 @@ export function parseReasoningEffortEnum(errText: unknown): string[] | null { const match = LIST_INTRO.exec(errText); if (!match) return null; const tokens = match[1] - .split(/,|\band\b|&/i) + .split(/,|\b(?:and|or)\b|&/i) .map((t) => t .replace(/`/g, "") .replace(/\([^)]*\)/g, "") .trim() .toLowerCase() + .replace(/^[^a-z]+|[^a-z]+$/g, "") ) .filter((t) => t.length > 0 && REASONING_EFFORT_ORDER.includes(t)); return tokens.length > 0 ? tokens : null; diff --git a/tests/unit/learned-reasoning-effort-caps.test.ts b/tests/unit/learned-reasoning-effort-caps.test.ts index 5d69a342c3..c98eb8fad0 100644 --- a/tests/unit/learned-reasoning-effort-caps.test.ts +++ b/tests/unit/learned-reasoning-effort-caps.test.ts @@ -18,7 +18,7 @@ after(() => { // ── REASONING_EFFORT_ORDER ────────────────────────────────────────────────── -test("REASONING_EFFORT_ORDER is none < minimal < low < medium < high < xhigh < max", () => { +test("REASONING_EFFORT_ORDER is none < minimal < low < medium < high < xhigh < max < ultra", () => { assert.deepEqual(REASONING_EFFORT_ORDER, [ "none", "minimal", @@ -27,6 +27,24 @@ test("REASONING_EFFORT_ORDER is none < minimal < low < medium < high < xhigh < m "high", "xhigh", "max", + "ultra", + ]); +}); + +test("REASONING_EFFORT_ORDER ends with ultra", () => { + assert.equal(REASONING_EFFORT_ORDER.at(-1), "ultra"); +}); +test("parseReasoningEffortEnum extracts please use low, high, or max", () => { + const err = + "This model always engages in thinking and cannot be disabled; please use low, high, or max"; + assert.deepEqual(parseReasoningEffortEnum(err), ["low", "high", "max"]); +}); +test("parseReasoningEffortEnum extracts please use with ultra", () => { + assert.deepEqual(parseReasoningEffortEnum("please use low, high, max, ultra"), [ + "low", + "high", + "max", + "ultra", ]); }); @@ -70,9 +88,10 @@ test("records the highest recognized value from the accepted list", () => { "medium", "low", "minimal", - ]); - assert.equal(learned, "high"); - assert.equal(getLearnedReasoningEffort("ovh", "qwen3-coder-30b-a3b-instruct"), "high"); + ]) as unknown as Set; + assert.ok(learned instanceof Set); + assert.ok(learned.has("high")); + assert.equal((getLearnedReasoningEffort("ovh", "qwen3-coder-30b-a3b-instruct") as unknown as Set).has("high"), true); }); test("returns null and stores nothing when acceptedValues has no recognized token", () => { @@ -89,26 +108,77 @@ test("monotonic decrease: a later, higher accepted-list never ratchets the cap b "medium", "high", "xhigh", - ]); - assert.equal(learned, "medium"); - assert.equal(getLearnedReasoningEffort("acme", "model-x"), "medium"); + ]) as unknown as Set; + assert.equal(learned.size, 3); + assert.ok(learned.has("medium")); + assert.equal((getLearnedReasoningEffort("acme", "model-x") as unknown as Set).size, 3); }); test("a later, lower accepted-list does ratchet the cap down", () => { recordLearnedReasoningEffort("acme", "model-x", ["none", "low", "medium", "high"]); - const learned = recordLearnedReasoningEffort("acme", "model-x", ["none", "low"]); - assert.equal(learned, "low"); - assert.equal(getLearnedReasoningEffort("acme", "model-x"), "low"); + const learned = recordLearnedReasoningEffort("acme", "model-x", ["none", "low"]) as unknown as Set; + assert.equal(learned.size, 2); + assert.ok(learned.has("low")); + assert.equal((getLearnedReasoningEffort("acme", "model-x") as unknown as Set).size, 2); }); +test("clampToLearned medium→low when accepted is low,high,max", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("medium", new Set(["low", "high", "max"])), "low"); +}); +test("clampToLearned xhigh→high when accepted is low,high,max", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("xhigh", new Set(["low", "high", "max"])), "high"); +}); +test("clampToLearned ultra→max when accepted is low,high,max", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("ultra", new Set(["low", "high", "max"])), "max"); +}); +test("clampToLearned ultra→medium when accepted is low,medium", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("ultra", new Set(["low", "medium"])), "medium"); +}); +test("clampToLearned high→medium when accepted is low,medium", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("high", new Set(["low", "medium"])), "medium"); +}); +test("clampToLearned returns null when already accepted", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("low", new Set(["low", "high", "max"])), null); +}); +test("clampToLearned returns null when effort < min (no upgrade)", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("low", new Set(["high", "max"])), null); +}); +test("clampToLearned returns null for turbo (not in ORDER)", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("turbo", new Set(["low", "high", "max"])), null); +}); +test("clampToLearned returns null when effort is none but accepted is low,high,max", async () => { + const { clampToLearned } = await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + assert.equal(clampToLearned("none", new Set(["low", "high", "max"])), null); +}); +test("recordLearned stores Set and getLearned returns Set", () => { + const s = recordLearnedReasoningEffort("acme", "m1", ["low", "high", "max"]); + assert.ok(s instanceof Set); + assert.deepEqual([...(s as unknown as Set)].sort(), ["high", "low", "max"]); + const g = getLearnedReasoningEffort("acme", "m1"); + assert.ok(g instanceof Set); +}); +test("monotonicity incomparable: keep existing when neither subset", () => { + recordLearnedReasoningEffort("acme", "m4", ["low", "high", "max"]); + const s4 = recordLearnedReasoningEffort("acme", "m4", ["low", "medium"]); + assert.equal((s4 as unknown as Set).size, 3); + assert.ok((s4 as unknown as Set).has("high")); +}); test("getLearnedReasoningEffort returns null for unknown provider+model", () => { assert.equal(getLearnedReasoningEffort("acme", "unknown-model"), null); }); test("getLearnedReasoningEffort is keyed case-insensitively on provider+model", () => { recordLearnedReasoningEffort("OVH", "Qwen3-Coder-30B", ["none", "high"]); - assert.equal(getLearnedReasoningEffort("ovh", "qwen3-coder-30b"), "high"); - assert.equal(getLearnedReasoningEffort("OVH", "QWEN3-CODER-30B"), "high"); + assert.ok((getLearnedReasoningEffort("ovh", "qwen3-coder-30b") as unknown as Set).has("high")); + assert.ok((getLearnedReasoningEffort("OVH", "QWEN3-CODER-30B") as unknown as Set).has("high")); }); test("different providers for the same model id have independent caps", () => { diff --git a/tests/unit/reasoning-effort-clamp-and-retry.test.ts b/tests/unit/reasoning-effort-clamp-and-retry.test.ts index a97ac16df0..fa925216c6 100644 --- a/tests/unit/reasoning-effort-clamp-and-retry.test.ts +++ b/tests/unit/reasoning-effort-clamp-and-retry.test.ts @@ -66,9 +66,8 @@ test("422 'unknown variant xhigh, expected one of ...' clamps reasoning_effort a assert.equal(capturedBodies.length, 2); assert.equal(capturedBodies[0].reasoning_effort, "xhigh"); assert.equal(capturedBodies[1].reasoning_effort, "high"); - assert.equal( - getLearnedReasoningEffort("openai-compatible-chat-eaff6869", "qwen3-coder-30b-a3b-instruct"), - "high" + assert.ok( + (getLearnedReasoningEffort("openai-compatible-chat-eaff6869", "qwen3-coder-30b-a3b-instruct") as unknown as Set).has("high") ); assert.equal(result.response.status, 200); } finally { @@ -108,3 +107,125 @@ test("a second request for the same provider+model sends the learned value on th globalThis.fetch = originalFetch; } }); + +test("400 please use low, high, or max clamps and retries once", async () => { + const executor = new SimpleExecutor(); + const originalFetch = globalThis.fetch; + const capturedBodies: Record[] = []; + const BODY_400_PLEASE_USE = JSON.stringify({ + error: { message: "This model always engages in thinking and cannot be disabled; please use low, high, or max" }, + }); + + globalThis.fetch = async (_url: string | URL | Request, init: RequestInit = {}) => { + const body = JSON.parse(String(init.body)); + capturedBodies.push(body); + if (capturedBodies.length === 1) { + return new Response(BODY_400_PLEASE_USE, { + status: 400, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + + try { + const result = await executor.execute({ + model: "x-preview-f-free", + body: { reasoning_effort: "medium" }, + stream: false, + credentials: {}, + }); + assert.equal(capturedBodies.length, 2); + assert.equal(capturedBodies[0].reasoning_effort, "medium"); + assert.equal(capturedBodies[1].reasoning_effort, "low"); + const learned = getLearnedReasoningEffort("openai-compatible-chat-eaff6869", "x-preview-f-free") as unknown as Set; + assert.ok(learned instanceof Set); + assert.ok(learned.has("low")); + assert.ok(learned.has("high")); + assert.equal(result.response.status, 200); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("400 please use low, medium with ultra retries to medium", async () => { + const executor = new SimpleExecutor(); + const originalFetch = globalThis.fetch; + const capturedBodies: Record[] = []; + const BODY_400_ULTRA = JSON.stringify({ + error: { message: "please use low, medium" }, + }); + + globalThis.fetch = async (_url: string | URL | Request, init: RequestInit = {}) => { + const body = JSON.parse(String(init.body)); + capturedBodies.push(body); + if (capturedBodies.length === 1) { + return new Response(BODY_400_ULTRA, { + status: 400, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + + try { + const result = await executor.execute({ + model: "x-preview-f-free-2", + body: { reasoning_effort: "ultra" }, + stream: false, + credentials: {}, + }); + assert.equal(capturedBodies.length, 2); + assert.equal(capturedBodies[0].reasoning_effort, "ultra"); + assert.equal(capturedBodies[1].reasoning_effort, "medium"); + assert.equal(result.response.status, 200); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("no-op clamp does not retry: learned {high,max} with low request stays single-fetch", async () => { + const executor = new SimpleExecutor(); + const originalFetch = globalThis.fetch; + const capturedBodies: Record[] = []; + const BODY_400_HIGH_MAX = JSON.stringify({ + error: { message: "please use high, or max" }, + }); + + globalThis.fetch = async (_url: string | URL | Request, init: RequestInit = {}) => { + const body = JSON.parse(String(init.body)); + capturedBodies.push(body); + if (capturedBodies.length === 1) { + return new Response(BODY_400_HIGH_MAX, { + status: 400, + headers: { "Content-Type": "application/json" }, + }); + } + return new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }; + + try { + // low is below the learned minimum {high,max}: downgrade-only passthrough, + // sanitizer leaves the body unchanged -> no identical-body retry. + const result = await executor.execute({ + model: "x-preview-f-free-3", + body: { reasoning_effort: "low" }, + stream: false, + credentials: {}, + }); + assert.equal(capturedBodies.length, 1); + assert.equal(capturedBodies[0].reasoning_effort, "low"); + assert.equal(result.response.status, 400); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/tests/unit/reasoning-effort-learned-capability.test.ts b/tests/unit/reasoning-effort-learned-capability.test.ts index 210a451341..9dab460d8f 100644 --- a/tests/unit/reasoning-effort-learned-capability.test.ts +++ b/tests/unit/reasoning-effort-learned-capability.test.ts @@ -89,3 +89,67 @@ test("deepseek's non-ordinal max<->xhigh translation is untouched by the learned // deepseek's special case returns early — xhigh -> max, never reaches the catch-all. assert.equal(result.reasoning_effort, "max"); }); + +test("proactive clamp: medium→low for learned {low,high,max}", () => { + recordLearnedReasoningEffort("opencode-zen-direct", "x-preview-f-free", ["low", "high", "max"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "medium", model: "x-preview-f-free" }, + "opencode-zen-direct", + "x-preview-f-free" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "low"); +}); +test("proactive clamp: xhigh→high for learned {low,high,max}", () => { + recordLearnedReasoningEffort("opencode-zen-direct", "x-preview-f-free-2", ["low", "high", "max"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "xhigh", model: "x-preview-f-free-2" }, + "opencode-zen-direct", + "x-preview-f-free-2" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "high"); +}); +test("proactive clamp: ultra→max for learned {low,high,max}", () => { + recordLearnedReasoningEffort("opencode-zen-direct", "x-preview-f-free-3", ["low", "high", "max"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "ultra", model: "x-preview-f-free-3" }, + "opencode-zen-direct", + "x-preview-f-free-3" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "max"); +}); +test("proactive clamp: ultra→medium for learned {low,medium}", () => { + recordLearnedReasoningEffort("acme", "m", ["low", "medium"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "ultra", model: "m" }, + "acme", + "m" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "medium"); +}); +test("proactive clamp: high→medium for learned {low,medium}", () => { + recordLearnedReasoningEffort("acme", "m2", ["low", "medium"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "high", model: "m2" }, + "acme", + "m2" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "medium"); +}); +test("no upgrade: low stays low for learned {high,max}", () => { + recordLearnedReasoningEffort("acme", "m3", ["high", "max"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "low", model: "m3" }, + "acme", + "m3" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "low"); +}); +test("custom model ultra→medium for learned {low,medium}", () => { + recordLearnedReasoningEffort("openai-compatible-chat-eaff6869", "qwen3-coder-30b-a3b-instruct-2", ["low", "medium"]); + const out = sanitizeReasoningEffortForProvider( + { reasoning_effort: "ultra", model: "qwen3-coder-30b-a3b-instruct-2" }, + "openai-compatible-chat-eaff6869", + "qwen3-coder-30b-a3b-instruct-2" + ) as { reasoning_effort: string }; + assert.equal(out.reasoning_effort, "medium"); +}); From a054ac408fb6573f8bb1cea752ebfb1f9179b06b Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:21:55 +0200 Subject: [PATCH 04/20] feat(providers): allow per-connection maxWaitMs rate-limit override (#11251) Validated on the combined 12-PR batch board: focused node:test suites green (incl. provider-rate-limit-overrides-schema + ratelimit-admission-control-6593 isolated 11/11 + the new EditConnectionModal vitest 3/3), typecheck:core clean, all static gates within baseline. Pushed one follow-up commit to your branch with the real Vietnamese translations for the two new keys (repo convention: vi never ships __MISSING__ placeholders). Per-connection maxWaitMs override lands, folding the zai-web exception into the general mechanism. Thank you @maxmad64bis! --- .../11251-connection-max-wait-ms-override.md | 1 + open-sse/services/rateLimitManager.ts | 28 ++- .../components/modals/EditConnectionModal.tsx | 15 ++ src/i18n/messages/ar.json | 2 + src/i18n/messages/az.json | 2 + src/i18n/messages/bg.json | 2 + src/i18n/messages/bn.json | 2 + src/i18n/messages/cs.json | 2 + src/i18n/messages/da.json | 2 + src/i18n/messages/de.json | 2 + src/i18n/messages/en.json | 2 + src/i18n/messages/es.json | 2 + src/i18n/messages/fa.json | 2 + src/i18n/messages/fi.json | 2 + src/i18n/messages/fr.json | 2 + src/i18n/messages/gu.json | 2 + src/i18n/messages/he.json | 2 + src/i18n/messages/hi.json | 2 + src/i18n/messages/hu.json | 2 + src/i18n/messages/id.json | 2 + src/i18n/messages/in.json | 2 + src/i18n/messages/it.json | 2 + src/i18n/messages/ja.json | 2 + src/i18n/messages/ko.json | 2 + src/i18n/messages/mr.json | 2 + src/i18n/messages/ms.json | 2 + src/i18n/messages/nl.json | 2 + src/i18n/messages/no.json | 2 + src/i18n/messages/phi.json | 2 + src/i18n/messages/pl.json | 2 + src/i18n/messages/pt-BR.json | 2 + src/i18n/messages/pt.json | 2 + src/i18n/messages/ro.json | 2 + src/i18n/messages/ru.json | 2 + src/i18n/messages/sk.json | 2 + src/i18n/messages/sv.json | 2 + src/i18n/messages/sw.json | 2 + src/i18n/messages/ta.json | 2 + src/i18n/messages/te.json | 2 + src/i18n/messages/th.json | 2 + src/i18n/messages/tr.json | 2 + src/i18n/messages/uk-UA.json | 2 + src/i18n/messages/ur.json | 2 + src/i18n/messages/vi.json | 2 + src/i18n/messages/zh-CN.json | 2 + src/i18n/messages/zh-TW.json | 2 + src/lib/db/providers/columns.ts | 2 +- src/shared/validation/schemas/provider.ts | 20 +-- tests/unit/columns-validation.test.ts | 10 ++ ...ection-modal-max-wait-ms-override.test.tsx | 164 ++++++++++++++++++ ...ovider-rate-limit-overrides-schema.test.ts | 47 ++++- .../ratelimit-admission-control-6593.test.ts | 42 +++++ 52 files changed, 393 insertions(+), 22 deletions(-) create mode 100644 changelog.d/features/11251-connection-max-wait-ms-override.md create mode 100644 tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx diff --git a/changelog.d/features/11251-connection-max-wait-ms-override.md b/changelog.d/features/11251-connection-max-wait-ms-override.md new file mode 100644 index 0000000000..25ac42dd98 --- /dev/null +++ b/changelog.d/features/11251-connection-max-wait-ms-override.md @@ -0,0 +1 @@ +- **feat(providers):** allow overriding the rate-limit queue wait timeout (`maxWaitMs`) per connection, alongside the existing `rpm`/`tpm`/`tpd`/`minTime`/`maxConcurrent` overrides — a single slow provider no longer has to lower the global wait budget for every other provider (#11251) diff --git a/open-sse/services/rateLimitManager.ts b/open-sse/services/rateLimitManager.ts index 18815b32a5..bd793ae1ca 100644 --- a/open-sse/services/rateLimitManager.ts +++ b/open-sse/services/rateLimitManager.ts @@ -140,28 +140,40 @@ function isAutoEnableActive(settings: RequestQueueSettings): boolean { const EFFECTIVELY_INFINITE = Number.MAX_SAFE_INTEGER; const EFFECTIVELY_INFINITE_CONCURRENCY = 1000; +// Shared override-resolution rule for every per-connection rate-limit field: +// a positive override wins, 0 or missing falls through to `fallback`. +function resolveOverride(override: number | undefined | null, fallback: number): number { + return typeof override === "number" && override > 0 ? override : fallback; +} + // Resolve an RPM override. 0 or missing means "infinite" (no rate cap). function resolveRpm(override: number | undefined | null): number { - return typeof override === "number" && override > 0 ? override : EFFECTIVELY_INFINITE; + return resolveOverride(override, EFFECTIVELY_INFINITE); } // Resolve a minTime override. 0 or missing means "no minimum gap". function resolveMinTime(override: number | undefined | null): number { - return typeof override === "number" && override > 0 ? override : 0; + return resolveOverride(override, 0); } // Resolve a maxConcurrent override. 0 or missing means "effectively infinite". function resolveMaxConcurrent(override: number | undefined | null): number { - return typeof override === "number" && override > 0 ? override : EFFECTIVELY_INFINITE_CONCURRENCY; + return resolveOverride(override, EFFECTIVELY_INFINITE_CONCURRENCY); } export function resolveRequestQueueMaxWaitMs( provider: string, - configuredMaxWaitMs: number = currentRequestQueueSettings.maxWaitMs + configuredMaxWaitMs: number = currentRequestQueueSettings.maxWaitMs, + connectionId?: string ): number { - return provider.trim().toLowerCase() === "zai-web" - ? Math.max(configuredMaxWaitMs, ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS) - : configuredMaxWaitMs; + const legacyDefault = + provider.trim().toLowerCase() === "zai-web" + ? Math.max(configuredMaxWaitMs, ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS) + : configuredMaxWaitMs; + const override = connectionId + ? connectionRateLimitOverrides.get(connectionId)?.maxWaitMs + : undefined; + return resolveOverride(override, legacyDefault); } function buildLimiterDefaults() { @@ -546,7 +558,7 @@ export async function withRateLimit(provider, connectionId, model, fn, signal = // Proactive sliding-window fallback for header-less providers with a declared cap // (Fase 8.2). No-op unless PROVIDER_DEFAULT_RATE_LIMITS has an entry for `provider`. - const maxWaitMs = resolveRequestQueueMaxWaitMs(provider); + const maxWaitMs = resolveRequestQueueMaxWaitMs(provider, undefined, connectionId); await awaitProviderDefaultSlot(provider, connectionId, signal, maxWaitMs); const limiter = getLimiter(provider, connectionId, model); diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx index 838b71788a..e2eb3288e2 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx @@ -108,6 +108,7 @@ export default function EditConnectionModal({ tpm: "", tpd: "", minTime: "", + maxWaitMs: "", rateLimitMaxConcurrent: "", apiKey: "", healthCheckInterval: 60, @@ -312,6 +313,10 @@ export default function EditConnectionModal({ connection.rateLimitOverrides?.minTime != null ? String(connection.rateLimitOverrides.minTime) : "", + maxWaitMs: + connection.rateLimitOverrides?.maxWaitMs != null + ? String(connection.rateLimitOverrides.maxWaitMs) + : "", rateLimitMaxConcurrent: connection.rateLimitOverrides?.maxConcurrent != null ? String(connection.rateLimitOverrides.maxConcurrent) @@ -528,6 +533,7 @@ export default function EditConnectionModal({ if (formData.tpm.trim()) overrides.tpm = Number(formData.tpm); if (formData.tpd.trim()) overrides.tpd = Number(formData.tpd); if (formData.minTime.trim()) overrides.minTime = Number(formData.minTime); + if (formData.maxWaitMs.trim()) overrides.maxWaitMs = Number(formData.maxWaitMs); if (formData.rateLimitMaxConcurrent.trim()) overrides.maxConcurrent = Number(formData.rateLimitMaxConcurrent); updates.rateLimitOverrides = Object.keys(overrides).length > 0 ? overrides : null; @@ -1224,6 +1230,15 @@ export default function EditConnectionModal({ placeholder={t("inherit")} hint={t("rateLimitOverridesMinTimeHint")} /> + setFormData({ ...formData, maxWaitMs: e.target.value })} + placeholder={t("inherit")} + hint={t("rateLimitOverridesMaxWaitMsHint")} + /> = {}; for (const [key, v] of Object.entries(value as Record)) { diff --git a/src/shared/validation/schemas/provider.ts b/src/shared/validation/schemas/provider.ts index 68b3b40beb..f92cf78188 100644 --- a/src/shared/validation/schemas/provider.ts +++ b/src/shared/validation/schemas/provider.ts @@ -426,17 +426,14 @@ export const providerNodeValidateSchema = z.object({ // an empty/non-numeric string fails validation (surfaced as a 400), while still // coercing legit numeric strings like "60". function rateLimitOverrideNumber(max: number) { - return z.preprocess( - (raw) => { - if (typeof raw === "string") { - if (raw.trim() === "") return NaN; - const parsed = Number(raw); - return Number.isNaN(parsed) ? raw : parsed; - } - return raw; - }, - z.coerce.number().int().min(0).max(max) - ); + return z.preprocess((raw) => { + if (typeof raw === "string") { + if (raw.trim() === "") return NaN; + const parsed = Number(raw); + return Number.isNaN(parsed) ? raw : parsed; + } + return raw; + }, z.coerce.number().int().min(0).max(max)); } export const updateProviderConnectionSchema = z @@ -501,6 +498,7 @@ export const updateProviderConnectionSchema = z tpd: rateLimitOverrideNumber(10_000_000_000).optional(), minTime: rateLimitOverrideNumber(60_000).optional(), maxConcurrent: rateLimitOverrideNumber(10_000).optional(), + maxWaitMs: rateLimitOverrideNumber(120_000).optional(), }) .partial() .strict() diff --git a/tests/unit/columns-validation.test.ts b/tests/unit/columns-validation.test.ts index 41f70cddeb..64f84b22fa 100644 --- a/tests/unit/columns-validation.test.ts +++ b/tests/unit/columns-validation.test.ts @@ -22,3 +22,13 @@ test("valid input yields no rejected keys", () => { assert.deepEqual(r.rejected, []); assert.deepEqual(r.sanitized, { rpm: 10, tpm: 20 }); }); + +// #11251 added `maxWaitMs` to the Zod validation schema and the +// EditConnectionModal UI, but not to this separate allowlist — saving the +// field from the dashboard threw "Refusing to persist rateLimitOverrides +// with rejected keys: maxWaitMs" (500) on every attempt. +test("sanitizeRateLimitOverrides accepts maxWaitMs (#11251 follow-up)", () => { + const r = sanitizeRateLimitOverrides({ minTime: 500, maxWaitMs: 30000 }); + assert.deepEqual(r.rejected, []); + assert.deepEqual(r.sanitized, { minTime: 500, maxWaitMs: 30000 }); +}); diff --git a/tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx b/tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx new file mode 100644 index 0000000000..4f8638bcea --- /dev/null +++ b/tests/unit/dashboard/edit-connection-modal-max-wait-ms-override.test.tsx @@ -0,0 +1,164 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/store/notificationStore", () => ({ + useNotificationStore: () => ({ notify: vi.fn() }), +})); + +vi.mock("@/store/emailPrivacyStore", () => ({ + default: () => ({ hidden: false, toggle: vi.fn() }), +})); + +// Expanding "Advanced settings" also mounts ProviderTierField (#7818), which +// fetches its current tier override on mount independent of this modal's own +// save flow. Mock it out — its network call is unrelated to maxWaitMs. +vi.mock( + "@/app/(dashboard)/dashboard/providers/[id]/components/modals/providerTierFieldApi", + () => ({ + fetchProviderTierOverride: vi.fn().mockResolvedValue(""), + saveProviderTierOverride: vi.fn().mockResolvedValue(undefined), + }) +); + +const { default: EditConnectionModal } = + await import("../../../src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx"); + +let container: HTMLDivElement; +let root: Root; + +beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); +}); + +afterEach(() => { + act(() => root.unmount()); + container.remove(); + vi.clearAllMocks(); +}); + +function renderModal(connection: Record) { + act(() => { + root.render( + + ); + }); +} + +function expandAdvancedSettings() { + // The "Rate Limit Overrides" section (like the rest of the advanced fields) + // is collapsed by default behind the "Advanced settings" disclosure toggle. + const toggle = container.querySelector( + 'button[aria-controls="edit-connection-advanced-settings"]' + ) as HTMLButtonElement | null; + expect(toggle).not.toBeNull(); + act(() => { + toggle!.click(); + }); +} + +function findMaxWaitMsInput(): HTMLInputElement | null { + const label = Array.from(container.querySelectorAll("label")).find( + (el) => el.textContent === "rateLimitOverridesMaxWaitMsLabel" + ); + const forId = label?.getAttribute("for"); + return forId ? (container.querySelector(`#${forId}`) as HTMLInputElement | null) : null; +} + +function clickSave() { + const button = Array.from(container.querySelectorAll("button")).find( + (b) => b.textContent === "save" + ); + expect(button).toBeTruthy(); + button!.click(); +} + +describe("EditConnectionModal — maxWaitMs rate-limit override", () => { + it("renders an empty maxWaitMs field for a connection with no override", () => { + renderModal({ + id: "conn-1", + provider: "nvidia", + authType: "apikey", + name: "key", + rateLimitOverrides: { rpm: 30 }, + }); + expandAdvancedSettings(); + const input = findMaxWaitMsInput(); + expect(input).not.toBeNull(); + expect(input?.value).toBe(""); + }); + + it("preserves a persisted maxWaitMs override in form state", () => { + renderModal({ + id: "conn-2", + provider: "nvidia", + authType: "apikey", + name: "key", + rateLimitOverrides: { maxWaitMs: 45000 }, + }); + expandAdvancedSettings(); + const input = findMaxWaitMsInput(); + expect(input?.value).toBe("45000"); + }); + + it("submits the entered maxWaitMs as rateLimitOverrides.maxWaitMs", async () => { + // The "Rate Limit Overrides" section only renders for non-OAuth + // connections (`{!isOAuth && (...)}` wraps it, same gate as + // rpm/minTime/maxConcurrent). formData.apiKey stays "" (untouched by this + // test), so handleSubmit's `!isOAuth && formData.apiKey` validation-fetch + // branch is skipped and the save completes synchronously without mocking + // `fetch`. + const onSave = vi.fn().mockResolvedValue(undefined); + act(() => { + root.render( + + ); + }); + + expandAdvancedSettings(); + const input = findMaxWaitMsInput(); + expect(input).not.toBeNull(); + const setter = Object.getOwnPropertyDescriptor( + window.HTMLInputElement.prototype, + "value" + )!.set!; + await act(async () => { + setter.call(input, "45000"); + input!.dispatchEvent(new Event("input", { bubbles: true })); + }); + + await act(async () => { + clickSave(); + }); + + expect(onSave).toHaveBeenCalledTimes(1); + const updates = onSave.mock.calls[0][0] as { + rateLimitOverrides: Record | null; + }; + expect(updates.rateLimitOverrides?.maxWaitMs).toBe(45000); + }); +}); diff --git a/tests/unit/provider-rate-limit-overrides-schema.test.ts b/tests/unit/provider-rate-limit-overrides-schema.test.ts index 84bca5b7f9..279e3a76eb 100644 --- a/tests/unit/provider-rate-limit-overrides-schema.test.ts +++ b/tests/unit/provider-rate-limit-overrides-schema.test.ts @@ -11,9 +11,23 @@ function parse(overrides: unknown) { } test("rateLimitOverrides: valid object with all fields", () => { - const r = parse({ rpm: 100, tpm: 50000, tpd: 1000000, minTime: 100, maxConcurrent: 5 }); + const r = parse({ + rpm: 100, + tpm: 50000, + tpd: 1000000, + minTime: 100, + maxConcurrent: 5, + maxWaitMs: 45000, + }); assert.ok(r.success, String(r.error)); - assert.deepEqual(r.data.rateLimitOverrides, { rpm: 100, tpm: 50000, tpd: 1000000, minTime: 100, maxConcurrent: 5 }); + assert.deepEqual(r.data.rateLimitOverrides, { + rpm: 100, + tpm: 50000, + tpd: 1000000, + minTime: 100, + maxConcurrent: 5, + maxWaitMs: 45000, + }); }); test("rateLimitOverrides: partial fields", () => { @@ -71,3 +85,32 @@ test("rateLimitOverrides: all zeros is valid", () => { const r = parse({ rpm: 0, tpm: 0, tpd: 0, minTime: 0, maxConcurrent: 0 }); assert.ok(r.success, String(r.error)); }); + +test("rateLimitOverrides: valid maxWaitMs", () => { + const r = parse({ maxWaitMs: 45000 }); + assert.ok(r.success, String(r.error)); + assert.deepEqual(r.data.rateLimitOverrides, { maxWaitMs: 45000 }); +}); + +test("rateLimitOverrides: maxWaitMs coerced from string", () => { + const r = parse({ maxWaitMs: "30000" }); + assert.ok(r.success, String(r.error)); + assert.equal(r.data.rateLimitOverrides.maxWaitMs, 30000); +}); + +test("rateLimitOverrides: rejects negative maxWaitMs", () => { + assert.equal(parse({ maxWaitMs: -1 }).success, false); +}); + +test("rateLimitOverrides: rejects float maxWaitMs", () => { + assert.equal(parse({ maxWaitMs: 1.5 }).success, false); +}); + +test("rateLimitOverrides: rejects maxWaitMs above 120000 ceiling", () => { + assert.equal(parse({ maxWaitMs: 120001 }).success, false); +}); + +test("rateLimitOverrides: maxWaitMs of 0 is valid (no override)", () => { + const r = parse({ maxWaitMs: 0 }); + assert.ok(r.success, String(r.error)); +}); diff --git a/tests/unit/ratelimit-admission-control-6593.test.ts b/tests/unit/ratelimit-admission-control-6593.test.ts index 80d3af4007..04f3dd9fe1 100644 --- a/tests/unit/ratelimit-admission-control-6593.test.ts +++ b/tests/unit/ratelimit-admission-control-6593.test.ts @@ -189,6 +189,48 @@ test("#6593 zai-web receives a provider-scoped 60s scheduling budget", () => { assert.equal(rateLimitManager.resolveRequestQueueMaxWaitMs("ZAI-WEB", 90_000), 90_000); }); +test("#6593 connection maxWaitMs override takes priority over the zai-web scheduling budget", () => { + rateLimitManager.refreshConnectionRateLimits("conn-maxwait-override", { maxWaitMs: 45_000 }); + try { + // Non-special provider: override wins over the passed-in configured default. + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("openai", 15_000, "conn-maxwait-override"), + 45_000 + ); + // zai-web: override wins over its hardcoded 60s floor too. + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("zai-web", 15_000, "conn-maxwait-override"), + 45_000 + ); + } finally { + rateLimitManager.refreshConnectionRateLimits("conn-maxwait-override", null); + } +}); + +test("#6593 a connection without a maxWaitMs override keeps the zai-web 60s floor", () => { + rateLimitManager.refreshConnectionRateLimits("conn-no-maxwait-override", { rpm: 10 }); + try { + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("zai-web", 15_000, "conn-no-maxwait-override"), + rateLimitManager.ZAI_WEB_REQUEST_QUEUE_MAX_WAIT_MS + ); + } finally { + rateLimitManager.refreshConnectionRateLimits("conn-no-maxwait-override", null); + } +}); + +test("#6593 a maxWaitMs override of 0 is treated as no override", () => { + rateLimitManager.refreshConnectionRateLimits("conn-zero-maxwait-override", { maxWaitMs: 0 }); + try { + assert.equal( + rateLimitManager.resolveRequestQueueMaxWaitMs("openai", 15_000, "conn-zero-maxwait-override"), + 15_000 + ); + } finally { + rateLimitManager.refreshConnectionRateLimits("conn-zero-maxwait-override", null); + } +}); + test("#6593 DEFAULT_REQUEST_QUEUE_MAX_DEPTH defaults to 0 (disabled) absent an env override", () => { assert.equal(process.env.RATE_LIMIT_MAX_QUEUE_DEPTH, undefined); assert.equal(resilienceSettings.DEFAULT_REQUEST_QUEUE_MAX_DEPTH, 0); From 34463f6b366698b09e43a6630aafcf56bc416690 Mon Sep 17 00:00:00 2001 From: Natizh <120684204+Natizh@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:23:36 +0200 Subject: [PATCH 05/20] docs(i18n): restore Italian README translation (#11246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined 12-PR batch board: docs gates (check:docs-all incl. doc-links + fabricated-docs strict) all pass. Italian README restored to a real translation and re-synced with the canonical root README. Thank you @Natizh — and thank you for the sharp analysis of the translation-pipeline skip logic; worth a follow-up issue. --- docs/i18n/it/README.md | 3328 ++++++++++++++++------------------------ 1 file changed, 1318 insertions(+), 2010 deletions(-) diff --git a/docs/i18n/it/README.md b/docs/i18n/it/README.md index dae91d3f96..cbb7f49c4c 100644 --- a/docs/i18n/it/README.md +++ b/docs/i18n/it/README.md @@ -1,2204 +1,1421 @@ -# 🚀 OmniRoute — The Free AI Gateway (Italiano) +# 🚀 OmniRoute — Il Gateway AI Gratuito -🌐 **Languages:** 🇺🇸 [English](../../../README.md) · 🇸🇦 [ar](../ar/README.md) · 🇧🇬 [bg](../bg/README.md) · 🇧🇩 [bn](../bn/README.md) · 🇨🇿 [cs](../cs/README.md) · 🇩🇰 [da](../da/README.md) · 🇩🇪 [de](../de/README.md) · 🇪🇸 [es](../es/README.md) · 🇮🇷 [fa](../fa/README.md) · 🇫🇮 [fi](../fi/README.md) · 🇫🇷 [fr](../fr/README.md) · 🇮🇳 [gu](../gu/README.md) · 🇮🇱 [he](../he/README.md) · 🇮🇳 [hi](../hi/README.md) · 🇭🇺 [hu](../hu/README.md) · 🇮🇩 [id](../id/README.md) · 🇮🇹 [it](../it/README.md) · 🇯🇵 [ja](../ja/README.md) · 🇰🇷 [ko](../ko/README.md) · 🇮🇳 [mr](../mr/README.md) · 🇲🇾 [ms](../ms/README.md) · 🇳🇱 [nl](../nl/README.md) · 🇳🇴 [no](../no/README.md) · 🇵🇭 [phi](../phi/README.md) · 🇵🇱 [pl](../pl/README.md) · 🇵🇹 [pt](../pt/README.md) · 🇧🇷 [pt-BR](../pt-BR/README.md) · 🇷🇴 [ro](../ro/README.md) · 🇷🇺 [ru](../ru/README.md) · 🇸🇰 [sk](../sk/README.md) · 🇸🇪 [sv](../sv/README.md) · 🇰🇪 [sw](../sw/README.md) · 🇮🇳 [ta](../ta/README.md) · 🇮🇳 [te](../te/README.md) · 🇹🇭 [th](../th/README.md) · 🇹🇷 [tr](../tr/README.md) · 🇺🇦 [uk-UA](../uk-UA/README.md) · 🇵🇰 [ur](../ur/README.md) · 🇻🇳 [vi](../vi/README.md) · 🇨🇳 [zh-CN](../zh-CN/README.md) - ---- - -### Keep coding through provider limits. Smart routing to free-access and low-cost AI models with automatic fallback. - -_Your universal API proxy — one endpoint, 329 provider catalog entries, resilient fallback subject to upstream availability. Includes **MCP Server (107 tools, 32 scopes)**, **A2A Protocol**, **Memory/Skills Systems** & **Electron Desktop App**._ - -**Chat Completions • Embeddings • Image Generation • Video • Music • Audio • Reranking • **Web Search** • MCP Server • A2A Protocol • 100% TypeScript** +🌐 **Lingue:** 🇺🇸 [English](../../../README.md) · 🇸🇦 [ar](../ar/README.md) · 🇦🇿 [az](../az/README.md) · 🇧🇬 [bg](../bg/README.md) · 🇧🇩 [bn](../bn/README.md) · 🇨🇿 [cs](../cs/README.md) · 🇩🇰 [da](../da/README.md) · 🇩🇪 [de](../de/README.md) · 🇪🇸 [es](../es/README.md) · 🇮🇷 [fa](../fa/README.md) · 🇫🇮 [fi](../fi/README.md) · 🇫🇷 [fr](../fr/README.md) · 🇮🇳 [gu](../gu/README.md) · 🇮🇱 [he](../he/README.md) · 🇮🇳 [hi](../hi/README.md) · 🇭🇺 [hu](../hu/README.md) · 🇮🇩 [id](../id/README.md) · 🇮🇩 [in](../in/README.md) · 🇮🇹 [it](../it/README.md) · 🇯🇵 [ja](../ja/README.md) · 🇰🇷 [ko](../ko/README.md) · 🇮🇳 [mr](../mr/README.md) · 🇲🇾 [ms](../ms/README.md) · 🇳🇱 [nl](../nl/README.md) · 🇳🇴 [no](../no/README.md) · 🇵🇭 [phi](../phi/README.md) · 🇵🇱 [pl](../pl/README.md) · 🇵🇹 [pt](../pt/README.md) · 🇧🇷 [pt-BR](../pt-BR/README.md) · 🇷🇴 [ro](../ro/README.md) · 🇷🇺 [ru](../ru/README.md) · 🇸🇰 [sk](../sk/README.md) · 🇸🇪 [sv](../sv/README.md) · 🇰🇪 [sw](../sw/README.md) · 🇮🇳 [ta](../ta/README.md) · 🇮🇳 [te](../te/README.md) · 🇹🇭 [th](../th/README.md) · 🇹🇷 [tr](../tr/README.md) · 🇺🇦 [uk-UA](../uk-UA/README.md) · 🇵🇰 [ur](../ur/README.md) · 🇻🇳 [vi](../vi/README.md) · 🇨🇳 [zh-CN](../zh-CN/README.md) · 🇹🇼 [zh-TW](../zh-TW/README.md) ---
+Dashboard di OmniRoute + +
+
+ + +# 🚀 OmniRoute — Il Gateway AI Gratuito + +OmniRoute — Non smettere mai di programmare. Ogni strumento AI → 349 provider — oltre 90 gratuiti — tramite un unico endpoint. Collega Claude Code, Codex, Cursor, Cline, Copilot e Antigravity a Claude / GPT / Gemini GRATUITI con fallback automatico. La compressione combinata RTK + Caveman riduce i token del 15–95% (~89% in media) — per non raggiungere i limiti. 350 provider AI · oltre 90 tier gratuiti · ~1,51 miliardi di token gratuiti/mese · 19 strategie di routing · $0 per iniziare. + +
+ +
+ +## 💰 ~1,51 miliardi di token gratuiti / mese + +
+ +> Combinare manualmente i tier gratuiti è scomodo: decine di SDK, decine di rate limit e nessuna idea chiara di quanta capacità sia davvero disponibile. OmniRoute aggrega i tier gratuiti **documentati** di **42 pool di provider / 495 modelli** in un unico numero trasparente e lo mostra in tempo reale nella dashboard (`/dashboard/free-tiers`). + +Scheda del budget dei tier gratuiti di OmniRoute: ~1,51 miliardi di token gratuiti al mese in modo continuativo, fino a ~2,13 miliardi nel primo mese includendo i crediti di registrazione, calcolati sui tier gratuiti documentati di 42 pool di provider / 495 modelli dietro un unico endpoint. Calcolo trasparente con deduplicazione dei pool: ogni pool condiviso viene conteggiato una sola volta (contare ogni rate limit 24/7 darebbe ~10 miliardi, dato non pubblicato); 15 provider sono segnalati per i ToS così puoi decidere. Barra del budget dei pool gratuiti conteggiabili con griglia per modello, crediti una tantum del primo mese e provider permanentemente gratuiti senza limite di token pubblicato, mostrati separatamente per non gonfiare il valore principale. Utilizzo e residuo in tempo reale su /dashboard/free-tiers. + +> Riepilogo animato della pagina live `/dashboard/free-tiers`. Metodologia completa (deduplicazione dei pool, tier di credito, condizioni dei provider): **[docs/reference/FREE_TIERS.md](../../reference/FREE_TIERS.md)**. +> +> Questi valori vengono ricontrollati ogni due settimane rispetto al catalogo live e **possono sia salire sia scendere**: se un provider termina un tier gratuito, il numero diminuisce; se ne arriva uno nuovo, aumenta. Pubblichiamo ciò che il catalogo calcola realmente, mai una stima ottimistica arrotondata verso l'alto. + +
+ +
+ +

+ +⭐ Metti una stella alla repo se OMNIROUTE ti ha aiutato a risparmiare e a lavorare meglio. + +

+ +[![Stars](https://img.shields.io/github/stars/diegosouzapw/OmniRoute?style=social)](https://github.com/diegosouzapw/OmniRoute) +diegosouzapw%2FOmniRoute | Trendshift +[![Star History Rank](https://api.star-history.com/badge?repo=diegosouzapw/OmniRoute&theme=dark)](https://www.star-history.com/diegosouzapw/omniroute) +[![olud.ai](https://olud.ai/badge.php?tool=diegosouzapw-omniroute)](https://olud.ai/project/diegosouzapw-omniroute.html) + +### 💬 Unisciti alla community + +**👋 Segui il maintainer — scopri per primo nuovi provider, release e suggerimenti:** + +[![Follow Diego on LinkedIn](https://img.shields.io/badge/Follow_Diego_on-LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/diegosouzapw/) +[![Follow @diegosouzapw on GitHub](https://img.shields.io/github/followers/diegosouzapw?style=for-the-badge&logo=github&logoColor=white&label=Follow%20on%20GitHub&color=181717)](https://github.com/diegosouzapw) + +[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/U47eFqAXCn) +[![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/omnirouteOficial) +[![WhatsApp Global](https://img.shields.io/badge/WhatsApp_Global-25D366?style=for-the-badge&logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) +[![WhatsApp Brasil](https://img.shields.io/badge/WhatsApp_Brasil-25D366?style=for-the-badge&logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz) +[![Website](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online) + +**Domande, suggerimenti sui provider, roadmap e supporto → [Discord](https://discord.gg/U47eFqAXCn) · [Telegram](https://t.me/omnirouteOficial) · WhatsApp [🌍 Global](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) / [🇧🇷 Brasil](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz)** + +
+ +## 📈 Il gateway continua a crescere + +
+ +| | v3.8.49 | **v3.8.50** | `v3.8.51+` | +| ------------------------- | :-----: | :---------: | :---------------: | +| 🌐 Provider | 290 | **342** | altri in arrivo | +| 🧠 Modelli documentati | 1185 | **1202** | — | +| 🖼️ Modality Bridge | — | 🆕 vision | video | +| 📡 Catalogo gratuito Radar| — | 🆕 opt-in | — | +| ⚖️ Scheduling quota-aware | — | — | 🔭 prossimamente| +| 📊 Telemetria delle quote | — | — | 🔭 prossimamente| + +**→ [Roadmap](../../../ROADMAP.md) — verso `v3.9.0 LTS`** + +
+ +
+ +## 🧩 Disponibile come + [![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute) +![NPM Monthly](https://img.shields.io/npm/dm/omniroute?label=npm/month&color=cb3837&logo=npm) [![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../../LICENSE) +![Docker Pulls](https://img.shields.io/docker/pulls/diegosouzapw/omniroute?label=docker%20pulls&logo=docker&color=2496ED) +![Electron Downloads](https://img.shields.io/github/downloads/diegosouzapw/omniroute/total?style=flat&label=electron%20downloads&logo=electron&color=47848F) -![NPM Downloads](https://img.shields.io/npm/dw/omniroute?label=npm%20down%20week&color=red) -![NPM Downloads](https://img.shields.io/npm/dm/omniroute?label=npm%20down%20month&color=red) - -![NPM Downloads](https://img.shields.io/npm/d18m/omniroute?label=npm%20down%20year&color=red) -![Docker Pulls](https://img.shields.io/docker/pulls/diegosouzapw/omniroute) -![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/diegosouzapw/omniroute/total?style=flat&label=eletron%20donwloads&color=blue) - -[![stars](https://custom-icon-badges.demolab.com/github/stars/diegosouzapw/OmniRoute?logo=star&style=flat)](https://github.com/diegosouzapw/OmniRoute/stargazers) -[![open issues](https://custom-icon-badges.demolab.com/github/issues-raw/diegosouzapw/OmniRoute?logo=issue)](https://github.com/diegosouzapw/OmniRoute/issues) -[![license](https://custom-icon-badges.demolab.com/github/license/diegosouzapw/OmniRoute?logo=law)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE) -[![last commit](https://custom-icon-badges.demolab.com/github/last-commit/diegosouzapw/OmniRoute?logo=history&logoColor=white)](https://github.com/diegosouzapw/OmniRoute/commits/main) -[![total contributions](https://custom-icon-badges.demolab.com/badge/dynamic/json?logo=graph&logoColor=fff&color=blue&label=total%20contributions&query=%24.totalContributions&url=https%3A%2F%2Fstreak-stats.demolab.com%2F%3Fuser%3Ddiegosouzapw%26type%3Djson)](https://github.com/diegosouzapw) -[![code size](https://custom-icon-badges.demolab.com/github/languages/code-size/diegosouzapw/OmniRoute?logo=file-code&logoColor=white)](https://github.com/diegosouzapw/OmniRoute) -[![pr closed](https://custom-icon-badges.demolab.com/github/issues-pr-closed/diegosouzapw/OmniRoute?color=purple&logo=git-pull-request&logoColor=white)](https://github.com/diegosouzapw/OmniRoute/pulls?q=is%3Apr+is%3Aclosed) -[![tag](https://custom-icon-badges.demolab.com/github/v/tag/diegosouzapw/OmniRoute?logo=tag&logoColor=white)](https://github.com/diegosouzapw/OmniRoute/tags) -[![github streak](https://custom-icon-badges.demolab.com/badge/dynamic/json?logo=fire&logoColor=fff&color=orange&label=github%20streak&query=%24.currentStreak.length&suffix=%20days&url=https%3A%2F%2Fstreak-stats.demolab.com%2F%3Fuser%3Ddiegosouzapw%26type%3Djson)](https://github.com/diegosouzapw) -[![followers](https://custom-icon-badges.demolab.com/github/followers/diegosouzapw?logo=person-add)](https://github.com/diegosouzapw?tab=followers) -[![fork](https://custom-icon-badges.demolab.com/github/forks/diegosouzapw/OmniRoute?logo=fork)](https://github.com/diegosouzapw/OmniRoute/network/members) -[![watch](https://custom-icon-badges.demolab.com/github/watchers/diegosouzapw/OmniRoute?logo=eye)](https://github.com/diegosouzapw/OmniRoute/watchers) - -[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE) -[![Website](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online) -[![WhatsApp](https://img.shields.io/badge/WhatsApp-Community-25D366?logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) - -[🌐 Website](https://omniroute.online) • [🚀 Quick Start](#-quick-start) • [💡 Features](#-key-features) • [📖 Docs](#-documentation) • [💰 Pricing](#-pricing-at-a-glance) • [💬 WhatsApp](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
🚀 Inizia🚀 Avvio rapido📦 Installazione🆓 Zero-config
💡 Scopri💥 La promessa🤔 Perché OmniRoute🏆 Cosa lo distingue
⚙️ Funzionalità🎯 Combo🌐 Provider🔌 CLI & MCP
🗜️ Compressione🖥️ Dove funziona🔒 Privacy
👀 Guarda🎬 In azione✨ Novità🤖 CLI compatibili
💚 Supporto💚 Supporta / Dona💬 Community💖 Sponsor
📦 Progetto🛠️ Stack tecnologico📖 Documentazione👥 Contributor
-🌐 **Available in:** 🇺🇸 [English](README.md) | 🇧🇷 [Português (Brasil)](docs/i18n/pt-BR/README.md) | 🇪🇸 [Español](docs/i18n/es/README.md) | 🇫🇷 [Français](docs/i18n/fr/README.md) | 🇮🇹 [Italiano](docs/i18n/it/README.md) | 🇷🇺 [Русский](docs/i18n/ru/README.md) | 🇨🇳 [中文 (简体)](docs/i18n/zh-CN/README.md) | 🇩🇪 [Deutsch](docs/i18n/de/README.md) | 🇮🇳 [हिन्दी](docs/i18n/in/README.md) | 🇹🇭 [ไทย](docs/i18n/th/README.md) | 🇺🇦 [Українська](docs/i18n/uk-UA/README.md) | 🇸🇦 [العربية](docs/i18n/ar/README.md) | 🇯🇵 [日本語](docs/i18n/ja/README.md) | 🇻🇳 [Tiếng Việt](docs/i18n/vi/README.md) | 🇧🇬 [Български](docs/i18n/bg/README.md) | 🇩🇰 [Dansk](docs/i18n/da/README.md) | 🇫🇮 [Suomi](docs/i18n/fi/README.md) | 🇮🇱 [עברית](docs/i18n/he/README.md) | 🇭🇺 [Magyar](docs/i18n/hu/README.md) | 🇮🇩 [Bahasa Indonesia](docs/i18n/id/README.md) | 🇰🇷 [한국어](docs/i18n/ko/README.md) | 🇲🇾 [Bahasa Melayu](docs/i18n/ms/README.md) | 🇳🇱 [Nederlands](docs/i18n/nl/README.md) | 🇳🇴 [Norsk](docs/i18n/no/README.md) | 🇵🇹 [Português (Portugal)](docs/i18n/pt/README.md) | 🇷🇴 [Română](docs/i18n/ro/README.md) | 🇵🇱 [Polski](docs/i18n/pl/README.md) | 🇸🇰 [Slovenčina](docs/i18n/sk/README.md) | 🇸🇪 [Svenska](docs/i18n/sv/README.md) | 🇵🇭 [Filipino](docs/i18n/phi/README.md) | 🇨🇿 [Čeština](docs/i18n/cs/README.md) - ---- - -## 🖼️ Main Dashboard -
- OmniRoute Dashboard + 🌐 In 43 lingue +

+ English (en) + Português — Brasil (pt-BR) + Português (pt) + Español (es) + Français (fr) + Italiano (it) + Deutsch (de) + Nederlands (nl) + Русский (ru) + Українська (uk-UA) + Polski (pl) + Čeština (cs) + Slovenčina (sk) + Română (ro) + Magyar (hu) + Български (bg) + Dansk (da) + Suomi (fi) + Norsk (no) + Svenska (sv) + 中文 — 简体 (zh-CN) + 中文 — 繁體 (zh-TW) + 日本語 (ja) + 한국어 (ko) + ไทย (th) + Tiếng Việt (vi) + Bahasa Indonesia (id) + Bahasa Melayu (ms) + Filipino (phi) + Bahasa Indonesia (Alt) (in) + हिन्दी (hi) + ગુજરાતી (gu) + मराठी (mr) + தமிழ் (ta) + తెలుగు (te) + বাংলা (bn) + اردو (ur) + فارسی (fa) + العربية (ar) + עברית (he) + Türkçe (tr) + Azərbaycan (az) + Kiswahili (sw)
---- +
+
-## 📸 Dashboard Preview +
-
-Click to see dashboard screenshots + +## 🆓 Funziona subito dopo l'installazione — nessuna chiave, nessuna configurazione -| Page | Screenshot | -| -------------- | ------------------------------------------------- | -| **Providers** | ![Providers](docs/screenshots/01-providers.png) | -| **Combos** | ![Combos](docs/screenshots/02-combos.png) | -| **Analytics** | ![Analytics](docs/screenshots/03-analytics.png) | -| **Health** | ![Health](docs/screenshots/04-health.png) | -| **Translator** | ![Translator](docs/screenshots/05-translator.png) | -| **Settings** | ![Settings](docs/screenshots/06-settings.png) | -| **CLI Tools** | ![CLI Tools](docs/screenshots/07-cli-tools.png) | -| **Usage Logs** | ![Usage](docs/screenshots/08-usage.png) | -| **Endpoints** | ![Endpoints](docs/screenshots/09-endpoint.png) | +
- - ---- - -### 🤖 Free AI Provider for your favorite coding agents - -_Connect any AI-powered IDE or CLI tool through OmniRoute — free-access AI gateway; provider limits and terms apply._ - - - - - - - - - - - - - - - -
- - OpenClaw
- OpenClaw -

- ⭐ 205K -
- - NanoBot
- NanoBot -

- ⭐ 20.9K -
- - PicoClaw
- PicoClaw -

- ⭐ 14.6K -
- - ZeroClaw
- ZeroClaw -

- ⭐ 9.9K -
- - IronClaw
- IronClaw -

- ⭐ 2.1K -
- - OpenCode
- OpenCode -

- ⭐ 106K -
- - Codex CLI
- Codex CLI -

- ⭐ 60.8K -
- - Claude Code
- Claude Code -

- ⭐ 67.3K -
- - Kilo Code
- Kilo Code -

- ⭐ 15.5K -
- -📡 All agents connect via http://localhost:20128/v1 or http://cloud.omniroute.online/v1 — one config; model access and quotas depend on providers - ---- - -## 🤔 Why OmniRoute? - -**Stop wasting money and hitting limits:** - -- Subscription quota expires unused every month -- Rate limits stop you mid-coding -- Expensive APIs ($20-50/month per provider) -- Manual switching between providers - -**OmniRoute solves this:** - -- ✅ **Maximize subscriptions** - Track quota, use every bit before reset -- ✅ **Auto fallback** - Subscription → API Key → Cheap → Free; availability depends on eligible upstream routes -- ✅ **Multi-account** - Round-robin between accounts per provider - ---- - -## 📧 Support - -> 💬 **Join our community!** [WhatsApp Group](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) — Get help, share tips, and stay updated. - -- **Website**: [omniroute.online](https://omniroute.online) -- **GitHub**: [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) -- **Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) -- **WhatsApp**: [Community Group](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) -- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md), open a PR, or pick a `good first issue` - -### 🐛 Reporting a Bug? - -When opening an issue, please run the system-info command and attach the generated file: +Funziona subito dopo l'installazione — configurazione zero. Tre passaggi: 1. Installa — npm i -g omniroute, il server parte su localhost:20128. 2. Punta il tuo strumento a http://localhost:20128/v1 — qualunque strumento compatibile con OpenAI (Claude Code, Cursor, Cline). 3. Risponde — usa il modello auto per una risposta immediata, senza API key, registrazione o configurazione. I provider gratuiti senza chiave OpenCode Free e Felo sono già collegati alla combo auto, quindi una nuova installazione risponde immediatamente. ```bash -npm run system-info +# Fresh install, zero credentials — `auto` already works: +curl http://localhost:20128/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{"model":"auto","messages":[{"role":"user","content":"Hello!"}]}' ``` -This generates a `system-info.txt` with your Node.js version, OmniRoute version, OS details, installed CLI tools (qoder, gemini, claude, codex, antigravity, droid, etc.), Docker/PM2 status, and system packages — everything we need to reproduce your issue quickly. Attach the file directly to your GitHub issue. +Preferisci uno specifico backend gratuito? Chiamalo direttamente, ad esempio `oc/…` (OpenCode Free) o `felo/…` (Felo). Poi passa a `auto` e lascia che sia OmniRoute a scegliere. ---- +📦 Script di avvio rapido pronti da copiare per **Python, Node.js, PHP e cURL** → [`examples/quickstart/`](../../../examples/quickstart/) -## 🔄 How It Works +
+ +
+ + +# 💥 La promessa + +
+ +La promessa — Un endpoint. 349 provider. Non smettere mai di creare: OmniRoute sceglie quello più economico che funziona. Sei pilastri: non raggiungere mai i limiti (fallback automatico tra 349 provider in millisecondi, zero downtime) · risparmia fino al 95% dei token (compressione combinata RTK + Caveman del 15–95%, ~89% in media nelle sessioni ricche di tool) · $0 per iniziare (oltre 90 tier gratuiti, 56 gratis per sempre, senza carta) · ogni strumento funziona (33 agenti di coding con una sola configurazione) · un endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API su /v1) · pronto per la produzione (circuit breaker, TLS stealth, MCP con 110 tool, A2A, memoria, guardrail, eval — oltre 25.000 test). + +
+
+ +
+ + +# 🤔 Perché OmniRoute? + +
+ +Perché OmniRoute — basta destreggiarsi tra 10 dashboard, API key non valide e fatture impreviste. Dieci problemi quotidiani e relative soluzioni: quota che scade inutilizzata → massimizza gli abbonamenti; rate limit durante il coding → fallback automatico a 4 livelli (Subscription → API → Cheap → Free); output dei tool che consumano token → compressione RTK + Caveman (15–95%); API costose → routing ottimizzato per i costi; ogni tool con una configurazione diversa → un endpoint, una dashboard; AI bloccata → proxy a 3 livelli + TLS stealth; chiavi non valide → resilienza a 3 livelli (circuit breaker, cooldown della chiave, lockout del modello); team che condivide un abbonamento → pool di chiavi con quote fair-share; prompt che passano dal cloud di altri → local-first con chiavi cifrate AES-256-GCM; nessuna visibilità sulla spesa → analytics live (utilizzo, quota, risparmio, latenza p95). + +
+ +Flusso delle richieste OmniRoute: il tuo IDE o CLI (Claude Code, Cursor, Cline…) chiama un unico endpoint locale (http://localhost:20128/v1); lo Smart Router di OmniRoute (compressione RTK + Caveman, 19 strategie di routing, circuit breaker, TLS stealth, MCP, A2A, guardrail) effettua automaticamente il fallback tra 4 livelli di provider — Tier 1 Subscription (Claude Code, Codex, Copilot), quota esaurita? Tier 2 API Key (DeepSeek, Groq, xAI), budget raggiunto? Tier 3 Cheap (GLM $0.5, MiniMax $0.2), budget raggiunto? Tier 4 Free (Kiro, Qoder, Pollinations) — sempre attivo. + +
+ +
+ +
+ +## 🤝 Supportato dai nostri amici dell'Open Source + +
+ +

+ + Kimi K3 — Open Frontier Intelligence · 2.8T parameters · 1M-token context + +

+ +> **Vuoi diventare un Open Source Friend?** Queste sono le aziende che sostengono l'open source e aiutano OmniRoute a continuare a crescere — e dichiariamo pubblicamente dove viene usato ogni token che ci forniscono. Contatto: [diegosouza.pw@outlook.com](mailto:diegosouza.pw@outlook.com) + + + + + + + + + + +
+ + + + Kimi (Moonshot AI) + + +
Kimi
Moonshot AI

+ Open Source Friend fondatore +
+ Grazie a Kimi (Moonshot AI), il nostro Open Source Friend fondatore, per il sostegno al progetto! Kimi è il laboratorio AI dietro le famiglie di modelli open-weight K2 e K3 — Kimi K3 offre una finestra di contesto da 1M token, vision nativa e capacità di coding di frontiera a una frazione del prezzo dei modelli chiusi, e funziona subito con Claude Code, Codex e ogni strumento di coding supportato da OmniRoute. +

+ Cosa rende possibile il supporto di Kimi: i crediti API di Kimi alimentano la pipeline di release validata dall'AI di OmniRoute — la fase merge validation powered by Kimi K3 che esamina ogni pull request prima del rilascio — oltre allo sviluppo quotidiano delle funzionalità. Il supporto Kimi di prima classe è disponibile su entrambi i canali: la Kimi API diretta (kimi-k3) e il piano di coding Kimi Code (OAuth e API key). OmniRoute è anche il primo progetto open source brasiliano nel programma di supporto di Kimi. Ottieni una Kimi API key con il 15% di crediti extra → +
+ + Cheaper Inference + +
Cheaper Inference
cheaperinference.com

+ Open Source Friend +
+ Grazie a Cheaper Inference, un Open Source Friend di OmniRoute, per il sostegno al progetto! Cheaper Inference è un gateway ordinato per costo che rivende 42 modelli di frontiera — Claude, GPT-5.x, Gemini, Kimi K3, GLM, DeepSeek, Grok e MiniMax — dietro un unico endpoint compatibile con OpenAI, instradando ogni richiesta verso il provider idoneo più economico senza mai addebitare più del prezzo di listino del produttore del modello. +

+ Supporto di prima classe in OmniRoute: Chat Completions, endpoint nativo /v1/responses, vision, tool calling e 3 modelli immagine (grok-imagine, nano-banana-pro, nano-banana-2, raggiungibili come cheaperinference/<model>). Ottieni una API key → +
+ +I link contrassegnati con aff=omniroute sono link partner. Finanziano il progetto senza costi aggiuntivi per te. + +
+ +
+🎟️ Promo affiliati — coupon gratuiti di registrazione da provider che non sponsorizziamo (clicca per espandere) + +Questa sezione contiene soltanto codici referral/coupon. Le partnership sponsorizzate sono riportate sopra in 🤝 Supportato dai nostri amici dell'Open Source. OmniRoute non ha sponsorizzazioni o partnership con i provider elencati qui: sono coupon pubblici utilizzabili da chiunque. + + + + + + +
+ + AgentRouter + +
AgentRouter
agentrouter.org +
+ AgentRouter — registrazione affiliata · $100 di crediti gratuiti alla registrazione (server gratuito, aspettati una latenza maggiore — ideale per test, non per produzione). Supporto di prima classe in OmniRoute dalla v3.8.50: Chat Completions, formato wire compatibile con Anthropic e percorso compatibile con OpenAI. I modelli disponibili includono claude-opus-4-8, claude-opus-5, gpt-5.6-sol e altri. Ottieni i tuoi $100 → +

+ ⚠️ Link affiliato — OmniRoute non ha sponsorizzazioni o partnership con questo provider. +
+ +Conosci un altro provider con un generoso coupon gratuito di registrazione utile agli utenti OmniRoute? Apri una issue e lo aggiungeremo qui. + +
+ +
+ +
+ + +## 🎯 Combo — La funzionalità di punta + +
+ +Tutte le 19 strategie di routing delle combo animate — una scheda per strategia: priority, fill-first, weighted, round-robin, p2c, least-used, random, strict-random, cost-optimized, headroom, reset-window, reset-aware, context-relay, context-optimized, cache-optimized, lkgp, auto, fusion, pipeline. Consulta la tabella seguente per capire cosa fa ciascuna. + +> Una **combo** è una catena di modelli tra cui OmniRoute instrada le richieste **automaticamente**. La quota finisce, un provider fallisce o i costi aumentano: la combo passa silenziosamente al modello successivo. **È questo che rende OmniRoute resistente ai guasti.** 🛡️ + +### ⚡ Zero-config — usa semplicemente `auto` + +Non devi creare nessuna combo. Imposta il modello su `auto` (o una sua variante) e OmniRoute costruisce una combo virtuale a partire dai provider collegati, assegnando i punteggi in tempo reale: + + + + + + + + + +
ID modelloCosa ottimizza
auto🎯 Predefinito bilanciato (LKGP — resta sull'ultimo provider valido)
auto/coding🧑‍💻 Pesi orientati prima alla qualità per la generazione di codice
auto/fast⚡ Prima la latenza più bassa
auto/cheap💰 Prima il costo per token più basso
auto/offline🔋 Prima il maggiore margine di quota / rate limit
auto/smart🔭 Prima la qualità + 10% di esplorazione per scoprire modelli migliori
+ +## + +### 🔀 Oppure creane una tua — 19 strategie di routing + +Tutte e **19** le strategie — combinabili liberamente per ogni passaggio della combo: + + + + + + + + + + + + + + + + + + + + + + + + + + +
#StrategiaCosa fa
1priorityLista ordinata con priorità al primo target — esaurisce ciascuno prima di passare al successivo 🥇
2fill-firstUsa completamente la quota di ogni target prima di passare oltre
3weightedScelta casuale pesata in base al peso assegnato a ogni target
4round-robinScorre ciclicamente i target in ordine
5p2cBilanciamento casuale del carico Power-of-Two-Choices
6least-usedSceglie il target con il carico corrente più basso
7randomScelta casuale uniforme (con deduplicazione)
8strict-randomCasuale senza deduplicare le ripetizioni 🎲
9cost-optimizedRiduce al minimo il costo per richiesta usando i prezzi live del catalogo 💸
10headroomSceglie il target con la maggiore quota residua
11reset-windowPreferisce il target la cui finestra di quota si resetta prima
12reset-awareOrdina in base al reset della quota — prima le finestre più brevi 📊
13context-relayPassa il contesto tra i target nelle conversazioni lunghe 🧠
14context-optimizedSceglie il target più adatto alla dimensione corrente del contesto
15cache-optimizedFissa ogni prefisso di prompt riutilizzabile allo stesso account — massimizza gli hit della prompt cache 🎯
16lkgpLast-Known-Good Path — resta sull'ultimo target che ha risposto correttamente
17autoPunteggio live su 14 fattori per ogni connessione 🤖
18fusionInvia la richiesta a un gruppo di modelli + un giudice sintetizza una sola risposta 🧬
19pipelineConcatena i passaggi — l'output di ogni target alimenta il successivo 🔗
+ +Il motore Auto-Combo valuta ogni candidato su **14 fattori** (salute, quota, costo, latenza, tasso di successo, freschezza…) — consulta [`docs/routing/AUTO-COMBO.md`](../../routing/AUTO-COMBO.md). + +## + +### 🧱 Resilienza integrata (3 livelli indipendenti) + +Resilienza di OmniRoute — 3 livelli indipendenti e autoriparanti, ciascuno dedicato al guasto corretto. Livello 1 circuit breaker del provider (intero provider): scatta solo su 408/5xx, soglie OAuth 10× / API-key 15× / locale 2×, reset dopo 60s/30s/15s con una sonda HALF-OPEN, recupero lazy; mentre è OPEN la combo passa al provider successivo. Livello 2 cooldown della connessione (una chiave/account): base 5s OAuth / 3s API-key, backoff esponenziale ×2 con protezione anti-thundering-herd, i 429 rispettano Retry-After, un successo azzera lo stato d'errore; una chiave in cooldown viene saltata mentre le altre continuano a servire. Livello 3 lockout del modello (un solo modello): 429 per-modello, 404 locali o dinieghi di modalità bloccano solo quel modello, mai l'intera connessione. Gli stati terminali (bannato, scaduto, crediti esauriti) richiedono l'intervento dell'operatore e non sono cooldown. + +📖 [Motore Auto-Combo](../../routing/AUTO-COMBO.md) · [Guida alla resilienza](../../architecture/RESILIENCE_GUIDE.md) + +
+ +
+ + +## 🏆 Cosa distingue OmniRoute + +
+ +Cosa distingue OmniRoute — tabella di confronto con 9router, OpenRouter, CLIProxyAPI e LiteLLM su 13 capacità. OmniRoute: 349 provider, oltre 90 provider gratuiti integrati, 19 strategie di routing, compressione token con 12 motori, server MCP integrato con 110 tool, protocollo agenti A2A, memoria persistente, guardrail, cloud agent, TLS fingerprint stealth, Desktop/Termux/PWA, 43 locale UI i18n, self-hosting 100% MIT. OmniRoute è l'unico a includere l'intero insieme; i concorrenti mostrano combinazioni di supporto completo, parziale e assente. Verificato sulla documentazione di ciascun progetto. + +📊 Metodologia completa e dettaglio per funzionalità rispetto a 9router, OpenRouter, CLIProxyAPI e LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](../../comparison/OMNIROUTE_VS_ALTERNATIVES.md) + +
+ + +## 💚 Supporta OmniRoute + +OmniRoute è distribuito con licenza MIT e mantenuto apertamente. Se ti fa risparmiare tempo o denaro, ecco come aiutarlo a restare indipendente — scegli ciò che preferisci. Le sponsorizzazioni non influenzano mai la priorità del routing: acquistano visibilità, non posizionamento. + + + + + + + + + +
Metti una stella alla repoGratis — aiuta davvero la visibilitàDai una stella a OmniRoute
🐙 GitHub SponsorsUna tantum o mensile · zero commissioni della piattaformagithub.com/sponsors/diegosouzapw
Ko-fiMancia una tantum, senza registrazione per chi donako-fi.com/diegosouzapw
🧋 Buy Me a CoffeePiccolo gesto informalebuymeacoffee.com/diegosouzapw
🖐 LiberapayRicorrente · non profit · open sourceliberapay.com/diegosouzapw
🇧🇷 PIX (Brasile)Istantaneo, senza commissionichiave e QR qui sotto
CryptoBTC · ETH · USDT-TRC20 · USDC-Solanaindirizzi qui sotto
+ +**🇧🇷 PIX** — istantaneo, senza commissioni (Brasile) + +Codice QR PIX di OmniRoute + +Chiave (casuale): `5d865059-bc44-483a-962d-43ceb80126eb` + +Pix copia-e-cola: ``` -┌─────────────┐ -│ Your CLI │ (Claude Code, Codex, OpenClaw, Cursor, Cline...) -│ Tool │ -└──────┬──────┘ - │ http://localhost:20128/v1 - ↓ -┌─────────────────────────────────────────┐ -│ OmniRoute (Smart Router) │ -│ • Format translation (OpenAI ↔ Claude) │ -│ • Quota tracking + Embeddings + Images │ -│ • Auto token refresh │ -└──────┬──────────────────────────────────┘ - │ - ├─→ [Tier 1: SUBSCRIPTION] Claude Code, Codex - │ ↓ quota exhausted - ├─→ [Tier 2: API KEY] DeepSeek, Groq, xAI, Mistral, NVIDIA NIM, etc. - │ ↓ budget limit - ├─→ [Tier 3: CHEAP] GLM ($0.6/1M), MiniMax ($0.2/1M) - │ ↓ budget limit - └─→ [Tier 4: FREE] Qoder, Qwen, Kiro (provider limits apply) - -Result: broader fallback coverage and cost control; availability is not guaranteed +00020101021126580014br.gov.bcb.pix01365d865059-bc44-483a-962d-43ceb80126eb5204000053039865802BR5922OMNIROUTE CONTRIBUICAO6006BRASIL62070503***630475DD ``` ---- - -## 🎯 What OmniRoute Solves — 30 Real Pain Points & Use Cases - -> **Every developer using AI tools faces these problems daily.** OmniRoute was built to solve them all — from cost overruns to regional blocks, from broken OAuth flows to protocol operations and enterprise observability. +
-💸 1. "I pay for an expensive subscription but still get interrupted by limits" +₿ Crypto — BTC · ETH · USDT-TRC20 · USDC-Solana (clicca per espandere) -Developers pay $20–200/month for Claude Pro, Codex Pro, or GitHub Copilot. Even paying, quota has a ceiling — 5h of usage, weekly limits, or per-minute rate limits. Mid-coding session, the provider stops responding and the developer loses flow and productivity. + + + + + +
₿ BTCBitcoin (SegWit)bc1qh00smz004sy85wyl28v77tenkt3ckl6eaep7fd
Ξ ETHEthereum (ERC20)0x64Cf6B68A6Ff34288e89172950a2d00102337a84
₮ USDTTron (TRC20)TKAF41JpuQrHbKTnsQa9svJE2T192Hvsc2
$ USDCSolana2emNNZzVVWQc3FQ2wk9M6qXUQmW8AKdjjL174fXR28Tu
-**How OmniRoute solves it:** - -- **Smart 4-Tier Fallback** — If subscription quota runs out, automatically redirects to API Key → Cheap → Free with zero manual intervention -- **Provider Limits Tracking** — Cached quota snapshots refresh on a server-side schedule (default `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70`) with manual refresh available in the UI -- **Multi-Account Support** — Multiple accounts per provider with auto round-robin — when one runs out, switches to the next -- **Custom Combos** — Customizable fallback chains with 13 balancing strategies (priority, weighted, fill-first, round-robin, P2C, random, least-used, cost-optimized, strict-random, auto, lkgp, context-optimized, **context-relay**) -- **Structured Combo Builder** — Build combos step-by-step with explicit provider + model + account selection, including repeated providers and fixed-account targets -- **Quota-Aware P2C** — Power-of-two account selection now factors quota headroom, backoff, recent errors, and consecutive use -- **Codex Business Quotas** — Business/Team workspace quota monitoring directly in the dashboard +⚠️ Invia ogni moneta esclusivamente sulla rete indicata: inviarla sulla rete sbagliata può causare la perdita dei fondi.
-
-🔌 2. "I need to use multiple providers but each has a different API" +🐛 Hai trovato un bug o vuoi lasciare un feedback? Apri una [Discussion](https://github.com/diegosouzapw/OmniRoute/discussions). + +
+ +

Note per gli sviluppatori: il progetto può generare un file locale .env durante npm install/postinstall per comodità nello sviluppo. Questo file viene intenzionalmente ignorato tramite .gitignore (vedi .gitignore) e non deve mai essere incluso nei commit; se viene committato accidentalmente, ruota ogni secret esposto e rimuovi il file dalla cronologia. Consulta docs/DEVELOPER-ENVIRONMENT.md per le indicazioni sulla gestione dei file di ambiente locali e dei secret.

+ +## 📡 OmniRoute Radar + +Il valore principale dei tier gratuiti resta **~1,53 miliardi di token/mese**, calcolato sul catalogo documentato con deduplicazione dei pool riportato sopra. I crediti temporanei di registrazione dei provider possono separatamente portare il primo mese a **~2,15 miliardi**. Radar è un overlay opzionale e firmato del catalogo, pensato per chi vuole informazioni più aggiornate sulla disponibilità dei modelli gratuiti tra una release di OmniRoute e la successiva; il catalogo della community e tutte le funzionalità gratuite esistenti restano gratuiti. + +I sostenitori possono ricevere il catalogo live e ulteriori opportunità offerte dai provider. Il relativo tetto separato e variabile è di **circa 3 miliardi di token/mese al massimo**, a seconda della disponibilità dei provider. Questo limite non è una garanzia: i provider possono modificare quote, requisiti, modelli o regioni in qualsiasi momento. + +Radar è opt-in e usa soltanto richieste GET. Il client OmniRoute non carica prompt, traffico, configurazione dei provider, telemetria d'uso o lo stato locale di chiusura degli annunci. Dettagli sui requisiti e sul catalogo corrente su **[radar.omniroute.online/planos](https://radar.omniroute.online/planos)**. + +
+ +
+ + +## ✨ Novità + +
+ +> Novità principali da **v3.8.20 → v3.8.50**. Cronologia completa in [`CHANGELOG.md`](../../../CHANGELOG.md). + +- **🎛️ OmniConductor** — delega A2A in ingresso alla tua flotta di agenti, skill Conductor nell'Agent Card e un pannello dashboard con chat vocale push-to-talk Faro. → [A2A Server](../../frameworks/A2A-SERVER.md) +- **🛂 Admission adattiva e protezione dal sovraccarico** — le richieste chat pesanti vengono messe in coda invece di ricevere 503, con lease RPM rolling atomici per connessione. → [Guida alla resilienza](../../architecture/RESILIENCE_GUIDE.md) +- **🗂️ Ordinamento canonico di `/v1/models`** — un blocco contiguo raggruppato per provider per ciascun provider (combo sempre in testa), stabile tra tutte le fonti del catalogo. → [Riferimento API](../../reference/API_REFERENCE.md) +- **🗜️ Rafforzamento della compressione** — protezione dall'inflazione attiva per impostazione predefinita, pack Caveman per DE / FR / JA + cinese (wényán), filtri RTK per Gradle e .NET. → [Compressione](../../compression/COMPRESSION_ENGINES.md) +- **💸 Costo flat-rate trasparente** — i provider in abbonamento / coding plan risultano a **$0** nelle analytics dei costi; budget, quote e routing continuano a fare stime. → [Riferimento API](../../reference/API_REFERENCE.md) +- **⚖️ Routing Quota-Share** — divide equamente la quota di un account condiviso tra chiavi in pool, in modo work-conserving così le porzioni inattive vengono prestate. → [Guida alla resilienza](../../architecture/RESILIENCE_GUIDE.md) +- **🤖 Configurazione CLI/agente con un comando** — `setup-*` configura oltre 12 strumenti di coding; `omniroute run` avvia 7 CLI (Claude Code, Codex, Aider, Goose, OpenCode, Qwen Code, Gemini CLI) senza scrivere configurazioni; `omniroute configure` è un selettore interattivo provider+modello con preferiti per contesto. → [Integrazioni CLI](../../guides/CLI-INTEGRATIONS.md) +- **🛰️ Modalità remota** — controlla un OmniRoute remoto con token scoped (`connect` / `contexts` / `tokens`) + helper OAuth `antigravity` per installazioni VPS. → [Modalità remota](../../guides/REMOTE-MODE.md) +- **🧭 Auto-routing più intelligente** — combo `auto/:`, **Fusion** (gruppo di modelli + giudice), routing task-aware, override per-request di modello / modalità / budget USD. → [Auto-Combo](../../routing/AUTO-COMBO.md) +- **🗜️ Compressione pluggable** — 12 motori componibili + Compression Studios: LLMLingua-2, Ultra a due livelli, omniglyph, fidelity gate per passaggio, GCF v3.2, editor drag-reorder. → [Compressione](../../compression/COMPRESSION_ENGINES.md) +- **🕵️ Decrittazione MITM trasparente (TPROXY)** — cattura le CLI che ignorano le variabili d'ambiente del proxy, con CA per-SNI + installer del trust store. → [MITM/TPROXY](../../security/MITM-TPROXY-DECRYPT.md) +- **💸 Telemetria dei costi ovunque** — header di costo/utilizzo `X-OmniRoute-*` su ogni endpoint, header del risparmio su cache HIT, quote di spesa USD per chiave. → [Riferimento API](../../reference/API_REFERENCE.md) +- **🧠 Memoria sotto il tuo controllo** — disattivata per impostazione predefinita, quantizzazione vettoriale int8 opt-in + decadimento tipizzato, `x-omniroute-no-memory` per-request. → [Memoria](../../frameworks/MEMORY.md) +- **🛡️ Sicurezza** — guard contro la prompt injection su ogni route LLM (suite red-team), guardrail opzionale per il masking delle credenziali (oscura API key/secret trapelati in entrambe le direzioni), web search DuckDuckGo gratuita come ultima risorsa e gate di login OIDC opzionale per la dashboard (il login con password resta sempre disponibile). → [Guardrail](../../security/GUARDRAILS.md) +- **🖼️ Nuovi endpoint** — `/v1/ocr` (Mistral OCR) e `/v1/audio/translations` (stile Whisper) completano la superficie media. → [Riferimento API](../../reference/API_REFERENCE.md) +- **🎨 Generazione immagini / video / audio** — una sola API per i media: xAI Grok Imagine e Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Segmind, EdgeTTS. → [Riferimento API](../../reference/API_REFERENCE.md) +- **🌍 Deployment e operazioni** — `basePath` del reverse proxy, rilevamento automatico della lingua del browser, tracking dei dispositivi per chiave, trust MITM senza root, localizzazione zh-TW. → [Ambiente](../../reference/ENVIRONMENT.md) +- **🤝 Più provider e agenti** — Cursor Cloud Agent, Grok Build (xAI) con login browser + OAuth, scheda Ollama di prima classe, Claude Opus 5 e Sonnet 5, partnership ufficiale Kimi (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… e un catalogo aggiornato di **350 provider**. → [Provider](../../reference/PROVIDER_REFERENCE.md) +- **📡 Trasparenza del routing** — ogni risposta include un header `X-OmniRoute-Decision` con strategia/provider/latenza che l'ha servita; una nuova strategia combo `cache-optimized` + il fattore `cacheAffinity` di Auto-Combo riportano le richieste ripetute alla connessione che possiede il prefisso in cache; un endpoint read-only `/v1/auto-combo/{channel}/candidates` espone il pool di candidati live di un canale `auto/*`. → [Auto-Combo](../../routing/AUTO-COMBO.md) +- **⚡ Prestazioni e infrastruttura locali** — Redis locale con un clic, deployer relay Cloudflare Workers / Deno Deploy, Bifrost e Mux come servizi embedded supervisionati. → [Servizi embedded](../../frameworks/EMBEDDED-SERVICES.md) + +
+ +
+ + +## 🤖 CLI e agenti di coding compatibili + +> Una sola configurazione — `http://localhost:20128/v1` — e **qualsiasi** IDE o CLI AI può usare modelli gratuiti e a basso costo. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Claude Code
Claude Code
                           
Codex CLI
Codex CLI
                           
Cline
Cline
                           
Kilo Code
Kilo Code
                           
Zoo Code
Zoo Code
                           
Continue
Continue
                           
Aider
Aider
                           
ForgeCode
ForgeCode
                           
jcode
jcode
                           
DeepSeek TUI
DeepSeek TUI
                           
CodeWhale
CodeWhale
                           
OpenCode
OpenCode
                           
Factory Droid
Factory Droid
                           
GitHub Copilot CLI
Copilot CLI
                           
Cursor CLI
Cursor CLI
                           
Smelt
Smelt
                           
Pi (pi-coding-agent)
Pi
                           
Grok Build (xAI)
Grok Build
                           
Hermes Agent (Nous Research)
Hermes Agent
                           
OpenClaw
OpenClaw
                           
Goose
Goose
                           
Open Interpreter
Open Interpreter
                           
Warp AI
Warp AI
                           
Agent Deck
Agent Deck
                           
+
+ +
++ funziona anche con · Kiro · Command Code · Antigravity · Windsurf · AMP · qualsiasi strumento compatibile con OpenAI +
+ +📖 Configurazione per ciascuno dei 34 strumenti (26 CLI Code + 8 CLI Agents) → [`docs/reference/CLI-TOOLS.md`](../../reference/CLI-TOOLS.md) · 🧩 Plugin OpenCode → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider) + +
+ +
+ +**Avvia qualsiasi CLI supportata tramite OmniRoute con un solo comando** — senza scrivere file di configurazione, +con le credenziali iniettate per singolo processo e una home temporanea isolata per Qwen/Gemini: + +```bash +omniroute run claude --model openai/gpt-5.4 # Claude Code +omniroute run codex --model glm/glm-5.2 # OpenAI Codex CLI +omniroute run aider --model glm/glm-5.2 -- --message "reply OK" +omniroute run goose --model glm/glm-5.2 +omniroute run opencode --model glm/glm-5.2 -- run "reply OK" +omniroute run qwen --model glm/glm-5.2 -- -p "reply OK" +omniroute run gemini --model glm/glm-5.2 -- --skip-trust -p "reply OK" + +# Or pick provider+model interactively and write the tool's own config: +omniroute configure codex # also: claude opencode qwen aider goose cline continue kilo +``` + +Ogni comando rispetta il contesto remoto attivo (`omniroute connect `); `--dry-run` +mostra in anteprima env/argomenti esatti senza eseguire nulla, mentre `--api-key-env NAME` evita che i segreti +finiscano nella cronologia della shell. → [Integrazioni CLI](../../guides/CLI-INTEGRATIONS.md) + +
+ + +
+ +## 🌐 349 provider AI — oltre 90 gratuiti + +
+ +> Il catalogo più completo tra i router open source: **349 provider**, **oltre 90 con un piano gratuito**, **56 gratuiti per sempre**. + +
+ +### 🏢 Tutti i principali laboratori — tramite un solo endpoint + + + + + + + + + + + + + + + + + + + + + + + + + + +
OpenAI
OpenAI
                           
Anthropic
Anthropic
                           
Gemini
Gemini
                           
xAI Grok
xAI Grok
                           
DeepSeek
DeepSeek
                           
Mistral
Mistral
                           
Qwen
Qwen
                           
Meta Llama
Meta Llama
                           
Groq
Groq
                           
NVIDIA
NVIDIA
                           
MiniMax
MiniMax
                           
Cohere
Cohere
                           
Perplexity
Perplexity
                           
Hugging Face
HuggingFace
                           
Together
Together
                           
Fireworks
Fireworks
                           
Cloudflare
Cloudflare
                           
Baidu
Baidu
                           
+ +…e oltre 220 altri — ogni icona viene risolta in tempo reale dal catalogo provider della dashboard. 📖 [Riferimento provider](../../reference/PROVIDER_REFERENCE.md) + +
+ +### 🆓 Gratuiti per sempre — $0, nessuna carta + + + + + + + + + + + + + + + + + + +
OpenCode Zen
OpenCode Zen
DeepSeek V4, Nemotron 3
Nessun limite di token
Kilo Code
Kilo Code
Auto-router, Tencent Hy3
Gratuito per sempre
Requesty
Requesty
GPT-OSS 120B, Nemotron
Gratuito per sempre
SiliconFlow
SiliconFlow
DeepSeek V3.2 / R1
Piano gratuito
Z.AI GLM
Z.AI GLM
GLM-4.7 / 4.5-Flash
Gratuito per sempre
Baidu ERNIE
Baidu ERNIE
ERNIE 4.0
Gratuito per sempre
Qoder AI
Qoder AI
Qwen3-Max, Kimi-K2
GRATUITO senza limiti
Pollinations
Pollinations
GPT, Llama, Claude
Nessuna chiave necessaria
Cloudflare AI
Cloudflare AI
50+ modelli
10K neuroni/giorno
NVIDIA NIM
NVIDIA NIM
GLM, MiniMax
~40 RPM gratuiti
Cerebras
Cerebras
GLM 4.7, GPT-OSS
1M token/giorno
OpenRouter
OpenRouter
modelli :free
+$10 → RPM più elevati
-OpenAI uses one format, Claude (Anthropic) uses another, Gemini yet another. If a dev wants to test models from different providers or fallback between them, they need to reconfigure SDKs, change endpoints, deal with incompatible formats. Custom providers (FriendLI, NIM) have non-standard model endpoints. +📖 Catalogo completo leggibile dalle macchine → [`docs/reference/PROVIDER_REFERENCE.md`](../../reference/PROVIDER_REFERENCE.md) -**How OmniRoute solves it:** +
+
-- **Unified Endpoint** — A single `http://localhost:20128/v1` serves as proxy for all 329 provider catalog entries -- **Format Translation** — Automatic and transparent: OpenAI ↔ Claude ↔ Gemini ↔ Responses API -- **Response Sanitization** — Strips non-standard fields (`x_groq`, `usage_breakdown`, `service_tier`) that break OpenAI SDK v1.83+ -- **Role Normalization** — Converts `developer` → `system` for non-OpenAI providers; `system` → `user` for GLM/ERNIE -- **Think Tag Extraction** — Extracts `` blocks from models like DeepSeek R1 into standardized `reasoning_content` -- **Structured Output for Gemini** — `json_schema` → `responseMimeType`/`responseSchema` automatic conversion -- **`stream` defaults to `false`** — Aligns with OpenAI spec, avoiding unexpected SSE in Python/Rust/Go SDKs + +
-
+## 🖥️ Dove gira OmniRoute — ovunque -
-🌐 3. "My AI provider blocks my region/country" + -Providers like OpenAI/Codex block access from certain geographic regions. Users get errors like `unsupported_country_region_territory` during OAuth and API connections. This is especially frustrating for developers from developing countries. +> La stessa app, sulla tua macchina, secondo le tue regole. Da un'installazione npm globale fino al **tuo telefono** tramite Termux. -**How OmniRoute solves it:** + + + + + + + + + + + +
PiattaformaInstallazionePunti di forza
📦 npm (globale)npm install -g omnirouteUn comando, qualsiasi OS
🐳 Dockerdocker run … diegosouzapw/omnirouteMulti-arch AMD64 + ARM64
🖥️ Desktop (Electron)npm run electron:buildFinestra nativa + system tray — Windows / macOS / Linux
💪 ARMnativo arm64Raspberry Pi, server ARM, Apple Silicon
📱 Android (Termux)pkg install nodejs && npx -y omnirouteGira sul tuo telefono, 24/7, senza root
📲 PWA"Aggiungi alla schermata Home"Schermo intero, offline, installabile dal browser
🧩 Plugin OpenCode@omniroute/opencode-providerIntegrazione nativa con OpenCode
🤖 VS Code Copilot Chatinstalla l'estensione OmniCopilotTutti i modelli OmniRoute nel selettore nativo di Copilot Chat — Stable e Insiders
🛠️ Da sorgentenpm install && npm run devModificalo e contribuisci
-- **3-Level Proxy Config** — Configurable proxy at 3 levels: global (all traffic), per-provider (one provider only), and per-connection/key -- **Color-Coded Proxy Badges** — Visual indicators: 🟢 global proxy, 🟡 provider proxy, 🔵 connection proxy, always showing the IP -- **OAuth Token Exchange Through Proxy** — OAuth flow also goes through the proxy, solving `unsupported_country_region_territory` -- **Connection Tests via Proxy** — Connection tests use the configured proxy (no more direct bypass) -- **SOCKS5 Support** — Full SOCKS5 proxy support for outbound routing -- **TLS Fingerprint Spoofing** — Browser-like TLS fingerprint via `wreq-js` to bypass bot detection -- **🔏 CLI Fingerprint Matching** — Reorders headers and body fields to match native CLI binary signatures, drastically reducing account flagging risk. The proxy IP is preserved — you get both stealth **and** IP masking simultaneously +📖 [Guida Docker](../../guides/DOCKER_GUIDE.md) · [Desktop](../../../electron/README.md) · [Termux](../../guides/TERMUX_GUIDE.md) · [PWA](../../guides/PWA_GUIDE.md) · [OpenCode](../../frameworks/OPENCODE.md) -
+
-
-🆓 4. "I want to use AI for coding but I have no money" +
-Not everyone can pay $20–200/month for AI subscriptions. Students, devs from emerging countries, hobbyists, and freelancers need access to quality models at zero cost. +### 🧩 Novità: OmniRoute dentro il Copilot Chat nativo di VS Code -**How OmniRoute solves it:** +
-- **Ollama Cloud** — Cloud-hosted Ollama models at `api.ollama.com` with free "Light usage" tier; use `ollamacloud/` prefix -- **Free-Only Combos** — Chain `if/kimi-k2-thinking → qw/qwen3-coder-plus` can use currently listed $0 access; limits and availability apply -- **NVIDIA NIM Free Access** — ~40 RPM free access as currently listed; provider terms and model availability apply at build.nvidia.com (transitioning from credits to pure rate limits) -- **Cost Optimized Strategy** — Routing strategy that automatically chooses the cheapest available provider +> Nessuna nuova barra laterale, nessuna nuova UI di chat — ogni modello servito da OmniRoute compare direttamente nel +> **selettore modelli di Copilot Chat che usi già**. Da VS Code 1.122, i modelli dei provider funzionano +> senza accesso GitHub né abbonamento Copilot — modalità agent, tool calling e vision, gratuitamente. -
+Installa l'estensione **[OmniCopilot](https://github.com/diegosouzapw/OmniCopilot)**, collegala +al tuo server OmniRoute (predefinito `localhost:20128`), poi apri Copilot Chat → selettore modelli +→ **Manage Models…** → **OmniRoute**. -
-🔒 5. "I need to protect my AI gateway from unauthorized access" + + + + +
StoreLinkCompatibile con
🧩 VS Code MarketplaceInstalla →VS Code — Stable e Insiders
🔓 Open VSX RegistryInstalla →Cursor, Windsurf, VSCodium, Theia, code-server, Gitpod, Antigravity, Kiro…
-When exposing an AI gateway to the network (LAN, VPS, Docker), anyone with the address can consume the developer's tokens/quota. Without protection, APIs are vulnerable to misuse, prompt injection, and abuse. +Dall'editor: apri la vista **Extensions**, cerca **"OmniRoute"**, fai clic su **Install** +— funziona allo stesso modo su entrambi gli store. Sorgenti, issue e runbook di pubblicazione sono su +[diegosouzapw/OmniCopilot](https://github.com/diegosouzapw/OmniCopilot). -**How OmniRoute solves it:** +📖 [Guida VS Code Copilot Chat](../../guides/VSCODE-COPILOT.md) — configurazione, contenuto del selettore, dashboard in una scheda, risoluzione dei problemi -- **API Key Management** — Generation, rotation, and scoping per provider with a dedicated `/dashboard/api-manager` page -- **Model-Level Permissions** — Restrict API keys to specific models (`openai/*`, wildcard patterns), with Allow All/Restrict toggle -- **API Endpoint Protection** — Require a key for `/v1/models` and block specific providers from the listing -- **Auth Guard + CSRF Protection** — All dashboard routes protected with `withAuth` middleware + CSRF tokens -- **Rate Limiter** — Per-IP rate limiting with configurable windows -- **IP Filtering** — Allowlist/blocklist for access control -- **Prompt Injection Guard** — Sanitization against malicious prompt patterns -- **AES-256-GCM Encryption** — Credentials encrypted at rest +
-
+ +
-
-🛑 6. "My provider went down and I lost my coding flow" +## 🔒 Privato e local-first -AI providers can become unstable, return 5xx errors, or hit temporary rate limits. If a dev depends on a single provider, they're interrupted. Without circuit breakers, repeated retries can crash the application. +
-**How OmniRoute solves it:** +Privato e local-first — le tue chiavi, la tua macchina, i tuoi dati; OmniRoute è un proxy locale che non comunica autonomamente con servizi cloud. Undici garanzie: gira al 100% sul tuo hardware (0 passaggi cloud), telemetria disattivata per impostazione predefinita, credenziali cifrate a riposo (AES-256-GCM), nessun account o registrazione, gateway rafforzato (scoping delle API key, filtro IP, rate limit, difesa dalla prompt injection), route di processo limitate al loopback, pulizia degli header upstream, redazione PII rigorosamente opt-in, errori sanitizzati che non espongono dettagli interni, audit trail locale nel tuo SQLite e codice completamente open source con licenza MIT. -- **Request Queue & Pacing** — Per-connection request buckets smooth bursts before they hit upstream rate caps -- **Connection Cooldown** — A single connection cools down after retryable failures with optional upstream `Retry-After` hints and exponential backoff -- **Provider Circuit Breaker** — The provider only trips after fallback is exhausted and the provider request still fails with provider-wide transient errors; connection-scoped `429` rate limits stay in Connection Cooldown -- **Wait For Cooldown** — The server can wait for the earliest connection cooldown to expire and retry the same client request automatically -- **Anti-Thundering Herd** — Mutex + semaphore protection against concurrent retry storms -- **Combo Fallback Chains** — If the primary provider fails, automatically falls through the chain with no intervention -- **Health Dashboard** — Uptime monitoring, provider circuit breaker states, cooldowns, cache stats, p50/p95/p99 latency +📖 [Autorizzazione](../../architecture/AUTHZ_GUIDE.md) · [Guardrail](../../security/GUARDRAILS.md) · [Conformità](../../security/COMPLIANCE.md) - +
-
-🔧 7. "Configuring each AI tool is tedious and repetitive" + +
-**How OmniRoute solves it:** +## 🔌 CLI completa + A2A e MCP -- **CLI Tools Dashboard** — Dedicated page with one-click setup for Claude Code, Codex CLI, OpenClaw, Kilo Code, Antigravity, Cline -- **GitHub Copilot Config Generator** — Generates `chatLanguageModels.json` for VS Code with bulk model selection -- **Onboarding Wizard** — Guided 4-step setup for first-time users -- **One endpoint, all models** — Configure `http://localhost:20128/v1` once, access 329 provider catalog entries +
-
+> Oltre al server, OmniRoute è una **console completa da riga di comando** con **oltre 80 comandi**, più protocolli agent aperti che permettono a un agent AI di gestirlo **autonomamente**. -
-🔑 8. "Managing OAuth tokens from multiple providers is hell" +### ⌨️ Una vera CLI (non solo `start`) -Claude Code, Codex, Copilot — all use OAuth 2.0 with expiring tokens. Developers need to re-authenticate constantly, deal with `client_secret is missing`, `redirect_uri_mismatch`, and failures on remote servers. OAuth on LAN/VPS is particularly problematic. +```bash +omniroute # serve gateway + dashboard (port 20128) +omniroute chat # interactive TUI chat client (slash: /model /combo /skill /memory) +omniroute setup # guided first-run wizard +omniroute doctor # diagnose providers, ports, native deps +``` -**How OmniRoute solves it:** +### 🛰️ Modalità remota — esegui qui la CLI, OmniRoute su un VPS -- **Auto Token Refresh** — OAuth tokens refresh in background before expiration -- **OAuth 2.0 (PKCE) Built-in** — Automatic flow for Claude Code, Codex, Copilot, Kiro, Qwen, Qoder -- **Multi-Account OAuth** — Multiple accounts per provider via JWT/ID token extraction -- **OAuth LAN/Remote Fix** — Private IP detection for `redirect_uri` + manual URL mode for remote servers -- **OAuth Behind Nginx** — Uses `window.location.origin` for reverse proxy compatibility -- **Remote OAuth Guide** — Step-by-step guide for Google Cloud credentials on VPS/Docker +OmniRoute gira su un server? Gestiscilo dal laptop con la **stessa CLI**. Accedi una volta +con un token di accesso con scope; da quel momento ogni comando punta all'istanza remota. -
+```bash +omniroute connect 192.168.0.15 # password → scoped token, saved as a context +omniroute models list # ← runs against the REMOTE server +omniroute configure codex # ← picks a remote model, writes a local Codex profile +omniroute tokens create --name ci --scope read # mint narrower tokens for other machines +omniroute contexts use default # ← switch back to the local server +``` -
-📊 9. "I don't know how much I'm spending or where" +I token hanno scope `read` / `write` / `admin`; le route che avviano processi restano limitate al loopback. +📖 [Modalità remota](../../guides/REMOTE-MODE.md) -Developers use multiple paid providers but have no unified view of spending. Each provider has its own billing dashboard, but there's no consolidated view. Unexpected costs can pile up. +
-**How OmniRoute solves it:** +Demo animata del terminale con la CLI OmniRoute — omniroute providers list, omniroute combo list, omniroute health — che scorre gli oltre 80 comandi disponibili: providers · oauth · keys · combo · nodes · models · cache · compression · cost · usage · quota · health · resilience · telemetry · logs · audit · mcp · a2a · cloud · memory · skills · eval · tunnel · backup · sync · webhooks · policy · pricing · translator · simulate … -- **Cost Analytics Dashboard** — Per-token cost tracking and budget management per provider -- **Budget Limits per Tier** — Spending ceiling per tier that triggers automatic fallback -- **Per-Model Pricing Configuration** — Configurable prices per model -- **Usage Statistics Per API Key** — Request count and last-used timestamp per key -- **Analytics Dashboard** — Stat cards, model usage chart, provider table with success rates and latency +
-
+### 🤝 Collega un agent — e controllerà OmniRoute stesso -
-🐛 10. "I can't diagnose errors and problems in AI calls" +Esponi OmniRoute tramite **MCP**, **A2A**, una **REST API**, **webhook** o una **CLI remota** — qualsiasi agent compatibile (o il tuo codice) ottiene accesso al gateway: routing, provider, combo, cache, compressione, memoria — in autonomia. Gli endpoint HTTP qui sotto sono serviti su `http://localhost:20128`. -When a call fails, the dev doesn't know if it was a rate limit, expired token, wrong format, or provider error. Fragmented logs across different terminals. Without observability, debugging is trial-and-error. + + + + + + + + + +
InterfacciaEndpoint / comandoA cosa serve
🧰 MCP (stdio)omniroute --mcpCollegamento a Claude Desktop, Cursor e qualsiasi client MCP
🌊 MCP (HTTP)/api/mcp/streamMCP remoto — 110 tool, 33 scope, audit trail completo
📡 MCP (SSE)/api/mcp/sseTrasporto MCP in streaming
🤝 A2A/.well-known/agent.jsonAgent-to-agent, JSON-RPC 2.0 + SSE, 6 skill
🌐 REST API/v1/*Compatibile con OpenAI — chat, embedding, immagini, audio, OCR
🔔 Webhook/api/webhooksInvia eventi (utilizzo, quota, errori, routing) al tuo URL
🛰️ CLI remotaomniroute connect Gestisci un'istanza remota con token di accesso con scope
-**How OmniRoute solves it:** +```bash +# Give Claude Code the full OmniRoute toolset over MCP: +claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp/stream +``` -- **Unified Logs Dashboard** — 4 tabs: Request Logs, Proxy Logs, Audit Logs, Console -- **Console Log Viewer** — Real-time terminal-style viewer with color-coded levels, auto-scroll, search, filter -- **SQLite Summary Logs** — Request and proxy log indexes stay queryable across restarts without loading large payload blobs into SQLite -- **Translator Playground** — 4 debugging modes: Playground (format translation), Chat Tester (round-trip), Test Bench (batch), Live Monitor (real-time) -- **Request Telemetry** — p50/p95/p99 latency + X-Request-Id tracing -- **File-Based Detail Artifacts** — App logs rotate by size, retention days, and archive count; detailed request/response payloads live in `DATA_DIR/call_logs/` and rotate independently of SQLite summaries -- **System Info Report** — `npm run system-info` generates `system-info.txt` with your full environment (Node version, OmniRoute version, OS, CLI tools, Docker/PM2 status). Attach it when reporting issues for instant triage. +📖 [MCP Server](../../frameworks/MCP-SERVER.md) · [A2A Server](../../frameworks/A2A-SERVER.md) · [Protocolli agent](../../frameworks/AGENT_PROTOCOLS_GUIDE.md) -
+
-
-🏗️ 11. "Deploying and maintaining the gateway is complex" + +
-Installing, configuring, and maintaining an AI proxy across different environments (local, VPS, Docker, cloud) is labor-intensive. Problems like hardcoded paths, `EACCES` on directories, port conflicts, and cross-platform builds add friction. +## 🗜️ Risparmia il 15–95% dei token — automaticamente -**How OmniRoute solves it:** +
-- **npm global install** — `npm install -g omniroute && omniroute` — done -- **Docker Multi-Platform** — AMD64 + ARM64 native (Apple Silicon, AWS Graviton, Raspberry Pi) -- **Docker Compose Profiles** — `base` (no CLI tools) and `cli` (with Claude Code, Codex, OpenClaw) -- **Electron Desktop App** — Native app for Windows/macOS/Linux with system tray, auto-start, offline mode -- **Split-Port Mode** — API and Dashboard on separate ports for advanced scenarios (reverse proxy, container networking) -- **Cloud Sync** — Config synchronization across devices via Cloudflare Workers -- **DB Backups** — Automatic backup, restore, export and import of all settings, with `DISABLE_SQLITE_AUTO_BACKUP` for externally managed backups +### 📖 Come funziona — pipeline, architettura e calcolo del risparmio -
+Pipeline di compressione OmniRoute: una richiesta client da 10.000 token attraversa 12 motori in cascata — Session-Dedup, CCR, Lite, RTK, Responses Tool Output, Headroom, Relevance, Caveman, Aggressive, LLMLingua-2, Ultra, OmniGlyph — e raggiunge il provider con circa 1.080 token, con un risparmio fino al 95%. Codice, URL e JSON sono sempre preservati byte per byte. -
-🌍 12. "The interface is English-only and my team doesn't speak English" - -Teams in non-English-speaking countries, especially in Latin America, Asia, and Europe, struggle with English-only interfaces. Language barriers reduce adoption and increase configuration errors. - -**How OmniRoute solves it:** - -- **Dashboard i18n — 30 Languages** — All 500+ keys translated including Arabic, Bulgarian, Danish, German, Spanish, Finnish, French, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Malay, Dutch, Norwegian, Polish, Portuguese (PT/BR), Romanian, Russian, Slovak, Swedish, Thai, Ukrainian, Vietnamese, Chinese, Filipino, English -- **RTL Support** — Right-to-left support for Arabic and Hebrew -- **Multi-Language READMEs** — 30 complete documentation translations -- **Language Selector** — Globe icon in header for real-time switching - -
- -
-🔄 13. "I need more than chat — I need embeddings, images, audio" - -AI isn't just chat completion. Devs need to generate images, transcribe audio, create embeddings for RAG, rerank documents, and moderate content. Each API has a different endpoint and format. - -**How OmniRoute solves it:** - -- **Embeddings** — `/v1/embeddings` with 6 providers and 9+ models -- **Image Generation** — `/v1/images/generations` with 10 providers and 20+ models (OpenAI, xAI, Together, Fireworks, Nebius, Hyperbolic, NanoBanana, Antigravity, SD WebUI, ComfyUI) -- **Text-to-Video** — `/v1/videos/generations` — ComfyUI (AnimateDiff, SVD) and SD WebUI -- **Text-to-Music** — `/v1/music/generations` — ComfyUI (Stable Audio Open, MusicGen) -- **Audio Transcription** — `/v1/audio/transcriptions` — Whisper + Nvidia NIM, HuggingFace, Qwen3 -- **Text-to-Speech** — `/v1/audio/speech` — ElevenLabs, Nvidia NIM, HuggingFace, Coqui, Tortoise, Qwen3, **Inworld**, **Cartesia**, **PlayHT**, + existing providers -- **Moderations** — `/v1/moderations` — Content safety checks -- **Reranking** — `/v1/rerank` — Document relevance reranking -- **Responses API** — Full `/v1/responses` support for Codex - -
- -
-🧪 14. "I have no way to test and compare quality across models" - -Developers want to know which model is best for their use case — code, translation, reasoning — but comparing manually is slow. No integrated eval tools exist. - -**How OmniRoute solves it:** - -- **LLM Evaluations** — Golden set testing with 10 pre-loaded cases covering greetings, math, geography, code generation, JSON compliance, translation, markdown, safety refusal -- **4 Match Strategies** — `exact`, `contains`, `regex`, `custom` (JS function) -- **Translator Playground Test Bench** — Batch testing with multiple inputs and expected outputs, cross-provider comparison -- **Chat Tester** — Full round-trip with visual response rendering -- **Live Monitor** — Real-time stream of all requests flowing through the proxy - -
- -
-📈 15. "I need to scale without losing performance" - -As request volume grows, without caching the same questions generate duplicate costs. Without idempotency, duplicate requests waste processing. Per-provider rate limits must be respected. - -**How OmniRoute solves it:** - -- **Semantic Cache** — Two-tier cache (signature + semantic) reduces cost and latency -- **Request Idempotency** — 5s deduplication window for identical requests -- **Rate Limit Detection** — Per-provider RPM, min gap, and max concurrent tracking -- **Request Queue & Pacing** — Configurable queue, pacing, and concurrency defaults in Settings → Resilience -- **API Key Validation Cache** — 3-tier cache for production performance -- **Health Dashboard with Telemetry** — p50/p95/p99 latency, cache stats, uptime - -
- -
-🤖 16. "I want to control model behavior globally" - -Developers who want all responses in a specific language, with a specific tone, or want to limit reasoning tokens. Configuring this in every tool/request is impractical. - -**How OmniRoute solves it:** - -- **System Prompt Injection** — Global prompt applied to all requests -- **Thinking Budget Validation** — Reasoning token allocation control per request (passthrough, auto, custom, adaptive) -- **9 Routing Strategies** — Global strategies that determine how requests are distributed -- **Wildcard Router** — `provider/*` patterns route dynamically to any provider -- **Combo Enable/Disable Toggle** — Toggle combos directly from the dashboard -- **Manual Combo Ordering** — Drag combo cards by handle and persist the order in SQLite -- **Provider Toggle** — Enable/disable all connections for a provider with one click -- **Blocked Providers** — Exclude specific providers from `/v1/models` listing - -
- -
-🧰 17. "I need MCP tools as first-class product capabilities" - -Many AI gateways expose MCP only as a hidden implementation detail. Teams need a visible, manageable operation layer. - -**How OmniRoute solves it:** - -- MCP appears in the dashboard navigation and endpoint protocol tab -- Dedicated MCP management page with process, tools, scopes, and audit -- Built-in quick-start for `omniroute --mcp` and client onboarding - -
- -
-🧠 18. "I need A2A orchestration with sync + stream task paths" - -Agent workflows need both direct replies and long-running streamed execution with lifecycle control. - -**How OmniRoute solves it:** - -- A2A JSON-RPC endpoint (`POST /a2a`) with `message/send` and `message/stream` -- SSE streaming with terminal state propagation -- Task lifecycle APIs for `tasks/get` and `tasks/cancel` - -
- -
-🛰️ 19. "I need real MCP process health, not guessed status" - -Operational teams need to know if MCP is actually alive, not just whether an API is reachable. - -**How OmniRoute solves it:** - -- Runtime heartbeat file with PID, timestamps, transport, tool count, and scope mode -- MCP status API combining heartbeat + recent activity -- UI status cards for process/uptime/heartbeat freshness - -
- -
-📋 20. "I need auditable MCP tool execution" - -When tools mutate config or trigger ops actions, teams need forensic traceability. - -**How OmniRoute solves it:** - -- SQLite-backed audit logging for MCP tool calls -- Filters by tool, success/failure, API key, and pagination -- Dashboard audit table + stats endpoints for automation - -
- -
-🔐 21. "I need scoped MCP permissions per integration" - -Different clients should have least-privilege access to tool categories. - -**How OmniRoute solves it:** - -- 32 granular MCP scopes for controlled tool access -- Scope enforcement and visibility in MCP management UI -- Safe default posture for operational tooling - -
- -
-⚙️ 22. "I need operational controls without redeploying" - -Teams need quick runtime changes during incidents or cost events. - -**How OmniRoute solves it:** - -- Switch combo activation directly from MCP dashboard -- Tune queue, cooldown, breaker, and wait settings from the dedicated Resilience page -- Review live provider breaker state from the Health dashboard - -
- -
-🔄 23. "I need live A2A task lifecycle visibility and cancellation" - -Without lifecycle visibility, task incidents become hard to triage. - -**How OmniRoute solves it:** - -- Task listing/filtering by state/skill with pagination -- Drill-down on task metadata, events, and artifacts -- Task cancellation endpoint and UI action with confirmation - -
- -
-🌊 24. "I need active stream metrics for A2A load" - -Streaming workflows require operational insight into concurrency and live connections. - -**How OmniRoute solves it:** - -- Active stream counters integrated into A2A status -- Last task timestamp and per-state counts -- A2A dashboard cards for real-time ops monitoring - -
- -
-🪪 25. "I need standard agent discovery for clients" - -External clients and orchestrators need machine-readable metadata for onboarding. - -**How OmniRoute solves it:** - -- Agent Card exposed at `/.well-known/agent.json` -- Capabilities and skills shown in management UI -- A2A status API includes discovery metadata for automation - -
- -
-🧭 26. "I need protocol discoverability in the product UX" - -If users cannot discover protocol surfaces, adoption and support quality drop. - -**How OmniRoute solves it:** - -- Consolidated **Endpoints** page with tabs for Proxy, MCP, A2A, and API Endpoints -- Inline service status toggles (Online/Offline) for MCP and A2A -- Links from overview to dedicated management tabs - -
- -
-🧪 27. "I need end-to-end protocol validation with real clients" - -Mock tests are not enough to validate protocol compatibility before release. - -**How OmniRoute solves it:** - -- E2E suite that boots app and uses real MCP SDK client transport -- A2A client tests for discovery, send, stream, get, and cancel flows -- Cross-check assertions against MCP audit and A2A tasks APIs - -
- -
-📡 28. "I need unified observability across all interfaces" - -Splitting observability by protocol creates blind spots and longer MTTR. - -**How OmniRoute solves it:** - -- Unified dashboards/logs/analytics in one product -- Health + audit + request telemetry across OpenAI, MCP, and A2A layers -- Operational APIs for status and automation - -
- -
-💼 29. "I need one runtime for proxy + tools + agent orchestration" - -Running many separate services increases operational cost and failure modes. - -**How OmniRoute solves it:** - -- OpenAI-compatible proxy, MCP server, and A2A server in one stack -- Shared auth, resilience, data store, and observability -- Consistent policy model across all interaction surfaces - -
- -
-🚀 30. "I need to ship agentic workflows without glue-code sprawl" - -Teams lose velocity when stitching multiple ad-hoc services and scripts. - -**How OmniRoute solves it:** - -- Unified endpoint strategy for clients and agents -- Built-in protocol management UIs and smoke validation paths -- Production-ready foundations (security, logging, resilience, backup) - -
- -
-📚 31. "My long sessions crash with 'context_length_exceeded' limits" - -During deep debugging, long histories with tool results quickly exceed provider token windows, causing failed requests and orphaned context. - -**How OmniRoute solves it:** - -- **Proactive Context Compression** — Evaluates token budgets before the request hits upstream and proactively prunes old conversation history with a smart binary-search mechanism. -- **Structural Integrity Guards** — Automatically tracks explicit `tool_use` definitions and ensures that if a tool input is truncated, its corresponding `tool_result` is also safely removed, preventing API validation errors. -- **Multi-Layer Dropping** — Progressively drops system messages, regular messages, and finally enforces strict length limits without breaking conversational logic. - -
- -### Example Playbooks (Integrated Use Cases) - -**Playbook A: Maximize paid subscription + cheap backup** +La combinazione in cascata predefinita esegue `RTK → Caveman`. Quando entrambi intervengono sullo stesso payload di tool/contesto, i risparmi si compongono: ```txt -Combo: "maximize-claude" - 1. cc/claude-opus-4-7 - 2. glm/glm-4.7 - 3. if/kimi-k2-thinking - -Monthly cost: $20 + small backup spend -Outcome: higher quality, near-zero interruption +combined = 1 − (1 − RTK) × (1 − Caveman_input) +average = 1 − (1 − 0.80) × (1 − 0.46) = 89.2% +range = 78.4 – 94.6% ``` -**Playbook B: Zero-cost coding stack** +Blocchi di codice, URL, JSON e dati strutturati sono **sempre protetti** dal motore di preservazione. -```txt -Combo: "free-access" - 1. if/kimi-k2-thinking (no published token cap; limits apply) - 2. qw/qwen3-coder-plus (no published token cap; limits apply) +> **Perché usare molti token quando ne bastano pochi?** Ogni richiesta attraversa la pipeline di compressione di OmniRoute **in modo trasparente** — senza modifiche al client. Ora è una **stack di 12 motori componibili** eseguiti in ordine e combinabili per ciascun routing combo — basati anche su idee di [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 90K+), [LLMLingua-2](https://github.com/microsoft/LLMLingua) e [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR). -Monthly cost: $0 -Outcome: broader free-access fallback; upstream availability is not guaranteed -``` +### 🧱 La stack di 12 motori -**Playbook C: 24/7 always-on fallback chain** +I motori vengono eseguiti nell'ordine della pipeline; ciascuno può essere attivato/disattivato e configurato indipendentemente per combo: -```txt -Combo: "multi-layer-fallback" - 1. cc/claude-opus-4-7 - 2. cx/gpt-5.2-codex - 3. glm/glm-4.7 - 4. minimax/MiniMax-M2.1 - 5. if/kimi-k2-thinking + + + + + + + + + + + + + + +
#MotoreCosa fa
1Session-DedupElimina contenuti ripetuti tra i turni (content-addressed, cross-turn)
2CCRArchivia blocchi grandi dietro marker di recupero, caricati su richiesta
3LiteRiduzione di spazi e URL immagine (baseline a bassa latenza)
4RTKFiltro intelligente dei risultati dei tool, deduplica e troncamento (consapevole del comando)
5Responses Tool OutputCompressione JSON lossless-first + diagnostica limitata per output shell/patch/search/build (Responses API)
6HeadroomCompattazione tabellare lossless di array JSON (~30%) tramite codec GCF incluso nel progetto
7RelevanceValutazione estrattiva delle frasi rispetto all'ultima richiesta dell'utente
8CavemanCompressione della prosa basata su regole (~65–75% sull'output)
9AggressiveRiepilogo + invecchiamento progressivo dei turni precedenti
10LLMLingua-2Pruning semantico ML tramite MobileBERT ONNX — code-safe, asincrono
11UltraPruning euristico dei token con livello opzionale basato su piccolo modello (SLM)
12OmniGlyphCodifica sperimentale del contesto come immagine per Claude Fable 5 misurato sul protocollo Anthropic diretto; i transformer GPT 5.6 restano fail-closed in attesa di ricevute del provider. Quattro profili di compressione (aggressive predefinito, balanced, coding-safe, passthrough) (il più aggressivo; opt-in)
-Outcome: deep fallback depth for deadline-critical workloads -``` +Blocchi di codice, URL e dati strutturati sono **sempre preservati** byte per byte. I **preset con un clic** combinano i motori: -**Playbook D: Agent ops with MCP + A2A** + + + + + + + + +
ModalitàRisparmioIdeale per
🪶 Lite~15%Impostazione predefinita sicura sempre attiva
🪨 Standard (Caveman)~30%Coding quotidiano
Aggressive~50%Sessioni lunghe con molti tool
🔥 Ultra~75%Massimo risparmio
🧰 RTK60–90%Output di shell/test/build/git
🔗 Stacked (RTK → Caveman)78–95%Prompt misti + log dei tool
-```txt -1) Start MCP transport (`omniroute --mcp`) for tool-driven operations -2) Run A2A tasks via `message/send` and `message/stream` -3) Observe via /dashboard/endpoint (MCP and A2A tabs) -4) Toggle services via inline status controls -``` +**Esempio reale — modalità Standard:** ---- +> **Prima (69 token):** _"The reason your React component is re-rendering is likely because you're creating a new object reference on each render cycle. When you pass an inline object as a prop, React's shallow comparison sees it as a different object every time, which triggers a re-render. I would recommend using useMemo to memoize the object."_ +> +> **Dopo (19 token):** _"New object ref each render. Inline object prop = new ref = re-render. Wrap in useMemo."_ +> +> **Stessa risposta. 72% di token in meno. Nessuna perdita di accuratezza.** ✅ -## 🆓 Start Free — Zero Configuration Cost +**Esempio PT-BR — modalità [Troglodita](https://github.com/leninejunior/troglodita):** -> Setup AI coding in minutes at **$0/month**. Connect these free accounts and use the built-in **Free Stack** combo. +> **Antes (42 tokens):** _"O problema é que o componente está re-renderizando porque uma nova referência de objeto está sendo criada em cada ciclo de renderização. Eu recomendaria usar useMemo."_ +> +> **Depois (12 tokens):** _"Re-render: ref nova cada ciclo (objeto inline recriado). Usar `useMemo`."_ +> +> **Mesma resposta. ~70% menos tokens. Precisão técnica intacta.** ✅ -| Step | Action | Providers Unlocked | -| ---- | -------------------------------------------------- | ------------------------------------------------------------------ | -| 1 | Connect **Kiro** (AWS Builder ID OAuth) | Claude Sonnet 4.5, Haiku 4.5 — provider/account limits apply | -| 2 | Connect **Qoder** (Google OAuth) | kimi-k2-thinking, qwen3-coder-plus, deepseek-r1... — provider/account limits apply | -| 3 | Connect **Qwen** (Device Code) | qwen3-coder-plus, qwen3-coder-flash... — provider/account limits apply | -| 4 | `/dashboard/combos` → **Free Stack ($0)** template | Round-robin all free providers automatically | +
-**Point any IDE/CLI to:** `http://localhost:20128/v1` · API Key: `any-string` · Done. +### 🎚️ Oltre i motori — output style, regolazione adattiva e controllo per richiesta -> **Optional extra coverage (current terms apply):** Groq, NVIDIA NIM, Cerebras, LongCat and Cloudflare Workers AI can provide free access or signup credits where currently listed. Quotas, models, accounts, regions and provider terms can change; see [`FREE_TIERS.md`](../../reference/FREE_TIERS.md). +I 12 motori sopra riducono ciò che entra **in input**. Altri tre livelli definiscono **come**, **quando** e cosa esce **in output**: -## Avvio Rapido +- **🪄 Output Styles** _(controllo dell'output)_ — iniettano istruzioni deterministiche e cache-safe per modellare la risposta; sono combinabili, ciascuno con intensità `lite` / `full` / `ultra`. Aggiungere uno style richiede una sola voce nel registry: + - **Terse prose** — elimina riempitivi / articoli / esitazioni; mantiene esatto il contenuto tecnico. + - **Less code** — YAGNI da "senior dev pigro": modifica minima funzionante, nessuna infrastruttura non richiesta. + - **Terse CJK (文言)** — stile cinese classico ultra-conciso (limitato alla locale `zh`). +- **🎯 Adaptive context-budget** _(la regolazione)_ — invece di una singola soglia token on/off, aumenta gradualmente l'uso dei motori più economici e lossless solo quanto necessario per **rientrare nella context window del modello**. Policy: `reserve-output` (predefinita, model-aware) · `percentage` · `absolute`. Modalità: `floor` (garantisce il fit) · `replace-autotrigger` (vince la tua scelta esplicita) · `off` (soglia legacy). +- **🎛️ Dove viene decisa la compressione** _(precedenza, alta → bassa)_ — header per richiesta `x-omniroute-compression` › override del routing combo › profilo nominato attivo › adaptive / auto-trigger › impostazione predefinita del pannello › off. Il piano applicato viene restituito nell'header di risposta `X-OmniRoute-Compression: ; source=`. -### 1) Install and run +Puoi attivare l'auto-trigger tramite soglia token, abilitare la regolazione adattiva, fissare un profilo nominato, impostare una scelta una tantum per richiesta oppure assegnare una pipeline a ciascun routing combo — scegli ciò che si adatta al carico di lavoro. Un **eval harness** offline opt-in (`npm run eval:compression`) misura fedeltà e risparmio su un corpus fissato prima di promuovere una modifica. + +📖 [`COMPRESSION_GUIDE.md`](../../compression/COMPRESSION_GUIDE.md) · [`RTK_COMPRESSION.md`](../../compression/RTK_COMPRESSION.md) · [`COMPRESSION_ENGINES.md`](../../compression/COMPRESSION_ENGINES.md) + +
+ + +
+ +# ⚡ Avvio rapido + +
+ +**1) Installa e avvia** ```bash npm install -g omniroute omniroute ``` -> **pnpm users:** Pass `--allow-build` at install time to enable native build scripts required by `better-sqlite3` and `@swc/core` (the `approve-builds -g` command is not supported for global installs on pnpm v11): -> -> ```bash -> pnpm add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core -> omniroute -> ``` +> 💡 Vedi `npm warn ERESOLVE` o avvisi sulle peer dependency? [Sono innocui](../../guides/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated). -Dashboard opens at `http://localhost:20128` and API base URL is `http://localhost:20128/v1`. +Dashboard su `http://localhost:20128` · API su `http://localhost:20128/v1`. -#### Arch Linux (AUR) +**2) Collega un provider GRATUITO (senza registrazione)** -Arch Linux users can install the [AUR package](https://aur.archlinux.org/packages/omniroute-bin), which installs OmniRoute and provides a systemd user service: +Dashboard → **Providers** → collega **Kiro AI** (Claude gratuito, ~50 crediti/mese per account) oppure **OpenCode Free** (nessuna autenticazione) → fatto. -```bash -yay -S omniroute-bin -systemctl --user enable --now omniroute.service -``` - -| Command | Description | -| ----------------------- | ----------------------------------------------------------- | -| `omniroute` | Start server (`PORT=20128`, API and dashboard on same port) | -| `omniroute --port 3000` | Set canonical/API port to 3000 | -| `omniroute --mcp` | Start MCP server (stdio transport) | -| `omniroute --no-open` | Don't auto-open browser | -| `omniroute --help` | Show help | - -Optional split-port mode: - -```bash -PORT=20128 DASHBOARD_PORT=20129 omniroute -# API: http://localhost:20128/v1 -# Dashboard: http://localhost:20129 -``` - -### 2) Uninstalling - -When you no longer need OmniRoute, we provide two quick scripts for a clean removal: - -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | - -> Note: To run these commands, navigate to the OmniRoute project folder (if you cloned it) and run them. Alternatively, if globally installed, you can simply run `npm uninstall -g omniroute`. - -### Long-Running Streaming Timeouts - -For most deployments, you only need: - -| Variable | Default | Purpose | -| ------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| `REQUEST_TIMEOUT_MS` | `600000` | Shared baseline for upstream response-start timeout, hidden Undici timeouts, TLS fingerprint requests, and API bridge request/proxy timeouts | -| `STREAM_IDLE_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Maximum gap between streaming chunks before OmniRoute aborts the SSE stream | - -Backward compatibility is preserved: existing `FETCH_TIMEOUT_MS`, `API_BRIDGE_PROXY_TIMEOUT_MS`, and other per-layer timeout vars still work and override the shared baseline. - -For Claude Code-compatible upstreams (`anthropic-compatible-cc-*`), OmniRoute also derives the outbound `X-Stainless-Timeout` header from the resolved fetch timeout so provider-side read timeouts stay aligned with your env configuration. - -For third-party Claude Code-compatible reverse proxies, OmniRoute keeps the default -`anthropic-beta` set conservative and, when `Client Cache Control` is left on `Auto`, -only forwards client-provided `cache_control` markers. If the request does not include -`cache_control`, OmniRoute does not inject bridge-owned markers. - -Advanced overrides are available if you need finer control: - -| Variable | Default | Purpose | -| ---------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------- | -| `FETCH_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Upstream response-start timeout used until response headers arrive | -| `FETCH_HEADERS_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit for receiving upstream response headers | -| `FETCH_BODY_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit between upstream body chunks (`0` disables it) | -| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | Undici TCP connect timeout | -| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Undici idle keep-alive socket timeout | -| `TLS_CLIENT_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Timeout for TLS fingerprint requests made through `wreq-js` | -| `API_BRIDGE_PROXY_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` or `600000` | Timeout for `/v1` proxy forwarding from API port to dashboard port | -| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `max(API_BRIDGE_PROXY_TIMEOUT_MS, 300000)` | Incoming request timeout on the API bridge server | -| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Incoming header timeout on the API bridge server | -| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Keep-alive timeout on the API bridge server | -| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Socket inactivity timeout on the API bridge server (`0` disables it) | - -For streaming requests, `FETCH_TIMEOUT_MS` only covers connection setup / waiting for the first upstream response. Once the stream is active, OmniRoute will only abort on an actual stall (`STREAM_IDLE_TIMEOUT_MS`) or Undici body inactivity (`FETCH_BODY_TIMEOUT_MS`). - -If you run OmniRoute behind Nginx, Caddy, Cloudflare, or another reverse proxy, make sure the proxy -timeouts are also higher than your OmniRoute stream/fetch timeouts. - -### 2) Connect providers and create your API key - -1. Open Dashboard → `Providers` and connect at least one provider (OAuth or API key). -2. Open Dashboard → `Endpoints` and create an API key. -3. (Optional) Open Dashboard → `Combos` and set your fallback chain. - -### 3) Point your coding tool to OmniRoute +**3) Configura il tuo strumento di coding** ```txt Base URL: http://localhost:20128/v1 -API Key: [copy from Endpoint page] -Model: if/kimi-k2-thinking (or any provider/model prefix) +API Key: [copy from Dashboard → Endpoints] +Model: auto (zero-config smart routing — or any provider/model) ``` -### 4) Enable and validate protocols (v2.0) - -**MCP (for tool-driven operations):** +**4) Verifica che funzioni** ```bash -omniroute --mcp +curl http://localhost:20128/v1/models -H "Authorization: Bearer YOUR_KEY" ``` -Then connect your MCP client over `stdio` and test tools like: +Dovresti vedere elencati i modelli collegati. 🎉 Tutto qui — inizia a programmare: OmniRoute instrada automaticamente le richieste ed esegue il fallback quando serve. -- `omniroute_get_health` -- `omniroute_list_combos` - -**A2A (for agent-to-agent workflows):** - -```bash -curl http://localhost:20128/.well-known/agent.json -``` - -```bash -curl -X POST http://localhost:20128/a2a \ - -H 'content-type: application/json' \ - -d '{"jsonrpc":"2.0","id":"quickstart","method":"message/send","params":{"skill":"quota-management","messages":[{"role":"user","content":"Give me a short quota summary."}]}}' -``` - -### 5) Validate everything end-to-end (recommended) - -```bash -npm run test:protocols:e2e -``` - -This suite validates real MCP and A2A client flows against a running app. - -### Alternative: run from source - -```bash -cp .env.example .env -npm install -PORT=20128 DASHBOARD_PORT=20129 NEXT_PUBLIC_BASE_URL=http://localhost:20129 npm run dev -``` - -
-Void Linux (`xbps-src` template) - -For Void Linux users, you can build a native package using `xbps-src`. Save this block as `srcpkgs/omniroute/template`: - -```bash -# Template file for 'omniroute' -pkgname=omniroute -version=3.4.1 -revision=1 -hostmakedepends="nodejs python3 make" -depends="openssl" -short_desc="Universal AI gateway with smart routing for multiple LLM providers" -maintainer="zenobit " -license="MIT" -homepage="https://github.com/diegosouzapw/OmniRoute" -distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz" -checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b -system_accounts="_omniroute" -omniroute_homedir="/var/lib/omniroute" -export NODE_ENV=production -export npm_config_engine_strict=false -export npm_config_loglevel=error -export npm_config_fund=false -export npm_config_audit=false - -do_build() { - # Determine target CPU arch for node-gyp - local _gyp_arch - case "$XBPS_TARGET_MACHINE" in - aarch64*) _gyp_arch=arm64 ;; - armv7*|armv6*) _gyp_arch=arm ;; - i686*) _gyp_arch=ia32 ;; - *) _gyp_arch=x64 ;; - esac - - # 1) Install all deps – skip scripts (no network in do_build, native modules - # compiled separately below; better-sqlite3 is serverExternalPackage so - # Next.js does not execute it during next build) - NODE_ENV=development npm ci --ignore-scripts - - # 2) Build the Next.js standalone bundle - npm run build - - # 3) Copy static assets into standalone - cp -r .next/static .next/standalone/.next/static - [ -d public ] && cp -r public .next/standalone/public || true - - # 4) Compile better-sqlite3 native binding for the target architecture. - # Use node-gyp directly so CC/CXX from xbps-src cross-toolchain are used - # without npm altering them. - local _node_gyp=/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js - (cd node_modules/better-sqlite3 && node "$_node_gyp" rebuild --arch="$_gyp_arch") - - # 5) Place the compiled binding into the standalone bundle - local _bs3_release=.next/standalone/node_modules/better-sqlite3/build/Release - mkdir -p "$_bs3_release" - cp node_modules/better-sqlite3/build/Release/better_sqlite3.node "$_bs3_release/" - - # 6) Remove arch-specific sharp bundles – upstream sets images.unoptimized=true - # so sharp is not used at runtime; x64 .so files would break aarch64 strip - rm -rf .next/standalone/node_modules/@img - - # 7) Copy pino runtime deps omitted by Next.js static analysis: - # pino-abstract-transport – required by pino's worker thread - # split2 – dep of pino-abstract-transport - # process-warning – dep of pino itself - for _mod in pino-abstract-transport split2 process-warning; do - cp -r "node_modules/$_mod" .next/standalone/node_modules/ - done -} - -do_check() { - npm run test:unit -} - -do_install() { - vmkdir usr/lib/omniroute/.next - - vcopy .next/standalone/. usr/lib/omniroute/.next/standalone - - # Prevent removal of empty Next.js app router dirs by the post-install hook - for _d in \ - .next/standalone/.next/server/app/dashboard \ - .next/standalone/.next/server/app/dashboard/settings \ - .next/standalone/.next/server/app/dashboard/providers; do - touch "${DESTDIR}/usr/lib/omniroute/${_d}/.keep" - done - - cat > "${WRKDIR}/omniroute" <<'EOF' -#!/bin/sh -export PORT="${PORT:-20128}" -export DATA_DIR="${DATA_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/omniroute}" -export APP_LOG_TO_FILE="${APP_LOG_TO_FILE:-false}" -mkdir -p "${DATA_DIR}" -exec node /usr/lib/omniroute/.next/standalone/server.js "$@" -EOF - vbin "${WRKDIR}/omniroute" -} - -post_install() { - vlicense LICENSE -} -``` - -
- ---- - -## 🐳 Docker - -OmniRoute is available as a public Docker image on [Docker Hub](https://hub.docker.com/r/diegosouzapw/omniroute). - -**Quick run:** - -```bash -docker run -d \ - --name omniroute \ - --restart unless-stopped \ - --stop-timeout 40 \ - -p 20128:20128 \ - -v omniroute-data:/app/data \ - diegosouzapw/omniroute:latest -``` - -**With environment file:** - -```bash -# Copy and edit .env first -cp .env.example .env - -docker run -d \ - --name omniroute \ - --restart unless-stopped \ - --stop-timeout 40 \ - --env-file .env \ - -p 20128:20128 \ - -v omniroute-data:/app/data \ - diegosouzapw/omniroute:latest -``` - -**Using Docker Compose:** - -```bash -# Base profile (no CLI tools) -docker compose --profile base up -d - -# CLI profile (Claude Code, Codex, OpenClaw built-in) -docker compose --profile cli up -d -``` - -Dashboard support for Docker deployments now includes a one-click **Cloudflare Quick Tunnel** on `Dashboard → Endpoints`. The first enable downloads `cloudflared` only when needed, starts a temporary tunnel to your current `/v1` endpoint, and shows the generated `https://*.trycloudflare.com/v1` URL directly below your normal public URL. - -Notes: - -- Quick Tunnel URLs are temporary and change after every restart. -- Quick Tunnels are not auto-restored after an OmniRoute or container restart. Re-enable them from the dashboard when needed. -- Managed install currently supports Linux, macOS, and Windows on `x64` / `arm64`. -- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained container environments. Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want a different transport. -- Docker images bundle system CA roots and pass them to managed `cloudflared`, which avoids TLS trust failures when the tunnel bootstraps inside the container. -- SQLite runs in WAL mode. `docker stop` should be allowed to finish so OmniRoute can checkpoint the latest changes back into `storage.sqlite`. -- The bundled Compose files already set a 40s stop grace period. If you run the image directly, keep `--stop-timeout 40` (or similar) so manual stops do not cut off shutdown cleanup. -- Set `CLOUDFLARED_BIN=/absolute/path/to/cloudflared` if you want OmniRoute to use an existing binary instead of downloading one. - -**Using Docker Compose with Caddy (HTTPS Auto-TLS):** - -OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP. - -```yaml -services: - omniroute: - image: diegosouzapw/omniroute:latest - container_name: omniroute - restart: unless-stopped - volumes: - - omniroute-data:/app/data - environment: - - PORT=20128 - - NEXT_PUBLIC_BASE_URL=https://your-domain.com - - caddy: - image: caddy:latest - container_name: caddy - restart: unless-stopped - ports: - - "80:80" - - "443:443" - command: caddy reverse-proxy --from https://your-domain.com --to http://omniroute:20128 - -volumes: - omniroute-data: -``` - -| Image | Tag | Size | Description | -| ------------------------ | -------- | ------ | --------------------- | -| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release | -| `diegosouzapw/omniroute` | `3.6.2` | ~250MB | Current version | - ---- - -## 🖥️ Desktop App — Offline & Always-On - -> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux. - -Run OmniRoute as a standalone desktop app — no terminal, no browser, no internet required for local models. The Electron-based app includes: - -- 🖥️ **Native Window** — Dedicated app window with system tray integration -- 🔄 **Auto-Start** — Launch OmniRoute on system login -- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues -- ⚡ **One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux) -- 🌐 **Offline Mode** — Works fully offline with bundled server - -### Avvio Rapido - -```bash -# Development mode -npm run electron:dev - -# Build for your platform -npm run electron:build # Current platform -npm run electron:build:win # Windows (.exe) -npm run electron:build:mac # macOS (.dmg) — x64 & arm64 -npm run electron:build:linux # Linux (.AppImage) -``` - -### System Tray - -When minimized, OmniRoute lives in your system tray with quick actions: - -- Open dashboard -- Change server port -- Quit application - -📖 Full documentation: [`electron/README.md`](electron/README.md) - ---- - -## 💰 Pricing at a Glance - -| Tier | Provider | Cost | Quota Reset | Best For | -| ------------------- | --------------------------- | ------------------------------------- | --------------------- | ---------------------------------- | -| **💳 SUBSCRIPTION** | Claude Code (Pro) | $20/mo | 5h + weekly | Already subscribed | -| | Codex (Plus/Pro) | $20-200/mo | 5h + weekly | OpenAI users | -| | GitHub Copilot | $10-19/mo | Monthly | GitHub users | -| **🔑 API KEY** | NVIDIA NIM | **FREE ACCESS** (current terms apply) | ~40 RPM | 70+ open models | -| | Cerebras | **FREE** (1M tok/day) | 60K TPM / 30 RPM | World's fastest | -| | Groq | **FREE** (30 RPM) | 14.4K RPD | Ultra-fast Llama/Gemma | -| | DeepSeek V3.2 | $0.27/$1.10 per 1M | None | Best price/quality reasoning | -| | xAI Grok-4 Fast | **$0.20/$0.50 per 1M** 🆕 | None | Fastest + tool calling, ultralow | -| | xAI Grok-4 (standard) | $0.20/$1.50 per 1M 🆕 | None | Reasoning flagship from xAI | -| | Mistral | Free trial + paid | Rate limited | European AI | -| | OpenRouter | Pay-per-use | None | 100+ models aggr. | -| **💰 CHEAP** | GLM-5 (via Z.AI) 🆕 | $0.5/1M | Daily 10AM | 128K output, newest flagship | -| | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup | -| | MiniMax M2.5 🆕 | $0.3/1M input | 5-hour rolling | Reasoning + agentic tasks | -| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option | -| | Kimi K2.5 (Moonshot API) 🆕 | Pay-per-use | None | Direct Moonshot API access | -| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost | -| **🆓 FREE ACCESS** | Qoder | **$0** | Limits apply | Selected models; terms apply | -| | Qwen | **$0** | Limits apply | Selected models; terms apply | -| | Kiro | **$0** | Credit/account limits | Claude access; current terms apply | -| | LongCat signup credit | **$0** (10M one-time; KYC) | One-time | Signup grant; not recurring | -| | Pollinations AI 🆕 | **$0** (no key needed) | 1 req/15s | GPT-5, Claude, DeepSeek, Llama 4 | -| | Cloudflare Workers AI 🆕 | **$0** (10K Neurons/day) | ~150 resp/day | 50+ models, global edge | -| | Scaleway AI 🆕 | **$0** (1M tokens total) | Rate limited | EU/GDPR, Qwen3 235B, Llama 70B | - -> 🆕 **New models added (Mar 2026):** Grok-4 Fast family at $0.20/$0.50/M (benchmarked at 1143ms — 30% faster than Gemini 2.5 Flash), GLM-5 via Z.AI with 128K output, MiniMax M2.5 reasoning, DeepSeek V3.2 updated pricing, Kimi K2.5 via Moonshot direct API. - -**💡 $0 Combo Stack — The Complete Free Setup:** - -``` -# 🆓 Free-access examples — provider limits and terms apply -Kiro (kr/) → Claude access — account/credit limits apply -Qoder (if/) → selected models — no published token cap; rate/account limits apply -LongCat (lc/) → LongCat-2.0 — 10M one-time signup credit; KYC required -Pollinations (pol/) → GPT-5, Claude, DeepSeek, Llama 4 — no key needed -Qwen (qw/) → selected models — no published token cap; rate/account limits apply -Gemini (gemini/) → selected free-tier models — current API quotas apply -Cloudflare AI (cf/) → Llama 70B, Gemma 3, Mistral — 10K Neurons/day -Scaleway (scw/) → Qwen3 235B, Llama 70B — 1M free tokens (EU) -Groq (groq/) → selected models — current per-model rate limits apply -NVIDIA NIM (nvidia/) → selected models — current rate limits apply -Cerebras (cerebras/) → Llama/Qwen world-fastest — 1M tok/day -``` - -**Current $0 access where listed; availability is not guaranteed.** A combo can try the next eligible route when a quota or upstream fails. - ---- - ---- - -## 🆓 Free Models — What You Actually Get - -> The entries below summarize access that was listed as free when audited. Provider quotas, card/account/KYC requirements, models, regions and terms can change. A combo broadens fallback coverage but does not guarantee uninterrupted $0 access. - -### 🔵 CLAUDE MODELS (via Kiro — AWS Builder ID) - -| Model | Prefix | Limit | Rate Limit | -| ------------------- | ------ | ------------- | --------------------- | -| `claude-sonnet-4.5` | `kr/` | No published token cap | Provider/account limits may apply | -| `claude-haiku-4.5` | `kr/` | No published token cap | Provider/account limits may apply | -| `claude-opus-4.6` | `kr/` | No published token cap | Latest Opus; provider/account limits apply | - -### 🟢 QODER MODELS (Free PAT via qodercli) - -| Model | Prefix | Limit | Rate Limit | -| ------------------ | ------ | ------------- | --------------- | -| `kimi-k2-thinking` | `if/` | No published token cap | Provider/account limits may apply | -| `qwen3-coder-plus` | `if/` | No published token cap | Provider/account limits may apply | -| `deepseek-r1` | `if/` | No published token cap | Provider/account limits may apply | -| `minimax-m2.1` | `if/` | No published token cap | Provider/account limits may apply | -| `kimi-k2` | `if/` | No published token cap | Provider/account limits may apply | - -> Recommended connection method: **Personal Access Token + `qodercli`**. Browser OAuth is -> experimental and disabled by default unless `QODER_OAUTH_*` environment variables are configured. - -### 🟡 QWEN MODELS (Device Code Auth) - -| Model | Prefix | Limit | Rate Limit | -| ------------------- | ------ | ------------- | ------------------- | -| `qwen3-coder-plus` | `qw/` | No published token cap | Provider/account limits may apply | -| `qwen3-coder-flash` | `qw/` | No published token cap | Provider/account limits may apply | -| `qwen3-coder-next` | `qw/` | No published token cap | Provider/account limits may apply | -| `vision-model` | `qw/` | No published token cap | Multimodal; provider/account limits may apply | - -### ⚫ NVIDIA NIM (Free API Key — build.nvidia.com) - -| Tier | Daily Limit | Rate Limit | Notes | -| ---------- | ------------ | ----------- | ------------------------------------------------------ | -| Free (Dev) | No token cap | **~40 RPM** | 70+ models; transitioning to pure rate limits mid-2025 | - -Popular free models: `moonshotai/kimi-k2.5` (Kimi K2.5), `z-ai/glm4.7` (GLM 4.7), `deepseek-ai/deepseek-v3.2` (DeepSeek V3.2), `nvidia/llama-3.3-70b-instruct`, `deepseek/deepseek-r1` - -### ⚪ CEREBRAS (Free API Key — inference.cerebras.ai) - -| Tier | Daily Limit | Rate Limit | Notes | -| ---- | ----------------- | ---------------- | ------------------------------------------- | -| Free | **1M tokens/day** | 60K TPM / 30 RPM | World's fastest LLM inference; resets daily | - -Available free: `llama-3.3-70b`, `llama-3.1-8b`, `deepseek-r1-distill-llama-70b` - -### 🔴 GROQ (Free API Key — console.groq.com) - -| Tier | Daily Limit | Rate Limit | Notes | -| ---- | ------------- | ---------------- | ----------------------------------------- | -| Free | **14.4K RPD** | 30 RPM per model | No credit card; 429 on limit, not charged | - -Available free: `llama-3.3-70b-versatile`, `gemma2-9b-it`, `mixtral-8x7b`, `whisper-large-v3` - -### 🔴 LONGCAT AI (Signup credit — KYC required) - -| Model | Prefix | Current catalog grant | Notes | -| ------------- | ------ | ----------------------- | --------------------------------------------------- | -| `LongCat-2.0` | `lc/` | **10M tokens one-time** | Signup grant; not a recurring monthly or daily pool | - -> Provider terms, eligibility and model availability can change. See [`FREE_TIERS.md`](../../reference/FREE_TIERS.md) for the audited catalog entry. - -### 🟢 POLLINATIONS AI (No API Key Required) 🆕 - -| Model | Prefix | Rate Limit | Provider Behind | -| ---------- | ------ | ---------- | ------------------ | -| `openai` | `pol/` | 1 req/15s | GPT-5 | -| `claude` | `pol/` | 1 req/15s | Anthropic Claude | -| `gemini` | `pol/` | 1 req/15s | Google Gemini | -| `deepseek` | `pol/` | 1 req/15s | DeepSeek V3 | -| `llama` | `pol/` | 1 req/15s | Meta Llama 4 Scout | -| `mistral` | `pol/` | 1 req/15s | Mistral AI | - -> ✨ **Zero friction:** No signup, no API key. Add the Pollinations provider with an empty key field and it works immediately. - -### 🟠 CLOUDFLARE WORKERS AI (Free API Key — cloudflare.com) 🆕 - -| Tier | Daily Neurons | Equivalent Usage | Notes | -| ---- | ------------- | --------------------------------------- | ----------------------- | -| Free | **10,000** | ~150 LLM resp / 500s audio / 15K embeds | Global edge, 50+ models | - -Popular free models: `@cf/meta/llama-3.3-70b-instruct`, `@cf/google/gemma-3-12b-it`, `@cf/openai/whisper-large-v3-turbo` (free audio!), `@cf/qwen/qwen2.5-coder-15b-instruct` - -> Requires API Token + Account ID from [dash.cloudflare.com](https://dash.cloudflare.com). Store Account ID in provider settings. - -### 🟣 SCALEWAY AI (1M Free Tokens — scaleway.com) 🆕 - -| Tier | Free Quota | Location | Notes | -| ---- | ------------- | ------------ | ----------------------------------- | -| Free | **1M tokens** | 🇫🇷 Paris, EU | No credit card needed within limits | - -Available free: `qwen3-235b-a22b-instruct-2507` (Qwen3 235B!), `llama-3.1-70b-instruct`, `mistral-small-3.2-24b-instruct-2506`, `deepseek-v3-0324` - -> EU/GDPR compliant. Get API key at [console.scaleway.com](https://console.scaleway.com). - -> **💡 Free-access examples (provider limits and terms apply):** -> -> ``` -> Kiro (kr/) → Claude access — account/credit limits apply -> Qoder (if/) → selected models — no published token cap; limits apply -> LongCat (lc/) → LongCat-2.0 — 10M one-time signup credit; KYC required -> Pollinations (pol/) → GPT-5, Claude, DeepSeek, Llama 4 — no key needed -> Qwen (qw/) → selected models — no published token cap; limits apply -> Gemini (gemini/) → selected free-tier models — current quotas apply -> Cloudflare AI (cf/) → 50+ models — 10K Neurons/day -> Scaleway (scw/) → Qwen3 235B, Llama 70B — 1M free tokens (EU) -> Groq (groq/) → selected models — current per-model rate limits apply -> NVIDIA NIM (nvidia/) → selected models — current rate limits apply -> Cerebras (cerebras/) → Llama/Qwen world-fastest — 1M tok/day -> ``` - -## 🎙️ Free Transcription Combo - -> Transcription access depends on each upstream allowance — Deepgram and AssemblyAI signup credits can lead, with Groq Whisper as a rate-limited fallback. - -| Provider | Free Credits | Best Model | Rate Limit | -| ----------------- | --------------------------- | -------------------------------------------- | ---------------------------------------- | -| 🟢 **Deepgram** | **$200 free** (signup) | `nova-3` — best accuracy, 30+ languages | No RPM limit on free credits | -| 🔵 **AssemblyAI** | **$50 free** (signup) | `universal-3-pro` — chapters, sentiment, PII | No RPM limit on free credits | -| 🔴 **Groq** | **Free tier; limits apply** | `whisper-large-v3` — OpenAI Whisper | Current model-specific rate limits apply | - -**Suggested combo in `/dashboard/combos`:** - -``` -Name: free-transcription -Strategy: Priority -Nodes: - [1] deepgram/nova-3 → uses $200 free first - [2] assemblyai/universal-3-pro → fallback when Deepgram credits run out - [3] groq/whisper-large-v3 → free access; rate limits apply -``` - -Then in `/dashboard/media` → **Transcription** tab: upload any audio or video file → select your combo endpoint → get transcription in supported formats. - -## 💡 Key Features - -OmniRoute v3.6 is built as an operational platform, not just a relay proxy. - -### 🆕 New — v3.6.x Highlights (Apr 2026) - -| Feature | What It Does | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🌐 **V1 WebSocket Bridge** | OpenAI-compatible WebSocket traffic upgraded and proxied via `/v1/ws` — full streaming over WS with session auth (API key or session cookie) | -| 🔑 **Sync Tokens & Config Bundle** | Issue/revoke sync tokens for config sync endpoints. Config bundles versioned with ETag for bandwidth-efficient polling | -| 🧠 **GLM Thinking (glmt) Preset** | GLM Thinking registered first-class: 65 536 max tokens, 24 576 thinking budget, 900s timeout, usage sync & pricing — Claude-compatible API | -| 🔢 **Hybrid Token Counting** | Uses provider-side `/messages/count_tokens` when available; falls back to estimation — accurate usage tracking without guessing | -| 🌱 **Model Alias Auto-Seed** | 30+ cross-proxy dialect aliases normalised at startup — no more routing mismatches | -| 🛡️ **Safe Outbound Fetch** | All provider validation and model discovery go through a guarded fetch layer blocking private/local URLs with retry, timeout, and SSRF protection | -| ⏳ **Wait For Cooldown** | Server-side chat retries when every candidate connection is cooling down; configurable `enabled`, `maxRetries`, and `maxRetryWaitSec` | -| 🔍 **Runtime Env Validation** | Startup validates all env vars with Zod schemas — clear errors for missing secrets, invalid URLs, or wrong types | -| 📋 **Compliance Audit Expansion** | Structured audit logs with pagination, request context, auth events, provider CRUD events, and SSRF-blocked validation logging | -| 🔐 **TPS Log Metric** | Log details modal shows Tokens Per Second (TPS) — quick performance at-a-glance for every request | -| 🗑️ **Uninstall / Full Uninstall** | `npm run uninstall` keeps data, `npm run uninstall:full` removes everything — clean removal for all install methods | -| 🔧 **OAuth Env Repair** | One-click "Repair env" action for OAuth providers restores missing env vars and fixes broken auth state | -| 🔒 **Graceful Electron Shutdown** | Electron `before-quit` shuts down Next.js gracefully, preventing SQLite WAL database locks on desktop close | -| 👁️ **Model Visibility Toggle** | Per-model visibility toggle (👁 icon) with search filter and active-count badge (`N/M active`) on provider pages | -| 📧 **Email Privacy Masking** | OAuth account emails masked (`di*****@g****.com`), full address visible on hover | -| 🔗 **Context Relay Strategy** | Combo strategy preserving session continuity via structured handoff summaries when accounts rotate mid-conversation | -| 🛡️ **Proxy Hardening** | Token health check, API key validation, and undici dispatcher all honor proxy config | -| ⚠️ **Node.js 24 Login Warning** | Login page proactively detects incompatible Node.js versions and shows a clear warning banner | -| 📎 **Gemini PDF Attachments** | PDF attachments correctly routed to Gemini via `inline_data` and generic base64 detection | -| 🔒 **CodeQL Security Hardening** | Resolved SSRF, insecure randomness, polynomial ReDoS, and incomplete URL sanitization alerts | - -### 🆕 New — ClawRouter-Inspired Improvements (Mar 2026) - -| Feature | What It Does | -| ------------------------------------ | ------------------------------------------------------------------------------------------- | -| ⚡ **Grok-4 Fast Family** | xAI models at $0.20/$0.50/M — benchmarked 1143ms (30% faster than Gemini 2.5 Flash) | -| 🧠 **GLM-5 via Z.AI** | 128K output context, $0.5/1M — newest flagship from the GLM family | -| 🔮 **MiniMax M2.5** | Reasoning + agentic tasks at $0.30/1M — significant upgrade from M2.1 | -| 🎯 **toolCalling Flag per Model** | Per-model `toolCalling: true/false` in registry — AutoCombo skips non-tool-capable models | -| 🌍 **Multilingual Intent Detection** | PT/ZH/ES/AR keywords in AutoCombo scoring — better model selection for non-English content | -| 📊 **Benchmark-Driven Fallbacks** | Real p95 latency from live requests feeds combo scoring — AutoCombo learns from actual data | -| 🔁 **Request Deduplication** | Content-hash based dedup window — multi-agent safe, prevents duplicate charges | -| 🔌 **Pluggable RouterStrategy** | Extensible `RouterStrategy` interface — add custom routing logic as plugins | - -### 🚀 Previous v2.0.9+ — Playground, CLI Fingerprints & ACP - -| Feature | What It Does | -| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing | -| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** | -| 🤖 **ACP Agents Dashboard** | Debug › Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool. **OpenCode** users get a "Download opencode.json" button that auto-generates a ready-to-use config with all available models. | -| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator | -| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID | -| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart | - -### 🤖 Agent & Protocol Operations (v2.0) - -| Feature | What It Does | -| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🔧 **MCP Server (107 tools)** | IDE/agent tools via 3 transports: stdio, SSE (`/api/mcp/sse`), Streamable HTTP (`/api/mcp/stream`). 107 unique tools across the registered tool families; enabled skills may add dynamic tools at runtime | -| 🤝 **A2A Server (JSON-RPC + SSE)** | Agent-to-agent task execution with sync and streaming flows | -| 🧭 **Consolidated Endpoints Page** | Tabbed management page with Endpoint Proxy, MCP, A2A, and API Endpoints tabs | -| 🎚️ **Service Enable/Disable Toggles** | ON/OFF switches for MCP and A2A with settings persistence (default: OFF) | -| 🛰️ **MCP Runtime Heartbeat** | Real process status (pid, uptime, heartbeat age, transport, scope mode) | -| 📋 **MCP Audit Trail** | Filterable audit logs with success/failure and key attribution | -| 🔐 **MCP Scope Enforcement** | 32 granular scope permissions for controlled tool access | -| 📡 **A2A Task Lifecycle Management** | List/filter tasks, inspect events/artifacts, cancel running tasks | -| 📋 **Agent Card Discovery** | `/.well-known/agent.json` for client auto-discovery | -| 🧪 **Protocol E2E Test Harness** | Real MCP SDK + A2A client flows in `test:protocols:e2e` | -| ⚙️ **Operational Controls** | Switch combos, tune resilience settings, and review breaker state from dedicated Health and Settings surfaces | - -### 🧠 Routing & Intelligence - -| Feature | What It Does | -| ---------------------------------- | ------------------------------------------------------------------------ | -| 🎯 **Smart 4-Tier Fallback** | Auto-route: Subscription → API Key → Cheap → Free | -| 📊 **Real-Time Quota Tracking** | Live token count + reset countdown per provider | -| 🔄 **Format Translation** | OpenAI ↔ Claude ↔ Gemini ↔ Responses with schema-safe conversions | -| 👥 **Multi-Account Support** | Multiple accounts per provider with intelligent selection | -| 🔄 **Auto Token Refresh** | OAuth tokens refresh automatically with retry | -| 🎨 **Custom Combos** | 13 balancing strategies + fallback chain control | -| 🔗 **Context Relay** | Session continuity handoffs when account rotation happens mid-session | -| 🌐 **Wildcard Router** | `provider/*` dynamic routing | -| 🧠 **Thinking Budget Controls** | Passthrough, auto, custom, and adaptive reasoning limits | -| 🔀 **Model Aliases** | Built-in + custom model aliasing and migration safety | -| ⚡ **Background Degradation** | Route low-priority background tasks to cheaper models | -| 🧪 **Task-Aware Smart Routing** | Auto-select model by content type (coding/vision/analysis/summarization) | -| 🔄 **A2A Agent Workflows** | Deterministic FSM orchestrator for stateful multi-step agent executions | -| 🔀 **Adaptive Routing** | Dynamic strategy override based on token volume and prompt complexity | -| 🎲 **Provider Diversity** | Shannon entropy scoring balancing auto-combo traffic distribution | -| 💬 **System Prompt Injection** | Global behavior controls applied consistently | -| 📄 **Responses API Compatibility** | Full `/v1/responses` support for Codex and advanced agentic workflows | - -### 🎵 Multi-Modal APIs - -| Feature | What It Does | -| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🖼️ **Image Generation** | `/v1/images/generations` with cloud and local backends | -| 📐 **Embeddings** | `/v1/embeddings` for search and RAG pipelines | -| 🎤 **Audio Transcription** | `/v1/audio/transcriptions` — 7 providers (Deepgram Nova 3, AssemblyAI, Groq Whisper, HuggingFace, ElevenLabs, OpenAI, Azure), auto-language detection, MP4/MP3/WAV support | -| 🔊 **Text-to-Speech** | `/v1/audio/speech` — 10 providers (ElevenLabs, OpenAI, Deepgram, Cartesia, PlayHT, HuggingFace, Nvidia NIM, Inworld, Coqui, Tortoise) with correct error messages | -| 🎬 **Video Generation** | `/v1/videos/generations` (ComfyUI + SD WebUI workflows) | -| 🎵 **Music Generation** | `/v1/music/generations` (ComfyUI workflows) | -| 🛡️ **Moderations** | `/v1/moderations` safety checks | -| 🔀 **Reranking** | `/v1/rerank` for relevance scoring | -| 🔍 **Web Search** 🆕 | `/v1/search` — 5 providers (Serper, Brave, Perplexity, Exa, Tavily), 6,500+ free/month, auto-failover, cache | - -### 🛡️ Resilience, Security & Governance - -| Feature | What It Does | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------- | -| 🔌 **Provider Circuit Breakers** | Provider-wide trip/recover after fallback exhaustion with configurable thresholds | -| 🔒 **Daily Quota Lock** 🆕 | Detects exhaustion signals and locks routing for the specific model until midnight | -| 🎯 **Endpoint-Aware Models** | Custom models declare supported endpoints + API format | -| 🛡️ **Anti-Thundering Herd** | Mutex + semaphore protections on retry/rate events | -| 🧠 **Semantic + Signature Cache** | Cost/latency reduction with two cache layers | -| ⚡ **Request Idempotency** | Duplicate protection window | -| 🔒 **TLS Fingerprint Spoofing** | Browser-like TLS fingerprint — **reduces bot detection and account flagging** | -| 🔏 **CLI Fingerprint Matching** | Matches native CLI request signatures — **reduces ban risk while preserving proxy IP** | -| 🌐 **IP Filtering** | Allowlist/blocklist control for exposed deployments | -| 🚦 **Request Queue & Pacing** | Configurable per-connection request buckets for RPM, spacing, concurrency, and max wait | -| 📉 **Graceful Degradation** | Multi-layer capability fallbacks protecting core gateway operations | -| 📜 **Config Audit Trail** | Diff-based change tracking preventing operational drift with simple rollbacks | -| ⏳ **Provider Health Sync** | Proactive token expiration monitoring triggering alerts before authorization failures | -| ❄️ **Connection Cooldown** | Retryable 408/429/5xx failures cool down a single connection with optional upstream hints | -| 🚪 **Auto-Disable Banned Accounts** | Permanently blocked token accounts can be disabled automatically | -| 🔑 **API Key Management + Scoping** | Secure key issuance/rotation and model/provider controls | -| 👁️ **Scoped API Key Reveal** 🆕 | Opt-in recovery of API keys via `ALLOW_API_KEY_REVEAL` | -| 🛡️ **Protected `/models`** | Optional auth gating and provider hiding for model catalog | -| 🛡️ **Safe Outbound Fetch** 🆕 | Guarded fetch for provider calls — blocks private/local URLs, retries, SSRF protection | -| ⏳ **Wait For Cooldown** 🆕 | Auto-retry chat after connection cooldowns; configurable `enabled`, `maxRetries`, and `maxRetryWaitSec` | -| 🔍 **Runtime Env Validation** 🆕 | Zod-based env schema validation at startup with actionable error messages | -| 📋 **Compliance Audit v2** 🆕 | Pagination, request context, auth events, provider CRUD, and SSRF-blocked logging | - -### 📊 Observability & Analytics - -| Feature | What It Does | -| -------------------------------- | ----------------------------------------------------- | -| 📝 **Request + Proxy Logging** | Full request/response and proxy logging | -| 📉 **Streamed Detailed Logs** | Reconstructs SSE payload streams cleanly into the UI | -| 🏷️ **Real-Time Model Badges** 🆕 | Live model status and daily quota countdown timers | -| 📋 **Unified Logs Dashboard** | Request, proxy, audit, and console views in one page | -| 🔍 **Request Telemetry** | p50/p95/p99 latency and request tracing | -| 🏥 **Health Dashboard** | Uptime, breaker states, lockouts, cache stats | -| 💰 **Cost Tracking** | Budget controls and per-model pricing visibility | -| 📈 **Analytics Visualizations** | Model/provider usage insights and trend views | -| 🧪 **Evaluation Framework** | Golden set testing with configurable match strategies | -| 📡 **Live Diagnostics** 🆕 | Semantic cache bypass for accurate combo live testing | -| 🔐 **TPS Log Metric** 🆕 | Tokens Per Second badge in log details modal | - -### ☁️ Deployment & Platform - -| Feature | What It Does | -| ------------------------------ | --------------------------------------------------------------------- | -| 🌐 **Deploy Anywhere** | Localhost, VPS, Docker, Cloud environments | -| 🚇 **Cloudflare Tunnel** 🆕 | One-click Quick Tunnel integration from the dashboard | -| 🔑 **API Key Model Filtering** | Native /v1/models response filtered via assigned Bearer context roles | -| ⚡ **Smart Cache Bypass** | Configurable TTL heuristics and forced refetch controls | -| 🔄 **Backup/Restore** | Export/import and disaster recovery flows | -| 🧙 **Onboarding Wizard** | First-run guided setup | -| 🔧 **CLI Tools Dashboard** | One-click setup for popular coding tools | -| 🎮 **Model Playground** | Test any provider/model/endpoint from the dashboard | -| 🔏 **CLI Fingerprint Toggle** | Per-provider fingerprint matching in Settings > Security | -| 🌐 **i18n (30 languages)** | Full dashboard + docs language support with RTL coverage | -| 🧹 **Clear All Models** | One-click model list clearing in provider details | -| 👁️ **Sidebar Controls** 🆕 | Hide components and integrations from Appearance Settings | -| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features | -| 📂 **Custom Data Directory** | `DATA_DIR` override for storage location | -| 🌐 **V1 WebSocket Bridge** 🆕 | OpenAI-compatible WebSocket traffic proxied via `/v1/ws` | -| 🔑 **Sync Tokens & Bundle** 🆕 | Config sync tokens + versioned bundle endpoint with ETag support | - -### Feature Deep Dive - -#### Smart fallback with practical cost control +Se il tuo client non può inviare header personalizzati, OmniRoute espone anche alias di compatibilità con token incorporato: ```txt -Combo: "my-coding-stack" - 1. cc/claude-opus-4-7 - 2. nvidia/llama-3.3-70b - 3. glm/glm-4.7 - 4. if/kimi-k2-thinking +OpenAI catalog: http://localhost:20128/vscode/YOUR_KEY/ +OpenAI models: http://localhost:20128/vscode/YOUR_KEY/models +OpenAI chat: http://localhost:20128/vscode/YOUR_KEY/chat/completions +OpenAI responses: http://localhost:20128/vscode/YOUR_KEY/responses +Ollama chat: http://localhost:20128/vscode/YOUR_KEY/api/chat +Ollama tags: http://localhost:20128/vscode/YOUR_KEY/api/tags ``` -When quota, rate, or health fails, OmniRoute automatically moves to the next candidate without manual switching. +Usali solo con client che non possono aggiungere `Authorization: Bearer ...`. L'autenticazione tramite header resta la modalità consigliata. -#### Protocol management that is visible and operable +
-- MCP + A2A are discoverable in UI and docs (not hidden) -- Protocol status APIs expose live operational data (`/api/mcp/*`, `/api/a2a/*`) -- Dashboards include actions for day-2 ops (combo toggles, breaker resets, task cancellation) + +## 📦 Altri metodi di installazione — Docker, sorgente, pnpm, Arch -#### Translator + validation workflow - -The Translator area includes: - -- **Playground**: request transformation checks -- **Chat Tester**: full request/response round-trip -- **Test Bench**: multiple cases in one run -- **Live Monitor**: real-time traffic view - -Plus protocol validation with real clients via `npm run test:protocols:e2e`. - -> 📖 **[MCP Server README](open-sse/mcp-server/README.md)** — Tool reference, IDE configs, and client examples -> -> 📖 **[A2A Server README](src/lib/a2a/README.md)** — Skills, JSON-RPC methods, streaming, and task lifecycle - -## 🧪 Evaluations (Evals) - -OmniRoute includes a built-in evaluation framework to test LLM response quality against a golden set. Access it via **Analytics → Evals** in the dashboard. - -### Built-in Golden Set - -The pre-loaded "OmniRoute Golden Set" contains test cases for: - -- Greetings, math, geography, code generation -- JSON format compliance, translation, markdown generation -- Safety refusal (harmful content), counting, boolean logic - -### Evaluation Strategies - -| Strategy | Description | Example | -| ---------- | ------------------------------------------------ | -------------------------------- | -| `exact` | Output must match exactly | `"4"` | -| `contains` | Output must contain substring (case-insensitive) | `"Paris"` | -| `regex` | Output must match regex pattern | `"1.*2.*3"` | -| `custom` | Custom JS function returns true/false | `(output) => output.length > 10` | - ---- - -## 📖 Setup Guide - -### Protocol Setup (MCP + A2A) - -
-🧩 MCP Setup (Model Context Protocol) - -Start MCP transport in stdio mode: +**🐳 Docker** ```bash -omniroute --mcp +docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \ + -p 127.0.0.1:20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest ``` -Recommended validation flow: +`:latest` segue la versione SemVer stabile **pubblicata** più alta. Non segue il branch git `main`. Per GitOps, fissa `:X.Y.Z`. Vedi [Canali di release Docker](../../guides/DOCKER_GUIDE.md#release-channels). L'immagine imposta **`OMNIROUTE_MEMORY_MB=1024`**. È sufficiente per la dashboard e una chat leggera. I **coding agent** (`POST /v1/responses` da Claude Code, Codex, Grok, …) richiedono un heap V8 molto più grande, altrimenti il processo va in `FATAL ERROR` a ~12 GiB con due contesti lunghi sovrapposti. Dimensiona il container oltre l'heap (i buffer nativi si trovano fuori da V8): -1. Connect your MCP client over stdio. -2. Run `omniroute_get_health`. -3. Run `omniroute_list_combos`. -4. Open `/dashboard/mcp` to confirm heartbeat, activity, and audit. - -Useful APIs for automation: - -- `GET /api/mcp/status` -- `GET /api/mcp/tools` -- `GET /api/mcp/audit` -- `GET /api/mcp/audit/stats` - -
- -
-🤝 A2A Setup (Agent2Agent) - -Discover the agent: +| Carico di lavoro | Heap (`-e OMNIROUTE_MEMORY_MB`) | Container (`--memory`) | +| ----------------------------------- | ------------------------------- | ---------------------- | +| Dashboard / chat leggera | `1024` (predefinito immagine) | ≥2 g | +| Un coding agent | `8192` | ≥10 g | +| Due `/v1/responses` lunghe simultanee | `10240`–`12288` | ≥12–16 g | ```bash -curl http://localhost:20128/.well-known/agent.json +docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \ + -e OMNIROUTE_MEMORY_MB=8192 --memory=10g \ + -p 127.0.0.1:20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest ``` -Send a task: +Tabella completa: [Guida Docker — RAM di runtime](../../guides/DOCKER_GUIDE.md#runtime-ram-for-coding-agents). + +> **Canale Docker pre-release:** `diegosouzapw/omniroute:next` e +> `diegosouzapw/omniroute:next-web` seguono l'attuale branch `release/v*` predefinito. +> Questi tag mutabili sono destinati esclusivamente al test di fix non ancora rilasciati e +> **non sono supportati in produzione**. Vedi +> [Canali di release Docker](../../guides/DOCKER_GUIDE.md#release-channels). + +**🥟 Bun** + +Sono supportati `bun install` standard e l'installazione globale (`bun install -g omniroute`) tramite rilevamento del runtime Bun: +- **`bun:sqlite` integrato**: OmniRoute usa il driver integrato `bun:sqlite` quando gira con Bun, con fallback a `better-sqlite3` su Node.js o a `sql.js`. +- **Selezione automatica del bundler Webpack**: sviluppo (`bun run dev`) e build di produzione (`bun run build`) rilevano automaticamente Bun e disabilitano Turbopack a favore di Webpack per evitare incompatibilità dei binding V8 nativi. +- **Dockerfile Bun dedicato**: `Dockerfile.bun` multi-stage per deployment di produzione nativi Bun (`docker build -f Dockerfile.bun -t omniroute:bun .`). ```bash -curl -X POST http://localhost:20128/a2a \ - -H 'content-type: application/json' \ - -d '{"jsonrpc":"2.0","id":"setup-a2a","method":"message/send","params":{"skill":"quota-management","messages":[{"role":"user","content":"Summarize quota status."}]}}' +# Install and run with Bun +bun install +bun run dev ``` -Manage lifecycle: - -- `GET /api/a2a/status` -- `GET /api/a2a/tasks` -- `GET /api/a2a/tasks/:id` -- `POST /api/a2a/tasks/:id/cancel` - -Operational UI: - -- `/dashboard/a2a` for task/state/stream observability and smoke actions - -
- -
-🧪 End-to-end protocol validation - -Validate both protocols with real clients: +**🛠️ Da sorgente** ```bash -npm run test:protocols:e2e +cp .env.example .env && npm install +PORT=20128 npm run dev ``` -This verifies: - -- MCP SDK client connect/list/call -- A2A discovery/send/stream/get/cancel -- Cross-check data in MCP audit and A2A task management APIs - -
- -
-💳 Subscription Providers - -### Claude Code (Pro/Max) +**📦 pnpm** ```bash -Dashboard → Providers → Connect Claude Code -→ OAuth login → Auto token refresh -→ 5-hour + weekly quota tracking - -Models: - cc/claude-opus-4-7 - cc/claude-sonnet-4-5-20250929 - cc/claude-haiku-4-5-20251001 +pnpm add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core && omniroute ``` -**Pro Tip:** Use Opus for complex tasks, Sonnet for speed. OmniRoute tracks quota per model! - -### OpenAI Codex (Plus/Pro) +**🐧 Arch Linux (AUR)** ```bash -Dashboard → Providers → Connect Codex -→ OAuth login (port 1455) -→ 5-hour + weekly reset - -Models: - cx/gpt-5.2-codex - cx/gpt-5.1-codex-max +yay -S omniroute-bin && systemctl --user enable --now omniroute.service ``` -#### Codex Account Limit Management (5h + Weekly) - -Each Codex account now has policy toggles in `Dashboard -> Providers`: - -- `5h` (ON/OFF): enforce the 5-hour window threshold policy. -- `Weekly` (ON/OFF): enforce the weekly window threshold policy. -- Threshold behavior: when an enabled window reaches >=90% usage, that account is skipped. -- Rotation behavior: OmniRoute routes to the next eligible Codex account automatically. -- Reset behavior: when the provider `resetAt` time passes, the account becomes eligible again automatically. - -Scenarios: - -- `5h ON` + `Weekly ON`: account is skipped when either window reaches threshold. -- `5h OFF` + `Weekly ON`: only weekly usage can block the account. -- `5h ON` + `Weekly OFF`: only 5-hour usage can block the account. -- `resetAt` passed: account re-enters rotation automatically (no manual re-enable). - -### GitHub Copilot +**🔧 Nix (Flake)** ```bash -Dashboard → Providers → Connect GitHub -→ OAuth via GitHub -→ Monthly reset (1st of month) - -Models: - gh/gpt-5 - gh/claude-4.5-sonnet - gh/gemini-3.1-pro-preview -``` - -
- -
-🔑 API Key Providers - -### NVIDIA NIM (FREE developer access — 70+ models) - -1. Sign up: [build.nvidia.com](https://build.nvidia.com) -2. Get free API key (1000 inference credits included) -3. Dashboard → Add Provider → NVIDIA NIM: - - API Key: `nvapi-your-key` - -**Models:** `nvidia/llama-3.3-70b-instruct`, `nvidia/mistral-7b-instruct`, and 50+ more - -**Pro Tip:** OpenAI-compatible API — works seamlessly with OmniRoute's format translation! - -### DeepSeek - -1. Sign up: [platform.deepseek.com](https://platform.deepseek.com) -2. Get API key -3. Dashboard → Add Provider → DeepSeek - -**Models:** `deepseek/deepseek-chat`, `deepseek/deepseek-coder` - -### Groq (Free Tier Available!) - -1. Sign up: [console.groq.com](https://console.groq.com) -2. Get API key (free tier included) -3. Dashboard → Add Provider → Groq - -**Models:** `groq/llama-3.3-70b`, `groq/mixtral-8x7b` - -**Pro Tip:** Ultra-fast inference — best for real-time coding! - -### OpenRouter (100+ Models) - -1. Sign up: [openrouter.ai](https://openrouter.ai) -2. Get API key -3. Dashboard → Add Provider → OpenRouter - -**Models:** Access 100+ models from all major providers through a single API key. - -**Dashboard behavior:** OpenRouter models are managed from **Available Models**. Manual add, import, and auto-sync all update the same list. - -
- -
-💰 Cheap Providers (Backup) - -### GLM-4.7 (Daily reset, $0.6/1M) - -1. Sign up: [Zhipu AI](https://open.bigmodel.cn/) -2. Get API key from Coding Plan -3. Dashboard → Add API Key: - - Provider: `glm` - - API Key: `your-key` - -**Use:** `glm/glm-4.7` - -**Pro Tip:** Coding Plan offers 3× quota at 1/7 cost! Reset daily 10:00 AM. - -### MiniMax M2.1 (5h reset, $0.20/1M) - -1. Sign up: [MiniMax](https://www.minimax.io/) -2. Get API key -3. Dashboard → Add API Key - -**Use:** `minimax/MiniMax-M2.1` - -**Pro Tip:** Cheapest option for long context (1M tokens)! - -### Kimi K2 ($9/month flat) - -1. Subscribe: [Moonshot AI](https://platform.moonshot.ai/) -2. Get API key -3. Dashboard → Add API Key - -**Use:** `kimi/kimi-latest` - -**Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost! - -
- -
-🆓 FREE Providers (Emergency Backup) - -### Qoder (5 FREE models via OAuth) - -```bash -Dashboard → Connect Qoder -→ Qoder OAuth login -→ Access is subject to current provider limits - -Models: - if/kimi-k2-thinking - if/qwen3-coder-plus - if/glm-4.7 - if/minimax-m2 - if/deepseek-r1 -``` - -### Qwen (4 FREE models via Device Code) - -```bash -Dashboard → Connect Qwen -→ Device code authorization -→ Access is subject to current provider limits - -Models: - qw/qwen3-coder-plus - qw/qwen3-coder-flash -``` - -### Kiro (Claude FREE) - -```bash -Dashboard → Connect Kiro -→ AWS Builder ID or Google/GitHub -→ Access is subject to current provider limits - -Models: - kr/claude-sonnet-4.5 - kr/claude-haiku-4.5 -``` - -
- -
-🎨 Create Combos - -### Example 1: Maximize Subscription → Cheap Backup - -``` -Dashboard → Combos → Create New - -Name: premium-coding -Models: - 1. cc/claude-opus-4-7 (Subscription primary) - 2. glm/glm-4.7 (Cheap backup, $0.6/1M) - 3. minimax/MiniMax-M2.1 (Cheapest fallback, $0.20/1M) - -Use in CLI: premium-coding -``` - -### Example 2: Free-Only (Zero Cost) - -``` -Name: free-combo -Models: - 1. if/kimi-k2-thinking (no published token cap; provider limits may apply) - 2. qw/qwen3-coder-plus (no published token cap; provider limits may apply) - -Cost: currently listed as $0; terms and availability may change -``` - -
- -
-🔧 CLI Integration - -### Cursor IDE - -``` -Settings → Models → Advanced: - OpenAI API Base URL: http://localhost:20128/v1 - OpenAI API Key: [from OmniRoute dashboard] - Model: cc/claude-opus-4-7 -``` - -### Claude Code - -Use the **CLI Tools** page in the dashboard for one-click configuration, or edit `~/.claude/settings.json` manually. - -### Codex CLI - -```bash -export OPENAI_BASE_URL="http://localhost:20128" -export OPENAI_API_KEY="your-omniroute-api-key" - -codex "your prompt" -``` - -### OpenClaw - -**Option 1 — Dashboard (recommended):** - -``` -Dashboard → CLI Tools → OpenClaw → Select Model → Apply -``` - -**Option 2 — Manual:** Edit `~/.openclaw/openclaw.json`: - -```json -{ - "models": { - "providers": { - "omniroute": { - "baseUrl": "http://127.0.0.1:20128/v1", - "apiKey": "sk_omniroute", - "api": "openai-completions" - } - } - } -} -``` - -> **Note:** OpenClaw only works with local OmniRoute. Use `127.0.0.1` instead of `localhost` to avoid IPv6 resolution issues. - -### Cline / Continue / RooCode - -``` -Settings → API Configuration: - Provider: OpenAI Compatible - Base URL: http://localhost:20128/v1 - API Key: [from OmniRoute dashboard] - Model: if/kimi-k2-thinking -``` - -### OpenCode - -**Step 1:** Add OmniRoute as a custom provider: - -```bash -opencode -/connect -# Select "Other" → Enter ID: "omniroute" → Enter your OmniRoute API key -``` - -**Step 2:** Create/edit `opencode.json` in your project root: - -```json -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "omniroute": { - "npm": "@ai-sdk/openai-compatible", - "name": "OmniRoute", - "options": { - "baseURL": "http://localhost:20128/v1" - }, - "models": { - "cc/claude-sonnet-4-20250514": { "name": "Claude Sonnet 4" }, - "gg/gemini-2.5-pro": { "name": "Gemini 2.5 Pro" }, - "if/kimi-k2-thinking": { "name": "Kimi K2 (Free)" } - } - } - } -} -``` - -**Step 3:** Select the model in OpenCode: - -```bash -/models -# Select any OmniRoute model from the list -``` - -> **Tip:** Add any model available in your OmniRoute `/v1/models` endpoint to the `models` section. Use the format `provider/model-id` from your OmniRoute dashboard. - -
- ---- - -## Risoluzione dei Problemi - -
-Click to expand troubleshooting guide - -**"Language model did not provide messages"** - -- Provider quota exhausted → Check dashboard quota tracker -- Solution: Use combo fallback or switch to cheaper tier - -**Rate limiting** - -- Subscription quota out → Fallback to GLM/MiniMax -- Add combo: `cc/claude-opus-4-7 → glm/glm-4.7 → if/kimi-k2-thinking` - -**OAuth token expired** - -- Auto-refreshed by OmniRoute -- If issues persist: Dashboard → Provider → Reconnect - -**High costs** - -- Check usage stats in Dashboard → Costs -- Switch primary model to GLM/MiniMax - -**Dashboard/API ports are wrong** - -- `PORT` is the canonical base port (and API port by default) -- `API_PORT` overrides only OpenAI-compatible API listener -- `DASHBOARD_PORT` overrides only dashboard/Next.js listener -- Set `NEXT_PUBLIC_BASE_URL` to your dashboard/public URL (for OAuth callbacks) - -**Cloud sync errors** - -- Verify `BASE_URL` points to your running instance -- Verify `CLOUD_URL` points to your expected cloud endpoint -- Keep `NEXT_PUBLIC_*` values aligned with server-side values - -**First login not working** - -- Check `INITIAL_PASSWORD` in `.env` -- If unset, fallback password is `123456` - -**No request logs** - -- `call_logs` in SQLite stores summary metadata for the Request Logs table and analytics views -- Detailed request/response payloads are written to `DATA_DIR/call_logs/` as one JSON artifact per request -- Enable pipeline capture from Dashboard → Logs → Request Logs if you need detailed per-stage payloads -- `Export Logs` reads the artifact files on demand, while `Export All` includes the `call_logs/` directory alongside `storage.sqlite` -- Set `APP_LOG_TO_FILE=true` if you also want application console logs in `logs/application/app.log` -- Adjust `APP_LOG_MAX_FILE_SIZE`, `APP_LOG_RETENTION_DAYS`, `APP_LOG_MAX_FILES`, and `CALL_LOG_MAX_ENTRIES` as needed - -**Connection test shows "Invalid" for OpenAI-compatible providers** - -- Many providers don't expose a `/models` endpoint -- OmniRoute v1.0.6+ includes fallback validation via chat completions -- Ensure base URL includes `/v1` suffix - -### 🔐 OAuth on a Remote Server - - - - -> **⚠️ Important for users running OmniRoute on a VPS, Docker, or any remote server** - -The OAuth credentials bundled in OmniRoute are registered **for `localhost` only**. When you access OmniRoute on a remote server (e.g. `https://omniroute.myserver.com`), Google rejects the authentication with: - -``` -Error 400: redirect_uri_mismatch -``` - -#### Solution: Configure your own OAuth credentials - -You need to create an **OAuth 2.0 Client ID** in Google Cloud Console with your server's URI. - -#### Step-by-step - -**1. Open Google Cloud Console** - -Go to: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials) - -**2. Create a new OAuth 2.0 Client ID** - -- Click **"+ Create Credentials"** → **"OAuth client ID"** -- Application type: **"Web application"** -- Name: anything you like (e.g. `OmniRoute Remote`) - -**3. Add Authorized Redirect URIs** - -In the **"Authorized redirect URIs"** field, add: - -``` -https://your-server.com/callback -``` - -> Replace `your-server.com` with your server's domain or IP (include the port if needed, e.g. `http://45.33.32.156:20128/callback`). - -**4. Save and copy the credentials** - -After creating, Google will show the **Client ID** and **Client Secret**. - -**5. Set environment variables** - -In your `.env` (or Docker environment variables): - -```bash -# For Antigravity: -ANTIGRAVITY_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com -ANTIGRAVITY_OAUTH_CLIENT_SECRET=GOCSPX-your-secret - -GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com -GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-secret -``` - -**6. Restart OmniRoute** - -```bash -# npm: +# Using Nix flakes +nix develop npm run dev -# Docker: -docker restart omniroute +# Or using devbox +devbox run npm run dev ``` -**7. Try connecting again** +📖 [Guida Docker](../../guides/DOCKER_GUIDE.md) — profili Compose, Caddy HTTPS, tunnel Cloudflare. -Google will now redirect correctly to `https://your-server.com/callback`. +**🦭 Podman** + +```bash +# 1. Prepare the bind-mounted data directory +mkdir -p data + +# 2. Linux + local rootless Podman only (never a remote Podman Machine client): +podman unshare chown 1000:1000 ./data + +# 3. Set the runtime hint, build the local Compose image, and start +echo "CONTAINER_HOST=podman" >> .env +podman compose --profile base up -d --build +``` + +Su macOS o Windows, Podman usa una Podman Machine remota: salta `podman unshare` e +segui le [indicazioni sui permessi della directory dati specifiche per topologia](../../../contrib/podman/README.md#data-directory-permissions-by-topology). + +📖 [Guida Podman](../../../contrib/podman/README.md) — build Compose, Podman Machine e +configurazione Quadlet Linux/systemd. + +**⚡ Installazione più rapida / leggera (salta la build nativa)** + +Il motore SQLite nativo (`better-sqlite3`) è una dipendenza **opzionale**, quindi un'installazione +globale non si blocca mai per compilare da sorgente: usa un binario precompilato quando disponibile +per la tua piattaforma/Node e altrimenti passa in modo trasparente a un motore pure-JS +(`node:sqlite` su Node 22+, altrimenti `sql.js` WASM incluso) — senza richiedere strumenti di build. + +Per saltare completamente il warm-up nativo post-installazione (CI, sistemi headless o macchine lente): + +```bash +OMNIROUTE_SKIP_POSTINSTALL=1 npm install -g omniroute # CI=1 also skips it +``` + +Per installazioni più rapide preferisci **pnpm** (store content-addressed + hard link — vedi sopra). +Per un runtime headless senza dashboard usa il profilo Docker `base` (sopra) oppure la +[guida Termux](../../guides/TERMUX_GUIDE.md). CLI e dashboard web sono servite dallo +stesso processo su una sola porta, quindi oggi non esiste un pacchetto separato solo CLI. + +
+ + +
+ +# 🎬 OmniRoute in azione + +
+ +## 📹 Guide video + +
+ +Dati di copertura social al 2026-08-17 · YT: 741 | TT: 137 | IG: 124 · Aggiornamento (giorni): YT 0 · TT 14 · IG 15 + + + + + + + + + +
+ + Instagram Reel +
+ 🎬 #1 — Instagram
+ nick_saraev — 1,628,910 visualizzazioni +
+ + YouTube — Vaibhav Sisinty +
+ 🎬 #2 — YouTube
+ Vaibhav Sisinty — 373,084 visualizzazioni +
+ + YouTube Shorts +
+ 🎬 #3 — YouTube Shorts
+ Nick Automates — 207,714 visualizzazioni +
+ + Miniatura TikTok +
+ 🎬 #4 — TikTok
+ milesreevesai — 620,400 visualizzazioni +
+ + Valency Labs +
+ 🎬 #5 — YouTube
+ Valency Labs — 135,974 visualizzazioni +
+ +
+ +**Classifica completa (`v > 0`, maggiore portata):** + +| #1 | #2 | #3 | #4 | #5 | +| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [nick_saraev — Instagram](https://www.instagram.com/reel/Da8ZthUPK98/) — **1,628,910** | [milesreevesai — TikTok](https://www.tiktok.com/@milesreevesai/video/7667980059189366019) — **620,400** | [Vaibhav Sisinty — YouTube](https://www.youtube.com/watch?v=QucgvbO5gsM) — **373,084** | [Nick Automates — YouTube Shorts](https://www.youtube.com/shorts/fZIBK_4fKq8) — **207,714** | [midudev — TikTok](https://www.tiktok.com/@midudev/video/7664636453544152342) — **177,800** | + +| #6 | #7 | #8 | #9 | #10 | +| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| [theopenstack — Instagram](https://www.instagram.com/reel/DaSs65mMrHk/) — **155,453** | [t.ghoush.ai — TikTok](https://www.tiktok.com/@t.ghoush.ai/video/7669497680527248656) — **152,800** | [Valency Labs — YouTube](https://www.youtube.com/watch?v=LkP6ocAoQkk) — **135,974** | [Asati — YouTube](https://www.youtube.com/watch?v=JjPtJcqwhqg) — **126,130** | [Vaibhav Sisinty — YouTube](https://www.youtube.com/watch?v=NuNDpeZYQ28) — **122,672** | + +Metriche di validazione: 1002 video monitorati · 7,069,190 visualizzazioni note · 595 profili/canali · 13+ lingue · 13+ creator. + +> 🎬 **Hai realizzato un video su OmniRoute?** Apri una [issue](https://github.com/diegosouzapw/OmniRoute/issues/new) o una [discussion](https://github.com/diegosouzapw/OmniRoute/discussions) con il link — lo metteremo in evidenza qui. + +
+ + +
+ +# 📧 Community e assistenza + +> Tutto in un unico posto — segui il maintainer, parla con la community oppure apri una issue. + +| Canale | Dove / come | +| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | +| 💼 **LinkedIn** — segui il maintainer | [linkedin.com/in/diegosouzapw](https://www.linkedin.com/in/diegosouzapw/) | +| 🐙 **GitHub** — segui release e suggerimenti | [@diegosouzapw](https://github.com/diegosouzapw) | +| 💬 **Discord** | [discord.gg/U47eFqAXCn](https://discord.gg/U47eFqAXCn) | +| ✈️ **Telegram** | [t.me/omnirouteOficial](https://t.me/omnirouteOficial) | +| 🟢 **WhatsApp — 🌍 Global** | [entra nel gruppo](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) | +| 🟢 **WhatsApp — 🇧🇷 Brasil** | [entra nel gruppo](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz) | +| 🌍 **Sito web** | [omniroute.online](https://omniroute.online) | +| 📦 **Codice sorgente** | [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) | +| 🐛 **Segnala un bug** | [apri una issue](https://github.com/diegosouzapw/OmniRoute/issues) — allega l'output di `npm run system-info` | +| 🤝 **Contribuisci** | [CONTRIBUTING.md](../../../CONTRIBUTING.md) · [Modello di branching e release](../../ops/BRANCHING_MODEL.md) · scegli una `good first issue` | +| 💚 **Sostieni il progetto** | [Modi per sostenere ↑](#-support-omniroute) · [GitHub Sponsors](https://github.com/sponsors/diegosouzapw) | + +
--- -#### Temporary workaround (without custom credentials) +
+
-If you don't want to set up your own credentials right now, you can still use the **manual URL flow**: + +## 🛠️ Stack tecnologico -1. OmniRoute opens the Google authorization URL -2. After authorizing, Google tries to redirect to `localhost` (which fails on the remote server) -3. **Copy the full URL** from your browser's address bar (even if the page doesn't load) -4. Paste that URL into the field shown in the OmniRoute connection modal -5. Click **"Connect"** +
-> This works because the authorization code in the URL is valid regardless of whether the redirect page loaded. + + + + + + + + + + + + + + + + + + + +
LivelloTecnologia
RuntimeNode.js 22.x / 24.x LTS — >=22.22.2 <23 || >=24.0.0 <27
LinguaggioTypeScript 6.0 — 100% TypeScript in src/ e open-sse/ (zero any nel core dalla v2.0)
FrameworkNext.js 16 + React 19 + Tailwind CSS 4
Databasebetter-sqlite3 (SQLite, journaling WAL) + LowDB (JSON legacy) — 120 moduli di dominio, 159 migrazioni
MemoriaRicerca full-text SQLite FTS5 + embedding vettoriali quantizzati int8, decadimento tipizzato
SchemiZod 4 — validazione I/O dei tool MCP + contratti API
ProtocolliMCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)
StreamingServer-Sent Events (SSE) + bridge WebSocket (/v1/ws)
CompressionePipeline a 12 motori — RTK, Caveman, LLMLingua-2 (MobileBERT ONNX), GCF, OmniGlyph
Auth e sicurezzaOAuth 2.0 (PKCE) + JWT + API Keys + autorizzazione MCP con scope · AES-256-GCM a riposo · DOMPurify
Stealthwreq-js — impersonificazione del fingerprint TLS JA3 / JA4, proxy a 3 livelli
ResilienzaCircuit breaker, backoff esponenziale, anti-thundering-herd, auto-combo self-healing
Loggingpino — log JSON strutturati con contesto della richiesta
TestTest runner Node.js + Vitest — oltre 25.000 casi di test su 3.300+ file (unitari, integrazione, E2E, sicurezza, ecosistema)
PiattaformeDesktop (Electron) · Android (Termux) · PWA (qualsiasi browser)
CI/CDGitHub Actions — pubblicazione automatica npm + Docker Hub alla release
LinkSito web · npm · Docker Hub
+ +
+ +
+ + +## 📖 Documentazione + +
+ +### 📘 Per iniziare + + + + + + + + + +
DocumentoDescrizione
Guida utenteProvider, combo, integrazione CLI, deployment
Guida alla configurazioneTutti i metodi di installazione, configurazioni degli strumenti CLI, protocolli, regolazione dei timeout
Guida agli strumenti CLIConfigurazione specifica per Claude Code, Codex, Cursor, Cline, OpenClaw, Kilo, Copilot
Modalità remotaGestisci un OmniRoute remoto (VPS) dalla CLI del laptop tramite token di accesso con scope
Configurazione Claude CodeCollega Claude Code a OmniRoute (locale/remoto) con launch + profili per modello
Avvio rapidoInstallazione in 3 passaggi → collega → configura
+ +### 🔧 Operazioni e deployment + + + + + + + + + + + +
DocumentoDescrizione
Guida DockerDocker run, profili Compose, Caddy HTTPS, tunnel, tag immagine
Guida PodmanIntegrazione Quadlet systemd, podman-compose, SELinux
Deployment VMGuida completa: VM + nginx + configurazione Cloudflare
Deployment Fly.ioDeployment su Fly.io con storage persistente
Guida TermuxEsegui OmniRoute su Android tramite Termux
Guida PWAInstallazione Progressive Web App, caching, architettura
Guida alla disinstallazioneRimozione pulita per tutti i metodi di installazione
Configurazione ambienteElenco completo di variabili .env e riferimenti
+ +### 🧠 Funzionalità e architettura + + + + + + + + + + + + + + + +
DocumentoDescrizione
ArchitetturaArchitettura del sistema, flusso dati e componenti interni
Guida alla compressionePipeline a 7 opzioni: off / lite / standard / aggressive / ultra / RTK / stacked
Compressione RTKCompressione dell'output dei comandi, filtri, trust, verifica, recupero dell'output grezzo
Motori di compressioneCaveman, RTK, pipeline stacked, superfici dashboard/API/MCP
Formato regole di compressioneSchemi JSON dei rule pack per filtri Caveman e RTK
Language pack di compressioneRilevamento lingua e creazione dei rule pack Caveman
Guida alla resilienzaCircuit breaker, cooldown, code, anti-thundering herd, TLS spoofing
Motore Auto-ComboScoring a 14 fattori, mode pack, self-healing
Guida proxySistema proxy a 3 livelli, marketplace 1proxy, CRUD del registry
Piani gratuitiDirectory consolidata di oltre 90 provider gratuiti (42 pool token documentati / 495 modelli)
Galleria funzionalitàTour visivo della dashboard con screenshot
Documentazione della codebaseIntroduzione alla codebase adatta ai principianti
+ +### 🤖 Protocolli e API + + + + + + + + + +
DocumentoDescrizione
Riferimento APITutti gli endpoint con esempi
Specifica OpenAPISpecifica OpenAPI 3.0
MCP Server109 tool MCP, configurazioni IDE, client Python/TS/Go
Guida MCP ServerInstallazione MCP, trasporti e riferimento dei tool
A2A ServerProtocollo JSON-RPC 2.0, skill, streaming, gestione task
Guida A2A ServerAgent Card A2A, task, skill e streaming
+ +### 📋 Progetto e qualità + + + + + + + + + + +
DocumentoDescrizione
ContribuireConfigurazione dell'ambiente di sviluppo e linee guida
Modello di branching e releaseDove puntano le PR (release/*) e cosa significano main e i tag
ChangelogCronologia completa delle release, versione per versione
Policy di sicurezzaSegnalazione vulnerabilità e pratiche di sicurezza
Guida i18nSupporto a 43 lingue, workflow di traduzione, RTL
Checklist di releasePassaggi di validazione pre-release
Piano di coverageStrategia di copertura dei test e suite da oltre 25.000 test
+ +
+ +
+ +# ⭐ Principali contributor + +> OmniRoute è plasmato da una community open source appassionata. Queste persone hanno apportato contributi eccezionali che incidono direttamente su qualità, stabilità e diffusione del progetto. **Grazie.** + + + + + + + + + + + + + + + + +
+ + oyi77
+ oyi77 +

+ 🥇 213 commit • +114K righe
+ Motore analytics, aggregazioni SQL,
marketplace proxy, copertura test
+
+ + R.D. & Randi
+ R.D. & Randi +

+ 🥈 108 commit • +38K righe
+ Pagina Endpoints, integrazioni tunnel,
workflow Docker, stato A2A, UI compressione
+
+ + Chris Staley
+ Chris Staley +

+ 🥉 70 commit • +1.8K righe
+ Hardening stream SSE, Responses API,
paginazione Gemini, fix di regressione test
+
+ + zenobit
+ zenobit +

+ 🏅 62 commit • +22K righe
+ Pipeline CI/CD, i18n per 33 lingue,
pacchetto Void Linux, fix di piattaforma
+
+ + Jan Leon
+ Jan Leon +

+ 🏅 58 commit • +22K righe
+ Routing reasoning-effort, controlli proxy,
visibilità quota, compressione Live Zone
+
+ + backryun
+ backryun +

+ 🏅 53 commit • +70K righe
+ Curatela catalogo provider — Perplexity, Kimi,
Cerebras, Copilot, aggiornamenti LMArena
+
+ + Chirag Singhal
+ Chirag Singhal +

+ 🏅 46 commit • +4.8K righe
+ Sanitizzazione errori, fix prefill MITM,
fusion judge, correttezza breaker/429
+
+ + kfiramar
+ kfiramar +

+ 🏅 38 commit • +1.7K righe
+ Codex WebSocket + passthrough, auth/onboarding,
hardening Electron, migrazioni DB
+
+ + Benson K B
+ Benson K B +

+ 🏅 28 commit • +9.2K righe
+ App desktop Electron, auto-updater,
workflow build release, CI multipiattaforma
+
+ + Hernan J. Ardila
+ Hernan J. Ardila +

+ 🏅 25 commit • +174K righe
+ Combo zero-latency, auto-routing vision bridge,
context-length catalogo, hint resilienza 429
+
+ +> 🙏 Funzionalità, bug fix e miglioramenti infrastrutturali di questi contributor sono una **parte fondamentale** di ciò che rende OmniRoute affidabile e ricco di funzionalità. Ogni pull request, ogni caso di test e ogni file di traduzione i18n conta. L'open source è costruito da persone come loro. + +
--- -## 🛠️ Tech Stack +
-
-Click to expand tech stack details + +## 💖 Sponsor -- **Runtime**: Node.js 18–22 LTS (⚠️ Node.js 24+ is **not supported** — `better-sqlite3` native binaries are incompatible) -- **Language**: TypeScript 5.9 — **100% TypeScript** across `src/` and `open-sse/` (zero `any` in core modules since v2.0) -- **Framework**: Next.js 16 + React 19 + Tailwind CSS 4 -- **Database**: better-sqlite3 (SQLite) + LowDB (JSON legacy) — domain state, proxy logs, MCP audit, routing decisions, memory, skills -- **Schemas**: Zod (MCP tool I/O validation, API contracts) -- **Protocols**: MCP (stdio/HTTP) + A2A v0.3 (JSON-RPC 2.0 + SSE) -- **Streaming**: Server-Sent Events (SSE) -- **Auth**: OAuth 2.0 (PKCE) + JWT + API Keys + MCP Scoped Authorization -- **Testing**: Node.js test runner + Vitest (900+ tests including unit, integration, E2E) -- **CI/CD**: GitHub Actions (auto npm publish + Docker Hub on release) -- **Website**: [omniroute.online](https://omniroute.online) -- **Package**: [npmjs.com/package/omniroute](https://www.npmjs.com/package/omniroute) -- **Docker**: [hub.docker.com/r/diegosouzapw/omniroute](https://hub.docker.com/r/diegosouzapw/omniroute) -- **Resilience**: Circuit breaker, exponential backoff, anti-thundering herd, TLS spoofing, auto-combo self-healing +
-
+Un grazie di cuore alle persone che finanziano OmniRoute di tasca propria — ogni contributo aiuta a mantenere il progetto gratuito, indipendente e in evoluzione. ---- + + + + + +
+ + Professor Igor Morais Vasconcelos
+ Prof. Igor Morais +

+ 💛 Sponsor +
+ + longtao
+ longtao +

+ 💛 Sponsor +
-## Documentazione +… e altri che preferiscono restare anonimi 💛 -| Document | Description | -| --------------------------------------------------------------------- | --------------------------------------------------- | -| [User Guide](docs/guides/USER_GUIDE.md) | Providers, combos, CLI integration, deployment | -| [API Reference](docs/reference/API_REFERENCE.md) | All endpoints with examples | -| [MCP Server](open-sse/mcp-server/README.md) | 107 MCP tools, IDE configs, Python/TS/Go clients | -| [A2A Server](src/lib/a2a/README.md) | JSON-RPC 2.0 protocol, skills, streaming, task mgmt | -| [Auto-Combo Engine](docs/routing/AUTO-COMBO.md) | 13-factor scoring, mode packs, self-healing | -| [Context Relay](docs/features/context-relay.md) | Session handoff strategy for account rotation | -| [Troubleshooting](docs/guides/TROUBLESHOOTING.md) | Common problems and solutions | -| [Architecture](docs/architecture/ARCHITECTURE.md) | System architecture and internals | -| [Codebase Documentation](docs/architecture/CODEBASE_DOCUMENTATION.md) | Beginner-friendly codebase walkthrough | -| [Uninstall Guide](docs/guides/UNINSTALL.md) | Clean removal for all install methods | -| [Environment Config](docs/reference/ENVIRONMENT.md) | Complete `.env` variables and references | -| [Contributing](CONTRIBUTING.md) | Development setup and guidelines | -| [OpenAPI Spec](docs/reference/openapi.yaml) | OpenAPI 3.0 specification | -| [Security Policy](SECURITY.md) | Vulnerability reporting and security practices | -| [VM Deployment](docs/ops/VM_DEPLOYMENT_GUIDE.md) | Complete guide: VM + nginx + Cloudflare setup | -| [Features Gallery](docs/guides/FEATURES.md) | Visual dashboard tour with screenshots | -| [Release Checklist](docs/ops/RELEASE_CHECKLIST.md) | Pre-release validation steps | +💖 Diventa sponsor → — ogni contributo mantiene OmniRoute gratuito e indipendente. ---- + -## 🗺️ Roadmap +
-OmniRoute has **218+ features planned** across multiple development phases. Here are the key areas: + +
-| Category | Planned Features | Highlights | -| ----------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- | -| 🧠 **Routing & Intelligence** | 25+ | Lowest-latency routing, tag-based routing, quota preflight, quota-aware P2C, step-based combo routing | -| 🔒 **Security & Compliance** | 20+ | SSRF hardening, credential cloaking, rate-limit per endpoint, management key scoping | -| 📊 **Observability** | 15+ | OpenTelemetry integration, real-time quota monitoring, combo target health, cost tracking per model | -| 🔄 **Provider Integrations** | 20+ | Dynamic model registry, connection cooldowns, multi-account Codex, Copilot quota parsing | -| ⚡ **Performance** | 15+ | Dual cache layer, prompt cache, response cache, streaming keepalive, batch API | -| 🌐 **Ecosystem** | 10+ | WebSocket API, config hot-reload, distributed config store, commercial mode | +## 👥 Oltre 320 contributor -### 🔜 Coming Soon +
-- 🔗 **OpenCode Integration** — Native provider support for the OpenCode AI coding IDE -- 🔗 **TRAE Integration** — Full support for the TRAE AI development framework -- 📦 **Batch API** — Asynchronous batch processing for bulk requests -- 🎯 **Tag-Based Routing** — Route requests based on custom tags and metadata -- 💰 **Lowest-Cost Strategy** — Automatically select the cheapest available provider +[![Contributors](https://contrib.rocks/image?repo=diegosouzapw/OmniRoute&max=400&columns=20&anon=1)](https://github.com/diegosouzapw/OmniRoute/graphs/contributors) -> 📝 Full feature specifications available in [`docs/new-features/`](docs/new-features/) (217 detailed specs) +### Come contribuire ---- +1. Fai un fork del repository +2. Crea il branch dalla punta della `release/vX.Y.Z` **attiva** (non da `main`) — vedi [Modello di branching e release](../../ops/BRANCHING_MODEL.md) +3. Crea il tuo feature branch (`git checkout -b feat/amazing-feature`) +4. Esegui il commit delle modifiche (`git commit -m 'feat: add amazing feature'`) +5. Esegui il push del branch (`git push origin feat/amazing-feature`) +6. Apri una Pull Request con **base = quel branch `release/vX.Y.Z`** -## 👥 Contributors +Vedi [CONTRIBUTING.md](../../../CONTRIBUTING.md) per le linee guida complete. -[![Contributors](https://contrib.rocks/image?repo=diegosouzapw/OmniRoute&max=100&columns=20&anon=1)](https://github.com/diegosouzapw/OmniRoute/graphs/contributors) - -### How to Contribute - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - -See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines. - -### Releasing a New Version +### Pubblicare una nuova versione ```bash # Create a release — npm publish happens automatically -gh release create v2.0.0 --title "v2.0.0" --generate-notes +gh release create v3.8.2 --title "v3.8.2" --generate-notes ``` ---- +
-## 📊 Star History +
- +## 📊 Stelle + + - - - Star History Chart + + + Grafico storico delle stelle +
+ + -## 🙏 Acknowledgments +
-Special thanks to **[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)** — the original Go implementation that inspired this JavaScript port. +
---- +## 🙏 Ringraziamenti -## Licenza +
-MIT License - see [LICENSE](LICENSE) for details. +OmniRoute è costruito sulle spalle di giganti. È nato come fork di **[9router](https://github.com/decolua/9router)** e come port TypeScript del progetto Go **[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)** — da lì, ogni sottosistema qui sotto è stato ispirato da un progetto open source arrivato prima. Ognuno ha influenzato una parte concreta di OmniRoute. Questo è il nostro ringraziamento a tutti loro. 🙏 + +> ⭐ conteggio stelle a luglio 2026 — vai a lasciare una stella a questi progetti. + +### 🧬 Origini e gateway + + + + + + +
ProgettoCome ha ispirato OmniRoute
9router22.7kIl progetto originale su cui si basa questo fork — esteso qui con API multimodali e una riscrittura completa in TypeScript.
CLIProxyAPI43.6kL'implementazione Go che ha ispirato questo port JavaScript / TypeScript.
LiteLLM54.0kIl gateway AI il cui dataset pubblico dei prezzi alimenta la sincronizzazione del cost tracking e il cui modello di normalizzazione dei provider ha influenzato il nostro routing.
+ +### 🗜️ Compressione di contesto e token — motori + + + + + + + + + + +
ProgettoCome ha ispirato OmniRoute
Caveman90.8kIl progetto virale "why use many token when few token do trick" — la sua filosofia caveman-speak alimenta la nostra modalità di compressione standard e oltre 30 regole di rimozione riempitivi/condensazione.
RTK – Rust Token Killer71.8kCompressione ad alte prestazioni dell'output dei comandi — ha ispirato il nostro motore RTK, la DSL per filtri JSON, il recupero dell'output grezzo e la pipeline stacked RTK → Caveman.
headroom60.1kCompressione reversibile del contesto (SmartCrusher) — ha ispirato il nostro motore headroom e il pattern dei marker di recupero ccr.
LLMLingua6.5kRicerca sulla compressione dei prompt (LLMLingua / LLMLingua-2) — ha ispirato il nostro motore llmlingua asincrono, code-safe e fail-open.
llmlingua-2-js30Il port JS/ONNX (MobileBERT / XLM-RoBERTa) usato come backend worker-thread dal nostro motore LLMLingua.
Troglodita26Compressione token PT-BR — alimenta il nostro language pack pt-BR: riduzione dei pleonasmi e rimozione dei riempitivi ottimizzate per la grammatica portoghese brasiliana.
ponytail86.0kLa skill virale da "lazy senior dev" basata su YAGNI — ha ispirato il nostro Output Style less-code: orientamento alla modifica minima funzionante che riduce il codice _generato_ (l'equivalente sull'asse output della prosa concisa di Caveman).
+ +### 🧩 Formati compatti, ricerca sui token e tooling code-aware + + + + + + + + + + + + + + + +
ProgettoCome ha ispirato OmniRoute
TOON24.9kToken-Oriented Object Notation — il suo modello colonnare con header + righe ha influenzato la nostra fase di compattazione tabellare.
GCF – Graph Compact Format22Ha inizialmente ispirato la nostra fase di compattazione tabellare; ora il suo encoder generic-profile lossless e senza dipendenze è incluso direttamente come codec Headroom (MIT, con marcatura SPDX), insieme ai successivi fix di correttezza per dominio numerico e discrepanze nei conteggi.
token-optimizer-mcp444Cache Brotli/SQLite + delta del contesto per sessione — ha ispirato il nostro motore session-dedup.
token-savior1.1kCompattazione dell'output Bash + profili MCP — ha ispirato la nostra disciplina di bail-out nella compressione e la riduzione del manifest dei tool MCP.
token-saver117Compressione dell'output consapevole del contenuto e del tipo di file, con bail-out in caso di errore — ha validato il nostro dispatch per tipo e lo skip basato sul guadagno minimo.
token-optimizer1.7k"Find the ghost tokens" — il suo pattern di offload + handle recuperabile ha influenzato il nostro approccio all'offload CCR.
TokenMizer16Un blueprint con grafo di sessione + deduplica cross-turn per riga che ha influenzato il design di session-dedup.
OmniCompress3JSON colonnare in Rust + retrieve content-addressed + deduplica cross-message — ha validato il design dei nostri motori headroom/ccr/session-dedup e l'invariante cache-stable "la forma compressa è indipendente dalla posizione".
mcp-compressor98Compressione degli schemi/descrizioni dei tool MCP — ha influenzato la riduzione della cardinalità del manifest dei tool MCP.
RepoMapper187Ranking della repo-map in stile Aider — ha influenzato la nostra esplorazione del ranking di repo-map / retrieval.
quiet-shell-mcp4Riduzione dichiarativa dell'output shell tramite MCP — ha validato la nostra compattazione dichiarativa dell'output Bash.
ts-morph6.1kToolkit per la TypeScript Compiler API — ha ispirato la nostra rimozione dei commenti basata su parser, che preserva stringhe, template e literal regex.
+ +### 🧠 Memoria e RAG + + + + + + +
ProgettoCome ha ispirato OmniRoute
Mem061.2kLayer di memoria universale — il suo modello proxy-as-write/read-boundary ha plasmato la nostra architettura della memoria.
Letta (MemGPT)23.9kAgent stateful con memoria a livelli — ha ispirato il nostro modello a livelli Context Control & Recovery (CCR).
WFGY1.8kLa tassonomia ProblemMap di 16 modalità ricorrenti di errore RAG/LLM — il vocabolario condiviso nella nostra guida alla risoluzione dei problemi.
+ +### 🛰️ Ispezione del traffico, MITM e proxy trasparente + + + + + +
ProgettoCome ha ispirato OmniRoute
llm-interceptor49Intercettazione/analisi MITM del traffico coding-assistant ↔ LLM — il nostro Traffic Inspector adatta il suo merge SSE, la normalizzazione delle conversazioni, il passthrough degli host e il masking dei segreti (MIT).
ProxyBridge5.5kRouting proxy trasparente per processo — ha ispirato il teardown MITM crash-safe, gli idle timeout dei socket, l'attribuzione dei processi tramite /proc e la cattura TPROXY.
+ +### 📚 Dati dei modelli, osservabilità e UI + + + + + + + + + +
ProgettoCome ha ispirato OmniRoute
models.dev6.0kDatabase aperto di specifiche, prezzi e capacità dei modelli AI — sincronizzato nativamente nel nostro catalogo modelli.
React Flow / xyflow37.7kLa libreria di grafi node-based che alimenta Compression Studio e Combo/Routing Studio in tempo reale.
LangGraph37.6kLa visualizzazione live dei grafi di workflow di LangGraph Studio ha ispirato la vista a cascata in tempo reale dei nostri Studio.
Langfuse31.4kIl suo modello di osservabilità trace → span → generation ha plasmato la waterfall di Compression Studio.
Kiali3.6kOsservabilità del service mesh Istio — ha ispirato i badge circuit-breaker e le visualizzazioni degli edge di errore in Routing/Combo Studio.
lobe-icons2.2kLoghi dei brand AI/LLM usati per le icone dei provider nella dashboard.
+ +### 🛡️ Sicurezza + + + + +
ProgettoCome ha ispirato OmniRoute
awesome-secure-defaults710Una raccolta curata di librerie secure-by-default che guida le nostre scelte di sicurezza (Helmet.js, DOMPurify, ssrf-req-filter, safe-regex, Google Tink).
+ +### 🧭 Strumenti complementari + + + +
ProgettoCome ha ispirato OmniRoute
+ +## 📄 Licenza + +Licenza MIT - vedi [LICENSE](../../../LICENSE) per i dettagli. ---
- Built with ❤️ for developers who code 24/7 -
- omniroute.online + +**[⬆ Torna all'inizio](#-omniroute)** · Realizzato con ❤️ per la community AI open source. + +OmniRoute v3.8.49 · Node ≥22.22.2 · Licenza MIT · omniroute.online +
From 158c6ec23324d9d2086872e070f1e5544f17cbfa Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:23:52 -0300 Subject: [PATCH 06/20] fix(embeddings): honor configured LM Studio connection URL via lm-studio alias (#11233) (#11260) The dashboard stores LM Studio connections under the hyphenated provider id "lm-studio", but the embedding registry keys the provider as "lmstudio" with no alias. As a result, "lm-studio/" embedding requests failed with a 400 unknown-provider error, and "lmstudio/" requests always hit the hardcoded http://localhost:1234/v1/embeddings endpoint, ignoring the baseUrl of the configured connection. Mirror the ollama-local pattern from #2824/#9225: - embeddingRegistry: add "lm-studio" -> "lmstudio" to EMBEDDING_PROVIDER_ALIASES (registry key unchanged so existing "lmstudio/" clients keep working). - embeddings service: extend the optional keyless-connection hydration to lmstudio; getProviderCredentials("lmstudio") already resolves the "lm-studio" connection via the provider search pool/alias, and a selection/rate-limit failure still proceeds without credentials. - embeddings handler: apply the same baseUrl override + normalization (strip trailing slashes and /v1, /v1/chat/completions, /v1/embeddings suffixes, then rebuild /v1/embeddings) to lmstudio, keeping the static localhost fallback when no connection or empty baseUrl. TDD: tests/unit/lmstudio-connection-baseurl-11233.test.ts failed on the alias, override and service-hydration asserts before the fix and passes after; ollama-local (#2824) and lmstudio registry (#7601) sibling tests remain green. Co-authored-by: Xiangzhe --- open-sse/config/embeddingRegistry.ts | 5 + open-sse/handlers/embeddings.ts | 30 ++-- src/lib/embeddings/service.ts | 13 +- .../lmstudio-connection-baseurl-11233.test.ts | 144 ++++++++++++++++++ 4 files changed, 167 insertions(+), 25 deletions(-) create mode 100644 tests/unit/lmstudio-connection-baseurl-11233.test.ts diff --git a/open-sse/config/embeddingRegistry.ts b/open-sse/config/embeddingRegistry.ts index e907e32509..85758e4535 100644 --- a/open-sse/config/embeddingRegistry.ts +++ b/open-sse/config/embeddingRegistry.ts @@ -413,6 +413,11 @@ export const EMBEDDING_PROVIDERS: Record = { const EMBEDDING_PROVIDER_ALIASES: Record = { jina: "jina-ai", voyage: "voyage-ai", + // The dashboard stores LM Studio connections under the hyphenated provider + // id "lm-studio" while the embedding registry keys the provider "lmstudio" + // (#11233). Alias the dashboard id so "lm-studio/" resolves instead + // of failing with an unknown-provider 400. + "lm-studio": "lmstudio", }; /** Family name used by clients; Jina's public SKU is omni-small. */ diff --git a/open-sse/handlers/embeddings.ts b/open-sse/handlers/embeddings.ts index df9fe26283..7cf322610d 100644 --- a/open-sse/handlers/embeddings.ts +++ b/open-sse/handlers/embeddings.ts @@ -182,12 +182,8 @@ export async function handleEmbedding({ ) : []; const nativeModalities = [ - ...(isJinaNativeEmbeddingInput(body.input) - ? collectJinaNativeModalities(body.input) - : []), - ...(isGeminiNativeEmbeddingInput(body.input) - ? collectGeminiNativeModalities(body.input) - : []), + ...(isJinaNativeEmbeddingInput(body.input) ? collectJinaNativeModalities(body.input) : []), + ...(isGeminiNativeEmbeddingInput(body.input) ? collectGeminiNativeModalities(body.input) : []), ].filter((modality) => modality !== "text"); if (structuredItems.length > 0 || nativeModalities.length > 0) { const supportedModalities = getEmbeddingModelModalities(providerConfig, model); @@ -266,7 +262,10 @@ export async function handleEmbedding({ } let upstreamUrl = providerConfig.baseUrl; - if (provider === "ollama-local") { + if (provider === "ollama-local" || provider === "lmstudio") { + // Keyless local servers (#2824 ollama-local, #11233 lmstudio): honor the + // configured connection's baseUrl when one was hydrated, and fall back to + // the static localhost registry default otherwise. const configuredBaseUrl = credentials?.providerSpecificData?.baseUrl; const rawBaseUrl = typeof configuredBaseUrl === "string" && configuredBaseUrl.trim().length > 0 @@ -277,11 +276,11 @@ export async function handleEmbedding({ // (CodeQL js/polynomial-redos) since baseUrl is operator-configured // per-connection data. See open-sse/utils/urlSanitize.ts. const normalizedBaseUrl = stripTrailingSlashes(rawBaseUrl.trim()); - const ollamaHost = normalizedBaseUrl + const localServerHost = normalizedBaseUrl .replace(/\/v1\/(?:chat\/completions|embeddings)$/i, "") .replace(/\/api\/chat$/i, "") .replace(/\/v1$/i, ""); - upstreamUrl = `${ollamaHost}/v1/embeddings`; + upstreamUrl = `${localServerHost}/v1/embeddings`; } let normalizeProviderResponse: ((data: Record) => Record) | null = null; @@ -321,10 +320,7 @@ export async function handleEmbedding({ // become N embeddings. Native multimodal parts take the same path. const useGeminiNativeTransport = providerConfig.structuredInputProtocol === "gemini-embed-content" && - (isGeminiEmbedding2Family(model) || - canonicalStructured || - geminiNative || - jinaNative); + (isGeminiEmbedding2Family(model) || canonicalStructured || geminiNative || jinaNative); if (providerConfig.structuredInputProtocol === "jina-v1" && jinaNative && canonicalStructured) { try { @@ -462,13 +458,7 @@ export async function handleEmbedding({ // best-effort. if (connectionId) { try { - await markAccountUnavailable( - connectionId, - response.status, - errorText, - provider, - model - ); + await markAccountUnavailable(connectionId, response.status, errorText, provider, model); } catch { // swallow — the upstream error response takes priority } diff --git a/src/lib/embeddings/service.ts b/src/lib/embeddings/service.ts index 3341de2899..dd399396bb 100644 --- a/src/lib/embeddings/service.ts +++ b/src/lib/embeddings/service.ts @@ -249,11 +249,14 @@ export async function createEmbeddingResponse( `[${provider}] All ${credentials.expiredCount || 1} connection(s) authentication expired — please reconnect in the dashboard` ); } - } else if (provider === "ollama-local") { - // Ollama is keyless, but a configured connection can still provide a - // custom local host. Hydrate that optional connection without imposing an - // authentication requirement, then keep the static localhost default when - // no connection exists. + } else if (provider === "ollama-local" || provider === "lmstudio") { + // Ollama and LM Studio are keyless, but a configured connection can still + // provide a custom local host. Hydrate that optional connection without + // imposing an authentication requirement, then keep the static localhost + // default when no connection exists. getProviderCredentials("lmstudio") + // resolves the dashboard's hyphenated "lm-studio" connection via the + // provider search pool/alias (#11233); a selection or rate-limit failure + // must not break the flow — proceed without credentials. const localCredentials = await getProviderCredentials(credentialsProviderId); if ( localCredentials && diff --git a/tests/unit/lmstudio-connection-baseurl-11233.test.ts b/tests/unit/lmstudio-connection-baseurl-11233.test.ts new file mode 100644 index 0000000000..78af8690e4 --- /dev/null +++ b/tests/unit/lmstudio-connection-baseurl-11233.test.ts @@ -0,0 +1,144 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const TEST_DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-lmstudio-embedding-11233-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const { parseEmbeddingModel } = await import("../../open-sse/config/embeddingRegistry.ts"); +const { handleEmbedding } = await import("../../open-sse/handlers/embeddings.ts"); +const core = await import("../../src/lib/db/core.ts"); +const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); +const { createEmbeddingResponse } = await import("../../src/lib/embeddings/service.ts"); + +test.after(() => { + core.resetDbInstance(); + rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +// Issue #11233: the dashboard stores LM Studio connections under the provider +// id "lm-studio" (hyphenated), but the embedding registry keys the provider as +// "lmstudio" with no alias. Two symptoms resulted: +// 1. "lm-studio/" embedding requests failed with 400 unknown provider. +// 2. "lmstudio/" requests always hit the hardcoded localhost:1234 +// endpoint, ignoring the baseUrl of the configured connection. +// The fix mirrors the ollama-local pattern from #2824/#9225: an embedding +// provider alias plus optional (non-auth) connection hydration and the same +// baseUrl normalization in the handler. + +test("lm-studio model strings resolve to the lmstudio embedding provider", () => { + assert.deepEqual(parseEmbeddingModel("lm-studio/nomic-embed-text"), { + provider: "lmstudio", + model: "nomic-embed-text", + }); +}); + +test("lmstudio routes to the configured connection baseUrl", async () => { + const originalFetch = globalThis.fetch; + let capturedUrl: string | null = null; + globalThis.fetch = async (url) => { + capturedUrl = String(url); + return new Response( + JSON.stringify({ + data: [{ object: "embedding", embedding: [0.1, 0.2], index: 0 }], + usage: { prompt_tokens: 2, total_tokens: 2 }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + }; + + try { + const result = await handleEmbedding({ + body: { model: "lmstudio/nomic-embed-text", input: "hello" }, + resolvedProvider: { + id: "lmstudio", + baseUrl: "http://localhost:1234/v1/embeddings", + authType: "none", + authHeader: "none", + models: [], + }, + resolvedModel: "nomic-embed-text", + credentials: { + providerSpecificData: { baseUrl: "http://192.168.1.50:1234/v1" }, + }, + log: null, + }); + + assert.equal(result.success, true); + } finally { + globalThis.fetch = originalFetch; + } + + assert.equal(capturedUrl, "http://192.168.1.50:1234/v1/embeddings"); +}); + +test("lmstudio keeps the static localhost default without credentials", async () => { + const originalFetch = globalThis.fetch; + let capturedUrl: string | null = null; + globalThis.fetch = async (url) => { + capturedUrl = String(url); + return new Response( + JSON.stringify({ + data: [{ object: "embedding", embedding: [0.3, 0.4], index: 0 }], + usage: { prompt_tokens: 2, total_tokens: 2 }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + }; + + try { + const result = await handleEmbedding({ + body: { model: "lmstudio/nomic-embed-text", input: "hello" }, + credentials: null, + log: null, + }); + + assert.equal(result.success, true); + } finally { + globalThis.fetch = originalFetch; + } + + assert.equal(capturedUrl, "http://localhost:1234/v1/embeddings"); +}); + +test("lmstudio service hydrates the lm-studio connection host without requiring a key", async () => { + await createProviderConnection({ + provider: "lm-studio", + authType: "none", + name: "LAN LM Studio", + isActive: true, + providerSpecificData: { baseUrl: "http://10.20.0.60:1234/v1/" }, + }); + + const originalFetch = globalThis.fetch; + let captured: { url: string; headers: Record } | null = null; + globalThis.fetch = async (url, options = {}) => { + captured = { + url: String(url), + headers: (options.headers as Record) || {}, + }; + return new Response( + JSON.stringify({ + data: [{ object: "embedding", embedding: [0.5, 0.6], index: 0 }], + usage: { prompt_tokens: 2, total_tokens: 2 }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + }; + + try { + const response = await createEmbeddingResponse({ + model: "lm-studio/nomic-embed-text", + input: "hello", + }); + assert.equal(response.status, 200); + } finally { + globalThis.fetch = originalFetch; + } + + assert.ok(captured); + assert.equal(captured.url, "http://10.20.0.60:1234/v1/embeddings"); + assert.equal(captured.headers.Authorization, undefined); +}); From 162ef913dac0b7672c9c737e391253d4db89417b Mon Sep 17 00:00:00 2001 From: Paco Cartones <253313177+pacocartones@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:24:05 +0200 Subject: [PATCH 07/20] fix(usage): order quota windows chronologically on every provider card (#11241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined 12-PR batch board: repro-7764 suite 17/17 on this tree (11/17 fail on a clean tip without the fix — proper red-to-green), typecheck:core clean, gates within baseline. Quota windows now order chronologically from the data shape instead of a provider whitelist. Thank you @pacocartones! --- changelog.d/fixes/7764-quota-window-order.md | 1 + .../parts/QuotaCardExpanded.tsx | 12 +- .../components/ProviderLimits/quotaParsing.ts | 68 ++++++++++ .../usage/components/ProviderLimits/utils.tsx | 11 +- .../repro-7764-collapsed-quota-order.test.ts | 126 ++++++++++++++++++ 5 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 changelog.d/fixes/7764-quota-window-order.md diff --git a/changelog.d/fixes/7764-quota-window-order.md b/changelog.d/fixes/7764-quota-window-order.md new file mode 100644 index 0000000000..297131ed28 --- /dev/null +++ b/changelog.d/fixes/7764-quota-window-order.md @@ -0,0 +1 @@ +- **fix(usage):** keep session/weekly/monthly quota windows in chronological order on every provider card. The order is now derived from the quota keys themselves instead of a provider whitelist, so Claude, MiniMax, Z.ai and Command Code stop rendering the two bars in opposite positions across sibling accounts ([#7764](https://github.com/diegosouzapw/OmniRoute/issues/7764)) diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded.tsx index 60346dc03b..8df84a4a57 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded.tsx @@ -19,7 +19,7 @@ import { } from "../utils"; import QuotaMiniBar from "../QuotaMiniBar"; import { translateUsageOrFallback, type UsageTranslationValues } from "../i18nFallback"; -import { hasFixedQuotaOrder } from "../quotaParsing"; +import { hasFixedQuotaOrder, hasCanonicalWindowOrder, sortQuotasByWindow } from "../quotaParsing"; const CURRENCY_SYMBOLS: Record = { USD: "$", @@ -92,9 +92,17 @@ export function sortQuotasByRemaining(quotas: any[]): any[] { * parseQuotaData() already established. Every other provider still gets the * remaining-percentage sort. Fixes #6687 (bars re-sorted by % undid the fixed * session/weekly order). + * + * #7764 residual: providers outside that whitelist which nonetheless report + * rolling time windows (claude, minimax, zai, command-code, ...) are ordered + * chronologically via `hasCanonicalWindowOrder`/`sortQuotasByWindow`, so the + * expanded card agrees with the collapsed card (`topQuotas`) and with sibling + * accounts of the same provider. */ export function resolveQuotaDisplayOrder(providerId: string | undefined, quotas: any[]): any[] { - return hasFixedQuotaOrder(providerId) ? [...quotas] : sortQuotasByRemaining(quotas); + if (hasFixedQuotaOrder(providerId)) return [...quotas]; + if (hasCanonicalWindowOrder(quotas)) return sortQuotasByWindow(quotas); + return sortQuotasByRemaining(quotas); } /** Pure helper — slices the sorted quotas down to the visible window. */ diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts index 63977b1d12..af29a1d6ed 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts @@ -22,6 +22,74 @@ export function hasFixedQuotaOrder(providerId: string | undefined): boolean { return id === "codex" || GLM_FAMILY_PROVIDERS.includes(id) || KIMI_CODING_PROVIDERS.includes(id); } +/** + * Canonical chronological rank of a rolling usage window, derived from the + * quota key itself rather than from a provider list. + * + * Providers name the same two windows in mutually incompatible ways — + * `"session (5h)"` (claude, minimax, kimi), `"5 Hours Quota"` (GLM/zai), + * `"five_hour"` (command-code, qwen-token-plan), `"code_5h"` (kimi-coding), + * plain `"session"` (codex) — so matching on the shape of the key is the only + * thing that generalizes. Returns `null` for anything that is not a recognizable + * time window (per-model buckets, credit balances, token counters), which is + * what keeps this from claiming quotas it has no opinion about. + */ +export function quotaWindowRank(name: unknown): number | null { + const key = String(name ?? "") + .trim() + .toLowerCase(); + if (!key) return null; + // Order matters: "mcp_monthly" must not be caught by the weekly probe, and + // "5 Hours Quota" must not be caught by anything before the session probe. + if (/month/.test(key)) return 2; + if (/week|7\s*d\b|_7d\b|seven[_\s-]?day/.test(key)) return 1; + if (/session|hour|\b5\s*h\b|_5h\b/.test(key)) return 0; + return null; +} + +/** + * #7764: whether a quota list is a set of rolling time windows whose relative + * order is inherent (session before weekly before monthly) and must therefore + * survive rendering. + * + * This is the structural counterpart to the provider whitelist above. The + * whitelist exists because a few providers need an order the window rank cannot + * express (Codex interleaves GPT-5.3-Codex-Spark windows and a banked-credit + * row between the canonical ones), but it went stale the moment any other + * provider started reporting session+weekly — claude, minimax, zai and + * command-code all do. Deriving the answer from the data means the next such + * provider is covered on arrival. + * + * Requires at least two DISTINCT ranks: with a single window there is no pair + * to keep stable, so the pre-existing worst-status-first sort is left alone. + */ +export function hasCanonicalWindowOrder(quotas: unknown): boolean { + if (!Array.isArray(quotas)) return false; + const ranks = new Set(); + for (const quota of quotas) { + if (!quota || (quota as any).isCredits) continue; + const rank = quotaWindowRank((quota as any).name); + if (rank !== null) ranks.add(rank); + } + return ranks.size >= 2; +} + +/** + * Stable sort of a quota list into canonical window order. Unrecognized entries + * (credits, token counters, per-model buckets) sink below the windows while + * keeping their relative order, so nothing is lost or shuffled. + */ +export function sortQuotasByWindow(quotas: T[]): T[] { + return [...quotas] + .map((quota, index) => ({ quota, index })) + .sort((a, b) => { + const ra = quotaWindowRank((a.quota as any)?.name) ?? 99; + const rb = quotaWindowRank((b.quota as any)?.name) ?? 99; + return ra - rb || a.index - b.index; + }) + .map((entry) => entry.quota); +} + function quotaEntries(data: any): Array<[string, any]> { return data?.quotas && typeof data.quotas === "object" ? Object.entries(data.quotas) : []; } diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx index dc102936d0..5f052623ef 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx @@ -1,5 +1,5 @@ export { parseQuotaData } from "./quotaParsing"; -import { hasFixedQuotaOrder } from "./quotaParsing"; +import { hasFixedQuotaOrder, hasCanonicalWindowOrder, sortQuotasByWindow } from "./quotaParsing"; const PROVIDER_PLAN_FALLBACKS = new Set([ "claude code", @@ -400,6 +400,15 @@ export function topQuotas(quotas: any[], n = 3, providerId?: string): any[] { return filtered.slice(0, n); } + // #7764 residual: any OTHER provider reporting rolling time windows (claude, + // minimax, zai, command-code, ...) has an equally inherent session→weekly→ + // monthly order. Re-sorting those by remaining % makes two accounts of the + // same provider render the bars in opposite positions. Detected from the + // quota keys, so a new provider needs no list update. + if (hasCanonicalWindowOrder(filtered)) { + return sortQuotasByWindow(filtered).slice(0, n); + } + return [...filtered] .sort((a, b) => { const sa = STATUS_ORDER[quotaStatus(a)]; diff --git a/tests/unit/repro-7764-collapsed-quota-order.test.ts b/tests/unit/repro-7764-collapsed-quota-order.test.ts index 45fa489e6c..ec75a478dd 100644 --- a/tests/unit/repro-7764-collapsed-quota-order.test.ts +++ b/tests/unit/repro-7764-collapsed-quota-order.test.ts @@ -5,6 +5,7 @@ import { parseQuotaData, hasFixedQuotaOrder, } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing"; +import { resolveQuotaDisplayOrder } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/parts/QuotaCardExpanded"; const quotaName = (quota: { name: string }) => quota.name; @@ -62,3 +63,128 @@ test("#7764: providers WITHOUT a fixed order still sort worst-status-first (no r const rendered = topQuotas(quotas, 3, "some-other-provider").map(quotaName); assert.deepEqual(rendered, ["beta", "gamma", "alpha"]); }); + +// --------------------------------------------------------------------------- +// #7764 residual: the original fix only whitelisted codex / GLM family / Kimi +// Coding in `hasFixedQuotaOrder`. Every OTHER provider that reports the same +// session + weekly rolling windows still gets re-sorted by remaining %, so two +// accounts of the SAME provider render the two bars in opposite positions +// depending on which window happens to be more depleted — the exact symptom in +// the report ("the indicators are not located in the same position per card"). +// +// Quota names below are the real upstream keys, not simplified ones: +// claude → open-sse/services/usage/claude.ts:107,112 "session (5h)" / "weekly (7d)" +// minimax → open-sse/services/usage/minimax.ts:312,325 "session (5h)" / "weekly (7d)" +// zai → routed to getGlmUsage (open-sse/services/usage.ts:191-194) +// so it emits "5 Hours Quota" / "Weekly Quota" (glm.ts:33-34) +// command-code → open-sse/services/usage/command-code.ts:193,196 "five_hour" / "weekly" +// --------------------------------------------------------------------------- + +/** Two refreshes of the same account family: in A the weekly window is the + * depleted one, in B it is the session window. A remaining-% sort flips the + * row order between the two; a canonical window order does not. */ +function windowPair(sessionKey: string, weeklyKey: string) { + return { + depletedWeekly: { + quotas: { + [sessionKey]: { used: 9, total: 100, remainingPercentage: 91, resetAt: null }, + [weeklyKey]: { used: 97, total: 100, remainingPercentage: 3, resetAt: null }, + }, + }, + depletedSession: { + quotas: { + [sessionKey]: { used: 99, total: 100, remainingPercentage: 1, resetAt: null }, + [weeklyKey]: { used: 43, total: 100, remainingPercentage: 57, resetAt: null }, + }, + }, + }; +} + +const WINDOW_PROVIDERS: Array<{ provider: string; session: string; weekly: string }> = [ + { provider: "claude", session: "session (5h)", weekly: "weekly (7d)" }, + { provider: "minimax", session: "session (5h)", weekly: "weekly (7d)" }, + { provider: "minimax-cn", session: "session (5h)", weekly: "weekly (7d)" }, + { provider: "zai", session: "5 Hours Quota", weekly: "Weekly Quota" }, + { provider: "command-code", session: "five_hour", weekly: "weekly" }, +]; + +for (const { provider, session, weekly } of WINDOW_PROVIDERS) { + test(`#7764 residual: ${provider} keeps session before weekly in the collapsed card across refreshes`, () => { + const { depletedWeekly, depletedSession } = windowPair(session, weekly); + const parsedA = parseQuotaData(provider, depletedWeekly); + const parsedB = parseQuotaData(provider, depletedSession); + + // parseQuotaData already yields the canonical upstream order for both. + assert.deepEqual(parsedA.map(quotaName), [session, weekly]); + assert.deepEqual(parsedB.map(quotaName), [session, weekly]); + + assert.deepEqual( + topQuotas(parsedA, 3, provider).map(quotaName), + [session, weekly], + `${provider}: collapsed card must not reorder rolling windows by remaining %` + ); + assert.deepEqual( + topQuotas(parsedB, 3, provider).map(quotaName), + [session, weekly], + `${provider}: window order must be identical on the sibling account` + ); + }); + + test(`#7764 residual: ${provider} expanded card window order matches the collapsed card`, () => { + const { depletedWeekly, depletedSession } = windowPair(session, weekly); + const parsedA = parseQuotaData(provider, depletedWeekly); + const parsedB = parseQuotaData(provider, depletedSession); + + assert.deepEqual(resolveQuotaDisplayOrder(provider, parsedA).map(quotaName), [session, weekly]); + assert.deepEqual(resolveQuotaDisplayOrder(provider, parsedB).map(quotaName), [session, weekly]); + }); +} + +test("#7764 residual: a card whose quotas are NOT rolling windows still sorts worst-first", () => { + // Antigravity-style per-model buckets: no canonical chronological order + // exists, so the worst-status-first sort remains the useful one. + const parsed = parseQuotaData("antigravity", { + quotas: { + "gemini-3-pro": { used: 10, total: 100, remainingPercentage: 90 }, + "gemini-3-flash": { used: 95, total: 100, remainingPercentage: 5 }, + }, + }); + assert.deepEqual(topQuotas(parsed, 3, "antigravity").map(quotaName), [ + "gemini-3-flash", + "gemini-3-pro", + ]); +}); + +test("#7764 residual: a single rolling window plus credits is left to the remaining-% sort", () => { + // Only ONE window → no two windows to keep in a stable relative order, so + // nothing is claimed and the pre-existing behaviour is preserved. + const quotas = [ + { name: "credits", used: 0, total: 0, remainingPercentage: 90, isCredits: true }, + { name: "session (5h)", used: 95, total: 100, remainingPercentage: 5 }, + ]; + assert.deepEqual(topQuotas(quotas, 3, "some-credit-provider").map(quotaName), [ + "session (5h)", + "credits", + ]); +}); + +test("#7764 residual: Claude per-model weekly windows keep upstream order and credits sink last", () => { + // Anthropic reports extra `weekly (7d)` buckets plus an extra_usage + // credits row. The window sort must be STABLE: same-rank siblings keep the + // order parseQuotaData produced, and the credits row is not promoted. + const parsed = parseQuotaData("claude", { + quotas: { + "session (5h)": { used: 9, total: 100, remainingPercentage: 91 }, + "weekly (7d)": { used: 97, total: 100, remainingPercentage: 3 }, + "weekly designer (7d)": { used: 50, total: 100, remainingPercentage: 50 }, + }, + extraUsage: { is_enabled: true, monthly_limit: 100, used_credits: 10, utilization: 10 }, + }); + + assert.deepEqual(topQuotas(parsed, 4, "claude").map(quotaName), [ + "session (5h)", + "weekly (7d)", + "weekly designer (7d)", + "extra_usage", + ]); +}); From e0a22ff619528dae520260f39f235f5f50f4735e Mon Sep 17 00:00:00 2001 From: Paco Cartones <253313177+pacocartones@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:24:15 +0200 Subject: [PATCH 08/20] test(cli): make the CLI suite pass on Windows (#11240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined 12-PR batch board: the touched CLI test files pass (alias-resolver-7791, run-command — incl. the new shellArgs helper), typecheck:core clean. Test-side only, no production behavior change. Thank you @pacocartones! --- tests/unit/cli/_helpers/shellArgs.mjs | 41 ++++++++++++++++++++++ tests/unit/cli/alias-resolver-7791.test.ts | 15 +++++--- tests/unit/cli/run-command.test.ts | 17 ++++----- 3 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 tests/unit/cli/_helpers/shellArgs.mjs diff --git a/tests/unit/cli/_helpers/shellArgs.mjs b/tests/unit/cli/_helpers/shellArgs.mjs new file mode 100644 index 0000000000..148fb94174 --- /dev/null +++ b/tests/unit/cli/_helpers/shellArgs.mjs @@ -0,0 +1,41 @@ +/** + * Reverse the Windows `shell: true` argument escaping so assertions can be + * written against the logical argv on every platform. + * + * `bin/cli/commands/run.mjs` escapes argv before spawning, because on win32 the + * launchers must go through cmd.exe to run npm `.cmd` shims (CVE-2024-27980) + * and Node's `shell: true` joins argv with no escaping at all (DEP0190). That + * escaping is correct and deliberate, but it means `plan.args` holds + * `^^^"--model^^^"` on Windows where it holds `--model` elsewhere. + * + * Tests care about *which* arguments a plan carries, not about how they survive + * cmd.exe, so they normalise first. Keep this in sync with + * `escapeWindowsShellArg` in bin/cli/utils/winShellArgs.mjs. + */ + +/** + * @param {unknown} arg + * @returns {string} + */ +export function unescapeWindowsShellArg(arg) { + let s = String(arg); + // 1. undo the two caret passes applied to cmd.exe metacharacters + s = s.replace(/\^(.)/g, "$1").replace(/\^(.)/g, "$1"); + // 2. drop the wrapping quotes added by the CRT argv layer + if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) s = s.slice(1, -1); + // 3. undo the doubled backslashes and the escaped embedded quotes + s = s.replace(/\\\\/g, "\\").replace(/\\"/g, '"'); + return s; +} + +/** + * Normalise a plan's argv to its logical form. A no-op off Windows. + * + * @param {unknown[]} args + * @param {NodeJS.Platform|string} [platform] + * @returns {string[]} + */ +export function logicalArgs(args, platform = process.platform) { + const list = [...(args ?? [])].map(String); + return platform === "win32" ? list.map(unescapeWindowsShellArg) : list; +} diff --git a/tests/unit/cli/alias-resolver-7791.test.ts b/tests/unit/cli/alias-resolver-7791.test.ts index 6a99522090..d6037796f4 100644 --- a/tests/unit/cli/alias-resolver-7791.test.ts +++ b/tests/unit/cli/alias-resolver-7791.test.ts @@ -18,7 +18,7 @@ import { spawnSync } from "node:child_process"; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { resolveAlias, @@ -30,6 +30,13 @@ import { const __dirname = fileURLToPath(new URL(".", import.meta.url)); const REPO_ROOT = join(__dirname, "..", "..", ".."); +// The child scripts below `import()` these paths, and `import()` resolves its +// specifier as a URL. Replacing backslashes with forward slashes is not enough +// on Windows: the leading drive letter is then parsed as the URL scheme `e:`, +// which the ESM loader rejects with ERR_UNSUPPORTED_ESM_URL_SCHEME. Emit a +// real file:// URL instead. +const repoFileUrl = (relPath) => pathToFileURL(join(REPO_ROOT, relPath)).href; + describe("aliasResolver.resolveAlias (pure)", () => { it("returns null for non-@/ specifiers (lets Node/tsx handle them)", () => { assert.equal(resolveAlias("node:fs", REPO_ROOT), null); @@ -260,11 +267,11 @@ describe("aliasResolver end-to-end (#7791 regression)", () => { const script = ` await import("tsx/esm"); import { join } from "node:path"; - import { registerAliasResolver } from "${join(REPO_ROOT, "bin/aliasResolver.mjs").replace(/\\/g, "/")}"; + import { registerAliasResolver } from ${JSON.stringify(repoFileUrl("bin/aliasResolver.mjs"))}; const ok = await registerAliasResolver(${JSON.stringify(REPO_ROOT)}); if (!ok) { console.error("FAIL: registerAliasResolver returned false"); process.exit(2); } try { - const m = await import(${JSON.stringify(join(REPO_ROOT, "src/shared/network/outboundUrlGuard.ts").replace(/\\/g, "/"))}); + const m = await import(${JSON.stringify(repoFileUrl("src/shared/network/outboundUrlGuard.ts"))}); const keys = Object.keys(m).sort().join(","); console.log("OK:" + keys); } catch (err) { @@ -286,7 +293,7 @@ describe("aliasResolver end-to-end (#7791 regression)", () => { it("does not interfere with bare/relative specifiers (regression guard)", () => { const script = ` - import { registerAliasResolver } from "${join(REPO_ROOT, "bin/aliasResolver.mjs").replace(/\\/g, "/")}"; + import { registerAliasResolver } from ${JSON.stringify(repoFileUrl("bin/aliasResolver.mjs"))}; await registerAliasResolver(${JSON.stringify(REPO_ROOT)}); // node:fs must still resolve via the default resolver const fs = await import("node:fs"); diff --git a/tests/unit/cli/run-command.test.ts b/tests/unit/cli/run-command.test.ts index aa2be59579..0767917450 100644 --- a/tests/unit/cli/run-command.test.ts +++ b/tests/unit/cli/run-command.test.ts @@ -7,6 +7,7 @@ import { resolveModelFromTargetOptions, runCliTarget, } from "../../../bin/cli/commands/run.mjs"; +import { logicalArgs } from "./_helpers/shellArgs.mjs"; test("resolveRunTarget resolves aliases", () => { assert.equal(resolveRunTarget("claude"), "claude"); @@ -39,7 +40,7 @@ test("buildRunPlan for claude includes env diff and model injection", async () = assert.equal(plan.target, "claude"); assert.equal(plan.baseUrl, "http://localhost:20128"); assert.equal(plan.model, "gpt-5"); - assert.equal(plan.args.includes("--help"), true); + assert.equal(logicalArgs(plan.args).includes("--help"), true); assert.equal(plan.envDiff.changedOrAdded.includes("ANTHROPIC_AUTH_TOKEN"), true); assert.equal(plan.authSource, "option"); assert.equal(plan.command.includes("claude"), true); @@ -54,9 +55,9 @@ test("buildRunPlan for codex injects model into provider args", async () => { assert.equal(plan.target, "codex"); assert.equal(plan.baseUrl, "http://localhost:20128"); assert.equal(plan.model, "glm/glm-4.5"); - assert.equal(plan.args.includes("--help"), true); + assert.equal(logicalArgs(plan.args).includes("--help"), true); assert.equal( - plan.args.some((a) => String(a).includes("model_providers.omniroute.model")), + logicalArgs(plan.args).some((a) => a.includes("model_providers.omniroute.model")), true ); assert.equal(plan.authSource, "option"); @@ -70,7 +71,7 @@ test("buildRunPlan for Aider uses its OpenAI-compatible root endpoint", async () ); assert.equal(plan.target, "aider"); assert.equal(plan.baseUrl, "https://relay.example.test"); - assert.deepEqual(plan.args.slice(0, 2), ["--model", "openai/glm/glm-5.2"]); + assert.deepEqual(logicalArgs(plan.args).slice(0, 2), ["--model", "openai/glm/glm-5.2"]); assert.equal(plan.envDiff.changedOrAdded.includes("OPENAI_API_BASE"), true); assert.equal(plan.envDiff.changedOrAdded.includes("OPENAI_API_KEY"), true); }); @@ -82,7 +83,7 @@ test("buildRunPlan for Goose injects provider and model without writing config", ["session"] ); assert.equal(plan.target, "goose"); - assert.deepEqual(plan.args, ["session"]); + assert.deepEqual(logicalArgs(plan.args), ["session"]); assert.equal(plan.envDiff.changedOrAdded.includes("GOOSE_PROVIDER"), true); assert.equal(plan.envDiff.changedOrAdded.includes("GOOSE_MODEL"), true); assert.equal(plan.envDiff.changedOrAdded.includes("OPENAI_HOST"), true); @@ -95,7 +96,7 @@ test("buildRunPlan for OpenCode uses an ephemeral compatible config", async () = ["run", "reply OK"] ); assert.equal(plan.target, "opencode"); - assert.deepEqual(plan.args.slice(0, 2), ["--model", "omniroute/glm/glm-5.2"]); + assert.deepEqual(logicalArgs(plan.args).slice(0, 2), ["--model", "omniroute/glm/glm-5.2"]); assert.equal(plan.envDiff.changedOrAdded.includes("OPENCODE_CONFIG_CONTENT"), true); assert.equal(plan.envDiff.changedOrAdded.includes("OMNIROUTE_API_KEY"), true); assert.equal(plan.configOverlay, "OPENCODE_CONFIG_CONTENT (process environment only)"); @@ -109,7 +110,7 @@ test("buildRunPlan for Qwen requires a deterministic model and injects only env ["-p", "reply OK"] ); assert.equal(plan.target, "qwen"); - assert.deepEqual(plan.args.slice(0, 2), ["--model", "glm/glm-5.2"]); + assert.deepEqual(logicalArgs(plan.args).slice(0, 2), ["--model", "glm/glm-5.2"]); assert.equal(plan.envDiff.changedOrAdded.includes("OMNIROUTE_API_KEY"), true); assert.equal(plan.configOverlay, "temporary QWEN_HOME (removed after exit)"); await assert.rejects( @@ -126,7 +127,7 @@ test("buildRunPlan for Gemini points the CLI at the /v1beta surface via env", as ); assert.equal(plan.target, "gemini"); assert.equal(plan.baseUrl, "https://relay.example.test"); - assert.deepEqual(plan.args.slice(0, 2), ["--model", "glm/glm-5.2"]); + assert.deepEqual(logicalArgs(plan.args).slice(0, 2), ["--model", "glm/glm-5.2"]); assert.equal(plan.envDiff.changedOrAdded.includes("GOOGLE_GEMINI_BASE_URL"), true); assert.equal(plan.envDiff.changedOrAdded.includes("GEMINI_API_KEY"), true); assert.equal(plan.envDiff.changedOrAdded.includes("GEMINI_DEFAULT_AUTH_TYPE"), true); From a55ee5dc058c215e03f667b5e6f93cca2dcc6246 Mon Sep 17 00:00:00 2001 From: Paco Cartones <253313177+pacocartones@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:24:45 +0200 Subject: [PATCH 09/20] feat(combo): make the global attempt budget operator-configurable (#11134) (#11239) Validated on the combined 12-PR batch board: combo-max-global-attempts-config 4/4 plus the neighboring combo suites green, typecheck:core clean, gates within baseline. clampGlobalAttempts mirrors clampComboDepth with a 200 hard cap, so a bad config can never disable the budget. Closes the configurability item from #11134. Thank you @pacocartones! --- .../11134-configurable-max-global-attempts.md | 1 + open-sse/services/combo.ts | 1140 +++++++++-------- open-sse/services/combo/comboPredicates.ts | 19 + open-sse/services/combo/dispatchPrelude.ts | 13 +- open-sse/services/comboConfig.ts | 5 + src/shared/validation/schemas/combo.ts | 3 + .../combo-max-global-attempts-config.test.ts | 83 ++ 7 files changed, 697 insertions(+), 567 deletions(-) create mode 100644 changelog.d/features/11134-configurable-max-global-attempts.md create mode 100644 tests/unit/combo-max-global-attempts-config.test.ts diff --git a/changelog.d/features/11134-configurable-max-global-attempts.md b/changelog.d/features/11134-configurable-max-global-attempts.md new file mode 100644 index 0000000000..2a5605061b --- /dev/null +++ b/changelog.d/features/11134-configurable-max-global-attempts.md @@ -0,0 +1 @@ +- **feat(combo):** the shared per-request combo attempt budget is now operator-configurable via `maxGlobalAttempts` (combo config / `comboDefaults` cascade), instead of the hardcoded 30. Lower it to fail fast on a dead target pool, raise it for large combos; clamped to `[1, 200]` so an unbounded budget can never cause runaway background requests ([#11134](https://github.com/diegosouzapw/OmniRoute/issues/11134)) diff --git a/open-sse/services/combo.ts b/open-sse/services/combo.ts index 2d83935794..fc9ae95a9f 100644 --- a/open-sse/services/combo.ts +++ b/open-sse/services/combo.ts @@ -185,10 +185,12 @@ import { TRANSIENT_FOR_SEMAPHORE, MAX_FALLBACK_WAIT_MS, MAX_GLOBAL_ATTEMPTS, + MAX_GLOBAL_ATTEMPTS_HARD_CAP, COMBO_LOOP_SAFETY_TIMEOUT_MS, COMBO_SAFETY_DRAIN_MS, isAllAccountsRateLimitedResponse, clampComboDepth, + clampGlobalAttempts, shouldSkipForPredictedTtft, shouldRecordProviderBreakerFailure, isComboRequestScopedFailure as isScopedFailure, @@ -288,6 +290,9 @@ export type { SingleModelTarget, ResolvedComboTarget }; export { validateResponseQuality }; export { clampComboDepth, + clampGlobalAttempts, + MAX_GLOBAL_ATTEMPTS, + MAX_GLOBAL_ATTEMPTS_HARD_CAP, shouldSkipForPredictedTtft, shouldRecordProviderBreakerFailure, isRequestScopedUpstreamFailure, @@ -955,6 +960,9 @@ async function handleComboChatInner({ const _registeredExecutionKeys = orderedTargets.map((t) => t.executionKey).filter(Boolean); let globalAttempts = 0; + // #11134: operator-configurable shared attempt budget (clamped to the hard + // cap). Defaults to MAX_GLOBAL_ATTEMPTS when unset. + const maxGlobalAttempts = clampGlobalAttempts(config.maxGlobalAttempts); // Cooldown-aware retry (Variante A). Originally quota-share (qtSd/) only; // extended to "auto" combos too (#7360 — a 2-model "default" auto combo @@ -1104,8 +1112,7 @@ async function handleComboChatInner({ // actionable 504 instead of dying silently. `comboExpired` is flipped so the // target loop stops launching new work; the existing comboExpired branch // returns the aggregated 504. - const loopSafetyMs = - comboTimeoutMs > 0 ? comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; + const loopSafetyMs = comboTimeoutMs > 0 ? comboTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; let loopSafetyFired = false; let loopSafetyTimer: ReturnType | null = null; const loopSafetyPromise = new Promise((resolve) => { @@ -1390,10 +1397,10 @@ async function handleComboChatInner({ return { ok: false, response: errorResponse(499, "Client disconnected") }; } globalAttempts++; - if (globalAttempts > MAX_GLOBAL_ATTEMPTS) { + if (globalAttempts > maxGlobalAttempts) { log.warn( "COMBO", - `Maximum combo attempts (${MAX_GLOBAL_ATTEMPTS}) exceeded across all targets and fallbacks. Terminating loop to prevent runaway background requests.` + `Maximum combo attempts (${maxGlobalAttempts}) exceeded across all targets and fallbacks. Terminating loop to prevent runaway background requests.` ); // Actionable failure instead of an opaque 503 when every candidate // failed the same recoverable way. If the dominant cause was reasoning @@ -2460,9 +2467,7 @@ async function handleComboChatInner({ // 502 so the request terminates with an actionable error. if (!anySuccess && globalResolve) { anySuccess = true; - globalResolve( - errorResponse(502, `Combo target ${i} failed with an unexpected error`) - ); + globalResolve(errorResponse(502, `Combo target ${i} failed with an unexpected error`)); } }); @@ -2527,12 +2532,10 @@ async function handleComboChatInner({ ? ` | tried: ${summary}${comboErrors.length > 5 ? `... (+${comboErrors.length - 5})` : ""}` : "") + " without a terminal response"; - return errorResponseWithComboDiagnostics( - 504, - msg, - buildComboDiag("combo_timeout"), - { code: "COMBO_TIMEOUT", type: "server_error" } - ); + return errorResponseWithComboDiagnostics(504, msg, buildComboDiag("combo_timeout"), { + code: "COMBO_TIMEOUT", + type: "server_error", + }); } // #10681: finalize the decision trace (success). @@ -3072,6 +3075,9 @@ async function handleRoundRobinCombo({ let globalAttempts = 0; let fallbackCount = 0; let recordedAttempts = 0; + // #11134: operator-configurable shared attempt budget (clamped to the hard + // cap). Defaults to MAX_GLOBAL_ATTEMPTS when unset. + const maxGlobalAttempts = clampGlobalAttempts(config.maxGlobalAttempts); // #10314: per-target outcome accumulator for the round-robin twin so the // terminal message lists each distinct reason separately (see the quality path // and the "Done with this model" path below), mirroring handleComboChat. @@ -3082,8 +3088,7 @@ async function handleRoundRobinCombo({ // forever with no response. Safety promise + timer bound the whole loop; when // it fires, rrExpired flips and every subsequent model attempt short-circuits // to the 504. Cleaned up in the loop's finally. - const rrConfiguredTimeoutMs = - (config as { comboTimeoutMs?: number }).comboTimeoutMs ?? 0; + const rrConfiguredTimeoutMs = (config as { comboTimeoutMs?: number }).comboTimeoutMs ?? 0; const rrLoopSafetyMs = rrConfiguredTimeoutMs > 0 ? rrConfiguredTimeoutMs : COMBO_LOOP_SAFETY_TIMEOUT_MS; let rrExpired = false; @@ -3099,7 +3104,10 @@ async function handleRoundRobinCombo({ `Round-robin loop exceeded ${rrLoopSafetyMs}ms without a terminal response — force-terminating` ); rrResolveSafety?.( - errorResponse(504, `Round-robin combo exceeded ${rrLoopSafetyMs}ms without a terminal response`) + errorResponse( + 504, + `Round-robin combo exceeded ${rrLoopSafetyMs}ms without a terminal response` + ) ); }, rrLoopSafetyMs); rrLoopSafetyTimer.unref?.(); @@ -3117,213 +3125,349 @@ async function handleRoundRobinCombo({ // G4: stop launching new work once the safety timer fired. if (rrExpired) break; const modelIndex = (rrStartIndex + offset) % modelCount; - const target = filteredTargets[modelIndex]; - const modelStr = target.modelStr; - const provider = target.provider; - const profile = await getRuntimeProviderProfile(provider); - const semaphoreKey = `combo:${combo.name}:${target.executionKey}`; - const allowRateLimitedConnection = - Boolean(provider && provider !== "unknown") && transientRateLimitedProviders.has(provider); - const targetForAttempt = allowRateLimitedConnection - ? { ...target, allowRateLimitedConnection: true } - : target; + const target = filteredTargets[modelIndex]; + const modelStr = target.modelStr; + const provider = target.provider; + const profile = await getRuntimeProviderProfile(provider); + const semaphoreKey = `combo:${combo.name}:${target.executionKey}`; + const allowRateLimitedConnection = + Boolean(provider && provider !== "unknown") && transientRateLimitedProviders.has(provider); + const targetForAttempt = allowRateLimitedConnection + ? { ...target, allowRateLimitedConnection: true } + : target; - // Pre-check availability - if (isModelAvailable) { - const available = await isModelAvailable(modelStr, targetForAttempt); - if (!available) { - log.debug?.( - "COMBO-RR", - `Skipping ${modelStr} — no credentials available or model excluded` - ); + // Pre-check availability + if (isModelAvailable) { + const available = await isModelAvailable(modelStr, targetForAttempt); + if (!available) { + log.debug?.( + "COMBO-RR", + `Skipping ${modelStr} — no credentials available or model excluded` + ); + if (offset > 0) fallbackCount++; + continue; + } + } + + if ( + resilienceSettings.providerCooldown.enabled && + Boolean(provider && provider !== "unknown") && + isProviderInCooldown( + provider, + target.connectionId as string | undefined, + resilienceSettings + ) + ) { + log.info("COMBO-RR", `Skipping ${modelStr} — provider ${provider} in global cooldown`); if (offset > 0) fallbackCount++; continue; } - } - if ( - resilienceSettings.providerCooldown.enabled && - Boolean(provider && provider !== "unknown") && - isProviderInCooldown(provider, target.connectionId as string | undefined, resilienceSettings) - ) { - log.info("COMBO-RR", `Skipping ${modelStr} — provider ${provider} in global cooldown`); - if (offset > 0) fallbackCount++; - continue; - } - - // #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate). - const exhaustedSkip = getExhaustedTargetSkipReason( - target, - exhaustedProviders, - exhaustedConnections - ); - if (exhaustedSkip) { - log.info("COMBO-RR", exhaustedSkip); - if (offset > 0) fallbackCount++; - continue; - } - - // #9654 Wave 2: per-target lane-aware admission probe (see executeTarget - // for the full contract — strictly non-blocking, lanes-off no-op). - if ( - perTargetAdmission && - !(await perTargetAdmission({ modelStr, executionKey: target.executionKey, body })) - ) { - log.info("COMBO-RR", `Skipping ${modelStr} — admission lane full (#9654)`); - if (offset > 0) fallbackCount++; - continue; - } - - // Acquire semaphore slot (may wait in queue). Honor the connection's own - // maxConcurrent cap when set; else fall back to the combo-level concurrency. - const targetConcurrency = await resolveTargetConcurrency(target.connectionId); - let release: () => void; - try { - release = await semaphore.acquire(semaphoreKey, { - maxConcurrency: targetConcurrency, - timeoutMs: queueTimeout, - maxQueueSize: queueDepth, - }); - } catch (err) { - const errCode = isRecord(err) && typeof err.code === "string" ? err.code : null; - if (errCode === "SEMAPHORE_TIMEOUT" || errCode === "SEMAPHORE_QUEUE_FULL") { - log.warn( - "COMBO-RR", - `Semaphore ${errCode === "SEMAPHORE_QUEUE_FULL" ? "queue full" : "timeout"} for ${modelStr}, trying next model` - ); + // #1731 / #1731v2: skip targets already known-exhausted this request (shared predicate). + const exhaustedSkip = getExhaustedTargetSkipReason( + target, + exhaustedProviders, + exhaustedConnections + ); + if (exhaustedSkip) { + log.info("COMBO-RR", exhaustedSkip); if (offset > 0) fallbackCount++; continue; } - throw err; - } - // Retry loop within this model - try { - for (let retry = 0; retry <= maxRetries; retry++) { - globalAttempts++; - if (globalAttempts > MAX_GLOBAL_ATTEMPTS) { + // #9654 Wave 2: per-target lane-aware admission probe (see executeTarget + // for the full contract — strictly non-blocking, lanes-off no-op). + if ( + perTargetAdmission && + !(await perTargetAdmission({ modelStr, executionKey: target.executionKey, body })) + ) { + log.info("COMBO-RR", `Skipping ${modelStr} — admission lane full (#9654)`); + if (offset > 0) fallbackCount++; + continue; + } + + // Acquire semaphore slot (may wait in queue). Honor the connection's own + // maxConcurrent cap when set; else fall back to the combo-level concurrency. + const targetConcurrency = await resolveTargetConcurrency(target.connectionId); + let release: () => void; + try { + release = await semaphore.acquire(semaphoreKey, { + maxConcurrency: targetConcurrency, + timeoutMs: queueTimeout, + maxQueueSize: queueDepth, + }); + } catch (err) { + const errCode = isRecord(err) && typeof err.code === "string" ? err.code : null; + if (errCode === "SEMAPHORE_TIMEOUT" || errCode === "SEMAPHORE_QUEUE_FULL") { log.warn( "COMBO-RR", - `Maximum combo attempts (${MAX_GLOBAL_ATTEMPTS}) exceeded. Terminating loop to prevent runaway requests.` + `Semaphore ${errCode === "SEMAPHORE_QUEUE_FULL" ? "queue full" : "timeout"} for ${modelStr}, trying next model` ); - return errorResponse(503, "Maximum combo retry limit reached"); - } - if (retry > 0) { - log.info( - "COMBO-RR", - `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})` - ); - await new Promise((r) => setTimeout(r, retryDelayMs)); + if (offset > 0) fallbackCount++; + continue; } + throw err; + } - log.info( - "COMBO-RR", - `[RR #${counter}] → ${modelStr}${offset > 0 ? ` (fallback +${offset})` : ""}${retry > 0 ? ` (retry ${retry})` : ""}` - ); - - // Issue #3587: Reasoning models can spend the whole output budget on - // reasoning. Apply any safe buffer to a per-attempt copy so round-robin - // retries never compound across models. - // #7847: UNCONDITIONAL — copying only when the buffer changed max_tokens left every - // other attempt sharing the caller's object, leaking chatCore's `body.model` forward. - let attemptBody = { ...(body as Record) } as typeof body; - { - const bodyRecord = attemptBody as Record; - const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens); - const bufferedMaxTokens = resolveReasoningBufferedMaxTokens( - modelStr, - bodyRecord.max_tokens, - { enabled: reasoningTokenBufferEnabled } - ); - if ( - currentMaxTokens !== null && - bufferedMaxTokens !== null && - bufferedMaxTokens !== currentMaxTokens - ) { - // Safe to write in place: bodyRecord is the per-attempt copy above, not the caller's. - bodyRecord.max_tokens = bufferedMaxTokens; - log.info( - "COMBO-RR", - `Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` - ); - } - } - - // #5501: combo system_message template expansion per target (same gate - // as the main iteration loop — round-robin branches here, not executeTarget). - attemptBody = expandComboSystemPromptIfPresent(attemptBody, combo, { - modelId: modelStr, - providerId: provider !== "unknown" ? provider : "", - account: - typeof target.label === "string" && target.label.trim().length > 0 - ? target.label.trim() - : "", - fingerprint: resolveTargetFingerprint(target) ?? "", - }); - - const result = await Promise.race([ - handleSingleModel(attemptBody, modelStr, { - ...targetForAttempt, - effectiveComboStrategy: "round-robin", - failoverBeforeRetry: config.failoverBeforeRetry, - }), - rrSafetyPromise, - ]); - if (rrExpired) return result; // G4: safety timer won — stop everything - - // Quota-aware scheduling: reserve the estimated budget for this - // dispatch (opt-in, same env gate as the pre-request check). Best-effort - // and non-blocking — recording must never break the request path. - if ( - process.env.OMNIROUTE_QUOTA_AWARE_ROUTING === "1" && - target.connectionId && - attemptBody && - typeof attemptBody === "object" - ) { - try { - const { reserveQuota } = await import("../../src/lib/quota/quotaScheduler.ts"); - reserveQuota(target.connectionId, modelStr, attemptBody as Record, { - tokenLimit: await resolveTargetTokenLimit(target), - }); - } catch { - // best-effort only - } - } - - // Success — validate response quality before returning - if (result.ok) { - let rrClone: Response; - try { - rrClone = result.clone(); - } catch { - rrClone = result; - } - const quality = await validateResponseQuality( - rrClone, - clientRequestedStream, - log, - config.responseValidation - ); - releaseQualityClone(rrClone, result, quality); - if (!quality.valid) { - releaseRejectedQualityResponse(rrClone, result); + // Retry loop within this model + try { + for (let retry = 0; retry <= maxRetries; retry++) { + globalAttempts++; + if (globalAttempts > maxGlobalAttempts) { log.warn( "COMBO-RR", - `${modelStr} returned 200 but failed quality check: ${quality.reason}` + `Maximum combo attempts (${maxGlobalAttempts}) exceeded. Terminating loop to prevent runaway requests.` ); - // #6692: same rationale as handleComboChat's quality-fail branch — - // a quality-rejected 200 never marks the connection row unhealthy, - // so release the sticky pin here rather than on the next turn. - { - const rrSelectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - releaseStickyPinOnFailure( - _rrSessionSticky.messageHash, - rrSelectedConnectionId || target.connectionId + return errorResponse(503, "Maximum combo retry limit reached"); + } + if (retry > 0) { + log.info( + "COMBO-RR", + `Retrying ${modelStr} in ${retryDelayMs}ms (attempt ${retry + 1}/${maxRetries + 1})` + ); + await new Promise((r) => setTimeout(r, retryDelayMs)); + } + + log.info( + "COMBO-RR", + `[RR #${counter}] → ${modelStr}${offset > 0 ? ` (fallback +${offset})` : ""}${retry > 0 ? ` (retry ${retry})` : ""}` + ); + + // Issue #3587: Reasoning models can spend the whole output budget on + // reasoning. Apply any safe buffer to a per-attempt copy so round-robin + // retries never compound across models. + // #7847: UNCONDITIONAL — copying only when the buffer changed max_tokens left every + // other attempt sharing the caller's object, leaking chatCore's `body.model` forward. + let attemptBody = { ...(body as Record) } as typeof body; + { + const bodyRecord = attemptBody as Record; + const currentMaxTokens = toPositiveInteger(bodyRecord.max_tokens); + const bufferedMaxTokens = resolveReasoningBufferedMaxTokens( + modelStr, + bodyRecord.max_tokens, + { enabled: reasoningTokenBufferEnabled } + ); + if ( + currentMaxTokens !== null && + bufferedMaxTokens !== null && + bufferedMaxTokens !== currentMaxTokens + ) { + // Safe to write in place: bodyRecord is the per-attempt copy above, not the caller's. + bodyRecord.max_tokens = bufferedMaxTokens; + log.info( + "COMBO-RR", + `Reasoning model ${modelStr}: adjusted max_tokens ${currentMaxTokens} -> ${bufferedMaxTokens}` ); } + } + + // #5501: combo system_message template expansion per target (same gate + // as the main iteration loop — round-robin branches here, not executeTarget). + attemptBody = expandComboSystemPromptIfPresent(attemptBody, combo, { + modelId: modelStr, + providerId: provider !== "unknown" ? provider : "", + account: + typeof target.label === "string" && target.label.trim().length > 0 + ? target.label.trim() + : "", + fingerprint: resolveTargetFingerprint(target) ?? "", + }); + + const result = await Promise.race([ + handleSingleModel(attemptBody, modelStr, { + ...targetForAttempt, + effectiveComboStrategy: "round-robin", + failoverBeforeRetry: config.failoverBeforeRetry, + }), + rrSafetyPromise, + ]); + if (rrExpired) return result; // G4: safety timer won — stop everything + + // Quota-aware scheduling: reserve the estimated budget for this + // dispatch (opt-in, same env gate as the pre-request check). Best-effort + // and non-blocking — recording must never break the request path. + if ( + process.env.OMNIROUTE_QUOTA_AWARE_ROUTING === "1" && + target.connectionId && + attemptBody && + typeof attemptBody === "object" + ) { + try { + const { reserveQuota } = await import("../../src/lib/quota/quotaScheduler.ts"); + reserveQuota(target.connectionId, modelStr, attemptBody as Record, { + tokenLimit: await resolveTargetTokenLimit(target), + }); + } catch { + // best-effort only + } + } + + // Success — validate response quality before returning + if (result.ok) { + let rrClone: Response; + try { + rrClone = result.clone(); + } catch { + rrClone = result; + } + const quality = await validateResponseQuality( + rrClone, + clientRequestedStream, + log, + config.responseValidation + ); + releaseQualityClone(rrClone, result, quality); + if (!quality.valid) { + releaseRejectedQualityResponse(rrClone, result); + log.warn( + "COMBO-RR", + `${modelStr} returned 200 but failed quality check: ${quality.reason}` + ); + // #6692: same rationale as handleComboChat's quality-fail branch — + // a quality-rejected 200 never marks the connection row unhealthy, + // so release the sticky pin here rather than on the next turn. + { + const rrSelectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + releaseStickyPinOnFailure( + _rrSessionSticky.messageHash, + rrSelectedConnectionId || target.connectionId + ); + } + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + // Fix #1707: Set terminal state so the fallback doesn't emit + // misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality. + lastError = `Upstream response failed quality validation: ${quality.reason}`; + lastStatus = 502; + rrOutcomes.push({ + model: modelStr, + status: 502, + error: quality.reason || "upstream response failed quality validation", + kind: "quality", + }); + if (offset > 0) fallbackCount++; + break; // move to next model + } + const latencyMs = Date.now() - startTime; + log.info( + "COMBO-RR", + `${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)` + ); + recordComboRequest(combo.name, modelStr, { + success: true, + latencyMs, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + + const selectedConnectionId = + result.headers?.get("X-OmniRoute-Selected-Connection-Id") || + result.headers?.get("x-omniroute-selected-connection-id") || + undefined; + const effectiveConnectionId = selectedConnectionId || target.connectionId || ""; + + const rawModel = parseModel(modelStr).model || modelStr; + if (provider && rawModel) { + const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel); + if (dcResult.cleared) { + log.info("COMBO-RR", `Model ${modelStr} fully recovered — lockout cleared`); + } else if (dcResult.newFailureCount > 0) { + log.debug?.( + "COMBO-RR", + `Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}` + ); + } + } + + if (provider && provider !== "unknown") { + recordProviderSuccess(provider, effectiveConnectionId || undefined); + } + + if (stickyRoundRobinEnabled) { + recordStickyRoundRobinSuccess(combo.name, target, stickyLimit, filteredTargets); + } else { + // #948: true round-robin (stickyLimit <= 1). The counter was advanced + // eagerly (+1 from the scheduled start index) before this loop ran, so + // when the scheduled model failed and a *different* model served via + // fallback, the next request reused the fallback-served model. Advance + // the pointer past the model that ACTUALLY served (modelIndex) instead, + // mirroring recordStickyRoundRobinSuccess's served-index logic. Read + // side applies `% modelCount`, so storing modelIndex + 1 is correct. + rrCounters.set(combo.name, modelIndex + 1); + } + + // #3825: (re)record the sticky binding so the next turn re-pins (prompt-cache). + if (_rrSessionSticky.messageHash) { + const stickyConn = effectiveConnectionId || target.connectionId; + if (stickyConn) recordStickyBinding(_rrSessionSticky.messageHash, stickyConn); + } + + if (provider) { + const connId = effectiveConnectionId || undefined; + void (async () => { + try { + const { setLKGP } = await import("../../src/lib/localDb"); + await Promise.all([ + setLKGP(combo.name, target.executionKey, provider, connId), + setLKGP(combo.name, combo.id || combo.name, provider, connId), + ]); + } catch (err) { + log.warn( + "COMBO-RR", + "Failed to record Last Known Good Provider. This is non-fatal.", + { + err, + } + ); + } + })(); + } + // Clone is consumed by quality check; original stays unlocked. + return result; + } + + // Extract error info + let errorText = result.statusText || ""; + let retryAfter: ComboRetryAfter | null = null; + let errorBody: ComboErrorBody = null; + try { + const cloned = result.clone(); + try { + const text = await cloned.text(); + if (text) { + errorText = text.substring(0, 500); + errorBody = JSON.parse(text); + const parsedError = errorBody?.error; + errorText = + (typeof parsedError === "object" && parsedError?.message) || + (typeof parsedError === "string" ? parsedError : null) || + errorBody?.message || + errorText; + retryAfter = errorBody?.retryAfter || null; + } + } catch { + /* Clone parse failed */ + } + } catch { + /* Clone failed */ + } + + if (result.status === 499) { + log.info( + "COMBO-RR", + `Client disconnected (499) during ${modelStr} — stopping combo loop` + ); recordComboRequest(combo.name, modelStr, { success: false, latencyMs: Date.now() - startTime, @@ -3332,381 +3476,253 @@ async function handleRoundRobinCombo({ target: toRecordedTarget(target), }); recordedAttempts++; - // Fix #1707: Set terminal state so the fallback doesn't emit - // misleading ALL_ACCOUNTS_INACTIVE when the real issue is quality. - lastError = `Upstream response failed quality validation: ${quality.reason}`; - lastStatus = 502; - rrOutcomes.push({ - model: modelStr, - status: 502, - error: quality.reason || "upstream response failed quality validation", - kind: "quality", - }); - if (offset > 0) fallbackCount++; - break; // move to next model + return result; } - const latencyMs = Date.now() - startTime; - log.info( - "COMBO-RR", - `${modelStr} succeeded (${latencyMs}ms, ${fallbackCount} fallbacks)` - ); - recordComboRequest(combo.name, modelStr, { - success: true, - latencyMs, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - recordedAttempts++; + if ( + retryAfter && + (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter)) + ) { + earliestRetryAfter = retryAfter; + } + + if (typeof errorText !== "string") { + try { + errorText = JSON.stringify(errorText); + } catch { + errorText = String(errorText); + } + } + + const isStreamReadinessFailure = + (result.status === 502 || result.status === 504) && + isStreamReadinessFailureErrorBody(errorBody); + + // FIX 5: a local per-API-key token-limit 429 must not cool shared accounts. + const isTokenLimitBreach = + result.status === 429 && isTokenLimitBreachErrorBody(errorBody); + const isLocalQueueCapacity = isLocalQueueCapacityErrorBody(errorBody); + + if (isLocalQueueCapacity) { + log.info( + "COMBO-RR", + `Local rate-limit queue capacity reached for ${modelStr} — returning without upstream fallback` + ); + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + recordedAttempts++; + return result; + } + + // Round-robin uses the same target-level fallback rule as other combo + // strategies: non-ok target responses fall through to the next target. + // Classification stays here only to support cooldown/semaphore pacing, + // not to decide whether fallback is allowed. + const rawError = errorBody?.error; + const structuredError = + rawError && typeof rawError === "object" + ? { + // Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}). + // Coerce to string if present instead of discarding, so downstream string + // ops (.toLowerCase, .startsWith) can run safely without type crashes. + code: + (rawError as Record).code !== undefined && + (rawError as Record).code !== null + ? String((rawError as Record).code) + : undefined, + type: + (rawError as Record).type !== undefined && + (rawError as Record).type !== null + ? String((rawError as Record).type) + : undefined, + } + : undefined; + const scopedFailure = isScopedFailure(result, errorText, structuredError); + const fallbackResult = checkFallbackError( + result.status, + errorText, + 0, + null, + provider, + result.headers, + profile, + structuredError + ); + const { cooldownMs } = fallbackResult; const selectedConnectionId = result.headers?.get("X-OmniRoute-Selected-Connection-Id") || result.headers?.get("x-omniroute-selected-connection-id") || undefined; - const effectiveConnectionId = selectedConnectionId || target.connectionId || ""; + const targetWithConnection = selectedConnectionId + ? { ...target, connectionId: selectedConnectionId } + : target; - const rawModel = parseModel(modelStr).model || modelStr; - if (provider && rawModel) { - const dcResult = decayModelFailureCount(provider, effectiveConnectionId, rawModel); - if (dcResult.cleared) { - log.info("COMBO-RR", `Model ${modelStr} fully recovered — lockout cleared`); - } else if (dcResult.newFailureCount > 0) { - log.debug?.( - "COMBO-RR", - `Model ${modelStr} decayed to failureCount=${dcResult.newFailureCount}` - ); - } - } - - if (provider && provider !== "unknown") { - recordProviderSuccess(provider, effectiveConnectionId || undefined); - } - - if (stickyRoundRobinEnabled) { - recordStickyRoundRobinSuccess(combo.name, target, stickyLimit, filteredTargets); - } else { - // #948: true round-robin (stickyLimit <= 1). The counter was advanced - // eagerly (+1 from the scheduled start index) before this loop ran, so - // when the scheduled model failed and a *different* model served via - // fallback, the next request reused the fallback-served model. Advance - // the pointer past the model that ACTUALLY served (modelIndex) instead, - // mirroring recordStickyRoundRobinSuccess's served-index logic. Read - // side applies `% modelCount`, so storing modelIndex + 1 is correct. - rrCounters.set(combo.name, modelIndex + 1); - } - - // #3825: (re)record the sticky binding so the next turn re-pins (prompt-cache). - if (_rrSessionSticky.messageHash) { - const stickyConn = effectiveConnectionId || target.connectionId; - if (stickyConn) recordStickyBinding(_rrSessionSticky.messageHash, stickyConn); - } - - if (provider) { - const connId = effectiveConnectionId || undefined; - void (async () => { - try { - const { setLKGP } = await import("../../src/lib/localDb"); - await Promise.all([ - setLKGP(combo.name, target.executionKey, provider, connId), - setLKGP(combo.name, combo.id || combo.name, provider, connId), - ]); - } catch (err) { - log.warn( - "COMBO-RR", - "Failed to record Last Known Good Provider. This is non-fatal.", - { - err, - } - ); - } - })(); - } - // Clone is consumed by quality check; original stays unlocked. - return result; - } - - // Extract error info - let errorText = result.statusText || ""; - let retryAfter: ComboRetryAfter | null = null; - let errorBody: ComboErrorBody = null; - try { - const cloned = result.clone(); - try { - const text = await cloned.text(); - if (text) { - errorText = text.substring(0, 500); - errorBody = JSON.parse(text); - const parsedError = errorBody?.error; - errorText = - (typeof parsedError === "object" && parsedError?.message) || - (typeof parsedError === "string" ? parsedError : null) || - errorBody?.message || - errorText; - retryAfter = errorBody?.retryAfter || null; - } - } catch { - /* Clone parse failed */ - } - } catch { - /* Clone failed */ - } - - if (result.status === 499) { - log.info( - "COMBO-RR", - `Client disconnected (499) during ${modelStr} — stopping combo loop` + const isAllAccountsRateLimited = isAllAccountsRateLimitedResponse( + result.status, + result.headers?.get("content-type") ?? null, + errorText ); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), + + // #1731: If the entire provider quota is exhausted, mark it so subsequent + // same-provider targets are skipped immediately. API-key 429s still use + // the short resilience cooldown, but explicit quota text should stop the + // combo from trying another target for the same provider in this request. + // #1731 / #1731v2: classify the upstream error and update the exhaustion sets + // (shared with handleComboChat). Returns whether the provider is fully exhausted. + const providerExhausted = applyComboTargetExhaustion(targetWithConnection, { + result, + fallbackResult, + errorText, + rawModel: parseModel(modelStr).model || modelStr, + isTokenLimitBreach, + allAccountsRateLimited: isAllAccountsRateLimited, + requestScopedFailure: scopedFailure, + sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders }, + log, + tag: "COMBO-RR", + exhaustedLogLevel: "debug", + structuredError, }); - recordedAttempts++; - return result; - } + // #6692: mirrors handleComboChat's exhaustion-point release above. + releaseStickyPinOnFailure( + _rrSessionSticky.messageHash, + targetWithConnection.connectionId + ); - if ( - retryAfter && - (!earliestRetryAfter || new Date(retryAfter) < new Date(earliestRetryAfter)) - ) { - earliestRetryAfter = retryAfter; - } - - if (typeof errorText !== "string") { - try { - errorText = JSON.stringify(errorText); - } catch { - errorText = String(errorText); + // Transient errors → mark in semaphore so round-robin stops stampeding this target. + if ( + !isStreamReadinessFailure && + !isTokenLimitBreach && + !scopedFailure && + TRANSIENT_FOR_SEMAPHORE.includes(result.status) && + cooldownMs > 0 + ) { + semaphore.markRateLimited(semaphoreKey, cooldownMs); + log.warn("COMBO-RR", `${modelStr} error ${result.status}, cooldown ${cooldownMs}ms`); } - } - const isStreamReadinessFailure = - (result.status === 502 || result.status === 504) && - isStreamReadinessFailureErrorBody(errorBody); - - // FIX 5: a local per-API-key token-limit 429 must not cool shared accounts. - const isTokenLimitBreach = result.status === 429 && isTokenLimitBreachErrorBody(errorBody); - const isLocalQueueCapacity = isLocalQueueCapacityErrorBody(errorBody); - - if (isLocalQueueCapacity) { - log.info( - "COMBO-RR", - `Local rate-limit queue capacity reached for ${modelStr} — returning without upstream fallback` - ); - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - recordedAttempts++; - return result; - } - - // Round-robin uses the same target-level fallback rule as other combo - // strategies: non-ok target responses fall through to the next target. - // Classification stays here only to support cooldown/semaphore pacing, - // not to decide whether fallback is allowed. - const rawError = errorBody?.error; - const structuredError = - rawError && typeof rawError === "object" - ? { - // Upstream JSON may carry a numeric `code`/`type` (e.g. {"code":40001}). - // Coerce to string if present instead of discarding, so downstream string - // ops (.toLowerCase, .startsWith) can run safely without type crashes. - code: - (rawError as Record).code !== undefined && - (rawError as Record).code !== null - ? String((rawError as Record).code) - : undefined, - type: - (rawError as Record).type !== undefined && - (rawError as Record).type !== null - ? String((rawError as Record).type) - : undefined, - } - : undefined; - const scopedFailure = isScopedFailure(result, errorText, structuredError); - const fallbackResult = checkFallbackError( - result.status, - errorText, - 0, - null, - provider, - result.headers, - profile, - structuredError - ); - const { cooldownMs } = fallbackResult; - const selectedConnectionId = - result.headers?.get("X-OmniRoute-Selected-Connection-Id") || - result.headers?.get("x-omniroute-selected-connection-id") || - undefined; - const targetWithConnection = selectedConnectionId - ? { ...target, connectionId: selectedConnectionId } - : target; - - const isAllAccountsRateLimited = isAllAccountsRateLimitedResponse( - result.status, - result.headers?.get("content-type") ?? null, - errorText - ); - - // #1731: If the entire provider quota is exhausted, mark it so subsequent - // same-provider targets are skipped immediately. API-key 429s still use - // the short resilience cooldown, but explicit quota text should stop the - // combo from trying another target for the same provider in this request. - // #1731 / #1731v2: classify the upstream error and update the exhaustion sets - // (shared with handleComboChat). Returns whether the provider is fully exhausted. - const providerExhausted = applyComboTargetExhaustion(targetWithConnection, { - result, - fallbackResult, - errorText, - rawModel: parseModel(modelStr).model || modelStr, - isTokenLimitBreach, - allAccountsRateLimited: isAllAccountsRateLimited, - requestScopedFailure: scopedFailure, - sets: { exhaustedProviders, exhaustedConnections, transientRateLimitedProviders }, - log, - tag: "COMBO-RR", - exhaustedLogLevel: "debug", - structuredError, - }); - // #6692: mirrors handleComboChat's exhaustion-point release above. - releaseStickyPinOnFailure(_rrSessionSticky.messageHash, targetWithConnection.connectionId); - - // Transient errors → mark in semaphore so round-robin stops stampeding this target. - if ( - !isStreamReadinessFailure && - !isTokenLimitBreach && - !scopedFailure && - TRANSIENT_FOR_SEMAPHORE.includes(result.status) && - cooldownMs > 0 - ) { - semaphore.markRateLimited(semaphoreKey, cooldownMs); - log.warn("COMBO-RR", `${modelStr} error ${result.status}, cooldown ${cooldownMs}ms`); - } - - if (isAllAccountsRateLimited) { - log.info( - "COMBO-RR", - `All accounts rate-limited for ${modelStr}, falling back to next model` - ); - } - - // Transient error → retry same model. - // A token-limit 429 is terminal for the client — never retry it. - const isTransient = - !isStreamReadinessFailure && - !isTokenLimitBreach && - !scopedFailure && - [408, 429, 500, 502, 503, 504].includes(result.status); - // See the same guard's comment in the "auto" strategy loop above — - // failoverBeforeRetry must prevent this same-model retry too, not - // just the lower-level skipUpstreamRetry mechanism. Only skip when - // `offset + 1 < modelCount` means a sibling target is actually left - // in this rotation; with none left, skipping just wastes the attempt. - // #10217 round-4 fix: opt-in only — read failoverBeforeRetryExplicit, - // not config.failoverBeforeRetry (see comboConfig.ts comment). - const hasNextRrTarget = offset + 1 < modelCount; - if ( - retry < maxRetries && - isTransient && - !providerExhausted && - (!config.failoverBeforeRetryExplicit || !hasNextRrTarget) - ) { - continue; - } - - // Done with this model - recordComboRequest(combo.name, modelStr, { - success: false, - latencyMs: Date.now() - startTime, - fallbackCount, - strategy: "round-robin", - target: toRecordedTarget(target), - }); - // LKGP (#919) mirror of handleComboChat's failure-path clear above — see - // that comment for why this must happen (nothing else clears a pin left - // by a request-scoped failure class like a stream-readiness timeout). - void (async () => { - try { - const { clearLKGP } = await import("../../src/lib/localDb"); - await Promise.all([ - clearLKGP(combo.name, target.executionKey), - clearLKGP(combo.name, combo.id || combo.name), - ]); - } catch (err) { - log.warn("COMBO-RR", "Failed to clear Last Known Good Provider. This is non-fatal.", { - err, - }); - } - })(); - recordedAttempts++; - lastError = errorText || String(result.status); - lastStatus = result.status; - rrOutcomes.push({ - model: modelStr, - status: result.status, - error: errorText || String(result.status), - kind: classifyComboOutcome(result.status, errorText), - }); - if (offset > 0) fallbackCount++; - log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { - status: result.status, - errorBody: redactConnectionLabel(errorText), - }); - - if ( - resilienceSettings.providerCooldown.enabled && - provider && - provider !== "unknown" && - !scopedFailure && - !( - (result.status === 500 || result.status === 429) && - hasPerModelQuota(provider, parseModel(modelStr).model || modelStr) - ) - ) { - recordProviderCooldown( - provider, - targetWithConnection.connectionId ?? undefined, - resilienceSettings - ); - } - - const fallbackWaitMs = - fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS - ? Math.min(cooldownMs, fallbackDelayMs) - : 0; - if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { - log.debug?.("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`); - await new Promise((resolve) => { - const timer = setTimeout(resolve, fallbackWaitMs); - signal?.addEventListener( - "abort", - () => { - clearTimeout(timer); - resolve(undefined); - }, - { once: true } + if (isAllAccountsRateLimited) { + log.info( + "COMBO-RR", + `All accounts rate-limited for ${modelStr}, falling back to next model` ); - }); - if (signal?.aborted) { - log.info("COMBO-RR", `Client disconnected during fallback wait — aborting`); - return errorResponse(499, "Client disconnected"); } - } - break; + // Transient error → retry same model. + // A token-limit 429 is terminal for the client — never retry it. + const isTransient = + !isStreamReadinessFailure && + !isTokenLimitBreach && + !scopedFailure && + [408, 429, 500, 502, 503, 504].includes(result.status); + // See the same guard's comment in the "auto" strategy loop above — + // failoverBeforeRetry must prevent this same-model retry too, not + // just the lower-level skipUpstreamRetry mechanism. Only skip when + // `offset + 1 < modelCount` means a sibling target is actually left + // in this rotation; with none left, skipping just wastes the attempt. + // #10217 round-4 fix: opt-in only — read failoverBeforeRetryExplicit, + // not config.failoverBeforeRetry (see comboConfig.ts comment). + const hasNextRrTarget = offset + 1 < modelCount; + if ( + retry < maxRetries && + isTransient && + !providerExhausted && + (!config.failoverBeforeRetryExplicit || !hasNextRrTarget) + ) { + continue; + } + + // Done with this model + recordComboRequest(combo.name, modelStr, { + success: false, + latencyMs: Date.now() - startTime, + fallbackCount, + strategy: "round-robin", + target: toRecordedTarget(target), + }); + // LKGP (#919) mirror of handleComboChat's failure-path clear above — see + // that comment for why this must happen (nothing else clears a pin left + // by a request-scoped failure class like a stream-readiness timeout). + void (async () => { + try { + const { clearLKGP } = await import("../../src/lib/localDb"); + await Promise.all([ + clearLKGP(combo.name, target.executionKey), + clearLKGP(combo.name, combo.id || combo.name), + ]); + } catch (err) { + log.warn("COMBO-RR", "Failed to clear Last Known Good Provider. This is non-fatal.", { + err, + }); + } + })(); + recordedAttempts++; + lastError = errorText || String(result.status); + lastStatus = result.status; + rrOutcomes.push({ + model: modelStr, + status: result.status, + error: errorText || String(result.status), + kind: classifyComboOutcome(result.status, errorText), + }); + if (offset > 0) fallbackCount++; + log.warn("COMBO-RR", `${modelStr} failed, trying next model`, { + status: result.status, + errorBody: redactConnectionLabel(errorText), + }); + + if ( + resilienceSettings.providerCooldown.enabled && + provider && + provider !== "unknown" && + !scopedFailure && + !( + (result.status === 500 || result.status === 429) && + hasPerModelQuota(provider, parseModel(modelStr).model || modelStr) + ) + ) { + recordProviderCooldown( + provider, + targetWithConnection.connectionId ?? undefined, + resilienceSettings + ); + } + + const fallbackWaitMs = + fallbackDelayMs > 0 && cooldownMs > 0 && cooldownMs <= MAX_FALLBACK_WAIT_MS + ? Math.min(cooldownMs, fallbackDelayMs) + : 0; + if ([502, 503, 504].includes(result.status) && fallbackWaitMs > 0) { + log.debug?.("COMBO-RR", `Waiting ${fallbackWaitMs}ms before fallback to next model`); + await new Promise((resolve) => { + const timer = setTimeout(resolve, fallbackWaitMs); + signal?.addEventListener( + "abort", + () => { + clearTimeout(timer); + resolve(undefined); + }, + { once: true } + ); + }); + if (signal?.aborted) { + log.info("COMBO-RR", `Client disconnected during fallback wait — aborting`); + return errorResponse(499, "Client disconnected"); + } + } + + break; + } + } finally { + // ALWAYS release semaphore slot + release(); } - } finally { - // ALWAYS release semaphore slot - release(); } - } } catch (err) { // G4: unexpected exception in the round-robin loop must never crash the // request silently — surface a 500 instead of hanging the client. diff --git a/open-sse/services/combo/comboPredicates.ts b/open-sse/services/combo/comboPredicates.ts index f7cfa3322d..80005c1ac3 100644 --- a/open-sse/services/combo/comboPredicates.ts +++ b/open-sse/services/combo/comboPredicates.ts @@ -96,6 +96,11 @@ export const MAX_COMBO_DEPTH = 3; export const MAX_COMBO_DEPTH_HARD_CAP = 10; export const MAX_FALLBACK_WAIT_MS = 5000; export const MAX_GLOBAL_ATTEMPTS = 30; +// Absolute safety ceiling for the operator-configured shared attempt budget +// (#11134). config.maxGlobalAttempts can raise the default (30) or lower it, +// but never above this cap — an unbounded attempt budget is the same runaway +// background-request DoS risk that motivated MAX_COMBO_DEPTH_HARD_CAP. +export const MAX_GLOBAL_ATTEMPTS_HARD_CAP = 200; /** * Clamp an operator-configured combo nesting depth (config.maxComboDepth) to a @@ -109,6 +114,20 @@ export function clampComboDepth(value: unknown): number { return Math.min(n, MAX_COMBO_DEPTH_HARD_CAP); } +/** + * Clamp an operator-configured shared per-request attempt budget + * (config.maxGlobalAttempts) to a safe integer in + * [1, MAX_GLOBAL_ATTEMPTS_HARD_CAP] (#11134). Mirrors clampComboDepth: anything + * non-numeric, < 1, NaN or Infinity falls back to the default + * MAX_GLOBAL_ATTEMPTS so a bad config can never disable the budget (runaway + * retries against a dead pool) nor blow past the safety ceiling. + */ +export function clampGlobalAttempts(value: unknown): number { + const n = Math.floor(Number(value)); + if (!Number.isFinite(n) || n < 1) return MAX_GLOBAL_ATTEMPTS; + return Math.min(n, MAX_GLOBAL_ATTEMPTS_HARD_CAP); +} + /** Minimum recorded requests before the predictive-TTFT breaker trusts the average. */ export const PREDICTIVE_TTFT_MIN_SAMPLES = 5; diff --git a/open-sse/services/combo/dispatchPrelude.ts b/open-sse/services/combo/dispatchPrelude.ts index c9c62ddbe6..604d5d228a 100644 --- a/open-sse/services/combo/dispatchPrelude.ts +++ b/open-sse/services/combo/dispatchPrelude.ts @@ -21,7 +21,7 @@ import { errorResponseWithComboDiagnostics } from "../../utils/error.ts"; import { parseModel } from "../model.ts"; import { handlePipelineChat, type PipelineStep } from "../pipeline.ts"; import type { resolveComboSetupConfig } from "../comboConfig.ts"; -import { clampComboDepth, MAX_GLOBAL_ATTEMPTS, resolveDelayMs } from "./comboPredicates.ts"; +import { clampComboDepth, clampGlobalAttempts, resolveDelayMs } from "./comboPredicates.ts"; import { deriveRequestCompatibilityRequirements, isVisionIncompatibleTarget, @@ -192,7 +192,7 @@ export function normalizeNestedComboMode(value: unknown): NestedComboMode { return value === "execute" ? "execute" : "flatten"; } -function buildDefaultNesting( +export function buildDefaultNesting( nesting: ComboNestingContext | null | undefined, comboName: string, config: ComboSetupConfig @@ -203,7 +203,9 @@ function buildDefaultNesting( maxDepth: clampComboDepth(config.maxComboDepth), visitedComboNames: [comboName], rootComboName: comboName, - attemptBudget: { count: 0, limit: MAX_GLOBAL_ATTEMPTS }, + // #11134: honor the operator-configured shared budget (clamped to the + // hard cap) instead of the hardcoded MAX_GLOBAL_ATTEMPTS. + attemptBudget: { count: 0, limit: clampGlobalAttempts(config.maxGlobalAttempts) }, } ); } @@ -327,12 +329,13 @@ export async function tryPinnedModelDispatch(args: { const pinnedTarget = comboTargets.find((t) => t.modelStr === pinnedModel); const pinnedBody = expandComboSystemPromptIfPresent(body, combo, { modelId: pinnedModel, - providerId: pinnedTarget && pinnedTarget.provider !== "unknown" ? pinnedTarget.provider : "", + providerId: + pinnedTarget && pinnedTarget.provider !== "unknown" ? pinnedTarget.provider : "", account: typeof pinnedTarget?.label === "string" && pinnedTarget.label.trim().length > 0 ? pinnedTarget.label.trim() : "", - fingerprint: pinnedTarget ? resolveTargetFingerprint(pinnedTarget) ?? "" : "", + fingerprint: pinnedTarget ? (resolveTargetFingerprint(pinnedTarget) ?? "") : "", }); pinnedResult = await handleSingleModelWithTimeout(pinnedBody, pinnedModel, { modelPinned: true, diff --git a/open-sse/services/comboConfig.ts b/open-sse/services/comboConfig.ts index a08a1312ec..9fa87f1568 100644 --- a/open-sse/services/comboConfig.ts +++ b/open-sse/services/comboConfig.ts @@ -114,6 +114,11 @@ const DEFAULT_COMBO_CONFIG = { handoffProviders: ["codex"], maxMessagesForSummary: 30, maxComboDepth: 3, + // #11134: shared per-request combo attempt budget. Previously the hardcoded + // MAX_GLOBAL_ATTEMPTS with no override — operators could neither fail fast on + // a dead pool nor raise it for large combos. Clamped by clampGlobalAttempts to + // [1, MAX_GLOBAL_ATTEMPTS_HARD_CAP] at every read site. + maxGlobalAttempts: 30, nestedComboMode: "flatten", trackMetrics: true, reasoningTokenBufferEnabled: true, diff --git a/src/shared/validation/schemas/combo.ts b/src/shared/validation/schemas/combo.ts index db825e11cc..988a4ad2db 100644 --- a/src/shared/validation/schemas/combo.ts +++ b/src/shared/validation/schemas/combo.ts @@ -183,6 +183,9 @@ export const comboRuntimeConfigSchema = z handoffProviders: z.array(z.string().trim().min(1).max(100)).max(10).optional(), maxMessagesForSummary: z.coerce.number().int().min(5).max(100).optional(), maxComboDepth: z.coerce.number().int().min(1).max(10).optional(), + // #11134: shared per-request attempt budget. Bounds mirror + // MAX_GLOBAL_ATTEMPTS_HARD_CAP (200) in comboPredicates.ts. + maxGlobalAttempts: z.coerce.number().int().min(1).max(200).optional(), nestedComboMode: z.enum(["flatten", "execute"]).optional(), trackMetrics: z.boolean().optional(), reasoningTokenBufferEnabled: z.boolean().optional(), diff --git a/tests/unit/combo-max-global-attempts-config.test.ts b/tests/unit/combo-max-global-attempts-config.test.ts new file mode 100644 index 0000000000..b7aa4e154b --- /dev/null +++ b/tests/unit/combo-max-global-attempts-config.test.ts @@ -0,0 +1,83 @@ +/** + * tests/unit/combo-max-global-attempts-config.test.ts + * + * Issue #11134: the shared per-request combo attempt budget was the hardcoded + * `MAX_GLOBAL_ATTEMPTS = 30` in comboPredicates.ts, with no env/config override + * (confirmed by the repo owner on the issue). Operators running large combos + * (or wanting to fail fast on a dead pool) could neither raise nor lower it. + * + * This mirrors the established `clampComboDepth` pattern exactly: an operator + * knob (`config.maxGlobalAttempts`) that can raise the default (30) or lower it, + * but never above `MAX_GLOBAL_ATTEMPTS_HARD_CAP` — an unbounded attempt budget + * is the same runaway-request DoS risk that motivated MAX_COMBO_DEPTH_HARD_CAP. + */ +import test from "node:test"; +import assert from "node:assert/strict"; + +test("clampGlobalAttempts — clamps to [1, hard cap]; invalid → default 30", async () => { + const { clampGlobalAttempts, MAX_GLOBAL_ATTEMPTS, MAX_GLOBAL_ATTEMPTS_HARD_CAP } = + await import("../../open-sse/services/combo.ts"); + assert.equal(MAX_GLOBAL_ATTEMPTS, 30, "default budget unchanged"); + assert.equal(MAX_GLOBAL_ATTEMPTS_HARD_CAP, 200, "absolute safety ceiling"); + + // Honors a LOWER configured budget (fail fast on a dead pool — the #11134 symptom). + assert.equal(clampGlobalAttempts(1), 1); + assert.equal(clampGlobalAttempts(5), 5); + // Honors a HIGHER configured budget (large combos legitimately need more). + assert.equal(clampGlobalAttempts(120), 120); + // …but never past the hard cap. + assert.equal(clampGlobalAttempts(10_000), 200, "hard cap at 200"); + // Invalid values fall back to the default, never disabling the budget. + assert.equal(clampGlobalAttempts(0), 30, "0 invalid → default 30"); + assert.equal(clampGlobalAttempts(-4), 30, "negative → default 30"); + assert.equal(clampGlobalAttempts(undefined), 30, "undefined → default 30"); + assert.equal(clampGlobalAttempts("abc"), 30, "non-numeric → default 30"); + assert.equal(clampGlobalAttempts(Number.NaN), 30, "NaN → default 30"); + assert.equal(clampGlobalAttempts(Infinity), 30, "Infinity → default 30 (never unbounded)"); + assert.equal(clampGlobalAttempts(4.9), 4, "floors to 4"); +}); + +test("DEFAULT_COMBO_CONFIG — exposes maxGlobalAttempts so the cascade can override it", async () => { + const { getDefaultComboConfig, resolveComboConfig } = + await import("../../open-sse/services/comboConfig.ts"); + assert.equal(getDefaultComboConfig().maxGlobalAttempts, 30, "default present in config surface"); + + // Per-combo config wins over the global default (standard cascade). + const resolved = resolveComboConfig({ config: { maxGlobalAttempts: 7 } }, {}); + assert.equal(resolved.maxGlobalAttempts, 7); + + // settings.comboDefaults layer also applies. + const fromGlobal = resolveComboConfig({}, { comboDefaults: { maxGlobalAttempts: 50 } }); + assert.equal(fromGlobal.maxGlobalAttempts, 50); +}); + +test("dispatchPrelude — configured budget reaches nesting.attemptBudget.limit", async () => { + const { buildDefaultNesting } = await import("../../open-sse/services/combo/dispatchPrelude.ts"); + // buildDefaultNesting only reads maxComboDepth/maxGlobalAttempts off config; + // the full resolved-config type is irrelevant to this assertion. + const build = (cfg: Record) => + ( + buildDefaultNesting as ( + n: null, + name: string, + c: unknown + ) => { attemptBudget: { limit: number } } + )(null, "c", cfg); + + // Unset → historical default of 30. + assert.equal(build({}).attemptBudget.limit, 30); + // Configured lower → honored (fail fast). + assert.equal(build({ maxGlobalAttempts: 6 }).attemptBudget.limit, 6); + // Configured higher → honored. + assert.equal(build({ maxGlobalAttempts: 90 }).attemptBudget.limit, 90); + // Absurd → hard-capped, never unbounded. + assert.equal(build({ maxGlobalAttempts: 1e9 }).attemptBudget.limit, 200); +}); + +test("combo schema — accepts maxGlobalAttempts within the hard cap, rejects beyond", async () => { + const { comboRuntimeConfigSchema: schema } = + await import("../../src/shared/validation/schemas/combo.ts"); + assert.equal(schema.parse({ maxGlobalAttempts: 45 }).maxGlobalAttempts, 45); + assert.equal(schema.safeParse({ maxGlobalAttempts: 201 }).success, false, "beyond hard cap"); + assert.equal(schema.safeParse({ maxGlobalAttempts: 0 }).success, false, "0 rejected"); +}); From e32b9264e883401ded66b02066f220badba01313 Mon Sep 17 00:00:00 2001 From: Paco Cartones <253313177+pacocartones@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:24:50 +0200 Subject: [PATCH 10/20] fix(cli): resolve dynamic imports to file:// URLs so the DB fallback works on Windows (#11238) Validated on the combined 12-PR batch board: cli-combo-command + windows-esm-import-paths suites pass, typecheck:core clean. pathToFileURL on the four dynamic-import call sites unbreaks the CLI offline DB fallback on Windows. Thank you @pacocartones! --- bin/cli/commands/serve.mjs | 4 +- bin/cli/commands/setup.mjs | 4 +- bin/cli/runtime.mjs | 11 ++- tests/unit/cli-combo-command.test.ts | 9 ++- .../unit/cli/windows-esm-import-paths.test.ts | 68 +++++++++++++++++++ 5 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 tests/unit/cli/windows-esm-import-paths.test.ts diff --git a/bin/cli/commands/serve.mjs b/bin/cli/commands/serve.mjs index 004b4815ac..456e2e6f77 100644 --- a/bin/cli/commands/serve.mjs +++ b/bin/cli/commands/serve.mjs @@ -1,7 +1,7 @@ import { spawn } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { platform, totalmem } from "node:os"; import { t } from "../i18n.mjs"; import { writePidFile, cleanupPidFile, waitForServer } from "../utils/pid.mjs"; @@ -414,7 +414,7 @@ async function runWithSupervisor( if (detectMitmCrash(crashLog)) { try { const PROJECT_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", ".."); - const { updateSettings } = await import(`${PROJECT_ROOT}/src/lib/db/settings.ts`); + const { updateSettings } = await import(pathToFileURL(join(PROJECT_ROOT, "src/lib/db/settings.ts")).href); updateSettings({ mitmEnabled: false }); } catch {} return "disable-mitm-and-retry"; diff --git a/bin/cli/commands/setup.mjs b/bin/cli/commands/setup.mjs index 4ded5032d4..5415cdd70b 100644 --- a/bin/cli/commands/setup.mjs +++ b/bin/cli/commands/setup.mjs @@ -1,4 +1,4 @@ -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { dirname, resolve } from "node:path"; import { createPrompt, printHeading, printInfo, printSuccess } from "../io.mjs"; import { openOmniRouteDb } from "../sqlite.mjs"; @@ -16,7 +16,7 @@ import { t } from "../i18n.mjs"; const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); async function getListCliTools() { - const { listCliTools } = await import(`${PROJECT_ROOT}/src/shared/constants/cliTools.ts`); + const { listCliTools } = await import(pathToFileURL(resolve(PROJECT_ROOT, "src/shared/constants/cliTools.ts")).href); return listCliTools; } diff --git a/bin/cli/runtime.mjs b/bin/cli/runtime.mjs index 6811896759..987fa82799 100644 --- a/bin/cli/runtime.mjs +++ b/bin/cli/runtime.mjs @@ -1,9 +1,14 @@ -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { dirname, resolve } from "node:path"; import { apiFetch, isServerUp } from "./api.mjs"; const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); +// Dynamic `import()` resolves its specifier as a URL, not as a filesystem path. +// On Windows an absolute path starts with a drive letter, which the ESM loader +// reads as the unsupported URL scheme `e:` and rejects. Pass a file:// URL. +const projectFileUrl = (relPath) => pathToFileURL(resolve(PROJECT_ROOT, relPath)).href; + export class ServerOfflineError extends Error { constructor(message = "Server is offline and operation requires HTTP runtime") { super(message); @@ -22,8 +27,8 @@ function makeHttpContext(opts) { async function importDbModules() { const [combos, recovery] = await Promise.all([ - import(`${PROJECT_ROOT}/src/lib/db/combos.ts`), - import(`${PROJECT_ROOT}/src/lib/db/recovery.ts`), + import(projectFileUrl("src/lib/db/combos.ts")), + import(projectFileUrl("src/lib/db/recovery.ts")), ]); return { combos, recovery }; } diff --git a/tests/unit/cli-combo-command.test.ts b/tests/unit/cli-combo-command.test.ts index dc38e3349e..1bf00e341d 100644 --- a/tests/unit/cli-combo-command.test.ts +++ b/tests/unit/cli-combo-command.test.ts @@ -27,7 +27,14 @@ async function withComboEnv(fn: (dataDir: string) => Promise) { } finally { console.log = originalLog; globalThis.fetch = ORIGINAL_FETCH; - fs.rmSync(dataDir, { recursive: true, force: true }); + // On Windows the SQLite file may still be held open by the db module when + // the test ends, and rmSync then throws EPERM, failing a test whose + // assertions all passed. Retry, then give up quietly. + try { + fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); + } catch { + // best effort: the OS reclaims its own temp dir + } if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR; else process.env.DATA_DIR = ORIGINAL_DATA_DIR; diff --git a/tests/unit/cli/windows-esm-import-paths.test.ts b/tests/unit/cli/windows-esm-import-paths.test.ts new file mode 100644 index 0000000000..e85385cc9d --- /dev/null +++ b/tests/unit/cli/windows-esm-import-paths.test.ts @@ -0,0 +1,68 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const PROJECT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); + +// Regression guard for the Windows-only ESM loader failure: +// +// Error: Only URLs with a scheme in: file, data, and node are supported by +// the default ESM loader. On Windows, absolute paths must be valid file:// +// URLs. Received protocol 'e:' +// +// `import()` resolves its specifier as a URL. A POSIX absolute path like +// /home/x/src/lib/db/combos.ts happens to also be a valid relative URL, so +// interpolating it works by accident. A Windows absolute path is +// E:\checkout\src\lib\db\combos.ts, whose leading drive letter the loader +// parses as the URL scheme `e:` and rejects. Every such call site must go +// through pathToFileURL(). +// +// This broke `omniroute combo list/create/delete/switch` on Windows whenever +// the CLI fell back to direct DB access with the server offline. + +const CLI_DIR = path.join(PROJECT_ROOT, "bin", "cli"); + +function collectMjsFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...collectMjsFiles(full)); + else if (entry.name.endsWith(".mjs")) out.push(full); + } + return out; +} + +test("bin/cli never passes an interpolated absolute path to dynamic import()", () => { + // Matches import(`${ANY_ROOT_CONST}/...`) — a raw filesystem path, not a URL. + const badImport = /\bimport\(\s*`\$\{[A-Za-z_$][\w$]*\}\//; + + const offenders: string[] = []; + for (const file of collectMjsFiles(CLI_DIR)) { + const source = fs.readFileSync(file, "utf8"); + source.split(/\r?\n/).forEach((line, i) => { + if (badImport.test(line)) { + offenders.push(`${path.relative(PROJECT_ROOT, file)}:${i + 1}: ${line.trim()}`); + } + }); + } + + assert.deepEqual( + offenders, + [], + "dynamic import() of an interpolated absolute path fails on Windows; " + + `wrap the path in pathToFileURL(...).href instead:\n${offenders.join("\n")}`, + ); +}); + +test("runtime.mjs resolves db modules to a file:// URL", async () => { + const source = fs.readFileSync(path.join(CLI_DIR, "runtime.mjs"), "utf8"); + assert.match(source, /pathToFileURL/, "runtime.mjs must build file:// URLs for dynamic imports"); + + // The real proof: the db fallback modules actually load on this platform. + const runtime = await import(pathToFileURL(path.join(CLI_DIR, "runtime.mjs")).href); + const ctx = await runtime.withDb(async (c: { kind: string; db: unknown }) => c); + assert.equal(ctx.kind, "db"); + assert.ok(ctx.db); +}); From 578db866a403d1d95518e7e710f1b135e49a1c31 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:25:04 -0300 Subject: [PATCH 11/20] fix(cli): complete Windows cliproxy platform handling + pid probe (#11236) (#11263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Residuals of #11236 after #10371/#10491 landed on the tip: - Dist-fold residuals (bugs 2+3): managedBinaryName() (binaryManager), resolveSpawnArgs() (installers/cliproxy) and the per-OS memory probes in getProcessInfo() (processManager) still read the process.platform literal, which the Linux build of the published artifact constant-folds (precedent: b43a212680 / #10244). Converted to call-time os.platform() reads, matching the module's documented anti-fold pattern. Test-side process.platform uses are not bundled and stay. - Fold guard: new tests/unit/windows-platform-fold-guard-11236.test.ts pins zero out-of-comment process.platform occurrences in the four artifact runtime files, with a comment-stripping tokenizer plus mutation self-checks. - Bug 6 (pid null on Windows): portProbe.resolvePortPid() only probed lsof/ss/net-tools netstat. Added a netstat -ano probe with a dedicated LISTENING-row parser (parseWindowsNetstatPid) as the last fallback; Unix probes unchanged, and the Windows parser never matches Unix rows (LISTEN vs LISTENING). Also converted the darwin args branch in the same array to os.platform() (same fold class, same hunk). - Bug 5 hardening: runOAuthStatus coerces an out-of-contract 200 payload to an empty list with a sanitized stderr warning instead of crashing on .filter over a non-array. TDD: guard test, parser tests and the oauth hardening test all failed RED before the fix and pass GREEN after; sibling suites (binaryManager, processManager, portProbePid, cli-oauth-commands, installers, ServiceSupervisor, version-manager) green. The 6877 spawn-args test's win32 mock moved from defineProperty(process.platform) to mock.method(os, "platform") to match the new runtime read — assertion unchanged. Co-authored-by: Xiangzhe --- bin/cli/commands/oauth.mjs | 16 +- src/lib/services/installers/cliproxy.ts | 8 +- src/lib/services/portProbe.ts | 51 +++++- src/lib/versionManager/binaryManager.ts | 10 +- src/lib/versionManager/processManager.ts | 9 +- tests/unit/cli-oauth-commands.test.ts | 35 +++- .../cliproxy-resolve-spawn-args-6877.test.ts | 13 +- tests/unit/services/portProbePid.test.ts | 40 ++++- .../windows-platform-fold-guard-11236.test.ts | 168 ++++++++++++++++++ 9 files changed, 329 insertions(+), 21 deletions(-) create mode 100644 tests/unit/windows-platform-fold-guard-11236.test.ts diff --git a/bin/cli/commands/oauth.mjs b/bin/cli/commands/oauth.mjs index c9f8386d2b..1cdaeb8267 100644 --- a/bin/cli/commands/oauth.mjs +++ b/bin/cli/commands/oauth.mjs @@ -258,8 +258,7 @@ async function runDeviceFlow(def, opts) { process.stdout.write(`\nAuthorization URL not available\n\n`); } - if (opts.browser !== false && verificationUri) - await openBrowser(verificationUri); + if (opts.browser !== false && verificationUri) await openBrowser(verificationUri); process.stderr.write("Waiting for device authorization...\n"); const deadline = Date.now() + (opts.timeout ?? 300000); const intervalMs = (start.intervalMs ?? start.interval ?? 5) * 1000; @@ -320,7 +319,18 @@ export async function runOAuthStatus(opts, cmd) { process.exit(1); } const data = await res.json(); - const connections = (data.connections ?? data.providers ?? data.items ?? data).filter( + const payload = data?.connections ?? data?.providers ?? data?.items ?? data; + // #11236 (bug 5 residual): a 200 whose body is out of contract (no + // connections/providers/items array — e.g. `{"status":"ok"}`) used to fall + // through to `.filter` on a non-array and crash with a bare TypeError plus a + // libuv teardown assertion on Windows. Coerce to an empty list with a + // sanitized one-line warning instead of dumping a stack trace. + if (!Array.isArray(payload)) { + process.stderr.write( + "Warning: unexpected response shape from /api/providers; showing no connections.\n" + ); + } + const connections = (Array.isArray(payload) ? payload : []).filter( (c) => c.authType === "oauth" || c.authType === "oauth2" ); emit(connections, globalOpts, connectionSchema); diff --git a/src/lib/services/installers/cliproxy.ts b/src/lib/services/installers/cliproxy.ts index 6ffb7e8196..dcb7dfc916 100644 --- a/src/lib/services/installers/cliproxy.ts +++ b/src/lib/services/installers/cliproxy.ts @@ -11,6 +11,7 @@ */ import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; import { DATA_DIR } from "@/lib/db/core"; import { upsertVersionManagerTool } from "@/lib/db/versionManager"; @@ -101,7 +102,12 @@ export async function update(): Promise { * async file I/O is not available here. */ export function resolveSpawnArgs(port: number): SpawnArgs { - const executableName = process.platform === "win32" ? "cliproxyapi.exe" : "cliproxyapi"; + // #11236 (bug 3 residual): runtime os.platform() read — a process.platform + // literal here is constant-folded to the Linux build machine when the + // published artifact is bundled, dropping the `.exe` suffix from the spawn + // path on Windows and failing with ENOENT even when a valid .exe exists + // (same fold class as b43a212680 / #10244/#10293). + const executableName = os.platform() === "win32" ? "cliproxyapi.exe" : "cliproxyapi"; const symlinkPath = path.join(BIN_DIR, executableName); fs.mkdirSync(CONFIG_DIR, { recursive: true }); diff --git a/src/lib/services/portProbe.ts b/src/lib/services/portProbe.ts index 9a890f541b..a4111400e5 100644 --- a/src/lib/services/portProbe.ts +++ b/src/lib/services/portProbe.ts @@ -15,6 +15,7 @@ import { createConnection } from "node:net"; import { spawn } from "node:child_process"; +import os from "node:os"; /** Result of probing the service before spawning. */ export interface PreSpawnProbe { @@ -190,6 +191,31 @@ export function parseNetstatPid(stdout: string, port: number): number | null { return null; } +/** + * Windows `netstat -ano` carries the pid in its own last column (#11236): + * + * Proto Local Address Foreign Address State PID + * TCP 0.0.0.0:20128 0.0.0.0:0 LISTENING 12345 + * TCP [::]:20128 [::]:0 LISTENING 12345 + * + * Only TCP LISTENING rows carry a pid (UDP rows have no state column at all). + * The local address is matched on `:` — the `:` anchor keeps a port that + * merely shares a suffix (128 vs 20128) or a foreign address ending in the + * same digits from being read as the listener. + */ +export function parseWindowsNetstatPid(stdout: string, port: number): number | null { + for (const line of stdout.split("\n")) { + const columns = line.trim().split(/\s+/); + // proto local-address foreign-address state pid + if (columns.length < 5) continue; + if (columns[3].toUpperCase() !== "LISTENING") continue; + if (!columns[1].endsWith(`:${port}`)) continue; + const pid = Number.parseInt(columns[columns.length - 1], 10); + if (Number.isFinite(pid)) return pid; + } + return null; +} + /** * Ways to ask the OS which process holds a port, in preference order. * @@ -198,6 +224,12 @@ export function parseNetstatPid(stdout: string, port: number): number | null { * once `spawn` has turned ENOENT into a null. `ss` ships with iproute2 and * `netstat` with net-tools, so between the three there is normally something * to ask on any host the supervisor runs on. + * + * The Windows `netstat -ano` probe runs last: on Windows the earlier probes + * fail fast (lsof/ss do not exist; the net-tools flags are rejected by the + * Windows netstat), while on Unix `netstat -ano` either errors out or prints + * the Linux/macOS row shapes the Windows parser deliberately never matches + * (LISTEN vs LISTENING), so it degrades to a no-op instead of a false pid. */ const PID_PROBES: ReadonlyArray<{ command: string; @@ -212,9 +244,17 @@ const PID_PROBES: ReadonlyArray<{ }, { command: "netstat", - args: () => (process.platform === "darwin" ? ["-anv", "-p", "tcp"] : ["-tlnp"]), + // #11236: runtime os.platform() read — a process.platform literal is + // constant-folded to the Linux build machine in the published artifact, + // pruning the darwin branch on macOS (same fold class as b43a212680). + args: () => (os.platform() === "darwin" ? ["-anv", "-p", "tcp"] : ["-tlnp"]), parse: parseNetstatPid, }, + { + command: "netstat", + args: () => ["-ano"], + parse: parseWindowsNetstatPid, + }, ]; /** Run one probe, resolving null on a missing binary, a non-match or a timeout. */ @@ -264,10 +304,11 @@ function runPidProbe( * way they trust a freshly-spawned one. Returns null if nothing is found or * the lookup fails/times out (best-effort; never blocks adoption on this). * - * Tries `lsof`, then `ss`, then `netstat`, so a host missing any one of them - * still reports a real pid instead of a silent null (#10431). The probes share - * one deadline, so the whole lookup still costs at most - * `PID_RESOLVE_TIMEOUT_MS`. + * Tries `lsof`, then `ss`, then `netstat`, then the Windows `netstat -ano` + * shape, so a host missing any one of them — including a stock Windows host + * with none of the Unix tools — still reports a real pid instead of a silent + * null (#10431, #11236). The probes share one deadline, so the whole lookup + * still costs at most `PID_RESOLVE_TIMEOUT_MS`. */ export async function resolvePortPid(port: number): Promise { const deadline = Date.now() + PID_RESOLVE_TIMEOUT_MS; diff --git a/src/lib/versionManager/binaryManager.ts b/src/lib/versionManager/binaryManager.ts index 743d13f54a..764168dba4 100644 --- a/src/lib/versionManager/binaryManager.ts +++ b/src/lib/versionManager/binaryManager.ts @@ -110,8 +110,16 @@ async function verifyChecksum(filePath: string, expectedSha256: string): Promise return hash.digest("hex").toLowerCase() === expectedSha256.toLowerCase(); } +/** + * #11236: read os.platform() at call time, never the build-foldable + * process.platform literal — the published-artifact build runs on Linux and + * constant-folds it, pruning the win32 branch so the managed binary lost its + * `.exe` suffix on Windows installs (same fold class as b43a212680 / + * #10244/#10293, which converted detectPlatform/detectArch; #10371 fixed the + * name in source but left this literal read behind). + */ function managedBinaryName(): string { - return process.platform === "win32" ? "cliproxyapi.exe" : "cliproxyapi"; + return os.platform() === "win32" ? "cliproxyapi.exe" : "cliproxyapi"; } function findBinaryInDir(dir: string): string | null { diff --git a/src/lib/versionManager/processManager.ts b/src/lib/versionManager/processManager.ts index ec538ab770..55164df864 100644 --- a/src/lib/versionManager/processManager.ts +++ b/src/lib/versionManager/processManager.ts @@ -152,14 +152,19 @@ export async function getProcessInfo(pid: number): Promise<{ } try { - if (process.platform === "linux" || process.platform === "android") { + // #11236: single runtime os.platform() read for the per-OS memory probes — + // a process.platform literal is constant-folded to the build machine's + // platform in the published artifact (same fold class as b43a212680 / + // #10244/#10293), so the darwin probe branch would be pruned on macOS. + const platform = os.platform(); + if (platform === "linux" || platform === "android") { const statusFile = `/proc/${pid}/status`; const content = await fs.readFile(statusFile, "utf-8"); const match = content.match(/VmRSS:\s+(\d+)\s+kB/); if (match) { return { pid, alive: true, memoryUsage: parseInt(match[1], 10) * 1024 }; } - } else if (process.platform === "darwin") { + } else if (platform === "darwin") { const { execFile } = await import("child_process"); const { promisify } = await import("util"); const execFileAsync = promisify(execFile); diff --git a/tests/unit/cli-oauth-commands.test.ts b/tests/unit/cli-oauth-commands.test.ts index 63301f818f..9d67c340a1 100644 --- a/tests/unit/cli-oauth-commands.test.ts +++ b/tests/unit/cli-oauth-commands.test.ts @@ -108,13 +108,46 @@ test("runOAuthStatus consumes the connections envelope", async () => { const parsed = JSON.parse(out); assert.deepEqual( parsed.map((connection: { id: string }) => connection.id), - ["conn1", "conn2"], + ["conn1", "conn2"] ); } finally { globalThis.fetch = origFetch; } }); +test("runOAuthStatus tolerates an out-of-contract 200 payload (#11236)", async () => { + // Bug 5 residual: #10491 added the `data.connections ??` envelope, but a 200 + // whose body is an object without connections/providers/items still fell + // through to `data` itself and crashed on `.filter is not a function` + // (followed by a libuv teardown assertion on Windows). The guard must coerce + // to an empty list and warn on stderr — never throw a raw TypeError. + const origFetch = globalThis.fetch; + // `as unknown as` (not `as any`): this file's no-explicit-any suppression is + // frozen at its pre-existing count, so new casts must be any-free. + globalThis.fetch = (() => + Promise.resolve(makeResp({ status: "ok" }))) as unknown as typeof globalThis.fetch; + + const stderrChunks: string[] = []; + const origStderr = process.stderr.write.bind(process.stderr); + process.stderr.write = ((chunk: string | Uint8Array) => { + if (typeof chunk === "string") stderrChunks.push(chunk); + return true; + }) as typeof process.stderr.write; + + try { + const { runOAuthStatus } = await import("../../bin/cli/commands/oauth.mjs"); + const out = await captureStdout(() => runOAuthStatus({}, makeCmd())); + assert.deepEqual(JSON.parse(out), []); + } finally { + globalThis.fetch = origFetch; + process.stderr.write = origStderr; + } + + const warning = stderrChunks.join(""); + assert.ok(warning.length > 0, "a sanitized warning must be written to stderr"); + assert.ok(!warning.includes("at /"), "warning must not leak a stack trace"); +}); + test("runOAuthRevoke com --yes chama endpoint de revogação", async () => { let capturedUrl = ""; let capturedMethod = ""; diff --git a/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts b/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts index a5e9d5ae6e..cc8b0a3102 100644 --- a/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts +++ b/tests/unit/services/installers/cliproxy-resolve-spawn-args-6877.test.ts @@ -12,7 +12,7 @@ * temp-directory filesystem. */ -import { describe, it, beforeEach, after } from "node:test"; +import { describe, it, beforeEach, after, mock } from "node:test"; import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; @@ -68,8 +68,11 @@ describe("resolveSpawnArgs (#6877 — real filesystem)", () => { }); it("uses the .exe command name on Windows", async () => { - const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform"); - Object.defineProperty(process, "platform", { value: "win32", configurable: true }); + // resolveSpawnArgs reads os.platform() at call time (#11236 — a + // process.platform literal is constant-folded away by the Linux build of + // the published artifact), so the Windows host is simulated through the + // same runtime os.platform() seam binaryManager.test.ts uses for #10244. + const platformMock = mock.method(os, "platform", () => "win32"); try { const { resolveSpawnArgs } = @@ -78,9 +81,7 @@ describe("resolveSpawnArgs (#6877 — real filesystem)", () => { assert.equal(result.command, path.join(dataDir, "bin", "cliproxyapi.exe")); } finally { - if (originalPlatformDescriptor) { - Object.defineProperty(process, "platform", originalPlatformDescriptor); - } + platformMock.mock.restore(); } }); diff --git a/tests/unit/services/portProbePid.test.ts b/tests/unit/services/portProbePid.test.ts index c58bf86045..b9df600eac 100644 --- a/tests/unit/services/portProbePid.test.ts +++ b/tests/unit/services/portProbePid.test.ts @@ -17,6 +17,7 @@ import { parseLsofPid, parseNetstatPid, parseSsPid, + parseWindowsNetstatPid, resolvePortPid, } from "@/lib/services/portProbe"; @@ -65,8 +66,7 @@ test("parseNetstatPid matches on the local address, not the foreign one", () => }); test("parseNetstatPid reads macOS process:pid output", () => { - const stdout = - "tcp4 0 0 127.0.0.1.20128 *.* LISTEN 0 0 131072 131072 node:596922 00100\n"; + const stdout = "tcp4 0 0 127.0.0.1.20128 *.* LISTEN 0 0 131072 131072 node:596922 00100\n"; assert.equal(parseNetstatPid(stdout, 20128), 596922); }); @@ -77,6 +77,42 @@ test("parseNetstatPid ignores non-listening rows and unknown ports", () => { assert.equal(parseNetstatPid("", 20128), null); }); +/** + * Realistic `netstat -ano` sample from Windows 11 (#11236 bug 6): the pid is + * the last whitespace-separated column and only exists on rows whose state is + * LISTENING. This is the only pid probe available on a stock Windows host — + * neither lsof nor ss nor net-tools `netstat -tlnp` exist there, so a Windows + * service adopted by the supervisor reported `pid: null` while healthy. + */ +const WINDOWS_NETSTAT_ANO = [ + "Active Connections", + "", + " Proto Local Address Foreign Address State PID", + " TCP 0.0.0.0:135 0.0.0.0:0 LISTENING 1244", + " TCP 0.0.0.0:20128 0.0.0.0:0 LISTENING 12345", + " TCP 127.0.0.1:8317 0.0.0.0:0 LISTENING 5678", + " TCP 192.168.1.10:52413 140.82.121.4:443 ESTABLISHED 9012", + " TCP [::]:20128 [::]:0 LISTENING 12345", + " UDP 0.0.0.0:5353 *:* 3460", + "", +].join("\r\n"); + +test("parseWindowsNetstatPid reads the pid from a LISTENING row (#11236)", () => { + assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 20128), 12345); + assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 8317), 5678); +}); + +test("parseWindowsNetstatPid matches the local address, not the foreign one", () => { + // 443 appears only as a foreign address on an ESTABLISHED row. + assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 443), null); + // 5353 appears only on a UDP row, which has no LISTENING state. + assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 5353), null); + // A port that shares a suffix with a listening one must not match: 0128 vs + // 20128 — the `:` anchor on the local address prevents the partial hit. + assert.equal(parseWindowsNetstatPid(WINDOWS_NETSTAT_ANO, 128), null); + assert.equal(parseWindowsNetstatPid("", 20128), null); +}); + test("resolvePortPid finds the pid holding a port", async () => { const server = createServer(); await new Promise((resolve) => server.listen(29994, "127.0.0.1", resolve)); diff --git a/tests/unit/windows-platform-fold-guard-11236.test.ts b/tests/unit/windows-platform-fold-guard-11236.test.ts new file mode 100644 index 0000000000..df2812b88b --- /dev/null +++ b/tests/unit/windows-platform-fold-guard-11236.test.ts @@ -0,0 +1,168 @@ +/** + * Structural regression guard for #11236 (Windows cliproxy residuals, bugs 2+3). + * + * Why this guard exists: the published npm artifact is bundled on Linux, and + * the bundler constant-folds every literal `process.platform` read to the + * BUILD machine's platform ("linux"), pruning the win32 branch from the + * shipped artifact. Precedent: b43a212680 (#10244/#10293), which converted + * detectPlatform/detectArch to runtime `os.platform()`/`os.arch()` reads for + * exactly this reason. #10371 later fixed the Windows `.exe` binary name in + * the source but left literal `process.platform` reads behind in the same + * runtime paths, so the shipped artifact still: + * - named the managed binary `cliproxyapi` (no `.exe`) at install time + * (binaryManager.managedBinaryName), and + * - spawned that extension-less path at start time + * (installers/cliproxy.resolveSpawnArgs) -> ENOENT on Windows even with a + * valid `.exe` in place (issue #11236 bugs 2 and 3). + * + * The runtime-safe pattern is a call-time `os.platform()` read. This guard + * fails if `process.platform` reappears outside a comment in any file whose + * platform branch feeds the published artifact's runtime behavior (binary + * name, spawn path, per-OS probe selection). + */ + +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); + +const GUARDED_FILES = [ + "src/lib/versionManager/binaryManager.ts", + "src/lib/versionManager/processManager.ts", + "src/lib/services/installers/cliproxy.ts", + "src/lib/services/portProbe.ts", +]; + +interface Offender { + line: number; + text: string; +} + +/** + * Returns the source with every `//` and `/* ... *\/` comment blanked out + * (replaced by spaces, newlines preserved so line numbers are stable). String + * literals are kept verbatim — a `process.platform` inside one is still + * flagged, which is acceptable: none of the guarded files carry the pattern + * in a string, and a false positive there is safer than a false negative in + * code. + */ +function stripComments(source: string): string { + let out = ""; + let i = 0; + let inBlock = false; + let inLine = false; + let inString: string | null = null; + while (i < source.length) { + const ch = source[i]; + const next = source[i + 1]; + if (inLine) { + if (ch === "\n") { + inLine = false; + out += ch; + } else { + out += " "; + } + i++; + continue; + } + if (inBlock) { + if (ch === "*" && next === "/") { + inBlock = false; + out += " "; + i += 2; + continue; + } + out += ch === "\n" ? "\n" : " "; + i++; + continue; + } + if (inString) { + out += ch; + if (ch === "\\") { + out += next ?? ""; + i += 2; + continue; + } + if (ch === inString) inString = null; + i++; + continue; + } + if (ch === "/" && next === "/") { + inLine = true; + out += " "; + i += 2; + continue; + } + if (ch === "/" && next === "*") { + inBlock = true; + out += " "; + i += 2; + continue; + } + if (ch === '"' || ch === "'" || ch === "`") inString = ch; + out += ch; + i++; + } + return out; +} + +/** + * Every remaining `process.platform` occurrence after comment stripping is an + * offender — the fold-explanation comments reference the pattern by name and + * must remain free to do so. + */ +function findFoldableReads(source: string): Offender[] { + const stripped = stripComments(source); + const offenders: Offender[] = []; + stripped.split("\n").forEach((line, index) => { + if (line.includes("process.platform")) { + offenders.push({ line: index + 1, text: source.split("\n")[index].trim() }); + } + }); + return offenders; +} + +for (const relPath of GUARDED_FILES) { + test(`${relPath} has no build-foldable process.platform reads (#11236)`, () => { + const source = fs.readFileSync(path.join(REPO_ROOT, relPath), "utf8"); + const offenders = findFoldableReads(source); + assert.deepEqual( + offenders, + [], + `${relPath} must read os.platform() at call time instead of the ` + + `build-foldable process.platform literal (Turbopack folds it to the ` + + `Linux build machine — b43a212680 / #10244 / #10371). Offenders: ` + + offenders.map((o) => `L${o.line}: ${o.text}`).join("; ") + ); + }); +} + +// Guard-the-guard (mutation check on synthetic input, so the real sources +// never need to be touched): a code occurrence MUST be caught, comment-only +// occurrences MUST be let through. +test("findFoldableReads catches a code occurrence (mutation self-check)", () => { + const snippet = [ + 'const name = process.platform === "win32" ? "a.exe" : "a";', + "// process.platform in a line comment is allowed", + "/**", + " * process.platform in a block comment is allowed", + " */", + "/* process.platform single-line block is allowed */", + "const ok = os.platform();", + ].join("\n"); + const offenders = findFoldableReads(snippet); + assert.equal(offenders.length, 1); + assert.equal(offenders[0].line, 1); +}); + +test("findFoldableReads reports nothing when only comments mention the pattern", () => { + const snippet = [ + "// process.platform", + "/* process.platform */", + "const p = os.platform();", + ].join("\n"); + assert.deepEqual(findFoldableReads(snippet), []); +}); From af90cb7f9bf413c664b313716457f2cc0f171b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ismail=20=C3=A7elik?= Date: Sun, 23 Aug 2026 20:25:09 +0300 Subject: [PATCH 12/20] docs(i18n): improve and complete Turkish documentation translations (#11237) Validated on the combined 12-PR batch board: check:docs-all passes (docs-sync, doc-links over 855 links, fabricated-docs strict). 26-file Turkish documentation suite at full parity with consistent terminology. Thank you @ismailcelik-tr! --- docs/i18n/tr/CLAUDE.md | 397 +- docs/i18n/tr/CODE_OF_CONDUCT.md | 140 +- docs/i18n/tr/CONTRIBUTING.md | 426 ++- docs/i18n/tr/GEMINI.md | 23 +- docs/i18n/tr/README.md | 3406 +++++++---------- docs/i18n/tr/SECURITY.md | 261 +- .../i18n/tr/docs/architecture/ARCHITECTURE.md | 1100 ++---- .../architecture/CODEBASE_DOCUMENTATION.md | 632 +-- .../tr/docs/cloudflare-zero-trust-guide.md | 127 +- docs/i18n/tr/docs/features/context-relay.md | 126 +- docs/i18n/tr/docs/frameworks/A2A-SERVER.md | 193 +- docs/i18n/tr/docs/frameworks/MCP-SERVER.md | 137 +- docs/i18n/tr/docs/guides/CLI-INTEGRATIONS.md | 278 +- docs/i18n/tr/docs/guides/FEATURES.md | 280 +- docs/i18n/tr/docs/guides/I18N.md | 485 +-- docs/i18n/tr/docs/guides/TROUBLESHOOTING.md | 366 +- docs/i18n/tr/docs/guides/UNINSTALL.md | 137 +- docs/i18n/tr/docs/guides/USER_GUIDE.md | 983 +---- docs/i18n/tr/docs/ops/COVERAGE_PLAN.md | 189 +- .../tr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md | 477 +-- docs/i18n/tr/docs/ops/RELEASE_CHECKLIST.md | 81 +- docs/i18n/tr/docs/ops/VM_DEPLOYMENT_GUIDE.md | 377 +- docs/i18n/tr/docs/reference/API_REFERENCE.md | 460 +-- docs/i18n/tr/docs/reference/CLI-TOOLS.md | 743 +--- docs/i18n/tr/docs/reference/ENVIRONMENT.md | 701 +--- docs/i18n/tr/docs/routing/AUTO-COMBO.md | 100 +- 26 files changed, 3063 insertions(+), 9562 deletions(-) diff --git a/docs/i18n/tr/CLAUDE.md b/docs/i18n/tr/CLAUDE.md index a962ff3ec9..2273575689 100644 --- a/docs/i18n/tr/CLAUDE.md +++ b/docs/i18n/tr/CLAUDE.md @@ -1,390 +1,49 @@ # CLAUDE.md (Türkçe) -🌐 **Languages:** 🇺🇸 [English](../../../CLAUDE.md) · 🇸🇦 [ar](../ar/CLAUDE.md) · 🇦🇿 [az](../az/CLAUDE.md) · 🇧🇬 [bg](../bg/CLAUDE.md) · 🇧🇩 [bn](../bn/CLAUDE.md) · 🇨🇿 [cs](../cs/CLAUDE.md) · 🇩🇰 [da](../da/CLAUDE.md) · 🇩🇪 [de](../de/CLAUDE.md) · 🇪🇸 [es](../es/CLAUDE.md) · 🇮🇷 [fa](../fa/CLAUDE.md) · 🇫🇮 [fi](../fi/CLAUDE.md) · 🇫🇷 [fr](../fr/CLAUDE.md) · 🇮🇳 [gu](../gu/CLAUDE.md) · 🇮🇱 [he](../he/CLAUDE.md) · 🇮🇳 [hi](../hi/CLAUDE.md) · 🇭🇺 [hu](../hu/CLAUDE.md) · 🇮🇩 [id](../id/CLAUDE.md) · 🇮🇩 [in](../in/CLAUDE.md) · 🇮🇹 [it](../it/CLAUDE.md) · 🇯🇵 [ja](../ja/CLAUDE.md) · 🇰🇷 [ko](../ko/CLAUDE.md) · 🇮🇳 [mr](../mr/CLAUDE.md) · 🇲🇾 [ms](../ms/CLAUDE.md) · 🇳🇱 [nl](../nl/CLAUDE.md) · 🇳🇴 [no](../no/CLAUDE.md) · 🇵🇭 [phi](../phi/CLAUDE.md) · 🇵🇱 [pl](../pl/CLAUDE.md) · 🇵🇹 [pt](../pt/CLAUDE.md) · 🇧🇷 [pt-BR](../pt-BR/CLAUDE.md) · 🇷🇴 [ro](../ro/CLAUDE.md) · 🇷🇺 [ru](../ru/CLAUDE.md) · 🇸🇰 [sk](../sk/CLAUDE.md) · 🇸🇪 [sv](../sv/CLAUDE.md) · 🇰🇪 [sw](../sw/CLAUDE.md) · 🇮🇳 [ta](../ta/CLAUDE.md) · 🇮🇳 [te](../te/CLAUDE.md) · 🇹🇭 [th](../th/CLAUDE.md) · 🇺🇦 [uk-UA](../uk-UA/CLAUDE.md) · 🇵🇰 [ur](../ur/CLAUDE.md) · 🇻🇳 [vi](../vi/CLAUDE.md) · 🇨🇳 [zh-CN](../zh-CN/CLAUDE.md) +🌐 **Languages:** 🇺🇸 [English](../../../CLAUDE.md) · 🇸🇦 [ar](../ar/CLAUDE.md) · 🇦🇿 [az](../az/CLAUDE.md) · 🇧🇬 [bg](../bg/CLAUDE.md) · 🇧🇩 [bn](../bn/CLAUDE.md) · 🇨🇿 [cs](../cs/CLAUDE.md) · 🇩🇰 [da](../da/CLAUDE.md) · 🇩🇪 [de](../de/CLAUDE.md) · 🇪🇸 [es](../es/CLAUDE.md) · 🇮🇷 [fa](../fa/CLAUDE.md) · 🇫🇮 [fi](../fi/CLAUDE.md) · 🇫🇷 [fr](../fr/CLAUDE.md) · 🇮🇳 [gu](../gu/CLAUDE.md) · 🇮🇱 [he](../he/CLAUDE.md) · 🇮🇳 [hi](../hi/CLAUDE.md) · 🇭🇺 [hu](../hu/CLAUDE.md) · 🇮🇩 [id](../id/CLAUDE.md) · 🇮🇩 [in](../in/CLAUDE.md) · 🇮🇹 [it](../it/CLAUDE.md) · 🇯🇵 [ja](../ja/CLAUDE.md) · 🇰🇷 [ko](../ko/CLAUDE.md) · 🇮🇳 [mr](../mr/CLAUDE.md) · 🇲🇾 [ms](../ms/CLAUDE.md) · 🇳🇱 [nl](../nl/CLAUDE.md) · 🇳🇴 [no](../no/CLAUDE.md) · 🇵🇭 [phi](../phi/CLAUDE.md) · 🇵🇱 [pl](../pl/CLAUDE.md) · 🇵🇹 [pt](../pt/CLAUDE.md) · 🇧🇷 [pt-BR](../pt-BR/CLAUDE.md) · 🇷🇴 [ro](../ro/CLAUDE.md) · 🇷🇺 [ru](../ru/CLAUDE.md) · 🇸🇰 [sk](../sk/CLAUDE.md) · 🇸🇪 [sv](../sv/CLAUDE.md) · 🇰🇪 [sw](../sw/CLAUDE.md) · 🇮🇳 [ta](../ta/CLAUDE.md) · 🇮🇳 [te](../te/CLAUDE.md) · 🇹🇭 [th](../th/CLAUDE.md) · 🇹🇷 [tr](../tr/CLAUDE.md) · 🇺🇦 [uk-UA](../uk-UA/CLAUDE.md) · 🇵🇰 [ur](../ur/CLAUDE.md) · 🇻🇳 [vi](../vi/CLAUDE.md) · 🇨🇳 [zh-CN](../zh-CN/CLAUDE.md) --- -Bu dosya, bu depoda kod çalıştırırken Claude Code (claude.ai/code) için rehberlik sağlar. +@AGENTS.md -## Hızlı Başlangıç +**Tüm proje kuralları [`AGENTS.md`](AGENTS.md) dosyasında yer almaktadır** — her yapay zeka asistanı için tek doğruluk kaynağıdır (mimari, kurallar, testler, kalite kapıları, git iş akışı, 23 Katı Kural, PII öğrenimleri). Tamamını okuyun; buraya yeniden proje kuralları eklemeyin. Aşağıdaki her şey YALNIZCA Claude Code için geçerlidir — `AGENTS.md` içinde zaten tanımlanmış kuralların operasyonel ayrıntılarıdır. -```bash -npm install # Bağımlılıkları yükle (otomatik olarak .env.example'dan .env oluşturur) -npm run dev # Geliştirme sunucusu http://localhost:20128 -npm run build # Üretim derlemesi (Next.js 16 bağımsız) -npm run lint # ESLint (0 hata bekleniyor; uyarılar önceden mevcut) -npm run typecheck:core # TypeScript kontrolü (temiz olmalı) -npm run typecheck:noimplicit:core # Sıkı kontrol (implicit any yok) -npm run test:coverage # Birim testleri + kapsama kapısı (75/75/75/70 — ifadeler/hatlar/fonksiyonlar/dallar) -npm run check # lint + test birleştirilmiş -npm run check:cycles # Dairesel bağımlılıkları tespit et -``` +## Worktree İzolasyonu — Claude Code Özel Notları -### Testleri Çalıştırma +Tam zorunlu worktree protokolü (hedef dal onayı, `.claude/worktrees/` kurallı yolu, `cp -al` node_modules, kaldırma kuralları) `AGENTS.md` → Git Workflow → "Worktree isolation" bölümündedir. Claude Code özel noktaları: -```bash -# Tek test dosyası (Node.js yerel test koşucusu — çoğu test) -node --import tsx/esm --test tests/unit/your-file.test.ts +- Operatör daha önce belirtmediyse, hedef dalı `AskUserQuestion` (Katı Kural #19) ile onaylayın. +- Yerel `EnterWorktree` aracını tercih edin — worktree'leri zaten `.claude/worktrees/` altında oluşturur (kurallı yol). Belgelenen `git worktree add` komutuyla worktree oluşturun, ardından `path` parametresi ile `EnterWorktree` çağırın. -# Vitest (MCP sunucusu, autoCombo, önbellek) -npm run test:vitest +## Oturumlar Arası Güvenlik — Claude Code Özel Notları -# Tüm test paketleri -npm run test:all -``` +Katı Kurallar #19/#21/#22 (`AGENTS.md` içinde) paralel oturumları yönetir. Bu ortam için operasyonel hatırlatmalar: -Tam test matrisini görmek için `CONTRIBUTING.md` → "Testleri Çalıştırma" kısmına bakın. Derin mimari için `AGENTS.md` dosyasına bakın. +- **Git'e dokunan her alt ajanın isteminde `git stash` yasağını kelimesi kelimesine tekrarlayın** (Agent tool / Workflow betikleri) — alt ajanlar bu dosyayı devralmaz ve kaydedilen stash olayı bir alt ajan aracılığıyla gerçekleşti. +- _Bu oturumda_ oluşturmadığınız herhangi bir PR'ı birleştirmeden veya push etmeden önce `git worktree list` çalıştırın ve `gh pr view --json state,headRefOid` kontrolü yapın (Katı Kural #22b). +- Her oturumu, ana checkout başladığı dalda olacak şekilde sonlandırın. ---- +## Superpowers / Planlama Yapıtları — Yol Geçersiz Kılmaları -## Projeye Genel Bakış +`_tasks/` kuralı `AGENTS.md` → "Planning & Research Artifacts" içinde tanımlanmıştır. Superpowers yetenekleri `docs/…` dizinini işaret eden varsayılanlarla gelir — bu varsayılanlar **burada geçersiz kılınmıştır**. Bir superpowers yeteneği "saved to `docs/superpowers/plans/…`" gibi bir yol duyurduğunda, yazmadan önce onu `_tasks/…` eşdeğerine yeniden yazın: -**OmniRoute** — birleşik AI proxy/yönlendirici. Tek uç nokta, 329 LLM sağlayıcısı, otomatik geri dönüş. +| Yapıt (Yetenek) | Varsayılan (KULLANMAYIN) | Bunun yerine buraya kaydedin | +| ---------------------------------- | ------------------------- | ------------------------------------------------------------- | +| Planlar (`writing-plans`) | `docs/superpowers/plans/` | `_tasks/superpowers/plans/YYYY-MM-DD-.md` | +| Şartnameler / tasarım (`brainstorming`) | `docs/superpowers/specs/` | `_tasks/superpowers/specs/YYYY-MM-DD--design.md` | +| Araştırma (`deep-research`, ad-hoc)| `docs/research/` | `_tasks/research/…` | +| Devirler (`/handoff`) | — | `_tasks/hands-off/__v_sess-/` | -| Katman | Konum | Amaç | -| ------------- | ----------------------- | ------------------------------------------------------------------------- | -| API Yolları | `src/app/api/v1/` | Next.js Uygulama Yönlendiricisi — giriş noktaları | -| İşleyiciler | `open-sse/handlers/` | İstek işleme (sohbet, gömme, vb.) | -| Yürütücüler | `open-sse/executors/` | Sağlayıcıya özel HTTP dağıtımı | -| Çeviriciler | `open-sse/translator/` | Format dönüşümü (OpenAI↔Claude↔Gemini) | -| Dönüştürücü | `open-sse/transformer/` | Yanıtlar API ↔ Sohbet Tamamlamaları | -| Hizmetler | `open-sse/services/` | Kombinasyon yönlendirme, hız sınırlamaları, önbellekleme, vb. | -| Veritabanı | `src/lib/db/` | 110 top-level SQLite domain modules, 130 migrations | -| Alan/Politika | `src/domain/` | Politika motoru, maliyet kuralları, geri dönüş mantığı | -| MCP Sunucusu | `open-sse/mcp-server/` | 107 unique tools, 3 transports (stdio / SSE / Streamable HTTP), 32 scopes | -| A2A Sunucusu | `src/lib/a2a/` | JSON-RPC 2.0 ajan protokolü | -| Beceriler | `src/lib/skills/` | Genişletilebilir beceri çerçevesi | -| Bellek | `src/lib/memory/` | Kalıcı konuşma belleği | +Bu yapıtları `_tasks/` deposu içinde commit edin (`git -C _tasks …`), asla ana depoda değil. -Monorepo: `src/` (Next.js 16 uygulaması), `open-sse/` (akış motoru çalışma alanı), `electron/` (masaüstü uygulaması), `tests/`, `bin/` (CLI giriş noktası). +## Geçici Dosyalar — `/tmp` Değil `_artifacts/` Kullanın ---- +Bu proje, çalışma ortamının varsayılan oturum karalama alanını (`/tmp/claude-*/…`) geçersiz kılar. Geçici/çalışma dosyalarını — dışa aktarmaları, oluşturulan zip'leri, tek seferlik ara çıktıları, aksi halde `/tmp` içine koyacağınız her şeyi — bunun yerine `/home/diegosouzapw/dev/proxys/OmniRoute/_artifacts/` dizinine yazın. -## İstek Boru Hattı +- `_artifacts/` bir kök `_*` yoludur: zaten gitignore edilmiştir (`AGENTS.md` → "Root `_*` paths"), yalnızca diskte yaşar, asla takip edilmez. +- Gerekçe: karalama çıktılarını proje içinde tutmak (vs `/tmp`), operatörün geçici her şeyi tek bir yerde bulup silmesini kolaylaştırır. +- Bunu `_tasks/` (Katı Kural #23, kalıcı planlar/şartnameler/araştırmalar için kendi özel git deposu) ile **karıştırmayın** — `_artifacts/` yalnızca tek kullanımlık çalışma dosyaları içindir. -``` -Client → /v1/chat/completions (Next.js route) - → CORS → Zod doğrulama → kimlik doğrulama? → politika kontrolü → istemci enjeksiyon koruması - → handleChatCore() [open-sse/handlers/chatCore.ts] - → önbellek kontrolü → oran sınırlaması → kombinasyon yönlendirmesi? - → resolveComboTargets() → hedef başına handleSingleModel() - → translateRequest() → getExecutor() → executor.execute() - → fetch() yukarı akış → geri çekilme ile yeniden deneme - → yanıt çevirisi → SSE akışı veya JSON - → Eğer Yanıtlar API'si: responsesTransformer.ts TransformStream -``` +## PR Açmadan Önce Base-Green Kontrolü -API yolları tutarlı bir desen izler: `Route → CORS ön uç → Zod gövde doğrulama → Opsiyonel kimlik doğrulama (extractApiKey/isValidApiKey) → API anahtarı politika uygulaması → İşleyici delegasyonu (open-sse)`. Global Next.js ara yazılımı yok — kesme işlemi yol spesifik. - -**Combo routing** (`open-sse/services/combo.ts`): 19 public strategies (priority, weighted, fill-first, round-robin, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, context-relay, fusion, pipeline). Each target calls `handleSingleModel()`, which wraps `handleChatCore()` with per-target error handling and circuit-breaker checks. See `docs/routing/AUTO-COMBO.md` for the 13-factor Auto-Combo scoring and `docs/architecture/RESILIENCE_GUIDE.md` for the 3 resilience layers. - ---- - -## Dayanıklılık Çalışma Durumu - -OmniRoute, üç ilgili ancak farklı geçici hata mekanizmasına sahiptir. Yönlendirme davranışını hata ayıklarken kapsamlarını ayrı tutun. Bir bakışta harita için [3 katmanlı dayanıklılık diyagramı](./docs/diagrams/exported/resilience-3layers.svg) (kaynak: [docs/diagrams/resilience-3layers.mmd](./docs/diagrams/resilience-3layers.mmd))'na bakın. - -### Sağlayıcı Devre Kesici - -**Kapsam**: tüm sağlayıcı, örneğin `glm`, `openai`, `anthropic`. - -**Amaç**: yukarı akış/hizmet seviyesinde sürekli olarak başarısız olan bir sağlayıcıya trafik göndermeyi durdurmak, böylece bir sağlıksız sağlayıcı her isteği yavaşlatmaz. - -**Uygulama**: - -- Temel sınıf: `src/shared/utils/circuitBreaker.ts` -- Sohbet kapısı/uygulama kablolaması: `src/sse/handlers/chatHelpers.ts`, `src/sse/handlers/chat.ts` -- Çalışma durumu API'si: `src/app/api/monitoring/health/route.ts` -- Paylaşılan sarmalayıcılar: `open-sse/services/accountFallback.ts` -- Kalıcı durum tablosu: `domain_circuit_breakers` - -**Durumlar**: - -- `CLOSED`: normal trafik izin verilir. -- `OPEN`: sağlayıcı geçici olarak engellenmiştir; arayanlar bir sağlayıcı-devre-açık yanıtı alır veya kombinasyon yönlendirmesi başka bir hedefe atlar. -- `HALF_OPEN`: sıfırlama zaman aşımı dolmuştur; bir prob isteğine izin verilir. Başarı devre kesiciyi kapatır, başarısızlık tekrar açar. - -**Varsayılanlar** (`open-sse/config/constants.ts`): - -- OAuth sağlayıcıları: eşik `3`, sıfırlama zaman aşımı `60s`. -- API anahtarı sağlayıcıları: eşik `5`, sıfırlama zaman aşımı `30s`. -- Yerel sağlayıcılar: eşik `2`, sıfırlama zaman aşımı `15s`. - -Sadece sağlayıcı düzeyindeki hata durumları sağlayıcı devre kesicisini tetiklemelidir: - -```ts -(408, 500, 502, 503, 504); -``` - -Normal hesap/anahtar/model hataları gibi çoğu `401`, `403` veya `429` durumları için tüm sağlayıcı devre kesicisini tetiklemeyin. Bunlar genellikle bağlantı soğuma veya model kilitlenmesi ile ilgilidir. Genel bir API anahtarı sağlayıcı `403` kurtarılabilir olmalıdır, aksi takdirde terminal sağlayıcı/hesap hatası olarak sınıflandırılır. - -Devre kesici tembel kurtarma kullanır, arka planda bir zamanlayıcı değil. `OPEN` süresi dolduğunda, `getStatus()`, `canExecute()` ve `getRetryAfterMs()` gibi okumalar durumu `HALF_OPEN` olarak yeniler, böylece paneller ve kombinasyon aday oluşturucuları süresi dolmuş bir sağlayıcıyı sonsuza kadar hariç tutmaz. - -### Bağlantı Soğuma - -**Kapsam**: bir sağlayıcı bağlantısı/hesap/anahtar. - -**Amaç**: aynı sağlayıcı için diğer bağlantıların istekleri karşılamaya devam etmesine izin verirken, bir kötü anahtar/hesabı geçici olarak atlamak. - -**Uygulama**: - -- Yazma/güncelleme yolu: `src/sse/services/auth.ts::markAccountUnavailable()` -- Hesap seçimi/filtreleme: `src/sse/services/auth.ts::getProviderCredentials...` -- Soğuma hesaplaması: `open-sse/services/accountFallback.ts::checkFallbackError()` -- Ayarlar: `src/lib/resilience/settings.ts` - -Sağlayıcı bağlantılarındaki önemli alanlar: - -```ts -rateLimitedUntil; -testStatus: "unavailable"; -lastError; -lastErrorType; -errorCode; -backoffLevel; -``` - -Hesap seçimi sırasında, bir bağlantı atlanırken: - -```ts -new Date(rateLimitedUntil).getTime() > Date.now(); -``` - -Soğumalar da tembel: `rateLimitedUntil` geçmişte olduğunda, bağlantı tekrar uygun hale gelir. Başarılı kullanımda, `clearAccountError()` `testStatus`, `rateLimitedUntil`, hata alanlarını ve `backoffLevel`'ı temizler. - -Varsayılan bağlantı soğuma davranışı: - -- OAuth temel soğuma: `5s`. -- API anahtarı temel soğuma: `3s`. -- API anahtarı `429`, mevcut olduğunda yukarı akış yeniden deneme ipuçlarını (`Retry-After`, sıfırlama başlıkları veya ayrıştırılabilir sıfırlama metni) tercih etmelidir. -- Tekrarlanan kurtarılabilir hatalar üstel geri çekilme kullanır: - -```ts -baseCooldownMs * 2 ** failureIndex; -``` - -Anti-thundering-herd koruması, aynı bağlantıda eşzamanlı hataların soğumayı sürekli uzatmasını veya `backoffLevel`'ı iki katına çıkarmasını önler. - -Terminal durumlar soğumalar değildir. `banned`, `expired` ve `credits_exhausted` kimlik bilgileri/ayarlar değişene kadar veya bir operatör bunları sıfırlayana kadar kullanılamaz durumda kalması amaçlanmıştır. Terminal durumları geçici soğuma durumu ile üzerine yazmayın. - -### Model Kilitlenmesi - -**Kapsam**: sağlayıcı + bağlantı + model. - -**Amaç**: yalnızca bir modelin kullanılamaz veya kota sınırlı olduğu durumlarda tüm bağlantıyı devre dışı bırakmaktan kaçınmak. - -Örnekler: - -- Her model için kota sağlayıcıları `429` döndürüyor. -- Bir eksik model için `404` döndüren yerel sağlayıcılar. -- Seçilen Grok modları gibi sağlayıcıya özgü mod/model izin hataları. - -Model kilitlenmesi `open-sse/services/accountFallback.ts` içinde yer alır ve aynı bağlantının diğer modelleri sunmaya devam etmesine izin verir. - -### Hata Ayıklama Rehberi - -- Bir sağlayıcı için tüm anahtarlar atlanıyorsa, hem sağlayıcı devre kesici durumunu hem de her bağlantının `rateLimitedUntil`/`testStatus`'ını kontrol edin. -- Bir sağlayıcı sıfırlama penceresinden sonra kalıcı olarak hariç tutuluyorsa, kodun `getStatus()`/`canExecute()` yerine ham `state` okuduğundan emin olun. -- Bir sağlayıcı anahtarı başarısız olursa ancak diğerleri çalışıyorsa, sağlayıcı devre kesicisi yerine bağlantı soğumasını tercih edin. -- Sadece bir model başarısız olursa, bağlantı soğuması yerine model kilitlenmesini tercih edin. -- Bir durum kendiliğinden kurtulmalıysa, gelecekteki bir zaman damgasına/sıfırlama zaman aşımına ve süresi dolmuş durumu yenileyen bir okuma yoluna sahip olmalıdır. Kalıcı durumlar manuel kimlik bilgisi veya yapılandırma değişiklikleri gerektirir. - -## Anahtar Sözleşmeler - -### Kod Stili - -- **2 boşluk**, noktalı virgüller, çift tırnak, 100 karakter genişliği, es5 son virgüller (lint-staged tarafından Prettier ile zorunlu kılınır) -- **İthalatlar**: harici → dahili (`@/`, `@omniroute/open-sse`) → göreceli -- **İsimlendirme**: dosyalar=camelCase/kebab, bileşenler=PascalCase, sabitler=UPPER_SNAKE -- **ESLint**: `no-eval`, `no-implied-eval`, `no-new-func` = her yerde hata; `no-explicit-any` = `open-sse/` ve `tests/` içinde uyarı -- **TypeScript**: `strict: false`, hedef ES2022, modül esnext, çözümleyici paketleyici. Açık türleri tercih edin. - -### Veritabanı - -- **Her zaman** `src/lib/db/` alan modüllerinden geçin — **asla** rotalarda veya işleyicilerde ham SQL yazmayın -- **Asla** `src/lib/localDb.ts` içine mantık eklemeyin (sadece yeniden ihracat katmanı) -- **Asla** `localDb.ts`'den silindirik ithalat yapmayın — bunun yerine belirli `db/` modüllerini içe aktarın -- DB singleton: `getDbInstance()` `src/lib/db/core.ts`'den (WAL günlüğü) -- Göçler: `src/lib/db/migrations/` — sürümlü SQL dosyaları, idempotent, işlemler içinde çalıştırılır - -### Hata Yönetimi - -- belirli hata türleri ile try/catch, pino bağlamı ile günlüğe kaydet -- SSE akışlarında hataları yutmayın — temizlik için iptal sinyalleri kullanın -- Uygun HTTP durum kodlarını döndürün (4xx/5xx) - -### Güvenlik - -- **Asla** `eval()`, `new Function()`, veya dolaylı eval kullanmayın -- Tüm girdileri Zod şemaları ile doğrulayın -- Kimlik bilgilerini dinlenirken şifreleyin (AES-256-GCM) -- Yukarı akış başlıkları yasak listesi: `src/shared/constants/upstreamHeaders.ts` — düzenlerken temizleme, Zod şemaları ve birim testlerinin uyumlu kalmasını sağlayın -- **Halka açık yukarı akış kimlik bilgileri** (Gemini/Antigravity/Windsurf tarzı OAuth client_id/secret + halka açık CLI'lerden çıkarılan Firebase Web anahtarları): **MUTLAKA** `resolvePublicCred()` ile gömülmelidir `open-sse/utils/publicCreds.ts`'den — **asla** dize sabitleri olarak. Zorunlu desen için `docs/security/PUBLIC_CREDS.md`'ye bakın. -- **Hata yanıtları** (HTTP / SSE / yürütücü / MCP işleyici): **MUTLAKA** `buildErrorBody()` veya `sanitizeErrorMessage()` üzerinden yönlendirilmelidir `open-sse/utils/error.ts`'den — **asla** ham `err.stack` veya `err.message`'i bir yanıt gövdesine koymayın. `docs/security/ERROR_SANITIZATION.md`'ye bakın. -- **Değişkenlerden oluşturulan kabuk komutları**: `exec()`/`spawn()` ile çalışma zamanı değerlerine ihtiyaç duyan bir betik çağırırken, bunları `env` seçeneği aracılığıyla geçirin (otomatik olarak kabukta kaçış yapılır) — **asla** güvenilmeyen/dış yolları betik gövdesine dize ile birleştirmeyin. Referans: `src/mitm/cert/install.ts::updateNssDatabases`. -- **Varsayılan olarak güvenli kütüphaneler** ([tldrsec/awesome-secure-defaults](https://github.com/tldrsec/awesome-secure-defaults)): yeni güvenlik hassas yüzeyleri eklerken, özel uygulamalar yerine Helmet.js, DOMPurify, ssrf-req-filter, safe-regex, Google Tink'i tercih edin. - ---- - -## Yaygın Değişiklik Senaryoları - -### Yeni Bir Sağlayıcı Ekleme - -1. `src/shared/constants/providers.ts` içinde kaydedin (yükleme sırasında Zod ile doğrulanır) -2. Özel mantık gerekiyorsa `open-sse/executors/` içinde yürütücü ekleyin ( `BaseExecutor`'ı genişletin) -3. OpenAI dışı bir format varsa `open-sse/translator/` içinde çevirmen ekleyin -4. OAuth tabanlı ise `src/lib/oauth/constants/oauth.ts` içinde OAuth yapılandırması ekleyin — yukarı akış CLI'si halka açık bir client_id/secret gönderiyorsa, `resolvePublicCred()` aracılığıyla gömün (bkz. `docs/security/PUBLIC_CREDS.md`), **asla** bir literal olarak -5. `open-sse/config/providerRegistry.ts` içinde modelleri kaydedin -6. `tests/unit/` içinde testler yazın (yeni bir gömülü varsayılan eklediyseniz publicCreds şekil doğrulamasını dahil edin) - -### Yeni Bir API Rotası Ekleme - -1. `src/app/api/v1/your-route/` altında dizin oluşturun -2. `GET`/`POST` işleyicileri ile `route.ts` oluşturun -3. Deseni takip edin: CORS → Zod gövde doğrulaması → isteğe bağlı kimlik doğrulama → işleyici delegasyonu -4. İşleyici `open-sse/handlers/` içinde yer alır (oradan içe aktarın, satır içinde değil) -5. Hata yanıtları `buildErrorBody()` / `errorResponse()` kullanır `open-sse/utils/error.ts`'den (otomatik olarak temizlenir — asla `err.stack` veya `err.message`'i ham olarak gövdeye koymayın). `docs/security/ERROR_SANITIZATION.md`'ye bakın. -6. Testler ekleyin — hata yanıtlarının yığın izlerini sızdırmadığını doğrulayan en az bir doğrulama dahil edin (`!body.error.message.includes("at /")`) - -### Yeni Bir DB Modülü Ekleme - -1. `src/lib/db/yourModule.ts` oluşturun — `./core.ts`'den `getDbInstance`'i içe aktarın -2. Alan tablonuz için CRUD işlevlerini dışa aktarın -3. Yeni tablolara ihtiyaç varsa `src/lib/db/migrations/` içinde göç ekleyin -4. `src/lib/localDb.ts`'den yeniden dışa aktarın (sadece yeniden dışa aktarma listesine ekleyin) -5. Testler yazın - -### Yeni Bir MCP Aracı Ekleme - -1. Zod girdi şeması + asenkron işleyici ile `open-sse/mcp-server/tools/` içinde araç tanımını ekleyin -2. Araç setinde kaydedin ( `createMcpServer()` ile bağlanır) -3. Uygun kapsam(lar)a atayın -4. Testler yazın (araç çağrısı `mcp_audit` tablosuna kaydedilir) - -### Yeni Bir A2A Yeteneği Ekleme - -1. `src/lib/a2a/skills/` içinde yetenek oluşturun (zaten 5 tane var: akıllı yönlendirme, kota yönetimi, sağlayıcı keşfi, maliyet analizi, sağlık raporu) -2. Yetenek görev bağlamını alır (mesajlar, meta veriler) → yapılandırılmış sonuç döndürür -3. `src/lib/a2a/taskExecution.ts` içinde `A2A_SKILL_HANDLERS`'da kaydedin -4. `src/app/.well-known/agent.json/route.ts` içinde açığa çıkarın (Agent Kartı) -5. `tests/unit/` içinde testler yazın -6. `docs/frameworks/A2A-SERVER.md` içinde yetenek tablosunu belgeleyin - -### Yeni Bir Bulut Ajanı Ekleme - -1. `src/lib/cloudAgent/agents/` içinde `CloudAgentBase`'i genişleten ajan sınıfı oluşturun (zaten 3 tane var: codex-cloud, devin, jules) -2. `createTask`, `getStatus`, `approvePlan`, `sendMessage`, `listSources`'ı uygulayın -3. `src/lib/cloudAgent/registry.ts` içinde kaydedin -4. Gerekirse OAuth/kimlik bilgileri yönetimini ekleyin (`src/lib/oauth/providers/`) -5. Testler + `docs/frameworks/CLOUD_AGENT.md` içinde belgeleyin - -### Yeni Bir Guardrail / Eval / Yetenek / Webhook olayı Ekleme - -- Guardrail: `src/lib/guardrails/` → belgeler: `docs/security/GUARDRAILS.md` -- Eval paketi: `src/lib/evals/` → belgeler: `docs/frameworks/EVALS.md` -- Yetenek (sandbox): `src/lib/skills/` → belgeler: `docs/frameworks/SKILLS.md` -- Webhook olayı: `src/lib/webhookDispatcher.ts` → belgeler: `docs/frameworks/WEBHOOKS.md` - -## Referans Dokümantasyonu - -Herhangi bir önemsiz değişiklik için, önce ilgili derinlemesine incelemeyi okuyun: - -| Alan | Doküman | -| -------------------------------------------------------- | ----------------------------------------------------------------- | -| Repo navigasyonu | `docs/architecture/REPOSITORY_MAP.md` | -| Mimari | `docs/architecture/ARCHITECTURE.md` | -| Mühendislik referansı | `docs/architecture/CODEBASE_DOCUMENTATION.md` | -| Auto-Combo (13-factor scoring, 19 public strategies) | `docs/routing/AUTO-COMBO.md` | -| Dayanıklılık (3 mekanizma) | `docs/architecture/RESILIENCE_GUIDE.md` | -| Akıl yürütme tekrarları | `docs/routing/REASONING_REPLAY.md` | -| Yetenekler çerçevesi | `docs/frameworks/SKILLS.md` | -| Bellek sistemi (FTS5 + Qdrant) | `docs/frameworks/MEMORY.md` | -| Bulut ajanları | `docs/frameworks/CLOUD_AGENT.md` | -| Koruma önlemleri (Kişisel Veriler / enjeksiyon / vizyon) | `docs/security/GUARDRAILS.md` | -| Kamu üst akış kimlik bilgileri (Gemini/vb.) | `docs/security/PUBLIC_CREDS.md` | -| Hata mesajı temizleme | `docs/security/ERROR_SANITIZATION.md` | -| Değerlendirmeler | `docs/frameworks/EVALS.md` | -| Uyum / denetim | `docs/security/COMPLIANCE.md` | -| Webhook'lar | `docs/frameworks/WEBHOOKS.md` | -| Yetkilendirme akışı | `docs/architecture/AUTHZ_GUIDE.md` | -| Gizlilik (TLS / parmak izi) | `docs/security/STEALTH_GUIDE.md` | -| Ajan protokolleri (A2A / ACP / Bulut) | `docs/frameworks/AGENT_PROTOCOLS_GUIDE.md` | -| MCP sunucusu | `docs/frameworks/MCP-SERVER.md` | -| A2A sunucusu | `docs/frameworks/A2A-SERVER.md` | -| API referansı + OpenAPI | `docs/reference/API_REFERENCE.md` + `docs/reference/openapi.yaml` | -| Sağlayıcı kataloğu (otomatik oluşturulmuş) | `docs/reference/PROVIDER_REFERENCE.md` | -| Sürüm akışı | `docs/ops/RELEASE_CHECKLIST.md` | - -## Test Etme - -| Ne | Komut | -| ----------------------- | ----------------------------------------------------------------------------- | -| Birim testleri | `npm run test:unit` | -| Tek dosya | `node --import tsx/esm --test tests/unit/file.test.ts` | -| Vitest (MCP, autoCombo) | `npm run test:vitest` | -| E2E (Playwright) | `npm run test:e2e` | -| Protokol E2E (MCP+A2A) | `npm run test:protocols:e2e` | -| Ekosistem | `npm run test:ecosystem` | -| Kapsam kapısı | `npm run test:coverage` (75/75/75/70 — ifadeler/hatlar/fonksiyonlar/kolonlar) | -| Kapsam raporu | `npm run coverage:report` | - -**PR kuralı**: Eğer `src/`, `open-sse/`, `electron/` veya `bin/` içindeki üretim kodunu değiştirirseniz, aynı PR içinde testleri eklemeli veya güncellemelisiniz. - -**Test katmanı tercihi**: birim önce → entegrasyon (çok modüllü veya DB durumu) → e2e (sadece UI/iş akışı). Hata yeniden üretimlerini düzeltmeden önce veya yanında otomatik testler olarak kodlayın. - -**Copilot kapsam politikası**: Bir PR üretim kodunu değiştiriyorsa ve kapsam %75'in (ifadeler/hatlar/fonksiyonlar) veya %70'in (kolonlar) altındaysa, sadece rapor etmekle kalmayın — test ekleyin veya güncelleyin, kapsam kapısını yeniden çalıştırın, ardından onay isteyin. Çalıştırılan komutları, değiştirilen test dosyalarını ve son kapsam sonucunu PR raporuna dahil edin. - ---- - -## Git İş Akışı - -```bash -# Asla doğrudan main'e commit yapmayın -git checkout -b feat/your-feature -git commit -m "feat: değişikliğinizi tanımlayın" -git push -u origin feat/your-feature -``` - -**Dal ön ekleri**: `feat/`, `fix/`, `refactor/`, `docs/`, `test/`, `chore/` - -**Commit formatı** (Geleneksel Commits): `feat(db): devre kesici ekle` — kapsamlar: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills` - -**Husky kancaları**: - -- **pre-commit**: lint-staged + `check-docs-sync` + `check:any-budget:t11` -- **pre-push**: `npm run test:unit` - ---- - -## Ortam - -- **Çalışma Zamanı**: Node.js ≥20.20.2 <21 | - | ≥22.22.2 <23 | - | ≥24 <25, ES Modülleri -- **TypeScript**: 5.9+, hedef ES2022, modül esnext, çözümleyici paketleyici -- **Yol takma adları**: `@/*` → `src/`, `@omniroute/open-sse` → `open-sse/`, `@omniroute/open-sse/*` → `open-sse/*` -- **Varsayılan port**: 20128 (API + kontrol paneli aynı portta) -- **Veri dizini**: `DATA_DIR` env değişkeni, varsayılan olarak `~/.omniroute/` -- **Ana env değişkenleri**: `PORT`, `JWT_SECRET`, `API_KEY_SECRET`, `INITIAL_PASSWORD`, `REQUIRE_API_KEY`, `APP_LOG_LEVEL` -- Kurulum: `cp .env.example .env` ardından `JWT_SECRET` (`openssl rand -base64 48`) ve `API_KEY_SECRET` (`openssl rand -hex 32`) oluşturun - ---- - -## Sert Kurallar - -1. Asla gizli bilgileri veya kimlik bilgilerini commit etmeyin -2. Asla `localDb.ts` içine mantık eklemeyin -3. Asla `eval()` / `new Function()` / dolaylı eval kullanmayın -4. Asla doğrudan `main`'e commit yapmayın -5. Asla rotalarda ham SQL yazmayın — `src/lib/db/` modüllerini kullanın -6. Asla SSE akışlarında hataları sessizce yutmayın -7. Her zaman Zod şemaları ile girdileri doğrulayın -8. Üretim kodunu değiştirirken her zaman testleri dahil edin -9. Kapsam ≥%75 (ifadeler, hatlar, fonksiyonlar) / ≥%70 (kolonlar) olmalıdır. Mevcut ölçülen: ~%82. -10. Açık operatör onayı olmadan Husky kancalarını (`--no-verify`, `--no-gpg-sign`) asla atlamayın. -11. Asla kamuya açık yukarı akış OAuth client_id/secret veya Firebase Web anahtarlarını string literal olarak gömün — her zaman `resolvePublicCred()` üzerinden geçin (`open-sse/utils/publicCreds.ts`). `docs/security/PUBLIC_CREDS.md`'ye bakın. -12. Asla HTTP / SSE / yürütücü yanıtlarında ham `err.stack` / `err.message` döndürmeyin — her zaman `buildErrorBody()` veya `sanitizeErrorMessage()` üzerinden yönlendirin (`open-sse/utils/error.ts`). `docs/security/ERROR_SANITIZATION.md`'ye bakın. -13. Asla dış yolları veya çalışma zamanı değerlerini `exec()`/`spawn()`'a geçirilen shell betiklerine string-interpolate etmeyin — bunun yerine `env` seçeneği aracılığıyla geçirin. Referans: `src/mitm/cert/install.ts::updateNssDatabases`. -14. Asla bir CodeQL / Secret-Scanning uyarısını (a) yukarıdaki desen belgelerini kontrol etmeden ve (b) reddetme yorumunda teknik gerekçeyi kaydetmeden geçiştirmeyin. Örnek: `js/stack-trace-exposure` hatası, zaten `sanitizeErrorMessage()` üzerinden yönlendirilmiş çağrı noktalarında ortaya çıkmaktadır ve bu bilinen bir CodeQL sınırlamasıdır (özel temizleyiciler tanınmaz) — `docs/security/ERROR_SANITIZATION.md`'ye atıfta bulunarak `false positive` olarak reddedin. -15. Asla çocuk süreçleri başlatan rotaları (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` içinde `isLocalOnlyPath()` sınıflandırması olmadan dahil etmeyin. Döngü geri uygulaması, herhangi bir kimlik doğrulama kontrolünden önce koşulsuz olarak gerçekleşir — tünel aracılığıyla sızdırılan JWT, süreç başlatmayı tetikleyemez. `docs/security/ROUTE_GUARD_TIERS.md`'ye bakın. -16. Asla AI asistanı, LLM veya otomasyon hesabını krediye alan `Co-Authored-By` ekleri içermeyin (örn. "Claude", "GPT", "Copilot", "Bot" içeren isimler; `anthropic.com` / `openai.com` / bot sahipli `noreply.github.com` adreslerindeki e-postalar). Bu tür ekler GitHub'da commit atfını bot hesabına yönlendirir ve PR geçmişinde gerçek yazarı (`diegosouzapw`) gizler. İnsan katkıda bulunanlar — upstream PR yazarları ve OmniRoute'a port edilen issue raporlayıcıları dahil — standart `Co-authored-by: Name ` ekleriyle krediye ALINABİLİR ve ALINMALIDIR; upstream-port iş akışları (`/port-upstream-features`, `/port-upstream-issues`) buna bağlıdır. +Bir dal açmadan veya PR oluşturmadan önce base-green kontrolünü çalıştırın (`AGENTS.md` → Git Workflow → "Base-green check"; proje yetenekleri bunu `.agents/skills/_shared/base-green.md` olarak referans alır). Temel uç (base tip) kırmızı iken açılan bir PR, gövdesinde `⚠️ base-red inherited: #` taşımalıdır. Birikmiş kırmızı durumu (temel uç + kırmızı PR'lar) boşaltmak için `/sweep-reds` yeteneğini kullanın. diff --git a/docs/i18n/tr/CODE_OF_CONDUCT.md b/docs/i18n/tr/CODE_OF_CONDUCT.md index 94a85d9a64..8203965141 100644 --- a/docs/i18n/tr/CODE_OF_CONDUCT.md +++ b/docs/i18n/tr/CODE_OF_CONDUCT.md @@ -1,132 +1,88 @@ -# Contributor Covenant Code of Conduct (Türkçe) +# Katılımcı Sözleşmesi Davranış Kuralları (Türkçe) 🌐 **Languages:** 🇺🇸 [English](../../../CODE_OF_CONDUCT.md) · 🇸🇦 [ar](../ar/CODE_OF_CONDUCT.md) · 🇧🇬 [bg](../bg/CODE_OF_CONDUCT.md) · 🇧🇩 [bn](../bn/CODE_OF_CONDUCT.md) · 🇨🇿 [cs](../cs/CODE_OF_CONDUCT.md) · 🇩🇰 [da](../da/CODE_OF_CONDUCT.md) · 🇩🇪 [de](../de/CODE_OF_CONDUCT.md) · 🇪🇸 [es](../es/CODE_OF_CONDUCT.md) · 🇮🇷 [fa](../fa/CODE_OF_CONDUCT.md) · 🇫🇮 [fi](../fi/CODE_OF_CONDUCT.md) · 🇫🇷 [fr](../fr/CODE_OF_CONDUCT.md) · 🇮🇳 [gu](../gu/CODE_OF_CONDUCT.md) · 🇮🇱 [he](../he/CODE_OF_CONDUCT.md) · 🇮🇳 [hi](../hi/CODE_OF_CONDUCT.md) · 🇭🇺 [hu](../hu/CODE_OF_CONDUCT.md) · 🇮🇩 [id](../id/CODE_OF_CONDUCT.md) · 🇮🇹 [it](../it/CODE_OF_CONDUCT.md) · 🇯🇵 [ja](../ja/CODE_OF_CONDUCT.md) · 🇰🇷 [ko](../ko/CODE_OF_CONDUCT.md) · 🇮🇳 [mr](../mr/CODE_OF_CONDUCT.md) · 🇲🇾 [ms](../ms/CODE_OF_CONDUCT.md) · 🇳🇱 [nl](../nl/CODE_OF_CONDUCT.md) · 🇳🇴 [no](../no/CODE_OF_CONDUCT.md) · 🇵🇭 [phi](../phi/CODE_OF_CONDUCT.md) · 🇵🇱 [pl](../pl/CODE_OF_CONDUCT.md) · 🇵🇹 [pt](../pt/CODE_OF_CONDUCT.md) · 🇧🇷 [pt-BR](../pt-BR/CODE_OF_CONDUCT.md) · 🇷🇴 [ro](../ro/CODE_OF_CONDUCT.md) · 🇷🇺 [ru](../ru/CODE_OF_CONDUCT.md) · 🇸🇰 [sk](../sk/CODE_OF_CONDUCT.md) · 🇸🇪 [sv](../sv/CODE_OF_CONDUCT.md) · 🇰🇪 [sw](../sw/CODE_OF_CONDUCT.md) · 🇮🇳 [ta](../ta/CODE_OF_CONDUCT.md) · 🇮🇳 [te](../te/CODE_OF_CONDUCT.md) · 🇹🇭 [th](../th/CODE_OF_CONDUCT.md) · 🇹🇷 [tr](../tr/CODE_OF_CONDUCT.md) · 🇺🇦 [uk-UA](../uk-UA/CODE_OF_CONDUCT.md) · 🇵🇰 [ur](../ur/CODE_OF_CONDUCT.md) · 🇻🇳 [vi](../vi/CODE_OF_CONDUCT.md) · 🇨🇳 [zh-CN](../zh-CN/CODE_OF_CONDUCT.md) --- -## Our Pledge +## Taahhüdümüz -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. +Topluluk üyeleri, katkıda bulunanlar ve liderler olarak; yaş, vücut ölçüsü, görünür veya görünmez engellilik, etnik köken, cinsiyet özellikleri, cinsiyet kimliği ve ifadesi, deneyim düzeyi, eğitim, sosyo-ekonomik durum, milliyet, kişisel görünüm, ırk, din veya cinsel kimlik ve yönelim gözetilmeksizin herkes için topluluğumuza katılımı tacizden uzak bir deneyim haline getirmeyi taahhüt ediyoruz. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. +Açık, sıcak, çeşitli, kapsayıcı ve sağlıklı bir topluluğa katkıda bulunacak şekilde davranmayı ve etkileşim kurmayı taahhüt ediyoruz. -## Our Standards +## Standartlarımız -Examples of behavior that contributes to a positive environment for our -community include: +Topluluğumuz için olumlu bir ortama katkıda bulunan davranış örnekleri şunlardır: -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community +- Diğer insanlara karşı empati ve nezaket göstermek +- Farklı görüşlere, bakış açılarına ve deneyimlere saygılı olmak +- Yapıcı geri bildirim vermek ve bunu olgunlukla kabul etmek +- Hatalarımızdan etkilenenlerden sorumluluk alıp özür dilemek ve bu deneyimden ders çıkarmak +- Sadece bireysel olarak bizim için değil, tüm topluluk için en iyi olana odaklanmak -Examples of unacceptable behavior include: +Kabul edilemez davranış örnekleri şunlardır: -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting +- Cinselleştirilmiş dil veya görsellerin kullanımı ile her türlü cinsel ilgi veya yakınlaşma +- Trolleme, aşağılayıcı veya rencide edici yorumlar ve kişisel ya da politik saldırılar +- Kamuya açık veya özel alanda taciz +- Açık izinleri olmadan başkalarının fiziksel adres veya e-posta adresi gibi özel bilgilerini yayımlamak +- Profesyonel bir ortamda makul olarak uygunsuz kabul edilebilecek diğer davranışlar -## Enforcement Responsibilities +## Uygulama Sorumlulukları -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. +Topluluk liderleri, kabul edilebilir davranış standartlarımızı açıklığa kavuşturmaktan ve uygulamaktan sorumludur; uygunsuz, tehdit edici, saldırgan veya zararlı gördükleri herhangi bir davranışa karşılık adil ve uygun düzeltici önlemleri alacaklardır. -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. +Topluluk liderleri, bu Davranış Kuralları ile uyumlu olmayan yorumları, commit'leri, kodları, wiki düzenlemelerini, issue'ları ve diğer katkıları kaldırma, düzenleme veya reddetme hakkına ve sorumluluğuna sahiptir ve uygun olduğunda moderasyon kararlarının gerekçelerini ileteceklerdir. -## Scope +## Kapsam -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. +Bu Davranış Kuralları tüm topluluk alanlarında geçerlidir ve ayrıca bir birey topluluğu kamusal alanlarda resmi olarak temsil ettiğinde de geçerlidir. Topluluğumuzu temsil etme örnekleri arasında resmi bir e-posta adresi kullanmak, resmi bir sosyal medya hesabı aracılığıyla paylaşım yapmak veya çevrimiçi ya da çevrimdışı bir etkinlikte atanmış bir temsilci olarak hareket etmek yer alır. -## Enforcement +## Yaptırım -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -. -All complaints will be reviewed and investigated promptly and fairly. +İstismar edici, taciz edici veya başka bir şekilde kabul edilemez davranış durumları, yaptırımdan sorumlu topluluk liderlerine şu adresten özel bir güvenlik bildirimi (security advisory) açılarak bildirilebilir: + +veya proje yöneticisine diegosouza.pw@outlook.com adresinden e-posta gönderilebilir. +Güvenlikle ilgili hassas olaylar için bkz. [`SECURITY.md`](SECURITY.md). +Tüm şikayetler derhal ve adil bir şekilde incelenecek ve araştırılacaktır. -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. +Tüm topluluk liderleri, herhangi bir olayı bildiren kişinin gizliliğine ve güvenliğine saygı duymakla yükümlüdür. -## Enforcement Guidelines +## Yaptırım Yönergeleri -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: +Topluluk liderleri, bu Davranış Kurallarını ihlal ettiğini düşündükleri herhangi bir eylemin sonuçlarını belirlerken aşağıdaki Topluluk Etki Yönergelerini izleyecektir: -### 1. Correction +### 1. Düzeltme -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. +**Topluluk Etkisi**: Toplulukta uygunsuz veya profesyonellik dışı kabul edilen dil kullanımı veya diğer davranışlar. -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. +**Sonuç**: Topluluk liderlerinden ihlalin niteliğini açıklayan ve davranışın neden uygunsuz olduğunu belirten özel, yazılı bir uyarı. Kamuya açık bir özür talep edilebilir. -### 2. Warning +### 2. Uyarı -**Community Impact**: A violation through a single incident or series -of actions. +**Topluluk Etkisi**: Tek bir olay veya bir dizi eylem yoluyla yapılan bir ihlal. -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. +**Sonuç**: Davranışın devam etmesi durumunda doğacak sonuçları içeren bir uyarı. Belirli bir süre boyunca, Davranış Kurallarını uygulayan kişilerle talep edilmeyen etkileşimler de dahil olmak üzere, ilgili kişilerle hiçbir etkileşimde bulunulamaz. Buna topluluk alanlarının yanı sıra sosyal medya gibi harici kanallardaki etkileşimlerden kaçınmak da dahildir. Bu koşulların ihlali geçici veya kalıcı bir uzaklaştırmaya yol açabilir. -### 3. Temporary Ban +### 3. Geçici Uzaklaştırma -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. +**Topluluk Etkisi**: Sürekli uygunsuz davranışlar da dahil olmak üzere topluluk standartlarının ciddi bir şekilde ihlali. -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. +**Sonuç**: Belirli bir süre boyunca toplulukla her türlü etkileşimden veya kamusal iletişimden geçici olarak men edilme. Bu süre zarfında, Davranış Kurallarını uygulayan kişilerle talep edilmeyen etkileşimler de dahil olmak üzere, ilgili kişilerle kamuya açık veya özel hiçbir etkileşime izin verilmez. Bu koşulların ihlali kalıcı bir uzaklaştırmaya yol açabilir. -### 4. Permanent Ban +### 4. Kalıcı Uzaklaştırma -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. +**Topluluk Etkisi**: Sürekli uygunsuz davranışlar, bir bireyin taciz edilmesi veya belirli insan gruplarına yönelik saldırganlık ya da aşağılama da dahil olmak üzere topluluk standartlarını sistematik olarak ihlal etme kalıbı sergilemek. -**Consequence**: A permanent ban from any sort of public interaction within -the community. +**Sonuç**: Topluluk içindeki her türlü kamusal etkileşimden kalıcı olarak men edilme. -## Attribution +## Kaynak ve Atıf -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +Bu Davranış Kuralları, [Contributor Covenant][homepage] sürüm 2.1'den uyarlanmıştır; orijinaline şu adresten ulaşılabilir: +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). +Topluluk Etki Yönergeleri, [Mozilla'nın davranış kuralları yaptırım merdiveninden](https://github.com/mozilla/diversity) esinlenmiştir. [homepage]: https://www.contributor-covenant.org -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +Bu davranış kuralları hakkında sık sorulan soruların yanıtları için https://www.contributor-covenant.org/faq adresindeki SSS bölümüne bakın. Çeviriler https://www.contributor-covenant.org/translations adresinde mevcuttur. diff --git a/docs/i18n/tr/CONTRIBUTING.md b/docs/i18n/tr/CONTRIBUTING.md index a260090457..57df213940 100644 --- a/docs/i18n/tr/CONTRIBUTING.md +++ b/docs/i18n/tr/CONTRIBUTING.md @@ -1,22 +1,30 @@ -# Contributing to OmniRoute (Türkçe) +# OmniRoute'a Katkıda Bulunma (Türkçe) 🌐 **Languages:** 🇺🇸 [English](../../../CONTRIBUTING.md) · 🇸🇦 [ar](../ar/CONTRIBUTING.md) · 🇧🇬 [bg](../bg/CONTRIBUTING.md) · 🇧🇩 [bn](../bn/CONTRIBUTING.md) · 🇨🇿 [cs](../cs/CONTRIBUTING.md) · 🇩🇰 [da](../da/CONTRIBUTING.md) · 🇩🇪 [de](../de/CONTRIBUTING.md) · 🇪🇸 [es](../es/CONTRIBUTING.md) · 🇮🇷 [fa](../fa/CONTRIBUTING.md) · 🇫🇮 [fi](../fi/CONTRIBUTING.md) · 🇫🇷 [fr](../fr/CONTRIBUTING.md) · 🇮🇳 [gu](../gu/CONTRIBUTING.md) · 🇮🇱 [he](../he/CONTRIBUTING.md) · 🇮🇳 [hi](../hi/CONTRIBUTING.md) · 🇭🇺 [hu](../hu/CONTRIBUTING.md) · 🇮🇩 [id](../id/CONTRIBUTING.md) · 🇮🇹 [it](../it/CONTRIBUTING.md) · 🇯🇵 [ja](../ja/CONTRIBUTING.md) · 🇰🇷 [ko](../ko/CONTRIBUTING.md) · 🇮🇳 [mr](../mr/CONTRIBUTING.md) · 🇲🇾 [ms](../ms/CONTRIBUTING.md) · 🇳🇱 [nl](../nl/CONTRIBUTING.md) · 🇳🇴 [no](../no/CONTRIBUTING.md) · 🇵🇭 [phi](../phi/CONTRIBUTING.md) · 🇵🇱 [pl](../pl/CONTRIBUTING.md) · 🇵🇹 [pt](../pt/CONTRIBUTING.md) · 🇧🇷 [pt-BR](../pt-BR/CONTRIBUTING.md) · 🇷🇴 [ro](../ro/CONTRIBUTING.md) · 🇷🇺 [ru](../ru/CONTRIBUTING.md) · 🇸🇰 [sk](../sk/CONTRIBUTING.md) · 🇸🇪 [sv](../sv/CONTRIBUTING.md) · 🇰🇪 [sw](../sw/CONTRIBUTING.md) · 🇮🇳 [ta](../ta/CONTRIBUTING.md) · 🇮🇳 [te](../te/CONTRIBUTING.md) · 🇹🇭 [th](../th/CONTRIBUTING.md) · 🇹🇷 [tr](../tr/CONTRIBUTING.md) · 🇺🇦 [uk-UA](../uk-UA/CONTRIBUTING.md) · 🇵🇰 [ur](../ur/CONTRIBUTING.md) · 🇻🇳 [vi](../vi/CONTRIBUTING.md) · 🇨🇳 [zh-CN](../zh-CN/CONTRIBUTING.md) --- -Thank you for your interest in contributing! This guide covers everything you need to get started. +Katkıda bulunmak istediğiniz için teşekkür ederiz! Bu kılavuz başlamak için ihtiyacınız olan her şeyi kapsar. + +Değişiklik başına resmi iş akışı için [Katkı Altın Yolu (Contribution Golden Path)](docs/ops/CONTRIBUTION_GOLDEN_PATH.md) belgesiyle başlayın. Sağlayıcı, yönlendirme, UI/UX, i18n, CLI, veritabanı ve derleme/dağıtım değişikliklerini ilgili sözleşmelere, odaklanmış testlere, CI kapsamına ve mutabakat adımlarına eşler. --- -## Development Setup +## Geliştirme Ortamı Kurulumu -### Prerequisites +### Ön Koşullar -- **Node.js** >= 18 < 24 (recommended: 22 LTS) +- **Node.js** `>=22.22.3 <23` veya `>=24.0.0 <27` (önerilen: 24 LTS) - **npm** 10+ + +> **npm v11+ kullanıcıları (Node 24+):** `npm install` sonrasında yerel modüllerin kurulduğunu doğrulayın: +> `node -e "require('better-sqlite3')"`. Eğer `MODULE_NOT_FOUND` hatası alırsanız, +> `npm approve-scripts better-sqlite3 && npm install` komutunu çalıştırın. Bkz. +> [Sorun Giderme](docs/guides/TROUBLESHOOTING.md#npm-v11-better-sqlite3-not-installed-cannot-find-module). + - **Git** -### Clone & Install +### Klonlama ve Kurulum ```bash git clone https://github.com/diegosouzapw/OmniRoute.git @@ -24,85 +32,117 @@ cd OmniRoute npm install ``` -### Environment Variables +### Ortam Değişkenleri ```bash -# Create your .env from the template +# Şablondan kendi .env dosyanızı oluşturun cp .env.example .env -# Generate required secrets +# Gerekli gizli anahtarları oluşturun echo "JWT_SECRET=$(openssl rand -base64 48)" >> .env echo "API_KEY_SECRET=$(openssl rand -hex 32)" >> .env ``` -Key variables for development: +Geliştirme için temel değişkenler: -| Variable | Development Default | Description | +| Değişken | Geliştirme Varsayılanı | Açıklama | | ---------------------- | ------------------------ | --------------------- | -| `PORT` | `20128` | Server port | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | Base URL for frontend | -| `JWT_SECRET` | (generate above) | JWT signing secret | -| `INITIAL_PASSWORD` | `CHANGEME` | First login password | -| `APP_LOG_LEVEL` | `info` | Log verbosity level | +| `PORT` | `20128` | Sunucu portu | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | Ön uç için temel URL | +| `JWT_SECRET` | (yukarıda oluşturulur) | JWT imzalama sırrı | +| `INITIAL_PASSWORD` | `CHANGEME` | İlk giriş parolası | +| `APP_LOG_LEVEL` | `info` | Günlük ayrıntı düzeyi | -### Dashboard Settings +### Pano Ayarları -The dashboard provides UI toggles for features that can also be configured via environment variables: +Pano, ortam değişkenleri aracılığıyla da yapılandırılabilen özellikler için arayüz anahtarları sunar: -| Setting Location | Toggle | Description | -| ------------------- | ------------------ | ------------------------------ | -| Settings → Advanced | Debug Mode | Enable debug request logs (UI) | -| Settings → General | Sidebar Visibility | Show/hide sidebar sections | +| Ayar Konumu | Anahtar | Açıklama | +| ------------------- | ------------------ | ------------------------------------- | +| Ayarlar → Gelişmiş | Hata Ayıklama Modu | İstek günlüklerini etkinleştirir (UI) | +| Ayarlar → Genel | Kenar Çubuğu Görünürlüğü | Kenar çubuğu bölümlerini göster/gizle | -These settings are stored in the database and persist across restarts, overriding env var defaults when set. +Bu ayarlar veritabanında saklanır ve yeniden başlatmalar arasında kalıcıdır; ayarlandıklarında ortam değişkeni varsayılanlarını geçersiz kılarlar. -### Running Locally +### Yerel Olarak Çalıştırma ```bash -# Development mode (hot reload) +# Geliştirme modu (hot reload) npm run dev -# Production build -npm run build +# Üretim derlemesi +npm run build # next build → .build/next/ ardından assembleStandalone → dist/ npm run start -# Common port configuration +# Sürüm derlemesi (temiz yeniden derleme + HEAD nöbetçisi — dağıtım için gereklidir) +npm run build:release # rm -rf .build dist && build + dist/BUILD_SHA yazar + +# Yaygın port yapılandırması PORT=20128 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run dev ``` -Default URLs: +### Derleme Çıktısı Düzeni -- **Dashboard**: `http://localhost:20128/dashboard` +| Dizin | İçerik | Takip Ediliyor mu? | +| --------- | ------------------------------------------------------------------------- | ------------------ | +| `src/` | Uygulama kaynak kodu (TypeScript / TSX) | Evet | +| `.build/` | Ara dosyalar — `next build` çıktısı (gitignored, `distDir = .build/next`) | Hayır | +| `dist/` | Dağıtılabilir paket — `assembleStandalone` tarafından toplanır (gitignored) | Hayır | + +Derleme hattı tek geçişlidir: + +``` +npm run build + └─ next build → .build/next/standalone (Next.js çıktısı) + └─ assembleStandalone() (standalone + static + public + yerel varlıkları kopyalar) + └─ çıktı: dist/ (server.js, .next/static/, public/, node_modules/) +``` + +`npm run build:release` ek olarak önce her iki dizini de temizler ve dağıtım bütünlüğü nöbetçisi olarak +`dist/BUILD_SHA` (= `git rev-parse --short HEAD`) yazar. + +> **VPS dağıtım notu:** uzak imaj dizini `/usr/lib/node_modules/omniroute/app/` +> değişmemiştir. Dağıtım yetenekleri `dist/` içeriğini rsync ile buraya aktarır. +> Yalnızca repo içi derleme çıktı yolu taşınmıştır (`app/` → `dist/`). + +Varsayılan URL'ler: + +- **Pano**: `http://localhost:20128/dashboard` - **API**: `http://localhost:20128/v1` --- -## Git Workflow +## Git İş Akışı -> ⚠️ **NEVER commit directly to `main`.** Always use feature branches. +> ⚠️ **KESİNLİKLE doğrudan `main` dalına commit atmayın.** Her zaman özellik dalları (feature branch) kullanın. +> +> **PR hedefi:** aktif `release/vX.Y.Z` dalını hedefleyin (`main` değil). Dal başına sürüm + yayımlama anında etiket modeli için +> [`docs/ops/BRANCHING_MODEL.md`](docs/ops/BRANCHING_MODEL.md) belgesine bakın. ```bash -git checkout -b feat/your-feature-name -# ... make changes ... -git commit -m "feat: describe your change" -git push -u origin feat/your-feature-name -# Open a Pull Request on GitHub +# Aktif sürüm ucundan dal oluşturun (örnek: release/v3.8.49) +git fetch origin +git checkout -b feat/ozellik-adiniz origin/release/v3.8.49 +# ... değişiklikleri yapın ... +git commit -m "feat: degisikliginizi aciklayin" +git push -u origin feat/ozellik-adiniz +# Hedef dal = release/v3.8.49 olacak şekilde Pull Request açın ``` -### Branch Naming +### Dal Adlandırma -| Prefix | Purpose | -| ----------- | ------------------------- | -| `feat/` | New features | -| `fix/` | Bug fixes | -| `refactor/` | Code restructuring | -| `docs/` | Documentation changes | -| `test/` | Test additions/fixes | -| `chore/` | Tooling, CI, dependencies | +| Önek | Amaç | +| ----------- | ----------------------------------- | +| `feat/` | Yeni özellikler | +| `fix/` | Hata düzeltmeleri | +| `refactor/` | Kod yeniden yapılandırması | +| `docs/` | Dokümantasyon değişiklikleri | +| `test/` | Test ekleme/düzeltme | +| `chore/` | Araçlar, CI, bağımlılıklar | -### Commit Messages +### Commit Mesajları -Follow [Conventional Commits](https://www.conventionalcommits.org/): +[Conventional Commits](https://www.conventionalcommits.org/) standartlarını izleyin: ``` feat: add circuit breaker for provider calls @@ -112,200 +152,248 @@ test: add observability unit tests refactor(db): consolidate rate limit tables ``` -Scopes: `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`. +Kapsamlar (v3.8): `db`, `sse`, `oauth`, `dashboard`, `api`, `cli`, `docker`, `ci`, `mcp`, `a2a`, `memory`, `skills`, `cloud-agent`, `guardrails`, `compression`, `auto-combo`, `resilience`, `providers`, `executors`, `translator`, `domain`, `authz`. --- -## Running Tests +## Testleri Çalıştırma ```bash -# All tests (unit + vitest + ecosystem + e2e) +# Tüm testler (unit + vitest + ecosystem + e2e) npm run test:all -# Single test file (Node.js native test runner — most tests use this) +# Tek bir test dosyası (Node.js yerel test çalıştırıcısı — çoğu test bunu kullanır) node --import tsx/esm --test tests/unit/your-file.test.ts -# Vitest (MCP server, autoCombo, cache) +# Vitest (MCP sunucusu, autoCombo, önbellek) npm run test:vitest -# E2E tests (requires Playwright) +# E2E testleri (Playwright gerektirir) npm run test:e2e -# Protocol clients E2E (MCP transports, A2A) +# Protokol istemcileri E2E (MCP taşımaları, A2A) npm run test:protocols:e2e -# Ecosystem compatibility tests +# Ekosistem uyumluluk testleri npm run test:ecosystem -# Coverage (60% min statements/lines/functions/branches) +# Kapsam kapısı: %60 statements/lines/functions/branches npm run test:coverage npm run coverage:report -# Lint + format check +# Lint + biçimlendirme kontrolü npm run lint npm run check + +# Gerçek yukarı akış kombo testi (VPS erişimi + gerçek sağlayıcı kredisi gerektirir) +# GERÇEK sağlayıcılara istek atar — küçük bir maliyeti vardır. CI'da ASLA çalışmaz. +RUN_COMBO_LIVE=1 npm run test:combo:live + +# Aşama-3 VPS canlı testi — doğrudan canlı .15 sunucusuna istek atar. +npm run test:combo:live:vps # 7 HTTP senaryosu (priority/round-robin/weighted/cost/fusion/auto + health) +npm run test:combo:live:vps:failover # gerçek sağlayıcılar arası geçiş senaryosu ekler (toplam 8) ``` -Coverage notes: +Test kapsamı notları: -- `npm run test:coverage` measures source coverage for the main unit test suite, excludes `tests/**`, and includes `open-sse/**` -- Pull requests must keep the overall coverage gate at **60% or higher** for statements, lines, functions, and branches -- If a PR changes production code in `src/`, `open-sse/`, `electron/`, or `bin/`, it must add or update automated tests in the same PR -- `npm run coverage:report` prints the detailed file-by-file report from the latest coverage run -- `npm run test:coverage:legacy` preserves the older metric for historical comparison -- See `docs/ops/COVERAGE_PLAN.md` for the phased coverage improvement roadmap +- `npm run test:coverage` ana birim test paketi için kaynak kapsamını ölçer, `tests/**` dizinini hariç tutar ve `open-sse/**` dizinini dahil eder +- Pull Request'ler kapsam kapısını **%60+** (statements/lines/functions/branches) seviyesinde tutmalıdır +- Bir PR `src/`, `open-sse/`, `electron/` veya `bin/` altındaki üretim kodunu değiştiriyorsa, aynı PR'da otomatik testler eklemeli veya güncellemelidir +- `npm run coverage:report` en son test çalıştırmasından detaylı dosya bazlı raporu yazdırır +- Kademeli kapsam iyileştirme yol haritası için `docs/ops/COVERAGE_PLAN.md` dosyasına bakın -### Pull Request Requirements +### Pull Request Gereksinimleri -Before opening or merging a PR: +Bir PR açmadan önce, değiştirdiğiniz alan için odaklanmış döngüyü çalıştırmak üzere [Katkı Altın Yolu](docs/ops/CONTRIBUTION_GOLDEN_PATH.md) belgesini kullanın: -- Run `npm run test:unit` -- Run `npm run test:coverage` -- Ensure the coverage gate stays at **60%+** for all metrics -- Include the changed or added test files in the PR description when production code changed -- Check the SonarQube result on the PR when the project secrets are configured in CI +- Değişikliğinizi kapsayan test dosyalarını çalıştırın: `node --import tsx/esm --test tests/unit/.test.ts` +- `npm run lint` çalıştırın +- Üretim kodu değiştiğinde her zaman aynı PR'a otomatik testler ekleyin veya güncelleyin +- Üretim kodu değiştiğinde PR açıklamasına değiştirilen veya eklenen test dosyalarını ekleyin +- CI'da proje sırları yapılandırıldığında PR üzerindeki SonarQube sonucunu kontrol edin -Current test status: **122 unit test files** covering: +Mevcut test durumu: **122 birim test dosyası** şunları kapsar: -- Provider translators and format conversion -- Rate limiting, circuit breaker, and resilience -- Semantic cache, idempotency, progress tracking -- Database operations and schema (21 DB modules) -- OAuth flows and authentication -- API endpoint validation (Zod v4) -- MCP server tools and scope enforcement -- Memory and Skills systems +- Sağlayıcı çevirmenleri ve format dönüştürme +- Hız sınırlaması, devre kesici ve dayanıklılık +- Anlamsal önbellek, tekilleştirme, ilerleme takibi +- Veritabanı işlemleri ve şeması (21 DB modülü) +- OAuth akışları ve kimlik doğrulama +- API uç noktası doğrulaması (Zod v4) +- MCP sunucu araçları ve kapsam denetimi +- Bellek ve Yetenek (Skills) sistemleri --- -## Code Style +## Kod Stili -- **ESLint** — Run `npm run lint` before committing -- **Prettier** — Auto-formatted via `lint-staged` on commit (2 spaces, semicolons, double quotes, 100 char width, es5 trailing commas) -- **TypeScript** — All `src/` code uses `.ts`/`.tsx`; `open-sse/` uses `.ts`/`.js`; document with TSDoc (`@param`, `@returns`, `@throws`) -- **No `eval()`** — ESLint enforces `no-eval`, `no-implied-eval`, `no-new-func` -- **Zod validation** — Use Zod v4 schemas for all API input validation -- **Naming**: Files = camelCase/kebab-case, components = PascalCase, constants = UPPER_SNAKE +- **ESLint** — Commit öncesinde `npm run lint` çalıştırın +- **Prettier** — Commit sırasında `lint-staged` aracılığıyla otomatik biçimlendirilir (2 boşluk, noktalı virgül, çift tırnak, 100 karakter genişlik, es5 son virgüller) +- **TypeScript** — Tüm `src/` kodu `.ts`/`.tsx` kullanır; `open-sse/` `.ts`/`.js` kullanır; TSDoc (`@param`, `@returns`, `@throws`) ile belgeleyin +- **`eval()` Yasaktır** — ESLint `no-eval`, `no-implied-eval`, `no-new-func` kurallarını zorunlu kılar +- **Zod doğrulaması** — Tüm API girdi doğrulamaları için Zod v4 şemalarını kullanın +- **Adlandırma**: Dosyalar = camelCase/kebab-case, bileşenler = PascalCase, sabitler = UPPER_SNAKE + +### Hata Yönetimi / Boş Catch Blokları + +Bir `catch` bloğunu asla açıklamasız bırakmayın. İki kategoriden birine ayırın: + +- **Kasıtlı (kendi en iyi çaba temizliğimiz/telemetrimiz)** — burada bir hata beklenir ve zararsızdır; tek satırlık bir gerekçe yorumu ekleyin, günlük kaydı yapmayın: + + ```ts + } catch {} // istemci bağlantısı kesildikten sonra zaten kapalı bir denetleyiciyi kapatmak beklenen bir durumdur + ``` + +- **Günlüğe kaydedilmeli (harici kod veya akışı değiştiren durumlar)** — catch'i koruyun ancak hatanın keşfedilebilmesi için bağlamsal bir `console.debug`/`warn` yayınlayın: + + ```ts + } catch (e) { + console.debug("[STREAM] onFailure callback error:", e); + } + ``` + +Uygulamalı örnekler için `open-sse/utils/stream.ts` ve `open-sse/utils/streamHandler.ts` dosyalarına bakın. --- -## Project Structure +## Proje Yapısı ``` src/ # TypeScript (.ts / .tsx) ├── app/ # Next.js 16 App Router -│ ├── (dashboard)/ # Dashboard pages (23 sections) -│ ├── api/ # API routes (51 directories) -│ └── login/ # Auth pages (.tsx) -├── domain/ # Policy engine (policyEngine, comboResolver, costRules, etc.) -├── lib/ # Core business logic (.ts) -│ ├── a2a/ # Agent-to-Agent v0.3 protocol server -│ ├── acp/ # Agent Communication Protocol registry -│ ├── compliance/ # Compliance policy engine -│ ├── db/ # SQLite database layer (110 top-level modules + 130 migrations) -│ ├── memory/ # Persistent conversational memory -│ ├── oauth/ # OAuth providers, services, and utilities -│ ├── skills/ # Extensible skill framework -│ ├── usage/ # Usage tracking and cost calculation -│ └── localDb.ts # Re-export layer only — never add logic here -├── middleware/ # Request middleware (promptInjectionGuard) -├── mitm/ # MITM proxy (cert, DNS, target routing) +│ ├── (dashboard)/ # Pano sayfaları (23 bölüm) +│ ├── api/ # API rotaları (51 dizin) +│ └── login/ # Kimlik doğrulama sayfaları (.tsx) +├── domain/ # Politika motoru (policyEngine, comboResolver, costRules, vb.) +├── lib/ # Çekirdek iş mantığı (.ts) +│ ├── a2a/ # Agent-to-Agent v0.3 protokol sunucusu +│ ├── acp/ # Ajan İletişim Protokolü kayıt defteri +│ ├── compliance/ # Uyumluluk politika motoru +│ ├── db/ # SQLite alan modülleri + 130 migrasyon +│ ├── memory/ # Kalıcı konuşma belleği +│ ├── oauth/ # OAuth sağlayıcıları, servisleri ve yardımcıları +│ ├── skills/ # Genişletilebilir yetenek çerçevesi +│ ├── usage/ # Kullanım takibi ve maliyet hesaplama +│ └── localDb.ts # Yalnızca yeniden dışa aktarma katmanı — buraya asla mantık eklemeyin +├── middleware/ # İstek ara yazılımı (promptInjectionGuard) +├── mitm/ # MITM proxy (sertifika, DNS, hedef yönlendirme) ├── shared/ -│ ├── components/ # React components (.tsx) -│ ├── constants/ # Provider definitions (329), MCP scopes, routing strategies -│ ├── utils/ # Circuit breaker, sanitizer, auth helpers -│ └── validation/ # Zod v4 schemas -└── sse/ # SSE proxy pipeline +│ ├── components/ # React bileşenleri (.tsx) +│ ├── constants/ # Sağlayıcı tanımları (329), MCP kapsamları, 19 yönlendirme stratejisi +│ ├── utils/ # Devre kesici, temizleyici, kimlik doğrulama yardımcıları +│ └── validation/ # Zod v4 şemaları +└── sse/ # SSE proxy hattı -open-sse/ # @omniroute/open-sse workspace -├── executors/ # 89 executor implementation modules -├── handlers/ # 11 request handlers (chat, responses, embeddings, images, etc.) -├── mcp-server/ # MCP server (107 tools, 3 transports, 32 scopes) -├── services/ # 178 top-level services (combo, autoCombo, rateLimitManager, etc.) -├── translator/ # Format translators (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama) -├── transformer/ # Responses API transformer -└── utils/ # 22 utility modules (stream, TLS, proxy, logging) +open-sse/ # @omniroute/open-sse çalışma alanı +├── executors/ # 89 yürütücü uygulama modülü +├── handlers/ # 11 istek işleyici (chat, responses, embeddings, images, vb.) +├── mcp-server/ # MCP sunucusu (107 benzersiz araç, 3 taşıma, 32 kapsam) +├── services/ # 178 üst düzey servis (combo, autoCombo, rateLimitManager, vb.) +├── translator/ # Format çevirmenleri (OpenAI ↔ Claude ↔ Gemini ↔ Responses ↔ Ollama) +├── transformer/ # Responses API dönüştürücüsü +└── utils/ # 22 yardımcı modül (stream, TLS, proxy, logging) -electron/ # Electron desktop app (cross-platform) +electron/ # Electron masaüstü uygulaması (platformlar arası) tests/ -├── unit/ # Node.js test runner (122 test files) -├── integration/ # Integration tests -├── e2e/ # Playwright tests -├── security/ # Security tests -├── translator/ # Translator-specific tests -└── load/ # Load tests +├── unit/ # Node.js test çalıştırıcısı (1.574 test dosyası) +├── integration/ # Entegrasyon testleri +├── e2e/ # Playwright testleri +├── security/ # Güvenlik testleri +├── translator/ # Çevirmene özel testler +└── load/ # Yük testleri -docs/ # Documentation -├── ARCHITECTURE.md # System architecture -├── API_REFERENCE.md # All endpoints -├── USER_GUIDE.md # Provider setup, CLI integration -├── TROUBLESHOOTING.md # Common issues -├── MCP-SERVER.md # MCP server (107 tools) -├── A2A-SERVER.md # A2A agent protocol -├── AUTO-COMBO.md # Auto-combo engine -├── CLI-TOOLS.md # CLI tools integration -├── COVERAGE_PLAN.md # Test coverage improvement plan -├── openapi.yaml # OpenAPI specification -└── adr/ # Architecture Decision Records +docs/ +├── adr/ # Mimari Karar Kayıtları (ADR) +├── architecture/ # Sistem mimarisi ve dayanıklılık +├── comparison/ # OmniRoute ve alternatifler +├── compression/ # Sıkıştırma kılavuzları ve kuralları +├── dev/ # Geliştirme kılavuzları +├── diagrams/ # Mimari diyagramları +├── frameworks/ # MCP, A2A, OpenCode, Bellek, Yetenekler +├── guides/ # Kullanıcı kılavuzu, Docker, kurulum, sorun giderme +├── i18n/ # Çok dilli README çevirileri +├── marketing/ # Pazarlama materyalleri +├── ops/ # Dağıtım, proxy, test kapsamı, sürümler +├── providers/ # Sağlayıcıya özel belgeler +├── reference/ # API referansı, ortam değişkenleri, CLI araçları, ücretsiz katmanlar +├── releases/ # Sürüm notları +├── routing/ # Auto-combo motoru, akıl yürütme tekrarı +├── screenshots/ # Pano ekran görüntüleri +├── security/ # Güvenlik önlemleri, uyumluluk, gizlilik, belirteçler +└── specs/ # Tasarım özellikleri ``` --- -## Adding a New Provider +## Yeni Bir Sağlayıcı Ekleme -### Step 1: Register Provider Constants +### Adım 1: Sağlayıcı Sabitlerini Kaydedin -Add to `src/shared/constants/providers.ts` — Zod-validated at module load. +`src/shared/constants/providers.ts` dosyasına ekleyin — modül yükleme sırasında Zod ile doğrulanır. -### Step 2: Add Executor (if custom logic needed) +### Adım 2: Yürütücü (Executor) Ekleyin (özel mantık gerekiyorsa) -Create executor in `open-sse/executors/your-provider.ts` extending the base executor. +`open-sse/executors/your-provider.ts` içinde temel yürütücüyü genişleten bir yürütücü oluşturun. -### Step 3: Add Translator (if non-OpenAI format) +### Adım 3: Çevirmen (Translator) Ekleyin (OpenAI dışı format ise) -Create request/response translators in `open-sse/translator/`. +`open-sse/translator/` altında istek/yanıt çevirmenleri oluşturun. -### Step 4: Add OAuth Config (if OAuth-based) +### Adım 4: OAuth Yapılandırması Ekleyin (OAuth tabanlıysa) -Add OAuth credentials in `src/lib/oauth/constants/oauth.ts` and service in `src/lib/oauth/services/`. +`src/lib/oauth/constants/oauth.ts` içine OAuth kimlik bilgilerini ve `src/lib/oauth/services/` içine servisini ekleyin. -### Step 5: Register Models +Yukarı akış sağlayıcısı genel bir OAuth client_id/secret veya Firebase Web API anahtarı dağıtıyorsa, bunu kaynak koda **dize sabiti olarak gömmeyin**. `open-sse/utils/publicCreds.ts` dosyasındaki `resolvePublicCred()` fonksiyonunu kullanın ve `EMBEDDED_DEFAULTS` içine maskelenmiş bayt girişi ekleyin. Zorunlu iş akışı [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) içinde belgelenmiştir. -Add model definitions in `open-sse/config/providerRegistry.ts`. +İşleyiciler/yürütücüler içinde istemciye ulaşan hata mesajları `open-sse/utils/error.ts` içindeki `buildErrorBody()` / `sanitizeErrorMessage()` üzerinden geçmelidir — Response gövdesine asla ham `err.stack` veya `err.message` koymayın. Bkz. [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md). -### Step 6: Add Tests +### Adım 5: Modelleri Kaydedin -Write unit tests in `tests/unit/` covering at minimum: +`open-sse/config/providerRegistry.ts` dosyasına model tanımlarını ekleyin. -- Provider registration -- Request/response translation -- Error handling +### Adım 6: Testleri Ekleyin + +`tests/unit/` altında en az şunları kapsayan birim testleri yazın: + +- Sağlayıcı kaydı +- İstek/yanıt çevirisi +- Hata yönetimi --- -## Pull Request Checklist +## Pull Request Kontrol Listesi -- [ ] Tests pass (`npm test`) -- [ ] Linting passes (`npm run lint`) -- [ ] Build succeeds (`npm run build`) -- [ ] TypeScript types added for new public functions and interfaces -- [ ] No hardcoded secrets or fallback values -- [ ] All inputs validated with Zod schemas -- [ ] CHANGELOG updated (if user-facing change) -- [ ] Documentation updated (if applicable) +- [ ] Testler geçiyor (`npm test`) +- [ ] Linting geçiyor (`npm run lint`) +- [ ] Derleme başarılı (`npm run build`) +- [ ] Yeni genel fonksiyonlar ve arayüzler için TypeScript tipleri eklendi +- [ ] Sabit kodlanmış sırlar veya geri dönüş değerleri yok +- [ ] Genel yukarı akış kimlik bilgileri `resolvePublicCred()` ile eklendi ([`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md)), asla sabit dize olarak değil +- [ ] Hata yanıtları `buildErrorBody()` / `sanitizeErrorMessage()` üzerinden geçiyor — yanıt gövdelerinde ham yığın izi (stack trace) yok ([`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md)) +- [ ] Kabuk komutları (`exec` / `spawn`) çalışma zamanı değerlerini dize birleştirme ile değil `env` ile iletiyor +- [ ] Tüm girdiler Zod şemaları ile doğrulanıyor +- [ ] Kullanıcıya yönelik değişiklikler için `changelog.d/{features|fixes|maintenance}/-.md` altında değişiklik günlüğü parçacığı (fragment) eklendi ([`changelog.d/README.md`](changelog.d/README.md)) — doğrudan `CHANGELOG.md` dosyasını düzenlemeyin +- [ ] Dokümantasyon güncellendi (varsa) +- [ ] Yeni CodeQL / Secret-Scanning uyarısı açılmadı veya her biri ilgili `docs/security/` belgesine atıfta bulunarak teknik gerekçeyle kapatıldı +- [ ] Alt süreçler başlatan rotalar (`/api/mcp/`, `/api/cli-tools/runtime/`) `src/server/authz/routeGuard.ts` içinde `isLocalOnlyPath()` olarak sınıflandırıldı +- [ ] Commit mesajlarında `Co-Authored-By` bulunmuyor — commit'ler yalnızca depo sahibinin Git kimliği altında görünmelidir --- -## Releasing +## Sürüm Yayımlama -Releases are managed via the `/generate-release` workflow. When a new GitHub Release is created, the package is **automatically published to npm** via GitHub Actions. +Sürümler `/generate-release` iş akışı aracılığıyla yönetilir. Yeni bir GitHub Sürümü oluşturulduğunda, paket GitHub Actions aracılığıyla **otomatik olarak npm'de yayımlanır**. + +VPS dağıtımları için `npm run build:release` kullanın — temiz bir yeniden derleme gerçekleştirir, paketi `dist/` içine toplar ve `dist/BUILD_SHA` nöbetçisini yazar. Ardından `dist/` dizinini uzak `app/` dizinine rsync eden `/deploy-vps-*-cc` yeteneklerini kullanın. --- -## Getting Help +## Yardım Alma -- **Architecture**: See [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) -- **API Reference**: See [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) -- **Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) -- **ADRs**: See `docs/adr/` for architectural decision records +- **Mimari**: Bkz. [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) +- **API Referansı**: Bkz. [`docs/reference/API_REFERENCE.md`](docs/reference/API_REFERENCE.md) +- **Güvenlik belgeleri**: [`docs/security/CLI_TOKEN.md`](docs/security/CLI_TOKEN.md), [`docs/security/ROUTE_GUARD_TIERS.md`](docs/security/ROUTE_GUARD_TIERS.md), [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md), [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) +- **Operasyon belgeleri**: [`docs/ops/SQLITE_RUNTIME.md`](docs/ops/SQLITE_RUNTIME.md) +- **Sorun Bildirimi (Issues)**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) +- **Mimari Karar Kayıtları (ADR)**: Mimari karar kayıtları için `docs/adr/` dizinine bakın diff --git a/docs/i18n/tr/GEMINI.md b/docs/i18n/tr/GEMINI.md index df63a60ea0..2e4e75eaf9 100644 --- a/docs/i18n/tr/GEMINI.md +++ b/docs/i18n/tr/GEMINI.md @@ -1,25 +1,12 @@ -# Security and Cleanliness Rules for AI Assistants (Türkçe) +# GEMINI.md (Türkçe) 🌐 **Languages:** 🇺🇸 [English](../../../GEMINI.md) · 🇸🇦 [ar](../ar/GEMINI.md) · 🇧🇬 [bg](../bg/GEMINI.md) · 🇧🇩 [bn](../bn/GEMINI.md) · 🇨🇿 [cs](../cs/GEMINI.md) · 🇩🇰 [da](../da/GEMINI.md) · 🇩🇪 [de](../de/GEMINI.md) · 🇪🇸 [es](../es/GEMINI.md) · 🇮🇷 [fa](../fa/GEMINI.md) · 🇫🇮 [fi](../fi/GEMINI.md) · 🇫🇷 [fr](../fr/GEMINI.md) · 🇮🇳 [gu](../gu/GEMINI.md) · 🇮🇱 [he](../he/GEMINI.md) · 🇮🇳 [hi](../hi/GEMINI.md) · 🇭🇺 [hu](../hu/GEMINI.md) · 🇮🇩 [id](../id/GEMINI.md) · 🇮🇹 [it](../it/GEMINI.md) · 🇯🇵 [ja](../ja/GEMINI.md) · 🇰🇷 [ko](../ko/GEMINI.md) · 🇮🇳 [mr](../mr/GEMINI.md) · 🇲🇾 [ms](../ms/GEMINI.md) · 🇳🇱 [nl](../nl/GEMINI.md) · 🇳🇴 [no](../no/GEMINI.md) · 🇵🇭 [phi](../phi/GEMINI.md) · 🇵🇱 [pl](../pl/GEMINI.md) · 🇵🇹 [pt](../pt/GEMINI.md) · 🇧🇷 [pt-BR](../pt-BR/GEMINI.md) · 🇷🇴 [ro](../ro/GEMINI.md) · 🇷🇺 [ru](../ru/GEMINI.md) · 🇸🇰 [sk](../sk/GEMINI.md) · 🇸🇪 [sv](../sv/GEMINI.md) · 🇰🇪 [sw](../sw/GEMINI.md) · 🇮🇳 [ta](../ta/GEMINI.md) · 🇮🇳 [te](../te/GEMINI.md) · 🇹🇭 [th](../th/GEMINI.md) · 🇹🇷 [tr](../tr/GEMINI.md) · 🇺🇦 [uk-UA](../uk-UA/GEMINI.md) · 🇵🇰 [ur](../ur/GEMINI.md) · 🇻🇳 [vi](../vi/GEMINI.md) · 🇨🇳 [zh-CN](../zh-CN/GEMINI.md) --- -## 1. File Placement & Organization +> **Tek doğruluk kaynağı:** Yapay zeka asistanları için tüm proje kuralları [`AGENTS.md`](AGENTS.md) dosyasında yer almaktadır. Herhangi bir değişiklik yapmadan önce tamamını okuyun — 23 Katı Kuralı, kalite kapılarını, kod kurallarını, dosya yerleşimi / depo kökü hijyen kurallarını, depo haritasını ve daha önce bu dosyada bulunan yerel geliştirme erişim notlarını içerir. -- **Test Files**: ALL unit tests, integration tests, ecosystem tests, or Vitest files MUST strictly be placed within the `tests/` directory (e.g., `tests/unit/`, `tests/integration/`). NEVER create test files in the project root (`/`). -- **Scripts and Utilities**: ALL maintenance, debugging, generation, or experimental scripts (`.cjs`, `.mjs`, `.js`, `.ts`) MUST be placed strictly inside the `scripts/` directory or `scripts/scratch/` for temporary one-offs. NEVER dump loose scripts in the project root (`/`). +Gemini'ye özel notlar: -**The Project Root MUST ONLY CONTAIN:** - -- Configuration files (`vitest.config.ts`, `next.config.mjs`, `eslint.config.mjs`, etc.) -- Dependency files (`package.json`, `package-lock.json`) -- Documentation files (`README.md`, `CHANGELOG.md`, `AGENTS.md`) -- CI/CD files and ignore definitions (`.gitignore`, `.dockerignore`) - -When creating _any_ validation tests or one-off logic scripts, default to using `scripts/scratch/` or the `tests/unit/` directories according to your goals. Do not pollute the `/` root context. - -## 2. VPS Dashboard Credentials - -| Environment | URL | Password | -| ----------- | ------------------------- | -------- | -| Local VPS | http://192.168.0.15:20128 | 123456 | +- Yetenekler (Skills), `activate_skill` aracı aracılığıyla etkinleştirilir (yetenek meta verileri oturum başlangıcında yüklenir ve tam içerik talep üzerine etkinleştirilir). +- Bugün için Gemini'ye özel başka bir kural yoktur. Buraya yeniden proje kuralları eklemeyin — her asistanın aynı talimatları görmesi için `AGENTS.md` dosyasını düzenleyin. diff --git a/docs/i18n/tr/README.md b/docs/i18n/tr/README.md index 32611a22bf..463c70caaa 100644 --- a/docs/i18n/tr/README.md +++ b/docs/i18n/tr/README.md @@ -1,2204 +1,1461 @@ -# 🚀 OmniRoute — The Free AI Gateway (Türkçe) +
-🌐 **Languages:** 🇺🇸 [English](../../../README.md) · 🇸🇦 [ar](../ar/README.md) · 🇧🇬 [bg](../bg/README.md) · 🇧🇩 [bn](../bn/README.md) · 🇨🇿 [cs](../cs/README.md) · 🇩🇰 [da](../da/README.md) · 🇩🇪 [de](../de/README.md) · 🇪🇸 [es](../es/README.md) · 🇮🇷 [fa](../fa/README.md) · 🇫🇮 [fi](../fi/README.md) · 🇫🇷 [fr](../fr/README.md) · 🇮🇳 [gu](../gu/README.md) · 🇮🇱 [he](../he/README.md) · 🇮🇳 [hi](../hi/README.md) · 🇭🇺 [hu](../hu/README.md) · 🇮🇩 [id](../id/README.md) · 🇮🇹 [it](../it/README.md) · 🇯🇵 [ja](../ja/README.md) · 🇰🇷 [ko](../ko/README.md) · 🇮🇳 [mr](../mr/README.md) · 🇲🇾 [ms](../ms/README.md) · 🇳🇱 [nl](../nl/README.md) · 🇳🇴 [no](../no/README.md) · 🇵🇭 [phi](../phi/README.md) · 🇵🇱 [pl](../pl/README.md) · 🇵🇹 [pt](../pt/README.md) · 🇧🇷 [pt-BR](../pt-BR/README.md) · 🇷🇴 [ro](../ro/README.md) · 🇷🇺 [ru](../ru/README.md) · 🇸🇰 [sk](../sk/README.md) · 🇸🇪 [sv](../sv/README.md) · 🇰🇪 [sw](../sw/README.md) · 🇮🇳 [ta](../ta/README.md) · 🇮🇳 [te](../te/README.md) · 🇹🇭 [th](../th/README.md) · 🇹🇷 [tr](../tr/README.md) · 🇺🇦 [uk-UA](../uk-UA/README.md) · 🇵🇰 [ur](../ur/README.md) · 🇻🇳 [vi](../vi/README.md) · 🇨🇳 [zh-CN](../zh-CN/README.md) +OmniRoute Dashboard ---- +
+
-### Keep coding through provider limits. Smart routing to free-access and low-cost AI models with automatic fallback. +# 🚀 OmniRoute — Ücretsiz AI Ağ Geçidi -_Your universal API proxy — one endpoint, 329 provider catalog entries, resilient fallback subject to upstream availability. Includes **MCP Server (107 tools, 32 scopes)**, **A2A Protocol**, **Memory/Skills Systems** & **Electron Desktop App**._ +OmniRoute — Kodlamayı asla bırakmayın. Her yapay zeka aracı → 349 sağlayıcı — 90+ ücretsiz — tek bir uç nokta üzerinden. Claude Code, Codex, Cursor, Cline, Copilot ve Antigravity'yi otomatik fallback ile ÜCRETSİZ Claude / GPT / Gemini modellerine bağlayın. RTK + Caveman katmanlı sıkıştırma %15–95 token tasarrufu sağlar (~%89 ortalama) — sınırlara asla takılmayın. 350 AI sağlayıcısı · 90+ ücretsiz katman · ~1.51B ücretsiz token/ay · 19 yönlendirme stratejisi · Başlamak için $0. -**Chat Completions • Embeddings • Image Generation • Video • Music • Audio • Reranking • **Web Search** • MCP Server • A2A Protocol • 100% TypeScript** - ---- +
+## 💰 Aylık ~1.51 Milyar Ücretsiz Token + +
+ +> Ücretsiz katmanları elle birleştirmek zahmetlidir — düzinelerce SDK, düzinelerce hız sınırı ve elinizde gerçekte ne kadar kota olduğuna dair hiçbir fikir olmaması. OmniRoute, **42 sağlayıcı havuzunda / 495 modelde** yer alan **belgelenmiş** ücretsiz katmanları tek bir dürüst sayıda toplar ve bunu panoda canlı olarak gösterir (`/dashboard/free-tiers`). + +OmniRoute ücretsiz katman bütçe kartı: Tek bir uç nokta arkasındaki 42 sağlayıcı havuzunun / 495 modelin belgelenmiş ücretsiz katmanlarından, sabit olarak ayda ~1.51B ücretsiz token, kayıt kredileriyle ilk ay ~2.13B'a kadar. Dürüst havuz tekilleştirme matematiği — her paylaşımlı havuz bir kez sayılır (tüm hız sınırlarını 7/24 saymak ~10B görünür; yayımlanmamıştır), 15 sağlayıcı Hizmet Şartları bayraklıdır, böylece kararı siz verirsiniz. Sayılabilir ücretsiz havuzların model bazında ızgara bütçe çubuğu (Mistral Large 3 1B, GPT-4o mini 150M, Gemini 2.5 Flash 60M … Claude Sonnet 4.5 25K), tek seferlik ilk ay kayıt kredileri (vertex 300M, agentrouter 200M, predibase 25M, together 25M, glm-cn 20M, doubao 15M, ai21 10M, longcat 10M, deepseek 5M, hyperbolic 5M, nscale 5M), ayrıca kalıcı olarak ücretsiz token sınırı olmayan sağlayıcılar (SiliconFlow, Z.AI GLM-Flash, Kilo, OpenCode Zen, baidu …) ve +24M/ay kilidini açan 10 dolarlık OpenRouter yüklemesi — başlığı asla yapay olarak şişirmemek için ayrı gösterilir. /dashboard/free-tiers üzerinde canlı kullanılan/kalan. + +> Canlı `/dashboard/free-tiers` sayfasının animasyonlu özeti. Tam metodoloji (havuz tekilleştirme, kredi katmanları, sağlayıcı şartları): **[docs/reference/FREE_TIERS.md](docs/reference/FREE_TIERS.md)**. +> +> Bu rakamlar canlı kataloğa göre iki haftada bir yeniden denetlenir ve **her iki yönde de değişir** — bir sağlayıcı ücretsiz katmanı sonlandırırsa sayı düşer; yeni biri gelirse yükselir. Asla yuvarlanmış iyimser senaryoları değil, kataloğun gerçekten hesapladığı değerleri yayımlıyoruz. + +
+ +
+ +

+ +⭐ OMNIROUTE paradan tasarruf etmenize ve işinizi kolaylaştırmanıza yardımcı olduysa depoya yıldız verin. + +

+ +[![Stars](https://img.shields.io/github/stars/diegosouzapw/OmniRoute?style=social)](https://github.com/diegosouzapw/OmniRoute) +diegosouzapw%2FOmniRoute | Trendshift +[![Star History Rank](https://api.star-history.com/badge?repo=diegosouzapw/OmniRoute&theme=dark)](https://www.star-history.com/diegosouzapw/omniroute) +[![olud.ai](https://olud.ai/badge.php?tool=diegosouzapw-omniroute)](https://olud.ai/project/diegosouzapw-omniroute.html) + +### 💬 Topluluğa katılın + +**👋 Geliştiriciyi takip edin — yeni sağlayıcılara, sürümlere ve ipuçlarına ilk siz ulaşın:** + +[![Follow Diego on LinkedIn](https://img.shields.io/badge/Follow_Diego_on-LinkedIn-0A66C2?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/diegosouzapw/) +[![Follow @diegosouzapw on GitHub](https://img.shields.io/github/followers/diegosouzapw?style=for-the-badge&logo=github&logoColor=white&label=Follow%20on%20GitHub&color=181717)](https://github.com/diegosouzapw) + +[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/U47eFqAXCn) +[![Telegram](https://img.shields.io/badge/Telegram-26A5E4?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/omnirouteOficial) +[![WhatsApp Global](https://img.shields.io/badge/WhatsApp_Global-25D366?style=for-the-badge&logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) +[![WhatsApp Brasil](https://img.shields.io/badge/WhatsApp_Brasil-25D366?style=for-the-badge&logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz) +[![Website](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online) + +**Sorular, sağlayıcı ipuçları, yol haritası ve destek → [Discord](https://discord.gg/U47eFqAXCn) · [Telegram](https://t.me/omnirouteOficial) · WhatsApp [🌍 Global](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) / [🇧🇷 Brezilya](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz)** + +
+ +## 📈 Ağ Geçidi Büyümeye Devam Ediyor + +
+ +| | v3.8.49 | **v3.8.50** | `v3.8.51+` | +| ----------------------------- | :-----: | :---------: | :---------: | +| 🌐 Sağlayıcılar | 290 | **342** | kuyrukta dahası var | +| 🧠 Belgelenmiş modeller | 1185 | **1202** | — | +| 🖼️ Modalite Köprüsü (Modality Bridge) | — | 🆕 vision | video | +| 📡 Radar ücretsiz kataloğu | — | 🆕 isteğe bağlı | — | +| ⚖️ Kota duyarlı zamanlama | — | — | 🔭 sırada | +| 📊 Kota telemetrisi | — | — | 🔭 sırada | + +**→ [Yol Haritası](ROADMAP.md) — `v3.9.0 LTS` hedefine doğru ilerliyor** + +
+ +
+ +## 🧩 Kullanılabilirlik + [![npm version](https://img.shields.io/npm/v/omniroute?color=cb3837&logo=npm)](https://www.npmjs.com/package/omniroute) +![NPM Monthly](https://img.shields.io/npm/dm/omniroute?label=npm/month&color=cb3837&logo=npm) [![Docker Hub](https://img.shields.io/docker/v/diegosouzapw/omniroute?label=Docker%20Hub&logo=docker&color=2496ED)](https://hub.docker.com/r/diegosouzapw/omniroute) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE) +![Docker Pulls](https://img.shields.io/docker/pulls/diegosouzapw/omniroute?label=docker%20pulls&logo=docker&color=2496ED) +![Electron Downloads](https://img.shields.io/github/downloads/diegosouzapw/omniroute/total?style=flat&label=electron%20downloads&logo=electron&color=47848F) -![NPM Downloads](https://img.shields.io/npm/dw/omniroute?label=npm%20down%20week&color=red) -![NPM Downloads](https://img.shields.io/npm/dm/omniroute?label=npm%20down%20month&color=red) - -![NPM Downloads](https://img.shields.io/npm/d18m/omniroute?label=npm%20down%20year&color=red) -![Docker Pulls](https://img.shields.io/docker/pulls/diegosouzapw/omniroute) -![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/diegosouzapw/omniroute/total?style=flat&label=eletron%20donwloads&color=blue) - -[![stars](https://custom-icon-badges.demolab.com/github/stars/diegosouzapw/OmniRoute?logo=star&style=flat)](https://github.com/diegosouzapw/OmniRoute/stargazers) -[![open issues](https://custom-icon-badges.demolab.com/github/issues-raw/diegosouzapw/OmniRoute?logo=issue)](https://github.com/diegosouzapw/OmniRoute/issues) -[![license](https://custom-icon-badges.demolab.com/github/license/diegosouzapw/OmniRoute?logo=law)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE) -[![last commit](https://custom-icon-badges.demolab.com/github/last-commit/diegosouzapw/OmniRoute?logo=history&logoColor=white)](https://github.com/diegosouzapw/OmniRoute/commits/main) -[![total contributions](https://custom-icon-badges.demolab.com/badge/dynamic/json?logo=graph&logoColor=fff&color=blue&label=total%20contributions&query=%24.totalContributions&url=https%3A%2F%2Fstreak-stats.demolab.com%2F%3Fuser%3Ddiegosouzapw%26type%3Djson)](https://github.com/diegosouzapw) -[![code size](https://custom-icon-badges.demolab.com/github/languages/code-size/diegosouzapw/OmniRoute?logo=file-code&logoColor=white)](https://github.com/diegosouzapw/OmniRoute) -[![pr closed](https://custom-icon-badges.demolab.com/github/issues-pr-closed/diegosouzapw/OmniRoute?color=purple&logo=git-pull-request&logoColor=white)](https://github.com/diegosouzapw/OmniRoute/pulls?q=is%3Apr+is%3Aclosed) -[![tag](https://custom-icon-badges.demolab.com/github/v/tag/diegosouzapw/OmniRoute?logo=tag&logoColor=white)](https://github.com/diegosouzapw/OmniRoute/tags) -[![github streak](https://custom-icon-badges.demolab.com/badge/dynamic/json?logo=fire&logoColor=fff&color=orange&label=github%20streak&query=%24.currentStreak.length&suffix=%20days&url=https%3A%2F%2Fstreak-stats.demolab.com%2F%3Fuser%3Ddiegosouzapw%26type%3Djson)](https://github.com/diegosouzapw) -[![followers](https://custom-icon-badges.demolab.com/github/followers/diegosouzapw?logo=person-add)](https://github.com/diegosouzapw?tab=followers) -[![fork](https://custom-icon-badges.demolab.com/github/forks/diegosouzapw/OmniRoute?logo=fork)](https://github.com/diegosouzapw/OmniRoute/network/members) -[![watch](https://custom-icon-badges.demolab.com/github/watchers/diegosouzapw/OmniRoute?logo=eye)](https://github.com/diegosouzapw/OmniRoute/watchers) - -[![License](https://img.shields.io/github/license/diegosouzapw/OmniRoute)](https://github.com/diegosouzapw/OmniRoute/blob/main/LICENSE) -[![Website](https://img.shields.io/badge/Website-omniroute.online-blue?logo=google-chrome&logoColor=white)](https://omniroute.online) -[![WhatsApp](https://img.shields.io/badge/WhatsApp-Community-25D366?logo=whatsapp&logoColor=white)](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) - -[🌐 Website](https://omniroute.online) • [🚀 Quick Start](#-quick-start) • [💡 Features](#-key-features) • [📖 Docs](#-documentation) • [💰 Pricing](#-pricing-at-a-glance) • [💬 WhatsApp](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
🚀 Başlangıç🚀 Hızlı Başlangıç📦 Kurulum🆓 Sıfır Yapılandırma
💡 Öğrenin💥 Vaat🤔 Neden OmniRoute🏆 Fark Yaratanlar
⚙️ Özellikler🎯 Kombolar🌐 Sağlayıcılar🔌 CLI & MCP
🗜️ Sıkıştırma🖥️ Nerede Çalışır🔒 Gizlilik
👀 İnceleyin🎬 İş Başında✨ Yenilikler🤖 Uyumlu CLI'lar
💚 Destek💚 Destek / Bağış💬 Topluluk💖 Sponsorlar
📦 Proje🛠️ Teknoloji Yığını📖 Belgeler👥 Katkıda Bulunanlar
-🌐 **Available in:** 🇺🇸 [English](README.md) | 🇧🇷 [Português (Brasil)](docs/i18n/pt-BR/README.md) | 🇪🇸 [Español](docs/i18n/es/README.md) | 🇫🇷 [Français](docs/i18n/fr/README.md) | 🇮🇹 [Italiano](docs/i18n/it/README.md) | 🇷🇺 [Русский](docs/i18n/ru/README.md) | 🇨🇳 [中文 (简体)](docs/i18n/zh-CN/README.md) | 🇩🇪 [Deutsch](docs/i18n/de/README.md) | 🇮🇳 [हिन्दी](docs/i18n/in/README.md) | 🇹🇭 [ไทย](docs/i18n/th/README.md) | 🇺🇦 [Українська](docs/i18n/uk-UA/README.md) | 🇸🇦 [العربية](docs/i18n/ar/README.md) | 🇯🇵 [日本語](docs/i18n/ja/README.md) | 🇻🇳 [Tiếng Việt](docs/i18n/vi/README.md) | 🇧🇬 [Български](docs/i18n/bg/README.md) | 🇩🇰 [Dansk](docs/i18n/da/README.md) | 🇫🇮 [Suomi](docs/i18n/fi/README.md) | 🇮🇱 [עברית](docs/i18n/he/README.md) | 🇭🇺 [Magyar](docs/i18n/hu/README.md) | 🇮🇩 [Bahasa Indonesia](docs/i18n/id/README.md) | 🇰🇷 [한국어](docs/i18n/ko/README.md) | 🇲🇾 [Bahasa Melayu](docs/i18n/ms/README.md) | 🇳🇱 [Nederlands](docs/i18n/nl/README.md) | 🇳🇴 [Norsk](docs/i18n/no/README.md) | 🇵🇹 [Português (Portugal)](docs/i18n/pt/README.md) | 🇷🇴 [Română](docs/i18n/ro/README.md) | 🇵🇱 [Polski](docs/i18n/pl/README.md) | 🇸🇰 [Slovenčina](docs/i18n/sk/README.md) | 🇸🇪 [Svenska](docs/i18n/sv/README.md) | 🇵🇭 [Filipino](docs/i18n/phi/README.md) | 🇨🇿 [Čeština](docs/i18n/cs/README.md) - ---- - -## 🖼️ Main Dashboard -
- OmniRoute Dashboard + 🌐 43 dilde +

+ English (en) + Português — Brasil (pt-BR) + Português (pt) + Español (es) + Français (fr) + Italiano (it) + Deutsch (de) + Nederlands (nl) + Русский (ru) + Українська (uk-UA) + Polski (pl) + Čeština (cs) + Slovenčina (sk) + Română (ro) + Magyar (hu) + Български (bg) + Dansk (da) + Suomi (fi) + Norsk (no) + Svenska (sv) + 中文 — 简体 (zh-CN) + 中文 — 繁體 (zh-TW) + 日本語 (ja) + 한국어 (ko) + ไทย (th) + Tiếng Việt (vi) + Bahasa Indonesia (id) + Bahasa Melayu (ms) + Filipino (phi) + हिन्दी (in) + हिन्दी (hi) + ગુજરાતી (gu) + मराठी (mr) + தமிழ் (ta) + తెలుగు (te) + বাংলা (bn) + اردو (ur) + فارسی (fa) + العربية (ar) + עברית (he) + Türkçe (tr) + Azərbaycan (az) + Kiswahili (sw)
---- +
+
-## 📸 Dashboard Preview +
-
-Click to see dashboard screenshots +## 🆓 Kurduğunuz anda çalışır — anahtar yok, yapılandırma yok -| Page | Screenshot | -| -------------- | ------------------------------------------------- | -| **Providers** | ![Providers](docs/screenshots/01-providers.png) | -| **Combos** | ![Combos](docs/screenshots/02-combos.png) | -| **Analytics** | ![Analytics](docs/screenshots/03-analytics.png) | -| **Health** | ![Health](docs/screenshots/04-health.png) | -| **Translator** | ![Translator](docs/screenshots/05-translator.png) | -| **Settings** | ![Settings](docs/screenshots/06-settings.png) | -| **CLI Tools** | ![CLI Tools](docs/screenshots/07-cli-tools.png) | -| **Usage Logs** | ![Usage](docs/screenshots/08-usage.png) | -| **Endpoints** | ![Endpoints](docs/screenshots/09-endpoint.png) | +
-
- ---- - -### 🤖 Free AI Provider for your favorite coding agents - -_Connect any AI-powered IDE or CLI tool through OmniRoute — free-access AI gateway; provider limits and terms apply._ - - - - - - - - - - - - - - - -
- - OpenClaw
- OpenClaw -

- ⭐ 205K -
- - NanoBot
- NanoBot -

- ⭐ 20.9K -
- - PicoClaw
- PicoClaw -

- ⭐ 14.6K -
- - ZeroClaw
- ZeroClaw -

- ⭐ 9.9K -
- - IronClaw
- IronClaw -

- ⭐ 2.1K -
- - OpenCode
- OpenCode -

- ⭐ 106K -
- - Codex CLI
- Codex CLI -

- ⭐ 60.8K -
- - Claude Code
- Claude Code -

- ⭐ 67.3K -
- - Kilo Code
- Kilo Code -

- ⭐ 15.5K -
- -📡 All agents connect via http://localhost:20128/v1 or http://cloud.omniroute.online/v1 — one config; model access and quotas depend on providers - ---- - -## 🤔 Why OmniRoute? - -**Stop wasting money and hitting limits:** - -- Subscription quota expires unused every month -- Rate limits stop you mid-coding -- Expensive APIs ($20-50/month per provider) -- Manual switching between providers - -**OmniRoute solves this:** - -- ✅ **Maximize subscriptions** - Track quota, use every bit before reset -- ✅ **Auto fallback** - Subscription → API Key → Cheap → Free; availability depends on eligible upstream routes -- ✅ **Multi-account** - Round-robin between accounts per provider - ---- - -## 📧 Support - -> 💬 **Join our community!** [WhatsApp Group](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) — Get help, share tips, and stay updated. - -- **Website**: [omniroute.online](https://omniroute.online) -- **GitHub**: [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) -- **Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) -- **WhatsApp**: [Community Group](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) -- **Contributing**: See [CONTRIBUTING.md](CONTRIBUTING.md), open a PR, or pick a `good first issue` - -### 🐛 Reporting a Bug? - -When opening an issue, please run the system-info command and attach the generated file: +Works the second you install it — zero config. Three steps: 1. Install — npm i -g omniroute, server boots on localhost:20128. 2. Point your tool at http://localhost:20128/v1 — any OpenAI-compatible tool (Claude Code, Cursor, Cline). 3. It answers — call model auto for an instant reply, with no API key, no signup, no configuration. Keyless free providers OpenCode Free and Felo are pre-wired into the auto combo, so a fresh install responds out of the box. ```bash -npm run system-info +# Fresh install, zero credentials — `auto` already works: +curl http://localhost:20128/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{"model":"auto","messages":[{"role":"user","content":"Hello!"}]}' ``` -This generates a `system-info.txt` with your Node.js version, OmniRoute version, OS details, installed CLI tools (qoder, gemini, claude, codex, antigravity, droid, etc.), Docker/PM2 status, and system packages — everything we need to reproduce your issue quickly. Attach the file directly to your GitHub issue. +Belirli bir ücretsiz arka uç mu tercih ediyorsunuz? Doğrudan çağırın, örn. `oc/…` (OpenCode Free) veya `felo/…` (Felo). Ardından `auto` modeline geçin ve seçimi OmniRoute'a bırakın. ---- +📦 **Python, Node.js, PHP ve cURL** için kopyala-yapıştır hızlı başlangıç betikleri → [`examples/quickstart/`](examples/quickstart/) -## 🔄 How It Works +
+ +
+ +# 💥 Vaat + +
+ +The Promise — One endpoint. 349 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 349 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 15–95%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals — 25,000+ tests). + +
+
+ +
+ +# 🤔 Neden OmniRoute? + +
+ +Why OmniRoute — stop juggling 10 dashboards, dead API keys and surprise bills. Ten daily pains vs fixes: quota expiring unused → maximize subscriptions; rate limits mid-coding → 4-tier auto-fallback (Subscription → API → Cheap → Free); tool outputs burning tokens → RTK + Caveman compression (15–95%); expensive APIs → cost-optimized routing; every tool its own setup → one endpoint, one dashboard; AI blocked → 3-level proxy + TLS stealth; dead keys → 3-layer resilience (circuit breakers, key cooldown, model lockout); team sharing one subscription → key pools with fair-share quotas; prompts through someone's cloud → local-first with AES-256-GCM encrypted keys; no spend visibility → live analytics (usage, quota, savings, p95 latency). + +
+ +OmniRoute request flow: your IDE or CLI (Claude Code, Cursor, Cline…) calls one local endpoint (http://localhost:20128/v1); the OmniRoute Smart Router (RTK + Caveman compression, 19 routing strategies, circuit breakers, TLS stealth, MCP, A2A, guardrails) auto-falls back across 4 provider tiers — Tier 1 Subscription (Claude Code, Codex, Copilot), quota out? Tier 2 API Key (DeepSeek, Groq, xAI), budget hit? Tier 3 Cheap (GLM $0.5, MiniMax $0.2), budget hit? Tier 4 Free (Kiro, Qoder, Pollinations) — always on. + +
+ +
+ +
+ +## 🤝 Açık Kaynak Dostlarımız Tarafından Desteklenmektedir + +
+ +

+ + Kimi K3 — Open Frontier Intelligence · 2.8T parameters · 1M-token context + +

+ +> **Açık Kaynak Dostu olarak katılmak ister misiniz?** Bunlar açık kaynağı destekleyen ve OmniRoute'un gelişimine güç katan şirketlerdir — ve bize sağladıkları her tokenın nereye gittiğini kamuoyuna açıkça belirtiyoruz. İletişime geçin: [diegosouza.pw@outlook.com](mailto:diegosouza.pw@outlook.com) + + + + + + + + + + +
+ + + + Kimi (Moonshot AI) + + +
Kimi
Moonshot AI

+ Founding Open Source Friend +
+ Bu projeyi destekleyen kurucu Açık Kaynak Dostumuz Kimi'ye (Moonshot AI) teşekkür ederiz! Kimi, açık ağırlıklı K2 ve K3 model ailelerinin arkasındaki yapay zeka laboratuvarıdır — Kimi K3, 1 milyon tokenlık bağlam penceresi, yerel görüntü yeteneği (vision) ve kapalı model fiyatlarının çok altında öncü düzeyde kodlama performansı sunar; Claude Code, Codex ve OmniRoute'un sunduğu tüm kodlama araçlarıyla kutudan çıktığı gibi çalışır. +

+ Kimi desteğinin sağladıkları: Kimi'nin API kredileri, OmniRoute'un yapay zeka ile doğrulanan sürüm hattına —her çekme isteğini (PR) yayımlanmadan önce inceleyen Kimi K3 destekli birleştirme doğrulama aşamasına— ve günlük özellik geliştirmelerine güç verir. Birinci sınıf Kimi desteği her iki kanalda da sunulur: doğrudan Kimi API (kimi-k3) ve Kimi Code kodlama planı (OAuth ve API anahtarı). OmniRoute ayrıca Kimi'nin destek programındaki ilk Brezilya açık kaynak projesidir. %15 ekstra kredili Kimi API anahtarı alın → +
+ + Cheaper Inference + +
Cheaper Inference
cheaperinference.com

+ Open Source Friend +
+ Bu projeyi destekleyen OmniRoute Açık Kaynak Dostu Cheaper Inference'a teşekkürler! Cheaper Inference, tek bir OpenAI uyumlu uç nokta arkasında 42 öncü modeli (Claude, GPT-5.x, Gemini, Kimi K3, GLM, DeepSeek, Grok ve MiniMax) maliyete göre sıralayan bir ağ geçididir; her isteği model üreticisinin liste fiyatının üzerinde asla ücretlendirmeden en ucuz uygun sağlayıcıya yönlendirir. +

+ OmniRoute'ta birinci sınıf destek: Chat Completions, yerel /v1/responses uç noktası, vision, araç çağırma ve 3 görsel modeli (cheaperinference/<model> olarak erişilebilen grok-imagine, nano-banana-pro, nano-banana-2). API anahtarı alın → +
+ +aff=omniroute etiketli bağlantılar ortaklık bağlantılarıdır. Size hiçbir ek maliyet getirmeden projeyi finanse eder. + +
+ +
+🎟️ Ortaklık Promosyonları — sponsor olmadığımız sağlayıcılardan ücretsiz kayıt kuponları (genişletmek için tıklayın) + +Bu bölüm yalnızca tavsiye/kupon kodları içindir. Sponsorlu ortaklıklar yukarıdaki 🤝 Açık Kaynak Dostlarımız Tarafından Desteklenmektedir bölümünde yer alır. OmniRoute'un burada listelenen sağlayıcılarla hiçbir sponsorluğu veya ortaklığı yoktur — bunlar herkesin kullanabileceği kamuya açık kuponlardır. + + + + + + +
+ + AgentRouter + +
AgentRouter
agentrouter.org +
+ AgentRouter — ortaklık kaydı · Kayıtta 100$ ücretsiz kredi (ücretsiz sunucu, daha yüksek gecikme süresi bekleyin — üretim için değil, test için en iyisidir). v3.8.50 sürümünden itibaren OmniRoute'ta birinci sınıf destek: Chat Completions, Anthropic uyumlu kablo formatı ve OpenAI uyumlu yol. Mevcut modeller arasında claude-opus-4-8, claude-opus-5, gpt-5.6-sol ve daha fazlası yer alır. 100$'ınızı hemen alın → +

+ ⚠️ Ortaklık bağlantısı — OmniRoute'un bu sağlayıcıyla hiçbir sponsorluğu veya ortaklığı yoktur. +
+ +OmniRoute kullanıcılarına fayda sağlayan cömert bir ücretsiz kayıt kuponuna sahip başka bir sağlayıcı biliyor musunuz? Bir issue açın, buraya ekleyelim. + +
+ +
+ +
+ +## 🎯 Kombolar — Amiral Gemisi + +
+ +All 19 combo routing strategies animated — one tile per strategy: priority, fill-first, weighted, round-robin, p2c, least-used, random, strict-random, cost-optimized, headroom, reset-window, reset-aware, context-relay, context-optimized, cache-optimized, lkgp, auto, fusion, pipeline. See the table above for what each one does. + +> Bir **kombo**, OmniRoute'un **otomatik olarak** yönlendirme yaptığı model zinciridir. Kota bittiğinde, sağlayıcı çöktüğünde veya maliyetler fırladığında — kombo sessizce bir sonraki modele geçer. **OmniRoute'u kesintisiz kılan şey budur.** 🛡️ + +### ⚡ Sıfır yapılandırma — sadece `auto` kullanın + +Oluşturulacak bir kombo yok. Modelinizi `auto` (veya bir varyantı) olarak ayarlayın; OmniRoute bağlı sağlayıcılarınızdan canlı olarak puanlanan sanal bir kombo oluşturur: + + + + + + + + + +
Model IDNeyi optimize eder
auto🎯 Dengeli varsayılan (LKGP — son başarılı sağlayıcınıza sadık kalır)
auto/coding🧑‍💻 Kod üretimi için kalite öncelikli ağırlıklar
auto/fast⚡ Öncelikli olarak en düşük gecikme süresi
auto/cheap💰 Öncelikli olarak token başına en ucuz model
auto/offline🔋 Öncelikli olarak en fazla kota / hız sınırı payı olan model
auto/smart🔭 Kalite öncelikli + daha iyi modeller keşfetmek için %10 keşif payı
+ +## + +### 🔀 Veya kendinizinkini oluşturun — 19 yönlendirme stratejisi + +Tüm **19** strateji — kombo adımı başına karıştırın ve eşleştirin: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#StratejiNe yapar
1priorityİlk hedeften sıralı liste — sonrakine geçmeden önce her birini tüketir 🥇
2fill-firstDevam etmeden önce her hedefin kotasını tamamen doldurur
3weightedHedef başına ağırlığa göre ağırlıklı rastgele seçim
4round-robinHedefler arasında sırayla döner
5p2cİki seçenekli güç (power-of-two-choices) rastgele yük dengeleme
6least-usedMevcut yükü en düşük olan hedefi seçer
7randomTekdüze rastgele seçim (tekilleştirilmiş)
8strict-randomTekrarları tekilleştirmeden rastgele seçim 🎲
9cost-optimizedCanlı katalog fiyatlandırması üzerinden istek başına maliyeti ($) en aza indirir 💸
10headroomEn çok kalan kotası olan hedefi seçer
11reset-windowKota penceresi en erken sıfırlanacak hedefi tercih eder
12reset-awareKota sıfırlama süresine göre sıralar — kısa pencereler önce 📊
13context-relayUzun konuşmalarda bağlamı hedefler arasında devreder 🧠
14context-optimizedMevcut bağlam boyutu için en uygun modeli seçer
15cache-optimizedHer yeniden kullanılabilir istem önekini aynı hesaba sabitler — istem önbelleği (prompt-cache) isabetlerini maksimize eder 🎯
16lkgpSon Bilinen İyi Yol (Last-Known-Good Path) — son başarılı hedefe bağlı kalır
17autoTüm bağlantılar arasında 14 faktörlü canlı puanlama 🤖
18fusionBir model paneline paralel dağıtır + bir hakem model tek bir nihai yanıt sentezler 🧬
19pipelineAdımları birbirine bağlar — her hedefin çıktısı sonrakini besler 🔗
+ +Auto-Combo motoru her adayı **14 faktör** üzerinden puanlar (sağlık, kota, maliyet, gecikme, başarı oranı, tazelik…) — bkz. [`docs/routing/AUTO-COMBO.md`](docs/routing/AUTO-COMBO.md). + +## + +### 🧱 Dayanıklılık yerleşiktir (3 bağımsız katman) + +OmniRoute resilience — 3 independent self-healing layers, the right layer for the right failure. Layer 1 provider circuit breaker (whole provider): trips only on 408/5xx, thresholds OAuth 10× / API-key 15× / local 2×, resets 60s/30s/15s into a HALF-OPEN probe, lazy recovery; while OPEN the combo reroutes to the next provider. Layer 2 connection cooldown (one key/account): base 5s OAuth / 3s API-key, exponential ×2 backoff with anti-thundering-herd guard, 429 honors Retry-After, success clears all error state; one cooling key is skipped while sibling keys keep serving. Layer 3 model lockout (one model): per-model 429, local 404 or mode denials lock just that model — never the whole connection. Terminal states (banned, expired, credits exhausted) are for the operator, not cooldowns. + +📖 [Auto-Combo Motoru](docs/routing/AUTO-COMBO.md) · [Dayanıklılık Kılavuzu](docs/architecture/RESILIENCE_GUIDE.md) + +
+ +
+ +## 🏆 OmniRoute'u Farklı Kılan Nedir + +
+ +What sets OmniRoute apart — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 349 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project's docs. + +📊 9router, OpenRouter, CLIProxyAPI ve LiteLLM'e karşı tam metodoloji ve özellik bazında detaylar → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md) + +
+ +## 💚 OmniRoute'u Destekleyin + +OmniRoute, MIT lisanslıdır ve açık olarak sürdürülmektedir. Size zaman veya para tasarrufu sağlıyorsa, bağımsız kalmasını nasıl sağlayabileceğinizi buradan görebilirsiniz — size en uygun yöntemi seçin. Sponsorluk yönlendirme önceliğini asla etkilemez; sıralamayı değil, görünürlüğü sağlar. + + + + + + + + + +
Depoya yıldız verinÜcretsizdir — görünürlüğe gerçekten yardımcı olurOmniRoute'a Yıldız Verin
🐙 GitHub SponsorsTek seferlik veya aylık · sıfır platform komisyonugithub.com/sponsors/diegosouzapw
Ko-fiHızlı tek seferlik bahşiş, bağışçı için kayıt gerekmezko-fi.com/diegosouzapw
🧋 Buy Me a CoffeeKüçük, samimi bir jestbuymeacoffee.com/diegosouzapw
🖐 LiberapayTekrarlayan · kar amacı gütmeyen · açık kaynakliberapay.com/diegosouzapw
🇧🇷 PIX (Brezilya)Anında, masrafsızanahtar ve QR aşağıda
KriptoBTC · ETH · USDT-TRC20 · USDC-Solanaadresler aşağıda
+ +**🇧🇷 PIX** — anında, masrafsız (Brezilya) + +OmniRoute PIX QR code + +Key (random): `5d865059-bc44-483a-962d-43ceb80126eb` + +Pix copia-e-cola: ``` -┌─────────────┐ -│ Your CLI │ (Claude Code, Codex, OpenClaw, Cursor, Cline...) -│ Tool │ -└──────┬──────┘ - │ http://localhost:20128/v1 - ↓ -┌─────────────────────────────────────────┐ -│ OmniRoute (Smart Router) │ -│ • Format translation (OpenAI ↔ Claude) │ -│ • Quota tracking + Embeddings + Images │ -│ • Auto token refresh │ -└──────┬──────────────────────────────────┘ - │ - ├─→ [Tier 1: SUBSCRIPTION] Claude Code, Codex - │ ↓ quota exhausted - ├─→ [Tier 2: API KEY] DeepSeek, Groq, xAI, Mistral, NVIDIA NIM, etc. - │ ↓ budget limit - ├─→ [Tier 3: CHEAP] GLM ($0.6/1M), MiniMax ($0.2/1M) - │ ↓ budget limit - └─→ [Tier 4: FREE] Qoder, Qwen, Kiro (provider limits apply) - -Result: broader fallback coverage and cost control; availability is not guaranteed +00020101021126580014br.gov.bcb.pix01365d865059-bc44-483a-962d-43ceb80126eb5204000053039865802BR5922OMNIROUTE CONTRIBUICAO6006BRASIL62070503***630475DD ``` ---- - -## 🎯 What OmniRoute Solves — 30 Real Pain Points & Use Cases - -> **Every developer using AI tools faces these problems daily.** OmniRoute was built to solve them all — from cost overruns to regional blocks, from broken OAuth flows to protocol operations and enterprise observability. +
-💸 1. "I pay for an expensive subscription but still get interrupted by limits" +₿ Kripto — BTC · ETH · USDT-TRC20 · USDC-Solana (genişletmek için tıklayın) -Developers pay $20–200/month for Claude Pro, Codex Pro, or GitHub Copilot. Even paying, quota has a ceiling — 5h of usage, weekly limits, or per-minute rate limits. Mid-coding session, the provider stops responding and the developer loses flow and productivity. + + + + + +
₿ BTCBitcoin (SegWit)bc1qh00smz004sy85wyl28v77tenkt3ckl6eaep7fd
Ξ ETHEthereum (ERC20)0x64Cf6B68A6Ff34288e89172950a2d00102337a84
₮ USDTTron (TRC20)TKAF41JpuQrHbKTnsQa9svJE2T192Hvsc2
$ USDCSolana2emNNZzVVWQc3FQ2wk9M6qXUQmW8AKdjjL174fXR28Tu
-**How OmniRoute solves it:** - -- **Smart 4-Tier Fallback** — If subscription quota runs out, automatically redirects to API Key → Cheap → Free with zero manual intervention -- **Provider Limits Tracking** — Cached quota snapshots refresh on a server-side schedule (default `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70`) with manual refresh available in the UI -- **Multi-Account Support** — Multiple accounts per provider with auto round-robin — when one runs out, switches to the next -- **Custom Combos** — Customizable fallback chains with 13 balancing strategies (priority, weighted, fill-first, round-robin, P2C, random, least-used, cost-optimized, strict-random, auto, lkgp, context-optimized, **context-relay**) -- **Structured Combo Builder** — Build combos step-by-step with explicit provider + model + account selection, including repeated providers and fixed-account targets -- **Quota-Aware P2C** — Power-of-two account selection now factors quota headroom, backoff, recent errors, and consecutive use -- **Codex Business Quotas** — Business/Team workspace quota monitoring directly in the dashboard +⚠️ Her coini yalnızca gösterilen ağ üzerinden gönderin — yanlış ağda göndermek fonların kaybolmasına neden olabilir.
-
-🔌 2. "I need to use multiple providers but each has a different API" +🐛 Bir hata mı buldunuz veya geri bildiriminiz mi var? Bir [Tartışma (Discussion)](https://github.com/diegosouzapw/OmniRoute/discussions) açın. + +
+ +

Geliştirici notları: Proje, geliştirici kolaylığı sağlamak amacıyla npm install/postinstall sırasında yerel bir .env dosyası oluşturabilir. Bu dosya kasıtlı olarak .gitignore aracılığıyla yoksayılır (bkz. .gitignore) ve kesinlikle commit edilmemelidir — yanlışlıkla commit edilirse, açığa çıkan sırları yenileyin ve dosyayı geçmişten kaldırın. Yerel ortam dosyalarını ve sırları yönetmeyle ilgili rehberlik için docs/DEVELOPER-ENVIRONMENT.md dosyasına bakın.

+ +## 📡 OmniRoute Radar + +Ana ücretsiz katman başlığı, yukarıdaki belgelenmiş ve havuz tekilleştirmesi yapılmış katalogdan **aylık ~1.53 milyar token** olarak kalmaya devam eder. Geçici sağlayıcı kayıt kredileri ilk ayı ayrıca **~2.15 milyar token** seviyesine çıkarabilir. Radar, OmniRoute sürümleri arasında daha güncel ücretsiz model kullanılabilirliği isteyenler için isteğe bağlı, imzalı bir katalog katmanıdır; topluluk kataloğu ve mevcut tüm ücretsiz özellikler ücretsiz kalmaya devam eder. + +Destekçiler canlı kataloğu ve ek sağlayıcı fırsatlarını alabilir. Ayrı ve değişken tavanı, sağlayıcı kullanılabilirliğine bağlı olarak **ayda en fazla yaklaşık 3 milyar token** düzeyindedir. Bu tavan bir garanti değildir: sağlayıcılar kotaları, uygunlukları, modelleri veya bölgeleri istedikleri zaman değiştirebilir. + +Radar isteğe bağlıdır (opt-in) ve yalnızca GET istekleri yapar. OmniRoute istemcisi istemleri, trafiği, sağlayıcı yapılandırmasını, kullanım telemetrisini veya yerel duyuru kapatma durumunu asla yüklemez (upload etmez). Uygunluk ve mevcut katalog hakkında bilgi edinmek için: **[radar.omniroute.online/planos](https://radar.omniroute.online/planos)**. + +
+ +
+ +## ✨ Yenilikler + +
+ +> **v3.8.20 → v3.8.50** sürümlerinden öne çıkan yenilikler. Tam geçmiş için: [`CHANGELOG.md`](CHANGELOG.md). + +- **🎛️ OmniConductor** — Ajan filonuza gelen A2A yetkilendirmesi, Agent Card üzerinde Conductor yetenekleri ve Faro bas-konuş sesli sohbet içeren pano paneli. → [A2A Sunucusu](docs/frameworks/A2A-SERVER.md) +- **🛂 Uyarlanabilir kabul ve aşırı yük koruması** — Ağır sohbet istekleri 503 hatası vermek yerine kuyruğa alınır; bağlantı başına atomik RPM kayan kiralamaları uygulanır. → [Dayanıklılık Kılavuzu](docs/architecture/RESILIENCE_GUIDE.md) +- **🗂️ Standart `/v1/models` sıralaması** — Sağlayıcı başına tek bir bitişik sağlayıcı gruplu blok (kombolar en başa sabitlenir), tüm katalog kaynaklarında kararlıdır. → [API Referansı](docs/reference/API_REFERENCE.md) +- **🗜️ Sıkıştırma güçlendirmesi** — Varsayılan olarak açık şişirme koruması (inflation guard), DE / FR / JA + Çince (wényán) için Caveman paketleri, Gradle ve .NET için RTK filtreleri. → [Sıkıştırma](docs/compression/COMPRESSION_ENGINES.md) +- **💸 Dürüst sabit maliyet** — Abonelik / kodlama planı sağlayıcıları maliyet analizlerinde **$0** olarak okunur; bütçe, kota ve yönlendirme tahmin yapmaya devam eder. → [API Referansı](docs/reference/API_REFERENCE.md) +- **⚖️ Kota Paylaşımlı (Quota-Share) yönlendirme** — Paylaşılan bir hesabın kotasını havuzdaki anahtarlar arasında adil bir şekilde böler; boşta kalan dilimlerin ödünç verilmesini sağlar. → [Dayanıklılık Kılavuzu](docs/architecture/RESILIENCE_GUIDE.md) +- **🤖 Tek komutla CLI/ajan kurulumu** — `setup-*` 12'den fazla kodlama aracını yapılandırır; `omniroute run` sıfır yapılandırma yazarak 7 CLI'yı (Claude Code, Codex, Aider, Goose, OpenCode, Qwen Code, Gemini CLI) başlatır; `omniroute configure` bağlam başına favorilere sahip etkileşimli bir sağlayıcı+model seçicisidir. → [CLI Entegrasyonları](docs/guides/CLI-INTEGRATIONS.md) +- **🛰️ Uzak mod** — Kapsamlı erişim tokenlarıyla (`connect` / `contexts` / `tokens`) uzak bir OmniRoute'u yönetin + VPS kurulumları için bir `antigravity` OAuth yardımcısı. → [Uzak Mod](docs/guides/REMOTE-MODE.md) +- **🧭 Daha akıllı otomatik yönlendirme** — `auto/:` komboları, **Fusion** (model paneli + hakem), görev duyarlı yönlendirme, istek başına model / mod / USD bütçesi geçersiz kılmaları. → [Auto-Combo](docs/routing/AUTO-COMBO.md) +- **🗜️ Eklenebilir sıkıştırma** — 12 birleştirilebilir motor + Sıkıştırma Stüdyoları: LLMLingua-2, iki katmanlı Ultra, omniglyph, adım başına doğruluk kapısı, GCF v3.2, sürükle-bırak sıralama düzenleyicisi. → [Sıkıştırma](docs/compression/COMPRESSION_ENGINES.md) +- **🕵️ Şeffaf MITM şifre çözme (TPROXY)** — SNI başına CA + güven deposu yükleyicisiyle proxy ortam değişkenlerini yoksayan CLI'ları yakalayın. → [MITM/TPROXY](docs/security/MITM-TPROXY-DECRYPT.md) +- **💸 Her yerde maliyet telemetrisi** — Her uç noktada `X-OmniRoute-*` maliyet/kullanım başlıkları, önbellek İSABETİ (cache-HIT) tasarruf başlığı, anahtar başına USD harcama kotaları. → [API Referansı](docs/reference/API_REFERENCE.md) +- **🧠 Kontrol ettiğiniz bellek** — Varsayılan olarak kapalı, isteğe bağlı int8 vektör niceleme + tipli sönümleme, istek başına `x-omniroute-no-memory`. → [Bellek](docs/frameworks/MEMORY.md) +- **🛡️ Güvenlik** — Her LLM rotasında istem enjeksiyonu koruması (red-team paketi), isteğe bağlı kimlik bilgisi maskeleme koruması (her iki yönde de sızan API anahtarlarını/gizli bilgileri sansürler), ücretsiz DuckDuckGo son çare web araması ve pano için isteğe bağlı OIDC giriş kapısı (şifreyle giriş her zaman kullanılabilir kalır). → [Güvenlik Önlemleri (Guardrails)](docs/security/GUARDRAILS.md) +- **🖼️ Yeni uç noktalar** — `/v1/ocr` (Mistral OCR) ve `/v1/audio/translations` (Whisper tarzı) medya yüzeyini tamamlar. → [API Referansı](docs/reference/API_REFERENCE.md) +- **🎨 Görsel / video / ses üretimi** — Medya için tek bir API: xAI Grok Imagine ve Novita AI video, ComfyUI, Freepik, Adobe Firefly, Microsoft Designer, Segmind, EdgeTTS. → [API Referansı](docs/reference/API_REFERENCE.md) +- **🌍 Dağıtım ve operasyonlar** — Ters proxy `basePath`, tarayıcı dili otomatik algılama, anahtar başına cihaz takibi, root gerektirmeyen MITM güveni, zh-TW yerelleştirmesi. → [Ortam Değişkenleri](docs/reference/ENVIRONMENT.md) +- **🤝 Daha fazla sağlayıcı ve ajan** — Cursor Cloud Agent, tarayıcı + OAuth girişiyle Grok Build (xAI), Ollama birinci sınıf kartı, Claude Opus 5 ve Sonnet 5, Kimi resmi ortaklığı (Code/Web/Moonshot), Zed, Requesty, SenseNova, Yuanbao, Agnes AI… ve yenilenen **350 sağlayıcılı katalog**. → [Sağlayıcılar](docs/reference/PROVIDER_REFERENCE.md) +- **📡 Yönlendirme şeffaflığı** — Her yanıt, isteğe hizmet veren stratejiyi/sağlayıcıyı/gecikmeyi belirten bir `X-OmniRoute-Decision` başlığı taşır, yeni bir `cache-optimized` kombo stratejisi + Auto-Combo `cacheAffinity` faktörü yinelenen istekleri önbelleğe alınmış öneki tutan bağlantıya geri yönlendirir ve salt okunur bir `/v1/auto-combo/{channel}/candidates` uç noktası bir `auto/*` kanalının canlı aday havuzunu gösterir. → [Auto-Combo](docs/routing/AUTO-COMBO.md) +- **⚡ Yerel performans ve altyapı** — Tek tıkla yerel Redis, Cloudflare Workers / Deno Deploy röle dağıtıcıları, denetlenen yerleşik servisler olarak Bifrost ve Mux. → [Gömülü Servisler](docs/frameworks/EMBEDDED-SERVICES.md) + +
+ +
+ +## 🤖 Uyumlu CLI'lar ve Kodlama Ajanları + +> Tek bir yapılandırma — `http://localhost:20128/v1` — ve **her** yapay zeka destekli IDE veya CLI, ücretsiz ve düşük maliyetli modeller üzerinde çalışır. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Claude Code
Claude Code
                           
Codex CLI
Codex CLI
                           
Cline
Cline
                           
Kilo Code
Kilo Code
                           
Zoo Code
Zoo Code
                           
Continue
Continue
                           
Aider
Aider
                           
ForgeCode
ForgeCode
                           
jcode
jcode
                           
DeepSeek TUI
DeepSeek TUI
                           
CodeWhale
CodeWhale
                           
OpenCode
OpenCode
                           
Factory Droid
Factory Droid
                           
GitHub Copilot CLI
Copilot CLI
                           
Cursor CLI
Cursor CLI
                           
Smelt
Smelt
                           
Pi (pi-coding-agent)
Pi
                           
Grok Build (xAI)
Grok Build
                           
Hermes Agent (Nous Research)
Hermes Agent
                           
OpenClaw
OpenClaw
                           
Goose
Goose
                           
Open Interpreter
Open Interpreter
                           
Warp AI
Warp AI
                           
Agent Deck
Agent Deck
                           
+
+ +
++ ayrıca şunlarla da çalışır · Kiro · Command Code · Antigravity · Windsurf · AMP · herhangi bir OpenAI uyumlu araç +
+ +📖 34 aracın tümü için araç bazında kurulum (26 CLI Kodlama + 8 CLI Ajanı) → [`docs/reference/CLI-TOOLS.md`](docs/reference/CLI-TOOLS.md) · 🧩 OpenCode eklentisi → [`@omniroute/opencode-provider`](https://www.npmjs.com/package/@omniroute/opencode-provider) + +
+ +
+ +**Desteklenen herhangi bir CLI'yı OmniRoute üzerinden tek bir komutla başlatın** — hiçbir yapılandırma dosyası yazılmaz, +kimlik bilgileri süreç başına enjekte edilir, Qwen/Gemini tek kullanımlık yalıtılmış bir ana dizin alır: + +```bash +omniroute run claude --model openai/gpt-5.4 # Claude Code +omniroute run codex --model glm/glm-5.2 # OpenAI Codex CLI +omniroute run aider --model glm/glm-5.2 -- --message "reply OK" +omniroute run goose --model glm/glm-5.2 +omniroute run opencode --model glm/glm-5.2 -- run "reply OK" +omniroute run qwen --model glm/glm-5.2 -- -p "reply OK" +omniroute run gemini --model glm/glm-5.2 -- --skip-trust -p "reply OK" + +# Veya sağlayıcı+modeli etkileşimli olarak seçip aracın kendi yapılandırmasını yazın: +omniroute configure codex # ayrıca: claude opencode qwen aider goose cline continue kilo +``` + +Her komut aktif uzak bağlama (`omniroute connect `) uyar, `--dry-run` +çalıştırmadan tam ortamı/argümanları önizler ve `--api-key-env NAME` sırları +kabuk geçmişinizin dışında tutar. → [CLI Entegrasyonları](docs/guides/CLI-INTEGRATIONS.md) + +
+ +
+ +## 🌐 349 AI Sağlayıcısı — 90+ Ücretsiz + +
+ +> Açık kaynaklı herhangi bir yönlendiricinin en eksiksiz kataloğu: **349 sağlayıcı**, **ücretsiz katmanı olan 90+ sağlayıcı**, **sonsuza kadar ücretsiz 56 sağlayıcı**. + +
+ +### 🏢 Her büyük laboratuvar — tek bir uç nokta üzerinden + + + + + + + + + + + + + + + + + + + + + + + + + + +
OpenAI
OpenAI
                           
Anthropic
Anthropic
                           
Gemini
Gemini
                           
xAI Grok
xAI Grok
                           
DeepSeek
DeepSeek
                           
Mistral
Mistral
                           
Qwen
Qwen
                           
Meta Llama
Meta Llama
                           
Groq
Groq
                           
NVIDIA
NVIDIA
                           
MiniMax
MiniMax
                           
Cohere
Cohere
                           
Perplexity
Perplexity
                           
Hugging Face
HuggingFace
                           
Together
Together
                           
Fireworks
Fireworks
                           
Cloudflare
Cloudflare
                           
Baidu
Baidu
                           
-OpenAI uses one format, Claude (Anthropic) uses another, Gemini yet another. If a dev wants to test models from different providers or fallback between them, they need to reconfigure SDKs, change endpoints, deal with incompatible formats. Custom providers (FriendLI, NIM) have non-standard model endpoints. +…ve 220+ fazlası — her simge panonun sağlayıcı kataloğundan canlı olarak çözümlenir. 📖 [Sağlayıcı Referansı](docs/reference/PROVIDER_REFERENCE.md) -**How OmniRoute solves it:** +
-- **Unified Endpoint** — A single `http://localhost:20128/v1` serves as proxy for all 329 provider catalog entries -- **Format Translation** — Automatic and transparent: OpenAI ↔ Claude ↔ Gemini ↔ Responses API -- **Response Sanitization** — Strips non-standard fields (`x_groq`, `usage_breakdown`, `service_tier`) that break OpenAI SDK v1.83+ -- **Role Normalization** — Converts `developer` → `system` for non-OpenAI providers; `system` → `user` for GLM/ERNIE -- **Think Tag Extraction** — Extracts `` blocks from models like DeepSeek R1 into standardized `reasoning_content` -- **Structured Output for Gemini** — `json_schema` → `responseMimeType`/`responseSchema` automatic conversion -- **`stream` defaults to `false`** — Aligns with OpenAI spec, avoiding unexpected SSE in Python/Rust/Go SDKs +### 🆓 Sonsuza Kadar Ücretsiz — $0, kart gerekmez -
+ + + + + + + + + + + + + + + + + +
OpenCode Zen
OpenCode Zen
DeepSeek V4, Nemotron 3
Token sınırı yok
Kilo Code
Kilo Code
Otomatik yönlendirici, Tencent Hy3
Sonsuza kadar ücretsiz
Requesty
Requesty
GPT-OSS 120B, Nemotron
Sonsuza kadar ücretsiz
SiliconFlow
SiliconFlow
DeepSeek V3.2 / R1
Ücretsiz katman
Z.AI GLM
Z.AI GLM
GLM-4.7 / 4.5-Flash
Sonsuza kadar ücretsiz
Baidu ERNIE
Baidu ERNIE
ERNIE 4.0
Sonsuza kadar ücretsiz
Qoder AI
Qoder AI
Qwen3-Max, Kimi-K2
Sınırsız ÜCRETSİZ
Pollinations
Pollinations
GPT, Llama, Claude
Anahtar gerekmez
Cloudflare AI
Cloudflare AI
50+ model
10K nöron/gün
NVIDIA NIM
NVIDIA NIM
GLM, MiniMax
~40 RPM ücretsiz
Cerebras
Cerebras
GLM 4.7, GPT-OSS
1M token/gün
OpenRouter
OpenRouter
:free modeller
+$10 → daha yüksek RPM
-
-🌐 3. "My AI provider blocks my region/country" +📖 Tam makine tarafından okunabilir katalog → [`docs/reference/PROVIDER_REFERENCE.md`](docs/reference/PROVIDER_REFERENCE.md) -Providers like OpenAI/Codex block access from certain geographic regions. Users get errors like `unsupported_country_region_territory` during OAuth and API connections. This is especially frustrating for developers from developing countries. +
+ -**How OmniRoute solves it:** +
-- **3-Level Proxy Config** — Configurable proxy at 3 levels: global (all traffic), per-provider (one provider only), and per-connection/key -- **Color-Coded Proxy Badges** — Visual indicators: 🟢 global proxy, 🟡 provider proxy, 🔵 connection proxy, always showing the IP -- **OAuth Token Exchange Through Proxy** — OAuth flow also goes through the proxy, solving `unsupported_country_region_territory` -- **Connection Tests via Proxy** — Connection tests use the configured proxy (no more direct bypass) -- **SOCKS5 Support** — Full SOCKS5 proxy support for outbound routing -- **TLS Fingerprint Spoofing** — Browser-like TLS fingerprint via `wreq-js` to bypass bot detection -- **🔏 CLI Fingerprint Matching** — Reorders headers and body fields to match native CLI binary signatures, drastically reducing account flagging risk. The proxy IP is preserved — you get both stealth **and** IP masking simultaneously +## 🖥️ OmniRoute Nerede Çalışır — Her Yerde -
+ -
-🆓 4. "I want to use AI for coding but I have no money" +> Aynı uygulama, sizin makineniz, sizin kurallarınız. Genel bir npm kurulumundan Termux ile **telefonunuza** kadar. -Not everyone can pay $20–200/month for AI subscriptions. Students, devs from emerging countries, hobbyists, and freelancers need access to quality models at zero cost. + + + + + + + + + + + +
PlatformKurulumÖne Çıkanlar
📦 npm (global)npm install -g omnirouteTek komut, tüm işletim sistemleri
🐳 Dockerdocker run … diegosouzapw/omnirouteÇoklu mimari AMD64 + ARM64
🖥️ Masaüstü (Electron)npm run electron:buildYerel pencere + sistem tepsisi — Windows / macOS / Linux
💪 ARMyerel arm64Raspberry Pi, ARM sunucuları, Apple Silicon
📱 Android (Termux)pkg install nodejs && npx -y omnirouteTelefonunuzda çalışır, 7/24, root gerekmez
📲 PWA"Ana Ekrana Ekle"Tam ekran, çevrimdışı, tarayıcıdan yüklenebilir
🧩 OpenCode eklentisi@omniroute/opencode-providerYerel OpenCode entegrasyonu
🤖 VS Code Copilot ChatOmniCopilot eklentisini kurunYerel Copilot Chat seçicisinde her OmniRoute modeli — stable ve Insiders
🛠️ Kaynak koddannpm install && npm run devGeliştirin, katkıda bulunun
-**How OmniRoute solves it:** +📖 [Docker Kılavuzu](docs/guides/DOCKER_GUIDE.md) · [Masaüstü](electron/README.md) · [Termux](docs/guides/TERMUX_GUIDE.md) · [PWA](docs/guides/PWA_GUIDE.md) · [OpenCode](docs/frameworks/OPENCODE.md) -- **Ollama Cloud** — Cloud-hosted Ollama models at `api.ollama.com` with free "Light usage" tier; use `ollamacloud/` prefix -- **Free-Only Combos** — Chain `if/kimi-k2-thinking → qw/qwen3-coder-plus` can use currently listed $0 access; limits and availability apply -- **NVIDIA NIM Free Access** — ~40 RPM free access as currently listed; provider terms and model availability apply at build.nvidia.com (transitioning from credits to pure rate limits) -- **Cost Optimized Strategy** — Routing strategy that automatically chooses the cheapest available provider +
-
+
-
-🔒 5. "I need to protect my AI gateway from unauthorized access" +### 🧩 Yeni: VS Code'un yerel Copilot Chat'i içinde OmniRoute -When exposing an AI gateway to the network (LAN, VPS, Docker), anyone with the address can consume the developer's tokens/quota. Without protection, APIs are vulnerable to misuse, prompt injection, and abuse. +
-**How OmniRoute solves it:** +> Yeni bir kenar çubuğu yok, yeni bir sohbet arayüzü yok — OmniRoute'un sunduğu her model doğrudan **zaten kullandığınız Copilot Chat model seçicisinde** görünür. VS Code 1.122'den bu yana, sağlayıcı modelleri GitHub oturumu veya Copilot aboneliği olmadan çalışır — ajan modu, araç çağırma ve vision, ücretsiz olarak. -- **API Key Management** — Generation, rotation, and scoping per provider with a dedicated `/dashboard/api-manager` page -- **Model-Level Permissions** — Restrict API keys to specific models (`openai/*`, wildcard patterns), with Allow All/Restrict toggle -- **API Endpoint Protection** — Require a key for `/v1/models` and block specific providers from the listing -- **Auth Guard + CSRF Protection** — All dashboard routes protected with `withAuth` middleware + CSRF tokens -- **Rate Limiter** — Per-IP rate limiting with configurable windows -- **IP Filtering** — Allowlist/blocklist for access control -- **Prompt Injection Guard** — Sanitization against malicious prompt patterns -- **AES-256-GCM Encryption** — Credentials encrypted at rest +**[OmniCopilot](https://github.com/diegosouzapw/OmniCopilot)** eklentisini kurun, OmniRoute sunucunuza yönlendirin (varsayılan: `localhost:20128`), ardından Copilot Chat → model seçici → **Modelleri Yönet… (Manage Models…)** → **OmniRoute** yolunu izleyin. - + + + + +
MağazaBağlantıŞunlarla çalışır
🧩 VS Code MarketplaceKurun →VS Code — stable ve Insiders
🔓 Open VSX RegistryKurun →Cursor, Windsurf, VSCodium, Theia, code-server, Gitpod, Antigravity, Kiro…
-
-🛑 6. "My provider went down and I lost my coding flow" +Düzenleyicinin içinden: **Uzantılar (Extensions)** görünümünü açın, **"OmniRoute"** araması yapın, **Kur (Install)** butonuna tıklayın — her iki mağazada da aynı şekilde çalışır. Kaynak kod, sorun bildirimleri ve yayınlama rehberi: [diegosouzapw/OmniCopilot](https://github.com/diegosouzapw/OmniCopilot). -AI providers can become unstable, return 5xx errors, or hit temporary rate limits. If a dev depends on a single provider, they're interrupted. Without circuit breakers, repeated retries can crash the application. +📖 [VS Code Copilot Chat kılavuzu](docs/guides/VSCODE-COPILOT.md) — kurulum, seçicinin gösterdikleri, sekmede pano, sorun giderme -**How OmniRoute solves it:** +
-- **Request Queue & Pacing** — Per-connection request buckets smooth bursts before they hit upstream rate caps -- **Connection Cooldown** — A single connection cools down after retryable failures with optional upstream `Retry-After` hints and exponential backoff -- **Provider Circuit Breaker** — The provider only trips after fallback is exhausted and the provider request still fails with provider-wide transient errors; connection-scoped `429` rate limits stay in Connection Cooldown -- **Wait For Cooldown** — The server can wait for the earliest connection cooldown to expire and retry the same client request automatically -- **Anti-Thundering Herd** — Mutex + semaphore protection against concurrent retry storms -- **Combo Fallback Chains** — If the primary provider fails, automatically falls through the chain with no intervention -- **Health Dashboard** — Uptime monitoring, provider circuit breaker states, cooldowns, cache stats, p50/p95/p99 latency +
-
+## 🔒 Gizli ve Önce Yerel (Local-First) -
-🔧 7. "Configuring each AI tool is tedious and repetitive" + -**How OmniRoute solves it:** +Private and local-first — your keys, your machine, your data; OmniRoute is a local proxy that never phones home. Eleven guarantees: runs 100% on your hardware (0 cloud hops), zero telemetry by default, credentials encrypted at rest (AES-256-GCM), no account or sign-up, hardened gateway (API-key scoping, IP filtering, rate limits, prompt-injection guard), loopback-only process routes, upstream header scrubbing, strictly opt-in PII redaction, sanitized errors that never leak internals, a local audit trail in your own SQLite, and MIT-licensed fully open-source code. -- **CLI Tools Dashboard** — Dedicated page with one-click setup for Claude Code, Codex CLI, OpenClaw, Kilo Code, Antigravity, Cline -- **GitHub Copilot Config Generator** — Generates `chatLanguageModels.json` for VS Code with bulk model selection -- **Onboarding Wizard** — Guided 4-step setup for first-time users -- **One endpoint, all models** — Configure `http://localhost:20128/v1` once, access 329 provider catalog entries +📖 [Yetkilendirme](docs/architecture/AUTHZ_GUIDE.md) · [Güvenlik Önlemleri](docs/security/GUARDRAILS.md) · [Uyumluluk](docs/security/COMPLIANCE.md) -
+
-
-🔑 8. "Managing OAuth tokens from multiple providers is hell" +
-Claude Code, Codex, Copilot — all use OAuth 2.0 with expiring tokens. Developers need to re-authenticate constantly, deal with `client_secret is missing`, `redirect_uri_mismatch`, and failures on remote servers. OAuth on LAN/VPS is particularly problematic. +## 🔌 Tam CLI + A2A ve MCP -**How OmniRoute solves it:** +
-- **Auto Token Refresh** — OAuth tokens refresh in background before expiration -- **OAuth 2.0 (PKCE) Built-in** — Automatic flow for Claude Code, Codex, Copilot, Kiro, Qwen, Qoder -- **Multi-Account OAuth** — Multiple accounts per provider via JWT/ID token extraction -- **OAuth LAN/Remote Fix** — Private IP detection for `redirect_uri` + manual URL mode for remote servers -- **OAuth Behind Nginx** — Uses `window.location.origin` for reverse proxy compatibility -- **Remote OAuth Guide** — Step-by-step guide for Google Cloud credentials on VPS/Docker +> Sunucunun ötesinde, OmniRoute **80'den fazla komuta** sahip **kapsamlı bir komut satırı kokpitidir**; ayrıca bir yapay zeka ajanının onu **kendi başına** yönetebilmesi için açık ajan protokollerine sahiptir. -
+### ⌨️ Gerçek bir CLI (yalnızca `start` değil) -
-📊 9. "I don't know how much I'm spending or where" +```bash +omniroute # ağ geçidini + panoyu sunar (port 20128) +omniroute chat # etkileşimli TUI sohbet istemcisi (komutlar: /model /combo /skill /memory) +omniroute setup # rehberli ilk çalıştırma sihirbazı +omniroute doctor # sağlayıcıları, portları ve yerel bağımlılıkları denetler +``` -Developers use multiple paid providers but have no unified view of spending. Each provider has its own billing dashboard, but there's no consolidated view. Unexpected costs can pile up. +### 🛰️ Uzak mod — CLI'ı burada, OmniRoute'u bir VPS üzerinde çalıştırın -**How OmniRoute solves it:** +OmniRoute bir sunucuda mı kurulu? Dizüstü bilgisayarınızdan **aynı CLI** ile yönetin. Kapsamlı bir erişim tokenıyla bir kez oturum açın; ardından her komut uzak sunucuyu hedefler. -- **Cost Analytics Dashboard** — Per-token cost tracking and budget management per provider -- **Budget Limits per Tier** — Spending ceiling per tier that triggers automatic fallback -- **Per-Model Pricing Configuration** — Configurable prices per model -- **Usage Statistics Per API Key** — Request count and last-used timestamp per key -- **Analytics Dashboard** — Stat cards, model usage chart, provider table with success rates and latency +```bash +omniroute connect 192.168.0.15 # şifre → kapsamlı token, bağlam olarak kaydedilir +omniroute models list # ← UZAK sunucuya karşı çalışır +omniroute configure codex # ← uzak bir model seçer, yerel bir Codex profili yazar +omniroute tokens create --name ci --scope read # diğer makineler için daha dar kapsamlı tokenlar üretir +omniroute contexts use default # ← yerel sunucuya geri döner +``` -
+Tokenlar `read` / `write` / `admin` kapsamlarına sahiptir; süreç başlatan rotalar yalnızca yerel döngüde (loopback) kalır. +📖 [Uzak Mod](docs/guides/REMOTE-MODE.md) -
-🐛 10. "I can't diagnose errors and problems in AI calls" +
-When a call fails, the dev doesn't know if it was a rate limit, expired token, wrong format, or provider error. Fragmented logs across different terminals. Without observability, debugging is trial-and-error. +Animated terminal demoing the OmniRoute CLI — omniroute providers list, omniroute combo list, omniroute health — cycling over the 80+ command surface: providers · oauth · keys · combo · nodes · models · cache · compression · cost · usage · quota · health · resilience · telemetry · logs · audit · mcp · a2a · cloud · memory · skills · eval · tunnel · backup · sync · webhooks · policy · pricing · translator · simulate … -**How OmniRoute solves it:** +
-- **Unified Logs Dashboard** — 4 tabs: Request Logs, Proxy Logs, Audit Logs, Console -- **Console Log Viewer** — Real-time terminal-style viewer with color-coded levels, auto-scroll, search, filter -- **SQLite Summary Logs** — Request and proxy log indexes stay queryable across restarts without loading large payload blobs into SQLite -- **Translator Playground** — 4 debugging modes: Playground (format translation), Chat Tester (round-trip), Test Bench (batch), Live Monitor (real-time) -- **Request Telemetry** — p50/p95/p99 latency + X-Request-Id tracing -- **File-Based Detail Artifacts** — App logs rotate by size, retention days, and archive count; detailed request/response payloads live in `DATA_DIR/call_logs/` and rotate independently of SQLite summaries -- **System Info Report** — `npm run system-info` generates `system-info.txt` with your full environment (Node version, OmniRoute version, OS, CLI tools, Docker/PM2 status). Attach it when reporting issues for instant triage. +### 🤝 Bir ajan bağlayın — ve OmniRoute'un kendisini yönetsin -
+OmniRoute'u **MCP**, **A2A**, bir **REST API**, **webhook'lar** veya bir **uzak CLI** üzerinden kullanıma açın — yetenekli herhangi bir ajan (veya kendi kodunuz) tüm ağ geçidinin anahtarlarını alır: yönlendirme, sağlayıcılar, kombolar, önbellek, sıkıştırma, bellek — tamamen özerk bir şekilde. Aşağıdaki HTTP uç noktaları `http://localhost:20128` altında sunulur. -
-🏗️ 11. "Deploying and maintaining the gateway is complex" + + + + + + + + + +
ArayüzUç nokta / komutKullanım amacı
🧰 MCP (stdio)omniroute --mcpClaude Desktop, Cursor veya herhangi bir MCP istemcisine bağlayın
🌊 MCP (HTTP)/api/mcp/streamUzak MCP — 110 araç, 33 kapsam, eksiksiz denetim kaydı
📡 MCP (SSE)/api/mcp/sseAkışlı MCP taşıması
🤝 A2A/.well-known/agent.jsonAjandan ajana (Agent-to-agent), JSON-RPC 2.0 + SSE, 6 yetenek
🌐 REST API/v1/*OpenAI uyumlu — sohbet, embeddings, görseller, ses, OCR
🔔 Webhook'lar/api/webhooksOlayları (kullanım, kota, hatalar, yönlendirme) URL'nize iletin
🛰️ Uzak CLIomniroute connect <host>Kapsamlı erişim tokenlarıyla uzak bir örneği yönetin
-Installing, configuring, and maintaining an AI proxy across different environments (local, VPS, Docker, cloud) is labor-intensive. Problems like hardcoded paths, `EACCES` on directories, port conflicts, and cross-platform builds add friction. +```bash +# Claude Code'a MCP üzerinden tam OmniRoute araç setini verin: +claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp/stream +``` -**How OmniRoute solves it:** +📖 [MCP Sunucusu](docs/frameworks/MCP-SERVER.md) · [A2A Sunucusu](docs/frameworks/A2A-SERVER.md) · [Ajan Protokolleri](docs/frameworks/AGENT_PROTOCOLS_GUIDE.md) -- **npm global install** — `npm install -g omniroute && omniroute` — done -- **Docker Multi-Platform** — AMD64 + ARM64 native (Apple Silicon, AWS Graviton, Raspberry Pi) -- **Docker Compose Profiles** — `base` (no CLI tools) and `cli` (with Claude Code, Codex, OpenClaw) -- **Electron Desktop App** — Native app for Windows/macOS/Linux with system tray, auto-start, offline mode -- **Split-Port Mode** — API and Dashboard on separate ports for advanced scenarios (reverse proxy, container networking) -- **Cloud Sync** — Config synchronization across devices via Cloudflare Workers -- **DB Backups** — Automatic backup, restore, export and import of all settings, with `DISABLE_SQLITE_AUTO_BACKUP` for externally managed backups +
-
+
-
-🌍 12. "The interface is English-only and my team doesn't speak English" +## 🗜️ Tokenlardan %15–95 Tasarruf Edin — Otomatik Olarak -Teams in non-English-speaking countries, especially in Latin America, Asia, and Europe, struggle with English-only interfaces. Language barriers reduce adoption and increase configuration errors. +
-**How OmniRoute solves it:** +### 📖 Nasıl çalışır — işlem hattı, mimari ve tasarruf matematiği -- **Dashboard i18n — 30 Languages** — All 500+ keys translated including Arabic, Bulgarian, Danish, German, Spanish, Finnish, French, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Malay, Dutch, Norwegian, Polish, Portuguese (PT/BR), Romanian, Russian, Slovak, Swedish, Thai, Ukrainian, Vietnamese, Chinese, Filipino, English -- **RTL Support** — Right-to-left support for Arabic and Hebrew -- **Multi-Language READMEs** — 30 complete documentation translations -- **Language Selector** — Globe icon in header for real-time switching +OmniRoute compression pipeline: a client request of 10,000 tokens passes through 12 stacked engines — Session-Dedup, CCR, Lite, RTK, Responses Tool Output, Headroom, Relevance, Caveman, Aggressive, LLMLingua-2, Ultra, OmniGlyph — and reaches the provider at about 1,080 tokens, up to 95% saved. Code, URLs and JSON are always preserved byte-perfect. - - -
-🔄 13. "I need more than chat — I need embeddings, images, audio" - -AI isn't just chat completion. Devs need to generate images, transcribe audio, create embeddings for RAG, rerank documents, and moderate content. Each API has a different endpoint and format. - -**How OmniRoute solves it:** - -- **Embeddings** — `/v1/embeddings` with 6 providers and 9+ models -- **Image Generation** — `/v1/images/generations` with 10 providers and 20+ models (OpenAI, xAI, Together, Fireworks, Nebius, Hyperbolic, NanoBanana, Antigravity, SD WebUI, ComfyUI) -- **Text-to-Video** — `/v1/videos/generations` — ComfyUI (AnimateDiff, SVD) and SD WebUI -- **Text-to-Music** — `/v1/music/generations` — ComfyUI (Stable Audio Open, MusicGen) -- **Audio Transcription** — `/v1/audio/transcriptions` — Whisper + Nvidia NIM, HuggingFace, Qwen3 -- **Text-to-Speech** — `/v1/audio/speech` — ElevenLabs, Nvidia NIM, HuggingFace, Coqui, Tortoise, Qwen3, **Inworld**, **Cartesia**, **PlayHT**, + existing providers -- **Moderations** — `/v1/moderations` — Content safety checks -- **Reranking** — `/v1/rerank` — Document relevance reranking -- **Responses API** — Full `/v1/responses` support for Codex - -
- -
-🧪 14. "I have no way to test and compare quality across models" - -Developers want to know which model is best for their use case — code, translation, reasoning — but comparing manually is slow. No integrated eval tools exist. - -**How OmniRoute solves it:** - -- **LLM Evaluations** — Golden set testing with 10 pre-loaded cases covering greetings, math, geography, code generation, JSON compliance, translation, markdown, safety refusal -- **4 Match Strategies** — `exact`, `contains`, `regex`, `custom` (JS function) -- **Translator Playground Test Bench** — Batch testing with multiple inputs and expected outputs, cross-provider comparison -- **Chat Tester** — Full round-trip with visual response rendering -- **Live Monitor** — Real-time stream of all requests flowing through the proxy - -
- -
-📈 15. "I need to scale without losing performance" - -As request volume grows, without caching the same questions generate duplicate costs. Without idempotency, duplicate requests waste processing. Per-provider rate limits must be respected. - -**How OmniRoute solves it:** - -- **Semantic Cache** — Two-tier cache (signature + semantic) reduces cost and latency -- **Request Idempotency** — 5s deduplication window for identical requests -- **Rate Limit Detection** — Per-provider RPM, min gap, and max concurrent tracking -- **Request Queue & Pacing** — Configurable queue, pacing, and concurrency defaults in Settings → Resilience -- **API Key Validation Cache** — 3-tier cache for production performance -- **Health Dashboard with Telemetry** — p50/p95/p99 latency, cache stats, uptime - -
- -
-🤖 16. "I want to control model behavior globally" - -Developers who want all responses in a specific language, with a specific tone, or want to limit reasoning tokens. Configuring this in every tool/request is impractical. - -**How OmniRoute solves it:** - -- **System Prompt Injection** — Global prompt applied to all requests -- **Thinking Budget Validation** — Reasoning token allocation control per request (passthrough, auto, custom, adaptive) -- **9 Routing Strategies** — Global strategies that determine how requests are distributed -- **Wildcard Router** — `provider/*` patterns route dynamically to any provider -- **Combo Enable/Disable Toggle** — Toggle combos directly from the dashboard -- **Manual Combo Ordering** — Drag combo cards by handle and persist the order in SQLite -- **Provider Toggle** — Enable/disable all connections for a provider with one click -- **Blocked Providers** — Exclude specific providers from `/v1/models` listing - -
- -
-🧰 17. "I need MCP tools as first-class product capabilities" - -Many AI gateways expose MCP only as a hidden implementation detail. Teams need a visible, manageable operation layer. - -**How OmniRoute solves it:** - -- MCP appears in the dashboard navigation and endpoint protocol tab -- Dedicated MCP management page with process, tools, scopes, and audit -- Built-in quick-start for `omniroute --mcp` and client onboarding - -
- -
-🧠 18. "I need A2A orchestration with sync + stream task paths" - -Agent workflows need both direct replies and long-running streamed execution with lifecycle control. - -**How OmniRoute solves it:** - -- A2A JSON-RPC endpoint (`POST /a2a`) with `message/send` and `message/stream` -- SSE streaming with terminal state propagation -- Task lifecycle APIs for `tasks/get` and `tasks/cancel` - -
- -
-🛰️ 19. "I need real MCP process health, not guessed status" - -Operational teams need to know if MCP is actually alive, not just whether an API is reachable. - -**How OmniRoute solves it:** - -- Runtime heartbeat file with PID, timestamps, transport, tool count, and scope mode -- MCP status API combining heartbeat + recent activity -- UI status cards for process/uptime/heartbeat freshness - -
- -
-📋 20. "I need auditable MCP tool execution" - -When tools mutate config or trigger ops actions, teams need forensic traceability. - -**How OmniRoute solves it:** - -- SQLite-backed audit logging for MCP tool calls -- Filters by tool, success/failure, API key, and pagination -- Dashboard audit table + stats endpoints for automation - -
- -
-🔐 21. "I need scoped MCP permissions per integration" - -Different clients should have least-privilege access to tool categories. - -**How OmniRoute solves it:** - -- 32 granular MCP scopes for controlled tool access -- Scope enforcement and visibility in MCP management UI -- Safe default posture for operational tooling - -
- -
-⚙️ 22. "I need operational controls without redeploying" - -Teams need quick runtime changes during incidents or cost events. - -**How OmniRoute solves it:** - -- Switch combo activation directly from MCP dashboard -- Tune queue, cooldown, breaker, and wait settings from the dedicated Resilience page -- Review live provider breaker state from the Health dashboard - -
- -
-🔄 23. "I need live A2A task lifecycle visibility and cancellation" - -Without lifecycle visibility, task incidents become hard to triage. - -**How OmniRoute solves it:** - -- Task listing/filtering by state/skill with pagination -- Drill-down on task metadata, events, and artifacts -- Task cancellation endpoint and UI action with confirmation - -
- -
-🌊 24. "I need active stream metrics for A2A load" - -Streaming workflows require operational insight into concurrency and live connections. - -**How OmniRoute solves it:** - -- Active stream counters integrated into A2A status -- Last task timestamp and per-state counts -- A2A dashboard cards for real-time ops monitoring - -
- -
-🪪 25. "I need standard agent discovery for clients" - -External clients and orchestrators need machine-readable metadata for onboarding. - -**How OmniRoute solves it:** - -- Agent Card exposed at `/.well-known/agent.json` -- Capabilities and skills shown in management UI -- A2A status API includes discovery metadata for automation - -
- -
-🧭 26. "I need protocol discoverability in the product UX" - -If users cannot discover protocol surfaces, adoption and support quality drop. - -**How OmniRoute solves it:** - -- Consolidated **Endpoints** page with tabs for Proxy, MCP, A2A, and API Endpoints -- Inline service status toggles (Online/Offline) for MCP and A2A -- Links from overview to dedicated management tabs - -
- -
-🧪 27. "I need end-to-end protocol validation with real clients" - -Mock tests are not enough to validate protocol compatibility before release. - -**How OmniRoute solves it:** - -- E2E suite that boots app and uses real MCP SDK client transport -- A2A client tests for discovery, send, stream, get, and cancel flows -- Cross-check assertions against MCP audit and A2A tasks APIs - -
- -
-📡 28. "I need unified observability across all interfaces" - -Splitting observability by protocol creates blind spots and longer MTTR. - -**How OmniRoute solves it:** - -- Unified dashboards/logs/analytics in one product -- Health + audit + request telemetry across OpenAI, MCP, and A2A layers -- Operational APIs for status and automation - -
- -
-💼 29. "I need one runtime for proxy + tools + agent orchestration" - -Running many separate services increases operational cost and failure modes. - -**How OmniRoute solves it:** - -- OpenAI-compatible proxy, MCP server, and A2A server in one stack -- Shared auth, resilience, data store, and observability -- Consistent policy model across all interaction surfaces - -
- -
-🚀 30. "I need to ship agentic workflows without glue-code sprawl" - -Teams lose velocity when stitching multiple ad-hoc services and scripts. - -**How OmniRoute solves it:** - -- Unified endpoint strategy for clients and agents -- Built-in protocol management UIs and smoke validation paths -- Production-ready foundations (security, logging, resilience, backup) - -
- -
-📚 31. "My long sessions crash with 'context_length_exceeded' limits" - -During deep debugging, long histories with tool results quickly exceed provider token windows, causing failed requests and orphaned context. - -**How OmniRoute solves it:** - -- **Proactive Context Compression** — Evaluates token budgets before the request hits upstream and proactively prunes old conversation history with a smart binary-search mechanism. -- **Structural Integrity Guards** — Automatically tracks explicit `tool_use` definitions and ensures that if a tool input is truncated, its corresponding `tool_result` is also safely removed, preventing API validation errors. -- **Multi-Layer Dropping** — Progressively drops system messages, regular messages, and finally enforces strict length limits without breaking conversational logic. - -
- -### Example Playbooks (Integrated Use Cases) - -**Playbook A: Maximize paid subscription + cheap backup** +Varsayılan katmanlı kombo `RTK → Caveman` çalıştırır. Her ikisi de aynı araç/bağlam yükü üzerinde çalıştığında, tasarruflar katlanarak artar: ```txt -Combo: "maximize-claude" - 1. cc/claude-opus-4-7 - 2. glm/glm-4.7 - 3. if/kimi-k2-thinking - -Monthly cost: $20 + small backup spend -Outcome: higher quality, near-zero interruption +combined = 1 − (1 − RTK) × (1 − Caveman_input) +average = 1 − (1 − 0.80) × (1 − 0.46) = 89.2% +range = 78.4 – 94.6% ``` -**Playbook B: Zero-cost coding stack** +Kod blokları, URL'ler, JSON ve yapılandırılmış veriler koruma motoru tarafından **her zaman korunur**. -```txt -Combo: "free-access" - 1. if/kimi-k2-thinking (no published token cap; limits apply) - 2. qw/qwen3-coder-plus (no published token cap; limits apply) +> **Az token işi görüyorsa neden çok token kullanasınız?** Her istek OmniRoute'un sıkıştırma hattından **şeffaf bir şekilde** geçer — istemci değişikliği gerekmez. Artık sırayla çalışan ve yönlendirme kombosu başına karıştırılıp eşleştirilebilen **12 birleştirilebilir motordan oluşan bir yığındır** — [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 90K+), [LLMLingua-2](https://github.com/microsoft/LLMLingua) ve [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR) fikirleri üzerine inşa edilmiştir. -Monthly cost: $0 -Outcome: broader free-access fallback; upstream availability is not guaranteed -``` +### 🧱 12 motorlu sıkıştırma yığını -**Playbook C: 24/7 always-on fallback chain** +Motorlar işlem hattı sırasına göre çalışır; her biri bağımsız olarak açılıp kapatılabilir ve kombo başına yapılandırılabilir: -```txt -Combo: "multi-layer-fallback" - 1. cc/claude-opus-4-7 - 2. cx/gpt-5.2-codex - 3. glm/glm-4.7 - 4. minimax/MiniMax-M2.1 - 5. if/kimi-k2-thinking + + + + + + + + + + + + + + +
#MotorNe yapar
1Session-DedupTurlar arasında tekrarlanan içerikleri çıkarır (içerik adresli, turlar arası)
2CCRBüyük blokları geri getirme işaretçilerinin arkasında arşivler, talep üzerine getirir
3LiteBoşluklar + görsel URL'lerini kırpar (düşük gecikmeli temel hat)
4RTKAkıllı araç sonucu filtreleme, tekilleştirme ve kırpma (komut duyarlı)
5Responses Tool OutputKabuk/yama/arama/derleme çıktıları için kayıpsız öncelikli JSON + sınırlı tanısal sıkıştırma (Responses API)
6HeadroomYerleşik bir GCF codec'i aracılığıyla JSON dizilerinin kayıpsız tablosal sıkıştırılması (~%30)
7RelevanceSon kullanıcı sorgusuna göre çıkarımsal cümle puanlaması
8CavemanKural tabanlı düz yazı sıkıştırması (çıktıda ~%65–75)
9AggressiveÖzetleme + eski turların kademeli yaşlandırılması
10LLMLingua-2MobileBERT ONNX aracılığıyla ML anlamsal budama — kod güvenli, asenkron
11Ultraİsteğe bağlı küçük model (SLM) katmanıyla sezgisel token budama
12OmniGlyphDoğrudan Anthropic kablosu üzerindeki ölçülen Claude Fable 5 için deneysel görüntü olarak bağlam kodlama; GPT 5.6 dönüştürücüleri sağlayıcı dekontları beklenirken kapalı kalır. Dört sıkıştırma profili (agresif varsayılan, dengeli, kodlama güvenli, doğrudan geçiş) (en agresif; isteğe bağlı)
-Outcome: deep fallback depth for deadline-critical workloads -``` +Kod blokları, URL'ler ve yapılandırılmış veriler **her zaman byte düzeyinde kusursuz korunur**. **Tek tıkla hazır önayarlar** motorları birleştirir: -**Playbook D: Agent ops with MCP + A2A** + + + + + + + + +
ModTasarrufEn uygun kullanım
🪶 Lite~%15Her zaman açık güvenli varsayılan
🪨 Standard (Caveman)~%30Günlük kodlama
Aggressive~%50Uzun araç yoğun oturumlar
🔥 Ultra~%75Maksimum tasarruf
🧰 RTK%60–90Kabuk/test/derleme/git çıktısı
🔗 Katmanlı (RTK → Caveman)%78–95Karışık istemler + araç günlükleri
-```txt -1) Start MCP transport (`omniroute --mcp`) for tool-driven operations -2) Run A2A tasks via `message/send` and `message/stream` -3) Observe via /dashboard/endpoint (MCP and A2A tabs) -4) Toggle services via inline status controls -``` +**Gerçek örnek — Standard mod:** ---- +> **Önce (69 token):** _"The reason your React component is re-rendering is likely because you're creating a new object reference on each render cycle. When you pass an inline object as a prop, React's shallow comparison sees it as a different object every time, which triggers a re-render. I would recommend using useMemo to memoize the object."_ +> +> **Sonra (19 token):** _"New object ref each render. Inline object prop = new ref = re-render. Wrap in useMemo."_ +> +> **Aynı yanıt. %72 daha az token. Sıfır doğruluk kaybı.** ✅ -## 🆓 Start Free — Zero Configuration Cost +**PT-BR örneği — [Troglodita](https://github.com/leninejunior/troglodita) modu:** -> Setup AI coding in minutes at **$0/month**. Connect these free accounts and use the built-in **Free Stack** combo. +> **Antes (42 tokens):** _"O problema é que o componente está re-renderizando porque uma nova referência de objeto está sendo criada em cada ciclo de renderização. Eu recomendaria usar useMemo."_ +> +> **Depois (12 tokens):** _"Re-render: ref nova cada ciclo (objeto inline recriado). Usar `useMemo`."_ +> +> **Mesma resposta. ~70% menos tokens. Precisão técnica intacta.** ✅ -| Step | Action | Providers Unlocked | -| ---- | -------------------------------------------------- | ------------------------------------------------------------------ | -| 1 | Connect **Kiro** (AWS Builder ID OAuth) | Claude Sonnet 4.5, Haiku 4.5 — provider/account limits apply | -| 2 | Connect **Qoder** (Google OAuth) | kimi-k2-thinking, qwen3-coder-plus, deepseek-r1... — provider/account limits apply | -| 3 | Connect **Qwen** (Device Code) | qwen3-coder-plus, qwen3-coder-flash... — provider/account limits apply | -| 4 | `/dashboard/combos` → **Free Stack ($0)** template | Round-robin all free providers automatically | +
-**Point any IDE/CLI to:** `http://localhost:20128/v1` · API Key: `any-string` · Done. +### 🎚️ Motorların ötesinde — çıktı stilleri, uyarlanabilir kadran ve istek başına kontrol -> **Optional extra coverage (current terms apply):** Groq, NVIDIA NIM, Cerebras, LongCat and Cloudflare Workers AI can provide free access or signup credits where currently listed. Quotas, models, accounts, regions and provider terms can change; see [`FREE_TIERS.md`](../../reference/FREE_TIERS.md). +Yukarıdaki 12 motor içeri giren metni küçültür. Üç ek katman ise **nasıl**, **ne zaman** ve **neyin** çıkacağını şekillendirir: -## Hızlı Başlangıç +- **🪄 Çıktı Stilleri** _(çıktı ekseninde yönlendirme)_ — deterministik, önbellek güvenli yanıt şekillendirme talimatları enjekte eder; birleştirilebilir, her biri `lite` / `full` / `ultra` yoğunluğundadır. Yeni bir stil eklemek tek satırlık bir kayıt işlemidir: + - **Terse prose** — dolgu sözcükleri / makaleleri / tereddütlü ifadeleri çıkarır; teknik içeriği eksiksiz korur. + - **Less code** — "tembel kıdemli geliştirici" YAGNI yaklaşımı: istenmeyen iskele kodları olmadan çalışan en küçük değişiklik. + - **Terse CJK (文言)** — klasik Çince ultra kısa stil (`zh` diline kilitli). +- **🎯 Uyarlanabilir bağlam bütçesi** _(kadran)_ — tek bir açık/kapalı token eşiği yerine, yalnızca **modelin bağlam penceresine sığması için** gereken en ucuz ve en kayıpsız motorları kademeli olarak devreye sokar. İlke: `reserve-output` (varsayılan, model duyarlı) · `percentage` · `absolute`. Mod: `floor` (uyumu garanti eder) · `replace-autotrigger` (açık seçiminiz kazanır) · `off` (eski eşik). +- **🎛️ Sıkıştırmaya nerede karar verilir** _(öncelik sırası, yüksekten düşüğe)_ — istek başına `x-omniroute-compression` başlığı › yönlendirme kombosu geçersiz kılma › aktif adlandırılmış profil › uyarlanabilir / otomatik tetikleyici › panel varsayılanı › kapalı. Uygulanan plan `X-OmniRoute-Compression: ; source=` yanıt başlığında geri döndürülür. -### 1) Install and run +Token eşiğine göre otomatik tetikleyin, uyarlanabilir kadranı açın, adlandırılmış bir profil sabitleyin, istek başına tek seferlik ayarlayın veya yönlendirme kombosu başına bir işlem hattı atayın — iş yüküne hangisi uyuyorsa. İsteğe bağlı bir çevrimdışı **değerlendirme aracı** (`npm run eval:compression`), bir değişikliği yayımlamadan önce sabit bir külliyat üzerinde doğruluk ile tasarrufu puanlar. + +📖 [`COMPRESSION_GUIDE.md`](docs/compression/COMPRESSION_GUIDE.md) · [`RTK_COMPRESSION.md`](docs/compression/RTK_COMPRESSION.md) · [`COMPRESSION_ENGINES.md`](docs/compression/COMPRESSION_ENGINES.md) + +
+ +
+ +# ⚡ Hızlı Başlangıç + +
+ +**1) Kurun ve çalıştırın** ```bash npm install -g omniroute omniroute ``` -> **pnpm users:** Pass `--allow-build` at install time to enable native build scripts required by `better-sqlite3` and `@swc/core` (the `approve-builds -g` command is not supported for global installs on pnpm v11): -> -> ```bash -> pnpm add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core -> omniroute -> ``` +> 💡 `npm warn ERESOLVE` veya eş bağımlılık (peer-dep) uyarıları mı görüyorsunuz? [Zararsızdırlar](docs/guides/TROUBLESHOOTING.md#npm-install-warnings-eresolve--peer--deprecated). -Dashboard opens at `http://localhost:20128` and API base URL is `http://localhost:20128/v1`. +Pano: `http://localhost:20128` · API: `http://localhost:20128/v1`. -#### Arch Linux (AUR) +**2) ÜCRETSİZ bir sağlayıcı bağlayın (kayıt gerekmez)** -Arch Linux users can install the [AUR package](https://aur.archlinux.org/packages/omniroute-bin), which installs OmniRoute and provides a systemd user service: +Pano → **Sağlayıcılar (Providers)** → **Kiro AI** (ücretsiz Claude, hesap başına aylık ~50 kredi) veya **OpenCode Free** (kimlik doğrulama yok) bağlayın → tamamlandı. -```bash -yay -S omniroute-bin -systemctl --user enable --now omniroute.service -``` - -| Command | Description | -| ----------------------- | ----------------------------------------------------------- | -| `omniroute` | Start server (`PORT=20128`, API and dashboard on same port) | -| `omniroute --port 3000` | Set canonical/API port to 3000 | -| `omniroute --mcp` | Start MCP server (stdio transport) | -| `omniroute --no-open` | Don't auto-open browser | -| `omniroute --help` | Show help | - -Optional split-port mode: - -```bash -PORT=20128 DASHBOARD_PORT=20129 omniroute -# API: http://localhost:20128/v1 -# Dashboard: http://localhost:20129 -``` - -### 2) Uninstalling - -When you no longer need OmniRoute, we provide two quick scripts for a clean removal: - -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | - -> Note: To run these commands, navigate to the OmniRoute project folder (if you cloned it) and run them. Alternatively, if globally installed, you can simply run `npm uninstall -g omniroute`. - -### Long-Running Streaming Timeouts - -For most deployments, you only need: - -| Variable | Default | Purpose | -| ------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| `REQUEST_TIMEOUT_MS` | `600000` | Shared baseline for upstream response-start timeout, hidden Undici timeouts, TLS fingerprint requests, and API bridge request/proxy timeouts | -| `STREAM_IDLE_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Maximum gap between streaming chunks before OmniRoute aborts the SSE stream | - -Backward compatibility is preserved: existing `FETCH_TIMEOUT_MS`, `API_BRIDGE_PROXY_TIMEOUT_MS`, and other per-layer timeout vars still work and override the shared baseline. - -For Claude Code-compatible upstreams (`anthropic-compatible-cc-*`), OmniRoute also derives the outbound `X-Stainless-Timeout` header from the resolved fetch timeout so provider-side read timeouts stay aligned with your env configuration. - -For third-party Claude Code-compatible reverse proxies, OmniRoute keeps the default -`anthropic-beta` set conservative and, when `Client Cache Control` is left on `Auto`, -only forwards client-provided `cache_control` markers. If the request does not include -`cache_control`, OmniRoute does not inject bridge-owned markers. - -Advanced overrides are available if you need finer control: - -| Variable | Default | Purpose | -| ---------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------- | -| `FETCH_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` | Upstream response-start timeout used until response headers arrive | -| `FETCH_HEADERS_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit for receiving upstream response headers | -| `FETCH_BODY_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Undici time limit between upstream body chunks (`0` disables it) | -| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | Undici TCP connect timeout | -| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Undici idle keep-alive socket timeout | -| `TLS_CLIENT_TIMEOUT_MS` | inherits `FETCH_TIMEOUT_MS` | Timeout for TLS fingerprint requests made through `wreq-js` | -| `API_BRIDGE_PROXY_TIMEOUT_MS` | inherits `REQUEST_TIMEOUT_MS` or `600000` | Timeout for `/v1` proxy forwarding from API port to dashboard port | -| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `max(API_BRIDGE_PROXY_TIMEOUT_MS, 300000)` | Incoming request timeout on the API bridge server | -| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Incoming header timeout on the API bridge server | -| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Keep-alive timeout on the API bridge server | -| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Socket inactivity timeout on the API bridge server (`0` disables it) | - -For streaming requests, `FETCH_TIMEOUT_MS` only covers connection setup / waiting for the first upstream response. Once the stream is active, OmniRoute will only abort on an actual stall (`STREAM_IDLE_TIMEOUT_MS`) or Undici body inactivity (`FETCH_BODY_TIMEOUT_MS`). - -If you run OmniRoute behind Nginx, Caddy, Cloudflare, or another reverse proxy, make sure the proxy -timeouts are also higher than your OmniRoute stream/fetch timeouts. - -### 2) Connect providers and create your API key - -1. Open Dashboard → `Providers` and connect at least one provider (OAuth or API key). -2. Open Dashboard → `Endpoints` and create an API key. -3. (Optional) Open Dashboard → `Combos` and set your fallback chain. - -### 3) Point your coding tool to OmniRoute +**3) Kodlama aracınızı yönlendirin** ```txt Base URL: http://localhost:20128/v1 -API Key: [copy from Endpoint page] -Model: if/kimi-k2-thinking (or any provider/model prefix) +API Key: [Pano → Uç Noktalar sayfasından kopyalayın] +Model: auto (sıfır yapılandırmalı akıllı yönlendirme — veya herhangi bir sağlayıcı/model) ``` -### 4) Enable and validate protocols (v2.0) - -**MCP (for tool-driven operations):** +**4) Çalıştığını doğrulayın** ```bash -omniroute --mcp +curl http://localhost:20128/v1/models -H "Authorization: Bearer YOUR_KEY" ``` -Then connect your MCP client over `stdio` and test tools like: +Bağlı modellerinizi listelenmiş olarak görmelisiniz. 🎉 İşte bu kadar — kodlamaya başlayın, gerisini OmniRoute otomatik yönlendirsin ve gerektiğinde diğerine geçsin. -- `omniroute_get_health` -- `omniroute_list_combos` - -**A2A (for agent-to-agent workflows):** - -```bash -curl http://localhost:20128/.well-known/agent.json -``` - -```bash -curl -X POST http://localhost:20128/a2a \ - -H 'content-type: application/json' \ - -d '{"jsonrpc":"2.0","id":"quickstart","method":"message/send","params":{"skill":"quota-management","messages":[{"role":"user","content":"Give me a short quota summary."}]}}' -``` - -### 5) Validate everything end-to-end (recommended) - -```bash -npm run test:protocols:e2e -``` - -This suite validates real MCP and A2A client flows against a running app. - -### Alternative: run from source - -```bash -cp .env.example .env -npm install -PORT=20128 DASHBOARD_PORT=20129 NEXT_PUBLIC_BASE_URL=http://localhost:20129 npm run dev -``` - -
-Void Linux (`xbps-src` template) - -For Void Linux users, you can build a native package using `xbps-src`. Save this block as `srcpkgs/omniroute/template`: - -```bash -# Template file for 'omniroute' -pkgname=omniroute -version=3.4.1 -revision=1 -hostmakedepends="nodejs python3 make" -depends="openssl" -short_desc="Universal AI gateway with smart routing for multiple LLM providers" -maintainer="zenobit " -license="MIT" -homepage="https://github.com/diegosouzapw/OmniRoute" -distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz" -checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b -system_accounts="_omniroute" -omniroute_homedir="/var/lib/omniroute" -export NODE_ENV=production -export npm_config_engine_strict=false -export npm_config_loglevel=error -export npm_config_fund=false -export npm_config_audit=false - -do_build() { - # Determine target CPU arch for node-gyp - local _gyp_arch - case "$XBPS_TARGET_MACHINE" in - aarch64*) _gyp_arch=arm64 ;; - armv7*|armv6*) _gyp_arch=arm ;; - i686*) _gyp_arch=ia32 ;; - *) _gyp_arch=x64 ;; - esac - - # 1) Install all deps – skip scripts (no network in do_build, native modules - # compiled separately below; better-sqlite3 is serverExternalPackage so - # Next.js does not execute it during next build) - NODE_ENV=development npm ci --ignore-scripts - - # 2) Build the Next.js standalone bundle - npm run build - - # 3) Copy static assets into standalone - cp -r .next/static .next/standalone/.next/static - [ -d public ] && cp -r public .next/standalone/public || true - - # 4) Compile better-sqlite3 native binding for the target architecture. - # Use node-gyp directly so CC/CXX from xbps-src cross-toolchain are used - # without npm altering them. - local _node_gyp=/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js - (cd node_modules/better-sqlite3 && node "$_node_gyp" rebuild --arch="$_gyp_arch") - - # 5) Place the compiled binding into the standalone bundle - local _bs3_release=.next/standalone/node_modules/better-sqlite3/build/Release - mkdir -p "$_bs3_release" - cp node_modules/better-sqlite3/build/Release/better_sqlite3.node "$_bs3_release/" - - # 6) Remove arch-specific sharp bundles – upstream sets images.unoptimized=true - # so sharp is not used at runtime; x64 .so files would break aarch64 strip - rm -rf .next/standalone/node_modules/@img - - # 7) Copy pino runtime deps omitted by Next.js static analysis: - # pino-abstract-transport – required by pino's worker thread - # split2 – dep of pino-abstract-transport - # process-warning – dep of pino itself - for _mod in pino-abstract-transport split2 process-warning; do - cp -r "node_modules/$_mod" .next/standalone/node_modules/ - done -} - -do_check() { - npm run test:unit -} - -do_install() { - vmkdir usr/lib/omniroute/.next - - vcopy .next/standalone/. usr/lib/omniroute/.next/standalone - - # Prevent removal of empty Next.js app router dirs by the post-install hook - for _d in \ - .next/standalone/.next/server/app/dashboard \ - .next/standalone/.next/server/app/dashboard/settings \ - .next/standalone/.next/server/app/dashboard/providers; do - touch "${DESTDIR}/usr/lib/omniroute/${_d}/.keep" - done - - cat > "${WRKDIR}/omniroute" <<'EOF' -#!/bin/sh -export PORT="${PORT:-20128}" -export DATA_DIR="${DATA_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/omniroute}" -export APP_LOG_TO_FILE="${APP_LOG_TO_FILE:-false}" -mkdir -p "${DATA_DIR}" -exec node /usr/lib/omniroute/.next/standalone/server.js "$@" -EOF - vbin "${WRKDIR}/omniroute" -} - -post_install() { - vlicense LICENSE -} -``` - -
- ---- - -## 🐳 Docker - -OmniRoute is available as a public Docker image on [Docker Hub](https://hub.docker.com/r/diegosouzapw/omniroute). - -**Quick run:** - -```bash -docker run -d \ - --name omniroute \ - --restart unless-stopped \ - --stop-timeout 40 \ - -p 20128:20128 \ - -v omniroute-data:/app/data \ - diegosouzapw/omniroute:latest -``` - -**With environment file:** - -```bash -# Copy and edit .env first -cp .env.example .env - -docker run -d \ - --name omniroute \ - --restart unless-stopped \ - --stop-timeout 40 \ - --env-file .env \ - -p 20128:20128 \ - -v omniroute-data:/app/data \ - diegosouzapw/omniroute:latest -``` - -**Using Docker Compose:** - -```bash -# Base profile (no CLI tools) -docker compose --profile base up -d - -# CLI profile (Claude Code, Codex, OpenClaw built-in) -docker compose --profile cli up -d -``` - -Dashboard support for Docker deployments now includes a one-click **Cloudflare Quick Tunnel** on `Dashboard → Endpoints`. The first enable downloads `cloudflared` only when needed, starts a temporary tunnel to your current `/v1` endpoint, and shows the generated `https://*.trycloudflare.com/v1` URL directly below your normal public URL. - -Notes: - -- Quick Tunnel URLs are temporary and change after every restart. -- Quick Tunnels are not auto-restored after an OmniRoute or container restart. Re-enable them from the dashboard when needed. -- Managed install currently supports Linux, macOS, and Windows on `x64` / `arm64`. -- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained container environments. Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want a different transport. -- Docker images bundle system CA roots and pass them to managed `cloudflared`, which avoids TLS trust failures when the tunnel bootstraps inside the container. -- SQLite runs in WAL mode. `docker stop` should be allowed to finish so OmniRoute can checkpoint the latest changes back into `storage.sqlite`. -- The bundled Compose files already set a 40s stop grace period. If you run the image directly, keep `--stop-timeout 40` (or similar) so manual stops do not cut off shutdown cleanup. -- Set `CLOUDFLARED_BIN=/absolute/path/to/cloudflared` if you want OmniRoute to use an existing binary instead of downloading one. - -**Using Docker Compose with Caddy (HTTPS Auto-TLS):** - -OmniRoute can be securely exposed using Caddy's automatic SSL provisioning. Ensure your domain's DNS A record points to your server's IP. - -```yaml -services: - omniroute: - image: diegosouzapw/omniroute:latest - container_name: omniroute - restart: unless-stopped - volumes: - - omniroute-data:/app/data - environment: - - PORT=20128 - - NEXT_PUBLIC_BASE_URL=https://your-domain.com - - caddy: - image: caddy:latest - container_name: caddy - restart: unless-stopped - ports: - - "80:80" - - "443:443" - command: caddy reverse-proxy --from https://your-domain.com --to http://omniroute:20128 - -volumes: - omniroute-data: -``` - -| Image | Tag | Size | Description | -| ------------------------ | -------- | ------ | --------------------- | -| `diegosouzapw/omniroute` | `latest` | ~250MB | Latest stable release | -| `diegosouzapw/omniroute` | `3.6.2` | ~250MB | Current version | - ---- - -## 🖥️ Desktop App — Offline & Always-On - -> 🆕 **NEW!** OmniRoute is now available as a **native desktop application** for Windows, macOS, and Linux. - -Run OmniRoute as a standalone desktop app — no terminal, no browser, no internet required for local models. The Electron-based app includes: - -- 🖥️ **Native Window** — Dedicated app window with system tray integration -- 🔄 **Auto-Start** — Launch OmniRoute on system login -- 🔔 **Native Notifications** — Get alerts for quota exhaustion or provider issues -- ⚡ **One-Click Install** — NSIS (Windows), DMG (macOS), AppImage (Linux) -- 🌐 **Offline Mode** — Works fully offline with bundled server - -### Hızlı Başlangıç - -```bash -# Development mode -npm run electron:dev - -# Build for your platform -npm run electron:build # Current platform -npm run electron:build:win # Windows (.exe) -npm run electron:build:mac # macOS (.dmg) — x64 & arm64 -npm run electron:build:linux # Linux (.AppImage) -``` - -### System Tray - -When minimized, OmniRoute lives in your system tray with quick actions: - -- Open dashboard -- Change server port -- Quit application - -📖 Full documentation: [`electron/README.md`](electron/README.md) - ---- - -## 💰 Pricing at a Glance - -| Tier | Provider | Cost | Quota Reset | Best For | -| ------------------- | --------------------------- | ------------------------------------- | --------------------- | ---------------------------------- | -| **💳 SUBSCRIPTION** | Claude Code (Pro) | $20/mo | 5h + weekly | Already subscribed | -| | Codex (Plus/Pro) | $20-200/mo | 5h + weekly | OpenAI users | -| | GitHub Copilot | $10-19/mo | Monthly | GitHub users | -| **🔑 API KEY** | NVIDIA NIM | **FREE ACCESS** (current terms apply) | ~40 RPM | 70+ open models | -| | Cerebras | **FREE** (1M tok/day) | 60K TPM / 30 RPM | World's fastest | -| | Groq | **FREE** (30 RPM) | 14.4K RPD | Ultra-fast Llama/Gemma | -| | DeepSeek V3.2 | $0.27/$1.10 per 1M | None | Best price/quality reasoning | -| | xAI Grok-4 Fast | **$0.20/$0.50 per 1M** 🆕 | None | Fastest + tool calling, ultralow | -| | xAI Grok-4 (standard) | $0.20/$1.50 per 1M 🆕 | None | Reasoning flagship from xAI | -| | Mistral | Free trial + paid | Rate limited | European AI | -| | OpenRouter | Pay-per-use | None | 100+ models aggr. | -| **💰 CHEAP** | GLM-5 (via Z.AI) 🆕 | $0.5/1M | Daily 10AM | 128K output, newest flagship | -| | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup | -| | MiniMax M2.5 🆕 | $0.3/1M input | 5-hour rolling | Reasoning + agentic tasks | -| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option | -| | Kimi K2.5 (Moonshot API) 🆕 | Pay-per-use | None | Direct Moonshot API access | -| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost | -| **🆓 FREE ACCESS** | Qoder | **$0** | Limits apply | Selected models; terms apply | -| | Qwen | **$0** | Limits apply | Selected models; terms apply | -| | Kiro | **$0** | Credit/account limits | Claude access; current terms apply | -| | LongCat signup credit | **$0** (10M one-time; KYC) | One-time | Signup grant; not recurring | -| | Pollinations AI 🆕 | **$0** (no key needed) | 1 req/15s | GPT-5, Claude, DeepSeek, Llama 4 | -| | Cloudflare Workers AI 🆕 | **$0** (10K Neurons/day) | ~150 resp/day | 50+ models, global edge | -| | Scaleway AI 🆕 | **$0** (1M tokens total) | Rate limited | EU/GDPR, Qwen3 235B, Llama 70B | - -> 🆕 **New models added (Mar 2026):** Grok-4 Fast family at $0.20/$0.50/M (benchmarked at 1143ms — 30% faster than Gemini 2.5 Flash), GLM-5 via Z.AI with 128K output, MiniMax M2.5 reasoning, DeepSeek V3.2 updated pricing, Kimi K2.5 via Moonshot direct API. - -**💡 $0 Combo Stack — The Complete Free Setup:** - -``` -# 🆓 Free-access examples — provider limits and terms apply -Kiro (kr/) → Claude access — account/credit limits apply -Qoder (if/) → selected models — no published token cap; rate/account limits apply -LongCat (lc/) → LongCat-2.0 — 10M one-time signup credit; KYC required -Pollinations (pol/) → GPT-5, Claude, DeepSeek, Llama 4 — no key needed -Qwen (qw/) → selected models — no published token cap; rate/account limits apply -Gemini (gemini/) → selected free-tier models — current API quotas apply -Cloudflare AI (cf/) → Llama 70B, Gemma 3, Mistral — 10K Neurons/day -Scaleway (scw/) → Qwen3 235B, Llama 70B — 1M free tokens (EU) -Groq (groq/) → selected models — current per-model rate limits apply -NVIDIA NIM (nvidia/) → selected models — current rate limits apply -Cerebras (cerebras/) → Llama/Qwen world-fastest — 1M tok/day -``` - -**Current $0 access where listed; availability is not guaranteed.** A combo can try the next eligible route when a quota or upstream fails. - ---- - ---- - -## 🆓 Free Models — What You Actually Get - -> The entries below summarize access that was listed as free when audited. Provider quotas, card/account/KYC requirements, models, regions and terms can change. A combo broadens fallback coverage but does not guarantee uninterrupted $0 access. - -### 🔵 CLAUDE MODELS (via Kiro — AWS Builder ID) - -| Model | Prefix | Limit | Rate Limit | -| ------------------- | ------ | ------------- | --------------------- | -| `claude-sonnet-4.5` | `kr/` | No published token cap | Provider/account limits may apply | -| `claude-haiku-4.5` | `kr/` | No published token cap | Provider/account limits may apply | -| `claude-opus-4.6` | `kr/` | No published token cap | Latest Opus; provider/account limits apply | - -### 🟢 QODER MODELS (Free PAT via qodercli) - -| Model | Prefix | Limit | Rate Limit | -| ------------------ | ------ | ------------- | --------------- | -| `kimi-k2-thinking` | `if/` | No published token cap | Provider/account limits may apply | -| `qwen3-coder-plus` | `if/` | No published token cap | Provider/account limits may apply | -| `deepseek-r1` | `if/` | No published token cap | Provider/account limits may apply | -| `minimax-m2.1` | `if/` | No published token cap | Provider/account limits may apply | -| `kimi-k2` | `if/` | No published token cap | Provider/account limits may apply | - -> Recommended connection method: **Personal Access Token + `qodercli`**. Browser OAuth is -> experimental and disabled by default unless `QODER_OAUTH_*` environment variables are configured. - -### 🟡 QWEN MODELS (Device Code Auth) - -| Model | Prefix | Limit | Rate Limit | -| ------------------- | ------ | ------------- | ------------------- | -| `qwen3-coder-plus` | `qw/` | No published token cap | Provider/account limits may apply | -| `qwen3-coder-flash` | `qw/` | No published token cap | Provider/account limits may apply | -| `qwen3-coder-next` | `qw/` | No published token cap | Provider/account limits may apply | -| `vision-model` | `qw/` | No published token cap | Multimodal; provider/account limits may apply | - -### ⚫ NVIDIA NIM (Free API Key — build.nvidia.com) - -| Tier | Daily Limit | Rate Limit | Notes | -| ---------- | ------------ | ----------- | ------------------------------------------------------ | -| Free (Dev) | No token cap | **~40 RPM** | 70+ models; transitioning to pure rate limits mid-2025 | - -Popular free models: `moonshotai/kimi-k2.5` (Kimi K2.5), `z-ai/glm4.7` (GLM 4.7), `deepseek-ai/deepseek-v3.2` (DeepSeek V3.2), `nvidia/llama-3.3-70b-instruct`, `deepseek/deepseek-r1` - -### ⚪ CEREBRAS (Free API Key — inference.cerebras.ai) - -| Tier | Daily Limit | Rate Limit | Notes | -| ---- | ----------------- | ---------------- | ------------------------------------------- | -| Free | **1M tokens/day** | 60K TPM / 30 RPM | World's fastest LLM inference; resets daily | - -Available free: `llama-3.3-70b`, `llama-3.1-8b`, `deepseek-r1-distill-llama-70b` - -### 🔴 GROQ (Free API Key — console.groq.com) - -| Tier | Daily Limit | Rate Limit | Notes | -| ---- | ------------- | ---------------- | ----------------------------------------- | -| Free | **14.4K RPD** | 30 RPM per model | No credit card; 429 on limit, not charged | - -Available free: `llama-3.3-70b-versatile`, `gemma2-9b-it`, `mixtral-8x7b`, `whisper-large-v3` - -### 🔴 LONGCAT AI (Signup credit — KYC required) - -| Model | Prefix | Current catalog grant | Notes | -| ------------- | ------ | ----------------------- | --------------------------------------------------- | -| `LongCat-2.0` | `lc/` | **10M tokens one-time** | Signup grant; not a recurring monthly or daily pool | - -> Provider terms, eligibility and model availability can change. See [`FREE_TIERS.md`](../../reference/FREE_TIERS.md) for the audited catalog entry. - -### 🟢 POLLINATIONS AI (No API Key Required) 🆕 - -| Model | Prefix | Rate Limit | Provider Behind | -| ---------- | ------ | ---------- | ------------------ | -| `openai` | `pol/` | 1 req/15s | GPT-5 | -| `claude` | `pol/` | 1 req/15s | Anthropic Claude | -| `gemini` | `pol/` | 1 req/15s | Google Gemini | -| `deepseek` | `pol/` | 1 req/15s | DeepSeek V3 | -| `llama` | `pol/` | 1 req/15s | Meta Llama 4 Scout | -| `mistral` | `pol/` | 1 req/15s | Mistral AI | - -> ✨ **Zero friction:** No signup, no API key. Add the Pollinations provider with an empty key field and it works immediately. - -### 🟠 CLOUDFLARE WORKERS AI (Free API Key — cloudflare.com) 🆕 - -| Tier | Daily Neurons | Equivalent Usage | Notes | -| ---- | ------------- | --------------------------------------- | ----------------------- | -| Free | **10,000** | ~150 LLM resp / 500s audio / 15K embeds | Global edge, 50+ models | - -Popular free models: `@cf/meta/llama-3.3-70b-instruct`, `@cf/google/gemma-3-12b-it`, `@cf/openai/whisper-large-v3-turbo` (free audio!), `@cf/qwen/qwen2.5-coder-15b-instruct` - -> Requires API Token + Account ID from [dash.cloudflare.com](https://dash.cloudflare.com). Store Account ID in provider settings. - -### 🟣 SCALEWAY AI (1M Free Tokens — scaleway.com) 🆕 - -| Tier | Free Quota | Location | Notes | -| ---- | ------------- | ------------ | ----------------------------------- | -| Free | **1M tokens** | 🇫🇷 Paris, EU | No credit card needed within limits | - -Available free: `qwen3-235b-a22b-instruct-2507` (Qwen3 235B!), `llama-3.1-70b-instruct`, `mistral-small-3.2-24b-instruct-2506`, `deepseek-v3-0324` - -> EU/GDPR compliant. Get API key at [console.scaleway.com](https://console.scaleway.com). - -> **💡 Free-access examples (provider limits and terms apply):** -> -> ``` -> Kiro (kr/) → Claude access — account/credit limits apply -> Qoder (if/) → selected models — no published token cap; limits apply -> LongCat (lc/) → LongCat-2.0 — 10M one-time signup credit; KYC required -> Pollinations (pol/) → GPT-5, Claude, DeepSeek, Llama 4 — no key needed -> Qwen (qw/) → selected models — no published token cap; limits apply -> Gemini (gemini/) → selected free-tier models — current quotas apply -> Cloudflare AI (cf/) → 50+ models — 10K Neurons/day -> Scaleway (scw/) → Qwen3 235B, Llama 70B — 1M free tokens (EU) -> Groq (groq/) → selected models — current per-model rate limits apply -> NVIDIA NIM (nvidia/) → selected models — current rate limits apply -> Cerebras (cerebras/) → Llama/Qwen world-fastest — 1M tok/day -> ``` - -## 🎙️ Free Transcription Combo - -> Transcription access depends on each upstream allowance — Deepgram and AssemblyAI signup credits can lead, with Groq Whisper as a rate-limited fallback. - -| Provider | Free Credits | Best Model | Rate Limit | -| ----------------- | --------------------------- | -------------------------------------------- | ---------------------------------------- | -| 🟢 **Deepgram** | **$200 free** (signup) | `nova-3` — best accuracy, 30+ languages | No RPM limit on free credits | -| 🔵 **AssemblyAI** | **$50 free** (signup) | `universal-3-pro` — chapters, sentiment, PII | No RPM limit on free credits | -| 🔴 **Groq** | **Free tier; limits apply** | `whisper-large-v3` — OpenAI Whisper | Current model-specific rate limits apply | - -**Suggested combo in `/dashboard/combos`:** - -``` -Name: free-transcription -Strategy: Priority -Nodes: - [1] deepgram/nova-3 → uses $200 free first - [2] assemblyai/universal-3-pro → fallback when Deepgram credits run out - [3] groq/whisper-large-v3 → free access; rate limits apply -``` - -Then in `/dashboard/media` → **Transcription** tab: upload any audio or video file → select your combo endpoint → get transcription in supported formats. - -## 💡 Key Features - -OmniRoute v3.6 is built as an operational platform, not just a relay proxy. - -### 🆕 New — v3.6.x Highlights (Apr 2026) - -| Feature | What It Does | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🌐 **V1 WebSocket Bridge** | OpenAI-compatible WebSocket traffic upgraded and proxied via `/v1/ws` — full streaming over WS with session auth (API key or session cookie) | -| 🔑 **Sync Tokens & Config Bundle** | Issue/revoke sync tokens for config sync endpoints. Config bundles versioned with ETag for bandwidth-efficient polling | -| 🧠 **GLM Thinking (glmt) Preset** | GLM Thinking registered first-class: 65 536 max tokens, 24 576 thinking budget, 900s timeout, usage sync & pricing — Claude-compatible API | -| 🔢 **Hybrid Token Counting** | Uses provider-side `/messages/count_tokens` when available; falls back to estimation — accurate usage tracking without guessing | -| 🌱 **Model Alias Auto-Seed** | 30+ cross-proxy dialect aliases normalised at startup — no more routing mismatches | -| 🛡️ **Safe Outbound Fetch** | All provider validation and model discovery go through a guarded fetch layer blocking private/local URLs with retry, timeout, and SSRF protection | -| ⏳ **Wait For Cooldown** | Server-side chat retries when every candidate connection is cooling down; configurable `enabled`, `maxRetries`, and `maxRetryWaitSec` | -| 🔍 **Runtime Env Validation** | Startup validates all env vars with Zod schemas — clear errors for missing secrets, invalid URLs, or wrong types | -| 📋 **Compliance Audit Expansion** | Structured audit logs with pagination, request context, auth events, provider CRUD events, and SSRF-blocked validation logging | -| 🔐 **TPS Log Metric** | Log details modal shows Tokens Per Second (TPS) — quick performance at-a-glance for every request | -| 🗑️ **Uninstall / Full Uninstall** | `npm run uninstall` keeps data, `npm run uninstall:full` removes everything — clean removal for all install methods | -| 🔧 **OAuth Env Repair** | One-click "Repair env" action for OAuth providers restores missing env vars and fixes broken auth state | -| 🔒 **Graceful Electron Shutdown** | Electron `before-quit` shuts down Next.js gracefully, preventing SQLite WAL database locks on desktop close | -| 👁️ **Model Visibility Toggle** | Per-model visibility toggle (👁 icon) with search filter and active-count badge (`N/M active`) on provider pages | -| 📧 **Email Privacy Masking** | OAuth account emails masked (`di*****@g****.com`), full address visible on hover | -| 🔗 **Context Relay Strategy** | Combo strategy preserving session continuity via structured handoff summaries when accounts rotate mid-conversation | -| 🛡️ **Proxy Hardening** | Token health check, API key validation, and undici dispatcher all honor proxy config | -| ⚠️ **Node.js 24 Login Warning** | Login page proactively detects incompatible Node.js versions and shows a clear warning banner | -| 📎 **Gemini PDF Attachments** | PDF attachments correctly routed to Gemini via `inline_data` and generic base64 detection | -| 🔒 **CodeQL Security Hardening** | Resolved SSRF, insecure randomness, polynomial ReDoS, and incomplete URL sanitization alerts | - -### 🆕 New — ClawRouter-Inspired Improvements (Mar 2026) - -| Feature | What It Does | -| ------------------------------------ | ------------------------------------------------------------------------------------------- | -| ⚡ **Grok-4 Fast Family** | xAI models at $0.20/$0.50/M — benchmarked 1143ms (30% faster than Gemini 2.5 Flash) | -| 🧠 **GLM-5 via Z.AI** | 128K output context, $0.5/1M — newest flagship from the GLM family | -| 🔮 **MiniMax M2.5** | Reasoning + agentic tasks at $0.30/1M — significant upgrade from M2.1 | -| 🎯 **toolCalling Flag per Model** | Per-model `toolCalling: true/false` in registry — AutoCombo skips non-tool-capable models | -| 🌍 **Multilingual Intent Detection** | PT/ZH/ES/AR keywords in AutoCombo scoring — better model selection for non-English content | -| 📊 **Benchmark-Driven Fallbacks** | Real p95 latency from live requests feeds combo scoring — AutoCombo learns from actual data | -| 🔁 **Request Deduplication** | Content-hash based dedup window — multi-agent safe, prevents duplicate charges | -| 🔌 **Pluggable RouterStrategy** | Extensible `RouterStrategy` interface — add custom routing logic as plugins | - -### 🚀 Previous v2.0.9+ — Playground, CLI Fingerprints & ACP - -| Feature | What It Does | -| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🎮 **Model Playground** | Dashboard page to test any model directly — provider/model/endpoint selectors, Monaco Editor, streaming, abort, timing | -| 🔏 **CLI Fingerprint Matching** | Per-provider header/body ordering to match native CLI signatures — toggle per provider in Settings > Security. **Your proxy IP is preserved** | -| 🤖 **ACP Agents Dashboard** | Debug › Agents page — grid of 14 agents with install status, version, custom agent form for any CLI tool. **OpenCode** users get a "Download opencode.json" button that auto-generates a ready-to-use config with all available models. | -| 🔧 **Custom Model `apiFormat` Routing** | Custom models with `apiFormat: "responses"` now correctly route to the Responses API translator | -| 🏢 **Codex Workspace Isolation** | Multiple Codex workspaces per email — OAuth correctly separates connections by workspace ID | -| 🔄 **Electron Auto-Update** | Desktop app checks for updates + auto-install on restart | - -### 🤖 Agent & Protocol Operations (v2.0) - -| Feature | What It Does | -| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🔧 **MCP Server (107 tools)** | IDE/agent tools via 3 transports: stdio, SSE (`/api/mcp/sse`), Streamable HTTP (`/api/mcp/stream`). 107 unique tools across the registered tool families; enabled skills may add dynamic tools at runtime | -| 🤝 **A2A Server (JSON-RPC + SSE)** | Agent-to-agent task execution with sync and streaming flows | -| 🧭 **Consolidated Endpoints Page** | Tabbed management page with Endpoint Proxy, MCP, A2A, and API Endpoints tabs | -| 🎚️ **Service Enable/Disable Toggles** | ON/OFF switches for MCP and A2A with settings persistence (default: OFF) | -| 🛰️ **MCP Runtime Heartbeat** | Real process status (pid, uptime, heartbeat age, transport, scope mode) | -| 📋 **MCP Audit Trail** | Filterable audit logs with success/failure and key attribution | -| 🔐 **MCP Scope Enforcement** | 32 granular scope permissions for controlled tool access | -| 📡 **A2A Task Lifecycle Management** | List/filter tasks, inspect events/artifacts, cancel running tasks | -| 📋 **Agent Card Discovery** | `/.well-known/agent.json` for client auto-discovery | -| 🧪 **Protocol E2E Test Harness** | Real MCP SDK + A2A client flows in `test:protocols:e2e` | -| ⚙️ **Operational Controls** | Switch combos, tune resilience settings, and review breaker state from dedicated Health and Settings surfaces | - -### 🧠 Routing & Intelligence - -| Feature | What It Does | -| ---------------------------------- | ------------------------------------------------------------------------ | -| 🎯 **Smart 4-Tier Fallback** | Auto-route: Subscription → API Key → Cheap → Free | -| 📊 **Real-Time Quota Tracking** | Live token count + reset countdown per provider | -| 🔄 **Format Translation** | OpenAI ↔ Claude ↔ Gemini ↔ Responses with schema-safe conversions | -| 👥 **Multi-Account Support** | Multiple accounts per provider with intelligent selection | -| 🔄 **Auto Token Refresh** | OAuth tokens refresh automatically with retry | -| 🎨 **Custom Combos** | 13 balancing strategies + fallback chain control | -| 🔗 **Context Relay** | Session continuity handoffs when account rotation happens mid-session | -| 🌐 **Wildcard Router** | `provider/*` dynamic routing | -| 🧠 **Thinking Budget Controls** | Passthrough, auto, custom, and adaptive reasoning limits | -| 🔀 **Model Aliases** | Built-in + custom model aliasing and migration safety | -| ⚡ **Background Degradation** | Route low-priority background tasks to cheaper models | -| 🧪 **Task-Aware Smart Routing** | Auto-select model by content type (coding/vision/analysis/summarization) | -| 🔄 **A2A Agent Workflows** | Deterministic FSM orchestrator for stateful multi-step agent executions | -| 🔀 **Adaptive Routing** | Dynamic strategy override based on token volume and prompt complexity | -| 🎲 **Provider Diversity** | Shannon entropy scoring balancing auto-combo traffic distribution | -| 💬 **System Prompt Injection** | Global behavior controls applied consistently | -| 📄 **Responses API Compatibility** | Full `/v1/responses` support for Codex and advanced agentic workflows | - -### 🎵 Multi-Modal APIs - -| Feature | What It Does | -| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 🖼️ **Image Generation** | `/v1/images/generations` with cloud and local backends | -| 📐 **Embeddings** | `/v1/embeddings` for search and RAG pipelines | -| 🎤 **Audio Transcription** | `/v1/audio/transcriptions` — 7 providers (Deepgram Nova 3, AssemblyAI, Groq Whisper, HuggingFace, ElevenLabs, OpenAI, Azure), auto-language detection, MP4/MP3/WAV support | -| 🔊 **Text-to-Speech** | `/v1/audio/speech` — 10 providers (ElevenLabs, OpenAI, Deepgram, Cartesia, PlayHT, HuggingFace, Nvidia NIM, Inworld, Coqui, Tortoise) with correct error messages | -| 🎬 **Video Generation** | `/v1/videos/generations` (ComfyUI + SD WebUI workflows) | -| 🎵 **Music Generation** | `/v1/music/generations` (ComfyUI workflows) | -| 🛡️ **Moderations** | `/v1/moderations` safety checks | -| 🔀 **Reranking** | `/v1/rerank` for relevance scoring | -| 🔍 **Web Search** 🆕 | `/v1/search` — 5 providers (Serper, Brave, Perplexity, Exa, Tavily), 6,500+ free/month, auto-failover, cache | - -### 🛡️ Resilience, Security & Governance - -| Feature | What It Does | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------- | -| 🔌 **Provider Circuit Breakers** | Provider-wide trip/recover after fallback exhaustion with configurable thresholds | -| 🔒 **Daily Quota Lock** 🆕 | Detects exhaustion signals and locks routing for the specific model until midnight | -| 🎯 **Endpoint-Aware Models** | Custom models declare supported endpoints + API format | -| 🛡️ **Anti-Thundering Herd** | Mutex + semaphore protections on retry/rate events | -| 🧠 **Semantic + Signature Cache** | Cost/latency reduction with two cache layers | -| ⚡ **Request Idempotency** | Duplicate protection window | -| 🔒 **TLS Fingerprint Spoofing** | Browser-like TLS fingerprint — **reduces bot detection and account flagging** | -| 🔏 **CLI Fingerprint Matching** | Matches native CLI request signatures — **reduces ban risk while preserving proxy IP** | -| 🌐 **IP Filtering** | Allowlist/blocklist control for exposed deployments | -| 🚦 **Request Queue & Pacing** | Configurable per-connection request buckets for RPM, spacing, concurrency, and max wait | -| 📉 **Graceful Degradation** | Multi-layer capability fallbacks protecting core gateway operations | -| 📜 **Config Audit Trail** | Diff-based change tracking preventing operational drift with simple rollbacks | -| ⏳ **Provider Health Sync** | Proactive token expiration monitoring triggering alerts before authorization failures | -| ❄️ **Connection Cooldown** | Retryable 408/429/5xx failures cool down a single connection with optional upstream hints | -| 🚪 **Auto-Disable Banned Accounts** | Permanently blocked token accounts can be disabled automatically | -| 🔑 **API Key Management + Scoping** | Secure key issuance/rotation and model/provider controls | -| 👁️ **Scoped API Key Reveal** 🆕 | Opt-in recovery of API keys via `ALLOW_API_KEY_REVEAL` | -| 🛡️ **Protected `/models`** | Optional auth gating and provider hiding for model catalog | -| 🛡️ **Safe Outbound Fetch** 🆕 | Guarded fetch for provider calls — blocks private/local URLs, retries, SSRF protection | -| ⏳ **Wait For Cooldown** 🆕 | Auto-retry chat after connection cooldowns; configurable `enabled`, `maxRetries`, and `maxRetryWaitSec` | -| 🔍 **Runtime Env Validation** 🆕 | Zod-based env schema validation at startup with actionable error messages | -| 📋 **Compliance Audit v2** 🆕 | Pagination, request context, auth events, provider CRUD, and SSRF-blocked logging | - -### 📊 Observability & Analytics - -| Feature | What It Does | -| -------------------------------- | ----------------------------------------------------- | -| 📝 **Request + Proxy Logging** | Full request/response and proxy logging | -| 📉 **Streamed Detailed Logs** | Reconstructs SSE payload streams cleanly into the UI | -| 🏷️ **Real-Time Model Badges** 🆕 | Live model status and daily quota countdown timers | -| 📋 **Unified Logs Dashboard** | Request, proxy, audit, and console views in one page | -| 🔍 **Request Telemetry** | p50/p95/p99 latency and request tracing | -| 🏥 **Health Dashboard** | Uptime, breaker states, lockouts, cache stats | -| 💰 **Cost Tracking** | Budget controls and per-model pricing visibility | -| 📈 **Analytics Visualizations** | Model/provider usage insights and trend views | -| 🧪 **Evaluation Framework** | Golden set testing with configurable match strategies | -| 📡 **Live Diagnostics** 🆕 | Semantic cache bypass for accurate combo live testing | -| 🔐 **TPS Log Metric** 🆕 | Tokens Per Second badge in log details modal | - -### ☁️ Deployment & Platform - -| Feature | What It Does | -| ------------------------------ | --------------------------------------------------------------------- | -| 🌐 **Deploy Anywhere** | Localhost, VPS, Docker, Cloud environments | -| 🚇 **Cloudflare Tunnel** 🆕 | One-click Quick Tunnel integration from the dashboard | -| 🔑 **API Key Model Filtering** | Native /v1/models response filtered via assigned Bearer context roles | -| ⚡ **Smart Cache Bypass** | Configurable TTL heuristics and forced refetch controls | -| 🔄 **Backup/Restore** | Export/import and disaster recovery flows | -| 🧙 **Onboarding Wizard** | First-run guided setup | -| 🔧 **CLI Tools Dashboard** | One-click setup for popular coding tools | -| 🎮 **Model Playground** | Test any provider/model/endpoint from the dashboard | -| 🔏 **CLI Fingerprint Toggle** | Per-provider fingerprint matching in Settings > Security | -| 🌐 **i18n (30 languages)** | Full dashboard + docs language support with RTL coverage | -| 🧹 **Clear All Models** | One-click model list clearing in provider details | -| 👁️ **Sidebar Controls** 🆕 | Hide components and integrations from Appearance Settings | -| 📋 **Issue Templates** | Standardized GitHub templates for bugs and features | -| 📂 **Custom Data Directory** | `DATA_DIR` override for storage location | -| 🌐 **V1 WebSocket Bridge** 🆕 | OpenAI-compatible WebSocket traffic proxied via `/v1/ws` | -| 🔑 **Sync Tokens & Bundle** 🆕 | Config sync tokens + versioned bundle endpoint with ETag support | - -### Feature Deep Dive - -#### Smart fallback with practical cost control +İstemciniz özel başlıklar (custom headers) gönderemiyorsa, OmniRoute belirteçli uyumluluk takma adları da sunar: ```txt -Combo: "my-coding-stack" - 1. cc/claude-opus-4-7 - 2. nvidia/llama-3.3-70b - 3. glm/glm-4.7 - 4. if/kimi-k2-thinking +OpenAI catalog: http://localhost:20128/vscode/YOUR_KEY/ +OpenAI models: http://localhost:20128/vscode/YOUR_KEY/models +OpenAI chat: http://localhost:20128/vscode/YOUR_KEY/chat/completions +OpenAI responses: http://localhost:20128/vscode/YOUR_KEY/responses +Ollama chat: http://localhost:20128/vscode/YOUR_KEY/api/chat +Ollama tags: http://localhost:20128/vscode/YOUR_KEY/api/tags ``` -When quota, rate, or health fails, OmniRoute automatically moves to the next candidate without manual switching. +Bunları yalnızca `Authorization: Bearer ...` ekleyemeyen istemciler için kullanın. Başlık kimlik doğrulaması tercih edilen mod olmaya devam eder. -#### Protocol management that is visible and operable +
-- MCP + A2A are discoverable in UI and docs (not hidden) -- Protocol status APIs expose live operational data (`/api/mcp/*`, `/api/a2a/*`) -- Dashboards include actions for day-2 ops (combo toggles, breaker resets, task cancellation) +## 📦 Daha fazla kurulum yöntemi — Docker, kaynak kod, pnpm, Arch -#### Translator + validation workflow - -The Translator area includes: - -- **Playground**: request transformation checks -- **Chat Tester**: full request/response round-trip -- **Test Bench**: multiple cases in one run -- **Live Monitor**: real-time traffic view - -Plus protocol validation with real clients via `npm run test:protocols:e2e`. - -> 📖 **[MCP Server README](open-sse/mcp-server/README.md)** — Tool reference, IDE configs, and client examples -> -> 📖 **[A2A Server README](src/lib/a2a/README.md)** — Skills, JSON-RPC methods, streaming, and task lifecycle - -## 🧪 Evaluations (Evals) - -OmniRoute includes a built-in evaluation framework to test LLM response quality against a golden set. Access it via **Analytics → Evals** in the dashboard. - -### Built-in Golden Set - -The pre-loaded "OmniRoute Golden Set" contains test cases for: - -- Greetings, math, geography, code generation -- JSON format compliance, translation, markdown generation -- Safety refusal (harmful content), counting, boolean logic - -### Evaluation Strategies - -| Strategy | Description | Example | -| ---------- | ------------------------------------------------ | -------------------------------- | -| `exact` | Output must match exactly | `"4"` | -| `contains` | Output must contain substring (case-insensitive) | `"Paris"` | -| `regex` | Output must match regex pattern | `"1.*2.*3"` | -| `custom` | Custom JS function returns true/false | `(output) => output.length > 10` | - ---- - -## 📖 Setup Guide - -### Protocol Setup (MCP + A2A) - -
-🧩 MCP Setup (Model Context Protocol) - -Start MCP transport in stdio mode: +**🐳 Docker** ```bash -omniroute --mcp +docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \ + -p 127.0.0.1:20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest ``` -Recommended validation flow: +`:latest` etiketi **yayımlanmış** en yüksek kararlı SemVer sürümünü takip eder. Git `main` dalını takip etmez. GitOps için `:X.Y.Z` sürümünü sabitleyin. Bkz. [Docker Sürüm Kanalları](docs/guides/DOCKER_GUIDE.md#release-channels). İmaj **`OMNIROUTE_MEMORY_MB=1024`** değerini sabitler. Bu, pano ve hafif bir sohbet için yeterlidir. **Kodlama ajanları** (Claude Code, Codex, Grok, vb.'den gelen `POST /v1/responses`), çok daha büyük bir V8 heap alanına ihtiyaç duyar; aksi takdirde iki örtüşen uzun bağlam altında süreç ~12 GiB seviyesinde `FATAL ERROR` verir. Konteyneri heap boyutunun üzerinde boyutlandırın (yerel arabellekler V8'in dışında yer alır): -1. Connect your MCP client over stdio. -2. Run `omniroute_get_health`. -3. Run `omniroute_list_combos`. -4. Open `/dashboard/mcp` to confirm heartbeat, activity, and audit. - -Useful APIs for automation: - -- `GET /api/mcp/status` -- `GET /api/mcp/tools` -- `GET /api/mcp/audit` -- `GET /api/mcp/audit/stats` - -
- -
-🤝 A2A Setup (Agent2Agent) - -Discover the agent: +| İş Yükü | Heap (`-e OMNIROUTE_MEMORY_MB`) | Konteyner (`--memory`) | +| ----------------------------------- | ------------------------------- | ---------------------- | +| Pano / hafif sohbet | `1024` (imaj varsayılanı) | ≥2 g | +| Tek bir kodlama ajanı | `8192` | ≥10 g | +| İki eşzamanlı uzun `/v1/responses` | `10240`–`12288` | ≥12–16 g | ```bash -curl http://localhost:20128/.well-known/agent.json +docker run -d --name omniroute --restart unless-stopped --stop-timeout 40 \ + -e OMNIROUTE_MEMORY_MB=8192 --memory=10g \ + -p 127.0.0.1:20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest ``` -Send a task: +Tam tablo: [Docker Kılavuzu — çalışma zamanı RAM](docs/guides/DOCKER_GUIDE.md#runtime-ram-for-coding-agents). + +> **Ön sürüm Docker kanalı:** `diegosouzapw/omniroute:next` ve +> `diegosouzapw/omniroute:next-web` geçerli varsayılan `release/v*` +> dalını takip eder. Bu değişken etiketler yalnızca yayımlanmamış düzeltmeleri test etmek içindir ve +> **üretim ortamı için desteklenmez**. Bkz. +> [Docker Sürüm Kanalları](docs/guides/DOCKER_GUIDE.md#release-channels). + +**🥟 Bun** + +Standart `bun install` ve genel kurulum (`bun install -g omniroute`), Bun çalışma zamanı algılamasıyla desteklenir: +- **Yerleşik `bun:sqlite`**: OmniRoute, Bun altında çalışırken Bun'ın yerleşik `bun:sqlite` sürücüsünü kullanır; Node.js altında `better-sqlite3` veya `sql.js`'e geri döner. +- **Otomatik Webpack paketleyici seçimi**: Geliştirme (`bun run dev`) ve üretim derlemeleri (`bun run build`), Bun'ı otomatik olarak algılar ve yerel V8 bağlama uyumsuzluklarını önlemek için Turbopack yerine Webpack'i seçer. +- **Özel Bun Dockerfile**: Yerel Bun üretim dağıtımları için çok aşamalı `Dockerfile.bun` (`docker build -f Dockerfile.bun -t omniroute:bun .`). ```bash -curl -X POST http://localhost:20128/a2a \ - -H 'content-type: application/json' \ - -d '{"jsonrpc":"2.0","id":"setup-a2a","method":"message/send","params":{"skill":"quota-management","messages":[{"role":"user","content":"Summarize quota status."}]}}' +# Bun ile kurun ve çalıştırın +bun install +bun run dev ``` -Manage lifecycle: - -- `GET /api/a2a/status` -- `GET /api/a2a/tasks` -- `GET /api/a2a/tasks/:id` -- `POST /api/a2a/tasks/:id/cancel` - -Operational UI: - -- `/dashboard/a2a` for task/state/stream observability and smoke actions - -
- -
-🧪 End-to-end protocol validation - -Validate both protocols with real clients: +**🛠️ Kaynak koddan** ```bash -npm run test:protocols:e2e +cp .env.example .env && npm install +PORT=20128 npm run dev ``` -This verifies: - -- MCP SDK client connect/list/call -- A2A discovery/send/stream/get/cancel -- Cross-check data in MCP audit and A2A task management APIs - -
- -
-💳 Subscription Providers - -### Claude Code (Pro/Max) +**📦 pnpm** ```bash -Dashboard → Providers → Connect Claude Code -→ OAuth login → Auto token refresh -→ 5-hour + weekly quota tracking - -Models: - cc/claude-opus-4-7 - cc/claude-sonnet-4-5-20250929 - cc/claude-haiku-4-5-20251001 +pnpm add -g omniroute@latest --allow-build=better-sqlite3 --allow-build=@swc/core && omniroute ``` -**Pro Tip:** Use Opus for complex tasks, Sonnet for speed. OmniRoute tracks quota per model! - -### OpenAI Codex (Plus/Pro) +**🐧 Arch Linux (AUR)** ```bash -Dashboard → Providers → Connect Codex -→ OAuth login (port 1455) -→ 5-hour + weekly reset - -Models: - cx/gpt-5.2-codex - cx/gpt-5.1-codex-max +yay -S omniroute-bin && systemctl --user enable --now omniroute.service ``` -#### Codex Account Limit Management (5h + Weekly) - -Each Codex account now has policy toggles in `Dashboard -> Providers`: - -- `5h` (ON/OFF): enforce the 5-hour window threshold policy. -- `Weekly` (ON/OFF): enforce the weekly window threshold policy. -- Threshold behavior: when an enabled window reaches >=90% usage, that account is skipped. -- Rotation behavior: OmniRoute routes to the next eligible Codex account automatically. -- Reset behavior: when the provider `resetAt` time passes, the account becomes eligible again automatically. - -Scenarios: - -- `5h ON` + `Weekly ON`: account is skipped when either window reaches threshold. -- `5h OFF` + `Weekly ON`: only weekly usage can block the account. -- `5h ON` + `Weekly OFF`: only 5-hour usage can block the account. -- `resetAt` passed: account re-enters rotation automatically (no manual re-enable). - -### GitHub Copilot +**🔧 Nix (Flake)** ```bash -Dashboard → Providers → Connect GitHub -→ OAuth via GitHub -→ Monthly reset (1st of month) - -Models: - gh/gpt-5 - gh/claude-4.5-sonnet - gh/gemini-3.1-pro-preview -``` - -
- -
-🔑 API Key Providers - -### NVIDIA NIM (FREE developer access — 70+ models) - -1. Sign up: [build.nvidia.com](https://build.nvidia.com) -2. Get free API key (1000 inference credits included) -3. Dashboard → Add Provider → NVIDIA NIM: - - API Key: `nvapi-your-key` - -**Models:** `nvidia/llama-3.3-70b-instruct`, `nvidia/mistral-7b-instruct`, and 50+ more - -**Pro Tip:** OpenAI-compatible API — works seamlessly with OmniRoute's format translation! - -### DeepSeek - -1. Sign up: [platform.deepseek.com](https://platform.deepseek.com) -2. Get API key -3. Dashboard → Add Provider → DeepSeek - -**Models:** `deepseek/deepseek-chat`, `deepseek/deepseek-coder` - -### Groq (Free Tier Available!) - -1. Sign up: [console.groq.com](https://console.groq.com) -2. Get API key (free tier included) -3. Dashboard → Add Provider → Groq - -**Models:** `groq/llama-3.3-70b`, `groq/mixtral-8x7b` - -**Pro Tip:** Ultra-fast inference — best for real-time coding! - -### OpenRouter (100+ Models) - -1. Sign up: [openrouter.ai](https://openrouter.ai) -2. Get API key -3. Dashboard → Add Provider → OpenRouter - -**Models:** Access 100+ models from all major providers through a single API key. - -**Dashboard behavior:** OpenRouter models are managed from **Available Models**. Manual add, import, and auto-sync all update the same list. - -
- -
-💰 Cheap Providers (Backup) - -### GLM-4.7 (Daily reset, $0.6/1M) - -1. Sign up: [Zhipu AI](https://open.bigmodel.cn/) -2. Get API key from Coding Plan -3. Dashboard → Add API Key: - - Provider: `glm` - - API Key: `your-key` - -**Use:** `glm/glm-4.7` - -**Pro Tip:** Coding Plan offers 3× quota at 1/7 cost! Reset daily 10:00 AM. - -### MiniMax M2.1 (5h reset, $0.20/1M) - -1. Sign up: [MiniMax](https://www.minimax.io/) -2. Get API key -3. Dashboard → Add API Key - -**Use:** `minimax/MiniMax-M2.1` - -**Pro Tip:** Cheapest option for long context (1M tokens)! - -### Kimi K2 ($9/month flat) - -1. Subscribe: [Moonshot AI](https://platform.moonshot.ai/) -2. Get API key -3. Dashboard → Add API Key - -**Use:** `kimi/kimi-latest` - -**Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost! - -
- -
-🆓 FREE Providers (Emergency Backup) - -### Qoder (5 FREE models via OAuth) - -```bash -Dashboard → Connect Qoder -→ Qoder OAuth login -→ Access is subject to current provider limits - -Models: - if/kimi-k2-thinking - if/qwen3-coder-plus - if/glm-4.7 - if/minimax-m2 - if/deepseek-r1 -``` - -### Qwen (4 FREE models via Device Code) - -```bash -Dashboard → Connect Qwen -→ Device code authorization -→ Access is subject to current provider limits - -Models: - qw/qwen3-coder-plus - qw/qwen3-coder-flash -``` - -### Kiro (Claude FREE) - -```bash -Dashboard → Connect Kiro -→ AWS Builder ID or Google/GitHub -→ Access is subject to current provider limits - -Models: - kr/claude-sonnet-4.5 - kr/claude-haiku-4.5 -``` - -
- -
-🎨 Create Combos - -### Example 1: Maximize Subscription → Cheap Backup - -``` -Dashboard → Combos → Create New - -Name: premium-coding -Models: - 1. cc/claude-opus-4-7 (Subscription primary) - 2. glm/glm-4.7 (Cheap backup, $0.6/1M) - 3. minimax/MiniMax-M2.1 (Cheapest fallback, $0.20/1M) - -Use in CLI: premium-coding -``` - -### Example 2: Free-Only (Zero Cost) - -``` -Name: free-combo -Models: - 1. if/kimi-k2-thinking (no published token cap; provider limits may apply) - 2. qw/qwen3-coder-plus (no published token cap; provider limits may apply) - -Cost: currently listed as $0; terms and availability may change -``` - -
- -
-🔧 CLI Integration - -### Cursor IDE - -``` -Settings → Models → Advanced: - OpenAI API Base URL: http://localhost:20128/v1 - OpenAI API Key: [from OmniRoute dashboard] - Model: cc/claude-opus-4-7 -``` - -### Claude Code - -Use the **CLI Tools** page in the dashboard for one-click configuration, or edit `~/.claude/settings.json` manually. - -### Codex CLI - -```bash -export OPENAI_BASE_URL="http://localhost:20128" -export OPENAI_API_KEY="your-omniroute-api-key" - -codex "your prompt" -``` - -### OpenClaw - -**Option 1 — Dashboard (recommended):** - -``` -Dashboard → CLI Tools → OpenClaw → Select Model → Apply -``` - -**Option 2 — Manual:** Edit `~/.openclaw/openclaw.json`: - -```json -{ - "models": { - "providers": { - "omniroute": { - "baseUrl": "http://127.0.0.1:20128/v1", - "apiKey": "sk_omniroute", - "api": "openai-completions" - } - } - } -} -``` - -> **Note:** OpenClaw only works with local OmniRoute. Use `127.0.0.1` instead of `localhost` to avoid IPv6 resolution issues. - -### Cline / Continue / RooCode - -``` -Settings → API Configuration: - Provider: OpenAI Compatible - Base URL: http://localhost:20128/v1 - API Key: [from OmniRoute dashboard] - Model: if/kimi-k2-thinking -``` - -### OpenCode - -**Step 1:** Add OmniRoute as a custom provider: - -```bash -opencode -/connect -# Select "Other" → Enter ID: "omniroute" → Enter your OmniRoute API key -``` - -**Step 2:** Create/edit `opencode.json` in your project root: - -```json -{ - "$schema": "https://opencode.ai/config.json", - "provider": { - "omniroute": { - "npm": "@ai-sdk/openai-compatible", - "name": "OmniRoute", - "options": { - "baseURL": "http://localhost:20128/v1" - }, - "models": { - "cc/claude-sonnet-4-20250514": { "name": "Claude Sonnet 4" }, - "gg/gemini-2.5-pro": { "name": "Gemini 2.5 Pro" }, - "if/kimi-k2-thinking": { "name": "Kimi K2 (Free)" } - } - } - } -} -``` - -**Step 3:** Select the model in OpenCode: - -```bash -/models -# Select any OmniRoute model from the list -``` - -> **Tip:** Add any model available in your OmniRoute `/v1/models` endpoint to the `models` section. Use the format `provider/model-id` from your OmniRoute dashboard. - -
- ---- - -## Sorun Giderme - -
-Click to expand troubleshooting guide - -**"Language model did not provide messages"** - -- Provider quota exhausted → Check dashboard quota tracker -- Solution: Use combo fallback or switch to cheaper tier - -**Rate limiting** - -- Subscription quota out → Fallback to GLM/MiniMax -- Add combo: `cc/claude-opus-4-7 → glm/glm-4.7 → if/kimi-k2-thinking` - -**OAuth token expired** - -- Auto-refreshed by OmniRoute -- If issues persist: Dashboard → Provider → Reconnect - -**High costs** - -- Check usage stats in Dashboard → Costs -- Switch primary model to GLM/MiniMax - -**Dashboard/API ports are wrong** - -- `PORT` is the canonical base port (and API port by default) -- `API_PORT` overrides only OpenAI-compatible API listener -- `DASHBOARD_PORT` overrides only dashboard/Next.js listener -- Set `NEXT_PUBLIC_BASE_URL` to your dashboard/public URL (for OAuth callbacks) - -**Cloud sync errors** - -- Verify `BASE_URL` points to your running instance -- Verify `CLOUD_URL` points to your expected cloud endpoint -- Keep `NEXT_PUBLIC_*` values aligned with server-side values - -**First login not working** - -- Check `INITIAL_PASSWORD` in `.env` -- If unset, fallback password is `123456` - -**No request logs** - -- `call_logs` in SQLite stores summary metadata for the Request Logs table and analytics views -- Detailed request/response payloads are written to `DATA_DIR/call_logs/` as one JSON artifact per request -- Enable pipeline capture from Dashboard → Logs → Request Logs if you need detailed per-stage payloads -- `Export Logs` reads the artifact files on demand, while `Export All` includes the `call_logs/` directory alongside `storage.sqlite` -- Set `APP_LOG_TO_FILE=true` if you also want application console logs in `logs/application/app.log` -- Adjust `APP_LOG_MAX_FILE_SIZE`, `APP_LOG_RETENTION_DAYS`, `APP_LOG_MAX_FILES`, and `CALL_LOG_MAX_ENTRIES` as needed - -**Connection test shows "Invalid" for OpenAI-compatible providers** - -- Many providers don't expose a `/models` endpoint -- OmniRoute v1.0.6+ includes fallback validation via chat completions -- Ensure base URL includes `/v1` suffix - -### 🔐 OAuth on a Remote Server - - - - -> **⚠️ Important for users running OmniRoute on a VPS, Docker, or any remote server** - -The OAuth credentials bundled in OmniRoute are registered **for `localhost` only**. When you access OmniRoute on a remote server (e.g. `https://omniroute.myserver.com`), Google rejects the authentication with: - -``` -Error 400: redirect_uri_mismatch -``` - -#### Solution: Configure your own OAuth credentials - -You need to create an **OAuth 2.0 Client ID** in Google Cloud Console with your server's URI. - -#### Step-by-step - -**1. Open Google Cloud Console** - -Go to: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials) - -**2. Create a new OAuth 2.0 Client ID** - -- Click **"+ Create Credentials"** → **"OAuth client ID"** -- Application type: **"Web application"** -- Name: anything you like (e.g. `OmniRoute Remote`) - -**3. Add Authorized Redirect URIs** - -In the **"Authorized redirect URIs"** field, add: - -``` -https://your-server.com/callback -``` - -> Replace `your-server.com` with your server's domain or IP (include the port if needed, e.g. `http://45.33.32.156:20128/callback`). - -**4. Save and copy the credentials** - -After creating, Google will show the **Client ID** and **Client Secret**. - -**5. Set environment variables** - -In your `.env` (or Docker environment variables): - -```bash -# For Antigravity: -ANTIGRAVITY_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com -ANTIGRAVITY_OAUTH_CLIENT_SECRET=GOCSPX-your-secret - -GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com -GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-secret -``` - -**6. Restart OmniRoute** - -```bash -# npm: +# Nix flakes kullanarak +nix develop npm run dev -# Docker: -docker restart omniroute +# Veya devbox kullanarak +devbox run npm run dev ``` -**7. Try connecting again** +📖 [Docker Kılavuzu](docs/guides/DOCKER_GUIDE.md) — Compose profilleri, Caddy HTTPS, Cloudflare tünelleri. -Google will now redirect correctly to `https://your-server.com/callback`. - ---- - -#### Temporary workaround (without custom credentials) - -If you don't want to set up your own credentials right now, you can still use the **manual URL flow**: - -1. OmniRoute opens the Google authorization URL -2. After authorizing, Google tries to redirect to `localhost` (which fails on the remote server) -3. **Copy the full URL** from your browser's address bar (even if the page doesn't load) -4. Paste that URL into the field shown in the OmniRoute connection modal -5. Click **"Connect"** - -> This works because the authorization code in the URL is valid regardless of whether the redirect page loaded. - ---- - -## 🛠️ Tech Stack - -
-Click to expand tech stack details - -- **Runtime**: Node.js 18–22 LTS (⚠️ Node.js 24+ is **not supported** — `better-sqlite3` native binaries are incompatible) -- **Language**: TypeScript 5.9 — **100% TypeScript** across `src/` and `open-sse/` (zero `any` in core modules since v2.0) -- **Framework**: Next.js 16 + React 19 + Tailwind CSS 4 -- **Database**: better-sqlite3 (SQLite) + LowDB (JSON legacy) — domain state, proxy logs, MCP audit, routing decisions, memory, skills -- **Schemas**: Zod (MCP tool I/O validation, API contracts) -- **Protocols**: MCP (stdio/HTTP) + A2A v0.3 (JSON-RPC 2.0 + SSE) -- **Streaming**: Server-Sent Events (SSE) -- **Auth**: OAuth 2.0 (PKCE) + JWT + API Keys + MCP Scoped Authorization -- **Testing**: Node.js test runner + Vitest (900+ tests including unit, integration, E2E) -- **CI/CD**: GitHub Actions (auto npm publish + Docker Hub on release) -- **Website**: [omniroute.online](https://omniroute.online) -- **Package**: [npmjs.com/package/omniroute](https://www.npmjs.com/package/omniroute) -- **Docker**: [hub.docker.com/r/diegosouzapw/omniroute](https://hub.docker.com/r/diegosouzapw/omniroute) -- **Resilience**: Circuit breaker, exponential backoff, anti-thundering herd, TLS spoofing, auto-combo self-healing - -
- ---- - -## Belgeler - -| Document | Description | -| --------------------------------------------------------------------- | --------------------------------------------------- | -| [User Guide](docs/guides/USER_GUIDE.md) | Providers, combos, CLI integration, deployment | -| [API Reference](docs/reference/API_REFERENCE.md) | All endpoints with examples | -| [MCP Server](open-sse/mcp-server/README.md) | 107 MCP tools, IDE configs, Python/TS/Go clients | -| [A2A Server](src/lib/a2a/README.md) | JSON-RPC 2.0 protocol, skills, streaming, task mgmt | -| [Auto-Combo Engine](docs/routing/AUTO-COMBO.md) | 13-factor scoring, mode packs, self-healing | -| [Context Relay](docs/features/context-relay.md) | Session handoff strategy for account rotation | -| [Troubleshooting](docs/guides/TROUBLESHOOTING.md) | Common problems and solutions | -| [Architecture](docs/architecture/ARCHITECTURE.md) | System architecture and internals | -| [Codebase Documentation](docs/architecture/CODEBASE_DOCUMENTATION.md) | Beginner-friendly codebase walkthrough | -| [Uninstall Guide](docs/guides/UNINSTALL.md) | Clean removal for all install methods | -| [Environment Config](docs/reference/ENVIRONMENT.md) | Complete `.env` variables and references | -| [Contributing](CONTRIBUTING.md) | Development setup and guidelines | -| [OpenAPI Spec](docs/reference/openapi.yaml) | OpenAPI 3.0 specification | -| [Security Policy](SECURITY.md) | Vulnerability reporting and security practices | -| [VM Deployment](docs/ops/VM_DEPLOYMENT_GUIDE.md) | Complete guide: VM + nginx + Cloudflare setup | -| [Features Gallery](docs/guides/FEATURES.md) | Visual dashboard tour with screenshots | -| [Release Checklist](docs/ops/RELEASE_CHECKLIST.md) | Pre-release validation steps | - ---- - -## 🗺️ Roadmap - -OmniRoute has **218+ features planned** across multiple development phases. Here are the key areas: - -| Category | Planned Features | Highlights | -| ----------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------- | -| 🧠 **Routing & Intelligence** | 25+ | Lowest-latency routing, tag-based routing, quota preflight, quota-aware P2C, step-based combo routing | -| 🔒 **Security & Compliance** | 20+ | SSRF hardening, credential cloaking, rate-limit per endpoint, management key scoping | -| 📊 **Observability** | 15+ | OpenTelemetry integration, real-time quota monitoring, combo target health, cost tracking per model | -| 🔄 **Provider Integrations** | 20+ | Dynamic model registry, connection cooldowns, multi-account Codex, Copilot quota parsing | -| ⚡ **Performance** | 15+ | Dual cache layer, prompt cache, response cache, streaming keepalive, batch API | -| 🌐 **Ecosystem** | 10+ | WebSocket API, config hot-reload, distributed config store, commercial mode | - -### 🔜 Coming Soon - -- 🔗 **OpenCode Integration** — Native provider support for the OpenCode AI coding IDE -- 🔗 **TRAE Integration** — Full support for the TRAE AI development framework -- 📦 **Batch API** — Asynchronous batch processing for bulk requests -- 🎯 **Tag-Based Routing** — Route requests based on custom tags and metadata -- 💰 **Lowest-Cost Strategy** — Automatically select the cheapest available provider - -> 📝 Full feature specifications available in [`docs/new-features/`](docs/new-features/) (217 detailed specs) - ---- - -## 👥 Contributors - -[![Contributors](https://contrib.rocks/image?repo=diegosouzapw/OmniRoute&max=100&columns=20&anon=1)](https://github.com/diegosouzapw/OmniRoute/graphs/contributors) - -### How to Contribute - -1. Fork the repository -2. Create your feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request - -See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines. - -### Releasing a New Version +**🦭 Podman** ```bash -# Create a release — npm publish happens automatically -gh release create v2.0.0 --title "v2.0.0" --generate-notes +# 1. Bind-mount veri dizinini hazırlayın +mkdir -p data + +# 2. Yalnızca Linux + yerel rootless Podman (asla uzak Podman Machine istemcisi değil): +podman unshare chown 1000:1000 ./data + +# 3. Çalışma zamanı ipucunu ayarlayın, yerel Compose imajını derleyin ve başlatın +echo "CONTAINER_HOST=podman" >> .env +podman compose --profile base up -d --build ``` +macOS veya Windows üzerinde Podman uzak bir Podman Machine kullanır: `podman unshare` adımını atlayın ve +[topolojiye özel veri dizini rehberini](contrib/podman/README.md#data-directory-permissions-by-topology) izleyin. + +📖 [Podman Kılavuzu](contrib/podman/README.md) — Compose derlemeleri, Podman Machine ve +Linux/systemd Quadlet kurulumu. + +**⚡ Daha hızlı / daha hafif kurulum (yerel derlemeyi atlayın)** + +Yerel SQLite motoru (`better-sqlite3`) **isteğe bağlı** bir bağımlılıktır; bu nedenle genel bir +kurulum asla kaynak koddan derlemeyi beklemez: platformunuza/Node sürümünüze uygun önceden derlenmiş bir ikili dosya olduğunda onu kullanır, aksi takdirde şeffaf bir şekilde saf JS motoruna +(Node 22+ üzerinde `node:sqlite`, aksi halde paketlenmiş `sql.js` WASM) geri döner — derleme araçları gerekmez. + +Kurulum sonrası yerel ısınmayı tamamen atlamak için (CI, headless veya yavaş makineler): + +```bash +OMNIROUTE_SKIP_POSTINSTALL=1 npm install -g omniroute # CI=1 de bunu atlar +``` + +En hızlı kurulumlar için **pnpm** tercih edin (içerik adresli depolama + hard linkler — yukarıya bakın). +Panosuz, headless bir çalışma zamanı için Docker `base` profilini (yukarıda) veya +[Termux kılavuzunu](docs/guides/TERMUX_GUIDE.md) kullanın. CLI ve web panosu aynı port üzerinde +aynı süreç tarafından sunulur, bu nedenle bugün için ayrı bir yalnızca CLI paketi bulunmamaktadır. + +
+ +
+ +# 🎬 OmniRoute İş Başında + +
+ +## 📹 Video Rehberleri + +
+ +Sosyal medya verileri (2026-08-17) · YT: 741 | TT: 137 | IG: 124 · Tazelik (gün): YT 0 · TT 14 · IG 15 + + + + + + + + + +
+ + Instagram Reel +
+ 🎬 #1 — Instagram
+ nick_saraev — 1.628.910 görüntüleme +
+ + YouTube — Vaibhav Sisinty +
+ 🎬 #2 — YouTube
+ Vaibhav Sisinty — 373.084 görüntüleme +
+ + YouTube Shorts +
+ 🎬 #3 — YouTube Shorts
+ Nick Automates — 207.714 görüntüleme +
+ + TikTok Thumbnail +
+ 🎬 #4 — TikTok
+ milesreevesai — 620.400 görüntüleme +
+ + Valency Labs +
+ 🎬 #5 — YouTube
+ Valency Labs — 135.974 görüntüleme +
+ +
+ +**Tam sıralama (`v > 0`, en yüksek erişim):** + +| #1 | #2 | #3 | #4 | #5 | +| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| [nick_saraev — Instagram](https://www.instagram.com/reel/Da8ZthUPK98/) — **1.628.910** | [milesreevesai — TikTok](https://www.tiktok.com/@milesreevesai/video/7667980059189366019) — **620.400** | [Vaibhav Sisinty — YouTube](https://www.youtube.com/watch?v=QucgvbO5gsM) — **373.084** | [Nick Automates — YouTube Shorts](https://www.youtube.com/shorts/fZIBK_4fKq8) — **207.714** | [midudev — TikTok](https://www.tiktok.com/@midudev/video/7664636453544152342) — **177.800** | + +| #6 | #7 | #8 | #9 | #10 | +| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| [theopenstack — Instagram](https://www.instagram.com/reel/DaSs65mMrHk/) — **155.453** | [t.ghoush.ai — TikTok](https://www.tiktok.com/@t.ghoush.ai/video/7669497680527248656) — **152.800** | [Valency Labs — YouTube](https://www.youtube.com/watch?v=LkP6ocAoQkk) — **135.974** | [Asati — YouTube](https://www.youtube.com/watch?v=JjPtJcqwhqg) — **126.130** | [Vaibhav Sisinty — YouTube](https://www.youtube.com/watch?v=NuNDpeZYQ28) — **122.672** | + +Doğrulama metrikleri: 1002 takip edilen video · 7.069.190 bilinen görüntüleme · 595 profil/kanal · 13+ dil · 13+ içerik üreticisi. + +> 🎬 **OmniRoute hakkında bir video mu çektiniz?** Bağlantıyla birlikte bir [issue](https://github.com/diegosouzapw/OmniRoute/issues/new) veya [discussion](https://github.com/diegosouzapw/OmniRoute/discussions) açın — burada yer verelim. + +
+ +
+ +# 📧 Topluluk ve Yardım + +> Her şey tek bir yerde — geliştiriciyi takip edin, toplulukla sohbet edin veya bir issue açın. + +| Kanal | Nerede / Nasıl | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| 💼 **LinkedIn** — geliştiriciyi takip edin | [linkedin.com/in/diegosouzapw](https://www.linkedin.com/in/diegosouzapw/) | +| 🐙 **GitHub** — sürümler ve ipuçları için | [@diegosouzapw](https://github.com/diegosouzapw) | +| 💬 **Discord** | [discord.gg/U47eFqAXCn](https://discord.gg/U47eFqAXCn) | +| ✈️ **Telegram** | [t.me/omnirouteOficial](https://t.me/omnirouteOficial) | +| 🟢 **WhatsApp — 🌍 Global** | [gruba katılın](https://chat.whatsapp.com/JI7cDQ1GyaiDHhVBpLxf8b?mode=gi_t) | +| 🟢 **WhatsApp — 🇧🇷 Brezilya** | [gruba katılın](https://chat.whatsapp.com/LTSpdFhXTxjH4R6CCNiKWz) | +| 🌍 **Web Sitesi** | [omniroute.online](https://omniroute.online) | +| 📦 **Kaynak Kod** | [github.com/diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) | +| 🐛 **Hata Bildirimi** | [issue açın](https://github.com/diegosouzapw/OmniRoute/issues) — `npm run system-info` çıktısını ekleyin | +| 🤝 **Katkıda Bulunun** | [CONTRIBUTING.md](CONTRIBUTING.md) · [Dallanma ve Sürüm Modeli](docs/ops/BRANCHING_MODEL.md) · bir `good first issue` seçin | +| 💚 **Projeyi Destekleyin** | [Destekleme yolları ↑](#-omnirouteu-destekleyin) · [GitHub Sponsors](https://github.com/sponsors/diegosouzapw) | + +
+ --- -## 📊 Star History +
+ + + + + + + + + + + + + + + + + + + + + +
KatmanTeknoloji
Çalışma ZamanıNode.js 22.x / 24.x LTS — >=22.22.2 <23 || >=24.0.0 <27
DilTypeScript 6.0 — src/ ve open-sse/ genelinde %100 TypeScript (v2.0'dan bu yana çekirdekte sıfır any)
FrameworkNext.js 16 + React 19 + Tailwind CSS 4
Veritabanıbetter-sqlite3 (SQLite, WAL günlük kaydı) + LowDB (JSON eski) — 120 alan modülü, 159 migrasyon
BellekSQLite FTS5 tam metin + int8 nicelenmiş vektör embeddings, tipli sönümleme
ŞemalarZod 4 — MCP araç G/Ç doğrulaması + API sözleşmeleri
ProtokollerMCP (stdio / HTTP / SSE) + A2A v0.3 (JSON-RPC 2.0 + SSE)
Akış (Streaming)Server-Sent Events (SSE) + WebSocket köprüsü (/v1/ws)
Sıkıştırma12 motorlu işlem hattı — RTK, Caveman, LLMLingua-2 (MobileBERT ONNX), GCF, OmniGlyph
Kimlik Doğrulama & GüvenlikOAuth 2.0 (PKCE) + JWT + API Anahtarları + MCP kapsamlı yetkilendirme · Dinlenmede AES-256-GCM · DOMPurify
Gizlilik (Stealth)wreq-js — JA3 / JA4 TLS parmak izi taklidi, 3 seviyeli proxy
DayanıklılıkDevre kesici, üstel geri çekilme, sürü önleme (anti-thundering-herd), auto-combo kendi kendini iyileştirme
Günlük Kaydı (Logging)pino — istek bağlamına sahip yapılandırılmış JSON günlükleri
TestNode.js test runner + Vitest — 3.300'den fazla dosyada 25.000+ test senaryosu (birim, entegrasyon, E2E, güvenlik, ekosistem)
PlatformlarMasaüstü (Electron) · Android (Termux) · PWA (tüm tarayıcılar)
CI/CDGitHub Actions — sürümde otomatik npm yayını + Docker Hub
BağlantılarWeb Sitesi · npm · Docker Hub
+ +
+ +
+ +## 📖 Belgeler + +
+ +### 📘 Başlarken + + + + + + + + + +
BelgeAçıklama
Kullanıcı KılavuzuSağlayıcılar, kombolar, CLI entegrasyonu, dağıtım
Kurulum KılavuzuTam kurulum yöntemleri, CLI araç yapılandırmaları, protokol kurulumu, zaman aşımı ayarı
CLI Araçları KılavuzuClaude Code, Codex, Cursor, Cline, OpenClaw, Kilo, Copilot için araç bazında kurulum
Uzak ModKapsamlı erişim tokenlarıyla dizüstü bilgisayarınızın CLI'ından uzak bir OmniRoute'u (VPS) yönetin
Claude Code Yapılandırmasılaunch + model bazında profillerle Claude Code'u OmniRoute'a yönlendirin (yerel/uzak)
Hızlı Başlangıç3 adımda kurun → bağlayın → yapılandırın
+ +### 🔧 Operasyonlar ve Dağıtım + + + + + + + + + + + +
BelgeAçıklama
Docker KılavuzuDocker run, Compose profilleri, Caddy HTTPS, tüneller, imaj etiketleri
Podman KılavuzuQuadlet systemd entegrasyonu, podman-compose, SELinux
Sanal Makine (VM) DağıtımıEksiksiz kılavuz: Sanal makine + nginx + Cloudflare kurulumu
Fly.io DağıtımıKalıcı depolama ile Fly.io'ya dağıtım
Termux KılavuzuOmniRoute'u Android üzerinde Termux ile çalıştırın
PWA KılavuzuProgressive Web App kurulumu, önbelleğe alma, mimari
Kaldırma KılavuzuTüm kurulum yöntemleri için temiz kaldırma
Ortam YapılandırmasıEksiksiz .env değişkenleri ve referansları
+ +### 🧠 Özellikler ve Mimari + + + + + + + + + + + + + + + +
BelgeAçıklama
MimariSistem mimarisi, veri akışı ve dahili bileşenler
Sıkıştırma Kılavuzu7 seçenekli işlem hattı: off / lite / standard / aggressive / ultra / RTK / stacked
RTK SıkıştırmaKomut çıktısı sıkıştırma, filtreler, güven, doğrulama, ham çıktı kurtarma
Sıkıştırma MotorlarıCaveman, RTK, katmanlı işlem hatları, pano/API/MCP yüzeyleri
Sıkıştırma Kuralları FormatıCaveman ve RTK filtreleri için JSON kural paketi şemaları
Sıkıştırma Dil PaketleriDil algılama ve Caveman kural paketi yazımı
Dayanıklılık KılavuzuDevre kesiciler, bekleme süreleri, kuyruk, sürü önleme, TLS taklidi
Auto-Combo Motoru14 faktörlü puanlama, mod paketleri, kendi kendini iyileştirme
Proxy Kılavuzu3 seviyeli proxy sistemi, 1proxy pazarı, kayıt CRUD işlemleri
Ücretsiz Katmanlar90+ ücretsiz sağlayıcının birleştirilmiş dizini (42 belgelenmiş token havuzu / 495 model)
Özellikler GalerisiEkran görüntüleriyle görsel pano turu
Kod Tabanı BelgeleriYeni başlayanlar için kod tabanı incelemesi
+ +### 🤖 Protokoller ve API'ler + + + + + + + + + +
BelgeAçıklama
API ReferansıÖrneklerle tüm uç noktalar
OpenAPI ŞartnamesiOpenAPI 3.0 şartnamesi
MCP Sunucusu109 MCP aracı, IDE yapılandırmaları, Python/TS/Go istemcileri
MCP Sunucu KılavuzuMCP kurulumu, taşımalar ve araç referansı
A2A SunucusuJSON-RPC 2.0 protokolü, yetenekler, akış, görev yönetimi
A2A Sunucu KılavuzuA2A ajan kartı, görevler, yetenekler ve akış
+ +### 📋 Proje ve Kalite + + + + + + + + + + +
BelgeAçıklama
Katkıda BulunmaGeliştirme kurulumu ve yönergeleri
Dallanma ve Sürüm ModeliPR'ların nereyi hedeflediği (release/*), main ve etiketlerin anlamı
Değişiklik Günlüğü (Changelog)Sürüm bazında tam yayın geçmişi
Güvenlik PolitikasıGüvenlik açığı bildirme ve güvenlik uygulamaları
i18n Kılavuzu43 dil desteği, çeviri iş akışı, RTL
Sürüm Kontrol ListesiSürüm öncesi doğrulama adımları
Test Kapsam PlanıTest kapsamı stratejisi ve 25.000+ test paketi
+ +
+ +
+ +# ⭐ Öne Çıkan Katkıda Bulunanlar + +> OmniRoute tutkulu bir açık kaynak topluluğu tarafından şekillendirilmektedir. Bu kişiler, projenin kalitesini, kararlılığını ve erişimini doğrudan etkileyen olağanüstü katkılarda bulunmuşlardır. **Teşekkür ederiz.** + + + + + + + + + + + + + + + + +
+ + oyi77
+ oyi77 +

+ 🥇 213 commit • +114K satır
+ Analitik motoru, SQL toplamaları,
proxy pazarı, test kapsamı
+
+ + R.D. & Randi
+ R.D. & Randi +

+ 🥈 108 commit • +38K satır
+ Uç noktalar sayfası, tünel entegrasyonları,
Docker iş akışları, A2A durumu, sıkıştırma arayüzü
+
+ + Chris Staley
+ Chris Staley +

+ 🥉 70 commit • +1.8K satır
+ SSE akış güçlendirmesi, Responses API,
Gemini sayfalama, test regresyon düzeltmeleri
+
+ + zenobit
+ zenobit +

+ 🏅 62 commit • +22K satır
+ CI/CD hattı, 33 dil için i18n,
Void Linux paketi, platform düzeltmeleri
+
+ + Jan Leon
+ Jan Leon +

+ 🏅 58 commit • +22K satır
+ Reasoning-effort yönlendirmesi, proxy kontrolleri,
kota görünürlüğü, Live Zone sıkıştırması
+
+ + backryun
+ backryun +

+ 🏅 53 commit • +70K satır
+ Sağlayıcı kataloğu düzenleme — Perplexity, Kimi,
Cerebras, Copilot, LMArena güncellemeleri
+
+ + Chirag Singhal
+ Chirag Singhal +

+ 🏅 46 commit • +4.8K satır
+ Hata temizleme, MITM prefill düzeltmesi,
fusion hakemi, devre kesici/429 doğruluğu
+
+ + kfiramar
+ kfiramar +

+ 🏅 38 commit • +1.7K satır
+ Codex websocket + doğrudan geçiş, yetkilendirme/karşılama,
Electron güçlendirme, DB migrasyonları
+
+ + Benson K B
+ Benson K B +

+ 🏅 28 commit • +9.2K satır
+ Electron masaüstü uygulaması, otomatik güncelleyici,
sürüm derleme iş akışları, platformlar arası CI
+
+ + Hernan J. Ardila
+ Hernan J. Ardila +

+ 🏅 25 commit • +174K satır
+ Sıfır gecikmeli kombolar, vision-bridge otomatik yönlendirmesi,
katalog bağlam uzunluğu, dayanıklılık 429 ipuçları
+
+ +> 🙏 Bu katkıda bulunanların sunduğu özellikler, hata düzeltmeleri ve altyapı iyileştirmeleri, OmniRoute'u güvenilir ve zengin özelliklere sahip kılan temel unsurlardır. Her pull request, her test senaryosu ve her i18n çeviri dosyası değerlidir. Açık kaynak onlar gibi insanlar tarafından inşa edilir. + +
+ +--- + +
+ +## 💖 Sponsorlar + +
+ +
+ +
+ +## 👥 320+ Katkıda Bulunan + +
+ +[![Contributors](https://contrib.rocks/image?repo=diegosouzapw/OmniRoute&max=400&columns=20&anon=1)](https://github.com/diegosouzapw/OmniRoute/graphs/contributors) + +### Nasıl Katkıda Bulunulur + +1. Depoyu forklayın +2. **Aktif** `release/vX.Y.Z` dalından (`main` değil) bir dal oluşturun — bkz. [Dallanma ve Sürüm Modeli](docs/ops/BRANCHING_MODEL.md) +3. Özellik dalınızı oluşturun (`git checkout -b feat/harika-ozellik`) +4. Değişikliklerinizi commit edin (`git commit -m 'feat: harika ozellik ekle'`) +5. Dalınıza push edin (`git push origin feat/harika-ozellik`) +6. **Hedef dal = ilgili `release/vX.Y.Z` dalı** olacak şekilde bir Pull Request açın + +Ayrıntılı yönergeler için [CONTRIBUTING.md](CONTRIBUTING.md) dosyasına bakın. + +### Yeni Bir Sürüm Yayımlama + +```bash +# Bir sürüm oluşturun — npm yayını otomatik olarak gerçekleşir +gh release create v3.8.2 --title "v3.8.2" --generate-notes +``` + +
+ +
+ +## 📊 Yıldızlar + + - - - Star History Chart + + + Star History Chart +
+ + -## 🙏 Acknowledgments +
-Special thanks to **[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)** — the original Go implementation that inspired this JavaScript port. +
---- +## 🙏 Teşekkürler -## Lisans +
-MIT License - see [LICENSE](LICENSE) for details. +OmniRoute devlerin omuzlarında yükselmektedir. **[9router](https://github.com/decolua/9router)** projesinin bir çatalı ve Go projesi **[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)**'nin bir TypeScript uyarlaması olarak başladı — ve oradan itibaren aşağıdaki her alt sistem, oraya ilk ulaşan bir açık kaynak projesinden ilham aldı. Her biri OmniRoute'un somut bir parçasını şekillendirdi. Hepsine teşekkür ederiz. 🙏 + +> ⭐ Temmuz 2026 itibarıyla yıldız sayıları — bu projelere de bir yıldız verin. + +### 🧬 Köken ve ağ geçidi + + + + + + +
ProjeOmniRoute'a nasıl ilham verdi
9router22.7kBu çatalın üzerine inşa edildiği orijinal proje — çok modlu API'ler ve tam bir TypeScript yeniden yazımı ile burada genişletildi.
CLIProxyAPI43.6kBu JavaScript / TypeScript uyarlamasına ilham veren Go uygulaması.
LiteLLM54.0kKamuya açık fiyatlandırma veri seti maliyet takibi senkronizasyonumuzu besleyen ve sağlayıcı normalizasyon modeli yönlendirmemize rehberlik eden yapay zeka ağ geçidi.
+ +### 🗜️ Bağlam ve token sıkıştırması — motorlar + + + + + + + + + + +
ProjeOmniRoute'a nasıl ilham verdi
Caveman90.8kViral olan "az token işi görüyorsa neden çok token kullanasınız" projesi — mağara adamı dili felsefesi standart sıkıştırma modumuza ve 30'dan fazla dolgu/yoğunlaştırma kuralımıza güç verir.
RTK – Rust Token Killer71.8kYüksek performanslı komut çıktısı sıkıştırması — RTK motorumuza, JSON filtre DSL'imize, ham çıktı kurtarmaya ve katmanlı RTK → Caveman işlem hattına ilham verdi.
headroom60.1kGeri döndürülebilir bağlam sıkıştırması (SmartCrusher) — headroom motorumuza ve ccr geri getirme işaretçisi modeline ilham verdi.
LLMLingua6.5kİstem sıkıştırma araştırması (LLMLingua / LLMLingua-2) — asenkron, kod güvenli, başarısızlık durumunda açık (fail-open) llmlingua motorumuza ilham verdi.
llmlingua-2-js30LLMLingua motorumuz için çalışan iş parçacığı (worker-thread) arka ucu olarak kullanılan JS/ONNX portu (MobileBERT / XLM-RoBERTa).
Troglodita26PT-BR token sıkıştırması — pt-BR dil paketimize güç verir: Brezilya Portekizcesi gramerine göre ayarlanmış anlatım bozukluğu azaltma ve dolgu sözcük temizleme.
ponytail86.0kViral olan "tembel kıdemli geliştirici" YAGNI kodlama yeteneği — less-code Çıktı Stilimize ilham verdi: _üretilen_ kodu azaltan çalışan en küçük değişiklik yönlendirmesi (Caveman'in kısa düz yazısının çıktı eksenindeki eşleniği).
+ +### 🧩 Kompakt formatlar, token araştırmaları ve kod duyarlı araçlar + + + + + + + + + + + + + + + +
ProjeOmniRoute'a nasıl ilham verdi
TOON24.9kToken-Oriented Object Notation — sütunlu, başlık artı satırlar modeli tablosal sıkıştırma aşamamızı şekillendirdi.
GCF – Graph Compact Format22İlk olarak tablosal sıkıştırma aşamamıza ilham verdi; artık sıfır bağımlılıklı, kayıpsız genel profil kodlayıcısı doğrudan Headroom codec'i olarak yerleşik olarak (vendored) sunulmaktadır (MIT, SPDX işaretli), daha sonraki sayısal alan ve sayı uyumsuzluğu doğruluk düzeltmeleriyle birlikte.
token-optimizer-mcp444Brotli/SQLite önbelleği + oturum başına bağlam deltası — session-dedup motorumuza ilham verdi.
token-savior1.1kBash çıktısı sıkıştırması + MCP profilleri — sıkıştırmadan vazgeçme disiplinimize ve MCP araç bildirimi küçültmesine ilham verdi.
token-saver117Hata duyarlı vazgeçme özelliğine sahip içerik duyarlı, dosya türü başına çıktı sıkıştırması — tür başına dağıtımımızı ve minimum kazanç atlamamızı doğruladı.
token-optimizer1.7k"Hayalet tokenları bulun" — boşaltma + kurtarılabilir tanıtıcı modeli CCR boşaltma düşüncemizi besledi.
TokenMizer16Oturum grafiği + turlar arası satır tekilleştirme taslağı session-dedup tasarımımızı besledi.
OmniCompress3Rust sütunlu JSON + içerik adresli geri getirme + mesajlar arası tekilleştirme — headroom/ccr/session-dedup motor tasarımımızı ve önbellek kararlı "sıkıştırılmış form konumdan bağımsızdır" kuralımızı doğruladı.
mcp-compressor98MCP araç şeması/açıklama sıkıştırması — MCP araç bildirimi kardinalite azaltmamızı besledi.
RepoMapper187Aider tarzı depo haritası sıralaması — depo haritası / getirme sıralaması keşfimizi besledi.
quiet-shell-mcp4MCP üzerinden bildirimsel kabuk çıktısı azaltma — bildirimsel bash çıktısı sıkıştırmamızı doğruladı.
ts-morph6.1kTypeScript Compiler API araç seti — dize, şablon ve regex sabit değerlerini koruyan ayrıştırıcı tabanlı yorum satırı temizlememize ilham verdi.
+ +### 🧠 Bellek ve RAG + + + + + + +
ProjeOmniRoute'a nasıl ilham verdi
Mem061.2kEvrensel bellek katmanı — yazma/okuma sınırı olarak proxy modeli bellek mimarimizi şekillendirdi.
Letta (MemGPT)23.9kKademeli belleğe sahip durum bilgisi tutan ajanlar — Bağlam Kontrolü ve Kurtarma (CCR) kademeli modelimize ilham verdi.
WFGY1.8k16 tekrarlayan RAG/LLM hata modunun ProblemMap sınıflandırması — sorun giderme kılavuzumuzdaki paylaşılan terminoloji.
+ +### 🛰️ Trafik denetimi, MITM ve şeffaf proxy + + + + + +
ProjeOmniRoute'a nasıl ilham verdi
llm-interceptor49Kodlama asistanı ↔ LLM trafiğinin MITM yakalaması/analizi — Trafik Denetçimiz bunun SSE birleştirmesini, konuşma normalizasyonunu, ana bilgisayar doğrudan geçişini ve sır maskelemesini uyarladı (MIT).
ProxyBridge5.5kSüreç bazında şeffaf proxy yönlendirmesi — çökmeye dayanıklı MITM kapatma, soket boşta kalma zaman aşımları, /proc süreç atıfı ve TPROXY yakalamamıza ilham verdi.
+ +### 📚 Model verileri, gözlemlenebilirlik ve arayüz + + + + + + + + + +
ProjeOmniRoute'a nasıl ilham verdi
models.dev6.0kYapay zeka modeli özellikleri, fiyatlandırması ve yeteneklerinin açık veritabanı — model kataloğumuzla yerel olarak senkronize edilir.
React Flow / xyflow37.7kGerçek zamanlı Sıkıştırma Stüdyomuzu ve Kombo/Yönlendirme Stüdyomuzu destekleyen düğüm tabanlı grafik kütüphanesi.
LangGraph37.6kLangGraph Studio'nun canlı iş akışı grafiği görselleştirmesi, Stüdyolarımızın gerçek zamanlı basamaklı görünümüne ilham verdi.
Langfuse31.4kİzleme → aralık → üretim gözlemlenebilirlik modeli Sıkıştırma Stüdyosu şelale görünümümüzü şekillendirdi.
Kiali3.6kIstio servis ağı (service-mesh) gözlemlenebilirliği — Yönlendirme/Kombo Stüdyosundaki devre kesici rozetlerimize ve hata kenarı görsellerimize ilham verdi.
lobe-icons2.2kPanomuz genelinde sağlayıcı simgelerini oluşturan yapay zeka/LLM marka logoları.
+ +### 🛡️ Güvenlik + + + + +
ProjeOmniRoute'a nasıl ilham verdi
awesome-secure-defaults710Güvenlik tercihlerimize rehberlik eden, varsayılan olarak güvenli kütüphanelerin derlenmiş listesi (Helmet.js, DOMPurify, ssrf-req-filter, safe-regex, Google Tink).
+ +### 🧭 Tamamlayıcı araçlar + + + +
ProjeOmniRoute'a nasıl ilham verdi
+ +## 📄 Lisans + +MIT Lisansı - ayrıntılar için [LICENSE](LICENSE) dosyasına bakın. ---
- Built with ❤️ for developers who code 24/7 -
- omniroute.online + +**[⬆ Başa dön](#-omniroute--ücretsiz-ai-ağ-geçidi)** · Açık kaynaklı yapay zeka topluluğu için ❤️ ile geliştirildi. + +OmniRoute v3.8.49 · Node ≥22.22.2 · MIT Lisansı · omniroute.online +
diff --git a/docs/i18n/tr/SECURITY.md b/docs/i18n/tr/SECURITY.md index b9260fd17b..a87df8298d 100644 --- a/docs/i18n/tr/SECURITY.md +++ b/docs/i18n/tr/SECURITY.md @@ -1,159 +1,184 @@ -# Security Policy (Türkçe) +# Güvenlik Politikası (Türkçe) 🌐 **Languages:** 🇺🇸 [English](../../../SECURITY.md) · 🇸🇦 [ar](../ar/SECURITY.md) · 🇧🇬 [bg](../bg/SECURITY.md) · 🇧🇩 [bn](../bn/SECURITY.md) · 🇨🇿 [cs](../cs/SECURITY.md) · 🇩🇰 [da](../da/SECURITY.md) · 🇩🇪 [de](../de/SECURITY.md) · 🇪🇸 [es](../es/SECURITY.md) · 🇮🇷 [fa](../fa/SECURITY.md) · 🇫🇮 [fi](../fi/SECURITY.md) · 🇫🇷 [fr](../fr/SECURITY.md) · 🇮🇳 [gu](../gu/SECURITY.md) · 🇮🇱 [he](../he/SECURITY.md) · 🇮🇳 [hi](../hi/SECURITY.md) · 🇭🇺 [hu](../hu/SECURITY.md) · 🇮🇩 [id](../id/SECURITY.md) · 🇮🇹 [it](../it/SECURITY.md) · 🇯🇵 [ja](../ja/SECURITY.md) · 🇰🇷 [ko](../ko/SECURITY.md) · 🇮🇳 [mr](../mr/SECURITY.md) · 🇲🇾 [ms](../ms/SECURITY.md) · 🇳🇱 [nl](../nl/SECURITY.md) · 🇳🇴 [no](../no/SECURITY.md) · 🇵🇭 [phi](../phi/SECURITY.md) · 🇵🇱 [pl](../pl/SECURITY.md) · 🇵🇹 [pt](../pt/SECURITY.md) · 🇧🇷 [pt-BR](../pt-BR/SECURITY.md) · 🇷🇴 [ro](../ro/SECURITY.md) · 🇷🇺 [ru](../ru/SECURITY.md) · 🇸🇰 [sk](../sk/SECURITY.md) · 🇸🇪 [sv](../sv/SECURITY.md) · 🇰🇪 [sw](../sw/SECURITY.md) · 🇮🇳 [ta](../ta/SECURITY.md) · 🇮🇳 [te](../te/SECURITY.md) · 🇹🇭 [th](../th/SECURITY.md) · 🇹🇷 [tr](../tr/SECURITY.md) · 🇺🇦 [uk-UA](../uk-UA/SECURITY.md) · 🇵🇰 [ur](../ur/SECURITY.md) · 🇻🇳 [vi](../vi/SECURITY.md) · 🇨🇳 [zh-CN](../zh-CN/SECURITY.md) --- -## Reporting Vulnerabilities +## Güvenlik Açıklarını Bildirme -If you discover a security vulnerability in OmniRoute, please report it responsibly: +OmniRoute'ta bir güvenlik açığı keşfederseniz, lütfen sorumlu bir şekilde bildirin: -1. **DO NOT** open a public GitHub issue -2. Use [GitHub Security Advisories](https://github.com/diegosouzapw/OmniRoute/security/advisories/new) -3. Include: description, reproduction steps, and potential impact +1. **KESİNLİKLE** herkese açık bir GitHub issue'su açmayın +2. [GitHub Security Advisories](https://github.com/diegosouzapw/OmniRoute/security/advisories/new) kullanın +3. Şunları ekleyin: açıklama, yeniden oluşturma adımları ve olası etki -## Response Timeline +## Yanıt Zaman Çizelgesi -| Stage | Target | -| ------------------- | --------------------------- | -| Acknowledgment | 48 hours | -| Triage & Assessment | 5 business days | -| Patch Release | 14 business days (critical) | +| Aşama | Hedef Süre | +| --------------------- | --------------------------- | +| İlk Bildirim Teyidi | 48 saat | +| Ön İnceleme ve Değerlendirme | 5 iş günü | +| Yama Sürümü (Patch) | 14 iş günü (kritik) | -## Supported Versions +## Desteklenen Sürümler -| Version | Support Status | +| Sürüm | Destek Durumu | | ------- | -------------- | -| 3.6.x | ✅ Active | -| 3.5.x | ✅ Security | -| < 3.5.0 | ❌ Unsupported | +| 3.8.x | ✅ Aktif | +| 3.7.x | ✅ Güvenlik | +| < 3.7.0 | ❌ Desteklenmiyor | --- -## Security Architecture +## Güvenlik Mimarisi -OmniRoute implements a multi-layered security model: +OmniRoute çok katmanlı bir güvenlik modeli uygular: ``` -Request → CORS → API Key Auth → Prompt Injection Guard → Input Sanitizer → Rate Limiter → Circuit Breaker → Provider +Request → CORS → Authz pipeline (classify → policies → enforce) + → Guardrails (PII masker, prompt injection, vision bridge) + → Rate Limiter → Circuit Breaker → Cooldown → Model Lockout → Provider ``` -### 🔐 Authentication & Authorization +### 🔐 Kimlik Doğrulama ve Yetkilendirme -| Feature | Implementation | -| -------------------- | ---------------------------------------------------------- | -| **Dashboard Login** | Password-based auth with JWT tokens (HttpOnly cookies) | -| **API Key Auth** | HMAC-signed keys with CRC validation | -| **OAuth 2.0 + PKCE** | Secure provider auth (Claude, Codex, Gemini, Cursor, etc.) | -| **Token Refresh** | Automatic OAuth token refresh before expiry | -| **Secure Cookies** | `AUTH_COOKIE_SECURE=true` for HTTPS environments | -| **MCP Scopes** | 32 granular scopes for MCP tool access control | +| Özellik | Uygulama | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| **Pano Girişi** | JWT belirteçleri ile parola tabanlı kimlik doğrulama (HttpOnly çerezler) | +| **API Anahtarı Doğrulaması** | CRC doğrulamalı HMAC imzalı anahtarlar | +| **OAuth 2.0 + PKCE** | Sağlayıcıya özel tarayıcı/cihaz OAuth'u desteklenen yerlerde PKCE kullanır; yalnızca içe aktarılan Devin kimlik bilgileri ayrı işlenir. | +| **Belirteç Yenileme** | Süresi dolmadan önce otomatik OAuth belirteci yenileme | +| **Güvenli Çerezler** | HTTPS ortamları için `AUTH_COOKIE_SECURE=true` | +| **Yetkilendirme Hattı** | Rota sınıflandırması (PUBLIC / CLIENT_API / MANAGEMENT) — bkz. `docs/architecture/AUTHZ_GUIDE.md` | +| **Rota Koruma Katmanları** | Yönetim rotaları için 3 katmanlı model (LOCAL_ONLY / ALWAYS_PROTECTED / MANAGEMENT) — bkz. `docs/security/ROUTE_GUARD_TIERS.md` | +| **Yönetim Kapsamlı MCP** | `manage` kapsamına sahip API anahtarlarıyla korunan uzak `/api/mcp/*` erişimi; `/api/cli-tools/runtime/*` katı yerel döngüde kalır. | +| **MCP Kapsamları** | 32 ayrıntılı kapsam (read:health, write:combos, execute:completions vb.) — bkz. `docs/frameworks/MCP-SERVER.md` | -### 🛡️ Encryption at Rest +### 🛡️ Dinlenmede Şifreleme (Encryption at Rest) -All sensitive data stored in SQLite is encrypted using **AES-256-GCM** with scrypt key derivation: +SQLite'ta saklanan tüm hassas veriler, scrypt anahtar türetme ile **AES-256-GCM** kullanılarak şifrelenir: -- API keys, access tokens, refresh tokens, and ID tokens -- Versioned format: `enc:v1:::` -- Passthrough mode (plaintext) when `STORAGE_ENCRYPTION_KEY` is not set +- API anahtarları, erişim belirteçleri, yenileme belirteçleri ve ID belirteçleri +- Sürümlendirilmiş format: `enc:v1:::` +- `STORAGE_ENCRYPTION_KEY` ayarlanmadığında doğrudan geçiş modu (düz metin) ```bash -# Generate encryption key: +# Şifreleme anahtarı oluşturun: STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32) ``` -### 🧠 Prompt Injection Guard +### 🛡️ Güvenlik Önlemleri Çerçevesi (Guardrails Framework) -Middleware that detects and blocks prompt injection attacks in LLM requests: +OmniRoute, öncelik sırasına göre sıralanmış 3 yerleşik güvenlik önlemi içeren, çalışırken yeniden yüklenebilir bir **güvenlik önlemleri kayıt defteri** (`src/lib/guardrails/`) ile gelir: -| Pattern Type | Severity | Example | -| ------------------- | -------- | ---------------------------------------------- | -| System Override | High | "ignore all previous instructions" | -| Role Hijack | High | "you are now DAN, you can do anything" | -| Delimiter Injection | Medium | Encoded separators to break context boundaries | -| DAN/Jailbreak | High | Known jailbreak prompt patterns | -| Instruction Leak | Medium | "show me your system prompt" | +| Güvenlik Önlemi | Öncelik | Amaç | +| ------------------ | ------- | --------------------------------------------------------------------------------------- | +| `vision-bridge` | 5 | Vision desteği olmayan modelleri görüntü açıklamalarıyla destekler; görsel URL'leri için SSRF koruması sağlar | +| `pii-masker` | 10 | Çağrı öncesi ve sonrası PII (kişisel veri) maskeleme (e-posta, telefon, CPF, CNPJ, kredi kartı, SSN) | +| `prompt-injection` | 20 | Geçersiz kılma / rol ele geçirme / jailbreak / sızıntı kalıplarını algılar | -Configure via dashboard (Settings → Security) or `.env`: +Özel güvenlik önlemleri `registerGuardrail(new MyGuardrail())` aracılığıyla kaydedilir. Model hata durumunda açıktır (fail-open; istisnalar trafiği asla engellemez). İstek başına devre dışı bırakma `x-omniroute-disabled-guardrails` başlığı ile yapılır. → Bkz. [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md). + +### 🧠 İstem Enjeksiyonu Koruması (Prompt Injection Guard) + +LLM isteklerindeki istem enjeksiyonu modellerini algılayan en iyi çaba (heuristic) ara yazılımıdır. +**Eksiksiz bir istem enjeksiyonu güvenlik duvarı değildir** — yanlış pozitifler (zararsız +persona/RPG istemleri) ve yanlış negatifler (leetspeak, boşluk manipülasyonu, İngilizce dışı kalıplar) üretebilir. + +| Kalıp Türü | Önem Derecesi | Örnek | +| ------------------- | ------------- | ---------------------------------------------- | +| Sistem Geçersiz Kılma | Yüksek (High) | "ignore all previous instructions" | +| Rol Ele Geçirme | Orta (Medium) | "you are now DAN, you can do anything" | +| Ayırıcı Enjeksiyonu | Yüksek (High) | Bağlam sınırlarını kırmak için kodlanmış ayırıcılar | +| DAN / Jailbreak | Orta (Medium) | Bilinen jailbreak istem kalıpları | +| Talimat Sızıntısı | Yüksek (High) | "show me your system prompt" | +| Kodlama Kaçırma | Orta (Medium) | base64/rot13/hex kod çözme + talimat anahtar kelimeleri | + +`block` modunda yalnızca **High (Yüksek)** önem derecesindeki tespitler engellenir. Orta önem derecesindeki +aileler günlüğe kaydedilir ancak `sanitizeRequest` tarafından asla engellenmez. + +Pano (Ayarlar → Güvenlik) veya `.env` üzerinden yapılandırın: ```env INPUT_SANITIZER_ENABLED=true -INPUT_SANITIZER_MODE=block # warn | block | redact +INPUT_SANITIZER_MODE=block # warn | block (enjeksiyon politikası; eski "redact" modu enjeksiyon metnini silmez) +INPUT_SANITIZER_BLOCK_THRESHOLD=high # high (varsayılan) | medium | low — block modunda bu seviye ve üstü engellenir ``` -### 🔒 PII Redaction +### 🔒 PII (Kişisel Veri) Maskeleme -Automatic detection and optional redaction of personally identifiable information: +Kişisel olarak tanımlanabilir bilgilerin otomatik olarak algılanması ve isteğe bağlı olarak maskelenmesi: -| PII Type | Pattern | Replacement | +| PII Türü | Kalıp | Değiştirilen Değer | | ------------- | --------------------- | ------------------ | -| Email | `user@domain.com` | `[EMAIL_REDACTED]` | -| CPF (Brazil) | `123.456.789-00` | `[CPF_REDACTED]` | -| CNPJ (Brazil) | `12.345.678/0001-00` | `[CNPJ_REDACTED]` | -| Credit Card | `4111-1111-1111-1111` | `[CC_REDACTED]` | -| Phone | `+55 11 99999-9999` | `[PHONE_REDACTED]` | -| SSN (US) | `123-45-6789` | `[SSN_REDACTED]` | +| E-posta | `user@domain.com` | `[EMAIL_REDACTED]` | +| CPF (Brezilya)| `123.456.789-00` | `[CPF_REDACTED]` | +| CNPJ (Brezilya)| `12.345.678/0001-00` | `[CNPJ_REDACTED]` | +| Kredi Kartı | `4111-1111-1111-1111` | `[CC_REDACTED]` | +| Telefon | `+55 11 99999-9999` | `[PHONE_REDACTED]` | +| SSN (ABD) | `123-45-6789` | `[SSN_REDACTED]` | ```env -PII_REDACTION_ENABLED=true +PII_REDACTION_ENABLED=true # istek PII yeniden yazımı; INPUT_SANITIZER_MODE'dan bağımsızdır +PII_RESPONSE_SANITIZATION=true # isteğe bağlı: istemcilere döndürülen sağlayıcı yanıtlarındaki PII'yi maskeler ``` -### 🌐 Network Security +### 🌐 Ağ Güvenliği -| Feature | Description | -| ------------------------ | ---------------------------------------------------------------- | -| **CORS** | Configurable origin control (`CORS_ORIGIN` env var, default `*`) | -| **IP Filtering** | Allowlist/blocklist IP ranges in dashboard | -| **Rate Limiting** | Per-provider rate limits with automatic backoff | -| **Anti-Thundering Herd** | Mutex + per-connection locking prevents cascading 502s | -| **TLS Fingerprint** | Browser-like TLS fingerprint spoofing to reduce bot detection | -| **CLI Fingerprint** | Per-provider header/body ordering to match native CLI signatures | +| Özellik | Açıklama | +| ------------------------ | ------------------------------------------------------------------------------ | +| **CORS** | Açık kaynaklar arası izin listesi (`CORS_ALLOWED_ORIGINS`; eski `CORS_ORIGIN`) | +| **IP Filtreleme** | Panoda IP aralıklarını izin listesine / engelleme listesine alma | +| **Hız Sınırlaması** | Otomatik geri çekilme ile sağlayıcı başına hız sınırları | +| **Sürü Önleme (Anti-Thundering Herd)** | Mutex + bağlantı başına kilitleme ile basamaklı 502 hatalarını önler | +| **TLS Parmak İzi** | Bot algılamasını azaltmak için tarayıcı benzeri TLS parmak izi taklidi | +| **CLI Parmak İzi** | Yerel CLI imzalarıyla eşleşmesi için sağlayıcı başına başlık/gövde sıralaması | -### 🔌 Resilience & Availability +### 🔌 Dayanıklılık ve Erişilebilirlik -| Feature | Description | +| Özellik | Açıklama | | ----------------------- | ------------------------------------------------------------------ | -| **Circuit Breaker** | 3-state (Closed → Open → Half-Open) per provider, SQLite-persisted | -| **Request Idempotency** | 5-second dedup window for duplicate requests | -| **Exponential Backoff** | Automatic retry with increasing delays | -| **Health Dashboard** | Real-time provider health monitoring | +| **Devre Kesici (Circuit Breaker)** | Sağlayıcı başına 3 durumlu (Kapalı → Açık → Yarı Açık), SQLite ile kalıcı | +| **İstek Tekilleştirme** | Yinelenen istekler için 5 saniyelik tekilleştirme penceresi | +| **Üstel Geri Çekilme** | Artan gecikmelerle otomatik yeniden deneme | +| **Sağlık Panosu** | Gerçek zamanlı sağlayıcı sağlığı izleme | -### 📋 Compliance +### 📋 Uyumluluk (Compliance) -| Feature | Description | +| Özellik | Açıklama | | ------------------ | ----------------------------------------------------------- | -| **Log Retention** | Automatic cleanup after `CALL_LOG_RETENTION_DAYS` | -| **No-Log Opt-out** | Per API key `noLog` flag disables request logging | -| **Audit Log** | Administrative actions tracked in `audit_log` table | -| **MCP Audit** | SQLite-backed audit logging for all MCP tool calls | -| **Zod Validation** | All API inputs validated with Zod v4 schemas at module load | +| **Günlük Saklama** | `CALL_LOG_RETENTION_DAYS` sonrasında otomatik temizleme | +| **Günlük Tutmama Tercihi** | API anahtarı başına `noLog` bayrağı istek kaydını devre dışı bırakır | +| **Denetim Günlüğü**| `audit_log` tablosunda izlenen yönetim eylemleri | +| **MCP Denetimi** | Tüm MCP araç çağrıları için SQLite tabanlı denetim kaydı | +| **Zod Doğrulaması**| Modül yükleme sırasında Zod v4 şemalarıyla doğrulanan tüm API girdileri | --- -## Required Environment Variables +## Gerekli Ortam Değişkenleri -All secrets must be set before starting the server. The server will **fail fast** if they are missing or weak. +Sunucuyu başlatmadan önce tüm gizli anahtarlar ayarlanmalıdır. Eksik veya zayıf olmaları durumunda sunucu **hızlı bir şekilde hata vererek (fail fast)** durur. ```bash -# REQUIRED — server will not start without these: -JWT_SECRET=$(openssl rand -base64 48) # min 32 chars -API_KEY_SECRET=$(openssl rand -hex 32) # min 16 chars +# GEREKLİ — sunucu bunlar olmadan başlamaz: +JWT_SECRET=$(openssl rand -base64 48) # min 32 karakter +API_KEY_SECRET=$(openssl rand -hex 32) # min 16 karakter -# RECOMMENDED — enables encryption at rest: +# ÖNERİLEN — dinlenmede şifrelemeyi etkinleştirir: STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 32) ``` -The server actively rejects known-weak values like `changeme`, `secret`, or `password`. +Sunucu `changeme`, `secret` veya `password` gibi bilinen zayıf değerleri açıkça reddeder. --- -## Docker Security +## Docker Güvenliği -- Use non-root user in production -- Mount secrets as read-only volumes -- Never copy `.env` files into Docker images -- Use `.dockerignore` to exclude sensitive files -- Set `AUTH_COOKIE_SECURE=true` when behind HTTPS +- Üretimde root olmayan bir kullanıcı kullanın +- Gizli anahtarları salt okunur birimler (read-only volumes) olarak bağlayın +- `.env` dosyalarını asla Docker imajlarına kopyalamayın +- Hassas dosyaları hariç tutmak için `.dockerignore` kullanın +- HTTPS arkasındayken `AUTH_COOKIE_SECURE=true` ayarlayın ```bash docker run -d \ @@ -170,10 +195,52 @@ docker run -d \ --- -## Dependencies +## Bağımlılıklar -- Run `npm audit` regularly -- Keep dependencies updated -- The project uses `husky` + `lint-staged` for pre-commit checks -- CI pipeline runs ESLint security rules on every push -- Provider constants validated at module load via Zod (`src/shared/validation/providerSchema.ts`) +- Düzenli olarak `npm audit` çalıştırın (`npm run audit:deps` ana projeyi + electron'u kapsar) +- Bağımlılıkları güncel tutun +- Proje, commit öncesi kontroller için `husky` + `lint-staged` kullanır (lint-staged + check-docs-sync + check:any-budget:t11) +- CI hattı her push işleminde ESLint güvenlik kurallarını çalıştırır (`no-eval`, `no-implied-eval`, `no-new-func` = hata) +- Sağlayıcı sabitleri modül yükleme sırasında Zod aracılığıyla doğrulanır (`src/shared/validation/schemas.ts`) +- Varsayılan olarak güvenli kütüphaneler kullanılır: `dompurify` / `isomorphic-dompurify` (XSS), `jose` (JWT), `better-sqlite3` (parametreli sorgularla sıfır SQLi riski), `bcryptjs` (şifre karma) + +## Katı Güvenlik Kuralları (Hard Security Rules) + +Bu kurallar araçlar ve inceleyiciler tarafından zorunlu kılınmıştır: + +1. **Sırları asla commit etmeyin** — `.env` gitignore edilmiştir; `.env.example` şablondur (sabit değerler yok, yalnızca yorumlar — bkz. PUBLIC_CREDS.md) +2. **Asla `eval()`, `new Function()` veya dolaylı eval kullanmayın** — ESLint tarafından zorunlu kılınır +3. **Husky kancalarını asla atlamayın** (`--no-verify`, `--no-gpg-sign`), açık operatör onayı olmadan +4. **Rotalarda asla ham SQL yazmayın** — her zaman `src/lib/db/` üzerinden geçin (parametrelendirilmiş) +5. **Girdileri her zaman Zod ile doğrulayın** — `src/shared/validation/schemas.ts` +6. **Yukarı akış başlıklarını her zaman temizleyin** — `src/shared/constants/upstreamHeaders.ts` içindeki engelleme listesi +7. **Kimlik bilgilerini dinlenmede şifreleyin** — `src/lib/db/encryption.ts` aracılığıyla AES-256-GCM +8. **Genel yukarı akış OAuth kimlikleri `resolvePublicCred()` aracılığıyla kullanılmalıdır** — kaynak koda asla `AIza…` / `GOCSPX-…` / `…apps.googleusercontent.com` sabit değerlerini gömmeyin. Bkz. [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md). +9. **Hata yanıtları `buildErrorBody()` / `sanitizeErrorMessage()` üzerinden geçmelidir** — HTTP / SSE / executor / MCP yanıt gövdelerine asla ham `err.stack` / `err.message` koymayın. Bkz. [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md). +10. **`exec()` / `spawn()` çalışma zamanı değerleri `env` seçeneği üzerinden iletilmelidir** — kabuk komutlarına harici yolları veya güvenilmeyen değerleri asla dize birleştirme ile eklemeyin. Referans: `src/mitm/cert/install.ts::updateNssDatabases`. +11. **Varsayılan olarak güvenli kütüphaneleri tercih edin** — bkz. [tldrsec/awesome-secure-defaults](https://github.com/tldrsec/awesome-secure-defaults) (Helmet.js, DOMPurify, ssrf-req-filter, safe-regex, Google Tink). Kendi çözümünüzü yazmadan önce bunlara başvurun. + +## Tedarik Zinciri Tarayıcı Bulguları (Socket.dev / Snyk / Benzeri) + +Yayımlanan `omniroute` npm paketi, Next.js `output: "standalone"` derlemesini paketler; bu da belgelenmiş ayrıcalıklı özellikler (MITM, Zed içe aktarma, Cloud Sync, gömülü servis süpervizörü) dahil her rota işleyicisinin `.next/server/*.js` küçültülmüş yığınlarında yer alması anlamına gelir. Sezgisel tedarik zinciri tarayıcıları bu yığınları sıklıkla kötü amaçlı yazılım imzalarıyla eşleştirebilir. + +Her bulgu kategorisi için proje yöneticisi onay beyanı tutulmaktadır: + +- **[`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md)** — + bulgu başına harita: kaynak dosya ↔ işaretlenen yığın ↔ davranış ↔ v3.8.6'da uygulanan hafifletme. +- İşaretlenen her fonksiyondaki kaynak içi `SECURITY-AUDITOR-NOTE:` blokları aynı belgeye işaret eder. + +Geliştirme hattında uyarıları esnetemeyen kullanıcılar için: `OMNIROUTE_BUILD_PROFILE=minimal npm run build` ile derleme yapın. Bu, dört hassas modülü çalışma zamanında HTTP 503 `feature-disabled` döndüren taslaklarla değiştirir; böylece ayrıcalıklı kod yolları pakette fiziksel olarak bulunmaz. Yayımlama tarifi için bkz. [`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md). + +## Referanslar + +- [`docs/architecture/AUTHZ_GUIDE.md`](docs/architecture/AUTHZ_GUIDE.md) — yetkilendirme hattı +- [`docs/security/GUARDRAILS.md`](docs/security/GUARDRAILS.md) — güvenlik önlemleri çerçevesi +- [`docs/security/COMPLIANCE.md`](docs/security/COMPLIANCE.md) — denetim günlüğü ve saklama +- [`docs/security/PUBLIC_CREDS.md`](docs/security/PUBLIC_CREDS.md) — genel yukarı akış kimlik bilgileri için **zorunlu** model +- [`docs/security/ERROR_SANITIZATION.md`](docs/security/ERROR_SANITIZATION.md) — hata yanıtları için **zorunlu** model +- [`docs/security/SOCKET_DEV_FINDINGS.md`](docs/security/SOCKET_DEV_FINDINGS.md) — tedarik zinciri tarayıcı bulguları için onay beyanı +- [`docs/architecture/RESILIENCE_GUIDE.md`](docs/architecture/RESILIENCE_GUIDE.md) — devre kesici + soğuma süresi + model kilitleme +- [`docs/security/STEALTH_GUIDE.md`](docs/security/STEALTH_GUIDE.md) — TLS parmak izi (yasal/etik bildirim) +- [`CLAUDE.md`](CLAUDE.md) — yapay zeka ajanları için katı kurallar +- [tldrsec/awesome-secure-defaults](https://github.com/tldrsec/awesome-secure-defaults) — derlenmiş varsayılan olarak güvenli kütüphaneler diff --git a/docs/i18n/tr/docs/architecture/ARCHITECTURE.md b/docs/i18n/tr/docs/architecture/ARCHITECTURE.md index 9e409d9243..cd4345faeb 100644 --- a/docs/i18n/tr/docs/architecture/ARCHITECTURE.md +++ b/docs/i18n/tr/docs/architecture/ARCHITECTURE.md @@ -1,149 +1,192 @@ -# OmniRoute Architecture (Türkçe) +--- +title: "OmniRoute Mimarisi" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/ARCHITECTURE.md) · 🇸🇦 [ar](../../ar/docs/ARCHITECTURE.md) · 🇧🇬 [bg](../../bg/docs/ARCHITECTURE.md) · 🇧🇩 [bn](../../bn/docs/ARCHITECTURE.md) · 🇨🇿 [cs](../../cs/docs/ARCHITECTURE.md) · 🇩🇰 [da](../../da/docs/ARCHITECTURE.md) · 🇩🇪 [de](../../de/docs/ARCHITECTURE.md) · 🇪🇸 [es](../../es/docs/ARCHITECTURE.md) · 🇮🇷 [fa](../../fa/docs/ARCHITECTURE.md) · 🇫🇮 [fi](../../fi/docs/ARCHITECTURE.md) · 🇫🇷 [fr](../../fr/docs/ARCHITECTURE.md) · 🇮🇳 [gu](../../gu/docs/ARCHITECTURE.md) · 🇮🇱 [he](../../he/docs/ARCHITECTURE.md) · 🇮🇳 [hi](../../hi/docs/ARCHITECTURE.md) · 🇭🇺 [hu](../../hu/docs/ARCHITECTURE.md) · 🇮🇩 [id](../../id/docs/ARCHITECTURE.md) · 🇮🇹 [it](../../it/docs/ARCHITECTURE.md) · 🇯🇵 [ja](../../ja/docs/ARCHITECTURE.md) · 🇰🇷 [ko](../../ko/docs/ARCHITECTURE.md) · 🇮🇳 [mr](../../mr/docs/ARCHITECTURE.md) · 🇲🇾 [ms](../../ms/docs/ARCHITECTURE.md) · 🇳🇱 [nl](../../nl/docs/ARCHITECTURE.md) · 🇳🇴 [no](../../no/docs/ARCHITECTURE.md) · 🇵🇭 [phi](../../phi/docs/ARCHITECTURE.md) · 🇵🇱 [pl](../../pl/docs/ARCHITECTURE.md) · 🇵🇹 [pt](../../pt/docs/ARCHITECTURE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ARCHITECTURE.md) · 🇷🇴 [ro](../../ro/docs/ARCHITECTURE.md) · 🇷🇺 [ru](../../ru/docs/ARCHITECTURE.md) · 🇸🇰 [sk](../../sk/docs/ARCHITECTURE.md) · 🇸🇪 [sv](../../sv/docs/ARCHITECTURE.md) · 🇰🇪 [sw](../../sw/docs/ARCHITECTURE.md) · 🇮🇳 [ta](../../ta/docs/ARCHITECTURE.md) · 🇮🇳 [te](../../te/docs/ARCHITECTURE.md) · 🇹🇭 [th](../../th/docs/ARCHITECTURE.md) · 🇹🇷 [tr](../../tr/docs/ARCHITECTURE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ARCHITECTURE.md) · 🇵🇰 [ur](../../ur/docs/ARCHITECTURE.md) · 🇻🇳 [vi](../../vi/docs/ARCHITECTURE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ARCHITECTURE.md) +# OmniRoute Mimarisi (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/architecture/ARCHITECTURE.md) · 🇸🇦 [ar](../../ar/docs/architecture/ARCHITECTURE.md) · 🇧🇬 [bg](../../bg/docs/architecture/ARCHITECTURE.md) · 🇧🇩 [bn](../../bn/docs/architecture/ARCHITECTURE.md) · 🇨🇿 [cs](../../cs/docs/architecture/ARCHITECTURE.md) · 🇩🇰 [da](../../da/docs/architecture/ARCHITECTURE.md) · 🇩🇪 [de](../../de/docs/architecture/ARCHITECTURE.md) · 🇪🇸 [es](../../es/docs/architecture/ARCHITECTURE.md) · 🇮🇷 [fa](../../fa/docs/architecture/ARCHITECTURE.md) · 🇫🇮 [fi](../../fi/docs/architecture/ARCHITECTURE.md) · 🇫🇷 [fr](../../fr/docs/architecture/ARCHITECTURE.md) · 🇮🇳 [gu](../../gu/docs/architecture/ARCHITECTURE.md) · 🇮🇱 [he](../../he/docs/architecture/ARCHITECTURE.md) · 🇮🇳 [hi](../../hi/docs/architecture/ARCHITECTURE.md) · 🇭🇺 [hu](../../hu/docs/architecture/ARCHITECTURE.md) · 🇮🇩 [id](../../id/docs/architecture/ARCHITECTURE.md) · 🇮🇹 [it](../../it/docs/architecture/ARCHITECTURE.md) · 🇯🇵 [ja](../../ja/docs/architecture/ARCHITECTURE.md) · 🇰🇷 [ko](../../ko/docs/architecture/ARCHITECTURE.md) · 🇮🇳 [mr](../../mr/docs/architecture/ARCHITECTURE.md) · 🇲🇾 [ms](../../ms/docs/architecture/ARCHITECTURE.md) · 🇳🇱 [nl](../../nl/docs/architecture/ARCHITECTURE.md) · 🇳🇴 [no](../../no/docs/architecture/ARCHITECTURE.md) · 🇵🇭 [phi](../../phi/docs/architecture/ARCHITECTURE.md) · 🇵🇱 [pl](../../pl/docs/architecture/ARCHITECTURE.md) · 🇵🇹 [pt](../../pt/docs/architecture/ARCHITECTURE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/architecture/ARCHITECTURE.md) · 🇷🇴 [ro](../../ro/docs/architecture/ARCHITECTURE.md) · 🇷🇺 [ru](../../ru/docs/architecture/ARCHITECTURE.md) · 🇸🇰 [sk](../../sk/docs/architecture/ARCHITECTURE.md) · 🇸🇪 [sv](../../sv/docs/architecture/ARCHITECTURE.md) · 🇰🇪 [sw](../../sw/docs/architecture/ARCHITECTURE.md) · 🇮🇳 [ta](../../ta/docs/architecture/ARCHITECTURE.md) · 🇮🇳 [te](../../te/docs/architecture/ARCHITECTURE.md) · 🇹🇭 [th](../../th/docs/architecture/ARCHITECTURE.md) · 🇹🇷 [tr](../../tr/docs/architecture/ARCHITECTURE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/architecture/ARCHITECTURE.md) · 🇵🇰 [ur](../../ur/docs/architecture/ARCHITECTURE.md) · 🇻🇳 [vi](../../vi/docs/architecture/ARCHITECTURE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/architecture/ARCHITECTURE.md) --- -_Last updated: 2026-04-15_ +_Son güncelleme: 2026-08-23_ -## Executive Summary +## Yönetici Özeti -OmniRoute is a local AI routing gateway and dashboard built on Next.js. -It provides a single OpenAI-compatible endpoint (`/v1/*`) and routes traffic across multiple upstream providers with translation, fallback, token refresh, and usage tracking. +OmniRoute, Next.js üzerine inşa edilmiş yerel bir yapay zeka yönlendirme ağ geçidi (AI routing gateway) ve yönetim panosudur. +Tek bir OpenAI uyumlu uç nokta (`/v1/*`) sunar ve trafiği format dönüşümü, geri dönüş (fallback), belirteç yenileme ve kullanım takibi ile birden çok yukarı akış sağlayıcısına yönlendirir. -Core capabilities: +Temel yetenekler: -- OpenAI-compatible API surface for CLI/tools (329 provider catalog entries, 89 executor implementation modules) -- Request/response translation across provider formats -- Model combo fallback (multi-model sequence) -- Structured combo steps (`provider + model + connection`) with runtime ordering by `compositeTiers` -- Account-level fallback (multi-account per provider) -- Quota preflight and quota-aware P2C account selection in the main chat path -- OAuth + API-key provider connection management (23 OAuth catalog entries backed by 21 provider modules) -- Embedding generation via `/v1/embeddings` (6 providers, 9 models) -- Image generation via `/v1/images/generations` (10+ providers, 20+ models) -- Audio transcription via `/v1/audio/transcriptions` (7 providers) -- Text-to-speech via `/v1/audio/speech` (10 providers) -- Video generation via `/v1/videos/generations` (ComfyUI + SD WebUI) -- Music generation via `/v1/music/generations` (ComfyUI) -- Web search via `/v1/search` (12 providers) -- Moderations via `/v1/moderations` -- Reranking via `/v1/rerank` -- Think tag parsing (`...`) for reasoning models -- Response sanitization for strict OpenAI SDK compatibility -- Role normalization (developer→system, system→user) for cross-provider compatibility -- Structured output conversion (json_schema → Gemini responseSchema) -- Local persistence for providers, keys, aliases, combos, settings, pricing (110 top-level DB modules) -- Usage/cost tracking and request logging -- Optional cloud sync for multi-device/state sync -- IP allowlist/blocklist for API access control -- Thinking budget management (passthrough/auto/custom/adaptive) -- Global system prompt injection -- Session tracking and fingerprinting -- Per-account enhanced rate limiting with provider-specific profiles -- Circuit breaker pattern for provider resilience -- Anti-thundering herd protection with mutex locking -- Signature-based request deduplication cache -- Domain layer: cost rules, fallback policy, lockout policy -- Context Relay: session handoff summaries for account rotation continuity -- Domain state persistence (SQLite write-through cache for fallbacks, budgets, lockouts, circuit breakers) -- Policy engine for centralized request evaluation (lockout → budget → fallback) -- Request telemetry with p50/p95/p99 latency aggregation -- Combo target telemetry and historical combo target health via `combo_execution_key` / `combo_step_id` -- Correlation ID (X-Request-Id) for end-to-end tracing -- Compliance audit logging with opt-out per API key -- Eval framework for LLM quality assurance -- Health dashboard with real-time provider circuit breaker status -- MCP Server (107 unique tools, 32 scopes) with 3 transports (stdio/SSE/Streamable HTTP) -- A2A Server (JSON-RPC 2.0 + SSE) with skills and task lifecycle -- Memory system (extraction, injection, retrieval, summarization) -- Skills system (registry, executor, sandbox, built-in skills) -- MITM proxy with certificate management and DNS handling -- Prompt injection guard middleware -- ACP (Agent Communication Protocol) registry -- Modular OAuth providers (21 implementation modules under `src/lib/oauth/providers/`) -- Uninstall/full-uninstall scripts -- OAuth environment repair action -- WebSocket bridge for OpenAI-compatible WS clients (`/v1/ws`) -- Sync token management (issue/revoke, ETag-versioned config bundle download) -- GLM Thinking (`glmt`) first-class provider preset -- Hybrid token counting (provider-side `/messages/count_tokens` with estimation fallback) -- Model alias auto-seeding (30+ cross-proxy dialect normalizations at startup) -- Safe outbound fetch with SSRF guard, private URL blocking, and configurable retry -- Cooldown-aware chat retries with configurable `requestRetry` and `maxRetryIntervalSec` -- Runtime environment validation with Zod at startup -- Compliance audit v2 with pagination, provider CRUD events, and SSRF-blocked validation logging +- CLI/araçlar için OpenAI uyumlu API yüzeyi (349 sağlayıcı, 101 yürütücü modülü) +- Sağlayıcı formatları arasında istek/yanıt çevirisi +- Model kombo geri dönüşü (çoklu model sırası) +- `compositeTiers` ile çalışma zamanı sıralamasına sahip yapılandırılmış kombo adımları (`provider + model + connection`) +- Hesap düzeyinde geri dönüş (sağlayıcı başına çoklu hesap) +- Ana sohbet yolunda kota ön kontrolü ve kota duyarlı P2C hesap seçimi +- OAuth + API anahtarı sağlayıcı bağlantı yönetimi (23 OAuth sağlayıcı modülü) +- `/v1/embeddings` üzerinden embedding üretimi (6 sağlayıcı, 9 model) +- `/v1/images/generations` üzerinden görsel üretimi (10+ sağlayıcı, 20+ model) +- `/v1/audio/transcriptions` üzerinden ses deşifresi (7 sağlayıcı) +- `/v1/audio/speech` üzerinden metinden sese (10 sağlayıcı) +- `/v1/videos/generations` üzerinden video üretimi (ComfyUI + SD WebUI) +- `/v1/music/generations` üzerinden müzik üretimi (ComfyUI) +- `/v1/search` üzerinden web araması (5 sağlayıcı) +- `/v1/moderations` üzerinden içerik denetimi +- `/v1/rerank` üzerinden yeniden sıralama +- Akıl yürütme modelleri için düşünme etiketi ayrıştırması (`...`) +- Katı OpenAI SDK uyumluluğu için yanıt temizleme +- Çapraz sağlayıcı uyumluluğu için rol normalizasyonu (developer→system, system→user) +- Yapılandırılmış çıktı dönüştürme (json_schema → Gemini responseSchema) +- Sağlayıcılar, anahtarlar, takma adlar, kombolar, ayarlar, fiyatlandırma için yerel kalıcılık (120 DB modülü) +- Kullanım/maliyet takibi ve istek kaydı +- Çoklu cihaz/durum senkronizasyonu için isteğe bağlı bulut senkronizasyonu +- API erişim kontrolü için IP izin listesi / engelleme listesi +- Düşünme bütçesi yönetimi (passthrough/auto/custom/adaptive) +- Genel sistem istemi (system prompt) enjeksiyonu +- Oturum takibi ve parmak izi oluşturma +- Sağlayıcıya özel profillerle hesap başına gelişmiş hız sınırlaması +- Sağlayıcı dayanıklılığı için devre kesici (circuit breaker) modeli +- Mutex kilitleme ile sürü önleme koruması (anti-thundering herd) +- İmza tabanlı istek tekilleştirme önbelleği +- Alan katmanı: maliyet kuralları, geri dönüş politikası, kilitleme politikası +- Context Relay: hesap rotasyonunda oturum sürekliliği için devir özetleri +- Alan durumu kalıcılığı (geri dönüşler, bütçeler, kilitlemeler, devre kesiciler için SQLite doğrudan yazma önbelleği) +- Merkezi istek değerlendirmesi için politika motoru (kilitleme → bütçe → geri dönüş) +- p50/p95/p99 gecikme toplama ile istek telemetrisi +- `combo_execution_key` / `combo_step_id` aracılığıyla kombo hedef telemetrisi ve geçmiş sağlık durumu +- Uçtan uca izleme için korelasyon kimliği (X-Request-Id) +- API anahtarı başına vazgeçme seçeneğiyle uyumluluk denetim kaydı +- LLM kalite güvencesi için değerlendirme (eval) çerçevesi +- Gerçek zamanlı sağlayıcı devre kesici durumu içeren sağlık panosu +- 3 taşıma protokolüne (stdio/SSE/Streamable HTTP) sahip MCP Sunucusu (110 araç) +- Yetenekler ve görev yaşam döngüsüne sahip A2A Sunucusu (JSON-RPC 2.0 + SSE) +- Bellek sistemi (çıkarma, enjeksiyon, getirme, özetleme) +- Yetenekler sistemi (kayıt defteri, yürütücü, korumalı alan, yerleşik yetenekler) +- Sertifika yönetimi ve DNS işleme özellikli MITM proxy +- İstem enjeksiyonu koruma ara yazılımı +- Caveman, RTK, katmanlı işlem hatları, sıkıştırma komboları, dil paketleri ve analitik içeren istem sıkıştırma hattı +- ACP (Agent Communication Protocol) kayıt defteri +- Modüler OAuth sağlayıcıları (`src/lib/oauth/providers/` altında 23 ayrı modül) +- Kaldırma / tam kaldırma betikleri +- OAuth ortam onarım eylemi +- OpenAI uyumlu WS istemcileri için WebSocket köprüsü (`/v1/ws`) +- Senkronizasyon belirteci yönetimi (oluşturma/iptal etme, ETag sürümlü yapılandırma paketi indirme) +- GLM Thinking (`glmt`) birinci sınıf sağlayıcı önayarı +- Hibrit token sayımı (tahmin geri dönüşü ile sağlayıcı tarafı `/messages/count_tokens`) +- Model takma adı otomatik tohumlama (başlangıçta 30'dan fazla proxy arası diyalekt normalizasyonu) +- SSRF koruması, özel URL engelleme ve yapılandırılabilir yeniden deneme ile güvenli giden çağrılar +- Yapılandırılabilir `requestRetry` ve `maxRetryIntervalSec` ile soğuma duyarlı sohbet yeniden denemeleri +- Başlangıçta Zod ile çalışma zamanı ortam doğrulaması +- Sayfalama, sağlayıcı CRUD olayları ve SSRF engelleme doğrulama günlüğü içeren uyumluluk denetimi v2 -Primary runtime model: +Birincil çalışma zamanı modeli: -- Next.js app routes under `src/app/api/*` implement both dashboard APIs and compatibility APIs -- A shared SSE/routing core in `src/sse/*` + `open-sse/*` handles provider execution, translation, streaming, fallback, and usage +- `src/app/api/*` altındaki Next.js uygulama rotaları hem pano API'lerini hem de uyumluluk API'lerini uygular +- `src/sse/*` + `open-sse/*` içindeki paylaşılan SSE/yönlendirme çekirdeği; sağlayıcı yürütme, çeviri, akış, geri dönüş ve kullanım işlemlerini yönetir -## Scope and Boundaries +## Referans Diyagramları -### In Scope +Platformun Mermaid diyagram kaynakları [`docs/diagrams/`](docs/diagrams/README.md) dizininde yer almaktadır. -- Local gateway runtime -- Dashboard management APIs -- Provider authentication and token refresh -- Request translation and SSE streaming -- Local state + usage persistence -- Optional cloud sync orchestration +![İstek işlem hattı (/v1/chat/completions)](docs/diagrams/exported/request-pipeline.svg) -### Out of Scope +> Kaynak: [diagrams/request-pipeline.mmd](docs/diagrams/request-pipeline.mmd) -- Cloud service implementation behind `NEXT_PUBLIC_CLOUD_URL` -- Provider SLA/control plane outside local process -- External CLI binaries themselves (Claude CLI, Codex CLI, etc.) +![3 katmanlı dayanıklılık modeli](docs/diagrams/exported/resilience-3layers.svg) -## Dashboard Surface (Current) +> Kaynak: [diagrams/resilience-3layers.mmd](docs/diagrams/resilience-3layers.mmd) — ayrıca [RESILIENCE_GUIDE.md](docs/architecture/RESILIENCE_GUIDE.md) belgesinde yer almaktadır. -Main pages under `src/app/(dashboard)/dashboard/`: +--- -- `/dashboard` — quick start + provider overview -- `/dashboard/endpoint` — endpoint proxy + MCP + A2A + API endpoint tabs -- `/dashboard/providers` — provider connections and credentials -- `/dashboard/combos` — combo strategies, templates, step-based builder, model routing rules, manual persisted ordering -- `/dashboard/costs` — cost aggregation and pricing visibility -- `/dashboard/analytics` — usage analytics, evaluations, combo target health -- `/dashboard/limits` — quota/rate controls -- `/dashboard/cli-tools` — CLI onboarding, runtime detection, config generation -- `/dashboard/agents` — detected ACP agents + custom agent registration -- `/dashboard/media` — image/video/music playground -- `/dashboard/search-tools` — search provider testing and history -- `/dashboard/health` — uptime, circuit breakers, rate limits, quota-monitored sessions -- `/dashboard/logs` — request/proxy/audit/console logs -- `/dashboard/settings` — system settings tabs (general, routing, combo defaults, etc.) -- `/dashboard/api-manager` — API key lifecycle and model permissions +## Kapsam ve Sınırlar -## High-Level System Context +### Kapsam Dahilinde Olanlar + +- Yerel ağ geçidi çalışma zamanı +- Pano yönetim API'leri +- Sağlayıcı kimlik doğrulaması ve belirteç yenileme +- İstek çevirisi ve SSE akışı +- Yerel durum + kullanım kalıcılığı +- İsteğe bağlı bulut senkronizasyon orkestrasyonu + +### Kapsam Dışında Olanlar + +- `NEXT_PUBLIC_CLOUD_URL` arkasındaki bulut hizmeti uygulaması +- Yerel sürecin dışındaki sağlayıcı SLA/kontrol düzlemi +- Harici CLI ikili dosyalarının kendileri (Claude CLI, Codex CLI vb.) + +--- + +## Pano Yüzeyi (Dashboard Surface) + +`src/app/(dashboard)/dashboard/` altındaki ana sayfalar: + +- `/dashboard` — hızlı başlangıç + sağlayıcı genel bakışı +- `/dashboard/endpoint` — uç nokta proxy + MCP + A2A + API uç noktaları sekmeleri +- `/dashboard/providers` — sağlayıcı bağlantıları ve kimlik bilgileri +- `/dashboard/combos` — kombo stratejileri, şablonlar, adım tabanlı oluşturucu, model yönlendirme kuralları, manuel kalıcı sıralama +- `/dashboard/auto-combo` — Auto Combo Motoru: puanlama ağırlıkları, mod paketleri, sanal fabrika önayarları, teleometri +- `/dashboard/costs` — maliyet toplama ve fiyatlandırma görünürlüğü +- `/dashboard/analytics` — kullanım analitiği, değerlendirmeler, kombo hedef sağlığı +- `/dashboard/limits` — kota/hız denetimleri +- `/dashboard/cli-tools` — CLI yapılandırma, çalışma zamanı algılama, yapılandırma üretimi +- `/dashboard/agents` — algılanan ACP ajanları + özel ajan kaydı +- `/dashboard/cloud-agents` — bulut tabanlı ajan görevleri (Codex Cloud, Devin, Jules) ve görev yaşam döngüsü +- `/dashboard/skills` — A2A yetenek kayıt defteri, korumalı alan yürütme, yerleşik yetenek kataloğu +- `/dashboard/memory` — kalıcı konuşma belleği inceleme ve getirme +- `/dashboard/webhooks` — giden webhook abonelikleri, sır rotasyonu, yeniden deneme istatistikleri +- `/dashboard/batch` — toplu iş gönderimi ve ilerleme durumu +- `/dashboard/cache` — doğrudan okuma ve akıl yürütme önbelleği istatistikleri, temizleme denetimleri +- `/dashboard/playground` — yapılandırılmış herhangi bir kombo/modele karşı etkileşimli sohbet alanı +- `/dashboard/changelog` — uygulama içi değişiklik günlüğü görüntüleyici (`CHANGELOG.md` içeriğini işler) +- `/dashboard/system` — çalışma zamanı tanılamaları, sürüm bilgisi, ortam doğrulama yüzeyi +- `/dashboard/onboarding` — yeni kurulumlar için ilk çalıştırma sihirbazı +- `/dashboard/media` — görsel/video/müzik oyun alanı +- `/dashboard/search-tools` — arama sağlayıcısı testi ve geçmişi +- `/dashboard/health` — çalışma süresi, devre kesiciler, hız sınırları, kota izlenen oturumlar +- `/dashboard/logs` — istek/proxy/denetim/konsol günlükleri +- `/dashboard/settings` — sistem ayarları sekmeleri (genel, yönlendirme, kombo varsayılanları vb.) +- `/dashboard/context/caveman` — Caveman sıkıştırma kuralları, dil paketleri, önizleme ve çıktı modu +- `/dashboard/context/rtk` — RTK komut çıktısı filtreleri, önizleme ve çalışma zamanı güvenlik ayarları +- `/dashboard/context/combos` — yönlendirme kombolarına atanan adlandırılmış sıkıştırma hatları +- `/dashboard/translator` — çevirmen inceleme ve istek formatı dönüştürme önizlemesi +- `/dashboard/audit` — sayfalama ve yapılandırılmış meta veriler içeren uyumluluk denetim günlüğü tarayıcısı +- `/dashboard/usage` — `usage_history` tablosuna bağlı istek başına kullanım tarayıcısı +- `/dashboard/compression` — sıkıştırma analitiği, istatistikler ve işlem hattı ataması +- `/dashboard/api-manager` — API anahtarı yaşam döngüsü ve model izinleri + +--- + +## Yüksek Düzey Sistem Bağlamı ```mermaid flowchart LR - subgraph Clients[Developer Clients] + subgraph Clients[Geliştirici İstemcileri] C1[Claude Code] C2[Codex CLI] C3[OpenClaw / Droid / Cline / Continue / Roo] - C4[Custom OpenAI-compatible clients] - BROWSER[Browser Dashboard] + C4[Özel OpenAI uyumlu istemciler] + BROWSER[Tarayıcı Panosu] end - subgraph Router[OmniRoute Local Process] - API[V1 Compatibility API\n/v1/*] - DASH[Dashboard + Management API\n/api/*] - CORE[SSE + Translation Core\nopen-sse + src/sse] + subgraph Router[OmniRoute Yerel Süreci] + API[V1 Uyumluluk API'si\n/v1/*] + DASH[Pano + Yönetim API'si\n/api/*] + CORE[SSE + Çeviri Çekirdeği\nopen-sse + src/sse] DB[(storage.sqlite)] - UDB[(usage tables + log artifacts)] + UDB[(kullanım tabloları + günlükler)] end - subgraph Upstreams[Upstream Providers] - P1[OAuth Providers\nClaude/Codex/Gemini/Qwen/Qoder/GitHub/Kiro/Cursor/Antigravity] - P2[API Key Providers\nOpenAI/Anthropic/OpenRouter/GLM/Kimi/MiniMax\nDeepSeek/Groq/xAI/Mistral/Perplexity\nTogether/Fireworks/Cerebras/Cohere/NVIDIA] - P3[Compatible Nodes\nOpenAI-compatible / Anthropic-compatible] + subgraph Upstreams[Yukarı Akış Sağlayıcıları] + P1[OAuth Sağlayıcıları\nClaude/Codex/Gemini/Qoder/GitHub/Kiro/Cursor/Antigravity] + P2[API Anahtarı Sağlayıcıları\nOpenAI/Anthropic/OpenRouter/GLM/Kimi/MiniMax\nDeepSeek/Groq/xAI/Mistral/Perplexity\nTogether/Fireworks/Cerebras/Cohere/NVIDIA] + P3[Uyumlu Düğümler\nOpenAI uyumlu / Anthropic uyumlu] end - subgraph Cloud[Optional Cloud Sync] - CLOUD[Cloud Sync Endpoint\nNEXT_PUBLIC_CLOUD_URL] + subgraph Cloud[İsteğe Bağlı Bulut Senkronizasyonu] + CLOUD[Bulut Senkronizasyon Uç Noktası\nNEXT_PUBLIC_CLOUD_URL] end C1 --> API @@ -164,724 +207,139 @@ flowchart LR DASH --> CLOUD ``` -## Core Runtime Components +--- -## 1) API and Routing Layer (Next.js App Routes) +## Çekirdek Çalışma Zamanı Bileşenleri -Main directories: +### 1) API ve Yönlendirme Katmanı (Next.js App Router) -- `src/app/api/v1/*` and `src/app/api/v1beta/*` for compatibility APIs -- `src/app/api/*` for management/configuration APIs -- Next rewrites in `next.config.mjs` map `/v1/*` to `/api/v1/*` +Ana dizinler: -Important compatibility routes: +- Uyumluluk API'leri için `src/app/api/v1/*` ve `src/app/api/v1beta/*` +- Yönetim/yapılandırma API'leri için `src/app/api/*` +- `next.config.mjs` içindeki yönlendirmeler `/v1/*` yollarını `/api/v1/*` rotalarına eşler + +Önemli uyumluluk rotaları: - `src/app/api/v1/chat/completions/route.ts` - `src/app/api/v1/messages/route.ts` - `src/app/api/v1/responses/route.ts` -- `src/app/api/v1/models/route.ts` — includes custom models with `custom: true` -- `src/app/api/v1/embeddings/route.ts` — embedding generation (6 providers) -- `src/app/api/v1/images/generations/route.ts` — image generation (4+ providers incl. Antigravity/Nebius) +- `src/app/api/v1/models/route.ts` — `custom: true` içeren özel modelleri de kapsar +- `src/app/api/v1/embeddings/route.ts` — embedding üretimi (6 sağlayıcı) +- `src/app/api/v1/images/generations/route.ts` — görsel üretimi (10+ sağlayıcı) - `src/app/api/v1/messages/count_tokens/route.ts` -- `src/app/api/v1/providers/[provider]/chat/completions/route.ts` — dedicated per-provider chat -- `src/app/api/v1/providers/[provider]/embeddings/route.ts` — dedicated per-provider embeddings -- `src/app/api/v1/providers/[provider]/images/generations/route.ts` — dedicated per-provider images +- `src/app/api/v1/providers/[provider]/chat/completions/route.ts` — özel sağlayıcı sohbet rotası +- `src/app/api/v1/providers/[provider]/embeddings/route.ts` — özel sağlayıcı embedding rotası +- `src/app/api/v1/providers/[provider]/images/generations/route.ts` — özel sağlayıcı görsel rotası - `src/app/api/v1beta/models/route.ts` - `src/app/api/v1beta/models/[...path]/route.ts` -Management domains: +Yönetim alanları: -- Auth/settings: `src/app/api/auth/*`, `src/app/api/settings/*` -- Providers/connections: `src/app/api/providers*` -- Provider nodes: `src/app/api/provider-nodes*` -- Custom models: `src/app/api/provider-models` (GET/POST/DELETE) -- Model catalog: `src/app/api/models/route.ts` (GET) -- Proxy config: `src/app/api/settings/proxy` (GET/PUT/DELETE) + `src/app/api/settings/proxy/test` (POST) +- Kimlik doğrulama/ayarlar: `src/app/api/auth/*`, `src/app/api/settings/*` +- Sağlayıcılar/bağlantılar: `src/app/api/providers*` +- Sağlayıcı düğümleri: `src/app/api/provider-nodes*` +- Özel modeller: `src/app/api/provider-models` (GET/POST/DELETE) +- Model kataloğu: `src/app/api/models/route.ts` (GET) +- Proxy yapılandırması: `src/app/api/settings/proxy` (GET/PUT/DELETE) + `src/app/api/settings/proxy/test` (POST) - OAuth: `src/app/api/oauth/*` -- Keys/aliases/combos/pricing: `src/app/api/keys*`, `src/app/api/models/alias`, `src/app/api/combos*`, `src/app/api/pricing` -- Usage: `src/app/api/usage/*` -- Sync/cloud: `src/app/api/sync/*`, `src/app/api/cloud/*` -- CLI tooling helpers: `src/app/api/cli-tools/*` -- IP filter: `src/app/api/settings/ip-filter` (GET/PUT) -- Thinking budget: `src/app/api/settings/thinking-budget` (GET/PUT) -- System prompt: `src/app/api/settings/system-prompt` (GET/PUT) -- Sessions: `src/app/api/sessions` (GET) -- Rate limits: `src/app/api/rate-limits` (GET) -- Resilience: `src/app/api/resilience` (GET/PATCH) — request queue, connection cooldown, provider breaker, wait-for-cooldown config -- Resilience reset: `src/app/api/resilience/reset` (POST) — reset provider breakers -- Cache stats: `src/app/api/cache/stats` (GET/DELETE) -- Telemetry: `src/app/api/telemetry/summary` (GET) -- Budget: `src/app/api/usage/budget` (GET/POST) -- Fallback chains: `src/app/api/fallback/chains` (GET/POST/DELETE) -- Compliance audit: `src/app/api/compliance/audit-log` (GET, with pagination + structured metadata) -- Evals: `src/app/api/evals` (GET/POST), `src/app/api/evals/[suiteId]` (GET) -- Policies: `src/app/api/policies` (GET/POST) -- Sync tokens: `src/app/api/sync/tokens` (GET/POST), `src/app/api/sync/tokens/[id]` (GET/DELETE) -- Config bundle: `src/app/api/sync/bundle` (GET, ETag-versioned snapshot of settings/providers/combos/keys) -- WebSocket: `src/app/api/v1/ws/route.ts` — Upgrade handler for OpenAI-compatible WS clients - -## 2) SSE + Translation Core - -Main flow modules: - -- Entry: `src/sse/handlers/chat.ts` -- Core orchestration: `open-sse/handlers/chatCore.ts` -- Provider execution adapters: `open-sse/executors/*` -- Format detection/provider config: `open-sse/services/provider.ts` -- Model parse/resolve: `src/sse/services/model.ts`, `open-sse/services/model.ts` -- Account fallback logic: `open-sse/services/accountFallback.ts` -- Translation registry: `open-sse/translator/index.ts` -- Stream transformations: `open-sse/utils/stream.ts`, `open-sse/utils/streamHandler.ts` -- Usage extraction/normalization: `open-sse/utils/usageTracking.ts` -- Think tag parser: `open-sse/utils/thinkTagParser.ts` -- Embedding handler: `open-sse/handlers/embeddings.ts` -- Embedding provider registry: `open-sse/config/embeddingRegistry.ts` -- Image generation handler: `open-sse/handlers/imageGeneration.ts` -- Image provider registry: `open-sse/config/imageRegistry.ts` -- Response sanitization: `open-sse/handlers/responseSanitizer.ts` -- Role normalization: `open-sse/services/roleNormalizer.ts` - -Services (business logic): - -- Account selection/scoring: `open-sse/services/accountSelector.ts` -- Context lifecycle management: `open-sse/services/contextManager.ts` -- IP filter enforcement: `open-sse/services/ipFilter.ts` -- Session tracking: `open-sse/services/sessionManager.ts` -- Request deduplication: `open-sse/services/signatureCache.ts` -- System prompt injection: `open-sse/services/systemPrompt.ts` -- Thinking budget management: `open-sse/services/thinkingBudget.ts` -- Wildcard model routing: `open-sse/services/wildcardRouter.ts` -- Rate limit management: `open-sse/services/rateLimitManager.ts` -- Circuit breaker: `open-sse/services/circuitBreaker.ts` -- Context handoff: `open-sse/services/contextHandoff.ts` — handoff summary generation and injection for context-relay strategy -- Codex quota fetcher: `open-sse/services/codexQuotaFetcher.ts` — fetches Codex quota for context-relay handoff decisions -- Cooldown-aware retry: `src/sse/services/cooldownAwareRetry.ts` — per-model cooldown retries with configurable `requestRetry` / `maxRetryIntervalSec` -- Safe outbound fetch: `src/shared/network/safeOutboundFetch.ts` — guarded provider/model fetch with SSRF guard, private-URL blocking, retry, and timeout -- Outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — validates provider URLs against private/localhost CIDR ranges -- Provider request defaults: `open-sse/services/providerRequestDefaults.ts` — provider-level `maxTokens`, `temperature`, `thinkingBudgetTokens` defaults -- GLM provider constants: `open-sse/config/glmProvider.ts` — shared GLM models, quota URLs, GLMT timeout/defaults -- Antigravity upstream: `open-sse/config/antigravityUpstream.ts` — base URL and discovery path constants -- Codex client constants: `open-sse/config/codexClient.ts` — versioned user-agent and client-version values -- Model alias seed: `src/lib/modelAliasSeed.ts` — seeds 30+ cross-proxy dialect aliases at startup - -Domain layer modules: - -- Cost rules/budgets: `src/lib/domain/costRules.ts` -- Fallback policy: `src/lib/domain/fallbackPolicy.ts` -- Combo resolver: `src/lib/domain/comboResolver.ts` -- Lockout policy: `src/lib/domain/lockoutPolicy.ts` -- Policy engine: `src/domain/policyEngine.ts` — centralized lockout → budget → fallback evaluation -- Error codes catalog: `src/lib/domain/errorCodes.ts` -- Request ID: `src/lib/domain/requestId.ts` -- Fetch timeout: `src/lib/domain/fetchTimeout.ts` -- Request telemetry: `src/lib/domain/requestTelemetry.ts` -- Compliance/audit: `src/lib/domain/compliance/index.ts` -- Eval runner: `src/lib/domain/evalRunner.ts` -- Domain state persistence: `src/lib/db/domainState.ts` — SQLite CRUD for fallback chains, budgets, cost history, lockout state, circuit breakers - -OAuth provider modules (21 implementation modules under `src/lib/oauth/providers/`): - -- Registry index: `src/lib/oauth/providers/index.ts` -- Individual providers: `claude.ts`, `codex.ts`, `gemini.ts`, `antigravity.ts`, `qoder.ts`, `qwen.ts`, `kimi-coding.ts`, `github.ts`, `kiro.ts`, `cursor.ts`, `kilocode.ts`, `cline.ts` -- Thin wrapper: `src/lib/oauth/providers.ts` — re-exports from individual modules - -## 3) Persistence Layer - -Primary state DB (SQLite): - -- Core infra: `src/lib/db/core.ts` (better-sqlite3, migrations, WAL) -- Re-export facade: `src/lib/localDb.ts` (thin compatibility layer for callers) -- file: `${DATA_DIR}/storage.sqlite` (or `$XDG_CONFIG_HOME/omniroute/storage.sqlite` when set, else `~/.omniroute/storage.sqlite`) -- entities (tables + KV namespaces): providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt** - -Usage persistence: - -- facade: `src/lib/usageDb.ts` (decomposed modules in `src/lib/usage/*`) -- SQLite tables in `storage.sqlite`: `usage_history`, `call_logs`, `proxy_logs` -- optional file artifacts remain for compatibility/debug (`${DATA_DIR}/log.txt`, `${DATA_DIR}/call_logs/`, `/logs/...`) -- legacy JSON files are migrated to SQLite by startup migrations when present - -Domain State DB (SQLite): - -- `src/lib/db/domainState.ts` — CRUD operations for domain state -- Tables (created in `src/lib/db/core.ts`): `domain_fallback_chains`, `domain_budgets`, `domain_cost_history`, `domain_lockout_state`, `domain_circuit_breakers` -- Write-through cache pattern: in-memory Maps are authoritative at runtime; mutations are written synchronously to SQLite; state is restored from DB on cold start - -## 4) Auth + Security Surfaces - -- Dashboard cookie auth: `src/proxy.ts`, `src/app/api/auth/login/route.ts` -- API key generation/verification: `src/shared/utils/apiKey.ts` -- Provider secrets persisted in `providerConnections` entries -- Outbound proxy support via `open-sse/utils/proxyFetch.ts` (env vars) and `open-sse/utils/networkProxy.ts` (configurable per-provider or global) -- SSRF / outbound URL guard: `src/shared/network/outboundUrlGuard.ts` — blocks private/loopback/link-local ranges for all provider calls -- Runtime env validation: `src/lib/env/runtimeEnv.ts` — Zod schema for all environment variables, surfaced as startup errors/warnings -- Sync tokens: `src/lib/db/syncTokens.ts` — scoped tokens for config bundle download endpoints; backed by `sync_tokens` SQLite table (migration `024_create_sync_tokens.sql`) -- WebSocket handshake auth: `src/lib/ws/handshake.ts` — validates WS upgrade requests via API key or session cookie - -## 5) Cloud Sync - -- Scheduler init: `src/lib/initCloudSync.ts`, `src/shared/services/initializeCloudSync.ts`, `src/shared/services/modelSyncScheduler.ts` -- Periodic task: `src/shared/services/cloudSyncScheduler.ts` -- Periodic task: `src/shared/services/modelSyncScheduler.ts` -- Control route: `src/app/api/sync/cloud/route.ts` - -## Request Lifecycle (`/v1/chat/completions`) - -```mermaid -sequenceDiagram - autonumber - participant Client as CLI/SDK Client - participant Route as /api/v1/chat/completions - participant Chat as src/sse/handlers/chat - participant Core as open-sse/handlers/chatCore - participant Model as Model Resolver - participant Auth as Credential Selector - participant Exec as Provider Executor - participant Prov as Upstream Provider - participant Stream as Stream Translator - participant Usage as usageDb - - Client->>Route: POST /v1/chat/completions - Route->>Chat: handleChat(request) - Chat->>Model: parse/resolve model or combo - - alt Combo model - Chat->>Chat: iterate combo models (handleComboChat) - end - - Chat->>Auth: getProviderCredentials(provider) - Auth-->>Chat: active account + tokens/api key - - Chat->>Core: handleChatCore(body, modelInfo, credentials) - Core->>Core: detect source format - Core->>Core: translate request to target format - Core->>Exec: execute(provider, transformedBody) - Exec->>Prov: upstream API call - Prov-->>Exec: SSE/JSON response - Exec-->>Core: response + metadata - - alt 401/403 - Core->>Exec: refreshCredentials() - Exec-->>Core: updated tokens - Core->>Exec: retry request - end - - Core->>Stream: translate/normalize stream to client format - Stream-->>Client: SSE chunks / JSON response - - Stream->>Usage: extract usage + persist history/log -``` - -## Combo + Account Fallback Flow - -```mermaid -flowchart TD - A[Incoming model string] --> B{Is combo name?} - B -- Yes --> C[Load combo models sequence] - B -- No --> D[Single model path] - - C --> E[Try model N] - E --> F[Resolve provider/model] - D --> F - - F --> G[Select account credentials] - G --> H{Credentials available?} - H -- No --> I[Return provider unavailable] - H -- Yes --> J[Execute request] - - J --> K{Success?} - K -- Yes --> L[Return response] - K -- No --> M{Fallback-eligible error?} - - M -- No --> N[Return error] - M -- Yes --> O[Mark account unavailable cooldown] - O --> P{Another account for provider?} - P -- Yes --> G - P -- No --> Q{In combo with next model?} - Q -- Yes --> E - Q -- No --> R[Return all unavailable] -``` - -Fallback decisions are driven by `open-sse/services/accountFallback.ts` using status codes and error-message heuristics. Combo routing adds one extra guard: provider-scoped 400s such as upstream content-block and role-validation failures are treated as model-local failures so later combo targets can still run. - -## OAuth Onboarding and Token Refresh Lifecycle - -```mermaid -sequenceDiagram - autonumber - participant UI as Dashboard UI - participant OAuth as /api/oauth/[provider]/[action] - participant ProvAuth as Provider Auth Server - participant DB as localDb - participant Test as /api/providers/[id]/test - participant Exec as Provider Executor - - UI->>OAuth: GET authorize or device-code - OAuth->>ProvAuth: create auth/device flow - ProvAuth-->>OAuth: auth URL or device code payload - OAuth-->>UI: flow data - - UI->>OAuth: POST exchange or poll - OAuth->>ProvAuth: token exchange/poll - ProvAuth-->>OAuth: access/refresh tokens - OAuth->>DB: createProviderConnection(oauth data) - OAuth-->>UI: success + connection id - - UI->>Test: POST /api/providers/[id]/test - Test->>Exec: validate credentials / optional refresh - Exec-->>Test: valid or refreshed token info - Test->>DB: update status/tokens/errors - Test-->>UI: validation result -``` - -Refresh during live traffic is executed inside `open-sse/handlers/chatCore.ts` via executor `refreshCredentials()`. - -## Cloud Sync Lifecycle (Enable / Sync / Disable) - -```mermaid -sequenceDiagram - autonumber - participant UI as Endpoint Page UI - participant Sync as /api/sync/cloud - participant DB as localDb - participant Cloud as External Cloud Sync - participant Claude as ~/.claude/settings.json - - UI->>Sync: POST action=enable - Sync->>DB: set cloudEnabled=true - Sync->>DB: ensure API key exists - Sync->>Cloud: POST /sync/{machineId} (providers/aliases/combos/keys) - Cloud-->>Sync: sync result - Sync->>Cloud: GET /{machineId}/v1/verify - Sync-->>UI: enabled + verification status - - UI->>Sync: POST action=sync - Sync->>Cloud: POST /sync/{machineId} - Cloud-->>Sync: remote data - Sync->>DB: update newer local tokens/status - Sync-->>UI: synced - - UI->>Sync: POST action=disable - Sync->>DB: set cloudEnabled=false - Sync->>Cloud: DELETE /sync/{machineId} - Sync->>Claude: switch ANTHROPIC_BASE_URL back to local (if needed) - Sync-->>UI: disabled -``` - -Periodic sync is triggered by `CloudSyncScheduler` when cloud is enabled. - -## Data Model and Storage Map - -```mermaid -erDiagram - SETTINGS ||--o{ PROVIDER_CONNECTION : controls - PROVIDER_NODE ||--o{ PROVIDER_CONNECTION : backs_compatible_provider - PROVIDER_CONNECTION ||--o{ USAGE_ENTRY : emits_usage - - SETTINGS { - boolean cloudEnabled - number stickyRoundRobinLimit - boolean requireLogin - string password_hash - string fallbackStrategy - json rateLimitDefaults - json providerProfiles - } - - PROVIDER_CONNECTION { - string id - string provider - string authType - string name - number priority - boolean isActive - string apiKey - string accessToken - string refreshToken - string expiresAt - string testStatus - string lastError - string rateLimitedUntil - json providerSpecificData - } - - PROVIDER_NODE { - string id - string type - string name - string prefix - string apiType - string baseUrl - } - - MODEL_ALIAS { - string alias - string targetModel - } - - COMBO { - string id - string name - string[] models - } - - API_KEY { - string id - string name - string key - string machineId - } - - USAGE_ENTRY { - string provider - string model - number prompt_tokens - number completion_tokens - string connectionId - string timestamp - } - - CUSTOM_MODEL { - string id - string name - string providerId - } - - PROXY_CONFIG { - string global - json providers - } - - IP_FILTER { - string mode - string[] allowlist - string[] blocklist - } - - THINKING_BUDGET { - string mode - number customBudget - string effortLevel - } - - SYSTEM_PROMPT { - boolean enabled - string prompt - string position - } -``` - -Physical storage files: - -- primary runtime DB: `${DATA_DIR}/storage.sqlite` -- request log lines: `${DATA_DIR}/log.txt` (compat/debug artifact) -- structured call payload archives: `${DATA_DIR}/call_logs/` -- optional translator/request debug sessions: `/logs/...` - -## Deployment Topology - -```mermaid -flowchart LR - subgraph LocalHost[Developer Host] - CLI[CLI Tools] - Browser[Dashboard Browser] - end - - subgraph ContainerOrProcess[OmniRoute Runtime] - Next[Next.js Server\nPORT=20128] - Core[SSE Core + Executors] - MainDB[(storage.sqlite)] - UsageDB[(usage tables + log artifacts)] - end - - subgraph External[External Services] - Providers[AI Providers] - SyncCloud[Cloud Sync Service] - end - - CLI --> Next - Browser --> Next - Next --> Core - Next --> MainDB - Core --> MainDB - Core --> UsageDB - Core --> Providers - Next --> SyncCloud -``` - -## Module Mapping (Decision-Critical) - -### Route and API Modules - -- `src/app/api/v1/*`, `src/app/api/v1beta/*`: compatibility APIs -- `src/app/api/v1/providers/[provider]/*`: dedicated per-provider routes (chat, embeddings, images) -- `src/app/api/providers*`: provider CRUD, validation, testing -- `src/app/api/provider-nodes*`: custom compatible node management -- `src/app/api/provider-models`: custom model management (CRUD) -- `src/app/api/models/route.ts`: model catalog API (aliases + custom models) -- `src/app/api/oauth/*`: OAuth/device-code flows -- `src/app/api/keys*`: local API key lifecycle -- `src/app/api/models/alias`: alias management -- `src/app/api/combos*`: fallback combo management -- `src/app/api/pricing`: pricing overrides for cost calculation -- `src/app/api/settings/proxy`: proxy configuration (GET/PUT/DELETE) -- `src/app/api/settings/proxy/test`: outbound proxy connectivity test (POST) -- `src/app/api/usage/*`: usage and logs APIs -- `src/app/api/sync/*` + `src/app/api/cloud/*`: cloud sync and cloud-facing helpers -- `src/app/api/cli-tools/*`: local CLI config writers/checkers -- `src/app/api/settings/ip-filter`: IP allowlist/blocklist (GET/PUT) -- `src/app/api/settings/thinking-budget`: thinking token budget config (GET/PUT) -- `src/app/api/settings/system-prompt`: global system prompt (GET/PUT) -- `src/app/api/sessions`: active session listing (GET) -- `src/app/api/rate-limits`: per-account rate limit status (GET) -- `src/app/api/sync/tokens`: sync token CRUD (GET/POST) -- `src/app/api/sync/tokens/[id]`: sync token get/delete (GET/DELETE) -- `src/app/api/sync/bundle`: config bundle download (GET, ETag versioning) -- `src/app/api/v1/ws`: WebSocket upgrade handler for OpenAI-compatible WS clients - -### Routing and Execution Core - -- `src/sse/handlers/chat.ts`: request parse, combo handling, account selection loop -- `open-sse/handlers/chatCore.ts`: translation, executor dispatch, retry/refresh handling, stream setup -- `open-sse/executors/*`: provider-specific network and format behavior - -### Translation Registry and Format Converters - -- `open-sse/translator/index.ts`: translator registry and orchestration -- Request translators: `open-sse/translator/request/*` -- Response translators: `open-sse/translator/response/*` -- Format constants: `open-sse/translator/formats.ts` - -### Persistence - -- `src/lib/db/*`: persistent config/state and domain persistence on SQLite -- `src/lib/localDb.ts`: compatibility re-export for DB modules -- `src/lib/usageDb.ts`: usage history/call logs facade on top of SQLite tables - -## Provider Executor Coverage (Strategy Pattern) - -Each provider has a specialized executor extending `BaseExecutor` (in `open-sse/executors/base.ts`), which provides URL building, header construction, retry with exponential backoff, credential refresh hooks, and the `execute()` orchestration method. - -| Executor | Provider(s) | Special Handling | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| `DefaultExecutor` | OpenAI, Claude, Gemini, Qwen, OpenRouter, GLM, Kimi, MiniMax, DeepSeek, Groq, xAI, Mistral, Perplexity, Together, Fireworks, Cerebras, Cohere, NVIDIA, etc. | Dynamic URL/header config per provider | -| `AntigravityExecutor` | Google Antigravity | Custom project/session IDs, Retry-After parsing | -| `CliProxyApiExecutor` | CLIProxyAPI-compatible providers | Custom auth and protocol handling | -| `CloudflareAiExecutor` | Cloudflare Workers AI | Account ID injection, Neurons-based usage tracking | -| `CodexExecutor` | OpenAI Codex | Injects system instructions, forces reasoning effort | -| `CursorExecutor` | Cursor IDE | ConnectRPC protocol, Protobuf encoding, request signing via checksum | -| `GithubExecutor` | GitHub Copilot | Copilot token refresh, VSCode-mimicking headers | -| `KiroExecutor` | AWS CodeWhisperer/Kiro | AWS EventStream binary format → SSE conversion | -| `OpenCodeExecutor` | OpenCode | AI SDK compatible provider setup | -| `PollinationsExecutor` | Pollinations AI | No API key required, rate-limited requests | -| `QoderExecutor` | Qoder AI | PAT and OAuth support, multi-model free tier | -| `VertexExecutor` | Google Vertex AI | Service account auth, region-based endpoints | - -All other providers (including custom compatible nodes) use the `DefaultExecutor`. - -## Provider Compatibility Matrix - -| Provider | Format | Auth | Stream | Non-Stream | Token Refresh | Usage API | -| ---------------- | ---------------- | --------------------- | ---------------- | ---------- | ------------- | ------------------ | -| Claude | claude | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Admin only | -| Gemini | gemini | API Key / OAuth | ✅ | ✅ | ✅ | ⚠️ Cloud Console | -| Antigravity | antigravity | OAuth | ✅ | ✅ | ✅ | ✅ Full quota API | -| OpenAI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Codex | openai-responses | OAuth | ✅ forced | ❌ | ✅ | ✅ Rate limits | -| GitHub Copilot | openai | OAuth + Copilot Token | ✅ | ✅ | ✅ | ✅ Quota snapshots | -| Cursor | cursor | Custom checksum | ✅ | ✅ | ❌ | ❌ | -| Kiro | kiro | AWS SSO OIDC | ✅ (EventStream) | ❌ | ✅ | ✅ Usage limits | -| Qwen | openai | OAuth | ✅ | ✅ | ✅ | ⚠️ Per request | -| Qoder | openai | OAuth / PAT | ✅ | ✅ | ✅ | ⚠️ Per request | -| Kilo Code | openai | OAuth | ✅ | ✅ | ✅ | ❌ | -| Cline | openai | OAuth | ✅ | ✅ | ✅ | ❌ | -| Kimi Coding | openai | OAuth | ✅ | ✅ | ✅ | ❌ | -| OpenRouter | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| GLM/Kimi/MiniMax | claude | API Key | ✅ | ✅ | ❌ | ❌ | -| DeepSeek | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Groq | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| xAI (Grok) | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Mistral | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Perplexity | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Together AI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Fireworks AI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Cerebras | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Cohere | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| NVIDIA NIM | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Cloudflare AI | openai | API Token + Acct ID | ✅ | ✅ | ❌ | ❌ | -| Pollinations | openai | None (no key) | ✅ | ✅ | ❌ | ❌ | -| Scaleway AI | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| LongCat | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Ollama Cloud | openai | API Key (optional) | ✅ | ✅ | ❌ | ❌ | -| HuggingFace | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Nebius | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| SiliconFlow | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Hyperbolic | openai | API Key | ✅ | ✅ | ❌ | ❌ | -| Vertex AI | gemini | Service Account | ✅ | ✅ | ✅ | ⚠️ Cloud Console | - -## Format Translation Coverage - -Detected source formats include: - -- `openai` -- `openai-responses` -- `claude` -- `gemini` - -Target formats include: - -- OpenAI chat/Responses -- Claude -- Gemini/Antigravity envelope -- Kiro -- Cursor - -Translations use **OpenAI as the hub format** — all conversions go through OpenAI as intermediate: - -``` -Source Format → OpenAI (hub) → Target Format -``` - -Translations are selected dynamically based on source payload shape and provider target format. - -Additional processing layers in the translation pipeline: - -- **Response sanitization** — Strips non-standard fields from OpenAI-format responses (both streaming and non-streaming) to ensure strict SDK compliance -- **Role normalization** — Converts `developer` → `system` for non-OpenAI targets; merges `system` → `user` for models that reject the system role (GLM, ERNIE) -- **Think tag extraction** — Parses `...` blocks from content into `reasoning_content` field -- **Structured output** — Converts OpenAI `response_format.json_schema` to Gemini's `responseMimeType` + `responseSchema` - -## Supported API Endpoints - -| Endpoint | Format | Handler | -| -------------------------------------------------- | ------------------ | ------------------------------------------------------------------- | -| `POST /v1/chat/completions` | OpenAI Chat | `src/sse/handlers/chat.ts` | -| `POST /v1/messages` | Claude Messages | Same handler (auto-detected) | -| `POST /v1/responses` | OpenAI Responses | `open-sse/handlers/responsesHandler.ts` | -| `POST /v1/embeddings` | OpenAI Embeddings | `open-sse/handlers/embeddings.ts` | -| `GET /v1/embeddings` | Model listing | API route | -| `POST /v1/images/generations` | OpenAI Images | `open-sse/handlers/imageGeneration.ts` | -| `GET /v1/images/generations` | Model listing | API route | -| `POST /v1/providers/{provider}/chat/completions` | OpenAI Chat | Dedicated per-provider with model validation | -| `POST /v1/providers/{provider}/embeddings` | OpenAI Embeddings | Dedicated per-provider with model validation | -| `POST /v1/providers/{provider}/images/generations` | OpenAI Images | Dedicated per-provider with model validation | -| `POST /v1/messages/count_tokens` | Claude Token Count | API route | -| `GET /v1/models` | OpenAI Models list | API route (chat + embedding + image + custom models) | -| `GET /api/models/catalog` | Catalog | All models grouped by provider + type | -| `POST /v1beta/models/*:streamGenerateContent` | Gemini native | API route | -| `GET/PUT/DELETE /api/settings/proxy` | Proxy Config | Network proxy configuration | -| `POST /api/settings/proxy/test` | Proxy Connectivity | Proxy health/connectivity test endpoint | -| `GET/POST/DELETE /api/provider-models` | Provider Models | Provider model metadata backing custom and managed available models | - -## Bypass Handler - -The bypass handler (`open-sse/utils/bypassHandler.ts`) intercepts known "throwaway" requests from Claude CLI — warmup pings, title extractions, and token counts — and returns a **fake response** without consuming upstream provider tokens. This is triggered only when `User-Agent` contains `claude-cli`. - -## Request Logging and Artifacts - -The older file-based request logger (`open-sse/utils/requestLogger.ts`) is retained only for -legacy compatibility. The current runtime contract uses: - -- `APP_LOG_TO_FILE=true` for application and audit logs written under `/logs/` -- SQLite-backed call log records in `call_logs` -- `${DATA_DIR}/call_logs/YYYY-MM-DD/...` artifacts when the call log pipeline is enabled - -## Failure Modes and Resilience - -## 1) Account/Provider Availability - -- connection cooldown on retryable upstream failures -- account fallback before failing request -- combo model fallback when current model/provider path is exhausted - -## 2) Token Expiry - -- pre-check and refresh with retry for refreshable providers -- 401/403 retry after refresh attempt in core path - -## 3) Stream Safety - -- disconnect-aware stream controller -- translation stream with end-of-stream flush and `[DONE]` handling -- usage estimation fallback when provider usage metadata is missing - -## 4) Cloud Sync Degradation - -- sync errors are surfaced but local runtime continues -- scheduler has retry-capable logic, but periodic execution currently calls single-attempt sync by default - -## 5) Data Integrity - -- SQLite schema migrations and auto-upgrade hooks at startup -- legacy JSON → SQLite migration compatibility path - -## 6) SSRF / Outbound URL Guard - -- `src/shared/network/outboundUrlGuard.ts` blocks all private/loopback/link-local target URLs before they reach provider executors -- Provider model discovery and validation routes use `src/shared/network/safeOutboundFetch.ts` which applies the guard before every outbound request -- Guard errors surface as `URL_GUARD_BLOCKED` with HTTP 422 and are logged to the compliance audit trail via `providerAudit.ts` - -## Observability and Operational Signals - -Runtime visibility sources: - -- console logs from `src/sse/utils/logger.ts` -- per-request usage aggregates in SQLite (`usage_history`, `call_logs`, `proxy_logs`) -- four-stage detailed payload captures in SQLite (`request_detail_logs`) when `settings.detailed_logs_enabled=true` -- textual request status log in `log.txt` (optional/compat) -- optional application log files under `logs/` when `APP_LOG_TO_FILE=true` -- optional request artifacts under `${DATA_DIR}/call_logs/` when the call log pipeline is enabled -- dashboard usage endpoints (`/api/usage/*`) for UI consumption - -Detailed request payload capture stores up to four JSON payload stages per routed call: - -- raw request received from the client -- translated request actually sent upstream -- provider response reconstructed as JSON; streamed responses are compacted to the final summary plus stream metadata -- final client response returned by OmniRoute; streamed responses are stored in the same compact summary form - -## Security-Sensitive Boundaries - -- JWT secret (`JWT_SECRET`) secures dashboard session cookie verification/signing -- Initial password bootstrap (`INITIAL_PASSWORD`) should be explicitly configured for first-run provisioning -- API key HMAC secret (`API_KEY_SECRET`) secures generated local API key format -- Provider secrets (API keys/tokens) are persisted in local DB and should be protected at filesystem level -- Cloud sync endpoints rely on API key auth + machine id semantics - -## Environment and Runtime Matrix - -Environment variables actively used by code: - -- App/auth: `JWT_SECRET`, `INITIAL_PASSWORD` -- Storage: `DATA_DIR` -- Compatible node behavior: `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` -- Optional storage base override (Linux/macOS when `DATA_DIR` unset): `XDG_CONFIG_HOME` -- Security hashing: `API_KEY_SECRET`, `MACHINE_ID_SALT` -- Logging: `APP_LOG_TO_FILE`, `APP_LOG_RETENTION_DAYS`, `CALL_LOG_RETENTION_DAYS` -- Sync/cloud URLing: `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_CLOUD_URL` -- Outbound proxy: `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, `NO_PROXY` and lowercase variants -- SOCKS5 feature flags: `ENABLE_SOCKS5_PROXY`, `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` -- Platform/runtime helpers (not app-specific config): `APPDATA`, `NODE_ENV`, `PORT`, `HOSTNAME` - -## Known Architectural Notes - -1. `usageDb` and `localDb` share the same base directory policy (`DATA_DIR` -> `XDG_CONFIG_HOME/omniroute` -> `~/.omniroute`) with legacy file migration. -2. `/api/v1/route.ts` delegates to the same unified catalog builder used by `/api/v1/models` (`src/app/api/v1/models/catalog.ts`) to avoid semantic drift. -3. Request logger writes full headers/body when enabled; treat log directory as sensitive. -4. Cloud behavior depends on correct `NEXT_PUBLIC_BASE_URL` and cloud endpoint reachability. -5. The `open-sse/` directory is published as the `@omniroute/open-sse` **npm workspace package**. Source code imports it via `@omniroute/open-sse/...` (resolved by Next.js `transpilePackages`). File paths in this document still use the directory name `open-sse/` for consistency. -6. Charts in the dashboard use **Recharts** (SVG-based) for accessible, interactive analytics visualizations (model usage bar charts, provider breakdown tables with success rates). -7. E2E tests use **Playwright** (`tests/e2e/`), run via `npm run test:e2e`. Unit tests use **Node.js test runner** (`tests/unit/`), run via `npm run test:unit`. Source code under `src/` is **TypeScript** (`.ts`/`.tsx`); the `open-sse/` workspace remains JavaScript (`.js`). -8. Settings page is organized into 7 tabs: General, Appearance, AI, Security, Routing, Resilience, Advanced. The Resilience page only configures request queue, connection cooldown, provider breaker, and wait-for-cooldown behavior; live breaker runtime state is shown on the Health page. -9. **Context Relay** strategy (`context-relay`) is split across two layers: `combo.ts` decides if a handoff should be generated, `chat.ts` injects the handoff after account resolution. Handoff data lives in `context_handoffs` SQLite table. This split is intentional because only `chat.ts` knows whether the actual account changed. -10. **Proxy enforcement** is now comprehensive: `tokenHealthCheck.ts` resolves proxy per connection, `/api/providers/validate` uses `runWithProxyContext`, and `proxyFetch.ts` uses `undici.fetch()` to maintain dispatcher compatibility on Node 22. -11. **Node.js runtime policy detection**: `/api/settings/require-login` returns `nodeVersion` and `nodeCompatible` fields. The login page renders a warning banner when the runtime falls outside the supported secure Node.js lines. - -## Operational Verification Checklist - -- Build from source: `npm run build` -- Build Docker image: `docker build -t omniroute .` -- Start service and verify: -- `GET /api/settings` -- `GET /api/v1/models` -- CLI target base URL should be `http://:20128/v1` when `PORT=20128` +- Anahtarlar/takma adlar/kombolar/fiyatlandırma: `src/app/api/keys*`, `src/app/api/models/alias`, `src/app/api/combos*`, `src/app/api/pricing` +- Kullanım: `src/app/api/usage/*` +- Senkronizasyon/bulut: `src/app/api/sync/*`, `src/app/api/cloud/*` +- CLI araç yardımcıları: `src/app/api/cli-tools/*` +- IP filtresi: `src/app/api/settings/ip-filter` (GET/PUT) +- Düşünme bütçesi: `src/app/api/settings/thinking-budget` (GET/PUT) +- Sistem istemi: `src/app/api/settings/system-prompt` (GET/PUT) +- Sıkıştırma: `src/app/api/settings/compression`, `src/app/api/compression/*`, `src/app/api/context/*` +- Oturumlar: `src/app/api/sessions` (GET) +- Hız sınırları: `src/app/api/rate-limits` (GET) +- Dayanıklılık: `src/app/api/resilience` (GET/PATCH) +- Dayanıklılık sıfırlama: `src/app/api/resilience/reset` (POST) +- Önbellek istatistikleri: `src/app/api/cache/stats` (GET/DELETE) +- Telemetri: `src/app/api/telemetry/summary` (GET) +- Bütçe: `src/app/api/usage/budget` (GET/POST) +- Geri dönüş zincirleri: `src/app/api/fallback/chains` (GET/POST/DELETE) +- Uyumluluk denetimi: `src/app/api/compliance/audit-log` (GET) +- Değerlendirmeler: `src/app/api/evals` (GET/POST), `src/app/api/evals/[suiteId]` (GET) +- Politikalar: `src/app/api/policies` (GET/POST) +- Senkronizasyon belirteçleri: `src/app/api/sync/tokens` (GET/POST), `src/app/api/sync/tokens/[id]` (GET/DELETE) +- Yapılandırma paketi: `src/app/api/sync/bundle` (GET) +- WebSocket: `src/app/api/v1/ws/route.ts` + +### 2) SSE ve Çeviri Çekirdeği + +Ana akış modülleri: + +- Giriş: `src/sse/handlers/chat.ts` +- Çekirdek orkestrasyon: `open-sse/handlers/chatCore.ts` +- Sağlayıcı yürütme bağdaştırıcıları: `open-sse/executors/*` +- Format algılama/sağlayıcı yapılandırması: `open-sse/services/provider.ts` +- Model ayrıştırma/çözümleme: `src/sse/services/model.ts`, `open-sse/services/model.ts` +- Hesap geri dönüş mantığı: `open-sse/services/accountFallback.ts` +- Çeviri kayıt defteri: `open-sse/translator/index.ts` +- Akış dönüşümleri: `open-sse/utils/stream.ts`, `open-sse/utils/streamHandler.ts` +- Kullanım çıkarma/normalizasyonu: `open-sse/utils/usageTracking.ts` +- Düşünme etiketi ayrıştırıcısı: `open-sse/utils/thinkTagParser.ts` +- Embedding işleyicisi: `open-sse/handlers/embeddings.ts` +- Görsel üretimi işleyicisi: `open-sse/handlers/imageGeneration.ts` +- Yanıt temizleme: `open-sse/handlers/responseSanitizer.ts` +- Rol normalizasyonu: `open-sse/services/roleNormalizer.ts` + +Servisler (İş Mantığı): + +- Hesap seçimi/puanlaması: `open-sse/services/accountSelector.ts` +- Bağlam yaşam döngüsü yönetimi: `open-sse/services/contextManager.ts` +- IP filtre denetimi: `open-sse/services/ipFilter.ts` +- Oturum takibi: `open-sse/services/sessionManager.ts` +- İstek tekilleştirme: `open-sse/services/signatureCache.ts` +- Sistem istemi enjeksiyonu: `open-sse/services/systemPrompt.ts` +- Düşünme bütçesi yönetimi: `open-sse/services/thinkingBudget.ts` +- Joker model yönlendirmesi: `open-sse/services/wildcardRouter.ts` +- Hız sınırı yönetimi: `open-sse/services/rateLimitManager.ts` +- Devre kesici: `src/shared/utils/circuitBreaker.ts` +- Context handoff: `open-sse/services/contextHandoff.ts` +- Sıkıştırma motorları: `open-sse/services/compression/*` +- Soğuma duyarlı yeniden deneme: `src/sse/services/cooldownAwareRetry.ts` + +--- + +## 3) Veritabanı ve Kalıcılık Mimarisi + +OmniRoute, **SQLite** (better-sqlite3) ve **WAL (Write-Ahead Logging)** günlük kaydı kullanır: + +- Çekirdek veritabanı tekili: `src/lib/db/core.ts` (`getDbInstance()`) +- Alan modülleri: `src/lib/db/` altında 120 modül (providers, combos, apiKeys, settings vb.) +- Migrasyonlar: `src/lib/db/migrations/` altında 159 sürüm kontrollü SQL dosyası +- `localDb.ts` katmanı: Yalnızca yeniden dışa aktarma (re-export) katmanıdır, asla doğrudan mantık içermez + +--- + +## 4) Güvenlik ve Yetkilendirme + +- **Yetkilendirme Hattı:** İstekler `PUBLIC`, `CLIENT_API`, `MANAGEMENT` olarak sınıflandırılır. +- **Dinlenmede Şifreleme:** AES-256-GCM ile scrypt anahtar türetme (`src/lib/db/encryption.ts`). +- **Güvenlik Önlemleri (Guardrails):** `vision-bridge` (5), `pii-masker` (10), `prompt-injection` (20) öncelik sırasıyla yürütülür. +- **SSRF Koruması:** Giden tüm URL isteklerinde özel IP'ler ve iç ağlar engellenir. + +--- + +## 5) Dayanıklılık Modeli (3 Bağımsız Katman) + +1. **Sağlayıcı Devre Kesici (Whole Provider):** Yalnızca 408/5xx durumlarında tetiklenir (OAuth: 10, API Key: 15, Local: 2 başarısızlık eşiği). +2. **Bağlantı Bekleme/Soğuma Süresi (One Connection):** 429 veya geçici hatalarda tek bir hesabı/anahtarı bekletir, kardeş anahtarlar hizmet vermeye devam eder. +3. **Model Kilitleme (One Model):** Belirli bir model kotası bittiğinde veya model bulunamadığında yalnızca o modeli kilitler. + +--- + +## 6) İstem Sıkıştırma İşlem Hattı (12 Motor) + +İstekler sağlayıcıya iletilmeden önce 12 aşamalı sıkıştırma hattından geçebilir: +1. **Session-Dedup** → 2. **CCR** → 3. **Lite** → 4. **RTK** → 5. **Responses Tool Output** → 6. **Headroom (GCF)** → 7. **Relevance** → 8. **Caveman** → 9. **Aggressive** → 10. **LLMLingua-2** → 11. **Ultra** → 12. **OmniGlyph** + +--- + +## 7) Protokoller: MCP, A2A ve ACP + +- **MCP Sunucusu (`open-sse/mcp-server/`):** 110 araç, 33 kapsam, 3 taşıma modu (stdio, SSE, Streamable HTTP). +- **A2A Sunucusu (`src/lib/a2a/`):** JSON-RPC 2.0 + SSE, 6 yetenek (`smart-routing`, `quota-management`, `provider-discovery`, `cost-analysis`, `health-report`, `list-capabilities`). +- **ACP Kayıt Defteri (`src/lib/acp/`):** Kodlama CLI araçları ve özerk ajanlar için iletişim ve durum yönetimi. diff --git a/docs/i18n/tr/docs/architecture/CODEBASE_DOCUMENTATION.md b/docs/i18n/tr/docs/architecture/CODEBASE_DOCUMENTATION.md index 73c7034856..5bd1be42a0 100644 --- a/docs/i18n/tr/docs/architecture/CODEBASE_DOCUMENTATION.md +++ b/docs/i18n/tr/docs/architecture/CODEBASE_DOCUMENTATION.md @@ -1,587 +1,107 @@ -# omniroute — Codebase Documentation (Türkçe) +--- +title: "OmniRoute Kod Tabanı Dokümantasyonu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/CODEBASE_DOCUMENTATION.md) · 🇸🇦 [ar](../../ar/docs/CODEBASE_DOCUMENTATION.md) · 🇧🇬 [bg](../../bg/docs/CODEBASE_DOCUMENTATION.md) · 🇧🇩 [bn](../../bn/docs/CODEBASE_DOCUMENTATION.md) · 🇨🇿 [cs](../../cs/docs/CODEBASE_DOCUMENTATION.md) · 🇩🇰 [da](../../da/docs/CODEBASE_DOCUMENTATION.md) · 🇩🇪 [de](../../de/docs/CODEBASE_DOCUMENTATION.md) · 🇪🇸 [es](../../es/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇷 [fa](../../fa/docs/CODEBASE_DOCUMENTATION.md) · 🇫🇮 [fi](../../fi/docs/CODEBASE_DOCUMENTATION.md) · 🇫🇷 [fr](../../fr/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [gu](../../gu/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇱 [he](../../he/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [hi](../../hi/docs/CODEBASE_DOCUMENTATION.md) · 🇭🇺 [hu](../../hu/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇩 [id](../../id/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇹 [it](../../it/docs/CODEBASE_DOCUMENTATION.md) · 🇯🇵 [ja](../../ja/docs/CODEBASE_DOCUMENTATION.md) · 🇰🇷 [ko](../../ko/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [mr](../../mr/docs/CODEBASE_DOCUMENTATION.md) · 🇲🇾 [ms](../../ms/docs/CODEBASE_DOCUMENTATION.md) · 🇳🇱 [nl](../../nl/docs/CODEBASE_DOCUMENTATION.md) · 🇳🇴 [no](../../no/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇭 [phi](../../phi/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇱 [pl](../../pl/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇹 [pt](../../pt/docs/CODEBASE_DOCUMENTATION.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/CODEBASE_DOCUMENTATION.md) · 🇷🇴 [ro](../../ro/docs/CODEBASE_DOCUMENTATION.md) · 🇷🇺 [ru](../../ru/docs/CODEBASE_DOCUMENTATION.md) · 🇸🇰 [sk](../../sk/docs/CODEBASE_DOCUMENTATION.md) · 🇸🇪 [sv](../../sv/docs/CODEBASE_DOCUMENTATION.md) · 🇰🇪 [sw](../../sw/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [ta](../../ta/docs/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [te](../../te/docs/CODEBASE_DOCUMENTATION.md) · 🇹🇭 [th](../../th/docs/CODEBASE_DOCUMENTATION.md) · 🇹🇷 [tr](../../tr/docs/CODEBASE_DOCUMENTATION.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/CODEBASE_DOCUMENTATION.md) · 🇵🇰 [ur](../../ur/docs/CODEBASE_DOCUMENTATION.md) · 🇻🇳 [vi](../../vi/docs/CODEBASE_DOCUMENTATION.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/CODEBASE_DOCUMENTATION.md) +# OmniRoute Kod Tabanı Dokümantasyonu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇸🇦 [ar](../../ar/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇧🇬 [bg](../../bg/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇧🇩 [bn](../../bn/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇨🇿 [cs](../../cs/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇩🇰 [da](../../da/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇩🇪 [de](../../de/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇪🇸 [es](../../es/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇷 [fa](../../fa/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇫🇮 [fi](../../fi/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇫🇷 [fr](../../fr/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [gu](../../gu/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇱 [he](../../he/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [hi](../../hi/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇭🇺 [hu](../../hu/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇩 [id](../../id/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇹 [it](../../it/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇯🇵 [ja](../../ja/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇰🇷 [ko](../../ko/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [mr](../../mr/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇲🇾 [ms](../../ms/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇳🇱 [nl](../../nl/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇳🇴 [no](../..//no/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇵🇭 [phi](../../phi/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇵🇱 [pl](../../pl/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇵🇹 [pt](../../pt/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇷🇴 [ro](../../ro/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇷🇺 [ru](../../ru/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇸🇰 [sk](../../sk/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇸🇪 [sv](../../sv/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇰🇪 [sw](../../sw/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [ta](../../ta/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇮🇳 [te](../../te/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇹🇭 [th](../../th/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇹🇷 [tr](../../tr/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇵🇰 [ur](../../ur/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇻🇳 [vi](../../vi/docs/architecture/CODEBASE_DOCUMENTATION.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/architecture/CODEBASE_DOCUMENTATION.md) --- -> A comprehensive, beginner-friendly guide to the **omniroute** multi-provider AI proxy router. +> **Hedef Kitle:** OmniRoute'a katkıda bulunan veya üzerine entegrasyonlar oluşturan mühendisler. +> +> Yüksek düzey mimari diyagramları ve her alt sistemin gerekçeleri için [ARCHITECTURE.md](docs/architecture/ARCHITECTURE.md) dosyasını okuyun. + +Bu belge, yeni bir mühendisin proje ağacında gezinebilmesi, çalışma zamanı katmanlarını anlaması ve yeni modüller icat etmeden nereye kod ekleyeceğini bilmesi için **bugün depoda neyin var olduğunu** açıklar. --- -## 1. What Is omniroute? +## 1. Teknoloji Yığını -omniroute is a **proxy router** that sits between AI clients (Claude CLI, Codex, Cursor IDE, etc.) and AI providers (Anthropic, Google, OpenAI, AWS, GitHub, etc.). It solves one big problem: +| Alan | Tercih | +| ------------- | ------------------------------------------------------------------------------------------------------------------------ | +| Web framework | **Next.js 16** (App Router, standalone çıktı, global middleware yok) | +| Dil | **TypeScript 6.0+** — hedef `ES2022`, `module: esnext`, `moduleResolution: bundler`, `strict: false` | +| Çalışma Zamanı| **Node.js** `>=22.22.2 <23` veya `>=24.0.0 <27` | +| Veritabanı | `better-sqlite3` ile **SQLite** (singleton, WAL günlük kaydı) | +| Masaüstü | **Electron 41** + `electron-builder` (`electron/` altında ayrı çalışma alanı) | +| Testler | **Node yerel test çalıştırıcısı** (unit/integration), **Vitest** (MCP, autoCombo, önbellek), **Playwright** (E2E) | +| Derleme | `scripts/build/build-next-isolated.mjs` üzerinden Next.js standalone | +| Lint/Format | ESLint flat config + Prettier (`lint-staged` ile Husky pre-commit) | +| Modül Sistemi | Her yerde ESM (`"type": "module"`) | +| Çalışma Alanı | npm workspace — `open-sse` alt çalışma alanıdır | -> **Different AI clients speak different "languages" (API formats), and different AI providers expect different "languages" too.** omniroute translates between them automatically. +Yol Takma Adları (`tsconfig.json`): -Think of it like a universal translator at the United Nations — any delegate can speak any language, and the translator converts it for any other delegate. +- `@/*` → `src/*` +- `@omniroute/open-sse` → `open-sse/index.ts` +- `@omniroute/open-sse/*` → `open-sse/*` + +Varsayılan HTTP portu: **`20128`** (API ve pano aynı süreci paylaşır). Veri dizini `DATA_DIR` ortam değişkenidir (varsayılan: `~/.omniroute/`). --- -## 2. Architecture Overview - -```mermaid -graph LR - subgraph Clients - A[Claude CLI] - B[Codex] - C[Cursor IDE] - D[OpenAI-compatible] - end - - subgraph omniroute - E[Handler Layer] - F[Translator Layer] - G[Executor Layer] - H[Services Layer] - end - - subgraph Providers - I[Anthropic Claude] - J[Google Gemini] - K[OpenAI / Codex] - L[GitHub Copilot] - M[AWS Kiro] - N[Antigravity] - O[Cursor API] - end - - A --> E - B --> E - C --> E - D --> E - E --> F - F --> G - G --> I - G --> J - G --> K - G --> L - G --> M - G --> N - G --> O - H -.-> E - H -.-> G -``` - -### Core Principle: Hub-and-Spoke Translation - -All format translation passes through **OpenAI format as the hub**: +## 2. Depo Düzeni ``` -Client Format → [OpenAI Hub] → Provider Format (request) -Provider Format → [OpenAI Hub] → Client Format (response) -``` - -This means you only need **N translators** (one per format) instead of **N²** (every pair). - ---- - -## 3. Project Structure - -``` -omniroute/ -├── open-sse/ ← Core proxy library (portable, framework-agnostic) -│ ├── index.js ← Main entry point, exports everything -│ ├── config/ ← Configuration & constants -│ ├── executors/ ← Provider-specific request execution -│ ├── handlers/ ← Request handling orchestration -│ ├── services/ ← Business logic (auth, models, fallback, usage) -│ ├── translator/ ← Format translation engine -│ │ ├── request/ ← Request translators (8 files) -│ │ ├── response/ ← Response translators (7 files) -│ │ └── helpers/ ← Shared translation utilities (6 files) -│ └── utils/ ← Utility functions -├── src/ ← Application layer (Express/Worker runtime) -│ ├── app/ ← Web UI, API routes, middleware -│ ├── lib/ ← Database, auth, and shared library code -│ ├── mitm/ ← Man-in-the-middle proxy utilities -│ ├── models/ ← Database models -│ ├── shared/ ← Shared utilities (wrappers around open-sse) -│ ├── sse/ ← SSE endpoint handlers -│ └── store/ ← State management -├── data/ ← Runtime data (credentials, logs) -│ └── provider-credentials.json (external credentials override, gitignored) -└── tester/ ← Test utilities +OmniRoute/ +├── src/ Next.js uygulaması (App Router, kütüphaneler, alan katmanı, sunucu, paylaşılanlar) +├── open-sse/ Akış motoru çalışma alanı (@omniroute/open-sse) +├── electron/ Masaüstü uygulaması (Electron 41 main + preload) +├── bin/ CLI giriş noktaları (omniroute, reset-password) +├── tests/ Birim, entegrasyon, e2e, protokol, çevirmen, güvenlik testleri +├── scripts/ Derleme, senkronizasyon, kontrol, migrasyon ve çalışma zamanı yardımcı betikleri +├── docs/ Genel dokümantasyon +├── public/ Statik varlıklar, PWA manifesti, servis çalışanı +├── config/ Çalışma zamanı yapılandırma örnekleri +├── CLAUDE.md Claude Code için kurallar +├── AGENTS.md Yapay zeka ajanları için derin mimari referansı +├── package.json Çalışma alanı kökü +└── tsconfig.json Yol takma adları ve derleyici seçenekleri ``` --- -## 4. Module-by-Module Breakdown +## 3. `src/` — Next.js Uygulaması -### 4.1 Config (`open-sse/config/`) - -The **single source of truth** for all provider configuration. - -| File | Purpose | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `constants.ts` | `PROVIDERS` object with base URLs, OAuth credentials (defaults), headers, and default system prompts for every provider. Also defines `HTTP_STATUS`, `ERROR_TYPES`, `COOLDOWN_MS`, `BACKOFF_CONFIG`, and `SKIP_PATTERNS`. | -| `credentialLoader.ts` | Loads external credentials from `data/provider-credentials.json` and merges them over the hardcoded defaults in `PROVIDERS`. Keeps secrets out of source control while maintaining backwards compatibility. | -| `providerModels.ts` | Central model registry: maps provider aliases → model IDs. Functions like `getModels()`, `getProviderByAlias()`. | -| `codexInstructions.ts` | System instructions injected into Codex requests (editing constraints, sandbox rules, approval policies). | -| `defaultThinkingSignature.ts` | Default "thinking" signatures for Claude and Gemini models. | -| `ollamaModels.ts` | Schema definition for local Ollama models (name, size, family, quantization). | - -#### Credential Loading Flow - -```mermaid -flowchart TD - A["App starts"] --> B["constants.ts defines PROVIDERS\nwith hardcoded defaults"] - B --> C{"data/provider-credentials.json\nexists?"} - C -->|Yes| D["credentialLoader reads JSON"] - C -->|No| E["Use hardcoded defaults"] - D --> F{"For each provider in JSON"} - F --> G{"Provider exists\nin PROVIDERS?"} - G -->|No| H["Log warning, skip"] - G -->|Yes| I{"Value is object?"} - I -->|No| J["Log warning, skip"] - I -->|Yes| K["Merge clientId, clientSecret,\ntokenUrl, authUrl, refreshUrl"] - K --> F - H --> F - J --> F - F -->|Done| L["PROVIDERS ready with\nmerged credentials"] - E --> L +``` +src/ +├── app/ App Router sayfaları + API rotaları +├── lib/ Çekirdek kütüphaneler (DB, kimlik doğrulama, OAuth, yetenekler, bellek vb.) +├── domain/ Saf alan katmanı (politika, geri dönüş, maliyet, kilitleme vb.) +├── server/ Yalnızca sunucu tarafı modüller (authz, cors, auth) +├── shared/ Tipler, sabitler, doğrulama, sözleşmeler, yardımcılar +├── mitm/ CLI entegrasyonu için Man-in-the-middle proxy yardımcıları +├── models/ Yerel model meta verileri / takma adlar +├── sse/ src/ altında yaşayan SSE işleyicileri +├── store/ İstemci tarafı Zustand durum depoları +├── middleware/ Rota düzeyinde ara yazılım yardımcıları (Next.js global middleware DEĞİL) +└── types/ TypeScript tip tanımları ``` --- -### 4.2 Executors (`open-sse/executors/`) +## 4. `open-sse/` — Akış ve Yürütücü Motoru -Executors encapsulate **provider-specific logic** using the **Strategy Pattern**. Each executor overrides base methods as needed. - -```mermaid -classDiagram - class BaseExecutor { - +buildUrl(model, stream, options) - +buildHeaders(credentials, stream, body) - +transformRequest(body, model, stream, credentials) - +execute(url, options) - +shouldRetry(status, error) - +refreshCredentials(credentials, log) - } - - class DefaultExecutor { - +refreshCredentials() - } - - class AntigravityExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +shouldRetry() - +refreshCredentials() - } - - class CursorExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +parseResponse() - +generateChecksum() - } - - class KiroExecutor { - +buildUrl() - +buildHeaders() - +transformRequest() - +parseEventStream() - +refreshCredentials() - } - - BaseExecutor <|-- DefaultExecutor - BaseExecutor <|-- AntigravityExecutor - BaseExecutor <|-- CursorExecutor - BaseExecutor <|-- KiroExecutor - BaseExecutor <|-- CodexExecutor - BaseExecutor <|-- GithubExecutor ``` - -| Executor | Provider | Key Specializations | -| ---------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | -| `base.ts` | — | Abstract base: URL building, headers, retry logic, credential refresh | -| `default.ts` | Claude, Gemini, OpenAI, GLM, Kimi, MiniMax | Generic OAuth token refresh for standard providers | -| `antigravity.ts` | Google Cloud Code | Project/session ID generation, multi-URL fallback, custom retry parsing from error messages ("reset after 2h7m23s") | -| `cursor.ts` | Cursor IDE | **Most complex**: SHA-256 checksum auth, Protobuf request encoding, binary EventStream → SSE response parsing | -| `codex.ts` | OpenAI Codex | Injects system instructions, manages thinking levels, removes unsupported parameters | -| `github.ts` | GitHub Copilot | Dual token system (GitHub OAuth + Copilot token), VSCode header mimicking | -| `kiro.ts` | AWS CodeWhisperer | AWS EventStream binary parsing, AMZN event frames, token estimation | -| `index.ts` | — | Factory: maps provider name → executor class, with default fallback | - ---- - -### 4.3 Handlers (`open-sse/handlers/`) - -The **orchestration layer** — coordinates translation, execution, streaming, and error handling. - -| File | Purpose | -| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `chatCore.ts` | **Central orchestrator** (~600 lines). Handles the complete request lifecycle: format detection → translation → executor dispatch → streaming/non-streaming response → token refresh → error handling → usage logging. | -| `responsesHandler.ts` | Adapter for OpenAI's Responses API: converts Responses format → Chat Completions → sends to `chatCore` → converts SSE back to Responses format. | -| `embeddings.ts` | Embedding generation handler: resolves embedding model → provider, dispatches to provider API, returns OpenAI-compatible embedding response. Supports 6+ providers. | -| `imageGeneration.ts` | Image generation handler: resolves image model → provider, supports OpenAI-compatible, Gemini-image (Antigravity), and fallback (Nebius) modes. Returns base64 or URL images. | - -#### Request Lifecycle (chatCore.ts) - -```mermaid -sequenceDiagram - participant Client - participant chatCore - participant Translator - participant Executor - participant Provider - - Client->>chatCore: Request (any format) - chatCore->>chatCore: Detect source format - chatCore->>chatCore: Check bypass patterns - chatCore->>chatCore: Resolve model & provider - chatCore->>Translator: Translate request (source → OpenAI → target) - chatCore->>Executor: Get executor for provider - Executor->>Executor: Build URL, headers, transform request - Executor->>Executor: Refresh credentials if needed - Executor->>Provider: HTTP fetch (streaming or non-streaming) - - alt Streaming - Provider-->>chatCore: SSE stream - chatCore->>chatCore: Pipe through SSE transform stream - Note over chatCore: Transform stream translates
each chunk: target → OpenAI → source - chatCore-->>Client: Translated SSE stream - else Non-streaming - Provider-->>chatCore: JSON response - chatCore->>Translator: Translate response - chatCore-->>Client: Translated JSON - end - - alt Error (401, 429, 500...) - chatCore->>Executor: Retry with credential refresh - chatCore->>chatCore: Account fallback logic - end +open-sse/ +├── executors/ Sağlayıcıya özel istek yürütücüleri (101 modül) +├── handlers/ API türü başına istek işleyicileri (chat, responses, embeddings, images vb.) +├── mcp-server/ 110 araç ve 33 kapsam içeren yerleşik MCP sunucusu +├── services/ Yönlendirme, hız sınırlamaları, auto-combo, oturum yönetimi vb. +├── translator/ OpenAI ↔ Claude ↔ Gemini ↔ Ollama ↔ DeepSeek format çevirmenleri +├── transformer/ OpenAI Responses API dönüştürücüsü +└── utils/ Akış, TLS, proxy, günlük kaydı yardımcıları ``` --- -### 4.4 Services (`open-sse/services/`) +## 5. `tests/` — Test Paketleri -Business logic that supports the handlers and executors. - -| File | Purpose | -| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `provider.ts` | **Format detection** (`detectFormat`): analyzes request body structure to identify Claude/OpenAI/Gemini/Antigravity/Responses formats (includes `max_tokens` heuristic for Claude). Also: URL building, header building, thinking config normalization. Supports `openai-compatible-*` and `anthropic-compatible-*` dynamic providers. | -| `model.ts` | Model string parsing (`claude/model-name` → `{provider: "claude", model: "model-name"}`), alias resolution with collision detection, input sanitization (rejects path traversal/control chars), and model info resolution with async alias getter support. | -| `accountFallback.ts` | Rate-limit handling: exponential backoff (1s → 2s → 4s → max 2min), account cooldown management, error classification (which errors trigger fallback vs. not). | -| `tokenRefresh.ts` | OAuth token refresh for **every provider**: Google (Gemini, Antigravity), Claude, Codex, Qwen, Qoder, GitHub (OAuth + Copilot dual-token), Kiro (AWS SSO OIDC + Social Auth). Includes in-flight promise deduplication cache and retry with exponential backoff. | -| `combo.ts` | **Combo models**: chains of fallback models. If model A fails with a fallback-eligible error, try model B, then C, etc. Returns actual upstream status codes. | -| `usage.ts` | Fetches quota/usage data from provider APIs (GitHub Copilot quotas, Antigravity model quotas, Codex rate limits, Kiro usage breakdowns, Claude settings). | -| `accountSelector.ts` | Smart account selection with scoring algorithm: considers priority, health status, round-robin position, and cooldown state to pick the optimal account for each request. | -| `contextManager.ts` | Request context lifecycle management: creates and tracks per-request context objects with metadata (request ID, timestamps, provider info) for debugging and logging. | -| `ipFilter.ts` | IP-based access control: supports allowlist and blocklist modes. Validates client IP against configured rules before processing API requests. | -| `sessionManager.ts` | Session tracking with client fingerprinting: tracks active sessions using hashed client identifiers, monitors request counts, and provides session metrics. | -| `signatureCache.ts` | Request signature-based deduplication cache: prevents duplicate requests by caching recent request signatures and returning cached responses for identical requests within a time window. | -| `systemPrompt.ts` | Global system prompt injection: prepends or appends a configurable system prompt to all requests, with per-provider compatibility handling. | -| `thinkingBudget.ts` | Reasoning token budget management: supports passthrough, auto (strip thinking config), custom (fixed budget), and adaptive (complexity-scaled) modes for controlling thinking/reasoning tokens. | -| `wildcardRouter.ts` | Wildcard model pattern routing: resolves wildcard patterns (e.g., `*/claude-*`) to concrete provider/model pairs based on availability and priority. | - -#### Token Refresh Deduplication - -```mermaid -sequenceDiagram - participant R1 as Request 1 - participant R2 as Request 2 - participant Cache as refreshPromiseCache - participant OAuth as OAuth Provider - - R1->>Cache: getAccessToken("gemini", token) - Cache->>Cache: No in-flight promise - Cache->>OAuth: Start refresh - R2->>Cache: getAccessToken("gemini", token) - Cache->>Cache: Found in-flight promise - Cache-->>R2: Return existing promise - OAuth-->>Cache: New access token - Cache-->>R1: New access token - Cache-->>R2: Same access token (shared) - Cache->>Cache: Delete cache entry -``` - -#### Account Fallback State Machine - -```mermaid -stateDiagram-v2 - [*] --> Active - Active --> Error: Request fails (401/429/500) - Error --> Cooldown: Apply backoff - Cooldown --> Active: Cooldown expires - Active --> Active: Request succeeds (reset backoff) - - state Error { - [*] --> ClassifyError - ClassifyError --> ShouldFallback: Rate limit / Auth / Transient - ClassifyError --> NoFallback: 400 Bad Request - } - - state Cooldown { - [*] --> ExponentialBackoff - ExponentialBackoff: Level 0 = 1s - ExponentialBackoff: Level 1 = 2s - ExponentialBackoff: Level 2 = 4s - ExponentialBackoff: Max = 2min - } -``` - -#### Combo Model Chain - -```mermaid -flowchart LR - A["Request with\ncombo model"] --> B["Model A"] - B -->|"2xx Success"| C["Return response"] - B -->|"429/401/500"| D{"Fallback\neligible?"} - D -->|Yes| E["Model B"] - D -->|No| F["Return error"] - E -->|"2xx Success"| C - E -->|"429/401/500"| G{"Fallback\neligible?"} - G -->|Yes| H["Model C"] - G -->|No| F - H -->|"2xx Success"| C - H -->|"Fail"| I["All failed →\nReturn last status"] -``` - ---- - -### 4.5 Translator (`open-sse/translator/`) - -The **format translation engine** using a self-registering plugin system. - -#### Mimari - -```mermaid -graph TD - subgraph "Request Translation" - A["Claude → OpenAI"] - B["Gemini → OpenAI"] - C["Antigravity → OpenAI"] - D["OpenAI Responses → OpenAI"] - E["OpenAI → Claude"] - F["OpenAI → Gemini"] - G["OpenAI → Kiro"] - H["OpenAI → Cursor"] - end - - subgraph "Response Translation" - I["Claude → OpenAI"] - J["Gemini → OpenAI"] - K["Kiro → OpenAI"] - L["Cursor → OpenAI"] - M["OpenAI → Claude"] - N["OpenAI → Antigravity"] - O["OpenAI → Responses"] - end -``` - -| Directory | Files | Description | -| ------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `request/` | 8 translators | Convert request bodies between formats. Each file self-registers via `register(from, to, fn)` on import. | -| `response/` | 7 translators | Convert streaming response chunks between formats. Handles SSE event types, thinking blocks, tool calls. | -| `helpers/` | 6 helpers | Shared utilities: `claudeHelper` (system prompt extraction, thinking config), `geminiHelper` (parts/contents mapping), `openaiHelper` (format filtering), `toolCallHelper` (ID generation, missing response injection), `maxTokensHelper`, `responsesApiHelper`. | -| `index.ts` | — | Translation engine: `translateRequest()`, `translateResponse()`, state management, registry. | -| `formats.ts` | — | Format constants: `OPENAI`, `CLAUDE`, `GEMINI`, `ANTIGRAVITY`, `KIRO`, `CURSOR`, `OPENAI_RESPONSES`. | - -#### Key Design: Self-Registering Plugins - -```javascript -// Each translator file calls register() on import: -import { register } from "../index.js"; -register("claude", "openai", translateClaudeToOpenAI); - -// The index.js imports all translator files, triggering registration: -import "./request/claude-to-openai.js"; // ← self-registers -``` - ---- - -### 4.6 Utils (`open-sse/utils/`) - -| File | Purpose | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `error.ts` | Error response building (OpenAI-compatible format), upstream error parsing, Antigravity retry-time extraction from error messages, SSE error streaming. | -| `stream.ts` | **SSE Transform Stream** — the core streaming pipeline. Two modes: `TRANSLATE` (full format translation) and `PASSTHROUGH` (normalize + extract usage). Handles chunk buffering, usage estimation, content length tracking. Per-stream encoder/decoder instances avoid shared state. | -| `streamHelpers.ts` | Low-level SSE utilities: `parseSSELine` (whitespace-tolerant), `hasValuableContent` (filters empty chunks for OpenAI/Claude/Gemini), `fixInvalidId`, `formatSSE` (format-aware SSE serialization with `perf_metrics` cleanup). | -| `usageTracking.ts` | Token usage extraction from any format (Claude/OpenAI/Gemini/Responses), estimation with separate tool/message char-per-token ratios, buffer addition (2000 tokens safety margin), format-specific field filtering, console logging with ANSI colors. | -| `requestLogger.ts` | Legacy file-based request logging helper kept for compatibility. Current deployments should prefer `APP_LOG_TO_FILE` for application logs and the call log pipeline for persisted request artifacts. | -| `bypassHandler.ts` | Intercepts specific patterns from Claude CLI (title extraction, warmup, count) and returns fake responses without calling any provider. Supports both streaming and non-streaming. Intentionally limited to Claude CLI scope. | -| `networkProxy.ts` | Resolves outbound proxy URL for a given provider with precedence: provider-specific config → global config → environment variables (`HTTPS_PROXY`/`HTTP_PROXY`/`ALL_PROXY`). Supports `NO_PROXY` exclusions. Caches config for 30s. | - -#### SSE Streaming Pipeline - -```mermaid -flowchart TD - A["Provider SSE stream"] --> B["TextDecoder\n(per-stream instance)"] - B --> C["Buffer lines\n(split on newline)"] - C --> D["parseSSELine()\n(trim whitespace, parse JSON)"] - D --> E{"Mode?"} - E -->|TRANSLATE| F["translateResponse()\ntarget → OpenAI → source"] - E -->|PASSTHROUGH| G["fixInvalidId()\nnormalize chunk"] - F --> H["hasValuableContent()\nfilter empty chunks"] - G --> H - H -->|"Has content"| I["extractUsage()\ntrack token counts"] - H -->|"Empty"| J["Skip chunk"] - I --> K["formatSSE()\nserialize + clean perf_metrics"] - K --> L["TextEncoder\n(per-stream instance)"] - L --> M["Enqueue to\nclient stream"] - - style A fill:#f9f,stroke:#333 - style M fill:#9f9,stroke:#333 -``` - -#### Request Logger Session Structure - -``` -logs/ -└── claude_gemini_claude-sonnet_20260208_143045/ - ├── 1_req_client.json ← Raw client request - ├── 2_req_source.json ← After initial conversion - ├── 3_req_openai.json ← OpenAI intermediate format - ├── 4_req_target.json ← Final target format - ├── 5_res_provider.txt ← Provider SSE chunks (streaming) - ├── 5_res_provider.json ← Provider response (non-streaming) - ├── 6_res_openai.txt ← OpenAI intermediate chunks - ├── 7_res_client.txt ← Client-facing SSE chunks - └── 6_error.json ← Error details (if any) -``` - ---- - -### 4.7 Application Layer (`src/`) - -| Directory | Purpose | -| ------------- | ---------------------------------------------------------------------- | -| `src/app/` | Web UI, API routes, Express middleware, OAuth callback handlers | -| `src/lib/` | Database access (`localDb.ts`, `usageDb.ts`), authentication, shared | -| `src/mitm/` | Man-in-the-middle proxy utilities for intercepting provider traffic | -| `src/models/` | Database model definitions | -| `src/shared/` | Wrappers around open-sse functions (provider, stream, error, etc.) | -| `src/sse/` | SSE endpoint handlers that wire the open-sse library to Express routes | -| `src/store/` | Application state management | - -#### Notable API Routes - -| Route | Methods | Purpose | -| --------------------------------------------- | --------------- | ------------------------------------------------------------------------------------- | -| `/api/provider-models` | GET/POST/DELETE | CRUD for custom models per provider | -| `/api/models/catalog` | GET | Aggregated catalog of all models (chat, embedding, image, custom) grouped by provider | -| `/api/settings/proxy` | GET/PUT/DELETE | Hierarchical outbound proxy configuration (`global/providers/combos/keys`) | -| `/api/settings/proxy/test` | POST | Validates proxy connectivity and returns public IP/latency | -| `/v1/providers/[provider]/chat/completions` | POST | Dedicated per-provider chat completions with model validation | -| `/v1/providers/[provider]/embeddings` | POST | Dedicated per-provider embeddings with model validation | -| `/v1/providers/[provider]/images/generations` | POST | Dedicated per-provider image generation with model validation | -| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist management | -| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget configuration (passthrough/auto/custom/adaptive) | -| `/api/settings/system-prompt` | GET/PUT | Global system prompt injection for all requests | -| `/api/sessions` | GET | Active session tracking and metrics | -| `/api/rate-limits` | GET | Per-account rate limit status | - ---- - -## 5. Key Design Patterns - -### 5.1 Hub-and-Spoke Translation - -All formats translate through **OpenAI format as the hub**. Adding a new provider only requires writing **one pair** of translators (to/from OpenAI), not N pairs. - -### 5.2 Executor Strategy Pattern - -Each provider has a dedicated executor class inheriting from `BaseExecutor`. The factory in `executors/index.ts` selects the right one at runtime. - -### 5.3 Self-Registering Plugin System - -Translator modules register themselves on import via `register()`. Adding a new translator is just creating a file and importing it. - -### 5.4 Account Fallback with Exponential Backoff - -When a provider returns 429/401/500, the system can switch to the next account, applying exponential cooldowns (1s → 2s → 4s → max 2min). - -### 5.5 Combo Model Chains - -A "combo" groups multiple `provider/model` strings. If the first fails, fallback to the next automatically. - -### 5.6 Stateful Streaming Translation - -Response translation maintains state across SSE chunks (thinking block tracking, tool call accumulation, content block indexing) via the `initState()` mechanism. - -### 5.7 Usage Safety Buffer - -A 2000-token buffer is added to reported usage to prevent clients from hitting context window limits due to overhead from system prompts and format translation. - ---- - -## 6. Supported Formats - -| Format | Direction | Identifier | -| ----------------------- | --------------- | ------------------ | -| OpenAI Chat Completions | source + target | `openai` | -| OpenAI Responses API | source + target | `openai-responses` | -| Anthropic Claude | source + target | `claude` | -| Google Gemini | source + target | `gemini` | -| Antigravity | source + target | `antigravity` | -| AWS Kiro | target only | `kiro` | -| Cursor | target only | `cursor` | - ---- - -## 7. Supported Providers - -| Provider | Auth Method | Executor | Key Notes | -| ------------------------ | ---------------------- | ----------- | --------------------------------------------- | -| Anthropic Claude | API key or OAuth | Default | Uses `x-api-key` header | -| Google Gemini | API key or OAuth | Default | Uses `x-goog-api-key` header | -| Antigravity | OAuth | Antigravity | Multi-URL fallback, custom retry parsing | -| OpenAI | API key | Default | Standard Bearer auth | -| Codex | OAuth | Codex | Injects system instructions, manages thinking | -| GitHub Copilot | OAuth + Copilot token | Github | Dual token, VSCode header mimicking | -| Kiro (AWS) | AWS SSO OIDC or Social | Kiro | Binary EventStream parsing | -| Cursor IDE | Checksum auth | Cursor | Protobuf encoding, SHA-256 checksums | -| Qwen | OAuth | Default | Standard auth | -| Qoder | OAuth (Basic + Bearer) | Default | Dual auth header | -| OpenRouter | API key | Default | Standard Bearer auth | -| GLM, Kimi, MiniMax | API key | Default | Claude-compatible, use `x-api-key` | -| `openai-compatible-*` | API key | Default | Dynamic: any OpenAI-compatible endpoint | -| `anthropic-compatible-*` | API key | Default | Dynamic: any Claude-compatible endpoint | - ---- - -## 8. Data Flow Summary - -### Streaming Request - -```mermaid -flowchart LR - A["Client"] --> B["detectFormat()"] - B --> C["translateRequest()\nsource → OpenAI → target"] - C --> D["Executor\nbuildUrl + buildHeaders"] - D --> E["fetch(providerURL)"] - E --> F["createSSEStream()\nTRANSLATE mode"] - F --> G["parseSSELine()"] - G --> H["translateResponse()\ntarget → OpenAI → source"] - H --> I["extractUsage()\n+ addBuffer"] - I --> J["formatSSE()"] - J --> K["Client receives\ntranslated SSE"] - K --> L["logUsage()\nsaveRequestUsage()"] -``` - -### Non-Streaming Request - -```mermaid -flowchart LR - A["Client"] --> B["detectFormat()"] - B --> C["translateRequest()\nsource → OpenAI → target"] - C --> D["Executor.execute()"] - D --> E["translateResponse()\ntarget → OpenAI → source"] - E --> F["Return JSON\nresponse"] -``` - -### Bypass Flow (Claude CLI) - -```mermaid -flowchart LR - A["Claude CLI request"] --> B{"Match bypass\npattern?"} - B -->|"Title/Warmup/Count"| C["Generate fake\nOpenAI response"] - B -->|"No match"| D["Normal flow"] - C --> E["Translate to\nsource format"] - E --> F["Return without\ncalling provider"] -``` +- `tests/unit/`: Node.js yerleşik test çalıştırıcısı ile 2.700'den fazla test dosyası +- `tests/integration/`: Modüller arası entegrasyon testleri +- `tests/e2e/`: Playwright uçtan uca tarayıcı testleri +- `tests/security/`: İstem enjeksiyonu, PII, yetkilendirme güvenlik testleri +- `tests/translator/`: Format çevirmen doğruluk testleri diff --git a/docs/i18n/tr/docs/cloudflare-zero-trust-guide.md b/docs/i18n/tr/docs/cloudflare-zero-trust-guide.md index bc7c719766..40071306cc 100644 --- a/docs/i18n/tr/docs/cloudflare-zero-trust-guide.md +++ b/docs/i18n/tr/docs/cloudflare-zero-trust-guide.md @@ -1,106 +1,103 @@ -# Guia Completo: Cloudflare Tunnel & Zero Trust (Split-Port) (Türkçe) +# Kapsamlı Kılavuz: Cloudflare Tunnel ve Zero Trust (Split-Port) (Türkçe) 🌐 **Languages:** 🇺🇸 [English](../../../../docs/cloudflare-zero-trust-guide.md) · 🇪🇸 [es](../../es/docs/cloudflare-zero-trust-guide.md) · 🇫🇷 [fr](../../fr/docs/cloudflare-zero-trust-guide.md) · 🇩🇪 [de](../../de/docs/cloudflare-zero-trust-guide.md) · 🇮🇹 [it](../../it/docs/cloudflare-zero-trust-guide.md) · 🇷🇺 [ru](../../ru/docs/cloudflare-zero-trust-guide.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/cloudflare-zero-trust-guide.md) · 🇯🇵 [ja](../../ja/docs/cloudflare-zero-trust-guide.md) · 🇰🇷 [ko](../../ko/docs/cloudflare-zero-trust-guide.md) · 🇸🇦 [ar](../../ar/docs/cloudflare-zero-trust-guide.md) · 🇮🇳 [hi](../../hi/docs/cloudflare-zero-trust-guide.md) · 🇮🇳 [in](../../in/docs/cloudflare-zero-trust-guide.md) · 🇹🇭 [th](../../th/docs/cloudflare-zero-trust-guide.md) · 🇻🇳 [vi](../../vi/docs/cloudflare-zero-trust-guide.md) · 🇮🇩 [id](../../id/docs/cloudflare-zero-trust-guide.md) · 🇲🇾 [ms](../../ms/docs/cloudflare-zero-trust-guide.md) · 🇳🇱 [nl](../../nl/docs/cloudflare-zero-trust-guide.md) · 🇵🇱 [pl](../../pl/docs/cloudflare-zero-trust-guide.md) · 🇸🇪 [sv](../../sv/docs/cloudflare-zero-trust-guide.md) · 🇳🇴 [no](../../no/docs/cloudflare-zero-trust-guide.md) · 🇩🇰 [da](../../da/docs/cloudflare-zero-trust-guide.md) · 🇫🇮 [fi](../../fi/docs/cloudflare-zero-trust-guide.md) · 🇵🇹 [pt](../../pt/docs/cloudflare-zero-trust-guide.md) · 🇷🇴 [ro](../../ro/docs/cloudflare-zero-trust-guide.md) · 🇭🇺 [hu](../../hu/docs/cloudflare-zero-trust-guide.md) · 🇧🇬 [bg](../../bg/docs/cloudflare-zero-trust-guide.md) · 🇸🇰 [sk](../../sk/docs/cloudflare-zero-trust-guide.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/cloudflare-zero-trust-guide.md) · 🇮🇱 [he](../../he/docs/cloudflare-zero-trust-guide.md) · 🇵🇭 [phi](../../phi/docs/cloudflare-zero-trust-guide.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/cloudflare-zero-trust-guide.md) · 🇨🇿 [cs](../../cs/docs/cloudflare-zero-trust-guide.md) · 🇹🇷 [tr](../../tr/docs/cloudflare-zero-trust-guide.md) --- -Este guia documenta o padrão ouro de infraestrutura de rede para proteger o **OmniRoute** e expor sua aplicação de forma segura para a internet, **sem abrir nenhuma porta (Zero Inbound)**. +Bu kılavuz, **OmniRoute**'u korumak ve uygulamanızı **hiçbir gelen bağlantı portu açmadan (Zero Inbound)** internete güvenli bir şekilde sunmak için altın standart ağ altyapısını belgeler. -## O que foi feito na sua VM? +## Sanal Makinenizde (VM) Ne Yapıldı? -Nós ativamos o OmniRoute em modo **Split-Port** através do PM2: +OmniRoute'u PM2 aracılığıyla **Split-Port (Ayrık Port)** modunda etkinleştiriyoruz: -- **Porta \`20128\`:** Roda **apenas a API** `/v1`. -- **Porta \`20129\`:** Roda **apenas o Dashboard** Administrativo visual. +- **Port `20128`:** **Yalnızca API** (`/v1`) çalıştırır. +- **Port `20129`:** **Yalnızca görsel Yönetim Panosunu** çalıştırır. -Além disso, o serviço interno exige \`REQUIRE_API_KEY=true\`, o que significa que nenhum agente pode consumir os endpoints da API sem enviar um "Bearer Token" legítimo gerado na aba API Keys do Painel. +Ayrıca dahili servis `REQUIRE_API_KEY=true` gerektirir; bu da hiçbir ajanın Panonun API Keys sekmesinde oluşturulan geçerli bir "Bearer Token" göndermeden API uç noktalarını tüketemeyeceği anlamına gelir. -Isso nos permite criar duas regras completamente independentes na rede. É aqui que entra o **Cloudflare Tunnel (cloudflared)**. +Bu yapı ağda tamamen bağımsız iki kural oluşturmamıza olanak tanır. **Cloudflare Tunnel (cloudflared)** burada devreye girer. --- -## 1. Como Criar o Túnel na Cloudflare +## 1. Cloudflare'de Tünel Oluşturma -O utilitário \`cloudflared\` já está instalado na sua máquina. Siga os passos na nuvem: +`cloudflared` yardımcı programı makinenizde kuruludur. Bulut adımlarını izleyin: -1. Acesse seu painel **Cloudflare Zero Trust** (One.dash.cloudflare.com). -2. No menu à esquerda, vá em **Networks > Tunnels**. -3. Clique em **Add a Tunnel**, escolha **Cloudflared** e dê o nome \`OmniRoute-VM\`. -4. Ele vai gerar um comando na tela chamado "Install and run a connector". **Você só precisa copiar o Token (a string longa após `--token`)**. -5. Logue via SSH na sua máquina virtual (ou Terminal do Proxmox) e execute: - \`\`\`bash - # Inicia e amarra o túnel permanentemente à sua conta - cloudflared service install SEU_TOKEN_GIGANTE_AQUI - \`\`\` +1. **Cloudflare Zero Trust** panonuza erişin (one.dash.cloudflare.com). +2. Sol menüden **Networks > Tunnels** yolunu izleyin. +3. **Add a Tunnel** seçeneğine tıklayın, **Cloudflared** seçin ve tünele `OmniRoute-VM` adını verin. +4. Ekranda "Install and run a connector" başlıklı bir komut oluşturulacaktır. **Yalnızca Belirteci (`--token` sonrasındaki uzun dize) kopyalamanız yeterlidir**. +5. Sanal makinenize SSH ile bağlanın ve çalıştırın: + ```bash + # Tüneli başlatır ve kalıcı olarak hesabınıza bağlar + cloudflared service install BURAYA_UZUN_TOKENINIZI_YAPISTIRIN + ``` --- -## 2. Configurando o Roteamento (Public Hostnames) +## 2. Yönlendirmeyi Yapılandırma (Public Hostnames) -Ainda na tela do Tunnel recém-criado, vá para a aba **Public Hostnames** e adicione as **duas** rotas, aproveitando a separação que fizemos: +Yeni oluşturulan Tunnel ekranında **Public Hostnames** sekmesine gidin ve yaptığımız ayrımdan yararlanarak **iki** rotayı ekleyin: -### Rota 1: API Segura (Limitada) +### Rota 1: Güvenli API (Kısıtlı) -- **Subdomain:** \`api\` -- **Domain:** \`seuglobal.com.br\` (escolha seu domínio real) -- **Service Type:** \`HTTP\` -- **URL:** \`127.0.0.1:20128\` _(Porta interna da API)_ +- **Subdomain:** `api` +- **Domain:** `alanadiniz.com` (kendi gerçek alan adınızı seçin) +- **Service Type:** `HTTP` +- **URL:** `127.0.0.1:20128` _(Dahili API portu)_ -### Rota 2: Painel Zero Trust (Fechado) +### Rota 2: Zero Trust Pano (Kapalı) -- **Subdomain:** \`omniroute\` ou \`painel\` -- **Domain:** \`seuglobal.com.br\` -- **Service Type:** \`HTTP\` -- **URL:** \`127.0.0.1:20129\` _(Porta interna do App/Visual)_ - -Neste momento, a conectividade "Física" está resolvida. Agora vamos blindar de verdade. +- **Subdomain:** `omniroute` veya `panel` +- **Domain:** `alanadiniz.com` +- **Service Type:** `HTTP` +- **URL:** `127.0.0.1:20129` _(Dahili Uygulama/Pano portu)_ --- -## 3. Blindando o Painel com Zero Trust (Access) +## 3. Panoyu Zero Trust (Access) ile Güçlendirme -Nenhuma senha local protege melhor o seu painel do que remover totalmente o acesso a ele da internet aberta. +Hiçbir yerel şifre, panonuzu internete tamamen kapatmaktan daha iyi koruyamaz. -1. No painel Zero Trust, vá em **Access > Applications > Add an application**. -2. Selecione **Self-hosted**. -3. Em **Application name**, coloque \`Painel OmniRoute\`. -4. Em **Application domain**, coloque \`omniroute.seuglobal.com.br\` (O mesmo que você fez na "Rota 2"). -5. Clique em **Next**. -6. Em **Rule action**, escolha \`Allow\`. Em nome da Rule coloque \`Admin Apenas\`. -7. Em **Include**, no seletor de "Selector" escolha \`Emails\` e digite o seu email, por exemplo \`admin@spgeo.com.br\`. -8. Salve (`Add application`). +1. Zero Trust panosunda **Access > Applications > Add an application** seçeneğine gidin. +2. **Self-hosted** seçin. +3. **Application name** kısmına `OmniRoute Paneli` yazın. +4. **Application domain** kısmına `omniroute.alanadiniz.com` ("Rota 2"de belirlediğiniz adres) yazın. +5. **Next** butonuna tıklayın. +6. **Rule action** için `Allow` seçin. Kural adına `Yalnızca Yönetici` yazın. +7. **Include** altında "Selector" olarak `Emails` seçin ve e-posta adresinizi girin (örn. `admin@alanadiniz.com`). +8. Kaydedin (`Add application`). -> **O que isso fez:** Se você tentar abrir \`omniroute.seuglobal.com.br\`, não cai mais na sua aplicação OmniRoute! Cai numa tela elegante da Cloudflare pedindo para digitar seu email. Somente se você (ou o email que você botou) for digitado lá, ele recebe no Outlook/Gmail um código de 6 dígitos temporário que libera o túnel até a porta \`20129\`. +> **Bu ne sağladı:** Artık `omniroute.alanadiniz.com` adresini açtığınızda doğrudan uygulamanıza düşmez! Cloudflare'in e-posta isteyen şık bir giriş ekranı çıkar. Yalnızca belirttiğiniz e-posta girildiğinde, gelen kutunuza `20129` portuna tüneli açan tek kullanımlık 6 haneli bir kod gönderilir. --- -## 4. Limitando e Protegendo a API com Rate Limit (WAF) +## 4. API'yi Hız Sınırı (WAF) ile Korumak -O Dashboard do Zero Trust não se aplica à rota da API (\`api.seuglobal.com.br\`), porque é um acesso programático via ferramentas automatizadas (agentes) sem navegador. Para ele, usaremos o Firewall principal (WAF) da Cloudflare. +Zero Trust Panosu API rotasına (`api.alanadiniz.com`) uygulanmaz; çünkü bu tarayıcısız, otomatik araçlar (ajanlar) aracılığıyla yapılan programatik bir erişimdir. Bunun için Cloudflare'in ana Güvenlik Duvarını (WAF) kullanacağız. -1. Acesse o **Painel Normal** da Cloudflare (dash.cloudflare.com) e entre no seu Domínio. -2. No menu esquerdo, vá em **Security > WAF > Rate limiting rules**. -3. Clique em **Create rule**. -4. **Name:** \`Anti-Abuso OmniRoute API\` +1. Cloudflare **Normal Panosuna** (dash.cloudflare.com) erişin ve Alan Adınıza girin. +2. Sol menüden **Security > WAF > Rate limiting rules** yolunu izleyin. +3. **Create rule** butonuna tıklayın. +4. **Name:** `OmniRoute API Kötüye Kullanım Önleme` 5. **If incoming requests match...** - - Escolha em Field: \`Hostname\` - - Operator: \`equals\` - - Value: \`api.seuglobal.com.br\` -6. Em **With the same characteristics:** Mantenha \`IP\`. -7. Nos limites (Limit): - - **When requests exceed:** \`50\` - - **Period:** \`1 minute\` -8. No final, em **Action**: \`Block\` (Bloquear) e decida se o bloqueio dura por 1 minuto ou 1 hora. + - Field: `Hostname` + - Operator: `equals` + - Value: `api.alanadiniz.com` +6. **With the same characteristics:** `IP` olarak bırakın. +7. Sınırlar (Limit): + - **When requests exceed:** `50` + - **Period:** `1 minute` +8. **Action:** `Block` seçin ve engelleme süresini belirleyin (1 dakika veya 1 saat). 9. **Deploy**. -> **O que isso fez:** Ninguém pode mandar mais de 50 requisições num período de 60 segundos na sua URL de API. Como você roda vários agentes e os consumos por trás já batem rate limit e já rastreiam tokens, isso é apenas uma medida na Borda da Internet (Edge Layer) que protege sua Instância On-Premises de cair por estresse térmico antes mesmo do tráfego descer pelo túnel. +> **Bu ne sağladı:** Hiç kimse API URL'nize 60 saniyelik bir süre içinde 50'den fazla istek gönderemez. Bu, trafiğin tünelden sunucunuza inmesine gerek kalmadan ağın kenarında (Edge Layer) sunucunuzu aşırı yükten korur. --- -## Finalização +## Özet -1. A sua VM **não possui nenhuma porta exposta** em `/etc/ufw`. -2. O OmniRoute só conversa HTTPS saindo (\`cloudflared\`) e não recebendo TCP direto do mundo. -3. Seus requets pro OpenAI são ofuscados porque configuramos eles globalmente pra passar em um Proxy SOCKS5 (A nuvem não liga pro SOCKS5 porque ela vem Inbound). -4. Seu painel web tem 2-Factor com Email. -5. Sua API está ratelimitada na borda pela Cloudflare e só trafega Bearer Tokens. +1. Sanal makinenizde güvenlik duvarında (`/etc/ufw`) **hiçbir açık gelen port bulunmaz**. +2. OmniRoute yalnızca giden HTTPS (`cloudflared`) trafiğiyle haberleşir ve dünyadan doğrudan TCP bağlantısı almaz. +3. Yönetim web panonuz e-posta tabanlı İki Faktörlü Doğrulama (2FA) ile korunur. +4. API'niz Cloudflare tarafından sınırlandırılmıştır ve yalnızca Bearer Token'lar kabul edilir. diff --git a/docs/i18n/tr/docs/features/context-relay.md b/docs/i18n/tr/docs/features/context-relay.md index c62c6377a7..9237c05133 100644 --- a/docs/i18n/tr/docs/features/context-relay.md +++ b/docs/i18n/tr/docs/features/context-relay.md @@ -4,62 +4,54 @@ --- -`context-relay` is a combo strategy that keeps session continuity when the active account -rotates before the conversation is finished. +`context-relay`, konuşma tamamlanmadan önce aktif hesap değiştiğinde (rotasyon) oturum sürekliliğini koruyan bir kombo stratejisidir. -The current runtime behaves like priority routing for model selection, then adds a -handoff layer on top: +Mevcut çalışma zamanı model seçimi için öncelikli (priority) yönlendirme gibi davranır, ardından üzerine bir devir (handoff) katmanı ekler: -- before the active account is exhausted, OmniRoute generates a compact structured summary -- after authentication selects a different account for the same session, OmniRoute injects - that summary as a system message into the next request -- once the handoff is consumed successfully, it is removed from storage +- Aktif hesap tükenmeden önce OmniRoute kompakt ve yapılandırılmış bir özet üretir +- Kimlik doğrulama aynı oturum için farklı bir hesap seçtikten sonra, OmniRoute bu özeti sonraki isteğe bir sistem mesajı olarak enjekte eder +- Devir başarıyla tüketildiğinde depodan silinir -## When To Use It +## Ne Zaman Kullanılmalı -Use `context-relay` when all of the following are true: +Aşağıdakilerin tümü doğru olduğunda `context-relay` kullanın: -- the combo is expected to rotate between multiple accounts of the same provider -- losing short-term conversational continuity would hurt task quality -- the provider exposes enough quota information to predict an approaching account limit +- Kombonun aynı sağlayıcının birden çok hesabı arasında geçiş yapması bekleniyorsa +- Kısa vadeli konuşma sürekliliğini kaybetmek görev kalitesine zarar verecekse +- Sağlayıcı yaklaşan bir hesap sınırını tahmin etmek için yeterli kota bilgisi sunuyorsa -This is most useful for long-running coding or research sessions that may outlive a single -account window. +Bu özellik, tek bir hesap penceresinden daha uzun sürebilecek uzun kodlama veya araştırma oturumları için son derece kullanışlıdır. -## Runtime Flow +## Çalışma Zamanı Akışı -The current behavior is intentionally split across two runtime layers. +Mevcut davranış kasıtlı olarak iki çalışma zamanı katmanına ayrılmıştır. -### 0% to 84% quota used +### %0 ila %84 Kota Kullanımı -No handoff is generated. Requests behave like normal priority routing. +Hiçbir devir özeti üretilmez. İstekler normal öncelik yönlendirmesi gibi davranır. -### 85% to 94% quota used +### %85 ila %94 Kota Kullanımı -If the active provider is enabled in `handoffProviders`, OmniRoute generates a structured -handoff summary in the background before the account is fully exhausted. +Aktif sağlayıcı `handoffProviders` içinde etkinleştirilmişse, OmniRoute hesap tamamen tükenmeden önce arka planda yapılandırılmış bir devir özeti üretir. -Important details: +Önemli detaylar: -- the default warning threshold is `0.85` -- the hard stop for generation is `0.95` -- only one in-flight handoff generation is allowed per `sessionId + comboName` -- if an active handoff already exists for that session/combo, no duplicate summary is generated +- Varsayılan uyarı eşiği `0.85`'tir +- Üretim için kesin durma noktası `0.95`'tir +- `sessionId + comboName` başına yalnızca bir devam eden devir üretimine izin verilir +- Bu oturum/kombo için zaten etkin bir devir varsa, mükerrer özet üretilmez -### 95% or more quota used +### %95 veya Daha Fazla Kota Kullanımı -No new handoff is generated. At this point the system is already in or near exhaustion and -the runtime avoids scheduling another summary request. +Yeni bir devir üretilmez. Bu noktada sistem zaten tükenme sınırındadır veya tükenmiştir; çalışma zamanı başka bir özet isteği zamanlamaktan kaçınır. -### After account rotation +### Hesap Rotasyonundan Sonra -When the next request for the same session resolves to a different authenticated account, -OmniRoute prepends the stored handoff as a system message. Injection happens only after the -real account switch is known. +Aynı oturum için bir sonraki istek farklı bir kimliği doğrulanmış hesaba çözümlendiğinde, OmniRoute saklanan devir özetini bir sistem mesajı olarak başa ekler. Enjeksiyon yalnızca gerçek hesap değişikliği bilindikten sonra gerçekleşir. -## Handoff Payload +## Devir Yükü (Handoff Payload) -The persisted handoff payload is stored in `context_handoffs` and includes: +Kalıcı devir yükü `context_handoffs` tablosunda saklanır ve şunları içerir: - `sessionId` - `comboName` @@ -74,57 +66,49 @@ The persisted handoff payload is stored in `context_handoffs` and includes: - `generatedAt` - `expiresAt` -The summary model is instructed to return a JSON object with this structure: +Özet modeline şu yapıda bir JSON nesnesi döndürmesi talimatı verilir: ```json { - "summary": "Dense summary of what matters for continuity", - "keyDecisions": ["Decision 1", "Decision 2"], - "taskProgress": "What is done, what is pending, and the next step", - "activeEntities": ["fileA.ts", "feature X", "provider Y"] + "summary": "Süreklilik için önemli olan konuların yoğun özeti", + "keyDecisions": ["Karar 1", "Karar 2"], + "taskProgress": "Ne yapıldı, ne bekliyor ve bir sonraki adım", + "activeEntities": ["dosyaA.ts", "özellik X", "sağlayıcı Y"] } ``` -At injection time, OmniRoute converts that payload into a `` system -message so the next account can continue with the correct local context. +Enjeksiyon anında OmniRoute bu yükü bir `` sistem mesajına dönüştürür; böylece sonraki hesap doğru yerel bağlamla devam edebilir. ## Yapılandırma -`context-relay` supports these config fields: +`context-relay` şu yapılandırma alanlarını destekler: -- `handoffThreshold`: warning threshold for summary generation, default `0.85` -- `handoffModel`: optional model override used only for summary generation -- `handoffProviders`: allowlist of providers allowed to trigger handoff generation +- `handoffThreshold`: Özet üretimi için uyarı eşiği, varsayılan `0.85` +- `handoffModel`: Yalnızca özet üretimi için kullanılan isteğe bağlı model geçersiz kılma +- `handoffProviders`: Devir üretimini tetiklemesine izin verilen sağlayıcıların izin listesi -Global defaults can be configured in Settings, and combo-specific values can override them -in the Combos page. +Genel varsayılanlar Ayarlar sayfasında yapılandırılabilir ve kombo bazlı değerler bunları Kombolar sayfasında geçersiz kılabilir. -## Architectural Note +## Mimari Not -The current implementation does not use a standalone `handleContextRelayCombo` handler. +Mevcut uygulama bağımsız bir `handleContextRelayCombo` işleyicisi kullanmaz. -Instead: +Bunun yerine: -- `open-sse/services/combo.ts` decides whether a successful turn should generate a handoff -- `src/sse/handlers/chat.ts` injects the handoff only after authentication resolves the - actual account used for the request +- `open-sse/services/combo.ts` başarılı bir turun devir üretip üretmeyeceğine karar verir +- `src/sse/handlers/chat.ts` devir özetini yalnızca kimlik doğrulama istek için kullanılan gerçek hesabı belirledikten sonra enjekte eder -This split is intentional in the current codebase because the combo loop alone does not know -whether the request stayed on the same account or actually switched accounts. +## Sınırlamalar -## Limitations +- Etkili çalışma zamanı desteği şu anda `codex` kota rotasyonu üzerinde yoğunlaşmıştır. +- `handoffProviders` bir yapılandırma yüzeyi olarak modellenmiştir ancak gerçek devir üretimi hala sağlayıcıya özel kota altyapısına bağlıdır. +- Özet kasıtlı olarak kompakt ve yakın geçmişe dayalıdır; tam bir konuşma geçmişi tekrar oynatma mekanizması değildir. +- Devirler `sessionId + comboName` ile kapsama alınır ve otomatik olarak sona erer. +- Oturum hesap değiştirmezse, saklanan devir enjekte edilmez. -- Effective runtime support is currently centered on `codex` quota rotation. -- `handoffProviders` is already modeled as a config surface, but real handoff generation - still depends on provider-specific quota plumbing. -- The summary is intentionally compact and recent-history based; it is not a full transcript - replay mechanism. -- Handoffs are scoped by `sessionId + comboName` and expire automatically. -- If the session does not switch accounts, the stored handoff is not injected. +## Önerilen Kullanım Modeli -## Recommended Usage Pattern - -- use multiple accounts from the same provider -- keep stable `sessionId` values across the session -- set `handoffThreshold` early enough to leave room for the background summary request -- treat the feature as continuity assistance, not as a replacement for persistent memory +- Aynı sağlayıcıdan birden fazla hesap kullanın +- Oturum boyunca kararlı `sessionId` değerleri koruyun +- Arka plan özet isteğine yer bırakmak için `handoffThreshold` değerini yeterince erken bir seviyeye ayarlayın +- Bu özelliği kalıcı belleğin yerine geçen bir mekanizma olarak değil, bir süreklilik desteği olarak değerlendirin diff --git a/docs/i18n/tr/docs/frameworks/A2A-SERVER.md b/docs/i18n/tr/docs/frameworks/A2A-SERVER.md index 721396fe1a..69c038ebf5 100644 --- a/docs/i18n/tr/docs/frameworks/A2A-SERVER.md +++ b/docs/i18n/tr/docs/frameworks/A2A-SERVER.md @@ -1,38 +1,55 @@ -# OmniRoute A2A Server Documentation (Türkçe) +--- +title: "OmniRoute A2A Sunucu Dokümantasyonu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/A2A-SERVER.md) · 🇸🇦 [ar](../../ar/docs/A2A-SERVER.md) · 🇧🇬 [bg](../../bg/docs/A2A-SERVER.md) · 🇧🇩 [bn](../../bn/docs/A2A-SERVER.md) · 🇨🇿 [cs](../../cs/docs/A2A-SERVER.md) · 🇩🇰 [da](../../da/docs/A2A-SERVER.md) · 🇩🇪 [de](../../de/docs/A2A-SERVER.md) · 🇪🇸 [es](../../es/docs/A2A-SERVER.md) · 🇮🇷 [fa](../../fa/docs/A2A-SERVER.md) · 🇫🇮 [fi](../../fi/docs/A2A-SERVER.md) · 🇫🇷 [fr](../../fr/docs/A2A-SERVER.md) · 🇮🇳 [gu](../../gu/docs/A2A-SERVER.md) · 🇮🇱 [he](../../he/docs/A2A-SERVER.md) · 🇮🇳 [hi](../../hi/docs/A2A-SERVER.md) · 🇭🇺 [hu](../../hu/docs/A2A-SERVER.md) · 🇮🇩 [id](../../id/docs/A2A-SERVER.md) · 🇮🇹 [it](../../it/docs/A2A-SERVER.md) · 🇯🇵 [ja](../../ja/docs/A2A-SERVER.md) · 🇰🇷 [ko](../../ko/docs/A2A-SERVER.md) · 🇮🇳 [mr](../../mr/docs/A2A-SERVER.md) · 🇲🇾 [ms](../../ms/docs/A2A-SERVER.md) · 🇳🇱 [nl](../../nl/docs/A2A-SERVER.md) · 🇳🇴 [no](../../no/docs/A2A-SERVER.md) · 🇵🇭 [phi](../../phi/docs/A2A-SERVER.md) · 🇵🇱 [pl](../../pl/docs/A2A-SERVER.md) · 🇵🇹 [pt](../../pt/docs/A2A-SERVER.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/A2A-SERVER.md) · 🇷🇴 [ro](../../ro/docs/A2A-SERVER.md) · 🇷🇺 [ru](../../ru/docs/A2A-SERVER.md) · 🇸🇰 [sk](../../sk/docs/A2A-SERVER.md) · 🇸🇪 [sv](../../sv/docs/A2A-SERVER.md) · 🇰🇪 [sw](../../sw/docs/A2A-SERVER.md) · 🇮🇳 [ta](../../ta/docs/A2A-SERVER.md) · 🇮🇳 [te](../../te/docs/A2A-SERVER.md) · 🇹🇭 [th](../../th/docs/A2A-SERVER.md) · 🇹🇷 [tr](../../tr/docs/A2A-SERVER.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/A2A-SERVER.md) · 🇵🇰 [ur](../../ur/docs/A2A-SERVER.md) · 🇻🇳 [vi](../../vi/docs/A2A-SERVER.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/A2A-SERVER.md) +# OmniRoute A2A Sunucu Dokümantasyonu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/frameworks/A2A-SERVER.md) · 🇸🇦 [ar](../../ar/docs/frameworks/A2A-SERVER.md) · 🇧🇬 [bg](../../bg/docs/frameworks/A2A-SERVER.md) · 🇧🇩 [bn](../../bn/docs/frameworks/A2A-SERVER.md) · 🇨🇿 [cs](../../cs/docs/frameworks/A2A-SERVER.md) · 🇩🇰 [da](../../da/docs/frameworks/A2A-SERVER.md) · 🇩🇪 [de](../../de/docs/frameworks/A2A-SERVER.md) · 🇪🇸 [es](../../es/docs/frameworks/A2A-SERVER.md) · 🇮🇷 [fa](../../fa/docs/frameworks/A2A-SERVER.md) · 🇫🇮 [fi](../../fi/docs/frameworks/A2A-SERVER.md) · 🇫🇷 [fr](../../fr/docs/frameworks/A2A-SERVER.md) · 🇮🇳 [gu](../../gu/docs/frameworks/A2A-SERVER.md) · 🇮🇱 [he](../../he/docs/frameworks/A2A-SERVER.md) · 🇮🇳 [hi](../../hi/docs/frameworks/A2A-SERVER.md) · 🇭🇺 [hu](../../hu/docs/frameworks/A2A-SERVER.md) · 🇮🇩 [id](../../id/docs/frameworks/A2A-SERVER.md) · 🇮🇹 [it](../../it/docs/frameworks/A2A-SERVER.md) · 🇯🇵 [ja](../../ja/docs/frameworks/A2A-SERVER.md) · 🇰🇷 [ko](../../ko/docs/frameworks/A2A-SERVER.md) · 🇮🇳 [mr](../../mr/docs/frameworks/A2A-SERVER.md) · 🇲🇾 [ms](../../ms/docs/frameworks/A2A-SERVER.md) · 🇳🇱 [nl](../../nl/docs/frameworks/A2A-SERVER.md) · 🇳🇴 [no](../../no/docs/frameworks/A2A-SERVER.md) · 🇵🇭 [phi](../../phi/docs/frameworks/A2A-SERVER.md) · 🇵🇱 [pl](../../pl/docs/frameworks/A2A-SERVER.md) · 🇵🇹 [pt](../../pt/docs/frameworks/A2A-SERVER.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/frameworks/A2A-SERVER.md) · 🇷🇴 [ro](../../ro/docs/frameworks/A2A-SERVER.md) · 🇷🇺 [ru](../../ru/docs/frameworks/A2A-SERVER.md) · 🇸🇰 [sk](../../sk/docs/frameworks/A2A-SERVER.md) · 🇸🇪 [sv](../../sv/docs/frameworks/A2A-SERVER.md) · 🇰🇪 [sw](../../sw/docs/frameworks/A2A-SERVER.md) · 🇮🇳 [ta](../../ta/docs/frameworks/A2A-SERVER.md) · 🇮🇳 [te](../../te/docs/frameworks/A2A-SERVER.md) · 🇹🇭 [th](../../th/docs/frameworks/A2A-SERVER.md) · 🇹🇷 [tr](../../tr/docs/frameworks/A2A-SERVER.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/frameworks/A2A-SERVER.md) · 🇵🇰 [ur](../../ur/docs/frameworks/A2A-SERVER.md) · 🇻🇳 [vi](../../vi/docs/frameworks/A2A-SERVER.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/frameworks/A2A-SERVER.md) --- -> Agent-to-Agent Protocol v0.3 — OmniRoute as an intelligent routing agent +> Agent-to-Agent Protokolü v0.3 — Akıllı bir yönlendirme ajanı olarak OmniRoute -## Agent Discovery +A2A yüzeyinin iki arayüzü vardır: + +- `POST /a2a` adresinde **JSON-RPC 2.0** (kurallı giriş noktası, `src/app/a2a/route.ts` içinde tanımlı). +- Panolar ve araçlar için `/api/a2a/*` altında **REST** (durum, görev listesi, iptal). + +Görevler `A2ATaskManager` (`src/lib/a2a/taskManager.ts`, varsayılan 5 dakikalık TTL) tarafından izlenir. Yetenekler `src/lib/a2a/taskExecution.ts` içindeki `A2A_SKILL_HANDLERS` aracılığıyla dağıtılır. + +## Ajan Keşfi (Agent Discovery) ```bash curl http://localhost:20128/.well-known/agent.json ``` -Returns the Agent Card describing OmniRoute's capabilities, skills, and authentication requirements. +OmniRoute'un yeteneklerini, becerilerini ve kimlik doğrulama gereksinimlerini açıklayan Ajan Kartını (Agent Card) döndürür. --- -## Authentication +## Kimlik Doğrulama -All `/a2a` requests require an API key via the `Authorization` header: +Tüm `/a2a` istekleri `Authorization` başlığı aracılığıyla bir API anahtarı gerektirir: ``` -Authorization: Bearer YOUR_OMNIROUTE_API_KEY +Authorization: Bearer SIZIN_OMNIROUTE_API_ANAHTARINIZ ``` -If no API key is configured on the server, authentication is bypassed. +Sunucuda hiçbir API anahtarı yapılandırılmamışsa, kimlik doğrulama atlanır. + +## Etkinleştirme + +A2A, **Uç Noktalar → A2A** anahtarıyla kontrol edilir ve varsayılan olarak devre dışıdır. Devre dışıyken, `GET /api/a2a/status` `status: "disabled"` ve `online: false` bildirir; `POST /a2a` çağrıları `-32000` JSON-RPC hata koduyla HTTP 503 döndürür. --- -## JSON-RPC 2.0 Methods +## JSON-RPC 2.0 Metotları -### `message/send` — Synchronous Execution +### `message/send` — Eşzamanlı Yürütme -Sends a message to a skill and waits for the complete response. +Bir yeteneğe mesaj gönderir ve tam yanıtı bekler. ```bash curl -X POST http://localhost:20128/a2a \ @@ -50,151 +67,25 @@ curl -X POST http://localhost:20128/a2a \ }' ``` -**Response:** +### `message/stream` — SSE Akışı -```json -{ - "jsonrpc": "2.0", - "id": "1", - "result": { - "task": { "id": "uuid", "state": "completed" }, - "artifacts": [{ "type": "text", "content": "..." }], - "metadata": { - "routing_explanation": "Selected claude-sonnet via provider \"anthropic\" (latency: 1200ms, cost: $0.003)", - "cost_envelope": { "estimated": 0.005, "actual": 0.003, "currency": "USD" }, - "resilience_trace": [ - { "event": "primary_selected", "provider": "anthropic", "timestamp": "..." } - ], - "policy_verdict": { "allowed": true, "reason": "within budget and quota limits" } - } - } -} -``` +`message/send` ile aynıdır ancak gerçek zamanlı akış için Server-Sent Events döndürür. -### `message/stream` — SSE Streaming +### `tasks/get` — Görev Durumu Alma -Same as `message/send` but returns Server-Sent Events for real-time streaming. +`params.id` ile bir görevin durumunu, yapıtlarını ve yürütme meta verilerini sorgular. -```bash -curl -N -X POST http://localhost:20128/a2a \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ - -d '{ - "jsonrpc": "2.0", - "id": "1", - "method": "message/stream", - "params": { - "skill": "smart-routing", - "messages": [{"role": "user", "content": "Explain quantum computing"}] - } - }' -``` +### `tasks/cancel` — Görevi İptal Etme -**SSE Events:** - -``` -data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"working"},"chunk":{"type":"text","content":"..."}}} - -: heartbeat 2026-03-03T17:00:00Z - -data: {"jsonrpc":"2.0","method":"message/stream","params":{"task":{"id":"...","state":"completed"},"metadata":{...}}} -``` - -### `tasks/get` — Query Task Status - -```bash -curl -X POST http://localhost:20128/a2a \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ - -d '{"jsonrpc":"2.0","id":"2","method":"tasks/get","params":{"taskId":"TASK_UUID"}}' -``` - -### `tasks/cancel` — Cancel a Task - -```bash -curl -X POST http://localhost:20128/a2a \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer YOUR_KEY" \ - -d '{"jsonrpc":"2.0","id":"3","method":"tasks/cancel","params":{"taskId":"TASK_UUID"}}' -``` +Çalışan bir görevi iptal eder. --- -## Available Skills +## Desteklenen A2A Yetenekleri (Skills) -| Skill | Description | -| :----------------- | :------------------------------------------------------------------------------------------------------------------------------ | -| `smart-routing` | Routes prompts through OmniRoute's intelligent pipeline. Returns response with routing explanation, cost, and resilience trace. | -| `quota-management` | Answers natural-language queries about provider quotas, suggests free combos, and provides quota rankings. | - ---- - -## Task Lifecycle - -``` -submitted → working → completed - → failed - → cancelled -``` - -- Tasks expire after 5 minutes (configurable) -- Terminal states: `completed`, `failed`, `cancelled` -- Event log tracks every state transition - ---- - -## Error Codes - -| Code | Meaning | -| :----- | :----------------------------- | -| -32700 | Parse error (invalid JSON) | -| -32600 | Invalid request / Unauthorized | -| -32601 | Method or skill not found | -| -32602 | Invalid params | -| -32603 | Internal error | - ---- - -## Integration Examples - -### Python (requests) - -```python -import requests - -resp = requests.post("http://localhost:20128/a2a", json={ - "jsonrpc": "2.0", "id": "1", - "method": "message/send", - "params": { - "skill": "smart-routing", - "messages": [{"role": "user", "content": "Hello"}] - } -}, headers={"Authorization": "Bearer YOUR_KEY"}) - -result = resp.json()["result"] -print(result["artifacts"][0]["content"]) -print(result["metadata"]["routing_explanation"]) -``` - -### TypeScript (fetch) - -```typescript -const resp = await fetch("http://localhost:20128/a2a", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer YOUR_KEY", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: "1", - method: "message/send", - params: { - skill: "smart-routing", - messages: [{ role: "user", content: "Hello" }], - }, - }), -}); -const { result } = await resp.json(); -console.log(result.metadata.routing_explanation); -``` +1. **`smart-routing`** — Akıllı yönlendirme ve çok sağlayıcılı geri dönüş ile mesaj gönderme. +2. **`quota-management`** — Tüm bağlı sağlayıcılardaki kota durumunu ve sıfırlanma sürelerini kontrol etme. +3. **`provider-discovery`** — Uygun sağlayıcıları ve modelleri yeteneklere göre listeleme. +4. **`cost-analysis`** — Oturum veya zaman dilimi bazında maliyet analiz raporu alma. +5. **`health-report`** — Sistem çalışma süresi, devre kesiciler ve sağlayıcı sağlık durumu. +6. **`list-capabilities`** — Desteklenen tüm modelleri, komboları ve stratejileri listeleme. diff --git a/docs/i18n/tr/docs/frameworks/MCP-SERVER.md b/docs/i18n/tr/docs/frameworks/MCP-SERVER.md index c8fab1b16a..4ce420445d 100644 --- a/docs/i18n/tr/docs/frameworks/MCP-SERVER.md +++ b/docs/i18n/tr/docs/frameworks/MCP-SERVER.md @@ -1,87 +1,102 @@ -# OmniRoute MCP Server Documentation (Türkçe) +--- +title: "OmniRoute MCP Sunucu Dokümantasyonu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/MCP-SERVER.md) · 🇸🇦 [ar](../../ar/docs/MCP-SERVER.md) · 🇧🇬 [bg](../../bg/docs/MCP-SERVER.md) · 🇧🇩 [bn](../../bn/docs/MCP-SERVER.md) · 🇨🇿 [cs](../../cs/docs/MCP-SERVER.md) · 🇩🇰 [da](../../da/docs/MCP-SERVER.md) · 🇩🇪 [de](../../de/docs/MCP-SERVER.md) · 🇪🇸 [es](../../es/docs/MCP-SERVER.md) · 🇮🇷 [fa](../../fa/docs/MCP-SERVER.md) · 🇫🇮 [fi](../../fi/docs/MCP-SERVER.md) · 🇫🇷 [fr](../../fr/docs/MCP-SERVER.md) · 🇮🇳 [gu](../../gu/docs/MCP-SERVER.md) · 🇮🇱 [he](../../he/docs/MCP-SERVER.md) · 🇮🇳 [hi](../../hi/docs/MCP-SERVER.md) · 🇭🇺 [hu](../../hu/docs/MCP-SERVER.md) · 🇮🇩 [id](../../id/docs/MCP-SERVER.md) · 🇮🇹 [it](../../it/docs/MCP-SERVER.md) · 🇯🇵 [ja](../../ja/docs/MCP-SERVER.md) · 🇰🇷 [ko](../../ko/docs/MCP-SERVER.md) · 🇮🇳 [mr](../../mr/docs/MCP-SERVER.md) · 🇲🇾 [ms](../../ms/docs/MCP-SERVER.md) · 🇳🇱 [nl](../../nl/docs/MCP-SERVER.md) · 🇳🇴 [no](../../no/docs/MCP-SERVER.md) · 🇵🇭 [phi](../../phi/docs/MCP-SERVER.md) · 🇵🇱 [pl](../../pl/docs/MCP-SERVER.md) · 🇵🇹 [pt](../../pt/docs/MCP-SERVER.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/MCP-SERVER.md) · 🇷🇴 [ro](../../ro/docs/MCP-SERVER.md) · 🇷🇺 [ru](../../ru/docs/MCP-SERVER.md) · 🇸🇰 [sk](../../sk/docs/MCP-SERVER.md) · 🇸🇪 [sv](../../sv/docs/MCP-SERVER.md) · 🇰🇪 [sw](../../sw/docs/MCP-SERVER.md) · 🇮🇳 [ta](../../ta/docs/MCP-SERVER.md) · 🇮🇳 [te](../../te/docs/MCP-SERVER.md) · 🇹🇭 [th](../../th/docs/MCP-SERVER.md) · 🇹🇷 [tr](../../tr/docs/MCP-SERVER.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/MCP-SERVER.md) · 🇵🇰 [ur](../../ur/docs/MCP-SERVER.md) · 🇻🇳 [vi](../../vi/docs/MCP-SERVER.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/MCP-SERVER.md) +# OmniRoute MCP Sunucu Dokümantasyonu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/frameworks/MCP-SERVER.md) · 🇸🇦 [ar](../../ar/docs/frameworks/MCP-SERVER.md) · 🇧🇬 [bg](../../bg/docs/frameworks/MCP-SERVER.md) · 🇧🇩 [bn](../../bn/docs/frameworks/MCP-SERVER.md) · 🇨🇿 [cs](../../cs/docs/frameworks/MCP-SERVER.md) · 🇩🇰 [da](../../da/docs/frameworks/MCP-SERVER.md) · 🇩🇪 [de](../../de/docs/frameworks/MCP-SERVER.md) · 🇪🇸 [es](../../es/docs/frameworks/MCP-SERVER.md) · 🇮🇷 [fa](../../fa/docs/frameworks/MCP-SERVER.md) · 🇫🇮 [fi](../../fi/docs/frameworks/MCP-SERVER.md) · 🇫🇷 [fr](../../fr/docs/frameworks/MCP-SERVER.md) · 🇮🇳 [gu](../../gu/docs/frameworks/MCP-SERVER.md) · 🇮🇱 [he](../../he/docs/frameworks/MCP-SERVER.md) · 🇮🇳 [hi](../../hi/docs/frameworks/MCP-SERVER.md) · 🇭🇺 [hu](../../hu/docs/frameworks/MCP-SERVER.md) · 🇮🇩 [id](../../id/docs/frameworks/MCP-SERVER.md) · 🇮🇹 [it](../../it/docs/frameworks/MCP-SERVER.md) · 🇯🇵 [ja](../../ja/docs/frameworks/MCP-SERVER.md) · 🇰🇷 [ko](../../ko/docs/frameworks/MCP-SERVER.md) · 🇮🇳 [mr](../../mr/docs/frameworks/MCP-SERVER.md) · 🇲🇾 [ms](../../ms/docs/frameworks/MCP-SERVER.md) · 🇳🇱 [nl](../../nl/docs/frameworks/MCP-SERVER.md) · 🇳🇴 [no](../../no/docs/frameworks/MCP-SERVER.md) · 🇵🇭 [phi](../../phi/docs/frameworks/MCP-SERVER.md) · 🇵🇱 [pl](../../pl/docs/frameworks/MCP-SERVER.md) · 🇵🇹 [pt](../../pt/docs/frameworks/MCP-SERVER.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/frameworks/MCP-SERVER.md) · 🇷🇴 [ro](../../ro/docs/frameworks/MCP-SERVER.md) · 🇷🇺 [ru](../../ru/docs/frameworks/MCP-SERVER.md) · 🇸🇰 [sk](../../sk/docs/frameworks/MCP-SERVER.md) · 🇸🇪 [sv](../../sv/docs/frameworks/MCP-SERVER.md) · 🇰🇪 [sw](../../sw/docs/frameworks/MCP-SERVER.md) · 🇮🇳 [ta](../../ta/docs/frameworks/MCP-SERVER.md) · 🇮🇳 [te](../../te/docs/frameworks/MCP-SERVER.md) · 🇹🇭 [th](../../th/docs/frameworks/MCP-SERVER.md) · 🇹🇷 [tr](../../tr/docs/frameworks/MCP-SERVER.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/frameworks/MCP-SERVER.md) · 🇵🇰 [ur](../../ur/docs/frameworks/MCP-SERVER.md) · 🇻🇳 [vi](../../vi/docs/frameworks/MCP-SERVER.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/frameworks/MCP-SERVER.md) --- -> Model Context Protocol server with 16 intelligent tools +> Yönlendirme, önbellek, sıkıştırma, bellek, yetenekler, proxy, havuz, Radar ve bağlam kaynak işlemleri genelinde 110 araç içeren Model Context Protocol (MCP) sunucusu. +> +> Doğruluk kaynağı: `open-sse/mcp-server/server.ts` dosyası `countUniqueMcpTools()` ile **110 benzersiz araç** hesaplar: 45 kurallı tanım (altı CCR yaşam döngüsü aracı, ajan yetenekleri üçlüsü, `omniroute_radar_catalog` ve `omniroute_x_search` dahil), artı bellek (3), yetenekler (4), GitHub yetenekleri (3), havuz (6), oyunlaştırma (8), eklentiler (8), Notion (6), Obsidian (22), yerel külliyat (3) ve iki RTK sıkıştırma aracı. ## Kurulum -OmniRoute MCP is built-in. Start it with: +OmniRoute MCP yerleşik olarak gelir. Şununla başlatın: ```bash omniroute --mcp ``` -Or via the open-sse transport: +Veya open-sse taşıması aracılığıyla: ```bash -# HTTP streamable transport (port 20130) -omniroute --dev # MCP auto-starts on /mcp endpoint +# HTTP akış taşıması (port 20130) +omniroute --dev # MCP /mcp uç noktasında otomatik başlar ``` -## IDE Configuration +## Taşıma Modları (Transports) -See [IDE Configs](integrations/ide-configs.md) for Antigravity, Cursor, Copilot, and Claude Desktop setup. +MCP sunucusu, tümü aynı `createMcpServer()` fabrikası tarafından desteklenen üç taşıma protokolü sunar: + +| Taşıma | Konum | Ne zaman kullanılır | +| :---------------- | :------------------------------------------ | :--------------------------------------------------- | +| `stdio` | `open-sse/mcp-server/server.ts` | IDE entegrasyonları (Claude Desktop, Cursor vb.) | +| `sse` | `httpTransport` ile `POST/GET /api/mcp/sse` | Olay akışına ihtiyaç duyan tarayıcı/ajan istemcileri | +| `streamable-http` | `POST/GET/DELETE /api/mcp/stream` | Çoklu oturumlu HTTP istemcileri (`mcp-session-id`) | + +Etkin HTTP taşıması (`sse` veya `streamable-http`) `mcpTransport` ayarıyla seçilir. Taşıma modunu değiştirmek diğer taşımadaki mevcut oturumları kapatır. + +### Uzaktan Erişim (`manage` Kapsamı) + +`/api/mcp/*` LOCAL_ONLY katmanındadır (`src/server/authz/routeGuard.ts`) — varsayılan olarak yalnızca yerel döngü ana bilgisayarları (`localhost`, `127.0.0.1`, `::1`) erişebilir. v3.8.2'den bu yana, yerel olmayan istemciler `manage` kapsamına sahip bir `Authorization: Bearer ` anahtarı sunduklarında bağlanabilirler. Bu, tünel, ters proxy veya genel ana bilgisayar adı üzerinden uzak MCP sunucusuna erişmenin tek yoludur. + +```bash +# Uzak bir MCP istemcisinden bağlanın: +curl -i \ + -H "Host: your-public-host.example" \ + -H "Authorization: Bearer sk-…" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"my-client","version":"0"}}}' \ + https://your-public-host.example/api/mcp/stream +``` --- -## Essential Tools (8) +## Temel Araçlar (13) — Aşama 1 -| Tool | Description | -| :------------------------------ | :--------------------------------------- | -| `omniroute_get_health` | Gateway health, circuit breakers, uptime | -| `omniroute_list_combos` | All configured combos with models | -| `omniroute_get_combo_metrics` | Performance metrics for a specific combo | -| `omniroute_switch_combo` | Switch active combo by ID/name | -| `omniroute_check_quota` | Quota status per provider or all | -| `omniroute_route_request` | Send a chat completion through OmniRoute | -| `omniroute_cost_report` | Cost analytics for a time period | -| `omniroute_list_models_catalog` | Full model catalog with capabilities | +| Araç | Kapsamlar | Açıklama | +| :------------------------------ | :-------------------- | :------------------------------------------------------------ | +| `omniroute_get_health` | `read:health` | Çalışma süresi, bellek, devre kesiciler, hız sınırları, önbellek | +| `omniroute_list_combos` | `read:combos` | Stratejileriyle birlikte yapılandırılmış tüm kombolar | +| `omniroute_get_combo_metrics` | `read:combos` | Belirli bir kombo için performans metrikleri | +| `omniroute_switch_combo` | `write:combos` | Bir komboyu etkinleştirme veya devre dışı bırakma | +| `omniroute_create_combo` | `write:combos` | Doğrulanmış bir kombo oluşturma | +| `omniroute_check_quota` | `read:quota` | Kullanılan/toplam kota, kalan yüzde, sıfırlanma süresi | +| `omniroute_route_request` | `execute:completions` | OmniRoute yönlendirmesi üzerinden sohbet tamamlama gönderme | +| `omniroute_cost_report` | `read:usage` | Döneme göre maliyet raporu (oturum/gün/hafta/ay) | +| `omniroute_list_models_catalog` | `read:models` | Yetenekler, durum ve fiyatlandırma ile tam model kataloğu | +| `omniroute_radar_catalog` | `read:radar` | Yerel imzalı Radar kataloğu; isteğe bağlı filtreler | +| `omniroute_tool_search` | `read:tools` | Kayıtlı MCP kataloğundan araçları keşfetme | +| `omniroute_web_search` | `execute:search` | Yapılandırılmış sağlayıcılar üzerinden web araması | +| `omniroute_x_search` | `execute:search` | SuperGrok / xAI üzerinden X (Twitter) araması | +| `omniroute_web_fetch` | `execute:search` | Yapılandırılmış getirme sağlayıcıları üzerinden web içeriği alma | -## Advanced Tools (8) +## Gelişmiş Araçlar (11) — Aşama 2 -| Tool | Description | -| :--------------------------------- | :---------------------------------------------------------- | -| `omniroute_simulate_route` | Dry-run routing simulation with fallback tree | -| `omniroute_set_budget_guard` | Session budget with degrade/block/alert actions | -| `omniroute_set_resilience_profile` | Apply conservative/balanced/aggressive preset | -| `omniroute_test_combo` | Live-test all models in a combo via a real upstream request | -| `omniroute_get_provider_metrics` | Detailed metrics for one provider | -| `omniroute_best_combo_for_task` | Task-fitness recommendation with alternatives | -| `omniroute_explain_route` | Explain a past routing decision | -| `omniroute_get_session_snapshot` | Full session state: costs, tokens, errors | +| Araç | Kapsamlar | Açıklama | +| :--------------------------------- | :----------------------------------- | :------------------------------------------------------------------------------------ | +| `omniroute_simulate_route` | `read:health`, `read:combos` | Geri dönüş ağacı ile yönlendirme simülasyonu (kuru çalıştırma) | +| `omniroute_set_budget_guard` | `write:budget` | Düşürme/engelleme/uyarı eylemi ile oturum bütçesi koruması | +| `omniroute_set_routing_strategy` | `write:combos` | Çalışma zamanında kombo stratejisini güncelleme | +| `omniroute_set_resilience_profile` | `write:resilience` | `aggressive` / `balanced` / `conservative` dayanıklılık önayarı uygulama | +| `omniroute_test_combo` | `execute:completions`, `read:combos` | Gerçek bir çağrı kullanarak kombodaki her sağlayıcıyı canlı test etme | +| `omniroute_get_provider_metrics` | `read:health` | p50/p95/p99 gecikme ve devre kesici durumu ile sağlayıcı başına metrikler | +| `omniroute_best_combo_for_task` | `read:combos`, `read:health` | Bütçe/gecikme kısıtlamalarıyla görev türüne göre kombo önerme | +| `omniroute_explain_route` | `read:health`, `read:usage` | Bir isteğin neden belirli bir sağlayıcıya yönlendirildiğini açıklama | +| `omniroute_get_session_snapshot` | `read:usage` | Tam oturum anlık görüntüsü: maliyet, tokenlar, modeller, hatalar | +| `omniroute_db_health_check` | `read:health`, `write:resilience` | Veritabanı sapmalarını tanılama (ve isteğe bağlı otomatik onarma) | +| `omniroute_sync_pricing` | `pricing:write` | Dış kaynaklardan (LiteLLM) fiyatlandırma verilerini senkronize etme | -## Authentication +--- -MCP tools are authenticated via API key scopes. Each tool requires specific scopes: +## Bağlam, Bellek ve Yetenek Araçları -| Scope | Tools | -| :------------- | :----------------------------------------------- | -| `read:health` | get_health, get_provider_metrics | -| `read:combos` | list_combos, get_combo_metrics | -| `write:combos` | switch_combo | -| `read:quota` | check_quota | -| `write:route` | route_request, simulate_route, test_combo | -| `read:usage` | cost_report, get_session_snapshot, explain_route | -| `write:config` | set_budget_guard, set_resilience_profile | -| `read:models` | list_models_catalog, best_combo_for_task | - -## Audit Logging - -Every tool call is logged to `mcp_tool_audit` with: - -- Tool name, arguments, result -- Duration (ms), success/failure -- API key hash, timestamp - -## Files - -| File | Purpose | -| :------------------------------------------- | :------------------------------------------ | -| `open-sse/mcp-server/server.ts` | MCP server creation + 16 tool registrations | -| `open-sse/mcp-server/transport.ts` | Stdio + HTTP transport | -| `open-sse/mcp-server/auth.ts` | API key + scope validation | -| `open-sse/mcp-server/audit.ts` | Tool call audit logging | -| `open-sse/mcp-server/tools/advancedTools.ts` | 8 advanced tool handlers | +- **Bellek Araçları:** `omniroute_memory_search`, `omniroute_memory_store`, `omniroute_memory_delete` +- **Yetenek Araçları:** `omniroute_skill_execute`, `omniroute_skill_list`, `omniroute_skill_register` +- **Bağlam Kaynakları:** Notion (`omniroute_notion_*`), Obsidian (`omniroute_obsidian_*`), Yerel Külliyat (`omniroute_corpus_*`) diff --git a/docs/i18n/tr/docs/guides/CLI-INTEGRATIONS.md b/docs/i18n/tr/docs/guides/CLI-INTEGRATIONS.md index 4b44e6b522..2ea66ee174 100644 --- a/docs/i18n/tr/docs/guides/CLI-INTEGRATIONS.md +++ b/docs/i18n/tr/docs/guides/CLI-INTEGRATIONS.md @@ -1,23 +1,18 @@ -# CLI-INTEGRATIONS (Türkçe) - -🌐 **Languages:** 🇺🇸 [English](../../../../guides/CLI-INTEGRATIONS.md) · 🇸🇦 [ar](../../../ar/docs/guides/CLI-INTEGRATIONS.md) · 🇦🇿 [az](../../../az/docs/guides/CLI-INTEGRATIONS.md) · 🇧🇬 [bg](../../../bg/docs/guides/CLI-INTEGRATIONS.md) · 🇧🇩 [bn](../../../bn/docs/guides/CLI-INTEGRATIONS.md) · 🇨🇿 [cs](../../../cs/docs/guides/CLI-INTEGRATIONS.md) · 🇩🇰 [da](../../../da/docs/guides/CLI-INTEGRATIONS.md) · 🇩🇪 [de](../../../de/docs/guides/CLI-INTEGRATIONS.md) · 🇪🇸 [es](../../../es/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇷 [fa](../../../fa/docs/guides/CLI-INTEGRATIONS.md) · 🇫🇮 [fi](../../../fi/docs/guides/CLI-INTEGRATIONS.md) · 🇫🇷 [fr](../../../fr/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [gu](../../../gu/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇱 [he](../../../he/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [hi](../../../hi/docs/guides/CLI-INTEGRATIONS.md) · 🇭🇺 [hu](../../../hu/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇩 [id](../../../id/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇩 [in](../../../in/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇹 [it](../../../it/docs/guides/CLI-INTEGRATIONS.md) · 🇯🇵 [ja](../../../ja/docs/guides/CLI-INTEGRATIONS.md) · 🇰🇷 [ko](../../../ko/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [mr](../../../mr/docs/guides/CLI-INTEGRATIONS.md) · 🇲🇾 [ms](../../../ms/docs/guides/CLI-INTEGRATIONS.md) · 🇳🇱 [nl](../../../nl/docs/guides/CLI-INTEGRATIONS.md) · 🇳🇴 [no](../../../no/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇭 [phi](../../../phi/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇱 [pl](../../../pl/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇹 [pt](../../../pt/docs/guides/CLI-INTEGRATIONS.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/guides/CLI-INTEGRATIONS.md) · 🇷🇴 [ro](../../../ro/docs/guides/CLI-INTEGRATIONS.md) · 🇷🇺 [ru](../../../ru/docs/guides/CLI-INTEGRATIONS.md) · 🇸🇰 [sk](../../../sk/docs/guides/CLI-INTEGRATIONS.md) · 🇸🇪 [sv](../../../sv/docs/guides/CLI-INTEGRATIONS.md) · 🇰🇪 [sw](../../../sw/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [ta](../../../ta/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [te](../../../te/docs/guides/CLI-INTEGRATIONS.md) · 🇹🇭 [th](../../../th/docs/guides/CLI-INTEGRATIONS.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇰 [ur](../../../ur/docs/guides/CLI-INTEGRATIONS.md) · 🇻🇳 [vi](../../../vi/docs/guides/CLI-INTEGRATIONS.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/guides/CLI-INTEGRATIONS.md) · 🇹🇼 [zh-TW](../../../zh-TW/docs/guides/CLI-INTEGRATIONS.md) - --- - ---- - -title: "CLI Entegrasyonları — herhangi bir kodlama CLI'sını OmniRoute'a yönlendirin" +title: "CLI Entegrasyonları — Herhangi bir kodlama CLI'ını OmniRoute'a Bağlayın" version: 3.8.50 -lastUpdated: 2026-08-18 +lastUpdated: 2026-08-23 --- -# CLI Entegrasyonları +# CLI Entegrasyonları (Türkçe) -OmniRoute, bir kodlama CLI'sını (Codex, Claude Code, OpenCode, Cline, …) OmniRoute'u arka uç olarak kullanacak şekilde yapılandıran bir dizi `setup-*` komutu ile birlikte gelir — böylece araç **bir** uç noktaya bağlanır ve OmniRoute doğru sağlayıcıya otomatik olarak yönlendirir. Her komut, çalışan bir OmniRoute'tan (yerel veya uzaktan) **canlı** model kataloğunu okur ve aracın kendi yapılandırma dosyasını **sizin** makinenizde yazar. API anahtarı, aracın desteklediği her yerde bir ortam değişkeni ile referans alınır. Araç yerel bir ortam dosyasını kalıcı hale getiren komutlar aşağıda belirtilmiştir. +🌐 **Languages:** 🇺🇸 [English](../../../../docs/guides/CLI-INTEGRATIONS.md) · 🇸🇦 [ar](../../ar/docs/guides/CLI-INTEGRATIONS.md) · 🇧🇬 [bg](../../bg/docs/guides/CLI-INTEGRATIONS.md) · 🇧🇩 [bn](../../bn/docs/guides/CLI-INTEGRATIONS.md) · 🇨🇿 [cs](../../cs/docs/guides/CLI-INTEGRATIONS.md) · 🇩🇰 [da](../../da/docs/guides/CLI-INTEGRATIONS.md) · 🇩🇪 [de](../../de/docs/guides/CLI-INTEGRATIONS.md) · 🇪🇸 [es](../../es/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇷 [fa](../../fa/docs/guides/CLI-INTEGRATIONS.md) · 🇫🇮 [fi](../../fi/docs/guides/CLI-INTEGRATIONS.md) · 🇫🇷 [fr](../../fr/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [gu](../../gu/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇱 [he](../../he/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [hi](../../hi/docs/guides/CLI-INTEGRATIONS.md) · 🇭🇺 [hu](../../hu/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇩 [id](../../id/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇹 [it](../../it/docs/guides/CLI-INTEGRATIONS.md) · 🇯🇵 [ja](../../ja/docs/guides/CLI-INTEGRATIONS.md) · 🇰🇷 [ko](../../ko/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [mr](../../mr/docs/guides/CLI-INTEGRATIONS.md) · 🇲🇾 [ms](../../ms/docs/guides/CLI-INTEGRATIONS.md) · 🇳🇱 [nl](../../nl/docs/guides/CLI-INTEGRATIONS.md) · 🇳🇴 [no](../../no/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇭 [phi](../../phi/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇱 [pl](../../pl/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇹 [pt](../../pt/docs/guides/CLI-INTEGRATIONS.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/guides/CLI-INTEGRATIONS.md) · 🇷🇴 [ro](../../ro/docs/guides/CLI-INTEGRATIONS.md) · 🇷🇺 [ru](../../ru/docs/guides/CLI-INTEGRATIONS.md) · 🇸🇰 [sk](../../sk/docs/guides/CLI-INTEGRATIONS.md) · 🇸🇪 [sv](../../sv/docs/guides/CLI-INTEGRATIONS.md) · 🇰🇪 [sw](../../sw/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [ta](../../ta/docs/guides/CLI-INTEGRATIONS.md) · 🇮🇳 [te](../../te/docs/guides/CLI-INTEGRATIONS.md) · 🇹🇭 [th](../../th/docs/guides/CLI-INTEGRATIONS.md) · 🇹🇷 [tr](../../tr/docs/guides/CLI-INTEGRATIONS.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/guides/CLI-INTEGRATIONS.md) · 🇵🇰 [ur](../../ur/docs/guides/CLI-INTEGRATIONS.md) · 🇻🇳 [vi](../../vi/docs/guides/CLI-INTEGRATIONS.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/guides/CLI-INTEGRATIONS.md) -Ayrıca, herhangi bir yapılandırma yazmadan doğru ortamı enjekte eden `omniroute run ` adlı genel bir başlatıcı da vardır; bu, `claude`, `codex`, `aider`, `goose`, `opencode`, `qwen` veya `gemini`'yi başlatır. Hedefler ve takma adları, kanonik manifestodan `bin/cli/cli-manifest.mjs` gelir (`claude-code|cc|anthropic`, `codex-cli|openai-codex|openai`, `goose-cli`, `open-code`, `qwen-code`, `gemini-cli`), ve `omniroute completion` aynı manifestodan türetilmiş hedef kelimeleri sunar. Eski her araç için başlatıcılar — `omniroute launch` (Claude Code) ve `omniroute launch-codex` (Codex) — kullanılabilir durumda kalır. +--- -Sağlayıcı kaydı, aynı yerel/uzaktan bağlamdan mevcuttur. Aşağıdaki API-first komutları, yönetim kimlik doğrulamasını sağlayıcı kimlik bilgilerinden ayrı tutar ve asla yapılandırılmış çıktıda bir kimlik bilgisi yazdırmaz: +OmniRoute, kodlama CLI araçlarını (Codex, Claude Code, OpenCode, Cline vb.) arka uç olarak OmniRoute'u kullanacak şekilde yapılandıran bir dizi `setup-*` komutu sunar — böylece araç **tek bir** uç nokta ile konuşur ve OmniRoute otomatik geri dönüş ile doğru sağlayıcıya yönlendirir. + +Ayrıca hiçbir yapılandırma dosyası yazmadan doğru ortam değişkenleriyle `claude`, `codex`, `aider`, `goose`, `opencode`, `qwen` veya `gemini` başlatan genel bir çalıştırıcı vardır: `omniroute run `. ```bash omniroute providers add glm --credential-env GLM_API_KEY --name work @@ -27,246 +22,23 @@ omniroute providers edit --default-model glm/glm-5.2 omniroute providers remove --yes ``` -Betikler için `--credential-stdin` veya `--credential-env` tercih edilmelidir; `--credential` kontrollü yerel kullanım için saklanmıştır. `providers remove`, etkileşimli olmayan bir terminalde `--yes` gerektirir ve beş komut da aktif bağlamı veya global `--base-url`/`--api-key` seçeneklerini dikkate alır. - -İki en zengin entegrasyonun bir kerelik, el yazısı ile yapılan temel kurulumu için, her araç için derinlemesine incelemelere bakın: - -- [Claude Code yapılandırması](./CLAUDE-CODE-CONFIGURATION.md) -- [Codex CLI yapılandırması](./CODEX-CLI-CONFIGURATION.md) -- [Uzaktan Mod](./REMOTE-MODE.md) — dizüstü bilgisayarınızdan uzaktan bir OmniRoute'u yönetin (VPS / Tailnet) -- [VS Code Copilot Chat](./VSCODE-COPILOT.md) — OmniCopilot uzantısı; ayrıca bu `setup-*` komutlarını editör içinde sizin için çalıştırabilir - --- -## Ana tablo +## Ana Kurulum Tablosu -Her komut, **aktif bağlamı** ( `omniroute connect` ile ayarlanmış, bkz. [Uzaktan Mod](./REMOTE-MODE.md)) veya açık `--remote --api-key ` bayraklarını dikkate alır. Aşağıdaki "Yerel vs uzaktan" ifadesi: bayraksız olarak `http://localhost:20128`'i hedef alır; `--remote` ile (veya aktif bir uzaktan bağlam ile) o sunucudan katalogu alır ve yapılandırmayı yerel olarak yazar. - -| Komut | Araç | Yazdığı şey | Ana bayraklar | Yerel vs uzaktan | -| -------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | -| `omniroute setup-codex` | OpenAI Codex CLI | `~/.codex/.config.toml` — uyumlu metin modeli başına bir profil (`codex --profile `) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--codex-home` | Her ikisi | -| `omniroute setup-claude` | Claude Code | `~/.claude/profiles//settings.json` — eşleşen model başına bir profil (`CLAUDE_CONFIG_DIR`) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--claude-home` | Her ikisi | -| `omniroute setup-opencode` | OpenCode (openai-uyumlu) | `~/.config/opencode/opencode.json` — her katalog modeline sahip `omniroute` sağlayıcısı (`opencode -m omniroute/`) | `--remote` `--api-key` `--only` `--model` `--dry-run` `--port` | Her ikisi | -| `omniroute setup-cline` | Cline | `~/.cline/data/{globalState,secrets}.json` (CLI modu) + VS Code uzantı ayarlarını yazdırır | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--cline-dir` | Her ikisi | -| `omniroute setup-kilo` | Kilo Code | `~/.local/share/kilo/auth.json` (CLI) + mevcutsa `kilocode.*`'u VS Code `settings.json` içine birleştirir | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--auth-path` `--vscode-settings` | Her ikisi | -| `omniroute setup-continue` | Continue / `cn` CLI | `~/.continue/config.yaml` — `provider: openai` modelleri, anahtar `${{ secrets.OMNIROUTE_API_KEY }}` aracılığıyla | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Her ikisi | -| `omniroute setup-cursor` | Cursor | Hiçbir şey — uygulama içindeki adımları yazdırır (Cursor yapılandırması opak SQLite) | `--remote` `--api-key` `--only` `--port` | Her ikisi | -| `omniroute setup-roo` | Roo Code | `~/.omniroute/roo-settings.json` (içe aktarma belgesi) + bir VS Code `settings.json` varsa `roo-cline.autoImportSettingsPath` ayarlar | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--import-path` `--vscode-settings` | Her ikisi | -| `omniroute setup-crush` | Crush | `~/.config/crush/crush.json` — `openai-uyumlu` sağlayıcı, anahtar `$OMNIROUTE_API_KEY` aracılığıyla | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Her ikisi | -| `omniroute setup-goose` | Goose | `~/.config/goose/config.yaml` (`GOOSE_PROVIDER`/`OPENAI_HOST`/`GOOSE_MODEL`) + ortam tarifini yazdırır | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Her ikisi | -| `omniroute setup-aider` | Aider | `~/.aider.conf.yml` (`openai-api-base` + `model: openai/`) + ortam tarifini yazdırır | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Her ikisi | -| `omniroute setup-qwen` | Qwen Code | `~/.qwen/settings.json` — V4 `modelProviders.openai` dizisi + `OMNIROUTE_API_KEY` `~/.qwen/.env` içinde | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` `--env-path` | Her ikisi | -| `omniroute run ` | Çalışma başlatma (genel) | Hiçbir şey — doğru ortam ve argümanlarla `claude`/`codex`/`aider`/`goose`/`opencode`/`qwen`/`gemini` başlatır; Qwen ve Gemini geçici izole bir ev kullanır | `--remote` `--base-url` `--context` `--provider` `--model` `--api-key` `--api-key-env` `--dry-run` `--json` `--port` `--profile` `--token` | Her ikisi | -| `omniroute launch` | Claude Code | Hiçbir şey — `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` ile `claude` başlatır | `--remote` `--api-key` `--token` `--profile` `--port` | Her ikisi | -| `omniroute launch-codex` | OpenAI Codex CLI | Hiçbir şey — `-c` bayrakları aracılığıyla `omniroute` sağlayıcısı ile `codex` başlatır | `--remote` `--api-key` `--profile` (`-p`) `--port` | Her ikisi | - -Bayraklar hakkında notlar (komut kaynağında doğrulanmıştır): - -- `--remote ` — uzaktan bir OmniRoute'tan katalogu alır ( `--port` ve aktif bağlamı geçersiz kılar). `--api-key ` o sunucu için kimlik bilgilerini sağlar (varsayılan olarak `OMNIROUTE_API_KEY` ortam değişkenine veya aktif bağlamın jetonuna ayarlanır). -- `--only ` — virgülle ayrılmış alt dizeler; yalnızca eşleşen model kimliklerini tutar (örneğin, `--only glm,kimi`). `setup-codex`, `setup-claude`, `setup-opencode`, `setup-continue`, `setup-cursor`, `setup-crush` üzerinde mevcuttur. -- `--dry-run` — dosya sistemine dokunmadan yazılacak olanı tam olarak yazdırır. Her `setup-*` komutunda mevcuttur **hariç** `setup-cursor` (asla bir dosya yazmaz). -- `--model ` — otomatik model keşfi olmayan araçlar için gereklidir (veya etkileşimli olarak seçilir): Cline, Kilo, Roo, Goose, Qwen, Aider. Bu araçlar ayrıca etkileşimli çalıştırmalar için `--yes`'i kabul eder (bu durumda `--model` gereklidir). `setup-opencode`, varsayılan üst düzey modeli ayarlamak için `--model` alır. -- `--model ` `omniroute run` üzerinde manifestonun her hedef için bağlantısını takip eder (`bin/cli/cli-manifest.mjs`): **aider** `--model openai/` alır ve **opencode** `--model omniroute/` (ön ek yalnızca id zaten taşımıyorsa eklenir); **qwen** ve **gemini** id'yi olduğu gibi alır; **claude** bunu `ANTHROPIC_MODEL` aracılığıyla alır, **goose** `GOOSE_MODEL` aracılığıyla ve **codex** `-c model_providers.omniroute.*` argümanları aracılığıyla alır. **Qwen, yalnızca `--model` gerektiren tek çalıştırma hedefidir** — `omniroute run qwen` olmadan çıkış kodu `2` ile açık bir hata verir. -- `--port ` — yerel OmniRoute portu (varsayılan `20128`, `--remote` ayarlandığında göz ardı edilir). Tüm `setup-*` ve her iki başlatıcıda mevcuttur. -- `omniroute run` çıkış kodları: çocuk CLI'nın kendi çıkış kodu olduğu gibi iletilir; `2` = geçersiz argümanlar (desteklenmeyen hedef, eksik gerekli `--model`, konteyner koruması); `127` = hedef ikili `PATH` içinde değil; `130`/`143`/`129` başlatma `SIGINT`/`SIGTERM`/`SIGHUP` ile sonlandığında; `1` = diğer çalışma zamanı başlatma hatası. -- İki başlatıcı (`launch`, `launch-codex`) `setup-claude` / `setup-codex` tarafından yazılan bir profili seçmek için `--profile ` alır, ayrıca temel `claude` / `codex` ikili için geçiş argümanları alır. - -Etkileşimli seçim aracı, kurulum tarifleri ile de paylaşılmaktadır: - -```bash -# Aktif yerel veya uzaktan model kataloğundan seçin ve hedefi yapılandırın. -omniroute configure claude -omniroute configure opencode --provider glm -omniroute configure qwen --model qwen/qwen3.8-max-preview --yes -``` - -`configure` şu anda `codex`, `claude`, `opencode`, `qwen`, `aider`, `goose`, `cline`, `continue` ve `kilo` için test edilen tariflere devreder. Sadece IDE, MITM ve rehber olarak katalog girişleri açıkça `setup-*`/manuel akışlar olarak kalır ve başlatılabilir hedefler olarak sunulmaz. - -> `setup-opencode`, **hafif openai-uyumlu** OpenCode entegrasyonudur. -> Ayrıca daha zengin bir eklenti entegrasyonu vardır — `omniroute setup opencode` — bu, `@omniroute/opencode-plugin`'i yükler. Bunlar farklı komutlardır; yukarıdaki tablo `setup-opencode`'yi belgeler. - ---- - -## Yerel kullanım - -`localhost:20128` üzerinde OmniRoute çalışırken, sadece aracınız için kurulum komutunu çalıştırın. Katalog yerel sunucudan alınır. - -```bash -# Codex: eşleşen model başına ~/.codex/ içine bir profil yaz -omniroute setup-codex -codex --profile glm52 # oluşturulan profili kullan - -# Claude Code: model başına profiller yaz, sonra birini başlat -omniroute setup-claude -omniroute launch --profile glm52 - -# OpenCode: tüm katalog modelleri ile openai uyumlu sağlayıcıyı yaz -omniroute setup-opencode -export OMNIROUTE_API_KEY=sk-... # {env:OMNIROUTE_API_KEY} ile referans alınır, asla diskte değil -opencode -m omniroute/glm/glm-5.2 "..." - -# Otomatik keşif yapmayan araçlar açık bir model gerektirir: -omniroute setup-aider --model glm/glm-5.2 -omniroute setup-qwen --model qwen/qwen3.8-max-preview - -# Hiçbir şey yazmadan önizleme: -omniroute setup-continue --dry-run -``` - -Hiçbir yapılandırma yazmadan başlatın (sadece ortam enjekte etme): - -```bash -omniroute launch # Claude Code → yerel OmniRoute -omniroute launch-codex # Codex CLI → yerel OmniRoute -omniroute launch-codex --profile glm52 -omniroute run claude --model openai/gpt-5.4 -omniroute run codex --model openai/gpt-5.4 --dry-run --json -omniroute run aider --model glm/glm-5.2 -- --message "reply OK" -omniroute run goose --model glm/glm-5.2 -omniroute run opencode --model glm/glm-5.2 -- run "reply OK" -omniroute run qwen --model glm/glm-5.2 -- -p "reply OK" -omniroute run gemini --model glm/glm-5.2 -- --skip-trust -p "reply OK" - -# Açık komut yolu: -- sonrası gelen her şeyi geçirin -omniroute run claude -- --print-system-prompt "bu farkı gözden geçir" -``` - ---- - -## Uzaktan kullanım - -Herhangi bir kurulum komutunu `--remote` + `--api-key` ile uzaktaki bir OmniRoute'a yönlendirin. Katalog uzaktan alınır; yapılandırma yerel makinenizde yazılır. - -```bash -# Uzaktaki bir VPS'ye karşı OpenCode, yalnızca glm/kimi modellerini tut -omniroute setup-opencode --remote http://192.168.0.15:20128 --api-key oma_live_xxx \ - --only glm,kimi -opencode -m omniroute/glm/glm-5.2 "..." # önce OMNIROUTE_API_KEY'i dışa aktar - -# Uzaktan bir katalogdan Codex profilleri -omniroute setup-codex --remote http://192.168.0.15:20128 --api-key oma_live_xxx - -# CLI'yi doğrudan uzaktaki sunucuya karşı başlat -omniroute launch --remote http://192.168.0.15:20128 --api-key oma_live_xxx -omniroute launch-codex --remote http://192.168.0.15:20128 --api-key oma_live_xxx -``` - -Her seferinde `--remote`/`--api-key` geçmek yerine, bir kez giriş yapın ve **aktif bağlam** bunları otomatik olarak sağlasın: - -```bash -omniroute connect 192.168.0.15 # kapsamlı bir token oluşturur, bağlamı saklar -omniroute setup-codex # ← artık uzaktan katalogu kullanır -omniroute setup-opencode # ← aynı -omniroute launch # ← Claude Code uzakta -``` - -Bağlamlar, kapsamlar ve token yönetimi için [Uzaktan Mod](./REMOTE-MODE.md) sayfasına bakın. - ---- - -## Temel URL konvansiyonları (hangi araçlar `/v1` ister) - -OmniRoute, OpenAI yüzeyini `/v1`'de, Anthropic yüzeyini kök dizinde ve yerel Gemini yüzeyini `/v1beta`'da sunar. Her entegrasyon, aracının beklediği forma bağlıdır (komut kaynağında doğrulanmıştır): - -| Entegrasyon | Yazılan Temel URL | `/v1`? | -| -------------------------------------------------------------------------- | ----------------- | -------------------------------------------- | -| `setup-cline` (`openAiBaseUrl`) | kök | Hayır — Cline `/v1/chat/completions` ekler | -| `setup-goose` (`OPENAI_HOST`) | kök | Hayır — Goose yolu ekler | -| `setup-aider` (`OPENAI_API_BASE`) | kök | Hayır — LiteLLM `/v1/chat/completions` ekler | -| `setup-kilo`, `setup-roo`, `setup-continue`, `setup-crush`, `setup-cursor` | `/v1` ile | Evet | -| `setup-claude` (`ANTHROPIC_BASE_URL`), `launch` | kök | Hayır — Claude Code `/v1/messages` ekler | -| `setup-codex`, `launch-codex` (`model_providers.omniroute.base_url`) | `/v1` ile | Evet | -| `setup-qwen` (`modelProviders.openai[].baseUrl`) | `/v1` ile | Evet | -| `run gemini` (`GOOGLE_GEMINI_BASE_URL`) | kök | Hayır — SDK `/v1beta/models/…` ekler | - ---- - -## Yerel bağımlılıkları güncellemede tutmak: `--include=optional` - -`omniroute update` ile güncelleme yaptığınızda (onayladıktan sonra veya `--apply` ile), -OmniRoute, `--include=optional` seçeneği ile yüklemeyi gerçekleştirir: - -```bash -npm install -g omniroute@latest --include=optional -``` - -Bu, `omniroute update` komutuna geçirdiğiniz bir bayrak **değildir** — her zaman -güncelleyici tarafından uygulanır. `optionalDependencies` (`better-sqlite3`, `keytar`, -`tls-client`, LLMLingua SLM yığını) güncelleme sırasında hayatta kalmasını garanti eder, -npm yapılandırmanızda `omit=optional` ayarı olsa bile, bu durumda yerel SQLite -sürücüsü ve OS-anahtar bağıntısı sessizce kaldırılır. Uygulamadan önce tam komutu -önizlemek için: - -```bash -omniroute update --dry-run -# [DRY RUN] Şu komut çalıştırılacak: npm install -g omniroute@latest --include=optional -``` - -Diğer `omniroute update` bayrakları (kaynakta doğrulanmıştır): `--check` (eskiyse 1 ile çık), -`--apply` (sormadan yükle), `--changelog`, `--no-backup`, `--yes`. - ---- - -## Google Gemini CLI `omniroute run gemini` ile - -`@google/gemini-cli` 0.50.0 ile doğrulanan sözleşme: CLI, `GOOGLE_GEMINI_BASE_URL`'yi -kabul eder ve `POST /v1beta/models/:generateContent` -(ve `:streamGenerateContent?alt=sse`) talep eder — tam olarak OmniRoute'un yerel -Gemini yüzeyi (`/v1beta`). `omniroute run gemini` bunu otomatik olarak bağlar: - -- `GOOGLE_GEMINI_BASE_URL` → aktif OmniRoute temel URL'si (kök, `/v1` yok); -- `GEMINI_API_KEY` → çözümlenen OmniRoute kimlik bilgisi (seçenek/env/bağlam); -- **geçici izole `GEMINI_CLI_HOME`** `.gemini/settings.json` dosyası - `gemini-api-key` kimlik doğrulamasını seçer, böylece saklanan Google OAuth oturumu - (Kod Yardımcı) asla OmniRoute yönlendirmeli başlatmayı geçersiz kılmaz — çıkıştan sonra - kaldırılır; -- **env hijyeni**: çocuk ortamı `GOOGLE_API_KEY`, - `GOOGLE_GENAI_USE_VERTEXAI` ve `GOOGLE_GENAI_USE_GCA`'dan arındırılır (bu - kimlik doğrulamasını Vertex/Kod Yardımcıya yönlendirebilir), ve `GEMINI_DEFAULT_AUTH_TYPE=gemini-api-key` - bir yedek olarak ayarlanır — diğer `run` hedefleri kendi çelişen değişkenleri için - aynı muameleyi alır; -- `--model ` enjeksiyonu `--provider`/`--model`'dan. - -```bash -omniroute run gemini --model glm/glm-5.2 -- --skip-trust -p "hello" -``` - -Gemini'nin çalışma alanı güvenlik koruması hala başsız modda geçerlidir — `--skip-trust` -geçirin (veya dizini etkileşimli olarak güvenilir hale getirin); başlatıcı bunu -kasıtlı olarak atlamaz. Bu başlatıcı, **ACP kaydı** (`src/lib/acp/registry.ts`, `gemini --acp`) -ile farklıdır, bu hala `/dashboard/acp-agents` için ajan-protokol entegrasyonudur. - ---- - -## Gerçek duman taraması (isteğe bağlı) - -Deterministik başlatma planı regresyon testleri CI'da (`tests/unit/cli/run-command.test.ts`, -`tests/unit/cli/run-execution.test.ts`). GERÇEK ikili dosyaları GERÇEK -OmniRoute sunucusuna karşı doğrulamak için, `tests/integration/upstream-cli-smoke.int.test.ts` -adresinde isteğe bağlı bir sistem bulunmaktadır. Bu otomatik olarak çalışmaz -(her alt test, `RUN_CLI_SMOKE=1` ayarı yapılmadıkça atlanır), kimlik bilgilerini -çevre değişkeni ADI ile iletir (değer ile değil), anahtar biçimindeki dizeleri -herhangi bir kaydedilmiş çıktıda sansürler, ikili dosyası yüklü olmayan hedefleri -atlar ve hataları kimlik doğrulama / yukarı akış / yapılandırma olarak sınıflandırır, -basit bir boolean yerine: - -```bash -RUN_CLI_SMOKE=1 \ -OMNIROUTE_SMOKE_BASE_URL="http://localhost:20128" \ -OMNIROUTE_SMOKE_MODEL="" \ -OMNIROUTE_SMOKE_API_KEY_ENV="OMNIROUTE_API_KEY" \ -node --import tsx/esm --test tests/integration/upstream-cli-smoke.int.test.ts -``` - -İsteğe bağlı: `OMNIROUTE_SMOKE_TARGETS="codex,opencode,qwen"` taramayı kısıtlar; -`OMNIROUTE_SMOKE_TIMEOUT_MS` her hedef için 120s zaman aşımını geçersiz kılar. - ---- - -## Ayrıca bakınız - -- [Claude Code yapılandırması](./CLAUDE-CODE-CONFIGURATION.md) — daha derin bir Claude Code kılavuzu -- [Codex CLI yapılandırması](./CODEX-CLI-CONFIGURATION.md) — bir kerelik `[model_providers.omniroute]` temel kurulumu -- [Uzaktan Mod](./REMOTE-MODE.md) — bağlamlar, kapsamlı erişim jetonları, uzaktan bir sunucuyu yönetme -- [CLI Araçları referansı](../reference/CLI-TOOLS.md) — desteklenen araçların tam kataloğu + kontrol paneli sayfaları -- [Kurulum Kılavuzu](./SETUP_GUIDE.md) — kurulum yöntemleri ve ilk çalışma eğitimi +| Komut | Araç | Ne Yazar | Temel Bayraklar | Yerel vs Uzak | +| -------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| `omniroute setup-codex` | OpenAI Codex CLI | `~/.codex/.config.toml` — uyumlu model başına bir profil (`codex --profile `) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--codex-home` | Her ikisi de | +| `omniroute setup-claude` | Claude Code | `~/.claude/profiles//settings.json` — eşleşen model başına bir profil (`CLAUDE_CONFIG_DIR`) | `--remote` `--api-key` `--only` `--dry-run` `--port` `--claude-home` | Her ikisi de | +| `omniroute setup-opencode` | OpenCode (openai-compatible) | `~/.config/opencode/opencode.json` — katalogdaki her modelle `omniroute` sağlayıcısı (`opencode -m omniroute/`) | `--remote` `--api-key` `--only` `--model` `--dry-run` `--port` | Her ikisi de | +| `omniroute setup-cline` | Cline | `~/.cline/data/{globalState,secrets}.json` (CLI modu) + VS Code eklenti ayarlarını yazdırır | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--cline-dir` | Her ikisi de | +| `omniroute setup-kilo` | Kilo Code | `~/.local/share/kilo/auth.json` (CLI) + varsa VS Code `settings.json` içine `kilocode.*` birleştirir | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--auth-path` `--vscode-settings` | Her ikisi de | +| `omniroute setup-continue` | Continue / `cn` CLI | `~/.continue/config.yaml` — `provider: openai` modelleri, anahtar `${{ secrets.OMNIROUTE_API_KEY }}` üzerinden | `--remote` `--api-key` `--only` `--dry-run` `--port` `--config-path` | Her ikisi de | +| `omniroute setup-cursor` | Cursor | Hiçbir dosya yazmaz — uygulama içi adımları konsola yazdırır | `--remote` `--api-key` `--only` `--port` | Her ikisi de | +| `omniroute setup-roo` | Roo Code | `~/.omniroute/roo-settings.json` (içe aktarma belgesi) | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--import-path` `--vscode-settings` | Her ikisi de | +| `omniroute setup-goose` | Goose | `~/.config/goose/config.yaml` (`GOOSE_PROVIDER`/`OPENAI_HOST`/`GOOSE_MODEL`) | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Her ikisi de | +| `omniroute setup-aider` | Aider | `~/.aider.conf.yml` (`openai-api-base` + `model: openai/`) | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` | Her ikisi de | +| `omniroute setup-qwen` | Qwen Code | `~/.qwen/settings.json` — V4 `modelProviders.openai` dizisi | `--remote` `--api-key` `--model` `--yes` `--dry-run` `--port` `--config-path` `--env-path` | Her ikisi de | +| `omniroute run ` | Doğrudan Başlatma (Genel) | Dosya yazmaz — doğru ortam değişkenleriyle hedef aracı doğrudan başlatır | `--remote` `--base-url` `--context` `--provider` `--model` `--api-key` `--api-key-env` `--dry-run` `--json` `--port` `--profile` `--token` | Her ikisi de | +| `omniroute launch` | Claude Code | Dosya yazmaz — `ANTHROPIC_BASE_URL`/`ANTHROPIC_AUTH_TOKEN` ile `claude` başlatır | `--remote` `--api-key` `--token` `--profile` `--port` | Her ikisi de | +| `omniroute launch-codex` | OpenAI Codex CLI | Dosya yazmaz — `-c` parametreleri ile `codex` başlatır | `--remote` `--api-key` `--profile` (`-p`) `--port` | Her ikisi de | diff --git a/docs/i18n/tr/docs/guides/FEATURES.md b/docs/i18n/tr/docs/guides/FEATURES.md index 63acb9a9a1..9690ff359c 100644 --- a/docs/i18n/tr/docs/guides/FEATURES.md +++ b/docs/i18n/tr/docs/guides/FEATURES.md @@ -1,269 +1,51 @@ -# OmniRoute — Dashboard Features Gallery (Türkçe) +--- +title: "OmniRoute — Pano Özellikleri Galerisi" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/FEATURES.md) · 🇸🇦 [ar](../../ar/docs/FEATURES.md) · 🇧🇬 [bg](../../bg/docs/FEATURES.md) · 🇧🇩 [bn](../../bn/docs/FEATURES.md) · 🇨🇿 [cs](../../cs/docs/FEATURES.md) · 🇩🇰 [da](../../da/docs/FEATURES.md) · 🇩🇪 [de](../../de/docs/FEATURES.md) · 🇪🇸 [es](../../es/docs/FEATURES.md) · 🇮🇷 [fa](../../fa/docs/FEATURES.md) · 🇫🇮 [fi](../../fi/docs/FEATURES.md) · 🇫🇷 [fr](../../fr/docs/FEATURES.md) · 🇮🇳 [gu](../../gu/docs/FEATURES.md) · 🇮🇱 [he](../../he/docs/FEATURES.md) · 🇮🇳 [hi](../../hi/docs/FEATURES.md) · 🇭🇺 [hu](../../hu/docs/FEATURES.md) · 🇮🇩 [id](../../id/docs/FEATURES.md) · 🇮🇹 [it](../../it/docs/FEATURES.md) · 🇯🇵 [ja](../../ja/docs/FEATURES.md) · 🇰🇷 [ko](../../ko/docs/FEATURES.md) · 🇮🇳 [mr](../../mr/docs/FEATURES.md) · 🇲🇾 [ms](../../ms/docs/FEATURES.md) · 🇳🇱 [nl](../../nl/docs/FEATURES.md) · 🇳🇴 [no](../../no/docs/FEATURES.md) · 🇵🇭 [phi](../../phi/docs/FEATURES.md) · 🇵🇱 [pl](../../pl/docs/FEATURES.md) · 🇵🇹 [pt](../../pt/docs/FEATURES.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/FEATURES.md) · 🇷🇴 [ro](../../ro/docs/FEATURES.md) · 🇷🇺 [ru](../../ru/docs/FEATURES.md) · 🇸🇰 [sk](../../sk/docs/FEATURES.md) · 🇸🇪 [sv](../../sv/docs/FEATURES.md) · 🇰🇪 [sw](../../sw/docs/FEATURES.md) · 🇮🇳 [ta](../../ta/docs/FEATURES.md) · 🇮🇳 [te](../../te/docs/FEATURES.md) · 🇹🇭 [th](../../th/docs/FEATURES.md) · 🇹🇷 [tr](../../tr/docs/FEATURES.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/FEATURES.md) · 🇵🇰 [ur](../../ur/docs/FEATURES.md) · 🇻🇳 [vi](../../vi/docs/FEATURES.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/FEATURES.md) +# OmniRoute — Pano Özellikleri Galerisi (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/guides/FEATURES.md) · 🇸🇦 [ar](../../ar/docs/guides/FEATURES.md) · 🇧🇬 [bg](../../bg/docs/guides/FEATURES.md) · 🇧🇩 [bn](../../bn/docs/guides/FEATURES.md) · 🇨🇿 [cs](../../cs/docs/guides/FEATURES.md) · 🇩🇰 [da](../../da/docs/guides/FEATURES.md) · 🇩🇪 [de](../../de/docs/guides/FEATURES.md) · 🇪🇸 [es](../../es/docs/guides/FEATURES.md) · 🇮🇷 [fa](../../fa/docs/guides/FEATURES.md) · 🇫🇮 [fi](../../fi/docs/guides/FEATURES.md) · 🇫🇷 [fr](../../fr/docs/guides/FEATURES.md) · 🇮🇳 [gu](../../gu/docs/guides/FEATURES.md) · 🇮🇱 [he](../../he/docs/guides/FEATURES.md) · 🇮🇳 [hi](../../hi/docs/guides/FEATURES.md) · 🇭🇺 [hu](../../hu/docs/guides/FEATURES.md) · 🇮🇩 [id](../../id/docs/guides/FEATURES.md) · 🇮🇹 [it](../../it/docs/guides/FEATURES.md) · 🇯🇵 [ja](../../ja/docs/guides/FEATURES.md) · 🇰🇷 [ko](../../ko/docs/guides/FEATURES.md) · 🇮🇳 [mr](../../mr/docs/guides/FEATURES.md) · 🇲🇾 [ms](../../ms/docs/guides/FEATURES.md) · 🇳🇱 [nl](../../nl/docs/guides/FEATURES.md) · 🇳🇴 [no](../../no/docs/guides/FEATURES.md) · 🇵🇭 [phi](../../phi/docs/guides/FEATURES.md) · 🇵🇱 [pl](../../pl/docs/guides/FEATURES.md) · 🇵🇹 [pt](../../pt/docs/guides/FEATURES.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/guides/FEATURES.md) · 🇷🇴 [ro](../../ro/docs/guides/FEATURES.md) · 🇷🇺 [ru](../../ru/docs/guides/FEATURES.md) · 🇸🇰 [sk](../../sk/docs/guides/FEATURES.md) · 🇸🇪 [sv](../../sv/docs/guides/FEATURES.md) · 🇰🇪 [sw](../../sw/docs/guides/FEATURES.md) · 🇮🇳 [ta](../../ta/docs/guides/FEATURES.md) · 🇮🇳 [te](../../te/docs/guides/FEATURES.md) · 🇹🇭 [th](../../th/docs/guides/FEATURES.md) · 🇹🇷 [tr](../../tr/docs/guides/FEATURES.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/guides/FEATURES.md) · 🇵🇰 [ur](../../ur/docs/guides/FEATURES.md) · 🇻🇳 [vi](../../vi/docs/guides/FEATURES.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/guides/FEATURES.md) --- -Visual guide to every section of the OmniRoute dashboard. +OmniRoute panosunun her bölümüne ilişkin görsel ve işlevsel kılavuz. --- -## 🔌 Providers +## ✨ v3.8.x Öne Çıkanlar - -![Providers Dashboard](screenshots/01-providers.png) +- 🤖 **Auto Combo / Sıfır Yapılandırmalı Otomatik Yönlendirme** — `auto/coding`, `auto/fast`, `auto/cheap`, `auto/offline`, `auto/smart`, `auto/lkgp` önekleri. 14 faktörlü puanlama motoru ve 4 küratörlü mod paketi (ship-fast, cost-saver, quality-first, offline-friendly) ile desteklenir. +- 🆕 **Command Code ve Z.AI sağlayıcıları** — Kota etiketleri ve model kataloğu ile birinci sınıf kayıt. +- 🎬 **KIE Medya Genişletmesi** — Video ve müzik üretimi modelleri dahil genişletilmiş katalog. +- 🔐 **Devin Kimlik Doğrulaması** — Masaüstü mevcut bir Devin API anahtarını içe aktarır; CLI yerel kimlik bilgilerini kullanır. +- 🆓 **Yeni Ücretsiz Sağlayıcılar** — LLM7, Lepton, UncloseAI, BazaarLink, Completions, Enally, FreeTheAi vb. +- 🎨 **Cursor Tam OpenAI Eşitliği** — Araç çağırma (tool calls), akış ve uçtan uca oturum yönetimi. +- 📌 **Oturum Başına Yapışkan Yönlendirme (Sticky Routing)** — Codex oturumları turlar arasında aynı hesaba sabitlenir. +- 🔄 **Sıfırlama Duyarlı Yönlendirme Stratejisi** — Kombolar, kota penceresi en erken sıfırlanan hesapları tercih eder. +- 🩺 **Model Soğuma Süreleri Panosu** — Model bazlı kilitlenmeleri izleme ve kullanıcı arayüzünden manuel olarak yeniden etkinleştirme. +- 💻 **CLI Geliştirme Paketi** — `omniroute providers`, `omniroute combos`, `omniroute doctor`, `omniroute setup` dahil 20'den fazla komut. +- 🧠 **Akıl Yürütme Tekrar Oynatma Önbelleği (Reasoning Replay Cache)** — Akıl yürütme izlerinin hibrit bellek içi + SQLite kalıcılığı. --- -## 🎨 Combos +## 🔌 Sağlayıcılar (Providers) -Create model routing combos with 13 strategies: priority, weighted, round-robin, random, least-used, cost-optimized, strict-random, auto, fill-first, p2c, lkgp, context-optimized, and **context-relay**. Each combo chains multiple models with automatic fallback and includes quick templates and readiness checks. +AI sağlayıcı bağlantılarını yönetin: OAuth sağlayıcıları (Claude Code, Codex), API anahtarı sağlayıcıları (Groq, DeepSeek, OpenRouter) ve ücretsiz sağlayıcılar (Qoder, Kiro). -Recent combo improvements: +## 🎨 Kombolar (Combos) -- **Structured combo builder** — create each step by selecting provider, model, and exact account/connection -- **Repeated provider support** — reuse the same provider many times in one combo as long as the `(provider, model, connection)` tuple is unique -- **Combo target health** — analytics and health surfaces now distinguish individual combo targets/steps instead of collapsing everything into model strings -- **Composite tier ordering** — `defaultTier -> fallbackTier` now influences runtime execution/fallback order for top-level combo steps +19 genel strateji ile model yönlendirme komboları oluşturun: priority, weighted, round-robin, context-relay, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, cache-optimized, **fusion** ve **pipeline**. -![Combos Dashboard](screenshots/02-combos.png) +## 📊 Analitik (Analytics) ---- +Token tüketimi, maliyet tahminleri, etkinlik ısı haritaları, haftalık dağılım grafikleri ve sağlayıcı bazında ayrıntılarla kapsamlı kullanım analitiği. -## 📊 Analytics +## 🏥 Sistem Sağlığı (System Health) -Comprehensive usage analytics with token consumption, cost estimates, activity heatmaps, weekly distribution charts, and per-provider breakdowns. +Gerçek zamanlı izleme: çalışma süresi, bellek, sürüm, gecikme yüzdelikleri (p50/p95/p99), önbellek istatistikleri, sağlayıcı devre kesici durumları ve kota izlenen aktif oturumlar. -![Analytics Dashboard](screenshots/03-analytics.png) +## 🛠️ CLI Araçları ve Ajanlar ---- - -## 🏥 System Health - -Real-time monitoring: uptime, memory, version, latency percentiles (p50/p95/p99), cache statistics, provider circuit breaker states, active quota-monitored sessions, and combo target health. - -![Health Dashboard](screenshots/04-health.png) - ---- - -## 🔧 Translator Playground - -Four modes for debugging API translations: **Playground** (format converter), **Chat Tester** (live requests), **Test Bench** (batch tests), and **Live Monitor** (real-time stream). - -![Translator Playground](screenshots/05-translator.png) - ---- - -## 🎮 Model Playground _(v2.0.9+)_ - -Test any model directly from the dashboard. Select provider, model, and endpoint, write prompts with Monaco Editor, stream responses in real-time, abort mid-stream, and view timing metrics. - ---- - -## 🎨 Themes _(v2.0.5+)_ - -Customizable color themes for the entire dashboard. Choose from 7 preset colors (Coral, Blue, Red, Green, Violet, Orange, Cyan) or create a custom theme by picking any hex color. Supports light, dark, and system mode. - ---- - -## ⚙️ Settings - -Comprehensive settings panel with tabs: - -- **General** — System storage, backup management (export/import database) -- **Appearance** — Theme selector (dark/light/system), color theme presets and custom colors, health log visibility, sidebar item visibility controls -- **Security** — API endpoint protection, custom provider blocking, IP filtering, session info -- **Routing** — Model aliases, background task degradation -- **Resilience** — Rate limit persistence, circuit breaker tuning, auto-disable banned accounts, provider expiration monitoring, **Context Relay** handoff threshold and summary model configuration -- **Advanced** — Configuration overrides, configuration audit trail, fallback degradation mode - -![Settings Dashboard](screenshots/06-settings.png) - ---- - -## 🔧 CLI Tools - -One-click configuration for AI coding tools: Claude Code, Codex CLI, OpenClaw, Kilo Code, Antigravity, Cline, Continue, Cursor, and Factory Droid. Features automated config apply/reset, connection profiles, and model mapping. - -![CLI Tools Dashboard](screenshots/07-cli-tools.png) - ---- - -## 🤖 CLI Agents _(v2.0.11+)_ - -Dashboard for discovering and managing CLI agents. Shows a grid of 17 built-in agents (Codex, Claude, Goose, OpenClaw, Aider, OpenCode, Cline, Qwen Code, ForgeCode, Amazon Q, Open Interpreter, Cursor CLI, Warp, **Windsurf**, **Devin CLI**, **Kimi Coding**, **Command Code**) with: - -- **Installation status** — Installed / Not Found with version detection -- **Protocol badges** — stdio, HTTP, etc. -- **Custom agents** — Register any CLI tool via form (name, binary, version command, spawn args) -- **CLI Fingerprint Matching** — Per-provider toggle to match native CLI request signatures, reducing ban risk while preserving proxy IP - ---- - -## 🔗 Context Relay _(v3.5.5+)_ - -A combo strategy that preserves session continuity when account rotation happens mid-conversation. Before the active account is exhausted, OmniRoute generates a structured handoff summary in the background. After the next request resolves to a different account, the summary is injected as a system message so the new account continues with full context. - -Configurable via combo-level or global settings: - -- **Handoff Threshold** — Quota usage percentage that triggers summary generation (default 85%) -- **Max Messages For Summary** — How much recent history to condense -- **Summary Model** — Optional override model for generating the handoff summary - -Currently supports Codex account rotation. See [Context Relay documentation](features/context-relay.md). - ---- - -## 🛡️ Proxy Hardening _(v3.5.5+)_ - -Comprehensive proxy configuration enforcement across the entire request pipeline: - -- **Token Health Check** — Background OAuth refresh now resolves proxy config per connection, preventing failures in proxy-required environments -- **API Key Validation** — Provider key validation (`POST /api/providers/validate`) routes through `runWithProxyContext`, honoring provider-level and global proxy settings -- **undici Dispatcher Fix** — Proxy dispatchers use undici's own fetch implementation instead of Node's built-in fetch, resolving `invalid onRequestStart method` errors on Node.js 22 -- **Node.js Version Detection** — Login page proactively detects incompatible Node.js versions (24+) and displays a warning banner with instructions to use Node 22 LTS - ---- - -## 📧 Email Privacy Masking _(v3.5.6+)_ - -OAuth account emails are now masked in the provider dashboard (e.g. `di*****@g****.com`) to prevent accidental exposure when sharing screenshots or recording demos. The full email address remains accessible via hover tooltip (`title` attribute). - ---- - -## 👁️ Model Visibility Toggle _(v3.5.6+)_ - -The provider page model list now includes: - -- **Real-time search/filter bar** — Quickly find specific models -- **Per-model visibility toggle** (👁 icon) — Hidden models are grayed out and excluded from the `/v1/models` catalog -- **Active-count badge** (`N/M active`) — Shows at a glance how many models are enabled vs total - ---- - -## 🔧 OAuth Env Repair _(v3.6.1+)_ - -One-click "Repair env" action for OAuth providers that restores missing environment variables and fixes broken auth state. Accessible from `Dashboard → Providers → [OAuth Provider] → Repair env`. Automatically detects and repairs: - -- Missing OAuth client credentials -- Corrupted env file entries -- Backup path sanitization - ---- - -## 🗑️ Uninstall / Full Uninstall _(v3.6.2+)_ - -Clean removal scripts for all installation methods: - -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | - ---- - -## 🖼️ Media _(v2.0.3+)_ - -Generate images, videos, and music from the dashboard. Supports OpenAI, xAI, Together, Hyperbolic, SD WebUI, ComfyUI, AnimateDiff, Stable Audio Open, and MusicGen. - ---- - -## 📝 Request Logs - -Real-time request logging with filtering by provider, model, account, and API key. Shows status codes, token usage, latency, and response details. - -![Usage Logs](screenshots/08-usage.png) - ---- - -## 🌐 API Endpoint - -Your unified API endpoint with capability breakdown: Chat Completions, Responses API, Embeddings, Image Generation, Reranking, Audio Transcription, Text-to-Speech, Moderations, and registered API keys. Cloudflare Quick Tunnel integration and cloud proxy support for remote access. - -![Endpoint Dashboard](screenshots/09-endpoint.png) - ---- - -## 🔑 API Key Management - -Create, scope, and revoke API keys. Each key can be restricted to specific models/providers with full access or read-only permissions. Visual key management with usage tracking. - ---- - -## 📋 Audit Log - -Administrative action tracking with filtering by action type, actor, target, IP address, and timestamp. Full security event history. - ---- - -## 🖥️ Desktop Application - -Native Electron desktop app for Windows, macOS, and Linux. Run OmniRoute as a standalone application with system tray integration, offline support, auto-update, and one-click install. - -Key features: - -- Server readiness polling (no blank screen on cold start) -- System tray with port management -- Content Security Policy -- Single-instance lock -- Auto-update on restart -- Platform-conditional UI (macOS traffic lights, Windows/Linux default titlebar) -- Hardened Electron build packaging — symlinked `node_modules` in the standalone bundle is detected and rejected before packaging, preventing runtime dependency on the build machine (v2.5.5+) -- **Graceful shutdown** — Electron `before-quit` shuts down Next.js cleanly, preventing SQLite WAL database locks (v3.6.2+) - -📖 See [`electron/README.md`](../electron/README.md) for full documentation. - ---- - -## 🌐 V1 WebSocket Bridge _(v3.6.6+)_ - -OmniRoute now supports **OpenAI-compatible WebSocket clients** via the `/v1/ws` upgrade endpoint. The custom `scripts/v1-ws-bridge.mjs` server wraps Next.js and upgrades WS connections to full bidirectional streaming sessions. Authentication uses the same API key or session cookie as HTTP requests. - -Key behaviours: - -- WS upgrade validated by `src/lib/ws/handshake.ts` before the connection is established -- Streams terminated cleanly on session close or upstream error -- Works alongside the existing HTTP+SSE streaming path simultaneously - ---- - -## 🔑 Sync Tokens & Config Bundle _(v3.6.6+)_ - -Multi-device and external operator access is now possible via **scoped sync tokens**: - -- **`POST /api/sync/tokens`** — Issue a new sync token (scoped, with optional expiry) -- **`DELETE /api/sync/tokens/:id`** — Revoke a token -- **`GET /api/sync/bundle`** — Download a versioned, ETag-keyed JSON snapshot of all non-sensitive settings (passwords redacted) - -The config bundle is built by `src/lib/sync/bundle.ts`. Consumers compare the `ETag` response header to detect changes without re-downloading the full payload. - ---- - -## 🧠 GLM Thinking Preset _(v3.6.6+)_ - -**GLM Thinking (`glmt`)** is now a registered first-class provider: 65 536 max output tokens, 24 576 thinking budget, 900 s default timeout, Claude-compatible API format, and shared usage sync with the GLM family. - -**Hybrid token counting** also lands in v3.6.6: when a Claude-compatible provider exposes `/messages/count_tokens`, OmniRoute calls it before large requests with graceful estimation fallback. - ---- - -## 🛡️ Safe Outbound Fetch & SSRF Guard _(v3.6.6+)_ - -All provider validation and model discovery calls now go through a two-layer outbound guard: - -1. **URL guard** (`src/shared/network/outboundUrlGuard.ts`) — Blocks private/loopback/link-local IP ranges before the socket is opened. -2. **Safe fetch wrapper** (`src/shared/network/safeOutboundFetch.ts`) — Applies the URL guard, normalises timeouts, and retries transient errors with exponential backoff. - -Guard violations surface as HTTP 422 (`URL_GUARD_BLOCKED`) and are written to the compliance audit log via `providerAudit.ts`. - ---- - -## 🔄 Cooldown-Aware Retries _(v3.6.6+)_ - -Chat requests now **automatically retry** when an upstream provider returns a model-scoped cooldown. Configurable via `REQUEST_RETRY` (default: 2) and `MAX_RETRY_INTERVAL_SEC` (default: 30 s). Rate-limit header learning improved across `x-ratelimit-reset-requests`, `x-ratelimit-reset-tokens`, and `Retry-After` — per-model cooldown state is visible in the Resilience dashboard. - ---- - -## 📋 Compliance Audit v2 _(v3.6.6+)_ - -The audit log has been expanded with cursor-based pagination, request context enrichment (request ID, user agent, IP), structured auth events, provider CRUD events with diff context, and SSRF-blocked validation logging. New events emitted by `src/lib/compliance/providerAudit.ts`. +14'ten fazla yerleşik kodlama CLI aracını tek tıkla yapılandırın, algılayın ve doğrudan OmniRoute'a bağlayın. diff --git a/docs/i18n/tr/docs/guides/I18N.md b/docs/i18n/tr/docs/guides/I18N.md index 6ecb94e2af..d2f44cc3aa 100644 --- a/docs/i18n/tr/docs/guides/I18N.md +++ b/docs/i18n/tr/docs/guides/I18N.md @@ -1,441 +1,66 @@ -# i18n — Internationalization Guide (Türkçe) +--- +title: "i18n — Uluslararasılaşma Kılavuzu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/I18N.md) · 🇸🇦 [ar](../../ar/docs/I18N.md) · 🇧🇬 [bg](../../bg/docs/I18N.md) · 🇧🇩 [bn](../../bn/docs/I18N.md) · 🇨🇿 [cs](../../cs/docs/I18N.md) · 🇩🇰 [da](../../da/docs/I18N.md) · 🇩🇪 [de](../../de/docs/I18N.md) · 🇪🇸 [es](../../es/docs/I18N.md) · 🇮🇷 [fa](../../fa/docs/I18N.md) · 🇫🇮 [fi](../../fi/docs/I18N.md) · 🇫🇷 [fr](../../fr/docs/I18N.md) · 🇮🇳 [gu](../../gu/docs/I18N.md) · 🇮🇱 [he](../../he/docs/I18N.md) · 🇮🇳 [hi](../../hi/docs/I18N.md) · 🇭🇺 [hu](../../hu/docs/I18N.md) · 🇮🇩 [id](../../id/docs/I18N.md) · 🇮🇹 [it](../../it/docs/I18N.md) · 🇯🇵 [ja](../../ja/docs/I18N.md) · 🇰🇷 [ko](../../ko/docs/I18N.md) · 🇮🇳 [mr](../../mr/docs/I18N.md) · 🇲🇾 [ms](../../ms/docs/I18N.md) · 🇳🇱 [nl](../../nl/docs/I18N.md) · 🇳🇴 [no](../../no/docs/I18N.md) · 🇵🇭 [phi](../../phi/docs/I18N.md) · 🇵🇱 [pl](../../pl/docs/I18N.md) · 🇵🇹 [pt](../../pt/docs/I18N.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/I18N.md) · 🇷🇴 [ro](../../ro/docs/I18N.md) · 🇷🇺 [ru](../../ru/docs/I18N.md) · 🇸🇰 [sk](../../sk/docs/I18N.md) · 🇸🇪 [sv](../../sv/docs/I18N.md) · 🇰🇪 [sw](../../sw/docs/I18N.md) · 🇮🇳 [ta](../../ta/docs/I18N.md) · 🇮🇳 [te](../../te/docs/I18N.md) · 🇹🇭 [th](../../th/docs/I18N.md) · 🇹🇷 [tr](../../tr/docs/I18N.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/I18N.md) · 🇵🇰 [ur](../../ur/docs/I18N.md) · 🇻🇳 [vi](../../vi/docs/I18N.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/I18N.md) +# i18n — Uluslararasılaşma Kılavuzu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/guides/I18N.md) · 🇸🇦 [ar](../../ar/docs/guides/I18N.md) · 🇧🇬 [bg](../../bg/docs/guides/I18N.md) · 🇧🇩 [bn](../../bn/docs/guides/I18N.md) · 🇨🇿 [cs](../../cs/docs/guides/I18N.md) · 🇩🇰 [da](../../da/docs/guides/I18N.md) · 🇩🇪 [de](../../de/docs/guides/I18N.md) · 🇪🇸 [es](../../es/docs/guides/I18N.md) · 🇮🇷 [fa](../../fa/docs/guides/I18N.md) · 🇫🇮 [fi](../../fi/docs/guides/I18N.md) · 🇫🇷 [fr](../../fr/docs/guides/I18N.md) · 🇮🇳 [gu](../../gu/docs/guides/I18N.md) · 🇮🇱 [he](../../he/docs/guides/I18N.md) · 🇮🇳 [hi](../../hi/docs/guides/I18N.md) · 🇭🇺 [hu](../../hu/docs/guides/I18N.md) · 🇮🇩 [id](../../id/docs/guides/I18N.md) · 🇮🇹 [it](../../it/docs/guides/I18N.md) · 🇯🇵 [ja](../../ja/docs/guides/I18N.md) · 🇰🇷 [ko](../../ko/docs/guides/I18N.md) · 🇮🇳 [mr](../../mr/docs/guides/I18N.md) · 🇲🇾 [ms](../../ms/docs/guides/I18N.md) · 🇳🇱 [nl](../../nl/docs/guides/I18N.md) · 🇳🇴 [no](../../no/docs/guides/I18N.md) · 🇵🇭 [phi](../../phi/docs/guides/I18N.md) · 🇵🇱 [pl](../../pl/docs/guides/I18N.md) · 🇵🇹 [pt](../../pt/docs/guides/I18N.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/guides/I18N.md) · 🇷🇴 [ro](../../ro/docs/guides/I18N.md) · 🇷🇺 [ru](../../ru/docs/guides/I18N.md) · 🇸🇰 [sk](../../sk/docs/guides/I18N.md) · 🇸🇪 [sv](../../sv/docs/guides/I18N.md) · 🇰🇪 [sw](../../sw/docs/guides/I18N.md) · 🇮🇳 [ta](../../ta/docs/guides/I18N.md) · 🇮🇳 [te](../../te/docs/guides/I18N.md) · 🇹🇭 [th](../../th/docs/guides/I18N.md) · 🇹🇷 [tr](../../tr/docs/guides/I18N.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/guides/I18N.md) · 🇵🇰 [ur](../../ur/docs/guides/I18N.md) · 🇻🇳 [vi](../../vi/docs/guides/I18N.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/guides/I18N.md) --- -OmniRoute supports **30 languages** with full dashboard UI translation, translated documentation, and RTL support for Arabic and Hebrew. +OmniRoute, eksiksiz pano kullanıcı arayüzü çevirisi, çevrilmiş dokümantasyon ve Arapça/İbranice için RTL desteği ile **43 dili** destekler. -## Quick Reference +## Çeviri İşlem Hattı (v3.8.0 Önerilen) -| Task | Command | -| ---------------------- | --------------------------------------------------------------------------------------- | -| Generate translations | `node scripts/i18n/generate-multilang.mjs messages` | -| Translate docs (LLM) | `python3 scripts/i18n_autotranslate.py --api-url --api-key --model ` | -| Validate a locale | `python3 scripts/validate_translation.py quick -l cs` | -| Check code keys | `python3 scripts/check_translations.py` | -| Generate QA report | `node scripts/i18n/generate-qa-checklist.mjs` | -| Visual QA (Playwright) | `node scripts/i18n/run-visual-qa.mjs` | +OmniRoute, belgeler için OpenAI uyumlu bir LLM uç noktası tarafından desteklenen karma (hash) tabanlı artımlı bir çevirmen kullanır: + +```bash +# Çevirileri çalıştır (artımlı — yalnızca değişen kaynaklara dokunur) +npm run i18n:run + +# Tek bir yerel ayarla sınırla +npm run i18n:run -- --locale=tr + +# Belirli dosyaları çevir (virgülle ayrılmış, depoya göreli yollar) +npm run i18n:run -- --files=CLAUDE.md,docs/architecture/ARCHITECTURE.md + +# Önizleme (API çağrısı veya yazma yapmaz) +npm run i18n:run:dry + +# CI kalite kapısı — çeviri sapması varsa sıfır olmayan kodla çıkar +npm run i18n:check +``` + +**Doğruluk Kaynağı:** `config/i18n.json` tüm yerel ayarları (UI + belgeler), RTL kümesini ve `docsExcluded` kodlarını listeler. `src/i18n/config.ts` içindeki çalışma zamanı yapılandırması bu JSON üzerinde ince bir bağdaştırıcıdır. + +--- + +## Hızlı Başvuru + +| Görev | Komut | +| -------------------------- | ---------------------------------------------------------- | +| Belgeleri Çevirme (LLM) | `npm run i18n:run` (tercih edilen — artımlı, hash tabanlı) | +| UI Dizelerini Çevirme | `node scripts/i18n/generate-multilang.mjs messages` | +| Çeviri Sapmasını Kontrol Et| `npm run i18n:check` | +| Bir Dili Doğrulama | `python3 scripts/i18n/validate_translation.py quick -l tr` | +| Kod Anahtarlarını Kontrol | `python3 scripts/i18n/check_translations.py` | +| Kalite Raporu Üretme | `node scripts/i18n/generate-qa-checklist.mjs` | +| Görsel Kalite (Playwright) | `node scripts/i18n/run-visual-qa.mjs` | + +--- ## Mimari -### Source of Truth +- **UI Dizeleri**: `src/i18n/messages/en.json` (İngilizce kaynak, ~2800 anahtar) +- **Yerel Ayar Dosyaları**: `src/i18n/messages/{locale}.json` (43 çeviri) +- **Framework**: Çerez tabanlı yerel ayar çözümlemesi ile `next-intl` +- **Yapılandırma**: `src/i18n/config.ts` — tüm 43 yerel ayarı, dil adlarını ve bayrakları tanımlar -- **UI strings**: `src/i18n/messages/en.json` (English source, ~2800 keys) -- **Locale files**: `src/i18n/messages/{locale}.json` (30 translations) -- **Framework**: `next-intl` with cookie-based locale resolution -- **Config**: `src/i18n/config.ts` — defines all 30 locales, language names, flags +### Çalışma Zamanı Akışı -### Runtime Flow - -1. User selects language → `NEXT_LOCALE` cookie set -2. `src/i18n/request.ts` resolves locale: cookie → `Accept-Language` header → fallback `en` -3. Dynamic import loads `messages/{locale}.json` -4. Components use `useTranslations("namespace")` and `t("key")` - -### Supported Locales - -| Code | Language | RTL | Google Translate Code | -| ------- | -------------------- | --- | --------------------- | -| `ar` | العربية | Yes | `ar` | -| `bg` | Български | No | `bg` | -| `cs` | Čeština | No | `cs` | -| `da` | Dansk | No | `da` | -| `de` | Deutsch | No | `de` | -| `es` | Español | No | `es` | -| `fi` | Suomi | No | `fi` | -| `fr` | Français | No | `fr` | -| `he` | עברית | Yes | `iw` | -| `hi` | हिन्दी | No | `hi` | -| `hu` | Magyar | No | `hu` | -| `id` | Bahasa Indonesia | No | `id` | -| `it` | Italiano | No | `it` | -| `ja` | 日本語 | No | `ja` | -| `ko` | 한국어 | No | `ko` | -| `ms` | Bahasa Melayu | No | `ms` | -| `nl` | Nederlands | No | `nl` | -| `no` | Norsk | No | `no` | -| `phi` | Filipino | No | `tl` | -| `pl` | Polski | No | `pl` | -| `pt` | Português (Portugal) | No | `pt` | -| `pt-BR` | Português (Brasil) | No | `pt` | -| `ro` | Română | No | `ro` | -| `ru` | Русский | No | `ru` | -| `sk` | Slovenčina | No | `sk` | -| `sv` | Svenska | No | `sv` | -| `th` | ไทย | No | `th` | -| `tr` | Türkçe | No | `tr` | -| `uk-UA` | Українська | No | `uk` | -| `vi` | Tiếng Việt | No | `vi` | -| `zh-CN` | 中文 (简体) | No | `zh-CN` | - -## Adding a New Language - -### 1. Register the Locale - -Edit `src/i18n/config.ts`: - -```ts -// Add to LOCALES array -"xx", -// Add to LANGUAGES array -{ code: "xx", label: "XX", name: "Language Name", flag: "🏳️" }, -``` - -### 2. Add to Generator - -Edit `scripts/i18n/generate-multilang.mjs` — add entry to `LOCALE_SPECS`: - -```js -{ - code: "xx", - googleTl: "xx", - label: "XX", - flag: "🏳️", - languageName: "Language Name", - readmeName: "Language Name", - docsName: "Language Name", -}, -``` - -### 3. Generate Initial Translation - -```bash -node scripts/i18n/generate-multilang.mjs messages -``` - -This creates `src/i18n/messages/xx.json` auto-translated from `en.json` via Google Translate. - -### 4. Review & Fix Auto-Translations - -Auto-translations are a starting point. Review manually for: - -- Technical accuracy -- Context-appropriate terminology -- Proper handling of placeholders (`{count}`, `{value}`, etc.) - -### 5. Validate - -```bash -python3 scripts/validate_translation.py quick -l xx -python3 scripts/validate_translation.py diff common -l xx -``` - -### 6. Generate Translated Documentation - -```bash -node scripts/i18n/generate-multilang.mjs docs -``` - -## Auto-Translation Pipeline - -### generate-multilang.mjs (Google Translate) - -**Primary auto-translation engine** — uses Google Translate free API to generate translations for UI strings, READMEs, and documentation. - -```bash -node scripts/i18n/generate-multilang.mjs [messages|readme|docs|all] -``` - -| Mode | What it does | -| ---------- | ----------------------------------------------------------------------------- | -| `messages` | Translates missing keys in `src/i18n/messages/{locale}.json` from `en.json` | -| `readme` | Translates `README.md` into all locales as `README.{code}.md` in project root | -| `docs` | Translates `DOC_SOURCE_FILES` into `docs/i18n/{locale}/{docName}` | -| `all` | Runs all three modes | - -**Features:** - -- **Text protection**: Masks code blocks (` ``` `), inline code (`` ` ``), markdown links/images (`[text](url)`), HTML tags, tables, and ICU placeholders (`{count}`, `{value}`, `{total}`, etc.) before translation, then restores them -- **Chunked batching**: Joins multiple strings with `__OMNIROUTE_I18N_SEPARATOR__` delimiters to minimize API calls (max 1800 chars per request) -- **In-memory cache**: Avoids redundant API calls for repeated strings within a session -- **Retry logic**: Exponential backoff (up to 5 attempts with 300ms × attempt delay) for 429/5xx errors -- **Timeout**: 20 seconds per request -- **Skip existing**: If target file already exists, it is NOT overwritten - -**Important behaviors:** - -- `docs/i18n/README.md` is **regenerated** each run — it's an auto-generated index of all docs -- Root `README.{code}.md` files are only created if they don't exist (skips locales in `EXISTING_README_CODES`) -- Language bars (`🌐 **Languages:** ...`) are automatically inserted/updated in all translated docs - -### i18n_autotranslate.py (LLM-based) - -**Secondary translator** — uses any OpenAI-compatible LLM API (including OmniRoute itself) to translate existing `docs/i18n/` markdown files. Best for polishing or re-translating docs with better quality than Google Translate. - -```bash -python3 scripts/i18n_autotranslate.py \ - --api-url http://localhost:20128/v1 \ - --api-key sk-your-key \ - --model gpt-4o -``` - -**Features:** - -- Scans `docs/i18n/` markdown files for English paragraphs -- Skips code blocks, tables, and already-translated content -- Sends paragraphs to LLM with technical translation system prompt -- Supports all 30 languages - -## Validation & QA - -### validate_translation.py - -**Translation validator** — compares any locale JSON against `en.json` and reports issues. - -```bash -# Quick check (counts only) -python3 scripts/validate_translation.py quick -l cs -# Output: -# Missing: 0 -# Untranslated: 0 -# Ignored (UNTRANSLATABLE_KEYS): 236 - -# Detailed diff by category -python3 scripts/validate_translation.py diff common -l cs -python3 scripts/validate_translation.py diff settings -l cs - -# Export to CSV -python3 scripts/validate_translation.py csv -l cs > report.csv - -# Export to Markdown -python3 scripts/validate_translation.py md -l cs > report.md - -# Full report (default) -python3 scripts/validate_translation.py -l cs -``` - -**Detects:** - -- **Missing keys** — keys in `en.json` but not in locale file -- **Extra keys** — keys in locale file but not in `en.json` -- **Untranslated keys** — keys where locale value equals English source (excluding allowlist) -- **Placeholder mismatches** — ICU placeholders that don't match between source and translation - -**Exit codes:** -| Code | Meaning | -|------|---------| -| 0 | OK | -| 1 | Generic error | -| 2 | Missing strings (hard error) | -| 3 | Untranslated warning (soft) | - -**Environment:** Set `TRANSLATION_LANG=cs` or use `-l cs` flag. - -### check_translations.py - -**Code-to-JSON key checker** — scans `src/**/*.tsx` and `src/**/*.ts` for `useTranslations()` calls and verifies all referenced keys exist in `en.json`. - -```bash -# Basic check -python3 scripts/check_translations.py - -# Verbose output -python3 scripts/check_translations.py --verbose - -# Auto-fix (adds missing keys to en.json) -python3 scripts/check_translations.py --fix -``` - -### generate-qa-checklist.mjs - -**Static analysis QA** — scans Next.js page files for i18n risk metrics and generates a Markdown report. - -```bash -node scripts/i18n/generate-qa-checklist.mjs -``` - -**Checks:** - -- Fixed-width class usage (overflow risk) -- Directional left/right classes (RTL risk) -- Clipping-prone patterns -- Locale parity (missing/extra keys vs `en.json`) -- README language selector bars in priority locales (`es`, `fr`, `de`, `ja`, `ar`) - -**Output:** `docs/reports/i18n-qa-checklist-{date}.md` - -### run-visual-qa.mjs - -**Visual QA via Playwright** — takes screenshots of all dashboard routes in multiple locales and viewports, then evaluates page health. - -```bash -# Default: es, fr, de, ja, ar on localhost:20128 -node scripts/i18n/run-visual-qa.mjs - -# Custom base URL and locales -QA_BASE_URL=http://staging.example.com QA_LOCALES=de,fr node scripts/i18n/run-visual-qa.mjs - -# Custom routes -QA_ROUTES=/dashboard/settings,/dashboard/providers node scripts/i18n/run-visual-qa.mjs -``` - -**Detects:** - -- Text overflow -- Element clipping -- RTL layout mismatches - -**Output:** `docs/reports/i18n-visual-qa-{date}.md` + JSON report - -## Managing Untranslatable Keys - -### untranslatable-keys.json - -**File:** `scripts/i18n/untranslatable-keys.json` - -Allowlist of keys that should remain identical to English source. Used by `validate_translation.py` to avoid false-positive "untranslated" warnings. - -```json -{ - "description": "Keys that should remain untranslated...", - "keys": [ - "common.model", - "common.oauth", - "health.cpu", - ... - ] -} -``` - -**What belongs here:** - -- Brand/product names: `landing.brandName`, `common.social-github` -- Technical terms/acronyms: `health.cpu`, `mcpDashboard.pid`, `settings.ai` -- ICU/format strings: `apiManager.modelsCount`, `health.millisecondsShort` -- Placeholder values: `providers.openaiBaseUrlPlaceholder`, `cliTools.baseUrlPlaceholder` -- Protocol names: `common.http`, `common.oauth`, `providers.oauth2Label` -- Navigation sections: `sidebar.primarySection`, `sidebar.cliSection` - -**To add a key:** Edit the `keys` array in `scripts/i18n/untranslatable-keys.json` and re-run validation. - -## CI Integration - -### GitHub Actions (`.github/workflows/ci.yml`) - -The CI pipeline validates all locales on every push and PR: - -1. **`i18n-matrix` job** — dynamically discovers all locale files (excluding `en.json`) -2. **`i18n` job** — runs `validate_translation.py quick -l ''` for each locale in parallel -3. **`ci-summary` job** — aggregates results into a dashboard summary - -```yaml -# i18n-matrix: discovers languages -LANGS=$(ls src/i18n/messages/*.json | xargs -n1 basename | sed 's/.json$//' | grep -v '^en$') - -# i18n: validates each language -python3 scripts/validate_translation.py quick -l '${{ matrix.lang }}' -``` - -**Dashboard output:** - -``` -## 🌍 Translations -| Metric | Value | -|--------|------| -| Languages checked | 30 | -| Total untranslated | 0 | - -✅ All translations complete -``` - -## File Structure - -``` -src/i18n/ -├── config.ts # Locale definitions (30 locales, RTL config) -├── request.ts # Runtime locale resolution -└── messages/ - ├── en.json # Source of truth (~2800 keys) - ├── cs.json # Czech translation - ├── de.json # German translation - └── ... # 30 locale files total - -scripts/ -├── i18n/ -│ ├── generate-multilang.mjs # Auto-translation engine (Google Translate, 888 lines) -│ ├── generate-qa-checklist.mjs # Static analysis QA -│ ├── run-visual-qa.mjs # Playwright visual QA -│ └── untranslatable-keys.json # Allowlist for validation (236 keys) -├── validate_translation.py # Translation validator -├── check_translations.py # Code-to-JSON key checker -└── i18n_autotranslate.py # LLM-based doc translator - -.github/workflows/ -└── ci.yml # i18n validation in CI matrix - -docs/ -├── I18N.md # This file — i18n toolchain documentation -├── i18n/ -│ ├── README.md # Auto-generated language index -│ ├── cs/ # Czech docs -│ │ └── docs/ -│ │ ├── I18N.md # Czech translation of this file -│ │ └── ... -│ ├── de/ # German docs -│ └── ... # 30 locale directories -└── reports/ - ├── i18n-qa-checklist-*.md # Static analysis reports - └── i18n-visual-qa-*.md # Visual QA reports -``` - -## Best Practices - -### When Editing Translations - -1. **Always edit `en.json` first** — it's the source of truth -2. **Run `generate-multilang.mjs messages`** to propagate new keys to all locales -3. **Review auto-translations** — Google Translate is a starting point, not final -4. **Validate before committing** — `python3 scripts/validate_translation.py quick -l ` -5. **Update `untranslatable-keys.json`** if a key should remain in English - -### Placeholder Safety - -- ICU placeholders (`{count}`, `{value}`, `{total}`, `{seconds}`) must be preserved exactly -- Plural formats (`{count, plural, one {# model} other {# models}}`) must maintain structure -- The validator detects placeholder mismatches automatically - -### Adding New Translation Keys in Code - -```tsx -// Use namespaced keys -const t = useTranslations("settings"); -t("cacheSettings"); // maps to settings.cacheSettings in JSON - -// Run check_translations.py to verify keys exist -python3 scripts/check_translations.py --verbose -``` - -### RTL Considerations - -- Arabic (`ar`) and Hebrew (`he`) are RTL locales -- Avoid hardcoded `left`/`right` CSS — use `start`/`end` logical properties -- Visual QA catches RTL layout mismatches via `run-visual-qa.mjs` - -## Known Issues & History - -### `in.json` → `hi.json` Fix - -The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This created an orphaned `in.json` duplicate of `hi.json`. Fixed by changing `code: "in"` to `code: "hi"` in `generate-multilang.mjs` and removing the orphaned file. - -### `docs/i18n/README.md` Is Auto-Generated - -The `docs/i18n/README.md` file is completely regenerated by `generate-multilang.mjs docs`. Any manual edits will be lost. Use `docs/guides/I18N.md` (this file) for hand-written documentation that should persist. - -### External Untranslatable Keys List - -The `untranslatable-keys.json` allowlist was moved from an inline Python set in `validate_translation.py` to an external JSON file for easier maintenance. The validator loads it at runtime. - -### `generate-multilang.mjs` Hindi Code Fix - -The generator originally used `code: "in"` (deprecated Google Translate code) for Hindi instead of the correct ISO 639-1 `hi`. This was introduced in upstream commit `952b0b22c` by `diegosouzapw`. Fixed by changing `code: "in"` to `code: "hi"` in the `LOCALE_SPECS` array and removing the orphaned `in.json` file. - -### `validate_translation.py` Ignored Count Output - -The `quick` check now displays the count of ignored keys from `untranslatable-keys.json`: - -``` -Missing: 0 -Untranslated: 0 -Ignored (UNTRANSLATABLE_KEYS): 236 -``` +1. Kullanıcı dili seçer → `NEXT_LOCALE` çerezi ayarlanır +2. `src/i18n/request.ts` yerel ayarı çözer: çerez → `Accept-Language` başlığı → geri dönüş `en` +3. Dinamik içe aktarma `messages/{locale}.json` dosyasını yükler +4. Bileşenler `useTranslations("namespace")` ve `t("key")` kullanır diff --git a/docs/i18n/tr/docs/guides/TROUBLESHOOTING.md b/docs/i18n/tr/docs/guides/TROUBLESHOOTING.md index b6181952e5..eb78f9e45e 100644 --- a/docs/i18n/tr/docs/guides/TROUBLESHOOTING.md +++ b/docs/i18n/tr/docs/guides/TROUBLESHOOTING.md @@ -1,340 +1,48 @@ -# Troubleshooting (Türkçe) +--- +title: "Sorun Giderme" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/TROUBLESHOOTING.md) · 🇸🇦 [ar](../../ar/docs/TROUBLESHOOTING.md) · 🇧🇬 [bg](../../bg/docs/TROUBLESHOOTING.md) · 🇧🇩 [bn](../../bn/docs/TROUBLESHOOTING.md) · 🇨🇿 [cs](../../cs/docs/TROUBLESHOOTING.md) · 🇩🇰 [da](../../da/docs/TROUBLESHOOTING.md) · 🇩🇪 [de](../../de/docs/TROUBLESHOOTING.md) · 🇪🇸 [es](../../es/docs/TROUBLESHOOTING.md) · 🇮🇷 [fa](../../fa/docs/TROUBLESHOOTING.md) · 🇫🇮 [fi](../../fi/docs/TROUBLESHOOTING.md) · 🇫🇷 [fr](../../fr/docs/TROUBLESHOOTING.md) · 🇮🇳 [gu](../../gu/docs/TROUBLESHOOTING.md) · 🇮🇱 [he](../../he/docs/TROUBLESHOOTING.md) · 🇮🇳 [hi](../../hi/docs/TROUBLESHOOTING.md) · 🇭🇺 [hu](../../hu/docs/TROUBLESHOOTING.md) · 🇮🇩 [id](../../id/docs/TROUBLESHOOTING.md) · 🇮🇹 [it](../../it/docs/TROUBLESHOOTING.md) · 🇯🇵 [ja](../../ja/docs/TROUBLESHOOTING.md) · 🇰🇷 [ko](../../ko/docs/TROUBLESHOOTING.md) · 🇮🇳 [mr](../../mr/docs/TROUBLESHOOTING.md) · 🇲🇾 [ms](../../ms/docs/TROUBLESHOOTING.md) · 🇳🇱 [nl](../../nl/docs/TROUBLESHOOTING.md) · 🇳🇴 [no](../../no/docs/TROUBLESHOOTING.md) · 🇵🇭 [phi](../../phi/docs/TROUBLESHOOTING.md) · 🇵🇱 [pl](../../pl/docs/TROUBLESHOOTING.md) · 🇵🇹 [pt](../../pt/docs/TROUBLESHOOTING.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/TROUBLESHOOTING.md) · 🇷🇴 [ro](../../ro/docs/TROUBLESHOOTING.md) · 🇷🇺 [ru](../../ru/docs/TROUBLESHOOTING.md) · 🇸🇰 [sk](../../sk/docs/TROUBLESHOOTING.md) · 🇸🇪 [sv](../../sv/docs/TROUBLESHOOTING.md) · 🇰🇪 [sw](../../sw/docs/TROUBLESHOOTING.md) · 🇮🇳 [ta](../../ta/docs/TROUBLESHOOTING.md) · 🇮🇳 [te](../../te/docs/TROUBLESHOOTING.md) · 🇹🇭 [th](../../th/docs/TROUBLESHOOTING.md) · 🇹🇷 [tr](../../tr/docs/TROUBLESHOOTING.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/TROUBLESHOOTING.md) · 🇵🇰 [ur](../../ur/docs/TROUBLESHOOTING.md) · 🇻🇳 [vi](../../vi/docs/TROUBLESHOOTING.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/TROUBLESHOOTING.md) +# Sorun Giderme (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/guides/TROUBLESHOOTING.md) · 🇸🇦 [ar](../../ar/docs/guides/TROUBLESHOOTING.md) · 🇧🇬 [bg](../../bg/docs/guides/TROUBLESHOOTING.md) · 🇧🇩 [bn](../../bn/docs/guides/TROUBLESHOOTING.md) · 🇨🇿 [cs](../../cs/docs/guides/TROUBLESHOOTING.md) · 🇩🇰 [da](../../da/docs/guides/TROUBLESHOOTING.md) · 🇩🇪 [de](../../de/docs/guides/TROUBLESHOOTING.md) · 🇪🇸 [es](../../es/docs/guides/TROUBLESHOOTING.md) · 🇮🇷 [fa](../../fa/docs/guides/TROUBLESHOOTING.md) · 🇫🇮 [fi](../../fi/docs/guides/TROUBLESHOOTING.md) · 🇫🇷 [fr](../../fr/docs/guides/TROUBLESHOOTING.md) · 🇮🇳 [gu](../../gu/docs/guides/TROUBLESHOOTING.md) · 🇮🇱 [he](../../he/docs/guides/TROUBLESHOOTING.md) · 🇮🇳 [hi](../../hi/docs/guides/TROUBLESHOOTING.md) · 🇭🇺 [hu](../../hu/docs/guides/TROUBLESHOOTING.md) · 🇮🇩 [id](../../id/docs/guides/TROUBLESHOOTING.md) · 🇮🇹 [it](../../it/docs/guides/TROUBLESHOOTING.md) · 🇯🇵 [ja](../../ja/docs/guides/TROUBLESHOOTING.md) · 🇰🇷 [ko](../../ko/docs/guides/TROUBLESHOOTING.md) · 🇮🇳 [mr](../../mr/docs/guides/TROUBLESHOOTING.md) · 🇲🇾 [ms](../../ms/docs/guides/TROUBLESHOOTING.md) · 🇳🇱 [nl](../../nl/docs/guides/TROUBLESHOOTING.md) · 🇳🇴 [no](../../no/docs/guides/TROUBLESHOOTING.md) · 🇵🇭 [phi](../../phi/docs/guides/TROUBLESHOOTING.md) · 🇵🇱 [pl](../../pl/docs/guides/TROUBLESHOOTING.md) · 🇵🇹 [pt](../../pt/docs/guides/TROUBLESHOOTING.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/guides/TROUBLESHOOTING.md) · 🇷🇴 [ro](../../ro/docs/guides/TROUBLESHOOTING.md) · 🇷🇺 [ru](../../ru/docs/guides/TROUBLESHOOTING.md) · 🇸🇰 [sk](../../sk/docs/guides/TROUBLESHOOTING.md) · 🇸🇪 [sv](../../sv/docs/guides/TROUBLESHOOTING.md) · 🇰🇪 [sw](../../sw/docs/guides/TROUBLESHOOTING.md) · 🇮🇳 [ta](../../ta/docs/guides/TROUBLESHOOTING.md) · 🇮🇳 [te](../../te/docs/guides/TROUBLESHOOTING.md) · 🇹🇭 [th](../../th/docs/guides/TROUBLESHOOTING.md) · 🇹🇷 [tr](../../tr/docs/guides/TROUBLESHOOTING.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/guides/TROUBLESHOOTING.md) · 🇵🇰 [ur](../../ur/docs/guides/TROUBLESHOOTING.md) · 🇻🇳 [vi](../../vi/docs/guides/TROUBLESHOOTING.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/guides/TROUBLESHOOTING.md) --- -Common problems and solutions for OmniRoute. +OmniRoute için sık karşılaşılan sorunlar ve çözümleri. --- -## Quick Fixes +## Hızlı Başvuru -| Problem | Solution | -| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| First login not working | Set `INITIAL_PASSWORD` in `.env` (no hardcoded default) | -| Dashboard opens on wrong port | Set `PORT=20128` and `NEXT_PUBLIC_BASE_URL=http://localhost:20128` | -| No logs written to disk | Set `APP_LOG_TO_FILE=true` and verify call log capture is enabled | -| EACCES: permission denied | Set `DATA_DIR=/path/to/writable/dir` to override `~/.omniroute` | -| Routing strategy not saving | Update to v1.4.11+ (Zod schema fix for settings persistence) | -| Login crash / blank page | Check Node.js version — see [Node.js Compatibility](#nodejs-compatibility) below | -| `dlopen` / `slice is not valid mach-o file` (macOS) | Run `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` — see [macOS native module rebuild](#macos-native-module-rebuild) below | -| Proxy "fetch failed" | Ensure proxy config is set at the correct level — see [Proxy Issues](#proxy-issues) below | +**OmniRoute'ta yeni misiniz?** Buradan başlayın — sorunların %90'ını çözer: + +| Gördüğüm Durum | Ne Anlama Geliyor | Ne Yapılmalı | +| ------------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------- | +| "Bağlanamıyor" | OmniRoute çalışmıyor | `omniroute` veya `docker restart omniroute` çalıştırın | +| "Geçersiz API Anahtarı" | Anahtarınız yanlış veya süresi doldu| Sağlayıcının web sitesinden anahtarı yeniden kopyalayın | +| "Hız Sınırı Aşıldı" | Çok fazla istek gönderiyorsunuz | 1 dakika bekleyin veya otomatik geri dönüş için `model: "auto"` kullanın | +| "Kota Aşıldı" | Ücretsiz/ücretli kotanız bitti | Daha fazla sağlayıcı bağlayın veya ücretsiz sağlayıcıları kullanın | +| "Yavaş Yanıtlar" | Sağlayıcı meşgul veya uzakta | `model: "auto/fast"` kullanın veya daha hızlı bir sağlayıcı bağlayın (Groq, Cerebras) | +| "Yanlış Sağlayıcı Seçimi"| `auto` farklı bir sağlayıcı seçti | Bu normaldir! `auto` en iyisini seçer. Belirli bir sağlayıcıyı `model: "openai/gpt-4o"` ile zorlayın | +| "502 Bad Gateway" | Sağlayıcı çöktü | Bekleyip yeniden deneyin veya sağlayıcı değiştirmek için `model: "auto"` kullanın | +| "401 Unauthorized" | Kimlik bilgileriniz geçersiz | API anahtarınızı kontrol edin veya OAuth ile yeniden doğrulayın | +| "429 Too Many Requests" | Hız sınırına takıldı | 1 dakika bekleyin veya daha fazla sağlayıcı bağlayın | --- -## Node.js Compatibility - - - -### Login page crashes or shows "Module self-registration" error - -**Cause:** You are running a Node.js version outside OmniRoute's approved secure runtime floor. The most common case is running an older Node 20, 22, or 24 patch level that falls below the patched security floor OmniRoute requires. - -**Symptoms:** - -- Login page shows a blank screen or a server error -- Console shows `Error: Module did not self-register` or similar native binding errors -- The login page shows an **orange warning banner** with your Node version if the runtime is outside the supported secure policy - -**Fix:** - -1. Install a supported Node.js LTS release (recommended: Node.js 24.x): - ```bash - nvm install 24 - nvm use 24 - ``` -2. Verify your version: `node --version` should show `v24.0.0` or newer on the 24.x LTS line -3. Reinstall OmniRoute: `npm install -g omniroute` -4. Restart: `omniroute` - -> **Supported secure versions:** `>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`. Node.js 24.x LTS (Krypton) is fully supported. - -### macOS: `dlopen` / "slice is not valid mach-o file" - - - -**Cause:** After a global `npm install -g omniroute`, the `better-sqlite3` native binary inside the package may have been compiled for a different architecture or Node.js ABI than what is running locally. This is common on macOS (both Apple Silicon and Intel) when the pre-built binary does not match your environment. - -**Symptoms:** - -- Server fails immediately on startup with a `dlopen` error -- Error contains `slice is not valid mach-o file` -- Full example: - -``` -dlopen(/Users//.nvm/versions/node/v24.14.1/lib/node_modules/omniroute/app/node_modules/better-sqlite3/build/Release/better_sqlite3.node, 0x0001): tried: '...' (slice is not valid mach-o file) -``` - -**Fix — rebuild for your local environment (no Node.js downgrade required):** - -```bash -cd $(npm root -g)/omniroute/app -npm rebuild better-sqlite3 -omniroute -``` - -> **Note:** This recompiles the native binding against your local Node.js version and CPU architecture, resolving the binary mismatch. The officially supported range is **`>=20.20.2 <21`, `>=22.22.2 <23`, or `>=24.0.0 <25`** (`engines` field in `package.json`). Node.js 24.x LTS (Krypton) is fully supported with `better-sqlite3` v12.x. - ---- - -## Proxy Issues - - - -### Provider validation shows "fetch failed" - -**Cause:** The API key validation endpoint (`POST /api/providers/validate`) was previously bypassing proxy configuration, causing failures in environments that require proxy routing. - -**Fix (v3.5.5+):** This is now fixed. Provider validation routes through `runWithProxyContext`, honoring provider-level and global proxy settings automatically. - -### Token health check fails with "fetch failed" - -**Cause:** Background OAuth token refresh was not resolving proxy configuration per connection. - -**Fix (v3.5.5+):** The token health check scheduler now resolves proxy config per connection before attempting refresh. Update to v3.5.5+. - -### SOCKS5 proxy returns "invalid onRequestStart method" - -**Cause:** On Node.js 22, the undici@8 dispatcher is incompatible with Node's built-in `fetch()` implementation. - -**Fix (v3.5.5+):** OmniRoute now uses undici's own `fetch()` function when a proxy dispatcher is active, ensuring consistent behavior. Update to v3.5.5+. - ---- - -## Provider Issues - -### "Language model did not provide messages" - -**Cause:** Provider quota exhausted. - -**Fix:** - -1. Check dashboard quota tracker -2. Use a combo with fallback tiers -3. Switch to cheaper/free tier - -### Rate Limiting - -**Cause:** Subscription quota exhausted. - -**Fix:** - -- Add fallback: `cc/claude-opus-4-6 → glm/glm-4.7 → if/kimi-k2-thinking` -- Use GLM/MiniMax as cheap backup - -### OAuth Token Expired - -OmniRoute auto-refreshes tokens. If issues persist: - -1. Dashboard → Provider → Reconnect -2. Delete and re-add the provider connection - ---- - -## Cloud Issues - -### Cloud Sync Errors - -1. Verify `BASE_URL` points to your running instance (e.g., `http://localhost:20128`) -2. Verify `CLOUD_URL` points to your cloud endpoint (e.g., `https://omniroute.dev`) -3. Keep `NEXT_PUBLIC_*` values aligned with server-side values - -### Cloud `stream=false` Returns 500 - -**Symptom:** `Unexpected token 'd'...` on cloud endpoint for non-streaming calls. - -**Cause:** Upstream returns SSE payload while client expects JSON. - -**Workaround:** Use `stream=true` for cloud direct calls. Local runtime includes SSE→JSON fallback. - -### Cloud Says Connected but "Invalid API key" - -1. Create a fresh key from local dashboard (`/api/keys`) -2. Run cloud sync: Enable Cloud → Sync Now -3. Old/non-synced keys can still return `401` on cloud - ---- - -## Docker Issues - -### CLI Tool Shows Not Installed - -1. Check runtime fields: `curl http://localhost:20128/api/cli-tools/runtime/codex | jq` -2. For portable mode: use image target `runner-cli` (bundled CLIs) -3. For host mount mode: set `CLI_EXTRA_PATHS` and mount host bin directory as read-only -4. If `installed=true` and `runnable=false`: binary was found but failed healthcheck - -### Quick Runtime Validation - -```bash -curl -s http://localhost:20128/api/cli-tools/codex-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}' -curl -s http://localhost:20128/api/cli-tools/claude-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}' -curl -s http://localhost:20128/api/cli-tools/openclaw-settings | jq '{installed,runnable,commandPath,runtimeMode,reason}' -``` - ---- - -## Cost Issues - -### High Costs - -1. Check usage stats in Dashboard → Usage -2. Switch primary model to GLM/MiniMax -3. Set cost budgets per API key: Dashboard → API Keys → Budget - ---- - -## Debugging - -### Enable Log Files - -Set `APP_LOG_TO_FILE=true` in your `.env` file. Application logs are written under `logs/`. -Request artifacts are stored under `${DATA_DIR}/call_logs/` when the call log pipeline is -enabled in settings. - -### Check Provider Health - -```bash -# Health dashboard -http://localhost:20128/dashboard/health - -# API health check -curl http://localhost:20128/api/monitoring/health -``` - -### Runtime Storage - -- Main state: `${DATA_DIR}/storage.sqlite` (providers, combos, aliases, keys, settings) -- Usage: SQLite tables in `storage.sqlite` (`usage_history`, `call_logs`, `proxy_logs`) + optional `${DATA_DIR}/call_logs/` -- Application logs: `/logs/...` (when `APP_LOG_TO_FILE=true`) -- Call log artifacts: `${DATA_DIR}/call_logs/YYYY-MM-DD/...` when the call log pipeline is enabled - ---- - -## Circuit Breaker Issues - -### Provider stuck in OPEN state - -When a provider's circuit breaker is OPEN, requests are blocked until the cooldown expires. - -**Fix:** - -1. Go to **Dashboard → Settings → Resilience** -2. Check the circuit breaker card for the affected provider -3. Click **Reset All** to clear all breakers, or wait for the cooldown to expire -4. Verify the provider is actually available before resetting - -### Provider keeps tripping the circuit breaker - -If a provider repeatedly enters OPEN state: - -1. Check **Dashboard → Health → Provider Health** for the failure pattern -2. Go to **Settings → Resilience → Provider Profiles** and increase the failure threshold -3. Check if the provider has changed API limits or requires re-authentication -4. Review latency telemetry — high latency may cause timeout-based failures - ---- - -## Audio Transcription Issues - -### "Unsupported model" error - -- Ensure you're using the correct prefix: `deepgram/nova-3` or `assemblyai/best` -- Verify the provider is connected in **Dashboard → Providers** - -### Transcription returns empty or fails - -- Check supported audio formats: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm` -- Verify file size is within provider limits (typically < 25MB) -- Check provider API key validity in the provider card - ---- - -## Translator Debugging - -Use **Dashboard → Translator** to debug format translation issues: - -| Mode | When to Use | -| ---------------- | -------------------------------------------------------------------------------------------- | -| **Playground** | Compare input/output formats side by side — paste a failing request to see how it translates | -| **Chat Tester** | Send live messages and inspect the full request/response payload including headers | -| **Test Bench** | Run batch tests across format combinations to find which translations are broken | -| **Live Monitor** | Watch real-time request flow to catch intermittent translation issues | - -### Common format issues - -- **Thinking tags not appearing** — Check if the target provider supports thinking and the thinking budget setting -- **Tool calls dropping** — Some format translations may strip unsupported fields; verify in Playground mode -- **System prompt missing** — Claude and Gemini handle system prompts differently; check translation output -- **SDK returns raw string instead of object** — Fixed in v1.1.0: response sanitizer now strips non-standard fields (`x_groq`, `usage_breakdown`, etc.) that cause OpenAI SDK Pydantic validation failures -- **GLM/ERNIE rejects `system` role** — Fixed in v1.1.0: role normalizer automatically merges system messages into user messages for incompatible models -- **`developer` role not recognized** — Fixed in v1.1.0: automatically converted to `system` for non-OpenAI providers -- **`json_schema` not working with Gemini** — Fixed in v1.1.0: `response_format` is now converted to Gemini's `responseMimeType` + `responseSchema` - ---- - -## Resilience Settings - -### Auto rate-limit not triggering - -- Auto rate-limit only applies to API key providers (not OAuth/subscription) -- Verify **Settings → Resilience → Provider Profiles** has auto-rate-limit enabled -- Check if the provider returns `429` status codes or `Retry-After` headers - -### Tuning exponential backoff - -Provider profiles support these settings: - -- **Base delay** — Initial wait time after first failure (default: 1s) -- **Max delay** — Maximum wait time cap (default: 30s) -- **Multiplier** — How much to increase delay per consecutive failure (default: 2x) - -### Anti-thundering herd - -When many concurrent requests hit a rate-limited provider, OmniRoute uses mutex + auto rate-limiting to serialize requests and prevent cascading failures. This is automatic for API key providers. - ---- - -## Optional RAG / LLM failure taxonomy (16 problems) - -Some OmniRoute users place the gateway in front of RAG or agent stacks. In those setups it is common to see a strange pattern: OmniRoute looks healthy (providers up, routing profiles ok, no rate limit alerts) but the final answer is still wrong. - -In practice these incidents usually come from the downstream RAG pipeline, not from the gateway itself. - -If you want a shared vocabulary to describe those failures you can use the WFGY ProblemMap, an external MIT license text resource that defines sixteen recurring RAG / LLM failure patterns. At a high level it covers: - -- retrieval drift and broken context boundaries -- empty or stale indexes and vector stores -- embedding versus semantic mismatch -- prompt assembly and context window issues -- logic collapse and overconfident answers -- long chain and agent coordination failures -- multi agent memory and role drift -- deployment and bootstrap ordering problems - -The idea is simple: - -1. When you investigate a bad response, capture: - - user task and request - - route or provider combo in OmniRoute - - any RAG context used downstream (retrieved documents, tool calls, etc) -2. Map the incident to one or two WFGY ProblemMap numbers (`No.1` … `No.16`). -3. Store the number in your own dashboard, runbook, or incident tracker next to the OmniRoute logs. -4. Use the corresponding WFGY page to decide whether you need to change your RAG stack, retriever, or routing strategy. - -Full text and concrete recipes live here (MIT license, text only): - -[WFGY ProblemMap README](https://github.com/onestardao/WFGY/blob/main/ProblemMap/README.md) - -You can ignore this section if you do not run RAG or agent pipelines behind OmniRoute. - ---- - -## Still Stuck? - -- **GitHub Issues**: [github.com/diegosouzapw/OmniRoute/issues](https://github.com/diegosouzapw/OmniRoute/issues) -- **Architecture**: See [`docs/architecture/ARCHITECTURE.md`](ARCHITECTURE.md) for internal details -- **API Reference**: See [`docs/reference/API_REFERENCE.md`](API_REFERENCE.md) for all endpoints -- **Health Dashboard**: Check **Dashboard → Health** for real-time system status -- **Translator**: Use **Dashboard → Translator** to debug format issues +## Hızlı Düzeltmeler + +| Sorun | Çözüm | +| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| İlk giriş çalışmıyor | `.env` dosyasında `INITIAL_PASSWORD` ayarlayın (sabit kodlanmış varsayılan yoktur) | +| Pano yanlış portta açılıyor | `PORT=20128` ve `NEXT_PUBLIC_BASE_URL=http://localhost:20128` ayarlayın | +| Diske günlük yazılmıyor | `APP_LOG_TO_FILE=true` ayarlayın ve çağrı günlüğü kaydının etkin olduğunu doğrulayın | +| EACCES: permission denied | `~/.omniroute` dizinini geçersiz kılmak için `DATA_DIR=/yazilabilir/dizin/yolu` ayarlayın | +| Yönlendirme stratejisi kaydedilmiyor | En son v3.x sürümüne güncelleyin | +| Giriş çökmesi / boş sayfa | Node.js sürümünü kontrol edin (Node.js `>=22.22.2 <23` veya `>=24.0.0 <27` desteklenir) | +| `dlopen` / `slice is not valid mach-o file` (macOS) | `cd $(npm root -g)/omniroute/app && npm rebuild better-sqlite3 && omniroute` çalıştırın | +| Proxy "fetch failed" | Proxy yapılandırmasının doğru düzeyde ayarlandığından emin olun | +| Docker `curl: (56) Recv failure: Connection reset by peer` | Docker port bağlamanız IPv6'ya düşüyor olabilir. IPv4'ü zorlamak için `-p 127.0.0.1:20128:20128` kullanın veya `curl -4` ile test edin | +| Antivirüs `README.md` dosyasını karantinaya alıyor | Yanlış pozitif (false positive) alarmdır, güvenle geri yükleyebilirsiniz | diff --git a/docs/i18n/tr/docs/guides/UNINSTALL.md b/docs/i18n/tr/docs/guides/UNINSTALL.md index 61f3bd0d69..6fe5ce0392 100644 --- a/docs/i18n/tr/docs/guides/UNINSTALL.md +++ b/docs/i18n/tr/docs/guides/UNINSTALL.md @@ -1,157 +1,94 @@ -# OmniRoute — Uninstall Guide (Türkçe) +--- +title: "OmniRoute — Kaldırma Kılavuzu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/UNINSTALL.md) · 🇸🇦 [ar](../../ar/docs/UNINSTALL.md) · 🇧🇬 [bg](../../bg/docs/UNINSTALL.md) · 🇧🇩 [bn](../../bn/docs/UNINSTALL.md) · 🇨🇿 [cs](../../cs/docs/UNINSTALL.md) · 🇩🇰 [da](../../da/docs/UNINSTALL.md) · 🇩🇪 [de](../../de/docs/UNINSTALL.md) · 🇪🇸 [es](../../es/docs/UNINSTALL.md) · 🇮🇷 [fa](../../fa/docs/UNINSTALL.md) · 🇫🇮 [fi](../../fi/docs/UNINSTALL.md) · 🇫🇷 [fr](../../fr/docs/UNINSTALL.md) · 🇮🇳 [gu](../../gu/docs/UNINSTALL.md) · 🇮🇱 [he](../../he/docs/UNINSTALL.md) · 🇮🇳 [hi](../../hi/docs/UNINSTALL.md) · 🇭🇺 [hu](../../hu/docs/UNINSTALL.md) · 🇮🇩 [id](../../id/docs/UNINSTALL.md) · 🇮🇹 [it](../../it/docs/UNINSTALL.md) · 🇯🇵 [ja](../../ja/docs/UNINSTALL.md) · 🇰🇷 [ko](../../ko/docs/UNINSTALL.md) · 🇮🇳 [mr](../../mr/docs/UNINSTALL.md) · 🇲🇾 [ms](../../ms/docs/UNINSTALL.md) · 🇳🇱 [nl](../../nl/docs/UNINSTALL.md) · 🇳🇴 [no](../../no/docs/UNINSTALL.md) · 🇵🇭 [phi](../../phi/docs/UNINSTALL.md) · 🇵🇱 [pl](../../pl/docs/UNINSTALL.md) · 🇵🇹 [pt](../../pt/docs/UNINSTALL.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/UNINSTALL.md) · 🇷🇴 [ro](../../ro/docs/UNINSTALL.md) · 🇷🇺 [ru](../../ru/docs/UNINSTALL.md) · 🇸🇰 [sk](../../sk/docs/UNINSTALL.md) · 🇸🇪 [sv](../../sv/docs/UNINSTALL.md) · 🇰🇪 [sw](../../sw/docs/UNINSTALL.md) · 🇮🇳 [ta](../../ta/docs/UNINSTALL.md) · 🇮🇳 [te](../../te/docs/UNINSTALL.md) · 🇹🇭 [th](../../th/docs/UNINSTALL.md) · 🇹🇷 [tr](../../tr/docs/UNINSTALL.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/UNINSTALL.md) · 🇵🇰 [ur](../../ur/docs/UNINSTALL.md) · 🇻🇳 [vi](../../vi/docs/UNINSTALL.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/UNINSTALL.md) +# OmniRoute — Kaldırma Kılavuzu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/guides/UNINSTALL.md) · 🇸🇦 [ar](../../ar/docs/guides/UNINSTALL.md) · 🇧🇬 [bg](../../bg/docs/guides/UNINSTALL.md) · 🇧🇩 [bn](../../bn/docs/guides/UNINSTALL.md) · 🇨🇿 [cs](../../cs/docs/guides/UNINSTALL.md) · 🇩🇰 [da](../../da/docs/guides/UNINSTALL.md) · 🇩🇪 [de](../../de/docs/guides/UNINSTALL.md) · 🇪🇸 [es](../../es/docs/guides/UNINSTALL.md) · 🇮🇷 [fa](../../fa/docs/guides/UNINSTALL.md) · 🇫🇮 [fi](../../fi/docs/guides/UNINSTALL.md) · 🇫🇷 [fr](../../fr/docs/guides/UNINSTALL.md) · 🇮🇳 [gu](../../gu/docs/guides/UNINSTALL.md) · 🇮🇱 [he](../../he/docs/guides/UNINSTALL.md) · 🇮🇳 [hi](../../hi/docs/guides/UNINSTALL.md) · 🇭🇺 [hu](../../hu/docs/guides/UNINSTALL.md) · 🇮🇩 [id](../../id/docs/guides/UNINSTALL.md) · 🇮🇹 [it](../../it/docs/guides/UNINSTALL.md) · 🇯🇵 [ja](../../ja/docs/guides/UNINSTALL.md) · 🇰🇷 [ko](../../ko/docs/guides/UNINSTALL.md) · 🇮🇳 [mr](../../mr/docs/guides/UNINSTALL.md) · 🇲🇾 [ms](../../ms/docs/guides/UNINSTALL.md) · 🇳🇱 [nl](../../nl/docs/guides/UNINSTALL.md) · 🇳🇴 [no](../../no/docs/guides/UNINSTALL.md) · 🇵🇭 [phi](../../phi/docs/guides/UNINSTALL.md) · 🇵🇱 [pl](../../pl/docs/guides/UNINSTALL.md) · 🇵🇹 [pt](../../pt/docs/guides/UNINSTALL.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/guides/UNINSTALL.md) · 🇷🇴 [ro](../../ro/docs/guides/UNINSTALL.md) · 🇷🇺 [ru](../../ru/docs/guides/UNINSTALL.md) · 🇸🇰 [sk](../../sk/docs/guides/UNINSTALL.md) · 🇸🇪 [sv](../../sv/docs/guides/UNINSTALL.md) · 🇰🇪 [sw](../../sw/docs/guides/UNINSTALL.md) · 🇮🇳 [ta](../../ta/docs/guides/I18N.md) · 🇮🇳 [te](../../te/docs/guides/UNINSTALL.md) · 🇹🇭 [th](../../th/docs/guides/UNINSTALL.md) · 🇹🇷 [tr](../../tr/docs/guides/UNINSTALL.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/guides/UNINSTALL.md) · 🇵🇰 [ur](../../ur/docs/guides/UNINSTALL.md) · 🇻🇳 [vi](../../vi/docs/guides/UNINSTALL.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/guides/UNINSTALL.md) --- -This guide covers how to cleanly remove OmniRoute from your system. +Bu kılavuz, OmniRoute'u sisteminizden nasıl temiz bir şekilde kaldıracağınızı kapsar. --- -## Quick Uninstall (v3.6.2+) +## Hızlı Kaldırma (v3.6.2+) -OmniRoute provides two built-in scripts for clean removal: +OmniRoute temiz kaldırma için iki yerleşik betik sunar: -### Keep Your Data +### Verilerinizi Koruyarak Kaldırma ```bash npm run uninstall ``` -This removes the OmniRoute application but **preserves** your database, configurations, API keys, and provider settings in `~/.omniroute/`. Use this if you plan to reinstall later and want to keep your setup. +Bu, OmniRoute uygulamasını kaldırır ancak `~/.omniroute/` içindeki veritabanınızı, yapılandırmalarınızı, API anahtarlarınızı ve sağlayıcı ayarlarınızı **korur**. Daha sonra yeniden yüklemeyi planlıyorsanız ve kurulumunuzu saklamak istiyorsanız bunu kullanın. -### Full Removal +### Tam Kaldırma (Tüm Verileri Sil) ```bash npm run uninstall:full ``` -This removes the application **and permanently erases** all data: +Bu, uygulamayı kaldırır **ve tüm verileri kalıcı olarak siler**: -- Database (`storage.sqlite`) -- Provider configurations and API keys -- Backup files -- Log files -- All files in the `~/.omniroute/` directory +- Veritabanı (`storage.sqlite`) +- Sağlayıcı yapılandırmaları ve API anahtarları +- Yedekleme dosyaları +- Günlük dosyaları +- `~/.omniroute/` dizinindeki tüm dosyalar -> ⚠️ **Warning:** `npm run uninstall:full` is irreversible. All your provider connections, combos, API keys, and usage history will be permanently deleted. +> ⚠️ **Uyarı:** `npm run uninstall:full` işlemi geri alınamaz. Tüm sağlayıcı bağlantılarınız, kombolarınız, API anahtarlarınız ve kullanım geçmişiniz kalıcı olarak silinir. --- -## Manual Uninstall +## Manuel Kaldırma -### NPM Global Install +### NPM Global Kurulumu ```bash -# Remove the global package +# Global paketi kaldırın npm uninstall -g omniroute -# (Optional) Remove data directory -rm -rf ~/.omniroute -``` - -### pnpm Global Install - -```bash -pnpm uninstall -g omniroute +# (İsteğe bağlı) Veri dizinini silin rm -rf ~/.omniroute ``` ### Docker ```bash -# Stop and remove the container +# Konteyneri durdurun ve silin docker stop omniroute docker rm omniroute -# Remove the volume (deletes all data) +# Hacmi kaldırın (tüm verileri siler) docker volume rm omniroute-data -# (Optional) Remove the image +# (İsteğe bağlı) İmajı silin docker rmi diegosouzapw/omniroute:latest ``` ### Docker Compose ```bash -# Stop and remove containers +# Konteynerleri durdurun ve kaldırın docker compose down -# Also remove volumes (deletes all data) +# Hacimleri de kaldırın (tüm verileri siler) docker compose down -v ``` -### Electron Desktop App - -**Windows:** - -- Open `Settings → Apps → OmniRoute → Uninstall` -- Or run the NSIS uninstaller from the install directory +### Electron Masaüstü Uygulaması **macOS:** +- `OmniRoute.app` uygulamasını `/Applications` dizininden Çöp Sepetine sürükleyin +- Verileri silin: `rm -rf ~/Library/Application Support/omniroute` -- Drag `OmniRoute.app` from `/Applications` to Trash -- Remove data: `rm -rf ~/Library/Application Support/omniroute` +**Windows:** +- `Ayarlar → Uygulamalar → OmniRoute → Kaldır` **Linux:** - -- Remove the AppImage file -- Remove data: `rm -rf ~/.omniroute` - -### Source Install (git clone) - -```bash -# Remove the cloned directory -rm -rf /path/to/omniroute - -# (Optional) Remove data directory -rm -rf ~/.omniroute -``` - ---- - -## Data Directories - -OmniRoute stores data in the following locations by default: - -| Platform | Default Path | Override | -| ------------- | ----------------------------- | ------------------------- | -| Linux | `~/.omniroute/` | `DATA_DIR` env var | -| macOS | `~/.omniroute/` | `DATA_DIR` env var | -| Windows | `%APPDATA%/omniroute/` | `DATA_DIR` env var | -| Docker | `/app/data/` (mounted volume) | `DATA_DIR` env var | -| XDG-compliant | `$XDG_CONFIG_HOME/omniroute/` | `XDG_CONFIG_HOME` env var | - -### Files in the data directory - -| File/Directory | Description | -| -------------------- | ------------------------------------------------- | -| `storage.sqlite` | Main database (providers, combos, settings, keys) | -| `storage.sqlite-wal` | SQLite write-ahead log (temporary) | -| `storage.sqlite-shm` | SQLite shared memory (temporary) | -| `call_logs/` | Request payload archives | -| `backups/` | Automatic database backups | -| `log.txt` | Legacy request log (optional) | - ---- - -## Verify Complete Removal - -After uninstalling, verify there are no remaining files: - -```bash -# Check for global npm package -npm list -g omniroute 2>/dev/null - -# Check for data directory -ls -la ~/.omniroute/ 2>/dev/null - -# Check for running processes -pgrep -f omniroute -``` - -If any process is still running, stop it: - -```bash -pkill -f omniroute -``` +- AppImage veya paket yöneticisi üzerinden kaldırın +- Verileri silin: `rm -rf ~/.config/omniroute` diff --git a/docs/i18n/tr/docs/guides/USER_GUIDE.md b/docs/i18n/tr/docs/guides/USER_GUIDE.md index 594eea3b69..28d92e9f59 100644 --- a/docs/i18n/tr/docs/guides/USER_GUIDE.md +++ b/docs/i18n/tr/docs/guides/USER_GUIDE.md @@ -1,945 +1,120 @@ -# User Guide (Türkçe) +--- +title: "Kullanıcı Kılavuzu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/USER_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/USER_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/USER_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/USER_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/USER_GUIDE.md) · 🇩🇰 [da](../../da/docs/USER_GUIDE.md) · 🇩🇪 [de](../../de/docs/USER_GUIDE.md) · 🇪🇸 [es](../../es/docs/USER_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/USER_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/USER_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/USER_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/USER_GUIDE.md) · 🇮🇱 [he](../../he/docs/USER_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/USER_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/USER_GUIDE.md) · 🇮🇩 [id](../../id/docs/USER_GUIDE.md) · 🇮🇹 [it](../../it/docs/USER_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/USER_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/USER_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/USER_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/USER_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/USER_GUIDE.md) · 🇳🇴 [no](../../no/docs/USER_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/USER_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/USER_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/USER_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/USER_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/USER_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/USER_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/USER_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/USER_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/USER_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/USER_GUIDE.md) · 🇮🇳 [te](../../te/docs/USER_GUIDE.md) · 🇹🇭 [th](../../th/docs/USER_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/USER_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/USER_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/USER_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/USER_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/USER_GUIDE.md) +# Kullanıcı Kılavuzu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/guides/USER_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/guides/USER_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/guides/USER_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/guides/USER_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/guides/USER_GUIDE.md) · 🇩🇰 [da](../../da/docs/guides/USER_GUIDE.md) · 🇩🇪 [de](../../de/docs/guides/USER_GUIDE.md) · 🇪🇸 [es](../../es/docs/guides/USER_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/guides/USER_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/guides/USER_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/guides/USER_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/guides/USER_GUIDE.md) · 🇮🇱 [he](../../he/docs/guides/USER_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/guides/USER_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/guides/USER_GUIDE.md) · 🇮🇩 [id](../../id/docs/guides/USER_GUIDE.md) · 🇮🇹 [it](../../it/docs/guides/USER_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/guides/USER_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/guides/USER_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/guides/USER_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/guides/USER_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/guides/USER_GUIDE.md) · 🇳🇴 [no](../../no/docs/guides/USER_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/guides/USER_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/guides/USER_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/guides/USER_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/guides/USER_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/guides/USER_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/guides/USER_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/guides/USER_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/guides/USER_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/guides/USER_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/guides/USER_GUIDE.md) · 🇮🇳 [te](../../te/docs/guides/USER_GUIDE.md) · 🇹🇭 [th](../../th/docs/guides/USER_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/guides/USER_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/guides/USER_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/guides/USER_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/guides/USER_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/guides/USER_GUIDE.md) --- -Complete guide for configuring providers, creating combos, integrating CLI tools, and deploying OmniRoute. +Sağlayıcıları yapılandırma, kombolar oluşturma, CLI araçlarını entegre etme ve OmniRoute'u dağıtma konusunda eksiksiz kılavuz. --- -## Table of Contents +## İçindekiler -- [Pricing at a Glance](#-pricing-at-a-glance) -- [Use Cases](#-use-cases) -- [Provider Setup](#-provider-setup) -- [CLI Integration](#-cli-integration) -- [Deployment](#-deployment) -- [Available Models](#-available-models) -- [Advanced Features](#-advanced-features) +- [Bir Bakışta Fiyatlandırma](#-bir-bakışta-fiyatlandırma) +- [Kullanım Senaryoları](#-kullanım-senaryoları) +- [Sağlayıcı Kurulumu](#-sağlayıcı-kurulumu) +- [CLI Entegrasyonu](#-cli-entegrasyonu) +- [Dağıtım](#-dağıtım) +- [Kullanılabilir Modeller](#-kullanılabilir-modeller) +- [Gelişmiş Özellikler](#-gelişmiş-özellikler) +- [Otomatik Yönlendirme (Sıfır Yapılandırma)](#-otomatik-yönlendirme-sıfır-yapılandırma) +- [MCP ve A2A Entegrasyonu](#-mcp-ve-a2a-entegrasyonu) +- [Yetenekler Sistemi](#-yetenekler-sistemi) +- [Bellek Sistemi](#-bellek-sistemi) +- [Webhook'lar](#-webhooklar) +- [Bulut Ajanları](#-bulut-ajanları) +- [Programatik Yönetim](#-programatik-yönetim) +- [Dahili CLI](#-dahili-cli) +- [Masaüstü Uygulaması (Electron)](#-masaüstü-uygulaması-electron) --- -## 💰 Pricing at a Glance +## 💰 Bir Bakışta Fiyatlandırma -| Tier | Provider | Cost | Quota Reset | Best For | -| ------------------- | ----------------- | ----------- | -------------- | -------------------- | -| **💳 SUBSCRIPTION** | Claude Code (Pro) | $20/mo | 5h + weekly | Already subscribed | -| | Codex (Plus/Pro) | $20-200/mo | 5h + weekly | OpenAI users | -| | GitHub Copilot | $10-19/mo | Monthly | GitHub users | -| **🔑 API KEY** | DeepSeek | Pay per use | None | Cheap reasoning | -| | Groq | Pay per use | None | Ultra-fast inference | -| | xAI (Grok) | Pay per use | None | Grok 4 reasoning | -| | Mistral | Pay per use | None | EU-hosted models | -| | Perplexity | Pay per use | None | Search-augmented | -| | Together AI | Pay per use | None | Open-source models | -| | Fireworks AI | Pay per use | None | Fast FLUX images | -| | Cerebras | Pay per use | None | Wafer-scale speed | -| | Cohere | Pay per use | None | Command R+ RAG | -| | NVIDIA NIM | Pay per use | None | Enterprise models | -| **💰 CHEAP** | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup | -| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option | -| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost | -| **🆓 FREE** | Qoder | $0 | Provider limits apply | Verify current catalog | -| | Qwen | $0 | Provider limits apply | Verify current catalog | -| | Kiro | $0 | Provider limits apply | Claude free | +| Katman | Sağlayıcı | Maliyet | Kota Sıfırlanma | En Uygun Kullanım | +| ------------------- | ----------------- | ----------- | --------------- | -------------------- | +| **💳 ABONELİK** | Claude Code (Pro) | $20/ay | 5s + haftalık | Mevcut aboneler | +| | Codex (Plus/Pro) | $20-200/ay | 5s + haftalık | OpenAI kullanıcıları | +| | GitHub Copilot | $10-19/ay | Aylık | GitHub kullanıcıları | +| **🔑 API ANAHTARI** | DeepSeek | Kullandıkça | Yok | Ucuz akıl yürütme | +| | Groq | Kullandıkça | Yok | Ultra hızlı çıkarım | +| | xAI (Grok) | Kullandıkça | Yok | Grok 4 akıl yürütme | +| | Mistral | Kullandıkça | Yok | AB barındırmalı | +| | Perplexity | Kullandıkça | Yok | Arama destekli | +| | Together AI | Kullandıkça | Yok | Açık kaynak modeller | +| | Fireworks AI | Kullandıkça | Yok | Hızlı FLUX görseller | +| | Cerebras | Kullandıkça | Yok | Donanım hızlandırma | +| | Cohere | Kullandıkça | Yok | Command R+ RAG | +| | NVIDIA NIM | Kullandıkça | Yok | Kurumsal modeller | +| **💰 UCUZ** | GLM-4.7 | $0.6/1M | Günlük 10:00 | Bütçe dostu yedek | +| | MiniMax M2.1 | $0.2/1M | 5 saatlik döngü | En ucuz seçenek | +| | Kimi K2 | $9/ay sabit | 10M token/ay | Öngörülebilir maliyet| +| **🆓 ÜCRETSİZ** | Qoder | $0 | Sağlayıcı limiti| Katalogdan kontrol | +| | Qwen | $0 | Sağlayıcı limiti| Katalogdan kontrol | +| | Kiro | $0 | ~50 kredi/ay | Claude ücretsiz | --- -## 🎯 Use Cases +## 🎯 Kullanım Senaryoları -### Case 1: "I have Claude Pro subscription" +### Senaryo 1: "Claude Pro aboneliğim var" -**Problem:** Quota expires unused, rate limits during heavy coding +**Sorun:** Kota kullanılmadan kalıyor veya yoğun kodlamada hız sınırına takılıyor. ``` -Combo: "maximize-claude" - 1. cc/claude-opus-4-7 (use subscription fully) - 2. glm/glm-4.7 (cheap backup when quota out) - 3. if/kimi-k2-thinking (free emergency fallback) +Kombo: "maximize-claude" + 1. cc/claude-opus-4-7 (önce aboneliği sonuna kadar kullan) + 2. glm/glm-4.7 (kota bitince ucuz yedek) + 3. if/qwen3.8-max-preview (ücretsiz acil durum geri dönüşü) -Monthly cost: $20 (subscription) + ~$5 (backup) = $25 total -vs. $20 + hitting limits = frustration +Aylık maliyet: $20 (abonelik) + ~$5 (yedek) = $25 toplam ``` -### Case 2: "I want zero cost" +### Senaryo 2: "Sıfır maliyet istiyorum" -**Problem:** Can't afford subscriptions, need reliable AI coding +**Sorun:** Abonelik bütçesi yok, güvenilir AI kodlama gerekiyor. ``` -Combo: "free-tier-fallback" - 1. if/kimi-k2-thinking (no published token cap; limits apply) - 2. qw/qwen3-coder-plus (no published token cap; limits apply) +Kombo: "zero-cost" + 1. if/kimi-k2.7-code (ücretsiz erişim; hız sınırları geçerli olabilir) + 2. kr/qwen3-coder-next (Kiro ücretsiz geri dönüş) -Monthly cost: $0 -Quality: verify the model, limits, privacy, and SLA for your workload +Aylık maliyet: $0 ``` -### Case 3: "I need 24/7 coding, no interruptions" +### Senaryo 3: "7/24 kesintisiz kodlamaya ihtiyacım var" -**Problem:** Deadlines, can't afford downtime +**Sorun:** Teslim tarihleri yakın, kesinti kabul edilemez. ``` -Combo: "always-on" - 1. cc/claude-opus-4-7 (best quality) - 2. cx/gpt-5.2-codex (second subscription) - 3. glm/glm-4.7 (cheap, resets daily) - 4. minimax/MiniMax-M2.1 (cheapest, 5h reset) - 5. if/kimi-k2-thinking (free unlimited) - -Result: 5 fallback layers broaden resilience; upstream availability is not guaranteed -Monthly cost: $20-200 (subscriptions) + $10-20 (backup) -``` - -### Case 4: "I want FREE AI in OpenClaw" - -**Problem:** Need AI assistant in messaging apps, completely free - -``` -Combo: "openclaw-free" - 1. if/glm-4.7 (no published token cap; limits apply) - 2. if/minimax-m2.1 (no published token cap; limits apply) - 3. if/kimi-k2-thinking (no published token cap; limits apply) - -Monthly cost: $0 -Access via: WhatsApp, Telegram, Slack, Discord, iMessage, Signal... +Kombo: "always-on" + 1. cc/claude-opus-4-7 (en yüksek kalite) + 2. cx/gpt-5.5 (ikinci abonelik) + 3. glm/glm-4.7 (ucuz, günlük sıfırlanan) + 4. if/deepseek-v3.2 (ücretsiz son çare) ``` --- -## 📖 Provider Setup +## 🚀 Sağlayıcı Kurulumu -### 🔐 Subscription Providers - -#### Claude Code (Pro/Max) - -```bash -Dashboard → Providers → Connect Claude Code -→ OAuth login → Auto token refresh -→ 5-hour + weekly quota tracking - -Models: - cc/claude-opus-4-7 - cc/claude-sonnet-4-5-20250929 - cc/claude-haiku-4-5-20251001 -``` - -**Pro Tip:** Use Opus for complex tasks, Sonnet for speed. OmniRoute tracks quota per model! - -#### OpenAI Codex (Plus/Pro) - -```bash -Dashboard → Providers → Connect Codex -→ OAuth login (port 1455) -→ 5-hour + weekly reset - -Models: - cx/gpt-5.2-codex - cx/gpt-5.1-codex-max -``` - -#### GitHub Copilot - -```bash -Dashboard → Providers → Connect GitHub -→ OAuth via GitHub -→ Monthly reset (1st of month) - -Models: - gh/gpt-5 - gh/claude-4.5-sonnet - gh/gemini-3.1-pro-preview -``` - -### 💰 Cheap Providers - -#### GLM-4.7 (Daily reset, $0.6/1M) - -1. Sign up: [Zhipu AI](https://open.bigmodel.cn/) -2. Get API key from Coding Plan -3. Dashboard → Add API Key: Provider: `glm`, API Key: `your-key` - -**Use:** `glm/glm-4.7` — **Pro Tip:** Coding Plan offers 3× quota at 1/7 cost! Reset daily 10:00 AM. - -#### MiniMax M2.1 (5h reset, $0.20/1M) - -1. Sign up: [MiniMax](https://www.minimax.io/) -2. Get API key → Dashboard → Add API Key - -**Use:** `minimax/MiniMax-M2.1` — **Pro Tip:** Cheapest option for long context (1M tokens)! - -#### Kimi K2 ($9/month flat) - -1. Subscribe: [Moonshot AI](https://platform.moonshot.ai/) -2. Get API key → Dashboard → Add API Key - -**Use:** `kimi/kimi-latest` — **Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost! - -### 🆓 FREE Providers - -#### Qoder (8 FREE models) - -```bash -Dashboard → Connect Qoder → OAuth login → Access is subject to current provider limits - -Models: if/kimi-k2-thinking, if/qwen3-coder-plus, if/glm-4.7, if/minimax-m2, if/deepseek-r1 -``` - -#### Qwen (3 FREE models) - -```bash -Dashboard → Connect Qwen → Device code auth → Access is subject to current provider limits - -Models: qw/qwen3-coder-plus, qw/qwen3-coder-flash -``` - -#### Kiro (Claude FREE) - -```bash -Dashboard → Connect Kiro → AWS Builder ID or Google/GitHub → Unlimited - -Models: kr/claude-sonnet-4.5, kr/claude-haiku-4.5 -``` +1. **OAuth Sağlayıcıları:** Panoda **Providers > Connect** seçeneğine tıklayın. Tarayıcıda oturum açın, yetki verin. Belirteçler yerel olarak şifrelenir ve arka planda otomatik yenilenir. +2. **API Anahtarı Sağlayıcıları:** API anahtarınızı girin ve kaydedin. +3. **Ücretsiz Sağlayıcılar:** Tek tıkla etkinleştirin. --- -## 🎨 Combos +## 💻 CLI Entegrasyonu -You can reorder combo cards directly in **Dashboard → Combos** by dragging the handle on each card. The order is stored in SQLite and restored on reload. +OmniRoute, standart OpenAI uyumlu uç nokta sunduğundan tüm geliştirici araçlarıyla uyumludur: -### Example 1: Maximize Subscription → Cheap Backup - -``` -Dashboard → Combos → Create New - -Name: premium-coding -Models: - 1. cc/claude-opus-4-7 (Subscription primary) - 2. glm/glm-4.7 (Cheap backup, $0.6/1M) - 3. minimax/MiniMax-M2.1 (Cheapest fallback, $0.20/1M) - -Use in CLI: premium-coding -``` - -### Example 2: Free-Only (Zero Cost) - -``` -Name: free-combo -Models: - 1. if/kimi-k2-thinking (no published token cap; provider limits may apply) - 2. qw/qwen3-coder-plus (no published token cap; provider limits may apply) - -Cost: currently listed as $0; terms and availability may change -``` - ---- - -## 🔧 CLI Integration - -### Cursor IDE - -``` -Settings → Models → Advanced: - OpenAI API Base URL: http://localhost:20128/v1 - OpenAI API Key: [from omniroute dashboard] - Model: cc/claude-opus-4-7 -``` - -### Claude Code - -Edit `~/.claude/config.json`: - -```json -{ - "anthropic_api_base": "http://localhost:20128/v1", - "anthropic_api_key": "your-omniroute-api-key" -} -``` - -### Codex CLI - -```bash -export OPENAI_BASE_URL="http://localhost:20128" -export OPENAI_API_KEY="your-omniroute-api-key" -codex "your prompt" -``` - -### OpenClaw - -Edit `~/.openclaw/openclaw.json`: - -```json -{ - "agents": { - "defaults": { - "model": { "primary": "omniroute/if/glm-4.7" } - } - }, - "models": { - "providers": { - "omniroute": { - "baseUrl": "http://localhost:20128/v1", - "apiKey": "your-omniroute-api-key", - "api": "openai-completions", - "models": [{ "id": "if/glm-4.7", "name": "glm-4.7" }] - } - } - } -} -``` - -**Or use Dashboard:** CLI Tools → OpenClaw → Auto-config - -### Cline / Continue / RooCode - -``` -Provider: OpenAI Compatible -Base URL: http://localhost:20128/v1 -API Key: [from dashboard] -Model: cc/claude-opus-4-7 -``` - ---- - -## Dağıtım - -### Global npm install (Recommended) - -```bash -npm install -g omniroute - -# Create config directory -mkdir -p ~/.omniroute - -# Create .env file (see .env.example) -cp .env.example ~/.omniroute/.env - -# Start server -omniroute -# Or with custom port: -omniroute --port 3000 -``` - -The CLI automatically loads `.env` from `~/.omniroute/.env` or `./.env`. - -### Uninstalling - -When you no longer need OmniRoute, we provide two quick scripts for a clean removal: - -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | - -> Note: To run these commands, navigate to the OmniRoute project folder (if you cloned it) and run them. Alternatively, if globally installed, you can simply run `npm uninstall -g omniroute`. - -### VPS Deployment - -```bash -git clone https://github.com/diegosouzapw/OmniRoute.git -cd OmniRoute && npm install && npm run build - -export JWT_SECRET="your-secure-secret-change-this" -export INITIAL_PASSWORD="your-password" -export DATA_DIR="/var/lib/omniroute" -export PORT="20128" -export HOSTNAME="0.0.0.0" -export NODE_ENV="production" -export NEXT_PUBLIC_BASE_URL="http://localhost:20128" -export API_KEY_SECRET="endpoint-proxy-api-key-secret" - -npm run start -# Or: pm2 start npm --name omniroute -- start -``` - -### PM2 Deployment (Low Memory) - -For servers with limited RAM, use the memory limit option: - -```bash -# With 512MB limit (default) -pm2 start npm --name omniroute -- start - -# Or with custom memory limit -OMNIROUTE_MEMORY_MB=512 pm2 start npm --name omniroute -- start - -# Or using ecosystem.config.js -pm2 start ecosystem.config.js -``` - -Create `ecosystem.config.js`: - -```javascript -module.exports = { - apps: [ - { - name: "omniroute", - script: "npm", - args: "start", - env: { - NODE_ENV: "production", - OMNIROUTE_MEMORY_MB: "512", - JWT_SECRET: "your-secret", - INITIAL_PASSWORD: "your-password", - }, - node_args: "--max-old-space-size=512", - max_memory_restart: "300M", - }, - ], -}; -``` - -### Docker - -```bash -# Build image (default = runner-cli with codex/claude/droid preinstalled) -docker build -t omniroute:cli . - -# Portable mode (recommended) -docker run -d --name omniroute -p 20128:20128 --env-file ./.env -v omniroute-data:/app/data omniroute:cli -``` - -For host-integrated mode with CLI binaries, see the Docker section in the main docs. - -### Void Linux (xbps-src) - -Void Linux users can package and install OmniRoute natively using the `xbps-src` cross-compilation framework. This automates the Node.js standalone build along with the required `better-sqlite3` native bindings. - -
-View xbps-src template - -```bash -# Template file for 'omniroute' -pkgname=omniroute -version=3.2.4 -revision=1 -hostmakedepends="nodejs python3 make" -depends="openssl" -short_desc="Universal AI gateway with smart routing for multiple LLM providers" -maintainer="zenobit " -license="MIT" -homepage="https://github.com/diegosouzapw/OmniRoute" -distfiles="https://github.com/diegosouzapw/OmniRoute/archive/refs/tags/v${version}.tar.gz" -checksum=009400afee90a9f32599d8fe734145cfd84098140b7287990183dde45ae2245b -system_accounts="_omniroute" -omniroute_homedir="/var/lib/omniroute" -export NODE_ENV=production -export npm_config_engine_strict=false -export npm_config_loglevel=error -export npm_config_fund=false -export npm_config_audit=false - -do_build() { - # Determine target CPU arch for node-gyp - local _gyp_arch - case "$XBPS_TARGET_MACHINE" in - aarch64*) _gyp_arch=arm64 ;; - armv7*|armv6*) _gyp_arch=arm ;; - i686*) _gyp_arch=ia32 ;; - *) _gyp_arch=x64 ;; - esac - - # 1) Install all deps – skip scripts - NODE_ENV=development npm ci --ignore-scripts - - # 2) Build the Next.js standalone bundle - npm run build - - # 3) Copy static assets into standalone - cp -r .next/static .next/standalone/.next/static - [ -d public ] && cp -r public .next/standalone/public || true - - # 4) Compile better-sqlite3 native binding - local _node_gyp=/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js - (cd node_modules/better-sqlite3 && node "$_node_gyp" rebuild --arch="$_gyp_arch") - - # 5) Place the compiled binding into the standalone bundle - local _bs3_release=.next/standalone/node_modules/better-sqlite3/build/Release - mkdir -p "$_bs3_release" - cp node_modules/better-sqlite3/build/Release/better_sqlite3.node "$_bs3_release/" - - # 6) Remove arch-specific sharp bundles - rm -rf .next/standalone/node_modules/@img - - # 7) Copy pino runtime deps omitted by Next.js static analysis: - for _mod in pino-abstract-transport split2 process-warning; do - cp -r "node_modules/$_mod" .next/standalone/node_modules/ - done -} - -do_check() { - npm run test:unit -} - -do_install() { - vmkdir usr/lib/omniroute/.next - vcopy .next/standalone/. usr/lib/omniroute/.next/standalone - - # Prevent removal of empty Next.js app router dirs by the post-install hook - for _d in \ - .next/standalone/.next/server/app/dashboard \ - .next/standalone/.next/server/app/dashboard/settings \ - .next/standalone/.next/server/app/dashboard/providers; do - touch "${DESTDIR}/usr/lib/omniroute/${_d}/.keep" - done - - cat > "${WRKDIR}/omniroute" <<'EOF' -#!/bin/sh -export PORT="${PORT:-20128}" -export DATA_DIR="${DATA_DIR:-${XDG_DATA_HOME:-${HOME}/.local/share}/omniroute}" -export APP_LOG_TO_FILE="${APP_LOG_TO_FILE:-false}" -mkdir -p "${DATA_DIR}" -exec node /usr/lib/omniroute/.next/standalone/server.js "$@" -EOF - vbin "${WRKDIR}/omniroute" -} - -post_install() { - vlicense LICENSE -} -``` - -
- -### Environment Variables - -| Variable | Default | Description | -| --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) | -| `INITIAL_PASSWORD` | `123456` | First login password | -| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) | -| `PORT` | framework default | Service port (`20128` in examples) | -| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) | -| `NODE_ENV` | runtime default | Set `production` for deploy | -| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL | -| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL | -| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys | -| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` | -| `ALLOW_API_KEY_REVEAL` | `false` | Allow Api Manager to copy full API keys on demand | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | Server-side refresh cadence for cached Provider Limits data; UI refresh buttons still trigger manual sync | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | Disable automatic SQLite snapshots before writes/import/restore; manual backups still work | -| `APP_LOG_TO_FILE` | `true` | Enables application and audit log output to disk | -| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) | -| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download | -| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB | -| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries | - -For the full environment variable reference, see the [README](../README.md). - ---- - -## 📊 Available Models - -
-View all available models - -**Claude Code (`cc/`)** — Pro/Max: `cc/claude-opus-4-7`, `cc/claude-sonnet-4-5-20250929`, `cc/claude-haiku-4-5-20251001` - -**Codex (`cx/`)** — Plus/Pro: `cx/gpt-5.2-codex`, `cx/gpt-5.1-codex-max` - -**GitHub Copilot (`gh/`)**: `gh/gpt-5`, `gh/claude-4.5-sonnet` - -**GLM (`glm/`)** — $0.6/1M: `glm/glm-4.7` - -**MiniMax (`minimax/`)** — $0.2/1M: `minimax/MiniMax-M2.1` - -**Qoder (`if/`)** — FREE: `if/kimi-k2-thinking`, `if/qwen3-coder-plus`, `if/deepseek-r1` - -**Qwen (`qw/`)** — FREE: `qw/qwen3-coder-plus`, `qw/qwen3-coder-flash` - -**Kiro (`kr/`)** — FREE: `kr/claude-sonnet-4.5`, `kr/claude-haiku-4.5` - -**DeepSeek (`ds/`)**: `ds/deepseek-chat`, `ds/deepseek-reasoner` - -**Groq (`groq/`)**: `groq/llama-3.3-70b-versatile`, `groq/llama-4-maverick-17b-128e-instruct` - -**xAI (`xai/`)**: `xai/grok-4`, `xai/grok-4-0709-fast-reasoning`, `xai/grok-code-mini` - -**Mistral (`mistral/`)**: `mistral/mistral-large-2501`, `mistral/codestral-2501` - -**Perplexity (`pplx/`)**: `pplx/sonar-pro`, `pplx/sonar` - -**Together AI (`together/`)**: `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` - -**Fireworks AI (`fireworks/`)**: `fireworks/accounts/fireworks/models/deepseek-v3p1` - -**Cerebras (`cerebras/`)**: `cerebras/llama-3.3-70b` - -**Cohere (`cohere/`)**: `cohere/command-r-plus-08-2024` - -**NVIDIA NIM (`nvidia/`)**: `nvidia/nvidia/llama-3.3-70b-instruct` - -
- ---- - -## 🧩 Advanced Features - -### Custom Models - -Add any model ID to any provider without waiting for an app update: - -```bash -# Via API -curl -X POST http://localhost:20128/api/provider-models \ - -H "Content-Type: application/json" \ - -d '{"provider": "openai", "modelId": "gpt-4.5-preview", "modelName": "GPT-4.5 Preview"}' - -# List: curl http://localhost:20128/api/provider-models?provider=openai -# Remove: curl -X DELETE "http://localhost:20128/api/provider-models?provider=openai&model=gpt-4.5-preview" -``` - -Or use Dashboard: **Providers → [Provider] → Custom Models**. - -Notes: - -- OpenRouter and OpenAI/Anthropic-compatible providers are managed from **Available Models** only. Manual add, import, and auto-sync all land in the same available-model list, so there is no separate Custom Models section for those providers. -- The **Custom Models** section is intended for providers that do not expose managed available-model imports. - -### Dedicated Provider Routes - -Route requests directly to a specific provider with model validation: - -```bash -POST http://localhost:20128/v1/providers/openai/chat/completions -POST http://localhost:20128/v1/providers/openai/embeddings -POST http://localhost:20128/v1/providers/fireworks/images/generations -``` - -The provider prefix is auto-added if missing. Mismatched models return `400`. - -### Network Proxy Configuration - -```bash -# Set global proxy -curl -X PUT http://localhost:20128/api/settings/proxy \ - -d '{"global": {"type":"http","host":"proxy.example.com","port":"8080"}}' - -# Per-provider proxy -curl -X PUT http://localhost:20128/api/settings/proxy \ - -d '{"providers": {"openai": {"type":"socks5","host":"proxy.example.com","port":"1080"}}}' - -# Test proxy -curl -X POST http://localhost:20128/api/settings/proxy/test \ - -d '{"proxy":{"type":"socks5","host":"proxy.example.com","port":"1080"}}' -``` - -**Precedence:** Key-specific → Combo-specific → Provider-specific → Global → Environment. - -### Model Catalog API - -```bash -curl http://localhost:20128/api/models/catalog -``` - -Returns models grouped by provider with types (`chat`, `embedding`, `image`). - -### Cloud Sync - -- Sync providers, combos, and settings across devices -- Automatic background sync with timeout + fail-fast -- Prefer server-side `BASE_URL`/`CLOUD_URL` in production - -### Cloudflare Quick Tunnel - -- Available in **Dashboard → Endpoints** for Docker and other self-hosted deployments -- Creates a temporary `https://*.trycloudflare.com` URL that forwards to your current OpenAI-compatible `/v1` endpoint -- First enable installs `cloudflared` only when needed; later restarts reuse the same managed binary -- Quick Tunnels are not auto-restored after an OmniRoute or container restart; re-enable them from the dashboard when needed -- Tunnel URLs are ephemeral and change every time you stop/start the tunnel -- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained containers -- Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want to override the managed transport choice -- Set `CLOUDFLARED_BIN` if you prefer using a preinstalled `cloudflared` binary instead of the managed download - -### LLM Gateway Intelligence (Phase 9) - -- **Semantic Cache** — Auto-caches non-streaming, temperature=0 responses (bypass with `X-OmniRoute-No-Cache: true`) -- **Request Idempotency** — Deduplicates requests within 5s via `Idempotency-Key` or `X-Request-Id` header -- **Progress Tracking** — Opt-in SSE `event: progress` events via `X-OmniRoute-Progress: true` header - ---- - -### Translator Playground - -Access via **Dashboard → Translator**. Debug and visualize how OmniRoute translates API requests between providers. - -| Mode | Purpose | -| ---------------- | -------------------------------------------------------------------------------------- | -| **Playground** | Select source/target formats, paste a request, and see the translated output instantly | -| **Chat Tester** | Send live chat messages through the proxy and inspect the full request/response cycle | -| **Test Bench** | Run batch tests across multiple format combinations to verify translation correctness | -| **Live Monitor** | Watch real-time translations as requests flow through the proxy | - -**Use cases:** - -- Debug why a specific client/provider combination fails -- Verify that thinking tags, tool calls, and system prompts translate correctly -- Compare format differences between OpenAI, Claude, Gemini, and Responses API formats - ---- - -### Routing Strategies - -Configure via **Dashboard → Settings → Routing**. - -| Strategy | Description | -| ------------------------------ | ------------------------------------------------------------------------------------------------ | -| **Fill First** | Uses accounts in priority order — primary account handles all requests until unavailable | -| **Round Robin** | Cycles through all accounts with a configurable sticky limit (default: 3 calls per account) | -| **P2C (Power of Two Choices)** | Picks 2 random accounts and routes to the healthier one — balances load with awareness of health | -| **Random** | Randomly selects an account for each request using Fisher-Yates shuffle | -| **Least Used** | Routes to the account with the oldest `lastUsedAt` timestamp, distributing traffic evenly | -| **Cost Optimized** | Routes to the account with the lowest priority value, optimizing for lowest-cost providers | - -#### External Sticky Session Header - -For external session affinity (for example, Claude Code/Codex agents behind reverse proxies), send: - -```http -X-Session-Id: your-session-key -``` - -OmniRoute also accepts `x_session_id` and returns the effective session key in `X-OmniRoute-Session-Id`. - -If you use Nginx and send underscore-form headers, enable: - -```nginx -underscores_in_headers on; -``` - -#### Wildcard Model Aliases - -Create wildcard patterns to remap model names: - -``` -Pattern: claude-sonnet-* → Target: cc/claude-sonnet-4-5-20250929 -Pattern: gpt-* → Target: gh/gpt-5.1-codex -``` - -Wildcards support `*` (any characters) and `?` (single character). - -#### Fallback Chains - -Define global fallback chains that apply across all requests: - -``` -Chain: production-fallback - 1. cc/claude-opus-4-7 - 2. gh/gpt-5.1-codex - 3. glm/glm-4.7 -``` - ---- - -### Resilience & Circuit Breakers - -Configure via **Dashboard → Settings → Resilience**. - -OmniRoute implements provider-level resilience with five components: - -1. **Request Queue & Pacing** — System-level request shaping: - - **Requests Per Minute (RPM)** — Maximum requests per minute per account - - **Min Time Between Requests** — Minimum gap in milliseconds between requests - - **Max Concurrent Requests** — Maximum simultaneous requests per account - -2. **Connection Cooldown** — Per-auth-type configuration for a single connection after retryable failures: - - **Base Cooldown** — Default cooldown window for retryable upstream failures - - **Use Upstream Retry Hints** — Honors authoritative `Retry-After` or reset hints when provided - - **Max Backoff Steps** — Maximum exponential backoff level for repeated failures - -3. **Provider Circuit Breaker** — Tracks end-to-end provider failures and automatically opens the breaker when the configured threshold is reached: - - **Failure Threshold** — Consecutive provider failures before opening the breaker - - **Reset Timeout** — Time window before the provider is tested again - - **CLOSED** (Healthy) — Requests flow normally - - **OPEN** — Provider is temporarily blocked after repeated failures - - **HALF_OPEN** — Testing if provider has recovered - - Connection-scoped `429` rate limits stay in **Connection Cooldown** and do not count toward the provider breaker. - - The provider breaker runtime state is shown on **Dashboard → Health** only. - -4. **Wait For Cooldown** — If every candidate connection is already cooling down, OmniRoute can wait for the earliest cooldown and retry the same client request automatically. - -5. **Rate Limit Auto-Detection** — When upstream providers return explicit wait windows, those hints override the local connection cooldown when the setting is enabled. - -**Pro Tip:** Use the **Health** page to inspect and reset live provider breakers after an outage. The Resilience page only changes configuration. - ---- - -### Database Export / Import - -Manage database backups in **Dashboard → Settings → System & Storage**. - -| Action | Description | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| **Export Database** | Downloads the current SQLite database as a `.sqlite` file | -| **Export All (.tar.gz)** | Downloads a full backup archive including: database, settings, combos, provider connections (no credentials), API key metadata | -| **Import Database** | Upload a `.sqlite` file to replace the current database. A pre-import backup is automatically created unless `DISABLE_SQLITE_AUTO_BACKUP=true` | - -```bash -# API: Export database -curl -o backup.sqlite http://localhost:20128/api/db-backups/export - -# API: Export all (full archive) -curl -o backup.tar.gz http://localhost:20128/api/db-backups/exportAll - -# API: Import database -curl -X POST http://localhost:20128/api/db-backups/import \ - -F "file=@backup.sqlite" -``` - -**Import Validation:** The imported file is validated for integrity (SQLite pragma check), required tables (`provider_connections`, `provider_nodes`, `combos`, `api_keys`), and size (max 100MB). - -**Use Cases:** - -- Migrate OmniRoute between machines -- Create external backups for disaster recovery -- Share configurations between team members (export all → share archive) - ---- - -### Settings Dashboard - -The settings page is organized into 6 tabs for easy navigation: - -| Tab | Contents | -| -------------- | -------------------------------------------------------------------------------------------- | -| **General** | System storage tools, appearance settings, theme controls, and per-item sidebar visibility | -| **Security** | Login/Password settings, IP Access Control, API auth for `/models`, and Provider Blocking | -| **Routing** | Global routing strategy (6 options), wildcard model aliases, fallback chains, combo defaults | -| **Resilience** | Request queue, connection cooldown, provider breaker config, and wait-for-cooldown behavior | -| **AI** | Thinking budget configuration, global system prompt injection, prompt cache stats | -| **Advanced** | Global proxy configuration (HTTP/SOCKS5) | - ---- - -### Costs & Budget Management - -Access via **Dashboard → Costs**. - -| Tab | Purpose | -| ----------- | ---------------------------------------------------------------------------------------- | -| **Budget** | Set spending limits per API key with daily/weekly/monthly budgets and real-time tracking | -| **Pricing** | View and edit model pricing entries — cost per 1K input/output tokens per provider | - -```bash -# API: Set a budget -curl -X POST http://localhost:20128/api/usage/budget \ - -H "Content-Type: application/json" \ - -d '{"keyId": "key-123", "limit": 50.00, "period": "monthly"}' - -# API: Get current budget status -curl http://localhost:20128/api/usage/budget -``` - -**Cost Tracking:** Every request logs token usage and calculates cost using the pricing table. View breakdowns in **Dashboard → Usage** by provider, model, and API key. - ---- - -### Audio Transcription - -OmniRoute supports audio transcription via the OpenAI-compatible endpoint: - -```bash -POST /v1/audio/transcriptions -Authorization: Bearer your-api-key -Content-Type: multipart/form-data - -# Example with curl -curl -X POST http://localhost:20128/v1/audio/transcriptions \ - -H "Authorization: Bearer your-api-key" \ - -F "file=@audio.mp3" \ - -F "model=deepgram/nova-3" -``` - -Available providers: **Deepgram** (`deepgram/`), **AssemblyAI** (`assemblyai/`). - -Supported audio formats: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`. - ---- - -### Combo Balancing Strategies - -Configure per-combo balancing in **Dashboard → Combos → Create/Edit → Strategy**. - -| Strategy | Description | -| ------------------ | ------------------------------------------------------------------------ | -| **Round-Robin** | Rotates through models sequentially | -| **Priority** | Always tries the first model; falls back only on error | -| **Random** | Picks a random model from the combo for each request | -| **Weighted** | Routes proportionally based on assigned weights per model | -| **Least-Used** | Routes to the model with the fewest recent requests (uses combo metrics) | -| **Cost-Optimized** | Routes to the cheapest available model (uses pricing table) | - -Global combo defaults can be set in **Dashboard → Settings → Routing → Combo Defaults**. - ---- - -### Health Dashboard - -Access via **Dashboard → Health**. Real-time system health overview with 6 cards: - -| Card | What It Shows | -| --------------------- | ----------------------------------------------------------- | -| **System Status** | Uptime, version, memory usage, data directory | -| **Provider Health** | Global provider circuit breaker runtime state | -| **Rate Limits** | Active connection cooldowns per account with remaining time | -| **Active Lockouts** | Active model-scoped lockouts and temporary exclusions | -| **Signature Cache** | Deduplication cache stats (active keys, hit rate) | -| **Latency Telemetry** | p50/p95/p99 latency aggregation per provider | - -**Pro Tip:** The Health page auto-refreshes every 10 seconds. Use the circuit breaker card to identify which providers are experiencing issues. - ---- - -## 🖥️ Desktop Application (Electron) - -OmniRoute is available as a native desktop application for Windows, macOS, and Linux. - -### Kurulum - -```bash -# From the electron directory: -cd electron -npm install - -# Development mode (connect to running Next.js dev server): -npm run dev - -# Production mode (uses standalone build): -npm start -``` - -### Building Installers - -```bash -cd electron -npm run build # Current platform -npm run build:win # Windows (.exe NSIS) -npm run build:mac # macOS (.dmg universal) -npm run build:linux # Linux (.AppImage) -``` - -Output → `electron/dist-electron/` - -### Key Features - -| Feature | Description | -| --------------------------- | ---------------------------------------------------- | -| **Server Readiness** | Polls server before showing window (no blank screen) | -| **System Tray** | Minimize to tray, change port, quit from tray menu | -| **Port Management** | Change server port from tray (auto-restarts server) | -| **Content Security Policy** | Restrictive CSP via session headers | -| **Single Instance** | Only one app instance can run at a time | -| **Offline Mode** | Bundled Next.js server works without internet | - -### Environment Variables - -| Variable | Default | Description | -| --------------------- | ------- | -------------------------------- | -| `OMNIROUTE_PORT` | `20128` | Server port | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit (64–16384 MB) | - -📖 Full documentation: [`electron/README.md`](../electron/README.md) +- **Claude Code:** `CLAUDE_BASE_URL="http://localhost:20128/v1"` +- **OpenAI Codex:** `OPENAI_BASE_URL="http://localhost:20128/v1"` +- **Cursor IDE:** `Override OpenAI Base URL: http://localhost:20128/v1` +- **Cline / Roo Code / Continue:** OpenAI uyumlu sağlayıcı olarak `http://localhost:20128/v1` tanımlayın. diff --git a/docs/i18n/tr/docs/ops/COVERAGE_PLAN.md b/docs/i18n/tr/docs/ops/COVERAGE_PLAN.md index a9a8222e5c..697d2d6ea1 100644 --- a/docs/i18n/tr/docs/ops/COVERAGE_PLAN.md +++ b/docs/i18n/tr/docs/ops/COVERAGE_PLAN.md @@ -1,170 +1,37 @@ -# Test Coverage Plan (Türkçe) +--- +title: "Test Kapsam Planı" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/COVERAGE_PLAN.md) · 🇸🇦 [ar](../../ar/docs/COVERAGE_PLAN.md) · 🇧🇬 [bg](../../bg/docs/COVERAGE_PLAN.md) · 🇧🇩 [bn](../../bn/docs/COVERAGE_PLAN.md) · 🇨🇿 [cs](../../cs/docs/COVERAGE_PLAN.md) · 🇩🇰 [da](../../da/docs/COVERAGE_PLAN.md) · 🇩🇪 [de](../../de/docs/COVERAGE_PLAN.md) · 🇪🇸 [es](../../es/docs/COVERAGE_PLAN.md) · 🇮🇷 [fa](../../fa/docs/COVERAGE_PLAN.md) · 🇫🇮 [fi](../../fi/docs/COVERAGE_PLAN.md) · 🇫🇷 [fr](../../fr/docs/COVERAGE_PLAN.md) · 🇮🇳 [gu](../../gu/docs/COVERAGE_PLAN.md) · 🇮🇱 [he](../../he/docs/COVERAGE_PLAN.md) · 🇮🇳 [hi](../../hi/docs/COVERAGE_PLAN.md) · 🇭🇺 [hu](../../hu/docs/COVERAGE_PLAN.md) · 🇮🇩 [id](../../id/docs/COVERAGE_PLAN.md) · 🇮🇹 [it](../../it/docs/COVERAGE_PLAN.md) · 🇯🇵 [ja](../../ja/docs/COVERAGE_PLAN.md) · 🇰🇷 [ko](../../ko/docs/COVERAGE_PLAN.md) · 🇮🇳 [mr](../../mr/docs/COVERAGE_PLAN.md) · 🇲🇾 [ms](../../ms/docs/COVERAGE_PLAN.md) · 🇳🇱 [nl](../../nl/docs/COVERAGE_PLAN.md) · 🇳🇴 [no](../../no/docs/COVERAGE_PLAN.md) · 🇵🇭 [phi](../../phi/docs/COVERAGE_PLAN.md) · 🇵🇱 [pl](../../pl/docs/COVERAGE_PLAN.md) · 🇵🇹 [pt](../../pt/docs/COVERAGE_PLAN.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/COVERAGE_PLAN.md) · 🇷🇴 [ro](../../ro/docs/COVERAGE_PLAN.md) · 🇷🇺 [ru](../../ru/docs/COVERAGE_PLAN.md) · 🇸🇰 [sk](../../sk/docs/COVERAGE_PLAN.md) · 🇸🇪 [sv](../../sv/docs/COVERAGE_PLAN.md) · 🇰🇪 [sw](../../sw/docs/COVERAGE_PLAN.md) · 🇮🇳 [ta](../../ta/docs/COVERAGE_PLAN.md) · 🇮🇳 [te](../../te/docs/COVERAGE_PLAN.md) · 🇹🇭 [th](../../th/docs/COVERAGE_PLAN.md) · 🇹🇷 [tr](../../tr/docs/COVERAGE_PLAN.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/COVERAGE_PLAN.md) · 🇵🇰 [ur](../../ur/docs/COVERAGE_PLAN.md) · 🇻🇳 [vi](../../vi/docs/COVERAGE_PLAN.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/COVERAGE_PLAN.md) +# Test Kapsam Planı (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/ops/COVERAGE_PLAN.md) · 🇸🇦 [ar](../../ar/docs/ops/COVERAGE_PLAN.md) · 🇧🇬 [bg](../../bg/docs/ops/COVERAGE_PLAN.md) · 🇧🇩 [bn](../../bn/docs/ops/COVERAGE_PLAN.md) · 🇨🇿 [cs](../../cs/docs/ops/COVERAGE_PLAN.md) · 🇩🇰 [da](../../da/docs/ops/COVERAGE_PLAN.md) · 🇩🇪 [de](../../de/docs/ops/COVERAGE_PLAN.md) · 🇪🇸 [es](../../es/docs/ops/COVERAGE_PLAN.md) · 🇮🇷 [fa](../../fa/docs/ops/COVERAGE_PLAN.md) · 🇫🇮 [fi](../../fi/docs/ops/COVERAGE_PLAN.md) · 🇫🇷 [fr](../../fr/docs/ops/COVERAGE_PLAN.md) · 🇮🇳 [gu](../../gu/docs/ops/COVERAGE_PLAN.md) · 🇮🇱 [he](../../he/docs/ops/COVERAGE_PLAN.md) · 🇮🇳 [hi](../../hi/docs/ops/COVERAGE_PLAN.md) · 🇭🇺 [hu](../../hu/docs/ops/COVERAGE_PLAN.md) · 🇮🇩 [id](../../id/docs/ops/COVERAGE_PLAN.md) · 🇮🇹 [it](../../it/docs/ops/COVERAGE_PLAN.md) · 🇯🇵 [ja](../../ja/docs/ops/COVERAGE_PLAN.md) · 🇰🇷 [ko](../../ko/docs/ops/COVERAGE_PLAN.md) · 🇮🇳 [mr](../../mr/docs/ops/COVERAGE_PLAN.md) · 🇲🇾 [ms](../../ms/docs/ops/COVERAGE_PLAN.md) · 🇳🇱 [nl](../../nl/docs/ops/COVERAGE_PLAN.md) · 🇳🇴 [no](../../no/docs/ops/COVERAGE_PLAN.md) · 🇵🇭 [phi](../../phi/docs/ops/COVERAGE_PLAN.md) · 🇵🇱 [pl](../../pl/docs/ops/COVERAGE_PLAN.md) · 🇵🇹 [pt](../../pt/docs/ops/COVERAGE_PLAN.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ops/COVERAGE_PLAN.md) · 🇷🇴 [ro](../../ro/docs/ops/COVERAGE_PLAN.md) · 🇷🇺 [ru](../../ru/docs/ops/COVERAGE_PLAN.md) · 🇸🇰 [sk](../../sk/docs/ops/COVERAGE_PLAN.md) · 🇸🇪 [sv](../../sv/docs/ops/COVERAGE_PLAN.md) · 🇰🇪 [sw](../../sw/docs/ops/COVERAGE_PLAN.md) · 🇮🇳 [ta](../../ta/docs/ops/COVERAGE_PLAN.md) · 🇮🇳 [te](../../te/docs/ops/COVERAGE_PLAN.md) · 🇹🇭 [th](../../th/docs/ops/COVERAGE_PLAN.md) · 🇹🇷 [tr](../../tr/docs/ops/COVERAGE_PLAN.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ops/COVERAGE_PLAN.md) · 🇵🇰 [ur](../../ur/docs/ops/COVERAGE_PLAN.md) · 🇻🇳 [vi](../../vi/docs/ops/COVERAGE_PLAN.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ops/COVERAGE_PLAN.md) --- -Last updated: 2026-03-28 +## Taban Çizgisi -## Baseline +| Metrik | Kapsam | İfadeler / Satırlar | Dallar | Fonksiyonlar | Notlar | +| -------------------- | ----------------------------------------------------- | ------------------: | -------: | -----------: | --------------------------------------------------- | +| Önerilen taban çizgi | Yalnızca kaynak kod, testler hariç, `open-sse` dahil | 82.58% | 75.22% | 84.23% | İyileştirilecek proje genelindeki taban çizgisidir | -There are multiple coverage numbers depending on how the report is computed. For planning, only one of them is useful. +## Kurallar -| Metric | Scope | Statements / Lines | Branches | Functions | Notes | -| -------------------- | ----------------------------------------------------- | -----------------: | -------: | --------: | --------------------------------------------------- | -| Legacy | Old `npm run test:coverage` | 79.42% | 75.15% | 67.94% | Inflated: counts test files and excludes `open-sse` | -| Diagnostic | Source-only, excluding tests and excluding `open-sse` | 68.16% | 63.55% | 64.06% | Useful only to isolate `src/**` | -| Recommended baseline | Source-only, excluding tests and including `open-sse` | 56.95% | 66.05% | 57.80% | This is the project-wide baseline to improve | +- Kapsam hedefleri `tests/**` için değil, kaynak dosyalar için geçerlidir. +- `open-sse/**` ürünün bir parçasıdır ve kapsamda kalmalıdır. +- Yeni kod, dokunulan alanlardaki kapsamı düşürmemelidir. +- Uygulama ayrıntıları yerine davranış ve dal sonuçlarını test etmeyi tercih edin. +- `src/lib/db/**` için geniş mock'lar yerine geçici SQLite veritabanlarını ve küçük fikstürleri tercih edin. -The recommended baseline is the number to optimize against. +## Aşamalar -## Rules - -- Coverage targets apply to source files, not to `tests/**`. -- `open-sse/**` is part of the product and must remain in scope. -- New code should not reduce coverage in touched areas. -- Prefer testing behavior and branch outcomes over implementation details. -- Prefer temp SQLite databases and small fixtures over broad mocks for `src/lib/db/**`. - -## Current command set - -- `npm run test:coverage` - - Main source coverage gate for the unit test suite - - Generates `text-summary`, `html`, `json-summary`, and `lcov` -- `npm run coverage:report` - - Detailed file-by-file report from the latest run -- `npm run test:coverage:legacy` - - Historical comparison only - -## Milestones - -| Phase | Target | Focus | -| ------- | ---------------------: | ------------------------------------------------- | -| Phase 1 | 60% statements / lines | Quick wins and low-risk utility coverage | -| Phase 2 | 65% statements / lines | DB and route foundations | -| Phase 3 | 70% statements / lines | Provider validation and usage analytics | -| Phase 4 | 75% statements / lines | `open-sse` translators and helpers | -| Phase 5 | 80% statements / lines | `open-sse` handlers and executor branches | -| Phase 6 | 85% statements / lines | Harder edge cases, branch debt, regression suites | -| Phase 7 | 90% statements / lines | Final sweep, gap closure, strict ratchet | - -Branches and functions should ratchet upward with each phase, but the primary hard target is statements / lines. - -## Priority hotspots - -These files or areas offer the best return for the next phases: - -1. `open-sse/handlers` - - `chatCore.ts` at 7.57% - - Overall directory at 29.07% -2. `open-sse/translator/request` - - Overall directory at 36.39% - - Many translators are still near single-digit coverage -3. `open-sse/translator/response` - - Overall directory at 8.07% -4. `open-sse/executors` - - Overall directory at 36.62% -5. `src/lib/db` - - `models.ts` at 20.66% - - `registeredKeys.ts` at 34.46% - - `modelComboMappings.ts` at 36.25% - - `settings.ts` at 46.40% - - `webhooks.ts` at 33.33% -6. `src/lib/usage` - - `usageHistory.ts` at 21.12% - - `usageStats.ts` at 9.56% - - `costCalculator.ts` at 30.00% -7. `src/lib/providers` - - `validation.ts` at 41.16% -8. Low-risk utility and API files for early gains - - `src/shared/utils/upstreamError.ts` - - `src/shared/utils/apiAuth.ts` - - `src/lib/api/errorResponse.ts` - - `src/app/api/settings/require-login/route.ts` - - `src/app/api/providers/[id]/models/route.ts` - -## Execution checklist - -### Phase 1: 56.95% -> 60% - -- [x] Fix coverage metric so it reflects source code instead of test files -- [x] Keep a legacy coverage script for comparison -- [x] Record the baseline and hotspots in-repo -- [ ] Add focused tests for low-risk utilities: - - `src/shared/utils/upstreamError.ts` - - `src/shared/utils/fetchTimeout.ts` - - `src/lib/api/errorResponse.ts` - - `src/shared/utils/apiAuth.ts` - - `src/lib/display/names.ts` -- [ ] Add route tests for: - - `src/app/api/settings/require-login/route.ts` - - `src/app/api/providers/[id]/models/route.ts` - -### Phase 2: 60% -> 65% - -- [ ] Add DB-backed tests for: - - `src/lib/db/modelComboMappings.ts` - - `src/lib/db/settings.ts` - - `src/lib/db/registeredKeys.ts` -- [ ] Cover branch behavior in: - - `src/lib/providers/validation.ts` - - `src/app/api/v1/embeddings/route.ts` - - `src/app/api/v1/moderations/route.ts` - -### Phase 3: 65% -> 70% - -- [ ] Add usage analytics tests for: - - `src/lib/usage/usageHistory.ts` - - `src/lib/usage/usageStats.ts` - - `src/lib/usage/costCalculator.ts` -- [ ] Expand route coverage for proxy management and settings branches - -### Phase 4: 70% -> 75% - -- [ ] Cover translator helpers and central translation paths: - - `open-sse/translator/index.ts` - - `open-sse/translator/helpers/*` - - `open-sse/translator/request/*` - - `open-sse/translator/response/*` - -### Phase 5: 75% -> 80% - -- [ ] Add handler-level tests for: - - `open-sse/handlers/chatCore.ts` - - `open-sse/handlers/responsesHandler.js` - - `open-sse/handlers/imageGeneration.js` - - `open-sse/handlers/embeddings.js` -- [ ] Add executor branch coverage for provider-specific auth, retries, and endpoint overrides - -### Phase 6: 80% -> 85% - -- [ ] Merge more edge-case suites into the main coverage path -- [ ] Increase function coverage for DB modules with weak constructor/helper coverage -- [ ] Close branch gaps in `settings.ts`, `registeredKeys.ts`, `validation.ts`, and translator helpers - -### Phase 7: 85% -> 90% - -- [ ] Treat the remaining low-coverage files as blockers -- [ ] Add regression tests for every uncovered production bug fixed during the push to 90% -- [ ] Raise the coverage gate in CI only after the local baseline is stable for at least two consecutive runs - -## Ratchet policy - -Update `npm run test:coverage` thresholds only after the project actually exceeds the next milestone with a comfortable buffer. - -Recommended ratchet sequence: - -1. 55/60/55 -2. 60/62/58 -3. 65/64/62 -4. 70/66/66 -5. 75/70/72 -6. 80/75/78 -7. 85/80/84 -8. 90/85/88 - -Order is `statements-lines / branches / functions`. - -## Known gap - -The current coverage command measures the main Node unit suite and includes source reached from it, including `open-sse`. It does not yet merge Vitest coverage into a single unified report. That merge is worth doing later, but it is not a blocker for starting the 60% -> 80% climb. +| Aşama | Hedef | Odak Alanı | Durum | +| ------- | --------------------: | ------------------------------------------------- | ------------ | +| Aşama 1 | %60 ifadeler / satır | Hızlı kazanımlar ve düşük riskli yardımcılar | ✅ Tamamlandı| +| Aşama 2 | %65 ifadeler / satır | Veritabanı ve rota temelleri | ✅ Tamamlandı| +| Aşama 3 | %70 ifadeler / satır | Sağlayıcı doğrulaması ve kullanım analitiği | ✅ Tamamlandı| +| Aşama 4 | %75 ifadeler / satır | `open-sse` çevirmenleri ve yardımcıları | ✅ Tamamlandı| +| Aşama 5 | %80 ifadeler / satır | `open-sse` işleyicileri ve yürütücü dalları | ✅ Tamamlandı| +| Aşama 6 | %85 ifadeler / satır | Uç durumlar, dal borcu, regresyon paketleri | Devam ediyor | +| Aşama 7 | %90 ifadeler / satır | Son tarama, boşluk kapatma, sıkı kalite kapısı | Bekliyor | diff --git a/docs/i18n/tr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md b/docs/i18n/tr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md index d7449d66df..7a2351f25b 100644 --- a/docs/i18n/tr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md +++ b/docs/i18n/tr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md @@ -1,455 +1,58 @@ -# OmniRoute Fly.io 部署指南 (Türkçe) +--- +title: "OmniRoute Fly.io Dağıtım Kılavuzu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/FLY_IO_DEPLOYMENT_GUIDE.md) +# OmniRoute Fly.io Dağıtım Kılavuzu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md) --- -本文档记录 OmniRoute 在 Fly.io 上的实际部署方法,适用于两类场景: - -- 首次把当前项目部署到 Fly.io -- 后续代码更新后继续发布 -- 新项目参考同样流程部署 - -本文基于当前项目已经验证通过的配置整理,应用名为 `omniroute`。 +Bu belge, OmniRoute'un Fly.io platformunda dağıtım sürecini adım adım açıklar. --- -## 1. 部署目标 +## 1. Dağıtım Hedefleri -- 平台:Fly.io -- 部署方式:本地 `flyctl` 直接发布 -- 运行方式:使用仓库内现有 `Dockerfile` 和 `fly.toml` -- 数据持久化:Fly Volume 挂载到 `/data` -- 访问地址:`https://omniroute.fly.dev/` +- **Platform:** Fly.io +- **Dağıtım yöntemi:** Yerel `flyctl` ile doğrudan yayınlama +- **Çalışma Zamanı:** Depodaki mevcut `Dockerfile` ve `fly.toml` +- **Veri Kalıcılığı:** `/data` dizinine bağlanmış Fly Volume +- **Erişim Adresi:** `https://omniroute.fly.dev/` --- -## 2. 当前项目关键配置 +## 2. Ön Koşullar ve `flyctl` Kurulumu -当前仓库中的 `fly.toml` 已确认包含以下关键项: +```bash +# Fly CLI kurulumu (macOS / Linux): +curl -L https://fly.io/install.sh | sh -```toml -app = 'omniroute' -primary_region = 'sin' - -[[mounts]] - source = 'data' - destination = '/data' - -[processes] - app = 'node run-standalone.mjs' - -[http_service] - internal_port = 20128 - -[env] - TZ = "Asia/Shanghai" - HOST = "0.0.0.0" - HOSTNAME = "0.0.0.0" - BIND = "0.0.0.0" -``` - -说明: - -- `app = 'omniroute'` 决定实际部署到哪个 Fly 应用 -- `destination = '/data'` 决定持久卷挂载目录 -- 本项目必须让 `DATA_DIR=/data`,否则数据库和密钥会写到容器临时目录 - ---- - -## 3. 必备工具 - -### 3.1 安装 Fly CLI - -Windows PowerShell: - -```powershell -pwsh -Command "iwr https://fly.io/install.ps1 -useb | iex" -``` - -如果安装脚本在当前环境失败,也可以手动下载 `flyctl` 二进制并放到 `PATH` 中。 - -### 3.2 登录 Fly 账号 - -```powershell +# Giriş yapma: flyctl auth login ``` -### 3.3 检查登录状态 - -```powershell -flyctl auth whoami -flyctl version -``` - --- -## 4. 首次部署当前项目 - -### 4.1 获取代码并进入目录 - -```powershell -git clone https://github.com/diegosouzapw/OmniRoute.git -cd OmniRoute -``` - -### 4.2 确认应用名 - -打开 `fly.toml`,重点看这一行: - -```toml -app = 'omniroute' -``` - -如果你准备部署到自己的新应用,可改成全局唯一名称,例如: - -```toml -app = 'omniroute-yourname' -``` - -注意: - -- 控制台里要看的是与 `fly.toml` 里 `app` 一致的应用 -- 以前如果用过别的名字,例如 `oroute`,不要和 `omniroute` 混淆 - -### 4.3 创建应用 - -如果该应用尚不存在: - -```powershell -flyctl apps create omniroute -``` - -如果你已经改成别的应用名,把 `omniroute` 替换成你的名字。 - -### 4.4 首次部署 - -```powershell -flyctl deploy -``` - ---- - -## 5. 必配参数 - -本项目在 Fly.io 上建议至少配置以下参数。 - -### 5.1 已验证使用的参数 - -这些参数已经在当前 `omniroute` 应用上实际部署: - -- `API_KEY_SECRET` -- `DATA_DIR` -- `JWT_SECRET` -- `MACHINE_ID_SALT` -- `NEXT_PUBLIC_BASE_URL` -- `STORAGE_ENCRYPTION_KEY` - -### 5.2 关于 `INITIAL_PASSWORD` - -当前项目没有设置 `INITIAL_PASSWORD`,因为本次部署按需求不使用它。 - -如果不设置: - -- 启动日志会提示默认密码是 `CHANGEME` -- 部署后应尽快在系统设置中修改登录密码 - -如果你希望无人值守初始化后台密码,也可以后续补: - -- `INITIAL_PASSWORD` - ---- - -## 6. 推荐参数说明 - -### 6.1 Secrets 中设置 - -建议放入 Fly Secrets: - -| 变量名 | 是否推荐 | 说明 | -| ------------------------ | -------- | ------------------------------ | -| `API_KEY_SECRET` | 必需 | API Key 生成与校验使用 | -| `JWT_SECRET` | 必需 | 登录态和 JWT 签名使用 | -| `STORAGE_ENCRYPTION_KEY` | 强烈推荐 | 加密存储敏感连接信息 | -| `MACHINE_ID_SALT` | 推荐 | 生成稳定机器标识 | -| `INITIAL_PASSWORD` | 可选 | 首次部署时直接指定后台初始密码 | -| OAuth/API 私密凭证 | 按需 | 各类外部平台鉴权配置 | - -### 6.2 当前项目推荐值 - -| 变量名 | 推荐值 | -| ---------------------- | --------------------------- | -| `DATA_DIR` | `/data` | -| `NEXT_PUBLIC_BASE_URL` | `https://omniroute.fly.dev` | - -说明: - -- `DATA_DIR=/data` 非常关键,必须与 Fly Volume 挂载点一致 -- `NEXT_PUBLIC_BASE_URL` 用于调度器和前端回调等场景 - ---- - -## 7. 一键设置参数 - -下面命令会生成安全随机值,并把当前项目需要的参数一次性写入 Fly Secrets。 - -说明: - -- 不包含 `INITIAL_PASSWORD` -- 适用于当前项目 `omniroute` - -```powershell -$apiKeySecret = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() -$jwtSecret = [Convert]::ToHexString((1..64 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() -$machineIdSalt = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() -$storageKey = [Convert]::ToHexString((1..32 | ForEach-Object { Get-Random -Minimum 0 -Maximum 256 })).ToLower() - -flyctl secrets set ` - API_KEY_SECRET=$apiKeySecret ` - JWT_SECRET=$jwtSecret ` - MACHINE_ID_SALT=$machineIdSalt ` - STORAGE_ENCRYPTION_KEY=$storageKey ` - DATA_DIR=/data ` - NEXT_PUBLIC_BASE_URL=https://omniroute.fly.dev ` - -a omniroute -``` - -如果你还要加初始密码: - -```powershell -flyctl secrets set INITIAL_PASSWORD=你的强密码 -a omniroute -``` - ---- - -## 8. 查看当前参数 - -```powershell -flyctl secrets list -a omniroute -``` - -如果控制台 `Secrets` 页面没有显示你期待的变量,先检查: - -- 看的应用是不是 `omniroute` -- `fly.toml` 的 `app` 是否和控制台应用一致 - ---- - -## 9. 后续更新发布 - -代码有更新后,发布步骤很简单: - -```powershell -git pull -flyctl deploy -``` - -如果只更新参数,不改代码: - -```powershell -flyctl secrets set KEY=value -a omniroute -``` - -Fly 会自动滚动更新机器。 - -### 9.1 跟踪原仓库更新并保留 fork 的 `fly.toml` - -如果当前仓库是 fork,并且你要同步上游 `https://github.com/diegosouzapw/OmniRoute` 的更新,推荐按下面流程执行。 - -先确认远程: - -```powershell -git remote -v -``` - -应至少包含: - -- `origin` 指向你自己的 fork -- `upstream` 指向原仓库 - -如果没有 `upstream`,先添加: - -```powershell -git remote add upstream https://github.com/diegosouzapw/OmniRoute.git -``` - -同步上游前,先抓取最新提交和标签: - -```powershell -git fetch upstream --tags -``` - -查看当前版本和上游标签: - -```powershell -git describe --tags --always -git show --no-patch --oneline v3.4.7 -``` - -如果你想合并上游最新 `main`,并强制保留 fork 当前的 `fly.toml`,可按下面流程执行: - -```powershell -git merge upstream/main -git checkout HEAD~1 -- fly.toml -git add -- fly.toml -git commit -m "chore(deploy): keep fork fly.toml" -git push origin main -``` - -说明: - -- `git merge upstream/main` 用于同步原仓库最新代码 -- `git checkout HEAD~1 -- fly.toml` 用于恢复合并前你 fork 自己的 `fly.toml` -- 如果上游没有改 `fly.toml`,这一步不会带来额外差异 -- 如果上游改了 `fly.toml`,这一步能确保 Fly 应用名、挂载卷、区域等 fork 自定义部署配置不被覆盖 - -如果你明确只想对齐某个发布标签,例如 `v3.4.7`,也可以先确认标签是否已经包含在 `upstream/main`: - -```powershell -git merge-base --is-ancestor v3.4.7 upstream/main -``` - -返回成功表示 `upstream/main` 已经包含该版本,直接合并 `upstream/main` 即可。 - -### 9.2 同步上游后的标准发布顺序 - -同步原仓库完成后,推荐按下面顺序发布: - -1. `git fetch upstream --tags` -2. `git merge upstream/main` -3. 恢复 fork 的 `fly.toml` -4. `git push origin main` -5. `flyctl deploy` -6. `flyctl status -a omniroute` -7. `flyctl logs --no-tail -a omniroute` - -这就是当前项目升级到 `v3.4.7` 时使用的实际流程。 - ---- - -## 10. 发布后检查 - -### 10.1 查看应用状态 - -```powershell -flyctl status -a omniroute -``` - -### 10.2 查看启动日志 - -```powershell -flyctl logs --no-tail -a omniroute -``` - -### 10.3 检查网站可访问 - -```powershell -try { - (Invoke-WebRequest -Uri "https://omniroute.fly.dev" -MaximumRedirection 5 -UseBasicParsing).StatusCode -} catch { - if ($_.Exception.Response) { - $_.Exception.Response.StatusCode.value__ - } else { - throw - } -} -``` - -返回 `200` 说明站点已正常响应。 - ---- - -## 11. 成功标志 - -部署成功后,日志里应看到类似内容: - -```text -[bootstrap] Secrets persisted to: /data/server.env -[DB] SQLite database ready: /data/storage.sqlite -``` - -这两个点很关键: - -- `/data/server.env` 说明运行时密钥落到了持久卷 -- `/data/storage.sqlite` 说明数据库写入持久卷 - -如果你看到的是 `/app/data/...`,说明 `DATA_DIR` 没配对,需要立即修正。 - ---- - -## 12. 常见问题 - -### 12.1 `Secrets` 页面是空的 - -通常有两种原因: - -- 你还没执行 `flyctl secrets set` -- 你打开的是另一个应用,例如 `oroute`,不是 `omniroute` - -### 12.2 `flyctl deploy` 报 `app not found` - -先创建应用: - -```powershell -flyctl apps create omniroute -``` - -### 12.3 `fly.toml` 解析失败 - -重点检查: - -- 注释里是否有乱码字符 -- TOML 引号和缩进是否正确 - -### 12.4 数据没有持久化 - -检查以下两点: - -- `fly.toml` 中是否存在 `destination = '/data'` -- `DATA_DIR` 是否设置为 `/data` - -### 12.5 不设置 `INITIAL_PASSWORD` 是否能跑 - -可以运行,但会回退到默认 `CHANGEME`。生产环境建议尽快修改后台密码。 - ---- - -## 13. 新项目复用建议 - -如果以后是新项目照着这份文档部署,最少改这几项: - -1. 修改 `fly.toml` 里的 `app` -2. 修改 `NEXT_PUBLIC_BASE_URL` -3. 保持 `DATA_DIR=/data` -4. 重新生成 `API_KEY_SECRET`、`JWT_SECRET`、`MACHINE_ID_SALT`、`STORAGE_ENCRYPTION_KEY` -5. 首次部署后检查日志是否写入 `/data` - -不要直接复用旧项目的密钥。 - ---- - -## 14. 当前项目的最小发布清单 - -当前项目后续最常用的命令如下: - -```powershell -flyctl auth whoami -flyctl status -a omniroute -flyctl secrets list -a omniroute -flyctl deploy -flyctl logs --no-tail -a omniroute -``` - -如果只是正常发版,核心就是: - -```powershell -flyctl deploy -``` - -如果是新环境首次部署,核心就是: - -1. `flyctl auth login` -2. `flyctl apps create omniroute` -3. `flyctl secrets set ... -a omniroute` -4. `flyctl deploy` -5. `flyctl logs --no-tail -a omniroute` +## 3. İlk Dağıtım Adımları + +1. **Volume Oluşturma (Kalıcı Depolama):** + ```bash + flyctl volumes create data --size 3 --region sin + ``` + +2. **Gizli Değişkenleri (Secrets) Ayarlama:** + ```bash + flyctl secrets set \ + JWT_SECRET="guclu-jwt-anahtariniz" \ + API_KEY_SECRET="guclu-aes-anahtariniz" \ + INITIAL_PASSWORD="yonetici-sifreniz" \ + DATA_DIR="/data" + ``` + +3. **Uygulamayı Dağıtma:** + ```bash + flyctl deploy + ``` diff --git a/docs/i18n/tr/docs/ops/RELEASE_CHECKLIST.md b/docs/i18n/tr/docs/ops/RELEASE_CHECKLIST.md index 23b681a69e..4fb0fbc422 100644 --- a/docs/i18n/tr/docs/ops/RELEASE_CHECKLIST.md +++ b/docs/i18n/tr/docs/ops/RELEASE_CHECKLIST.md @@ -1,44 +1,53 @@ -# Release Checklist (Türkçe) +--- +title: "Sürüm Kontrol Listesi (Release Checklist)" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/RELEASE_CHECKLIST.md) · 🇸🇦 [ar](../../ar/docs/RELEASE_CHECKLIST.md) · 🇧🇬 [bg](../../bg/docs/RELEASE_CHECKLIST.md) · 🇧🇩 [bn](../../bn/docs/RELEASE_CHECKLIST.md) · 🇨🇿 [cs](../../cs/docs/RELEASE_CHECKLIST.md) · 🇩🇰 [da](../../da/docs/RELEASE_CHECKLIST.md) · 🇩🇪 [de](../../de/docs/RELEASE_CHECKLIST.md) · 🇪🇸 [es](../../es/docs/RELEASE_CHECKLIST.md) · 🇮🇷 [fa](../../fa/docs/RELEASE_CHECKLIST.md) · 🇫🇮 [fi](../../fi/docs/RELEASE_CHECKLIST.md) · 🇫🇷 [fr](../../fr/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [gu](../../gu/docs/RELEASE_CHECKLIST.md) · 🇮🇱 [he](../../he/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [hi](../../hi/docs/RELEASE_CHECKLIST.md) · 🇭🇺 [hu](../../hu/docs/RELEASE_CHECKLIST.md) · 🇮🇩 [id](../../id/docs/RELEASE_CHECKLIST.md) · 🇮🇹 [it](../../it/docs/RELEASE_CHECKLIST.md) · 🇯🇵 [ja](../../ja/docs/RELEASE_CHECKLIST.md) · 🇰🇷 [ko](../../ko/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [mr](../../mr/docs/RELEASE_CHECKLIST.md) · 🇲🇾 [ms](../../ms/docs/RELEASE_CHECKLIST.md) · 🇳🇱 [nl](../../nl/docs/RELEASE_CHECKLIST.md) · 🇳🇴 [no](../../no/docs/RELEASE_CHECKLIST.md) · 🇵🇭 [phi](../../phi/docs/RELEASE_CHECKLIST.md) · 🇵🇱 [pl](../../pl/docs/RELEASE_CHECKLIST.md) · 🇵🇹 [pt](../../pt/docs/RELEASE_CHECKLIST.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/RELEASE_CHECKLIST.md) · 🇷🇴 [ro](../../ro/docs/RELEASE_CHECKLIST.md) · 🇷🇺 [ru](../../ru/docs/RELEASE_CHECKLIST.md) · 🇸🇰 [sk](../../sk/docs/RELEASE_CHECKLIST.md) · 🇸🇪 [sv](../../sv/docs/RELEASE_CHECKLIST.md) · 🇰🇪 [sw](../../sw/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [ta](../../ta/docs/RELEASE_CHECKLIST.md) · 🇮🇳 [te](../../te/docs/RELEASE_CHECKLIST.md) · 🇹🇭 [th](../../th/docs/RELEASE_CHECKLIST.md) · 🇹🇷 [tr](../../tr/docs/RELEASE_CHECKLIST.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/RELEASE_CHECKLIST.md) · 🇵🇰 [ur](../../ur/docs/RELEASE_CHECKLIST.md) · 🇻🇳 [vi](../../vi/docs/RELEASE_CHECKLIST.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/RELEASE_CHECKLIST.md) +# Sürüm Kontrol Listesi (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/ops/RELEASE_CHECKLIST.md) · 🇸🇦 [ar](../../ar/docs/ops/RELEASE_CHECKLIST.md) · 🇧🇬 [bg](../../bg/docs/ops/RELEASE_CHECKLIST.md) · 🇧🇩 [bn](../../bn/docs/ops/RELEASE_CHECKLIST.md) · 🇨🇿 [cs](../../cs/docs/ops/RELEASE_CHECKLIST.md) · 🇩🇰 [da](../../da/docs/ops/RELEASE_CHECKLIST.md) · 🇩🇪 [de](../../de/docs/ops/RELEASE_CHECKLIST.md) · 🇪🇸 [es](../../es/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇷 [fa](../../fa/docs/ops/RELEASE_CHECKLIST.md) · 🇫🇮 [fi](../../fi/docs/ops/RELEASE_CHECKLIST.md) · 🇫🇷 [fr](../../fr/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇳 [gu](../../gu/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇱 [he](../../he/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇳 [hi](../../hi/docs/ops/RELEASE_CHECKLIST.md) · 🇭🇺 [hu](../../hu/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇩 [id](../../id/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇹 [it](../../it/docs/ops/RELEASE_CHECKLIST.md) · 🇯🇵 [ja](../../ja/docs/ops/RELEASE_CHECKLIST.md) · 🇰🇷 [ko](../../ko/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇳 [mr](../../mr/docs/ops/RELEASE_CHECKLIST.md) · 🇲🇾 [ms](../../ms/docs/ops/RELEASE_CHECKLIST.md) · 🇳🇱 [nl](../../nl/docs/ops/RELEASE_CHECKLIST.md) · 🇳🇴 [no](../../no/docs/ops/RELEASE_CHECKLIST.md) · 🇵🇭 [phi](../../phi/docs/ops/RELEASE_CHECKLIST.md) · 🇵🇱 [pl](../../pl/docs/ops/RELEASE_CHECKLIST.md) · 🇵🇹 [pt](../../pt/docs/ops/RELEASE_CHECKLIST.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ops/RELEASE_CHECKLIST.md) · 🇷🇴 [ro](../../ro/docs/ops/RELEASE_CHECKLIST.md) · 🇷🇺 [ru](../../ru/docs/ops/RELEASE_CHECKLIST.md) · 🇸🇰 [sk](../../sk/docs/ops/RELEASE_CHECKLIST.md) · 🇸🇪 [sv](../../sv/docs/ops/RELEASE_CHECKLIST.md) · 🇰🇪 [sw](../../sw/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇳 [ta](../../ta/docs/ops/RELEASE_CHECKLIST.md) · 🇮🇳 [te](../../te/docs/ops/RELEASE_CHECKLIST.md) · 🇹🇭 [th](../../th/docs/ops/RELEASE_CHECKLIST.md) · 🇹🇷 [tr](../../tr/docs/ops/RELEASE_CHECKLIST.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ops/RELEASE_CHECKLIST.md) · 🇵🇰 [ur](../../ur/docs/ops/RELEASE_CHECKLIST.md) · 🇻🇳 [vi](../../vi/docs/ops/RELEASE_CHECKLIST.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ops/RELEASE_CHECKLIST.md) --- -Use this checklist before tagging or publishing a new OmniRoute release. - -## Version and Changelog - -1. Bump `package.json` version (`x.y.z`) in the release branch. -2. Move release notes from `## [Unreleased]` in `CHANGELOG.md` to a dated section: - - `## [x.y.z] — YYYY-MM-DD` -3. Keep `## [Unreleased]` as the first changelog section for upcoming work. -4. Ensure the latest semver section in `CHANGELOG.md` equals `package.json` version. - -## API Docs - -1. Update `docs/reference/openapi.yaml`: - - `info.version` must equal `package.json` version. -2. Validate endpoint examples if API contracts changed. - -## Runtime Docs - -1. Review `docs/architecture/ARCHITECTURE.md` for storage/runtime drift. -2. Review `docs/guides/TROUBLESHOOTING.md` for env var and operational drift. -3. Verify the release/runtime Node.js version still satisfies the supported secure floor: - - `>=20.20.2 <21` or `>=22.22.2 <23` - - `npm run check:node-runtime` -4. Validate the npm publish artifact after building the standalone package: - - `npm run build:cli` - - `npm run check:pack-artifact` - - confirm no `app.__qa_backup`, `scripts/scratch`, `package-lock.json`, or other local residue -5. Update localized docs if source docs changed significantly. - -## Automated Check - -Run the sync guard locally before opening PR: +## Özet Akış ```bash -npm run check:docs-sync +# 1. Sürümü artırın + CHANGELOG oluşturun +/version-bump-cc patch # veya minor/major + +# 2. Kalite kapısını yerel olarak çalıştırın +npm run check # lint + testler +npm run test:coverage # tam kapsam kapısı (60/60/60/60) + +# 3. Derleme & Başlatma Testi +npm run build +npm run test:e2e # isteğe bağlı ancak önerilir + +# 4. Sürüm oluşturma +/generate-release-cc + +# 5. Dağıtım +/deploy-vps-both-cc # veya akamai-cc / local-cc + +# 6. Sürüm kanıtlarını yakalama +/capture-release-evidences-cc ``` -CI also runs this check in `.github/workflows/ci.yml` (lint job). +--- + +## Aşamalı Yayınlama (npm Staged Publishing) + +npm-publish iş akışı doğrudan yayınlama yapmaz: paketlenmiş tarball'ı (`check:pack-boot`) başlatır ve ardından `npm stage publish` çalıştırır — tam baytlar kayıt defterine park edilir, **sahibi onaylayana kadar kurulamaz**. İnsan 2FA kapısı kanıttan SONRA gelir. + +### Onay Akışı + +1. `npm stage list omniroute` — aşama kimliğini (stage id) bulun. +2. Paketlenmiş baytları doğrulayın: `npm stage download `, ardından geçici bir dizine kurun ve başlatın (`npm run check:pack-boot`). +3. `npm stage approve ` — 2FA istemi yayını tamamlar. `npm stage reject ` iptal eder. + +--- + +## Acil Düzeltme Hızlı Şeridi (`hotfix` Etiketi) + +`hotfix` etiketli bir PR, ağır CI matrisini (9 parçalı E2E, kapsam kontrolü) atlar ve hızlı, yüksek sinyalli kapıları korur: build, unit, integration, vitest, lint/typecheck, docs-sync, `check:pack-artifact` ve tarball boot-smoke (`check:pack-boot`). Hedef: ~33 dakika yerine ≤15 dakikada yeşil. diff --git a/docs/i18n/tr/docs/ops/VM_DEPLOYMENT_GUIDE.md b/docs/i18n/tr/docs/ops/VM_DEPLOYMENT_GUIDE.md index 158ca59804..ce06ba8d31 100644 --- a/docs/i18n/tr/docs/ops/VM_DEPLOYMENT_GUIDE.md +++ b/docs/i18n/tr/docs/ops/VM_DEPLOYMENT_GUIDE.md @@ -1,407 +1,80 @@ -# OmniRoute — Deployment Guide on VM with Cloudflare (Türkçe) +--- +title: "OmniRoute — Cloudflare ile VM Üzerinde Dağıtım Kılavuzu" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/VM_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/VM_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/VM_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/VM_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/VM_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/VM_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/VM_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/VM_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/VM_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/VM_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/VM_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/VM_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/VM_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/VM_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/VM_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/VM_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/VM_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/VM_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/VM_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/VM_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/VM_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/VM_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/VM_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/VM_DEPLOYMENT_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/VM_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/VM_DEPLOYMENT_GUIDE.md) +# OmniRoute — Cloudflare ile VM Üzerinde Dağıtım Kılavuzu (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇩🇰 [da](../../da/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇩🇪 [de](../../de/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇪🇸 [es](../../es/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇱 [he](../../he/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇩 [id](../../id/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇹 [it](../../it/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇳🇴 [no](../../no/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇮🇳 [te](../../te/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇹🇭 [th](../../th/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/ops/VM_DEPLOYMENT_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ops/VM_DEPLOYMENT_GUIDE.md) --- -Complete guide to install and configure OmniRoute on a VM (VPS) with domain managed via Cloudflare. +Cloudflare üzerinden yönetilen bir alan adı ile VM (VPS) üzerinde OmniRoute kurulumu ve yapılandırması için eksiksiz kılavuz. --- -## Prerequisites +## Ön Koşullar -| Item | Minimum | Recommended | +| Öğe | Minimum | Önerilen | | ---------- | ------------------------ | ---------------- | | **CPU** | 1 vCPU | 2 vCPU | | **RAM** | 1 GB | 2 GB | | **Disk** | 10 GB SSD | 25 GB SSD | -| **OS** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | -| **Domain** | Registered on Cloudflare | — | +| **İşletim Sistemi** | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS | +| **Alan Adı** | Cloudflare'e yönlendirilmiş | — | | **Docker** | Docker Engine 24+ | Docker 27+ | -**Tested providers**: Akamai (Linode), DigitalOcean, Vultr, Hetzner, AWS Lightsail. - --- -## 1. Configure the VM +## 1. VM Yapılandırması -### 1.1 Create the instance - -On your preferred VPS provider: - -- Choose Ubuntu 24.04 LTS -- Select the minimum plan (1 vCPU / 1 GB RAM) -- Set a strong root password or configure SSH key -- Note the **public IP** (e.g., `203.0.113.10`) - -### 1.2 Connect via SSH +### 1.1 SSH ile Bağlantı ```bash -ssh root@203.0.113.10 +ssh root@SUNUCU_IP_ADRESINIZ ``` -### 1.3 Update the system +### 1.2 Sistemi Güncelleme ```bash apt update && apt upgrade -y ``` -### 1.4 Install Docker +### 1.3 Docker Kurulumu ```bash -# Install dependencies apt install -y ca-certificates curl gnupg - -# Add official Docker repository install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg -echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $ (. /etc/os-release && echo “$VERSION_CODENAME”) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null apt update apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin ``` -### 1.5 Install nginx - -```bash -apt install -y nginx -``` - -### 1.6 Configure Firewall (UFW) +### 1.4 Güvenlik Duvarı (UFW) ```bash ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp # SSH -ufw allow 80/tcp # HTTP (redirect) +ufw allow 80/tcp # HTTP ufw allow 443/tcp # HTTPS ufw enable ``` -> **Tip**: For maximum security, restrict ports 80 and 443 to Cloudflare IPs only. See the [Advanced Security](#advanced-security) section. - --- -## 2. Install OmniRoute - -### 2.1 Create configuration directory +## 2. OmniRoute Kurulumu ```bash mkdir -p /opt/omniroute +cd /opt/omniroute ``` -### 2.2 Create environment variables file +Docker Compose ile OmniRoute'u başlatın: ```bash -cat > /opt/omniroute/.env << ‘EOF’ -# === Security === -JWT_SECRET=CHANGE-TO-A-UNIQUE-64-CHAR-SECRET-KEY -INITIAL_PASSWORD=YourSecurePassword123! -API_KEY_SECRET=REPLACE-WITH-ANOTHER-SECRET-KEY -STORAGE_ENCRYPTION_KEY=REPLACE-WITH-THIRD-SECRET-KEY -STORAGE_ENCRYPTION_KEY_VERSION=v1 -MACHINE_ID_SALT=CHANGE-TO-A-UNIQUE-SALT - -# === App === -PORT=20128 -NODE_ENV=production -HOSTNAME=0.0.0.0 -DATA_DIR=/app/data -STORAGE_DRIVER=sqlite -APP_LOG_TO_FILE=true -AUTH_COOKIE_SECURE=false -REQUIRE_API_KEY=false - -# === Domain (change to your domain) === -BASE_URL=https://llms.seudominio.com -NEXT_PUBLIC_BASE_URL=https://llms.seudominio.com - -# === Cloud Sync (optional) === -# CLOUD_URL=https://cloud.omniroute.online -# NEXT_PUBLIC_CLOUD_URL=https://cloud.omniroute.online -EOF +docker compose up -d ``` - -> ⚠️ **IMPORTANT**: Generate unique secret keys! Use `openssl rand -hex 32` for each key. - -### 2.3 Start the container - -```bash -docker pull diegosouzapw/omniroute:latest - -docker run -d \ - --name omniroute \ - --restart unless-stopped \ - --env-file /opt/omniroute/.env \ - -p 20128:20128 \ - -v omniroute-data:/app/data \ - diegosouzapw/omniroute:latest -``` - -### 2.4 Verify that it is running - -```bash -docker ps | grep omniroute -docker logs omniroute --tail 20 -``` - -It should display: `[DB] SQLite database ready` and `listening on port 20128`. - ---- - -## 3. Configure nginx (Reverse Proxy) - -### 3.1 Generate SSL certificate (Cloudflare Origin) - -In the Cloudflare dashboard: - -1. Go to **SSL/TLS → Origin Server** -2. Click **Create Certificate** -3. Keep the defaults (15 years, \*.yourdomain.com) -4. Copy the **Origin Certificate** and the **Private Key** - -```bash -mkdir -p /etc/nginx/ssl - -# Paste the certificate -nano /etc/nginx/ssl/origin.crt - -# Paste the private key -nano /etc/nginx/ssl/origin.key - -chmod 600 /etc/nginx/ssl/origin.key -``` - -### 3.2 Nginx Configuration - -```bash -cat > /etc/nginx/sites-available/omniroute << ‘NGINX’ -# Default server — blocks direct access via IP -server { - listen 80 default_server; - listen [::]:80 default_server; - listen 443 ssl default_server; - listen [::]:443 ssl default_server; - ssl_certificate /etc/nginx/ssl/origin.crt; - ssl_certificate_key /etc/nginx/ssl/origin.key; - server_name _; - return 444; -} - -# OmniRoute — HTTPS -server { - listen 443 ssl; - listen [::]:443 ssl; - server_name llms.yourdomain.com; # Change to your domain - - ssl_certificate /etc/nginx/ssl/origin.crt; - ssl_certificate_key /etc/nginx/ssl/origin.key; - ssl_protocols TLSv1.2 TLSv1.3; - - client_max_body_size 100M; - - location / { - proxy_pass http://127.0.0.1:20128; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # WebSocket support - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection “upgrade”; - - # SSE (Server-Sent Events) — streaming AI responses - proxy_buffering off; - proxy_cache off; - proxy_read_timeout 600s; - proxy_send_timeout 600s; - } -} - -# HTTP → HTTPS redirect -server { - listen 80; - listen [::]:80; - server_name llms.yourdomain.com; - return 301 https://$server_name$request_uri; -} -NGINX -``` - -Keep reverse-proxy stream timeouts aligned with your OmniRoute timeout env vars. If you raise -`FETCH_TIMEOUT_MS` / `STREAM_IDLE_TIMEOUT_MS`, raise `proxy_read_timeout` / `proxy_send_timeout` -above the same threshold. - -### 3.3 Enable and Test - -```bash -# Remove default configuration -rm -f /etc/nginx/sites-enabled/default - -# Enable OmniRoute -ln -sf /etc/nginx/sites-available/omniroute /etc/nginx/sites-enabled/omniroute - -# Test and reload -nginx -t && systemctl reload nginx -``` - ---- - -## 4. Configure Cloudflare DNS - -### 4.1 Add DNS record - -In the Cloudflare dashboard → DNS: - -| Type | Name | Content | Proxy | -| ---- | ------ | ---------------------- | ---------- | -| A | `llms` | `203.0.113.10` (VM IP) | ✅ Proxied | - -### 4.2 Configure SSL - -Under **SSL/TLS → Overview**: - -- Mode: **Full (Strict)** - -Under **SSL/TLS → Edge Certificates**: - -- Always Use HTTPS: ✅ On -- Minimum TLS Version: TLS 1.2 -- Automatic HTTPS Rewrites: ✅ On - -### 4.3 Testing - -```bash -curl -sI https://llms.seudominio.com/health -# Should return HTTP/2 200 -``` - ---- - -## 5. Operations and Maintenance - -### Upgrade to a new version - -```bash -docker pull diegosouzapw/omniroute:latest -docker stop omniroute && docker rm omniroute -docker run -d --name omniroute --restart unless-stopped \ - --env-file /opt/omniroute/.env \ - -p 20128:20128 \ - -v omniroute-data:/app/data \ - diegosouzapw/omniroute:latest -``` - -### View logs - -```bash -docker logs -f omniroute # Real-time stream -docker logs omniroute --tail 50 # Last 50 lines -``` - -### Manual database backup - -```bash -# Copy data from the volume to the host -docker cp omniroute:/app/data ./backup-$(date +%F) - -# Or compress the entire volume -docker run --rm -v omniroute-data:/data -v $(pwd):/backup \ - alpine tar czf /backup/omniroute-data-$(date +%F).tar.gz /data -``` - -### Restore from backup - -```bash -docker stop omniroute -docker run --rm -v omniroute-data:/data -v $(pwd):/backup \ - alpine sh -c “rm -rf /data/* && tar xzf /backup/omniroute-data-YYYY-MM-DD.tar.gz -C /” -docker start omniroute -``` - ---- - -## 6. Advanced Security - -### Restrict nginx to Cloudflare IPs - -```bash -cat > /etc/nginx/cloudflare-ips.conf << ‘CF’ -# Cloudflare IPv4 ranges — update periodically -# https://www.cloudflare.com/ips-v4/ -set_real_ip_from 173.245.48.0/20; -set_real_ip_from 103.21.244.0/22; -set_real_ip_from 103.22.200.0/22; -set_real_ip_from 103.31.4.0/22; -set_real_ip_from 141.101.64.0/18; -set_real_ip_from 108.162.192.0/18; -set_real_ip_from 190.93.240.0/20; -set_real_ip_from 188.114.96.0/20; -set_real_ip_from 197.234.240.0/22; -set_real_ip_from 198.41.128.0/17; -set_real_ip_from 162.158.0.0/15; -set_real_ip_from 104.16.0.0/13; -set_real_ip_from 104.24.0.0/14; -set_real_ip_from 172.64.0.0/13; -set_real_ip_from 131.0.72.0/22; -real_ip_header CF-Connecting-IP; -CF -``` - -Add the following to `nginx.conf` inside the `http {}` block: - -```nginx -include /etc/nginx/cloudflare-ips.conf; -``` - -### Install fail2ban - -```bash -apt install -y fail2ban -systemctl enable fail2ban -systemctl start fail2ban - -# Check status -fail2ban-client status sshd -``` - -### Block direct access to the Docker port - -```bash -# Prevent direct external access to port 20128 -iptables -I DOCKER-USER -p tcp --dport 20128 -j DROP -iptables -I DOCKER-USER -i lo -p tcp --dport 20128 -j ACCEPT - -# Persist the rules -apt install -y iptables-persistent -netfilter-persistent save -``` - ---- - -## 7. Deploy to Cloudflare Workers (Optional) - -For remote access via Cloudflare Workers (without exposing the VM directly): - -```bash -# In the local repository -cd omnirouteCloud -npm install -npx wrangler login -npx wrangler deploy -``` - -See the full documentation at [omnirouteCloud/README.md](../omnirouteCloud/README.md). - ---- - -## Port Summary - -| Port | Service | Access | -| ----- | ----------- | -------------------------- | -| 22 | SSH | Public (with fail2ban) | -| 80 | nginx HTTP | Redirect → HTTPS | -| 443 | nginx HTTPS | Via Cloudflare Proxy | -| 20128 | OmniRoute | Localhost only (via nginx) | diff --git a/docs/i18n/tr/docs/reference/API_REFERENCE.md b/docs/i18n/tr/docs/reference/API_REFERENCE.md index 8891773c76..66596f109f 100644 --- a/docs/i18n/tr/docs/reference/API_REFERENCE.md +++ b/docs/i18n/tr/docs/reference/API_REFERENCE.md @@ -1,28 +1,42 @@ -# API Reference (Türkçe) +--- +title: "API Referansı" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/API_REFERENCE.md) · 🇸🇦 [ar](../../ar/docs/API_REFERENCE.md) · 🇧🇬 [bg](../../bg/docs/API_REFERENCE.md) · 🇧🇩 [bn](../../bn/docs/API_REFERENCE.md) · 🇨🇿 [cs](../../cs/docs/API_REFERENCE.md) · 🇩🇰 [da](../../da/docs/API_REFERENCE.md) · 🇩🇪 [de](../../de/docs/API_REFERENCE.md) · 🇪🇸 [es](../../es/docs/API_REFERENCE.md) · 🇮🇷 [fa](../../fa/docs/API_REFERENCE.md) · 🇫🇮 [fi](../../fi/docs/API_REFERENCE.md) · 🇫🇷 [fr](../../fr/docs/API_REFERENCE.md) · 🇮🇳 [gu](../../gu/docs/API_REFERENCE.md) · 🇮🇱 [he](../../he/docs/API_REFERENCE.md) · 🇮🇳 [hi](../../hi/docs/API_REFERENCE.md) · 🇭🇺 [hu](../../hu/docs/API_REFERENCE.md) · 🇮🇩 [id](../../id/docs/API_REFERENCE.md) · 🇮🇹 [it](../../it/docs/API_REFERENCE.md) · 🇯🇵 [ja](../../ja/docs/API_REFERENCE.md) · 🇰🇷 [ko](../../ko/docs/API_REFERENCE.md) · 🇮🇳 [mr](../../mr/docs/API_REFERENCE.md) · 🇲🇾 [ms](../../ms/docs/API_REFERENCE.md) · 🇳🇱 [nl](../../nl/docs/API_REFERENCE.md) · 🇳🇴 [no](../../no/docs/API_REFERENCE.md) · 🇵🇭 [phi](../../phi/docs/API_REFERENCE.md) · 🇵🇱 [pl](../../pl/docs/API_REFERENCE.md) · 🇵🇹 [pt](../../pt/docs/API_REFERENCE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/API_REFERENCE.md) · 🇷🇴 [ro](../../ro/docs/API_REFERENCE.md) · 🇷🇺 [ru](../../ru/docs/API_REFERENCE.md) · 🇸🇰 [sk](../../sk/docs/API_REFERENCE.md) · 🇸🇪 [sv](../../sv/docs/API_REFERENCE.md) · 🇰🇪 [sw](../../sw/docs/API_REFERENCE.md) · 🇮🇳 [ta](../../ta/docs/API_REFERENCE.md) · 🇮🇳 [te](../../te/docs/API_REFERENCE.md) · 🇹🇭 [th](../../th/docs/API_REFERENCE.md) · 🇹🇷 [tr](../../tr/docs/API_REFERENCE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/API_REFERENCE.md) · 🇵🇰 [ur](../../ur/docs/API_REFERENCE.md) · 🇻🇳 [vi](../../vi/docs/API_REFERENCE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/API_REFERENCE.md) +# API Referansı (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/reference/API_REFERENCE.md) · 🇸🇦 [ar](../../ar/docs/reference/API_REFERENCE.md) · 🇧🇬 [bg](../../bg/docs/reference/API_REFERENCE.md) · 🇧🇩 [bn](../../bn/docs/reference/API_REFERENCE.md) · 🇨🇿 [cs](../../cs/docs/reference/API_REFERENCE.md) · 🇩🇰 [da](../../da/docs/reference/API_REFERENCE.md) · 🇩🇪 [de](../../de/docs/reference/API_REFERENCE.md) · 🇪🇸 [es](../../es/docs/reference/API_REFERENCE.md) · 🇮🇷 [fa](../../fa/docs/reference/API_REFERENCE.md) · 🇫🇮 [fi](../../fi/docs/reference/API_REFERENCE.md) · 🇫🇷 [fr](../../fr/docs/reference/API_REFERENCE.md) · 🇮🇳 [gu](../../gu/docs/reference/API_REFERENCE.md) · 🇮🇱 [he](../../he/docs/reference/API_REFERENCE.md) · 🇮🇳 [hi](../../hi/docs/reference/API_REFERENCE.md) · 🇭🇺 [hu](../../hu/docs/reference/API_REFERENCE.md) · 🇮🇩 [id](../../id/docs/reference/API_REFERENCE.md) · 🇮🇹 [it](../../it/docs/reference/API_REFERENCE.md) · 🇯🇵 [ja](../../ja/docs/reference/API_REFERENCE.md) · 🇰🇷 [ko](../../ko/docs/reference/API_REFERENCE.md) · 🇮🇳 [mr](../../mr/docs/reference/API_REFERENCE.md) · 🇲🇾 [ms](../../ms/docs/reference/API_REFERENCE.md) · 🇳🇱 [nl](../../nl/docs/reference/API_REFERENCE.md) · 🇳🇴 [no](../../no/docs/reference/API_REFERENCE.md) · 🇵🇭 [phi](../../phi/docs/reference/API_REFERENCE.md) · 🇵🇱 [pl](../../pl/docs/reference/API_REFERENCE.md) · 🇵🇹 [pt](../../pt/docs/reference/API_REFERENCE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/reference/API_REFERENCE.md) · 🇷🇴 [ro](../../ro/docs/reference/API_REFERENCE.md) · 🇷🇺 [ru](../../ru/docs/reference/API_REFERENCE.md) · 🇸🇰 [sk](../../sk/docs/reference/API_REFERENCE.md) · 🇸🇪 [sv](../../sv/docs/reference/API_REFERENCE.md) · 🇰🇪 [sw](../../sw/docs/reference/API_REFERENCE.md) · 🇮🇳 [ta](../../ta/docs/reference/API_REFERENCE.md) · 🇮🇳 [te](../../te/docs/reference/API_REFERENCE.md) · 🇹🇭 [th](../../th/docs/reference/API_REFERENCE.md) · 🇹🇷 [tr](../../tr/docs/reference/API_REFERENCE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/reference/API_REFERENCE.md) · 🇵🇰 [ur](../../ur/docs/reference/API_REFERENCE.md) · 🇻🇳 [vi](../../vi/docs/reference/API_REFERENCE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/reference/API_REFERENCE.md) --- -Complete reference for all OmniRoute API endpoints. +Tüm OmniRoute API uç noktaları için eksiksiz referans dokümantasyonu. --- -## Table of Contents +## İçindekiler -- [Chat Completions](#chat-completions) -- [Embeddings](#embeddings) -- [Image Generation](#image-generation) -- [List Models](#list-models) -- [Compatibility Endpoints](#compatibility-endpoints) -- [Semantic Cache](#semantic-cache) -- [Dashboard & Management](#dashboard--management) -- [Request Processing](#request-processing) -- [Authentication](#authentication) +- [Sohbet Tamamlama (Chat Completions)](#sohbet-tamamlama-chat-completions) +- [Özel Başlıklar (Custom Headers)](#özel-başlıklar) +- [Gömme (Embeddings)](#gömme-embeddings) +- [Görsel Üretimi (Image Generation)](#görsel-üretimi) +- [Ses ve Medya API'leri](#ses-ve-medya-apileri) +- [Modelleri Listeleme (List Models)](#modelleri-listeleme) +- [Uyumluluk Uç Noktaları](#uyumluluk-uç-noktaları) +- [Arama API'si (Search API)](#arama-apisi) +- [WebSocket Akışı](#websocket-akışı) +- [Anlamsal Önbellek (Semantic Cache)](#anlamsal-önbellek) +- [Pano ve Yönetim API'leri](#pano-ve-yönetim-apileri) +- [Kombo Yönetimi](#kombo-yönetimi) +- [Webhook'lar](#webhooklar) +- [Kayıtlı Anahtarlar (Otomatik Yönetim)](#kayıtlı-anahtarlar) +- [Ajanlar Protokolü (ACP)](#ajanlar-protokolü) +- [Yetenekler ve Bellek API'leri](#yetenekler-ve-bellek-apileri) +- [Kimlik Doğrulama](#kimlik-doğrulama) --- -## Chat Completions +## Sohbet Tamamlama (Chat Completions) ```bash POST /v1/chat/completions @@ -32,32 +46,29 @@ Content-Type: application/json { "model": "cc/claude-opus-4-6", "messages": [ - {"role": "user", "content": "Write a function to..."} + {"role": "user", "content": "Python'da bir fonksiyon yaz..."} ], "stream": true } ``` -### Custom Headers +### Özel Başlıklar -| Header | Direction | Description | -| ------------------------ | --------- | ------------------------------------------------ | -| `X-OmniRoute-No-Cache` | Request | Set to `true` to bypass cache | -| `X-OmniRoute-Progress` | Request | Set to `true` for progress events | -| `X-Session-Id` | Request | Sticky session key for external session affinity | -| `x_session_id` | Request | Underscore variant also accepted (direct HTTP) | -| `Idempotency-Key` | Request | Dedup key (5s window) | -| `X-Request-Id` | Request | Alternative dedup key | -| `X-OmniRoute-Cache` | Response | `HIT` or `MISS` (non-streaming) | -| `X-OmniRoute-Idempotent` | Response | `true` if deduplicated | -| `X-OmniRoute-Progress` | Response | `enabled` if progress tracking on | -| `X-OmniRoute-Session-Id` | Response | Effective session ID used by OmniRoute | - -> Nginx note: if you rely on underscore headers (for example `x_session_id`), enable `underscores_in_headers on;`. +| Başlık | Yön | Açıklama | +| ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `X-OmniRoute-No-Cache` | İstek | Önbelleği atlamak için `true` ayarlayın | +| `x-omniroute-no-memory` | İstek | Bu istek için bellek ve yetenek enjeksiyonunu atlamak için `true` ayarlayın | +| `X-OmniRoute-Progress` | İstek | İlerleme olayları için `true` ayarlayın | +| `X-Session-Id` | İstek | Harici oturum yakınlığı için yapışkan oturum anahtarı | +| `Idempotency-Key` | İstek | Tekilleştirme anahtarı (5 saniyelik pencere) | +| `X-OmniRoute-Cache` | Yanıt | `HIT` veya `MISS` (akışsız modda) | +| `X-OmniRoute-Idempotent` | Yanıt | İstek tekilleştirilmişse `true` | +| `X-OmniRoute-Version` | Yanıt | OmniRoute derleme sürümü (her zaman bulunur) | +| `X-OmniRoute-Decision` | Yanıt | Yönlendirme izi: `strategy=; provider=; latency_ms=` | --- -## Embeddings +## Gömme (Embeddings) ```bash POST /v1/embeddings @@ -65,21 +76,14 @@ Authorization: Bearer your-api-key Content-Type: application/json { - "model": "nebius/Qwen/Qwen3-Embedding-8B", - "input": "The food was delicious" + "model": "text-embedding-3-small", + "input": "Vektör haline getirilecek metin" } ``` -Available providers: Nebius, OpenAI, Mistral, Together AI, Fireworks, NVIDIA, **OpenRouter**, **GitHub Models**. - -```bash -# List all embedding models -GET /v1/embeddings -``` - --- -## Image Generation +## Görsel Üretimi (Image Generation) ```bash POST /v1/images/generations @@ -87,383 +91,33 @@ Authorization: Bearer your-api-key Content-Type: application/json { - "model": "openai/gpt-image-2", - "prompt": "A beautiful sunset over mountains", + "prompt": "Güneş batarken fütüristik bir şehir", + "n": 1, "size": "1024x1024" } ``` -Available providers: OpenAI (GPT Image 2), xAI (Grok Image), Together AI (FLUX), Fireworks AI, Nebius (FLUX), Hyperbolic, NanoBanana, **OpenRouter**, SD WebUI (local), ComfyUI (local). - -```bash -# List all image models -GET /v1/images/generations -``` - --- -## List Models +## Arama API'si (Search API) ```bash -GET /v1/models +POST /v1/search Authorization: Bearer your-api-key - -→ Returns all chat, embedding, and image models + combos in OpenAI format -``` - ---- - -## Compatibility Endpoints - -| Method | Path | Format | -| ------ | --------------------------- | ---------------------- | -| POST | `/v1/chat/completions` | OpenAI | -| POST | `/v1/messages` | Anthropic | -| POST | `/v1/responses` | OpenAI Responses | -| POST | `/v1/embeddings` | OpenAI | -| POST | `/v1/images/generations` | OpenAI | -| GET | `/v1/models` | OpenAI | -| POST | `/v1/messages/count_tokens` | Anthropic | -| GET | `/v1beta/models` | Gemini | -| POST | `/v1beta/models/{...path}` | Gemini generateContent | -| POST | `/v1/api/chat` | Ollama | - -### Dedicated Provider Routes - -```bash -POST /v1/providers/{provider}/chat/completions -POST /v1/providers/{provider}/embeddings -POST /v1/providers/{provider}/images/generations -``` - -The provider prefix is auto-added if missing. Mismatched models return `400`. - ---- - -## Semantic Cache - -```bash -# Get cache stats -GET /api/cache/stats - -# Clear all caches -DELETE /api/cache/stats -``` - -Response example: - -```json -{ - "semanticCache": { - "memorySize": 42, - "memoryMaxSize": 500, - "dbSize": 128, - "hitRate": 0.65 - }, - "idempotency": { - "activeKeys": 3, - "windowMs": 5000 - } -} -``` - ---- - -## Dashboard & Management - -### Authentication - -| Endpoint | Method | Description | -| ----------------------------- | ------- | --------------------- | -| `/api/auth/login` | POST | Login | -| `/api/auth/logout` | POST | Logout | -| `/api/settings/require-login` | GET/PUT | Toggle login required | - -### Provider Management - -| Endpoint | Method | Description | -| ---------------------------- | --------------------- | ---------------------------------------------- | -| `/api/providers` | GET/POST | List / create providers | -| `/api/providers/[id]` | GET/PUT/DELETE | Manage a provider | -| `/api/providers/[id]/test` | POST | Test provider connection | -| `/api/providers/[id]/models` | GET | List provider models | -| `/api/providers/validate` | POST | Validate provider config | -| `/api/provider-nodes*` | Various | Provider node management | -| `/api/provider-models` | GET/POST/PATCH/DELETE | Custom models (add, update, hide/show, delete) | - -### OAuth Flows - -| Endpoint | Method | Description | -| -------------------------------- | ------- | ----------------------- | -| `/api/oauth/[provider]/[action]` | Various | Provider-specific OAuth | - -### Routing & Config - -| Endpoint | Method | Description | -| --------------------- | -------- | ----------------------------- | -| `/api/models/alias` | GET/POST | Model aliases | -| `/api/models/catalog` | GET | All models by provider + type | -| `/api/combos*` | Various | Combo management | -| `/api/keys*` | Various | API key management | -| `/api/pricing` | GET | Model pricing | - -### Usage & Analytics - -| Endpoint | Method | Description | -| --------------------------- | ------ | -------------------- | -| `/api/usage/history` | GET | Usage history | -| `/api/usage/logs` | GET | Usage logs | -| `/api/usage/request-logs` | GET | Request-level logs | -| `/api/usage/[connectionId]` | GET | Per-connection usage | - -### Settings - -| Endpoint | Method | Description | -| ------------------------------- | ------------- | ---------------------- | -| `/api/settings` | GET/PUT/PATCH | General settings | -| `/api/settings/proxy` | GET/PUT | Network proxy config | -| `/api/settings/proxy/test` | POST | Test proxy connection | -| `/api/settings/ip-filter` | GET/PUT | IP allowlist/blocklist | -| `/api/settings/thinking-budget` | GET/PUT | Reasoning token budget | -| `/api/settings/system-prompt` | GET/PUT | Global system prompt | - -### Monitoring - -| Endpoint | Method | Description | -| ------------------------ | ---------- | ---------------------------------------------------------------------------------------------------- | -| `/api/sessions` | GET | Active session tracking | -| `/api/rate-limits` | GET | Per-account rate limits | -| `/api/monitoring/health` | GET | Health check + provider summary (`catalogCount`, `configuredCount`, `activeCount`, `monitoredCount`) | -| `/api/cache/stats` | GET/DELETE | Cache stats / clear | - -### Backup & Export/Import - -| Endpoint | Method | Description | -| --------------------------- | ------ | --------------------------------------- | -| `/api/db-backups` | GET | List available backups | -| `/api/db-backups` | PUT | Create a manual backup | -| `/api/db-backups` | POST | Restore from a specific backup | -| `/api/db-backups/export` | GET | Download database as .sqlite file | -| `/api/db-backups/import` | POST | Upload .sqlite file to replace database | -| `/api/db-backups/exportAll` | GET | Download full backup as .tar.gz archive | - -### Cloud Sync - -| Endpoint | Method | Description | -| ---------------------- | ------- | --------------------- | -| `/api/sync/cloud` | Various | Cloud sync operations | -| `/api/sync/initialize` | POST | Initialize sync | -| `/api/cloud/*` | Various | Cloud management | - -### Tunnels - -| Endpoint | Method | Description | -| -------------------------- | ------ | ----------------------------------------------------------------------- | -| `/api/tunnels/cloudflared` | GET | Read Cloudflare Quick Tunnel install/runtime status for the dashboard | -| `/api/tunnels/cloudflared` | POST | Enable or disable the Cloudflare Quick Tunnel (`action=enable/disable`) | - -### CLI Tools - -| Endpoint | Method | Description | -| ---------------------------------- | ------ | ------------------- | -| `/api/cli-tools/claude-settings` | GET | Claude CLI status | -| `/api/cli-tools/codex-settings` | GET | Codex CLI status | -| `/api/cli-tools/droid-settings` | GET | Droid CLI status | -| `/api/cli-tools/openclaw-settings` | GET | OpenClaw CLI status | -| `/api/cli-tools/runtime/[toolId]` | GET | Generic CLI runtime | - -CLI responses include: `installed`, `runnable`, `command`, `commandPath`, `runtimeMode`, `reason`. - -### ACP Agents - -| Endpoint | Method | Description | -| ----------------- | ------ | -------------------------------------------------------- | -| `/api/acp/agents` | GET | List all detected agents (built-in + custom) with status | -| `/api/acp/agents` | POST | Add custom agent or refresh detection cache | -| `/api/acp/agents` | DELETE | Remove a custom agent by `id` query param | - -GET response includes `agents[]` (id, name, binary, version, installed, protocol, isCustom) and `summary` (total, installed, notFound, builtIn, custom). - -### Resilience & Rate Limits - -| Endpoint | Method | Description | -| ----------------------- | --------- | ---------------------------------------------------------------------------------- | -| `/api/resilience` | GET/PATCH | Get/update request queue, connection cooldown, provider breaker, and wait settings | -| `/api/resilience/reset` | POST | Reset provider circuit breakers | -| `/api/rate-limits` | GET | Per-account rate limit status | -| `/api/rate-limit` | GET | Global rate limit configuration | - -### Evals - -| Endpoint | Method | Description | -| ------------ | -------- | --------------------------------- | -| `/api/evals` | GET/POST | List eval suites / run evaluation | - -### Policies - -| Endpoint | Method | Description | -| --------------- | --------------- | ----------------------- | -| `/api/policies` | GET/POST/DELETE | Manage routing policies | - -### Compliance - -| Endpoint | Method | Description | -| --------------------------- | ------ | ----------------------------- | -| `/api/compliance/audit-log` | GET | Compliance audit log (last N) | - -### v1beta (Gemini-Compatible) - -| Endpoint | Method | Description | -| -------------------------- | ------ | --------------------------------- | -| `/v1beta/models` | GET | List models in Gemini format | -| `/v1beta/models/{...path}` | POST | Gemini `generateContent` endpoint | - -These endpoints mirror Gemini's API format for clients that expect native Gemini SDK compatibility. - -### Internal / System APIs - -| Endpoint | Method | Description | -| ------------------------ | ------ | ---------------------------------------------------- | -| `/api/init` | GET | Application initialization check (used on first run) | -| `/api/tags` | GET | Ollama-compatible model tags (for Ollama clients) | -| `/api/restart` | POST | Trigger graceful server restart | -| `/api/shutdown` | POST | Trigger graceful server shutdown | -| `/api/system/env/repair` | POST | Repair OAuth provider environment variables | -| `/api/system-info` | GET | Generate system diagnostics report | - -> **Note:** These endpoints are used internally by the system or for Ollama client compatibility. They are not typically called by end users. - -### OAuth Environment Repair _(v3.6.1+)_ - -```bash -POST /api/system/env/repair Content-Type: application/json { - "provider": "claude-code" -} -``` - -Repairs missing or corrupted OAuth environment variables for a specific provider. Returns: - -```json -{ - "success": true, - "repaired": ["CLAUDE_CODE_OAUTH_CLIENT_ID", "CLAUDE_CODE_OAUTH_CLIENT_SECRET"], - "backupPath": "/home/user/.omniroute/backups/env-repair-2026-04-11.bak" + "query": "OmniRoute AI gateway nedir?", + "provider": "perplexity" } ``` --- -## Audio Transcription +## Uyumluluk Uç Noktaları -```bash -POST /v1/audio/transcriptions -Authorization: Bearer your-api-key -Content-Type: multipart/form-data -``` - -Transcribe audio files using Deepgram or AssemblyAI. - -**Request:** - -```bash -curl -X POST http://localhost:20128/v1/audio/transcriptions \ - -H "Authorization: Bearer your-api-key" \ - -F "file=@recording.mp3" \ - -F "model=deepgram/nova-3" -``` - -**Response:** - -```json -{ - "text": "Hello, this is the transcribed audio content.", - "task": "transcribe", - "language": "en", - "duration": 12.5 -} -``` - -**Supported providers:** `deepgram/nova-3`, `assemblyai/best`. - -**Supported formats:** `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`. - ---- - -## Ollama Compatibility - -For clients that use Ollama's API format: - -```bash -# Chat endpoint (Ollama format) -POST /v1/api/chat - -# Model listing (Ollama format) -GET /api/tags -``` - -Requests are automatically translated between Ollama and internal formats. - ---- - -## Telemetry - -```bash -# Get latency telemetry summary (p50/p95/p99 per provider) -GET /api/telemetry/summary -``` - -**Response:** - -```json -{ - "providers": { - "claudeCode": { "p50": 245, "p95": 890, "p99": 1200, "count": 150 }, - "github": { "p50": 180, "p95": 620, "p99": 950, "count": 320 } - } -} -``` - ---- - -## Budget - -```bash -# Get budget status for all API keys -GET /api/usage/budget - -# Set or update a budget -POST /api/usage/budget -Content-Type: application/json - -{ - "keyId": "key-123", - "limit": 50.00, - "period": "monthly" -} -``` - -## Request Processing - -1. Client sends request to `/v1/*` -2. Route handler calls `handleChat`, `handleEmbedding`, `handleAudioTranscription`, or `handleImageGeneration` -3. Model is resolved (direct provider/model or alias/combo) -4. Credentials selected from local DB with account availability filtering -5. For chat: `handleChatCore` — format detection, translation, cache check, idempotency check -6. Provider executor sends upstream request -7. Response translated back to client format (chat) or returned as-is (embeddings/images/audio) -8. Usage/logging recorded -9. Fallback applies on errors according to combo rules - -Full architecture reference: [`ARCHITECTURE.md`](ARCHITECTURE.md) - ---- - -## Authentication - -- Dashboard routes (`/dashboard/*`) use `auth_token` cookie -- Login uses saved password hash; fallback to `INITIAL_PASSWORD` -- `requireLogin` toggleable via `/api/settings/require-login` -- `/v1/*` routes optionally require Bearer API key when `REQUIRE_API_KEY=true` +- **OpenAI Responses:** `POST /v1/responses` +- **Anthropic Messages:** `POST /v1/messages` +- **Gemini Native:** `POST /v1beta/models/{model}:generateContent` +- **Ollama Chat:** `POST /v1/api/chat` +- **Token Sayımı:** `POST /v1/messages/count_tokens` diff --git a/docs/i18n/tr/docs/reference/CLI-TOOLS.md b/docs/i18n/tr/docs/reference/CLI-TOOLS.md index 2ac587cc60..e204c1d966 100644 --- a/docs/i18n/tr/docs/reference/CLI-TOOLS.md +++ b/docs/i18n/tr/docs/reference/CLI-TOOLS.md @@ -1,63 +1,50 @@ -# CLI-TOOLS (Türkçe) - -🌐 **Languages:** 🇺🇸 [English](../../../../reference/CLI-TOOLS.md) · 🇸🇦 [ar](../../../ar/docs/reference/CLI-TOOLS.md) · 🇦🇿 [az](../../../az/docs/reference/CLI-TOOLS.md) · 🇧🇬 [bg](../../../bg/docs/reference/CLI-TOOLS.md) · 🇧🇩 [bn](../../../bn/docs/reference/CLI-TOOLS.md) · 🇨🇿 [cs](../../../cs/docs/reference/CLI-TOOLS.md) · 🇩🇰 [da](../../../da/docs/reference/CLI-TOOLS.md) · 🇩🇪 [de](../../../de/docs/reference/CLI-TOOLS.md) · 🇪🇸 [es](../../../es/docs/reference/CLI-TOOLS.md) · 🇮🇷 [fa](../../../fa/docs/reference/CLI-TOOLS.md) · 🇫🇮 [fi](../../../fi/docs/reference/CLI-TOOLS.md) · 🇫🇷 [fr](../../../fr/docs/reference/CLI-TOOLS.md) · 🇮🇳 [gu](../../../gu/docs/reference/CLI-TOOLS.md) · 🇮🇱 [he](../../../he/docs/reference/CLI-TOOLS.md) · 🇮🇳 [hi](../../../hi/docs/reference/CLI-TOOLS.md) · 🇭🇺 [hu](../../../hu/docs/reference/CLI-TOOLS.md) · 🇮🇩 [id](../../../id/docs/reference/CLI-TOOLS.md) · 🇮🇩 [in](../../../in/docs/reference/CLI-TOOLS.md) · 🇮🇹 [it](../../../it/docs/reference/CLI-TOOLS.md) · 🇯🇵 [ja](../../../ja/docs/reference/CLI-TOOLS.md) · 🇰🇷 [ko](../../../ko/docs/reference/CLI-TOOLS.md) · 🇮🇳 [mr](../../../mr/docs/reference/CLI-TOOLS.md) · 🇲🇾 [ms](../../../ms/docs/reference/CLI-TOOLS.md) · 🇳🇱 [nl](../../../nl/docs/reference/CLI-TOOLS.md) · 🇳🇴 [no](../../../no/docs/reference/CLI-TOOLS.md) · 🇵🇭 [phi](../../../phi/docs/reference/CLI-TOOLS.md) · 🇵🇱 [pl](../../../pl/docs/reference/CLI-TOOLS.md) · 🇵🇹 [pt](../../../pt/docs/reference/CLI-TOOLS.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/reference/CLI-TOOLS.md) · 🇷🇴 [ro](../../../ro/docs/reference/CLI-TOOLS.md) · 🇷🇺 [ru](../../../ru/docs/reference/CLI-TOOLS.md) · 🇸🇰 [sk](../../../sk/docs/reference/CLI-TOOLS.md) · 🇸🇪 [sv](../../../sv/docs/reference/CLI-TOOLS.md) · 🇰🇪 [sw](../../../sw/docs/reference/CLI-TOOLS.md) · 🇮🇳 [ta](../../../ta/docs/reference/CLI-TOOLS.md) · 🇮🇳 [te](../../../te/docs/reference/CLI-TOOLS.md) · 🇹🇭 [th](../../../th/docs/reference/CLI-TOOLS.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/reference/CLI-TOOLS.md) · 🇵🇰 [ur](../../../ur/docs/reference/CLI-TOOLS.md) · 🇻🇳 [vi](../../../vi/docs/reference/CLI-TOOLS.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/reference/CLI-TOOLS.md) · 🇹🇼 [zh-TW](../../../zh-TW/docs/reference/CLI-TOOLS.md) - --- - ---- - title: "CLI Araçları — OmniRoute" version: 3.8.50 -lastUpdated: 2026-08-18 +lastUpdated: 2026-08-23 --- -# CLI Araçları — OmniRoute +# CLI Araçları — OmniRoute (Türkçe) -Son güncelleme: 2026-08-18 - -OmniRoute, üç özel kontrol paneli sayfasında dağıtılmış üç kategori CLI aracı ile entegre olur: - -| Sayfa | Rota | Kavram | Sayı | -| ---------------- | ----------------------- | ------------------------------------------------------------------------------------- | --------------------- | -| **CLI Kodu** | `/dashboard/cli-code` | OmniRoute'a yönlendirdiğiniz kodlama araçları (Müşteri → CLI → OmniRoute → Sağlayıcı) | 26 | -| **CLI Ajanları** | `/dashboard/cli-agents` | OmniRoute'a yönlendirdiğiniz otonom ajanlar (aynı akış, daha geniş kapsam) | 8 | -| **ACP Ajanları** | `/dashboard/acp-agents` | OmniRoute'un stdio/ACP aracılığıyla arka planda oluşturduğu CLIs (ters akış) | kayıt defterine bakın | - -Eski rotalar 308 ile yönlendirilir: `/dashboard/cli-tools` → `/dashboard/cli-code`, `/dashboard/agents` → `/dashboard/acp-agents`. +🌐 **Languages:** 🇺🇸 [English](../../../../docs/reference/CLI-TOOLS.md) · 🇸🇦 [ar](../../ar/docs/reference/CLI-TOOLS.md) · 🇧🇬 [bg](../../bg/docs/reference/CLI-TOOLS.md) · 🇧🇩 [bn](../../bn/docs/reference/CLI-TOOLS.md) · 🇨🇿 [cs](../../cs/docs/reference/CLI-TOOLS.md) · 🇩🇰 [da](../../da/docs/reference/CLI-TOOLS.md) · 🇩🇪 [de](../../de/docs/reference/CLI-TOOLS.md) · 🇪🇸 [es](../../es/docs/reference/CLI-TOOLS.md) · 🇮🇷 [fa](../../fa/docs/reference/CLI-TOOLS.md) · 🇫🇮 [fi](../../fi/docs/reference/CLI-TOOLS.md) · 🇫🇷 [fr](../../fr/docs/reference/CLI-TOOLS.md) · 🇮🇳 [gu](../../gu/docs/reference/CLI-TOOLS.md) · 🇮🇱 [he](../../he/docs/reference/CLI-TOOLS.md) · 🇮🇳 [hi](../../hi/docs/reference/CLI-TOOLS.md) · 🇭🇺 [hu](../../hu/docs/reference/CLI-TOOLS.md) · 🇮🇩 [id](../../id/docs/reference/CLI-TOOLS.md) · 🇮🇹 [it](../../it/docs/reference/CLI-TOOLS.md) · 🇯🇵 [ja](../../ja/docs/reference/CLI-TOOLS.md) · 🇰🇷 [ko](../../ko/docs/reference/CLI-TOOLS.md) · 🇮🇳 [mr](../../mr/docs/reference/CLI-TOOLS.md) · 🇲🇾 [ms](../../ms/docs/reference/CLI-TOOLS.md) · 🇳🇱 [nl](../../nl/docs/reference/CLI-TOOLS.md) · 🇳🇴 [no](../../no/docs/reference/CLI-TOOLS.md) · 🇵🇭 [phi](../../phi/docs/reference/CLI-TOOLS.md) · 🇵🇱 [pl](../../pl/docs/reference/CLI-TOOLS.md) · 🇵🇹 [pt](../../pt/docs/reference/CLI-TOOLS.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/reference/CLI-TOOLS.md) · 🇷🇴 [ro](../../ro/docs/reference/CLI-TOOLS.md) · 🇷🇺 [ru](../../ru/docs/reference/CLI-TOOLS.md) · 🇸🇰 [sk](../../sk/docs/reference/CLI-TOOLS.md) · 🇸🇪 [sv](../../sv/docs/reference/CLI-TOOLS.md) · 🇰🇪 [sw](../../sw/docs/reference/CLI-TOOLS.md) · 🇮🇳 [ta](../../ta/docs/reference/CLI-TOOLS.md) · 🇮🇳 [te](../../te/docs/reference/CLI-TOOLS.md) · 🇹🇭 [th](../../th/docs/reference/CLI-TOOLS.md) · 🇹🇷 [tr](../../tr/docs/reference/CLI-TOOLS.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/reference/CLI-TOOLS.md) · 🇵🇰 [ur](../../ur/docs/reference/CLI-TOOLS.md) · 🇻🇳 [vi](../../vi/docs/reference/CLI-TOOLS.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/reference/CLI-TOOLS.md) --- -## Nasıl Çalışır +OmniRoute, üç özel pano sayfasına yayılmış üç CLI araçları kategorisiyle entegre olur: + +| Sayfa | Rota | Konsept | Sayı | +| -------------- | ----------------------- | -------------------------------------------------------------------------- | ------------ | +| **CLI Code's** | `/dashboard/cli-code` | OmniRoute'a yönlendirdiğiniz kodlama araçları (İstemci → CLI → OmniRoute → Sağlayıcı) | 26 | +| **CLI Ajanları**| `/dashboard/cli-agents` | OmniRoute'a yönlendirdiğiniz özerk ajanlar (aynı akış, daha geniş kapsam) | 8 | +| **ACP Ajanları**| `/dashboard/acp-agents` | OmniRoute'un stdio/ACP ile başlattığı CLI'lar (ters başlatma akışı) | bkz. kayıt | + +--- + +## Nasıl Çalışır? ``` -CLI Kodu / CLI Ajanları (tüketim akışı): -Claude / Codex / OpenCode / Cline / KiloCode / Continue / Hermes Ajanı / Goose / ... +CLI Araçları (Tüketim Akışı): +Claude / Codex / OpenCode / Cline / KiloCode / Continue / Hermes / Goose / ... │ - ▼ (hepsi OmniRoute'a yönlendirir) - http://YOUR_SERVER:20128/v1 + ▼ (hepsi OmniRoute'a yönlendirilir) + http://SUNUCUNUZ:20128/v1 │ ▼ (OmniRoute doğru sağlayıcıya yönlendirir) Anthropic / OpenAI / Gemini / DeepSeek / Groq / Mistral / ... - -ACP Ajanları (ters oluşturma akışı): - Müşteri isteği → OmniRoute → stdio/ACP aracılığıyla CLI oluşturur → yanıt ``` -**Faydalar:** +**Avantajlar:** - Tüm araçları yönetmek için tek bir API anahtarı -- Kontrol panelindeki tüm CLIs arasında maliyet takibi -- Her aracı yeniden yapılandırmadan model değiştirme -- Yerel ve uzaktan sunucularda (VPS, Docker, Akamai, Cloudflare Tüneli) çalışır +- Panoda tüm CLI'lar genelinde maliyet takibi +- Her aracı yeniden yapılandırmadan anında model değiştirme +- Yerel ortamda ve uzak sunucularda (VPS, Docker, Cloudflare Tunnel) sorunsuz çalışma --- ## `setup-*` ile Otomatik Yapılandırma -Her aracın yapılandırmasını elle yazmak zorunda değilsiniz. OmniRoute, çalışan bir -OmniRoute'tan (yerel veya uzaktan) **canlı** model kataloğunu okuyan ve aracın kendi -yapılandırmasını makinenize yazan her desteklenen CLI için bir `setup-*` -komutu gönderir: +Her aracın yapılandırmasını elle yazmanıza gerek yoktur: ```bash omniroute setup-codex omniroute setup-claude omniroute setup-opencode @@ -65,687 +52,3 @@ omniroute setup-cline omniroute setup-kilo omniroute setup-contin omniroute setup-cursor omniroute setup-roo omniroute setup-crush omniroute setup-goose omniroute setup-qwen omniroute setup-aider ``` - -Her biri `--remote --api-key ` (uzaktaki bir OmniRoute'a karşı yerel bir aracı yapılandırma), `--dry-run` (yazmadan önizleme) ve `--port` alır. Model otomatik keşfi olmayan araçlar (Cline, Kilo, Roo, Goose, Aider, Qwen) `--model ` (ve etkileşimsiz çalıştırmalar için `--yes`) alır. Doğru ortamın enjekte edildiği ve hiç yapılandırma yazılmadan bir CLI başlatmak için, genel `omniroute run ` başlatıcısını kullanın (claude, codex, aider, goose, opencode, qwen, gemini — hedefler ve takma adlar `bin/cli/cli-manifest.mjs`'den gelir); eski her araç için başlatıcılar `omniroute launch` (Claude Kodu) ve `omniroute launch-codex` (Codex) kullanılmaya devam eder. Gemini CLI yalnızca başlatma içindir: bir `omniroute run` hedefidir ancak `setup-*`/`configure` tarifi yoktur. - -> **Tam referans:** her komutun ne yazdığı, her bayrak, yerel ve uzaktan, ve hangi araçların `/v1` son ekine ihtiyaç duyduğuna dair ana tablo **[CLI Entegrasyonları](../guides/CLI-INTEGRATIONS.md)**'nda bulunmaktadır. - -### Bir konteyner içinde bunları çalıştırma - -OmniRoute konteyneri içinde yürütülen bir `setup-*` komutu, konteynerin kendi evine yazar, bu da hiçbir ana CLI tarafından okunmaz ve konteyner ile birlikte kaybolur. OmniRoute bunu algılar ve yazmak yerine talimatlarla `2` ile çıkar. İki desteklenen yol — CLI'yi ana makinede kurmak ve konteynere `omniroute connect` yapmak veya yapılandırma dizinlerini bağlamak ve `CLI_CONFIG_HOME` ayarlamaktır (compose `host` profili). Her `setup-*` komutu, ayrıca `omniroute configure` ve `omniroute config set`, konteynerin kendi CLIs'ini yapılandırmanın gerçekten ne anlama geldiği durumunda `--allow-container-write` alır; `OMNIROUTE_ALLOW_CONTAINER_CONFIG_WRITE=true` sunucu için aynı şeyi yapar. Bakınız -[Docker Kılavuzu → Ana CLI araçlarını yapılandırma](../guides/DOCKER_GUIDE.md#configuring-host-cli-tools-when-omniroute-runs-in-docker). - -Kontrol panelinin **uygulama uç noktası** (`POST /api/cli-tools/apply`) aynı korumayı uygular: bir konteynerde, hedefi ana makineden bağlanmamış bir yazma işlemi **`422`** ile `containerEphemeralTarget: true` yanıtını verir, güvenli hata metni ve — ana makine tarifi olan araçlar için (claude, codex, opencode, cline, kilo, continue) — ana makinede çalıştırılacak bir `hostSetupCommand` (örneğin `omniroute setup-opencode`); hiçbir şey yazılmaz. `dryRun: true` konteyner modunda çalışmaya devam eder ve diskle temas etmeden üretilen içeriği + hedef yolunu döndürür, böylece kontrol panelinden önizleme yapabilir ve ana makinede uygulayabilirsiniz. Bu davranış kasıtlıdır ve `tests/unit/api/cli-tools/apply-container-guard.test.ts` ile geriye dönük olarak korunmaktadır — asla bir 422'yi korumayı kaldırarak "düzeltmeyin". - ---- - -## Gerçek Kaynağı - -Birleşik katalog `src/shared/constants/cliTools.ts` içinde `CLI_TOOLS: Record` olarak yer almaktadır. - -Her bir girişin bu alanları vardır (tanımlı `src/shared/schemas/cliCatalog.ts` içinde): - -| Alan | Tür | Açıklama | -| ----------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------ | -| `category` | `"code" \| "agent"` | Araç hangi sayfada görünür | -| `vendor` | `string` | Araç kaynağı ("Anthropic", "OSS (P. Gauthier)") | -| `acpSpawnable` | `boolean` | ACP Ajanı olarak da kullanılabilir (rozet gösterilir) | -| `baseUrlSupport` | `"full" \| "partial" \| "none"` | Özel uç nokta destek seviyesi. `"none"` = MITM backlog | -| `configType` | `"env" \| "custom" \| "guide" \| "custom-builder" \| "mitm"` | Yapılandırma mekanizması | -| `id`, `name`, `color`, `description`, `docsUrl` | standart | Temel görüntüleme alanları | - -`baseUrlSupport: "none"` olan girişler, gösterim sayfalarında **gösterilmez** — bunlar plan 11 için MITM backlog'unda kaydedilmiştir (bkz. `_tasks/features-v3.8.6/refactorpages/_orchestration/_plan11-mitm-backlog.md`). - -### Yetenek katmanları (kataloglu × tespit edilebilir × yapılandırılabilir × başlatılabilir) - -Her kataloglu araç tespit edilebilir, yapılandırılabilir veya başlatılabilir değildir. Her katmanın bir -belirleyici kaynağı vardır ve bir drift testi bunları uyumlu tutar: - -| Katman | Anlamı | Belirtilen | -| ---------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------ | -| **Kataloglu** | Gösterim katalogunda görünür (isim, satıcı, belgeler, yapılandırma türü) | `src/shared/constants/cliTools.ts` (`CLI_TOOLS`) | -| **Tespit Edilebilir** | İkili/yapılandırma tespiti, sağlık kontrolleri, yapılandırma yolları | `src/shared/services/cliRuntime.ts` (`CLI_TOOLS` çalışma kataloğu) | -| **Yapılandırılabilir** | `omniroute configure ` tarafından desteklenir (kurulum tarifi mevcut) | `bin/cli/cli-manifest.mjs` (`configure: true`) | -| **Başlatılabilir** | `omniroute run ` tarafından desteklenir (env/args enjeksiyonu tanımlı) | `bin/cli/cli-manifest.mjs` (`run: true`) | - -`bin/cli/cli-manifest.mjs`, CLI komut yüzeyleri için kanonik yürütülebilir manifestodur: `run`, `configure` ve shell-tamamlayıcı jeneratörleri tüm hedef listelerini, takma ad çözümlemelerini (örneğin `kilocode`/`kilo-code`/`kilo_cli` → `kilo`) ve `--model` bayrağı bağlantılarını buradan alır. Drift koruma -`tests/unit/cli/cli-manifest-drift.test.ts`, manifestonun, çalışma -kataloğunun, UI kataloğunun ve her tüketici yüzeyinin senkron kalmasını sağlar — bir yüzeye eklenen bir hedef, diğerleri olmadan eklenirse, sessizce drift etmek yerine test grubunu başarısız kılar. - -## 1. CLI Kod Kataloğu (26 araç) - -`/dashboard/cli-code` içinde yer alan tüm araçlar. `baseUrlSupport: none` olanlar, özel bir temel URL yerine MITM veya manuel bir kılavuz aracılığıyla bağlanmıştır: - -| id | isim | satıcı | baseUrlSupport | configType | acpSpawnable | -| ------------ | ------------------------- | ----------------------------- | -------------- | -------------- | ------------ | -| claude | Claude Kodu | Anthropic | full | env | true | -| codex | OpenAI Codex CLI | OpenAI | full | custom | true | -| zcode | ZCode (GLM Kodlama Planı) | Z.ai | none | custom | false | -| cline | Cline | OSS (eski-Claude Geliştirici) | full | custom | true | -| kilo | Kilo Kodu | Kilo-Org | full | custom | false | -| roo | Roo Kodu | Roo (OSS) | full | guide | false | -| continue | Devam Et | continue.dev | full | guide | false | -| aider | Aider | OSS (P. Gauthier) | full | guide | true | -| forge | ForgeCode | Antinomy HQ | full | custom | true | -| jcode | jcode | 1jehuang (OSS) | full | custom | false | -| deepseek-tui | DeepSeek TUI | Hunter Bown (OSS) | full | custom | false | -| codewhale | CodeWhale | Hmbown (OSS) | full | custom | false | -| opencode | OpenCode | Anomaly (eski-SST) | full | guide | true | -| droid | Factory Droid | Factory AI | partial | guide | false | -| copilot | GitHub Copilot CLI | GitHub/MS | full | custom | false | -| cursor-cli | Cursor CLI | Anysphere | partial | guide | true | -| smelt | Smelt | leonardcser (OSS) | full | custom | false | -| pi | Pi (pi-coding-agent) | M. Zechner (OSS) | full | custom | false | -| grok-build | Grok Build | xAI | full | custom | false | -| crush | Crush | OSS (Charm) | full | custom | false | -| qwen | Qwen Kodu | Alibaba | full | guide | true | -| cursor | Cursor | Anysphere | none | guide | false | -| antigravity | Antigravity | Google | none | mitm | false | -| hermes | Hermes | Nous Research | none | guide | false | -| kiro | Kiro AI | Amazon | none | mitm | false | -| custom | Özel CLI | — | full | custom-builder | false | - -`baseUrlSupport: "partial"` olan araçlar, gösterge paneli kartında "⚠ Temel URL kısmi" rozetini gösterir. - -## 2. CLI Ajanları Kataloğu (8 araç) - -`/dashboard/cli-agents` içinde görünen otonom ajanlar: - -| id | isim | satıcı | baseUrlDestek | acpSpawnable | -| ------------ | ---------------- | ------------------------ | ------------- | ------------ | -| hermes-agent | Hermes Ajanı | Nous Research | tam | false | -| openclaw | OpenClaw | OSS (P. Steinberger) | tam | true | -| goose | Goose | Block / Linux Foundation | tam | true | -| interpreter | Open Interpreter | OSS | tam | true | -| warp | Warp AI | Warp Inc. | kısmi | true | -| agent-deck | Ajan Destesi | asheshgoplani (OSS) | tam | false | -| omp | Oh My Pi | OSS | tam | true | -| letta | Letta CLI | Letta | tam | false | - ---- - -## 3. ACP Ajanları (/dashboard/acp-agents) - -Bu sayfa (`/dashboard/agents`'dan yeniden adlandırılmıştır) OmniRoute'un stdio/ACP protokolü aracılığıyla **oluşturabileceği** arka uç yürütme motorlarını gösterir. Katalog, `src/lib/acp/registry.ts` içinde ayrı olarak korunmaktadır ve `CLI_TOOLS` ile **aynı değildir**. - ---- - -## 4. MITM Bekleme Listesi (dashboard'da gösterilmez) - -Aşağıdaki CLIs yerel olarak özel bir temel URL'yi desteklememektedir ve CLI Kodu veya CLI Ajanları sayfalarında **listelenmemiştir**. Plan 11'de MITM müdahalesi için adaylardır: - -| CLI | Sebep | -| ------------------- | -------------------------------------------------- | -| windsurf | BYOK, seçili Claude modelleri + kurumsal URL/token | -| amp | Kapalı ekosistem (Sourcegraph) | -| amazon-q / kiro-cli | AWS SSO kimlik doğrulama, özel URL yok | -| cowork | Anthropic Desktop, yapılandırılabilir uç nokta yok | - -Tam çapraz referans için `_tasks/features-v3.8.6/refactorpages/_orchestration/_plan11-mitm-backlog.md`'ye bakın. - ---- - -## 5. Batch Tespit API'si - -Tüm araç tespiti tek bir uç nokta üzerinden toplanmaktadır: - -**`GET /api/cli-tools/all-statuses`** - -- Yetki: `requireCliToolsAuth(request)` (diğer `/api/cli-tools/` yollarıyla aynı) -- Döner: `Record` (tip: `src/shared/types/cliBatchStatus.ts`) -- Strateji: Tüm araçlar üzerinde `Promise.all`, her araç için 5s zaman aşımı -- Önbellek: yapılandırma dosyası `mtime` ile indekslenmiş bellek içi LRU. mtime değiştiğinde önbellek geçersiz kılınır. Sunucu yeniden başlatıldığında sıfırlanır. - -Araç başına yanıt şekli: - -```ts -interface ToolBatchStatus { - detection: { - installed: boolean; - runnable: boolean; - version?: string; - command?: string; - commandPath?: string; - reason?: string; - }; - config: { - status: "configured" | "not_configured" | "not_installed" | "unknown" | "other"; - endpoint?: string | null; - lastConfiguredAt?: string | null; - }; - error?: string; // temizlenmiş, yığın izleri yok -} -``` - -## 6. Yeni Araçlar için Ayar İşleyicileri - -`configType: "custom"` olan yeni araçların özel ayar API yolları vardır: - -| Yol | Araç | -| ------------------------------------------- | -------------------------------------------------------------------------- | -| `POST /api/cli-tools/forge-settings` | ForgeCode (.forge.toml) | -| `POST /api/cli-tools/jcode-settings` | jcode (--base-url bayrağı) | -| `POST /api/cli-tools/deepseek-tui-settings` | DeepSeek TUI (OPENAI_BASE_URL, eski) | -| `POST /api/cli-tools/codewhale-settings` | CodeWhale (OPENAI_BASE_URL, birincil + eski `~/.deepseek` senkronizasyonu) | -| `POST /api/cli-tools/smelt-settings` | Smelt | -| `POST /api/cli-tools/pi-settings` | Pi kodlama aracı | -| `POST /api/cli-tools/grok-build-settings` | Grok Build (~/.grok/config.toml, `[model.omniroute]`) | -| `POST /api/cli-tools/qwen-settings` | Qwen Code (`~/.qwen/settings.json` + özel `.env` anahtarı) | - -Tüm yollar hata yanıtları için `sanitizeErrorMessage()` kullanır (Sert Kural #12). - ---- - -## 7. Gösterge Paneli Sayfaları Mimarisi - -### CLI Kodu (`/dashboard/cli-code`) - -- `src/app/(dashboard)/dashboard/cli-code/page.tsx` — sunucu bileşeni -- `src/app/(dashboard)/dashboard/cli-code/CliCodePageClient.tsx` — istemci ızgarası -- `src/app/(dashboard)/dashboard/cli-code/[id]/page.tsx` — araç detay sayfası -- `src/app/(dashboard)/dashboard/cli-code/components/` — 12 özel araç kartı + `ToolDetailClient.tsx` - -### CLI Ajanları (`/dashboard/cli-agents`) - -- `src/app/(dashboard)/dashboard/cli-agents/page.tsx` — sunucu bileşeni -- `src/app/(dashboard)/dashboard/cli-agents/CliAgentsPageClient.tsx` — istemci ızgarası -- `src/app/(dashboard)/dashboard/cli-agents/[id]/page.tsx` — `ToolDetailClient`'i yeniden kullanır - -### ACP Ajanları (`/dashboard/acp-agents`) - -- `src/app/(dashboard)/dashboard/acp-agents/page.tsx` — sunucu bileşeni ( `agents/`'dan taşındı) - -### Paylaşılan UI Bileşenleri (`src/shared/components/cli/`) - -| Dosya | Amaç | -| ----------------------- | ----------------------------------------------------- | -| `CliToolCard.tsx` | Akıllı durum kartı (tespit + yapılandırma + uç nokta) | -| `CliConceptCard.tsx` | Sayfa başına kavram açıklama kartı | -| `CliComparisonCard.tsx` | CLI türleri arasında üç sütunlu karşılaştırma | -| `BaseUrlSelect.tsx` | Uç nokta açılır menüsü (Yerel/Bulut/Özel) | -| `ApiKeySelect.tsx` | API anahtarı seçici | -| `ManualConfigModal.tsx` | Kopyalanabilir yapılandırma kesiti modali | - -### Paylaşılan Hook (`src/shared/hooks/cli/`) - -| Dosya | Amaç | -| ------------------------- | ----------------------------------------------------------------------- | -| `useToolBatchStatuses.ts` | `/api/cli-tools/all-statuses`'i alır, yükleme/yenileme durumunu yönetir | - -## 8. i18n - -Plan 14 F9'da eklenen yeni ad alanları: - -| Ad Alanı | Amaç | -| ----------- | --------------------------------------------------------------------------------------- | -| `cliCommon` | Paylaşılan metinler (kart etiketleri, kavram/kıyas metinleri, detay sayfası etiketleri) | -| `cliCode` | CLI Kodu sayfası metinleri | -| `cliAgents` | CLI Ajanları sayfası metinleri | -| `acpAgents` | ACP Ajanları sayfası metinleri | - -Tam PT-BR ve EN çevirileri sağlanmıştır. 39 diğer yerel ayar, `src/i18n/request.ts` içindeki ad alanı düzeyinde birleştirme ile otomatik olarak EN'ye geri döner. - ---- - -## 9. Hızlı Başlangıç - -### Adım 1 — OmniRoute API Anahtarı Alın - -1. `/dashboard/api-manager`'ı açın → **API Anahtarı Oluştur** -2. Bir isim verin (örn. `cli-tools`) ve tüm izinleri seçin -3. Anahtarı kopyalayın — aşağıdaki her CLI için buna ihtiyacınız olacak - -> Anahtarınız şöyle görünecek: `sk-xxxxxxxxxxxxxxxx-xxxxxxxxx` - ---- - -### Adım 2 — CLI Araçlarını Yükleyin - -Tüm npm tabanlı araçlar Node.js 22.22.2+ veya 24.x gerektirir: - -```bash -# Claude Code (Anthropic) -npm install -g @anthropic-ai/claude-code - -# OpenAI Codex -npm install -g @openai/codex - -# OpenCode -npm install -g opencode-ai - -# Cline -npm install -g cline - -# KiloCode -npm install -g kilocode - -# Qwen Code -npm install -g @qwen-code/qwen-code - -# Google Gemini CLI (launchable via `omniroute run gemini` → /v1beta surface) -npm install -g @google/gemini-cli - -# Aider -pip install aider-chat - -# Smelt -cargo install smelt # Rust tabanlı - -# Pi coding agent -# yükleme için https://github.com/zechnerj/pi-coding-agent adresine bakın - -# jcode -# yükleme için https://github.com/1jehuang/jcode adresine bakın -``` - ---- - -### Adım 3 — Dashboard Üzerinden Yapılandırın - -1. `http://localhost:20128/dashboard/cli-code` adresine gidin -2. Araçlar ızgarasında aracınızı bulun -3. Aracı detay sayfasını açmak için karta tıklayın -4. API anahtarınızı ve temel URL'yi seçin -5. **Yapılandırmayı Uygula**'ya tıklayın veya manuel yapılandırma parçasını kopyalayın - ---- - -### Adım 4 — Küresel Ortam Değişkenlerini Ayarlayın - -```bash -# OmniRoute Evrensel Uç Noktası -export OPENAI_BASE_URL="http://localhost:20128/v1" -export OPENAI_API_KEY="sk-your-omniroute-key" -export ANTHROPIC_BASE_URL="http://localhost:20128" -export ANTHROPIC_AUTH_TOKEN="sk-your-omniroute-key" -# Gemini CLI, KÖK'te GOOGLE_GEMINI_BASE_URL okur (SDK'sı /v1beta/... ekler) -export GOOGLE_GEMINI_BASE_URL="http://localhost:20128" -export GEMINI_API_KEY="sk-your-omniroute-key" -``` - -> **Uzak bir sunucu** için `localhost:20128`'i sunucu IP'si veya alan adı ile değiştirin, -> örn. `http://:20128`. - ---- - -### Adım 4 — Her Aracı Yapılandırın - -#### Claude Code - -```bash -# ~/.claude/settings.json oluşturun: -mkdir -p ~/.claude && cat > ~/.claude/settings.json << EOF -{ - "env": { - "ANTHROPIC_BASE_URL": "http://localhost:20128", - "ANTHROPIC_AUTH_TOKEN": "sk-your-omniroute-key" - } -} -EOF -``` - -Claude Code için birleşik Anthropic geçiş kökünü kullanın. Burada `/v1` eklemeyin. - -**Test:** `claude "merhaba de"` - ---- - -#### OpenAI Codex - -Modern Codex (v0.137+) yalnızca `~/.codex/config.toml` dosyasını okur — eski -`config.yaml`, miras npm CLI'ye aittir ve sessizce yok sayılır. API -anahtarı, dosya içinde asla değil, `OMNIROUTE_API_KEY` ortam değişkeninde (`env_key`) kalır: - -```bash -mkdir -p ~/.codex && cat > ~/.codex/config.toml << EOF -model_provider = "omniroute" - -[model_providers.omniroute] -name = "OmniRoute" -base_url = "http://localhost:20128/v1" -env_key = "OMNIROUTE_API_KEY" -requires_openai_auth = false -EOF -export OMNIROUTE_API_KEY="sk-your-omniroute-key" -``` - -Tam referans (profiller, `wire_api`, bağlam pencereleri): [CODEX-CLI-CONFIGURATION.md](../guides/CODEX-CLI-CONFIGURATION.md). - -**Test:** `codex "2+2 nedir?"` - ---- - -#### OpenCode - -```bash -mkdir -p ~/.config/opencode && cat > ~/.config/opencode/opencode.json << EOF -{ - "\$schema": "https://opencode.ai/config.json", - "provider": { - "omniroute": { - "npm": "@ai-sdk/openai-compatible", - "name": "OmniRoute", - "options": { - "baseURL": "http://localhost:20128/v1", - "apiKey": "sk-your-omniroute-key" - }, - "models": { - "claude-sonnet-4-5": { "name": "claude-sonnet-4-5" }, - "claude-sonnet-4-5-thinking": { "name": "claude-sonnet-4-5-thinking" }, - "gemini-3-flash": { "name": "gemini-3-flash" } - } - } - } -} -EOF -``` - -**Test:** `opencode` - -> Düşünme varyantlarını göndermek için `opencode run "prompt'iniz" --model omniroute/claude-sonnet-4-5-thinking --variant high` kullanın. - ---- - -#### Cline (CLI veya VS Code) - -**CLI modu:** - -```bash -mkdir -p ~/.cline/data && cat > ~/.cline/data/globalState.json << EOF -{ - "apiProvider": "openai", - "openAiBaseUrl": "http://localhost:20128/v1", - "openAiApiKey": "sk-your-omniroute-key" -} -EOF -``` - -**VS Code modu:** -Cline uzantı ayarları → API Sağlayıcı: `OpenAI Uyumluluğu` → Temel URL: `http://localhost:20128/v1` - -Ya da OmniRoute dashboard'unu kullanarak → **CLI Araçları → Cline → Yapılandırmayı Uygula**. - ---- - -#### KiloCode (CLI veya VS Code) - -**CLI modu:** - -```bash -kilocode --api-base http://localhost:20128/v1 --api-key sk-your-omniroute-key -``` - -**VS Code ayarları:** - -```json -{ - "kilo-code.openAiBaseUrl": "http://localhost:20128/v1", - "kilo-code.apiKey": "sk-your-omniroute-key" -} -``` - -Ya da OmniRoute dashboard'unu kullanarak → **CLI Araçları → KiloCode → Yapılandırmayı Uygula**. - ---- - -#### Continue (VS Code Uzantısı) - -`~/.continue/config.yaml` dosyasını düzenleyin: - -```yaml -models: - - name: OmniRoute - provider: openai - model: auto - apiBase: http://localhost:20128/v1 - apiKey: sk-your-omniroute-key - default: true -``` - -Düzenledikten sonra VS Code'u yeniden başlatın. - ---- - -#### VS Code Insiders (`chatLanguageModels.json`) - -VS Code Insiders, özel uç nokta modelleri için yapılandırıldığında ve OmniRoute'un özel bir başlık alanı olmadan çalışmasını istediğinizde bunu kullanın. - -**Tavsiye edilen konum:** - -- Linux: `~/.config/Code - Insiders/User/chatLanguageModels.json` -- Windows: `%APPDATA%/Code - Insiders/User/chatLanguageModels.json` - -**Tokenize edilmiş OmniRoute takma adını kullanarak örnek:** - -```json -[ - { - "vendor": "customendpoint", - "id": "auto", - "name": "OmniRoute Auto", - "family": "gpt-4", - "version": "1.0.0", - "url": "http://localhost:20128/api/v1/vscode/sk-your-omniroute-key/chat/completions", - "modelsUrl": "http://localhost:20128/api/v1/vscode/sk-your-omniroute-key/models", - "requestFormat": "openai-chat-completions", - "contextWindow": 256000, - "maxOutputTokens": 32768, - "auth": { - "type": "none" - } - } -] -``` - -**Notlar:** - -- `sk-your-omniroute-key`'i OmniRoute'da oluşturulan bir API anahtarı ile değiştirin. -- `url` alanı `/api/v1/vscode/{token}/chat/completions`'a işaret etmelidir. -- `modelsUrl` alanı `/api/v1/vscode/{token}/models`'a işaret etmelidir. -- İstemci özel başlıkları desteklediğinde normal `/v1` + Bearer başlık akışını tercih edin. -- URL'ye gömülü tokenler, uyumluluk geri dönüşü olarak kullanılmaktadır ve editör günlüklerinde veya proxy geçmişinde görünebilir. - ---- - -#### Kiro CLI (Amazon) - -```bash -# AWS/Kiro hesabınıza giriş yapın: -kiro-cli login - -# CLI kendi kimlik doğrulamasını kullanır — Kiro CLI için arka uç olarak OmniRoute gerekli değildir. -# Diğer araçlar için OmniRoute ile birlikte kiro-cli kullanın. -kiro-cli status -``` - -**Kiro IDE** masaüstü uygulaması için, OmniRoute tarafından sağlanan MITM uç noktasını kullanın -`/dashboard/cli-tools → Kiro` altında. - -## 10. Dahili OmniRoute CLI - -`omniroute` ikili dosyası, sunucu yaşam döngüsü, kurulum, tanılama ve sağlayıcı yönetimi için komutlar sağlar. Giriş noktası: `bin/omniroute.mjs`. - -```bash -omniroute # Sunucuyu başlat (varsayılan port 20128) -omniroute setup # Etkileşimli kurulum sihirbazı -omniroute doctor # Yapılandırmayı, DB'yi, portları, çalışma zamanını kontrol et -omniroute providers list # Yapılandırılmış sağlayıcı bağlantıları -omniroute providers test-all # Her aktif bağlantıyı test et -omniroute reset-password # Yönetici şifresini sıfırla -omniroute logs # İstek günlüklerini akıt -omniroute health # Ayrıntılı sağlık durumu (kesiciler, önbellek, bellek) -omniroute --version # Sürümü yazdır -omniroute --help # Tüm komutları göster -``` - -### Kurulum ve Başlatma - -```bash -omniroute setup # Etkileşimli kurulum sihirbazı -omniroute setup --non-interactive # CI/otomasyon modu (çevre değişkenlerini + bayrakları okur) -omniroute setup --password '' # Yönetici şifresini doğrudan ayarla -omniroute setup --add-provider \ - --provider openai \ - --api-key '' \ - --test-provider # Bir sağlayıcıyı ekle ve test et -``` - -Etkileşimli olmayan kurulum için tanınan çevre değişkenleri: - -| Var | Amaç | -| ------------------- | --------------------------------------------------------------------------------- | -| `OMNIROUTE_API_KEY` | Sağlayıcı API anahtarı (Commander `.env()` aracılığıyla `--api-key` ile bağlanır) | -| `DATA_DIR` | OmniRoute veri dizinini geçersiz kıl | - -Diğer tüm etkileşimli olmayan girdiler bayraklar olarak geçilir, çevre değişkenleri olarak değil: -`--password`, `--provider`, `--provider-name`, `--provider-base-url`, `--default-model` -(bkz. yukarıdaki `omniroute setup` seçenekleri). - -### Tanılama - -```bash -omniroute doctor # Yapılandırmayı, DB'yi, portları, çalışma zamanını, belleği, canlılığı kontrol et -omniroute doctor --json # Makine okunabilir JSON -omniroute doctor --no-liveness # HTTP sağlık sorgusunu atla -omniroute doctor --host 0.0.0.0 # Canlılık ana bilgisayarını geçersiz kıl -omniroute doctor --liveness-url # Tam sağlık uç noktası URL'sini geçersiz kıl -``` - -Doktor bu kontrolleri yapar: `Yapılandırma`, `Veritabanı`, `Depolama/şifreleme`, -`Port kullanılabilirliği`, `Node çalışma zamanı`, `Yerel ikili` (better-sqlite3), -`Bellek` ve `Sunucu canlılığı`. Herhangi bir kontrol `başarısız` olursa sıfırdan farklı bir çıkış yapar. - -### Sağlayıcı Yönetimi - -```bash -omniroute providers available # OmniRoute sağlayıcı kataloğu -omniroute providers available --search openai # Kataloğu id/ad/alias/kategoriye göre filtrele -omniroute providers available --category api-key # Kategoriye göre filtrele (api-key, oauth, ücretsiz, ...) -omniroute providers available --json # Makine okunabilir JSON - -omniroute providers list # Yapılandırılmış sağlayıcı bağlantıları -omniroute providers list --json - -omniroute providers test # Bir yapılandırılmış bağlantıyı test et -omniroute providers test-all # Her aktif bağlantıyı test et -omniroute providers validate # Yerel yalnızca yapısal doğrulama -omniroute providers add --credential-env PROVIDER_KEY -omniroute providers import ./providers.json --dry-run --json -omniroute providers auth # Mevcut OAuth akışı -omniroute providers edit --default-model -omniroute providers remove --yes -``` - -`providers add/import/auth/edit/remove` API-first'tır ve bu nedenle -aktif yerel veya uzaktan bağlama karşı çalışır. Kimlik bilgisi girişi -`--credential-stdin` veya `--credential-env` kullanmalıdır; `--dry-run --json` yalnızca -gizlenmiş varlık/şekil raporları. `providers available` OmniRoute kataloğunu okur; -`providers list/test/test-all/validate` yerel SQLite davranışlarını korur ve -sunucunun çalışmasını gerektirmez. - -### Kurtarma ve Sıfırlama - -```bash -omniroute reset-password # Yönetici şifresini sıfırla (ayrıca: omniroute-reset-password) -omniroute reset-encrypted-columns # Şifreli kimlik bilgisi sıfırlama için uyarı göster + kuru çalışma -omniroute reset-encrypted-columns --force # SQLite'daki şifreli kimlik bilgilerini gerçekten sıfırla -``` - -### Kimlik Bilgisi Dışa Aktarma (⚠ dikkatli kullanın) - -```bash -omniroute auth export # Uyarı göster + onay kapısı — DB erişimi yok -omniroute auth export --force # Tüm bağlantıların ŞİFRESİZ kimlik bilgilerini stdout'a JSON olarak dışa aktar -omniroute auth export --force --id # Sadece eşleşen bağlantıyı dışa aktar -omniroute auth export --force --format env # OMNIROUTE__= satırlarını yayınla -omniroute auth export --force --out creds.json # Bir dosyaya yaz (0600 izinleri ile oluşturulur) -``` - -`auth export` **yerel yalnızca** (doğrudan SQLite okuma, HTTP rotası yok) ve kasıtlı olarak **düz metin** `apiKey`/`accessToken`/`refreshToken`/`idToken` değerlerini yazdırır/yazar — bu bir özellik, hata değil. Veritabanından hiçbir şey okunmaz ve hiçbir şey şifrelenmez, `--force` olmadan. Herhangi bir düz metin yayımlanmadan önce her zaman bir stderr uyarı bandı yazdırılır. `STORAGE_ENCRYPTION_KEY` ayarlanmış olmalıdır. Şifrelemeyi başaramayan bir alan (eski anahtar, bozuk şifreli metin) `export` işlemini durdurmak veya temel hatayı sızdırmak yerine `"DecryptFailed: true"` olarak rapor edilir. - -### Diğer alt komutlar - -Bunlar, aksi belirtilmedikçe çalışan bir OmniRoute sunucusu varsayar: - -```bash -omniroute status # Kapsamlı çalışma durumu -omniroute logs # İstek günlüklerini akıt (--json, --search, --follow) -omniroute config show # Mevcut yapılandırmayı görüntüle - -omniroute provider list # Mevcut sağlayıcıları listele (providers list'in takma adı) -omniroute provider add # OmniRoute'u bir araçta sağlayıcı olarak kaydet -omniroute keys add | list | remove # API anahtarlarını yönet -omniroute models [provider] # Modelleri listele (--json, --search) -omniroute combo list | switch | create | delete - -omniroute backup # Yapılandırma + DB anlık görüntüsü -omniroute restore # Önceki bir anlık görüntüden geri yükle - -omniroute health # Ayrıntılı sağlık durumu (kesiciler, önbellek, bellek) -omniroute quota # Sağlayıcı kota kullanımı -omniroute cache # Önbellek durumu -omniroute cache clear # Anlamsal + imza önbelleklerini temizle - -omniroute mcp status | restart # MCP sunucu durumu / yeniden başlat -omniroute a2a status | card # A2A sunucu durumu / ajan kartı - -omniroute tunnel list | create | stop # Tünelleri yönet (cloudflare/tailscale/ngrok) -omniroute env show | get | set # Çevre değişkenlerini denetle / ayarla (geçici) - -omniroute test # Sağlayıcı bağlantı testi -omniroute update # Güncellemeleri kontrol et -omniroute completion # Shell tamamlama oluştur -``` - -### Yaygın bayraklar - -| Bayrak | Açıklama | -| ------------------- | --------------------------------------------------------- | -| `--no-open` | Başlangıçta tarayıcıyı otomatik açma | -| `--port ` | API portunu geçersiz kıl (varsayılan 20128) | -| `--mcp` | IDE'ler için stdio üzerinden MCP sunucusu olarak çalıştır | -| `--non-interactive` | CI modu (hiçbir istem; çevre/bayraklardan okur) | -| `--json` | Makine okunabilir JSON çıktısı (doctor, providers, vb.) | -| `--help`, `-h` | Komut spesifik yardım göster | -| `--version`, `-v` | Yüklenen sürümü yazdır | - ---- - -## Mevcut API Uç Noktaları - -| Uç Nokta | Açıklama | Kullanım Alanı | -| -------------------------- | ---------------------------------- | ------------------------------- | -| `/v1/chat/completions` | Standart sohbet (tüm sağlayıcılar) | Tüm modern araçlar | -| `/v1/responses` | Yanıtlar API'si (OpenAI formatı) | Codex, ajans iş akışları | -| `/v1/completions` | Eski metin tamamlama | `prompt:` kullanan eski araçlar | -| `/v1/embeddings` | Metin gömme | RAG, arama | -| `/v1/images/generations` | Görüntü üretimi | GPT-Image, Flux, vb. | -| `/v1/audio/speech` | Metinden sese | ElevenLabs, OpenAI TTS | -| `/v1/audio/transcriptions` | Sesten metne | Deepgram, AssemblyAI | - -Yapıştırmaya hazır örnekler ile token'lı OmniRoute URL'si: - -```txt -Token örneği: sk-a3ab3c080beaee3a-69f4a4-070d71af - -Standart OpenAI tabanı: http://localhost:20128/v1 -VS Code modelleri: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/models -VS Code sohbeti: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/chat/completions -VS Code yanıtları: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/responses -Ollama etiketleri: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/api/tags -Ollama sohbeti: http://localhost:20128/api/v1/vscode/sk-a3ab3c080beaee3a-69f4a4-070d71af/api/chat -``` - ---- - -## Sorun Giderme - -| Hata | Sebep | Çözüm | -| ---------------------------------------------------- | ---------------------------------- | ------------------------------------------------- | -| `Connection refused` | OmniRoute çalışmıyor | `omniroute serve` | -| `401 Unauthorized` | Yanlış API anahtarı | `/dashboard/api-manager` içinde kontrol edin | -| `No combo configured` | Aktif yönlendirme kombinasyonu yok | `/dashboard/combos` içinde ayarlayın | -| CLI "not installed" gösteriyor | İkili dosya PATH'te değil | `which ` kontrol edin | -| Dashboard kurulumdan sonra "not detected" gösteriyor | Önbellek eski | Dashboard'da "⟳ Tespiti yenile" butonuna tıklayın | -| Eski bağlantı `/dashboard/cli-tools` | Pre-v3.8.6 yer imi | `/dashboard/cli-code` (308) yönlendirilmiştir | -| Eski bağlantı `/dashboard/agents` | Pre-v3.8.6 yer imi | `/dashboard/acp-agents` (308) yönlendirilmiştir | diff --git a/docs/i18n/tr/docs/reference/ENVIRONMENT.md b/docs/i18n/tr/docs/reference/ENVIRONMENT.md index 1c3336c5b5..8324022a48 100644 --- a/docs/i18n/tr/docs/reference/ENVIRONMENT.md +++ b/docs/i18n/tr/docs/reference/ENVIRONMENT.md @@ -1,665 +1,90 @@ -# Environment Variables Reference (Türkçe) +--- +title: "Ortam Değişkenleri Referansı" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/ENVIRONMENT.md) · 🇸🇦 [ar](../../ar/docs/ENVIRONMENT.md) · 🇧🇬 [bg](../../bg/docs/ENVIRONMENT.md) · 🇧🇩 [bn](../../bn/docs/ENVIRONMENT.md) · 🇨🇿 [cs](../../cs/docs/ENVIRONMENT.md) · 🇩🇰 [da](../../da/docs/ENVIRONMENT.md) · 🇩🇪 [de](../../de/docs/ENVIRONMENT.md) · 🇪🇸 [es](../../es/docs/ENVIRONMENT.md) · 🇮🇷 [fa](../../fa/docs/ENVIRONMENT.md) · 🇫🇮 [fi](../../fi/docs/ENVIRONMENT.md) · 🇫🇷 [fr](../../fr/docs/ENVIRONMENT.md) · 🇮🇳 [gu](../../gu/docs/ENVIRONMENT.md) · 🇮🇱 [he](../../he/docs/ENVIRONMENT.md) · 🇮🇳 [hi](../../hi/docs/ENVIRONMENT.md) · 🇭🇺 [hu](../../hu/docs/ENVIRONMENT.md) · 🇮🇩 [id](../../id/docs/ENVIRONMENT.md) · 🇮🇹 [it](../../it/docs/ENVIRONMENT.md) · 🇯🇵 [ja](../../ja/docs/ENVIRONMENT.md) · 🇰🇷 [ko](../../ko/docs/ENVIRONMENT.md) · 🇮🇳 [mr](../../mr/docs/ENVIRONMENT.md) · 🇲🇾 [ms](../../ms/docs/ENVIRONMENT.md) · 🇳🇱 [nl](../../nl/docs/ENVIRONMENT.md) · 🇳🇴 [no](../../no/docs/ENVIRONMENT.md) · 🇵🇭 [phi](../../phi/docs/ENVIRONMENT.md) · 🇵🇱 [pl](../../pl/docs/ENVIRONMENT.md) · 🇵🇹 [pt](../../pt/docs/ENVIRONMENT.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/ENVIRONMENT.md) · 🇷🇴 [ro](../../ro/docs/ENVIRONMENT.md) · 🇷🇺 [ru](../../ru/docs/ENVIRONMENT.md) · 🇸🇰 [sk](../../sk/docs/ENVIRONMENT.md) · 🇸🇪 [sv](../../sv/docs/ENVIRONMENT.md) · 🇰🇪 [sw](../../sw/docs/ENVIRONMENT.md) · 🇮🇳 [ta](../../ta/docs/ENVIRONMENT.md) · 🇮🇳 [te](../../te/docs/ENVIRONMENT.md) · 🇹🇭 [th](../../th/docs/ENVIRONMENT.md) · 🇹🇷 [tr](../../tr/docs/ENVIRONMENT.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/ENVIRONMENT.md) · 🇵🇰 [ur](../../ur/docs/ENVIRONMENT.md) · 🇻🇳 [vi](../../vi/docs/ENVIRONMENT.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/ENVIRONMENT.md) +# Ortam Değişkenleri Referansı (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/reference/ENVIRONMENT.md) · 🇸🇦 [ar](../../ar/docs/reference/ENVIRONMENT.md) · 🇧🇬 [bg](../../bg/docs/reference/ENVIRONMENT.md) · 🇧🇩 [bn](../../bn/docs/reference/ENVIRONMENT.md) · 🇨🇿 [cs](../../cs/docs/reference/ENVIRONMENT.md) · 🇩🇰 [da](../../da/docs/reference/ENVIRONMENT.md) · 🇩🇪 [de](../../de/docs/reference/ENVIRONMENT.md) · 🇪🇸 [es](../../es/docs/reference/ENVIRONMENT.md) · 🇮🇷 [fa](../../fa/docs/reference/ENVIRONMENT.md) · 🇫🇮 [fi](../../fi/docs/reference/ENVIRONMENT.md) · 🇫🇷 [fr](../../fr/docs/reference/ENVIRONMENT.md) · 🇮🇳 [gu](../../gu/docs/reference/ENVIRONMENT.md) · 🇮🇱 [he](../../he/docs/reference/ENVIRONMENT.md) · 🇮🇳 [hi](../../hi/docs/reference/ENVIRONMENT.md) · 🇭🇺 [hu](../../hu/docs/reference/ENVIRONMENT.md) · 🇮🇩 [id](../../id/docs/reference/ENVIRONMENT.md) · 🇮🇹 [it](../../it/docs/reference/ENVIRONMENT.md) · 🇯🇵 [ja](../../ja/docs/reference/ENVIRONMENT.md) · 🇰🇷 [ko](../../ko/docs/reference/ENVIRONMENT.md) · 🇮🇳 [mr](../../mr/docs/reference/ENVIRONMENT.md) · 🇲🇾 [ms](../../ms/docs/reference/ENVIRONMENT.md) · 🇳🇱 [nl](../../nl/docs/reference/ENVIRONMENT.md) · 🇳🇴 [no](../../no/docs/reference/ENVIRONMENT.md) · 🇵🇭 [phi](../../phi/docs/reference/ENVIRONMENT.md) · 🇵🇱 [pl](../../pl/docs/reference/ENVIRONMENT.md) · 🇵🇹 [pt](../../pt/docs/reference/ENVIRONMENT.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/reference/ENVIRONMENT.md) · 🇷🇴 [ro](../../ro/docs/reference/ENVIRONMENT.md) · 🇷🇺 [ru](../../ru/docs/reference/ENVIRONMENT.md) · 🇸🇰 [sk](../../sk/docs/reference/ENVIRONMENT.md) · 🇸🇪 [sv](../../sv/docs/reference/ENVIRONMENT.md) · 🇰🇪 [sw](../../sw/docs/reference/ENVIRONMENT.md) · 🇮🇳 [ta](../../ta/docs/reference/ENVIRONMENT.md) · 🇮🇳 [te](../../te/docs/reference/ENVIRONMENT.md) · 🇹🇭 [th](../../th/docs/reference/ENVIRONMENT.md) · 🇹🇷 [tr](../../tr/docs/reference/ENVIRONMENT.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/reference/ENVIRONMENT.md) · 🇵🇰 [ur](../../ur/docs/reference/ENVIRONMENT.md) · 🇻🇳 [vi](../../vi/docs/reference/ENVIRONMENT.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/reference/ENVIRONMENT.md) --- -> Complete reference for every environment variable recognized by OmniRoute. -> For a quick-start template, see [`.env.example`](../.env.example). +> OmniRoute tarafından tanınan her ortam değişkeni için eksiksiz başvuru kılavuzu. +> Hızlı başlangıç şablonu için [`.env.example`](../../../../.env.example) dosyasına bakın. + +> [!IMPORTANT] +> Burada belgelenen her değişken aynı zamanda `.env.example` içinde yer almalı ve `.env.example` içindeki her değişken burada görünmelidir. `npm run check:env-doc-sync` bunu commit sırasında ve CI üzerinde zorunlu kılar. --- -## Table of Contents +## İçindekiler -- [1. Required Secrets](#1-required-secrets) -- [2. Storage & Database](#2-storage--database) -- [3. Network & Ports](#3-network--ports) -- [4. Security & Authentication](#4-security--authentication) -- [5. Input Sanitization & PII Protection](#5-input-sanitization--pii-protection) -- [6. Tool & Routing Policies](#6-tool--routing-policies) -- [7. URLs & Cloud Sync](#7-urls--cloud-sync) -- [8. Outbound Proxy](#8-outbound-proxy) -- [9. CLI Tool Integration](#9-cli-tool-integration) -- [10. Internal Agent & MCP Integrations](#10-internal-agent--mcp-integrations) -- [11. OAuth Provider Credentials](#11-oauth-provider-credentials) -- [12. Provider User-Agent Overrides](#12-provider-user-agent-overrides) -- [13. CLI Fingerprint Compatibility](#13-cli-fingerprint-compatibility) -- [14. API Key Providers](#14-api-key-providers) -- [15. Timeout Settings](#15-timeout-settings) -- [16. Logging](#16-logging) -- [17. Memory Optimization](#17-memory-optimization) -- [18. Pricing Sync](#18-pricing-sync) -- [19. Model Sync (Dev)](#19-model-sync-dev) -- [20. Provider-Specific Settings](#20-provider-specific-settings) -- [21. Proxy Health](#21-proxy-health) -- [22. Debugging](#22-debugging) -- [23. GitHub Integration](#23-github-integration) -- [Deployment Scenarios](#deployment-scenarios) -- [Audit: Removed / Dead Variables](#audit-removed--dead-variables) +- [1. Zorunlu Sırlar](#1-zorunlu-sırlar) +- [2. Depolama ve Veritabanı](#2-depolama-ve-veritabanı) +- [3. Ağ ve Portlar](#3-ağ-ve-portlar) +- [4. Güvenlik ve Kimlik Doğrulama](#4-güvenlik-ve-kimlik-doğrulama) +- [5. Girdi Temizleme ve PII Koruması](#5-girdi-temizleme-ve-pii-koruması) +- [6. Araç ve Yönlendirme Politikaları](#6-araç-ve-yönlendirme-politikaları) +- [7. URL'ler ve Bulut Senkronizasyonu](#7-urller-ve-bulut-senkronizasyonu) +- [8. Giden Proxy (Outbound Proxy)](#8-giden-proxy) +- [9. CLI Araç Entegrasyonu](#9-cli-araç-entegrasyonu) +- [10. Dahili Ajan ve MCP Entegrasyonları](#10-dahili-ajan-ve-mcp-entegrasyonları) +- [11. OAuth Sağlayıcı Kimlik Bilgileri](#11-oauth-sağlayıcı-kimlik-bilgileri) +- [12. Sağlayıcı User-Agent Geçersiz Kılmaları](#12-sağlayıcı-user-agent-geçersiz-kılmaları) +- [13. CLI Parmak İzi Uyumluluğu](#13-cli-parmak-izi-uyumluluğu) +- [14. API Anahtarı Sağlayıcıları](#14-api-anahtarı-sağlayıcıları) +- [15. Zaman Aşımı Ayarları](#15-zaman-aşımı-ayarları) +- [16. Günlük Kaydı (Logging)](#16-günlük-kaydı) +- [17. Bellek Optimizasyonu](#17-bellek-optimizasyonu) +- [18. Fiyatlandırma Senkronizasyonu](#18-fiyatlandırma-senkronizasyonu) +- [19. Model Senkronizasyonu](#19-model-senkronizasyonu) +- [20. Sağlayıcıya Özel Ayarlar](#20-sağlayıcıya-özel-ayarlar) +- [21. Proxy Sağlığı](#21-proxy-sağlığı) +- [22. Hata Ayıklama (Debug)](#22-hata-ayıklama) --- -## 1. Required Secrets +## 1. Zorunlu Sırlar -These **must** be set before the first run. Without them, the application will either refuse to start or operate with insecure defaults. +Bunlar ilk çalıştırmadan önce **mutlaka** ayarlanmalıdır. Bunlar olmadan uygulama ya başlamayı reddeder ya da güvensiz varsayılanlarla çalışır. -| Variable | Required | Default | Source File | Description | -| ------------------ | -------- | -------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | **Yes** | _(none)_ | `src/lib/auth` | Signs/verifies all dashboard session cookies (JWT). Generate with `openssl rand -base64 48`. | -| `API_KEY_SECRET` | **Yes** | _(none)_ | `src/lib/db/apiKeys.ts` | AES encryption key for API key values at rest in SQLite. Generate with `openssl rand -hex 32`. | -| `INITIAL_PASSWORD` | **Yes** | `123456` | Bootstrap script | Sets the initial admin dashboard password. **Change before first use.** After login, change via Dashboard → Settings → Security. | +| Değişken | Zorunlu | Varsayılan | Kaynak Dosya | Açıklama | +| ---------------------------- | -------------------- | ----------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | **Evet** | _(yok)_ | `src/lib/auth` | Tüm pano oturum çerezlerini (JWT) imzalar ve doğrular. `openssl rand -base64 48` ile üretin. | +| `API_KEY_SECRET` | **Evet** | _(yok)_ | `src/lib/db/apiKeys.ts` | SQLite'ta saklanan API anahtarı değerleri için AES şifreleme anahtarı. `openssl rand -hex 32` ile üretin. | +| `INITIAL_PASSWORD` | **Evet** | `CHANGEME` | Bootstrap betiği | İlk yönetici pano şifresini belirler. **İlk kullanımdan önce değiştirin.** | +| `OMNIROUTE_WS_BRIDGE_SECRET` | **Evet** (üretimde) | _(ayarlanmamış)_ | `src/app/api/internal/codex-responses-ws/route.ts` | Dahili Codex Responses WebSocket köprüsü için paylaşılan sır. `openssl rand -base64 32` ile üretin. | -### Generation Commands +### Üretim Komutları ```bash -# Generate all three secrets at once: +# Dört sırrı tek seferde üretin: echo "JWT_SECRET=$(openssl rand -base64 48)" echo "API_KEY_SECRET=$(openssl rand -hex 32)" echo "INITIAL_PASSWORD=$(openssl rand -base64 16)" -``` - -> [!CAUTION] -> Never commit `.env` files with real secrets to version control. The `.gitignore` already excludes `.env`, but verify before pushing. - ---- - -## 2. Storage & Database - -OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These variables control data location, encryption, and lifecycle. - -| Variable | Default | Source File | Description | -| -------------------------------- | -------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `DATA_DIR` | `~/.omniroute/` | `src/lib/db/core.ts` | Root directory for SQLite DB, backups, and data files. Override for Docker volumes or custom paths. | -| `STORAGE_ENCRYPTION_KEY` | _(empty = disabled)_ | `src/lib/db/encryption.ts` | AES key for full SQLite database encryption at rest. Generate with `openssl rand -hex 32`. | -| `STORAGE_ENCRYPTION_KEY_VERSION` | `v1` | `scripts/bootstrap-env.mjs`, `electron/main.js` | Version label for the encryption key. Increment when performing key rotation to support decryption of old backups. | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `src/lib/db/backup.ts` | When `true`, skips the automatic database backup that runs before migrations on every startup. | -| `OMNIROUTE_CRYPT_KEY` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** for `STORAGE_ENCRYPTION_KEY`. Accepted as a fallback when the primary variable is absent. | -| `OMNIROUTE_API_KEY_BASE64` | _(unset)_ | `src/lib/db/encryption.ts` | **Legacy alias** (Base64-encoded form) accepted as a fallback. Decoded automatically before use. | - -### Scenarios - -| Scenario | Configuration | -| --------------------- | -------------------------------------------------------------------------------- | -| **Local development** | Leave all defaults. DB lives at `~/.omniroute/omniroute.db`. | -| **Docker** | `DATA_DIR=/data` + mount a volume at `/data`. | -| **Encrypted at rest** | Set `STORAGE_ENCRYPTION_KEY` + keep backups of the key! Losing it = losing data. | -| **CI/Testing** | `DATA_DIR=/tmp/omniroute-test` — ephemeral, no encryption needed. | - ---- - -## 3. Network & Ports - -| Variable | Default | Source File | Description | -| --------------------- | ------------ | -------------------------- | -------------------------------------------------------------------------------------- | -| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). | -| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. | -| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. | -| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. | -| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. | -| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. | -| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. | -| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. | - -### Port Modes - -``` -┌─────────────────────────── Single Port (default) ──────────────────────────┐ -│ PORT=20128 │ -│ → Dashboard: http://localhost:20128 │ -│ → API: http://localhost:20128/v1/chat/completions │ -└─────────────────────────────────────────────────────────────────────────────┘ - -┌─────────────────────────── Split Ports ─────────────────────────────────────┐ -│ DASHBOARD_PORT=20128 │ -│ API_PORT=20129 │ -│ API_HOST=0.0.0.0 │ -│ → Dashboard: http://localhost:20128 │ -│ → API: http://0.0.0.0:20129/v1/chat/completions │ -│ Use case: Expose API to LAN while restricting Dashboard to localhost. │ -└─────────────────────────────────────────────────────────────────────────────┘ - -┌─────────────────────────── Docker Production ──────────────────────────────┐ -│ PROD_DASHBOARD_PORT=443 PROD_API_PORT=8443 │ -│ → Maps container ports to host ports in docker-compose.prod.yml. │ -└─────────────────────────────────────────────────────────────────────────────┘ +echo "OMNIROUTE_WS_BRIDGE_SECRET=$(openssl rand -base64 32)" ``` --- -## 4. Security & Authentication +## 2. Depolama ve Veritabanı -| Variable | Default | Source File | Description | -| ----------------------------- | --------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `MACHINE_ID_SALT` | `endpoint-proxy-salt` | `src/lib/auth` | Salt combined with hardware identifiers for machine fingerprinting. Change per-deployment for isolation. | -| `AUTH_COOKIE_SECURE` | `false` | `src/lib/auth` | Sets the `Secure` flag on session cookies. **Must be `true`** when running behind HTTPS. | -| `REQUIRE_API_KEY` | `false` | API middleware | When `true`, all `/v1/*` proxy requests must include a valid API key. | -| `ALLOW_API_KEY_REVEAL` | `false` | Dashboard providers page | Allows revealing full API key values in the Dashboard UI. Security risk on shared instances. | -| `NO_LOG_API_KEY_IDS` | _(empty)_ | `src/lib/compliance/index.ts` | Comma-separated API key IDs that bypass request logging (GDPR compliance). | -| `MAX_BODY_SIZE_BYTES` | `10485760` (10 MB) | `src/shared/middleware/bodySizeGuard.ts` | Maximum allowed request body size. Rejects payloads exceeding this limit. | -| `CORS_ORIGIN` | `*` | Next.js middleware | CORS `Access-Control-Allow-Origin` value. Restrict for production. | -| `OUTBOUND_SSRF_GUARD_ENABLED` | `true` | `src/shared/network/outboundUrlGuard.ts` | Block provider calls targeting private/loopback/link-local IP ranges. Disable only in isolated test envs. | - -### Hardening Checklist - -```bash -# Production security minimum: -AUTH_COOKIE_SECURE=true # Requires HTTPS -REQUIRE_API_KEY=true # Authenticate all proxy calls -ALLOW_API_KEY_REVEAL=false # Never expose keys in UI -CORS_ORIGIN=https://your.domain.com -MAX_BODY_SIZE_BYTES=5242880 # 5 MB limit -``` +| Değişken | Varsayılan | Açıklama | +| -------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------ | +| `DATA_DIR` | `~/.omniroute/` | SQLite veritabanı, yedeklemeler ve veri dosyaları için kök dizin. Docker hacimleri için geçersiz kılın. | +| `STORAGE_ENCRYPTION_KEY` | _(boş = devre dışı)_ | SQLite veritabanının diskte AES ile şifrelenmesi için anahtar. `openssl rand -hex 32` ile üretin. | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | `true` olduğunda otomatik başlatma ve yazma öncesi yedeklemeleri atlar. | +| `OMNIROUTE_WAL_TRUNCATE_INTERVAL_MS` | `21600000` (6h) | Periyodik `wal_checkpoint(TRUNCATE)` aralığı (ms). | --- -## 5. Input Sanitization & PII Protection - -OmniRoute provides a two-layer defense: request-side injection scanning and response-side PII stripping. - -### Request-Side: Prompt Injection Guard - -| Variable | Default | Source File | Description | -| ------------------------- | --------- | ---------------------------------------- | ------------------------------------------------------------------------------------------- | -| `INPUT_SANITIZER_ENABLED` | `true` | `src/middleware/promptInjectionGuard.ts` | Enable scanning of incoming messages for prompt injection patterns. | -| `INPUT_SANITIZER_MODE` | `warn` | `src/middleware/promptInjectionGuard.ts` | `warn` = log only, `block` = reject request with 400, `redact` = strip suspicious patterns. | -| `INJECTION_GUARD_MODE` | _(unset)_ | `src/middleware/promptInjectionGuard.ts` | Legacy alias for `INPUT_SANITIZER_MODE` — same behavior. | -| `PII_REDACTION_ENABLED` | `false` | `src/middleware/promptInjectionGuard.ts` | Detect PII (emails, phones, SSNs) in incoming requests. | - -### Response-Side: PII Sanitizer - -| Variable | Default | Source File | Description | -| -------------------------------- | -------- | ------------------------- | ----------------------------------------------------------------------- | -| `PII_RESPONSE_SANITIZATION` | `false` | `src/lib/piiSanitizer.ts` | Scan LLM responses for leaked PII before returning to client. | -| `PII_RESPONSE_SANITIZATION_MODE` | `redact` | `src/lib/piiSanitizer.ts` | `redact` = mask PII, `warn` = log only, `block` = drop entire response. | - -### Scenarios - -| Scenario | Configuration | -| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| **Enterprise compliance** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=block`, `PII_REDACTION_ENABLED=true`, `PII_RESPONSE_SANITIZATION=true` | -| **Monitoring only** | `INPUT_SANITIZER_ENABLED=true`, `INPUT_SANITIZER_MODE=warn` — logs but never blocks | -| **Personal use** | Leave all disabled — zero overhead | - ---- - -## 6. Tool & Routing Policies - -| Variable | Default | Source File | Description | -| ------------------ | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | Controls LLM tool/function-calling access. `allowlist` = only listed tools, `denylist` = all except listed, `disabled` = no restrictions. | - ---- - -## 7. URLs & Cloud Sync - -| Variable | Default | Source File | Description | -| ----------------------- | ------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. | -| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). | -| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. | -| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** | -| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. | -| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. | - -> [!IMPORTANT] -> When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match. - ---- - -## 8. Outbound Proxy - -Route upstream LLM provider calls through an HTTP or SOCKS5 proxy for egress control, geo-routing, or IP masking. - -| Variable | Default | Source File | Description | -| --------------------------------- | --------- | -------------------- | ----------------------------------------------------------------------------------- | -| `ENABLE_SOCKS5_PROXY` | `true` | `open-sse/executors` | Enable SOCKS5 proxy agent for upstream calls. | -| `NEXT_PUBLIC_ENABLE_SOCKS5_PROXY` | `true` | Client-side | Client-side awareness of SOCKS5 availability. | -| `HTTP_PROXY` | _(unset)_ | Node.js standard | HTTP proxy for upstream calls. | -| `HTTPS_PROXY` | _(unset)_ | Node.js standard | HTTPS proxy for upstream calls. | -| `ALL_PROXY` | _(unset)_ | Node.js standard | Universal proxy (supports `socks5://`). | -| `NO_PROXY` | _(unset)_ | Node.js standard | Comma-separated hostnames/IPs to bypass the proxy. | -| `ENABLE_TLS_FINGERPRINT` | `false` | `open-sse/executors` | Spoof TLS fingerprint using wreq-js (mimics Chrome 124). Counters JA3/JA4 blocking. | - -### Scenarios - -| Scenario | Configuration | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| **SOCKS5 through SSH tunnel** | `ALL_PROXY=socks5://127.0.0.1:7890`, `ENABLE_SOCKS5_PROXY=true` | -| **Corporate HTTP proxy** | `HTTP_PROXY=http://proxy.corp.com:3128`, `HTTPS_PROXY=http://proxy.corp.com:3128`, `NO_PROXY=localhost,internal.corp.com` | -| **Anti-fingerprint** | `ENABLE_TLS_FINGERPRINT=true` — requires `wreq-js` (included) | - ---- - -## 9. CLI Tool Integration - -Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex, etc.). - -| Variable | Default | Source File | Description | -| ------------------------- | ---------- | ----------------------------------- | -------------------------------------------------------------------------- | -| `CLI_MODE` | `auto` | `src/shared/services/cliRuntime.ts` | `auto` = search system PATH; `manual` = use explicit paths only. | -| `CLI_EXTRA_PATHS` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Additional PATH entries for CLI binary discovery (colon-separated). | -| `CLI_CONFIG_HOME` | _(unset)_ | `src/shared/services/cliRuntime.ts` | Override home directory for reading CLI configs (`~/.claude`, `~/.codex`). | -| `CLI_ALLOW_CONFIG_WRITES` | `false` | `src/shared/services/cliRuntime.ts` | Allow OmniRoute to write CLI config files (token refresh, session data). | -| `CLI_CLAUDE_BIN` | `claude` | `src/shared/services/cliRuntime.ts` | Custom path to Claude CLI binary. | -| `CLI_CODEX_BIN` | `codex` | `src/shared/services/cliRuntime.ts` | Custom path to Codex CLI binary. | -| `CLI_DROID_BIN` | `droid` | `src/shared/services/cliRuntime.ts` | Custom path to Droid CLI binary. | -| `CLI_OPENCLAW_BIN` | `openclaw` | `src/shared/services/cliRuntime.ts` | Custom path to OpenClaw CLI binary. | -| `CLI_CURSOR_BIN` | `agent` | `src/shared/services/cliRuntime.ts` | Custom path to Cursor agent binary. | -| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. | -| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. | -| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. | - -### Docker Example - -```bash -# Mount host binaries into the container and tell OmniRoute where they are: -CLI_EXTRA_PATHS=/host-cli/bin -CLI_CONFIG_HOME=/root -CLI_ALLOW_CONFIG_WRITES=true -CLI_CLAUDE_BIN=/host-cli/bin/claude -``` - ---- - -## 10. Internal Agent & MCP Integrations - -| Variable | Default | Source File | Description | -| --------------------------------------- | ----------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `OMNIROUTE_BASE_URL` | auto-detect | `open-sse/mcp-server/server.ts` | Explicit URL for MCP/A2A tools to reach OmniRoute. Overrides localhost auto-detection. | -| `OMNIROUTE_API_KEY` | _(unset)_ | MCP/A2A modules | API key for internal MCP tool and A2A skill calls. | -| `OMNIROUTE_API_KEY_ID` | _(unset)_ | `open-sse/mcp-server/audit.ts` | Key ID for MCP audit log attribution. | -| `ROUTER_API_KEY` | _(unset)_ | Legacy | Legacy alias for `OMNIROUTE_API_KEY`. | -| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. | -| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. | -| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | Provider rate-limit and quota polling interval. | -| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. | -| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | Set `true` by bootstrap script after initial setup. Controls setup wizard visibility. | -| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | Escape hatch: allow request body to override the Antigravity project field. | - -### OAuth CLI Bridge (Internal) - -| Variable | Default | Source File | Description | -| ------------------- | ----------- | ------------------------------- | ----------------------------------------- | -| `OMNIROUTE_SERVER` | auto-detect | `src/lib/oauth/config/index.ts` | Server URL for CLI↔OmniRoute auth bridge. | -| `OMNIROUTE_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Auth token for CLI bridge. | -| `OMNIROUTE_USER_ID` | `cli` | `src/lib/oauth/config/index.ts` | User ID for CLI bridge sessions. | -| `SERVER_URL` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_SERVER`. | -| `CLI_TOKEN` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_TOKEN`. | -| `CLI_USER_ID` | _(unset)_ | `src/lib/oauth/config/index.ts` | Legacy alias for `OMNIROUTE_USER_ID`. | - ---- - -## 11. OAuth Provider Credentials - -Built-in credentials for **localhost development**. For remote deployments, register your own at each provider's developer console. - -| Variable | Provider | Notes | -| --------------------------------- | ----------------------- | --------------------------------------------------------------------------------- | -| `CLAUDE_OAUTH_CLIENT_ID` | Claude Code (Anthropic) | Public client — no secret needed. | -| `CLAUDE_CODE_REDIRECT_URI` | Claude Code | Override redirect URI. Default: `https://platform.claude.com/oauth/code/callback` | -| `CODEX_OAUTH_CLIENT_ID` | Codex / OpenAI | Public client. | -| `GEMINI_OAUTH_CLIENT_ID` | Gemini (Google) | Requires matching `_SECRET`. | -| `GEMINI_OAUTH_CLIENT_SECRET` | Gemini (Google) | — | -| `QWEN_OAUTH_CLIENT_ID` | Qwen (Alibaba) | Public client. | -| `KIMI_CODING_OAUTH_CLIENT_ID` | Kimi Coding (Moonshot) | Public client. | -| `ANTIGRAVITY_OAUTH_CLIENT_ID` | Antigravity (Google) | Requires matching `_SECRET`. | -| `ANTIGRAVITY_OAUTH_CLIENT_SECRET` | Antigravity (Google) | — | -| `GITHUB_OAUTH_CLIENT_ID` | GitHub Copilot | Public client. | -| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — | -| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. | -| `QODER_OAUTH_TOKEN_URL` | Qoder | — | -| `QODER_OAUTH_USERINFO_URL` | Qoder | — | -| `QODER_OAUTH_CLIENT_ID` | Qoder | — | -| `QODER_PERSONAL_ACCESS_TOKEN` | Qoder | Direct API key fallback (bypasses OAuth). | -| `QODER_CLI_WORKSPACE` | Qoder | Workspace ID for Qoder CLI. | -| `OMNIROUTE_QODER_WORKSPACE` | Qoder | Alias for `QODER_CLI_WORKSPACE`. | - -> [!WARNING] -> -> 1. Go to [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials) -> 2. Create an OAuth 2.0 Client ID (type: "Web application") -> 3. Add your server URL as Authorized redirect URI -> 4. Replace the credential values in `.env`. - ---- - -## 12. Provider User-Agent Overrides - -Override the `User-Agent` header sent to each upstream provider. This is dynamically resolved at runtime by the executor base class: - -``` -process.env[`${PROVIDER_ID}_USER_AGENT`] -``` - -> **Source:** `open-sse/executors/base.ts` → `buildHeaders()` - -| Variable | Default Value | When to Update | -| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- | -| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version | -| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI | -| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string | -| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates | -| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | When Antigravity IDE updates | -| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates | -| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates | -| `QWEN_USER_AGENT` | `QwenCode/0.15.11 (linux; x64)` | When Qwen Code updates | -| `CURSOR_USER_AGENT` | `connect-es/1.6.1` | When Cursor updates | - -> [!TIP] -> You can add User-Agent overrides for **any** provider using the pattern `{PROVIDER_ID}_USER_AGENT`. The executor dynamically constructs the env var name. - ---- - -## 13. CLI Fingerprint Compatibility - -When enabled, OmniRoute reorders HTTP headers and JSON body fields to match the exact signature of official CLI tools. This reduces the risk of account flagging while preserving your proxy IP. - -**Source:** `open-sse/config/cliFingerprints.ts`, `open-sse/executors/base.ts` - -### Per-Provider - -| Variable | Effect | -| -------------------------- | --------------------------------------- | -| `CLI_COMPAT_CODEX=1` | Mimics Codex CLI request signature | -| `CLI_COMPAT_CLAUDE=1` | Mimics Claude Code request signature | -| `CLI_COMPAT_GITHUB=1` | Mimics GitHub Copilot request signature | -| `CLI_COMPAT_ANTIGRAVITY=1` | Mimics Antigravity request signature | -| `CLI_COMPAT_KIRO=1` | Mimics Kiro IDE request signature | -| `CLI_COMPAT_CURSOR=1` | Mimics Cursor request signature | -| `CLI_COMPAT_KIMI_CODING=1` | Mimics Kimi Coding request signature | -| `CLI_COMPAT_KILOCODE=1` | Mimics Kilo Code request signature | -| `CLI_COMPAT_CLINE=1` | Mimics Cline request signature | -| `CLI_COMPAT_QWEN=1` | Mimics Qwen Code request signature | - -### Global - -| Variable | Effect | -| ------------------ | --------------------------------------------------------------- | -| `CLI_COMPAT_ALL=1` | Enable fingerprint compatibility for **all** providers at once. | - -> [!NOTE] -> This feature works alongside the User-Agent overrides (§12). The fingerprint system handles header ordering and body field ordering, while User-Agent overrides handle the specific UA string. Both can be enabled independently. - ---- - -## 14. API Key Providers - -API keys for providers that use direct authentication. **Preferred setup:** Dashboard → Providers → Add API Key. - -Setting via environment variables is an alternative for Docker or headless deployments. - -Recognized pattern: `{PROVIDER_ID}_API_KEY` - -| Variable | Provider | -| -------------------- | ------------------- | -| `DEEPSEEK_API_KEY` | DeepSeek | -| `GROQ_API_KEY` | Groq | -| `XAI_API_KEY` | xAI (Grok) | -| `MISTRAL_API_KEY` | Mistral AI | -| `PERPLEXITY_API_KEY` | Perplexity | -| `TOGETHER_API_KEY` | Together AI | -| `FIREWORKS_API_KEY` | Fireworks AI | -| `CEREBRAS_API_KEY` | Cerebras | -| `COHERE_API_KEY` | Cohere | -| `NVIDIA_API_KEY` | NVIDIA NIM | -| `NEBIUS_API_KEY` | Nebius (embeddings) | - -> [!TIP] -> Keys set via the Dashboard are stored encrypted in SQLite and take precedence over environment variables. - ---- - -## 15. Timeout Settings - -All values are in **milliseconds**. Centralized resolution in `src/shared/utils/runtimeTimeouts.ts`. - -### Timeout Hierarchy - -``` -REQUEST_TIMEOUT_MS (global override) -├─→ FETCH_TIMEOUT_MS (upstream provider calls, default: 600000) -│ ├─→ FETCH_HEADERS_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS) -│ ├─→ FETCH_BODY_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS) -│ ├─→ TLS_CLIENT_TIMEOUT_MS (inherits from FETCH_TIMEOUT_MS) -│ ├── FETCH_CONNECT_TIMEOUT_MS (independent, default: 30000) -│ └── FETCH_KEEPALIVE_TIMEOUT_MS (independent, default: 4000) -├─→ STREAM_IDLE_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 600000) -└─→ API_BRIDGE_PROXY_TIMEOUT_MS (inherits from REQUEST_TIMEOUT_MS, default: 600000) - ├─→ API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS (derived, default: 600000) - ├── API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS (default: 60000) - ├── API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS (default: 5000) - └── API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS (default: 0 = disabled) -``` - -| Variable | Default | Description | -| ---------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------- | -| `REQUEST_TIMEOUT_MS` | _(unset)_ | Global shortcut — overrides both `FETCH_TIMEOUT_MS` and `STREAM_IDLE_TIMEOUT_MS` defaults. | -| `FETCH_TIMEOUT_MS` | `600000` | Total HTTP request timeout for upstream provider calls. | -| `STREAM_IDLE_TIMEOUT_MS` | `600000` | Max silence between SSE chunks before aborting. Extended-thinking models rarely pause >90s. | -| `FETCH_HEADERS_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive response headers. | -| `FETCH_BODY_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | Time to receive the full response body. | -| `FETCH_CONNECT_TIMEOUT_MS` | `30000` | TCP connection establishment timeout. | -| `FETCH_KEEPALIVE_TIMEOUT_MS` | `4000` | Keep-alive socket idle timeout. | -| `TLS_CLIENT_TIMEOUT_MS` | = `FETCH_TIMEOUT_MS` | TLS fingerprint proxy (wreq-js) timeout. | -| `API_BRIDGE_PROXY_TIMEOUT_MS` | `600000` | Proxy hop timeout for `/v1` bridge requests. | -| `API_BRIDGE_SERVER_REQUEST_TIMEOUT_MS` | `600000` | Overall server request timeout for the bridge. | -| `API_BRIDGE_SERVER_HEADERS_TIMEOUT_MS` | `60000` | Time to send response headers via the bridge. | -| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Bridge keep-alive idle timeout. | -| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Raw socket timeout (0 = disabled). | -| `SHUTDOWN_TIMEOUT_MS` | `30000` | Grace period on SIGTERM/SIGINT before force-exit. | - -### Scenarios - -| Scenario | Configuration | -| -------------------------------- | ------------------------------------------------------ | -| **Long-running code generation** | `REQUEST_TIMEOUT_MS=900000` (15 min) | -| **Fast-fail for production API** | `API_BRIDGE_PROXY_TIMEOUT_MS=10000` | -| **Extended thinking models** | `STREAM_IDLE_TIMEOUT_MS=300000` (5 min between chunks) | - ---- - -## 16. Logging - -The logging system writes to both stdout and rotated log files. All configuration is read by `src/lib/logEnv.ts`. - -| Variable | Default | Description | -| --------------------------- | -------------------------- | ---------------------------------------------------------------------------- | -| `APP_LOG_LEVEL` | `info` | Minimum log level: `debug`, `info`, `warn`, `error`. | -| `APP_LOG_FORMAT` | `text` | Output format: `text` (human-readable) or `json` (structured). | -| `APP_LOG_TO_FILE` | `true` | Write logs to file alongside stdout. | -| `APP_LOG_FILE_PATH` | `logs/application/app.log` | Log file path (relative to project root or `DATA_DIR`). | -| `APP_LOG_MAX_FILE_SIZE` | `50M` | Max file size before rotation. Accepts: `50M`, `1G`, `512K`, or plain bytes. | -| `APP_LOG_RETENTION_DAYS` | `7` | Days to keep rotated application log files. | -| `APP_LOG_MAX_FILES` | `20` | Maximum rotated log file backups. | -| `CALL_LOG_RETENTION_DAYS` | `7` | Days to keep request/call log entries in the database. | -| `CALL_LOG_MAX_ENTRIES` | `10000` | Max call log entries in the in-memory buffer. | -| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `call_logs` SQLite table before pruning. | -| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `proxy_logs` SQLite table before pruning. | - ---- - -## 17. Memory Optimization - -| Variable | Default | Description | -| -------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------- | -| `OMNIROUTE_MEMORY_MB` | `512` | Runtime V8 heap limit. Docker standalone and `omniroute serve` use it to set `--max-old-space-size`. | -| `PROMPT_CACHE_MAX_SIZE` | `50` | Max cached system prompt entries. | -| `PROMPT_CACHE_MAX_BYTES` | `2097152` (2 MB) | Max total prompt cache size. | -| `PROMPT_CACHE_TTL_MS` | `300000` (5 min) | Prompt cache entry TTL. | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max cached temperature=0 responses. | -| `SEMANTIC_CACHE_MAX_BYTES` | `4194304` (4 MB) | Max total semantic cache size. | -| `SEMANTIC_CACHE_TTL_MS` | `1800000` (30 min) | Semantic cache entry TTL. | -| `STREAM_HISTORY_MAX` | `50` | Max recent stream events in the Dashboard live view buffer. | -| `CONTEXT_LENGTH_DEFAULT` | `128000` | Global fallback max context length for models without explicit config. | -| `USAGE_TOKEN_BUFFER` | `100` | Extra token headroom reserved when tracking usage quotas. | - -### Low-RAM Docker Example - -```bash -OMNIROUTE_MEMORY_MB=128 -PROMPT_CACHE_MAX_SIZE=20 -PROMPT_CACHE_MAX_BYTES=524288 # 512 KB -SEMANTIC_CACHE_MAX_SIZE=25 -SEMANTIC_CACHE_MAX_BYTES=1048576 # 1 MB -STREAM_HISTORY_MAX=10 -``` - ---- - -## 18. Pricing Sync - -Automatic model pricing data synchronization from external sources. - -| Variable | Default | Source File | Description | -| ----------------------- | ------------- | ------------------------ | ----------------------------- | -| `PRICING_SYNC_ENABLED` | `false` | `src/lib/pricingSync.ts` | Opt-in periodic pricing sync. | -| `PRICING_SYNC_INTERVAL` | `86400` (24h) | `src/lib/pricingSync.ts` | Sync interval in seconds. | -| `PRICING_SYNC_SOURCES` | `litellm` | `src/lib/pricingSync.ts` | Comma-separated data sources. | - ---- - -## 19. Model Sync (Dev) - -| Variable | Default | Source File | Description | -| -------------------------- | ------------- | -------------------------- | -------------------------------------------------------- | -| `MODELS_DEV_SYNC_INTERVAL` | `86400` (24h) | `src/lib/modelsDevSync.ts` | Development-time model catalog sync interval in seconds. | - ---- - -## 20. Provider-Specific Settings - -| Variable | Default | Source File | Description | -| ----------------------------------------- | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------- | -| `OPENROUTER_CATALOG_TTL_MS` | `86400000` (24h) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter model catalog cache TTL. | -| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. | -| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. | -| `CLOUDFLARE_ACCOUNT_ID` | _(unset)_ | `open-sse/executors/cloudflare-ai.ts` | Account ID for Cloudflare Workers AI. | -| `CLOUDFLARED_BIN` | auto-detect | `src/lib/cloudflaredTunnel.ts` | Custom path to `cloudflared` binary. | -| `SEARCH_CACHE_TTL_MS` | `300000` (5 min) | `open-sse/services/searchCache.ts` | TTL for search API (Perplexity, Brave, etc.) response caching. | -| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | Allow multiple simultaneous connections per OpenAI-compatible provider. | -| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | Enable experimental Claude Code compatible provider endpoint. | -| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). | -| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. | -| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. | -| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). | - ---- - -## 21. Proxy Health - -| Variable | Default | Source File | Description | -| ---------------------------- | ---------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. | -| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. | -| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. | -| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. | -| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. | - ---- - -## 22. Debugging - -> [!CAUTION] -> These variables produce **verbose output** and may leak sensitive data. **Never enable in production.** - -| Variable | Default | Source File | Description | -| -------------------------------- | --------- | ----------------------------------------- | -------------------------------------------------------------- | -| `CURSOR_PROTOBUF_DEBUG` | _(unset)_ | `open-sse/utils/cursorProtobuf.ts` | Set `1` to dump Cursor protobuf decode/encode details. | -| `CURSOR_STREAM_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Set `1` to dump raw Cursor SSE stream data. | -| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. | -| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). | - ---- - -## 23. GitHub Integration - -Allow users to report issues directly from the Dashboard. - -| Variable | Default | Source File | Description | -| --------------------- | --------- | --------------------------------------- | ------------------------------------------------------- | -| `GITHUB_ISSUES_REPO` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | Repository in `owner/repo` format. | -| `GITHUB_ISSUES_TOKEN` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | GitHub Personal Access Token with `issues:write` scope. | - ---- - -## Deployment Scenarios - -### Minimal Local Development - -```bash -JWT_SECRET=$(openssl rand -base64 48) -API_KEY_SECRET=$(openssl rand -hex 32) -INITIAL_PASSWORD=dev123 -PORT=20128 -NODE_ENV=development -``` - -### Docker Production - -```bash -JWT_SECRET= -API_KEY_SECRET= -INITIAL_PASSWORD= -STORAGE_ENCRYPTION_KEY= -DATA_DIR=/data -PORT=20128 -API_PORT=20129 -NODE_ENV=production -AUTH_COOKIE_SECURE=true -REQUIRE_API_KEY=true -NEXT_PUBLIC_BASE_URL=https://omniroute.example.com -BASE_URL=http://localhost:20128 -OMNIROUTE_MEMORY_MB=512 -CORS_ORIGIN=https://your-frontend.example.com -``` - -### Air-Gapped / CI - -```bash -JWT_SECRET=test-jwt-secret-for-ci -API_KEY_SECRET=test-api-key-secret-for-ci -INITIAL_PASSWORD=testpass -NODE_ENV=production -OMNIROUTE_DISABLE_BACKGROUND_SERVICES=true -APP_LOG_TO_FILE=false -``` - -### VPS with Reverse Proxy (nginx + Cloudflare) - -```bash -JWT_SECRET= -API_KEY_SECRET= -STORAGE_ENCRYPTION_KEY= -PORT=20128 -AUTH_COOKIE_SECURE=true -REQUIRE_API_KEY=true -NEXT_PUBLIC_BASE_URL=https://omniroute.example.com -BASE_URL=http://127.0.0.1:20128 -CORS_ORIGIN=https://omniroute.example.com -ENABLE_TLS_FINGERPRINT=true -CLI_COMPAT_ALL=1 -``` - ---- - -## Audit: Removed / Dead Variables - -The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed: - -| Variable | Reason | -| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -| `STORAGE_DRIVER=sqlite` | Never read by any source file. SQLite is the only supported driver — no selection needed. | -| `INSTANCE_NAME=omniroute` | Present in old docs/env templates but unused at runtime. May return in a future multi-instance feature. | -| `SQLITE_MAX_SIZE_MB=2048` | Not referenced in source code. Database size is not artificially limited. | -| `SQLITE_CLEAN_LEGACY_FILES=true` | Not referenced in source code. Legacy cleanup was likely removed. | -| `CLI_ROO_BIN` | Not registered in `src/shared/services/cliRuntime.ts`. | -| `CLI_KIMI_CODING_BIN` | Not registered in `src/shared/services/cliRuntime.ts` (Kimi Coding uses OAuth, not a CLI binary). | -| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | Not referenced anywhere in source code. | - -### Default Value Corrections - -| Variable | Old `.env.example` Value | Actual Code Default | Fixed | -| ------------------------- | ------------------------ | ------------------- | ------------------------------------------------------ | -| `APP_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default | -| `CALL_LOG_RETENTION_DAYS` | `90` | `7` | ✅ Removed misleading value; documented `7` as default | +## 3. Ağ ve Portlar + +| Değişken | Varsayılan | Açıklama | +| -------------------------- | --------------------------- | ------------------------------------------------------------- | +| `PORT` | `20128` | HTTP dinleme portu (Pano ve API aynı süreci paylaşır). | +| `HOST` / `HOSTNAME` | `0.0.0.0` | Ağ bağlama adresi (tüm arayüzleri dinler). | +| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth geri çağırma URL'leri ve istemci yönlendirmeleri için. | +| `RATE_LIMIT_AUTO_ENABLE` | `true` | Sağlayıcı başına hız sınırlamasını otomatik etkinleştirir. | +| `RATE_LIMIT_MAX_WAIT_MS` | `30000` | Hız sınırı kuyruğunda maksimum bekleme süresi (ms). | diff --git a/docs/i18n/tr/docs/routing/AUTO-COMBO.md b/docs/i18n/tr/docs/routing/AUTO-COMBO.md index 22a11b1244..66e3a70a02 100644 --- a/docs/i18n/tr/docs/routing/AUTO-COMBO.md +++ b/docs/i18n/tr/docs/routing/AUTO-COMBO.md @@ -1,67 +1,65 @@ -# OmniRoute Auto-Combo Engine (Türkçe) +--- +title: "OmniRoute Auto-Combo Motoru" +version: 3.8.50 +lastUpdated: 2026-08-23 +--- -🌐 **Languages:** 🇺🇸 [English](../../../../docs/AUTO-COMBO.md) · 🇸🇦 [ar](../../ar/docs/AUTO-COMBO.md) · 🇧🇬 [bg](../../bg/docs/AUTO-COMBO.md) · 🇧🇩 [bn](../../bn/docs/AUTO-COMBO.md) · 🇨🇿 [cs](../../cs/docs/AUTO-COMBO.md) · 🇩🇰 [da](../../da/docs/AUTO-COMBO.md) · 🇩🇪 [de](../../de/docs/AUTO-COMBO.md) · 🇪🇸 [es](../../es/docs/AUTO-COMBO.md) · 🇮🇷 [fa](../../fa/docs/AUTO-COMBO.md) · 🇫🇮 [fi](../../fi/docs/AUTO-COMBO.md) · 🇫🇷 [fr](../../fr/docs/AUTO-COMBO.md) · 🇮🇳 [gu](../../gu/docs/AUTO-COMBO.md) · 🇮🇱 [he](../../he/docs/AUTO-COMBO.md) · 🇮🇳 [hi](../../hi/docs/AUTO-COMBO.md) · 🇭🇺 [hu](../../hu/docs/AUTO-COMBO.md) · 🇮🇩 [id](../../id/docs/AUTO-COMBO.md) · 🇮🇹 [it](../../it/docs/AUTO-COMBO.md) · 🇯🇵 [ja](../../ja/docs/AUTO-COMBO.md) · 🇰🇷 [ko](../../ko/docs/AUTO-COMBO.md) · 🇮🇳 [mr](../../mr/docs/AUTO-COMBO.md) · 🇲🇾 [ms](../../ms/docs/AUTO-COMBO.md) · 🇳🇱 [nl](../../nl/docs/AUTO-COMBO.md) · 🇳🇴 [no](../../no/docs/AUTO-COMBO.md) · 🇵🇭 [phi](../../phi/docs/AUTO-COMBO.md) · 🇵🇱 [pl](../../pl/docs/AUTO-COMBO.md) · 🇵🇹 [pt](../../pt/docs/AUTO-COMBO.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/AUTO-COMBO.md) · 🇷🇴 [ro](../../ro/docs/AUTO-COMBO.md) · 🇷🇺 [ru](../../ru/docs/AUTO-COMBO.md) · 🇸🇰 [sk](../../sk/docs/AUTO-COMBO.md) · 🇸🇪 [sv](../../sv/docs/AUTO-COMBO.md) · 🇰🇪 [sw](../../sw/docs/AUTO-COMBO.md) · 🇮🇳 [ta](../../ta/docs/AUTO-COMBO.md) · 🇮🇳 [te](../../te/docs/AUTO-COMBO.md) · 🇹🇭 [th](../../th/docs/AUTO-COMBO.md) · 🇹🇷 [tr](../../tr/docs/AUTO-COMBO.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/AUTO-COMBO.md) · 🇵🇰 [ur](../../ur/docs/AUTO-COMBO.md) · 🇻🇳 [vi](../../vi/docs/AUTO-COMBO.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/AUTO-COMBO.md) +# OmniRoute Auto-Combo Motoru (Türkçe) + +🌐 **Languages:** 🇺🇸 [English](../../../../docs/routing/AUTO-COMBO.md) · 🇸🇦 [ar](../../ar/docs/routing/AUTO-COMBO.md) · 🇧🇬 [bg](../../bg/docs/routing/AUTO-COMBO.md) · 🇧🇩 [bn](../../bn/docs/routing/AUTO-COMBO.md) · 🇨🇿 [cs](../../cs/docs/routing/AUTO-COMBO.md) · 🇩🇰 [da](../../da/docs/routing/AUTO-COMBO.md) · 🇩🇪 [de](../../de/docs/routing/AUTO-COMBO.md) · 🇪🇸 [es](../../es/docs/routing/AUTO-COMBO.md) · 🇮🇷 [fa](../../fa/docs/routing/AUTO-COMBO.md) · 🇫🇮 [fi](../../fi/docs/routing/AUTO-COMBO.md) · 🇫🇷 [fr](../../fr/docs/routing/AUTO-COMBO.md) · 🇮🇳 [gu](../../gu/docs/routing/AUTO-COMBO.md) · 🇮🇱 [he](../../he/docs/routing/AUTO-COMBO.md) · 🇮🇳 [hi](../../hi/docs/routing/AUTO-COMBO.md) · 🇭🇺 [hu](../../hu/docs/routing/AUTO-COMBO.md) · 🇮🇩 [id](../../id/docs/routing/AUTO-COMBO.md) · 🇮🇹 [it](../../it/docs/routing/AUTO-COMBO.md) · 🇯🇵 [ja](../../ja/docs/routing/AUTO-COMBO.md) · 🇰🇷 [ko](../../ko/docs/routing/AUTO-COMBO.md) · 🇮🇳 [mr](../../mr/docs/routing/AUTO-COMBO.md) · 🇲🇾 [ms](../../ms/docs/routing/AUTO-COMBO.md) · 🇳🇱 [nl](../../nl/docs/routing/AUTO-COMBO.md) · 🇳🇴 [no](../../no/docs/routing/AUTO-COMBO.md) · 🇵🇭 [phi](../../phi/docs/routing/AUTO-COMBO.md) · 🇵🇱 [pl](../../pl/docs/routing/AUTO-COMBO.md) · 🇵🇹 [pt](../../pt/docs/routing/AUTO-COMBO.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/routing/AUTO-COMBO.md) · 🇷🇴 [ro](../../ro/docs/routing/AUTO-COMBO.md) · 🇷🇺 [ru](../../ru/docs/routing/AUTO-COMBO.md) · 🇸🇰 [sk](../../sk/docs/routing/AUTO-COMBO.md) · 🇸🇪 [sv](../../sv/docs/routing/AUTO-COMBO.md) · 🇰🇪 [sw](../../sw/docs/routing/AUTO-COMBO.md) · 🇮🇳 [ta](../../ta/docs/routing/AUTO-COMBO.md) · 🇮🇳 [te](../../te/docs/routing/AUTO-COMBO.md) · 🇹🇭 [th](../../th/docs/routing/AUTO-COMBO.md) · 🇹🇷 [tr](../../tr/docs/routing/AUTO-COMBO.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/routing/AUTO-COMBO.md) · 🇵🇰 [ur](../../ur/docs/routing/AUTO-COMBO.md) · 🇻🇳 [vi](../../vi/docs/routing/AUTO-COMBO.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/routing/AUTO-COMBO.md) --- -> Self-managing model chains with adaptive scoring +> Uyarlanabilir puanlama + sıfır yapılandırmalı otomatik yönlendirme ile kendi kendini yöneten model zincirleri -## How It Works +## Sıfır Yapılandırmalı Otomatik Yönlendirme (`auto/` Öneki) -The Auto-Combo Engine dynamically selects the best provider/model for each request using a **6-factor scoring function**: +> **YENİ:** Kombo oluşturma gerektirmez. Herhangi bir istemcide doğrudan `auto/` önekini kullanın. -| Factor | Weight | Description | -| :--------- | :----- | :---------------------------------------------- | -| Quota | 0.20 | Remaining capacity [0..1] | -| Health | 0.25 | Circuit breaker: CLOSED=1.0, HALF=0.5, OPEN=0.0 | -| CostInv | 0.20 | Inverse cost (cheaper = higher score) | -| LatencyInv | 0.15 | Inverse p95 latency (faster = higher) | -| TaskFit | 0.10 | Model × task type fitness score | -| Stability | 0.10 | Low variance in latency/errors | +### Hızlı Örnekler -## Mode Packs +| Model ID | Varyant | Davranış | +| -------------- | ------- | ------------------------------------------------------------------------ | +| `auto` | varsayılan | Tüm bağlı sağlayıcılar, LKGP stratejisi, dengeli ağırlıklar | +| `auto/coding` | coding | Kalite öncelikli ağırlıklar, kod üretimi için optimize | +| `auto/fast` | fast | Düşük gecikmeli ağırlıklı seçim | +| `auto/cheap` | cheap | Maliyet optimizasyonlu yönlendirme (en ucuz olan önce) | +| `auto/offline` | offline | En yüksek kota kullanılabilirliğine sahip sağlayıcıları tercih eder | +| `auto/smart` | smart | Kalite öncelikli + daha iyi model keşfi için %10 keşif oranı | +| `auto/lkgp` | lkgp | Açık LKGP (varsayılan `auto` ile aynı) | -| Pack | Focus | Key Weight | -| :---------------------- | :----------- | :--------------- | -| 🚀 **Ship Fast** | Speed | latencyInv: 0.35 | -| 💰 **Cost Saver** | Economy | costInv: 0.40 | -| 🎯 **Quality First** | Best model | taskFit: 0.40 | -| 📡 **Offline Friendly** | Availability | quota: 0.40 | +### Kategori × Katman Birleşimi (`auto/:`) -## Self-Healing +OpenRouter tarzı sonekler, **ne tür bir rota** (kategori) ile **nasıl optimize edileceğini** (katman) ayırır: -- **Temporary exclusion**: Score < 0.2 → excluded for 5 min (progressive backoff, max 30 min) -- **Circuit breaker awareness**: OPEN → auto-excluded; HALF_OPEN → probe requests -- **Incident mode**: >50% OPEN → disable exploration, maximize stability -- **Cooldown recovery**: After exclusion, first request is a "probe" with reduced timeout +- **Kategoriler** (aday havuzunu yeteneğe göre filtreler): `coding` · `reasoning` · `vision` · `chat` · `multimodal`. +- **Katmanlar** (puanlama ağırlıklarını seçer): `fast` · `cheap` · `reliable` · `free` / `pro`. -## Bandit Exploration +| Örnek | Çözümlendiği Rota | +| ---------------------- | ------------------------------------------------------- | +| `auto/coding:fast` | kodlama havuzu, düşük gecikmeli ağırlıklar | +| `auto/coding:cheap` | kodlama havuzu, maliyet optimizasyonlu | +| `auto/reasoning:pro` | yalnızca akıl yürütme/düşünme modelleri, premium katman | +| `auto/vision` | vision yetenekli modeller (dengeli ağırlıklar) | +| `auto/multimodal:free` | çok modlu modeller, yalnızca ücretsiz katman | -5% of requests (configurable) are routed to random providers for exploration. Disabled in incident mode. +--- -## API +## 14 Faktörlü Auto-Combo Puanlama Matrisi -```bash -# Create auto-combo -curl -X POST http://localhost:20128/api/combos/auto \ - -H "Content-Type: application/json" \ - -d '{"id":"my-auto","name":"Auto Coder","candidatePool":["anthropic","google","openai"],"modePack":"ship-fast"}' +Auto-Combo motoru, her istek için aday sağlayıcıları **14 bağımsız faktör** üzerinden canlı olarak puanlar: -# List auto-combos -curl http://localhost:20128/api/combos/auto -``` - -## Task Fitness - -30+ models scored across 6 task types (`coding`, `review`, `planning`, `analysis`, `debugging`, `documentation`). Supports wildcard patterns (e.g., `*-coder` → high coding score). - -## Files - -| File | Purpose | -| :------------------------------------------- | :------------------------------------ | -| `open-sse/services/autoCombo/scoring.ts` | Scoring function & pool normalization | -| `open-sse/services/autoCombo/taskFitness.ts` | Model × task fitness lookup | -| `open-sse/services/autoCombo/engine.ts` | Selection logic, bandit, budget cap | -| `open-sse/services/autoCombo/selfHealing.ts` | Exclusion, probes, incident mode | -| `open-sse/services/autoCombo/modePacks.ts` | 4 weight profiles | -| `src/app/api/combos/auto/route.ts` | REST API | +1. **Sağlık Durumu (Health):** Devre kesici durumu (KAPALI = 1.0, AÇIK = 0.0). +2. **Kalan Kota Oranı (Quota Remaining):** Mevcut kota penceresinde kalan yüzde. +3. **Kota Hacmi (Quota Headroom):** Kalan mutlak token veya istek miktarı. +4. **Maliyet Etkinliği (Cost):** Giriş/çıkış token başına katalog fiyatı ($). +5. **Gecikme (Latency):** p50/p95 geçmiş yanıt süresi (ms). +6. **Başarı Oranı (Success Rate):** Son 100 çağrıdaki 2xx HTTP yanıt oranı. +7. **Tazelik (Freshness):** Sağlayıcının son başarılı kullanımından bu yana geçen süre. +8. **LKGP Uyumu (Stickiness):** Son başarılı sağlayıcıya sadakat puanı. +9. **Hata Oranı Eğilimi (Error Rate Trend):** Son 5 dakikadaki 429/5xx hata sıklığı. +10. **Kota Sıfırlanma Yakınlığı (Reset Proximity):** Kota sıfırlanmasına kalan süre. +11. **Önbellek Uyumu (Cache Affinity):** İstem önbelleğini (prompt cache) tutan bağlantıya öncelik verme. +12. **Model Yetenek Uyumu (Capability Match):** Vision, araç çağırma, JSON şema desteği. +13. **Bandit Keşif Payı (Exploration Boost):** Daha iyi modelleri keşfetmek için rastgele deneme ağırlığı. +14. **Yük Dengeleme (Load Distribution):** P2C (power of two choices) ile eşzamanlı istek dağılımı. From b5e4c2c0cea09666f5c4119fffb760bca77017d7 Mon Sep 17 00:00:00 2001 From: crmbadesaba-commits Date: Sun, 23 Aug 2026 20:55:14 +0330 Subject: [PATCH 13/20] docs(i18n): add complete Persian user guide translation (#11254) Validated on the combined 12-PR batch board: check:docs-all passes (doc-links + fabricated-docs strict). Complete Persian USER_GUIDE translation with preserved commands, identifiers and fixed relative links. Thank you @crmbadesaba-commits! --- docs/i18n/fa/docs/guides/USER_GUIDE.md | 660 ++++++++++++------------- 1 file changed, 330 insertions(+), 330 deletions(-) diff --git a/docs/i18n/fa/docs/guides/USER_GUIDE.md b/docs/i18n/fa/docs/guides/USER_GUIDE.md index 6a103e37e9..998249c08c 100644 --- a/docs/i18n/fa/docs/guides/USER_GUIDE.md +++ b/docs/i18n/fa/docs/guides/USER_GUIDE.md @@ -1,139 +1,139 @@ -# User Guide (فارسی) +# راهنمای کاربر (فارسی) -🌐 **Languages:** 🇺🇸 [English](../../../../docs/USER_GUIDE.md) · 🇸🇦 [ar](../../ar/docs/USER_GUIDE.md) · 🇧🇬 [bg](../../bg/docs/USER_GUIDE.md) · 🇧🇩 [bn](../../bn/docs/USER_GUIDE.md) · 🇨🇿 [cs](../../cs/docs/USER_GUIDE.md) · 🇩🇰 [da](../../da/docs/USER_GUIDE.md) · 🇩🇪 [de](../../de/docs/USER_GUIDE.md) · 🇪🇸 [es](../../es/docs/USER_GUIDE.md) · 🇮🇷 [fa](../../fa/docs/USER_GUIDE.md) · 🇫🇮 [fi](../../fi/docs/USER_GUIDE.md) · 🇫🇷 [fr](../../fr/docs/USER_GUIDE.md) · 🇮🇳 [gu](../../gu/docs/USER_GUIDE.md) · 🇮🇱 [he](../../he/docs/USER_GUIDE.md) · 🇮🇳 [hi](../../hi/docs/USER_GUIDE.md) · 🇭🇺 [hu](../../hu/docs/USER_GUIDE.md) · 🇮🇩 [id](../../id/docs/USER_GUIDE.md) · 🇮🇹 [it](../../it/docs/USER_GUIDE.md) · 🇯🇵 [ja](../../ja/docs/USER_GUIDE.md) · 🇰🇷 [ko](../../ko/docs/USER_GUIDE.md) · 🇮🇳 [mr](../../mr/docs/USER_GUIDE.md) · 🇲🇾 [ms](../../ms/docs/USER_GUIDE.md) · 🇳🇱 [nl](../../nl/docs/USER_GUIDE.md) · 🇳🇴 [no](../../no/docs/USER_GUIDE.md) · 🇵🇭 [phi](../../phi/docs/USER_GUIDE.md) · 🇵🇱 [pl](../../pl/docs/USER_GUIDE.md) · 🇵🇹 [pt](../../pt/docs/USER_GUIDE.md) · 🇧🇷 [pt-BR](../../pt-BR/docs/USER_GUIDE.md) · 🇷🇴 [ro](../../ro/docs/USER_GUIDE.md) · 🇷🇺 [ru](../../ru/docs/USER_GUIDE.md) · 🇸🇰 [sk](../../sk/docs/USER_GUIDE.md) · 🇸🇪 [sv](../../sv/docs/USER_GUIDE.md) · 🇰🇪 [sw](../../sw/docs/USER_GUIDE.md) · 🇮🇳 [ta](../../ta/docs/USER_GUIDE.md) · 🇮🇳 [te](../../te/docs/USER_GUIDE.md) · 🇹🇭 [th](../../th/docs/USER_GUIDE.md) · 🇹🇷 [tr](../../tr/docs/USER_GUIDE.md) · 🇺🇦 [uk-UA](../../uk-UA/docs/USER_GUIDE.md) · 🇵🇰 [ur](../../ur/docs/USER_GUIDE.md) · 🇻🇳 [vi](../../vi/docs/USER_GUIDE.md) · 🇨🇳 [zh-CN](../../zh-CN/docs/USER_GUIDE.md) +🌐 **زبان‌ها:** 🇺🇸 [English](../../../../guides/USER_GUIDE.md) · 🇸🇦 [ar](../../../ar/docs/guides/USER_GUIDE.md) · 🇧🇬 [bg](../../../bg/docs/guides/USER_GUIDE.md) · 🇧🇩 [bn](../../../bn/docs/guides/USER_GUIDE.md) · 🇨🇿 [cs](../../../cs/docs/guides/USER_GUIDE.md) · 🇩🇰 [da](../../../da/docs/guides/USER_GUIDE.md) · 🇩🇪 [de](../../../de/docs/guides/USER_GUIDE.md) · 🇪🇸 [es](../../../es/docs/guides/USER_GUIDE.md) · 🇮🇷 [fa](../../../fa/docs/guides/USER_GUIDE.md) · 🇫🇮 [fi](../../../fi/docs/guides/USER_GUIDE.md) · 🇫🇷 [fr](../../../fr/docs/guides/USER_GUIDE.md) · 🇮🇳 [gu](../../../gu/docs/guides/USER_GUIDE.md) · 🇮🇱 [he](../../../he/docs/guides/USER_GUIDE.md) · 🇮🇳 [hi](../../../hi/docs/guides/USER_GUIDE.md) · 🇭🇺 [hu](../../../hu/docs/guides/USER_GUIDE.md) · 🇮🇩 [id](../../../id/docs/guides/USER_GUIDE.md) · 🇮🇹 [it](../../../it/docs/guides/USER_GUIDE.md) · 🇯🇵 [ja](../../../ja/docs/guides/USER_GUIDE.md) · 🇰🇷 [ko](../../../ko/docs/guides/USER_GUIDE.md) · 🇮🇳 [mr](../../../mr/docs/guides/USER_GUIDE.md) · 🇲🇾 [ms](../../../ms/docs/guides/USER_GUIDE.md) · 🇳🇱 [nl](../../../nl/docs/guides/USER_GUIDE.md) · 🇳🇴 [no](../../../no/docs/guides/USER_GUIDE.md) · 🇵🇭 [phi](../../../phi/docs/guides/USER_GUIDE.md) · 🇵🇱 [pl](../../../pl/docs/guides/USER_GUIDE.md) · 🇵🇹 [pt](../../../pt/docs/guides/USER_GUIDE.md) · 🇧🇷 [pt-BR](../../../pt-BR/docs/guides/USER_GUIDE.md) · 🇷🇴 [ro](../../../ro/docs/guides/USER_GUIDE.md) · 🇷🇺 [ru](../../../ru/docs/guides/USER_GUIDE.md) · 🇸🇰 [sk](../../../sk/docs/guides/USER_GUIDE.md) · 🇸🇪 [sv](../../../sv/docs/guides/USER_GUIDE.md) · 🇰🇪 [sw](../../../sw/docs/guides/USER_GUIDE.md) · 🇮🇳 [ta](../../../ta/docs/guides/USER_GUIDE.md) · 🇮🇳 [te](../../../te/docs/guides/USER_GUIDE.md) · 🇹🇭 [th](../../../th/docs/guides/USER_GUIDE.md) · 🇹🇷 [tr](../../../tr/docs/guides/USER_GUIDE.md) · 🇺🇦 [uk-UA](../../../uk-UA/docs/guides/USER_GUIDE.md) · 🇵🇰 [ur](../../../ur/docs/guides/USER_GUIDE.md) · 🇻🇳 [vi](../../../vi/docs/guides/USER_GUIDE.md) · 🇨🇳 [zh-CN](../../../zh-CN/docs/guides/USER_GUIDE.md) --- -Complete guide for configuring providers, creating combos, integrating CLI tools, and deploying OmniRoute. +راهنمای کامل پیکربندی ارائه‌دهندگان، ساخت ترکیب‌ها، یکپارچه‌سازی ابزارهای خط فرمان و استقرار OmniRoute. --- -## Table of Contents +## فهرست مطالب -- [Pricing at a Glance](#-pricing-at-a-glance) -- [Use Cases](#-use-cases) -- [Provider Setup](#-provider-setup) -- [CLI Integration](#-cli-integration) -- [Deployment](#-deployment) -- [Available Models](#-available-models) -- [Advanced Features](#-advanced-features) +- [مرور سریع هزینه‌ها](#-مرور-سریع-هزینه‌ها) +- [موارد استفاده](#-موارد-استفاده) +- [راه‌اندازی ارائه‌دهندگان](#-راه‌اندازی-ارائه‌دهندگان) +- [یکپارچه‌سازی با ابزارهای خط فرمان](#-یکپارچه‌سازی-با-ابزارهای-خط-فرمان) +- [استقرار](#-استقرار) +- [مدل‌های موجود](#-مدل‌های-موجود) +- [قابلیت‌های پیشرفته](#-قابلیت‌های-پیشرفته) --- -## 💰 Pricing at a Glance +## 💰 مرور سریع هزینه‌ها -| Tier | Provider | Cost | Quota Reset | Best For | -| ------------------- | ----------------- | ----------- | -------------- | -------------------- | -| **💳 SUBSCRIPTION** | Claude Code (Pro) | $20/mo | 5h + weekly | Already subscribed | -| | Codex (Plus/Pro) | $20-200/mo | 5h + weekly | OpenAI users | -| | GitHub Copilot | $10-19/mo | Monthly | GitHub users | -| **🔑 API KEY** | DeepSeek | Pay per use | None | Cheap reasoning | -| | Groq | Pay per use | None | Ultra-fast inference | -| | xAI (Grok) | Pay per use | None | Grok 4 reasoning | -| | Mistral | Pay per use | None | EU-hosted models | -| | Perplexity | Pay per use | None | Search-augmented | -| | Together AI | Pay per use | None | Open-source models | -| | Fireworks AI | Pay per use | None | Fast FLUX images | -| | Cerebras | Pay per use | None | Wafer-scale speed | -| | Cohere | Pay per use | None | Command R+ RAG | -| | NVIDIA NIM | Pay per use | None | Enterprise models | -| **💰 CHEAP** | GLM-4.7 | $0.6/1M | Daily 10AM | Budget backup | -| | MiniMax M2.1 | $0.2/1M | 5-hour rolling | Cheapest option | -| | Kimi K2 | $9/mo flat | 10M tokens/mo | Predictable cost | -| **🆓 FREE** | Qoder | $0 | Provider limits apply | Verify current catalog | -| | Qwen | $0 | Provider limits apply | Verify current catalog | -| | Kiro | $0 | Provider limits apply | Claude free | +| رده | ارائه‌دهنده | هزینه | بازنشانی سهمیه | مناسب برای | +| ---------------------- | ----------------- | ---------------- | ------------------------- | -------------------------------- | +| **💳 اشتراکی** | Claude Code (Pro) | ماهانه ۲۰ دلار | ۵ ساعته + هفتگی | کاربران دارای اشتراک | +| | Codex (Plus/Pro) | ماهانه ۲۰ تا ۲۰۰ دلار | ۵ ساعته + هفتگی | کاربران OpenAI | +| | GitHub Copilot | ماهانه ۱۰ تا ۱۹ دلار | ماهانه | کاربران GitHub | +| **🔑 کلید API** | DeepSeek | پرداخت به‌ازای مصرف | ندارد | استدلال کم‌هزینه | +| | Groq | پرداخت به‌ازای مصرف | ندارد | استنتاج بسیار سریع | +| | xAI (Grok) | پرداخت به‌ازای مصرف | ندارد | استدلال با Grok 4 | +| | Mistral | پرداخت به‌ازای مصرف | ندارد | مدل‌های میزبانی‌شده در اتحادیه اروپا | +| | Perplexity | پرداخت به‌ازای مصرف | ندارد | جست‌وجوی تقویت‌شده | +| | Together AI | پرداخت به‌ازای مصرف | ندارد | مدل‌های متن‌باز | +| | Fireworks AI | پرداخت به‌ازای مصرف | ندارد | تولید سریع تصویر با FLUX | +| | Cerebras | پرداخت به‌ازای مصرف | ندارد | پردازش پرسرعت در مقیاس ویفر | +| | Cohere | پرداخت به‌ازای مصرف | ندارد | بازیابی تقویت‌شده با Command R+ | +| | NVIDIA NIM | پرداخت به‌ازای مصرف | ندارد | مدل‌های سازمانی | +| **💰 مقرون‌به‌صرفه** | GLM-4.7 | ۰٫۶ دلار/۱میلیون | روزانه ساعت ۱۰ | پشتیبان اقتصادی | +| | MiniMax M2.1 | ۰٫۲ دلار/۱میلیون | بازه چرخشی ۵ ساعته | ارزان‌ترین گزینه | +| | Kimi K2 | ماهانه ۹ دلار ثابت | ماهانه ۱۰ میلیون توکن | هزینه قابل پیش‌بینی | +| **🆓 رایگان** | Qoder | ۰ دلار | تابع محدودیت ارائه‌دهنده | بررسی فهرست فعلی | +| | Qwen | ۰ دلار | تابع محدودیت ارائه‌دهنده | بررسی فهرست فعلی | +| | Kiro | ۰ دلار | تابع محدودیت ارائه‌دهنده | Claude رایگان | --- -## 🎯 Use Cases +## 🎯 موارد استفاده -### Case 1: "I have Claude Pro subscription" +### مورد ۱: «اشتراک Claude Pro دارم» -**Problem:** Quota expires unused, rate limits during heavy coding +**مسئله:** سهمیه بدون استفاده منقضی می‌شود و هنگام کدنویسی سنگین با محدودیت نرخ روبه‌رو می‌شوید. ``` -Combo: "maximize-claude" - 1. cc/claude-opus-4-7 (use subscription fully) - 2. glm/glm-4.7 (cheap backup when quota out) - 3. if/kimi-k2-thinking (free emergency fallback) +ترکیب: "maximize-claude" + 1. cc/claude-opus-4-7 (استفاده کامل از اشتراک) + 2. glm/glm-4.7 (پشتیبان کم‌هزینه پس از پایان سهمیه) + 3. if/kimi-k2-thinking (جایگزین اضطراری رایگان) -Monthly cost: $20 (subscription) + ~$5 (backup) = $25 total -vs. $20 + hitting limits = frustration +هزینه ماهانه: ۲۰ دلار اشتراک + حدود ۵ دلار پشتیبان = در مجموع ۲۵ دلار +در مقایسه با پرداخت ۲۰ دلار و روبه‌روشدن با محدودیت‌ها ``` -### Case 2: "I want zero cost" +### مورد ۲: «می‌خواهم هیچ هزینه‌ای نپردازم» -**Problem:** Can't afford subscriptions, need reliable AI coding +**مسئله:** امکان پرداخت هزینه اشتراک را ندارید و به یک ابزار هوش مصنوعی قابل‌اعتماد برای کدنویسی نیاز دارید. ``` -Combo: "free-tier-fallback" - 1. if/kimi-k2-thinking (no published token cap; limits apply) - 2. qw/qwen3-coder-plus (no published token cap; limits apply) +ترکیب: "free-tier-fallback" + 1. if/kimi-k2-thinking (سقف توکن منتشر نشده است؛ محدودیت‌ها اعمال می‌شوند) + 2. qw/qwen3-coder-plus (سقف توکن منتشر نشده است؛ محدودیت‌ها اعمال می‌شوند) -Monthly cost: $0 -Quality: verify the model, limits, privacy, and SLA for your workload +هزینه ماهانه: ۰ دلار +کیفیت: مدل، محدودیت‌ها، حریم خصوصی و SLA را متناسب با بار کاری خود بررسی کنید ``` -### Case 3: "I need 24/7 coding, no interruptions" +### مورد ۳: «به کدنویسی شبانه‌روزی و بدون وقفه نیاز دارم» -**Problem:** Deadlines, can't afford downtime +**مسئله:** موعد تحویل نزدیک است و نمی‌توانید توقف سرویس را بپذیرید. ``` -Combo: "always-on" - 1. cc/claude-opus-4-7 (best quality) - 2. cx/gpt-5.2-codex (second subscription) - 3. glm/glm-4.7 (cheap, resets daily) - 4. minimax/MiniMax-M2.1 (cheapest, 5h reset) - 5. if/kimi-k2-thinking (free unlimited) +ترکیب: "always-on" + 1. cc/claude-opus-4-7 (بهترین کیفیت) + 2. cx/gpt-5.2-codex (اشتراک دوم) + 3. glm/glm-4.7 (کم‌هزینه با بازنشانی روزانه) + 4. minimax/MiniMax-M2.1 (ارزان‌ترین گزینه با بازنشانی ۵ ساعته) + 5. if/kimi-k2-thinking (رایگان و نامحدود) -Result: 5 fallback layers broaden resilience; upstream availability is not guaranteed -Monthly cost: $20-200 (subscriptions) + $10-20 (backup) +نتیجه: پنج لایه جایگزین، تاب‌آوری را افزایش می‌دهد؛ دسترس‌پذیری سرویس بالادستی تضمین‌شده نیست +هزینه ماهانه: ۲۰ تا ۲۰۰ دلار اشتراک + ۱۰ تا ۲۰ دلار پشتیبان ``` -### Case 4: "I want FREE AI in OpenClaw" +### مورد ۴: «در OpenClaw یک هوش مصنوعی رایگان می‌خواهم» -**Problem:** Need AI assistant in messaging apps, completely free +**مسئله:** به یک دستیار هوش مصنوعی کاملاً رایگان در پیام‌رسان‌ها نیاز دارید. ``` -Combo: "openclaw-free" - 1. if/glm-4.7 (no published token cap; limits apply) - 2. if/minimax-m2.1 (no published token cap; limits apply) - 3. if/kimi-k2-thinking (no published token cap; limits apply) +ترکیب: "openclaw-free" + 1. if/glm-4.7 (سقف توکن منتشر نشده است؛ محدودیت‌ها اعمال می‌شوند) + 2. if/minimax-m2.1 (سقف توکن منتشر نشده است؛ محدودیت‌ها اعمال می‌شوند) + 3. if/kimi-k2-thinking (سقف توکن منتشر نشده است؛ محدودیت‌ها اعمال می‌شوند) -Monthly cost: $0 -Access via: WhatsApp, Telegram, Slack, Discord, iMessage, Signal... +هزینه ماهانه: ۰ دلار +دسترسی از طریق: WhatsApp، Telegram، Slack، Discord، iMessage، Signal و غیره ``` --- -## 📖 Provider Setup +## 📖 راه‌اندازی ارائه‌دهندگان -### 🔐 Subscription Providers +### 🔐 ارائه‌دهندگان اشتراکی #### Claude Code (Pro/Max) ```bash Dashboard → Providers → Connect Claude Code -→ OAuth login → Auto token refresh -→ 5-hour + weekly quota tracking +→ ورود با OAuth → نوسازی خودکار توکن +→ پایش سهمیه ۵ ساعته و هفتگی -Models: +مدل‌ها: cc/claude-opus-4-7 cc/claude-sonnet-4-5-20250929 cc/claude-haiku-4-5-20251001 ``` -**Pro Tip:** Use Opus for complex tasks, Sonnet for speed. OmniRoute tracks quota per model! +**نکته کاربردی:** برای کارهای پیچیده از Opus و برای سرعت بیشتر از Sonnet استفاده کنید. OmniRoute سهمیه هر مدل را جداگانه پایش می‌کند. #### OpenAI Codex (Plus/Pro) ```bash Dashboard → Providers → Connect Codex -→ OAuth login (port 1455) -→ 5-hour + weekly reset +→ ورود با OAuth (درگاه ۱۴۵۵) +→ بازنشانی ۵ ساعته و هفتگی -Models: +مدل‌ها: cx/gpt-5.2-codex cx/gpt-5.1-codex-max ``` @@ -142,101 +142,101 @@ Models: ```bash Dashboard → Providers → Connect GitHub -→ OAuth via GitHub -→ Monthly reset (1st of month) +→ احراز هویت OAuth از طریق GitHub +→ بازنشانی ماهانه (روز نخست ماه) -Models: +مدل‌ها: gh/gpt-5 gh/claude-4.5-sonnet gh/gemini-3.1-pro-preview ``` -### 💰 Cheap Providers +### 💰 ارائه‌دهندگان مقرون‌به‌صرفه -#### GLM-4.7 (Daily reset, $0.6/1M) +#### GLM-4.7 (بازنشانی روزانه، ۰٫۶ دلار به‌ازای یک میلیون توکن) -1. Sign up: [Zhipu AI](https://open.bigmodel.cn/) -2. Get API key from Coding Plan -3. Dashboard → Add API Key: Provider: `glm`, API Key: `your-key` +1. در [Zhipu AI](https://open.bigmodel.cn/) ثبت‌نام کنید. +2. کلید API را از Coding Plan دریافت کنید. +3. در پیشخوان، گزینه Add API Key را انتخاب کنید و Provider را روی `glm` و API Key را روی `your-key` قرار دهید. -**Use:** `glm/glm-4.7` — **Pro Tip:** Coding Plan offers 3× quota at 1/7 cost! Reset daily 10:00 AM. +**نحوه استفاده:** `glm/glm-4.7` — **نکته کاربردی:** Coding Plan با یک‌هفتم هزینه، سه برابر سهمیه ارائه می‌دهد. سهمیه هر روز ساعت ۱۰ صبح بازنشانی می‌شود. -#### MiniMax M2.1 (5h reset, $0.20/1M) +#### MiniMax M2.1 (بازنشانی ۵ ساعته، ۰٫۲۰ دلار به‌ازای یک میلیون توکن) -1. Sign up: [MiniMax](https://www.minimax.io/) -2. Get API key → Dashboard → Add API Key +1. در [MiniMax](https://www.minimax.io/) ثبت‌نام کنید. +2. کلید API را دریافت کنید و سپس در پیشخوان، Add API Key را انتخاب کنید. -**Use:** `minimax/MiniMax-M2.1` — **Pro Tip:** Cheapest option for long context (1M tokens)! +**نحوه استفاده:** `minimax/MiniMax-M2.1` — **نکته کاربردی:** این گزینه برای متن‌های طولانی تا یک میلیون توکن، ارزان‌ترین انتخاب است. -#### Kimi K2 ($9/month flat) +#### Kimi K2 (ماهانه ۹ دلار ثابت) -1. Subscribe: [Moonshot AI](https://platform.moonshot.ai/) -2. Get API key → Dashboard → Add API Key +1. در [Moonshot AI](https://platform.moonshot.ai/) اشتراک تهیه کنید. +2. کلید API را دریافت کنید و سپس در پیشخوان، Add API Key را انتخاب کنید. -**Use:** `kimi/kimi-latest` — **Pro Tip:** Fixed $9/month for 10M tokens = $0.90/1M effective cost! +**نحوه استفاده:** `kimi/kimi-latest` — **نکته کاربردی:** هزینه ثابت ۹ دلار در ماه برای ۱۰ میلیون توکن، معادل هزینه مؤثر ۰٫۹۰ دلار به‌ازای هر یک میلیون توکن است. -### 🆓 FREE Providers +### 🆓 ارائه‌دهندگان رایگان -#### Qoder (8 FREE models) +#### Qoder (۸ مدل رایگان) ```bash -Dashboard → Connect Qoder → OAuth login → Access is subject to current provider limits +Dashboard → Connect Qoder → ورود با OAuth → دسترسی تابع محدودیت‌های فعلی ارائه‌دهنده است -Models: if/kimi-k2-thinking, if/qwen3-coder-plus, if/glm-4.7, if/minimax-m2, if/deepseek-r1 +مدل‌ها: if/kimi-k2-thinking, if/qwen3-coder-plus, if/glm-4.7, if/minimax-m2, if/deepseek-r1 ``` -#### Qwen (3 FREE models) +#### Qwen (۳ مدل رایگان) ```bash -Dashboard → Connect Qwen → Device code auth → Access is subject to current provider limits +Dashboard → Connect Qwen → احراز هویت با کد دستگاه → دسترسی تابع محدودیت‌های فعلی ارائه‌دهنده است -Models: qw/qwen3-coder-plus, qw/qwen3-coder-flash +مدل‌ها: qw/qwen3-coder-plus, qw/qwen3-coder-flash ``` -#### Kiro (Claude FREE) +#### Kiro (دسترسی رایگان به Claude) ```bash -Dashboard → Connect Kiro → AWS Builder ID or Google/GitHub → Unlimited +Dashboard → Connect Kiro → شناسه AWS Builder یا Google/GitHub → نامحدود -Models: kr/claude-sonnet-4.5, kr/claude-haiku-4.5 +مدل‌ها: kr/claude-sonnet-4.5, kr/claude-haiku-4.5 ``` --- -## 🎨 Combos +## 🎨 ترکیب‌ها -You can reorder combo cards directly in **Dashboard → Combos** by dragging the handle on each card. The order is stored in SQLite and restored on reload. +می‌توانید کارت‌های ترکیب را مستقیماً در مسیر **Dashboard → Combos** با کشیدن دستگیره هر کارت مرتب کنید. ترتیب در SQLite ذخیره می‌شود و پس از بارگذاری مجدد نیز باقی می‌ماند. -### Example 1: Maximize Subscription → Cheap Backup +### مثال ۱: استفاده حداکثری از اشتراک ← پشتیبان کم‌هزینه ``` Dashboard → Combos → Create New -Name: premium-coding -Models: - 1. cc/claude-opus-4-7 (Subscription primary) - 2. glm/glm-4.7 (Cheap backup, $0.6/1M) - 3. minimax/MiniMax-M2.1 (Cheapest fallback, $0.20/1M) +نام: premium-coding +مدل‌ها: + 1. cc/claude-opus-4-7 (اشتراک اصلی) + 2. glm/glm-4.7 (پشتیبان کم‌هزینه، ۰٫۶ دلار/۱میلیون) + 3. minimax/MiniMax-M2.1 (ارزان‌ترین جایگزین، ۰٫۲۰ دلار/۱میلیون) -Use in CLI: premium-coding +استفاده در ابزار خط فرمان: premium-coding ``` -### Example 2: Free-Only (Zero Cost) +### مثال ۲: فقط گزینه‌های رایگان (بدون هزینه) ``` -Name: free-combo -Models: - 1. if/kimi-k2-thinking (no published token cap; provider limits may apply) - 2. qw/qwen3-coder-plus (no published token cap; provider limits may apply) +نام: free-combo +مدل‌ها: + 1. if/kimi-k2-thinking (سقف توکن منتشر نشده است؛ ممکن است محدودیت ارائه‌دهنده اعمال شود) + 2. qw/qwen3-coder-plus (سقف توکن منتشر نشده است؛ ممکن است محدودیت ارائه‌دهنده اعمال شود) -Cost: currently listed as $0; terms and availability may change +هزینه: درحال‌حاضر ۰ دلار اعلام شده است؛ شرایط و دسترس‌پذیری ممکن است تغییر کند ``` --- -## 🔧 CLI Integration +## 🔧 یکپارچه‌سازی با ابزارهای خط فرمان -### Cursor IDE +### محیط توسعه Cursor ``` Settings → Models → Advanced: @@ -247,7 +247,7 @@ Settings → Models → Advanced: ### Claude Code -Edit `~/.claude/config.json`: +فایل `~/.claude/config.json` را ویرایش کنید: ```json { @@ -256,7 +256,7 @@ Edit `~/.claude/config.json`: } ``` -### Codex CLI +### ابزار خط فرمان Codex ```bash export OPENAI_BASE_URL="http://localhost:20128" @@ -266,7 +266,7 @@ codex "your prompt" ### OpenClaw -Edit `~/.openclaw/openclaw.json`: +فایل `~/.openclaw/openclaw.json` را ویرایش کنید: ```json { @@ -288,7 +288,7 @@ Edit `~/.openclaw/openclaw.json`: } ``` -**Or use Dashboard:** CLI Tools → OpenClaw → Auto-config +**یا از پیشخوان استفاده کنید:** CLI Tools → OpenClaw → Auto-config ### Cline / Continue / RooCode @@ -301,9 +301,9 @@ Model: cc/claude-opus-4-7 --- -## Despliegue +## 🚀 استقرار -### Global npm install (Recommended) +### نصب سراسری با npm (پیشنهادی) ```bash npm install -g omniroute @@ -320,20 +320,20 @@ omniroute omniroute --port 3000 ``` -The CLI automatically loads `.env` from `~/.omniroute/.env` or `./.env`. +ابزار خط فرمان فایل `.env` را به‌طور خودکار از مسیر `~/.omniroute/.env` یا `./.env` بارگذاری می‌کند. -### Uninstalling +### حذف برنامه -When you no longer need OmniRoute, we provide two quick scripts for a clean removal: +هنگامی که دیگر به OmniRoute نیاز ندارید، برای حذف تمیز برنامه دو اسکریپت سریع در اختیار دارید: -| Command | Action | -| ------------------------ | ----------------------------------------------------------------------------------- | -| `npm run uninstall` | Removes the system app but **keeps your DB and configurations** in `~/.omniroute`. | -| `npm run uninstall:full` | Removes the app AND permanently **erases all configurations, keys, and databases**. | +| دستور | عملکرد | +| ----------------------- | ---------------------------------------------------------------------------------------------- | +| `npm run uninstall` | برنامه را از سیستم حذف می‌کند، اما **پایگاه داده و تنظیمات شما** را در `~/.omniroute` نگه می‌دارد. | +| `npm run uninstall:full` | برنامه را حذف می‌کند و **تمام تنظیمات، کلیدها و پایگاه‌های داده را برای همیشه پاک می‌کند**. | -> Note: To run these commands, navigate to the OmniRoute project folder (if you cloned it) and run them. Alternatively, if globally installed, you can simply run `npm uninstall -g omniroute`. +> **توجه:** اگر مخزن را کلون کرده‌اید، برای اجرای این دستورها به پوشه پروژه OmniRoute بروید. اگر برنامه را به‌صورت سراسری نصب کرده‌اید، می‌توانید از دستور `npm uninstall -g omniroute` استفاده کنید. -### VPS Deployment +### استقرار روی VPS ```bash git clone https://github.com/diegosouzapw/OmniRoute.git @@ -352,9 +352,9 @@ npm run start # Or: pm2 start npm --name omniroute -- start ``` -### PM2 Deployment (Low Memory) +### استقرار با PM2 (حافظه کم) -For servers with limited RAM, use the memory limit option: +برای سرورهایی با حافظه محدود، از گزینه تعیین سقف حافظه استفاده کنید: ```bash # With 512MB limit (default) @@ -367,7 +367,7 @@ OMNIROUTE_MEMORY_MB=512 pm2 start npm --name omniroute -- start pm2 start ecosystem.config.js ``` -Create `ecosystem.config.js`: +فایل `ecosystem.config.js` را ایجاد کنید: ```javascript module.exports = { @@ -399,14 +399,14 @@ docker build -t omniroute:cli . docker run -d --name omniroute -p 20128:20128 --env-file ./.env -v omniroute-data:/app/data omniroute:cli ``` -For host-integrated mode with CLI binaries, see the Docker section in the main docs. +برای استفاده در حالت یکپارچه با میزبان و همراه با فایل‌های اجرایی خط فرمان، بخش Docker در مستندات اصلی را ببینید. -### Void Linux (xbps-src) +### Void Linux ‏(xbps-src) -Void Linux users can package and install OmniRoute natively using the `xbps-src` cross-compilation framework. This automates the Node.js standalone build along with the required `better-sqlite3` native bindings. +کاربران Void Linux می‌توانند با چارچوب کامپایل چندسکویی `xbps-src`، بسته بومی OmniRoute را بسازند و نصب کنند. این فرایند، ساخت مستقل Node.js و اتصال‌های بومی لازم برای `better-sqlite3` را به‌صورت خودکار انجام می‌دهد.
-View xbps-src template +مشاهده قالب xbps-src ```bash # Template file for 'omniroute' @@ -501,39 +501,39 @@ post_install() {
-### Environment Variables +### متغیرهای محیطی -| Variable | Default | Description | -| --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | -| `JWT_SECRET` | `omniroute-default-secret-change-me` | JWT signing secret (**change in production**) | -| `INITIAL_PASSWORD` | `123456` | First login password | -| `DATA_DIR` | `~/.omniroute` | Data directory (db, usage, logs) | -| `PORT` | framework default | Service port (`20128` in examples) | -| `HOSTNAME` | framework default | Bind host (Docker defaults to `0.0.0.0`) | -| `NODE_ENV` | runtime default | Set `production` for deploy | -| `BASE_URL` | `http://localhost:20128` | Server-side internal base URL | -| `CLOUD_URL` | `https://omniroute.dev` | Cloud sync endpoint base URL | -| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | HMAC secret for generated API keys | -| `REQUIRE_API_KEY` | `false` | Enforce Bearer API key on `/v1/*` | -| `ALLOW_API_KEY_REVEAL` | `false` | Allow Api Manager to copy full API keys on demand | -| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | Server-side refresh cadence for cached Provider Limits data; UI refresh buttons still trigger manual sync | -| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | Disable automatic SQLite snapshots before writes/import/restore; manual backups still work | -| `APP_LOG_TO_FILE` | `true` | Enables application and audit log output to disk | -| `AUTH_COOKIE_SECURE` | `false` | Force `Secure` auth cookie (behind HTTPS reverse proxy) | -| `CLOUDFLARED_BIN` | unset | Use an existing `cloudflared` binary instead of managed download | -| `CLOUDFLARED_PROTOCOL` | `http2` | Transport for managed Quick Tunnels (`http2`, `quic`, or `auto`) | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit in MB | -| `PROMPT_CACHE_MAX_SIZE` | `50` | Max prompt cache entries | -| `SEMANTIC_CACHE_MAX_SIZE` | `100` | Max semantic cache entries | +| متغیر | مقدار پیش‌فرض | توضیح | +| --------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------- | +| `JWT_SECRET` | `omniroute-default-secret-change-me` | کلید محرمانه امضای JWT؛ **در محیط عملیاتی تغییر دهید** | +| `INITIAL_PASSWORD` | `123456` | گذرواژه نخستین ورود | +| `DATA_DIR` | `~/.omniroute` | پوشه داده‌ها شامل پایگاه داده، میزان مصرف و گزارش‌ها | +| `PORT` | پیش‌فرض چارچوب | درگاه سرویس؛ در مثال‌ها `20128` | +| `HOSTNAME` | پیش‌فرض چارچوب | میزبان اتصال؛ مقدار پیش‌فرض Docker برابر `0.0.0.0` است | +| `NODE_ENV` | پیش‌فرض محیط اجرا | برای استقرار روی `production` تنظیم کنید | +| `BASE_URL` | `http://localhost:20128` | نشانی پایه داخلی سمت سرور | +| `CLOUD_URL` | `https://omniroute.dev` | نشانی پایه نقطه پایانی همگام‌سازی ابری | +| `API_KEY_SECRET` | `endpoint-proxy-api-key-secret` | کلید محرمانه HMAC برای تولید کلیدهای API | +| `REQUIRE_API_KEY` | `false` | الزام کلید Bearer API برای مسیرهای `/v1/*` | +| `ALLOW_API_KEY_REVEAL` | `false` | اجازه به مدیر API برای کپی کامل کلیدهای API در صورت درخواست | +| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | فاصله به‌روزرسانی داده‌های ذخیره‌شده محدودیت ارائه‌دهنده در سرور؛ دکمه‌های به‌روزرسانی رابط همچنان همگام‌سازی دستی را اجرا می‌کنند | +| `DISABLE_SQLITE_AUTO_BACKUP` | `false` | غیرفعال‌کردن نسخه پشتیبان خودکار SQLite پیش از نوشتن، ورود یا بازیابی؛ پشتیبان‌گیری دستی همچنان فعال است | +| `APP_LOG_TO_FILE` | `true` | فعال‌سازی ذخیره گزارش برنامه و ممیزی روی دیسک | +| `AUTH_COOKIE_SECURE` | `false` | اجبار ویژگی `Secure` برای کوکی احراز هویت در پشت پراکسی معکوس HTTPS | +| `CLOUDFLARED_BIN` | تنظیم‌نشده | استفاده از فایل اجرایی موجود `cloudflared` به‌جای دانلود مدیریت‌شده | +| `CLOUDFLARED_PROTOCOL` | `http2` | روش انتقال برای تونل‌های سریع مدیریت‌شده؛ یکی از `http2`، `quic` یا `auto` | +| `OMNIROUTE_MEMORY_MB` | `512` | سقف حافظه heap در Node.js بر حسب مگابایت | +| `PROMPT_CACHE_MAX_SIZE` | `50` | حداکثر تعداد ورودی‌های حافظه نهان پرامپت | +| `SEMANTIC_CACHE_MAX_SIZE` | `100` | حداکثر تعداد ورودی‌های حافظه نهان معنایی | -For the full environment variable reference, see the [README](../README.md). +برای مشاهده فهرست کامل متغیرهای محیطی، به [README](../../README.md) مراجعه کنید. --- -## 📊 Available Models +## 📊 مدل‌های موجود
-View all available models +مشاهده همه مدل‌های موجود **Claude Code (`cc/`)** — Pro/Max: `cc/claude-opus-4-7`, `cc/claude-sonnet-4-5-20250929`, `cc/claude-haiku-4-5-20251001` @@ -545,11 +545,11 @@ For the full environment variable reference, see the [README](../README.md). **MiniMax (`minimax/`)** — $0.2/1M: `minimax/MiniMax-M2.1` -**Qoder (`if/`)** — FREE: `if/kimi-k2-thinking`, `if/qwen3-coder-plus`, `if/deepseek-r1` +**Qoder (`if/`)** — رایگان: `if/kimi-k2-thinking`, `if/qwen3-coder-plus`, `if/deepseek-r1` -**Qwen (`qw/`)** — FREE: `qw/qwen3-coder-plus`, `qw/qwen3-coder-flash` +**Qwen (`qw/`)** — رایگان: `qw/qwen3-coder-plus`, `qw/qwen3-coder-flash` -**Kiro (`kr/`)** — FREE: `kr/claude-sonnet-4.5`, `kr/claude-haiku-4.5` +**Kiro (`kr/`)** — رایگان: `kr/claude-sonnet-4.5`, `kr/claude-haiku-4.5` **DeepSeek (`ds/`)**: `ds/deepseek-chat`, `ds/deepseek-reasoner` @@ -575,11 +575,11 @@ For the full environment variable reference, see the [README](../README.md). --- -## 🧩 Advanced Features +## 🧩 قابلیت‌های پیشرفته -### Custom Models +### مدل‌های سفارشی -Add any model ID to any provider without waiting for an app update: +بدون نیاز به انتظار برای به‌روزرسانی برنامه، شناسه هر مدلی را به هر ارائه‌دهنده اضافه کنید: ```bash # Via API @@ -591,16 +591,16 @@ curl -X POST http://localhost:20128/api/provider-models \ # Remove: curl -X DELETE "http://localhost:20128/api/provider-models?provider=openai&model=gpt-4.5-preview" ``` -Or use Dashboard: **Providers → [Provider] → Custom Models**. +یا در پیشخوان به مسیر **Providers → [Provider] → Custom Models** بروید. -Notes: +نکات: -- OpenRouter and OpenAI/Anthropic-compatible providers are managed from **Available Models** only. Manual add, import, and auto-sync all land in the same available-model list, so there is no separate Custom Models section for those providers. -- The **Custom Models** section is intended for providers that do not expose managed available-model imports. +- ارائه‌دهندگان سازگار با OpenRouter و OpenAI/Anthropic فقط از بخش **Available Models** مدیریت می‌شوند. افزودن دستی، درون‌ریزی و همگام‌سازی خودکار همگی به یک فهرست مشترک از مدل‌های موجود وارد می‌شوند؛ بنابراین برای این ارائه‌دهندگان بخش جداگانه‌ای با عنوان Custom Models وجود ندارد. +- بخش **Custom Models** برای ارائه‌دهندگانی است که امکان مدیریت و درون‌ریزی مدل‌های موجود را فراهم نمی‌کنند. -### Dedicated Provider Routes +### مسیرهای اختصاصی ارائه‌دهندگان -Route requests directly to a specific provider with model validation: +درخواست‌ها را همراه با اعتبارسنجی مدل، مستقیماً به یک ارائه‌دهنده مشخص هدایت کنید: ```bash POST http://localhost:20128/v1/providers/openai/chat/completions @@ -608,9 +608,9 @@ POST http://localhost:20128/v1/providers/openai/embeddings POST http://localhost:20128/v1/providers/fireworks/images/generations ``` -The provider prefix is auto-added if missing. Mismatched models return `400`. +اگر پیشوند ارائه‌دهنده وجود نداشته باشد، به‌طور خودکار افزوده می‌شود. در صورت ناسازگاری مدل، پاسخ `400` برگردانده می‌شود. -### Network Proxy Configuration +### پیکربندی پراکسی شبکه ```bash # Set global proxy @@ -626,103 +626,103 @@ curl -X POST http://localhost:20128/api/settings/proxy/test \ -d '{"proxy":{"type":"socks5","host":"proxy.example.com","port":"1080"}}' ``` -**Precedence:** Key-specific → Combo-specific → Provider-specific → Global → Environment. +**ترتیب اولویت:** مختص کلید ← مختص ترکیب ← مختص ارائه‌دهنده ← سراسری ← محیط. -### Model Catalog API +### API فهرست مدل‌ها ```bash curl http://localhost:20128/api/models/catalog ``` -Returns models grouped by provider with types (`chat`, `embedding`, `image`). +مدل‌ها را بر اساس ارائه‌دهنده و همراه با نوع آن‌ها (`chat`، `embedding` و `image`) برمی‌گرداند. -### Cloud Sync +### همگام‌سازی ابری -- Sync providers, combos, and settings across devices -- Automatic background sync with timeout + fail-fast -- Prefer server-side `BASE_URL`/`CLOUD_URL` in production +- همگام‌سازی ارائه‌دهندگان، ترکیب‌ها و تنظیمات بین دستگاه‌ها +- همگام‌سازی خودکار در پس‌زمینه همراه با مهلت زمانی و توقف سریع در صورت خطا +- اولویت‌دادن به `BASE_URL` و `CLOUD_URL` سمت سرور در محیط عملیاتی -### Cloudflare Quick Tunnel +### تونل سریع Cloudflare -- Available in **Dashboard → Endpoints** for Docker and other self-hosted deployments -- Creates a temporary `https://*.trycloudflare.com` URL that forwards to your current OpenAI-compatible `/v1` endpoint -- First enable installs `cloudflared` only when needed; later restarts reuse the same managed binary -- Quick Tunnels are not auto-restored after an OmniRoute or container restart; re-enable them from the dashboard when needed -- Tunnel URLs are ephemeral and change every time you stop/start the tunnel -- Managed Quick Tunnels default to HTTP/2 transport to avoid noisy QUIC UDP buffer warnings in constrained containers -- Set `CLOUDFLARED_PROTOCOL=quic` or `auto` if you want to override the managed transport choice -- Set `CLOUDFLARED_BIN` if you prefer using a preinstalled `cloudflared` binary instead of the managed download +- برای Docker و دیگر استقرارهای خودمیزبان از مسیر **Dashboard → Endpoints** در دسترس است. +- یک نشانی موقت `https://*.trycloudflare.com` می‌سازد که درخواست‌ها را به نقطه پایانی فعلی و سازگار با OpenAI در مسیر `/v1` هدایت می‌کند. +- در نخستین فعال‌سازی، `cloudflared` فقط در صورت نیاز نصب می‌شود؛ در راه‌اندازی‌های بعدی همان فایل اجرایی مدیریت‌شده دوباره استفاده خواهد شد. +- تونل‌های سریع پس از راه‌اندازی مجدد OmniRoute یا کانتینر، خودکار بازیابی نمی‌شوند؛ در صورت نیاز آن‌ها را دوباره از پیشخوان فعال کنید. +- نشانی تونل‌ها موقتی است و با هر بار توقف و شروع تونل تغییر می‌کند. +- روش انتقال پیش‌فرض تونل‌های سریع مدیریت‌شده HTTP/2 است تا در کانتینرهای محدود، هشدارهای پرتعداد بافر UDP مربوط به QUIC ایجاد نشود. +- برای تغییر روش انتقال مدیریت‌شده، مقدار `CLOUDFLARED_PROTOCOL` را روی `quic` یا `auto` قرار دهید. +- اگر ترجیح می‌دهید به‌جای دانلود مدیریت‌شده از فایل اجرایی ازپیش‌نصب‌شده `cloudflared` استفاده کنید، `CLOUDFLARED_BIN` را تنظیم کنید. -### LLM Gateway Intelligence (Phase 9) +### هوشمندی درگاه مدل‌های زبانی بزرگ (مرحله ۹) -- **Semantic Cache** — Auto-caches non-streaming, temperature=0 responses (bypass with `X-OmniRoute-No-Cache: true`) -- **Request Idempotency** — Deduplicates requests within 5s via `Idempotency-Key` or `X-Request-Id` header -- **Progress Tracking** — Opt-in SSE `event: progress` events via `X-OmniRoute-Progress: true` header +- **حافظه نهان معنایی** — پاسخ‌های غیرجریانی با `temperature=0` را خودکار ذخیره می‌کند؛ برای عبور از آن از `X-OmniRoute-No-Cache: true` استفاده کنید. +- **تکرارناپذیری درخواست** — درخواست‌های تکراری در بازه ۵ ثانیه را با سرآیند `Idempotency-Key` یا `X-Request-Id` حذف می‌کند. +- **پایش پیشرفت** — با سرآیند `X-OmniRoute-Progress: true`، رویدادهای اختیاری SSE از نوع `event: progress` را فعال می‌کند. --- -### Translator Playground +### محیط آزمایش مترجم -Access via **Dashboard → Translator**. Debug and visualize how OmniRoute translates API requests between providers. +از مسیر **Dashboard → Translator** وارد شوید. در این بخش می‌توانید نحوه تبدیل درخواست‌های API بین ارائه‌دهندگان توسط OmniRoute را اشکال‌زدایی و مشاهده کنید. -| Mode | Purpose | -| ---------------- | -------------------------------------------------------------------------------------- | -| **Playground** | Select source/target formats, paste a request, and see the translated output instantly | -| **Chat Tester** | Send live chat messages through the proxy and inspect the full request/response cycle | -| **Test Bench** | Run batch tests across multiple format combinations to verify translation correctness | -| **Live Monitor** | Watch real-time translations as requests flow through the proxy | +| حالت | کاربرد | +| -------------------- | ------------------------------------------------------------------------------------------ | +| **Playground** | انتخاب قالب مبدأ و مقصد، درج یک درخواست و مشاهده فوری خروجی تبدیل‌شده | +| **Chat Tester** | ارسال پیام‌های زنده گفت‌وگو از طریق پراکسی و بررسی چرخه کامل درخواست و پاسخ | +| **Test Bench** | اجرای آزمون‌های دسته‌ای روی ترکیب‌های گوناگون قالب برای اطمینان از صحت تبدیل | +| **Live Monitor** | مشاهده تبدیل‌ها به‌صورت زنده هم‌زمان با عبور درخواست‌ها از پراکسی | -**Use cases:** +**موارد استفاده:** -- Debug why a specific client/provider combination fails -- Verify that thinking tags, tool calls, and system prompts translate correctly -- Compare format differences between OpenAI, Claude, Gemini, and Responses API formats +- بررسی علت شکست یک ترکیب مشخص از کارخواه و ارائه‌دهنده +- اطمینان از تبدیل درست برچسب‌های تفکر، فراخوانی ابزارها و پرامپت‌های سامانه +- مقایسه تفاوت قالب‌ها میان OpenAI، Claude، Gemini و Responses API --- -### Routing Strategies +### راهبردهای مسیریابی -Configure via **Dashboard → Settings → Routing**. +از مسیر **Dashboard → Settings → Routing** پیکربندی کنید. -| Strategy | Description | -| ------------------------------ | ------------------------------------------------------------------------------------------------ | -| **Fill First** | Uses accounts in priority order — primary account handles all requests until unavailable | -| **Round Robin** | Cycles through all accounts with a configurable sticky limit (default: 3 calls per account) | -| **P2C (Power of Two Choices)** | Picks 2 random accounts and routes to the healthier one — balances load with awareness of health | -| **Random** | Randomly selects an account for each request using Fisher-Yates shuffle | -| **Least Used** | Routes to the account with the oldest `lastUsedAt` timestamp, distributing traffic evenly | -| **Cost Optimized** | Routes to the account with the lowest priority value, optimizing for lowest-cost providers | +| راهبرد | توضیح | +| ------------------------------ | ------------------------------------------------------------------------------------------------------- | +| **Fill First** | حساب‌ها را به‌ترتیب اولویت به کار می‌گیرد؛ حساب اصلی تا زمان خارج‌شدن از دسترس همه درخواست‌ها را پردازش می‌کند. | +| **Round Robin** | میان همه حساب‌ها می‌چرخد و از محدودیت چسبندگی قابل‌تنظیم استفاده می‌کند؛ پیش‌فرض سه فراخوانی برای هر حساب است. | +| **P2C (Power of Two Choices)** | دو حساب را تصادفی انتخاب می‌کند و درخواست را به حساب سالم‌تر می‌فرستد؛ بار را با درنظرگرفتن سلامت متعادل می‌کند. | +| **Random** | برای هر درخواست، یک حساب را با درهم‌ریزی Fisher–Yates به‌صورت تصادفی انتخاب می‌کند. | +| **Least Used** | درخواست را به حسابی با قدیمی‌ترین زمان `lastUsedAt` می‌فرستد تا ترافیک به‌طور یکنواخت توزیع شود. | +| **Cost Optimized** | درخواست را به حساب دارای کمترین مقدار اولویت می‌فرستد تا ارائه‌دهندگان کم‌هزینه‌تر انتخاب شوند. | -#### External Sticky Session Header +#### سرآیند خارجی نشست چسبنده -For external session affinity (for example, Claude Code/Codex agents behind reverse proxies), send: +برای حفظ وابستگی نشست در سامانه‌های خارجی، مانند عامل‌های Claude Code یا Codex پشت پراکسی معکوس، سرآیند زیر را ارسال کنید: ```http X-Session-Id: your-session-key ``` -OmniRoute also accepts `x_session_id` and returns the effective session key in `X-OmniRoute-Session-Id`. +OmniRoute مقدار `x_session_id` را نیز می‌پذیرد و کلید مؤثر نشست را در `X-OmniRoute-Session-Id` برمی‌گرداند. -If you use Nginx and send underscore-form headers, enable: +اگر از Nginx استفاده می‌کنید و سرآیندها را با نویسه زیرخط می‌فرستید، گزینه زیر را فعال کنید: ```nginx underscores_in_headers on; ``` -#### Wildcard Model Aliases +#### نام‌های مستعار مدل با نویسه‌های عام -Create wildcard patterns to remap model names: +برای نگاشت دوباره نام مدل‌ها، الگوهای دارای نویسه عام بسازید: ``` Pattern: claude-sonnet-* → Target: cc/claude-sonnet-4-5-20250929 Pattern: gpt-* → Target: gh/gpt-5.1-codex ``` -Wildcards support `*` (any characters) and `?` (single character). +نویسه‌های عام شامل `*` برای هر تعداد نویسه و `?` برای یک نویسه هستند. -#### Fallback Chains +#### زنجیره‌های جایگزین -Define global fallback chains that apply across all requests: +زنجیره‌های جایگزین سراسری تعریف کنید تا بر همه درخواست‌ها اعمال شوند: ``` Chain: production-fallback @@ -733,50 +733,50 @@ Chain: production-fallback --- -### Resilience & Circuit Breakers +### تاب‌آوری و مدارشکن‌ها -Configure via **Dashboard → Settings → Resilience**. +از مسیر **Dashboard → Settings → Resilience** پیکربندی کنید. -OmniRoute implements provider-level resilience with five components: +OmniRoute تاب‌آوری در سطح ارائه‌دهنده را با پنج مؤلفه پیاده‌سازی می‌کند: -1. **Request Queue & Pacing** — System-level request shaping: - - **Requests Per Minute (RPM)** — Maximum requests per minute per account - - **Min Time Between Requests** — Minimum gap in milliseconds between requests - - **Max Concurrent Requests** — Maximum simultaneous requests per account +1. **صف و آهنگ درخواست‌ها** — شکل‌دهی درخواست‌ها در سطح سامانه: + - **درخواست در دقیقه (RPM)** — حداکثر تعداد درخواست در دقیقه برای هر حساب + - **حداقل فاصله میان درخواست‌ها** — کمترین فاصله زمانی میان درخواست‌ها بر حسب میلی‌ثانیه + - **حداکثر درخواست‌های هم‌زمان** — بیشترین تعداد درخواست هم‌زمان برای هر حساب -2. **Connection Cooldown** — Per-auth-type configuration for a single connection after retryable failures: - - **Base Cooldown** — Default cooldown window for retryable upstream failures - - **Use Upstream Retry Hints** — Honors authoritative `Retry-After` or reset hints when provided - - **Max Backoff Steps** — Maximum exponential backoff level for repeated failures +2. **دوره انتظار اتصال** — پیکربندی بر اساس نوع احراز هویت برای یک اتصال پس از خطاهای قابل‌تلاش مجدد: + - **دوره انتظار پایه** — بازه پیش‌فرض انتظار برای خطاهای قابل‌تلاش مجدد سرویس بالادستی + - **استفاده از راهنمای تلاش مجدد سرویس بالادستی** — رعایت مقدار معتبر `Retry-After` یا راهنمای بازنشانی در صورت ارائه + - **حداکثر مراحل عقب‌نشینی** — بیشترین سطح عقب‌نشینی نمایی برای خطاهای تکراری -3. **Provider Circuit Breaker** — Tracks end-to-end provider failures and automatically opens the breaker when the configured threshold is reached: - - **Failure Threshold** — Consecutive provider failures before opening the breaker - - **Reset Timeout** — Time window before the provider is tested again - - **CLOSED** (Healthy) — Requests flow normally - - **OPEN** — Provider is temporarily blocked after repeated failures - - **HALF_OPEN** — Testing if provider has recovered +3. **مدارشکن ارائه‌دهنده** — خطاهای سرتاسری ارائه‌دهنده را پایش می‌کند و پس از رسیدن به آستانه تعیین‌شده، مدار را خودکار باز می‌کند: + - **آستانه خطا** — تعداد خطاهای پیاپی ارائه‌دهنده پیش از بازشدن مدار + - **مهلت بازنشانی** — بازه زمانی پیش از آزمایش دوباره ارائه‌دهنده + - **CLOSED** (سالم) — درخواست‌ها به‌طور عادی جریان دارند + - **OPEN** — ارائه‌دهنده پس از خطاهای تکراری موقتاً مسدود می‌شود + - **HALF_OPEN** — بازیابی ارائه‌دهنده در حال آزمایش است - Connection-scoped `429` rate limits stay in **Connection Cooldown** and do not count toward the provider breaker. + محدودیت نرخ `429` در سطح اتصال داخل **Connection Cooldown** باقی می‌ماند و در مدارشکن ارائه‌دهنده محاسبه نمی‌شود. - The provider breaker runtime state is shown on **Dashboard → Health** only. + وضعیت زمان اجرای مدارشکن ارائه‌دهنده فقط در **Dashboard → Health** نمایش داده می‌شود. -4. **Wait For Cooldown** — If every candidate connection is already cooling down, OmniRoute can wait for the earliest cooldown and retry the same client request automatically. +4. **انتظار برای پایان دوره توقف** — اگر همه اتصال‌های نامزد در دوره انتظار باشند، OmniRoute می‌تواند تا پایان نخستین دوره منتظر بماند و همان درخواست کارخواه را خودکار دوباره اجرا کند. -5. **Rate Limit Auto-Detection** — When upstream providers return explicit wait windows, those hints override the local connection cooldown when the setting is enabled. +5. **تشخیص خودکار محدودیت نرخ** — وقتی ارائه‌دهنده بالادستی بازه انتظار صریحی برمی‌گرداند، در صورت فعال‌بودن این تنظیم، آن راهنما جایگزین دوره انتظار محلی اتصال می‌شود. -**Pro Tip:** Use the **Health** page to inspect and reset live provider breakers after an outage. The Resilience page only changes configuration. +**نکته کاربردی:** پس از اختلال، برای بررسی و بازنشانی مدارشکن‌های فعال ارائه‌دهندگان از صفحه **Health** استفاده کنید. صفحه Resilience فقط پیکربندی را تغییر می‌دهد. --- -### Database Export / Import +### برون‌برد و درون‌ریزی پایگاه داده -Manage database backups in **Dashboard → Settings → System & Storage**. +نسخه‌های پشتیبان پایگاه داده را از مسیر **Dashboard → Settings → System & Storage** مدیریت کنید. -| Action | Description | -| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| **Export Database** | Downloads the current SQLite database as a `.sqlite` file | -| **Export All (.tar.gz)** | Downloads a full backup archive including: database, settings, combos, provider connections (no credentials), API key metadata | -| **Import Database** | Upload a `.sqlite` file to replace the current database. A pre-import backup is automatically created unless `DISABLE_SQLITE_AUTO_BACKUP=true` | +| عملیات | توضیح | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Export Database** | پایگاه داده فعلی SQLite را در قالب فایل `.sqlite` دریافت می‌کند. | +| **Export All (.tar.gz)** | یک بایگانی پشتیبان کامل شامل پایگاه داده، تنظیمات، ترکیب‌ها، اتصال‌های ارائه‌دهندگان بدون اطلاعات ورود و فراداده کلیدهای API دریافت می‌کند. | +| **Import Database** | یک فایل `.sqlite` را برای جایگزینی پایگاه داده فعلی بارگذاری می‌کند. مگر آنکه `DISABLE_SQLITE_AUTO_BACKUP=true` باشد، پیش از درون‌ریزی خودکار نسخه پشتیبان می‌سازد. | ```bash # API: Export database @@ -790,39 +790,39 @@ curl -X POST http://localhost:20128/api/db-backups/import \ -F "file=@backup.sqlite" ``` -**Import Validation:** The imported file is validated for integrity (SQLite pragma check), required tables (`provider_connections`, `provider_nodes`, `combos`, `api_keys`), and size (max 100MB). +**اعتبارسنجی درون‌ریزی:** یکپارچگی فایل واردشده با بررسی pragma در SQLite، وجود جدول‌های لازم (`provider_connections`، `provider_nodes`، `combos` و `api_keys`) و اندازه فایل تا سقف ۱۰۰ مگابایت کنترل می‌شود. -**Use Cases:** +**موارد استفاده:** -- Migrate OmniRoute between machines -- Create external backups for disaster recovery -- Share configurations between team members (export all → share archive) +- انتقال OmniRoute میان دستگاه‌ها +- ساخت نسخه پشتیبان بیرونی برای بازیابی پس از خرابی +- اشتراک‌گذاری پیکربندی میان اعضای تیم با برون‌برد کامل و ارسال بایگانی --- -### Settings Dashboard +### پیشخوان تنظیمات -The settings page is organized into 6 tabs for easy navigation: +صفحه تنظیمات برای دسترسی آسان در شش زبانه سازمان‌دهی شده است: -| Tab | Contents | -| -------------- | -------------------------------------------------------------------------------------------- | -| **General** | System storage tools, appearance settings, theme controls, and per-item sidebar visibility | -| **Security** | Login/Password settings, IP Access Control, API auth for `/models`, and Provider Blocking | -| **Routing** | Global routing strategy (6 options), wildcard model aliases, fallback chains, combo defaults | -| **Resilience** | Request queue, connection cooldown, provider breaker config, and wait-for-cooldown behavior | -| **AI** | Thinking budget configuration, global system prompt injection, prompt cache stats | -| **Advanced** | Global proxy configuration (HTTP/SOCKS5) | +| زبانه | محتوا | +| ------------------ | -------------------------------------------------------------------------------------------------------------------- | +| **General** | ابزارهای ذخیره‌سازی سامانه، تنظیمات ظاهری، کنترل پوسته و نمایش یا پنهان‌سازی هر مورد در نوار کناری | +| **Security** | تنظیمات ورود و گذرواژه، کنترل دسترسی بر اساس IP، احراز هویت API برای `/models` و مسدودسازی ارائه‌دهنده | +| **Routing** | راهبرد مسیریابی سراسری با شش گزینه، نام‌های مستعار مدل با نویسه عام، زنجیره‌های جایگزین و پیش‌فرض‌های ترکیب | +| **Resilience** | صف درخواست، دوره انتظار اتصال، پیکربندی مدارشکن ارائه‌دهنده و رفتار انتظار برای پایان دوره توقف | +| **AI** | پیکربندی بودجه تفکر، تزریق پرامپت سراسری سامانه و آمار حافظه نهان پرامپت | +| **Advanced** | پیکربندی پراکسی سراسری HTTP/SOCKS5 | --- -### Costs & Budget Management +### مدیریت هزینه و بودجه -Access via **Dashboard → Costs**. +از مسیر **Dashboard → Costs** وارد شوید. -| Tab | Purpose | -| ----------- | ---------------------------------------------------------------------------------------- | -| **Budget** | Set spending limits per API key with daily/weekly/monthly budgets and real-time tracking | -| **Pricing** | View and edit model pricing entries — cost per 1K input/output tokens per provider | +| زبانه | کاربرد | +| -------------- | ----------------------------------------------------------------------------------------------------------- | +| **Budget** | تعیین سقف هزینه برای هر کلید API با بودجه روزانه، هفتگی یا ماهانه و پایش لحظه‌ای | +| **Pricing** | مشاهده و ویرایش قیمت مدل‌ها؛ هزینه هر هزار توکن ورودی و خروجی برای هر ارائه‌دهنده | ```bash # API: Set a budget @@ -834,13 +834,13 @@ curl -X POST http://localhost:20128/api/usage/budget \ curl http://localhost:20128/api/usage/budget ``` -**Cost Tracking:** Every request logs token usage and calculates cost using the pricing table. View breakdowns in **Dashboard → Usage** by provider, model, and API key. +**پایش هزینه:** برای هر درخواست، میزان مصرف توکن ثبت و هزینه بر اساس جدول قیمت محاسبه می‌شود. جزئیات تفکیکی را بر اساس ارائه‌دهنده، مدل و کلید API در مسیر **Dashboard → Usage** ببینید. --- -### Audio Transcription +### رونویسی صوت -OmniRoute supports audio transcription via the OpenAI-compatible endpoint: +OmniRoute از رونویسی صوت از طریق نقطه پایانی سازگار با OpenAI پشتیبانی می‌کند: ```bash POST /v1/audio/transcriptions @@ -854,51 +854,51 @@ curl -X POST http://localhost:20128/v1/audio/transcriptions \ -F "model=deepgram/nova-3" ``` -Available providers: **Deepgram** (`deepgram/`), **AssemblyAI** (`assemblyai/`). +ارائه‌دهندگان موجود: **Deepgram** با پیشوند `deepgram/` و **AssemblyAI** با پیشوند `assemblyai/`. -Supported audio formats: `mp3`, `wav`, `m4a`, `flac`, `ogg`, `webm`. +قالب‌های صوتی پشتیبانی‌شده: `mp3`، `wav`، `m4a`، `flac`، `ogg` و `webm`. --- -### Combo Balancing Strategies +### راهبردهای متعادل‌سازی ترکیب -Configure per-combo balancing in **Dashboard → Combos → Create/Edit → Strategy**. +متعادل‌سازی هر ترکیب را از مسیر **Dashboard → Combos → Create/Edit → Strategy** پیکربندی کنید. -| Strategy | Description | -| ------------------ | ------------------------------------------------------------------------ | -| **Round-Robin** | Rotates through models sequentially | -| **Priority** | Always tries the first model; falls back only on error | -| **Random** | Picks a random model from the combo for each request | -| **Weighted** | Routes proportionally based on assigned weights per model | -| **Least-Used** | Routes to the model with the fewest recent requests (uses combo metrics) | -| **Cost-Optimized** | Routes to the cheapest available model (uses pricing table) | +| راهبرد | توضیح | +| ---------------------- | -------------------------------------------------------------------------------------- | +| **Round-Robin** | مدل‌ها را به‌ترتیب و به‌صورت چرخشی انتخاب می‌کند. | +| **Priority** | همیشه ابتدا مدل اول را امتحان می‌کند و فقط در صورت خطا سراغ مدل جایگزین می‌رود. | +| **Random** | برای هر درخواست، یک مدل را به‌صورت تصادفی از ترکیب انتخاب می‌کند. | +| **Weighted** | درخواست‌ها را متناسب با وزن تعیین‌شده برای هر مدل هدایت می‌کند. | +| **Least-Used** | درخواست را به مدلی با کمترین تعداد درخواست اخیر می‌فرستد و از معیارهای ترکیب بهره می‌گیرد. | +| **Cost-Optimized** | با استفاده از جدول قیمت، درخواست را به ارزان‌ترین مدل موجود هدایت می‌کند. | -Global combo defaults can be set in **Dashboard → Settings → Routing → Combo Defaults**. +پیش‌فرض‌های سراسری ترکیب را می‌توان در مسیر **Dashboard → Settings → Routing → Combo Defaults** تنظیم کرد. --- -### Health Dashboard +### پیشخوان سلامت -Access via **Dashboard → Health**. Real-time system health overview with 6 cards: +از مسیر **Dashboard → Health** وارد شوید. نمای لحظه‌ای سلامت سامانه در شش کارت ارائه می‌شود: -| Card | What It Shows | -| --------------------- | ----------------------------------------------------------- | -| **System Status** | Uptime, version, memory usage, data directory | -| **Provider Health** | Global provider circuit breaker runtime state | -| **Rate Limits** | Active connection cooldowns per account with remaining time | -| **Active Lockouts** | Active model-scoped lockouts and temporary exclusions | -| **Signature Cache** | Deduplication cache stats (active keys, hit rate) | -| **Latency Telemetry** | p50/p95/p99 latency aggregation per provider | +| کارت | اطلاعات نمایش‌داده‌شده | +| ------------------------- | -------------------------------------------------------------------------------------- | +| **System Status** | مدت فعالیت، نسخه، میزان مصرف حافظه و پوشه داده‌ها | +| **Provider Health** | وضعیت زمان اجرای مدارشکن سراسری ارائه‌دهنده | +| **Rate Limits** | دوره‌های انتظار فعال اتصال برای هر حساب همراه با زمان باقی‌مانده | +| **Active Lockouts** | انسدادهای فعال در سطح مدل و موارد حذف موقت | +| **Signature Cache** | آمار حافظه نهان حذف موارد تکراری شامل کلیدهای فعال و نرخ اصابت | +| **Latency Telemetry** | تجمیع زمان تأخیر p50، p95 و p99 برای هر ارائه‌دهنده | -**Pro Tip:** The Health page auto-refreshes every 10 seconds. Use the circuit breaker card to identify which providers are experiencing issues. +**نکته کاربردی:** صفحه Health هر ۱۰ ثانیه خودکار به‌روزرسانی می‌شود. با کارت مدارشکن، ارائه‌دهندگانی را که دچار مشکل شده‌اند شناسایی کنید. --- -## 🖥️ Desktop Application (Electron) +## 🖥️ برنامه دسکتاپ (Electron) -OmniRoute is available as a native desktop application for Windows, macOS, and Linux. +OmniRoute به‌صورت برنامه دسکتاپ بومی برای Windows، macOS و Linux در دسترس است. -### Instalar +### نصب ```bash # From the electron directory: @@ -912,7 +912,7 @@ npm run dev npm start ``` -### Building Installers +### ساخت نصب‌کننده‌ها ```bash cd electron @@ -922,24 +922,24 @@ npm run build:mac # macOS (.dmg universal) npm run build:linux # Linux (.AppImage) ``` -Output → `electron/dist-electron/` +مسیر خروجی ← `electron/dist-electron/` -### Key Features +### قابلیت‌های کلیدی -| Feature | Description | -| --------------------------- | ---------------------------------------------------- | -| **Server Readiness** | Polls server before showing window (no blank screen) | -| **System Tray** | Minimize to tray, change port, quit from tray menu | -| **Port Management** | Change server port from tray (auto-restarts server) | -| **Content Security Policy** | Restrictive CSP via session headers | -| **Single Instance** | Only one app instance can run at a time | -| **Offline Mode** | Bundled Next.js server works without internet | +| قابلیت | توضیح | +| ----------------------------- | --------------------------------------------------------------------- | +| **آمادگی سرور** | پیش از نمایش پنجره، وضعیت سرور را بررسی می‌کند تا صفحه خالی نشان داده نشود. | +| **سینی سامانه** | کوچک‌کردن برنامه در سینی، تغییر درگاه و خروج از طریق منوی سینی | +| **مدیریت درگاه** | تغییر درگاه سرور از سینی و راه‌اندازی مجدد خودکار سرور | +| **سیاست امنیت محتوا** | اعمال CSP محدودکننده از طریق سرآیندهای نشست | +| **اجرای تک‌نمونه‌ای** | در هر لحظه فقط یک نمونه از برنامه می‌تواند اجرا شود. | +| **حالت آفلاین** | سرور همراه Next.js بدون اینترنت کار می‌کند. | -### Environment Variables +### متغیرهای محیطی -| Variable | Default | Description | -| --------------------- | ------- | -------------------------------- | -| `OMNIROUTE_PORT` | `20128` | Server port | -| `OMNIROUTE_MEMORY_MB` | `512` | Node.js heap limit (64–16384 MB) | +| متغیر | مقدار پیش‌فرض | توضیح | +| ---------------------- | ------------- | ------------------------------------------------- | +| `OMNIROUTE_PORT` | `20128` | درگاه سرور | +| `OMNIROUTE_MEMORY_MB` | `512` | سقف حافظه heap در Node.js از ۶۴ تا ۱۶۳۸۴ مگابایت | -📖 Full documentation: [`electron/README.md`](../electron/README.md) +📖 مستندات کامل: [`electron/README.md`](../../../../../electron/README.md) From 5cf16028fefbc1f768d4944516ebb549e511b557 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:26:08 -0300 Subject: [PATCH 14/20] fix(cli): real OpenRouter key validation + auth export argument wiring (#11226) (#11264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two defects exposed by #11226 (the 401 "User not found." itself is upstream OpenRouter's response to a bad key — not an OmniRoute bug): 1. OpenRouter key validation was vacuous. Both the dashboard Check (via validateProviderApiKey -> validateOpenAILikeProvider) and 'omniroute providers test' (bin/cli/provider-test.mjs) probed /api/v1/models, which is PUBLIC and answers 200 to any key — so invalid keys were saved/marked valid and only failed on real chat traffic. OpenRouter's authenticated key-info endpoint (GET /api/v1/auth/key, 200 = valid / 401 = invalid) is now the probe: registered as testKeyModelsUrl on the openrouter registry entry (same mechanism as perplexity) and as keyCheckPath in the CLI test configs. No other provider's probe changes; error results keep using canned strings, never the raw upstream body (Hard Rule #12). 2. 'omniroute auth export' crashed with "cmd.optsWithGlobals is not a function". .command("auth export") does not register a two-word command: commander parses the bare word 'export' as a required positional argument, so the action received (exportArgValue, options, command) while expecting (options, command). Registered 'export' as a proper nested subcommand of 'auth' — the documented CLI surface 'omniroute auth export [--force] [--id] [--format] [--out]' is unchanged, and unknown positionals (e.g. 'omniroute auth bogus') are now rejected instead of silently running the export. TDD: tests/unit/openrouter-key-validation-auth-endpoint.test.ts (stub mimics the real OpenRouter: /models public-200, /auth/key 401 'User not found.') and tests/unit/cli-auth-export-wiring.test.ts (real commander wiring via createProgram) were RED before the fix and are GREEN after. Co-authored-by: Xiangzhe --- bin/cli/commands/auth-export.mjs | 9 +- bin/cli/provider-test.mjs | 22 ++- .../providers/registry/openrouter/index.ts | 5 + tests/unit/cli-auth-export-wiring.test.ts | 131 +++++++++++++++ ...outer-key-validation-auth-endpoint.test.ts | 152 ++++++++++++++++++ 5 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 tests/unit/cli-auth-export-wiring.test.ts create mode 100644 tests/unit/openrouter-key-validation-auth-endpoint.test.ts diff --git a/bin/cli/commands/auth-export.mjs b/bin/cli/commands/auth-export.mjs index c968f0dab3..16ed31d872 100644 --- a/bin/cli/commands/auth-export.mjs +++ b/bin/cli/commands/auth-export.mjs @@ -22,8 +22,15 @@ const VALID_FORMATS = new Set(["json", "env"]); const SECURE_FILE_MODE = 0o600; export function registerAuthExport(program) { + // #11226: `.command("auth export")` does NOT register a two-word command — commander + // parses the bare word `export` as a required positional argument of `auth`, so the + // action received (exportArgValue, options, command) while expecting (options, command) + // and crashed with "cmd.optsWithGlobals is not a function". Register `export` as a + // proper nested subcommand instead; the CLI surface stays `omniroute auth export`. program - .command("auth export") + .command("auth") + .description(t("authExport.description")) + .command("export") .description(t("authExport.description")) .option("--id ", t("authExport.idOpt")) .option("--format ", t("authExport.formatOpt"), "json") diff --git a/bin/cli/provider-test.mjs b/bin/cli/provider-test.mjs index 4ab68bcde0..13cf9644bc 100644 --- a/bin/cli/provider-test.mjs +++ b/bin/cli/provider-test.mjs @@ -10,6 +10,10 @@ const PROVIDER_TEST_CONFIGS = { format: "openai", baseUrl: "https://openrouter.ai/api/v1", model: "openai/gpt-4o-mini", + // #11226: /models is public on OpenRouter (200 with any or no key) — probe the + // authenticated key-info endpoint instead so a bad key fails the test here + // instead of on the first real chat request. + keyCheckPath: "/auth/key", }, groq: { format: "openai", @@ -101,13 +105,19 @@ async function testOpenAILikeProvider(input, config) { "Content-Type": "application/json", }; - const modelsRes = await fetchWithTimeout(joinUrl(config.baseUrl, "/models"), { - method: "GET", - headers, - }); + // Providers whose /models endpoint is public (e.g. OpenRouter) declare a + // keyCheckPath pointing at an authenticated endpoint so the probe actually + // exercises the key instead of the public catalog. + const probeRes = await fetchWithTimeout( + joinUrl(config.baseUrl, config.keyCheckPath || "/models"), + { + method: "GET", + headers, + } + ); - if (modelsRes.ok || modelsRes.status === 401 || modelsRes.status === 403) { - return classifyResponse(modelsRes); + if (probeRes.ok || probeRes.status === 401 || probeRes.status === 403) { + return classifyResponse(probeRes); } const chatRes = await fetchWithTimeout(joinUrl(config.baseUrl, "/chat/completions"), { diff --git a/open-sse/config/providers/registry/openrouter/index.ts b/open-sse/config/providers/registry/openrouter/index.ts index 1116badd4d..b8302c9580 100644 --- a/open-sse/config/providers/registry/openrouter/index.ts +++ b/open-sse/config/providers/registry/openrouter/index.ts @@ -9,6 +9,11 @@ export const openrouterProvider: RegistryEntry = { authType: "apikey", authHeader: "bearer", defaultContextLength: 128000, + // #11226: OpenRouter's /api/v1/models is PUBLIC (200 with any or no key), so the + // generic /models probe validated every key — even garbage ones — and bad keys + // only surfaced later as upstream 401 "User not found." on real chat traffic. + // /api/v1/auth/key is the authenticated key-info endpoint: 200 = valid, 401 = invalid. + testKeyModelsUrl: "https://openrouter.ai/api/v1/auth/key", headers: { "HTTP-Referer": "https://endpoint-proxy.local", "X-Title": "Endpoint Proxy", diff --git a/tests/unit/cli-auth-export-wiring.test.ts b/tests/unit/cli-auth-export-wiring.test.ts new file mode 100644 index 0000000000..798b13c753 --- /dev/null +++ b/tests/unit/cli-auth-export-wiring.test.ts @@ -0,0 +1,131 @@ +// #11226 — `omniroute auth export` crashed with "cmd.optsWithGlobals is not a +// function" because the command was registered as `.command("auth export")`: +// commander parses the bare word `export` as a REQUIRED POSITIONAL ARGUMENT, so +// the action received ("export", options, command) while its signature expected +// (options, command) — the classic opts/cmd swap. The fix registers `export` as +// a proper nested subcommand of `auth`, restoring the documented CLI surface +// (docs/reference/CLI-TOOLS.md): `omniroute auth export [--force] [--id] [--format] [--out]`. +// +// These tests exercise the REAL commander wiring via createProgram() — no DB is +// touched on any of these paths (the no-force gate prints and returns before any +// DB access; an invalid --format fails validation before opening the DB). +import test from "node:test"; +import assert from "node:assert/strict"; + +import { createProgram } from "../../bin/cli/program.mjs"; + +function captureConsole(): { captured: { logs: string[]; errors: string[] }; restore: () => void } { + const originalLog = console.log; + const originalError = console.error; + const captured = { logs: [] as string[], errors: [] as string[] }; + console.log = (msg?: unknown) => { + captured.logs.push(String(msg ?? "")); + }; + console.error = (msg?: unknown) => { + captured.errors.push(String(msg ?? "")); + }; + return { + captured, + restore: () => { + console.log = originalLog; + console.error = originalError; + }, + }; +} + +function stubProcessExit(): { exitCodes: number[]; restore: () => void } { + const originalExit = process.exit; + const exitCodes: number[] = []; + process.exit = ((code?: number) => { + exitCodes.push(code ?? 0); + }) as typeof process.exit; + return { + exitCodes, + restore: () => { + process.exit = originalExit; + }, + }; +} + +test("auth command exposes 'export' as a subcommand, not a positional argument", () => { + const program = createProgram(); + const auth = program.commands.find((c) => c.name() === "auth"); + assert.ok(auth, "auth command exists"); + + const exportCmd = auth.commands.find((c) => c.name() === "export"); + assert.ok(exportCmd, "export must be a nested subcommand of auth"); + + const registeredArgs = (auth as unknown as { registeredArguments?: unknown[] }) + .registeredArguments; + assert.equal( + registeredArgs?.length ?? 0, + 0, + "auth must not declare positional arguments (a bare word in .command() becomes one)" + ); +}); + +test("auth export action receives (options, command): flags reach the handler end-to-end", async () => { + const program = createProgram(); + const exitStub = stubProcessExit(); + const { captured, restore } = captureConsole(); + try { + // --format bogus makes runAuthExportCommand return 1 BEFORE any DB access; + // the action must then call process.exit(1). With the opts/cmd swap this + // parse rejects with "cmd.optsWithGlobals is not a function" instead. + await program.parseAsync([ + "node", + "omniroute", + "auth", + "export", + "--force", + "--format", + "bogus", + ]); + } finally { + restore(); + exitStub.restore(); + } + + assert.deepEqual( + exitStub.exitCodes, + [1], + "handler must receive --format and exit 1 on bogus value" + ); + assert.ok( + captured.errors.join("\n").includes("Invalid format"), + `expected the invalid-format error, got: ${captured.errors.join(" | ")}` + ); +}); + +test("auth export without --force prints the confirmation gate (no crash, no DB)", async () => { + const program = createProgram(); + const exitStub = stubProcessExit(); + const { captured, restore } = captureConsole(); + try { + await program.parseAsync(["node", "omniroute", "auth", "export"]); + } finally { + restore(); + exitStub.restore(); + } + + assert.deepEqual(exitStub.exitCodes, [], "dry run exits 0 without calling process.exit"); + assert.ok( + captured.logs.join("\n").includes("DECRYPTED"), + `expected the confirmation gate, got: ${captured.logs.join(" | ")}` + ); +}); + +test("auth rejects an unknown positional (was silently accepted as the 'export' argument)", async () => { + const program = createProgram(); + await assert.rejects( + program.parseAsync(["node", "omniroute", "auth", "bogus-word"]), + (err: unknown) => { + assert.ok(err instanceof Error); + assert.match( + (err as { code?: string }).code || "", + /commander\.(unknownCommand|helpDisplayed)/ + ); + return true; + } + ); +}); diff --git a/tests/unit/openrouter-key-validation-auth-endpoint.test.ts b/tests/unit/openrouter-key-validation-auth-endpoint.test.ts new file mode 100644 index 0000000000..f636bd6dee --- /dev/null +++ b/tests/unit/openrouter-key-validation-auth-endpoint.test.ts @@ -0,0 +1,152 @@ +// #11226 — OpenRouter key validation was vacuous: the probe targeted the PUBLIC +// /api/v1/models endpoint, which answers 200 to any key (or no key at all), so a +// bad key was saved as "valid" and only failed later on real chat traffic with the +// upstream 401 "User not found.". The authenticated key-info endpoint +// (/api/v1/auth/key) is the correct probe: 200 = valid, 401 = invalid. +// +// The fetch stubs below mimic the REAL OpenRouter behavior verified live: +// GET /api/v1/models → 200 without any auth (public catalog) +// GET /api/v1/auth/key → 401 {"error":{"message":"User not found.","code":401}} for a bad key +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; + +const { getRegistryEntry } = await import("../../open-sse/config/providerRegistry.ts"); +const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts"); +const { testProviderApiKey } = await import("../../bin/cli/provider-test.mjs"); + +const AUTH_KEY_URL = "https://openrouter.ai/api/v1/auth/key"; +const PUBLIC_MODELS_URL = "https://openrouter.ai/api/v1/models"; + +const BAD_KEY = "sk-or-v1-definitely-invalid-key"; +const GOOD_KEY = "sk-or-v1-valid-key"; + +interface RecordedCall { + url: string; + authorization: string | null; +} + +/** + * Stub fetch with the real OpenRouter behavior: /models is public (always 200), + * /auth/key requires a valid bearer (401 "User not found." otherwise). + */ +function stubRealOpenRouter() { + const calls: RecordedCall[] = []; + const originalFetch = globalThis.fetch; + globalThis.fetch = (async (input: string | URL | Request, init?: RequestInit) => { + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + const headers = new Headers( + init?.headers ?? (input instanceof Request ? input.headers : undefined) + ); + calls.push({ url, authorization: headers.get("authorization") }); + + if (url.startsWith(AUTH_KEY_URL)) { + const bearer = headers.get("authorization") || ""; + if (bearer === `Bearer ${GOOD_KEY}`) { + return new Response(JSON.stringify({ data: { label: "ok", is_free_tier: false } }), { + status: 200, + }); + } + return new Response(JSON.stringify({ error: { message: "User not found.", code: 401 } }), { + status: 401, + }); + } + if (url.includes("/models")) { + // Public catalog — answers 200 regardless of the Authorization header. + return new Response(JSON.stringify({ data: [] }), { status: 200 }); + } + return new Response("{}", { status: 404 }); + }) as typeof fetch; + return { + calls, + restore: () => { + globalThis.fetch = originalFetch; + }, + }; +} + +describe("openrouter registry — authenticated key-validation endpoint (#11226)", () => { + it("declares the authenticated /auth/key probe as its key-test endpoint", () => { + const entry = getRegistryEntry("openrouter"); + assert.ok(entry, "openrouter must be registered in the execution registry"); + assert.equal(entry.testKeyModelsUrl, AUTH_KEY_URL); + }); + + it("marks a bad key INVALID even though the public /models endpoint answers 200", async () => { + const stub = stubRealOpenRouter(); + try { + const result = await validateProviderApiKey({ provider: "openrouter", apiKey: BAD_KEY }); + assert.equal(result.valid, false, "bad key must not validate against the public catalog"); + assert.equal(result.error, "Invalid API key"); + assert.deepEqual( + stub.calls.map((c) => c.url), + [AUTH_KEY_URL], + "must probe the authenticated key endpoint, not the public /models" + ); + assert.equal(stub.calls[0].authorization, `Bearer ${BAD_KEY}`); + } finally { + stub.restore(); + } + }); + + it("marks a good key VALID via /auth/key and never falls back to the chat probe", async () => { + const stub = stubRealOpenRouter(); + try { + const result = await validateProviderApiKey({ provider: "openrouter", apiKey: GOOD_KEY }); + assert.equal(result.valid, true); + assert.equal(result.error, null); + assert.deepEqual( + stub.calls.map((c) => c.url), + [AUTH_KEY_URL] + ); + } finally { + stub.restore(); + } + }); +}); + +describe("omniroute providers test — openrouter probe (#11226)", () => { + it("marks a bad key INVALID even though the public /models endpoint answers 200", async () => { + const stub = stubRealOpenRouter(); + try { + const result = await testProviderApiKey({ provider: "openrouter", apiKey: BAD_KEY }); + assert.equal(result.valid, false, "CLI test must not trust the public /models endpoint"); + assert.equal(result.error, "Invalid API key"); + assert.deepEqual( + stub.calls.map((c) => c.url), + [AUTH_KEY_URL] + ); + } finally { + stub.restore(); + } + }); + + it("marks a good key VALID via /auth/key", async () => { + const stub = stubRealOpenRouter(); + try { + const result = await testProviderApiKey({ provider: "openrouter", apiKey: GOOD_KEY }); + assert.equal(result.valid, true); + assert.equal(result.error, null); + assert.deepEqual( + stub.calls.map((c) => c.url), + [AUTH_KEY_URL] + ); + } finally { + stub.restore(); + } + }); + + it("does not change the probe for other OpenAI-like providers (openai still uses /models)", async () => { + const stub = stubRealOpenRouter(); + try { + const result = await testProviderApiKey({ provider: "openai", apiKey: GOOD_KEY }); + assert.equal(result.valid, true); + assert.deepEqual( + stub.calls.map((c) => c.url), + ["https://api.openai.com/v1/models"] + ); + assert.ok(!stub.calls.some((c) => c.url === PUBLIC_MODELS_URL)); + } finally { + stub.restore(); + } + }); +}); From 5b92dbded10053594337adc53ad9997c90643ffe Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:26:43 +0200 Subject: [PATCH 15/20] feat(lint): add no-unused-vars ratchet scoped to src+open-sse+tests (#11247) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated on the combined 12-PR batch board: typecheck:core clean, file-size/changelog/complexity/cognitive gates within baseline, eslint config smoke-tested. Freeze-then-ratchet for @typescript-eslint/no-unused-vars scoped to src/open-sse/tests (1393 pre-existing frozen, _ escape hatch, args:all), following the #7879 pattern — plus it untracks the two docs/superpowers planning files that leaked in via #11213 and were tripping check:tracked-artifacts for everyone. Thank you @maxmad64bis! --- .../11247-ratchet-no-unused-vars.md | 1 + config/quality/eslint-suppressions.json | 3373 ++++++++++++++++- eslint.config.mjs | 25 + 3 files changed, 3398 insertions(+), 1 deletion(-) create mode 100644 changelog.d/maintenance/11247-ratchet-no-unused-vars.md diff --git a/changelog.d/maintenance/11247-ratchet-no-unused-vars.md b/changelog.d/maintenance/11247-ratchet-no-unused-vars.md new file mode 100644 index 0000000000..f86559b7b6 --- /dev/null +++ b/changelog.d/maintenance/11247-ratchet-no-unused-vars.md @@ -0,0 +1 @@ +- **chore(lint):** ratchet `@typescript-eslint/no-unused-vars` scoped to `src/` + `open-sse/` + `tests/` (`args: "all"`, `_`-prefix escape hatch) and freeze the 1393 pre-existing violations via bulk suppressions — same pattern as the #7879 `toNumber` ratchet. New unused bindings now fail lint. ([#11247](https://github.com/diegosouzapw/OmniRoute/pull/11247)) diff --git a/config/quality/eslint-suppressions.json b/config/quality/eslint-suppressions.json index 79875e3148..d873a51adb 100644 --- a/config/quality/eslint-suppressions.json +++ b/config/quality/eslint-suppressions.json @@ -1,39 +1,209 @@ { + "open-sse/config/cliFingerprints.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/credentialLoader.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/providerRegistry.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 8 + } + }, + "open-sse/config/providers/registry/bailian-coding-plan/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/providers/registry/claude/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 + } + }, + "open-sse/config/providers/registry/vertex/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/providers/registry/zai/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/providers/shared.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/registryUtils.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/config/rerankRegistry.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/antigravity.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/executors/awsPollyTts.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/azure-openai.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/base.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 6 + } + }, "open-sse/executors/blackbox-web.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "open-sse/executors/chipotle.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/claude-web.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/executors/cliproxyapi.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "open-sse/executors/codex.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/copilot-web.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/executors/cursor.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "open-sse/executors/deepseek-web.ts": { "@typescript-eslint/no-explicit-any": { "count": 12 } }, + "open-sse/executors/default.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/devin-cli.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/duckduckgo-web.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/executors/gemini-web.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 } }, + "open-sse/executors/ghe-copilot.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "open-sse/executors/github.ts": { "@typescript-eslint/no-explicit-any": { "count": 14 } }, + "open-sse/executors/gitlab.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/glm.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/executors/grok-web/tool-bridge.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/executors/hyperagent.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/kiro.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/executors/kiro/eventstream.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/executors/muse-spark-web.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/executors/notion-web.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/executors/opencode.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 } }, + "open-sse/executors/perplexity-web/protocol.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/executors/pollinations.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 } }, + "open-sse/executors/raycast.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/executors/t3-chat-web.ts": { "@typescript-eslint/no-explicit-any": { "count": 11 @@ -47,11 +217,27 @@ "open-sse/executors/tinycmsSigner.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "open-sse/executors/vertex.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/handlers/chatCore.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 26 + } + }, + "open-sse/handlers/chatCore/clientUsageBuffer.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "open-sse/handlers/chatCore/comboContextCache.ts": { @@ -59,24 +245,58 @@ "count": 1 } }, + "open-sse/handlers/chatCore/executorHelpers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/handlers/chatCore/passthroughHelpers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/handlers/chatCore/streamFinalize.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/handlers/imageGeneration.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 6 + } + }, "open-sse/handlers/musicGeneration.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, "open-sse/handlers/responseSanitizer.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-syntax": { "count": 1 } }, "open-sse/handlers/responseTranslator.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-syntax": { "count": 1 } }, + "open-sse/handlers/responsesHandler.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/handlers/search.ts": { "@typescript-eslint/no-explicit-any": { "count": 33 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "open-sse/handlers/sseParser.ts": { @@ -87,6 +307,19 @@ "open-sse/handlers/videoGeneration.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "open-sse/handlers/videoGeneration/leonardoHandler.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/handlers/videoGeneration/openai.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "open-sse/lib/deepseek-pow.ts": { @@ -114,10 +347,18 @@ "count": 1 } }, + "open-sse/mcp-server/httpTransport.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/mcp-server/server.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + }, "no-restricted-syntax": { "count": 1 } @@ -127,12 +368,30 @@ "count": 1 } }, + "open-sse/mcp-server/tools/compressionTools.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "open-sse/mcp-server/tools/gamificationTools.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 } }, + "open-sse/mcp-server/tools/githubSkillTools.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/mcp-server/tools/obsidianTools.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "open-sse/mcp-server/tools/pickFastestModel.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-syntax": { "count": 1 } @@ -142,11 +401,86 @@ "count": 1 } }, + "open-sse/services/__tests__/claudeTlsClient.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/__tests__/tierResolver.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/accountFallback.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/services/adobeFireflyClient.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/adobeFireflySession.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/agentrouterQuotaFetcher.ts": { "no-restricted-syntax": { "count": 1 } }, + "open-sse/services/alibabaFreeTierQuotaFetcher.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/antigravityClientProfile.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/antigravityIdentity.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/antigravityProjectBootstrap.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/antigravityQuotaFamily.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/autoCombo/__tests__/autoCombo.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/services/autoCombo/chaosEngine.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/services/autoCombo/engine.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/autoCombo/pipelineRouter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/services/autoCombo/routerStrategy.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "open-sse/services/bailianQuotaFetcher.ts": { "no-restricted-syntax": { "count": 1 @@ -163,9 +497,27 @@ "count": 1 } }, + "open-sse/services/browserBackedChat.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "open-sse/services/claudeTurnstileSolver.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "open-sse/services/claudeWebAutoRefresh.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/codexAccount/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "open-sse/services/codexQuotaFetcher.ts": { @@ -178,19 +530,62 @@ "count": 1 } }, + "open-sse/services/combo.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 22 + } + }, "open-sse/services/combo/concurrencyCaps.ts": { "no-restricted-imports": { "count": 1 } }, + "open-sse/services/combo/providerWildcard.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/combo/quotaExhaustionCutoff.ts": { "no-restricted-imports": { "count": 1 } }, + "open-sse/services/comboAgentMiddleware.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/compression/aggressive.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 + } + }, + "open-sse/services/compression/caveman.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/compression/engines/cavemanAdapter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/compression/engines/ccr/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/compression/engines/headroom/gcf/decode_generic.ts": { "@typescript-eslint/no-explicit-any": { "count": 22 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/compression/engines/headroom/gcf/generic.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "open-sse/services/compression/engines/headroom/gcf/scalar.ts": { @@ -203,9 +598,22 @@ "count": 2 } }, + "open-sse/services/compression/stats.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/services/conversationTracker.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/credentialGate.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "open-sse/services/crofUsageFetcher.ts": { @@ -223,6 +631,16 @@ "count": 1 } }, + "open-sse/services/grokQuotaFetcher.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/imageCombo.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/inAppLoginService.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -233,6 +651,16 @@ "count": 1 } }, + "open-sse/services/manifestAdapter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/services/notionWebModels.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/opencodeOllamaUsage.ts": { "no-restricted-syntax": { "count": 1 @@ -243,24 +671,85 @@ "count": 1 } }, + "open-sse/services/providerCostData.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/rateLimitManager.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/services/routing/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/sessionPool/sessionPool.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "open-sse/services/taskAwareRouter.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 } }, + "open-sse/services/tierConfig.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/services/tierResolver.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/tlsClientBase.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/services/tokenLimitCounter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } }, + "open-sse/services/tokenRefresh.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "open-sse/services/toolLatencyTracker.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "open-sse/services/usage.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "open-sse/services/usage/codebuddy-cn.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/usage/github.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/services/usage/glm.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "open-sse/services/usage/scalars.ts": { @@ -273,6 +762,96 @@ "count": 1 } }, + "open-sse/services/videoCombo.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/transformer/responsesTransformer.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/translator/helpers/schemaCoercion.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/translator/request/claude-to-gemini.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/translator/request/openai-to-claude.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/translator/request/openai-to-cursor.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/translator/request/openai-to-gemini.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 + } + }, + "open-sse/translator/request/openai-to-kiro.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/translator/response/cursor-to-openai.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/translator/response/openai-to-claude.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/translator/webTools.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/types.d.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/utils/bypassHandler.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/utils/cursorAgentProtobuf.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/utils/earlyStreamKeepalive.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/utils/error.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "open-sse/utils/ollamaTransform.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/utils/proxyDispatcher.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/utils/proxyFallback.ts": { "no-restricted-imports": { "count": 1 @@ -283,36 +862,252 @@ "count": 5 } }, + "open-sse/utils/stream.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "open-sse/utils/streamHelpers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "open-sse/utils/streamPayloadCollector.ts": { "no-restricted-syntax": { "count": 1 } }, + "open-sse/utils/usageTracking.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/browser-worker.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/environment.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/HomePageClient.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + }, "react-hooks/exhaustive-deps": { "count": 1 } }, + "src/app/(dashboard)/dashboard/a2a/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 6 + } + }, + "src/app/(dashboard)/dashboard/analytics/CompressionAnalyticsTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/batch/components/wizard/InputStep.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/cli-agents/CliAgentsPageClient.tsx": { "no-restricted-syntax": { "count": 4 } }, "src/app/(dashboard)/dashboard/cli-code/components/AntigravityToolCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "react-hooks/exhaustive-deps": { "count": 1 } }, + "src/app/(dashboard)/dashboard/cli-code/components/ClineToolCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/cli-code/components/CopilotToolCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/(dashboard)/dashboard/cli-code/components/HermesAgentToolCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/cli-code/components/KiloToolCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/combos/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 9 + } + }, + "src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/costs/costExplorerUtils.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/health/ProviderHealthAutopilotCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/health/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/mcp/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/onboarding/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx": { "react-hooks/exhaustive-deps": { "count": 1 } }, + "src/app/(dashboard)/dashboard/playground/components/tabs/CompareTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/ProviderDetailPageClient.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/__tests__/phase1f.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/CompatibleModelsSection.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionRow.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionsHeaderToolbar.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/ConnectionsListPanel.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/EmptyConnectionsPlaceholder.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/PassthroughModelsSection.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/__tests__/CursorAgentNudge.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/components/modals/ImportCodexAuthModal.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelImportHandlers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/hooks/useModelVisibilityHandlers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/providers/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "src/app/(dashboard)/dashboard/providers/utils/buildCurl.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/resilience/connections/components/ConnectionDetail.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/resilience/connections/components/ConnectionsTable.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/search-tools/components/SearchHistory.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/search-tools/components/tabs/CompareTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/search-tools/components/tabs/SearchTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx": { "@next/next/no-img-element": { "count": 4 @@ -323,31 +1118,146 @@ "count": 1 } }, + "src/app/(dashboard)/dashboard/settings/components/CliproxyapiSettingsTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/settings/components/ComboDefaultsTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/settings/components/MitmProxyTab.tsx": { "@next/next/no-html-link-for-pages": { "count": 1 } }, + "src/app/(dashboard)/dashboard/settings/components/ModelAliasesTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/settings/components/ModelCapabilityOverridesTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/settings/components/ModelLockoutCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/settings/components/ProviderAccountRoutingCard.tsx": { "react-hooks/exhaustive-deps": { "count": 1 } }, + "src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/(dashboard)/dashboard/settings/components/ResponsesStatePolicyTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/settings/components/SessionInfoCard.tsx": { "react-hooks/exhaustive-deps": { "count": 1 } }, + "src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/settings/components/useProxyPoolModal.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx": { "no-restricted-syntax": { "count": 3 } }, + "src/app/(dashboard)/dashboard/tools/traffic-inspector/components/RequestStreamingList.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/tools/traffic-inspector/components/TopBarControls.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/translator/components/ResultNarrated.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/translator/components/SimpleControls.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/translator/components/TranslateTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/usage/components/RateLimitStatus.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/usage/components/SessionsTab.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/(dashboard)/dashboard/webhooks/__tests__/webhook-wizard.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/(dashboard)/home/page.tsx": { "no-restricted-imports": { "count": 1 } }, + "src/app/api/assess/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/auth/oidc/callback/route.ts": { "no-restricted-imports": { "count": 1 @@ -378,6 +1288,11 @@ "count": 1 } }, + "src/app/api/cli-tools/cline-settings/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/cli-tools/codex-settings/route.ts": { "no-restricted-imports": { "count": 1 @@ -388,11 +1303,21 @@ "count": 1 } }, + "src/app/api/cli-tools/kilo-settings/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/cli/connect/route.ts": { "no-restricted-imports": { "count": 1 } }, + "src/app/api/combos/duplicate/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/app/api/combos/reorder/route.ts": { "no-restricted-imports": { "count": 1 @@ -443,6 +1368,11 @@ "count": 1 } }, + "src/app/api/github-skills/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/internal/codex-responses-ws/route.ts": { "no-restricted-imports": { "count": 1 @@ -464,21 +1394,33 @@ } }, "src/app/api/keys/groups/[id]/keys/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + }, "no-restricted-imports": { "count": 1 } }, "src/app/api/keys/groups/[id]/permissions/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 + }, "no-restricted-imports": { "count": 1 } }, "src/app/api/keys/groups/[id]/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 9 + }, "no-restricted-imports": { "count": 1 } }, "src/app/api/keys/groups/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + }, "no-restricted-imports": { "count": 1 } @@ -493,7 +1435,15 @@ "count": 1 } }, + "src/app/api/memory/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/app/api/middleware/hooks/[name]/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } @@ -523,6 +1473,16 @@ "count": 1 } }, + "src/app/api/oauth/[provider]/[action]/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "src/app/api/oauth/kiro/auto-import/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/pricing/models/route.ts": { "no-restricted-imports": { "count": 1 @@ -543,7 +1503,15 @@ "count": 1 } }, + "src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/providers/[id]/models/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 9 + }, "no-restricted-imports": { "count": 1 } @@ -568,6 +1536,11 @@ "count": 1 } }, + "src/app/api/providers/bulk-web-session/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/providers/bulk/route.ts": { "no-restricted-imports": { "count": 1 @@ -648,11 +1621,21 @@ "count": 1 } }, + "src/app/api/relay/tokens/[id]/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/app/api/resilience/route.ts": { "no-restricted-imports": { "count": 1 } }, + "src/app/api/search/stats/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/services/[name]/logs/route.ts": { "no-restricted-syntax": { "count": 1 @@ -713,6 +1696,11 @@ "count": 1 } }, + "src/app/api/settings/obsidian/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/app/api/settings/payload-rules/route.ts": { "no-restricted-imports": { "count": 1 @@ -799,6 +1787,9 @@ } }, "src/app/api/settings/qdrant/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } @@ -819,6 +1810,9 @@ } }, "src/app/api/settings/thinking-budget/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + }, "no-restricted-imports": { "count": 1 } @@ -833,6 +1827,16 @@ "count": 1 } }, + "src/app/api/sync/initialize/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/api/telegram/update/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/token-health/route.ts": { "no-restricted-imports": { "count": 1 @@ -843,6 +1847,11 @@ "count": 1 } }, + "src/app/api/tools/traffic-inspector/ws/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/translator/send/route.ts": { "no-restricted-imports": { "count": 1 @@ -853,6 +1862,11 @@ "count": 1 } }, + "src/app/api/usage/analytics/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/usage/quota/route.ts": { "no-restricted-imports": { "count": 1 @@ -868,6 +1882,11 @@ "count": 1 } }, + "src/app/api/v1/audio/speech/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1/audio/transcriptions/route.ts": { "no-restricted-imports": { "count": 1 @@ -893,11 +1912,21 @@ "count": 1 } }, + "src/app/api/v1/chat/completions/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1/combos/route.ts": { "no-restricted-imports": { "count": 1 } }, + "src/app/api/v1/embeddings/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1/files/[id]/content/route.ts": { "no-restricted-imports": { "count": 1 @@ -918,6 +1947,11 @@ "count": 2 } }, + "src/app/api/v1/images/generations/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1/management/proxies/assignments/route.ts": { "no-restricted-imports": { "count": 1 @@ -939,20 +1973,91 @@ } }, "src/app/api/v1/messages/count_tokens/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + }, "no-restricted-imports": { "count": 1 } }, + "src/app/api/v1/messages/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1/models/catalog.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 10 + }, "no-restricted-imports": { "count": 1 } }, + "src/app/api/v1/models/catalogCache.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/models/catalogOpenrouter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/moderations/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/music/generations/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/ocr/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/providers/[provider]/embeddings/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/app/api/v1/rerank/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/api/v1/search/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/app/api/v1/videos/generations/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/vscode/[token]/combos/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1/vscode/[token]/models/route.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/app/api/v1/vscode/combos/[token]/[[...slug]]/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/api/v1/vscode/raw/[token]/combos/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/app/api/v1beta/models/route.ts": { "no-restricted-imports": { "count": 1 @@ -969,6 +2074,9 @@ } }, "src/app/api/webhooks/[id]/test/route.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 2 } @@ -978,17 +2086,50 @@ "count": 1 } }, + "src/app/docs/components/FeedbackWidget.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/app/login/page.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/domain/assessment/assessor.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/domain/assessment/selfHealer.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/domain/costRules.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/domain/fallbackPolicy.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/domain/providerExpiration.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/domain/quotaCache.ts": { "no-restricted-imports": { "count": 1 } }, "src/hooks/useLiveDashboard.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "react-hooks/exhaustive-deps": { "count": 2 } @@ -998,11 +2139,56 @@ "count": 1 } }, + "src/lib/a2a/streaming.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/api/proxyRegistryRouteHandlers.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/cli-helper/config-generator/claude.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/cli-helper/config-generator/cline.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/cli-helper/config-generator/continue.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/cli-helper/config-generator/hermes-agent.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/cli-helper/config-generator/hermes.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/cli-helper/config-generator/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/cli-helper/config-generator/kilocode.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/cli-helper/doctor/checks.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/lib/cloudSync.ts": { "no-restricted-imports": { "count": 1 @@ -1018,41 +2204,154 @@ "count": 1 } }, + "src/lib/compliance/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/container.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/copilot/engine.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/copilot/tools.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/lib/credentialHealth/scheduler.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } }, + "src/lib/db/apiKeys.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "src/lib/db/comboForecast.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/lib/db/compression.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/compressionCacheStats.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/db/compressionCombos.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/core.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/lib/db/databaseSettings.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/db/domainState.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/lib/db/files.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/healthCheck.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/db/middleware.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/db/migrationRunner.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/db/paramFilters.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/probeUtils.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/lib/db/prompts.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/lib/db/providers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, "src/lib/db/providers/lazyConnectionView.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/lib/db/providers/usageIdentityReconciliation.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/proxies.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/quotaConsumption.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/quotaSnapshots.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/db/registeredKeys.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/lib/db/tokenLimits.ts": { "no-restricted-syntax": { "count": 1 } }, + "src/lib/db/usageAnalytics/sources.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/evals/runtime.ts": { "no-restricted-imports": { "count": 1 @@ -1068,6 +2367,31 @@ "count": 1 } }, + "src/lib/guardrails/promptInjection.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/guardrails/videoBridgeContactSheet.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/guardrails/videoBridgeRuntime.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/guardrails/visionBridgeHelpers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/guardrails/visionBridgeRouter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/idempotencyLayer.ts": { "no-restricted-imports": { "count": 1 @@ -1078,21 +2402,51 @@ "count": 1 } }, + "src/lib/jobRegistry/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/localHealthCheck.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/memory/__tests__/generic-backend.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "src/lib/memory/__tests__/schemas.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/memory/embedding/index.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/memory/genericBackend.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/memory/reindex.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/memory/retrieval.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/memory/sqliteBackend.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/lib/memory/store.ts": { "no-restricted-imports": { "count": 1 @@ -1103,7 +2457,20 @@ "count": 1 } }, + "src/lib/middleware/registry.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/modelsDevSync.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/monitoring/providerHealthAutopilot.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 }, @@ -1116,6 +2483,56 @@ "count": 1 } }, + "src/lib/ngrokTunnel.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/notion/api.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/oauth/providers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/cline.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/codebuddy-cn.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/codex.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/ghe-copilot.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/github.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/kimi-coding.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oauth/providers/kiro.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "src/lib/oauth/utils/agyAuthImport.ts": { "no-restricted-imports": { "count": 1 @@ -1127,6 +2544,9 @@ } }, "src/lib/oauth/utils/claudeAuthImport.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } @@ -1141,11 +2561,56 @@ "count": 1 } }, + "src/lib/obsidian/api.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/oneproxyRotator.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/oneproxySync.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/piiSanitizer.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/plugins/manager.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "src/lib/providerModels/managedAvailableModels.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/providers/validation.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/providers/validation/searchProviders.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/providers/validation/webProvidersA.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/providers/validation/webProvidersB.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/proxyHealth/scheduler.ts": { "no-restricted-imports": { "count": 1 @@ -1156,6 +2621,11 @@ "count": 1 } }, + "src/lib/quota/quotaAdapters.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/quota/quotaCombos.ts": { "no-restricted-imports": { "count": 1 @@ -1167,11 +2637,17 @@ } }, "src/lib/quota/redisQuotaStore.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } }, "src/lib/quota/sqliteQuotaStore.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } @@ -1181,16 +2657,71 @@ "count": 1 } }, + "src/lib/services/ServiceSupervisor.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/services/quotaAutoPing.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/skills/a2a.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/skills/builtin/browser.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/skills/githubCollector.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/skills/hybrid.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 + } + }, + "src/lib/skills/memoryBuiltins.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/skills/schemas.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/sseTextTransform.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/streamingPiiTransform.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/sync/bundle.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/tailscaleTunnel.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/lib/telegram/initData.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/tokenHealthCheck.ts": { "no-restricted-imports": { "count": 1 @@ -1211,6 +2742,11 @@ "count": 1 } }, + "src/lib/usage/callLogs.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/usage/codexResetCredits.ts": { "no-restricted-imports": { "count": 1 @@ -1221,12 +2757,20 @@ "count": 1 } }, + "src/lib/usage/fetcher.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 + } + }, "src/lib/usage/internalUsageCommand.ts": { "no-restricted-syntax": { "count": 1 } }, "src/lib/usage/providerWindowCosts.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-syntax": { "count": 1 } @@ -1236,34 +2780,160 @@ "count": 1 } }, + "src/lib/versionManager/index.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/lib/versionManager/processManager.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/lib/warmupScheduler.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/lib/ws/handshake.ts": { "no-restricted-imports": { "count": 1 } }, + "src/lib/zed-oauth/keychain-reader.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/mitm/cert/install.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/mitm/dns/dnsConfig.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/mitm/dns/provision.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/mitm/manager.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "src/models/index.ts": { "no-restricted-imports": { "count": 1 } }, + "src/server/ws/liveServer.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 + } + }, + "src/shared/components/DegradationBadge.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/components/KiroAuthModal.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/components/LanguageSelector.tsx": { "@next/next/no-img-element": { "count": 1 } }, + "src/shared/components/NotificationToast.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/components/OAuthModal.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/shared/components/PricingModal.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/components/ProxyConfigModal.tsx": { "react-hooks/exhaustive-deps": { "count": 1 } }, + "src/shared/components/ProxyLogDetail.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/components/RequestLoggerV2.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + }, "react-hooks/exhaustive-deps": { "count": 6 } }, + "src/shared/components/RequestTimeline.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/components/Sidebar.tsx": { "@next/next/no-img-element": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/shared/components/analytics/charts.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/shared/components/analytics/rechartsUsageCharts.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/components/docs/CodeBlock.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/components/docs/DocsBreadcrumbs.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/components/docs/DocsSidebar.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/components/docs/DocsThemeProvider.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "src/shared/constants/agentSkills.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/constants/capabilities/capabilityFilter.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "src/shared/contracts/quota.ts": { @@ -1271,17 +2941,38 @@ "count": 1 } }, + "src/shared/hooks/useTheme.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/middleware/chatBodyAdmission.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/services/apiKeyResolver.ts": { "no-restricted-imports": { "count": 1 } }, + "src/shared/services/backupService.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/services/cloudSyncScheduler.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } }, "src/shared/services/initializeCloudSync.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } @@ -1296,21 +2987,137 @@ "count": 1 } }, + "src/shared/utils/apiKey.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/shared/utils/apiKeyPolicy.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + }, "no-restricted-imports": { "count": 1 } }, + "src/shared/utils/cloud.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "src/shared/utils/requestTelemetry.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/utils/structuredLogger.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "src/shared/validation/schemas/apiV1.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 10 + } + }, + "src/shared/validation/schemas/auth.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/cli.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/cloud.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/combo.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 9 + } + }, + "src/shared/validation/schemas/evals.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/gemini.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/keys.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/misc.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 9 + } + }, + "src/shared/validation/schemas/payloadRules.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/pricing.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/provider.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 10 + } + }, + "src/shared/validation/schemas/proxy.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/routing.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/settings.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, + "src/shared/validation/schemas/translator.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 11 + } + }, "src/sse/handlers/autoRouting.ts": { "no-restricted-imports": { "count": 1 } }, + "src/sse/handlers/chat.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 9 + } + }, "src/sse/handlers/chatHelpers.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + }, "no-restricted-imports": { "count": 1 } }, + "src/sse/services/auth.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "src/sse/services/model.ts": { "no-restricted-imports": { "count": 2 @@ -1326,6 +3133,26 @@ "count": 1 } }, + "tests/benchmarks/pipeline-accuracy.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/boundary/gemma4-multiturn.live.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/boundary/gemma4-newline-investigation.live.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/e2e/analytics-tabs.spec.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/e2e/api.spec.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -1336,19 +3163,62 @@ "count": 17 } }, + "tests/e2e/group-b-redirect-logs-activity.spec.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/e2e/helpers/dashboardAuth.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 } }, + "tests/e2e/navigation.spec.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/e2e/protocol-clients.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 } }, + "tests/e2e/responsiveSpecs.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/e2e/search-tools-studio.spec.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/e2e/skills-marketplace.spec.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/e2e/system-failover.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/fixtures/welcome-banner-plugin/index.mjs": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/golden-set/compression-quality.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/_chatPipelineHarness.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/integration/_comboRoutingHarness.ts": { @@ -1356,6 +3226,21 @@ "count": 3 } }, + "tests/integration/active-request-completion.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/agent-skills-content.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/integration/all-statuses-route.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/integration/api-keys.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 23 @@ -1364,16 +3249,30 @@ "tests/integration/api-routes-critical.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 39 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/chat-pipeline.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 20 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/chatcore-compression-integration.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 9 + }, + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "tests/integration/cli-settings-forge.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/combo-live/_liveHarness.ts": { @@ -1389,16 +3288,25 @@ "tests/integration/combo-live/cost-and-fusion.live.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 29 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/combo-live/ordered.live.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/combo-matrix/context-relay-handoff.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/combo-provider-exhaustion.test.ts": { @@ -1411,29 +3319,71 @@ "count": 7 } }, + "tests/integration/compression-pipeline.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/integration/files-api.test.ts": { "no-restricted-imports": { "count": 1 } }, + "tests/integration/fingerprint-expansion.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/gemini-tool-call-escaping.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 + } + }, + "tests/integration/live-gemini-agentic-loop.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/integration/live-gemini-nonstream.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/live-gemini-workload.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/live-gemini.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/integration/liveDefaultComboShared.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/llama-cpp-provider.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/integration/memory-pipeline.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/memory-reindex.test.ts": { @@ -1444,21 +3394,43 @@ "tests/integration/memory-route-put.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 5 } }, "tests/integration/memory-summarize.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/modelsDevSync.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/obsidian-plugin-e2e.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 12 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/integration/performance-regression.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/pipeline-combo.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 } }, "tests/integration/proxy-context-passthrough.test.ts": { @@ -1476,14 +3448,42 @@ "count": 19 } }, + "tests/integration/quota-pools-usage.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/integration/resilience-http-e2e.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 } }, + "tests/integration/services/cliproxy-coexistence.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/integration/skills-pipeline.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 14 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/integration/traffic-inspector-error-sanitization.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/traffic-inspector-hosts.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/integration/upstream-cli-smoke.int.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/integration/v1-contracts-behavior.test.ts": { @@ -1499,6 +3499,9 @@ "tests/theoldllm-stress.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 10 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/translator/testFromFile.ts": { @@ -1506,9 +3509,37 @@ "count": 3 } }, + "tests/unit/8370-priority-affinity-reorder.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/9034-alias-backed-prefix-id-repro.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/9560-turbopack-nft-lazy-module-fs.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/9568-gemini-tool-casing-mismatch.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/account-fallback-service.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/account-selector.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/acp-agents-route.test.ts": { @@ -1516,14 +3547,47 @@ "count": 2 } }, + "tests/unit/adaptive-admission-runtime.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/admin-audit-events.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 11 } }, + "tests/unit/admission-virtual-lanes-9654.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/adobe-firefly.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/adversarialPii.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/agent-bridge-mappings-sync-8656.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/agent-skills-page.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/agentrouter-quota-visibility.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/agy-usage-quota.test.ts": { @@ -1541,6 +3605,11 @@ "count": 2 } }, + "tests/unit/antigravity-discovery-bootstrap.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/antigravity-local-usage-fallback-3821.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -1551,16 +3620,56 @@ "count": 3 } }, + "tests/unit/api-key-mask-fix.test.mjs": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, "tests/unit/api-key-reveal-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 8 } }, + "tests/unit/api/compression/compression-api.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/api/services/9router-models.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/api/services/9router-status-reveal.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/api/sync-models-readiness.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/api/v1/relay-completions-errors.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/api/webhooks/webhook-url-ssrf-guard.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/apikey-connection-health-check.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/apikeypolicy-disable-non-public.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/audio-speech-handler.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 9 @@ -1586,6 +3695,11 @@ "count": 5 } }, + "tests/unit/auth-clear-provider-routes.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/auth-disable-cooling-2997.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 11 @@ -1601,6 +3715,16 @@ "count": 15 } }, + "tests/unit/authz/probe-9033-repro.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/autoCombo/tieredRotation.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/autocombo-unification.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -1609,6 +3733,19 @@ "tests/unit/bailian-quota-fetcher.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 21 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/bailian-usage.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 + } + }, + "tests/unit/base-executor-buildheaders-extra-keys-8493.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/base-executor-sanitize-effort.test.ts": { @@ -1616,11 +3753,31 @@ "count": 6 } }, + "tests/unit/batch-a-domain.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/batch-b-final.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/batch-deletion.test.ts": { "no-restricted-imports": { "count": 1 } }, + "tests/unit/batch-page-static.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/batch-processor.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 6 + } + }, "tests/unit/batch_api.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 @@ -1629,6 +3786,9 @@ "tests/unit/batch_results.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/blackbox-web.test.ts": { @@ -1636,16 +3796,36 @@ "count": 10 } }, + "tests/unit/body-timeout-integration.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/build-next-isolated.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/build/check-licenses.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 } }, + "tests/unit/build/check-lockfile.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/bypass-handler.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 } }, + "tests/unit/cache-sweeps.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/call-log-cap.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 43 @@ -1661,9 +3841,17 @@ "count": 3 } }, + "tests/unit/capability-filter.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/cc-bridge-transforms.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 31 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cc-compatible-model-catalog.test.ts": { @@ -1676,6 +3864,11 @@ "count": 37 } }, + "tests/unit/chat-body-admission.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/chat-combo-live-test.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 @@ -1709,6 +3902,9 @@ "tests/unit/chat-route-edge-cases.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 7 } }, "tests/unit/chat-safetynet-reqid-6097.test.ts": { @@ -1719,6 +3915,14 @@ "tests/unit/chatcore-compression-integration.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 5 + } + }, + "tests/unit/chatcore-memory-pressure.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/chatcore-translation-paths.test.ts": { @@ -1731,6 +3935,16 @@ "count": 6 } }, + "tests/unit/check-docs-symbols.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/check-route-guard-membership.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/chipotle-executor.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -1761,9 +3975,27 @@ "count": 8 } }, + "tests/unit/claude-web-auto-refresh.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/claude-web.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/claudeAuthImport.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/cli-a2a-invoke-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-audit-commands.test.ts": { @@ -1774,21 +4006,33 @@ "tests/unit/cli-batches-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/cli-chat.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 17 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/cli-cloud-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 18 + }, + "@typescript-eslint/no-unused-vars": { + "count": 6 } }, "tests/unit/cli-combo-suggest-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-completion-dynamic.test.ts": { @@ -1804,6 +4048,9 @@ "tests/unit/cli-context-eng-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 19 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-cost.test.ts": { @@ -1824,16 +4071,30 @@ "tests/unit/cli-files-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 12 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-helper/config-generator.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/cli-helper/hermes-home-env.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-lang-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-mcp-call-commands.test.ts": { @@ -1846,29 +4107,64 @@ "count": 18 } }, + "tests/unit/cli-memory-types.test.mjs": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, "tests/unit/cli-nodes-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 19 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-oauth-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 20 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-oneproxy-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 22 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cli-open-command.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/cli-openapi-codegen.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-openapi-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cli-plugin-system.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-policy-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 20 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/cli-pricing-commands.test.ts": { @@ -1879,6 +4175,9 @@ "tests/unit/cli-process-supervisor.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-program.test.ts": { @@ -1891,6 +4190,11 @@ "count": 15 } }, + "tests/unit/cli-redis-command.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/cli-remote-mode.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -1899,6 +4203,9 @@ "tests/unit/cli-resilience-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-runtime-extended.test.ts": { @@ -1906,9 +4213,22 @@ "count": 1 } }, + "tests/unit/cli-serve-stop-command.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cli-server-commands.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/cli-sessions-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 12 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-simulate.test.ts": { @@ -1921,29 +4241,49 @@ "count": 22 } }, + "tests/unit/cli-stop-supervisor-respawn-9455.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/cli-stream.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 13 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-sync-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 21 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-tags-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 17 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/cli-telemetry-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/cli-translator-commands.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/cli-usage.test.ts": { @@ -1956,6 +4296,26 @@ "count": 23 } }, + "tests/unit/cli/alias-resolver-7791.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cli/cli-manifest-drift.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cli/setup-continue.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cliproxyapi-executor.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/cloud-agent-cursor-4227.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -1969,6 +4329,9 @@ "tests/unit/cloudflaredTunnel-extended.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/codex-banked-reset-credits-5199.test.ts": { @@ -1991,6 +4354,11 @@ "count": 4 } }, + "tests/unit/combo-auto-promote.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/combo-builder-options-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -2001,11 +4369,36 @@ "count": 2 } }, + "tests/unit/combo-context-overflow-compression-probe.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/combo-context-relay.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/combo-fingerprint-expansion.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/combo-health-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 } }, + "tests/unit/combo-pipeline.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/combo-prescreen.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/combo-provider-cooldown.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -2014,6 +4407,14 @@ "tests/unit/combo-provider-wildcard.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 18 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/combo-quota-share-cooldown-wait.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/combo-routes-composite-tiers.test.ts": { @@ -2031,11 +4432,26 @@ "count": 1 } }, + "tests/unit/combo-selected-connection-success.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/combo-session-stickiness.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/combo-sessionless-pin-3825.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 17 } }, + "tests/unit/combo-strategies.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/combo-strategy-fallbacks.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 33 @@ -2046,21 +4462,46 @@ "count": 17 } }, + "tests/unit/combo-task-aware.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/combo-test-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 10 } }, + "tests/unit/combos-duplicate-resolution-audit.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/combos-quota-protected.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 } }, + "tests/unit/comfyui-baseurl-override-6928.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/command-code-executor.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/command-code-maxtokens-negative-5166.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 } }, + "tests/unit/commandClassification.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/compliance-audit-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2076,16 +4517,101 @@ "count": 3 } }, + "tests/unit/compression/caveman-engine.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/compression/codestripper-lazy-ts-7096.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/compression-header-dispatch.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/compressionMcpTools.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/compression/db.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 } }, + "tests/unit/compression/engine-catalog.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/compression/eval-runner.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/compression/gcf-benchmark.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/llmlingua-failopen.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/llmlingua-worker-resolution.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/omniglyph-registries.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/progressiveAging.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/relevance-engine.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/result-memo.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/rtk-grouping.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/compression/types.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/compression/ultra.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/conductor-agent-card.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/conductor-ask-route.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/context-manager.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 10 @@ -2101,6 +4627,16 @@ "count": 4 } }, + "tests/unit/correctness/goldenSnapshot.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/cursor-agent-session.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/cursor-usage-fetcher.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2111,6 +4647,16 @@ "count": 6 } }, + "tests/unit/dashboard/batch/components/NewBatchWizard.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/dashboard/batch/sanitization.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/db-agent-bridge-bypass.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2126,6 +4672,11 @@ "count": 1 } }, + "tests/unit/db-backup-export-streaming-9045.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/db-combos-crud.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 9 @@ -2139,6 +4690,9 @@ "tests/unit/db-core-migration.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/db-core.test.ts": { @@ -2146,6 +4700,11 @@ "count": 4 } }, + "tests/unit/db-credit-balance.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/db-detailed-logs.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -2184,6 +4743,9 @@ "tests/unit/db-migration-runner.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 12 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/db-model-aliases-cascade.test.ts": { @@ -2214,6 +4776,9 @@ "tests/unit/db-providers-crud.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 18 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/db-proxies-crud.test.ts": { @@ -2226,6 +4791,11 @@ "count": 1 } }, + "tests/unit/db-quota-migrations-idempotency.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/db-quota-pools.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2234,6 +4804,9 @@ "tests/unit/db-read-cache.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/db-registeredKeys-crud.test.ts": { @@ -2259,6 +4832,9 @@ "tests/unit/deepseek-quota-fetcher.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 8 + }, + "@typescript-eslint/no-unused-vars": { + "count": 5 } }, "tests/unit/deepseek-web-autorefresh-401-response.test.ts": { @@ -2266,14 +4842,27 @@ "count": 4 } }, + "tests/unit/deepseek-web.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/display-and-error-utils.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 } }, + "tests/unit/dns-config-generic.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/domain-branch-hardening.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/domain-cost-rules.test.ts": { @@ -2291,9 +4880,17 @@ "count": 1 } }, + "tests/unit/domain-persistence.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/duckduckgo-web-executor.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 8 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/electron-main.test.ts": { @@ -2301,6 +4898,26 @@ "count": 2 } }, + "tests/unit/electron-preload.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/embedding-cooldown-integration-10347.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/embeddings-auth.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/embeddings-nvidia-input-type.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/embeddings-proxy-forwarding.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2321,11 +4938,21 @@ "count": 19 } }, + "tests/unit/eviction-guards-codexQuotaFetcher.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/executor-agy.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/executor-antigravity.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/executor-base-utils.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -2341,6 +4968,11 @@ "count": 4 } }, + "tests/unit/executor-codex.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/executor-default-base.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 42 @@ -2369,12 +5001,23 @@ "tests/unit/fetch-timeout.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/field-400-downgrade.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/file-expiration-policy.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + }, "no-restricted-imports": { "count": 1 } @@ -2382,6 +5025,9 @@ "tests/unit/fix-tool-adjacency.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/fixes-p1.test.ts": { @@ -2389,6 +5035,11 @@ "count": 20 } }, + "tests/unit/functional-gateway-mirrors-append.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/gamification/events.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2399,6 +5050,11 @@ "count": 3 } }, + "tests/unit/gemini-business-provider.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/gemini-finish-reason-normalization.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -2437,6 +5093,14 @@ "tests/unit/glm-provider-model-import-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/grok-quota-fetcher.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 } }, "tests/unit/grok-web.test.ts": { @@ -2444,31 +5108,111 @@ "count": 25 } }, + "tests/unit/gtts-provider.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/guardrails/visionBridgeHelpers.callVisionModel.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/head-request-closes-6400.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/i18n-nest-dotted-keys.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 } }, + "tests/unit/image-generation-fetch-timeout.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/image-generation-handler.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/image-generation-route.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/inspector-agent-bridge-hook.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 } }, + "tests/unit/json-size-exactness.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/kimi-credentials-extract.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/kimi-web-401-retry.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/kiro-tool-args-streaming.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/lib/batches/csvToJsonl.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/lib/jobRegistry/registry.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/lib/managementCliToken.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/lib/warmupScheduler/redisCircuitBreakerStore.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/llamacpp-model-delete.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/lmarena-split-cookie-4271.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/lmarena-string-chunk-repro.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/log-retention.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 12 } }, + "tests/unit/log-rotation.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/management-password.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -2479,11 +5223,26 @@ "count": 4 } }, + "tests/unit/memory-embedding-remote.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/memory-extraction.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/memory-glm-injection.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/memory-retrieve-preview.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/memory-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -2514,11 +5273,31 @@ "count": 4 } }, + "tests/unit/model-deprecation.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/model-lockout-decay.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/model-lockout-max-cooldown.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 } }, + "tests/unit/model-overrides-provider-prefix-9557.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/model-strip.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/model-sync-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 @@ -2537,6 +5316,9 @@ "tests/unit/models-catalog-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 79 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/modelsDevSync-extended.test.ts": { @@ -2544,6 +5326,11 @@ "count": 2 } }, + "tests/unit/modelsDevSync.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/moderations-handler.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 @@ -2564,6 +5351,11 @@ "count": 2 } }, + "tests/unit/oauth-400-recovery.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/oauth-providers-config.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 @@ -2574,11 +5366,21 @@ "count": 1 } }, + "tests/unit/obsidian-plugin-sync.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/ocr-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 8 } }, + "tests/unit/oidc-callback.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/openai-tool-opaque-object-schema.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -2609,6 +5411,11 @@ "count": 1 } }, + "tests/unit/opencode-premium-keyless-gate-8681.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/opencode-proxy-rotation-4954.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 @@ -2644,6 +5451,11 @@ "count": 2 } }, + "tests/unit/perplexity-web.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 7 + } + }, "tests/unit/persist-429-cooldown-account-fallback.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 @@ -2659,14 +5471,50 @@ "count": 1 } }, + "tests/unit/plugins-config.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/plugins-dev-mode.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/plugins-edge-cases.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/plugins-fs-safety.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/plugins-hooks.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/plugins-index.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 9 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/plugins-manager-lifecycle.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/plugins-welcome-banner-e2e.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 4 } }, "tests/unit/pollinations-jsonmode-3981.test.ts": { @@ -2674,6 +5522,11 @@ "count": 3 } }, + "tests/unit/probe-9575-tool-name-case.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, "tests/unit/prompt-injection-guard.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -2739,9 +5592,22 @@ "count": 8 } }, + "tests/unit/provider-proxy-lazy.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/provider-request-failure-pipeline.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 6 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/provider-scoped-aliases.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/provider-validation-hardening.test.ts": { @@ -2754,6 +5620,11 @@ "count": 1 } }, + "tests/unit/provider-validation-specialty.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, "tests/unit/providers-route-managed-catalog.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -2762,6 +5633,9 @@ "tests/unit/providers-validate-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/proxy-egress-visibility.test.ts": { @@ -2772,6 +5646,9 @@ "tests/unit/proxy-fetch.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/proxy-management-v1-route.test.ts": { @@ -2787,6 +5664,9 @@ "tests/unit/proxy-registry.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 54 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/proxy-resolution-status-filter.test.ts": { @@ -2794,6 +5674,16 @@ "count": 3 } }, + "tests/unit/proxySubscription.service.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/proxyfetch-direct-response-start-timeout-10214.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/qoder-executor.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -2814,6 +5704,21 @@ "count": 2 } }, + "tests/unit/quota-email-privacy.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/quota-enforce.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 6 + } + }, + "tests/unit/quota-fetch-throttle-scope-6911.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/quota-groups-crud.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2829,6 +5734,11 @@ "count": 1 } }, + "tests/unit/quota-phase2.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/quota-pool-connections.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2844,14 +5754,87 @@ "count": 9 } }, + "tests/unit/quota-redis-store.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/quota-spend-recorder.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/quota-store-factory.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/quota-summed-budget.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/qwen-web-cookie-validation-3958.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/radar-api-routes.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/radar-apply-feed.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/radar-db.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 + } + }, + "tests/unit/radar-referrals-sync.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/radar-sync.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/rate-limit-enhanced.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/rate-limit-queue-timeout-lockout.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/rateLimitManager-idle-eviction.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/rateLimitManager-update-sequencing.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/refactor-buildHeaders-preamble.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/refactor-resolveBaseUrl.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/regional-provider-cn-notices-5462.test.ts": { @@ -2859,16 +5842,36 @@ "count": 2 } }, + "tests/unit/registry-utils.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/remaining-tasks.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/repro-7023.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/repro-9406-claude-web-429-valid.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/request-log-payloads.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 } }, + "tests/unit/request-logger-endpoints.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/request-logger-signature.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 8 @@ -2884,6 +5887,11 @@ "count": 83 } }, + "tests/unit/responses-transformer.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/responses-translation-fixes.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 35 @@ -2892,6 +5900,24 @@ "tests/unit/route-edge-coverage.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 57 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/route-guard-loopback-via-proxy.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/routing-events-concurrency.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/safe-outbound-fetch-probe-timeout.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/safe-outbound-fetch.test.ts": { @@ -2904,9 +5930,22 @@ "count": 12 } }, + "tests/unit/search-blocked-providers.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/search-handler-extended.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/search-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/security/cloud-sync-hmac.test.ts": { @@ -2914,6 +5953,16 @@ "count": 5 } }, + "tests/unit/serial/provider-health-autopilot.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/service-combo-metrics.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/service-context-handoff.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -2924,9 +5973,22 @@ "count": 1 } }, + "tests/unit/service-intent-classifier.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/service-system-transforms.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/service-token-limit-counter.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/services-branch-hardening.test.ts": { @@ -2934,9 +5996,47 @@ "count": 1 } }, + "tests/unit/services/embed-proxy.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/services/end-to-end-shape.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/services/installers/cliproxy.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, + "tests/unit/services/installers/ninerouter.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/services/lifecycle.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/services/logs-sse.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 7 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/services/modelSync.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/session-pool-modular.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/session-pool-rest-api.test.ts": { @@ -2944,6 +6044,11 @@ "count": 3 } }, + "tests/unit/session-pool.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/settings-route-password.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -2969,6 +6074,21 @@ "count": 1 } }, + "tests/unit/silent-sse-close-7699.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/skills-collect-routes.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/skills-registry.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/skills-routes.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 9 @@ -2987,6 +6107,19 @@ "tests/unit/sse-auth.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 13 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/sse-heartbeat-integration.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/sseTextTransform.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 } }, "tests/unit/startup-stale-cooldown-recovery.test.ts": { @@ -2999,9 +6132,27 @@ "count": 2 } }, + "tests/unit/stream-prompt-tokens-zero-upstream.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/stream-timing.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/stream-utilities.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/stream-utils.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 14 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/streamingPiiTransform.test.ts": { @@ -3019,6 +6170,11 @@ "count": 5 } }, + "tests/unit/t08-allowed-connections.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/t19-codex-responses-empty-content.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 4 @@ -3029,6 +6185,16 @@ "count": 1 } }, + "tests/unit/t3-chat-web.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/t31-t33-t34-t38-model-specs.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/t3chat-web-cookie-hint-5465.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -3044,11 +6210,26 @@ "count": 1 } }, + "tests/unit/tailscaleTunnel-anti-fold-10293.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/telemetry-summary-route.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 } }, + "tests/unit/thundering-herd.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/tlsClient-circuit-breaker.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/token-health-check-circuit-breaker.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 3 @@ -3062,6 +6243,9 @@ "tests/unit/token-limits.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/token-refresh-route-service.test.ts": { @@ -3072,6 +6256,9 @@ "tests/unit/token-refresh-service.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 17 + }, + "@typescript-eslint/no-unused-vars": { + "count": 3 } }, "tests/unit/tool-request-sanitization.test.ts": { @@ -3109,6 +6296,21 @@ "count": 1 } }, + "tests/unit/translator-friendly-page-client.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/translator-friendly-raw-json-panel.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/translator-friendly-translate-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/translator-helper-branches.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 25 @@ -3119,6 +6321,11 @@ "count": 76 } }, + "tests/unit/translator-openai-to-claude.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/translator-openai-to-gemini.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 74 @@ -3144,11 +6351,21 @@ "count": 17 } }, + "tests/unit/translator-resp-empty-string-tool-arg.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, "tests/unit/translator-resp-gemini-to-openai.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 75 } }, + "tests/unit/translator-resp-openai-responses-roundtrip.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 4 + } + }, "tests/unit/translator-resp-openai-to-claude.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 14 @@ -3169,11 +6386,121 @@ "count": 9 } }, + "tests/unit/tryBackedChat.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/ui/activity-page-redirect.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/allocation-table.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/cheaperInferenceSponsorBanner.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/cli-code-detail-page.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/combo-defaults-fusion-5598.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/engine-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/fleetAggregation.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 3 + } + }, + "tests/unit/ui/memories-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/model-alias-edit.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/model-capability-overrides-tab-9557.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/ui/model-select-modal-deselect.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/playground-compare-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/playground-structured-output-editor.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/provider-quota-widget-auto-refresh-label-4611.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, + "tests/unit/ui/search-tools-compare-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/search-tools-scrape-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/search-tools-search-tab.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/use-stream-metrics.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/use-traffic-stream.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/ui/useToolBatchStatuses.test.tsx": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/universal-handoff.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 16 } }, + "tests/unit/usage-analytics-route.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/usage-analytics.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 5 @@ -3204,14 +6531,25 @@ "count": 4 } }, + "tests/unit/vercel-deploy-sso-protection-check.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/version-manager.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 } }, "tests/unit/versionManager-orchestrator.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 + }, + "@typescript-eslint/no-unused-vars": { + "count": 2 } }, "tests/unit/vertex-express-apikey.test.ts": { @@ -3224,9 +6562,32 @@ "count": 3 } }, + "tests/unit/video-custom-provider-route.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, "tests/unit/vscode-token-routes.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 65 + }, + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/waitForServer-tcp-fallback-6800.test.mjs": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/warmupScheduler.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 1 + } + }, + "tests/unit/web-cookie-providers-new.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 5 } }, "tests/unit/web-runtime-env.test.ts": { @@ -3234,6 +6595,11 @@ "count": 1 } }, + "tests/unit/web-search-9279-repro.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } + }, "tests/unit/web-search-fallback-format.test.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -3248,5 +6614,10 @@ "@typescript-eslint/no-explicit-any": { "count": 5 } + }, + "tests/unit/xiaomi-providers-registry.test.ts": { + "@typescript-eslint/no-unused-vars": { + "count": 2 + } } -} \ No newline at end of file +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 4d54c7e91a..cf70711662 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -145,6 +145,31 @@ const eslintConfig = [ "react-hooks/rules-of-hooks": "off", }, }, + // Ratchet: bar NEW unused vars/args/catches outside the `_` escape hatch. + // Pre-existing violations are frozen via config/quality/eslint-suppressions.json + // (same pattern as #7879 toNumber); only genuinely NEW unused bindings fail + // lint. `args: "all"` (not `after-used`) so a leading unused param is never + // silently skipped, e.g. `function handle(req, _opts, next)` must flag `req`. + { + files: ["src/**/*.{ts,tsx,js,jsx}", "open-sse/**/*.ts", "tests/**/*.{ts,tsx,mjs}"], + plugins: { + "@typescript-eslint": tseslint.plugin, + }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + ignoreRestSiblings: true, + }, + ], + }, + }, // Global ignores — keep ESLint scoped to source files only { ignores: [ From 92a083ab8c4376f415c4269a0e61229157702e20 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:28:19 -0300 Subject: [PATCH 16/20] fix(resilience): honor dashboard quota snapshots in opencode-go preflight (#11234) (#11267) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The opencode-go quota preflight ignored the dashboard quota snapshots, so priority combos kept selecting connections whose weekly window was already drained. Two gaps, two fixes: A) fetchOpencodeQuota only consulted the live upstream endpoint, which has no public quota API (404 — the module JSDoc already admits this). Every preflight therefore evaluated null and proceeded (fail-open) even with a connection at 0% weekly remaining in plain sight on the dashboard. The fetcher now synthesizes its triple-window QuotaInfo from the cached dashboard snapshots when the live endpoint yields nothing, mapping session→window_5h, weekly→window_weekly, mcp_monthly→window_monthly and mirroring getQuotaWindowStatus semantics (expired resetAt = window rolled over = must not count as exhausted; fractionReported=false = unknown, never exhaustion). Read-only via src/domain/quotaCache.ts accessors — never SQL, never a re-scrape on the hot path — and gated on the connection actually having dashboard scrape config, so unconfigured connections never touch the snapshot store. Fail-open is preserved: no snapshots → null, exactly as before. The quotaCache import is dynamic because a static edge would close an initialization cycle (fetcher → quotaCache → usage.ts → usage/opencode.ts → fetcher). B) The sibling-selection latency gate in getProviderCredentialsWithQuotaPreflight never consulted resilience.quotaPreflight.enabled (QUOTA_PREFLIGHT_CUTOFF_ENABLED) — that flag only armed the auto-strategy candidate builder and the per-target cutoff for pinned connections, so a priority combo over sibling opencode-go connections (connectionId null at combo level) skipped preflight entirely. The flag now arms the gate as well; the default (flag off) is unchanged. TDD (Hard Rule #18), tests/unit/quota-exhaustion-cutoff-opencode.test.ts: - fetcher 404 + seeded snapshots weekly=0%/session=80% → cutoff blocks (RED before, GREEN after); also asserts the bridge is read-only (single upstream fetch, no re-scrape). - weekly 0% with next_reset_at in the past → not blocked (window dropped). - per-window threshold override resolves against the mapped window_weekly key (50% override blocks at 40% remaining; factory 2% does not). - fail-open guard: configured dashboard with no snapshots still returns null. - selector level: flag on, two opencode-go sisters, priority-1 exhausted → selection skips to the healthy one (RED before, GREEN after). Sibling suites green: opencode-quota-fetcher (18), quota-preflight, combo-priority-quota-exhaustion-cutoff-5923, issue-6686, 8431, throttle-6911, sse-auth*, quota fetchers, combo strategies, snapshot/hydration tests (~500 tests). eslint (with suppressions) and typecheck:core clean. Closes #11234 Co-authored-by: Xiangzhe --- open-sse/services/opencodeOllamaUsage.ts | 2 +- open-sse/services/opencodeQuotaFetcher.ts | 130 +++++++- src/sse/services/auth.ts | 7 + .../quota-exhaustion-cutoff-opencode.test.ts | 303 ++++++++++++++++++ 4 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 tests/unit/quota-exhaustion-cutoff-opencode.test.ts diff --git a/open-sse/services/opencodeOllamaUsage.ts b/open-sse/services/opencodeOllamaUsage.ts index b3beb4606d..4dd52c4f6d 100644 --- a/open-sse/services/opencodeOllamaUsage.ts +++ b/open-sse/services/opencodeOllamaUsage.ts @@ -103,7 +103,7 @@ function getProviderSpecificString(data: JsonRecord | undefined, keys: string[]) return ""; } -function resolveOpenCodeGoDashboardConfig( +export function resolveOpenCodeGoDashboardConfig( providerSpecificData?: JsonRecord ): OpenCodeGoDashboardConfig { const workspaceId = diff --git a/open-sse/services/opencodeQuotaFetcher.ts b/open-sse/services/opencodeQuotaFetcher.ts index 79d38b81c2..359213f97b 100644 --- a/open-sse/services/opencodeQuotaFetcher.ts +++ b/open-sse/services/opencodeQuotaFetcher.ts @@ -48,6 +48,7 @@ import { registerQuotaFetcher, registerQuotaWindows, type QuotaInfo } from "./quotaPreflight.ts"; import { registerMonitorFetcher } from "./quotaMonitor.ts"; import { throttleQuotaFetch } from "./quotaFetchThrottle.ts"; +import { resolveOpenCodeGoDashboardConfig } from "./opencodeOllamaUsage.ts"; // OpenCode quota endpoint — same key works across opencode, opencode-go, opencode-zen // Default points at /zen/go/v1/quota which returns 404 today (no public quota API yet, @@ -227,6 +228,114 @@ function parseOpencodeQuotaResponse(data: unknown): OpencodeTripleWindowQuota | // ─── Core Fetcher ───────────────────────────────────────────────────────────── +// ─── Dashboard Snapshot Bridge (#11234) ─────────────────────────────────────── +// +// The live endpoint above has no public quota API today (404 — see module +// JSDoc), so without this bridge every preflight evaluated `null` and +// proceeded (fail-open) even when the dashboard already showed a drained +// window. The dashboard scrape (`getOpenCodeGoUsage` in +// opencodeOllamaUsage.ts) persists per-window snapshots through +// `src/domain/quotaCache.ts::setQuotaCache` under the window keys +// session / weekly / mcp_monthly; this bridge synthesizes the same +// OpencodeTripleWindowQuota shape from those cached snapshots so the quota +// cutoff sees them. +// +// Read-only: accessors only, never SQL, never a re-scrape on the hot path. +// Fail-open is preserved — no snapshots means `null`, exactly as before. + +// Dashboard snapshot key → fetcher/preflight window key. +const DASHBOARD_SNAPSHOT_WINDOW_MAP: ReadonlyArray = [ + ["session", OPENCODE_WINDOW_5H], + ["weekly", OPENCODE_WINDOW_WEEKLY], + ["mcp_monthly", OPENCODE_WINDOW_MONTHLY], +]; + +function hasDashboardQuotaConfig(connection?: Record): boolean { + // Snapshots can only exist when the operator configured the dashboard + // scrape for this connection (or globally via env). Gating on it keeps the + // snapshot read (and its cold-start DB hydration) off connections that + // could never have produced one. + const psd = connection?.providerSpecificData as Record | undefined; + return resolveOpenCodeGoDashboardConfig(psd).state !== "none"; +} + +async function synthesizeQuotaFromDashboardSnapshots( + connectionId: string +): Promise { + let quotaCacheDomain: typeof import("../../src/domain/quotaCache.ts"); + try { + // Dynamic import: a static edge would close an initialization cycle + // (opencodeQuotaFetcher → quotaCache → usage.ts → usage/opencode.ts → + // opencodeQuotaFetcher). + quotaCacheDomain = await import("../../src/domain/quotaCache.ts"); + } catch { + return null; + } + + // Hydrate the in-memory cache from persisted snapshots when cold (the + // accessor does this internally), then read the raw per-window rows. + quotaCacheDomain.getQuotaWindowStatus(connectionId, DASHBOARD_SNAPSHOT_WINDOW_MAP[0][0]); + const entry = quotaCacheDomain.getQuotaCache(connectionId); + const quotas = entry?.quotas; + if (!quotas || typeof quotas !== "object") return null; + + const now = Date.now(); + const windows: Record = {}; + + for (const [snapshotKey, windowKey] of DASHBOARD_SNAPSHOT_WINDOW_MAP) { + const raw = quotas[snapshotKey]; + if (!raw || typeof raw.remainingPercentage !== "number") continue; + // #10095 mirror: a window whose fraction upstream never reported is + // "unknown", not 0% — it must not count as exhausted. + if (raw.fractionReported === false) continue; + const resetAt = typeof raw.resetAt === "string" && raw.resetAt ? raw.resetAt : null; + if (resetAt) { + const resetMs = Date.parse(resetAt); + // Mirror getQuotaWindowStatus (quotaCache.ts): an expired resetAt means + // the window has rolled into a fresh period — the cached percentage is + // stale and must not count as exhausted. + if (Number.isFinite(resetMs) && resetMs <= now) continue; + } + const remaining = Math.max(0, Math.min(100, raw.remainingPercentage)); + windows[windowKey] = { percentUsed: 1 - remaining / 100, resetAt }; + } + + if (Object.keys(windows).length === 0) return null; + + const window5h = windows[OPENCODE_WINDOW_5H] ?? { percentUsed: 0, resetAt: null }; + const windowWeekly = windows[OPENCODE_WINDOW_WEEKLY] ?? { percentUsed: 0, resetAt: null }; + const windowMonthly = windows[OPENCODE_WINDOW_MONTHLY] ?? { percentUsed: 0, resetAt: null }; + + const worstPercent = Math.max( + window5h.percentUsed, + windowWeekly.percentUsed, + windowMonthly.percentUsed + ); + + // Dominant reset: pick the window with the worst usage (same policy as the + // live-response parser above). + let dominantResetAt: string | null = null; + if (worstPercent === window5h.percentUsed) { + dominantResetAt = window5h.resetAt ?? windowWeekly.resetAt ?? windowMonthly.resetAt; + } else if (worstPercent === windowWeekly.percentUsed) { + dominantResetAt = windowWeekly.resetAt ?? window5h.resetAt ?? windowMonthly.resetAt; + } else { + dominantResetAt = windowMonthly.resetAt ?? windowWeekly.resetAt ?? window5h.resetAt; + } + + return { + used: worstPercent * 100, + total: 100, + percentUsed: worstPercent, + resetAt: dominantResetAt, + windows, + window5h, + windowWeekly, + windowMonthly, + limitReached: worstPercent >= 1, + }; +} + /** * Fetch current quota for an OpenCode connection. * Returns percentUsed = max(5h%, weekly%, monthly%) — worst-case across all windows. @@ -242,18 +351,37 @@ export async function fetchOpencodeQuota( connectionId: string, connection?: Record ): Promise { + // Snapshots can only exist when the dashboard scrape is configured for this + // connection (or globally via env); without it the bridge stays off and the + // fetcher never touches the snapshot store. + const dashboardConfigured = hasDashboardQuotaConfig(connection); + // Check cache first const cached = quotaCache.get(connectionId); if (cached) { // 404 sentinel — use longer TTL to avoid hammering a non-existent endpoint if (cached.noEndpoint && Date.now() - cached.fetchedAt < NO_ENDPOINT_TTL_MS) { - return null; + // The live endpoint is known-absent — serve dashboard snapshots if the + // operator configured the scrape (#11234). + return dashboardConfigured ? synthesizeQuotaFromDashboardSnapshots(connectionId) : null; } if (cached.quota !== null && Date.now() - cached.fetchedAt < CACHE_TTL_MS) { return cached.quota; } } + const live = await fetchLiveOpencodeQuota(connectionId, connection); + if (live) return live; + + // #11234 — the live endpoint has no public quota API (404) or failed: + // fall back to the operator-configured dashboard snapshots, read-only. + return dashboardConfigured ? synthesizeQuotaFromDashboardSnapshots(connectionId) : null; +} + +async function fetchLiveOpencodeQuota( + connectionId: string, + connection?: Record +): Promise { // Extract API key from connection const apiKey = typeof connection?.apiKey === "string" && connection.apiKey.trim().length > 0 diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 5d3d885178..a14c30462f 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -2274,6 +2274,11 @@ export async function getProviderCredentialsWithQuotaPreflight( // • a per-connection override on this row // • a per-(provider, window) default in resilience settings // • the legacy `quotaPreflightEnabled` flag in providerSpecificData + // • the operator-enabled quota cutoff (resilience.quotaPreflight.enabled / + // QUOTA_PREFLIGHT_CUTOFF_ENABLED) — #11234: it previously only armed the + // auto-strategy candidate builder and the per-target cutoff for pinned + // connections, so priority combos over sibling connections (no pinned + // connectionId) never filtered an exhausted sister // • the global default is stricter than the factory no-op level // (factory = 2% remaining, basically "right before 429" — anything // stricter means the operator wants enforcement everywhere) @@ -2295,10 +2300,12 @@ export async function getProviderCredentialsWithQuotaPreflight( const hasConnectionOverrides = Object.keys(perConnectionWindowOverrides).length > 0; const legacyForceEnable = isQuotaPreflightEnabled(credentials as Record); + const globalCutoffEnabled = resilience.quotaPreflight.enabled === true; if ( !hasConnectionOverrides && !providerHasDefaults && !legacyForceEnable && + !globalCutoffEnabled && !globalDefaultIsRestrictive ) { const committed = await commitLease(); diff --git a/tests/unit/quota-exhaustion-cutoff-opencode.test.ts b/tests/unit/quota-exhaustion-cutoff-opencode.test.ts new file mode 100644 index 0000000000..d212fc9394 --- /dev/null +++ b/tests/unit/quota-exhaustion-cutoff-opencode.test.ts @@ -0,0 +1,303 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +/** + * #11234 — opencode-go quota preflight ignored the dashboard quota snapshots. + * + * Root cause (two gaps): + * + * A) `fetchOpencodeQuota` (open-sse/services/opencodeQuotaFetcher.ts) only + * consulted the live upstream endpoint, which has no public quota API + * (404 — see module JSDoc). It never read the quota snapshots the + * dashboard scrape (`getOpenCodeGoUsage`, keyed session/weekly/mcp_monthly) + * persists through `src/domain/quotaCache.ts`. Every preflight therefore + * evaluated `null` and proceeded (fail-open), even with a sister + * connection sitting at 0% weekly remaining in plain sight on the + * dashboard. + * + * B) The sibling-selection latency gate in + * `src/sse/services/auth.ts::getProviderCredentialsWithQuotaPreflight` + * never consulted `resilience.quotaPreflight.enabled` + * (QUOTA_PREFLIGHT_CUTOFF_ENABLED). That flag only armed the auto-strategy + * candidate builder and the per-target cutoff for pinned connections, so + * a priority combo over sibling opencode-go connections (connectionId + * null at combo level) skipped preflight entirely. + * + * Fix: + * A) The fetcher now synthesizes its triple-window quota from the cached + * dashboard snapshots (read-only, accessors only, no re-scrape on the hot + * path) when the live endpoint yields nothing — mapping + * session→window_5h, weekly→window_weekly, mcp_monthly→window_monthly and + * mirroring `getQuotaWindowStatus` semantics (expired resetAt = window has + * rolled over = must not count as exhausted). + * B) `resilience.quotaPreflight.enabled === true` now arms the + * sibling-selection latency gate as well. + * + * These tests are the regression guards: fetcher-level for (A), selector-level + * for (B). + */ + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omni-quota-11234-")); +process.env.DATA_DIR = TEST_DATA_DIR; +// Part (B): the operator flag must be ON before the resilience settings module +// is first imported (its defaults are computed at module load). +process.env.QUOTA_PREFLIGHT_CUTOFF_ENABLED = "true"; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "quota-11234-secret"; + +const originalFetch = globalThis.fetch; + +const coreDb = await import("../../src/lib/db/core.ts"); +const quotaSnapshotsDb = await import("../../src/lib/db/quotaSnapshots.ts"); +const quotaCache = await import("../../src/domain/quotaCache.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const apiKeysDb = await import("../../src/lib/db/apiKeys.ts"); +const { fetchOpencodeQuota, invalidateOpencodeQuotaCache } = await import( + "../../open-sse/services/opencodeQuotaFetcher.ts" +); +const { evaluateQuotaCutoff, registerQuotaFetcher } = await import( + "../../open-sse/services/quotaPreflight.ts" +); +const { buildAutoQuotaThresholds } = await import( + "../../open-sse/services/combo/quotaExhaustionCutoff.ts" +); +const { resolveResilienceSettings } = await import("../../src/lib/resilience/settings.ts"); +const auth = await import("../../src/sse/services/auth.ts"); + +const PROVIDER = "opencode-go"; +// Dashboard scrape window keys (opencodeOllamaUsage.ts::OPENCODE_GO_QUOTA_ORDER) +const DASH_SESSION = "session"; +const DASH_WEEKLY = "weekly"; +// Fetcher/preflight window keys (opencodeQuotaFetcher.ts registry) +const WINDOW_5H = "window_5h"; +const WINDOW_WEEKLY = "window_weekly"; + +function seedSnapshot( + connectionId: string, + windowKey: string, + remainingPercentage: number, + nextResetAt: string | null +) { + quotaSnapshotsDb.saveQuotaSnapshot({ + provider: PROVIDER, + connection_id: connectionId, + window_key: windowKey, + remaining_percentage: remainingPercentage, + is_exhausted: remainingPercentage <= 0 ? 1 : 0, + next_reset_at: nextResetAt, + window_duration_ms: null, + raw_data: null, + }); +} + +function dashboardConfiguredConnection(apiKey: string): Record { + // Mirrors the operator-configured dashboard scrape + // (opencodeOllamaUsage.ts::resolveOpenCodeGoDashboardConfig). + return { + apiKey, + providerSpecificData: { + openCodeGoWorkspaceId: "ws-11234", + openCodeGoAuthCookie: "auth-cookie-11234", + }, + }; +} + +function hoursFromNow(hours: number): string { + return new Date(Date.now() + hours * 3_600_000).toISOString(); +} + +test.after(() => { + globalThis.fetch = originalFetch; + coreDb.resetDbInstance(); + apiKeysDb.resetApiKeyState(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test.afterEach(() => { + globalThis.fetch = originalFetch; + quotaCache.__clearForTests(); +}); + +// ─── (A) fetcher bridge: dashboard snapshots → QuotaInfo ──────────────────── + +test("#11234 dashboard snapshots feed the quota cutoff when the live endpoint has no quota API", async () => { + const connectionId = `oc-11234-block-${Date.now()}`; + let fetchCalls = 0; + globalThis.fetch = async () => { + fetchCalls += 1; + return new Response(null, { status: 404 }); + }; + + // Dashboard shows: weekly fully drained (0% remaining, reset in 3 days), + // session healthy (80% remaining). + seedSnapshot(connectionId, DASH_WEEKLY, 0, hoursFromNow(72)); + seedSnapshot(connectionId, DASH_SESSION, 80, hoursFromNow(2)); + + const quota = await fetchOpencodeQuota(connectionId, dashboardConfiguredConnection("sk-test")); + + assert.ok(quota, "fetcher must synthesize quota from dashboard snapshots when the live endpoint 404s"); + assert.equal(fetchCalls, 1, "snapshot bridge must be read-only — no re-scrape on the hot path"); + + // Key mapping: weekly → window_weekly (0% remaining = 100% used), + // session → window_5h (80% remaining = 20% used). + assert.equal(quota.windows?.[WINDOW_WEEKLY]?.percentUsed, 1); + assert.ok( + Math.abs((quota.windows?.[WINDOW_5H]?.percentUsed ?? 0) - 0.2) < 1e-9, + `window_5h percentUsed should be ~0.2, got ${quota.windows?.[WINDOW_5H]?.percentUsed}` + ); + + const decision = evaluateQuotaCutoff( + quota, + buildAutoQuotaThresholds(PROVIDER, undefined, null) + ); + assert.equal(decision.proceed, false, "weekly at 0% remaining must block the connection"); + assert.equal(decision.reason, "quota_exhausted"); + + invalidateOpencodeQuotaCache(connectionId); +}); + +test("#11234 a snapshot whose reset already passed must not count as exhausted", async () => { + const connectionId = `oc-11234-expired-${Date.now()}`; + globalThis.fetch = async () => new Response(null, { status: 404 }); + + // Weekly hit 0% but its reset is 1h in the PAST — the window rolled into a + // fresh period, so the stale 0% must not block (mirrors + // getQuotaWindowStatus: expired resetAt → reachedThreshold = false). + seedSnapshot(connectionId, DASH_WEEKLY, 0, hoursFromNow(-1)); + seedSnapshot(connectionId, DASH_SESSION, 80, hoursFromNow(2)); + + const quota = await fetchOpencodeQuota(connectionId, dashboardConfiguredConnection("sk-test")); + + assert.ok(quota, "the healthy session snapshot should still synthesize"); + assert.equal( + quota.windows?.[WINDOW_WEEKLY], + undefined, + "an expired weekly window must be dropped from the synthesized quota" + ); + + const decision = evaluateQuotaCutoff( + quota, + buildAutoQuotaThresholds(PROVIDER, undefined, null) + ); + assert.equal(decision.proceed, true, "an expired weekly window must not block the connection"); + + invalidateOpencodeQuotaCache(connectionId); +}); + +test("#11234 per-window threshold overrides apply to the mapped window_weekly key", async () => { + const connectionId = `oc-11234-threshold-${Date.now()}`; + globalThis.fetch = async () => new Response(null, { status: 404 }); + + // Weekly at 40% remaining — above the factory 2% cutoff (would proceed), + // but below an operator override of 50% min-remaining for window_weekly. + seedSnapshot(connectionId, DASH_WEEKLY, 40, hoursFromNow(72)); + seedSnapshot(connectionId, DASH_SESSION, 90, hoursFromNow(2)); + + const quota = await fetchOpencodeQuota(connectionId, dashboardConfiguredConnection("sk-test")); + assert.ok(quota); + + const factoryDecision = evaluateQuotaCutoff( + quota, + buildAutoQuotaThresholds(PROVIDER, undefined, null) + ); + assert.equal( + factoryDecision.proceed, + true, + "factory 2% cutoff must not block a window at 40% remaining" + ); + + const settings = resolveResilienceSettings({ + resilienceSettings: { + quotaPreflight: { + enabled: true, + providerWindowDefaults: { [PROVIDER]: { [WINDOW_WEEKLY]: 50 } }, + }, + }, + }); + const overrideDecision = evaluateQuotaCutoff( + quota, + buildAutoQuotaThresholds(PROVIDER, undefined, settings) + ); + assert.equal( + overrideDecision.proceed, + false, + "a 50% window_weekly override must block at 40% remaining — the override resolves against the mapped key" + ); + + invalidateOpencodeQuotaCache(connectionId); +}); + +test("#11234 fail-open preserved: configured dashboard with no snapshots still returns null", async () => { + const connectionId = `oc-11234-failopen-${Date.now()}`; + globalThis.fetch = async () => new Response(null, { status: 404 }); + + const quota = await fetchOpencodeQuota(connectionId, dashboardConfiguredConnection("sk-test")); + assert.equal(quota, null, "no snapshots → fail-open (null), exactly as before"); + + invalidateOpencodeQuotaCache(connectionId); +}); + +// ─── (B) flag scope: sibling-selection latency gate ───────────────────────── + +test("#11234 quotaPreflight.enabled arms sibling selection: the exhausted sister is skipped for the healthy one", async () => { + const tag = Date.now(); + + const exhausted = await providersDb.createProviderConnection({ + provider: PROVIDER, + authType: "apikey", + name: `oc-11234-exhausted-${tag}`, + apiKey: "sk-oc-11234-exhausted", + priority: 1, + isActive: true, + testStatus: "active", + }); + const healthy = await providersDb.createProviderConnection({ + provider: PROVIDER, + authType: "apikey", + name: `oc-11234-healthy-${tag}`, + apiKey: "sk-oc-11234-healthy", + priority: 2, + isActive: true, + testStatus: "active", + }); + + // Stub the upstream quota signal: the priority-1 sister is fully drained, + // the priority-2 sister is healthy. No per-connection overrides, no + // per-(provider, window) defaults, no legacy quotaPreflightEnabled flag, + // factory 2% global threshold — so TODAY the latency gate skips preflight + // entirely and the selector returns the exhausted sister. With + // resilience.quotaPreflight.enabled arming the gate, preflight must run and + // skip her. + registerQuotaFetcher(PROVIDER, async (connectionId: string) => { + if (connectionId === exhausted.id) { + return { + used: 100, + total: 100, + percentUsed: 1.0, + resetAt: hoursFromNow(1), + }; + } + return { used: 0, total: 100, percentUsed: 0, resetAt: null }; + }); + + try { + const selection = await auth.getProviderCredentialsWithQuotaPreflight( + PROVIDER, + null, + null, + null + ); + const result = selection as { connectionId?: string } | null; + + assert.equal( + result?.connectionId, + healthy.id, + "with quotaPreflight.enabled the selector must skip the exhausted priority-1 sister and pick the healthy one" + ); + } finally { + await providersDb.deleteProviderConnection(exhausted.id); + await providersDb.deleteProviderConnection(healthy.id); + } +}); From 7c2dba0b9b58809fe7edce7c8e1f9dc8a2b671b6 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:29:32 -0300 Subject: [PATCH 17/20] feat(monitoring): expose structural chat admission snapshot + shed counters (#11244) (#11268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The structural admission gate (src/shared/middleware/chatBodyAdmission.ts — the bounded heavyweight lease + healthy-headroom path from #10110/#10437) returns its 503 chat_admission_busy BEFORE request logging, so a shed left no trace: no counter, no log line, and the process-wide snapshot was never consumed by any route. This adds pure observability — admission behavior, defaults, and thresholds are untouched. - chatBodyAdmission.ts: in-memory shed history (shedTotal + shedsByReason) on ChatAdmissionController, recorded at the two capacity-driven give-up points in acquireHeavyWithin (queue_timeout when the bounded wait expires, queued_bytes_budget when the heap valve refuses to park). A client abort mid-wait is deliberately not counted — capacity was never denied. Each shed also emits exactly one structured pino warn (module chat-admission) with reason/activeHeavy/waiting/queuedBytes and the HMAC session fingerprint — never a raw credential. PerConnectionAdmission Controller.snapshot() now carries the counters plus activeHealthyHeadroom. - /api/monitoring/health: the structural snapshot is exposed under a new additive chatAdmission key next to the existing adaptiveAdmission (the shadow-mode layer) — allowlisted projection in observability.ts, nothing removed from the current payload. - Tests: tests/unit/chat-admission-visibility-11244.test.ts (RED→GREEN: shed counting per reason, abort exclusion, snapshot shape, and the warn log carrying the fingerprint but never the raw API key) + a chatAdmission allowlist-projection test in observability-payloads.test.ts. Co-authored-by: Xiangzhe --- src/app/api/monitoring/health/route.ts | 15 ++ src/lib/monitoring/observability.ts | 45 ++++ src/shared/middleware/chatBodyAdmission.ts | 127 +++++++++- .../chat-admission-visibility-11244.test.ts | 238 ++++++++++++++++++ tests/unit/observability-payloads.test.ts | 69 +++++ 5 files changed, 485 insertions(+), 9 deletions(-) create mode 100644 tests/unit/chat-admission-visibility-11244.test.ts diff --git a/src/app/api/monitoring/health/route.ts b/src/app/api/monitoring/health/route.ts index f3dceac558..b3e031b348 100644 --- a/src/app/api/monitoring/health/route.ts +++ b/src/app/api/monitoring/health/route.ts @@ -74,6 +74,7 @@ export async function GET(request: Request) { credentialHealthModule, localHealthModule, adaptiveAdmissionModule, + chatAdmissionModule, settingsResult, connectionsResult, ] = await Promise.allSettled([ @@ -86,6 +87,7 @@ export async function GET(request: Request) { import("@/lib/credentialHealth/cache"), import("@/lib/localHealthCheck"), import("@omniroute/open-sse/services/admission/runtime.ts"), + import("@/shared/middleware/chatBodyAdmission"), getCachedSettings(), getProviderConnections(), ]); @@ -172,6 +174,17 @@ export async function GET(request: Request) { null ) : null; + // #11244: the STRUCTURAL admission gate (chatBodyAdmission.ts — bounded + // heavyweight lease + shed counters), exposed next to but distinct from the + // adaptive shadow-mode snapshot above. Additive key — nothing existing moves. + const chatAdmission = + chatAdmissionModule.status === "fulfilled" + ? readHealthValue( + "chat admission", + () => chatAdmissionModule.value.perConnectionAdmissionController.snapshot(), + null + ) + : null; const payload = buildHealthPayload({ appVersion: APP_CONFIG.version, @@ -200,6 +213,7 @@ export async function GET(request: Request) { activeSessionsByKey, credentialHealth, adaptiveAdmission, + chatAdmission, }); healthPayloadCache = { payload, expiresAt: Date.now() + HEALTH_PAYLOAD_TTL_MS }; @@ -218,6 +232,7 @@ export async function GET(request: Request) { quotaMonitor: { ...fallbackQuotaMonitorSummary, monitors: [] }, sessions: { activeCount: 0, stickyBoundCount: 0, byApiKey: {}, top: [] }, adaptiveAdmission: null, + chatAdmission: null, dedup: { inflightRequests: 0 }, }); } diff --git a/src/lib/monitoring/observability.ts b/src/lib/monitoring/observability.ts index 4cc18b27fd..cb4d239a65 100644 --- a/src/lib/monitoring/observability.ts +++ b/src/lib/monitoring/observability.ts @@ -3,9 +3,48 @@ import { getCodexParentAccountDiagnostic, } from "@omniroute/open-sse/services/codexAccount/index.ts"; import type { AdaptiveAdmissionPublicSnapshot } from "@omniroute/open-sse/services/admission/runtime.ts"; +import type { PerConnectionAdmissionController } from "@/shared/middleware/chatBodyAdmission"; type JsonRecord = Record; +/** Process-wide structural chat-admission snapshot type (chatBodyAdmission.ts). */ +export type ChatAdmissionSnapshot = ReturnType; + +/** + * Low-card structural chat-admission health summary (#11244) — the bounded + * heavyweight-lease gate from chatBodyAdmission.ts (#10110/#10437), NOT the + * adaptive shadow-mode layer above. Lane keys are opaque HMAC fairness + * fingerprints (resolveSessionId), never raw credentials. + */ +export type ChatAdmissionHealthSummary = { + activeHeavy: number; + activeHealthyHeadroom: number; + waiting: number; + queuedBytes: number; + shedTotal: number; + shedsByReason: Record; + lanes: Array<{ key: string; waiting: number }>; +}; + +/** + * Explicit allowlisted projection of the structural admission snapshot. + * Never spreads the snapshot — only the documented low-cardinality fields pass. + */ +export function projectChatAdmissionSummary( + snapshot: ChatAdmissionSnapshot | null | undefined +): ChatAdmissionHealthSummary | null { + if (!snapshot || typeof snapshot !== "object") return null; + return { + activeHeavy: snapshot.activeHeavy, + activeHealthyHeadroom: snapshot.activeHealthyHeadroom, + waiting: snapshot.waiting, + queuedBytes: snapshot.queuedBytes, + shedTotal: snapshot.shedTotal, + shedsByReason: { ...(snapshot.shedsByReason ?? {}) }, + lanes: (snapshot.lanes ?? []).map((lane) => ({ key: lane.key, waiting: lane.waiting })), + }; +} + /** Low-card adaptive-admission health summary — no tenant/request/body/queue details. */ export type AdaptiveAdmissionHealthSummary = { mode: AdaptiveAdmissionPublicSnapshot["mode"]; @@ -160,6 +199,8 @@ interface BuildHealthPayloadOptions { }; /** Optional injected public adaptive-admission snapshot; projected, never raw-spread. */ adaptiveAdmission?: AdaptiveAdmissionPublicSnapshot | null; + /** #11244: optional structural chat-admission snapshot; projected, never raw-spread. */ + chatAdmission?: ChatAdmissionSnapshot | null; } function limitMonitors(monitors: QuotaMonitorSnapshot[], maxItems = 8): QuotaMonitorSnapshot[] { @@ -347,6 +388,7 @@ export function buildHealthPayload({ activeSessionsByKey = {}, credentialHealth, adaptiveAdmission = null, + chatAdmission = null, buildSha = null, }: BuildHealthPayloadOptions) { const timestamp = new Date().toISOString(); @@ -449,6 +491,9 @@ export function buildHealthPayload({ sessions: buildSessionsSummary({ activeSessions, activeSessionsByKey }), credentialHealth, // may be undefined if credentialHealth module not loaded adaptiveAdmission: projectAdaptiveAdmissionSummary(adaptiveAdmission), + // #11244: the STRUCTURAL gate (chatBodyAdmission.ts) next to the adaptive one — + // distinct key so clients reading `adaptiveAdmission` are untouched. + chatAdmission: projectChatAdmissionSummary(chatAdmission), dedup: { inflightRequests, }, diff --git a/src/shared/middleware/chatBodyAdmission.ts b/src/shared/middleware/chatBodyAdmission.ts index 9b20d78e5a..3e37f3ca3b 100644 --- a/src/shared/middleware/chatBodyAdmission.ts +++ b/src/shared/middleware/chatBodyAdmission.ts @@ -16,6 +16,7 @@ */ import { CORS_HEADERS } from "../utils/cors"; +import { createLogger } from "../utils/logger"; import { createHmac } from "crypto"; import v8 from "node:v8"; import { trackRequest } from "../../lib/gracefulShutdown"; @@ -168,6 +169,47 @@ interface AdmissionWaiter { readonly resolve: () => void; } +/** + * Why a structural shed (503 `chat_admission_busy`) happened (#11244): + * - `queue_timeout`: the bounded wait expired with no heavyweight capacity freed + * (includes the `queueMs=0` legacy immediate-reject path — capacity was busy at + * the instant the request arrived). + * - `queued_bytes_budget`: the queued-bytes heap valve (#9654 / U3) refused to + * park the waiter because the buffered-body budget was already exhausted. + * + * A client abort mid-wait is deliberately NOT a shed: capacity was never denied, + * the caller simply left (its 503 is dropped on the dead connection). + */ +export type ChatAdmissionShedReason = "queue_timeout" | "queued_bytes_budget"; + +/** + * One structural-shed observation, emitted to the shed sink at warn level. + * `lane` is the opaque fairness key — the HMAC fingerprint produced by + * `resolveSessionId` (or "anonymous"/"default"), never a raw credential. + */ +export interface ChatAdmissionShedEvent { + reason: ChatAdmissionShedReason; + activeHeavy: number; + waiting: number; + queuedBytes: number; + lane: string; +} + +export type ChatAdmissionShedSink = (event: ChatAdmissionShedEvent) => void; + +const shedLog = createLogger("chat-admission"); + +/** + * Default shed sink (#11244): exactly one structured warn per structural shed. + * The 503 returns BEFORE request logging, so without this line a shed left no + * trace anywhere. No raw credentials — `lane` is already the HMAC fingerprint, + * and the shared logger's redaction hook (logRedaction.ts) is the safety net. + * Nothing is logged for admitted requests (noise). + */ +function defaultChatAdmissionShedSink(event: ChatAdmissionShedEvent): void { + shedLog.warn(event, "structural chat admission shed (chat_admission_busy)"); +} + /** * Process-local heavyweight reservation. The capacity check and increment execute in one * synchronous JavaScript turn, making acquisition atomic within an OmniRoute process. @@ -190,6 +232,12 @@ export class ChatAdmissionController { /** Keys in creation order; #fairCursor scans them round-robin. */ #fairKeys: string[] = []; #fairCursor = 0; + /** #11244: in-memory shed history (total + per reason). The 503 chat_admission_busy + * response returns before request logging, so without these counters a structural + * shed was invisible. Same in-memory lifetime as the rest of the snapshot state. */ + #shedTotal = 0; + #shedsByReason = new Map(); + readonly #onShed: ChatAdmissionShedSink; constructor( readonly maxHeavyInFlight = 1, @@ -197,7 +245,10 @@ export class ChatAdmissionController { /** #10437: bounded extra capacity for the healthy-heap fast path. `0` disables * the bypass entirely — every busy request then falls through to the same * bounded-wait/shed path used under real heap pressure. */ - readonly healthyHeadroom = CHAT_ADMISSION_HEALTHY_HEADROOM + readonly healthyHeadroom = CHAT_ADMISSION_HEALTHY_HEADROOM, + /** #11244: sink notified once per structural shed. Defaults to the shared pino + * logger (warn); tests inject a capture/no-op sink. */ + onShed: ChatAdmissionShedSink = defaultChatAdmissionShedSink ) { if (!Number.isSafeInteger(maxHeavyInFlight) || maxHeavyInFlight < 1) { throw new RangeError("maxHeavyInFlight must be a positive integer"); @@ -208,6 +259,7 @@ export class ChatAdmissionController { if (!Number.isSafeInteger(healthyHeadroom) || healthyHeadroom < 0) { throw new RangeError("healthyHeadroom must be a non-negative integer"); } + this.#onShed = onShed; } get activeHeavy(): number { @@ -264,6 +316,37 @@ export class ChatAdmissionController { return out; } + /** Total structural sheds since process start (#11244). */ + get shedTotal(): number { + return this.#shedTotal; + } + + /** Structural sheds by reason since process start (#11244). */ + get shedsByReason(): Record { + const out: Record = {}; + for (const [reason, count] of this.#shedsByReason) out[reason] = count; + return out; + } + + /** + * Record one structural shed (503 chat_admission_busy) and notify the shed sink + * (#11244). Called internally at every capacity-driven give-up point in + * `acquireHeavyWithin`; public so the aggregate snapshot wiring and tests can + * exercise the same single path. `lane` is the opaque fairness key (HMAC + * fingerprint), never a raw credential. + */ + recordShed(reason: ChatAdmissionShedReason, lane = "default"): void { + this.#shedTotal += 1; + this.#shedsByReason.set(reason, (this.#shedsByReason.get(reason) ?? 0) + 1); + this.#onShed({ + reason, + activeHeavy: this.#activeHeavy, + waiting: this.waitingCount, + queuedBytes: this.#queuedBytes, + lane, + }); + } + tryAcquireHeavy(): ChatAdmissionLease | null { if (this.#activeHeavy >= this.maxHeavyInFlight) return null; this.#activeHeavy += 1; @@ -318,9 +401,16 @@ export class ChatAdmissionController { const lease = this.tryAcquireHeavy(); if (lease) return lease; const remaining = deadline - Date.now(); - if (remaining <= 0) return null; + if (remaining <= 0) { + // Wait window exhausted (or queueMs=0 immediate reject) with capacity still + // busy — the caller answers the retryable 503. Count it (#11244). + this.recordShed("queue_timeout", sessionKey); + return null; + } // Heap valve: refuse to park when the queued-bytes budget is exhausted. if (queuedBytes > 0 && this.#queuedBytes + queuedBytes > this.maxQueuedBytes) { + // Same retryable 503, distinct cause: the wait itself would amplify the heap. + this.recordShed("queued_bytes_budget", sessionKey); return null; } this.#queuedBytes += queuedBytes; @@ -367,7 +457,13 @@ export class ChatAdmissionController { // Cancel the deadline timer when abort/release wins; a fired timer is a no-op. if (deadlineTimer) clearTimeout(deadlineTimer); if (onAbort) signal?.removeEventListener("abort", onAbort); - if (timedOut) return null; + if (timedOut) { + // The deadline timer won the race: a genuine shed. When the client ABORT + // won instead (signal aborted while parked), capacity was never denied — + // the 503 is dropped on the dead connection, so it is not counted (#11244). + if (!signal?.aborted) this.recordShed("queue_timeout", sessionKey); + return null; + } } } @@ -483,11 +579,18 @@ export class PerConnectionAdmissionController { constructor( readonly maxHeavyInFlight = 1, - // Deprecated pre-#10110 lane-eviction knobs: accepted for API - // compatibility and ignored — there are no per-session lanes to evict. - _opts?: { maxSessions?: number; sessionTtlMs?: number } + // `maxSessions`/`sessionTtlMs` are deprecated pre-#10110 lane-eviction knobs: + // accepted for API compatibility and ignored — there are no per-session lanes + // to evict. `onShed` (#11244) is live: it replaces the shed sink of the shared + // controller (tests inject a capture/no-op sink; production keeps the pino warn). + _opts?: { maxSessions?: number; sessionTtlMs?: number; onShed?: ChatAdmissionShedSink } ) { - this.#controller = new ChatAdmissionController(maxHeavyInFlight); + this.#controller = new ChatAdmissionController( + maxHeavyInFlight, + undefined, + undefined, + _opts?.onShed + ); } /** Returns the process-global budget — the same instance for every session. */ @@ -497,20 +600,26 @@ export class PerConnectionAdmissionController { /** * Process-wide aggregate snapshot for observability: global totals plus - * per-key waiter depths. Keys are opaque scheduler keys, never raw - * credentials. + * per-key waiter depths and the #11244 shed history (total + per reason). + * Keys are opaque scheduler keys, never raw credentials. */ snapshot(): { activeHeavy: number; + activeHealthyHeadroom: number; queuedBytes: number; waiting: number; lanes: ReadonlyArray<{ key: string; waiting: number }>; + shedTotal: number; + shedsByReason: Record; } { return { activeHeavy: this.#controller.activeHeavy, + activeHealthyHeadroom: this.#controller.activeHealthyHeadroom, queuedBytes: this.#controller.queuedBytes, waiting: this.#controller.waitingCount, lanes: this.#controller.waitersByKey, + shedTotal: this.#controller.shedTotal, + shedsByReason: this.#controller.shedsByReason, }; } diff --git a/tests/unit/chat-admission-visibility-11244.test.ts b/tests/unit/chat-admission-visibility-11244.test.ts new file mode 100644 index 0000000000..663d0943f2 --- /dev/null +++ b/tests/unit/chat-admission-visibility-11244.test.ts @@ -0,0 +1,238 @@ +// #11244: visibility for the STRUCTURAL chat admission gate +// (src/shared/middleware/chatBodyAdmission.ts — the bounded heavyweight lease + +// healthy-headroom path from #10110/#10437, NOT the adaptive shadow-mode layer in +// open-sse/services/admission/). The 503 chat_admission_busy shed returns BEFORE +// request logging, so today a shed is invisible: no counter, no log line, and the +// process-wide snapshot (PerConnectionAdmissionController.snapshot()) reports only +// live state (activeHeavy/queuedBytes/waiting/lanes) with no shed history. +// +// These tests pin the observability contract WITHOUT changing admission behavior: +// (a) every structural shed (503 chat_admission_busy) increments an in-memory +// counter — total + per reason ("queue_timeout" when the bounded wait expires, +// "queued_bytes_budget" when the queued-bytes heap valve refuses to park) — +// while a client abort mid-wait is NOT a shed (capacity was never denied); +// (b) the process-wide snapshot exposes shedTotal + shedsByReason next to the +// existing live fields; +// (c) each shed emits exactly one structured pino warn carrying +// reason/activeHeavy/waiting and the HMAC session fingerprint — never the raw +// API key (resolveSessionId already fingerprints the credential). +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +// Configure the shared pino logger BEFORE importing the admission module — the +// logger builds its transports at import time (see logger-redaction-wiring.test.ts +// for the same pattern). JSON to a temp file keeps test (c)'s capture deterministic. +const logDir = mkdtempSync(join(tmpdir(), "omniroute-admission-11244-")); +const logFile = join(logDir, "app.log"); +process.env.NODE_ENV = "production"; +process.env.APP_LOG_TO_FILE = "true"; +process.env.APP_LOG_FILE_PATH = logFile; + +const { + ChatAdmissionController, + PerConnectionAdmissionController, + perConnectionAdmissionController, + admitChatStructure, + resolveSessionId, +} = await import("../../src/shared/middleware/chatBodyAdmission.ts"); + +function heavyBody() { + return { + messages: Array.from({ length: 200 }, () => ({ role: "user", content: "x".repeat(40) })), + tools: [] as unknown[], + }; +} + +const heapHealthy = () => false; // "not under pressure" — the healthy-heap fast path +const heapPressured = () => true; // forces the bounded-wait/shed path deterministically +const silentSink = () => {}; // keep non-logging tests off the pino transport + +test("#11244 (a): a structural shed after the bounded wait increments shedTotal and shedsByReason", async () => { + // Primary lease (1) + bounded healthy-headroom (1): two concurrent heavy requests + // admit on a healthy heap; the third must wait queueMs and then shed with a 503. + const controller = new ChatAdmissionController(1, undefined, 1, silentSink); + + const first = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapHealthy, + queueMs: 0, + }); + const second = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapHealthy, + queueMs: 0, + }); + assert.equal(first.admit, true, "first heavy request takes the primary lease"); + assert.equal(second.admit, true, "second heavy request takes the bounded headroom lease"); + assert.equal(controller.shedTotal, 0, "admitted requests never count as sheds"); + assert.deepEqual(controller.shedsByReason, {}); + + const shed = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapHealthy, + queueMs: 50, + }); + assert.equal(shed.admit, false, "third heavy request must shed once both budgets are busy"); + if (!shed.admit) { + assert.equal(shed.response.status, 503); + const payload = await shed.response.json(); + assert.equal(payload.error.code, "chat_admission_busy"); + } + + assert.equal( + controller.shedTotal, + 1, + "the shed must be counted even though it skips request logging" + ); + assert.deepEqual( + controller.shedsByReason, + { queue_timeout: 1 }, + "a bounded wait that expires with no freed capacity is a queue_timeout shed" + ); + + // Counters are history, not live state: releasing the leases must not rewind them. + if (first.admit) first.lease?.release(); + if (second.admit) second.lease?.release(); + assert.equal(controller.shedTotal, 1, "shed history survives lease release"); + + // And a subsequently admitted request must not be counted. + const fourth = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapHealthy, + queueMs: 0, + }); + assert.equal(fourth.admit, true); + assert.equal(controller.shedTotal, 1); + if (fourth.admit) fourth.lease?.release(); +}); + +test("#11244 (a2): the queued-bytes heap valve rejection is counted with its own reason", async () => { + // maxQueuedBytes smaller than the conservative 256KB structural wait weight: the + // valve refuses to park and the shed must be distinguishable from a queue timeout. + const controller = new ChatAdmissionController(1, 1024, 0, silentSink); + + const first = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapPressured, + queueMs: 0, + }); + assert.equal(first.admit, true); + + const shed = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapPressured, + queueMs: 1000, + }); + assert.equal(shed.admit, false); + if (!shed.admit) assert.equal(shed.response.status, 503); + assert.equal(controller.shedTotal, 1); + assert.deepEqual(controller.shedsByReason, { queued_bytes_budget: 1 }); + + if (first.admit) first.lease?.release(); +}); + +test("#11244 (a3): a client abort while parked is not a shed — capacity was never denied", async () => { + const controller = new ChatAdmissionController(1, undefined, 0, silentSink); + + const first = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapPressured, + queueMs: 0, + }); + assert.equal(first.admit, true); + + const abort = new AbortController(); + const pending = admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapPressured, + queueMs: 5000, + signal: abort.signal, + }); + setTimeout(() => abort.abort(), 20); + const result = await pending; + assert.equal( + result.admit, + false, + "the caller still answers a (dropped) 503 on the dead connection" + ); + assert.equal(controller.shedTotal, 0, "an aborted wait frees capacity instead of shedding"); + assert.deepEqual(controller.shedsByReason, {}); + + if (first.admit) first.lease?.release(); +}); + +test("#11244 (b): the process-wide snapshot exposes shed counters next to the live fields", async () => { + const empty = perConnectionAdmissionController.snapshot(); + assert.equal(typeof empty.activeHeavy, "number"); + assert.equal(typeof empty.queuedBytes, "number"); + assert.equal(typeof empty.waiting, "number"); + assert.ok(Array.isArray(empty.lanes)); + assert.equal( + empty.shedTotal, + 0, + "no shed happened through the production singleton in this process" + ); + assert.deepEqual(empty.shedsByReason, {}); + + // A shed recorded through a session's controller surfaces in the aggregate snapshot. + const pc = new PerConnectionAdmissionController(1, { onShed: silentSink }); + const controller = pc.getController("key_visibility11244"); + controller.recordShed("queue_timeout", "key_visibility11244"); + controller.recordShed("queue_timeout", "key_visibility11244"); + controller.recordShed("queued_bytes_budget", "key_visibility11244"); + + const snap = pc.snapshot(); + assert.equal(snap.shedTotal, 3); + assert.deepEqual(snap.shedsByReason, { queue_timeout: 2, queued_bytes_budget: 1 }); +}); + +test("#11244 (c): each shed logs one structured warn with the session fingerprint, never the raw key", async () => { + const rawKey = "visRAWSECRETtoken11244xyz"; // matches no logRedaction pattern — a leak would show verbatim + const fingerprint = resolveSessionId( + new Request("http://localhost/v1/chat/completions", { + headers: { authorization: `Bearer ${rawKey}` }, + }) + ); + assert.ok(fingerprint.startsWith("key_"), "resolveSessionId returns the HMAC fingerprint"); + assert.ok(!fingerprint.includes(rawKey)); + + // Default sink (no injected onShed): the shed must go through the shared pino logger. + const controller = new ChatAdmissionController(1); + const primary = controller.tryAcquireHeavy(); + assert.ok(primary); + + const shed = await admitChatStructure(heavyBody(), null, { + controller, + heapPressureCheck: heapPressured, + queueMs: 25, + sessionId: fingerprint, + }); + assert.equal(shed.admit, false); + primary.release(); + + // Poll the worker-thread-written log file until the shed line lands. + const deadline = Date.now() + 4000; + let contents = ""; + while (Date.now() < deadline) { + if (existsSync(logFile)) { + contents = readFileSync(logFile, "utf8"); + if (contents.includes("chat_admission_busy")) break; + } + await new Promise((resolve) => setTimeout(resolve, 50)); + } + + assert.ok(contents.includes("chat_admission_busy"), "the shed log line names the rejection code"); + assert.match( + contents, + /"level":(40|"warn")/, + "sheds log at warn level (numeric 40 when the file transport strips the level formatter)" + ); + assert.ok(contents.includes('"module":"chat-admission"'), "the log is scoped to the gate"); + assert.ok(contents.includes('"reason":"queue_timeout"'), "the shed reason is structured"); + assert.ok(contents.includes('"activeHeavy":1'), "live state travels with the log line"); + assert.ok(contents.includes(fingerprint), "the lane fingerprint allows per-key correlation"); + assert.ok(!contents.includes(rawKey), "the raw API key must never reach the shed log"); +}); diff --git a/tests/unit/observability-payloads.test.ts b/tests/unit/observability-payloads.test.ts index 297d92d6ec..5cb9241b28 100644 --- a/tests/unit/observability-payloads.test.ts +++ b/tests/unit/observability-payloads.test.ts @@ -6,6 +6,7 @@ import { buildSessionsSummary, buildTelemetryPayload, projectAdaptiveAdmissionSummary, + projectChatAdmissionSummary, } from "../../src/lib/monitoring/observability.ts"; test("buildSessionsSummary returns sticky counts and ordered top sessions", () => { @@ -336,3 +337,71 @@ test("buildHealthPayload projects allowlisted adaptiveAdmission aggregates only" assert.equal(projectAdaptiveAdmissionSummary(null), null); assert.equal(projectAdaptiveAdmissionSummary(undefined), null); }); + +// #11244: the STRUCTURAL chat-admission gate (chatBodyAdmission.ts) must surface in +// the health payload next to — never instead of — the adaptive snapshot, with only +// the documented low-cardinality fields projected. +test("buildHealthPayload projects allowlisted structural chatAdmission fields only", () => { + const snapshot = { + activeHeavy: 1, + activeHealthyHeadroom: 1, + waiting: 2, + queuedBytes: 524_288, + shedTotal: 3, + shedsByReason: { queue_timeout: 2, queued_bytes_budget: 1 }, + lanes: [ + { key: "key_c49d1c242feda590", waiting: 1 }, + { key: "anonymous", waiting: 1 }, + ], + // Extra keys that must never leak into the public payload. + internalController: { secret: "controller-state" }, + rawAuthorization: "Bearer raw-SHOULD-NOT-LEAK", + } as unknown as import("../../src/lib/monitoring/observability.ts").ChatAdmissionSnapshot; + + const payload = buildHealthPayload({ + appVersion: "9.9.9", + settings: { setupComplete: false }, + connections: [], + circuitBreakers: [], + rateLimitStatus: {}, + learnedLimits: {}, + lockouts: {}, + localProviders: {}, + inflightRequests: 0, + quotaMonitorSummary: { + active: 0, + alerting: 0, + exhausted: 0, + errors: 0, + statusCounts: { starting: 0, idle: 0, healthy: 0, warning: 0, exhausted: 0, error: 0 }, + byProvider: {}, + }, + quotaMonitorMonitors: [], + activeSessions: [], + chatAdmission: snapshot, + }); + + assert.deepEqual(payload.chatAdmission, { + activeHeavy: 1, + activeHealthyHeadroom: 1, + waiting: 2, + queuedBytes: 524_288, + shedTotal: 3, + shedsByReason: { queue_timeout: 2, queued_bytes_budget: 1 }, + lanes: [ + { key: "key_c49d1c242feda590", waiting: 1 }, + { key: "anonymous", waiting: 1 }, + ], + }); + // The adaptive projection is untouched by the new key. + assert.equal(payload.adaptiveAdmission, null); + + const json = JSON.stringify(payload); + assert.equal(json.includes("controller-state"), false); + assert.equal(json.includes("raw-SHOULD-NOT-LEAK"), false); + assert.equal(json.includes("internalController"), false); + + // Absent / null snapshot projects to null (degraded path parity). + assert.equal(projectChatAdmissionSummary(null), null); + assert.equal(projectChatAdmissionSummary(undefined), null); +}); From 00c80fd14ab5af1e0e018bcc5bd295a70e0cc482 Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:38:57 +0200 Subject: [PATCH 18/20] feat(models): surface learned reasoning_effort sets in catalog, variants, and dispatch (#11252) Validated on the combined 12-PR batch board + the resolved merge against the post-#11232 tip: focused suites 73/73 (learned-reasoning-effort-caps, synced-capabilities-learned-effort-override, synced-effort-suffix-learned-validation, effort-tiers-loop-catalog-e2e, reasoning-effort-clamp-and-retry, reasoning-effort-learned-capability) + opencode-plugin effort-tier-variants 4/4, typecheck:core clean, gates within baseline. The stacked-branch conflict after #11232 squash-landed was resolved by hand (the learned-caps module keeps both the Set API and the new model-scoped lookup). The effort_tiers loop is closed end-to-end: catalog advertises exactly what the upstream accepts, and - suffix variants resolve against the learned set. Thank you @maxmad64bis! --- @omniroute/opencode-plugin/package.json | 2 +- @omniroute/opencode-plugin/src/index.ts | 20 +++- .../tests/effort-tier-variants.test.ts | 62 ++++++++++ .../effort-tiers-loop-learned-sets.md | 1 + .../services/learnedReasoningEffortCaps.ts | 32 ++++- open-sse/utils/syncedEffortVariants.ts | 3 +- src/app/api/v1/models/catalog.ts | 14 ++- src/app/api/v1/models/syncedCapabilities.ts | 49 ++++++-- src/sse/services/model.ts | 33 +++++- .../effort-tiers-loop-catalog-e2e.test.ts | 112 ++++++++++++++++++ .../learned-reasoning-effort-caps.test.ts | 62 +++++++++- ...pabilities-learned-effort-override.test.ts | 98 +++++++++++++++ ...d-effort-suffix-learned-validation.test.ts | 80 +++++++++++++ 13 files changed, 541 insertions(+), 27 deletions(-) create mode 100644 @omniroute/opencode-plugin/tests/effort-tier-variants.test.ts create mode 100644 changelog.d/features/effort-tiers-loop-learned-sets.md create mode 100644 tests/unit/effort-tiers-loop-catalog-e2e.test.ts create mode 100644 tests/unit/synced-capabilities-learned-effort-override.test.ts create mode 100644 tests/unit/synced-effort-suffix-learned-validation.test.ts diff --git a/@omniroute/opencode-plugin/package.json b/@omniroute/opencode-plugin/package.json index f096226d79..fa373a83b6 100644 --- a/@omniroute/opencode-plugin/package.json +++ b/@omniroute/opencode-plugin/package.json @@ -23,7 +23,7 @@ "scripts": { "build": "tsup", "clean": "rm -rf dist", - "test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/feature-defaults.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts tests/model-allowlist.test.ts tests/log-level.test.ts", + "test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/feature-defaults.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts tests/model-allowlist.test.ts tests/log-level.test.ts tests/effort-tier-variants.test.ts", "prepublishOnly": "npm run clean && npm run build && npm test" }, "keywords": [ diff --git a/@omniroute/opencode-plugin/src/index.ts b/@omniroute/opencode-plugin/src/index.ts index be985361c9..50768e9351 100644 --- a/@omniroute/opencode-plugin/src/index.ts +++ b/@omniroute/opencode-plugin/src/index.ts @@ -1161,6 +1161,8 @@ export interface OmniRouteRawModelEntry { attachment?: boolean; structured_output?: boolean; temperature?: boolean; + /** Runtime-learned or synced reasoning tiers (server-gated, blind-mapped). */ + effort_tiers?: string[]; }; release_date?: string; last_updated?: string; @@ -1302,6 +1304,18 @@ export function mapRawModelToModelV2( ctx: { providerId: string; baseURL: string; apiFormat?: { anthropicPrefixes?: string[] } } ): ModelV2 { const caps = raw.capabilities ?? {}; + // effort_tiers loop: server-declared tiers become ModelV2 variants so the + // UI offers exactly the tiers OmniRoute vouches for (instead of opencode's + // invented [low, medium, high] fallback). Blind: filtering/exclusion rules + // live server-side. Absent/empty/malformed => key omitted ENTIRELY (an + // empty variants object would suppress opencode's fallback for this model). + const declaredTiers = Array.isArray(caps.effort_tiers) + ? caps.effort_tiers.filter((t): t is string => typeof t === "string" && t.length > 0) + : []; + const variants = + declaredTiers.length > 0 + ? Object.fromEntries(declaredTiers.map((tier) => [tier, { reasoningEffort: tier }])) + : undefined; const inMods = new Set(raw.input_modalities ?? ["text"]); const outMods = new Set(raw.output_modalities ?? ["text"]); @@ -1315,10 +1329,7 @@ export function mapRawModelToModelV2( // OpenCode looks up `-m /` as model id `` under // the plugin provider (#10345). Other bare ids still prefix with // `providerId` so credentials resolve as `(omniroute, model)`. - id: - raw.id.includes("/") || raw.owned_by === "combo" - ? raw.id - : `${ctx.providerId}/${raw.id}`, + id: raw.id.includes("/") || raw.owned_by === "combo" ? raw.id : `${ctx.providerId}/${raw.id}`, /** * Display name. Falls back to raw.id when no enrichment is available; * the caller (`createOmniRouteProviderHook`) overlays @@ -1357,6 +1368,7 @@ export function mapRawModelToModelV2( ...(typeof raw.max_input_tokens === "number" ? { input: raw.max_input_tokens } : {}), output: typeof raw.max_output_tokens === "number" ? raw.max_output_tokens : 0, }, + ...(variants ? { variants } : {}), status: "active", options: {}, headers: {}, diff --git a/@omniroute/opencode-plugin/tests/effort-tier-variants.test.ts b/@omniroute/opencode-plugin/tests/effort-tier-variants.test.ts new file mode 100644 index 0000000000..2127ea8109 --- /dev/null +++ b/@omniroute/opencode-plugin/tests/effort-tier-variants.test.ts @@ -0,0 +1,62 @@ +/** + * effort_tiers loop — plugin maps server-declared tiers to ModelV2 variants. + * Blind mapping (I3): no owned_by/provider knowledge here — the SERVER gates + * eligibility (shouldExposeSyncedEffortVariants). Absence semantics (M3): + * no tiers => NO variants key at all (an empty object would also kill + * opencode's own fallback for non-tiered models). + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mapRawModelToModelV2, type OmniRouteRawModelEntry } from "../src/index.js"; + +const CTX = { providerId: "omniroute", baseURL: "http://127.0.0.1:20128" } as const; + +test("maps declared tiers to reasoningEffort variants", () => { + const raw: OmniRouteRawModelEntry = { + id: "oc/x-preview-f-free", + owned_by: "opencode", + capabilities: { reasoning: true, effort_tiers: ["low", "high", "max"] }, + }; + const model = mapRawModelToModelV2(raw, { ...CTX }); + const variants = (model as unknown as Record).variants as + Record> | undefined; + assert.ok(variants, "variants key present when tiers declared"); + assert.deepEqual(Object.keys(variants).sort(), ["high", "low", "max"]); + assert.deepEqual(variants.max, { reasoningEffort: "max" }); + assert.deepEqual(variants.low, { reasoningEffort: "low" }); +}); + +test("no tiers => NO variants key (not an empty object)", () => { + const raw: OmniRouteRawModelEntry = { + id: "plain-model", + capabilities: { reasoning: true }, + }; + const model = mapRawModelToModelV2(raw, { ...CTX }) as unknown as Record; + assert.equal("variants" in model, false); +}); + +test("empty or malformed tiers array => NO variants key", () => { + const empty = mapRawModelToModelV2( + { id: "m", capabilities: { effort_tiers: [] } }, + { ...CTX } + ) as unknown as Record; + assert.equal("variants" in empty, false); + + const junk = mapRawModelToModelV2( + { id: "m", capabilities: { effort_tiers: [42, null, "ok"] as unknown as string[] } }, + { ...CTX } + ) as unknown as Record; + const variants = junk.variants as Record> | undefined; + assert.deepEqual(Object.keys(variants ?? {}), ["ok"], "non-string tokens dropped"); +}); + +test("static registry entry WITH tiers also gets variants (N1 blast radius)", () => { + const raw: OmniRouteRawModelEntry = { + id: "some-static-model", + owned_by: "registry", + capabilities: { effort_tiers: ["minimal", "high"] }, + }; + const model = mapRawModelToModelV2(raw, { ...CTX }) as unknown as Record; + const variants = model.variants as Record> | undefined; + assert.deepEqual(Object.keys(variants ?? {}).sort(), ["high", "minimal"]); +}); diff --git a/changelog.d/features/effort-tiers-loop-learned-sets.md b/changelog.d/features/effort-tiers-loop-learned-sets.md new file mode 100644 index 0000000000..29b5b10ec6 --- /dev/null +++ b/changelog.d/features/effort-tiers-loop-learned-sets.md @@ -0,0 +1 @@ +- **feat(catalog):** surface runtime-learned `reasoning_effort` tiers in `/v1/models` `capabilities.effort_tiers` (learned set replaces synced metadata when present), map them to OpenCode `ModelV2.variants` in the OmniRoute plugin, and align dispatch `-` suffix validation to the effective (learned ?? synced) set — so the UI offers exactly the tiers the upstream accepts (e.g. `{low, high, max}` for `oc/x-preview-f-free`) and each advertised variant completes. Excludes codex/glm/kimi, which keep their own dedicated `-{effort}` suffix mechanism and never gain `effort_tiers` from this path (related to #7694, builds on #11232) diff --git a/open-sse/services/learnedReasoningEffortCaps.ts b/open-sse/services/learnedReasoningEffortCaps.ts index ef7e7b5f32..5ed852765c 100644 --- a/open-sse/services/learnedReasoningEffortCaps.ts +++ b/open-sse/services/learnedReasoningEffortCaps.ts @@ -63,6 +63,30 @@ export function getLearnedReasoningEffort( return v ? new Set(v) : null; } +/** + * Model-scoped lookup bridging the key-space gap between executors and the + * catalog: executors record under their CONNECTION id + * (`openai-compatible-chat-:`, cf. compatibleProviderId.ts), + * while the catalog loops on provider ids (`opencode`, …) — an exact + * `${provider}:${model}` lookup would always miss. Scans by model segment + * instead. Multiple connections teaching different sets for the same model + * name intersect (most restrictive proven set wins — conservative across + * connections sharing one catalog entry). + */ +export function getLearnedReasoningEffortForModel( + model: string | null | undefined +): Set | null { + const m = typeof model === "string" ? model.trim().toLowerCase() : ""; + if (!m || learnedCaps.size === 0) return null; + let result: Set | null = null; + for (const [key, value] of learnedCaps) { + const colon = key.indexOf(":"); + if (colon === -1 || key.slice(colon + 1) !== m) continue; + result = result ? new Set([...result].filter((v) => value.has(v))) : new Set(value); + } + return result && result.size > 0 ? result : null; +} + /** * Record that `acceptedValues` is the enum the upstream advertised for * provider+model, and store the accepted set. Returns the stored set, or null @@ -85,7 +109,13 @@ export function recordLearnedReasoningEffort( const lowered = typeof raw === "string" ? raw.trim().toLowerCase() : ""; if (lowered && REASONING_EFFORT_ORDER.includes(lowered)) newSet.add(lowered); } - if (newSet.size === 0) return null; + if (newSet.size === 0) { + // OBS2/M5: a 4xx advertised an enum we cannot map — say so, never learn silently. + console.warn( + `[learnedReasoningEffortCaps] unrecognized reasoning_effort enum for ${key}: ${acceptedValues.join(", ")} — nothing learned` + ); + return null; + } const existing = learnedCaps.get(key); if (existing !== undefined) { diff --git a/open-sse/utils/syncedEffortVariants.ts b/open-sse/utils/syncedEffortVariants.ts index 33c5ec8c56..213128cb38 100644 --- a/open-sse/utils/syncedEffortVariants.ts +++ b/open-sse/utils/syncedEffortVariants.ts @@ -33,7 +33,8 @@ export const SYNCED_EFFORT_SKIP_PROVIDERS = new Set(["codex", "glm", "glm-cn", " /** Provider-id prefixes covering that mechanism's multiple connection variants (kimi-coding, kimi-coding-apikey). */ const SYNCED_EFFORT_SKIP_PROVIDER_PREFIXES = ["kimi"]; -function isSkippedEffortProvider(ownedBy: string): boolean { +/** Whether `ownedBy` already owns its own `-{effort}` suffix mechanism (never synthesize/expose another). */ +export function isSkippedEffortProvider(ownedBy: string): boolean { return ( SYNCED_EFFORT_SKIP_PROVIDERS.has(ownedBy) || SYNCED_EFFORT_SKIP_PROVIDER_PREFIXES.some((prefix) => ownedBy.startsWith(prefix)) diff --git a/src/app/api/v1/models/catalog.ts b/src/app/api/v1/models/catalog.ts index c56a543d56..9a2252d9ee 100644 --- a/src/app/api/v1/models/catalog.ts +++ b/src/app/api/v1/models/catalog.ts @@ -1120,6 +1120,9 @@ async function buildUnifiedModelsResponseCore( else if (endpoints.includes("rerank")) modelType = "rerank"; else if (endpoints.includes("images")) modelType = "image"; else if (endpoints.includes("audio")) modelType = "audio"; + // Same owned_by the alias/canonical entries below will carry — computed once + // so the effort_tiers exclusion (codex/glm/kimi) and the entries agree. + const syncedOwnedBy = resolvePublicOwnerId(providerId, canonicalProviderId); const syncedFields = { ...(modelType ? { type: modelType } : {}), ...(apiFormat !== "chat-completions" ? { api_format: apiFormat } : {}), @@ -1133,12 +1136,19 @@ async function buildUnifiedModelsResponseCore( : {}), // #4264/#7694: vision + reasoning-effort-tier flags captured at sync time, // merged into a single capabilities object (see ./syncedCapabilities.ts). - ...(buildSyncedCapabilities(sm) ? { capabilities: buildSyncedCapabilities(sm) } : {}), + // ownedBy gates effort_tiers off for codex/glm/kimi (own suffix mechanism). + ...(buildSyncedCapabilities(sm, syncedOwnedBy) + ? { capabilities: buildSyncedCapabilities(sm, syncedOwnedBy) } + : {}), }; const existingAliasModel = models.find((model) => model.id === aliasId); if (existingAliasModel) { - const mergedCapabilities = mergeSyncedCapabilities(existingAliasModel.capabilities, sm); + const mergedCapabilities = mergeSyncedCapabilities( + existingAliasModel.capabilities, + sm, + syncedOwnedBy + ); Object.assign(existingAliasModel, syncedFields); if (mergedCapabilities) existingAliasModel.capabilities = mergedCapabilities; continue; diff --git a/src/app/api/v1/models/syncedCapabilities.ts b/src/app/api/v1/models/syncedCapabilities.ts index a42671b331..bf82f0f980 100644 --- a/src/app/api/v1/models/syncedCapabilities.ts +++ b/src/app/api/v1/models/syncedCapabilities.ts @@ -5,26 +5,54 @@ * to keep the vision (#4264) and reasoning-effort-tier (#7694) flags merged into a SINGLE * `capabilities` object rather than two separate spreads that would silently overwrite one * another via object-spread order. A model can be both vision- and reasoning-capable. + * + * effort_tiers loop (2026-08-23): a runtime-learned accepted set (#11232, + * learnedReasoningEffortCaps) REPLACES the synced `supportedThinkingEfforts` + * when one exists — the proven contract beats the advertised one. Lookup is + * model-scoped: executors record under connection ids while this module sees + * provider ids, so an exact provider:model key would always miss. + * + * Exclusion gate: `ownedBy` is REQUIRED and checked against + * `isSkippedEffortProvider` (codex/glm/kimi — providers that already own a + * conflicting `-{effort}` suffix mechanism, see syncedEffortVariants.ts, #7694). + * Without this, the blind opencode-plugin mapping (`capabilities.effort_tiers` + * -> ModelV2 `variants`) would double-handle those providers' native suffix + * ids. `shouldExposeSyncedEffortVariants` gates only the *synthetic* + * `-` catalog entries (open-sse/utils/syncedEffortVariants.ts) — it + * never runs over the base entry's `capabilities`, so it cannot substitute + * for this check. Required (not optional) so no call site can silently skip it. */ +// Use the same canonical alias as catalogModelPolicy.ts (l.1) — a relative path from +// src/app/api/v1/models/ to open-sse/ would need 5 `../` and silently breaks under +// refactors. (Confirmed convention: grep "from \"@omniroute/open-sse" src/app/api/v1/models/) +import { getLearnedReasoningEffortForModel } from "@omniroute/open-sse/services/learnedReasoningEffortCaps.ts"; +import { isSkippedEffortProvider } from "@omniroute/open-sse/utils/syncedEffortVariants.ts"; interface SyncedCapabilityFlags { + id?: string; supportsVision?: boolean; supportedThinkingEfforts?: string[]; } -function hasEffortTiers(sm: SyncedCapabilityFlags): boolean { - return Array.isArray(sm.supportedThinkingEfforts) && sm.supportedThinkingEfforts.length > 0; +function effectiveEffortTiers(sm: SyncedCapabilityFlags, ownedBy: string): string[] | undefined { + if (isSkippedEffortProvider(ownedBy)) return undefined; + const learned = sm.id ? getLearnedReasoningEffortForModel(sm.id) : null; + if (learned) return [...learned]; + return Array.isArray(sm.supportedThinkingEfforts) && sm.supportedThinkingEfforts.length > 0 + ? sm.supportedThinkingEfforts + : undefined; } /** Build the `capabilities` object for a fresh synced-model catalog entry, or `undefined` when neither flag applies. */ export function buildSyncedCapabilities( - sm: SyncedCapabilityFlags + sm: SyncedCapabilityFlags, + ownedBy: string ): Record | undefined { - const effortTiers = hasEffortTiers(sm); - if (!sm.supportsVision && !effortTiers) return undefined; + const tiers = effectiveEffortTiers(sm, ownedBy); + if (!sm.supportsVision && !tiers) return undefined; return { ...(sm.supportsVision ? { vision: true } : {}), - ...(effortTiers ? { effort_tiers: sm.supportedThinkingEfforts! } : {}), + ...(tiers ? { effort_tiers: tiers } : {}), }; } @@ -35,13 +63,14 @@ export function buildSyncedCapabilities( */ export function mergeSyncedCapabilities( existing: Record | undefined, - sm: SyncedCapabilityFlags + sm: SyncedCapabilityFlags, + ownedBy: string ): Record | undefined { - const effortTiers = hasEffortTiers(sm); - if (!sm.supportsVision && !effortTiers && !existing) return undefined; + const tiers = effectiveEffortTiers(sm, ownedBy); + if (!sm.supportsVision && !tiers && !existing) return undefined; return { ...(existing || {}), ...(sm.supportsVision ? { vision: true } : {}), - ...(effortTiers ? { effort_tiers: sm.supportedThinkingEfforts! } : {}), + ...(tiers ? { effort_tiers: tiers } : {}), }; } diff --git a/src/sse/services/model.ts b/src/sse/services/model.ts index 12179740e3..f39404df60 100644 --- a/src/sse/services/model.ts +++ b/src/sse/services/model.ts @@ -16,6 +16,7 @@ import { splitSyncedEffortSuffix, stripContextWindowSuffix, } from "@omniroute/open-sse/services/model.ts"; +import { getLearnedReasoningEffortForModel } from "@omniroute/open-sse/services/learnedReasoningEffortCaps.ts"; import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts"; import { getRegisteredProviderEffortBaseModelId } from "@omniroute/open-sse/utils/registeredEffortVariants.ts"; @@ -124,6 +125,20 @@ function isSyncedEffortSkippedProvider(providerId: string): boolean { return SYNCED_EFFORT_SKIP_PROVIDER_PREFIXES.some((prefix) => providerId.startsWith(prefix)); } +/** + * C1: effective tier set for suffix validation = learned ?? sync. The catalog + * advertises variants from the learned set; validating the suffix against raw + * synced metadata would strand learned-only tiers (dead-on-arrival ids). + */ +function effectiveKnownEfforts( + modelId: string, + syncedEfforts: readonly string[] | null | undefined +): string[] { + const learned = getLearnedReasoningEffortForModel(modelId); + if (learned) return [...learned]; + return Array.isArray(syncedEfforts) ? [...syncedEfforts] : []; +} + /** Resolve a suffix against an explicitly tiered static registry model. */ function resolveRegistryModelIdAndEffort( providerId: string, @@ -139,7 +154,10 @@ function resolveRegistryModelIdAndEffort( for (const candidate of registryModels) { if (!Array.isArray(candidate?.supportedThinkingEfforts)) continue; - const attempt = splitSyncedEffortSuffix(modelId, candidate.supportedThinkingEfforts); + const attempt = splitSyncedEffortSuffix( + modelId, + effectiveKnownEfforts(candidate.id, candidate.supportedThinkingEfforts) + ); if (attempt.effort && attempt.baseModel === candidate.id) { return { modelId: attempt.baseModel, effort: attempt.effort }; } @@ -183,7 +201,7 @@ function resolveSyncedModelIdAndEffort( } const attempt = splitSyncedEffortSuffix( modelId, - candidate.supportedThinkingEfforts as string[] + effectiveKnownEfforts(candidate.id, candidate.supportedThinkingEfforts as string[]) ); if (attempt.effort && attempt.baseModel === candidate.id) { return { modelId: attempt.baseModel, effort: attempt.effort }; @@ -232,7 +250,9 @@ function resolveRuntimeFormats( ): RuntimeModelMeta { const apiFormat = (typeof customMatch?.apiFormat === "string" ? customMatch.apiFormat : undefined) || - (typeof compatOverrideMatch?.apiFormat === "string" ? compatOverrideMatch.apiFormat : undefined) || + (typeof compatOverrideMatch?.apiFormat === "string" + ? compatOverrideMatch.apiFormat + : undefined) || (syncedMatch?.apiFormat === "responses" ? "responses" : undefined); const targetFormat = typeof customMatch?.targetFormat === "string" @@ -359,7 +379,12 @@ async function lookupModelMeta( const available = !liveCatalog.authoritative || Boolean(customMatch || syncedMatch || liveBackedEffortVariant); - const metadata = buildRuntimeModelMeta(customMatch, syncedMatch, registryMatch, compatOverrideMatch); + const metadata = buildRuntimeModelMeta( + customMatch, + syncedMatch, + registryMatch, + compatOverrideMatch + ); if (effort) metadata.resolvedThinkingEffort = effort; return { modelId: resolvedModelId, metadata, available }; diff --git a/tests/unit/effort-tiers-loop-catalog-e2e.test.ts b/tests/unit/effort-tiers-loop-catalog-e2e.test.ts new file mode 100644 index 0000000000..f2fe6f89c6 --- /dev/null +++ b/tests/unit/effort-tiers-loop-catalog-e2e.test.ts @@ -0,0 +1,112 @@ +/** + * effort_tiers loop — I1 end-to-end proof: a set recorded through the REAL + * record path (executor-style connection key) surfaces in the REAL catalog + * response (/api/v1/models), including the learned-only variant entry. + * Never "fix" this test by injecting the same string on both sides. + */ +import { test, after, beforeEach } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-effort-loop-e2e-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "loop-e2e-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelsDb = await import("../../src/lib/db/models.ts"); +const v1ModelsCatalog = await import("../../src/app/api/v1/models/catalog.ts"); +const { recordLearnedReasoningEffort, __test_resetLearnedReasoningEffortCaps } = + await import("../../open-sse/services/learnedReasoningEffortCaps.ts"); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +// Copied verbatim from sync-reasoning-supported-efforts-7694.test.ts +async function seedProviderConnection(provider: string) { + return providersDb.createProviderConnection({ + provider, + authType: "apikey", + name: `${provider}-${Math.random().toString(16).slice(2, 8)}`, + apiKey: `${provider}-key`, + isActive: true, + testStatus: "active", + }); +} + +test.beforeEach(async () => { + __test_resetLearnedReasoningEffortCaps(); + await resetStorage(); +}); + +test.after(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("learned set flows end-to-end into /v1/models capabilities and variant entries", async () => { + // Non-namespaced id on purpose: mirrors the real incident model + // (x-preview-f-free) where sm.id === the executor-visible post-strip id. + const MODEL_ID = "loop-model-e2e"; + const connection = await seedProviderConnection("huggingface"); + await modelsDb.replaceSyncedAvailableModelsForConnection("huggingface", connection.id, [ + { + id: MODEL_ID, + name: "Loop Model E2E", + supportedThinkingEfforts: ["none", "low", "medium", "high"], + }, + ]); + + // Simulate the real 400 learning path (base.ts calls exactly this, with the + // executor's CONNECTION id as provider key): + recordLearnedReasoningEffort("openai-compatible-chat-eaff6869", MODEL_ID, ["low", "high", "max"]); + + const response = await v1ModelsCatalog.getUnifiedModelsResponse( + new Request("http://localhost/api/v1/models") + ); + assert.equal(response.status, 200); + const body = (await response.json()) as { + data: Array<{ id: string; capabilities?: { effort_tiers?: string[] } }>; + }; + + const baseEntry = body.data.find((m) => m.id.endsWith(MODEL_ID)); + assert.ok(baseEntry, "base entry present"); + assert.deepEqual(baseEntry!.capabilities?.effort_tiers, ["low", "high", "max"]); + + const maxVariant = body.data.find((m) => m.id === `${baseEntry!.id}-max`); + assert.ok(maxVariant, "learned-only tier synthesized as a variant entry"); +}); + +test("excluded provider (glm) never surfaces effort_tiers, learned or synced", async () => { + const MODEL_ID = "glm-4-flash"; + const connection = await seedProviderConnection("glm"); + await modelsDb.replaceSyncedAvailableModelsForConnection("glm", connection.id, [ + { + id: MODEL_ID, + name: "GLM 4 Flash", + supportedThinkingEfforts: ["none", "low", "medium", "high"], + }, + ]); + recordLearnedReasoningEffort("glm-connection-1", MODEL_ID, ["low", "high"]); + + const response = await v1ModelsCatalog.getUnifiedModelsResponse( + new Request("http://localhost/api/v1/models") + ); + assert.equal(response.status, 200); + const body = (await response.json()) as { + data: Array<{ id: string; capabilities?: { effort_tiers?: string[] } }>; + }; + + const baseEntry = body.data.find((m) => m.id.endsWith(MODEL_ID)); + assert.ok(baseEntry, "base entry present"); + assert.equal( + baseEntry!.capabilities?.effort_tiers, + undefined, + "glm owns its own -{effort} suffix mechanism — the catalog must not also expose effort_tiers" + ); +}); diff --git a/tests/unit/learned-reasoning-effort-caps.test.ts b/tests/unit/learned-reasoning-effort-caps.test.ts index c98eb8fad0..c057351814 100644 --- a/tests/unit/learned-reasoning-effort-caps.test.ts +++ b/tests/unit/learned-reasoning-effort-caps.test.ts @@ -91,7 +91,12 @@ test("records the highest recognized value from the accepted list", () => { ]) as unknown as Set; assert.ok(learned instanceof Set); assert.ok(learned.has("high")); - assert.equal((getLearnedReasoningEffort("ovh", "qwen3-coder-30b-a3b-instruct") as unknown as Set).has("high"), true); + assert.equal( + ( + getLearnedReasoningEffort("ovh", "qwen3-coder-30b-a3b-instruct") as unknown as Set + ).has("high"), + true + ); }); test("returns null and stores nothing when acceptedValues has no recognized token", () => { @@ -116,7 +121,10 @@ test("monotonic decrease: a later, higher accepted-list never ratchets the cap b test("a later, lower accepted-list does ratchet the cap down", () => { recordLearnedReasoningEffort("acme", "model-x", ["none", "low", "medium", "high"]); - const learned = recordLearnedReasoningEffort("acme", "model-x", ["none", "low"]) as unknown as Set; + const learned = recordLearnedReasoningEffort("acme", "model-x", [ + "none", + "low", + ]) as unknown as Set; assert.equal(learned.size, 2); assert.ok(learned.has("low")); assert.equal((getLearnedReasoningEffort("acme", "model-x") as unknown as Set).size, 2); @@ -177,8 +185,12 @@ test("getLearnedReasoningEffort returns null for unknown provider+model", () => test("getLearnedReasoningEffort is keyed case-insensitively on provider+model", () => { recordLearnedReasoningEffort("OVH", "Qwen3-Coder-30B", ["none", "high"]); - assert.ok((getLearnedReasoningEffort("ovh", "qwen3-coder-30b") as unknown as Set).has("high")); - assert.ok((getLearnedReasoningEffort("OVH", "QWEN3-CODER-30B") as unknown as Set).has("high")); + assert.ok( + (getLearnedReasoningEffort("ovh", "qwen3-coder-30b") as unknown as Set).has("high") + ); + assert.ok( + (getLearnedReasoningEffort("OVH", "QWEN3-CODER-30B") as unknown as Set).has("high") + ); }); test("different providers for the same model id have independent caps", () => { @@ -194,3 +206,45 @@ test("handles empty/null provider or model gracefully", () => { assert.equal(recordLearnedReasoningEffort("", "m", ["high"]), null); assert.equal(recordLearnedReasoningEffort("p", "", ["high"]), null); }); + +// ── getLearnedReasoningEffortForModel ──────────────────────────────────────── + +import { getLearnedReasoningEffortForModel } from "../../open-sse/services/learnedReasoningEffortCaps.ts"; + +test("getLearnedReasoningEffortForModel finds a set recorded under any provider key", () => { + recordLearnedReasoningEffort("openai-compatible-chat-eaff6869", "X-Preview-F-Free", [ + "low", + "high", + "max", + ]); + const set = getLearnedReasoningEffortForModel("x-preview-f-free"); + assert.ok(set); + assert.deepEqual([...set].sort(), ["high", "low", "max"]); +}); + +test("getLearnedReasoningEffortForModel intersects when multiple providers disagree", () => { + recordLearnedReasoningEffort("conn-a", "shared-model", ["low", "high", "max"]); + recordLearnedReasoningEffort("conn-b", "shared-model", ["low"]); + const set = getLearnedReasoningEffortForModel("shared-model"); + assert.ok(set); + assert.deepEqual([...set], ["low"]); +}); + +test("getLearnedReasoningEffortForModel returns null when nothing learned or empty model", () => { + assert.equal(getLearnedReasoningEffortForModel("never-learned"), null); + assert.equal(getLearnedReasoningEffortForModel(""), null); + assert.equal(getLearnedReasoningEffortForModel(undefined), null); +}); + +test("recordLearnedReasoningEffort warns when every token is unrecognized", () => { + const warnings: string[] = []; + const orig = console.warn; + console.warn = (msg: string) => warnings.push(msg); + try { + const result = recordLearnedReasoningEffort("p", "m", ["bogus-one", "bogus-two"]); + assert.equal(result, null); + assert.ok(warnings.some((w) => w.includes("reasoning_effort") && w.includes("bogus-one"))); + } finally { + console.warn = orig; + } +}); diff --git a/tests/unit/synced-capabilities-learned-effort-override.test.ts b/tests/unit/synced-capabilities-learned-effort-override.test.ts new file mode 100644 index 0000000000..f33d08dc56 --- /dev/null +++ b/tests/unit/synced-capabilities-learned-effort-override.test.ts @@ -0,0 +1,98 @@ +/** + * effort_tiers loop — learned set overrides synced metadata in catalog + * capabilities (design 2026-08-23, decisions: appris > sync, in-memory). + * Records go through the REAL record path (executor-style connection keys) + * then read back through the catalog builders — proves the key-space bridge, + * unlike a unit injection of the same string on both sides. + */ +import { test, after, beforeEach } from "node:test"; +import assert from "node:assert/strict"; +import { + recordLearnedReasoningEffort, + __test_resetLearnedReasoningEffortCaps, +} from "../../open-sse/services/learnedReasoningEffortCaps.ts"; +import { + buildSyncedCapabilities, + mergeSyncedCapabilities, +} from "../../src/app/api/v1/models/syncedCapabilities.ts"; + +beforeEach(() => __test_resetLearnedReasoningEffortCaps()); +after(() => __test_resetLearnedReasoningEffortCaps()); + +const SYNC_TIERS = ["none", "low", "medium", "high", "xhigh"]; + +test("learned set replaces synced effort_tiers", () => { + recordLearnedReasoningEffort("openai-compatible-chat-eaff6869", "x-preview-f-free", [ + "low", + "high", + "max", + ]); + const caps = buildSyncedCapabilities( + { id: "x-preview-f-free", supportedThinkingEfforts: SYNC_TIERS }, + "huggingface" + ); + assert.deepEqual(caps?.effort_tiers, ["low", "high", "max"]); +}); + +test("nothing learned keeps synced metadata untouched", () => { + const caps = buildSyncedCapabilities( + { id: "some-synced-model", supportedThinkingEfforts: SYNC_TIERS }, + "huggingface" + ); + assert.deepEqual(caps?.effort_tiers, SYNC_TIERS); +}); + +test("neither learned nor synced yields undefined", () => { + const caps = buildSyncedCapabilities({ id: "plain-model" }, "huggingface"); + assert.equal(caps, undefined); +}); + +test("merge path keeps vision AND applies the learned override", () => { + recordLearnedReasoningEffort("conn-a", "vision-model", ["low", "max"]); + const merged = mergeSyncedCapabilities( + { tool_calling: true }, + { id: "vision-model", supportsVision: true, supportedThinkingEfforts: SYNC_TIERS }, + "huggingface" + ); + assert.equal(merged?.vision, true); + assert.equal(merged?.tool_calling, true); + assert.deepEqual(merged?.effort_tiers, ["low", "max"]); +}); + +// Exclusion gate (#7694): codex/glm/kimi already own a conflicting +// `-{effort}` suffix mechanism — the blind opencode-plugin mapping must never +// see effort_tiers for them, learned or synced, or it double-handles the suffix. +for (const ownedBy of ["codex", "glm", "glm-cn", "glmt", "kimi", "kimi-coding-apikey"]) { + test(`build: excluded provider "${ownedBy}" never gets effort_tiers (synced)`, () => { + const caps = buildSyncedCapabilities( + { id: "excluded-model", supportedThinkingEfforts: SYNC_TIERS }, + ownedBy + ); + assert.equal(caps?.effort_tiers, undefined); + }); + + test(`build: excluded provider "${ownedBy}" never gets effort_tiers (learned)`, () => { + recordLearnedReasoningEffort(`conn-${ownedBy}`, "excluded-model", ["low", "max"]); + const caps = buildSyncedCapabilities( + { id: "excluded-model", supportedThinkingEfforts: SYNC_TIERS }, + ownedBy + ); + assert.equal(caps?.effort_tiers, undefined); + }); +} + +test("excluded provider still gets vision through buildSyncedCapabilities", () => { + const caps = buildSyncedCapabilities({ id: "codex-vision-model", supportsVision: true }, "codex"); + assert.deepEqual(caps, { vision: true }); +}); + +test("merge path also excludes codex/glm/kimi from effort_tiers", () => { + recordLearnedReasoningEffort("conn-glm", "glm-model", ["low", "max"]); + const merged = mergeSyncedCapabilities( + { tool_calling: true }, + { id: "glm-model", supportsVision: true, supportedThinkingEfforts: SYNC_TIERS }, + "glm" + ); + assert.equal(merged?.vision, true); + assert.equal(merged?.effort_tiers, undefined); +}); diff --git a/tests/unit/synced-effort-suffix-learned-validation.test.ts b/tests/unit/synced-effort-suffix-learned-validation.test.ts new file mode 100644 index 0000000000..a06ff2d479 --- /dev/null +++ b/tests/unit/synced-effort-suffix-learned-validation.test.ts @@ -0,0 +1,80 @@ +/** + * C1 — the `-` suffix resolver validates against the EFFECTIVE tier set + * (learned ?? sync), not raw synced metadata. Without this, the catalog + * advertises /-max (learned set) but dispatch refuses to strip + * `-max` because sync metadata lacks the tier — dead-on-arrival variant. + * Harness mirrors deepseek-thinking-efforts.test.ts (custom provider + + * persistDiscoveredModels + async getModelInfo). + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-c1-effort-dispatch-")); +process.env.DATA_DIR = TEST_DATA_DIR; +process.env.API_KEY_SECRET = process.env.API_KEY_SECRET || "c1-test-secret"; + +const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); +const modelDiscovery = await import("../../src/lib/providerModels/modelDiscovery.ts"); +const { getModelInfo } = await import("../../src/sse/services/model.ts"); +const { recordLearnedReasoningEffort, __test_resetLearnedReasoningEffortCaps } = + await import("@omniroute/open-sse/services/learnedReasoningEffortCaps.ts"); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +const PROVIDER = "c1prov"; +const MODEL_ID = "c1-model"; + +async function seed() { + const connection = await providersDb.createProviderConnection({ + provider: PROVIDER, + authType: "apikey", + name: "c1-runtime-efforts", + apiKey: `${PROVIDER}-key`, + isActive: true, + testStatus: "active", + }); + // Sync tiers deliberately EXCLUDE max — only the learned set will vouch for it. + await modelDiscovery.persistDiscoveredModels(PROVIDER, connection.id, [ + { id: MODEL_ID, reasoning: { supported_efforts: ["none", "low", "medium", "high"] } }, + ]); +} + +test.beforeEach(async () => { + __test_resetLearnedReasoningEffortCaps(); + await resetStorage(); + await seed(); +}); + +test.after(async () => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("-max resolves once the learned set advertises it (sync metadata does not)", async () => { + // Real record path, executor-style CONNECTION key — NOT the provider alias. + recordLearnedReasoningEffort("openai-compatible-chat-eaff6869", MODEL_ID, ["low", "high", "max"]); + const info = await getModelInfo(`${PROVIDER}/${MODEL_ID}-max`); + assert.equal(info.provider, PROVIDER); + assert.equal(info.model, MODEL_ID); + assert.equal(info.resolvedThinkingEffort, "max"); +}); + +test("-medium still resolves via sync tiers even before anything is learned", async () => { + const info = await getModelInfo(`${PROVIDER}/${MODEL_ID}-medium`); + assert.equal(info.model, MODEL_ID); + assert.equal(info.resolvedThinkingEffort, "medium"); +}); + +test("a tier neither learned nor synced is left untouched (literal id)", async () => { + recordLearnedReasoningEffort("conn-a", MODEL_ID, ["low"]); + const info = await getModelInfo(`${PROVIDER}/${MODEL_ID}-ultra`); + assert.equal(info.resolvedThinkingEffort, undefined); +}); From d077e884562808f72dcf1cdef858015f5b1aee6d Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Sun, 23 Aug 2026 14:40:12 -0300 Subject: [PATCH 19/20] fix(providers): complete Hack Club AI removal from shared catalog (#11176) (#11262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(providers): complete Hack Club AI removal from shared catalog (#11176) PR #11118/#11123 removed hackclub from the open-sse REGISTRY but the canonical shared catalog kept the entry, so the dashboard, alias resolver, icon registry, onboarding i18n strings and the generated provider reference kept advertising a provider the router no longer serves. Removed: - APIKEY_PROVIDERS hackclub entry (id + "hc" alias) in apikey/gateways.ts - hackclub from ProviderIcon KNOWN_SVGS + public/providers/hackclub.svg asset - providers.onboardingProviderDescriptions.hackclub from all 43 locales - stale comments referencing hackclub as a living provider (web-cookie.ts, registry/huggingchat, registry/g4f-groq, registry/freetheai, test prose) Count cascade 351 -> 350 (measured per-file; the live catalog union across all 11 provider collections goes 351 -> 350): - README.md, AGENTS.md, llm.txt + 42 i18n llm.txt mirrors (byte-identical bodies, docs-sync gate green), package.json description - docs/reference/PROVIDER_REFERENCE.md regenerated (gen-provider-reference) - canonical numbers in readme-hero/promise-pillars/comparison-table/ cli-terminal SVGs (docs-counts-sync STRICT gate green) Kept intentionally (historical records, not catalog): - CHANGELOG.md + docs/i18n/*/CHANGELOG.md entries from when the provider was added (#2339, #2611) — release history - src/lib/db/migrations/162_remove_hackclub_provider.sql — the removal migration itself - tests/unit/remove-hackclub-11118.test.ts — the REGISTRY removal guard Validation: new tests/unit/hackclub-removed.test.ts (5 asserts: catalog absence, hc alias free, structural grep over provider sources, icon/asset gone, i18n key gone) fails before / passes after; provider sibling tests 46 pass; provider-catalog consumer batch 268 pass; typecheck:core clean; eslint clean on touched files; check:provider-consistency OK (350 canonical); check:docs-counts-sync + check:docs-sync green. Closes #11176 * test(providers): align APIKEY split count after hackclub removal (232 -> 231) (#11176) --------- Co-authored-by: Xiangzhe --- AGENTS.md | 2 +- README.md | 8 +- docs/diagrams/cli-terminal.svg | 2 +- docs/diagrams/comparison-table.svg | 2 +- docs/diagrams/promise-pillars.svg | 6 +- docs/diagrams/readme-hero.svg | 4 +- docs/i18n/ar/llm.txt | 4 +- docs/i18n/az/llm.txt | 4 +- docs/i18n/bg/llm.txt | 4 +- docs/i18n/bn/llm.txt | 4 +- docs/i18n/cs/llm.txt | 4 +- docs/i18n/da/llm.txt | 4 +- docs/i18n/de/llm.txt | 4 +- docs/i18n/es/llm.txt | 4 +- docs/i18n/fa/llm.txt | 4 +- docs/i18n/fi/llm.txt | 4 +- docs/i18n/fr/llm.txt | 4 +- docs/i18n/gu/llm.txt | 4 +- docs/i18n/he/llm.txt | 4 +- docs/i18n/hi/llm.txt | 4 +- docs/i18n/hu/llm.txt | 4 +- docs/i18n/id/llm.txt | 4 +- docs/i18n/in/llm.txt | 4 +- docs/i18n/it/llm.txt | 4 +- docs/i18n/ja/llm.txt | 4 +- docs/i18n/ko/llm.txt | 4 +- docs/i18n/mr/llm.txt | 4 +- docs/i18n/ms/llm.txt | 4 +- docs/i18n/nl/llm.txt | 4 +- docs/i18n/no/llm.txt | 4 +- docs/i18n/phi/llm.txt | 4 +- docs/i18n/pl/llm.txt | 4 +- docs/i18n/pt-BR/llm.txt | 4 +- docs/i18n/pt/llm.txt | 4 +- docs/i18n/ro/llm.txt | 4 +- docs/i18n/ru/llm.txt | 4 +- docs/i18n/sk/llm.txt | 4 +- docs/i18n/sv/llm.txt | 4 +- docs/i18n/sw/llm.txt | 4 +- docs/i18n/ta/llm.txt | 4 +- docs/i18n/te/llm.txt | 4 +- docs/i18n/th/llm.txt | 4 +- docs/i18n/tr/llm.txt | 4 +- docs/i18n/uk-UA/llm.txt | 4 +- docs/i18n/ur/llm.txt | 4 +- docs/i18n/vi/llm.txt | 4 +- docs/i18n/zh-CN/llm.txt | 4 +- docs/i18n/zh-TW/llm.txt | 4 +- docs/reference/PROVIDER_REFERENCE.md | 5 +- llm.txt | 4 +- .../providers/registry/freetheai/index.ts | 2 +- .../providers/registry/g4f-groq/index.ts | 2 +- .../providers/registry/huggingchat/index.ts | 4 +- package.json | 2 +- public/providers/hackclub.svg | 5 - src/i18n/messages/ar.json | 1 - src/i18n/messages/az.json | 1 - src/i18n/messages/bg.json | 1 - src/i18n/messages/bn.json | 1 - src/i18n/messages/cs.json | 1 - src/i18n/messages/da.json | 1 - src/i18n/messages/de.json | 1 - src/i18n/messages/en.json | 1 - src/i18n/messages/es.json | 1 - src/i18n/messages/fa.json | 1 - src/i18n/messages/fi.json | 1 - src/i18n/messages/fr.json | 1 - src/i18n/messages/gu.json | 1 - src/i18n/messages/he.json | 1 - src/i18n/messages/hi.json | 1 - src/i18n/messages/hu.json | 1 - src/i18n/messages/id.json | 1 - src/i18n/messages/in.json | 1 - src/i18n/messages/it.json | 1 - src/i18n/messages/ja.json | 1 - src/i18n/messages/ko.json | 1 - src/i18n/messages/mr.json | 1 - src/i18n/messages/ms.json | 1 - src/i18n/messages/nl.json | 1 - src/i18n/messages/no.json | 1 - src/i18n/messages/phi.json | 1 - src/i18n/messages/pl.json | 1 - src/i18n/messages/pt-BR.json | 1 - src/i18n/messages/pt.json | 1 - src/i18n/messages/ro.json | 1 - src/i18n/messages/ru.json | 1 - src/i18n/messages/sk.json | 1 - src/i18n/messages/sv.json | 1 - src/i18n/messages/sw.json | 1 - src/i18n/messages/ta.json | 1 - src/i18n/messages/te.json | 1 - src/i18n/messages/th.json | 1 - src/i18n/messages/tr.json | 1 - src/i18n/messages/uk-UA.json | 1 - src/i18n/messages/ur.json | 1 - src/i18n/messages/vi.json | 1 - src/i18n/messages/zh-CN.json | 1 - src/i18n/messages/zh-TW.json | 1 - src/shared/components/ProviderIcon.tsx | 1 - .../constants/providers/apikey/gateways.ts | 13 -- src/shared/constants/providers/web-cookie.ts | 3 +- tests/unit/g4f-space-gateway-6650.test.ts | 4 +- tests/unit/hackclub-removed.test.ts | 139 ++++++++++++++++++ tests/unit/provider-alias-uniqueness.test.ts | 3 +- .../unit/provider-registry-freetheai.test.ts | 4 +- tests/unit/providers-constants-split.test.ts | 11 +- tests/unit/providers-g4f-batch3.test.ts | 2 +- 107 files changed, 259 insertions(+), 180 deletions(-) delete mode 100644 public/providers/hackclub.svg create mode 100644 tests/unit/hackclub-removed.test.ts diff --git a/AGENTS.md b/AGENTS.md index 046f0a292f..34e60ac16d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below. ## Project at a Glance -**OmniRoute** — unified AI proxy/router. One endpoint, 351 LLM providers, auto-fallback. +**OmniRoute** — unified AI proxy/router. One endpoint, 350 LLM providers, auto-fallback. | Layer | Location | Purpose | | ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/README.md b/README.md index 502ee4676c..373c1ef558 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # 🚀 OmniRoute — The Free AI Gateway -OmniRoute — Never stop coding. Every AI tool → 351 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 351 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start. +OmniRoute — Never stop coding. Every AI tool → 350 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot & Antigravity into FREE Claude / GPT / Gemini with auto-fallback. RTK + Caveman stacked compression saves 15–95% tokens (~89% avg) — never hit limits. 350 AI providers · 90+ free tiers · ~1.51B free tokens/mo · 19 routing strategies · $0 to start.
@@ -210,7 +210,7 @@ curl http://localhost:20128/v1/chat/completions \ -The Promise — One endpoint. 351 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 351 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 15–95%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals — 25,000+ tests). +The Promise — One endpoint. 350 providers. Never stop building — OmniRoute picks the cheapest one that works. Six pillars: Never hit limits (auto-fallback across 350 providers in milliseconds, zero downtime) · Save up to 95% tokens (RTK + Caveman stacked compression cuts 15–95%, ~89% avg on tool-heavy sessions) · $0 to start (90+ free tiers, 56 free forever — no card needed) · Every tool works (33 coding agents through one config) · One endpoint (OpenAI ↔ Claude ↔ Gemini ↔ Responses API at /v1) · Production-grade (circuit breakers, TLS stealth, MCP 110 tools, A2A, memory, guardrails, evals — 25,000+ tests).

@@ -461,7 +461,7 @@ All **19** strategies — mix & match per combo step: -What sets OmniRoute apart — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 351 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project's docs. +What sets OmniRoute apart — comparison table vs 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute: 350 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, built-in MCP server with 110 tools, A2A agent protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, Desktop/Termux/PWA, 43 i18n UI locales, 100% MIT self-hosted. OmniRoute is the only one with the full set; competitors show a mix of checks, partials and crosses. Verified from each project's docs. 📊 Full methodology & per-feature detail vs 9router, OpenRouter, CLIProxyAPI & LiteLLM → [`docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md`](docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md) @@ -646,7 +646,7 @@ of your shell history. → [CLI Integrations](docs/guides/CLI-INTEGRATIONS.md) -> The most complete catalog of any open-source router: **351 providers**, **90+ with a free tier**, **56 free forever**. +> The most complete catalog of any open-source router: **350 providers**, **90+ with a free tier**, **56 free forever**.
diff --git a/docs/diagrams/cli-terminal.svg b/docs/diagrams/cli-terminal.svg index 1fb1dc4bd8..3a8d056e5c 100644 --- a/docs/diagrams/cli-terminal.svg +++ b/docs/diagrams/cli-terminal.svg @@ -1,4 +1,4 @@ - + Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen. diff --git a/docs/diagrams/comparison-table.svg b/docs/diagrams/comparison-table.svg index 76d891950f..24018c7fed 100644 --- a/docs/diagrams/comparison-table.svg +++ b/docs/diagrams/comparison-table.svg @@ -1,4 +1,4 @@ - + Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses. diff --git a/docs/diagrams/promise-pillars.svg b/docs/diagrams/promise-pillars.svg index a868a0279f..f0d30f74a3 100644 --- a/docs/diagrams/promise-pillars.svg +++ b/docs/diagrams/promise-pillars.svg @@ -1,4 +1,4 @@ - + Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle. @@ -21,7 +21,7 @@ - One endpoint. 351 providers. Never stop building — OmniRoute picks the cheapest one that works. + One endpoint. 350 providers. Never stop building — OmniRoute picks the cheapest one that works. @@ -38,7 +38,7 @@ Never hit limits - Auto-fallback across 351 providers in + Auto-fallback across 350 providers in milliseconds. Quota out? The next provider takes over — zero downtime. diff --git a/docs/diagrams/readme-hero.svg b/docs/diagrams/readme-hero.svg index feb4bd9da8..e3faa34758 100644 --- a/docs/diagrams/readme-hero.svg +++ b/docs/diagrams/readme-hero.svg @@ -1,4 +1,4 @@ - + Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame. @@ -28,7 +28,7 @@ Never stop coding. - Every AI tool → 351 providers90+ free — through one endpoint. + Every AI tool → 350 providers90+ free — through one endpoint. Claude Code · Codex · Cursor · Cline · Copilot · Antigravity  →  FREE Claude / GPT / Gemini · auto-fallback diff --git a/docs/i18n/ar/llm.txt b/docs/i18n/ar/llm.txt index c74af91c83..d808b999f8 100644 --- a/docs/i18n/ar/llm.txt +++ b/docs/i18n/ar/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/az/llm.txt b/docs/i18n/az/llm.txt index 5553f5982a..1a868b5c60 100644 --- a/docs/i18n/az/llm.txt +++ b/docs/i18n/az/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/bg/llm.txt b/docs/i18n/bg/llm.txt index 5553f5982a..1a868b5c60 100644 --- a/docs/i18n/bg/llm.txt +++ b/docs/i18n/bg/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/bn/llm.txt b/docs/i18n/bn/llm.txt index a5aa4f9a78..8ab68736ef 100644 --- a/docs/i18n/bn/llm.txt +++ b/docs/i18n/bn/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/cs/llm.txt b/docs/i18n/cs/llm.txt index a31daee4a1..0d74bb4482 100644 --- a/docs/i18n/cs/llm.txt +++ b/docs/i18n/cs/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/da/llm.txt b/docs/i18n/da/llm.txt index 1fbc44a151..c5265e9be3 100644 --- a/docs/i18n/da/llm.txt +++ b/docs/i18n/da/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/de/llm.txt b/docs/i18n/de/llm.txt index ab5420c5fe..2aedeaa9f7 100644 --- a/docs/i18n/de/llm.txt +++ b/docs/i18n/de/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/es/llm.txt b/docs/i18n/es/llm.txt index 337686004a..44a1a12781 100644 --- a/docs/i18n/es/llm.txt +++ b/docs/i18n/es/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/fa/llm.txt b/docs/i18n/fa/llm.txt index 17c9028618..6bb6906d53 100644 --- a/docs/i18n/fa/llm.txt +++ b/docs/i18n/fa/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/fi/llm.txt b/docs/i18n/fi/llm.txt index 3626fdbebc..c866207a48 100644 --- a/docs/i18n/fi/llm.txt +++ b/docs/i18n/fi/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/fr/llm.txt b/docs/i18n/fr/llm.txt index 5c101d7298..d7c00c7f60 100644 --- a/docs/i18n/fr/llm.txt +++ b/docs/i18n/fr/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/gu/llm.txt b/docs/i18n/gu/llm.txt index 00fcf8c24c..82307592c6 100644 --- a/docs/i18n/gu/llm.txt +++ b/docs/i18n/gu/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/he/llm.txt b/docs/i18n/he/llm.txt index 2a72799680..79c7155494 100644 --- a/docs/i18n/he/llm.txt +++ b/docs/i18n/he/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/hi/llm.txt b/docs/i18n/hi/llm.txt index 282b4bcb4a..554cb918b7 100644 --- a/docs/i18n/hi/llm.txt +++ b/docs/i18n/hi/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/hu/llm.txt b/docs/i18n/hu/llm.txt index a5762ebf8d..ec244518d9 100644 --- a/docs/i18n/hu/llm.txt +++ b/docs/i18n/hu/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/id/llm.txt b/docs/i18n/id/llm.txt index e2e70d444b..e38b51c992 100644 --- a/docs/i18n/id/llm.txt +++ b/docs/i18n/id/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/in/llm.txt b/docs/i18n/in/llm.txt index 89463a9728..3a4090d47a 100644 --- a/docs/i18n/in/llm.txt +++ b/docs/i18n/in/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/it/llm.txt b/docs/i18n/it/llm.txt index 8d3348fbf6..4cb65487fe 100644 --- a/docs/i18n/it/llm.txt +++ b/docs/i18n/it/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ja/llm.txt b/docs/i18n/ja/llm.txt index 81dba51c93..d115f0c317 100644 --- a/docs/i18n/ja/llm.txt +++ b/docs/i18n/ja/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ko/llm.txt b/docs/i18n/ko/llm.txt index dcb618f649..e56525c28d 100644 --- a/docs/i18n/ko/llm.txt +++ b/docs/i18n/ko/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/mr/llm.txt b/docs/i18n/mr/llm.txt index 792ad76470..cbc729864f 100644 --- a/docs/i18n/mr/llm.txt +++ b/docs/i18n/mr/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ms/llm.txt b/docs/i18n/ms/llm.txt index 8d8414c6f1..1840e8e763 100644 --- a/docs/i18n/ms/llm.txt +++ b/docs/i18n/ms/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/nl/llm.txt b/docs/i18n/nl/llm.txt index b3e3425144..4d7d2e6bb8 100644 --- a/docs/i18n/nl/llm.txt +++ b/docs/i18n/nl/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/no/llm.txt b/docs/i18n/no/llm.txt index 96fcb45971..fb1b2f64ae 100644 --- a/docs/i18n/no/llm.txt +++ b/docs/i18n/no/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/phi/llm.txt b/docs/i18n/phi/llm.txt index b9e231632d..07e949eb9b 100644 --- a/docs/i18n/phi/llm.txt +++ b/docs/i18n/phi/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/pl/llm.txt b/docs/i18n/pl/llm.txt index 61e88c8843..06caf81a28 100644 --- a/docs/i18n/pl/llm.txt +++ b/docs/i18n/pl/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/pt-BR/llm.txt b/docs/i18n/pt-BR/llm.txt index 5c339e3722..aa04decbe7 100644 --- a/docs/i18n/pt-BR/llm.txt +++ b/docs/i18n/pt-BR/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/pt/llm.txt b/docs/i18n/pt/llm.txt index af75e24713..25e3602087 100644 --- a/docs/i18n/pt/llm.txt +++ b/docs/i18n/pt/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ro/llm.txt b/docs/i18n/ro/llm.txt index 045770f0f6..bac029ee6d 100644 --- a/docs/i18n/ro/llm.txt +++ b/docs/i18n/ro/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ru/llm.txt b/docs/i18n/ru/llm.txt index aeaf1e4264..e0daa73d49 100644 --- a/docs/i18n/ru/llm.txt +++ b/docs/i18n/ru/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/sk/llm.txt b/docs/i18n/sk/llm.txt index 87bd8f286f..9440f1427e 100644 --- a/docs/i18n/sk/llm.txt +++ b/docs/i18n/sk/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/sv/llm.txt b/docs/i18n/sv/llm.txt index 496a06f5fb..5fe7f96126 100644 --- a/docs/i18n/sv/llm.txt +++ b/docs/i18n/sv/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/sw/llm.txt b/docs/i18n/sw/llm.txt index c56ca32fcc..244c29b4ef 100644 --- a/docs/i18n/sw/llm.txt +++ b/docs/i18n/sw/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ta/llm.txt b/docs/i18n/ta/llm.txt index 8f8324c0c5..7f84e1b192 100644 --- a/docs/i18n/ta/llm.txt +++ b/docs/i18n/ta/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/te/llm.txt b/docs/i18n/te/llm.txt index f6030e7c15..a85c227bce 100644 --- a/docs/i18n/te/llm.txt +++ b/docs/i18n/te/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/th/llm.txt b/docs/i18n/th/llm.txt index 5408406439..d0adfc7a57 100644 --- a/docs/i18n/th/llm.txt +++ b/docs/i18n/th/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/tr/llm.txt b/docs/i18n/tr/llm.txt index c0882db779..1b08ddc2fe 100644 --- a/docs/i18n/tr/llm.txt +++ b/docs/i18n/tr/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/uk-UA/llm.txt b/docs/i18n/uk-UA/llm.txt index f6bf8197a2..29030ea7df 100644 --- a/docs/i18n/uk-UA/llm.txt +++ b/docs/i18n/uk-UA/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/ur/llm.txt b/docs/i18n/ur/llm.txt index d639f34d79..95d9fb2228 100644 --- a/docs/i18n/ur/llm.txt +++ b/docs/i18n/ur/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/vi/llm.txt b/docs/i18n/vi/llm.txt index 2ddf81e084..c35f8cab70 100644 --- a/docs/i18n/vi/llm.txt +++ b/docs/i18n/vi/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/zh-CN/llm.txt b/docs/i18n/zh-CN/llm.txt index d88d42c243..b9539db6e7 100644 --- a/docs/i18n/zh-CN/llm.txt +++ b/docs/i18n/zh-CN/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/i18n/zh-TW/llm.txt b/docs/i18n/zh-TW/llm.txt index 817a818a16..abf7d405fe 100644 --- a/docs/i18n/zh-TW/llm.txt +++ b/docs/i18n/zh-TW/llm.txt @@ -4,7 +4,7 @@ --- -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/docs/reference/PROVIDER_REFERENCE.md b/docs/reference/PROVIDER_REFERENCE.md index be5d0baf59..731d53d83f 100644 --- a/docs/reference/PROVIDER_REFERENCE.md +++ b/docs/reference/PROVIDER_REFERENCE.md @@ -10,7 +10,7 @@ lastUpdated: 2026-08-23 > Regenerate with: `npm run gen:provider-reference` > **Last generated:** 2026-08-23 -Total providers: **351**. See category breakdown below. +Total providers: **350**. See category breakdown below. ## Categories @@ -122,7 +122,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `zai-web` | `zw` | Z.ai Web | Web cookie | [link](https://chat.z.ai) | Copy the "token" value from chat.z.ai → DevTools → Application → Local Storage. Do not copy cookies; OmniRoute handles the per-request CAPTCHA through its browser transport. | — | | `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — | -## API Key Providers (paid / paid-with-free-credits) (232) +## API Key Providers (paid / paid-with-free-credits) (231) | ID | Alias | Name | Tags | Website | Notes | |----|-------|------|------|---------|-------| @@ -215,7 +215,6 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each | `glm-cn` | `glmcn` | GLM Coding (China) | API key | [link](https://open.bigmodel.cn) | — | | `glmt` | `glmt` | GLM Thinking | API key | [link](https://open.bigmodel.cn) | — | | `groq` | `groq` | Groq | API key | [link](https://groq.com) | Free tier: 30 RPM / 14.4K RPD — no credit card | -| `hackclub` | `hc` | Hackclub AI | API key | [link](https://ai.hackclub.com) | Sign in with your Hack Club account at ai.hackclub.com. | | `haiper` | `hp` | Haiper | API key, video | [link](https://haiper.ai) | Get API key at haiper.ai/haiper-api | | `hcnsec` | `hcnsec` | Huancheng Public API | API key | [link](https://api.hcnsec.cn) | Get API key at api.hcnsec.cn | | `helixmind` | `helixmind` | HelixMind | API key, aggregator | [link](https://helixmind.online) | Previously circulated 3 RPM/50 RPD and no-card claims were not confirmed during the 2026-08-02 audit; current quota and billing require account verification. | diff --git a/llm.txt b/llm.txt index 3facf67014..9c5259a17b 100644 --- a/llm.txt +++ b/llm.txt @@ -1,6 +1,6 @@ # OmniRoute -> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 351 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. +> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 350 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app. ## Overview @@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo ## Key Features (v3.8.50) ### Core Proxy -- **351 AI providers** with automatic format translation +- **350 AI providers** with automatic format translation - **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible) - **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline - **4-tier fallback**: Subscription → API Key → Cheap → Free diff --git a/open-sse/config/providers/registry/freetheai/index.ts b/open-sse/config/providers/registry/freetheai/index.ts index 10c9033dcd..b0b3911e89 100644 --- a/open-sse/config/providers/registry/freetheai/index.ts +++ b/open-sse/config/providers/registry/freetheai/index.ts @@ -1,7 +1,7 @@ import type { RegistryEntry } from "../../shared.ts"; // FreeTheAi — OpenAI-compatible gateway with a Discord-signup free tier -// (issue #6670). Same shape as the hackclub/chutes aggregator entries: +// (issue #6670). Same shape as the chutes aggregator entries: // standard OpenAI chat/completions + /v1/models discovery, so no custom // executor/translator is needed. export const freetheaiProvider: RegistryEntry = { diff --git a/open-sse/config/providers/registry/g4f-groq/index.ts b/open-sse/config/providers/registry/g4f-groq/index.ts index 665b79732c..8eee1344dc 100644 --- a/open-sse/config/providers/registry/g4f-groq/index.ts +++ b/open-sse/config/providers/registry/g4f-groq/index.ts @@ -1,7 +1,7 @@ import type { RegistryEntry } from "../../shared.ts"; // g4f.space/api/groq — no-key reverse proxy to Groq (gpt4free project, issue #6650). -// Same OpenAI-compatible shape as the other no-key gateways (hackclub, uncloseai): +// Same OpenAI-compatible shape as the other no-key gateways (uncloseai): // standard chat/completions + /v1/models discovery, no custom executor/translator. export const g4f_groqProvider: RegistryEntry = { id: "g4f-groq", diff --git a/open-sse/config/providers/registry/huggingchat/index.ts b/open-sse/config/providers/registry/huggingchat/index.ts index ccc95fb4a0..4be26b8d9b 100644 --- a/open-sse/config/providers/registry/huggingchat/index.ts +++ b/open-sse/config/providers/registry/huggingchat/index.ts @@ -2,8 +2,8 @@ import type { RegistryEntry } from "../../shared.ts"; export const huggingchatProvider: RegistryEntry = { id: "huggingchat", - // Distinct alias: "hc" belongs to the hackclub provider; huggingchat is - // addressed by its own id to avoid the alias collision. + // Distinct alias: huggingchat is addressed by its own id to avoid the + // historical "hc" alias collision (the colliding provider was removed, #11176). alias: "huggingchat", format: "openai", executor: "huggingchat", diff --git a/package.json b/package.json index 66ec5b29e7..cf08e93b61 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "omniroute", "version": "3.8.50", - "description": "Unified AI router with 351 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.", + "description": "Unified AI router with 350 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.", "type": "module", "bin": { "omniroute": "bin/omniroute.mjs", diff --git a/public/providers/hackclub.svg b/public/providers/hackclub.svg deleted file mode 100644 index 86c07e144f..0000000000 --- a/public/providers/hackclub.svg +++ /dev/null @@ -1,5 +0,0 @@ - - Hack Club - - - diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 6da11ccd78..f9f2a06e24 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -6113,7 +6113,6 @@ "glmt": "ملف تعريف GLM مسبق الضبط بميزانية رموز أعلى، وتمكين التفكير، ومهلة أطول.", "getgoapi": "ربط GoAPI بمفتاح API.", "groq": "الفئة المجانية: 30 طلبًا في الدقيقة / 14.4 ألف طلب في اليوم — بدون بطاقة ائتمان", - "hackclub": "سجل الدخول باستخدام حساب Hack Club الخاص بك على ai.hackclub.com.", "haiper": "احصل على مفتاح API من haiper.ai/haiper-api", "heroku": "ربط Heroku AI بمفتاح API.", "hcnsec": "احصل على مفتاح API من api.hcnsec.cn", diff --git a/src/i18n/messages/az.json b/src/i18n/messages/az.json index 6ff01e06e5..c599bd248e 100644 --- a/src/i18n/messages/az.json +++ b/src/i18n/messages/az.json @@ -6113,7 +6113,6 @@ "glmt": "Daha yüksək token büdcəsi, düşünmə aktivləşdirilmiş və daha uzun vaxt aşımı olan hazır GLM profili.", "getgoapi": "GoAPI-ni API açarı ilə qoşun.", "groq": "Pulsuz tarif: 30 RPM / 14.4K RPD — kredit kartı tələb olunmur", - "hackclub": "ai.hackclub.com ünvanında Hack Club hesabınızla daxil olun.", "haiper": "API açarını haiper.ai/haiper-api ünvanından əldə edin", "heroku": "Heroku AI-ı API açarı ilə qoşun.", "hcnsec": "API açarını api.hcnsec.cn ünvanından əldə edin", diff --git a/src/i18n/messages/bg.json b/src/i18n/messages/bg.json index f20c42ce9c..8d375a8af3 100644 --- a/src/i18n/messages/bg.json +++ b/src/i18n/messages/bg.json @@ -6113,7 +6113,6 @@ "glmt": "Предварително зададен GLM профил с по-висок бюджет за токени, активирано мислене и по-дълъг таймаут.", "getgoapi": "Свържете GoAPI с API ключ.", "groq": "Безплатен план: 30 RPM / 14.4K RPD — без кредитна карта", - "hackclub": "Влезте с вашия Hack Club акаунт на ai.hackclub.com.", "haiper": "Вземете API ключ на haiper.ai/haiper-api", "heroku": "Свържете Heroku AI с API ключ.", "hcnsec": "Вземете API ключ на api.hcnsec.cn", diff --git a/src/i18n/messages/bn.json b/src/i18n/messages/bn.json index e1ee75d596..a9932c538c 100644 --- a/src/i18n/messages/bn.json +++ b/src/i18n/messages/bn.json @@ -6113,7 +6113,6 @@ "glmt": "উচ্চতর টোকেন বাজেট, থিংকিং সক্রিয় এবং দীর্ঘতর টাইমআউট সহ প্রিসেট GLM প্রোফাইল।", "getgoapi": "একটি API কী দিয়ে GoAPI কানেক্ট করুন।", "groq": "ফ্রি টিয়ার: 30 RPM / 14.4K RPD — কোনো ক্রেডিট কার্ড লাগবে না", - "hackclub": "ai.hackclub.com-এ আপনার Hack Club অ্যাকাউন্ট দিয়ে সাইন ইন করুন।", "haiper": "haiper.ai/haiper-api থেকে API কী পান", "heroku": "একটি API কী দিয়ে Heroku AI কানেক্ট করুন।", "hcnsec": "api.hcnsec.cn-এ API কী পান", diff --git a/src/i18n/messages/cs.json b/src/i18n/messages/cs.json index 11ea03e659..c27b27210d 100644 --- a/src/i18n/messages/cs.json +++ b/src/i18n/messages/cs.json @@ -6113,7 +6113,6 @@ "glmt": "Přednastavený profil GLM s vyšším rozpočtem tokenů, povoleným přemýšlením a delším časovým limitem.", "getgoapi": "Připojte GoAPI pomocí API klíče.", "groq": "Bezplatná úroveň: 30 RPM / 14,4K RPD – bez platební karty", - "hackclub": "Přihlaste se pomocí svého účtu Hack Club na ai.hackclub.com.", "haiper": "Získejte API klíč na haiper.ai/haiper-api", "heroku": "Připojte Heroku AI pomocí API klíče.", "hcnsec": "Získejte API klíč na api.hcnsec.cn", diff --git a/src/i18n/messages/da.json b/src/i18n/messages/da.json index 3f4bd16b7e..46887cc2e9 100644 --- a/src/i18n/messages/da.json +++ b/src/i18n/messages/da.json @@ -6113,7 +6113,6 @@ "glmt": "Forudindstillet GLM-profil med højere token-budget, tænkning aktiveret og længere timeout.", "getgoapi": "Forbind GoAPI med en API-nøgle.", "groq": "Gratis niveau: 30 RPM / 14,4K RPD — intet kreditkort", - "hackclub": "Log ind med din Hack Club-konto på ai.hackclub.com.", "haiper": "Hent API-nøgle på haiper.ai/haiper-api", "heroku": "Forbind Heroku AI med en API-nøgle.", "hcnsec": "Få API-nøgle på api.hcnsec.cn", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 9ed2c41912..1baab4afec 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -6113,7 +6113,6 @@ "glmt": "Voreingestelltes GLM-Profil mit höherem Token-Budget, aktiviertem Denken und längerem Timeout.", "getgoapi": "GoAPI mit einem API-Schlüssel verbinden.", "groq": "Kostenlose Stufe: 30 RPM / 14,4K RPD — keine Kreditkarte", - "hackclub": "Melden Sie sich mit Ihrem Hack Club-Konto unter ai.hackclub.com an.", "haiper": "API-Schlüssel unter haiper.ai/haiper-api anfordern", "heroku": "Heroku AI mit einem API-Schlüssel verbinden.", "hcnsec": "API-Schlüssel unter api.hcnsec.cn anfordern", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 4210a8d86a..12e1f03e47 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -6134,7 +6134,6 @@ "glmt": "Preset GLM profile with higher token budget, thinking enabled, and longer timeout.", "getgoapi": "Connect GoAPI with an API key.", "groq": "Free tier: 30 RPM / 14.4K RPD — no credit card", - "hackclub": "Sign in with your Hack Club account at ai.hackclub.com.", "haiper": "Get API key at haiper.ai/haiper-api", "heroku": "Connect Heroku AI with an API key.", "hcnsec": "Get API key at api.hcnsec.cn", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 70e8ea51e7..7b6767fd1d 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -6113,7 +6113,6 @@ "glmt": "Preset GLM profile with higher token budget, thinking enabled, and longer timeout.", "getgoapi": "Connect GoAPI with an API key.", "groq": "Free tier: 30 RPM / 14.4K RPD — no credit card", - "hackclub": "Sign in with your Hack Club account at ai.hackclub.com.", "haiper": "Get API key at haiper.ai/haiper-api", "heroku": "Connect Heroku AI with an API key.", "hcnsec": "Get API key at api.hcnsec.cn", diff --git a/src/i18n/messages/fa.json b/src/i18n/messages/fa.json index 7b15a9271d..0eac6a98dd 100644 --- a/src/i18n/messages/fa.json +++ b/src/i18n/messages/fa.json @@ -6113,7 +6113,6 @@ "glmt": "پروفایل پیش‌فرض GLM با بودجه توکن بالاتر، فعال بودن تفکر (thinking) و زمان انتظار (timeout) طولانی‌تر.", "getgoapi": "اتصال GoAPI با یک کلید API.", "groq": "سطح رایگان: ۳۰ RPM / ۱۴.۴K RPD — بدون نیاز به کارت اعتباری", - "hackclub": "با حساب کاربری Hack Club خود در ai.hackclub.com وارد شوید.", "haiper": "کلید API را در haiper.ai/haiper-api دریافت کنید", "heroku": "اتصال Heroku AI با یک کلید API.", "hcnsec": "دریافت کلید API در api.hcnsec.cn", diff --git a/src/i18n/messages/fi.json b/src/i18n/messages/fi.json index fbec8122e6..9d61297ecb 100644 --- a/src/i18n/messages/fi.json +++ b/src/i18n/messages/fi.json @@ -6113,7 +6113,6 @@ "glmt": "Esiasetettu GLM-profiili suuremmalla token-budjetilla, ajattelu käytössä ja pidemmällä aikakatkaisulla.", "getgoapi": "Yhdistä GoAPI API-avaimella.", "groq": "Ilmainen taso: 30 RPM / 14,4K RPD — ei luottokorttia", - "hackclub": "Kirjaudu sisään Hack Club -tililläsi osoitteessa ai.hackclub.com.", "haiper": "Hanki API-avain osoitteesta haiper.ai/haiper-api", "heroku": "Yhdistä Heroku AI API-avaimella.", "hcnsec": "Hanki API-avain osoitteesta api.hcnsec.cn", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index ad2ea643ba..522a0a4ebf 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -6113,7 +6113,6 @@ "glmt": "Profil GLM prédéfini avec un budget de tokens plus élevé, mode pensée activé et délai d'attente plus long.", "getgoapi": "Connectez GoAPI avec une clé API.", "groq": "Offre gratuite : 30 RPM / 14,4K RPD — sans carte de crédit", - "hackclub": "Connectez-vous avec votre compte Hack Club sur ai.hackclub.com.", "haiper": "Obtenez une clé API sur haiper.ai/haiper-api", "heroku": "Connectez Heroku AI avec une clé API.", "hcnsec": "Obtenir une clé API sur api.hcnsec.cn", diff --git a/src/i18n/messages/gu.json b/src/i18n/messages/gu.json index 56318eb5fb..566db670be 100644 --- a/src/i18n/messages/gu.json +++ b/src/i18n/messages/gu.json @@ -6113,7 +6113,6 @@ "glmt": "ઉચ્ચ ટોકન બજેટ, વિચારવાની ક્ષમતા સક્ષમ અને લાંબા સમયસમાપ્તિ સાથે પ્રીસેટ GLM પ્રોફાઇલ.", "getgoapi": "API કી વડે GoAPI ને કનેક્ટ કરો.", "groq": "મફત સ્તર: 30 RPM / 14.4K RPD — કોઈ ક્રેડિટ કાર્ડ નહીં", - "hackclub": "ai.hackclub.com પર તમારા Hack Club એકાઉન્ટ વડે સાઇન ઇન કરો.", "haiper": "haiper.ai/haiper-api પર API કી મેળવો", "heroku": "API કી વડે Heroku AI ને કનેક્ટ કરો.", "hcnsec": "api.hcnsec.cn પર API કી મેળવો", diff --git a/src/i18n/messages/he.json b/src/i18n/messages/he.json index f17a47487c..d377d85283 100644 --- a/src/i18n/messages/he.json +++ b/src/i18n/messages/he.json @@ -6113,7 +6113,6 @@ "glmt": "פרופיל GLM מוגדר מראש עם תקציב טוקנים גבוה יותר, חשיבה מופעלת ופסק זמן ארוך יותר.", "getgoapi": "חבר את GoAPI באמצעות מפתח API.", "groq": "מסלול חינמי: 30 RPM / 14.4K RPD — ללא כרטיס אשראי", - "hackclub": "התחבר עם חשבון ה-Hack Club שלך ב-ai.hackclub.com.", "haiper": "קבל מפתח API ב-haiper.ai/haiper-api", "heroku": "חבר את Heroku AI באמצעות מפתח API.", "hcnsec": "קבל מפתח API ב-api.hcnsec.cn", diff --git a/src/i18n/messages/hi.json b/src/i18n/messages/hi.json index 5c90eb7606..6585020ebe 100644 --- a/src/i18n/messages/hi.json +++ b/src/i18n/messages/hi.json @@ -6113,7 +6113,6 @@ "glmt": "उच्च टोकन बजट, थिंकिंग (thinking) सक्षम और लंबे टाइमआउट के साथ प्रीसेट GLM प्रोफ़ाइल।", "getgoapi": "GoAPI को एक API कुंजी से कनेक्ट करें।", "groq": "निःशुल्क टियर: 30 RPM / 14.4K RPD — कोई क्रेडिट कार्ड नहीं", - "hackclub": "ai.hackclub.com पर अपने Hack Club खाते से साइन इन करें।", "haiper": "haiper.ai/haiper-api पर API कुंजी प्राप्त करें", "heroku": "Heroku AI को एक API कुंजी से कनेक्ट करें।", "hcnsec": "api.hcnsec.cn पर API कुंजी प्राप्त करें", diff --git a/src/i18n/messages/hu.json b/src/i18n/messages/hu.json index 9552621a19..bc576af3b0 100644 --- a/src/i18n/messages/hu.json +++ b/src/i18n/messages/hu.json @@ -6113,7 +6113,6 @@ "glmt": "Előre beállított GLM-profil magasabb tokenkerettel, engedélyezett gondolkodással és hosszabb időtúllépéssel.", "getgoapi": "Csatlakoztassa a GoAPI-t egy API-kulccsal.", "groq": "Ingyenes csomag: 30 RPM / 14,4K RPD — bankkártya nem szükséges", - "hackclub": "Jelentkezzen be Hack Club-fiókjával az ai.hackclub.com oldalon.", "haiper": "Szerezzen API-kulcsot a haiper.ai/haiper-api oldalon", "heroku": "Csatlakoztassa a Heroku AI-t egy API-kulccsal.", "hcnsec": "Szerezzen API-kulcsot itt: api.hcnsec.cn", diff --git a/src/i18n/messages/id.json b/src/i18n/messages/id.json index 56bfb571b6..3c466610f8 100644 --- a/src/i18n/messages/id.json +++ b/src/i18n/messages/id.json @@ -6113,7 +6113,6 @@ "glmt": "Profil GLM prasetel dengan anggaran token yang lebih tinggi, proses berpikir diaktifkan, dan batas waktu yang lebih lama.", "getgoapi": "Hubungkan GoAPI dengan kunci API.", "groq": "Tingkat gratis: 30 RPM / 14,4K RPD — tanpa kartu kredit", - "hackclub": "Masuk dengan akun Hack Club Anda di ai.hackclub.com.", "haiper": "Dapatkan kunci API di haiper.ai/haiper-api", "heroku": "Hubungkan Heroku AI dengan kunci API.", "hcnsec": "Dapatkan kunci API di api.hcnsec.cn", diff --git a/src/i18n/messages/in.json b/src/i18n/messages/in.json index 10a174fe8f..9068ec4e24 100644 --- a/src/i18n/messages/in.json +++ b/src/i18n/messages/in.json @@ -6113,7 +6113,6 @@ "glmt": "Profil GLM prasetel dengan anggaran token lebih tinggi, pemikiran diaktifkan, dan batas waktu lebih lama.", "getgoapi": "Hubungkan GoAPI dengan kunci API.", "groq": "Tingkat gratis: 30 RPM / 14.4K RPD — tanpa kartu kredit", - "hackclub": "Masuk dengan akun Hack Club Anda di ai.hackclub.com.", "haiper": "Dapatkan kunci API di haiper.ai/haiper-api", "heroku": "Hubungkan Heroku AI dengan kunci API.", "hcnsec": "Dapatkan kunci API di api.hcnsec.cn", diff --git a/src/i18n/messages/it.json b/src/i18n/messages/it.json index 7b9c267726..e2d704ff8d 100644 --- a/src/i18n/messages/it.json +++ b/src/i18n/messages/it.json @@ -6113,7 +6113,6 @@ "glmt": "Profilo GLM preimpostato con budget di token più elevato, pensiero abilitato e timeout più lungo.", "getgoapi": "Connetti GoAPI con una chiave API.", "groq": "Piano gratuito: 30 RPM / 14,4K RPD — nessuna carta di credito", - "hackclub": "Accedi con il tuo account Hack Club su ai.hackclub.com.", "haiper": "Ottieni la chiave API su haiper.ai/haiper-api", "heroku": "Connetti Heroku AI con una chiave API.", "hcnsec": "Ottieni la chiave API su api.hcnsec.cn", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 7386101a01..af3254ec9d 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -6113,7 +6113,6 @@ "glmt": "より大きなトークンバジェット、思考の有効化、およびより長いタイムアウトを備えたプリセットGLMプロファイル。", "getgoapi": "APIキーでGoAPIに接続します。", "groq": "無料枠: 30 RPM / 14.4K RPD — クレジットカード不要", - "hackclub": "ai.hackclub.com でHack Clubアカウントを使用してサインインします。", "haiper": "haiper.ai/haiper-api でAPIキーを取得", "heroku": "APIキーでHeroku AIに接続します。", "hcnsec": "api.hcnsec.cn でAPIキーを取得", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index dbea94479d..96fc3f83a9 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -6113,7 +6113,6 @@ "glmt": "더 높은 토큰 예산, 생각하기(thinking) 활성화 및 더 긴 타임아웃이 설정된 프리셋 GLM 프로필.", "getgoapi": "API 키로 GoAPI 연결.", "groq": "무료 티어: 30 RPM / 14.4K RPD — 신용카드 불필요", - "hackclub": "ai.hackclub.com 에서 Hack Club 계정으로 로그인하세요.", "haiper": "haiper.ai/haiper-api 에서 API 키를 가져오세요.", "heroku": "API 키로 Heroku AI 연결.", "hcnsec": "api.hcnsec.cn 에서 API 키를 가져오세요.", diff --git a/src/i18n/messages/mr.json b/src/i18n/messages/mr.json index 3bb8af3d33..d452413465 100644 --- a/src/i18n/messages/mr.json +++ b/src/i18n/messages/mr.json @@ -6113,7 +6113,6 @@ "glmt": "उच्च टोकन बजेट, थिंकिंग सक्षम आणि दीर्घ टाइमआउटसह प्रीसेट GLM प्रोफाइल.", "getgoapi": "API की सह GoAPI कनेक्ट करा.", "groq": "विनामूल्य टियर: 30 RPM / 14.4K RPD — क्रेडिट कार्ड नाही", - "hackclub": "ai.hackclub.com वर तुमच्या Hack Club खात्यासह साइन इन करा.", "haiper": "haiper.ai/haiper-api वर API की मिळवा", "heroku": "API की सह Heroku AI कनेक्ट करा.", "hcnsec": "api.hcnsec.cn वर API की मिळवा", diff --git a/src/i18n/messages/ms.json b/src/i18n/messages/ms.json index 9de9e21c81..7c5e7bce80 100644 --- a/src/i18n/messages/ms.json +++ b/src/i18n/messages/ms.json @@ -6113,7 +6113,6 @@ "glmt": "Profil GLM pratetap dengan belanjawan token yang lebih tinggi, pemikiran didayakan dan tamat masa yang lebih lama.", "getgoapi": "Sambungkan GoAPI dengan kunci API.", "groq": "Peringkat percuma: 30 RPM / 14.4K RPD — tiada kad kredit", - "hackclub": "Log masuk dengan akaun Hack Club anda di ai.hackclub.com.", "haiper": "Dapatkan kunci API di haiper.ai/haiper-api", "heroku": "Sambungkan Heroku AI dengan kunci API.", "hcnsec": "Dapatkan kunci API di api.hcnsec.cn", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index f1e63e98dc..ab9fb2f225 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -6113,7 +6113,6 @@ "glmt": "Vooraf ingesteld GLM-profiel met een hoger tokenbudget, denken ingeschakeld en een langere time-out.", "getgoapi": "Verbind GoAPI met een API-sleutel.", "groq": "Gratis abonnement: 30 RPM / 14,4K RPD — geen creditcard", - "hackclub": "Meld je aan met je Hack Club-account op ai.hackclub.com.", "haiper": "Haal de API-sleutel op via haiper.ai/haiper-api", "heroku": "Verbind Heroku AI met een API-sleutel.", "hcnsec": "Haal de API-sleutel op via api.hcnsec.cn", diff --git a/src/i18n/messages/no.json b/src/i18n/messages/no.json index a062bdea20..e51d66b5d4 100644 --- a/src/i18n/messages/no.json +++ b/src/i18n/messages/no.json @@ -6113,7 +6113,6 @@ "glmt": "Forhåndsinnstilt GLM-profil med høyere token-budsjett, tenkning aktivert og lengre tidsavbrudd.", "getgoapi": "Koble til GoAPI med en API-nøkkel.", "groq": "Gratisnivå: 30 RPM / 14,4K RPD — uten kredittkort", - "hackclub": "Logg inn med Hack Club-kontoen din på ai.hackclub.com.", "haiper": "Hent API-nøkkel på haiper.ai/haiper-api", "heroku": "Koble til Heroku AI med en API-nøkkel.", "hcnsec": "Hent API-nøkkel på api.hcnsec.cn", diff --git a/src/i18n/messages/phi.json b/src/i18n/messages/phi.json index 84580e5e7c..1ddf0eb55a 100644 --- a/src/i18n/messages/phi.json +++ b/src/i18n/messages/phi.json @@ -6113,7 +6113,6 @@ "glmt": "Preset na GLM profile na may mas mataas na token budget, naka-enable ang thinking, at mas mahabang timeout.", "getgoapi": "Ikonekta ang GoAPI gamit ang isang API key.", "groq": "Libreng tier: 30 RPM / 14.4K RPD — walang credit card", - "hackclub": "Mag-sign in gamit ang iyong Hack Club account sa ai.hackclub.com.", "haiper": "Kumuha ng API key sa haiper.ai/haiper-api", "heroku": "Ikonekta ang Heroku AI gamit ang isang API key.", "hcnsec": "Kumuha ng API key sa api.hcnsec.cn", diff --git a/src/i18n/messages/pl.json b/src/i18n/messages/pl.json index e88038279c..087852e060 100644 --- a/src/i18n/messages/pl.json +++ b/src/i18n/messages/pl.json @@ -6113,7 +6113,6 @@ "glmt": "Wstępnie zdefiniowany profil GLM z większym budżetem tokenów, włączonym myśleniem i dłuższym limitem czasu.", "getgoapi": "Połącz z GoAPI za pomocą klucza API.", "groq": "Darmowy plan: 30 RPM / 14.4K RPD — bez karty kredytowej", - "hackclub": "Zaloguj się za pomocą konta Hack Club na ai.hackclub.com.", "haiper": "Pobierz klucz API na haiper.ai/haiper-api", "heroku": "Połącz z Heroku AI za pomocą klucza API.", "hcnsec": "Pobierz klucz API na api.hcnsec.cn", diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 17d42b0413..7fbe30f278 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -6118,7 +6118,6 @@ "glmt": "Perfil GLM pré-configurado com orçamento de tokens maior, thinking ativado e timeout mais longo.", "getgoapi": "Conecte o GoAPI com uma chave de API.", "groq": "Nível gratuito: 30 RPM / 14,4K RPD — sem cartão de crédito", - "hackclub": "Entre com sua conta Hack Club em ai.hackclub.com.", "haiper": "Obtenha a chave de API em haiper.ai/haiper-api", "heroku": "Conecte o Heroku AI com uma chave de API.", "hcnsec": "Obtenha a chave de API em api.hcnsec.cn", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index a0984fa5d3..3194c7e492 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -6113,7 +6113,6 @@ "glmt": "Perfil predefinido do GLM com maior orçamento de tokens, raciocínio ativado e tempo limite mais longo.", "getgoapi": "Ligue a GoAPI com uma chave de API.", "groq": "Nível gratuito: 30 RPM / 14,4K RPD — sem cartão de crédito", - "hackclub": "Inicie sessão com a sua conta Hack Club em ai.hackclub.com.", "haiper": "Obtenha a chave de API em haiper.ai/haiper-api", "heroku": "Ligue o Heroku AI com uma chave de API.", "hcnsec": "Obtenha a chave de API em api.hcnsec.cn", diff --git a/src/i18n/messages/ro.json b/src/i18n/messages/ro.json index 126bc182c5..f0d50bae68 100644 --- a/src/i18n/messages/ro.json +++ b/src/i18n/messages/ro.json @@ -6113,7 +6113,6 @@ "glmt": "Profil GLM prestabilit cu un buget de tokenuri mai mare, gândire activată și timeout mai lung.", "getgoapi": "Conectați GoAPI cu o cheie API.", "groq": "Nivel gratuit: 30 RPM / 14.4K RPD — fără card de credit", - "hackclub": "Conectați-vă cu contul Hack Club la ai.hackclub.com.", "haiper": "Obțineți cheia API la haiper.ai/haiper-api", "heroku": "Conectați Heroku AI cu o cheie API.", "hcnsec": "Obțineți cheia API la api.hcnsec.cn", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 05a480f07e..7517e856d8 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -6113,7 +6113,6 @@ "glmt": "Предустановленный профиль GLM с увеличенным лимитом токенов, включенным режимом рассуждения и более длительным таймаутом.", "getgoapi": "Подключите GoAPI с помощью API-ключа.", "groq": "Бесплатный тариф: 30 RPM / 14.4K RPD — без кредитной карты", - "hackclub": "Войдите с помощью учетной записи Hack Club на ai.hackclub.com.", "haiper": "Получите API-ключ на haiper.ai/haiper-api", "heroku": "Подключите Heroku AI с помощью API-ключа.", "hcnsec": "Получите API-ключ на api.hcnsec.cn", diff --git a/src/i18n/messages/sk.json b/src/i18n/messages/sk.json index 8833d35f5b..3a65cab757 100644 --- a/src/i18n/messages/sk.json +++ b/src/i18n/messages/sk.json @@ -6113,7 +6113,6 @@ "glmt": "Prednastavený profil GLM s vyšším rozpočtom tokenov, povoleným premýšľaním a dlhším časovým limitom.", "getgoapi": "Pripojte GoAPI pomocou API kľúča.", "groq": "Bezplatná úroveň: 30 RPM / 14,4K RPD — bez kreditnej karty", - "hackclub": "Prihláste sa pomocou svojho účtu Hack Club na adrese ai.hackclub.com.", "haiper": "Získajte API kľúč na adrese haiper.ai/haiper-api", "heroku": "Pripojte Heroku AI pomocou API kľúča.", "hcnsec": "Získajte API kľúč na adrese api.hcnsec.cn", diff --git a/src/i18n/messages/sv.json b/src/i18n/messages/sv.json index 7eda0578f2..8d7dde53a3 100644 --- a/src/i18n/messages/sv.json +++ b/src/i18n/messages/sv.json @@ -6113,7 +6113,6 @@ "glmt": "Förinställd GLM-profil med högre tokenbudget, tänkande aktiverat och längre tidsgräns.", "getgoapi": "Anslut GoAPI med en API-nyckel.", "groq": "Gratisnivå: 30 RPM / 14,4K RPD — inget kreditkort", - "hackclub": "Logga in med ditt Hack Club-konto på ai.hackclub.com.", "haiper": "Hämta API-nyckel på haiper.ai/haiper-api", "heroku": "Anslut Heroku AI med en API-nyckel.", "hcnsec": "Hämta API-nyckel på api.hcnsec.cn", diff --git a/src/i18n/messages/sw.json b/src/i18n/messages/sw.json index b6c083eb27..5c7602ce68 100644 --- a/src/i18n/messages/sw.json +++ b/src/i18n/messages/sw.json @@ -6113,7 +6113,6 @@ "glmt": "Wasifu uliowekwa awali wa GLM wenye bajeti ya juu ya tokeni, kufikiri kumewashwa, na muda mrefu zaidi wa kuisha.", "getgoapi": "Unganisha GoAPI kwa kutumia ufunguo wa API.", "groq": "Kiwango cha bure: 30 RPM / 14.4K RPD — hakuna kadi ya mkopo", - "hackclub": "Ingia ukitumia akaunti yako ya Hack Club kwenye ai.hackclub.com.", "haiper": "Pata ufunguo wa API kwenye haiper.ai/haiper-api", "heroku": "Unganisha Heroku AI kwa kutumia ufunguo wa API.", "hcnsec": "Pata ufunguo wa API kwenye api.hcnsec.cn", diff --git a/src/i18n/messages/ta.json b/src/i18n/messages/ta.json index e3c996b490..a9f42ff2ec 100644 --- a/src/i18n/messages/ta.json +++ b/src/i18n/messages/ta.json @@ -6113,7 +6113,6 @@ "glmt": "அதிக டோக்கன் பட்ஜெட், சிந்தனை இயக்கப்பட்டது மற்றும் நீண்ட காலாவதி நேரத்துடன் கூடிய முன்னமைக்கப்பட்ட GLM சுயவிவரம்.", "getgoapi": "GoAPI ஐ ஒரு API விசையுடன் இணைக்கவும்.", "groq": "இலவச அடுக்கு: 30 RPM / 14.4K RPD — கிரெடிட் கார்டு தேவையில்லை", - "hackclub": "ai.hackclub.com இல் உங்கள் Hack Club கணக்குடன் உள்நுழையவும்.", "haiper": "haiper.ai/haiper-api இல் API விசையைப் பெறவும்", "heroku": "Heroku AI ஐ ஒரு API விசையுடன் இணைக்கவும்.", "hcnsec": "api.hcnsec.cn இல் API விசையைப் பெறுக", diff --git a/src/i18n/messages/te.json b/src/i18n/messages/te.json index e4d2553b2e..bce2574fc5 100644 --- a/src/i18n/messages/te.json +++ b/src/i18n/messages/te.json @@ -6113,7 +6113,6 @@ "glmt": "ఎక్కువ టోకెన్ బడ్జెట్, థింకింగ్ ఎనేబుల్ చేయబడిన మరియు ఎక్కువ టైమ్‌అవుట్‌తో కూడిన ప్రీసెట్ GLM ప్రొఫైల్.", "getgoapi": "API కీతో GoAPI ని కనెక్ట్ చేయండి.", "groq": "ఉచిత టైర్: 30 RPM / 14.4K RPD — క్రెడిట్ కార్డ్ అవసరం లేదు", - "hackclub": "ai.hackclub.com వద్ద మీ Hack Club ఖాతాతో సైన్ ఇన్ చేయండి.", "haiper": "haiper.ai/haiper-api వద్ద API కీని పొందండి", "heroku": "API కీతో Heroku AI ని కనెక్ట్ చేయండి.", "hcnsec": "api.hcnsec.cn వద్ద API కీని పొందండి", diff --git a/src/i18n/messages/th.json b/src/i18n/messages/th.json index 67c0941173..f13731a783 100644 --- a/src/i18n/messages/th.json +++ b/src/i18n/messages/th.json @@ -6113,7 +6113,6 @@ "glmt": "โปรไฟล์ GLM ที่ตั้งค่าไว้ล่วงหน้าพร้อมงบประมาณโทเค็นที่สูงขึ้น เปิดใช้งานการคิด และหมดเวลาการทำงานที่นานขึ้น", "getgoapi": "เชื่อมต่อ GoAPI ด้วยคีย์ API", "groq": "ระดับการใช้งานฟรี: 30 RPM / 14.4K RPD — ไม่ต้องใช้บัตรเครดิต", - "hackclub": "ลงชื่อเข้าใช้ด้วยบัญชี Hack Club ของคุณที่ ai.hackclub.com", "haiper": "รับคีย์ API ได้ที่ haiper.ai/haiper-api", "heroku": "เชื่อมต่อ Heroku AI ด้วยคีย์ API", "hcnsec": "รับ API key ได้ที่ api.hcnsec.cn", diff --git a/src/i18n/messages/tr.json b/src/i18n/messages/tr.json index de926b0a97..69890f1d89 100644 --- a/src/i18n/messages/tr.json +++ b/src/i18n/messages/tr.json @@ -6113,7 +6113,6 @@ "glmt": "Daha yüksek token bütçesi, düşünme etkinleştirilmiş ve daha uzun zaman aşımına sahip önceden ayarlanmış GLM profili.", "getgoapi": "GoAPI'yi bir API anahtarı ile bağlayın.", "groq": "Ücretsiz katman: 30 RPM / 14.4K RPD — kredi kartı gerekmez", - "hackclub": "ai.hackclub.com adresinde Hack Club hesabınızla oturum açın.", "haiper": "API anahtarını haiper.ai/haiper-api adresinden alın", "heroku": "Heroku AI'ı bir API anahtarı ile bağlayın.", "hcnsec": "API anahtarını api.hcnsec.cn adresinden alın", diff --git a/src/i18n/messages/uk-UA.json b/src/i18n/messages/uk-UA.json index 628da54d2a..a16e0058cc 100644 --- a/src/i18n/messages/uk-UA.json +++ b/src/i18n/messages/uk-UA.json @@ -6113,7 +6113,6 @@ "glmt": "Попередньо встановлений профіль GLM із більшим бюджетом токенів, увімкненим мисленням та довшим таймаутом.", "getgoapi": "Підключіть GoAPI за допомогою API-ключа.", "groq": "Безкоштовний тариф: 30 RPM / 14.4K RPD — без кредитної картки", - "hackclub": "Увійдіть за допомогою свого облікового запису Hack Club на ai.hackclub.com.", "haiper": "Отримайте API-ключ на haiper.ai/haiper-api", "heroku": "Підключіть Heroku AI за допомогою API-ключа.", "hcnsec": "Отримайте API-ключ на api.hcnsec.cn", diff --git a/src/i18n/messages/ur.json b/src/i18n/messages/ur.json index 3b0cb7c875..b7f65716ff 100644 --- a/src/i18n/messages/ur.json +++ b/src/i18n/messages/ur.json @@ -6113,7 +6113,6 @@ "glmt": "زیادہ ٹوکن بجٹ، تھنکنگ فعال، اور طویل ٹائم آؤٹ کے ساتھ پہلے سے سیٹ کردہ GLM پروفائل۔", "getgoapi": "GoAPI کو ایک API کی کے ساتھ منسلک کریں۔", "groq": "مفت ٹیر: 30 RPM / 14.4K RPD — کوئی کریڈٹ کارڈ نہیں", - "hackclub": "ai.hackclub.com پر اپنے Hack Club اکاؤنٹ کے ساتھ سائن ان کریں۔", "haiper": "haiper.ai/haiper-api پر API کی حاصل کریں", "heroku": "Heroku AI کو ایک API کی کے ساتھ منسلک کریں۔", "hcnsec": "api.hcnsec.cn پر API کی حاصل کریں", diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json index f934934088..787e6dd7d9 100644 --- a/src/i18n/messages/vi.json +++ b/src/i18n/messages/vi.json @@ -6134,7 +6134,6 @@ "glmt": "Hồ sơ GLM đặt sẵn với ngân sách token cao hơn, bật thinking và thời gian chờ dài hơn.", "getgoapi": "Kết nối GoAPI bằng khóa API.", "groq": "Gói miễn phí: 30 RPM / 14,4 nghìn RPD — không cần thẻ tín dụng", - "hackclub": "Đăng nhập bằng tài khoản Hack Club tại ai.hackclub.com.", "haiper": "Lấy khóa API tại haiper.ai/haiper-api", "heroku": "Kết nối Heroku AI bằng khóa API.", "hcnsec": "Lấy khóa API tại api.hcnsec.cn", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index e797a5b00f..dd0172b18b 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -6113,7 +6113,6 @@ "glmt": "预设 GLM 配置文件,具有更高的 Token 预算、启用思考功能以及更长的超时时间。", "getgoapi": "使用 API 密钥连接 GoAPI。", "groq": "免费层:30 RPM / 14.4K RPD — 无需信用卡", - "hackclub": "在 ai.hackclub.com 使用您的 Hack Club 账户登录。", "haiper": "在 haiper.ai/haiper-api 获取 API 密钥", "heroku": "使用 API 密钥连接 Heroku AI。", "hcnsec": "在 api.hcnsec.cn 获取 API 密钥", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index f10cc2e02c..e9876d5a3f 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -6113,7 +6113,6 @@ "glmt": "預設 GLM 設定檔,具有較高的 token 預算、啟用思考功能,以及更長的超時時間。", "getgoapi": "使用 API 金鑰連線 GoAPI。", "groq": "免費方案:每分鐘 30 次 / 每天 14,400 次請求 — 無需信用卡", - "hackclub": "在 ai.hackclub.com 使用你的 Hack Club 帳號登入。", "haiper": "在 haiper.ai/haiper-api 取得 API 金鑰", "heroku": "使用 API 金鑰連線 Heroku AI。", "hcnsec": "在 api.hcnsec.cn 取得 API 金鑰", diff --git a/src/shared/components/ProviderIcon.tsx b/src/shared/components/ProviderIcon.tsx index 56d54bf0dc..97c4e135aa 100644 --- a/src/shared/components/ProviderIcon.tsx +++ b/src/shared/components/ProviderIcon.tsx @@ -127,7 +127,6 @@ const KNOWN_SVGS = new Set([ "google", "grok", "groq", - "hackclub", "haiper", "hcnsec", "heroku", diff --git a/src/shared/constants/providers/apikey/gateways.ts b/src/shared/constants/providers/apikey/gateways.ts index 6dab614425..f1ba744f6f 100644 --- a/src/shared/constants/providers/apikey/gateways.ts +++ b/src/shared/constants/providers/apikey/gateways.ts @@ -639,19 +639,6 @@ export const APIKEY_PROVIDERS_GATEWAYS = { text: "Dahl auto-generates tokens via https://inference.dahl.global/tokens. No signup needed. Rate limits apply. You can also add your own API key.", }, }, - hackclub: { - id: "hackclub", - alias: "hc", - name: "Hackclub AI", - icon: "auto_awesome", - color: "#FF6B00", - textIcon: "HC", - website: "https://ai.hackclub.com", - hasFree: true, - freeNote: "Free AI for Hack Club members — 30+ models, no credit card.", - passthroughModels: true, - authHint: "Sign in with your Hack Club account at ai.hackclub.com.", - }, freetheai: { id: "freetheai", alias: "fta", diff --git a/src/shared/constants/providers/web-cookie.ts b/src/shared/constants/providers/web-cookie.ts index 65e63ac615..d458c578da 100644 --- a/src/shared/constants/providers/web-cookie.ts +++ b/src/shared/constants/providers/web-cookie.ts @@ -262,7 +262,8 @@ export const WEB_COOKIE_PROVIDERS = { }, huggingchat: { id: "huggingchat", - // "hc" belongs to the hackclub provider; huggingchat uses its own id as alias. + // huggingchat is addressed by its own id as alias (stable routing; the + // historical "hc" alias collided with another provider and was retired). alias: "huggingchat", name: "HuggingChat (Free)", icon: "auto_awesome", diff --git a/tests/unit/g4f-space-gateway-6650.test.ts b/tests/unit/g4f-space-gateway-6650.test.ts index 93fd99bd67..bf946886c6 100644 --- a/tests/unit/g4f-space-gateway-6650.test.ts +++ b/tests/unit/g4f-space-gateway-6650.test.ts @@ -10,7 +10,7 @@ * GET https://g4f.space/api/groq/... → live Groq backend * * Verifies each of the 5 sub-path providers is wired end-to-end the same way as - * the other no-key gateway providers (hackclub, uncloseai): + * the other no-key gateway providers (uncloseai): * - present in the executor REGISTRY with a no-key OpenAI-compatible shape * - resolvable through getExecutor() (falls through to DefaultExecutor) * - listed in AGGREGATOR_PROVIDER_IDS so it shows up in the aggregator @@ -84,7 +84,7 @@ for (const [id, subPath] of Object.entries(SUB_PATHS)) { test(`#6650 ${id} is classified as an aggregator/gateway provider`, () => { assert.ok( AGGREGATOR_PROVIDER_IDS.has(id), - `${id} must be listed in AGGREGATOR_PROVIDER_IDS alongside hackclub/uncloseai` + `${id} must be listed in AGGREGATOR_PROVIDER_IDS alongside uncloseai` ); }); diff --git a/tests/unit/hackclub-removed.test.ts b/tests/unit/hackclub-removed.test.ts new file mode 100644 index 0000000000..5503657fed --- /dev/null +++ b/tests/unit/hackclub-removed.test.ts @@ -0,0 +1,139 @@ +/** + * #11176 — Hack Club AI (hackclub) must be FULLY removed from the live catalogs. + * + * PR #11118/#11123 removed the provider from the open-sse REGISTRY, but the + * canonical shared catalog (`src/shared/constants/providers/`) kept the entry, + * so the dashboard, the alias resolver, the icon set, the onboarding i18n + * strings and the generated provider reference kept advertising a provider the + * router can no longer serve. This test pins the complete removal: + * + * 1. no canonical catalog (API-key / web-cookie / OAuth / no-auth / local / + * search / audio / upstream-proxy / cloud-agent / system) has a `hackclub` entry; + * 2. no provider in any catalog claims the `hc` alias (it belonged to hackclub); + * 3. the provider/catalog source trees carry no `hackclub` mention at all + * (structural grep — catches comments referencing it as a living provider); + * 4. the icon registry and the shipped SVG asset are gone; + * 5. the onboarding i18n description key is gone (en + all locale mirrors). + * + * Historical mentions intentionally KEPT (release records, not catalog): + * CHANGELOG.md, docs/i18n/*\/CHANGELOG.md, and the removal migration + * src/lib/db/migrations/162_remove_hackclub_provider.sql (it IS the removal). + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { + APIKEY_PROVIDERS, + WEB_COOKIE_PROVIDERS, + OAUTH_PROVIDERS, + FREE_PROVIDERS, + NOAUTH_PROVIDERS, + LOCAL_PROVIDERS, + SEARCH_PROVIDERS, + AUDIO_ONLY_PROVIDERS, + UPSTREAM_PROXY_PROVIDERS, + CLOUD_AGENT_PROVIDERS, + SYSTEM_PROVIDERS, +} from "../../src/shared/constants/providers.ts"; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); + +const CATALOGS: Record> = { + APIKEY_PROVIDERS, + WEB_COOKIE_PROVIDERS, + OAUTH_PROVIDERS: OAUTH_PROVIDERS as Record, + FREE_PROVIDERS: FREE_PROVIDERS as Record, + NOAUTH_PROVIDERS: NOAUTH_PROVIDERS as Record, + LOCAL_PROVIDERS: LOCAL_PROVIDERS as Record, + SEARCH_PROVIDERS: SEARCH_PROVIDERS as Record, + AUDIO_ONLY_PROVIDERS: AUDIO_ONLY_PROVIDERS as Record, + UPSTREAM_PROXY_PROVIDERS: UPSTREAM_PROXY_PROVIDERS as Record, + CLOUD_AGENT_PROVIDERS: CLOUD_AGENT_PROVIDERS as Record, + SYSTEM_PROVIDERS: SYSTEM_PROVIDERS as Record, +}; + +function walk(dir: string): string[] { + const out: string[] = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) out.push(...walk(full)); + else if (/\.(ts|tsx|mts|json)$/.test(entry.name)) out.push(full); + } + return out; +} + +test("hackclub is absent from every canonical provider catalog", () => { + for (const [name, catalog] of Object.entries(CATALOGS)) { + assert.equal( + "hackclub" in catalog, + false, + `${name} still contains a hackclub entry (#11176)` + ); + } +}); + +test("no provider in any catalog claims the `hc` alias (it belonged to hackclub)", () => { + const holders: string[] = []; + for (const [name, catalog] of Object.entries(CATALOGS)) { + for (const [key, p] of Object.entries(catalog)) { + if (p?.alias === "hc" || key === "hc") holders.push(`${name}:${key}`); + } + } + assert.deepEqual(holders, [], `alias "hc" still claimed by: ${holders.join(", ")}`); +}); + +test("no hackclub mention survives in the provider/catalog source trees", () => { + const scopedDirs = [ + path.join(ROOT, "src", "shared", "constants", "providers"), + path.join(ROOT, "open-sse", "config"), + ]; + const offenders: string[] = []; + for (const dir of scopedDirs) { + for (const file of walk(dir)) { + if (/hack\s*club|hackclub/i.test(fs.readFileSync(file, "utf8"))) { + offenders.push(path.relative(ROOT, file)); + } + } + } + assert.deepEqual( + offenders, + [], + `hackclub mentions left in catalog sources: ${offenders.join(", ")}` + ); +}); + +test("hackclub icon registration and shipped SVG asset are gone", () => { + const iconSource = fs.readFileSync( + path.join(ROOT, "src", "shared", "components", "ProviderIcon.tsx"), + "utf8" + ); + assert.equal( + /hackclub/i.test(iconSource), + false, + "ProviderIcon.tsx still registers hackclub (#11176)" + ); + assert.equal( + fs.existsSync(path.join(ROOT, "public", "providers", "hackclub.svg")), + false, + "public/providers/hackclub.svg still shipped (#11176)" + ); +}); + +test("onboarding i18n description for hackclub is gone from every locale", () => { + const messagesDir = path.join(ROOT, "src", "i18n", "messages"); + const offenders: string[] = []; + for (const file of fs.readdirSync(messagesDir)) { + if (!file.endsWith(".json")) continue; + const messages = JSON.parse(fs.readFileSync(path.join(messagesDir, file), "utf8")); + const descriptions = messages?.providers?.onboardingProviderDescriptions; + if (descriptions && "hackclub" in descriptions) offenders.push(file); + } + assert.deepEqual( + offenders, + [], + `onboardingProviderDescriptions.hackclub still present in: ${offenders.join(", ")}` + ); +}); diff --git a/tests/unit/provider-alias-uniqueness.test.ts b/tests/unit/provider-alias-uniqueness.test.ts index 7ddeec507e..8ed41a601e 100644 --- a/tests/unit/provider-alias-uniqueness.test.ts +++ b/tests/unit/provider-alias-uniqueness.test.ts @@ -5,7 +5,8 @@ * iteration order silently won, emitting a startup warning and shadowing a real * provider: * - "kimi" → kimi-web (shadowed the kimi provider that gained a dedicated executor) - * - "hc" → hackclub (shadowed huggingchat) + * - "hc" → the provider that held it shadowed huggingchat (it was later + * removed entirely, #11176; huggingchat keeps its own id as alias) * * The decision: the primary provider keeps the short alias; the web/secondary * variant takes its own id as alias. This test pins both the global uniqueness diff --git a/tests/unit/provider-registry-freetheai.test.ts b/tests/unit/provider-registry-freetheai.test.ts index b1e973fb9d..2df8061a4f 100644 --- a/tests/unit/provider-registry-freetheai.test.ts +++ b/tests/unit/provider-registry-freetheai.test.ts @@ -3,7 +3,7 @@ * (free tier via Discord signup). * * Verifies the new provider is wired end-to-end the same way as the other - * aggregator/gateway providers (hackclub, chutes, glhf, ...): + * aggregator/gateway providers (chutes, glhf, ...): * - present in the executor REGISTRY with an OpenAI-compatible shape * - resolvable through getExecutor() (falls through to DefaultExecutor, * same as every other `executor: "default"` registry entry) @@ -41,7 +41,7 @@ test("#6670 freetheai resolves through getExecutor() as a DefaultExecutor instan test("#6670 freetheai is classified as an aggregator/gateway provider", () => { assert.ok( AGGREGATOR_PROVIDER_IDS.has("freetheai"), - "freetheai must be listed in AGGREGATOR_PROVIDER_IDS alongside hackclub/chutes/etc" + "freetheai must be listed in AGGREGATOR_PROVIDER_IDS alongside chutes/etc" ); }); diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index e22b3fd4e8..50079fad5c 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -26,6 +26,7 @@ // merge-train batch — independently bumped the gateways family too, landing at 231; Freebuff // (gateways, #10531) brings it to 232. #8864 moves uncloseai (gateways family) into // NOAUTH_PROVIDERS, dropping the APIKEY_PROVIDERS count to 231. Logfare (gateways, #10987) brings it back to 232. +// #11176 removes hackclub (gateways family), landing at 231. import { test } from "node:test"; import assert from "node:assert/strict"; @@ -54,12 +55,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 232 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 231 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 232); - assert.equal(new Set(keys).size, 232, "duplicate keys after spread-merge"); + assert.equal(keys.length, 231); + assert.equal(new Set(keys).size, 231, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a - // strict partition (every provider in exactly one), so the sum must be exactly 232. + // strict partition (every provider in exactly one), so the sum must be exactly 231. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -79,7 +80,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 232 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 232, "families must partition all 232 providers"); + assert.equal(famTotal, 231, "families must partition all 231 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => { diff --git a/tests/unit/providers-g4f-batch3.test.ts b/tests/unit/providers-g4f-batch3.test.ts index 98339468d9..a64860bd70 100644 --- a/tests/unit/providers-g4f-batch3.test.ts +++ b/tests/unit/providers-g4f-batch3.test.ts @@ -2,7 +2,7 @@ * Issue #6674 — Add Naga.ac and ChatAnywhere as gpt4free-ecosystem aggregator providers. * * Verifies both providers are wired end-to-end the same way as the other aggregator - * gateway providers (g4f-groq, freetheai, hackclub): + * gateway providers (g4f-groq, freetheai): * - present in the executor REGISTRY with an OpenAI-compatible shape * - resolvable through getExecutor() (falls through to DefaultExecutor) * - listed in AGGREGATOR_PROVIDER_IDS so they show up in the aggregator category From 527da6565d67366474a66c084c922d73017a166f Mon Sep 17 00:00:00 2001 From: Rafa Martins <146174365+rafacpti23@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:42:09 -0300 Subject: [PATCH 20/20] feat: list embeddings from configured providers (#11249) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated: qdrant-routes integration suite 19/19 on the rebased branch, typecheck:core clean. I retargeted the PR from main to release/v3.8.50 and rebased your single commit onto the release tip (authorship untouched) — no content changes. Embedding models now list from configured/credentialed providers via the embedding registry, with vector dimensions in labels and the unconfigured OpenAI fallback removed. Thank you @rafacpti23! --- .../settings/qdrant/embedding-models/route.ts | 74 +++++++++---------- tests/integration/qdrant-routes.test.ts | 27 ++++++- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/app/api/settings/qdrant/embedding-models/route.ts b/src/app/api/settings/qdrant/embedding-models/route.ts index 9683e270f4..a0cd0b3441 100644 --- a/src/app/api/settings/qdrant/embedding-models/route.ts +++ b/src/app/api/settings/qdrant/embedding-models/route.ts @@ -1,20 +1,17 @@ import { NextRequest, NextResponse } from "next/server"; import { isAuthenticated } from "@/shared/utils/apiAuth"; -import { AI_MODELS } from "@/shared/constants/models"; import { getProviderConnections } from "@/lib/db/providers"; +import { getAllEmbeddingModels } from "@omniroute/open-sse/config/embeddingRegistry.ts"; import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts"; type EmbeddingModelOption = { value: string; label: string; + dimensions?: number; }; -function isLikelyEmbeddingModel(provider: string, model: string, name: string): boolean { - const haystack = `${provider}/${model} ${name}`.toLowerCase(); - if (haystack.includes("embedding")) return true; - if (haystack.includes("embed")) return true; - if (haystack.includes("text-embedding")) return true; - return false; +function modelLabel(value: string, name: string, dimensions?: number): string { + return `${value} - ${name}${dimensions ? ` (${dimensions}d)` : ""}`; } export async function GET(request: NextRequest) { @@ -23,24 +20,37 @@ export async function GET(request: NextRequest) { } try { - const options: EmbeddingModelOption[] = AI_MODELS.filter((m: any) => - isLikelyEmbeddingModel(String(m.provider || ""), String(m.model || ""), String(m.name || "")) - ) - .map((m: any) => ({ - value: `${m.provider}/${m.model}`, - label: `${m.provider}/${m.model} - ${m.name}`, + const activeConnections = (await getProviderConnections({ isActive: true })) as Array< + Record + >; + const configuredProviders = new Set( + activeConnections + .filter( + (connection) => + (typeof connection.apiKey === "string" && connection.apiKey.trim().length > 0) || + connection.authType === "oauth" + ) + .map((connection) => String(connection.provider || "")) + .filter(Boolean) + ); + + const options: EmbeddingModelOption[] = getAllEmbeddingModels() + .filter((model) => configuredProviders.has(model.provider)) + .map((model) => ({ + value: model.id, + label: modelLabel(model.id, model.name, model.dimensions), + ...(model.dimensions ? { dimensions: model.dimensions } : {}), })) - .sort((a, b) => a.value.localeCompare(b.value)); // teknik sıralama: ASCII kasıtlı + .sort((a, b) => a.value.localeCompare(b.value)); // Add OpenRouter account models that explicitly support embeddings. try { - const connections = (await getProviderConnections({ - provider: "openrouter", - isActive: true, - })) as Array>; - const apiKey = connections.find( - (c) => typeof c.apiKey === "string" && (c.apiKey as string).trim().length > 0 - )?.apiKey as string | undefined; + const apiKey = activeConnections + .filter((connection) => connection.provider === "openrouter") + .find( + (connection) => + typeof connection.apiKey === "string" && connection.apiKey.trim().length > 0 + )?.apiKey as string | undefined; if (apiKey) { const controller = new AbortController(); @@ -49,9 +59,7 @@ export async function GET(request: NextRequest) { try { res = await fetch("https://openrouter.ai/api/v1/models?output_modalities=embeddings", { method: "GET", - headers: { - Authorization: `Bearer ${apiKey}`, - }, + headers: { Authorization: `Bearer ${apiKey}` }, cache: "no-store", signal: controller.signal, }); @@ -65,11 +73,8 @@ export async function GET(request: NextRequest) { const id = typeof row?.id === "string" ? row.id.trim() : ""; if (!id) continue; const value = `openrouter/${id}`; - if (options.some((o) => o.value === value)) continue; - options.push({ - value, - label: `${value} - ${String(row?.name || id)}`, - }); + if (options.some((option) => option.value === value)) continue; + options.push({ value, label: modelLabel(value, String(row?.name || id)) }); } } } @@ -77,16 +82,7 @@ export async function GET(request: NextRequest) { // Best effort only: keep endpoint fast and resilient. } - // Ensure the default always exists as a safe fallback. - if (!options.some((o) => o.value === "openai/text-embedding-3-small")) { - options.unshift({ - value: "openai/text-embedding-3-small", - label: "openai/text-embedding-3-small - OpenAI Text Embedding 3 Small", - }); - } - - options.sort((a, b) => a.value.localeCompare(b.value)); // teknik sıralama: ASCII kasıtlı - + options.sort((a, b) => a.value.localeCompare(b.value)); return NextResponse.json({ models: options }); } catch (error) { const message = sanitizeErrorMessage(error instanceof Error ? error.message : String(error)); diff --git a/tests/integration/qdrant-routes.test.ts b/tests/integration/qdrant-routes.test.ts index 38de422c19..252c6e505f 100644 --- a/tests/integration/qdrant-routes.test.ts +++ b/tests/integration/qdrant-routes.test.ts @@ -370,10 +370,31 @@ test("GET /api/settings/qdrant/embedding-models — returns models array", async assert.strictEqual(res.status, 200); const body = await res.json(); assert.ok(Array.isArray(body.models), "should have models array"); - // Should have at least the default fallback model - assert.ok(body.models.length > 0, "should have at least one model"); + assert.strictEqual(body.models.length, 0, "should not list models without a configured provider"); +}); + +test("GET /api/settings/qdrant/embedding-models — lists only configured providers", async () => { + await localDb.createProviderConnection({ + provider: "openai", + authType: "apikey", + name: "embedding-test-openai", + apiKey: "sk-test-embedding", + }); + + const headers = await createManagementSessionHeaders(); + const req = new Request("http://localhost/api/settings/qdrant/embedding-models", { + method: "GET", + headers: Object.fromEntries(headers.entries()), + }); + + const res = await qdrantEmbeddingModelsRoute.GET(req as any); + assert.strictEqual(res.status, 200); + const body = await res.json(); + assert.ok(body.models.length > 0, "should list models for configured provider"); + assert.ok(body.models.every((model: any) => model.value.startsWith("openai/"))); + assert.ok(body.models.some((model: any) => model.value === "openai/text-embedding-3-small")); const defaultModel = body.models.find((m: any) => m.value === "openai/text-embedding-3-small"); - assert.ok(defaultModel, "should include openai/text-embedding-3-small as default"); + assert.match(defaultModel.label, /1536d/); }); test("GET /api/settings/qdrant/embedding-models — 401 without auth", async () => {