mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-10 01:02:14 +03:00
Compare commits
21 Commits
maint/cher
...
feat/audio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
689e1e0d87 | ||
|
|
60c0f19289 | ||
|
|
119898a901 | ||
|
|
44eb33a256 | ||
|
|
a83f85b765 | ||
|
|
f5a9484ad2 | ||
|
|
a439a81f49 | ||
|
|
0f106fd0e0 | ||
|
|
be569e55fb | ||
|
|
3bba4e8624 | ||
|
|
824508b222 | ||
|
|
043efcc8fd | ||
|
|
64c204f68c | ||
|
|
ee5f84c168 | ||
|
|
37ef7a7d9b | ||
|
|
f725dac4b2 | ||
|
|
904a54d602 | ||
|
|
1b9cd59740 | ||
|
|
b5c522604f | ||
|
|
1e2ef990e6 | ||
|
|
2dcb5bd422 |
134
.github/workflows/quality.yml
vendored
134
.github/workflows/quality.yml
vendored
@@ -151,6 +151,63 @@ jobs:
|
||||
key: eslint-${{ runner.os }}-${{ hashFiles('eslint.config.mjs', 'eslint.complexity-ratchets.config.mjs', 'config/quality/eslint-suppressions.json', 'package-lock.json') }}
|
||||
restore-keys: |
|
||||
eslint-${{ runner.os }}-
|
||||
- run: npm run check:provider-consistency
|
||||
- run: npm run check:fetch-targets
|
||||
# docs-all / openapi-routes / docs-symbols live in docs-gates (path-filtered).
|
||||
- run: npm run check:deps
|
||||
# #8522: --base-ref mode for PR events — compare against max(frozen, base) so
|
||||
# inherited drift (base already over frozen cap) doesn't red an innocent PR.
|
||||
# workflow_dispatch (no PR base) falls back to absolute comparison.
|
||||
- name: File-size ratchet (base-relative on PR)
|
||||
env:
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
run: |
|
||||
if [ -n "$PR_BASE_SHA" ]; then
|
||||
npm run check:file-size -- --base-ref "$PR_BASE_SHA"
|
||||
else
|
||||
npm run check:file-size
|
||||
fi
|
||||
- run: npm run check:error-helper
|
||||
- run: npm run check:migration-numbering
|
||||
- run: npm run check:public-creds
|
||||
- run: npm run check:db-rules
|
||||
- run: npm run check:known-symbols
|
||||
- run: npm run check:route-guard-membership
|
||||
- run: npm run check:test-discovery
|
||||
- run: npm run check:test-runner-api
|
||||
# Guards tap.testFiles drift: a covering unit test absent from stryker.conf.json
|
||||
# tap.testFiles makes its module's mutants survive on a cold nightly-mutation run,
|
||||
# false-failing the blocking mutationScore ratchet. See check-mutation-test-coverage.mjs.
|
||||
- run: npm run check:mutation-test-coverage
|
||||
- run: npm run check:any-budget:t11
|
||||
# Build-scope guard: fails if worktrees/cruft leak into the tsconfig include
|
||||
# scope (would OOM `next build`). Instant. See incident 2026-06-25 / #5031.
|
||||
- run: npm run check:build-scope
|
||||
# Pack-policy (unexpected-files allowlist) WITHOUT a build — catches a stray file
|
||||
# leaking into the npm tarball (v3.8.36: 6 ops bin/*.sh) per-PR instead of only on
|
||||
# the release PR's heavy Package Artifact job.
|
||||
- run: npm run check:pack-policy
|
||||
# Complexity + cognitive-complexity: ONE ESLint walk (both baselines still
|
||||
# enforced separately by ruleId). Avoids two cold tree walks on fast-path.
|
||||
- run: npm run check:complexity-ratchets
|
||||
# ── G0 (trilho .50): gates do trilho A que faltavam no trilho B ──────────────
|
||||
# The god-file refactor happens in PRs→release/**; without these, the release
|
||||
# rail never sees a new import cycle, dead code, duplication or a security
|
||||
# regression until the release PR to main. Deliberately NOT brought here:
|
||||
# bundle-size (self-skips without a build — this rail's build job is advisory
|
||||
# and uploads nothing, so it would be dead configuration) and the coverage
|
||||
# run (fast-unit already runs the full suite; the coverage ratchet stays on
|
||||
# the main rail via --allow-missing in lint-guard).
|
||||
- run: npm run check:cycles
|
||||
- run: npm run check:lockfile
|
||||
- name: Duplication ratchet
|
||||
run: npm run check:duplication
|
||||
- name: Dead-code ratchet (knip)
|
||||
run: npm run check:dead-code
|
||||
- name: Type coverage ratchet
|
||||
run: npm run check:type-coverage
|
||||
- name: Compression budget ratchet
|
||||
run: npm run check:compression-budget
|
||||
# Security scanners — same hardened install as ci.yml quality-extended
|
||||
# (gh release download = authenticated, 5000 req/hr; curl to api.github.com
|
||||
# is rate-limited to 60/hr and silently no-ops when throttled). The blocking
|
||||
@@ -194,63 +251,30 @@ jobs:
|
||||
"$HOME/.local/bin/osv-scanner" --version || true
|
||||
"$HOME/.local/bin/oasdiff" --version || true
|
||||
zizmor --version || true
|
||||
# Quality gates (all, non-fail-fast) — #8542: replaces 17 bare check:* steps,
|
||||
# 6 G0 gates, 4 ratchet gates, and 3 typecheck steps with a single aggregation
|
||||
# step. Each gate runs in a loop with ::group::; failures are collected and
|
||||
# reported at the end. set -uo pipefail (NOT set -e) so one failing gate does
|
||||
# not abort the job and mask every later gate. Release-added gates are folded
|
||||
# in: open-sse typecheck (#8781) and file-size base-relative mode (#8522).
|
||||
- name: Quality gates (all, non-fail-fast)
|
||||
- name: Secret scan (gitleaks, ratchet, blocking)
|
||||
run: npm run check:secrets -- --ratchet
|
||||
- name: Vulnerability ratchet (osv-scanner, ratchet, blocking)
|
||||
run: npm run check:vuln-ratchet -- --ratchet
|
||||
- name: Workflow lint (actionlint+zizmor, ratchet, blocking)
|
||||
run: npm run check:workflows -- --ratchet
|
||||
# BASE_REF is read by the script from the env (never interpolated into a
|
||||
# shell body) — workflow-injection-safe. actions/checkout fetches remote
|
||||
# refs, not a local branch named github.base_ref, so prefix origin/ or this
|
||||
# gate self-skips every PR with reason=base-unresolved.
|
||||
- name: OpenAPI breaking-change (oasdiff, ratchet, blocking)
|
||||
env:
|
||||
# #8522: base-relative file-size mode on PR events — inherited drift (base
|
||||
# already over frozen cap) must not red an innocent PR. Unset on
|
||||
# workflow_dispatch (no PR base) → absolute comparison.
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
BASE_REF: ${{ github.base_ref && format('origin/{0}', github.base_ref) || '' }}
|
||||
run: |
|
||||
set -uo pipefail
|
||||
gates=(
|
||||
provider-consistency fetch-targets deps file-size error-helper
|
||||
migration-numbering public-creds db-rules known-symbols
|
||||
route-guard-membership test-discovery test-runner-api
|
||||
mutation-test-coverage any-budget:t11 build-scope pack-policy
|
||||
complexity-ratchets
|
||||
cycles lockfile duplication dead-code type-coverage compression-budget
|
||||
# #8781: open-sse workspace typecheck gate — the workspace imports @/ which
|
||||
# escapes to src/ via undeclared path aliases. See check-open-sse-typecheck.mjs.
|
||||
open-sse-typecheck
|
||||
)
|
||||
ratchet_gates=(
|
||||
secrets vuln-ratchet workflows openapi-breaking
|
||||
)
|
||||
failed=()
|
||||
for g in "${gates[@]}"; do
|
||||
echo "::group::check:$g"
|
||||
# #8522: file-size is base-relative on PR events (compare against
|
||||
# max(frozen, base)) so inherited drift doesn't red an innocent PR;
|
||||
# workflow_dispatch (no PR base) falls back to absolute comparison.
|
||||
if [ "$g" = "file-size" ] && [ -n "${PR_BASE_SHA:-}" ]; then
|
||||
npm run "check:$g" -- --base-ref "$PR_BASE_SHA" || failed+=("$g")
|
||||
else
|
||||
npm run "check:$g" || failed+=("$g")
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
for g in "${ratchet_gates[@]}"; do
|
||||
echo "::group::check:$g (ratchet)"
|
||||
npm run "check:$g" -- --ratchet || failed+=("$g")
|
||||
echo "::endgroup::"
|
||||
done
|
||||
echo "::group::typecheck:core"
|
||||
npm run typecheck:core || failed+=("typecheck:core")
|
||||
echo "::endgroup::"
|
||||
echo "::group::check:dashboard-typecheck"
|
||||
npm run check:dashboard-typecheck || failed+=("check:dashboard-typecheck")
|
||||
echo "::endgroup::"
|
||||
if (( ${#failed[@]} )); then
|
||||
printf '::error::%d gate(s) failed: %s\n' "${#failed[@]}" "${failed[*]}"
|
||||
exit 1
|
||||
fi
|
||||
run: npm run check:openapi-breaking -- --ratchet
|
||||
- name: Typecheck (core)
|
||||
run: npm run typecheck:core
|
||||
# #7033: dashboard-scoped typecheck gate — src/app/(dashboard) TSX is not
|
||||
# covered by typecheck:core's curated allowlist. See check-dashboard-typecheck.mjs.
|
||||
- name: Typecheck (dashboard)
|
||||
run: npm run check:dashboard-typecheck
|
||||
# #8781: open-sse workspace typecheck gate — the workspace imports @/ which
|
||||
# escapes to src/ via undeclared path aliases. See check-open-sse-typecheck.mjs.
|
||||
- name: Typecheck (open-sse)
|
||||
run: npm run check:open-sse-typecheck
|
||||
# WS4.2 (v3.8.49 plan): TypeScript 7 native-compiler SHADOW — advisory only.
|
||||
# TS7 went GA 2026-07-08 with 8-12x type-check speedups; its Compiler API only
|
||||
# arrives in 7.1, so typescript-eslint / type-coverage / Stryker stay on 6.x
|
||||
|
||||
@@ -4403,11 +4403,11 @@ export function buildStaticProviderEntry(
|
||||
entry.release_date = raw.release_date;
|
||||
}
|
||||
|
||||
// #9175: OC's `getModel` looks the model up by BARE id — the part after
|
||||
// the first `/` in the user's request — so a dict key with an embedded
|
||||
// provider prefix (`<providerId>/<raw-id>`) is unreachable. Keys are the
|
||||
// raw id verbatim; ids that already contain `/` (e.g. `cc/claude-opus-4-7`)
|
||||
// keep it because the slash is part of the upstream model id itself.
|
||||
// OC's static-catalog reader parses each key on `/` and rejects the
|
||||
// entire provider block if ANY key resolves to a parsed providerID that
|
||||
// has no corresponding provider block. So bare keys (no `/`) MUST be
|
||||
// prefixed with the resolved providerId. Already-prefixed keys
|
||||
// (e.g. `cc/claude-opus-4-7`) are left as-is to avoid double-prefixing.
|
||||
models[raw.id] = entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
|
||||
// Stripped per-model shape: name + cap flags + modalities + (optional)
|
||||
// cost. OC's SDK static schema accepts only `limit.{context,output}` —
|
||||
// `limit.input` is NOT in the SDK shape and gets dropped silently.
|
||||
const claude = entry.models["claude-sonnet-4-6"];
|
||||
const claude = entry.models["opencode-omniroute/claude-sonnet-4-6"];
|
||||
assert.ok(claude, "claude model surfaced");
|
||||
assert.equal(claude.name, "claude-sonnet-4-6");
|
||||
assert.equal(claude.attachment, true);
|
||||
@@ -248,7 +248,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
|
||||
|
||||
// Combo surfaces under bare key + LCD'd
|
||||
// (gemini's reasoning=false → combo reasoning=false).
|
||||
const combo = entry.models["claude-tier"];
|
||||
const combo = entry.models["omniroute/claude-tier"];
|
||||
assert.ok(combo, "combo surfaced under bare key");
|
||||
assert.equal(combo.name, "Claude Tier");
|
||||
assert.equal(combo.reasoning, false, "LCD: any member reasoning=false → combo reasoning=false");
|
||||
@@ -471,10 +471,10 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
|
||||
assert.ok(entry);
|
||||
const ids = Object.keys(entry.models).sort();
|
||||
assert.deepEqual(ids, [
|
||||
"claude-sonnet-4-6",
|
||||
"gemini-3-flash",
|
||||
"opencode-omniroute/claude-sonnet-4-6",
|
||||
"opencode-omniroute/gemini-3-flash",
|
||||
]);
|
||||
assert.equal(entry.models["claude-tier"], undefined, "no combo entry");
|
||||
assert.equal(entry.models["omniroute/claude-tier"], undefined, "no combo entry");
|
||||
assert.ok(
|
||||
logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")),
|
||||
"combos-fetch breadcrumb emitted"
|
||||
@@ -723,7 +723,7 @@ test("buildStaticProviderEntry: stripped per-model shape matches sibling @omniro
|
||||
}
|
||||
|
||||
// Sanity: claude entry has all expected stripped fields.
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
|
||||
assert.equal(typeof claude.name, "string");
|
||||
assert.equal(typeof claude.attachment, "boolean");
|
||||
assert.equal(typeof claude.reasoning, "boolean");
|
||||
@@ -748,8 +748,8 @@ test("buildStaticProviderEntry: hidden combos are excluded", () => {
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
assert.equal(block.models["claude-tier"], undefined);
|
||||
assert.ok(block.models["claude-sonnet-4-6"]);
|
||||
assert.equal(block.models["omniroute/claude-tier"], undefined);
|
||||
assert.ok(block.models["opencode-omniroute/claude-sonnet-4-6"]);
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
@@ -765,7 +765,7 @@ test("buildStaticProviderEntry: emits modalities.input from raw.input_modalities
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
|
||||
assert.deepEqual(claude.modalities?.input, ["text", "image"]);
|
||||
assert.deepEqual(claude.modalities?.output, ["text"]);
|
||||
});
|
||||
@@ -779,7 +779,7 @@ test("buildStaticProviderEntry: never emits limit.input (OC SDK rejects it)", ()
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
|
||||
assert.equal((claude.limit as Record<string, unknown>).input, undefined);
|
||||
assert.equal(typeof claude.limit?.context, "number");
|
||||
assert.equal(typeof claude.limit?.output, "number");
|
||||
@@ -807,7 +807,7 @@ test("buildStaticProviderEntry: emits cost when enrichment carries pricing", ()
|
||||
"sk-test",
|
||||
enrichment
|
||||
);
|
||||
const claude = block.models["claude-sonnet-4-6"];
|
||||
const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
|
||||
assert.equal(claude.cost?.input, 3);
|
||||
assert.equal(claude.cost?.output, 15);
|
||||
assert.equal(claude.cost?.cache_read, 0.3);
|
||||
@@ -828,8 +828,8 @@ test("buildStaticProviderEntry: emits release_date when raw carries it; omits wh
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
assert.equal(block.models["claude-with-date"].release_date, "2026-02-19");
|
||||
assert.equal(block.models["gemini-3-flash"].release_date, undefined);
|
||||
assert.equal(block.models["opencode-omniroute/claude-with-date"].release_date, "2026-02-19");
|
||||
assert.equal(block.models["opencode-omniroute/gemini-3-flash"].release_date, undefined);
|
||||
});
|
||||
|
||||
test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)", () => {
|
||||
@@ -858,7 +858,7 @@ test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)
|
||||
"https://or.example/v1",
|
||||
"sk-test"
|
||||
);
|
||||
const combo = block.models["mixed-tier"];
|
||||
const combo = block.models["omniroute/mixed-tier"];
|
||||
assert.ok(combo, "combo emitted under slug key");
|
||||
// claude has text+image, text-only has text → intersection drops image.
|
||||
assert.deepEqual(combo.modalities?.input, ["text"]);
|
||||
@@ -967,10 +967,10 @@ test("config: enrichment fetched + name overlaid on raw-model entries", async ()
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.ok(entry);
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["gemini-3-flash"].name, "Gemini 3 Flash");
|
||||
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["opencode-omniroute/gemini-3-flash"].name, "Gemini 3 Flash");
|
||||
// Combo names still come from /api/combos — enrichment overlay does NOT touch combos.
|
||||
assert.equal(entry.models["claude-tier"].name, "Claude Tier");
|
||||
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier");
|
||||
assert.equal(enrichmentFetcher.callCount(), 1);
|
||||
});
|
||||
|
||||
@@ -1000,7 +1000,7 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
|
||||
assert.ok(entry);
|
||||
assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag");
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"claude-sonnet-4-6",
|
||||
"raw id retained"
|
||||
);
|
||||
@@ -1027,7 +1027,7 @@ test("config: enrichment fetcher throws → soft-fail (warn + raw-id static cata
|
||||
];
|
||||
assert.ok(entry, "static block still published on enrichment failure");
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"claude-sonnet-4-6",
|
||||
"raw id retained"
|
||||
);
|
||||
@@ -1229,11 +1229,11 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.ok(
|
||||
entry.models["claude-sonnet-4-6"],
|
||||
entry.models["opencode-omniroute/claude-sonnet-4-6"],
|
||||
"stale snapshot hydrated into static block"
|
||||
);
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude Sonnet 4.6 (cached)",
|
||||
"stale enrichment also reused"
|
||||
);
|
||||
@@ -1281,7 +1281,7 @@ test("config: cached rawEnrichment from earlier provider hook is reused (no refe
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
@@ -1332,12 +1332,12 @@ test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-
|
||||
];
|
||||
assert.ok(entry);
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude - Claude Sonnet 4.6"
|
||||
);
|
||||
assert.equal(entry.models["gemini-3-flash"].name, "Gemini - Gemini 3 Flash");
|
||||
assert.equal(entry.models["opencode-omniroute/gemini-3-flash"].name, "Gemini - Gemini 3 Flash");
|
||||
// Combos stay untouched — `Combo: ` prefix already conveys multi-upstream.
|
||||
assert.equal(entry.models["claude-tier"].name, "Claude Tier");
|
||||
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier");
|
||||
});
|
||||
|
||||
test("config: providerTag=false suppresses the suffix", async () => {
|
||||
@@ -1364,7 +1364,7 @@ test("config: providerTag=false suppresses the suffix", async () => {
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(
|
||||
entry.models["claude-sonnet-4-6"].name,
|
||||
entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude Sonnet 4.6",
|
||||
"enriched name kept, provider tag suppressed"
|
||||
);
|
||||
@@ -1396,7 +1396,7 @@ test("config: providerTag falls back to UPPER(alias) when providerDisplayName mi
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
test("config: providerTag skipped entirely when neither providerDisplayName nor providerAlias set", async () => {
|
||||
@@ -1423,7 +1423,7 @@ test("config: providerTag skipped entirely when neither providerDisplayName nor
|
||||
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(entry.models["claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
|
||||
});
|
||||
|
||||
test("config: providerTag is idempotent — second hook call doesn't double-suffix", async () => {
|
||||
@@ -1451,7 +1451,7 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(
|
||||
entryA.models["claude-sonnet-4-6"].name,
|
||||
entryA.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude - Claude Sonnet 4.6"
|
||||
);
|
||||
|
||||
@@ -1462,7 +1462,7 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
|
||||
"opencode-omniroute"
|
||||
];
|
||||
assert.equal(
|
||||
entryB.models["claude-sonnet-4-6"].name,
|
||||
entryB.models["opencode-omniroute/claude-sonnet-4-6"].name,
|
||||
"Claude - Claude Sonnet 4.6"
|
||||
);
|
||||
});
|
||||
@@ -1516,7 +1516,7 @@ test("buildStaticProviderEntry: nested combo-ref context is the bottleneck acros
|
||||
);
|
||||
// Pre-fix: Parent would advertise 200_000 (only raw-big counted).
|
||||
// Post-fix: Parent should advertise 8_000 (TinyCombo bottleneck).
|
||||
const parent = block.models["parent"];
|
||||
const parent = block.models["omniroute/parent"];
|
||||
assert.ok(parent, "Parent combo must be in the static catalog");
|
||||
assert.equal(parent.limit?.context, 8_000);
|
||||
});
|
||||
|
||||
@@ -111,9 +111,7 @@ test("#6859: createOmniRouteProviderHook end-to-end — catalog keys/providerID
|
||||
// `opencode-omniroute`. Confirmed against the issue's own curl repro
|
||||
// (`model: "opencode-omniroute/hermes-smart-stack"` → "No active
|
||||
// credentials for provider: opencode-omniroute").
|
||||
// #9175 tightened this further: OC's `getModel` looks models up by BARE id,
|
||||
// so combo dict keys now carry NO prefix at all (not even `omniroute/`).
|
||||
test("#7976/#9175: buildStaticProviderEntry keys combos by bare slug (no prefix at all — never the OC-gate providerId)", () => {
|
||||
test("#7976: buildStaticProviderEntry keys bare-slug combo ids with the unprefixed omnirouteProviderId (no double OC-gate prefix)", () => {
|
||||
const resolved = resolveOmniRoutePluginOptions({ providerId: "omniroute" });
|
||||
assert.equal(resolved.providerId, "opencode-omniroute");
|
||||
assert.equal(resolved.omnirouteProviderId, "omniroute");
|
||||
@@ -133,7 +131,7 @@ test("#7976/#9175: buildStaticProviderEntry keys combos by bare slug (no prefix
|
||||
"sk-test"
|
||||
);
|
||||
|
||||
assert.deepEqual(Object.keys(block.models), ["hermes-smart-stack"]);
|
||||
assert.deepEqual(Object.keys(block.models), ["omniroute/hermes-smart-stack"]);
|
||||
assert.equal(
|
||||
block.models["opencode-omniroute/hermes-smart-stack"],
|
||||
undefined,
|
||||
|
||||
@@ -52,11 +52,8 @@ export class ServerSupervisor {
|
||||
// silently, so a boot that never becomes ready looked like a dead hang with zero
|
||||
// output even at APP_LOG_LEVEL=debug. Pipe stdout too and buffer it alongside
|
||||
// stderr so a readiness timeout can surface what the child actually printed.
|
||||
// #9156: macOS launchd cannot resolve bare "node" because its PATH is
|
||||
// minimal. Always use process.execPath (the absolute path to the running
|
||||
// Node.js binary) so the supervisor never depends on PATH resolution.
|
||||
this.child = spawn(
|
||||
process.execPath,
|
||||
process.versions.bun ? process.execPath : "node",
|
||||
process.versions.bun
|
||||
? [this.serverPath]
|
||||
: buildNodeRuntimeArgs(process.env, this.memoryLimit, this.serverPath),
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- test(combo): guard auto/best-free never leaks the combo name as a model (#7754)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(ci): aggregate all fast-gates into non-fail-fast loop so one red gate no longer masks later gates (#8542)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(build): include better-sqlite3 prebuilds in standalone bun bundle
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(quota):** Deleting a quota pool now removes its scoped managed combos without racing in-flight pool mutations ([#8906](https://github.com/diegosouzapw/OmniRoute/pull/8906)) — thanks @xiaoyaner0201
|
||||
@@ -1 +0,0 @@
|
||||
- fix(github): add targetFormat to GPT-5.6 Sol/Terra/Luna models (#8951)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(translator):** the Responses-to-Chat promotion path called `normalizeResponsesReasoningEffort` without the model argument, so GPT-5.6 Sol/Terra/Luna requests with `reasoning.effort: "max""` were downgraded to `"xhigh"`. The model is now threaded through, preserving `max` for GPT-5.6 while keeping the legacy downgrade for older models ([#8997](https://github.com/diegosouzapw/OmniRoute/pull/8997))
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): use configured prefix instead of raw node UUID for alias-backed model id in /v1/models (#9034)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): auto/* routing aliases bypass API-key allowedConnections/disableNonPublicModels (#9057)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): admit audio-speech/audio-transcriptions apiType in audio route provider-node filters (#9096)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): resolve combo names in audio transcriptions route so /v1/models stays honest (#9134)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(vscode): allow built-in auto-routing models in VS Code model filter (#9140)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(background): detect Anthropic top-level system prompts for background task detection (#9142)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(cli): use process.execPath for macOS launchd autostart
|
||||
@@ -1 +0,0 @@
|
||||
- fix(model-discovery): ingest capabilities.effort_tiers for synced models (#9160)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(translator): buffer and normalize upstream tool-call argument deltas so optional null values are stripped before reaching the client (#9168)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(translator): avoid double-normalizing tool names in Gemini-to-Claude response path (#9177)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(classify429):** add missing `have exhausted their quota` pattern so the synthetic 429 from auth.ts is recognized as quota exhaustion, preventing the combo loop from burning retries against the same provider instead of falling back to a healthy one ([#9269](https://github.com/diegosouzapw/OmniRoute/issues/9269))
|
||||
@@ -1 +0,0 @@
|
||||
- fix(sse): broaden OMNIROUTE_SSE_COMMENTS to accept 'false','0','no' and gate metadata comment emission (#9305)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(lmarena): encode SSE stream chunks as Uint8Array to prevent TextDecoder TypeError (#9306)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): classify 400 out of extra usage as quota_exhausted for Anthropic OAuth
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(docker):** standalone co-location now completes packages Next's file tracing materialized partially (package.json without its `main` payload) — unblocks the Docker Hub publish that failed on every v3.8.50 push with `Cannot find module '@atjsh/llmlingua-2/dist/index.js'` ([#9615](https://github.com/diegosouzapw/OmniRoute/pull/9615))
|
||||
@@ -1 +0,0 @@
|
||||
- fix(resilience): failed connection test now sets a short cooldown so connections recover after transient outages (#9623)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(db): wire telemetry cleanup scheduler in Next.js startup path (#9624)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(db): align domain_cost_history cleanup cutoff with millisecond column (#9625)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(playground): surface provider model loading errors and offer retry (#9626)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(build): add build-next-isolated.mjs sibling imports to package.json files array
|
||||
@@ -1 +0,0 @@
|
||||
- Fixed `GET`/`PUT`/`DELETE /api/memory/[id]` always failing with a 500 (`Primary backend "sqlite" not registered`) when the route was reached before any other memory endpoint in the same process.
|
||||
@@ -1 +0,0 @@
|
||||
- Replaced hand-rolled body type checks with Zod validation in the plugins marketplace install route and the three Dario admin routes, restoring the `t06:route-validation` gate (Hard Rule #7).
|
||||
@@ -1 +0,0 @@
|
||||
- **test(cli):** OpenCode plugin suite realigned to the bare-key static-catalog contract from #9178/#9175 (21 tests were red on every opencode-plugin CI run; 287/287 after) ([#9614](https://github.com/diegosouzapw/OmniRoute/pull/9614))
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"_rebaseline_2026_08_09_9296_adobe_media_capabilities": "PR #9296 (artickc, fix/adobe-firefly-model-capabilities) own growth: src/app/api/v1/models/catalog.ts 1590->1597 (+7). The image and video catalog serializers now expose the already-normalized Adobe Firefly discovery capability data (media_capabilities, plus the existing video modality/size fields) at their only response-emission chokepoints. The discovery parser and capability normalization remain in open-sse/services/adobeFireflyModels.ts; extracting these seven serialization fields would obscure the catalog contract. Covered by tests/unit/adobe-firefly.test.ts and tests/unit/image-upscale.test.ts.",
|
||||
"_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.",
|
||||
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
|
||||
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
|
||||
"_rebaseline_2026_07_24_8470_hyperagent_sticky_thread": "PR #8470 (artickc, fix/hyperagent-tool-loop-thread-sticky) own growth: open-sse/executors/hyperagent.ts 936->1025 (wc -l; check-file-size.mjs counts via split(\"\\n\").length so the gate sees 937->1026, +89, crosses the 1000 cap). Fixes a real bug where a reverse-conversion proxy (text-Intent/JSON to Claude Code native tool_calls) rewrites assistant messages between agentic tool-loop turns, breaking HyperAgent’s conversation-prefix fingerprint and cold-starting the thread mid tool-loop. Adds Anthropic tool_use/tool_result flattening to extractMessageText() plus a new rootUserFingerprint()/root-key lookup tier in resolveHyperAgentThreadBinding()/storeHyperAgentThreadAfterTurn() so the thread stays sticky across the tool loop. Cohesive additions inside the existing single-file executor; not extractable without splitting the executor mid-request-flow. Covered by tests/unit/executor-hyperagent.test.ts (19/19, +5 new cases for tool_result/tool_use flattening + root-key stickiness). Pre-merge review flagged a cross-conversation root-key collision risk (tracked in the PR’s own mandatory pre-merge checklist, not yet addressed) — unrelated to this file-size ratchet, tracked separately by /fix-prs.",
|
||||
"_rebaseline_2026_07_25_8494_capability_filter_fail_closed": "PR #8494 (fix/capability-filters-fail-closed, #8488) own growth: open-sse/services/combo.ts 3640->3693 (+53) adds a fail-closed guard after filterTargetsByRequestCompatibility() — when every eligible target is excluded by request-capability filtering (vision/tools/etc) instead of quota/health, the combo now returns an explicit `capability_mismatch` 400 (describeCapabilityFilterExhaustion, imported from combo/comboStructure.ts) rather than silently falling through to a generic no-targets error, plus a `compatFilterFailOpen` escape hatch (combo config OR settings) mirrored at both the main/auto and round-robin call sites for symmetry. combo/comboStructure.ts (previously under cap, un-frozen) grows 794->918 (+124) — new home for describeCapabilityFilterExhaustion + providerSupportsEmulatedToolCalling (#5240 emulated tool-calling exemption so fail-closed does not regress prompt-emulation-only combos like all-chatgpt-web). Irreducible orchestration wiring at the existing filter chokepoint (same precedent as #7301's universal-cooldown-retry generalization). Companion test tests/unit/combo-routing-engine.test.ts 3409->3449 (+40, fail-closed/fail-open coverage across both call sites) also rebaselined. Covered by tests/unit/8488-capability-filter-fail-closed.test.ts (new) + 95/95 passing across both files. Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline — not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
|
||||
@@ -14,6 +16,7 @@
|
||||
"_rebaseline_2026_07_19_7546_ghe_copilot_route": "PR #7546 (GHE Copilot OAuth provider) own growth: oauth/[provider]/[action]/route.ts 960->963 (gate units, +3 = ghe-copilot device-code wiring at the existing multi-provider device-code branch — reading + HTTPS-validating the gheUrl search param (isValidGheUrl guards at both raw entry points, security-review hardening, 963->970), adding ghe-copilot to the no-PKCE provider set, and building the provider config override / threading gheUrl through poll->postExchange extraData). Mirrors the existing kiro/amazon-q startUrl override pattern right above it in the same branch; cohesive with the existing device-code dispatch chokepoint, not separately extractable without splitting a single provider-switch mid-branch. Frozen so can only shrink; structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_6846_nvidia_concurrency_gate": "Issue #6846 Phase 1 (nvidia NIM local RPM budget + per-model lockout + per-connection concurrency cap) own growth: open-sse/executors/default.ts 877->890 (+13 = the irreducible call-site wiring at DefaultExecutor.execute(), the only place nvidia requests dispatch through — the existing session-pool body was extracted verbatim into a new private executeWithSessionPool() so the outer execute() can wrap it in the nvidia concurrency-gate acquire/finally-release). All actual gating logic (semaphore key + cap resolution) lives in the new leaf open-sse/executors/default/nvidiaConcurrencyGate.ts (not frozen, well under cap). Covered by tests/unit/nvidia-quota-phase1.test.ts.",
|
||||
"_rebaseline_2026_07_18_v3849_provider_detail_wiring": "Merge campaign R2/R3 (2026-07-18): three authorized PRs each add irreducible call-site wiring to ProviderDetailPageClient.tsx — #7360 +5 (ProviderQuotaVisibilityToggle render, component extracted), #7419 +4 (NoAuthProviderControls wiring), #7062 +3 (Dahl provider hook) = 786->798. All three follow the extracted-component pattern (AgentrouterConsoleFields precedent); the frozen file only takes the wiring. Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_25_dario_upstream_proxy_selector": "PR #8523 (Dario embedded service): upstream-proxy mode selector replaces the binary CLIProxyAPI toggle with Native/CLIProxyAPI/Dario/Fallback + a fallback-backend picker. ProviderDetailPageClient.tsx 798->804 (+6, new hook fields threaded through to ConnectionsListPanel), ConnectionRow.tsx 942->958 (+16, the mode <select> + conditional fallback-backend <select> replacing a single pill button), useProviderConnections.ts 954->986 (+32, upstreamProxyMode/upstreamProxyFallbackBackend state + handleSetUpstreamProxyMode, handleToggleCliproxyapiMode kept as a thin backward-compat wrapper for the existing hook-shape test). All additive UI/state for the new modes — no unrelated refactor.",
|
||||
"_rebaseline_2026_07_18_pr7653_chat_tracker_import": "PR #7653 merge-interaction growth: release moved chat.ts to its 1796 cap while this PR adds the single side-effect import 'quotaTrackersBatch.ts' (line 130) — chat.ts IS the canonical quota-fetcher registration point (codex/bailian/deepseek/openrouter/opencode/generic all import+register there), so the +1 is irreducible call-site wiring. 1796->1797. Covered by tests/unit/{agentrouter,v0,freemodel}-quota-fetcher.test.ts.",
|
||||
"_rebaseline_2026_07_17_pr7653_agentrouter_console_fields": "PR #7653 own growth (missing acceptance criterion: the AgentRouter quota tracker (#6850) read providerSpecificData.consoleApiKey/newApiUserId but neither field had dashboard UI for provider agentrouter — consoleApiKey was gated to bailian-coding-plan only and newApiUserId had zero UI). AddApiKeyModal.tsx 961->967 (+6) and EditConnectionModal.tsx 1278->1286 (+8) = import + a single <AgentrouterConsoleFields .../> render call plus the newApiUserId formData init field. The actual Input rendering (both consoleApiKey reuse + the new newApiUserId field) was EXTRACTED into a new leaf src/app/(dashboard)/dashboard/providers/[id]/components/modals/AgentrouterConsoleFields.tsx (48 LOC, <cap), mirroring the QuotaScrapingFields.tsx / GlmTeamQuotaFields.tsx precedent (#6351) so the frozen modals only carry the irreducible call-site wiring. Persist logic lives in connectionProviderSpecificData.ts (not frozen). Covered by tests/unit/agentrouter-connection-modal-fields.test.ts.",
|
||||
"_rebaseline_2026_07_17_v3849_6842_free_window_wiring": "PR #7651 (openrouter :free-window quota tracking) follow-up: the counter shipped built but never wired into the request pipeline, so combos kept spending guaranteed-429 requests on exhausted free-tier targets. Own growth: src/sse/services/auth.ts 2461->2462 (+1, irreducible at the existing model-aware preflight chokepoint — the `provider === \"codex\"` check that forwards requestedModel into the connection arg is extended to also cover `openrouter`, one added boolean + a doc comment, offset to a single net line by dropping the now-redundant inline condition). Enforcement itself lives in open-sse/services/openrouterQuotaFetcher.ts (not frozen) and the dispatch-time record/correct hooks live in open-sse/executors/base.ts (not frozen). Covered by tests/unit/openrouter-free-window-wiring-6842.test.ts.",
|
||||
@@ -158,8 +161,134 @@
|
||||
"_rebaseline_2026_06_20_1409_1294_models": "Re-baseline src/lib/db/models.ts 1184->1221: combined growth of sibling fixes #1409 (cascade-delete orphaned model aliases when a provider is removed) + #1294 (persist max_input_tokens/max_output_tokens on custom models), both adding CRUD at the existing models domain module. Cohesive db module; not extractable.",
|
||||
"_rebaseline_2026_06_20_4389_thinking_toolchoice": "Re-baseline base.ts 1387->1399 (#4389): tool_choice-forced thinking guard at the existing Claude wire-image injection chokepoint (effThinking gate avoids the Anthropic 400 when tool_choice forces a tool). Cohesive guard; structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_18_6979_codex_test": "PR #6979 own growth: executor-codex.test.ts 1340->1347 (+7 = generalized ensureThinkingBudget assertion added to the existing codex thinking-budget cases). antigravity-test bump 942->977 REVERTED here: #7408's test split dropped that file to 888, so this PR's +35 fits under the original 942 frozen cap.",
|
||||
"_rebaseline_2026_07_24_8354_logs_timeline_sidebar": "PR #8354 (hartmark, feature/scrolling-log) own growth: src/shared/constants/sidebarVisibility/sections.ts 812->820 (+8, the single new logs-timeline SidebarItemDefinition entry added to LOGS_GROUP.items for the new /dashboard/logs/timeline scrolling request-timeline page). Irreducible data-literal wiring at the existing sidebar-sections chokepoint, same shape as every other item in the file; not extractable without an ad-hoc single-item exception to the file's otherwise-uniform multi-line item style.",
|
||||
"cap": 1000,
|
||||
"frozen": {
|
||||
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
|
||||
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
|
||||
"_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, <cap) and the async orderer orderTargetsByHeadroom is appended to the existing open-sse/services/combo/quotaStrategies.ts (<cap) next to its sibling reset-aware/reset-window orderers (reuses their connection-expansion machinery). headroom = 1 - max(util_5h, util_7d) from getSaturation (src/lib/quota/saturationSignals.ts), prefers the connection with the most free capacity. Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. fill-first stays default; all existing strategies untouched. Covered by tests/unit/combo-headroom-ranking.test.ts (pure helper) + tests/unit/combo-headroom-strategy.test.ts (orderer, saturation injected). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_quota_share_strategy": "Dedicated quota-share strategy (Phase 3 #9): combo.ts 3180->3190 (+10 = one new `else if (strategy === \"quota-share\")` dispatch branch in handleComboChat that delegates 100% to selectQuotaShareTarget + its log line, plus the import). All the new logic lives OUT of the god-file in two new leaves under open-sse/services/combo/: quotaShareInflight.ts (in-flight counter with TTL/lease, ~150 LOC <cap) and quotaShareStrategy.ts (per-model bucket gating via isBucketSaturated + DRR proportional to weight + P2C over in-flight, ~240 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the headroom/reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. ZERO existing strategy cases were modified — only this branch was added, and the qtSd/ combos switched from fill-first to quota-share in src/lib/quota/quotaCombos.ts. Covered by tests/unit/quota-share-strategy.test.ts (gating, DRR fairness, P2C in-flight, fail-open, activation). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_task_aware_routing": "Task-aware routing strategy (port PR #2045, OmniRoute #4945): combo.ts 3190->3225 (+35) = one new `else if (strategy === \"task-aware\")` dispatch branch delegating 100% to selectTaskAwareTarget + its imports/log lines. All scoring/classification logic lives OUT of the god-file in the new leaf open-sse/services/taskAwareRouting.ts (553 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors quota-share/headroom/reset-aware branches). ZERO existing strategy cases modified. Covered by tests/unit/combo-task-aware.test.ts (35 tests). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_26_fidelity_gate_extraction": "Milestone-B fidelity-gate wiring residual: bodyToText+gateAdvance extracted to fidelityGateStep.ts (889->854, -35), but the StackOptions.fidelityGate field, the `const fidelityGate` reads at the two stacked-loop dispatch chokepoints, and the import of FidelityGateConfig are irreducible wiring that cannot leave strategySelector without an architectural refactor of the pre-existing stacked pipeline. Net: 889->854 (+6 vs the pre-Milestone-B frozen 848). Covered by tests/unit/compression/*.test.ts (940 pass).",
|
||||
"_rebaseline_2026_06_27_5193_5203_antigravity_oauthmodal": "Antigravity remote-login own growth: OAuthModal.tsx 960->969 (gate units). #5193 (+~4: remote paste instruction shown for all remote incl. Google + its rationale comment) and #5203 (+~5: handleManualSubmit credential-blob branch + button guard; submit logic extracted to oauthBlobSubmit.ts to minimize). Frozen set to the SUM so either merge order passes. Cohesive at the existing manual-submit chokepoint.",
|
||||
"_rebaseline_2026_06_27_5193_antigravity_basered": "Base-red (pre-existing release drift, fast-gate PR->release skips check:file-size): accountFallback.ts 1773->1777 and src/app/api/providers/[id]/test/route.ts 924->940 were already over their frozen caps on release/v3.8.39 independent of any antigravity change. Owner chose to rebaseline (keep the documented issue-reference comments #1846/#1449/#347 etc.) rather than accept the contributor comment-stripping in #5200/#5198. Reverted #5200 to restore the comments; bumped these two frozen caps to the actual base sizes. No logic change.",
|
||||
"_rebaseline_2026_06_28_5237_impersonation_ua_refresh": "PR #5237 (refresh impersonation UAs): grok-web.ts 1871->1873 (+2), muse-spark-web.ts 1284->1302 (+18), perplexity-web.ts 1013->1032 (+19). Net semantic change in each file is a single User-Agent constant (Chrome 147->149 for grok/muse; perplexity kept at Firefox 148 to stay matched with the firefox_148 TLS profile — the contributor's 152 bump was reverted to avoid a UA-vs-JA3 mismatch, #2459). The growth is Prettier reflow that lint-staged unavoidably applies to these grandfathered long-line files the moment they are touched; not extractable. src/sse/services/auth.ts 2336->2401 in the same reconcile is #5222's antigravity-LRU-retry growth that merged via --admin without a baseline bump.",
|
||||
"_rebaseline_2026_06_28_5243_risk_gate_prepass": "PR #5243 (compression risk-gate pre-pass) own growth: open-sse/services/compression/strategySelector.ts 854->899 (+45). The three exported entry points (applyCompression/applyStackedCompression/applyStackedCompressionAsync) become thin wrappers over pure-extracted private bodies (runCompression/runStackedCompression/runStackedCompressionAsync) so the risk-gate mask->run->restore wrapper sits strictly OUTSIDE the per-step loop — a single universal integration point. The wrapper logic itself (resolveRiskGate/withRiskGate) lives in the new riskGate/strategyWrap.ts (<cap); the residual growth is the duplicated thin-wrapper signatures + the extracted bodies' dispatch boundary, guarded by a byte-identical parity test (riskGateIntegration). Default off (DEFAULT_COMPRESSION_CONFIG unchanged). Not extractable without hiding the dispatch boundary, mirroring prior compression rebaselines. Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_06_28_5275_correlation_id_extract": "Extraction of the safe CorrelationId subset of #5275 (hartmark) — request correlation id stored in call_logs (migration 109) and returned via the X-Correlation-Id response header, WITHOUT the combo/resilience or build/lazy-loading changes (those stay in #5275). Own growth: callLogs.ts 975->985 (correlation_id column on CallLogSummaryRow + read/map), usageHistory.ts 983->988 (correlationId metadata normalize), chat.ts 1575->1632 (withCorrelationId response wiring + combo-failure log carrying correlationId), chatHelpers.ts new 811 (withCorrelationId helper + reqId threading; was 791<cap pre-feature). Cohesive request/logging chokepoint wiring; structural shrink of chat.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_29_4038_cas_guard": "PR (#4038) own growth: tokenRefresh.ts 2103->2181 (+78 = the compare-and-swap guard on the refresh persist — runWithCasGuard/getActiveCasGuard AsyncLocalStorage pair mirroring runWithOnPersist, casGuardShouldSkipPersist that rereads the row right before persisting and skips the write when a concurrent writer already rotated the refresh_token past the one presented, plus getCasGuardStats counters). Fixes the sibling-rotation-revert → token-family-revocation storm. Gated behind an active guard (opt-in; no guard => byte-identical). Wiring lives at the two persist chokepoints inside getAccessToken; the comparison reuses wasRefreshTokenRotated from refreshSerializer. Not extractable without splitting the refresh hot path.",
|
||||
"_rebaseline_2026_06_29_5286_memoization": "PR #5286 own growth: strategySelector.ts 899->960 (+61 = the opt-in result-memoization branches in applyCompression/applyCompressionAsync — principal+determinism gate, makeMemoKey lookup/store with model+supportsVision folded into the key, recompute-with-memo-off). Default off (memoizeCompressionResults), so zero behavior change. The memo helpers live in the leaf resultMemo.ts (<cap); the chokepoint wiring here is not extractable. Structural shrink of this hot-path file tracked in #3501.",
|
||||
"_rebaseline_2026_07_01_v3843_release_5609": "Rebaseline v3.8.43 (PR #5609 release reconciliation). DRIFT dos 109 commits do ciclo: 8 god-files existentes cresceram (ApiManagerPageClient 2983->3017, combos/page 4594->4608, AddApiKeyModal 868->869, providerPageHelpers 974->996, chat.ts 1635->1647, auth.ts 2401->2403, batchProcessor 828->915, combo.ts 3368->3387) + 2 novos acima do cap (huggingchat.ts 813, tests web-cookie-providers-new 827) + 4 test files cresceram. Modularizacao deferida (blast-radius mid-release); congelado no estado atual p/ o proximo ciclo ratchetar daqui.",
|
||||
"_rebaseline_2026_07_02_5816_qoder": "PR #5816 (@AgentKiller45, qoder PAT via qodercli): qoderCli.ts 666->989, new-above-cap frozen (owner-approved baseline freeze). The growth is the legitimate PAT job-token exchange + quota parsing CLI transport (the pure-JS Cosy path 500'd on every PAT request); extracting the spawn/parse helpers now would just add indirection to a contributor PR mid-merge. Test frozen also raised for this PR's coverage growth: providers-page-utils.test.ts 1052->1092. Additionally clears an inherited base-red from the already-merged #5933 (codex json_schema->text.format): translator-openai-responses-req.test.ts 1097->1172 (+75 regression tests, no offending branch left). All remain frozen (cannot grow further); release captain's rebaseline-at-release supersedes.",
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dual_auth": "PR #6126 (@hajilok, dual-auth ClinePass) own growth: tokenRefresh.ts 2181->2182 (+1 = a single `case \"clinepass\":` fallthrough label added to the existing `case \"cline\":` in _getAccessTokenInternal's provider switch, so clinepass token refresh dispatches to the already-shared refreshClineToken() instead of silently falling through to the generic OAuth refresh). Irreducible 1-line switch-case wiring at the existing chokepoint; the header-building logic for the same feature was extracted to a new leaf src/shared/utils/clineAuth.ts::buildClinepassHeaders() (well under cap) to avoid growing open-sse/executors/default.ts. Covered by tests/unit/clinepass-provider.test.ts.",
|
||||
"_rebaseline_2026_07_09_6363_kiro_external_idp": "PR #6363 (@artickc, Kiro external IdP) own growth: tokenRefresh.ts 2182->2249 (+67 = the external_idp refresh branch inside refreshKiroToken — standard public-client OAuth2 refresh_token grant against the org IdP tokenEndpoint via buildExternalIdpRefreshParams/isExternalIdpAuthMethod from the new leaf open-sse/services/kiroExternalIdp.ts, with invalid_grant/invalid_client -> unrecoverable_refresh_error mapping). Cohesive addition at the existing refreshKiroToken chokepoint. Covered by tests/unit/kiro-external-idp.test.ts.",
|
||||
"_rebaseline_2026_07_09_6587_kiro_api_key_auth": "PR #6587 (@strangersp) own growth for Kiro long-lived API-key auth, merged onto v3.8.47 tip: openai-to-kiro.ts 890->912 (+22, auth-header selection for API-key-vs-OAuth-token connections), providerLimits.ts 998->1000 (+2, API-key auth-type branch), translator-openai-to-kiro.test.ts 1234->1257 (+23), providers-page-utils.test.ts 1109->1107 (net -2 after merging with parallel release drift; connectionMatchesProviderCard api_key coverage added), provider-validation-specialty.test.ts 2856->2980 (+124 net after merge with parallel release drift; this PR also removed the file's `@typescript-eslint/no-explicit-any` eslint-suppression entry by fixing all `any` usages, adding typed replacements). Cohesive additive feature growth, well tested; not extractable without splitting the existing chokepoints mid-merge.",
|
||||
"_rebaseline_2026_07_09_6678_routing_strategy_9router": "#6678 (SeaXen) — 9router-parity Routing Strategy settings card + per-provider/combo sticky-round-robin override. Own growth: ProviderDetailPageClient.tsx 784->786 (single ProviderAccountRoutingCard mount + import), auth.ts 2448->2458 (providerStrategies override resolution: fallbackStrategy/stickyRoundRobinLimit per-provider cascade in getProviderCredentials). Both additive, zero unrelated refactor; new UI/logic lives in new files (ProviderAccountRoutingCard.tsx, RoutingStrategyCard.tsx, rrState.ts::resolveComboStickyRoundRobinLimit). chat.ts value below reflects the current release tip (grown by other concurrent PRs, e.g. #6640), not this PR own change.",
|
||||
"_rebaseline_2026_07_10_6318_omp_letta": "PR #6318 (@hamsa0x7, omp+letta CLI integrations) own growth: cliTools.ts (+53 = 2 registry entries incl. omp docsUrl) and cliRuntime.ts (+18 = runtime-detection wiring for the 2 new tools). Cohesive registry/wiring growth at the existing chokepoints; scope reduced from the original 5 tools (pi/codewhale/jcode shipped separately).",
|
||||
"_rebaseline_2026_07_10_gcf_v3_2_decode": "PR #6838 own growth: new vendored file open-sse/services/compression/engines/headroom/gcf/decode_generic.ts frozen at 880 (> 800 cap). It is the vendored GCF generic-profile decoder (spec v3.2 nested flattening plus the prototype-pollution / hasOwnProperty hardening added in this PR's Gemini review). Kept as one file faithful to upstream gcf-typescript so re-vendoring stays a clean copy rather than a re-split each cycle (sibling generic.ts/scalar.ts stay < cap; extraction would also fragment the file's frozen eslint no-explicit-any suppressions). Round-trip + prototype-pollution regression coverage in tests/unit/compression/headroom-smartcrusher.test.ts. Frozen: only shrinks from here.",
|
||||
"_rebaseline_2026_07_12_v3847_mergeprs_tail": "v3.8.47 /merge-prs tail (owner-approved): src/lib/localDb.ts NEW>800 (799->805, +6 re-exports countFreeProxies + recordFreeProxySyncErrors/clearFreeProxySyncErrors/getFreeProxySyncErrors + FreeProxySyncErrors type for #6909 free-pool relay-repair; re-export-only per Hard Rule #2, not extractable).",
|
||||
"_rebaseline_2026_07_15_7070_combos_memo": "PR #7070 (perf/p1-memo) own growth: src/app/(dashboard)/dashboard/combos/page.tsx 4655->4656 (+1 = React.memo wrapping of ComboCard). Covered by tests/unit/ui/combos-page-smoke.test.tsx.",
|
||||
"_rebaseline_2026_07_18_7399_xai_oauth_modal": "PR #7399 (xAI OAuth PKCE) own growth: OAuthModal.tsx 993->998 (+5 = provider entry + PKCE flow branch wiring at the existing provider-switch chokepoint; the provider logic itself lives in src/lib/oauth/providers/xai-oauth.ts, new leaf). Third irreducible wiring bump on this modal (969->989->993->998); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_6636_codex_session_json": "#6636 own growth: OAuthModal.tsx 998->1030 (gate units, split(\"\\n\").length incl. trailing newline; +32 = session-JSON paste branch for handleManualSubmit plus a shared submitCodexAccessToken() helper extracted from the pre-existing bare-JWT branch, mirroring the #5203 oauthBlobSubmit.ts extraction precedent; the normalizer logic itself lives in the new src/lib/oauth/utils/codexSessionImport.ts leaf module, not here). Fourth irreducible wiring bump on this modal (969->989->993->998->1030); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_7546_ghe_copilot_modal": "PR #7546 (GHE Copilot OAuth provider) own growth: OAuthModal.tsx 1030->1056 (gate units). Adds a gheUrl input state, routes ghe-copilot through the existing device-code branch, and threads gheUrl into the device-code request/poll extraData at the existing provider-switch chokepoints (+~24 lines, cohesive with the same pattern as #7399/#6636). The standalone GHE enterprise-URL config step JSX (originally +31 lines inline) was extracted to the new src/shared/components/oauthModal/GheConfigStep.tsx leaf component to minimize the bump; what remains is the irreducible provider-branch wiring. Fifth bump on this modal (969->989->993->998->1030->1056); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_7787_ic2_localdb_reexports": "PR #7787 (IC2 raw connections cache + lazy-decrypt) own growth: localDb.ts 805->807 (gate units, +2). localDb.ts is the re-export-only layer (hard rule #2 — no logic); the PR adds 4 new db/readCache re-exports (touchConnectionLastUsed, getCachedRawProviderConnections, getCachedProviderConnectionById, getCachedProviderNodes) required by existing barrel importers. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7779_routingcombo_thread": "PR #7779 own growth: chatHelpers.ts 876->877 (+1, thread routingComboId into executeChatWithBreaker for compression-combo assignment). Frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7819_autocandidateoverrides_reexport": "PR for #7819 (Level 1+2: read-only auto/* candidate transparency + per-API-key exclusions) own growth: localDb.ts 807->808 (+1). Adds a single `export * from \"./db/autoCandidateOverrides\"` barrel re-export (hard rule #2 — no logic) for the new DB module backing per-apiKey candidate exclusions. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_21_8027_grok_cli_auth_json_paste": "PR #8027 (RaviTharuma, fix(grok-cli) #7610) own growth: OAuthModal.tsx 1080->1100 (gate units). Requires the full ~/.grok/auth.json (with refresh_token) on the paste-import path instead of a bare JWT, at the existing paste-token chokepoint (renamed tab label, updated instructions/placeholder, textarea for the auth.json blob, inline error surface). The validation logic itself (parseGrokCliPasteToken, previously an inline ~75-line function) was extracted to the new src/lib/oauth/utils/grokCliAuthJson.ts leaf module — mirroring the #6636/#7546 extraction precedent — so only the irreducible UI wiring remains here. Sixth bump on this modal (969->989->993->998->1030->1056->1100); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_21_8034_compression_exclusions_sidebar": "#8034 (compression exclusions dashboard tab) own growth: sections.ts 796->806 (+10, one new COMPRESSION_CONTEXT_GROUP sidebar item linking /dashboard/compression/exclusions). The file was already 796/800 before this PR (organic growth from prior sidebar entries), so a single new nav item pushed it 6 lines over cap. Freezing at 806 (cannot grow further); the sidebar item array is data, not extractable logic.",
|
||||
"_rebaseline_2026_07_22_7936_namespace_roundtrip": "#7936 (@RCrushMe, Responses-Chat namespace round-trip identity seam) own growth: open-sse/translator/response/openai-responses.ts 1092->1125 (+33) and open-sse/utils/stream.ts 2814->2869 (+55) — threading the namespace-identity seam through the Responses↔Chat translation + stream paths so tool-call namespaces survive the round-trip. Cohesive translation/stream wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8010_codex_responses_engine": "PR #8010 (@JxnLexn) own growth: open-sse/mcp-server/schemas/tools.ts 1497->1505 (+8 = threading the new \"codex-responses\" literal into the compressionConfigureInput strategy/autoTriggerMode Zod enums and setCompressionEngineInput engine enum, mirroring the existing rtk/omniglyph enum entries; no new tool). open-sse/services/compression/strategySelector.ts 1043->1054 (+11 = one new `if (mode === \"codex-responses\")` dispatch branch in runCompression that delegates 100% to the new codexResponsesEngine.apply, mirroring the existing rtk single-mode dispatch, plus threading config.codexResponsesConfig.preserveToolNames into the shared adaptBodyForCompression call at the 3 existing call sites). src/lib/db/compression.ts (untracked, new-file cap 800) 794->845 (+51 = normalizeCodexResponsesConfig, mirroring the existing normalizeRtkConfig normalizer, plus registering \"codex-responses\" in the COMPRESSION_MODES/STACKED_PIPELINE_ENGINE_IDS/SINGLE_MODE_ENGINE sets and the getCompressionSettings load/save switch) — added to the baseline at its current size. All three are cohesive dispatch/normalizer wiring at existing chokepoints (mirroring the prior compression-mode rebaselines #6534/#6556), not extractable without hiding the mode-dispatch boundary. Covered by tests/unit/compression/codex-responses.test.ts (6) + omniglyph-registries.test.ts/types.test.ts (22, updated for the new mode).",
|
||||
"_rebaseline_2026_07_22_8034_compression_exclusions_persistence": "#8034 (compression exclusions) own growth: src/lib/db/compression.ts 845->850 (+5 = threading the new compressionExclusions field through the existing getCompressionSettings/saveCompressionSettings load/save switch over the shared key_value compression namespace — no new table, no raw SQL). Mirrors the prior compression-field rebaselines (#8010 codex-responses normalizer at the same chokepoint); the load/save switch is a single dispatch boundary, not extractable without hiding it. Covered by the PR's 8 node:test + 3 vitest cases.",
|
||||
"_rebaseline_2026_07_22_8050_model_lockout_exact_family": "#8050 (@AndrianBalanescu) own growth: accountFallback.ts 1864->1892 (+28) — exact-vs-family model-lockout scoping (getModelLockKey/isModelLocked/clearModelLock/getModelLockoutInfo) so an Antigravity 404 for one bare model no longer hijacks the whole family cooldown. Cohesive lockout logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8081_reasoning_placeholder_guard": "#8081 (@Dingding-leo) own growth: openai-responses.ts 1125->1137 (+12) restructuring the reasoning-placeholder guard so it skips only the empty content block and still emits finish_reason/tool_calls in the same chunk. Cohesive translator wiring; frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8210_openrouter_midstream_error": "PR #8210 (hartmark, fix/openrouter-midstream-error-surfacing) own growth: open-sse/translator/response/openai-responses.ts 1137->1163 (+26) measured on the merged tip (release 1137 + this PR own growth). Adds a single new branch inside openaiToOpenAIResponsesResponse() that detects an OpenRouter-style mid-stream aggregator error (HTTP 200 SSE chunk with empty choices + a top-level error object) and surfaces it as state.upstreamError instead of silently falling through to the no-op/awaitingTrailingUsage path, which previously masked the failure as a false empty-success completion and skipped combo fallback. Irreducible call-site addition at the existing chunk-dispatch chokepoint (mirrors the Gemini-to-OpenAI translator's #4177 precedent for the same class of upstream error surfacing). Note: this baseline entry does NOT cover the separate pre-existing +11 drift already on the release tip from #8081/#8162 (1125->1136, unrelated reasoning-placeholder-stripping fix merged after this PR branched) — that drift belongs to the maintainer's rebaseline, not this PR.",
|
||||
"_rebaseline_2026_07_22_8211_gemini_malformed_tool_choice": "PR #8211 (hartmark, fix/gemini-malformed-function-call-tool-choice) own growth: open-sse/translator/response/gemini-to-openai.ts 771->821 (+50, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds MALFORMED_FUNCTION_CALL/UNEXPECTED_TOOL_CALL handling inside geminiToOpenAIResponse(): synthesizes a `malformed_tool_call` tool_calls entry so finish_reason normalizes to the standard \"tool_calls\" instead of an unrecognized raw enum value that OpenAI-compatible clients (e.g. OpenClaw) silently ignore, and always synthesizes (rather than skipping when a real tool call already exists) so a malformed attempt alongside a real one in the same turn is not silently discarded. Irreducible cohesive addition at the existing candidate/finishReason translation chokepoint (mirrors the 9router#2462 raw-finish-reason precedent immediately below it in the same function). Covered by the PR's own tests/unit test additions for both the malformed-only and malformed-plus-real-call cases.",
|
||||
"_rebaseline_2026_07_22_8213_chat_abandoned_target_abort": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/sse/handlers/chat.ts 1794->1860 (+66, measured against the PR's own merge-base — the release tip separately carries an unrelated -5 net shrink from #8013's antigravity callable-catalog alignment, which this PR's branch does not include and this entry does not cover). Adds resolveDispatchClientRawRequest(): merges a per-target modelAbortSignal into clientRawRequest.signal (via mergeAbortSignals) so a combo target abandoned by comboTargetTimeoutMs actually observes its own abort and reaches its cleanup path, instead of hanging forever inside withRateLimit/acquireAccountSemaphore and leaking a permanent 'pending' dashboard entry (live incident, log id 1784418258231-14961a). Also wires combo-exhausted rejection logging to capture request body + attempted models via the new rejectedRequestUsage helper. Irreducible additions at the existing chat dispatch chokepoint. Covered by the PR's own combo-config + integration test additions.",
|
||||
"_rebaseline_2026_07_22_8213_combo_cooldown_wait_recording": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/combo.ts 3548->3604 (+56, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Fixes combo cooldown-wait state recording so a bogus 503 is no longer crystallized when the cooldown-wait vars reset every setTry, adds an OpenAI-format SSE error frame path for combo-exhausted rejections (capturing request body + attempted models), and gives an abandoned per-target dispatch its own timeout instead of leaking a permanent 'pending' dashboard entry. Irreducible additions at the existing handleComboChat dispatch/retry chokepoint (mirrors the prior quota-share/headroom/task-aware strategy-branch precedents already frozen in this file). Covered by the PR's own combo-config + Gemini TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_22_8213_gemini_tpm_quota_cooldown_wait": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/accountFallback.ts 1857->1932 on the merged tip (release 1892 incl #8050 +35, plus this PR own growth +40); measured against the PR own merge-base was 1857->1898 (+41 — the release tip separately carries an unrelated +34 from #8050's antigravity 404 model-not-found lockout scoping, which this PR's branch does not include and this entry does not cover). Own growth is the Gemini TPM-ceiling classification + cooldown-wait wiring feeding into the combo cooldown-wait state machine (rate-limit wedge recovery) introduced by this PR's commit series. Irreducible additions at the existing account-fallback/model-lockout chokepoint. Covered by the PR's own gemini-rate-limit-tracker and TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_22_8213_health_unblock_model_cooldowns": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/app/(dashboard)/dashboard/health/page.tsx 1094->1165 (+71, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds handleUnblockAll/handleUnblockOne dashboard actions (DELETE /api/resilience/model-cooldowns) so an operator can manually clear a Gemini TPM-wedge model lockout surfaced by this PR's cooldown-wait fixes, instead of waiting out the ceiling. Irreducible UI wiring at the existing health-page action chokepoint. Covered by the PR's own dashboard/resilience test additions.",
|
||||
"_rebaseline_2026_07_22_8213_requestloggerdetail_unblock_ui": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/shared/components/RequestLoggerDetail.tsx 799->941 (+142, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip; crosses the general 800-line new-file cap so is frozen here for the first time). Adds a collapsible section header (open/expand-less toggle) plus per-log-entry unblock (`unblocking`/`cleared` state, isCombo503 detection) so the request-logger detail panel surfaces the same Gemini TPM cooldown-wait / model-lockout unblock action introduced by this PR at the individual-request level (mirrors the health-page bulk unblock action added in the same PR). Covered by the PR's own dashboard/resilience test additions.",
|
||||
"_rebaseline_2026_07_22_fusion_8013_8098_antigravity": "Fusion of #8013 (backryun, catalog/IDE-CLI-split rewrite) + #8098 (nguyenha935, protocol-fidelity/fail-closed/credits/tool-cloaking): open-sse/services/usage/antigravity.ts NEW 802 (>cap 800, +2 — #8098 credits/tier usage service on #8013's profile-aware headers). Test growth (models-catalog-route 1605->1608, provider-models-route 1752->1757 from #8013 Gemini 3.6 catalog) tracked in testFrozen.",
|
||||
"_rebaseline_2026_07_23_8127_grok_weekly_quota": "#8127 (@apoapostolov) own growth: src/sse/handlers/chat.ts 1861->1865 (+4) — weekly quota tracking for grok-web wires a quota-fetch hook at the existing dispatch chokepoint. Thin wiring mirroring adjacent provider-quota branches; not extractable. Covered by tests/unit/grok-quota-fetcher.test.ts.",
|
||||
"_rebaseline_2026_07_23_8143_empty_catch_logging": "#8143 (@chirag127) own growth: open-sse/utils/stream.ts 2869->2887 (+18) — replacing empty catch blocks in the SSE stream subsystem with console.debug logging (Rule #6 silent-swallow fix, issues #8138-#8142). Cohesive logging additions at the existing catch chokepoints, not extractable; frozen at new size. Covered by tests/unit/stream-handler-catch-logging-8143.test.ts.",
|
||||
"_rebaseline_2026_07_23_8219_cache_ttl_settings_sidebar": "#8219 (@oyi77) own growth: sections.ts 806->813 (+7) — configurable model-catalog cache-TTL settings adds a new sidebar nav entry + its visibility wiring. Sidebar item array is data, not extractable logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_23_8247_8248_model_unhealthy": "#8247+#8248 own growth: accountFallback.ts 1940->1941 (+1, irreducible import statement only — the substantive #8248 DEGRADED-pattern classifier was extracted into open-sse/config/errorConfig.ts, which has ample headroom, instead of growing this frozen file; #8247's fix is a single existing-line condition change, net zero lines). Scoping the credits-exhausted 403/429 branch to isCompatibleProvider() (per-model-quota openai/anthropic-compatible-* nicknames) so it stays model-scoped instead of terminalling the whole connection, and classifying NVIDIA NIM 'Function ... DEGRADED' 400 bodies as model-access-denied instead of a raw passthrough 400. Covered by tests/unit/8247-accountfallback-model-unhealthy.test.ts and tests/unit/8248-accountfallback-nvidia-degraded.test.ts.",
|
||||
"_rebaseline_2026_07_23_8252_combo_400_advance": "#8252 (@RaviTharuma) own growth: accountFallback.ts 1932->1940 (+8) + combo.ts 3604->3630 (+26) — advance combo on model-scoped 400s wrapped as invalid/Bad-Request. Irreducible wiring at existing account-fallback + combo dispatch chokepoints. Covered by combo-model-scoped-400-advance.test.ts.",
|
||||
"_rebaseline_2026_07_23_8266_alibaba_media": "#8266 (@backryun) own growth: imageRegistry.ts 821->979 (+158) — Alibaba-family media models (Qwen image/video, Bailian, Wan) added to the image/video registry. Registry model data, not extractable logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_24_8388_compression_detail_persist": "#8388 (compression engine DETAIL settings — Headroom/session-dedup/CCR — dropped on save) own growth: src/lib/db/compression.ts 866->872 (+6 = irreducible call-site wiring at the existing getCompressionSettings/updateCompressionSettings chokepoint: one import line, one `...buildDetailConfigDefaults()` spread in the seed config, and one `case \"sessionDedup\": case \"ccr\": applyDetailConfigUpdate(config, key, parsed); break;` load-switch case, mirroring the existing headroom/#8056 case immediately above it). The actual normalizer logic (normalizeSessionDedupConfig/normalizeCcrConfig, matching SESSION_DEDUP_SCHEMA/CCR_SCHEMA bounds) was EXTRACTED into a new leaf src/lib/db/compressionDetailNormalizers.ts (well under cap) so this frozen file only carries the minimal dispatch wiring. Covered by tests/unit/8388-compression-detail-persist.test.ts (schema-accept + full DB save->reload round-trip for both new sub-objects, plus a no-regression assertion on the existing headroom round-trip).",
|
||||
"_rebaseline_2026_07_24_responses_toolcalls_log_summary": "hartmark, fix/responses-tool-calls-log-summary own growth: open-sse/translator/response/openai-responses.ts 1163->1174 (+11). closeToolCall() now also writes the completed tool call into the shared state.toolCalls Map (already populated by the openai-to-claude / claude-to-openai / gemini-to-openai response translators) so stream.ts's completion-log summary builder (which reads state.toolCalls, not this translator's own funcCallIds/funcNames/funcArgsBuf bookkeeping) reports finish_reason \"tool_calls\" and message.tool_calls for openai->openai-responses translated streams instead of always logging \"stop\" with no tool_calls — the actual client-facing SSE events were already correct; only the persisted call-log summary was wrong. Irreducible call-site addition at the existing tool-call-close chokepoint. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts.",
|
||||
"_rebaseline_2026_07_25_8476_combo_input_bound_homogeneous_scope": "PR #8476 (herjarsa, fix/8375-8459-combo-image-fixes, #8375) own growth: open-sse/services/combo.ts 3642->3679 (+37 net: +29 the PR's own isInputBoundFailure short-circuit for deterministic context_length_exceeded/context_window_exceeded failures, +8 a /green-prs pre-merge fix scoping that short-circuit to homogeneous remainders only — the shipped code fired unconditionally on ANY target, regressing the intentional heterogeneous-combo fallback #6637/isContextOverflow400 protects, exactly as flagged by this PR's own review evidence but never actually implemented in the branch). The fix compares orderedTargets[i+1..] modelStr against the failing target's modelStr at the existing executeTarget dispatch chokepoint (mirrors the sameProviderNext precedent a few lines below) — irreducible call-site wiring, not extractable without hiding the dispatch boundary. Covered by tests/unit/combo-input-bound-failure-8375.test.ts (homogeneous pool still short-circuits) and the new tests/unit/combo-input-bound-heterogeneous-8375.test.ts (heterogeneous combo now correctly falls through to the larger-context target).",
|
||||
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 — check-file-size.mjs counts LOC via split(\"\\n\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
|
||||
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
|
||||
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
|
||||
"open-sse/executors/antigravity.ts": 1528,
|
||||
"open-sse/executors/base.ts": 1640,
|
||||
"open-sse/executors/chatgpt-web.ts": 3241,
|
||||
"open-sse/executors/codex.ts": 1562,
|
||||
"open-sse/executors/cursor.ts": 1563,
|
||||
"open-sse/executors/deepseek-web.ts": 1148,
|
||||
"open-sse/executors/grok-web.ts": 1044,
|
||||
"open-sse/executors/muse-spark-web.ts": 1405,
|
||||
"open-sse/handlers/chatCore.ts": 5034,
|
||||
"open-sse/handlers/imageGeneration.ts": 3101,
|
||||
"open-sse/handlers/responseSanitizer.ts": 1128,
|
||||
"open-sse/handlers/search.ts": 1536,
|
||||
"open-sse/handlers/videoGeneration.ts": 1063,
|
||||
"open-sse/mcp-server/schemas/tools.ts": 1553,
|
||||
"open-sse/mcp-server/server.ts": 1448,
|
||||
"open-sse/mcp-server/tools/advancedTools.ts": 1120,
|
||||
"open-sse/services/accountFallback.ts": 1978,
|
||||
"open-sse/services/adobeFireflyClient.ts": 2385,
|
||||
"open-sse/services/claudeCodeCompatible.ts": 1202,
|
||||
"open-sse/services/combo.ts": 3648,
|
||||
"open-sse/services/compression/strategySelector.ts": 1060,
|
||||
"open-sse/services/rateLimitManager.ts": 1167,
|
||||
"open-sse/translator/response/openai-responses.ts": 1204,
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": 1505,
|
||||
"open-sse/utils/stream.ts": 2889,
|
||||
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1388,
|
||||
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1031,
|
||||
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3117,
|
||||
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1067,
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": 4703,
|
||||
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1283,
|
||||
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1022,
|
||||
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2615,
|
||||
"src/app/(dashboard)/dashboard/health/page.tsx": 1165,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1324,
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 1944,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
|
||||
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1019,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1470,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1123,
|
||||
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1629,
|
||||
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1573,
|
||||
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1028,
|
||||
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2148,
|
||||
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1119,
|
||||
"src/app/api/providers/[id]/models/route.ts": 2361,
|
||||
"src/app/api/v1/models/catalog.ts": 1590,
|
||||
"src/lib/tokenHealthCheck.ts": 1053,
|
||||
"src/lib/db/apiKeys.ts": 1529,
|
||||
"src/lib/db/core.ts": 1639,
|
||||
"src/lib/db/migrationRunner.ts": 1094,
|
||||
"src/lib/db/models.ts": 1097,
|
||||
"src/lib/db/providers.ts": 1034,
|
||||
"src/lib/memory/retrieval.ts": 1073,
|
||||
"src/lib/tailscaleTunnel.ts": 1202,
|
||||
"src/lib/usage/providerLimits.ts": 1013,
|
||||
"src/shared/components/OAuthModal.tsx": 1134,
|
||||
"src/shared/components/RequestLoggerV2.tsx": 1629,
|
||||
"src/shared/components/analytics/charts.tsx": 1035,
|
||||
"src/shared/services/cliRuntime.ts": 1122,
|
||||
"src/sse/handlers/chat.ts": 1904,
|
||||
"src/sse/services/auth.ts": 2508,
|
||||
"tests/unit/account-fallback-service.test.ts": 1572,
|
||||
"tests/unit/provider-validation-specialty.test.ts": 2985,
|
||||
"open-sse/executors/hyperagent.ts": 1026,
|
||||
"open-sse/executors/default.ts": 1042,
|
||||
"open-sse/executors/kiro.ts": 1069,
|
||||
"open-sse/translator/request/openai-to-kiro.ts": 1057
|
||||
},
|
||||
"testCap": 1000,
|
||||
"testFrozen": {
|
||||
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
|
||||
@@ -284,134 +413,7 @@
|
||||
"_rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). v1 was cap 800->900 / testCap 800->900 on 2026-07-27; v2 = v1 +20% buffer = cap 900->1000 (+100), testCap 900->1000 (+100). Justification: same as complexity v2 — the v3.8.50 release cut coincides with high-merge activity; owner accepted enlarging the headroom to cover the entire PREPARE phase (5 minor cycles .50-.54) without per-PR rebaseline noise. Targets: decompose-existing-frozen unchanged (frozen still only-shrink — see frozen[] entries and the 105 files >900 that still need structural decomposition regardless of cap); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes (gives 150 units of post-tighten headroom vs the new 1000 ceiling). Tracked via same roadmap issue as complexity v2. Window: v3.8.50 (release cut) → v3.8.54 close (RE-TIGHTEN at v3.8.51 prep merge per ROADMAP.md). Last entry unless measured regression. v1 entry retained below for audit trail.",
|
||||
"_rebaseline_2026_07_27_3850_relax_filesize_cap": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). cap 800->900 (+100), testCap 800->900 (+100). Targets: decompose-existing-frozen unchanged (frozen still only-shrink); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes. SUPERSEDED by _rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct (v1 +20% buffer) — retained for audit. Tracked via same roadmap issue.",
|
||||
"_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)",
|
||||
"frozen": {
|
||||
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
|
||||
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
|
||||
"_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, <cap) and the async orderer orderTargetsByHeadroom is appended to the existing open-sse/services/combo/quotaStrategies.ts (<cap) next to its sibling reset-aware/reset-window orderers (reuses their connection-expansion machinery). headroom = 1 - max(util_5h, util_7d) from getSaturation (src/lib/quota/saturationSignals.ts), prefers the connection with the most free capacity. Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. fill-first stays default; all existing strategies untouched. Covered by tests/unit/combo-headroom-ranking.test.ts (pure helper) + tests/unit/combo-headroom-strategy.test.ts (orderer, saturation injected). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_quota_share_strategy": "Dedicated quota-share strategy (Phase 3 #9): combo.ts 3180->3190 (+10 = one new `else if (strategy === \"quota-share\")` dispatch branch in handleComboChat that delegates 100% to selectQuotaShareTarget + its log line, plus the import). All the new logic lives OUT of the god-file in two new leaves under open-sse/services/combo/: quotaShareInflight.ts (in-flight counter with TTL/lease, ~150 LOC <cap) and quotaShareStrategy.ts (per-model bucket gating via isBucketSaturated + DRR proportional to weight + P2C over in-flight, ~240 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the headroom/reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. ZERO existing strategy cases were modified — only this branch was added, and the qtSd/ combos switched from fill-first to quota-share in src/lib/quota/quotaCombos.ts. Covered by tests/unit/quota-share-strategy.test.ts (gating, DRR fairness, P2C in-flight, fail-open, activation). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_task_aware_routing": "Task-aware routing strategy (port PR #2045, OmniRoute #4945): combo.ts 3190->3225 (+35) = one new `else if (strategy === \"task-aware\")` dispatch branch delegating 100% to selectTaskAwareTarget + its imports/log lines. All scoring/classification logic lives OUT of the god-file in the new leaf open-sse/services/taskAwareRouting.ts (553 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors quota-share/headroom/reset-aware branches). ZERO existing strategy cases modified. Covered by tests/unit/combo-task-aware.test.ts (35 tests). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_26_fidelity_gate_extraction": "Milestone-B fidelity-gate wiring residual: bodyToText+gateAdvance extracted to fidelityGateStep.ts (889->854, -35), but the StackOptions.fidelityGate field, the `const fidelityGate` reads at the two stacked-loop dispatch chokepoints, and the import of FidelityGateConfig are irreducible wiring that cannot leave strategySelector without an architectural refactor of the pre-existing stacked pipeline. Net: 889->854 (+6 vs the pre-Milestone-B frozen 848). Covered by tests/unit/compression/*.test.ts (940 pass).",
|
||||
"_rebaseline_2026_06_27_5193_5203_antigravity_oauthmodal": "Antigravity remote-login own growth: OAuthModal.tsx 960->969 (gate units). #5193 (+~4: remote paste instruction shown for all remote incl. Google + its rationale comment) and #5203 (+~5: handleManualSubmit credential-blob branch + button guard; submit logic extracted to oauthBlobSubmit.ts to minimize). Frozen set to the SUM so either merge order passes. Cohesive at the existing manual-submit chokepoint.",
|
||||
"_rebaseline_2026_06_27_5193_antigravity_basered": "Base-red (pre-existing release drift, fast-gate PR->release skips check:file-size): accountFallback.ts 1773->1777 and src/app/api/providers/[id]/test/route.ts 924->940 were already over their frozen caps on release/v3.8.39 independent of any antigravity change. Owner chose to rebaseline (keep the documented issue-reference comments #1846/#1449/#347 etc.) rather than accept the contributor comment-stripping in #5200/#5198. Reverted #5200 to restore the comments; bumped these two frozen caps to the actual base sizes. No logic change.",
|
||||
"_rebaseline_2026_06_28_5237_impersonation_ua_refresh": "PR #5237 (refresh impersonation UAs): grok-web.ts 1871->1873 (+2), muse-spark-web.ts 1284->1302 (+18), perplexity-web.ts 1013->1032 (+19). Net semantic change in each file is a single User-Agent constant (Chrome 147->149 for grok/muse; perplexity kept at Firefox 148 to stay matched with the firefox_148 TLS profile — the contributor's 152 bump was reverted to avoid a UA-vs-JA3 mismatch, #2459). The growth is Prettier reflow that lint-staged unavoidably applies to these grandfathered long-line files the moment they are touched; not extractable. src/sse/services/auth.ts 2336->2401 in the same reconcile is #5222's antigravity-LRU-retry growth that merged via --admin without a baseline bump.",
|
||||
"_rebaseline_2026_06_28_5243_risk_gate_prepass": "PR #5243 (compression risk-gate pre-pass) own growth: open-sse/services/compression/strategySelector.ts 854->899 (+45). The three exported entry points (applyCompression/applyStackedCompression/applyStackedCompressionAsync) become thin wrappers over pure-extracted private bodies (runCompression/runStackedCompression/runStackedCompressionAsync) so the risk-gate mask->run->restore wrapper sits strictly OUTSIDE the per-step loop — a single universal integration point. The wrapper logic itself (resolveRiskGate/withRiskGate) lives in the new riskGate/strategyWrap.ts (<cap); the residual growth is the duplicated thin-wrapper signatures + the extracted bodies' dispatch boundary, guarded by a byte-identical parity test (riskGateIntegration). Default off (DEFAULT_COMPRESSION_CONFIG unchanged). Not extractable without hiding the dispatch boundary, mirroring prior compression rebaselines. Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_06_28_5275_correlation_id_extract": "Extraction of the safe CorrelationId subset of #5275 (hartmark) — request correlation id stored in call_logs (migration 109) and returned via the X-Correlation-Id response header, WITHOUT the combo/resilience or build/lazy-loading changes (those stay in #5275). Own growth: callLogs.ts 975->985 (correlation_id column on CallLogSummaryRow + read/map), usageHistory.ts 983->988 (correlationId metadata normalize), chat.ts 1575->1632 (withCorrelationId response wiring + combo-failure log carrying correlationId), chatHelpers.ts new 811 (withCorrelationId helper + reqId threading; was 791<cap pre-feature). Cohesive request/logging chokepoint wiring; structural shrink of chat.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_29_4038_cas_guard": "PR (#4038) own growth: tokenRefresh.ts 2103->2181 (+78 = the compare-and-swap guard on the refresh persist — runWithCasGuard/getActiveCasGuard AsyncLocalStorage pair mirroring runWithOnPersist, casGuardShouldSkipPersist that rereads the row right before persisting and skips the write when a concurrent writer already rotated the refresh_token past the one presented, plus getCasGuardStats counters). Fixes the sibling-rotation-revert → token-family-revocation storm. Gated behind an active guard (opt-in; no guard => byte-identical). Wiring lives at the two persist chokepoints inside getAccessToken; the comparison reuses wasRefreshTokenRotated from refreshSerializer. Not extractable without splitting the refresh hot path.",
|
||||
"_rebaseline_2026_06_29_5286_memoization": "PR #5286 own growth: strategySelector.ts 899->960 (+61 = the opt-in result-memoization branches in applyCompression/applyCompressionAsync — principal+determinism gate, makeMemoKey lookup/store with model+supportsVision folded into the key, recompute-with-memo-off). Default off (memoizeCompressionResults), so zero behavior change. The memo helpers live in the leaf resultMemo.ts (<cap); the chokepoint wiring here is not extractable. Structural shrink of this hot-path file tracked in #3501.",
|
||||
"_rebaseline_2026_07_01_v3843_release_5609": "Rebaseline v3.8.43 (PR #5609 release reconciliation). DRIFT dos 109 commits do ciclo: 8 god-files existentes cresceram (ApiManagerPageClient 2983->3017, combos/page 4594->4608, AddApiKeyModal 868->869, providerPageHelpers 974->996, chat.ts 1635->1647, auth.ts 2401->2403, batchProcessor 828->915, combo.ts 3368->3387) + 2 novos acima do cap (huggingchat.ts 813, tests web-cookie-providers-new 827) + 4 test files cresceram. Modularizacao deferida (blast-radius mid-release); congelado no estado atual p/ o proximo ciclo ratchetar daqui.",
|
||||
"_rebaseline_2026_07_02_5816_qoder": "PR #5816 (@AgentKiller45, qoder PAT via qodercli): qoderCli.ts 666->989, new-above-cap frozen (owner-approved baseline freeze). The growth is the legitimate PAT job-token exchange + quota parsing CLI transport (the pure-JS Cosy path 500'd on every PAT request); extracting the spawn/parse helpers now would just add indirection to a contributor PR mid-merge. Test frozen also raised for this PR's coverage growth: providers-page-utils.test.ts 1052->1092. Additionally clears an inherited base-red from the already-merged #5933 (codex json_schema->text.format): translator-openai-responses-req.test.ts 1097->1172 (+75 regression tests, no offending branch left). All remain frozen (cannot grow further); release captain's rebaseline-at-release supersedes.",
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dual_auth": "PR #6126 (@hajilok, dual-auth ClinePass) own growth: tokenRefresh.ts 2181->2182 (+1 = a single `case \"clinepass\":` fallthrough label added to the existing `case \"cline\":` in _getAccessTokenInternal's provider switch, so clinepass token refresh dispatches to the already-shared refreshClineToken() instead of silently falling through to the generic OAuth refresh). Irreducible 1-line switch-case wiring at the existing chokepoint; the header-building logic for the same feature was extracted to a new leaf src/shared/utils/clineAuth.ts::buildClinepassHeaders() (well under cap) to avoid growing open-sse/executors/default.ts. Covered by tests/unit/clinepass-provider.test.ts.",
|
||||
"_rebaseline_2026_07_09_6363_kiro_external_idp": "PR #6363 (@artickc, Kiro external IdP) own growth: tokenRefresh.ts 2182->2249 (+67 = the external_idp refresh branch inside refreshKiroToken — standard public-client OAuth2 refresh_token grant against the org IdP tokenEndpoint via buildExternalIdpRefreshParams/isExternalIdpAuthMethod from the new leaf open-sse/services/kiroExternalIdp.ts, with invalid_grant/invalid_client -> unrecoverable_refresh_error mapping). Cohesive addition at the existing refreshKiroToken chokepoint. Covered by tests/unit/kiro-external-idp.test.ts.",
|
||||
"_rebaseline_2026_07_09_6587_kiro_api_key_auth": "PR #6587 (@strangersp) own growth for Kiro long-lived API-key auth, merged onto v3.8.47 tip: openai-to-kiro.ts 890->912 (+22, auth-header selection for API-key-vs-OAuth-token connections), providerLimits.ts 998->1000 (+2, API-key auth-type branch), translator-openai-to-kiro.test.ts 1234->1257 (+23), providers-page-utils.test.ts 1109->1107 (net -2 after merging with parallel release drift; connectionMatchesProviderCard api_key coverage added), provider-validation-specialty.test.ts 2856->2980 (+124 net after merge with parallel release drift; this PR also removed the file's `@typescript-eslint/no-explicit-any` eslint-suppression entry by fixing all `any` usages, adding typed replacements). Cohesive additive feature growth, well tested; not extractable without splitting the existing chokepoints mid-merge.",
|
||||
"_rebaseline_2026_07_09_6678_routing_strategy_9router": "#6678 (SeaXen) — 9router-parity Routing Strategy settings card + per-provider/combo sticky-round-robin override. Own growth: ProviderDetailPageClient.tsx 784->786 (single ProviderAccountRoutingCard mount + import), auth.ts 2448->2458 (providerStrategies override resolution: fallbackStrategy/stickyRoundRobinLimit per-provider cascade in getProviderCredentials). Both additive, zero unrelated refactor; new UI/logic lives in new files (ProviderAccountRoutingCard.tsx, RoutingStrategyCard.tsx, rrState.ts::resolveComboStickyRoundRobinLimit). chat.ts value below reflects the current release tip (grown by other concurrent PRs, e.g. #6640), not this PR own change.",
|
||||
"_rebaseline_2026_07_10_6318_omp_letta": "PR #6318 (@hamsa0x7, omp+letta CLI integrations) own growth: cliTools.ts (+53 = 2 registry entries incl. omp docsUrl) and cliRuntime.ts (+18 = runtime-detection wiring for the 2 new tools). Cohesive registry/wiring growth at the existing chokepoints; scope reduced from the original 5 tools (pi/codewhale/jcode shipped separately).",
|
||||
"_rebaseline_2026_07_10_gcf_v3_2_decode": "PR #6838 own growth: new vendored file open-sse/services/compression/engines/headroom/gcf/decode_generic.ts frozen at 880 (> 800 cap). It is the vendored GCF generic-profile decoder (spec v3.2 nested flattening plus the prototype-pollution / hasOwnProperty hardening added in this PR's Gemini review). Kept as one file faithful to upstream gcf-typescript so re-vendoring stays a clean copy rather than a re-split each cycle (sibling generic.ts/scalar.ts stay < cap; extraction would also fragment the file's frozen eslint no-explicit-any suppressions). Round-trip + prototype-pollution regression coverage in tests/unit/compression/headroom-smartcrusher.test.ts. Frozen: only shrinks from here.",
|
||||
"_rebaseline_2026_07_12_v3847_mergeprs_tail": "v3.8.47 /merge-prs tail (owner-approved): src/lib/localDb.ts NEW>800 (799->805, +6 re-exports countFreeProxies + recordFreeProxySyncErrors/clearFreeProxySyncErrors/getFreeProxySyncErrors + FreeProxySyncErrors type for #6909 free-pool relay-repair; re-export-only per Hard Rule #2, not extractable).",
|
||||
"_rebaseline_2026_07_15_7070_combos_memo": "PR #7070 (perf/p1-memo) own growth: src/app/(dashboard)/dashboard/combos/page.tsx 4655->4656 (+1 = React.memo wrapping of ComboCard). Covered by tests/unit/ui/combos-page-smoke.test.tsx.",
|
||||
"_rebaseline_2026_07_18_7399_xai_oauth_modal": "PR #7399 (xAI OAuth PKCE) own growth: OAuthModal.tsx 993->998 (+5 = provider entry + PKCE flow branch wiring at the existing provider-switch chokepoint; the provider logic itself lives in src/lib/oauth/providers/xai-oauth.ts, new leaf). Third irreducible wiring bump on this modal (969->989->993->998); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_6636_codex_session_json": "#6636 own growth: OAuthModal.tsx 998->1030 (gate units, split(\"\\n\").length incl. trailing newline; +32 = session-JSON paste branch for handleManualSubmit plus a shared submitCodexAccessToken() helper extracted from the pre-existing bare-JWT branch, mirroring the #5203 oauthBlobSubmit.ts extraction precedent; the normalizer logic itself lives in the new src/lib/oauth/utils/codexSessionImport.ts leaf module, not here). Fourth irreducible wiring bump on this modal (969->989->993->998->1030); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_7546_ghe_copilot_modal": "PR #7546 (GHE Copilot OAuth provider) own growth: OAuthModal.tsx 1030->1056 (gate units). Adds a gheUrl input state, routes ghe-copilot through the existing device-code branch, and threads gheUrl into the device-code request/poll extraData at the existing provider-switch chokepoints (+~24 lines, cohesive with the same pattern as #7399/#6636). The standalone GHE enterprise-URL config step JSX (originally +31 lines inline) was extracted to the new src/shared/components/oauthModal/GheConfigStep.tsx leaf component to minimize the bump; what remains is the irreducible provider-branch wiring. Fifth bump on this modal (969->989->993->998->1030->1056); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_7787_ic2_localdb_reexports": "PR #7787 (IC2 raw connections cache + lazy-decrypt) own growth: localDb.ts 805->807 (gate units, +2). localDb.ts is the re-export-only layer (hard rule #2 — no logic); the PR adds 4 new db/readCache re-exports (touchConnectionLastUsed, getCachedRawProviderConnections, getCachedProviderConnectionById, getCachedProviderNodes) required by existing barrel importers. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7779_routingcombo_thread": "PR #7779 own growth: chatHelpers.ts 876->877 (+1, thread routingComboId into executeChatWithBreaker for compression-combo assignment). Frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7819_autocandidateoverrides_reexport": "PR for #7819 (Level 1+2: read-only auto/* candidate transparency + per-API-key exclusions) own growth: localDb.ts 807->808 (+1). Adds a single `export * from \"./db/autoCandidateOverrides\"` barrel re-export (hard rule #2 — no logic) for the new DB module backing per-apiKey candidate exclusions. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_21_8027_grok_cli_auth_json_paste": "PR #8027 (RaviTharuma, fix(grok-cli) #7610) own growth: OAuthModal.tsx 1080->1100 (gate units). Requires the full ~/.grok/auth.json (with refresh_token) on the paste-import path instead of a bare JWT, at the existing paste-token chokepoint (renamed tab label, updated instructions/placeholder, textarea for the auth.json blob, inline error surface). The validation logic itself (parseGrokCliPasteToken, previously an inline ~75-line function) was extracted to the new src/lib/oauth/utils/grokCliAuthJson.ts leaf module — mirroring the #6636/#7546 extraction precedent — so only the irreducible UI wiring remains here. Sixth bump on this modal (969->989->993->998->1030->1056->1100); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_21_8034_compression_exclusions_sidebar": "#8034 (compression exclusions dashboard tab) own growth: sections.ts 796->806 (+10, one new COMPRESSION_CONTEXT_GROUP sidebar item linking /dashboard/compression/exclusions). The file was already 796/800 before this PR (organic growth from prior sidebar entries), so a single new nav item pushed it 6 lines over cap. Freezing at 806 (cannot grow further); the sidebar item array is data, not extractable logic.",
|
||||
"_rebaseline_2026_07_22_7936_namespace_roundtrip": "#7936 (@RCrushMe, Responses-Chat namespace round-trip identity seam) own growth: open-sse/translator/response/openai-responses.ts 1092->1125 (+33) and open-sse/utils/stream.ts 2814->2869 (+55) — threading the namespace-identity seam through the Responses↔Chat translation + stream paths so tool-call namespaces survive the round-trip. Cohesive translation/stream wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8010_codex_responses_engine": "PR #8010 (@JxnLexn) own growth: open-sse/mcp-server/schemas/tools.ts 1497->1505 (+8 = threading the new \"codex-responses\" literal into the compressionConfigureInput strategy/autoTriggerMode Zod enums and setCompressionEngineInput engine enum, mirroring the existing rtk/omniglyph enum entries; no new tool). open-sse/services/compression/strategySelector.ts 1043->1054 (+11 = one new `if (mode === \"codex-responses\")` dispatch branch in runCompression that delegates 100% to the new codexResponsesEngine.apply, mirroring the existing rtk single-mode dispatch, plus threading config.codexResponsesConfig.preserveToolNames into the shared adaptBodyForCompression call at the 3 existing call sites). src/lib/db/compression.ts (untracked, new-file cap 800) 794->845 (+51 = normalizeCodexResponsesConfig, mirroring the existing normalizeRtkConfig normalizer, plus registering \"codex-responses\" in the COMPRESSION_MODES/STACKED_PIPELINE_ENGINE_IDS/SINGLE_MODE_ENGINE sets and the getCompressionSettings load/save switch) — added to the baseline at its current size. All three are cohesive dispatch/normalizer wiring at existing chokepoints (mirroring the prior compression-mode rebaselines #6534/#6556), not extractable without hiding the mode-dispatch boundary. Covered by tests/unit/compression/codex-responses.test.ts (6) + omniglyph-registries.test.ts/types.test.ts (22, updated for the new mode).",
|
||||
"_rebaseline_2026_07_22_8034_compression_exclusions_persistence": "#8034 (compression exclusions) own growth: src/lib/db/compression.ts 845->850 (+5 = threading the new compressionExclusions field through the existing getCompressionSettings/saveCompressionSettings load/save switch over the shared key_value compression namespace — no new table, no raw SQL). Mirrors the prior compression-field rebaselines (#8010 codex-responses normalizer at the same chokepoint); the load/save switch is a single dispatch boundary, not extractable without hiding it. Covered by the PR's 8 node:test + 3 vitest cases.",
|
||||
"_rebaseline_2026_07_22_8050_model_lockout_exact_family": "#8050 (@AndrianBalanescu) own growth: accountFallback.ts 1864->1892 (+28) — exact-vs-family model-lockout scoping (getModelLockKey/isModelLocked/clearModelLock/getModelLockoutInfo) so an Antigravity 404 for one bare model no longer hijacks the whole family cooldown. Cohesive lockout logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8081_reasoning_placeholder_guard": "#8081 (@Dingding-leo) own growth: openai-responses.ts 1125->1137 (+12) restructuring the reasoning-placeholder guard so it skips only the empty content block and still emits finish_reason/tool_calls in the same chunk. Cohesive translator wiring; frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8210_openrouter_midstream_error": "PR #8210 (hartmark, fix/openrouter-midstream-error-surfacing) own growth: open-sse/translator/response/openai-responses.ts 1137->1163 (+26) measured on the merged tip (release 1137 + this PR own growth). Adds a single new branch inside openaiToOpenAIResponsesResponse() that detects an OpenRouter-style mid-stream aggregator error (HTTP 200 SSE chunk with empty choices + a top-level error object) and surfaces it as state.upstreamError instead of silently falling through to the no-op/awaitingTrailingUsage path, which previously masked the failure as a false empty-success completion and skipped combo fallback. Irreducible call-site addition at the existing chunk-dispatch chokepoint (mirrors the Gemini-to-OpenAI translator's #4177 precedent for the same class of upstream error surfacing). Note: this baseline entry does NOT cover the separate pre-existing +11 drift already on the release tip from #8081/#8162 (1125->1136, unrelated reasoning-placeholder-stripping fix merged after this PR branched) — that drift belongs to the maintainer's rebaseline, not this PR.",
|
||||
"_rebaseline_2026_07_22_8211_gemini_malformed_tool_choice": "PR #8211 (hartmark, fix/gemini-malformed-function-call-tool-choice) own growth: open-sse/translator/response/gemini-to-openai.ts 771->821 (+50, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds MALFORMED_FUNCTION_CALL/UNEXPECTED_TOOL_CALL handling inside geminiToOpenAIResponse(): synthesizes a `malformed_tool_call` tool_calls entry so finish_reason normalizes to the standard \"tool_calls\" instead of an unrecognized raw enum value that OpenAI-compatible clients (e.g. OpenClaw) silently ignore, and always synthesizes (rather than skipping when a real tool call already exists) so a malformed attempt alongside a real one in the same turn is not silently discarded. Irreducible cohesive addition at the existing candidate/finishReason translation chokepoint (mirrors the 9router#2462 raw-finish-reason precedent immediately below it in the same function). Covered by the PR's own tests/unit test additions for both the malformed-only and malformed-plus-real-call cases.",
|
||||
"_rebaseline_2026_07_22_8213_chat_abandoned_target_abort": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/sse/handlers/chat.ts 1794->1860 (+66, measured against the PR's own merge-base — the release tip separately carries an unrelated -5 net shrink from #8013's antigravity callable-catalog alignment, which this PR's branch does not include and this entry does not cover). Adds resolveDispatchClientRawRequest(): merges a per-target modelAbortSignal into clientRawRequest.signal (via mergeAbortSignals) so a combo target abandoned by comboTargetTimeoutMs actually observes its own abort and reaches its cleanup path, instead of hanging forever inside withRateLimit/acquireAccountSemaphore and leaking a permanent 'pending' dashboard entry (live incident, log id 1784418258231-14961a). Also wires combo-exhausted rejection logging to capture request body + attempted models via the new rejectedRequestUsage helper. Irreducible additions at the existing chat dispatch chokepoint. Covered by the PR's own combo-config + integration test additions.",
|
||||
"_rebaseline_2026_07_22_8213_combo_cooldown_wait_recording": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/combo.ts 3548->3604 (+56, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Fixes combo cooldown-wait state recording so a bogus 503 is no longer crystallized when the cooldown-wait vars reset every setTry, adds an OpenAI-format SSE error frame path for combo-exhausted rejections (capturing request body + attempted models), and gives an abandoned per-target dispatch its own timeout instead of leaking a permanent 'pending' dashboard entry. Irreducible additions at the existing handleComboChat dispatch/retry chokepoint (mirrors the prior quota-share/headroom/task-aware strategy-branch precedents already frozen in this file). Covered by the PR's own combo-config + Gemini TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_22_8213_gemini_tpm_quota_cooldown_wait": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/accountFallback.ts 1857->1932 on the merged tip (release 1892 incl #8050 +35, plus this PR own growth +40); measured against the PR own merge-base was 1857->1898 (+41 — the release tip separately carries an unrelated +34 from #8050's antigravity 404 model-not-found lockout scoping, which this PR's branch does not include and this entry does not cover). Own growth is the Gemini TPM-ceiling classification + cooldown-wait wiring feeding into the combo cooldown-wait state machine (rate-limit wedge recovery) introduced by this PR's commit series. Irreducible additions at the existing account-fallback/model-lockout chokepoint. Covered by the PR's own gemini-rate-limit-tracker and TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_22_8213_health_unblock_model_cooldowns": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/app/(dashboard)/dashboard/health/page.tsx 1094->1165 (+71, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds handleUnblockAll/handleUnblockOne dashboard actions (DELETE /api/resilience/model-cooldowns) so an operator can manually clear a Gemini TPM-wedge model lockout surfaced by this PR's cooldown-wait fixes, instead of waiting out the ceiling. Irreducible UI wiring at the existing health-page action chokepoint. Covered by the PR's own dashboard/resilience test additions.",
|
||||
"_rebaseline_2026_07_22_8213_requestloggerdetail_unblock_ui": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/shared/components/RequestLoggerDetail.tsx 799->941 (+142, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip; crosses the general 800-line new-file cap so is frozen here for the first time). Adds a collapsible section header (open/expand-less toggle) plus per-log-entry unblock (`unblocking`/`cleared` state, isCombo503 detection) so the request-logger detail panel surfaces the same Gemini TPM cooldown-wait / model-lockout unblock action introduced by this PR at the individual-request level (mirrors the health-page bulk unblock action added in the same PR). Covered by the PR's own dashboard/resilience test additions.",
|
||||
"_rebaseline_2026_07_22_fusion_8013_8098_antigravity": "Fusion of #8013 (backryun, catalog/IDE-CLI-split rewrite) + #8098 (nguyenha935, protocol-fidelity/fail-closed/credits/tool-cloaking): open-sse/services/usage/antigravity.ts NEW 802 (>cap 800, +2 — #8098 credits/tier usage service on #8013's profile-aware headers). Test growth (models-catalog-route 1605->1608, provider-models-route 1752->1757 from #8013 Gemini 3.6 catalog) tracked in testFrozen.",
|
||||
"_rebaseline_2026_07_23_8127_grok_weekly_quota": "#8127 (@apoapostolov) own growth: src/sse/handlers/chat.ts 1861->1865 (+4) — weekly quota tracking for grok-web wires a quota-fetch hook at the existing dispatch chokepoint. Thin wiring mirroring adjacent provider-quota branches; not extractable. Covered by tests/unit/grok-quota-fetcher.test.ts.",
|
||||
"_rebaseline_2026_07_23_8143_empty_catch_logging": "#8143 (@chirag127) own growth: open-sse/utils/stream.ts 2869->2887 (+18) — replacing empty catch blocks in the SSE stream subsystem with console.debug logging (Rule #6 silent-swallow fix, issues #8138-#8142). Cohesive logging additions at the existing catch chokepoints, not extractable; frozen at new size. Covered by tests/unit/stream-handler-catch-logging-8143.test.ts.",
|
||||
"_rebaseline_2026_07_23_8219_cache_ttl_settings_sidebar": "#8219 (@oyi77) own growth: sections.ts 806->813 (+7) — configurable model-catalog cache-TTL settings adds a new sidebar nav entry + its visibility wiring. Sidebar item array is data, not extractable logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_23_8247_8248_model_unhealthy": "#8247+#8248 own growth: accountFallback.ts 1940->1941 (+1, irreducible import statement only — the substantive #8248 DEGRADED-pattern classifier was extracted into open-sse/config/errorConfig.ts, which has ample headroom, instead of growing this frozen file; #8247's fix is a single existing-line condition change, net zero lines). Scoping the credits-exhausted 403/429 branch to isCompatibleProvider() (per-model-quota openai/anthropic-compatible-* nicknames) so it stays model-scoped instead of terminalling the whole connection, and classifying NVIDIA NIM 'Function ... DEGRADED' 400 bodies as model-access-denied instead of a raw passthrough 400. Covered by tests/unit/8247-accountfallback-model-unhealthy.test.ts and tests/unit/8248-accountfallback-nvidia-degraded.test.ts.",
|
||||
"_rebaseline_2026_07_23_8252_combo_400_advance": "#8252 (@RaviTharuma) own growth: accountFallback.ts 1932->1940 (+8) + combo.ts 3604->3630 (+26) — advance combo on model-scoped 400s wrapped as invalid/Bad-Request. Irreducible wiring at existing account-fallback + combo dispatch chokepoints. Covered by combo-model-scoped-400-advance.test.ts.",
|
||||
"_rebaseline_2026_07_23_8266_alibaba_media": "#8266 (@backryun) own growth: imageRegistry.ts 821->979 (+158) — Alibaba-family media models (Qwen image/video, Bailian, Wan) added to the image/video registry. Registry model data, not extractable logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_24_8388_compression_detail_persist": "#8388 (compression engine DETAIL settings — Headroom/session-dedup/CCR — dropped on save) own growth: src/lib/db/compression.ts 866->872 (+6 = irreducible call-site wiring at the existing getCompressionSettings/updateCompressionSettings chokepoint: one import line, one `...buildDetailConfigDefaults()` spread in the seed config, and one `case \"sessionDedup\": case \"ccr\": applyDetailConfigUpdate(config, key, parsed); break;` load-switch case, mirroring the existing headroom/#8056 case immediately above it). The actual normalizer logic (normalizeSessionDedupConfig/normalizeCcrConfig, matching SESSION_DEDUP_SCHEMA/CCR_SCHEMA bounds) was EXTRACTED into a new leaf src/lib/db/compressionDetailNormalizers.ts (well under cap) so this frozen file only carries the minimal dispatch wiring. Covered by tests/unit/8388-compression-detail-persist.test.ts (schema-accept + full DB save->reload round-trip for both new sub-objects, plus a no-regression assertion on the existing headroom round-trip).",
|
||||
"_rebaseline_2026_07_24_responses_toolcalls_log_summary": "hartmark, fix/responses-tool-calls-log-summary own growth: open-sse/translator/response/openai-responses.ts 1163->1174 (+11). closeToolCall() now also writes the completed tool call into the shared state.toolCalls Map (already populated by the openai-to-claude / claude-to-openai / gemini-to-openai response translators) so stream.ts's completion-log summary builder (which reads state.toolCalls, not this translator's own funcCallIds/funcNames/funcArgsBuf bookkeeping) reports finish_reason \"tool_calls\" and message.tool_calls for openai->openai-responses translated streams instead of always logging \"stop\" with no tool_calls — the actual client-facing SSE events were already correct; only the persisted call-log summary was wrong. Irreducible call-site addition at the existing tool-call-close chokepoint. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts.",
|
||||
"_rebaseline_2026_07_25_8476_combo_input_bound_homogeneous_scope": "PR #8476 (herjarsa, fix/8375-8459-combo-image-fixes, #8375) own growth: open-sse/services/combo.ts 3642->3679 (+37 net: +29 the PR's own isInputBoundFailure short-circuit for deterministic context_length_exceeded/context_window_exceeded failures, +8 a /green-prs pre-merge fix scoping that short-circuit to homogeneous remainders only — the shipped code fired unconditionally on ANY target, regressing the intentional heterogeneous-combo fallback #6637/isContextOverflow400 protects, exactly as flagged by this PR's own review evidence but never actually implemented in the branch). The fix compares orderedTargets[i+1..] modelStr against the failing target's modelStr at the existing executeTarget dispatch chokepoint (mirrors the sameProviderNext precedent a few lines below) — irreducible call-site wiring, not extractable without hiding the dispatch boundary. Covered by tests/unit/combo-input-bound-failure-8375.test.ts (homogeneous pool still short-circuits) and the new tests/unit/combo-input-bound-heterogeneous-8375.test.ts (heterogeneous combo now correctly falls through to the larger-context target).",
|
||||
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 — check-file-size.mjs counts LOC via split(\"\\n\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
|
||||
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
|
||||
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
|
||||
"open-sse/executors/antigravity.ts": 1528,
|
||||
"open-sse/executors/base.ts": 1640,
|
||||
"open-sse/executors/chatgpt-web.ts": 3241,
|
||||
"open-sse/executors/codex.ts": 1562,
|
||||
"open-sse/executors/cursor.ts": 1563,
|
||||
"open-sse/executors/deepseek-web.ts": 1148,
|
||||
"open-sse/executors/grok-web.ts": 1044,
|
||||
"open-sse/executors/muse-spark-web.ts": 1405,
|
||||
"open-sse/handlers/chatCore.ts": 5034,
|
||||
"open-sse/handlers/imageGeneration.ts": 3101,
|
||||
"open-sse/handlers/responseSanitizer.ts": 1128,
|
||||
"open-sse/handlers/search.ts": 1536,
|
||||
"open-sse/handlers/videoGeneration.ts": 1063,
|
||||
"open-sse/mcp-server/schemas/tools.ts": 1553,
|
||||
"open-sse/mcp-server/server.ts": 1448,
|
||||
"open-sse/mcp-server/tools/advancedTools.ts": 1120,
|
||||
"open-sse/services/accountFallback.ts": 1978,
|
||||
"open-sse/services/adobeFireflyClient.ts": 2385,
|
||||
"open-sse/services/claudeCodeCompatible.ts": 1202,
|
||||
"open-sse/services/combo.ts": 3648,
|
||||
"open-sse/services/compression/strategySelector.ts": 1060,
|
||||
"open-sse/services/rateLimitManager.ts": 1167,
|
||||
"open-sse/translator/response/openai-responses.ts": 1204,
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": 1505,
|
||||
"open-sse/utils/stream.ts": 2889,
|
||||
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1388,
|
||||
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1031,
|
||||
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3117,
|
||||
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1067,
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": 4703,
|
||||
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1283,
|
||||
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1022,
|
||||
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2615,
|
||||
"src/app/(dashboard)/dashboard/health/page.tsx": 1165,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1324,
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 1944,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
|
||||
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1019,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1470,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1123,
|
||||
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1629,
|
||||
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1573,
|
||||
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1028,
|
||||
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2148,
|
||||
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1119,
|
||||
"src/app/api/providers/[id]/models/route.ts": 2361,
|
||||
"src/app/api/v1/models/catalog.ts": 1597,
|
||||
"src/lib/db/apiKeys.ts": 1529,
|
||||
"src/lib/db/core.ts": 1639,
|
||||
"src/lib/db/migrationRunner.ts": 1094,
|
||||
"src/lib/db/models.ts": 1097,
|
||||
"src/lib/db/providers.ts": 1034,
|
||||
"src/lib/memory/retrieval.ts": 1073,
|
||||
"src/lib/tailscaleTunnel.ts": 1202,
|
||||
"src/lib/usage/providerLimits.ts": 1013,
|
||||
"src/shared/components/OAuthModal.tsx": 1134,
|
||||
"src/shared/components/RequestLoggerV2.tsx": 1629,
|
||||
"src/shared/components/analytics/charts.tsx": 1035,
|
||||
"src/shared/services/cliRuntime.ts": 1122,
|
||||
"src/sse/handlers/chat.ts": 1904,
|
||||
"src/sse/services/auth.ts": 2520,
|
||||
"tests/unit/account-fallback-service.test.ts": 1572,
|
||||
"tests/unit/provider-validation-specialty.test.ts": 2985,
|
||||
"open-sse/executors/hyperagent.ts": 1026,
|
||||
"src/lib/tokenHealthCheck.ts": 1053,
|
||||
"open-sse/executors/default.ts": 1042,
|
||||
"open-sse/executors/kiro.ts": 1069,
|
||||
"open-sse/translator/request/openai-to-kiro.ts": 1057,
|
||||
"open-sse/utils/sseHeartbeat.ts": 149
|
||||
},
|
||||
"_rebaseline_2026_08_02_9242_token_health_transient": "PR #9242 (fix/refresh-circuit-transient): src/lib/tokenHealthCheck.ts 1021 (new file, above cap 1000). The file consolidates token-refresh health checking logic that was previously scattered across auth.ts and tokenRefresh.ts. Cohesive single-responsibility module for refresh circuit state management; not extractable without splitting the refresh state machine. Covered by tests/unit/tokenHealthCheck-transient.test.ts.",
|
||||
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
|
||||
"_rebaseline_2026_07_27_v3849_train3": "Merge-train 3 (13 PRs) — owner-approved 2026-07-27. Both entries are genuine irreducible growth at existing chokepoints, not new branches: src/lib/db/apiKeys.ts 1518->1529 (#8805 cx/* ≡ codex/* API-key model permissions); open-sse/handlers/chatCore.ts 5006->5020 (#8806 real response payload into plugin onResponse hooks). Covered by tests/unit/db-apiKeys-crud.test.ts (4 new cases) and the two plugin-hook test files updated in #8806 respectively.",
|
||||
"_rebaseline_2026_07_28_8842_antigravity_projectid_refresh": "PR #8842 (fix/antigravity-projectid-refresh) own growth: open-sse/executors/antigravity.ts 1493->1528 (+35 = projectId discovery in refreshCredentials: import ensureAntigravityProjectAssigned + trim projectId + call ensureAntigravityProjectAssigned with 8s timeout + persistDiscoveredAntigravityProjectId + log success/failure). Irreducible wiring at the existing credential-refresh chokepoint. Covered by tests/unit/executor-antigravity.test.ts (4 new test cases).",
|
||||
@@ -419,146 +421,15 @@
|
||||
"_rebaseline_2026_07_28_8860_tokenrefresh_projectid": "PR #8860 (fix/antigravity-projectid-centralized) own test growth: tests/unit/token-refresh-service.test.ts 1311->1378 (+67 = 4 cases covering projectId discovery on the tokenRefresh.ts path — the Dashboard/health-check refresh route, which #8842 did not reach since that fixed the executor path). Covered by the same file.",
|
||||
"_rebaseline_2026_07_28_8861_xiaomi_token_plan": "PR #8861 (feat/xiaomi-token-plan-protocol-selector) own growth: EditConnectionModal.tsx 1283->1316 (+33 = the per-connection API-protocol selector field) and open-sse/executors/base.ts 1540->1562 (+22 = alternate-format resolution at the existing buildUrl/headers chokepoint). Both are irreducible wiring at existing call sites.",
|
||||
"_rebaseline_2026_07_28_8863_firefly_detail_level": "PR #8863 (fix/adobe-firefly-gpt-detail-level-max) own growth: adobeFireflyClient.ts 2317->2322 (+5 = gpt-image detailLevel defaulting to maximal at the existing payload-build site). Covered by tests/unit/adobe-firefly.test.ts.",
|
||||
"_rebaseline_2026_07_29_8281_home_quickstart_prefetch": "Release v3.8.49 base-red fix (no PR — captain sweep): src/app/(dashboard)/dashboard/HomePageClient.tsx 1377->1381 (+4). #8292 added prefetch={false} to the sidebar but left /home's five quick-start Links prefetching, so first paint still fired 12 speculative RSC requests — caught by navigation.spec.ts only after the e2e helper bug (APP_ROUTE_PATTERN missing /home) was repaired in the same cycle. Growth is the five prefetch attributes; it was offset first by extracting the repeated className literals (INLINE_LINK x4, DOCS_LINK x1), which collapsed five wrapped <Link> blocks back to one line each — a naive fix measured 1391. Guard: tests/unit/sidebar-prefetch-policy-8281.test.ts.",
|
||||
"_rebaseline_2026_08_02_v3850_agentrouter_responses": "Release v3.8.50 AgentRouter/Codex compatibility reconciliation. open-sse/executors/base.ts 1562->1578: #9190 wires AgentRouter's selected Claude/OpenAI/Responses protocol through the existing executor URL, auth, identity-header and fingerprint chokepoints; the reusable alternate resolver remains outside base.ts. open-sse/utils/stream.ts 2887->2889: #9213 evaluates Responses ID and usage normalization independently so response.completed always receives finite usage.total_tokens instead of short-circuiting after an ID rewrite. tests/unit/chatcore-translation-paths.test.ts 2769->2776: #9191 updates the existing Claude-Code bridge assertions for the dynamic AgentRouter wire image. PR #9224 offsets its own chatCore growth by extracting the AgentRouter protocol decisions into chatCore/agentRouterProtocol.ts, leaving chatCore below its frozen ceiling. Covered by agentrouter executor/chatCore protocol tests, chatcore translation-path tests, and responses-commentary-passthrough tests.",
|
||||
"_rebaseline_2026_08_08_v3850_base_drift_batch_9757": "Base drift on release/v3.8.50, not own growth: the 08-06..08-08 merge batches grew 12 already-frozen (or newly-landed) files without carrying their rebaselines — the dedicated rebaseline PR #9616 was closed as 'superseded' but its file-size entries never actually reached the base, and later merges (#8894 combos page, #9539 EditConnectionModal, #8895 models route, #9294/#9293 catalog, #9541 db/core, #8970 tokenHealthCheck, #8925 mcp schemas+server, #8890 accountFallback, #9467 chat.ts, #8931 openai-to-kiro, ProxyRegistryManager) kept growing them. All 12 values re-measured on THIS branch's tree (= pure tip + this PR's 1-line chat.ts fix, which adds zero lines). This PR's own source changes (chat.ts identifier restore, stream.ts format carve-out) do not grow any frozen file past these values.",
|
||||
"_rebaseline_2026_08_08_migration_135_collision": "fix(db): resolve migration version 135 numbering collision — #9449's 135_connection_runtime_state.sql and #8908's 135_migrate_model_capability_max_token.sql both claimed version 135 (#9449 branched before #8908 merged and never got renumbered before landing on release/v3.8.50), which threw 'Migration version collision detected' the moment ANY code touched the database — a fresh install/deploy from this tip cannot even boot. Renumbered the later-landing file to 140 (next free slot) and added the matching isSchemaAlreadyApplied('140') retroactive guard, matching the established pattern already used for the prior 135/136 -> 137/138 renumber in the same file. Own growth: src/lib/db/migrationRunner.ts 1084->1094 (+10, the new case block) — irreducible, matches the existing per-case guard pattern exactly. Covered by tests/unit/migration-135-numbering-collision.test.ts (2/2), confirmed failing (reproducing the exact live crash) against the pre-fix colliding filenames, passing after.",
|
||||
"_rebaseline_2026_08_02_9259_rolling_rpm": "PR #9259 (issue #8733) own growth: open-sse/services/rateLimitManager.ts baseline 1060->1167 (+107; final source 1153). The existing withRateLimit chokepoint now composes process-local rolling RPM leases with Bottleneck admission, releases pre-dispatch leases on queue timeout/abort/connection disable, preserves caller abort reasons, and wires 429/header state into the extracted rollingRpmGate.ts. The remaining growth is irreducible lifecycle wiring at the dispatch boundary plus the real watchdog test hooks needed to verify queued-wedge recovery; moving it further would obscure lease ownership and Bottleneck cleanup. Covered by the focused rate-limit manager/sliding-window suite (33/33); distributed multi-instance coordination remains explicitly out of scope.",
|
||||
"_rebaseline_2026_07_25_dario_upstream_proxy_selector": "PR #8523 (Dario embedded service): upstream-proxy mode selector replaces the binary CLIProxyAPI toggle with Native/CLIProxyAPI/Dario/Fallback + a fallback-backend picker. ProviderDetailPageClient.tsx 798->804 (+6, new hook fields threaded through to ConnectionsListPanel), ConnectionRow.tsx 942->958 (+16, the mode <select> + conditional fallback-backend <select> replacing a single pill button), useProviderConnections.ts 954->986 (+32, upstreamProxyMode/upstreamProxyFallbackBackend state + handleSetUpstreamProxyMode, handleToggleCliproxyapiMode kept as a thin backward-compat wrapper for the existing hook-shape test). All additive UI/state for the new modes — no unrelated refactor.",
|
||||
"_rebaseline_2026_08_02_9242_token_health_transient": "PR #9242 (fix/refresh-circuit-transient): src/lib/tokenHealthCheck.ts 1021 (new file, above cap 1000). The file consolidates token-refresh health checking logic that was previously scattered across auth.ts and tokenRefresh.ts. Cohesive single-responsibility module for refresh circuit state management; not extractable without splitting the refresh state machine. Covered by tests/unit/tokenHealthCheck-transient.test.ts.",
|
||||
"_rebaseline_2026_07_28_8870_firefly_ref_cap_timeout": "PR #8870 (fix/adobe-firefly-gpt-ref-cap-timeout) own growth: adobeFireflyClient.ts 2322->2385 (+63 = gpt-image subject-ref hard cap at 2 + adaptive poll timeout budget (base 300s + 60s/ref, max 600s) + defensive .slice on referenceBlobs for gpt/nano/generic families). Fixes live 504s on multi-screenshot listing jobs (Featured Promo / Box Art) where 3–4+ subject refs stall colligo until the old 180s poll budget expires. Helpers adobeFireflyMaxImageRefs/adobeFireflyImageTimeoutMs live next to the existing payload/poll chokepoint (not extractable without splitting the wire recipe mid-PR). Covered by tests/unit/adobe-firefly.test.ts (ref-cap + timeout cases). Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_29_8281_home_quickstart_prefetch": "Release v3.8.49 base-red fix (no PR — captain sweep): src/app/(dashboard)/dashboard/HomePageClient.tsx 1377->1381 (+4). #8292 added prefetch={false} to the sidebar but left /home's five quick-start Links prefetching, so first paint still fired 12 speculative RSC requests — caught by navigation.spec.ts only after the e2e helper bug (APP_ROUTE_PATTERN missing /home) was repaired in the same cycle. Growth is the five prefetch attributes; it was offset first by extracting the repeated className literals (INLINE_LINK x4, DOCS_LINK x1), which collapsed five wrapped <Link> blocks back to one line each — a naive fix measured 1391. Guard: tests/unit/sidebar-prefetch-policy-8281.test.ts.",
|
||||
"_rebaseline_2026_08_01_8964_xai_agent_tools": "PR #8964 own growth: chatCore.ts 5020->5034 at the existing native-passthrough chokepoint. Adds xAI Agent Tools passthrough for /v1/responses (xai/xai-oauth/xao): resolve nativeXaiResponsesPassthrough, force openai-responses targetFormat, stamp body marker, and OR into the existing nativeCodexPassthrough sites (web-search bypass + requestEndpointPath). Leaf logic in passthroughHelpers, responsesEndpoint, targetFormat, xai executor, responseSanitizer, usageTracking. Cohesive wiring at the Codex passthrough boundary.",
|
||||
"_rebaseline_2026_08_01_8964_response_sanitizer": "PR #8964 own growth: responseSanitizer.ts 1115->1128. Keep cost_in_usd_ticks / server_side_tool_usage(_details) through sanitizeResponsesApiResponse allowlists so native xAI tool responses retain usage.",
|
||||
"_rebaseline_2026_08_02_v3850_agentrouter_responses": "Release v3.8.50 AgentRouter/Codex compatibility reconciliation. open-sse/executors/base.ts 1562->1578: #9190 wires AgentRouter's selected Claude/OpenAI/Responses protocol through the existing executor URL, auth, identity-header and fingerprint chokepoints; the reusable alternate resolver remains outside base.ts. open-sse/utils/stream.ts 2887->2889: #9213 evaluates Responses ID and usage normalization independently so response.completed always receives finite usage.total_tokens instead of short-circuiting after an ID rewrite. tests/unit/chatcore-translation-paths.test.ts 2769->2776: #9191 updates the existing Claude-Code bridge assertions for the dynamic AgentRouter wire image. PR #9224 offsets its own chatCore growth by extracting the AgentRouter protocol decisions into chatCore/agentRouterProtocol.ts, leaving chatCore below its frozen ceiling. Covered by agentrouter executor/chatCore protocol tests, chatcore translation-path tests, and responses-commentary-passthrough tests.",
|
||||
"_rebaseline_2026_08_05_9323_agentrouter_waf_retry": "PR #9323 (fix(agentrouter): retry on 400 content-blocked + burst guard) own growth: open-sse/executors/base.ts 1578->1623 (check-file-size.mjs conta via split(\"\\n\").length; wc -l ve 1622). As +45 linhas sao o WAF_RETRY_CONFIG + o burst guard via gateOutboundRequest() para o WAF do agentrouter.org, com comentarios explicando o porque de cada mitigacao e cobertos por tests/unit/base-executor-waf-retry.test.ts e tests/unit/wafRateLimit.test.ts. Crescimento funcional legitimo, nao inchaco.",
|
||||
"_rebaseline_2026_08_05_9529_own_growth": "PR #9529 own growth (base release/v3.8.50 medida EXATAMENTE nos frozen antigos, entao o modo base-relative #8522 nao cobre): open-sse/services/rateLimitManager.ts 1060->1105 (+45: helper applyLimiterSettings() que re-arma o heartbeat do reservoir apos updateSettings — fix do bug Bottleneck 2.19.5 que congelava a fila weighted; TDD em tests/unit/ratelimit-reservoir-refresh.test.ts); tests/integration/chat-pipeline.test.ts 1592->1598 (+6: User-Agent do codex derivado de getCodexClientVersion() em vez de literal pinado — teste-irmao alinhado ao contrato); tests/unit/provider-validation-specialty.test.ts 2980->2985 (+5: cobertura NOVA claude-web 429 -> valid:false, alinhamento #9406); open-sse/translator/response/openai-responses.ts 1174->1204 (+30: buildResponsesReasoningSummaryDelta MOVIDA do leaf pureHelpers.ts para o host — a funcao do #9500 muta stream state e violava o contrato do leaf puro; o LOC total do par host+leaf nao cresceu, o pureHelpers encolheu o mesmo tanto). Crescimento por fix de producao + cobertura adicional + realocacao arquitetural, nao inchaco.",
|
||||
"_rebaseline_2026_08_06_v3850_inherited_drift_reconcile": "Reconciliacao 2026-08-06 do drift ACUMULADO da release/v3.8.50 apos o lote de merges de 08-05/06: 13 arquivos acima do frozen no tip puro 8180b49ce1 (medidos pelo proprio gate). O modo PR base-relative (#8522) deixa PRs inocentes passarem, e os rebaselines individuais dos PRs se perderam nas resolucoes sucessivas de conflito deste hot-file — o drift so aparece no modo absoluto (nightly/local). Crescimentos funcionais dos PRs mergeados: #9024 topology click-nav src/app/(dashboard)/dashboard/HomePageClient.tsx; #9324 OpenRouter enrich src/app/(dashboard)/dashboard/providers/page.tsx; #9329 quota card ordering src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx; #9193 context-window suffixes src/sse/handlers/chat.ts; #9332 nested Claude server tool ids open-sse/executors/base.ts; #9228 strip orphaned tool outputs open-sse/executors/codex.ts; #9236 nvidia tool-name normalize open-sse/executors/default.ts; #9314 nested tool_call validation open-sse/executors/kiro.ts; #9260 caller identity REST hops open-sse/mcp-server/server.ts; #8934 cache breakpoints tests tests/unit/chatcore-translation-paths.test.ts; #9193 suffix tests tests/unit/combo-routing-engine.test.ts; #9196 reasoning-on-tool-finish tests tests/unit/sse-auth.test.ts; #9163 GPT-5.6 Max reasoning tests tests/unit/translator-openai-to-kiro.test.ts. default.ts e kiro.ts entram no frozen (estavam sem entrada, acima do cap 1000). Atualizacao pos-medicao (a base avancou durante o ciclo do PR): src/sse/handlers/chat.ts 1857->1877 (#9184 affinity EOF evict) e open-sse/executors/default.ts 1027->1042 (#9005 Kimi K3 tool-name backfill).",
|
||||
"_rebaseline_2026_08_06b_v3850_sweepreds_drift": "Segunda reconciliacao de 2026-08-06 (/sweep-reds sobre o tip puro 2ddbbc61a6): 3 arquivos voltaram a passar do frozen apos os merges do mesmo dia, com atribuicao 1:1 por commit. (1) src/app/(dashboard)/dashboard/providers/page.tsx 1928->1944 e (2) open-sse/executors/base.ts 1635->1640, ambos do #9515 (feat(radar): flag-gated signed free-model catalog overlay, commit e7f6b1d130) — o overlay do Radar entra por wiring nos chokepoints ja existentes (a resolucao/verificacao do catalogo assinado mora fora destes dois arquivos); +16 e +5 linhas liquidas nao sao extraiveis sem inventar um leaf por callsite. (3) open-sse/services/accountFallback.ts 1966->1972 do #8704 (commit c4527f97bd), +6 linhas de dados em CREDITS_EXHAUSTED_SIGNALS ('has been exhausted', fixes #8631). src/sse/handlers/chat.ts 1880>1877 tambem estava violando e NAO entra aqui de proposito: e drenado por encolhimento na PR #9598, sem rebaseline. Crescimento proprio DESTA PR: src/lib/db/migrationRunner.ts 1077->1084 (+7) — o guard retroativo em isSchemaAlreadyApplied para os arquivos renumerados 137/138, exigido pela propria mensagem de erro de colisao do runner (ambas as migracoes sao ALTER TABLE ADD COLUMN puro, nao idempotente). Dois `case` + dois `return hasColumn(...)` + 3 linhas de comentario dentro do switch existente; nao extraivel.",
|
||||
"_rebaseline_2026_08_06c_v3850_sweepreds_pr2": "Segunda PR do /sweep-reds (fix/release-v3.8.50-basereds-0806b): tests/unit/provider-models-route.test.ts 1784->1787 (medido pelo gate, que conta split(\"\\n\").length) (+2 apos compressao de comentarios) — alinhamento de contrato forcado por dois merges do dia: #9106 tornou gemini-3.1-pro-high user-callable (a entry do alias entra na lista esperada do teste de discovery-retry, +1 linha de dado + 1 de comentario) e ff012ff420 adicionou onboardUser como bootstrap hop (exclusao no mock, ja comprimida a 1 linha). Nao ha o que encolher sem apagar o comentario que explica o porque.",
|
||||
"_rebaseline_2026_08_07_v3850_sweepreds_pr2_toolnamemap": "tests/unit/translator-openai-to-gemini.test.ts 1616->1619 (+3). O frozen estava EXATAMENTE no tamanho da base, entao qualquer linha nova viola. #9568 (c9a3361e5a) fez buildChangedToolNameMap emitir entradas IDENTIDADE (o Gemini minusculiza nomes de tool nas respostas, entao o tradutor de resposta precisa da chave para mapear de volta), o que passou a incluir `_toolNameMap` no envelope Antigravity de qualquer request com tools. As 3 linhas sao: a chave nova na lista esperada de Object.keys, 1 comentario explicando POR QUE ela aparece (sem ele o proximo leitor tenta remove-la de novo) e 1 assert do CONTEUDO do map — presenca de chave sozinha nao provaria a entrada identidade, que e justamente o comportamento novo. Nao ha o que extrair: e alinhamento de contrato dentro de um teste existente.",
|
||||
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
|
||||
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\\\"tool\\\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
|
||||
"_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \\\"headroom\\\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, <cap) and the async orderer orderTargetsByHeadroom is appended to the existing open-sse/services/combo/quotaStrategies.ts (<cap) next to its sibling reset-aware/reset-window orderers (reuses their connection-expansion machinery). headroom = 1 - max(util_5h, util_7d) from getSaturation (src/lib/quota/saturationSignals.ts), prefers the connection with the most free capacity. Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. fill-first stays default; all existing strategies untouched. Covered by tests/unit/combo-headroom-ranking.test.ts (pure helper) + tests/unit/combo-headroom-strategy.test.ts (orderer, saturation injected). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_quota_share_strategy": "Dedicated quota-share strategy (Phase 3 #9): combo.ts 3180->3190 (+10 = one new `else if (strategy === \\\"quota-share\\\")` dispatch branch in handleComboChat that delegates 100% to selectQuotaShareTarget + its log line, plus the import). All the new logic lives OUT of the god-file in two new leaves under open-sse/services/combo/: quotaShareInflight.ts (in-flight counter with TTL/lease, ~150 LOC <cap) and quotaShareStrategy.ts (per-model bucket gating via isBucketSaturated + DRR proportional to weight + P2C over in-flight, ~240 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the headroom/reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. ZERO existing strategy cases were modified — only this branch was added, and the qtSd/ combos switched from fill-first to quota-share in src/lib/quota/quotaCombos.ts. Covered by tests/unit/quota-share-strategy.test.ts (gating, DRR fairness, P2C in-flight, fail-open, activation). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_task_aware_routing": "Task-aware routing strategy (port PR #2045, OmniRoute #4945): combo.ts 3190->3225 (+35) = one new `else if (strategy === \\\"task-aware\\\")` dispatch branch delegating 100% to selectTaskAwareTarget + its imports/log lines. All scoring/classification logic lives OUT of the god-file in the new leaf open-sse/services/taskAwareRouting.ts (553 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors quota-share/headroom/reset-aware branches). ZERO existing strategy cases modified. Covered by tests/unit/combo-task-aware.test.ts (35 tests). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_26_fidelity_gate_extraction": "Milestone-B fidelity-gate wiring residual: bodyToText+gateAdvance extracted to fidelityGateStep.ts (889->854, -35), but the StackOptions.fidelityGate field, the `const fidelityGate` reads at the two stacked-loop dispatch chokepoints, and the import of FidelityGateConfig are irreducible wiring that cannot leave strategySelector without an architectural refactor of the pre-existing stacked pipeline. Net: 889->854 (+6 vs the pre-Milestone-B frozen 848). Covered by tests/unit/compression/*.test.ts (940 pass).",
|
||||
"_rebaseline_2026_06_27_5193_5203_antigravity_oauthmodal": "Antigravity remote-login own growth: OAuthModal.tsx 960->969 (gate units). #5193 (+~4: remote paste instruction shown for all remote incl. Google + its rationale comment) and #5203 (+~5: handleManualSubmit credential-blob branch + button guard; submit logic extracted to oauthBlobSubmit.ts to minimize). Frozen set to the SUM so either merge order passes. Cohesive at the existing manual-submit chokepoint.",
|
||||
"_rebaseline_2026_06_27_5193_antigravity_basered": "Base-red (pre-existing release drift, fast-gate PR->release skips check:file-size): accountFallback.ts 1773->1777 and src/app/api/providers/[id]/test/route.ts 924->940 were already over their frozen caps on release/v3.8.39 independent of any antigravity change. Owner chose to rebaseline (keep the documented issue-reference comments #1846/#1449/#347 etc.) rather than accept the contributor comment-stripping in #5200/#5198. Reverted #5200 to restore the comments; bumped these two frozen caps to the actual base sizes. No logic change.",
|
||||
"_rebaseline_2026_06_28_5237_impersonation_ua_refresh": "PR #5237 (refresh impersonation UAs): grok-web.ts 1871->1873 (+2), muse-spark-web.ts 1284->1302 (+18), perplexity-web.ts 1013->1032 (+19). Net semantic change in each file is a single User-Agent constant (Chrome 147->149 for grok/muse; perplexity kept at Firefox 148 to stay matched with the firefox_148 TLS profile — the contributor's 152 bump was reverted to avoid a UA-vs-JA3 mismatch, #2459). The growth is Prettier reflow that lint-staged unavoidably applies to these grandfathered long-line files the moment they are touched; not extractable. src/sse/services/auth.ts 2336->2401 in the same reconcile is #5222's antigravity-LRU-retry growth that merged via --admin without a baseline bump.",
|
||||
"_rebaseline_2026_06_28_5243_risk_gate_prepass": "PR #5243 (compression risk-gate pre-pass) own growth: open-sse/services/compression/strategySelector.ts 854->899 (+45). The three exported entry points (applyCompression/applyStackedCompression/applyStackedCompressionAsync) become thin wrappers over pure-extracted private bodies (runCompression/runStackedCompression/runStackedCompressionAsync) so the risk-gate mask->run->restore wrapper sits strictly OUTSIDE the per-step loop — a single universal integration point. The wrapper logic itself (resolveRiskGate/withRiskGate) lives in the new riskGate/strategyWrap.ts (<cap); the residual growth is the duplicated thin-wrapper signatures + the extracted bodies' dispatch boundary, guarded by a byte-identical parity test (riskGateIntegration). Default off (DEFAULT_COMPRESSION_CONFIG unchanged). Not extractable without hiding the dispatch boundary, mirroring prior compression rebaselines. Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_06_28_5275_correlation_id_extract": "Extraction of the safe CorrelationId subset of #5275 (hartmark) — request correlation id stored in call_logs (migration 109) and returned via the X-Correlation-Id response header, WITHOUT the combo/resilience or build/lazy-loading changes (those stay in #5275). Own growth: callLogs.ts 975->985 (correlation_id column on CallLogSummaryRow + read/map), usageHistory.ts 983->988 (correlationId metadata normalize), chat.ts 1575->1632 (withCorrelationId response wiring + combo-failure log carrying correlationId), chatHelpers.ts new 811 (withCorrelationId helper + reqId threading; was 791<cap pre-feature). Cohesive request/logging chokepoint wiring; structural shrink of chat.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_29_4038_cas_guard": "PR (#4038) own growth: tokenRefresh.ts 2103->2181 (+78 = the compare-and-swap guard on the refresh persist — runWithCasGuard/getActiveCasGuard AsyncLocalStorage pair mirroring runWithOnPersist, casGuardShouldSkipPersist that rereads the row right before persisting and skips the write when a concurrent writer already rotated the refresh_token past the one presented, plus getCasGuardStats counters). Fixes the sibling-rotation-revert → token-family-revocation storm. Gated behind an active guard (opt-in; no guard => byte-identical). Wiring lives at the two persist chokepoints inside getAccessToken; the comparison reuses wasRefreshTokenRotated from refreshSerializer. Not extractable without splitting the refresh hot path.",
|
||||
"_rebaseline_2026_06_29_5286_memoization": "PR #5286 own growth: strategySelector.ts 899->960 (+61 = the opt-in result-memoization branches in applyCompression/applyCompressionAsync — principal+determinism gate, makeMemoKey lookup/store with model+supportsVision folded into the key, recompute-with-memo-off). Default off (memoizeCompressionResults), so zero behavior change. The memo helpers live in the leaf resultMemo.ts (<cap); the chokepoint wiring here is not extractable. Structural shrink of this hot-path file tracked in #3501.",
|
||||
"_rebaseline_2026_07_01_v3843_release_5609": "Rebaseline v3.8.43 (PR #5609 release reconciliation). DRIFT dos 109 commits do ciclo: 8 god-files existentes cresceram (ApiManagerPageClient 2983->3017, combos/page 4594->4608, AddApiKeyModal 868->869, providerPageHelpers 974->996, chat.ts 1635->1647, auth.ts 2401->2403, batchProcessor 828->915, combo.ts 3368->3387) + 2 novos acima do cap (huggingchat.ts 813, tests web-cookie-providers-new 827) + 4 test files cresceram. Modularizacao deferida (blast-radius mid-release); congelado no estado atual p/ o proximo ciclo ratchetar daqui.",
|
||||
"_rebaseline_2026_07_02_5816_qoder": "PR #5816 (@AgentKiller45, qoder PAT via qodercli): qoderCli.ts 666->989, new-above-cap frozen (owner-approved baseline freeze). The growth is the legitimate PAT job-token exchange + quota parsing CLI transport (the pure-JS Cosy path 500'd on every PAT request); extracting the spawn/parse helpers now would just add indirection to a contributor PR mid-merge. Test frozen also raised for this PR's coverage growth: providers-page-utils.test.ts 1052->1092. Additionally clears an inherited base-red from the already-merged #5933 (codex json_schema->text.format): translator-openai-responses-req.test.ts 1097->1172 (+75 regression tests, no offending branch left). All remain frozen (cannot grow further); release captain's rebaseline-at-release supersedes.",
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dual_auth": "PR #6126 (@hajilok, dual-auth ClinePass) own growth: tokenRefresh.ts 2181->2182 (+1 = a single `case \\\"clinepass\\\":` fallthrough label added to the existing `case \\\"cline\\\":` in _getAccessTokenInternal's provider switch, so clinepass token refresh dispatches to the already-shared refreshClineToken() instead of silently falling through to the generic OAuth refresh). Irreducible 1-line switch-case wiring at the existing chokepoint; the header-building logic for the same feature was extracted to a new leaf src/shared/utils/clineAuth.ts::buildClinepassHeaders() (well under cap) to avoid growing open-sse/executors/default.ts. Covered by tests/unit/clinepass-provider.test.ts.",
|
||||
"_rebaseline_2026_07_09_6363_kiro_external_idp": "PR #6363 (@artickc, Kiro external IdP) own growth: tokenRefresh.ts 2182->2249 (+67 = the external_idp refresh branch inside refreshKiroToken — standard public-client OAuth2 refresh_token grant against the org IdP tokenEndpoint via buildExternalIdpRefreshParams/isExternalIdpAuthMethod from the new leaf open-sse/services/kiroExternalIdp.ts, with invalid_grant/invalid_client -> unrecoverable_refresh_error mapping). Cohesive addition at the existing refreshKiroToken chokepoint. Covered by tests/unit/kiro-external-idp.test.ts.",
|
||||
"_rebaseline_2026_07_09_6587_kiro_api_key_auth": "PR #6587 (@strangersp) own growth for Kiro long-lived API-key auth, merged onto v3.8.47 tip: openai-to-kiro.ts 890->912 (+22, auth-header selection for API-key-vs-OAuth-token connections), providerLimits.ts 998->1000 (+2, API-key auth-type branch), translator-openai-to-kiro.test.ts 1234->1257 (+23), providers-page-utils.test.ts 1109->1107 (net -2 after merging with parallel release drift; connectionMatchesProviderCard api_key coverage added), provider-validation-specialty.test.ts 2856->2980 (+124 net after merge with parallel release drift; this PR also removed the file's `@typescript-eslint/no-explicit-any` eslint-suppression entry by fixing all `any` usages, adding typed replacements). Cohesive additive feature growth, well tested; not extractable without splitting the existing chokepoints mid-merge.",
|
||||
"_rebaseline_2026_07_09_6678_routing_strategy_9router": "#6678 (SeaXen) — 9router-parity Routing Strategy settings card + per-provider/combo sticky-round-robin override. Own growth: ProviderDetailPageClient.tsx 784->786 (single ProviderAccountRoutingCard mount + import), auth.ts 2448->2458 (providerStrategies override resolution: fallbackStrategy/stickyRoundRobinLimit per-provider cascade in getProviderCredentials). Both additive, zero unrelated refactor; new UI/logic lives in new files (ProviderAccountRoutingCard.tsx, RoutingStrategyCard.tsx, rrState.ts::resolveComboStickyRoundRobinLimit). chat.ts value below reflects the current release tip (grown by other concurrent PRs, e.g. #6640), not this PR own change.",
|
||||
"_rebaseline_2026_07_10_6318_omp_letta": "PR #6318 (@hamsa0x7, omp+letta CLI integrations) own growth: cliTools.ts (+53 = 2 registry entries incl. omp docsUrl) and cliRuntime.ts (+18 = runtime-detection wiring for the 2 new tools). Cohesive registry/wiring growth at the existing chokepoints; scope reduced from the original 5 tools (pi/codewhale/jcode shipped separately).",
|
||||
"_rebaseline_2026_07_10_gcf_v3_2_decode": "PR #6838 own growth: new vendored file open-sse/services/compression/engines/headroom/gcf/decode_generic.ts frozen at 880 (> 800 cap). It is the vendored GCF generic-profile decoder (spec v3.2 nested flattening plus the prototype-pollution / hasOwnProperty hardening added in this PR's Gemini review). Kept as one file faithful to upstream gcf-typescript so re-vendoring stays a clean copy rather than a re-split each cycle (sibling generic.ts/scalar.ts stay < cap; extraction would also fragment the file's frozen eslint no-explicit-any suppressions). Round-trip + prototype-pollution regression coverage in tests/unit/compression/headroom-smartcrusher.test.ts. Frozen: only shrinks from here.",
|
||||
"_rebaseline_2026_07_12_v3847_mergeprs_tail": "v3.8.47 /merge-prs tail (owner-approved): src/lib/localDb.ts NEW>800 (799->805, +6 re-exports countFreeProxies + recordFreeProxySyncErrors/clearFreeProxySyncErrors/getFreeProxySyncErrors + FreeProxySyncErrors type for #6909 free-pool relay-repair; re-export-only per Hard Rule #2, not extractable).",
|
||||
"_rebaseline_2026_07_15_7070_combos_memo": "PR #7070 (perf/p1-memo) own growth: src/app/(dashboard)/dashboard/combos/page.tsx 4655->4656 (+1 = React.memo wrapping of ComboCard). Covered by tests/unit/ui/combos-page-smoke.test.tsx.",
|
||||
"_rebaseline_2026_07_18_7399_xai_oauth_modal": "PR #7399 (xAI OAuth PKCE) own growth: OAuthModal.tsx 993->998 (+5 = provider entry + PKCE flow branch wiring at the existing provider-switch chokepoint; the provider logic itself lives in src/lib/oauth/providers/xai-oauth.ts, new leaf). Third irreducible wiring bump on this modal (969->989->993->998); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_6636_codex_session_json": "#6636 own growth: OAuthModal.tsx 998->1030 (gate units, split(\\\"\\\\n\\\").length incl. trailing newline; +32 = session-JSON paste branch for handleManualSubmit plus a shared submitCodexAccessToken() helper extracted from the pre-existing bare-JWT branch, mirroring the #5203 oauthBlobSubmit.ts extraction precedent; the normalizer logic itself lives in the new src/lib/oauth/utils/codexSessionImport.ts leaf module, not here). Fourth irreducible wiring bump on this modal (969->989->993->998->1030); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_7546_ghe_copilot_modal": "PR #7546 (GHE Copilot OAuth provider) own growth: OAuthModal.tsx 1030->1056 (gate units). Adds a gheUrl input state, routes ghe-copilot through the existing device-code branch, and threads gheUrl into the device-code request/poll extraData at the existing provider-switch chokepoints (+~24 lines, cohesive with the same pattern as #7399/#6636). The standalone GHE enterprise-URL config step JSX (originally +31 lines inline) was extracted to the new src/shared/components/oauthModal/GheConfigStep.tsx leaf component to minimize the bump; what remains is the irreducible provider-branch wiring. Fifth bump on this modal (969->989->993->998->1030->1056); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_19_7787_ic2_localdb_reexports": "PR #7787 (IC2 raw connections cache + lazy-decrypt) own growth: localDb.ts 805->807 (gate units, +2). localDb.ts is the re-export-only layer (hard rule #2 — no logic); the PR adds 4 new db/readCache re-exports (touchConnectionLastUsed, getCachedRawProviderConnections, getCachedProviderConnectionById, getCachedProviderNodes) required by existing barrel importers. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7779_routingcombo_thread": "PR #7779 own growth: chatHelpers.ts 876->877 (+1, thread routingComboId into executeChatWithBreaker for compression-combo assignment). Frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7819_autocandidateoverrides_reexport": "PR for #7819 (Level 1+2: read-only auto/* candidate transparency + per-API-key exclusions) own growth: localDb.ts 807->808 (+1). Adds a single `export * from \\\"./db/autoCandidateOverrides\\\"` barrel re-export (hard rule #2 — no logic) for the new DB module backing per-apiKey candidate exclusions. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_21_8027_grok_cli_auth_json_paste": "PR #8027 (RaviTharuma, fix(grok-cli) #7610) own growth: OAuthModal.tsx 1080->1100 (gate units). Requires the full ~/.grok/auth.json (with refresh_token) on the paste-import path instead of a bare JWT, at the existing paste-token chokepoint (renamed tab label, updated instructions/placeholder, textarea for the auth.json blob, inline error surface). The validation logic itself (parseGrokCliPasteToken, previously an inline ~75-line function) was extracted to the new src/lib/oauth/utils/grokCliAuthJson.ts leaf module — mirroring the #6636/#7546 extraction precedent — so only the irreducible UI wiring remains here. Sixth bump on this modal (969->989->993->998->1030->1056->1100); structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_21_8034_compression_exclusions_sidebar": "#8034 (compression exclusions dashboard tab) own growth: sections.ts 796->806 (+10, one new COMPRESSION_CONTEXT_GROUP sidebar item linking /dashboard/compression/exclusions). The file was already 796/800 before this PR (organic growth from prior sidebar entries), so a single new nav item pushed it 6 lines over cap. Freezing at 806 (cannot grow further); the sidebar item array is data, not extractable logic.",
|
||||
"_rebaseline_2026_07_22_7936_namespace_roundtrip": "#7936 (@RCrushMe, Responses-Chat namespace round-trip identity seam) own growth: open-sse/translator/response/openai-responses.ts 1092->1125 (+33) and open-sse/utils/stream.ts 2814->2869 (+55) — threading the namespace-identity seam through the Responses↔Chat translation + stream paths so tool-call namespaces survive the round-trip. Cohesive translation/stream wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8010_codex_responses_engine": "PR #8010 (@JxnLexn) own growth: open-sse/mcp-server/schemas/tools.ts 1497->1505 (+8 = threading the new \\\"codex-responses\\\" literal into the compressionConfigureInput strategy/autoTriggerMode Zod enums and setCompressionEngineInput engine enum, mirroring the existing rtk/omniglyph enum entries; no new tool). open-sse/services/compression/strategySelector.ts 1043->1054 (+11 = one new `if (mode === \\\"codex-responses\\\")` dispatch branch in runCompression that delegates 100% to the new codexResponsesEngine.apply, mirroring the existing rtk single-mode dispatch, plus threading config.codexResponsesConfig.preserveToolNames into the shared adaptBodyForCompression call at the 3 existing call sites). src/lib/db/compression.ts (untracked, new-file cap 800) 794->845 (+51 = normalizeCodexResponsesConfig, mirroring the existing normalizeRtkConfig normalizer, plus registering \\\"codex-responses\\\" in the COMPRESSION_MODES/STACKED_PIPELINE_ENGINE_IDS/SINGLE_MODE_ENGINE sets and the getCompressionSettings load/save switch) — added to the baseline at its current size. All three are cohesive dispatch/normalizer wiring at existing chokepoints (mirroring the prior compression-mode rebaselines #6534/#6556), not extractable without hiding the mode-dispatch boundary. Covered by tests/unit/compression/codex-responses.test.ts (6) + omniglyph-registries.test.ts/types.test.ts (22, updated for the new mode).",
|
||||
"_rebaseline_2026_07_22_8034_compression_exclusions_persistence": "#8034 (compression exclusions) own growth: src/lib/db/compression.ts 845->850 (+5 = threading the new compressionExclusions field through the existing getCompressionSettings/saveCompressionSettings load/save switch over the shared key_value compression namespace — no new table, no raw SQL). Mirrors the prior compression-field rebaselines (#8010 codex-responses normalizer at the same chokepoint); the load/save switch is a single dispatch boundary, not extractable without hiding it. Covered by the PR's 8 node:test + 3 vitest cases.",
|
||||
"_rebaseline_2026_07_22_8050_model_lockout_exact_family": "#8050 (@AndrianBalanescu) own growth: accountFallback.ts 1864->1892 (+28) — exact-vs-family model-lockout scoping (getModelLockKey/isModelLocked/clearModelLock/getModelLockoutInfo) so an Antigravity 404 for one bare model no longer hijacks the whole family cooldown. Cohesive lockout logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8081_reasoning_placeholder_guard": "#8081 (@Dingding-leo) own growth: openai-responses.ts 1125->1137 (+12) restructuring the reasoning-placeholder guard so it skips only the empty content block and still emits finish_reason/tool_calls in the same chunk. Cohesive translator wiring; frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8210_openrouter_midstream_error": "PR #8210 (hartmark, fix/openrouter-midstream-error-surfacing) own growth: open-sse/translator/response/openai-responses.ts 1137->1163 (+26) measured on the merged tip (release 1137 + this PR own growth). Adds a single new branch inside openaiToOpenAIResponsesResponse() that detects an OpenRouter-style mid-stream aggregator error (HTTP 200 SSE chunk with empty choices + a top-level error object) and surfaces it as state.upstreamError instead of silently falling through to the no-op/awaitingTrailingUsage path, which previously masked the failure as a false empty-success completion and skipped combo fallback. Irreducible call-site addition at the existing chunk-dispatch chokepoint (mirrors the Gemini-to-OpenAI translator's #4177 precedent for the same class of upstream error surfacing). Note: this baseline entry does NOT cover the separate pre-existing +11 drift already on the release tip from #8081/#8162 (1125->1136, unrelated reasoning-placeholder-stripping fix merged after this PR branched) — that drift belongs to the maintainer's rebaseline, not this PR.",
|
||||
"_rebaseline_2026_07_22_8211_gemini_malformed_tool_choice": "PR #8211 (hartmark, fix/gemini-malformed-function-call-tool-choice) own growth: open-sse/translator/response/gemini-to-openai.ts 771->821 (+50, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds MALFORMED_FUNCTION_CALL/UNEXPECTED_TOOL_CALL handling inside geminiToOpenAIResponse(): synthesizes a `malformed_tool_call` tool_calls entry so finish_reason normalizes to the standard \\\"tool_calls\\\" instead of an unrecognized raw enum value that OpenAI-compatible clients (e.g. OpenClaw) silently ignore, and always synthesizes (rather than skipping when a real tool call already exists) so a malformed attempt alongside a real one in the same turn is not silently discarded. Irreducible cohesive addition at the existing candidate/finishReason translation chokepoint (mirrors the 9router#2462 raw-finish-reason precedent immediately below it in the same function). Covered by the PR's own tests/unit test additions for both the malformed-only and malformed-plus-real-call cases.",
|
||||
"_rebaseline_2026_07_22_8213_chat_abandoned_target_abort": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/sse/handlers/chat.ts 1794->1860 (+66, measured against the PR's own merge-base — the release tip separately carries an unrelated -5 net shrink from #8013's antigravity callable-catalog alignment, which this PR's branch does not include and this entry does not cover). Adds resolveDispatchClientRawRequest(): merges a per-target modelAbortSignal into clientRawRequest.signal (via mergeAbortSignals) so a combo target abandoned by comboTargetTimeoutMs actually observes its own abort and reaches its cleanup path, instead of hanging forever inside withRateLimit/acquireAccountSemaphore and leaking a permanent 'pending' dashboard entry (live incident, log id 1784418258231-14961a). Also wires combo-exhausted rejection logging to capture request body + attempted models via the new rejectedRequestUsage helper. Irreducible additions at the existing chat dispatch chokepoint. Covered by the PR's own combo-config + integration test additions.",
|
||||
"_rebaseline_2026_07_22_8213_combo_cooldown_wait_recording": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/combo.ts 3548->3604 (+56, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Fixes combo cooldown-wait state recording so a bogus 503 is no longer crystallized when the cooldown-wait vars reset every setTry, adds an OpenAI-format SSE error frame path for combo-exhausted rejections (capturing request body + attempted models), and gives an abandoned per-target dispatch its own timeout instead of leaking a permanent 'pending' dashboard entry. Irreducible additions at the existing handleComboChat dispatch/retry chokepoint (mirrors the prior quota-share/headroom/task-aware strategy-branch precedents already frozen in this file). Covered by the PR's own combo-config + Gemini TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_22_8213_gemini_tpm_quota_cooldown_wait": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/accountFallback.ts 1857->1932 on the merged tip (release 1892 incl #8050 +35, plus this PR own growth +40); measured against the PR own merge-base was 1857->1898 (+41 — the release tip separately carries an unrelated +34 from #8050's antigravity 404 model-not-found lockout scoping, which this PR's branch does not include and this entry does not cover). Own growth is the Gemini TPM-ceiling classification + cooldown-wait wiring feeding into the combo cooldown-wait state machine (rate-limit wedge recovery) introduced by this PR's commit series. Irreducible additions at the existing account-fallback/model-lockout chokepoint. Covered by the PR's own gemini-rate-limit-tracker and TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_22_8213_health_unblock_model_cooldowns": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/app/(dashboard)/dashboard/health/page.tsx 1094->1165 (+71, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Adds handleUnblockAll/handleUnblockOne dashboard actions (DELETE /api/resilience/model-cooldowns) so an operator can manually clear a Gemini TPM-wedge model lockout surfaced by this PR's cooldown-wait fixes, instead of waiting out the ceiling. Irreducible UI wiring at the existing health-page action chokepoint. Covered by the PR's own dashboard/resilience test additions.",
|
||||
"_rebaseline_2026_07_22_8213_requestloggerdetail_unblock_ui": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/shared/components/RequestLoggerDetail.tsx 799->941 (+142, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip; crosses the general 800-line new-file cap so is frozen here for the first time). Adds a collapsible section header (open/expand-less toggle) plus per-log-entry unblock (`unblocking`/`cleared` state, isCombo503 detection) so the request-logger detail panel surfaces the same Gemini TPM cooldown-wait / model-lockout unblock action introduced by this PR at the individual-request level (mirrors the health-page bulk unblock action added in the same PR). Covered by the PR's own dashboard/resilience test additions.",
|
||||
"_rebaseline_2026_07_22_fusion_8013_8098_antigravity": "Fusion of #8013 (backryun, catalog/IDE-CLI-split rewrite) + #8098 (nguyenha935, protocol-fidelity/fail-closed/credits/tool-cloaking): open-sse/services/usage/antigravity.ts NEW 802 (>cap 800, +2 — #8098 credits/tier usage service on #8013's profile-aware headers). Test growth (models-catalog-route 1605->1608, provider-models-route 1752->1757 from #8013 Gemini 3.6 catalog) tracked in testFrozen.",
|
||||
"_rebaseline_2026_07_23_8127_grok_weekly_quota": "#8127 (@apoapostolov) own growth: src/sse/handlers/chat.ts 1861->1865 (+4) — weekly quota tracking for grok-web wires a quota-fetch hook at the existing dispatch chokepoint. Thin wiring mirroring adjacent provider-quota branches; not extractable. Covered by tests/unit/grok-quota-fetcher.test.ts.",
|
||||
"_rebaseline_2026_07_23_8143_empty_catch_logging": "#8143 (@chirag127) own growth: open-sse/utils/stream.ts 2869->2887 (+18) — replacing empty catch blocks in the SSE stream subsystem with console.debug logging (Rule #6 silent-swallow fix, issues #8138-#8142). Cohesive logging additions at the existing catch chokepoints, not extractable; frozen at new size. Covered by tests/unit/stream-handler-catch-logging-8143.test.ts.",
|
||||
"_rebaseline_2026_07_23_8219_cache_ttl_settings_sidebar": "#8219 (@oyi77) own growth: sections.ts 806->813 (+7) — configurable model-catalog cache-TTL settings adds a new sidebar nav entry + its visibility wiring. Sidebar item array is data, not extractable logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_23_8247_8248_model_unhealthy": "#8247+#8248 own growth: accountFallback.ts 1940->1941 (+1, irreducible import statement only — the substantive #8248 DEGRADED-pattern classifier was extracted into open-sse/config/errorConfig.ts, which has ample headroom, instead of growing this frozen file; #8247's fix is a single existing-line condition change, net zero lines). Scoping the credits-exhausted 403/429 branch to isCompatibleProvider() (per-model-quota openai/anthropic-compatible-* nicknames) so it stays model-scoped instead of terminalling the whole connection, and classifying NVIDIA NIM 'Function ... DEGRADED' 400 bodies as model-access-denied instead of a raw passthrough 400. Covered by tests/unit/8247-accountfallback-model-unhealthy.test.ts and tests/unit/8248-accountfallback-nvidia-degraded.test.ts.",
|
||||
"_rebaseline_2026_07_23_8252_combo_400_advance": "#8252 (@RaviTharuma) own growth: accountFallback.ts 1932->1940 (+8) + combo.ts 3604->3630 (+26) — advance combo on model-scoped 400s wrapped as invalid/Bad-Request. Irreducible wiring at existing account-fallback + combo dispatch chokepoints. Covered by combo-model-scoped-400-advance.test.ts.",
|
||||
"_rebaseline_2026_07_23_8266_alibaba_media": "#8266 (@backryun) own growth: imageRegistry.ts 821->979 (+158) — Alibaba-family media models (Qwen image/video, Bailian, Wan) added to the image/video registry. Registry model data, not extractable logic; frozen at new size.",
|
||||
"_rebaseline_2026_07_24_8388_compression_detail_persist": "#8388 (compression engine DETAIL settings — Headroom/session-dedup/CCR — dropped on save) own growth: src/lib/db/compression.ts 866->872 (+6 = irreducible call-site wiring at the existing getCompressionSettings/updateCompressionSettings chokepoint: one import line, one `...buildDetailConfigDefaults()` spread in the seed config, and one `case \\\"sessionDedup\\\": case \\\"ccr\\\": applyDetailConfigUpdate(config, key, parsed); break;` load-switch case, mirroring the existing headroom/#8056 case immediately above it). The actual normalizer logic (normalizeSessionDedupConfig/normalizeCcrConfig, matching SESSION_DEDUP_SCHEMA/CCR_SCHEMA bounds) was EXTRACTED into a new leaf src/lib/db/compressionDetailNormalizers.ts (well under cap) so this frozen file only carries the minimal dispatch wiring. Covered by tests/unit/8388-compression-detail-persist.test.ts (schema-accept + full DB save->reload round-trip for both new sub-objects, plus a no-regression assertion on the existing headroom round-trip).",
|
||||
"_rebaseline_2026_07_24_responses_toolcalls_log_summary": "hartmark, fix/responses-tool-calls-log-summary own growth: open-sse/translator/response/openai-responses.ts 1163->1174 (+11). closeToolCall() now also writes the completed tool call into the shared state.toolCalls Map (already populated by the openai-to-claude / claude-to-openai / gemini-to-openai response translators) so stream.ts's completion-log summary builder (which reads state.toolCalls, not this translator's own funcCallIds/funcNames/funcArgsBuf bookkeeping) reports finish_reason \\\"tool_calls\\\" and message.tool_calls for openai->openai-responses translated streams instead of always logging \\\"stop\\\" with no tool_calls — the actual client-facing SSE events were already correct; only the persisted call-log summary was wrong. Irreducible call-site addition at the existing tool-call-close chokepoint. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts.",
|
||||
"_rebaseline_2026_07_25_8476_combo_input_bound_homogeneous_scope": "PR #8476 (herjarsa, fix/8375-8459-combo-image-fixes, #8375) own growth: open-sse/services/combo.ts 3642->3679 (+37 net: +29 the PR's own isInputBoundFailure short-circuit for deterministic context_length_exceeded/context_window_exceeded failures, +8 a /green-prs pre-merge fix scoping that short-circuit to homogeneous remainders only — the shipped code fired unconditionally on ANY target, regressing the intentional heterogeneous-combo fallback #6637/isContextOverflow400 protects, exactly as flagged by this PR's own review evidence but never actually implemented in the branch). The fix compares orderedTargets[i+1..] modelStr against the failing target's modelStr at the existing executeTarget dispatch chokepoint (mirrors the sameProviderNext precedent a few lines below) — irreducible call-site wiring, not extractable without hiding the dispatch boundary. Covered by tests/unit/combo-input-bound-failure-8375.test.ts (homogeneous pool still short-circuits) and the new tests/unit/combo-input-bound-heterogeneous-8375.test.ts (heterogeneous combo now correctly falls through to the larger-context target).",
|
||||
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 — check-file-size.mjs counts LOC via split(\\\"\\\\n\\\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\\\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
|
||||
"_rebaseline_pr1043_minimax_tts": "Upstream port decolua/9router#1043 (toanalien) own growth: audioSpeech.ts 965->1061 (+96). Adds MiniMax T2A v2 TTS dispatch (handleMinimaxSpeech + hexToBytes helper) — provider entry was already in audioRegistry (format: minimax-tts) but no handler existed, falling through to the OpenAI-compatible default that fails (T2A has custom shape + hex-encoded audio + base_resp envelope). New branch sits next to the other inline provider branches (xiaomi-mimo, coqui, tortoise, aws-polly) — extracting would just create indirection. Covered by tests/unit/minimax-tts-1043.test.ts (3 tests, GREEN: success, base_resp error, invalid-hex).",
|
||||
"_rebaseline_pr4592_exclude_exhausted_auto": "Reconcile #4592 already-merged growth: combo.ts 2991->3036 (+45, terminal-status quota-cutoff exclusion in buildAutoCandidates + opt-in gate). Fast-gate PR->release does not run check:file-size.",
|
||||
"open-sse/executors/antigravity.ts": "1528",
|
||||
"open-sse/executors/base.ts": "1640",
|
||||
"open-sse/executors/chatgpt-web.ts": "3241",
|
||||
"open-sse/executors/codex.ts": "1562",
|
||||
"open-sse/executors/cursor.ts": "1563",
|
||||
"open-sse/executors/deepseek-web.ts": "1148",
|
||||
"open-sse/executors/grok-web.ts": "1044",
|
||||
"open-sse/executors/muse-spark-web.ts": "1405",
|
||||
"open-sse/handlers/chatCore.ts": "5034",
|
||||
"open-sse/handlers/imageGeneration.ts": "3101",
|
||||
"open-sse/handlers/responseSanitizer.ts": "1128",
|
||||
"open-sse/handlers/search.ts": "1536",
|
||||
"open-sse/handlers/videoGeneration.ts": "1063",
|
||||
"open-sse/mcp-server/schemas/tools.ts": "1553",
|
||||
"open-sse/mcp-server/server.ts": "1448",
|
||||
"open-sse/mcp-server/tools/advancedTools.ts": "1120",
|
||||
"open-sse/services/accountFallback.ts": "1978",
|
||||
"open-sse/services/adobeFireflyClient.ts": "2385",
|
||||
"open-sse/services/claudeCodeCompatible.ts": "1202",
|
||||
"open-sse/services/combo.ts": "3648",
|
||||
"open-sse/services/compression/strategySelector.ts": "1060",
|
||||
"open-sse/services/rateLimitManager.ts": "1167",
|
||||
"open-sse/translator/response/openai-responses.ts": "1204",
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": "1505",
|
||||
"open-sse/utils/stream.ts": "2889",
|
||||
"src/app/(dashboard)/dashboard/HomePageClient.tsx": "1388",
|
||||
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": "1031",
|
||||
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": "3117",
|
||||
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": "1067",
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": "4703",
|
||||
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": "1283",
|
||||
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": "1022",
|
||||
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": "2615",
|
||||
"src/app/(dashboard)/dashboard/health/page.tsx": "1165",
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324",
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": "1944",
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201",
|
||||
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019",
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470",
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123",
|
||||
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": "1629",
|
||||
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": "1573",
|
||||
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": "1028",
|
||||
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": "2148",
|
||||
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": "1119",
|
||||
"src/app/api/providers/[id]/models/route.ts": "2361",
|
||||
"src/app/api/v1/models/catalog.ts": "1597",
|
||||
"src/lib/tokenHealthCheck.ts": "1053",
|
||||
"src/lib/db/apiKeys.ts": "1529",
|
||||
"src/lib/db/core.ts": "1639",
|
||||
"src/lib/db/migrationRunner.ts": "1094",
|
||||
"src/lib/db/models.ts": "1097",
|
||||
"src/lib/db/providers.ts": "1034",
|
||||
"src/lib/memory/retrieval.ts": "1073",
|
||||
"src/lib/tailscaleTunnel.ts": "1202",
|
||||
"src/lib/usage/providerLimits.ts": "1013",
|
||||
"src/shared/components/OAuthModal.tsx": "1134",
|
||||
"src/shared/components/RequestLoggerV2.tsx": "1629",
|
||||
"src/shared/components/analytics/charts.tsx": "1035",
|
||||
"src/shared/services/cliRuntime.ts": "1122",
|
||||
"src/sse/handlers/chat.ts": "1904",
|
||||
"src/sse/services/auth.ts": "2508",
|
||||
"tests/unit/account-fallback-service.test.ts": "1572",
|
||||
"tests/unit/provider-validation-specialty.test.ts": "2985",
|
||||
"open-sse/executors/hyperagent.ts": "1026",
|
||||
"open-sse/executors/default.ts": "1042",
|
||||
"open-sse/executors/kiro.ts": "1069",
|
||||
"open-sse/translator/request/openai-to-kiro.ts": "1057",
|
||||
"open-sse/utils/sseHeartbeat.ts": "142",
|
||||
"_rebaseline_2026_08_04_9305_sse_comments": "#9305 fix: broadened sseCommentsEnabled()"
|
||||
"_rebaseline_2026_08_07_v3850_sweepreds_pr2_toolnamemap": "tests/unit/translator-openai-to-gemini.test.ts 1616->1619 (+3). O frozen estava EXATAMENTE no tamanho da base, entao qualquer linha nova viola. #9568 (c9a3361e5a) fez buildChangedToolNameMap emitir entradas IDENTIDADE (o Gemini minusculiza nomes de tool nas respostas, entao o tradutor de resposta precisa da chave para mapear de volta), o que passou a incluir `_toolNameMap` no envelope Antigravity de qualquer request com tools. As 3 linhas sao: a chave nova na lista esperada de Object.keys, 1 comentario explicando POR QUE ela aparece (sem ele o proximo leitor tenta remove-la de novo) e 1 assert do CONTEUDO do map — presenca de chave sozinha nao provaria a entrada identidade, que e justamente o comportamento novo. Nao ha o que extrair: e alinhamento de contrato dentro de um teste existente."
|
||||
}
|
||||
|
||||
@@ -92,9 +92,10 @@
|
||||
"_rebaseline_2026_07_13_v3847_release": "39.3 -> 38.0 (-1.3, beyond the 0.5 eps). v3.8.47 cycle drift: the cycle merged ~45 PRs adding API routes (relay repair/free-pool #6909, backpressure #6590, combo context requirements #6907, services/usage endpoints) faster than openapi.yaml documentation; same class as the v3.8.34/v3.8.39 rebaselines. Documented follow-up: raise coverage next cycle via docs/openapi.yaml additions."
|
||||
},
|
||||
"i18nUiCoverage.pct": {
|
||||
"value": 99,
|
||||
"value": 100,
|
||||
"direction": "up",
|
||||
"eps": 0.5,
|
||||
"_tighten_2026_08_08_modality_bridge": "99 -> 100. Tighten required by the PR quality gate after the Modality Bridge UI keys were translated across all 42 non-English locales. CI collect-metrics on PR #9782 measured i18nUiCoverage.pct=100 with 0 ESLint warnings and 0 ESLint errors; locale dry-sync and UI coverage also report 100% with no missing keys or placeholders.",
|
||||
"_rebaseline_2026_07_04_v3844_release": "77.5 -> 76.8 (-0.7, beyond the 0.5 eps). v3.8.44 cycle drift surfaced only on the release PR (i18n-ui-coverage does NOT run on PR->release fast-gates). The cycle added ~1352 new UI keys to the en.json denominator (Discovery dashboard tab #5939, Bifrost/Mux embedded-service tabs #5817/#6034, proxy batch-ops #5918, fusion defaults #5598, tool-source toggle #5978, quota-row collapse #5977, CodeWhale/Crush CLI cards #5996/#5970, etc.) that the async i18n translation workflow has not yet back-filled (worst locales measure 76.8; __MISSING__ placeholders count as uncovered by design). Same shape and remedy as _rebaseline_2026_06_28_v3839_release. Recover via the i18n workflow next cycle; tighten with --require-tighten once translations land.",
|
||||
"_rebaseline_2026_06_28_v3839_release": "78.4 -> 77.5 (-0.9, beyond the 0.5 eps). v3.8.39 cycle drift surfaced ONLY on the release PR (i18n-ui-coverage does NOT run on PR->release fast-gates). The cycle added new UI strings (compression studio TOON A/B table, antigravity remote-login dashboard field, amber warning icon) to the en denominator faster than the 37 non-en locales were translated; those locales need `npm run i18n:run` with OMNIROUTE_TRANSLATION_API_KEY (unavailable locally) — same precedent as _rebaseline_2026_06_18_v3828_cycle_close + _quality_rebaseline_2026_06_20_ci_ratchet. Measured by CI collect-metrics (run 28317145160) = 77.5. My release-finalize tree changes no src/i18n/messages/*.json. Tightening is tracked as follow-up (run i18n:run with creds).",
|
||||
"_rebaseline_2026_07_13_v3847_release": "76.8 -> 75.5 (-1.3, beyond the 0.5 eps). v3.8.47 cycle drift: merged UI features added EN strings (relay repair UI #6909, combo builder #6907/#6991, capability override UI #6727) ahead of the 42-locale mirrors; same class as the v3.8.39/v3.8.44 rebaselines.",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: "Guardrails"
|
||||
version: 3.8.50
|
||||
lastUpdated: 2026-08-07
|
||||
lastUpdated: 2026-08-08
|
||||
---
|
||||
|
||||
# Guardrails
|
||||
|
||||
> **Source of truth:** `src/lib/guardrails/`
|
||||
> **Last updated:** 2026-08-07 — v3.8.50 (Modality Bridge PR-1: mode selector, task-aware prompt, describe cache, transparency header + stats)
|
||||
> **Last updated:** 2026-08-08 — v3.8.50 (Modality Bridge PR-3: Audio Bridge runtime and functional Audio settings tab)
|
||||
|
||||
Guardrails enforce safety, policy, and content transformations at the boundary
|
||||
between OmniRoute and upstream providers. Each guardrail can inspect (and
|
||||
@@ -20,12 +20,13 @@ request. Blocking is an explicit decision (`block: true`), never an accident.
|
||||
|
||||
## Built-in Guardrails
|
||||
|
||||
The registry auto-loads four guardrails in priority order on import
|
||||
The registry auto-loads five guardrails in priority order on import
|
||||
(see `registry.ts` → `registerDefaultGuardrails()`):
|
||||
|
||||
| Priority | Name | Stage(s) | File |
|
||||
| -------- | ------------------- | -------------- | --------------------- |
|
||||
| `5` | `vision-bridge` | `preCall` | `visionBridge.ts` |
|
||||
| `6` | `audio-bridge` | `preCall` | `audioBridge.ts` |
|
||||
| `10` | `pii-masker` | `pre` + `post` | `piiMasker.ts` |
|
||||
| `20` | `prompt-injection` | `preCall` | `promptInjection.ts` |
|
||||
| `95` | `credential-masker` | `pre` + `post` | `credentialMasker.ts` |
|
||||
@@ -113,8 +114,8 @@ The new `modalityBridge*` keys are Zod-validated in `updateSettingsSchema`
|
||||
`modalityBridgeVisionMode`, `modalityBridgeVisionModel`,
|
||||
`modalityBridgeVisionTaskAware`, `modalityBridgeVisionPrompt`,
|
||||
`modalityBridgeVisionTimeout`, `modalityBridgeVisionMaxImages`, the
|
||||
`modalityBridgeCache*` trio, and the PR-3-reserved `modalityBridgeAudio*`
|
||||
group. Migration `141_modality_bridge_settings.sql` copies existing legacy
|
||||
`modalityBridgeCache*` trio, and the `modalityBridgeAudio*` group used by the
|
||||
Audio Bridge. Migration `141_modality_bridge_settings.sql` copies existing legacy
|
||||
`visionBridge*` values to the matching new keys (idempotent, never overwrites
|
||||
an operator-set `modalityBridge*` value); the legacy keys stay accepted as a
|
||||
read fallback for one release cycle.
|
||||
@@ -130,10 +131,27 @@ swap is already visible in the response body's `model` field.
|
||||
|
||||
`GET /api/modality-bridge/stats` (management auth, same tier as
|
||||
`GET /api/settings`) returns the in-memory per-modality counters
|
||||
`{ bridged, cacheHits, failures, lastUsedAt }` for `vision` (and the
|
||||
PR-3-reserved `audio`). Counters reset on process restart by design
|
||||
`{ bridged, cacheHits, failures, lastUsedAt }` for `vision` and `audio`.
|
||||
Counters reset on process restart by design
|
||||
(telemetry, not accounting).
|
||||
|
||||
#### Dashboard configuration
|
||||
|
||||
The dedicated dashboard page is
|
||||
`/dashboard/settings/modality-bridge`. Its URL-addressable `Vision`, `Audio`,
|
||||
and `Video` tabs preserve query parameters while switching the `tab` value.
|
||||
The Vision tab exposes enablement, mode, model selection (including the automatic
|
||||
default), task-aware prompting, advanced timeout/image/cache limits, runtime
|
||||
counters, and a guarded sample request. The Audio tab is also live: it exposes
|
||||
enablement, an STT-only model picker with Auto, timeout/max-clip limits, audio
|
||||
counters, and an `input_audio` sample test. Video remains the explicit placeholder
|
||||
tracked in issue `#9760`.
|
||||
|
||||
The former Vision Bridge card under AI settings is a compatibility link to the
|
||||
new page; it no longer owns a second copy of the form. Media Providers also
|
||||
links Image-to-Text and Speech-to-Text workflows to the corresponding Modality
|
||||
Bridge tabs without removing the existing Speech-to-Text playground.
|
||||
|
||||
**Self-loop admission bypass:** when the describe call routes through OmniRoute's
|
||||
own `/v1` self-loop (non-standard provider model), the sub-request sends
|
||||
`x-omniroute-admission-bypass: internal` and is authenticated with the resolved
|
||||
@@ -149,6 +167,64 @@ new mode/task-aware/cache defaults and the settings resolver live in
|
||||
`deps` constructor option so tests can inject fake `getSettings` and
|
||||
`callVisionModel` implementations.
|
||||
|
||||
### Audio Bridge (`audioBridge.ts`) — Modality Bridge PR-3
|
||||
|
||||
Intercepts audio-bearing chat requests before they reach a target that is not
|
||||
known to accept audio input. It never reroutes the chat request: audio parts are
|
||||
transcribed through the existing OpenAI-compatible multipart endpoint and the
|
||||
chosen chat model continues with text transcripts.
|
||||
|
||||
Flow:
|
||||
|
||||
1. Resolve `supportsAudio` through `getResolvedModelCapabilities()`. Explicit
|
||||
provider-registry metadata wins, then static model metadata, then synced
|
||||
`modalities_input`. A declared input list without `audio` is `false`; no
|
||||
capability evidence remains `null`. Both `false` and `null` activate the
|
||||
conservative bridge, while `true` bypasses it.
|
||||
2. Resolve `modalityBridgeAudio*` settings and extract spliceable top-level
|
||||
audio parts from every message through the shared `detectMediaParts()`
|
||||
detector. Supported wire shapes are OpenAI `input_audio`, `audio_url`, and
|
||||
`source.media_type: "audio/*"`. Nested audio is detected for routing but not
|
||||
removed by the splice path. Work is capped by `modalityBridgeAudioMaxClips`;
|
||||
later parts stay untouched.
|
||||
3. Honor a configured `provider/model`, or let `selectAudioBridgeModel()` walk
|
||||
`AUDIO_TRANSCRIPTION_PROVIDERS` in stable catalog order and select the first
|
||||
model with a usable active provider credential.
|
||||
4. `callAudioTranscription()` converts base64/data-URI audio to a multipart
|
||||
`file`, or downloads a remote `audio_url` through the public-only outbound
|
||||
guard with DNS pinning and a 25 MB bound. It then POSTs the file and selected
|
||||
model to the local `/v1/audio/transcriptions` self-loop, authenticated with
|
||||
`resolveSelfLoopBearer()`. The existing transcription route performs normal
|
||||
credential lookup, cooldown/rate-limit handling, and provider dispatch.
|
||||
5. Successful calls replace their parts with `[Audio N]: <transcript>`. Calls
|
||||
run with `Promise.allSettled`: an individual failure preserves that original
|
||||
audio part (#4012 contract). If every call fails and the target is proven
|
||||
`supportsAudio === false`, the parts become
|
||||
`[Audio N]: (unavailable — no STT provider connected)` (#8430 contract). For
|
||||
an unknown target (`null`), an all-failure result stays untouched. A proven
|
||||
text-only target with no usable STT credential receives the same explicit
|
||||
stub without issuing a network call.
|
||||
|
||||
Successful transcripts use the process-wide Modality Bridge LRU/TTL cache. The
|
||||
key combines the audio reference, the stable `audio-transcription` operation
|
||||
label, and selected STT model; failures are never cached. Audio attempts update
|
||||
the shared `bridged`, `cacheHits`, `failures`, and `lastUsedAt` counters.
|
||||
Transformed responses carry
|
||||
`x-omniroute-modality-bridge: audio->text;model=<sttModel>;parts=<n>`; untouched
|
||||
requests do not receive an Audio Bridge segment.
|
||||
|
||||
Runtime settings are DB-backed and Zod-validated:
|
||||
|
||||
| Key | Default | Range |
|
||||
| ----------------------------- | ------- | -------------- |
|
||||
| `modalityBridgeAudioEnabled` | `true` | — |
|
||||
| `modalityBridgeAudioModel` | `""` | Auto or STT ID |
|
||||
| `modalityBridgeAudioTimeout` | `60000` | 1000–300000 |
|
||||
| `modalityBridgeAudioMaxClips` | `3` | 1–10 |
|
||||
|
||||
The shared cache remains controlled by `modalityBridgeCacheEnabled`,
|
||||
`modalityBridgeCacheTtlMinutes`, and `modalityBridgeCacheMaxEntries`.
|
||||
|
||||
### PII Masker (`piiMasker.ts`)
|
||||
|
||||
Runs on **both** stages.
|
||||
@@ -354,10 +430,22 @@ Environment variables read by the built-in guardrails:
|
||||
| `PII_REDACTION_ENABLED` | `pii-masker` | When `true`, request PII is redacted (independent of injection mode). |
|
||||
| `PII_RESPONSE_SANITIZATION` / `_MODE` | `pii-masker` (downstream) | Controls response-side masker behavior. |
|
||||
|
||||
The Vision Bridge reads runtime config from the DB-backed settings store
|
||||
(`getSettings()`), not env vars: `visionBridgeEnabled`, `visionBridgeModel`,
|
||||
`visionBridgePrompt`, `visionBridgeTimeout`, `visionBridgeMaxImages`. Defaults
|
||||
live in `src/shared/constants/visionBridgeDefaults.ts`.
|
||||
The Modality Bridge guardrails read runtime config from the DB-backed settings
|
||||
store (`getSettings()`), not env vars. Vision's primary keys are
|
||||
`modalityBridgeVisionEnabled`, `modalityBridgeVisionMode`,
|
||||
`modalityBridgeVisionModel`, `modalityBridgeVisionTaskAware`,
|
||||
`modalityBridgeVisionPrompt`, `modalityBridgeVisionTimeout`,
|
||||
`modalityBridgeVisionMaxImages`, `modalityBridgeCacheEnabled`,
|
||||
`modalityBridgeCacheTtlMinutes`, and `modalityBridgeCacheMaxEntries`. The legacy
|
||||
`visionBridge*` keys are accepted only as the documented one-cycle read
|
||||
fallback; dashboard writes use the primary keys. Defaults and the fallback
|
||||
resolver live in `src/shared/constants/modalityBridgeDefaults.ts`, with legacy
|
||||
constants retained in `src/shared/constants/visionBridgeDefaults.ts`.
|
||||
|
||||
Audio uses `modalityBridgeAudioEnabled`, `modalityBridgeAudioModel`,
|
||||
`modalityBridgeAudioTimeout`, and `modalityBridgeAudioMaxClips`, plus the shared
|
||||
`modalityBridgeCache*` settings. Audio has no legacy-key fallback because these
|
||||
keys were introduced with the Modality Bridge schema.
|
||||
|
||||
## Custom Guardrails
|
||||
|
||||
@@ -396,9 +484,11 @@ Steps:
|
||||
|
||||
Use `resetGuardrailsForTests()` between tests to start from a known state.
|
||||
Pass `{ registerDefaults: false }` to start with an empty registry and
|
||||
register only the guardrails under test. The Vision Bridge guardrail accepts
|
||||
dependency injection (`deps.getSettings`, `deps.callVisionModel`) so tests can
|
||||
exercise the full flow without DB or network access.
|
||||
register only the guardrails under test. Vision Bridge accepts dependency
|
||||
injection (`deps.getSettings`, `deps.callVisionModel`); Audio Bridge exposes the
|
||||
equivalent seams for settings, capabilities, STT model selection, credential
|
||||
checks, and transcription. Tests can therefore exercise both flows without DB
|
||||
or network access.
|
||||
|
||||
## See Also
|
||||
|
||||
@@ -407,6 +497,7 @@ exercise the full flow without DB or network access.
|
||||
prompt-injection and PII masking
|
||||
- `src/shared/constants/visionBridgeDefaults.ts` — Vision Bridge defaults and
|
||||
forced-bridge model list
|
||||
- `src/shared/constants/modalityBridgeDefaults.ts` — shared Vision/Audio runtime defaults
|
||||
- `docs/architecture/RESILIENCE_GUIDE.md` — orthogonal layer (circuit breaker, cooldowns)
|
||||
- `docs/reference/ENVIRONMENT.md` — full env var reference
|
||||
|
||||
|
||||
@@ -603,7 +603,7 @@ export interface ProviderNodeRow {
|
||||
}
|
||||
|
||||
/** Hosts reachable only from the operator's machine/Docker network. */
|
||||
export function isLoopbackNodeHost(baseUrl: string): boolean {
|
||||
function isLoopbackNodeHost(baseUrl: string): boolean {
|
||||
try {
|
||||
const hostname = new URL(baseUrl).hostname;
|
||||
return (
|
||||
|
||||
@@ -149,18 +149,6 @@ export const ERROR_RULES: ErrorRule[] = [
|
||||
backoff: true,
|
||||
reason: "quota_exhausted",
|
||||
},
|
||||
{
|
||||
id: "out_of_extra_usage",
|
||||
text: "out of extra usage",
|
||||
backoff: true,
|
||||
reason: "quota_exhausted",
|
||||
},
|
||||
{
|
||||
id: "extra_usage_required",
|
||||
text: "extra usage required",
|
||||
backoff: true,
|
||||
reason: "quota_exhausted",
|
||||
},
|
||||
{ id: "capacity", text: "capacity", backoff: true, reason: "model_capacity" },
|
||||
{ id: "overloaded", text: "overloaded", backoff: true, reason: "model_capacity" },
|
||||
{ id: "high_demand", text: "high demand", backoff: true, reason: "model_capacity" },
|
||||
|
||||
@@ -12,10 +12,6 @@ import { FREEPIK_IMAGE_PROVIDER } from "./providers/registry/freepik/index.ts";
|
||||
import { STABILITY_AI_IMAGE_MODELS } from "./providers/registry/stability-ai/imageModels.ts";
|
||||
import { GEMINI_IMAGEN_PROVIDER } from "./providers/registry/gemini/imageModels.ts";
|
||||
import { CHEAPERINFERENCE_IMAGE_PROVIDER } from "./providers/registry/cheaperinference/imageModels.ts";
|
||||
import {
|
||||
ADOBE_FIREFLY_IMAGE_ROUTING_ALIASES,
|
||||
toRegistryImageModels,
|
||||
} from "../services/adobeFireflyModels.ts";
|
||||
|
||||
interface ImageModelEntry {
|
||||
id: string;
|
||||
@@ -26,8 +22,6 @@ interface ImageModelEntry {
|
||||
imageRequired?: boolean;
|
||||
description?: string;
|
||||
isMarket?: boolean;
|
||||
supportedSizes?: string[];
|
||||
mediaCapabilities?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ImageProviderConfig {
|
||||
@@ -41,7 +35,6 @@ interface ImageProviderConfig {
|
||||
authHeader: string;
|
||||
format: string;
|
||||
models: ImageModelEntry[];
|
||||
routingAliases?: readonly string[];
|
||||
supportedSizes: string[];
|
||||
}
|
||||
|
||||
@@ -53,7 +46,6 @@ interface ImageModelAliasEntry {
|
||||
inputModalities?: string[];
|
||||
imageRequired?: boolean;
|
||||
description?: string;
|
||||
mediaCapabilities?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ImageCatalogModelEntry {
|
||||
@@ -63,7 +55,6 @@ interface ImageCatalogModelEntry {
|
||||
supportedSizes: string[];
|
||||
inputModalities: string[];
|
||||
description?: string;
|
||||
mediaCapabilities?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const IMAGE_MODEL_ALIASES: Record<string, ImageModelAliasEntry> = {
|
||||
@@ -687,9 +678,55 @@ export const IMAGE_PROVIDERS: Record<string, ImageProviderConfig> = {
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
format: "adobe-firefly-image",
|
||||
models: toRegistryImageModels(),
|
||||
routingAliases: ADOBE_FIREFLY_IMAGE_ROUTING_ALIASES,
|
||||
supportedSizes: [],
|
||||
models: [
|
||||
{
|
||||
id: "nano-banana-pro",
|
||||
name: "Firefly Gemini 3.0 (Nano Banana Pro)",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "nano-banana",
|
||||
name: "Firefly Gemini 2.5 (Nano Banana)",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "nano-banana-2",
|
||||
name: "Firefly Gemini 3.1 (Nano Banana 2)",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{ id: "gpt-image-2", name: "Firefly GPT Image 2", inputModalities: ["text", "image"] },
|
||||
{ id: "gpt-image", name: "Firefly GPT Image 2", inputModalities: ["text", "image"] },
|
||||
{ id: "gpt-image-1.5", name: "Firefly GPT Image 1.5", inputModalities: ["text", "image"] },
|
||||
{ id: "flux-2", name: "Firefly Flux 2", inputModalities: ["text", "image"] },
|
||||
{ id: "flux-pro", name: "Firefly Flux 1.1 Pro", inputModalities: ["text", "image"] },
|
||||
{ id: "flux-ultra", name: "Firefly Flux 1.1 Ultra", inputModalities: ["text", "image"] },
|
||||
{ id: "seedream-4", name: "Firefly Seedream 4.0", inputModalities: ["text", "image"] },
|
||||
{
|
||||
id: "seedream-5-lite",
|
||||
name: "Firefly Seedream 5.0 Lite",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "runway-gen4-image",
|
||||
name: "Firefly Runway Gen-4 Image",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
// Topaz Labs upscalers (inputMediaUseCase: ["upscaling"]).
|
||||
// Served by firefly-3p /v2/3p-images/upsample — see config/upscaleRegistry.ts.
|
||||
{
|
||||
id: "topaz-standard",
|
||||
name: "Firefly Topaz Upscale (Standard)",
|
||||
inputModalities: ["image"],
|
||||
imageRequired: true,
|
||||
},
|
||||
{
|
||||
id: "topaz-bloom",
|
||||
name: "Firefly Topaz Bloom (Creative Upscale)",
|
||||
inputModalities: ["image"],
|
||||
imageRequired: true,
|
||||
},
|
||||
],
|
||||
supportedSizes: ["1:1", "16:9", "9:16", "4:3", "3:4", "1024x1024", "1792x1024", "1024x1792"],
|
||||
},
|
||||
|
||||
// Cheaper Inference (OSS-sponsor gateway). Declared AFTER adobe-firefly on
|
||||
@@ -850,7 +887,7 @@ export function parseImageModel(modelStr) {
|
||||
|
||||
// No provider prefix — try to find the model in every provider
|
||||
for (const [providerId, config] of Object.entries(IMAGE_PROVIDERS)) {
|
||||
if (config.routingAliases?.includes(modelStr) || config.models.some((m) => m.id === modelStr)) {
|
||||
if (config.models.some((m) => m.id === modelStr)) {
|
||||
return { provider: providerId, model: modelStr };
|
||||
}
|
||||
}
|
||||
@@ -869,10 +906,9 @@ function imageProviderCatalogEntries(
|
||||
id: `${providerId}/${model.id}`,
|
||||
name: model.name,
|
||||
provider: providerId,
|
||||
supportedSizes: model.supportedSizes || config.supportedSizes,
|
||||
supportedSizes: config.supportedSizes,
|
||||
inputModalities: model.inputModalities || ["text"],
|
||||
description: model.description || undefined,
|
||||
mediaCapabilities: model.mediaCapabilities,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,32 +8,9 @@ export const gemini_webProvider: RegistryEntry = {
|
||||
baseUrl: "https://gemini.google.com/app",
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
// #9356: `supportsReasoning: false` is a live-behavior statement, not a guess
|
||||
// about the underlying Gemini model. The executor drives the gemini.google.com
|
||||
// web UI by typing a prompt, so it has no thinking-budget control to set and
|
||||
// never surfaces `reasoning_content` — agent routers reading /v1/models must
|
||||
// not select these for reasoning work. `toolCalling: false` is the matching
|
||||
// statement for native function calling; the prompt-emulation shim (#7286)
|
||||
// stays available and is advertised separately as `toolCalling: "emulated"`
|
||||
// on the provider constant (src/shared/constants/providers/web-cookie.ts).
|
||||
models: [
|
||||
{
|
||||
id: "gemini-3.1-pro",
|
||||
name: "Gemini 3.1 Pro",
|
||||
toolCalling: false,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "gemini-3.5-flash",
|
||||
name: "Gemini 3.5 Flash",
|
||||
toolCalling: false,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{
|
||||
id: "gemini-3.1-flash-lite",
|
||||
name: "Gemini 3.1 Flash-Lite",
|
||||
toolCalling: false,
|
||||
supportsReasoning: false,
|
||||
},
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", toolCalling: false },
|
||||
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", toolCalling: false },
|
||||
{ id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash-Lite", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface RegistryModel {
|
||||
supportsReasoning?: boolean;
|
||||
supportedThinkingEfforts?: readonly string[];
|
||||
supportsVision?: boolean;
|
||||
supportsAudio?: boolean;
|
||||
supportsXHighEffort?: boolean;
|
||||
maxOutputTokens?: number;
|
||||
targetFormat?: string;
|
||||
|
||||
@@ -5,17 +5,14 @@
|
||||
* Supports local providers plus hosted task-based APIs such as Runway.
|
||||
*/
|
||||
|
||||
import { parseModelFromRegistry } from "./registryUtils.ts";
|
||||
import { parseModelFromRegistry, getAllModelsFromRegistry } from "./registryUtils.ts";
|
||||
import { RUNWAYML_SUPPORTED_VIDEO_MODELS } from "./runway.ts";
|
||||
import { SEGMIND_VIDEO_MODELS } from "./providers/registry/segmind/videoModels.ts";
|
||||
import { toRegistryVideoModels } from "../services/adobeFireflyModels.ts";
|
||||
|
||||
interface VideoModel {
|
||||
id: string;
|
||||
name: string;
|
||||
isMarket?: boolean;
|
||||
supportedSizes?: string[];
|
||||
mediaCapabilities?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface VideoProvider {
|
||||
@@ -329,7 +326,8 @@ export const VIDEO_PROVIDERS: Record<string, VideoProvider> = {
|
||||
},
|
||||
|
||||
// Adobe Firefly (unofficial) — same IMS/cookie credential as the image entry.
|
||||
// Exact async video models and capabilities from the verified discovery snapshot.
|
||||
// Async 3P video generate + poll (Sora 2, Veo 3.1, Kling …). Fallback list
|
||||
// from models/discovery capture (adobe/get_models.txt).
|
||||
"adobe-firefly": {
|
||||
id: "adobe-firefly",
|
||||
alias: "firefly",
|
||||
@@ -337,7 +335,18 @@ export const VIDEO_PROVIDERS: Record<string, VideoProvider> = {
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
format: "adobe-firefly-video",
|
||||
models: toRegistryVideoModels(),
|
||||
models: [
|
||||
{ id: "sora-2", name: "Firefly Sora 2" },
|
||||
{ id: "sora-2-pro", name: "Firefly Sora 2 Pro" },
|
||||
{ id: "veo-3.1", name: "Firefly Veo 3.1" },
|
||||
{ id: "veo-3.1-fast", name: "Firefly Veo 3.1 Fast" },
|
||||
{ id: "veo-3.1-ref", name: "Firefly Veo 3.1 Reference" },
|
||||
{ id: "kling-3", name: "Firefly Kling v3 Standard I2V" },
|
||||
{ id: "kling-v3-t2v", name: "Firefly Kling v3 Standard T2V" },
|
||||
{ id: "kling-v3-pro-i2v", name: "Firefly Kling v3 Pro I2V" },
|
||||
{ id: "luma-ray3", name: "Firefly Ray3" },
|
||||
{ id: "runway-gen4-turbo", name: "Firefly Runway Gen-4 Video" },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -359,17 +368,5 @@ export function parseVideoModel(modelStr: string | null) {
|
||||
* Get all video models as a flat list
|
||||
*/
|
||||
export function getAllVideoModels() {
|
||||
return Object.entries(VIDEO_PROVIDERS).flatMap(([providerId, config]) =>
|
||||
[providerId, config.alias]
|
||||
.filter((prefix): prefix is string => Boolean(prefix))
|
||||
.flatMap((prefix) =>
|
||||
config.models.map((model) => ({
|
||||
id: `${prefix}/${model.id}`,
|
||||
name: model.name,
|
||||
provider: providerId,
|
||||
supportedSizes: model.supportedSizes || [],
|
||||
mediaCapabilities: model.mediaCapabilities,
|
||||
}))
|
||||
)
|
||||
);
|
||||
return getAllModelsFromRegistry(VIDEO_PROVIDERS);
|
||||
}
|
||||
|
||||
@@ -14,13 +14,9 @@
|
||||
*/
|
||||
|
||||
import { BaseExecutor, type ExecuteInput } from "./base.ts";
|
||||
import { buildErrorBody, sanitizeErrorMessage } from "../utils/error.ts";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
import { prepareToolMessages } from "../translator/webTools.ts";
|
||||
import { buildToolModeResponse } from "./chatgptWebTools.ts";
|
||||
import {
|
||||
checkGeminiWebUnsupportedControls,
|
||||
GEMINI_WEB_UNSUPPORTED_CONTROL_CODE,
|
||||
} from "./gemini-web/capabilities.ts";
|
||||
|
||||
// ─── Constants ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -410,33 +406,6 @@ export class GeminiWebExecutor extends BaseExecutor {
|
||||
const { model, body, stream, credentials, signal, log, onCredentialsRefreshed } = input;
|
||||
const requestBody = body as GeminiRequestBody;
|
||||
|
||||
// #9356: fail fast on controls this provider cannot honor (reasoning_effort
|
||||
// above "minimal", forced tool_choice). Runs before the credential check and
|
||||
// before Playwright launches — the request is unservable no matter which
|
||||
// cookie is used, and answering 200 with ordinary prose made agents believe
|
||||
// their reasoning/tool requirements had been met. See ./gemini-web/capabilities.ts.
|
||||
const violation = checkGeminiWebUnsupportedControls(body as Record<string, unknown>);
|
||||
if (violation) {
|
||||
log?.warn?.(
|
||||
"GEMINI-WEB",
|
||||
`Rejected request: "${violation.param}" is not supported by this provider`
|
||||
);
|
||||
return {
|
||||
response: new Response(
|
||||
JSON.stringify(
|
||||
buildErrorBody(400, violation.message, null, {
|
||||
type: "invalid_request_error",
|
||||
code: GEMINI_WEB_UNSUPPORTED_CONTROL_CODE,
|
||||
})
|
||||
),
|
||||
{ status: 400, headers: { "Content-Type": "application/json" } }
|
||||
),
|
||||
url: GEMINI_URL,
|
||||
headers: {},
|
||||
transformedBody: body,
|
||||
};
|
||||
}
|
||||
|
||||
const cookie = resolveGeminiWebCookie(credentials);
|
||||
if (!cookie) {
|
||||
return {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* Request-contract guards for the Gemini Web executor (#9356).
|
||||
*
|
||||
* gemini-web is not an API client. It launches Playwright, types ONE flat
|
||||
* prompt string into the gemini.google.com `.ql-editor` contenteditable,
|
||||
* presses Enter, and captures the first `StreamGenerate` response off the page
|
||||
* (see ../gemini-web.ts). There is no JSON request body on the wire, which
|
||||
* makes two OpenAI controls structurally impossible to honor:
|
||||
*
|
||||
* • `reasoning_effort` — no field exists to carry a thinking budget. Unlike
|
||||
* deepseek-web or perplexity-web, which post a real payload and can flip a
|
||||
* `thinking_enabled` flag or swap the model preference, there is nothing
|
||||
* here to set.
|
||||
* • forced `tool_choice` — the tools support gemini-web does have is the
|
||||
* prompt-emulation shim (`translator/webTools.ts`, #7286): it ASKS the
|
||||
* model, in prose, to answer with `<tool>{...}</tool>` and parses whatever
|
||||
* comes back. That is best-effort by construction. "required" / "any" /
|
||||
* a named function is a GUARANTEE, and a prompt cannot make one.
|
||||
*
|
||||
* Before this module both were accepted and quietly ignored, so an agent got a
|
||||
* 200 with `finish_reason: "stop"`, no `reasoning_content`, and `tool_calls: []`
|
||||
* and concluded its requirements had been met (#9356). Failing the request is
|
||||
* the honest answer: the caller can drop the control, or route to a model that
|
||||
* actually implements it.
|
||||
*
|
||||
* Deliberately NOT rejected — these are already satisfied or already work:
|
||||
* • `reasoning_effort: "none" | "minimal"` — asking for as little reasoning as
|
||||
* possible is something a non-thinking provider trivially complies with.
|
||||
* • `tool_choice: "auto" | "none"` and plain `tools[]` — the #7286 emulation
|
||||
* path, which several shipped combos depend on (#5240, #8488). Untouched.
|
||||
*
|
||||
* Pure and dependency-free so the whole contract is unit-testable without a
|
||||
* browser.
|
||||
*/
|
||||
|
||||
/** `error.code` on every compatibility rejection raised here. */
|
||||
export const GEMINI_WEB_UNSUPPORTED_CONTROL_CODE = "unsupported_control_for_provider";
|
||||
|
||||
/** Effort levels a non-thinking provider already complies with. */
|
||||
const SATISFIED_EFFORT_LEVELS = new Set(["none", "minimal"]);
|
||||
|
||||
/** `tool_choice` strings that demand a tool call rather than merely offering one. */
|
||||
const FORCING_TOOL_CHOICE_STRINGS = new Set(["required", "any"]);
|
||||
|
||||
/** `tool_choice: { type }` values that pin the model to a specific/any tool. */
|
||||
const FORCING_TOOL_CHOICE_TYPES = new Set(["function", "tool", "any"]);
|
||||
|
||||
export interface GeminiWebCapabilityViolation {
|
||||
/** Which request field could not be honored. */
|
||||
param: "reasoning_effort" | "tool_choice";
|
||||
/** Client-facing explanation — already safe to put in a response body. */
|
||||
message: string;
|
||||
}
|
||||
|
||||
function normalizeString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim().toLowerCase() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when `tool_choice` demands a tool call. Covers the OpenAI strings
|
||||
* ("required"), the Anthropic-flavored ones the translators also emit ("any"),
|
||||
* and the object forms that name a function or force any tool. "auto" / "none"
|
||||
* and every unrecognized shape are treated as non-forcing — this guard only
|
||||
* blocks contracts it is certain gemini-web cannot keep.
|
||||
*/
|
||||
export function isForcingToolChoice(toolChoice: unknown): boolean {
|
||||
const asString = normalizeString(toolChoice);
|
||||
if (asString) return FORCING_TOOL_CHOICE_STRINGS.has(asString);
|
||||
|
||||
if (toolChoice && typeof toolChoice === "object" && !Array.isArray(toolChoice)) {
|
||||
const type = normalizeString((toolChoice as Record<string, unknown>).type);
|
||||
return type !== null && FORCING_TOOL_CHOICE_TYPES.has(type);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** True when `reasoning_effort` asks for MORE thinking than "none at all". */
|
||||
export function requestsThinkingBudget(reasoningEffort: unknown): boolean {
|
||||
const effort = normalizeString(reasoningEffort);
|
||||
if (effort === null) return false;
|
||||
return !SATISFIED_EFFORT_LEVELS.has(effort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect an OpenAI-shaped request body for controls gemini-web cannot honor.
|
||||
* Returns the first violation found, or `null` when the request is servable.
|
||||
*
|
||||
* `reasoning_effort` is checked before `tool_choice` only for determinism; a
|
||||
* request carrying both is rejected either way.
|
||||
*/
|
||||
export function checkGeminiWebUnsupportedControls(
|
||||
body: Record<string, unknown> | null | undefined
|
||||
): GeminiWebCapabilityViolation | null {
|
||||
if (!body || typeof body !== "object") return null;
|
||||
|
||||
if (requestsThinkingBudget(body.reasoning_effort)) {
|
||||
return {
|
||||
param: "reasoning_effort",
|
||||
message:
|
||||
'Model provider "gemini-web" does not support "reasoning_effort". It drives the ' +
|
||||
"gemini.google.com web UI through a typed prompt and has no thinking-budget control " +
|
||||
'to set, so any effort above "minimal" would be silently ignored. Remove ' +
|
||||
'"reasoning_effort" (or send "none"/"minimal") or route to a reasoning-capable model.',
|
||||
};
|
||||
}
|
||||
|
||||
if (isForcingToolChoice(body.tool_choice)) {
|
||||
return {
|
||||
param: "tool_choice",
|
||||
message:
|
||||
'Model provider "gemini-web" cannot guarantee a forced tool call. Its tool support is ' +
|
||||
"prompt-emulated — the model is asked to emit a tool block and may answer with prose " +
|
||||
'instead — so "tool_choice" values that require one ("required", "any", or a named ' +
|
||||
'function) cannot be honored. Use "auto" to keep best-effort tool calling, or route to ' +
|
||||
"a model with native function calling.",
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -7,8 +7,6 @@ import { isCloudflareChallenge } from "../../services/lmarenaTlsClient.ts";
|
||||
import { markLMArenaCatalogModelDead } from "./models.ts";
|
||||
import { parseArenaSSE } from "./stream.ts";
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
export function errorResponse(
|
||||
status: number,
|
||||
message: string,
|
||||
@@ -167,7 +165,7 @@ function baseChunk(model: string) {
|
||||
}
|
||||
|
||||
function enqueueSse(controller: ReadableStreamDefaultController, chunk: Record<string, unknown>) {
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
||||
controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
||||
}
|
||||
|
||||
function emitStopAndDone(controller: ReadableStreamDefaultController, model: string) {
|
||||
@@ -175,8 +173,7 @@ function emitStopAndDone(controller: ReadableStreamDefaultController, model: str
|
||||
...baseChunk(model),
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
});
|
||||
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ import {
|
||||
AdobeFireflyError,
|
||||
adobeFireflyGenerateImage,
|
||||
adobeFireflyImageTimeoutMs,
|
||||
adobeFireflyMaxImageRefs,
|
||||
resolveAdobeAccessToken,
|
||||
resolveAdobeSourceImageReferences,
|
||||
resolveAdobeSourceImageIds,
|
||||
resolveAdobeImageModel,
|
||||
} from "../../../services/adobeFireflyClient.ts";
|
||||
import { getAdobeReferenceUploadLimit } from "../../../services/adobeFireflyModels.ts";
|
||||
import { isAdobeFireflyUpscaleModel } from "../../../services/adobeFireflyUpscale.ts";
|
||||
import { handleAdobeFireflyImageUpscale } from "../../imageUpscale/adobeFirefly.ts";
|
||||
|
||||
@@ -90,8 +90,7 @@ export async function handleAdobeFireflyImageGeneration({
|
||||
|
||||
// Keep the raw credential blob for Cookie + sherlockToken (x-arp-session-id).
|
||||
// JWT may be embedded in the same paste as cookies (HAR / multi-line).
|
||||
const psd = (credentials as { providerSpecificData?: { cookie?: string } })
|
||||
?.providerSpecificData;
|
||||
const psd = (credentials as { providerSpecificData?: { cookie?: string } })?.providerSpecificData;
|
||||
const sessionCookie =
|
||||
(typeof psd?.cookie === "string" && psd.cookie.trim()) ||
|
||||
(typeof credentials?.apiKey === "string" && credentials.apiKey.trim()) ||
|
||||
@@ -99,11 +98,15 @@ export async function handleAdobeFireflyImageGeneration({
|
||||
? credentials.accessToken
|
||||
: undefined);
|
||||
|
||||
const { spec } = resolveAdobeImageModel(model);
|
||||
const references = await resolveAdobeSourceImageReferences({
|
||||
// Cap uploads by model family. gpt-image: 2 subject refs max (3–4+ stalls colligo → 504).
|
||||
// nano: 4 general refs for multi-panel composition.
|
||||
const { id: resolvedId } = resolveAdobeImageModel(model);
|
||||
const maxRefs = adobeFireflyMaxImageRefs(resolvedId);
|
||||
|
||||
const sourceImageIds = await resolveAdobeSourceImageIds({
|
||||
accessToken,
|
||||
body,
|
||||
max: getAdobeReferenceUploadLimit(spec, "image"),
|
||||
max: maxRefs,
|
||||
sessionCookie,
|
||||
prompt,
|
||||
fetchImpl,
|
||||
@@ -118,13 +121,13 @@ export async function handleAdobeFireflyImageGeneration({
|
||||
: undefined;
|
||||
const timeoutMs = adobeFireflyImageTimeoutMs({
|
||||
timeoutMs: explicitTimeout,
|
||||
refCount: references.length,
|
||||
refCount: sourceImageIds.length,
|
||||
});
|
||||
|
||||
log?.info?.(
|
||||
"IMAGE",
|
||||
`${provider}/${model} (adobe-firefly) | prompt: "${prompt.slice(0, 60)}${prompt.length > 60 ? "..." : ""}"` +
|
||||
(references.length ? ` | refs: ${references.length}` : "") +
|
||||
(sourceImageIds.length ? ` | refs: ${sourceImageIds.length}/${maxRefs}` : "") +
|
||||
` | pollTimeoutMs=${timeoutMs}`
|
||||
);
|
||||
|
||||
@@ -136,8 +139,9 @@ export async function handleAdobeFireflyImageGeneration({
|
||||
aspectRatio: body.aspect_ratio ?? body.aspectRatio ?? body.size,
|
||||
quality: body.quality,
|
||||
seed: Number.isFinite(seed as number) ? (seed as number) : undefined,
|
||||
negativePrompt: typeof body.negative_prompt === "string" ? body.negative_prompt : undefined,
|
||||
references: references.length ? references : undefined,
|
||||
negativePrompt:
|
||||
typeof body.negative_prompt === "string" ? body.negative_prompt : undefined,
|
||||
sourceImageIds: sourceImageIds.length ? sourceImageIds : undefined,
|
||||
sessionCookie,
|
||||
timeoutMs,
|
||||
fetchImpl,
|
||||
|
||||
@@ -10,10 +10,9 @@ import {
|
||||
AdobeFireflyError,
|
||||
adobeFireflyGenerateVideo,
|
||||
resolveAdobeAccessToken,
|
||||
resolveAdobeSourceImageReferences,
|
||||
resolveAdobeSourceImageIds,
|
||||
resolveAdobeVideoModel,
|
||||
} from "../../services/adobeFireflyClient.ts";
|
||||
import { getAdobeReferenceUploadLimit } from "../../services/adobeFireflyModels.ts";
|
||||
|
||||
function normalizePositiveNumber(value: unknown, fallback: number): number {
|
||||
const n = Number(value);
|
||||
@@ -56,8 +55,7 @@ export async function handleAdobeFireflyVideoGeneration({
|
||||
? Number(body.seed)
|
||||
: undefined;
|
||||
// Keep raw paste for Cookie + sherlockToken (x-arp-session-id).
|
||||
const psd = (credentials as { providerSpecificData?: { cookie?: string } })
|
||||
?.providerSpecificData;
|
||||
const psd = (credentials as { providerSpecificData?: { cookie?: string } })?.providerSpecificData;
|
||||
const sessionCookie =
|
||||
(typeof psd?.cookie === "string" && psd.cookie.trim()) ||
|
||||
(typeof credentials?.apiKey === "string" && credentials.apiKey.trim()) ||
|
||||
@@ -65,11 +63,13 @@ export async function handleAdobeFireflyVideoGeneration({
|
||||
? credentials.accessToken
|
||||
: undefined);
|
||||
|
||||
const { spec } = resolveAdobeVideoModel(String(model));
|
||||
const references = await resolveAdobeSourceImageReferences({
|
||||
// Kling i2v / Veo ref / Sora frame: upload reference images first.
|
||||
const { id: videoModelId } = resolveAdobeVideoModel(String(model));
|
||||
const maxFrames = videoModelId.includes("kling") || videoModelId.includes("sora") ? 2 : 3;
|
||||
const sourceImageIds = await resolveAdobeSourceImageIds({
|
||||
accessToken,
|
||||
body,
|
||||
max: getAdobeReferenceUploadLimit(spec, "image"),
|
||||
max: maxFrames,
|
||||
sessionCookie,
|
||||
prompt,
|
||||
fetchImpl,
|
||||
@@ -79,7 +79,7 @@ export async function handleAdobeFireflyVideoGeneration({
|
||||
log?.info?.(
|
||||
"VIDEO",
|
||||
`${provider}/${model} (adobe-firefly) | prompt: "${prompt.slice(0, 60)}${prompt.length > 60 ? "..." : ""}"` +
|
||||
(references.length ? ` | refs: ${references.length}` : "")
|
||||
(sourceImageIds.length ? ` | frames: ${sourceImageIds.length}` : "")
|
||||
);
|
||||
|
||||
const result = await adobeFireflyGenerateVideo({
|
||||
@@ -99,7 +99,7 @@ export async function handleAdobeFireflyVideoGeneration({
|
||||
? body.negativePrompt
|
||||
: undefined,
|
||||
generateAudio: body.generate_audio !== false && body.generateAudio !== false,
|
||||
references: references.length ? references : undefined,
|
||||
sourceImageIds: sourceImageIds.length ? sourceImageIds : undefined,
|
||||
sessionCookie,
|
||||
timeoutMs,
|
||||
fetchImpl,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,590 +1,328 @@
|
||||
/**
|
||||
* Adobe Firefly model discovery and normalized media capabilities.
|
||||
* Adobe Firefly model catalog: live discovery + static fallback from browser capture.
|
||||
*
|
||||
* The live discovery schema is authoritative. The generated snapshot is used only
|
||||
* when a request cannot perform authenticated discovery (for example /v1/models).
|
||||
* Live: POST firefly-3p.ff.adobe.io/v2/models/discovery (needs valid IMS token).
|
||||
* Fallback: curated rows from adobe/get_models.txt (2026-07 Firefly SPA capture) so
|
||||
* Media/Models still list usable ids when discovery fails or credentials are missing.
|
||||
*/
|
||||
|
||||
import { ADOBE_FIREFLY_DISCOVERY_SNAPSHOT } from "./adobeFireflyModelSnapshot.ts";
|
||||
|
||||
export type AdobeFireflyModality = "image" | "video" | "audio" | "unknown";
|
||||
|
||||
export interface AdobeFireflyDiscoveredModel {
|
||||
modelId: string;
|
||||
modelVersion: string;
|
||||
displayName: string;
|
||||
modality: AdobeFireflyModality;
|
||||
enabled: boolean;
|
||||
providerName?: string;
|
||||
releaseReadiness?: string;
|
||||
healthStatus?: string;
|
||||
inputMediaUseCases: string[];
|
||||
requestSchema?: Record<string, unknown>;
|
||||
backingModel?: string;
|
||||
}
|
||||
|
||||
export interface AdobeFireflyReferenceInputCapability {
|
||||
mediaType: string;
|
||||
usageType: string;
|
||||
minItems: number;
|
||||
maxItems: number | null;
|
||||
maxFileSizeBytes: number | null;
|
||||
}
|
||||
|
||||
export interface AdobeFireflyMediaCapabilities {
|
||||
inputMediaUseCases: string[];
|
||||
schemaProperties: string[];
|
||||
requiredProperties: string[];
|
||||
referenceInputs: AdobeFireflyReferenceInputCapability[];
|
||||
maxReferenceItems: number | null;
|
||||
supportedSizes: string[];
|
||||
supportedAspectRatios: string[];
|
||||
supportedResolutions: string[];
|
||||
supportedDurations: number[];
|
||||
durationMin: number | null;
|
||||
durationMax: number | null;
|
||||
durationDefault: number | null;
|
||||
outputCountMin: number | null;
|
||||
outputCountMax: number | null;
|
||||
promptMaxLength: number | null;
|
||||
releaseReadiness: string;
|
||||
healthStatus: string;
|
||||
}
|
||||
import {
|
||||
type AdobeFireflyDiscoveredModel,
|
||||
discoverAdobeFireflyModels,
|
||||
resolveAdobeAccessToken,
|
||||
} from "./adobeFireflyClient.ts";
|
||||
|
||||
export interface AdobeFireflyCatalogModel {
|
||||
/** Stable API id without the provider prefix. */
|
||||
/** OpenAI-style id without provider prefix, e.g. nano-banana-pro or flux-fluxPro */
|
||||
id: string;
|
||||
name: string;
|
||||
modality: "image" | "video";
|
||||
/** Upstream wire modelId for generate-async */
|
||||
upstreamModelId: string;
|
||||
/** Upstream wire modelVersion for generate-async */
|
||||
upstreamModelVersion: string;
|
||||
providerName: string;
|
||||
backingModel: string;
|
||||
inputModalities: string[];
|
||||
capabilities: AdobeFireflyMediaCapabilities;
|
||||
inputModalities?: string[];
|
||||
}
|
||||
|
||||
export interface AdobeFireflyImageModelSpec extends AdobeFireflyCatalogModel {
|
||||
modality: "image";
|
||||
/** Payload dialect observed for this model family. */
|
||||
family: "gemini" | "gpt-image" | "generic";
|
||||
}
|
||||
/**
|
||||
* Static fallback built from adobe/get_models.txt discovery response.
|
||||
* Friendly aliases first (Media page defaults), then popular upstream families.
|
||||
*/
|
||||
export const ADOBE_FIREFLY_FALLBACK_MODELS: AdobeFireflyCatalogModel[] = [
|
||||
// ── Friendly aliases (handler resolveAdobeImageModel / resolveAdobeVideoModel) ──
|
||||
{
|
||||
id: "nano-banana-pro",
|
||||
name: "Gemini 3.0 (Nano Banana Pro)",
|
||||
modality: "image",
|
||||
upstreamModelId: "gemini-flash",
|
||||
upstreamModelVersion: "nano-banana-2",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "nano-banana",
|
||||
name: "Gemini 2.5 (Nano Banana)",
|
||||
modality: "image",
|
||||
upstreamModelId: "gemini-flash",
|
||||
upstreamModelVersion: "nano-banana",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "nano-banana-2",
|
||||
name: "Gemini 3.1 (Nano Banana 2)",
|
||||
modality: "image",
|
||||
upstreamModelId: "gemini-flash",
|
||||
upstreamModelVersion: "nano-banana-3",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "gpt-image-2",
|
||||
name: "GPT Image 2",
|
||||
modality: "image",
|
||||
upstreamModelId: "gpt-image",
|
||||
upstreamModelVersion: "2",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "gpt-image",
|
||||
name: "GPT Image 2",
|
||||
modality: "image",
|
||||
upstreamModelId: "gpt-image",
|
||||
upstreamModelVersion: "2",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "gpt-image-1.5",
|
||||
name: "GPT Image 1.5",
|
||||
modality: "image",
|
||||
upstreamModelId: "gpt-image",
|
||||
upstreamModelVersion: "1.5",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "sora-2",
|
||||
name: "Sora 2",
|
||||
modality: "video",
|
||||
upstreamModelId: "sora",
|
||||
upstreamModelVersion: "sora-2",
|
||||
},
|
||||
{
|
||||
id: "sora-2-pro",
|
||||
name: "Sora 2 Pro",
|
||||
modality: "video",
|
||||
upstreamModelId: "sora",
|
||||
upstreamModelVersion: "sora-2-pro",
|
||||
},
|
||||
{
|
||||
id: "veo-3.1",
|
||||
name: "Veo 3.1",
|
||||
modality: "video",
|
||||
upstreamModelId: "veo",
|
||||
upstreamModelVersion: "3.1-generate",
|
||||
},
|
||||
{
|
||||
id: "veo-3.1-fast",
|
||||
name: "Veo 3.1 Fast",
|
||||
modality: "video",
|
||||
upstreamModelId: "veo",
|
||||
upstreamModelVersion: "3.1-fast-generate",
|
||||
},
|
||||
{
|
||||
id: "veo-3.1-ref",
|
||||
name: "Veo 3.1 Reference",
|
||||
modality: "video",
|
||||
upstreamModelId: "veo",
|
||||
upstreamModelVersion: "3.1-generate",
|
||||
},
|
||||
{
|
||||
id: "kling-3",
|
||||
name: "Kling Video v3 Standard Image to Video",
|
||||
modality: "video",
|
||||
upstreamModelId: "kling",
|
||||
upstreamModelVersion: "kling_v3_standard_i2v",
|
||||
},
|
||||
// ── Additional image families from discovery capture ──
|
||||
{
|
||||
id: "flux-2",
|
||||
name: "Flux 2",
|
||||
modality: "image",
|
||||
upstreamModelId: "flux",
|
||||
upstreamModelVersion: "2",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "flux-pro",
|
||||
name: "Flux 1.1 Pro",
|
||||
modality: "image",
|
||||
upstreamModelId: "flux",
|
||||
upstreamModelVersion: "fluxPro",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "flux-ultra",
|
||||
name: "Flux 1.1 Ultra",
|
||||
modality: "image",
|
||||
upstreamModelId: "flux",
|
||||
upstreamModelVersion: "fluxUltra",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "seedream-4",
|
||||
name: "Seedream 4.0",
|
||||
modality: "image",
|
||||
upstreamModelId: "seedream",
|
||||
upstreamModelVersion: "seedream_v4",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "seedream-5-lite",
|
||||
name: "Seedream 5.0 Lite",
|
||||
modality: "image",
|
||||
upstreamModelId: "seedream",
|
||||
upstreamModelVersion: "seedream_v5_lite",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
{
|
||||
id: "runway-gen4-image",
|
||||
name: "Runway Gen-4 Image",
|
||||
modality: "image",
|
||||
upstreamModelId: "runway-gen4-image",
|
||||
upstreamModelVersion: "gen4_image",
|
||||
inputModalities: ["text", "image"],
|
||||
},
|
||||
// ── Additional video families ──
|
||||
{
|
||||
id: "kling-v3-t2v",
|
||||
name: "Kling Video v3 Standard Text to Video",
|
||||
modality: "video",
|
||||
upstreamModelId: "kling",
|
||||
upstreamModelVersion: "kling_v3_standard_t2v",
|
||||
},
|
||||
{
|
||||
id: "kling-v3-pro-i2v",
|
||||
name: "Kling Video v3 Pro Image to Video",
|
||||
modality: "video",
|
||||
upstreamModelId: "kling",
|
||||
upstreamModelVersion: "kling_v3_pro_i2v",
|
||||
},
|
||||
{
|
||||
id: "luma-ray3",
|
||||
name: "Ray3",
|
||||
modality: "video",
|
||||
upstreamModelId: "luma",
|
||||
upstreamModelVersion: "3.0-ray",
|
||||
},
|
||||
{
|
||||
id: "runway-gen4-turbo",
|
||||
name: "Runway Gen-4 Video",
|
||||
modality: "video",
|
||||
upstreamModelId: "runway",
|
||||
upstreamModelVersion: "gen4_turbo",
|
||||
},
|
||||
];
|
||||
|
||||
export interface AdobeFireflyVideoModelSpec extends AdobeFireflyCatalogModel {
|
||||
modality: "video";
|
||||
defaultDuration: number;
|
||||
defaultResolution: string;
|
||||
}
|
||||
|
||||
interface MergedObjectSchema {
|
||||
properties: Record<string, Record<string, unknown>>;
|
||||
required: string[];
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
function asStringArray(value: unknown): string[] {
|
||||
return Array.isArray(value)
|
||||
? value.map((item) => String(item)).filter((item) => item.length > 0)
|
||||
: [];
|
||||
}
|
||||
|
||||
function finiteInteger(value: unknown): number | null {
|
||||
return Number.isInteger(value) ? (value as number) : null;
|
||||
}
|
||||
|
||||
/** Merge object properties/required keys contributed through JSON Schema allOf. */
|
||||
export function mergeAdobeObjectSchema(schema: unknown): MergedObjectSchema {
|
||||
const merged: MergedObjectSchema = { properties: {}, required: [] };
|
||||
const visit = (value: unknown) => {
|
||||
const node = asRecord(value);
|
||||
const properties = asRecord(node.properties);
|
||||
for (const [key, property] of Object.entries(properties)) {
|
||||
merged.properties[key] = asRecord(property);
|
||||
}
|
||||
merged.required.push(...asStringArray(node.required));
|
||||
if (Array.isArray(node.allOf)) node.allOf.forEach(visit);
|
||||
};
|
||||
visit(schema);
|
||||
merged.required = [...new Set(merged.required)];
|
||||
return merged;
|
||||
}
|
||||
|
||||
function schemaBranches(schema: unknown): Record<string, unknown>[] {
|
||||
const root = asRecord(schema);
|
||||
if (Object.keys(root).length === 0) return [];
|
||||
return [
|
||||
root,
|
||||
...(Array.isArray(root.anyOf) ? root.anyOf.map(asRecord) : []),
|
||||
...(Array.isArray(root.oneOf) ? root.oneOf.map(asRecord) : []),
|
||||
];
|
||||
}
|
||||
|
||||
function enumStrings(schema: unknown): string[] {
|
||||
return [
|
||||
...new Set(
|
||||
schemaBranches(schema)
|
||||
.flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function integerBranch(schema: unknown): Record<string, unknown> {
|
||||
return schemaBranches(schema).find((branch) => branch.type === "integer") || {};
|
||||
}
|
||||
|
||||
/** Stable, collision-resistant public id for an exact upstream model/version pair. */
|
||||
/** Stable slug for upstream modelId + modelVersion (catalog id when not a friendly alias). */
|
||||
export function slugifyAdobeModel(modelId: string, modelVersion: string): string {
|
||||
const slug = (value: string, allowDot = false) =>
|
||||
String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(allowDot ? /[^a-z0-9.]+/g : /[^a-z0-9]+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
const family = slug(modelId);
|
||||
// Adobe still uses `kling_v3_omni*` internally, while discovery exposes these
|
||||
// products to users as Kling O3. Never leak the obsolete/internal "omni" name
|
||||
// into the public API catalog; the untouched upstream version stays in the spec.
|
||||
const publicVersion =
|
||||
family === "kling" ? modelVersion.replace(/^kling_v3_omni/i, "kling_o3") : modelVersion;
|
||||
const version = slug(publicVersion, true);
|
||||
if (!version || version === "default" || version === family) return family || "model";
|
||||
return `${family}-${version}`;
|
||||
const mid = String(modelId || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
const ver = String(modelVersion || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9.]+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
if (!ver || ver === "default" || ver === mid) return mid || "model";
|
||||
return `${mid}-${ver}`;
|
||||
}
|
||||
|
||||
/** Parse POST /v2/models/discovery without discarding its resolved request schema. */
|
||||
export function parseAdobeModelsDiscovery(body: unknown): AdobeFireflyDiscoveredModel[] {
|
||||
const root = asRecord(body);
|
||||
const families = Array.isArray(root.models) ? root.models : [];
|
||||
const rows: AdobeFireflyDiscoveredModel[] = [];
|
||||
|
||||
for (const familyValue of families) {
|
||||
const family = asRecord(familyValue);
|
||||
const modelId = String(family.modelId || "").trim();
|
||||
if (!modelId) continue;
|
||||
for (const [modelVersion, versionValue] of Object.entries(asRecord(family.modelVersions))) {
|
||||
const version = asRecord(versionValue);
|
||||
if (version.enabled === false) continue;
|
||||
const outputModalities = asStringArray(version.outputModality).map((item) =>
|
||||
item.toLowerCase()
|
||||
);
|
||||
const modality: AdobeFireflyModality = outputModalities.includes("image")
|
||||
? "image"
|
||||
: outputModalities.includes("video")
|
||||
? "video"
|
||||
: outputModalities.includes("audio")
|
||||
? "audio"
|
||||
: "unknown";
|
||||
rows.push({
|
||||
modelId,
|
||||
modelVersion,
|
||||
displayName: String(
|
||||
version.modelDisplayName || version.modelCaiDisplayName || modelVersion
|
||||
),
|
||||
modality,
|
||||
enabled: version.enabled !== false,
|
||||
providerName:
|
||||
typeof family.acModelFamilyProviderDisplayName === "string"
|
||||
? family.acModelFamilyProviderDisplayName
|
||||
: undefined,
|
||||
releaseReadiness:
|
||||
typeof version.releaseReadiness === "string" ? version.releaseReadiness : undefined,
|
||||
healthStatus: typeof version.healthStatus === "string" ? version.healthStatus : undefined,
|
||||
inputMediaUseCases: asStringArray(version.inputMediaUseCase),
|
||||
requestSchema: asRecord(version.requestSchema),
|
||||
backingModel:
|
||||
typeof version.bksGenerationModel === "string" ? version.bksGenerationModel : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function normalizeCapabilities(row: AdobeFireflyDiscoveredModel): AdobeFireflyMediaCapabilities {
|
||||
const schema = mergeAdobeObjectSchema(row.requestSchema);
|
||||
const referenceSchema = asRecord(schema.properties.referenceBlobs);
|
||||
const referenceInputs: AdobeFireflyReferenceInputCapability[] = [];
|
||||
const mediaCapabilities = Array.isArray(referenceSchema["x-capabilities"])
|
||||
? referenceSchema["x-capabilities"]
|
||||
: [];
|
||||
for (const mediaValue of mediaCapabilities) {
|
||||
const media = asRecord(mediaValue);
|
||||
const maxFileSizeBytes = finiteInteger(media.maxFileSizeBytes);
|
||||
const usageConstraints = Array.isArray(media.usageConstraints) ? media.usageConstraints : [];
|
||||
for (const usageValue of usageConstraints) {
|
||||
const usage = asRecord(usageValue);
|
||||
if (usage.deprecated === true) continue;
|
||||
const usageType = String(usage.usageType || "");
|
||||
const mediaType = String(media.mediaType || "");
|
||||
if (!usageType || !mediaType) continue;
|
||||
referenceInputs.push({
|
||||
mediaType,
|
||||
usageType,
|
||||
minItems: finiteInteger(usage.minItems) ?? 0,
|
||||
maxItems: finiteInteger(usage.maxItems),
|
||||
maxFileSizeBytes,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const supportedSizes = [
|
||||
...new Set(
|
||||
schemaBranches(schema.properties.size)
|
||||
.flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
|
||||
.map(asRecord)
|
||||
.filter((size) => finiteInteger(size.width) !== null && finiteInteger(size.height) !== null)
|
||||
.map((size) => `${size.width}x${size.height}`)
|
||||
),
|
||||
];
|
||||
const supportedAspectRatios = [
|
||||
...new Set(
|
||||
schemaBranches(schema.properties.generationSettings).flatMap((branch) =>
|
||||
enumStrings(asRecord(asRecord(branch.properties).aspectRatio))
|
||||
)
|
||||
),
|
||||
];
|
||||
const duration = integerBranch(schema.properties.duration);
|
||||
const outputCount = integerBranch(schema.properties.n);
|
||||
const prompt =
|
||||
schemaBranches(schema.properties.prompt).find((branch) => branch.type === "string") || {};
|
||||
|
||||
return {
|
||||
inputMediaUseCases: [...row.inputMediaUseCases],
|
||||
schemaProperties: Object.keys(schema.properties),
|
||||
requiredProperties: [...schema.required],
|
||||
referenceInputs,
|
||||
maxReferenceItems: finiteInteger(referenceSchema.maxItems),
|
||||
supportedSizes,
|
||||
supportedAspectRatios,
|
||||
supportedResolutions: enumStrings(schema.properties.resolution),
|
||||
supportedDurations: [
|
||||
...new Set(
|
||||
schemaBranches(schema.properties.duration)
|
||||
.flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
|
||||
.filter((value): value is number => Number.isInteger(value))
|
||||
),
|
||||
],
|
||||
durationMin: finiteInteger(duration.minimum),
|
||||
durationMax: finiteInteger(duration.maximum),
|
||||
durationDefault: finiteInteger(duration.default),
|
||||
outputCountMin: finiteInteger(outputCount.minimum),
|
||||
outputCountMax: finiteInteger(outputCount.maximum),
|
||||
promptMaxLength: finiteInteger(prompt.maxLength),
|
||||
releaseReadiness: row.releaseReadiness || "",
|
||||
healthStatus: row.healthStatus || "",
|
||||
};
|
||||
}
|
||||
|
||||
function isCallableGenerationModel(row: AdobeFireflyDiscoveredModel): boolean {
|
||||
if (row.modality !== "image" && row.modality !== "video") return false;
|
||||
if (!mergeAdobeObjectSchema(row.requestSchema).properties.prompt) return false;
|
||||
const excluded = new Set(["upscaling", "sharpening", "denoising"]);
|
||||
return !row.inputMediaUseCases.some((value) => excluded.has(value.toLowerCase()));
|
||||
}
|
||||
|
||||
function deriveInputModalities(capabilities: AdobeFireflyMediaCapabilities): string[] {
|
||||
return ["text", ...new Set(capabilities.referenceInputs.map((reference) => reference.mediaType))];
|
||||
}
|
||||
|
||||
function semanticCatalogKey(model: AdobeFireflyCatalogModel): string {
|
||||
return JSON.stringify({
|
||||
backingModel: model.backingModel,
|
||||
name: model.name,
|
||||
modality: model.modality,
|
||||
capabilities: model.capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
/** Normalize and de-duplicate callable image/video rows from live discovery. */
|
||||
/** Map discovery rows → catalog entries (image/video only). */
|
||||
export function mapDiscoveredToCatalog(
|
||||
rows: AdobeFireflyDiscoveredModel[]
|
||||
): AdobeFireflyCatalogModel[] {
|
||||
const output: AdobeFireflyCatalogModel[] = [];
|
||||
const out: AdobeFireflyCatalogModel[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (!isCallableGenerationModel(row)) continue;
|
||||
const capabilities = normalizeCapabilities(row);
|
||||
const model: AdobeFireflyCatalogModel = {
|
||||
id: slugifyAdobeModel(row.modelId, row.modelVersion),
|
||||
name: row.displayName,
|
||||
modality: row.modality as "image" | "video",
|
||||
upstreamModelId: row.modelId,
|
||||
upstreamModelVersion: row.modelVersion,
|
||||
providerName: row.providerName || "",
|
||||
backingModel: row.backingModel || "",
|
||||
inputModalities: deriveInputModalities(capabilities),
|
||||
capabilities,
|
||||
};
|
||||
const key = semanticCatalogKey(model);
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
output.push(model);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function snapshotCatalog(): AdobeFireflyCatalogModel[] {
|
||||
return ADOBE_FIREFLY_DISCOVERY_SNAPSHOT.map((model) => {
|
||||
const capabilities: AdobeFireflyMediaCapabilities = {
|
||||
inputMediaUseCases: [...model.inputMediaUseCases],
|
||||
schemaProperties: [...model.schemaProperties],
|
||||
requiredProperties: [...model.requiredProperties],
|
||||
referenceInputs: model.referenceInputs.map((reference) => ({ ...reference })),
|
||||
maxReferenceItems: model.maxReferenceItems,
|
||||
supportedSizes: [...model.supportedSizes],
|
||||
supportedAspectRatios: [...model.supportedAspectRatios],
|
||||
supportedResolutions: [...model.supportedResolutions],
|
||||
supportedDurations: [...model.supportedDurations],
|
||||
durationMin: model.durationMin,
|
||||
durationMax: model.durationMax,
|
||||
durationDefault: model.durationDefault,
|
||||
outputCountMin: model.outputCountMin,
|
||||
outputCountMax: model.outputCountMax,
|
||||
promptMaxLength: model.promptMaxLength,
|
||||
releaseReadiness: model.releaseReadiness,
|
||||
healthStatus: model.healthStatus,
|
||||
};
|
||||
return {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
modality: model.modality,
|
||||
upstreamModelId: model.upstreamModelId,
|
||||
upstreamModelVersion: model.upstreamModelVersion,
|
||||
providerName: model.providerName,
|
||||
backingModel: model.backingModel,
|
||||
inputModalities: deriveInputModalities(capabilities),
|
||||
capabilities,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const ADOBE_FIREFLY_FALLBACK_MODELS: AdobeFireflyCatalogModel[] = snapshotCatalog();
|
||||
|
||||
export function getAdobeFireflyFallbackCatalog(
|
||||
modality?: "image" | "video"
|
||||
): AdobeFireflyCatalogModel[] {
|
||||
return ADOBE_FIREFLY_FALLBACK_MODELS.filter((model) => !modality || model.modality === modality);
|
||||
}
|
||||
|
||||
function imageFamily(model: AdobeFireflyCatalogModel): AdobeFireflyImageModelSpec["family"] {
|
||||
if (model.upstreamModelId === "gemini-flash") return "gemini";
|
||||
if (model.upstreamModelId === "gpt-image" || model.upstreamModelId === "gpt-4o-image") {
|
||||
return "gpt-image";
|
||||
}
|
||||
return "generic";
|
||||
}
|
||||
|
||||
export const ADOBE_FIREFLY_IMAGE_MODELS: Record<string, AdobeFireflyImageModelSpec> =
|
||||
Object.fromEntries(
|
||||
getAdobeFireflyFallbackCatalog("image").map((model) => [
|
||||
model.id,
|
||||
{ ...model, modality: "image" as const, family: imageFamily(model) },
|
||||
])
|
||||
);
|
||||
|
||||
function defaultDuration(model: AdobeFireflyCatalogModel): number {
|
||||
const caps = model.capabilities;
|
||||
return caps.durationDefault ?? caps.supportedDurations[0] ?? caps.durationMin ?? 5;
|
||||
}
|
||||
|
||||
function defaultResolution(model: AdobeFireflyCatalogModel): string {
|
||||
if (model.capabilities.supportedSizes.some((value) => value.includes("1920x1080"))) {
|
||||
return "1080p";
|
||||
}
|
||||
return "720p";
|
||||
}
|
||||
|
||||
export const ADOBE_FIREFLY_VIDEO_MODELS: Record<string, AdobeFireflyVideoModelSpec> =
|
||||
Object.fromEntries(
|
||||
getAdobeFireflyFallbackCatalog("video").map((model) => [
|
||||
model.id,
|
||||
{
|
||||
...model,
|
||||
modality: "video" as const,
|
||||
defaultDuration: defaultDuration(model),
|
||||
defaultResolution: defaultResolution(model),
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
const LEGACY_MODEL_ALIASES: Record<string, string> = {
|
||||
"nano-banana": "gemini-flash-nano-banana",
|
||||
"nano-banana-pro": "gemini-flash-nano-banana-2",
|
||||
"nano-banana-2": "gemini-flash-nano-banana-3",
|
||||
"gpt-image": "gpt-image-2",
|
||||
"gpt-image-2": "gpt-image-2",
|
||||
"gpt-image-1.5": "gpt-image-1.5",
|
||||
"flux-2": "flux-2",
|
||||
"flux-pro": "flux-fluxpro",
|
||||
"flux-ultra": "flux-fluxultra",
|
||||
"seedream-4": "seedream-seedream-v4",
|
||||
"seedream-5-lite": "seedream-seedream-v5-lite",
|
||||
"runway-gen4-image": "runway-gen4-image",
|
||||
"veo-3.1": "veo-3.1-generate",
|
||||
"veo-3.1-fast": "veo-3.1-fast-generate",
|
||||
"luma-ray3": "luma-3.0-ray",
|
||||
"runway-gen4-turbo": "runway-gen4-turbo",
|
||||
// Backward compatibility only; the catalog advertises the exact discovered id.
|
||||
"kling-3": "kling-kling-v3-standard-i2v",
|
||||
};
|
||||
|
||||
// Preserve established API aliases when (and only when) they resolve to a model
|
||||
// that is present in the verified discovery snapshot. These keys are not listed.
|
||||
for (const [alias, target] of Object.entries(LEGACY_MODEL_ALIASES)) {
|
||||
const imageTarget = ADOBE_FIREFLY_IMAGE_MODELS[target];
|
||||
if (imageTarget) ADOBE_FIREFLY_IMAGE_MODELS[alias] = imageTarget;
|
||||
const videoTarget = ADOBE_FIREFLY_VIDEO_MODELS[target];
|
||||
if (videoTarget) ADOBE_FIREFLY_VIDEO_MODELS[alias] = videoTarget;
|
||||
}
|
||||
|
||||
/** Backward-compatible request ids. Kept out of every advertised model catalog. */
|
||||
export const ADOBE_FIREFLY_IMAGE_ROUTING_ALIASES = Object.freeze(
|
||||
Object.entries(LEGACY_MODEL_ALIASES)
|
||||
.filter(([, target]) => Boolean(ADOBE_FIREFLY_IMAGE_MODELS[target]))
|
||||
.map(([alias]) => alias)
|
||||
);
|
||||
|
||||
function normalizeRequestedId(model: string): string {
|
||||
return String(model || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/^adobe-firefly\//, "")
|
||||
.replace(/^firefly\//, "");
|
||||
}
|
||||
|
||||
function resolveCatalogId(model: string): string {
|
||||
const requested = normalizeRequestedId(model);
|
||||
return LEGACY_MODEL_ALIASES[requested] || requested;
|
||||
}
|
||||
|
||||
export function resolveAdobeImageModel(model: string): {
|
||||
id: string;
|
||||
spec: AdobeFireflyImageModelSpec;
|
||||
} {
|
||||
const id = resolveCatalogId(model);
|
||||
const spec = ADOBE_FIREFLY_IMAGE_MODELS[id];
|
||||
if (!spec) {
|
||||
throw new Error(
|
||||
`Unknown Adobe Firefly image model: ${normalizeRequestedId(model) || "(empty)"}`
|
||||
// Prefer friendly aliases when upstream matches known fallback rows.
|
||||
for (const fb of ADOBE_FIREFLY_FALLBACK_MODELS) {
|
||||
const hit = rows.find(
|
||||
(r) =>
|
||||
r.modelId === fb.upstreamModelId &&
|
||||
r.modelVersion === fb.upstreamModelVersion &&
|
||||
(r.modality === fb.modality || r.modality === "unknown")
|
||||
);
|
||||
if (hit && !seen.has(fb.id)) {
|
||||
seen.add(fb.id);
|
||||
out.push({
|
||||
...fb,
|
||||
name: hit.displayName || fb.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
return { id, spec };
|
||||
}
|
||||
|
||||
export function resolveAdobeVideoModel(model: string): {
|
||||
id: string;
|
||||
spec: AdobeFireflyVideoModelSpec;
|
||||
} {
|
||||
const id = resolveCatalogId(model);
|
||||
const spec = ADOBE_FIREFLY_VIDEO_MODELS[id];
|
||||
if (!spec) {
|
||||
throw new Error(
|
||||
`Unknown Adobe Firefly video model: ${normalizeRequestedId(model) || "(empty)"}`
|
||||
);
|
||||
for (const r of rows) {
|
||||
if (r.modality !== "image" && r.modality !== "video") continue;
|
||||
const id = slugifyAdobeModel(r.modelId, r.modelVersion);
|
||||
if (seen.has(id)) continue;
|
||||
// Skip if already covered by a friendly alias with same upstream
|
||||
if (
|
||||
out.some(
|
||||
(o) =>
|
||||
o.upstreamModelId === r.modelId && o.upstreamModelVersion === r.modelVersion
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
out.push({
|
||||
id,
|
||||
name: r.displayName || id,
|
||||
modality: r.modality,
|
||||
upstreamModelId: r.modelId,
|
||||
upstreamModelVersion: r.modelVersion,
|
||||
inputModalities: r.modality === "image" ? ["text", "image"] : ["text"],
|
||||
});
|
||||
}
|
||||
return { id, spec };
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
export function toRegistryImageModels(): Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
inputModalities: string[];
|
||||
imageRequired?: boolean;
|
||||
supportedSizes: string[];
|
||||
mediaCapabilities: Record<string, unknown>;
|
||||
}> {
|
||||
const generated = getAdobeFireflyFallbackCatalog("image").map((model) => ({
|
||||
id: model.id,
|
||||
name: `Firefly ${model.name}`,
|
||||
inputModalities: model.inputModalities,
|
||||
supportedSizes: model.capabilities.supportedSizes,
|
||||
mediaCapabilities: toAdobeMediaCapabilitiesApi(model),
|
||||
}));
|
||||
// Upscaling uses a distinct Firefly endpoint and is not returned by the image
|
||||
// generation discovery schema. Keep its two supported Topaz models visible in
|
||||
// the same provider catalog so image clients can select them deliberately.
|
||||
return [
|
||||
...generated,
|
||||
{
|
||||
id: "topaz-standard",
|
||||
name: "Firefly Topaz Upscale (Standard)",
|
||||
inputModalities: ["image"],
|
||||
imageRequired: true,
|
||||
supportedSizes: [],
|
||||
mediaCapabilities: { input_media_use_cases: ["upscaling"] },
|
||||
},
|
||||
{
|
||||
id: "topaz-bloom",
|
||||
name: "Firefly Topaz Bloom (Creative Upscale)",
|
||||
inputModalities: ["image"],
|
||||
imageRequired: true,
|
||||
supportedSizes: [],
|
||||
mediaCapabilities: { input_media_use_cases: ["upscaling"] },
|
||||
},
|
||||
];
|
||||
export function getAdobeFireflyFallbackCatalog(modality?: "image" | "video"): AdobeFireflyCatalogModel[] {
|
||||
if (!modality) return [...ADOBE_FIREFLY_FALLBACK_MODELS];
|
||||
return ADOBE_FIREFLY_FALLBACK_MODELS.filter((m) => m.modality === modality);
|
||||
}
|
||||
|
||||
export function toRegistryVideoModels(): Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
supportedSizes: string[];
|
||||
mediaCapabilities: Record<string, unknown>;
|
||||
}> {
|
||||
return getAdobeFireflyFallbackCatalog("video").map((model) => ({
|
||||
id: model.id,
|
||||
name: `Firefly ${model.name}`,
|
||||
supportedSizes: model.capabilities.supportedSizes,
|
||||
mediaCapabilities: toAdobeMediaCapabilitiesApi(model),
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Live discovery when credentials resolve; otherwise static fallback from get_models capture.
|
||||
*/
|
||||
export async function resolveAdobeFireflyCatalog(opts: {
|
||||
credentials?: {
|
||||
apiKey?: string;
|
||||
accessToken?: string;
|
||||
providerSpecificData?: Record<string, unknown> | null;
|
||||
} | null;
|
||||
modality?: "image" | "video";
|
||||
fetchImpl?: typeof fetch;
|
||||
}): Promise<{ models: AdobeFireflyCatalogModel[]; source: "api" | "fallback" }> {
|
||||
const fetchImpl = opts.fetchImpl || fetch;
|
||||
try {
|
||||
if (opts.credentials) {
|
||||
const token = await resolveAdobeAccessToken(opts.credentials, fetchImpl);
|
||||
const discovered = await discoverAdobeFireflyModels(token, fetchImpl);
|
||||
let catalog = mapDiscoveredToCatalog(discovered);
|
||||
if (opts.modality) catalog = catalog.filter((m) => m.modality === opts.modality);
|
||||
if (catalog.length > 0) return { models: catalog, source: "api" };
|
||||
}
|
||||
} catch {
|
||||
// fall through to static catalog
|
||||
}
|
||||
|
||||
/** JSON-safe extension emitted by /v1/models. */
|
||||
export function toAdobeMediaCapabilitiesApi(
|
||||
model: AdobeFireflyCatalogModel
|
||||
): Record<string, unknown> {
|
||||
const caps = model.capabilities;
|
||||
return {
|
||||
upstream_model_id: model.upstreamModelId,
|
||||
upstream_model_version: model.upstreamModelVersion,
|
||||
provider_name: model.providerName,
|
||||
release_readiness: caps.releaseReadiness,
|
||||
health_status: caps.healthStatus,
|
||||
input_media_use_cases: caps.inputMediaUseCases,
|
||||
reference_inputs: caps.referenceInputs.map((reference) => ({
|
||||
media_type: reference.mediaType,
|
||||
usage_type: reference.usageType,
|
||||
min_items: reference.minItems,
|
||||
max_items: reference.maxItems,
|
||||
max_file_size_bytes: reference.maxFileSizeBytes,
|
||||
})),
|
||||
max_reference_items: caps.maxReferenceItems,
|
||||
supported_sizes: caps.supportedSizes,
|
||||
supported_aspect_ratios: caps.supportedAspectRatios,
|
||||
supported_resolutions: caps.supportedResolutions,
|
||||
supported_durations: caps.supportedDurations,
|
||||
duration_min: caps.durationMin,
|
||||
duration_max: caps.durationMax,
|
||||
duration_default: caps.durationDefault,
|
||||
output_count_min: caps.outputCountMin,
|
||||
output_count_max: caps.outputCountMax,
|
||||
prompt_max_length: caps.promptMaxLength,
|
||||
models: getAdobeFireflyFallbackCatalog(opts.modality),
|
||||
source: "fallback",
|
||||
};
|
||||
}
|
||||
|
||||
export function getAdobeReferenceUploadLimit(
|
||||
model: AdobeFireflyCatalogModel,
|
||||
mediaType: string
|
||||
): number {
|
||||
if (model.capabilities.maxReferenceItems !== null) {
|
||||
return Math.max(1, Math.min(32, model.capabilities.maxReferenceItems));
|
||||
}
|
||||
const declaredTotal = model.capabilities.referenceInputs
|
||||
.filter((reference) => reference.mediaType === mediaType)
|
||||
.reduce((total, reference) => total + (reference.maxItems ?? 0), 0);
|
||||
return Math.max(1, Math.min(32, declaredTotal || 1));
|
||||
/** Registry-shaped models for imageRegistry / videoRegistry. */
|
||||
export function toRegistryImageModels(
|
||||
models: AdobeFireflyCatalogModel[] = getAdobeFireflyFallbackCatalog("image")
|
||||
): Array<{ id: string; name: string; inputModalities?: string[] }> {
|
||||
return models
|
||||
.filter((m) => m.modality === "image")
|
||||
.map((m) => ({
|
||||
id: m.id,
|
||||
name: m.name.startsWith("Firefly ") ? m.name : `Firefly ${m.name}`,
|
||||
inputModalities: m.inputModalities || ["text", "image"],
|
||||
}));
|
||||
}
|
||||
|
||||
export function toRegistryVideoModels(
|
||||
models: AdobeFireflyCatalogModel[] = getAdobeFireflyFallbackCatalog("video")
|
||||
): Array<{ id: string; name: string }> {
|
||||
return models
|
||||
.filter((m) => m.modality === "video")
|
||||
.map((m) => ({
|
||||
id: m.id,
|
||||
name: m.name.startsWith("Firefly ") ? m.name : `Firefly ${m.name}`,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -190,31 +190,15 @@ export function getBackgroundTaskReason(
|
||||
const messages = toMessageArray(typedBody.messages ?? typedBody.input ?? []);
|
||||
if (!Array.isArray(messages) || messages.length === 0) return null;
|
||||
|
||||
// Derive system content from messages array (OpenAI format) or top-level
|
||||
// system field (Anthropic format).
|
||||
// Find system message
|
||||
const systemMsg = messages.find(
|
||||
(message: BackgroundMessage) => message.role === "system" || message.role === "developer"
|
||||
);
|
||||
let systemContent = "";
|
||||
if (systemMsg && typeof systemMsg.content === "string") {
|
||||
systemContent = systemMsg.content.toLowerCase();
|
||||
} else if (!systemMsg) {
|
||||
// Anthropic top-level system field: string or array of text blocks
|
||||
const raw = (typedBody as Record<string, unknown>).system;
|
||||
if (typeof raw === "string") {
|
||||
systemContent = raw.toLowerCase();
|
||||
} else if (Array.isArray(raw)) {
|
||||
systemContent = raw
|
||||
.map((part) =>
|
||||
part && typeof (part as { text?: unknown }).text === "string"
|
||||
? (part as { text: string }).text
|
||||
: ""
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.toLowerCase();
|
||||
}
|
||||
}
|
||||
if (!systemMsg) return null;
|
||||
|
||||
const systemContent =
|
||||
typeof systemMsg.content === "string" ? systemMsg.content.toLowerCase() : "";
|
||||
|
||||
if (!systemContent) return null;
|
||||
|
||||
// Check against detection patterns
|
||||
|
||||
@@ -190,26 +190,10 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an `error` field carries a real failure signal. A key-presence check
|
||||
* (`!= null`) false-positives on benign values some backends emit on every
|
||||
* chunk (`{}`, `""`, `false`, `0`) — e.g. tool-call turns where a chunk with
|
||||
* real tool_calls content also carries `"error": {}`. Only substantive values
|
||||
* are treated as upstream failures.
|
||||
*/
|
||||
function isSubstantiveError(value: unknown): boolean {
|
||||
if (value === null || value === undefined) return false;
|
||||
if (typeof value === "string") return value.trim().length > 0;
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
return Object.keys(value as Record<string, unknown>).length > 0;
|
||||
}
|
||||
return value === true;
|
||||
}
|
||||
|
||||
function isStreamingUpstreamError(parsed: unknown, eventType: string): boolean {
|
||||
if (eventType === "response.failed" || eventType === "error") return true;
|
||||
if (!isRecord(parsed)) return false;
|
||||
if (isSubstantiveError(parsed.error)) return true;
|
||||
if (parsed.error != null) return true;
|
||||
|
||||
const nestedResponse = isRecord(parsed.response) ? parsed.response : null;
|
||||
return nestedResponse?.status === "failed" && nestedResponse.error != null;
|
||||
|
||||
@@ -724,7 +724,7 @@ export function openaiResponsesToOpenAIRequest(
|
||||
const reasoningRec = toRecord(root.reasoning);
|
||||
const effort = toString(reasoningRec.effort);
|
||||
if (effort && result.reasoning_effort === undefined) {
|
||||
result.reasoning_effort = normalizeResponsesReasoningEffort(effort, model ?? root.model);
|
||||
result.reasoning_effort = normalizeResponsesReasoningEffort(effort, model);
|
||||
}
|
||||
if (
|
||||
credentialRecord._copilotClient === true &&
|
||||
|
||||
@@ -112,7 +112,7 @@ export function geminiToClaudeResponse(chunk, state) {
|
||||
// When the toolNameMap provides a match (e.g., lowercase "bash" → "Bash"),
|
||||
// use it directly without passing through normalizeToolName(), which would
|
||||
// reverse TitleCase back to lowercase via REVERSE_MAP (#9568).
|
||||
const restoredToolName = mappedName ?? normalizeToolName(rawToolName);
|
||||
const restoredToolName = mappedName || normalizeToolName(rawToolName);
|
||||
const idx = state.contentBlockIndex++;
|
||||
const toolId = fc.id || `toolu_${Date.now()}_${idx}`;
|
||||
|
||||
|
||||
@@ -874,7 +874,6 @@ function openaiResponsesToOpenAIResponseStream(chunk, state) {
|
||||
if (state.currentToolCallId) state.toolCallIdsSeen.add(state.currentToolCallId);
|
||||
|
||||
const toolName = normalizeToolName(item.name);
|
||||
state.currentToolName = toolName; // track for schema lookup at done time
|
||||
if (!toolName) {
|
||||
// Some Responses providers briefly emit placeholder/empty tool names.
|
||||
// Defer emission until output_item.done in case the final name is populated there.
|
||||
@@ -920,9 +919,26 @@ function openaiResponsesToOpenAIResponseStream(chunk, state) {
|
||||
state.currentToolCallArgsBuffer = (state.currentToolCallArgsBuffer || "") + argsDelta;
|
||||
if (state.currentToolCallDeferred) return null;
|
||||
|
||||
// #9168: buffer arguments until output_item.done for schema-aware null normalization
|
||||
// Previously emitted raw null values for optional enum fields (e.g. isolation: null).
|
||||
return null;
|
||||
return {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "gpt-4",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: state.toolCallIndex,
|
||||
function: { arguments: argsDelta },
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Function call done — emit args chunk from item.arguments when no deltas were received,
|
||||
@@ -995,35 +1011,6 @@ function openaiResponsesToOpenAIResponseStream(chunk, state) {
|
||||
if (item.arguments != null && !buffered) {
|
||||
const argsToEmit = stripEmptyOptionalToolArgs(item.arguments, toolName, toolSchema);
|
||||
|
||||
const argsStr = typeof argsToEmit === "string" ? argsToEmit : JSON.stringify(argsToEmit);
|
||||
if (argsStr) {
|
||||
return {
|
||||
id: state.chatId,
|
||||
object: "chat.completion.chunk",
|
||||
created: state.created,
|
||||
model: state.model || "gpt-4",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
tool_calls: [
|
||||
{
|
||||
index: currentIndex,
|
||||
function: { arguments: argsStr },
|
||||
},
|
||||
],
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
} else if (buffered) {
|
||||
// #9168: deltas were buffered — normalize against the original client schema
|
||||
// and emit the cleaned arguments once, stripping optional null values that
|
||||
// would otherwise reach the client raw.
|
||||
const argsToEmit = stripEmptyOptionalToolArgs(buffered, toolName, toolSchema);
|
||||
|
||||
const argsStr = typeof argsToEmit === "string" ? argsToEmit : JSON.stringify(argsToEmit);
|
||||
if (argsStr) {
|
||||
return {
|
||||
|
||||
@@ -79,8 +79,7 @@ export function sseCommentsEnabled(): boolean {
|
||||
if (typeof process === "undefined") return true;
|
||||
const v = process.env.OMNIROUTE_SSE_COMMENTS;
|
||||
if (v === undefined || v === "") return true;
|
||||
const normalized = v.trim().toLowerCase();
|
||||
return normalized !== "off" && normalized !== "false" && normalized !== "0" && normalized !== "no";
|
||||
return v.trim().toLowerCase() !== "off";
|
||||
}
|
||||
|
||||
export function createSseHeartbeatTransform({
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from "./streamHelpers.ts";
|
||||
import { calculateCost } from "@/lib/usage/costCalculator";
|
||||
import { buildOmniRouteSseMetadataComment } from "@/domain/omnirouteResponseMeta";
|
||||
import { sseCommentsEnabled } from "./sseHeartbeat.ts";
|
||||
import {
|
||||
createStructuredSSECollector,
|
||||
buildStreamSummaryFromEvents,
|
||||
@@ -1002,11 +1001,6 @@ export function createSSEStream(options: StreamOptions = {}) {
|
||||
controller: TransformStreamDefaultController,
|
||||
finalUsage: UsageTokenRecord | Record<string, unknown> | null | undefined
|
||||
) => {
|
||||
// Skip SSE metadata comment lines when OMNIROUTE_SSE_COMMENTS is disabled
|
||||
// (e.g., "off", "false", "0", "no"). Strict OpenAI-compatible clients that
|
||||
// JSON.parse every SSE line will crash on `: x-omniroute-*` comment lines.
|
||||
if (!sseCommentsEnabled()) return;
|
||||
|
||||
const costUsd = finalUsage ? await calculateCost(provider, model, finalUsage) : 0;
|
||||
const comment = buildOmniRouteSseMetadataComment({
|
||||
provider,
|
||||
|
||||
@@ -34,11 +34,8 @@
|
||||
"scripts/dev/tls-options.mjs",
|
||||
"scripts/check/check-supported-node-runtime.ts",
|
||||
"scripts/dev/sync-env.mjs",
|
||||
"scripts/build/assembleStandalone.mjs",
|
||||
"scripts/build/backendOnlyPages.mjs",
|
||||
"scripts/build/build-next-isolated.mjs",
|
||||
"scripts/build/build-tproxy-native.mjs",
|
||||
"scripts/build/native-binary-compat.mjs",
|
||||
"scripts/build/build-next-isolated.mjs",
|
||||
"scripts/build/runtime-env.mjs",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
|
||||
@@ -89,15 +89,6 @@ const NATIVE_ASSET_ENTRIES = [
|
||||
src: ["node_modules", "better-sqlite3", "build"],
|
||||
dest: ["node_modules", "better-sqlite3", "build"],
|
||||
},
|
||||
{
|
||||
// #8847: Bun (and npx -g global installs) resolve better-sqlite3's native
|
||||
// binary from prebuilds/ instead of build/Release/, so the compiled build/
|
||||
// copy alone leaves a hollow package that falls back to sql.js (OOM under
|
||||
// Bun). Ship the prebuilds alongside the compiled binary.
|
||||
label: "better-sqlite3 prebuilds (Bun / global installs)",
|
||||
src: ["node_modules", "better-sqlite3", "prebuilds"],
|
||||
dest: ["node_modules", "better-sqlite3", "prebuilds"],
|
||||
},
|
||||
{
|
||||
// TPROXY IP_TRANSPARENT addon (Fase 3 / Epic A). Built by build-tproxy-native
|
||||
// before assembly; Linux-only + opt-in, so the source is absent on non-Linux
|
||||
|
||||
@@ -47,8 +47,7 @@
|
||||
*/
|
||||
|
||||
import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, sep } from "node:path";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
/**
|
||||
* Entry packages of the SLM optional stack (the closure roots). `@huggingface/transformers` is
|
||||
@@ -97,33 +96,6 @@ export function computeDependencyClosure(nodeModulesDir, seeds = SEED_PACKAGES)
|
||||
return closure;
|
||||
}
|
||||
|
||||
/**
|
||||
* A package in the target tree counts as PRESENT only when its entrypoint
|
||||
* resolves from inside that tree — the same contract the Dockerfile's
|
||||
* post-build guard enforces. Next's file tracing can materialize a package
|
||||
* PARTIALLY (the package.json lands, the files its `main` points at do not),
|
||||
* and a directory-level `existsSync` check then skips the package forever
|
||||
* while the runtime dies with "Cannot find module <pkg>/dist/index.js".
|
||||
*
|
||||
* @param {string} targetNodeModulesDir
|
||||
* @param {string} name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPackageIntact(targetNodeModulesDir, name) {
|
||||
if (!existsSync(join(targetNodeModulesDir, name))) return false;
|
||||
try {
|
||||
const probe = createRequire(
|
||||
join(targetNodeModulesDir, "__colocate_probe__.js")
|
||||
);
|
||||
const resolved = probe.resolve(name);
|
||||
// A resolution that walked past the target into an ancestor tree does not
|
||||
// prove the target copy is usable.
|
||||
return resolved.startsWith(targetNodeModulesDir + sep);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Co-locate the SLM optional dependency closure from `<rootDir>/node_modules`
|
||||
* into a standalone bundle's `node_modules`.
|
||||
@@ -170,12 +142,11 @@ export function colocateLlmlinguaOptionals({
|
||||
|
||||
const closure = computeDependencyClosure(rootNm, seeds);
|
||||
|
||||
// Check the complete closure rather than only the entry package, and judge
|
||||
// presence by entrypoint integrity — a partially traced directory (see
|
||||
// isPackageIntact) must still receive its missing files.
|
||||
// Check the complete closure rather than only the entry package. A partially
|
||||
// populated bundle must still receive any missing transitive dependencies.
|
||||
if (
|
||||
closure.length > 0 &&
|
||||
closure.every((name) => isPackageIntact(targetNm, name))
|
||||
closure.every((name) => existsSync(join(targetNm, name)))
|
||||
) {
|
||||
return { skipped: true, reason: "already co-located" };
|
||||
}
|
||||
@@ -184,18 +155,11 @@ export function colocateLlmlinguaOptionals({
|
||||
|
||||
for (const name of closure) {
|
||||
const dest = join(targetNm, name);
|
||||
if (isPackageIntact(targetNm, name)) continue;
|
||||
if (existsSync(dest)) continue;
|
||||
|
||||
try {
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
// force:false merges into a partially traced directory: files the trace
|
||||
// already materialized are kept, missing ones (the package payload) are
|
||||
// filled in from the root tree.
|
||||
cpSync(join(rootNm, name), dest, {
|
||||
recursive: true,
|
||||
force: false,
|
||||
errorOnExist: false,
|
||||
});
|
||||
cpSync(join(rootNm, name), dest, { recursive: true });
|
||||
copied++;
|
||||
} catch (err) {
|
||||
log(
|
||||
|
||||
@@ -121,9 +121,6 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [
|
||||
// shipped via package.json "files", so it must be allowed in the tarball.
|
||||
"open-sse/utils/setupPolyfill.ts",
|
||||
"package.json",
|
||||
"scripts/build/assembleStandalone.mjs",
|
||||
"scripts/build/backendOnlyPages.mjs",
|
||||
"scripts/build/build-tproxy-native.mjs",
|
||||
"scripts/build/build-next-isolated.mjs",
|
||||
"scripts/check/check-supported-node-runtime.ts",
|
||||
"scripts/build/native-binary-compat.mjs",
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
function usage() {
|
||||
console.error(
|
||||
"Usage: node scripts/dev/generate-adobe-firefly-snapshot.mjs <discovery.json> <output.ts>"
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const [, , inputArg, outputArg] = process.argv;
|
||||
if (!inputArg || !outputArg) usage();
|
||||
|
||||
const inputPath = path.resolve(inputArg);
|
||||
const outputPath = path.resolve(outputArg);
|
||||
const inputBytes = fs.readFileSync(inputPath);
|
||||
const sourceHash = createHash("sha256").update(inputBytes).digest("hex");
|
||||
const root = JSON.parse(inputBytes.toString("utf8"));
|
||||
|
||||
function mergeObjectSchema(schema) {
|
||||
const merged = { properties: {}, required: [] };
|
||||
const visit = (node) => {
|
||||
if (!node || typeof node !== "object") return;
|
||||
if (node.properties && typeof node.properties === "object") {
|
||||
Object.assign(merged.properties, node.properties);
|
||||
}
|
||||
if (Array.isArray(node.required)) merged.required.push(...node.required);
|
||||
if (Array.isArray(node.allOf)) node.allOf.forEach(visit);
|
||||
};
|
||||
visit(schema);
|
||||
merged.required = [...new Set(merged.required)];
|
||||
return merged;
|
||||
}
|
||||
|
||||
function branches(schema) {
|
||||
if (!schema || typeof schema !== "object") return [];
|
||||
return [schema, ...(schema.anyOf || []), ...(schema.oneOf || [])];
|
||||
}
|
||||
|
||||
function stringEnums(schema) {
|
||||
return [
|
||||
...new Set(
|
||||
branches(schema)
|
||||
.flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
|
||||
.filter((value) => typeof value === "string")
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function integerSchema(schema) {
|
||||
return branches(schema).find((branch) => branch.type === "integer") || {};
|
||||
}
|
||||
|
||||
function publicModelId(modelId, modelVersion) {
|
||||
const slug = (value, allowDot = false) =>
|
||||
String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(allowDot ? /[^a-z0-9.]+/g : /[^a-z0-9]+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
const family = slug(modelId);
|
||||
const publicVersion =
|
||||
family === "kling" ? String(modelVersion).replace(/^kling_v3_omni/i, "kling_o3") : modelVersion;
|
||||
const version = slug(publicVersion, true);
|
||||
if (!version || version === "default" || version === family) return family || "model";
|
||||
return `${family}-${version}`;
|
||||
}
|
||||
|
||||
function normalizeModel(family, modelVersion, version) {
|
||||
const schema = mergeObjectSchema(version.requestSchema);
|
||||
const properties = schema.properties;
|
||||
const referenceSchema = properties.referenceBlobs || {};
|
||||
const referenceInputs = [];
|
||||
for (const media of referenceSchema["x-capabilities"] || []) {
|
||||
for (const usage of media.usageConstraints || []) {
|
||||
if (usage.deprecated === true) continue;
|
||||
referenceInputs.push({
|
||||
mediaType: String(media.mediaType || ""),
|
||||
usageType: String(usage.usageType || ""),
|
||||
minItems: Number.isInteger(usage.minItems) ? usage.minItems : 0,
|
||||
maxItems: Number.isInteger(usage.maxItems) ? usage.maxItems : null,
|
||||
maxFileSizeBytes: Number.isInteger(media.maxFileSizeBytes) ? media.maxFileSizeBytes : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const supportedSizes = [
|
||||
...new Set(
|
||||
branches(properties.size)
|
||||
.flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
|
||||
.filter(
|
||||
(size) =>
|
||||
size &&
|
||||
Number.isInteger(size.width) &&
|
||||
size.width > 0 &&
|
||||
Number.isInteger(size.height) &&
|
||||
size.height > 0
|
||||
)
|
||||
.map((size) => `${size.width}x${size.height}`)
|
||||
),
|
||||
];
|
||||
const supportedAspectRatios = [
|
||||
...new Set(
|
||||
branches(properties.generationSettings).flatMap((branch) =>
|
||||
stringEnums(branch?.properties?.aspectRatio)
|
||||
)
|
||||
),
|
||||
];
|
||||
const duration = integerSchema(properties.duration);
|
||||
const supportedDurations = [
|
||||
...new Set(
|
||||
branches(properties.duration)
|
||||
.flatMap((branch) => (Array.isArray(branch.enum) ? branch.enum : []))
|
||||
.filter(Number.isInteger)
|
||||
),
|
||||
];
|
||||
const prompt = branches(properties.prompt).find((branch) => branch.type === "string") || {};
|
||||
const outputCount = integerSchema(properties.n);
|
||||
|
||||
return {
|
||||
id: publicModelId(family.modelId, modelVersion),
|
||||
name: String(version.modelDisplayName || version.modelCaiDisplayName || modelVersion),
|
||||
modality: version.outputModality[0],
|
||||
upstreamModelId: family.modelId,
|
||||
upstreamModelVersion: modelVersion,
|
||||
providerName: String(family.acModelFamilyProviderDisplayName || ""),
|
||||
releaseReadiness: String(version.releaseReadiness || ""),
|
||||
healthStatus: String(version.healthStatus || ""),
|
||||
inputMediaUseCases: (version.inputMediaUseCase || []).map(String),
|
||||
schemaProperties: Object.keys(properties),
|
||||
requiredProperties: schema.required,
|
||||
referenceInputs,
|
||||
maxReferenceItems: Number.isInteger(referenceSchema.maxItems) ? referenceSchema.maxItems : null,
|
||||
supportedSizes,
|
||||
supportedAspectRatios,
|
||||
supportedResolutions: stringEnums(properties.resolution),
|
||||
supportedDurations,
|
||||
durationMin: Number.isInteger(duration.minimum) ? duration.minimum : null,
|
||||
durationMax: Number.isInteger(duration.maximum) ? duration.maximum : null,
|
||||
durationDefault: Number.isInteger(duration.default) ? duration.default : null,
|
||||
outputCountMin: Number.isInteger(outputCount.minimum) ? outputCount.minimum : null,
|
||||
outputCountMax: Number.isInteger(outputCount.maximum) ? outputCount.maximum : null,
|
||||
promptMaxLength: Number.isInteger(prompt.maxLength) ? prompt.maxLength : null,
|
||||
backingModel: String(version.bksGenerationModel || ""),
|
||||
};
|
||||
}
|
||||
|
||||
const rawModels = [];
|
||||
for (const family of Array.isArray(root.models) ? root.models : []) {
|
||||
for (const [modelVersion, version] of Object.entries(family.modelVersions || {})) {
|
||||
if (!version || version.enabled === false) continue;
|
||||
const modality = Array.isArray(version.outputModality)
|
||||
? version.outputModality.map((value) => String(value).toLowerCase())[0]
|
||||
: "";
|
||||
if (modality !== "image" && modality !== "video") continue;
|
||||
|
||||
const schema = mergeObjectSchema(version.requestSchema);
|
||||
if (!schema.properties.prompt) continue;
|
||||
const useCases = (version.inputMediaUseCase || []).map((value) => String(value).toLowerCase());
|
||||
if (useCases.some((value) => ["upscaling", "sharpening", "denoising"].includes(value))) {
|
||||
continue;
|
||||
}
|
||||
rawModels.push(normalizeModel(family, modelVersion, version));
|
||||
}
|
||||
}
|
||||
|
||||
// Discovery currently repeats a few exact aliases (for example flux/fluxPro and
|
||||
// fluxPro/1.1). Keep the first canonical wire pair and suppress duplicate cards.
|
||||
const seen = new Set();
|
||||
const models = [];
|
||||
for (const model of rawModels) {
|
||||
const semanticKey = JSON.stringify({
|
||||
backingModel: model.backingModel,
|
||||
name: model.name,
|
||||
modality: model.modality,
|
||||
schemaProperties: model.schemaProperties,
|
||||
requiredProperties: model.requiredProperties,
|
||||
referenceInputs: model.referenceInputs,
|
||||
maxReferenceItems: model.maxReferenceItems,
|
||||
supportedSizes: model.supportedSizes,
|
||||
supportedAspectRatios: model.supportedAspectRatios,
|
||||
supportedResolutions: model.supportedResolutions,
|
||||
supportedDurations: model.supportedDurations,
|
||||
durationMin: model.durationMin,
|
||||
durationMax: model.durationMax,
|
||||
});
|
||||
if (seen.has(semanticKey)) continue;
|
||||
seen.add(semanticKey);
|
||||
models.push(model);
|
||||
}
|
||||
|
||||
const source = `/**
|
||||
* Generated from Adobe Firefly POST /v2/models/discovery with resolveSchema=true.
|
||||
* Source SHA-256: ${sourceHash}
|
||||
* Regenerate with scripts/dev/generate-adobe-firefly-snapshot.mjs; do not edit by hand.
|
||||
* The generated literal stays compact to satisfy the repository's line-count gate.
|
||||
*/
|
||||
// prettier-ignore
|
||||
export const ADOBE_FIREFLY_DISCOVERY_SNAPSHOT = ${JSON.stringify(models)} as const;
|
||||
`;
|
||||
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, source, "utf8");
|
||||
console.log(`Wrote ${models.length} models to ${outputPath}`);
|
||||
@@ -609,6 +609,14 @@ async function main() {
|
||||
args: ["run", "check:pack-artifact"],
|
||||
timeout: 20 * 60 * 1000,
|
||||
});
|
||||
// WS1.2 (#7065 class): boot the REAL packed tarball from a clean install —
|
||||
// the runtime gate structure checks cannot provide. Reuses the same dist/ build.
|
||||
slow.push({
|
||||
id: "pack-boot",
|
||||
label: "Tarball boot-smoke (installed CLI serves /health)",
|
||||
args: ["run", "check:pack-boot"],
|
||||
timeout: 15 * 60 * 1000,
|
||||
});
|
||||
}
|
||||
slow.forEach((g) => announce(`${g.label} [parallel]`));
|
||||
const slowResults = await Promise.all(
|
||||
@@ -625,41 +633,6 @@ async function main() {
|
||||
detail: code === 0 ? "pass" : firstFailureLine(out),
|
||||
});
|
||||
});
|
||||
|
||||
if (WITH_BUILD) {
|
||||
// WS1.2 (#7065 class): boot the REAL packed tarball from a clean install.
|
||||
// check:pack-artifact is the builder for dist/ when staging is absent, so the
|
||||
// boot smoke MUST run after it completes. Running both in the parallel wave
|
||||
// races check:pack-boot against dist/server.js creation on clean worktrees.
|
||||
const packArtifactIndex = slow.findIndex((g) => g.id === "pack-artifact");
|
||||
const packArtifactResult = slowResults[packArtifactIndex];
|
||||
const bootLabel = "Tarball boot-smoke (installed CLI serves /health)";
|
||||
|
||||
if (!packArtifactResult || packArtifactResult.code !== 0) {
|
||||
const out = "skipped because package-artifact did not produce a valid dist/ build";
|
||||
saveGateLog("pack-boot", out);
|
||||
record({
|
||||
id: "pack-boot",
|
||||
label: bootLabel,
|
||||
kind: "hard",
|
||||
ok: false,
|
||||
detail: out,
|
||||
});
|
||||
} else {
|
||||
announce(bootLabel);
|
||||
const { code, out } = await runAsync(npmCmd, ["run", "check:pack-boot"], {
|
||||
timeout: 15 * 60 * 1000,
|
||||
});
|
||||
saveGateLog("pack-boot", out);
|
||||
record({
|
||||
id: "pack-boot",
|
||||
label: bootLabel,
|
||||
kind: "hard",
|
||||
ok: code === 0,
|
||||
detail: code === 0 ? "pass" : firstFailureLine(out),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (WITH_BUILD) {
|
||||
// --with-build without the suites (--quick): still verify the package artifact.
|
||||
const { code, out } = await runAsync(npmCmd, ["run", "check:pack-artifact"], {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button } from "@/shared/components";
|
||||
import MediaProviderHeader from "../../components/MediaProviderHeader";
|
||||
@@ -39,7 +40,10 @@ interface MediaProviderPageClientProps {
|
||||
function renderPlayground(
|
||||
kind: MediaKind,
|
||||
providerId: string,
|
||||
imageToTextCopy: { title: string; description: React.ReactNode }
|
||||
bridgeCopy: {
|
||||
imageToText: { title: string; description: React.ReactNode; cta: string };
|
||||
sttCta: string;
|
||||
}
|
||||
) {
|
||||
switch (kind) {
|
||||
case "embedding":
|
||||
@@ -49,7 +53,17 @@ function renderPlayground(
|
||||
case "tts":
|
||||
return <TtsExampleCard providerId={providerId} />;
|
||||
case "stt":
|
||||
return <SttExampleCard providerId={providerId} />;
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<SttExampleCard providerId={providerId} />
|
||||
<Link
|
||||
href="/dashboard/settings/modality-bridge?tab=audio"
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
{bridgeCopy.sttCta}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
case "webSearch":
|
||||
return <WebSearchExampleCard providerId={providerId} />;
|
||||
case "webFetch":
|
||||
@@ -66,9 +80,15 @@ function renderPlayground(
|
||||
<div className="flex flex-col gap-2 border border-dashed border-border rounded-xl p-6">
|
||||
<div className="flex items-center gap-2 text-text-muted">
|
||||
<span className="material-symbols-outlined text-[20px]">image_search</span>
|
||||
<h3 className="text-sm font-medium">{imageToTextCopy.title}</h3>
|
||||
<h3 className="text-sm font-medium">{bridgeCopy.imageToText.title}</h3>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted">{imageToTextCopy.description}</p>
|
||||
<p className="text-xs text-text-muted">{bridgeCopy.imageToText.description}</p>
|
||||
<Link
|
||||
href="/dashboard/settings/modality-bridge?tab=vision"
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
{bridgeCopy.imageToText.cta}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
@@ -197,10 +217,14 @@ export default function MediaProviderPageClient({
|
||||
|
||||
{/* Playground */}
|
||||
{renderPlayground(activeKind, providerId, {
|
||||
title: t("imageToText"),
|
||||
description: t.rich("imageToTextComingSoon", {
|
||||
code: (chunks) => <code className="font-mono bg-bg-subtle px-1 rounded">{chunks}</code>,
|
||||
}),
|
||||
imageToText: {
|
||||
title: t("imageToText"),
|
||||
description: t.rich("imageToTextComingSoon", {
|
||||
code: (chunks) => <code className="rounded bg-bg-subtle px-1 font-mono">{chunks}</code>,
|
||||
}),
|
||||
cta: t("imageToTextBridgeCta"),
|
||||
},
|
||||
sttCta: t("sttBridgeCta"),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -134,7 +134,7 @@ export function LlmChatCard({
|
||||
}: Props) {
|
||||
const t = useTranslations("miniPlayground");
|
||||
const { keys } = useApiKey();
|
||||
const { models, loading, error, retry } = useProviderModels(providerId);
|
||||
const { models } = useProviderModels(providerId);
|
||||
|
||||
const [internalSelectedKey, setInternalSelectedKey] = useState<string>("");
|
||||
const [internalModel, setInternalModel] = useState<string>(initialModel ?? "");
|
||||
@@ -392,31 +392,15 @@ export function LlmChatCard({
|
||||
<select
|
||||
value={model || firstModel}
|
||||
onChange={(e) => setModel(e.target.value)}
|
||||
disabled={loading}
|
||||
className="min-w-0 flex-1 rounded-md border border-border bg-bg-subtle text-xs px-2 py-1 text-text-main focus:outline-none focus:ring-1 focus:ring-primary disabled:opacity-60"
|
||||
className="min-w-0 flex-1 rounded-md border border-border bg-bg-subtle text-xs px-2 py-1 text-text-main focus:outline-none focus:ring-1 focus:ring-primary"
|
||||
>
|
||||
{modelOptions.length === 0 && !loading && <option value="">{initialModel || "—"}</option>}
|
||||
{loading && <option value="">{t("loading") ?? "Loading…"}</option>}
|
||||
{modelOptions.length === 0 && <option value="">{initialModel || "—"}</option>}
|
||||
{modelOptions.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.id}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{error && (
|
||||
<span className="text-xs text-red-500 flex items-center gap-1" role="alert">
|
||||
<span className="truncate max-w-[180px]" title={String(error)}>
|
||||
{String(error)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={retry}
|
||||
className="shrink-0 text-xs text-primary hover:text-primary-strong underline"
|
||||
>
|
||||
{t("retry") ?? "Retry"}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{/* Key select */}
|
||||
{keys.length > 0 && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export interface ProviderModel {
|
||||
id: string;
|
||||
@@ -18,8 +18,6 @@ interface UseProviderModelsResult {
|
||||
models: ProviderModel[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
/** Re-runs the model fetch for the current provider. Useful for a Retry action. */
|
||||
retry: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,14 +32,15 @@ export function useProviderModels(providerId: string): UseProviderModelsResult {
|
||||
const [models, setModels] = useState<ProviderModel[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// Cancels any in-flight load (component unmount or a retry superseding the
|
||||
// previous request) so a stale response never overwrites a newer one.
|
||||
const cleanupRef = useRef<(() => void) | null>(null);
|
||||
|
||||
const load = useCallback(() => {
|
||||
cleanupRef.current?.();
|
||||
useEffect(() => {
|
||||
if (!providerId) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const run = async () => {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
@@ -110,33 +109,11 @@ export function useProviderModels(providerId: string): UseProviderModelsResult {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
};
|
||||
void run();
|
||||
const cleanup = () => {
|
||||
void load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
cleanupRef.current = cleanup;
|
||||
return cleanup;
|
||||
}, [providerId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!providerId) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
return load();
|
||||
}, [providerId, load]);
|
||||
|
||||
// Release the current in-flight cleanup on unmount so no state updates leak.
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
cleanupRef.current?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const retry = useCallback(() => {
|
||||
if (!providerId) return;
|
||||
load();
|
||||
}, [providerId, load]);
|
||||
|
||||
return { models, loading, error, retry };
|
||||
return { models, loading, error };
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import ThinkingBudgetTab from "../components/ThinkingBudgetTab";
|
||||
import VisionBridgeSettingsTab from "../components/VisionBridgeSettingsTab";
|
||||
import ModalityBridgeMovedCard from "../components/ModalityBridgeMovedCard";
|
||||
import SystemPromptTab from "../components/SystemPromptTab";
|
||||
import ResponsesStatePolicyTab from "../components/ResponsesStatePolicyTab";
|
||||
import CodexFastTierTab from "../components/CodexFastTierTab";
|
||||
@@ -19,7 +19,7 @@ export default function SettingsAiPage() {
|
||||
<div className="space-y-6">
|
||||
<p className="text-sm text-text-muted">{t("aiSettingsIntro")}</p>
|
||||
<ThinkingBudgetTab />
|
||||
<VisionBridgeSettingsTab />
|
||||
<ModalityBridgeMovedCard />
|
||||
<SystemPromptTab />
|
||||
<ResponsesStatePolicyTab />
|
||||
<UsageTokenBufferTab />
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
// One-cycle migration notice. The legacy settings component remains in the
|
||||
// codebase for rollback, but Settings → AI no longer renders or writes it.
|
||||
export default function ModalityBridgeMovedCard() {
|
||||
const t = useTranslations("settings");
|
||||
|
||||
return (
|
||||
<section className="rounded-lg border border-border/70 bg-surface/40 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="material-symbols-outlined text-[21px] text-fuchsia-500" aria-hidden="true">
|
||||
image_search
|
||||
</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h4 className="text-base font-semibold text-text-main">
|
||||
{t("modalityBridgeMovedTitle")}
|
||||
</h4>
|
||||
<p className="mt-1 text-sm text-text-muted">{t("modalityBridgeMovedBody")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 border-t border-border pt-3 text-sm">
|
||||
<Link href="/dashboard/settings/modality-bridge" className="text-primary hover:underline">
|
||||
{t("modalityBridgeMovedCta")}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Card, Toggle } from "@/shared/components";
|
||||
import { VISION_BRIDGE_DEFAULTS } from "@/shared/constants/visionBridgeDefaults";
|
||||
|
||||
type SettingsState = {
|
||||
visionBridgeEnabled: boolean;
|
||||
visionBridgeModel: string;
|
||||
visionBridgePrompt: string;
|
||||
visionBridgeTimeout: number;
|
||||
visionBridgeMaxImages: number;
|
||||
};
|
||||
|
||||
export default function VisionBridgeSettingsTab() {
|
||||
const t = useTranslations("settings");
|
||||
const [settings, setSettings] = useState<SettingsState>({
|
||||
visionBridgeEnabled: VISION_BRIDGE_DEFAULTS.enabled,
|
||||
visionBridgeModel: VISION_BRIDGE_DEFAULTS.model,
|
||||
visionBridgePrompt: VISION_BRIDGE_DEFAULTS.prompt,
|
||||
visionBridgeTimeout: VISION_BRIDGE_DEFAULTS.timeoutMs,
|
||||
visionBridgeMaxImages: VISION_BRIDGE_DEFAULTS.maxImagesPerRequest,
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/settings")
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((data) => {
|
||||
if (!data) return;
|
||||
setSettings({
|
||||
visionBridgeEnabled: data.visionBridgeEnabled ?? VISION_BRIDGE_DEFAULTS.enabled,
|
||||
visionBridgeModel: data.visionBridgeModel ?? VISION_BRIDGE_DEFAULTS.model,
|
||||
visionBridgePrompt: data.visionBridgePrompt ?? VISION_BRIDGE_DEFAULTS.prompt,
|
||||
visionBridgeTimeout: data.visionBridgeTimeout ?? VISION_BRIDGE_DEFAULTS.timeoutMs,
|
||||
visionBridgeMaxImages:
|
||||
data.visionBridgeMaxImages ?? VISION_BRIDGE_DEFAULTS.maxImagesPerRequest,
|
||||
});
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
const updateSetting = async (patch: Partial<SettingsState>) => {
|
||||
try {
|
||||
const res = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
if (res.ok) {
|
||||
setSettings((prev) => ({ ...prev, ...patch }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update Vision Bridge settings:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-fuchsia-500/10 text-fuchsia-500">
|
||||
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
|
||||
image_search
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">{t("visionBridge")}</h3>
|
||||
<p className="text-sm text-text-muted">{t("visionBridgeDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<p className="font-medium">{t("visionBridgeEnabledLabel")}</p>
|
||||
<p className="text-sm text-text-muted">{t("visionBridgeEnabledDesc")}</p>
|
||||
</div>
|
||||
<Toggle
|
||||
checked={settings.visionBridgeEnabled}
|
||||
onChange={(checked) => updateSetting({ visionBridgeEnabled: checked })}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-border space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("visionBridgeModel")}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.visionBridgeModel}
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({ ...prev, visionBridgeModel: e.target.value }))
|
||||
}
|
||||
onBlur={() => updateSetting({ visionBridgeModel: settings.visionBridgeModel.trim() })}
|
||||
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm"
|
||||
placeholder={t("visionBridgeModelPlaceholder")}
|
||||
/>
|
||||
<p className="text-xs text-text-muted mt-1">{t("visionBridgeModelHint")}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("visionBridgePrompt")}</label>
|
||||
<textarea
|
||||
value={settings.visionBridgePrompt}
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({ ...prev, visionBridgePrompt: e.target.value }))
|
||||
}
|
||||
onBlur={() =>
|
||||
updateSetting({ visionBridgePrompt: settings.visionBridgePrompt.trim() })
|
||||
}
|
||||
className="min-h-[100px] w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm"
|
||||
placeholder={t("visionBridgePromptPlaceholder")}
|
||||
/>
|
||||
<p className="text-xs text-text-muted mt-1">{t("visionBridgePromptHint")}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">{t("visionBridgeTimeoutMs")}</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1000}
|
||||
max={300000}
|
||||
value={settings.visionBridgeTimeout}
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
visionBridgeTimeout: Number.parseInt(e.target.value, 10) || 0,
|
||||
}))
|
||||
}
|
||||
onBlur={() =>
|
||||
updateSetting({
|
||||
visionBridgeTimeout: Math.min(
|
||||
300000,
|
||||
Math.max(
|
||||
1000,
|
||||
settings.visionBridgeTimeout || VISION_BRIDGE_DEFAULTS.timeoutMs
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">
|
||||
{t("visionBridgeMaxImagesPerRequest")}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={20}
|
||||
value={settings.visionBridgeMaxImages}
|
||||
onChange={(e) =>
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
visionBridgeMaxImages: Number.parseInt(e.target.value, 10) || 0,
|
||||
}))
|
||||
}
|
||||
onBlur={() =>
|
||||
updateSetting({
|
||||
visionBridgeMaxImages: Math.min(
|
||||
20,
|
||||
Math.max(
|
||||
1,
|
||||
settings.visionBridgeMaxImages || VISION_BRIDGE_DEFAULTS.maxImagesPerRequest
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
className="w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Card, ModelSelectField, Toggle } from "@/shared/components";
|
||||
import type { ApiModel } from "@/shared/components/ModelSelectField";
|
||||
import {
|
||||
MODALITY_BRIDGE_DEFAULTS,
|
||||
resolveAudioBridgeRuntimeSettings,
|
||||
} from "@/shared/constants/modalityBridgeDefaults";
|
||||
|
||||
import ModalityBridgeAudioTestButton from "./ModalityBridgeAudioTestButton";
|
||||
import ModalityBridgeStatsRow from "./ModalityBridgeStatsRow";
|
||||
|
||||
interface AudioState {
|
||||
modalityBridgeAudioEnabled: boolean;
|
||||
modalityBridgeAudioModel: string;
|
||||
modalityBridgeAudioTimeout: number;
|
||||
modalityBridgeAudioMaxClips: number;
|
||||
}
|
||||
|
||||
function fromApi(data: Record<string, unknown>): AudioState {
|
||||
const runtime = resolveAudioBridgeRuntimeSettings(data);
|
||||
return {
|
||||
modalityBridgeAudioEnabled: runtime.enabled,
|
||||
modalityBridgeAudioModel: runtime.model,
|
||||
modalityBridgeAudioTimeout: runtime.timeoutMs,
|
||||
modalityBridgeAudioMaxClips: runtime.maxClips,
|
||||
};
|
||||
}
|
||||
|
||||
function asSettingsRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
function clampNumber(raw: string, min: number, max: number, fallback: number): number {
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
return Math.min(max, Math.max(min, Number.isFinite(parsed) ? parsed : fallback));
|
||||
}
|
||||
|
||||
export default function ModalityBridgeAudioTab() {
|
||||
const t = useTranslations("settings");
|
||||
const [settings, setSettings] = useState<AudioState | null>(null);
|
||||
const isSttModel = useCallback(
|
||||
(model: ApiModel) => model.type === "audio" && model.subtype === "transcription",
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetch("/api/settings")
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((data: unknown) => {
|
||||
if (!cancelled) setSettings(fromApi(asSettingsRecord(data)));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSettings(fromApi({}));
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const update = async (patch: Partial<AudioState>) => {
|
||||
try {
|
||||
const response = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
if (response.ok) {
|
||||
setSettings((previous) => (previous ? { ...previous, ...patch } : previous));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update Audio Bridge settings:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!settings) return null;
|
||||
|
||||
const setLocal = (patch: Partial<AudioState>) => {
|
||||
setSettings((previous) => (previous ? { ...previous, ...patch } : previous));
|
||||
};
|
||||
const commitNumber = (
|
||||
key: "modalityBridgeAudioTimeout" | "modalityBridgeAudioMaxClips",
|
||||
raw: string,
|
||||
min: number,
|
||||
max: number,
|
||||
fallback: number
|
||||
) => {
|
||||
const value = clampNumber(raw, min, max, fallback);
|
||||
setLocal({ [key]: value });
|
||||
void update({ [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("modalityBridgeAudioTitle")}
|
||||
subtitle={t("modalityBridgeAudioDesc")}
|
||||
icon="graphic_eq"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Toggle
|
||||
checked={settings.modalityBridgeAudioEnabled}
|
||||
onChange={(checked) => void update({ modalityBridgeAudioEnabled: checked })}
|
||||
label={t("modalityBridgeAudioEnabled")}
|
||||
description={t("modalityBridgeAudioEnabledDesc")}
|
||||
/>
|
||||
|
||||
<ModelSelectField
|
||||
label={t("modalityBridgeAudioModel")}
|
||||
value={settings.modalityBridgeAudioModel}
|
||||
placeholder={t("modalityBridgeAudioModelAuto")}
|
||||
allowEmpty
|
||||
modelFilter={isSttModel}
|
||||
modelSource="catalog"
|
||||
onChange={(value) => void update({ modalityBridgeAudioModel: value })}
|
||||
className="text-sm"
|
||||
/>
|
||||
|
||||
<details className="rounded-control border border-border p-3">
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
{t("modalityBridgeAdvanced")}
|
||||
</summary>
|
||||
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<NumberField
|
||||
testId="modality-bridge-audio-timeout"
|
||||
label={t("modalityBridgeTimeoutMs")}
|
||||
min={1000}
|
||||
max={300000}
|
||||
value={settings.modalityBridgeAudioTimeout}
|
||||
onChange={(value) => setLocal({ modalityBridgeAudioTimeout: value })}
|
||||
onBlur={(raw) =>
|
||||
commitNumber(
|
||||
"modalityBridgeAudioTimeout",
|
||||
raw,
|
||||
1000,
|
||||
300000,
|
||||
MODALITY_BRIDGE_DEFAULTS.audioTimeoutMs
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumberField
|
||||
testId="modality-bridge-audio-max-clips"
|
||||
label={t("modalityBridgeAudioMaxClips")}
|
||||
min={1}
|
||||
max={10}
|
||||
value={settings.modalityBridgeAudioMaxClips}
|
||||
onChange={(value) => setLocal({ modalityBridgeAudioMaxClips: value })}
|
||||
onBlur={(raw) =>
|
||||
commitNumber(
|
||||
"modalityBridgeAudioMaxClips",
|
||||
raw,
|
||||
1,
|
||||
10,
|
||||
MODALITY_BRIDGE_DEFAULTS.audioMaxClips
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<ModalityBridgeStatsRow kind="audio" />
|
||||
<ModalityBridgeAudioTestButton />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
interface NumberFieldProps {
|
||||
testId: string;
|
||||
label: string;
|
||||
min: number;
|
||||
max: number;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
onBlur: (raw: string) => void;
|
||||
}
|
||||
|
||||
function NumberField({ testId, label, min, max, value, onChange, onBlur }: NumberFieldProps) {
|
||||
return (
|
||||
<label className="block text-sm font-medium">
|
||||
{label}
|
||||
<input
|
||||
type="number"
|
||||
data-testid={testId}
|
||||
min={min}
|
||||
max={max}
|
||||
value={value}
|
||||
onChange={(event) => onChange(Number.parseInt(event.currentTarget.value, 10) || 0)}
|
||||
onBlur={(event) => onBlur(event.currentTarget.value)}
|
||||
className="mt-1 w-full rounded-control border border-border bg-surface px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const SAMPLE_INPUT = {
|
||||
model: "modality-bridge/self-test",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Transcribe this audio clip." },
|
||||
{
|
||||
type: "input_audio",
|
||||
input_audio: {
|
||||
data: "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA=",
|
||||
format: "wav",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const DISABLED_GUARDRAILS = [
|
||||
"vision-bridge",
|
||||
"pii-masker",
|
||||
"prompt-injection",
|
||||
"credential-masker",
|
||||
];
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function findAudioMeta(value: unknown): Record<string, unknown> | null {
|
||||
const body = asRecord(value);
|
||||
if (!Array.isArray(body?.results)) return null;
|
||||
for (const entry of body.results) {
|
||||
const result = asRecord(entry);
|
||||
if (result?.guardrail === "audio-bridge") return asRecord(result.meta);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readErrorMessage(value: unknown): string | null {
|
||||
const error = asRecord(asRecord(value)?.error);
|
||||
return typeof error?.message === "string" ? error.message : null;
|
||||
}
|
||||
|
||||
export default function ModalityBridgeAudioTestButton() {
|
||||
const t = useTranslations("settings");
|
||||
const [running, setRunning] = useState(false);
|
||||
const [result, setResult] = useState<string | null>(null);
|
||||
|
||||
const runTest = async () => {
|
||||
setRunning(true);
|
||||
setResult(null);
|
||||
try {
|
||||
const response = await fetch("/api/guardrails/test", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ input: SAMPLE_INPUT, disabledGuardrails: DISABLED_GUARDRAILS }),
|
||||
});
|
||||
const body: unknown = await response.json().catch(() => null);
|
||||
if (!response.ok) throw new Error(readErrorMessage(body) ?? `HTTP ${response.status}`);
|
||||
|
||||
const meta = findAudioMeta(body);
|
||||
if (typeof meta?.clipsProcessed === "number" && meta.clipsProcessed >= 1) {
|
||||
setResult(
|
||||
t("modalityBridgeAudioTestOk", {
|
||||
count: meta.clipsProcessed,
|
||||
model: String(meta.sttModel ?? "unknown"),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
setResult(t("modalityBridgeAudioTestNoop"));
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setResult(t("modalityBridgeAudioTestError", { message }));
|
||||
} finally {
|
||||
setRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-control border border-border px-3 py-2 text-sm font-medium hover:bg-surface-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={running}
|
||||
onClick={() => void runTest()}
|
||||
>
|
||||
{t(running ? "modalityBridgeAudioTestRunning" : "modalityBridgeAudioTestButton")}
|
||||
</button>
|
||||
{result && (
|
||||
<p className="text-xs text-text-muted" role="status">
|
||||
{result}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ModalityBridgeComingSoonTabProps {
|
||||
bodyKey: string;
|
||||
}
|
||||
|
||||
export default function ModalityBridgeComingSoonTab({ bodyKey }: ModalityBridgeComingSoonTabProps) {
|
||||
const t = useTranslations("settings");
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-card border border-dashed border-border p-6">
|
||||
<div className="flex items-center gap-2 text-text-muted">
|
||||
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
|
||||
hourglass_top
|
||||
</span>
|
||||
<p className="text-sm">{t(bodyKey)}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type BridgeKind = "vision" | "audio";
|
||||
|
||||
interface BridgeStats {
|
||||
bridged: number;
|
||||
cacheHits: number;
|
||||
failures: number;
|
||||
lastUsedAt: string | null;
|
||||
}
|
||||
|
||||
interface ModalityBridgeStatsRowProps {
|
||||
kind: BridgeKind;
|
||||
}
|
||||
|
||||
function parseStats(value: unknown): BridgeStats | null {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const record = value as Record<string, unknown>;
|
||||
const lastUsedAt = record.lastUsedAt;
|
||||
if (lastUsedAt !== null && typeof lastUsedAt !== "string") return null;
|
||||
if (
|
||||
typeof record.bridged !== "number" ||
|
||||
typeof record.cacheHits !== "number" ||
|
||||
typeof record.failures !== "number"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
bridged: record.bridged,
|
||||
cacheHits: record.cacheHits,
|
||||
failures: record.failures,
|
||||
lastUsedAt: typeof lastUsedAt === "string" ? lastUsedAt : null,
|
||||
};
|
||||
}
|
||||
|
||||
export default function ModalityBridgeStatsRow({ kind }: ModalityBridgeStatsRowProps) {
|
||||
const t = useTranslations("settings");
|
||||
const [stats, setStats] = useState<BridgeStats | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetch("/api/modality-bridge/stats")
|
||||
.then((response) => (response.ok ? response.json() : Promise.reject(new Error("fetch"))))
|
||||
.then((data: unknown) => {
|
||||
if (cancelled || !data || typeof data !== "object") return;
|
||||
setStats(parseStats((data as Record<string, unknown>)[kind]));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setStats(null);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [kind]);
|
||||
|
||||
if (!stats) return null;
|
||||
|
||||
const lastUsed = stats.lastUsedAt
|
||||
? new Date(stats.lastUsedAt).toLocaleString()
|
||||
: t("modalityBridgeStatsNever");
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-muted" aria-live="polite">
|
||||
<span>
|
||||
{stats.bridged} {t("modalityBridgeStatsBridged")}
|
||||
</span>
|
||||
<span>
|
||||
{stats.cacheHits} {t("modalityBridgeStatsCacheHits")}
|
||||
</span>
|
||||
<span>
|
||||
{stats.failures} {t("modalityBridgeStatsFailures")}
|
||||
</span>
|
||||
<span>
|
||||
{t("modalityBridgeStatsLastUsed")}: {lastUsed}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const SAMPLE_INPUT = {
|
||||
model: "modality-bridge/self-test",
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "What is in this image?" },
|
||||
{
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const DISABLED_GUARDRAILS = ["pii-masker", "prompt-injection", "credential-masker"];
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function findVisionMeta(value: unknown): Record<string, unknown> | null {
|
||||
const body = asRecord(value);
|
||||
if (!Array.isArray(body?.results)) return null;
|
||||
for (const entry of body.results) {
|
||||
const result = asRecord(entry);
|
||||
if (result?.guardrail === "vision-bridge") return asRecord(result.meta);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readErrorMessage(value: unknown): string | null {
|
||||
const body = asRecord(value);
|
||||
const error = asRecord(body?.error);
|
||||
return typeof error?.message === "string" ? error.message : null;
|
||||
}
|
||||
|
||||
export default function ModalityBridgeTestButton() {
|
||||
const t = useTranslations("settings");
|
||||
const [running, setRunning] = useState(false);
|
||||
const [result, setResult] = useState<string | null>(null);
|
||||
|
||||
const runTest = async () => {
|
||||
setRunning(true);
|
||||
setResult(null);
|
||||
try {
|
||||
const response = await fetch("/api/guardrails/test", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
input: SAMPLE_INPUT,
|
||||
disabledGuardrails: DISABLED_GUARDRAILS,
|
||||
}),
|
||||
});
|
||||
const body: unknown = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
throw new Error(readErrorMessage(body) ?? `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const meta = findVisionMeta(body);
|
||||
if (meta?.rerouted === true) {
|
||||
setResult(
|
||||
t("modalityBridgeTestReroute", {
|
||||
model: String(meta.toModel ?? "unknown"),
|
||||
})
|
||||
);
|
||||
} else if (typeof meta?.imagesProcessed === "number" && meta.imagesProcessed >= 1) {
|
||||
setResult(
|
||||
t("modalityBridgeTestOk", {
|
||||
count: meta.imagesProcessed,
|
||||
model: String(meta.visionModel ?? "unknown"),
|
||||
})
|
||||
);
|
||||
} else {
|
||||
setResult(t("modalityBridgeTestNoop"));
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setResult(t("modalityBridgeTestError", { message }));
|
||||
} finally {
|
||||
setRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-control border border-border px-3 py-2 text-sm font-medium hover:bg-surface-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={running}
|
||||
onClick={() => void runTest()}
|
||||
>
|
||||
{t(running ? "modalityBridgeTestRunning" : "modalityBridgeTestButton")}
|
||||
</button>
|
||||
{result && (
|
||||
<p className="text-xs text-text-muted" role="status">
|
||||
{result}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Card, ModelSelectField, Toggle } from "@/shared/components";
|
||||
import {
|
||||
MODALITY_BRIDGE_DEFAULTS,
|
||||
resolveVisionBridgeRuntimeSettings,
|
||||
type VisionBridgeMode,
|
||||
} from "@/shared/constants/modalityBridgeDefaults";
|
||||
import { VISION_BRIDGE_DEFAULTS } from "@/shared/constants/visionBridgeDefaults";
|
||||
|
||||
import ModalityBridgeStatsRow from "./ModalityBridgeStatsRow";
|
||||
import ModalityBridgeTestButton from "./ModalityBridgeTestButton";
|
||||
|
||||
interface VisionState {
|
||||
modalityBridgeVisionEnabled: boolean;
|
||||
modalityBridgeVisionMode: VisionBridgeMode;
|
||||
modalityBridgeVisionModel: string;
|
||||
modalityBridgeVisionTaskAware: boolean;
|
||||
modalityBridgeVisionPrompt: string;
|
||||
modalityBridgeVisionTimeout: number;
|
||||
modalityBridgeVisionMaxImages: number;
|
||||
modalityBridgeCacheEnabled: boolean;
|
||||
modalityBridgeCacheTtlMinutes: number;
|
||||
modalityBridgeCacheMaxEntries: number;
|
||||
}
|
||||
|
||||
function fromApi(data: Record<string, unknown>): VisionState {
|
||||
const runtime = resolveVisionBridgeRuntimeSettings(data);
|
||||
return {
|
||||
modalityBridgeVisionEnabled: runtime.enabled,
|
||||
modalityBridgeVisionMode: runtime.mode,
|
||||
modalityBridgeVisionModel: runtime.model,
|
||||
modalityBridgeVisionTaskAware: runtime.taskAware,
|
||||
modalityBridgeVisionPrompt: runtime.prompt,
|
||||
modalityBridgeVisionTimeout: runtime.timeoutMs,
|
||||
modalityBridgeVisionMaxImages: runtime.maxImages,
|
||||
modalityBridgeCacheEnabled: runtime.cacheEnabled,
|
||||
modalityBridgeCacheTtlMinutes: runtime.cacheTtlMinutes,
|
||||
modalityBridgeCacheMaxEntries: runtime.cacheMaxEntries,
|
||||
};
|
||||
}
|
||||
|
||||
function clampNumber(raw: string, min: number, max: number, fallback: number): number {
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
return Math.min(max, Math.max(min, Number.isFinite(parsed) ? parsed : fallback));
|
||||
}
|
||||
|
||||
export default function ModalityBridgeVisionTab() {
|
||||
const t = useTranslations("settings");
|
||||
const [settings, setSettings] = useState<VisionState | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
fetch("/api/settings")
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((data: unknown) => {
|
||||
if (cancelled) return;
|
||||
setSettings(fromApi(asSettingsRecord(data)));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setSettings(fromApi({}));
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const update = async (patch: Partial<VisionState>) => {
|
||||
try {
|
||||
const response = await fetch("/api/settings", {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
if (response.ok) {
|
||||
setSettings((previous) => (previous ? { ...previous, ...patch } : previous));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update Modality Bridge settings:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!settings) return null;
|
||||
|
||||
const setLocal = (patch: Partial<VisionState>) => {
|
||||
setSettings((previous) => (previous ? { ...previous, ...patch } : previous));
|
||||
};
|
||||
|
||||
const commitNumber = (
|
||||
key:
|
||||
| "modalityBridgeVisionTimeout"
|
||||
| "modalityBridgeVisionMaxImages"
|
||||
| "modalityBridgeCacheTtlMinutes"
|
||||
| "modalityBridgeCacheMaxEntries",
|
||||
raw: string,
|
||||
min: number,
|
||||
max: number,
|
||||
fallback: number
|
||||
) => {
|
||||
const value = clampNumber(raw, min, max, fallback);
|
||||
setLocal({ [key]: value });
|
||||
void update({ [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("modalityBridgeVisionTitle")}
|
||||
subtitle={t("modalityBridgeVisionDesc")}
|
||||
icon="image_search"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Toggle
|
||||
checked={settings.modalityBridgeVisionEnabled}
|
||||
onChange={(checked) => void update({ modalityBridgeVisionEnabled: checked })}
|
||||
label={t("visionBridgeEnabledLabel")}
|
||||
description={t("visionBridgeEnabledDesc")}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium" htmlFor="modality-bridge-mode">
|
||||
{t("modalityBridgeMode")}
|
||||
</label>
|
||||
<select
|
||||
id="modality-bridge-mode"
|
||||
data-testid="modality-bridge-mode"
|
||||
className="mt-1 w-full rounded-control border border-border bg-surface px-3 py-2 text-sm"
|
||||
value={settings.modalityBridgeVisionMode}
|
||||
onChange={(event) =>
|
||||
void update({ modalityBridgeVisionMode: event.target.value as VisionBridgeMode })
|
||||
}
|
||||
>
|
||||
<option value="auto">{t("modalityBridgeModeAuto")}</option>
|
||||
<option value="describe">{t("modalityBridgeModeDescribe")}</option>
|
||||
<option value="reroute">{t("modalityBridgeModeReroute")}</option>
|
||||
</select>
|
||||
<p className="mt-1 text-xs text-text-muted">
|
||||
{settings.modalityBridgeVisionMode === "auto" && t("modalityBridgeModeAutoHint")}
|
||||
{settings.modalityBridgeVisionMode === "describe" &&
|
||||
t("modalityBridgeModeDescribeHint")}
|
||||
{settings.modalityBridgeVisionMode === "reroute" && t("modalityBridgeModeRerouteHint")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ModelSelectField
|
||||
label={t("modalityBridgeVisionModel")}
|
||||
value={settings.modalityBridgeVisionModel}
|
||||
placeholder={t("modalityBridgeVisionModelAuto")}
|
||||
allowEmpty
|
||||
onChange={(value) => void update({ modalityBridgeVisionModel: value })}
|
||||
className="text-sm"
|
||||
/>
|
||||
|
||||
<Toggle
|
||||
checked={settings.modalityBridgeVisionTaskAware}
|
||||
onChange={(checked) => void update({ modalityBridgeVisionTaskAware: checked })}
|
||||
label={t("modalityBridgeTaskAware")}
|
||||
description={t("modalityBridgeTaskAwareDesc")}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium" htmlFor="modality-bridge-prompt">
|
||||
{t("modalityBridgePrompt")}
|
||||
</label>
|
||||
<textarea
|
||||
id="modality-bridge-prompt"
|
||||
className="mt-1 min-h-[100px] w-full rounded-control border border-border bg-surface px-3 py-2 text-sm"
|
||||
value={settings.modalityBridgeVisionPrompt}
|
||||
onChange={(event) =>
|
||||
setLocal({ modalityBridgeVisionPrompt: event.currentTarget.value })
|
||||
}
|
||||
onBlur={(event) => {
|
||||
const value = event.currentTarget.value.trim();
|
||||
setLocal({ modalityBridgeVisionPrompt: value });
|
||||
void update({ modalityBridgeVisionPrompt: value });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<details className="rounded-control border border-border p-3">
|
||||
<summary className="cursor-pointer text-sm font-medium">
|
||||
{t("modalityBridgeAdvanced")}
|
||||
</summary>
|
||||
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<NumberField
|
||||
testId="modality-bridge-timeout"
|
||||
label={t("modalityBridgeTimeoutMs")}
|
||||
min={1000}
|
||||
max={300000}
|
||||
value={settings.modalityBridgeVisionTimeout}
|
||||
onChange={(value) => setLocal({ modalityBridgeVisionTimeout: value })}
|
||||
onBlur={(raw) =>
|
||||
commitNumber(
|
||||
"modalityBridgeVisionTimeout",
|
||||
raw,
|
||||
1000,
|
||||
300000,
|
||||
VISION_BRIDGE_DEFAULTS.timeoutMs
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumberField
|
||||
testId="modality-bridge-max-images"
|
||||
label={t("modalityBridgeMaxImages")}
|
||||
min={1}
|
||||
max={20}
|
||||
value={settings.modalityBridgeVisionMaxImages}
|
||||
onChange={(value) => setLocal({ modalityBridgeVisionMaxImages: value })}
|
||||
onBlur={(raw) =>
|
||||
commitNumber(
|
||||
"modalityBridgeVisionMaxImages",
|
||||
raw,
|
||||
1,
|
||||
20,
|
||||
VISION_BRIDGE_DEFAULTS.maxImagesPerRequest
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div className="md:col-span-2">
|
||||
<Toggle
|
||||
checked={settings.modalityBridgeCacheEnabled}
|
||||
onChange={(checked) => void update({ modalityBridgeCacheEnabled: checked })}
|
||||
label={t("modalityBridgeCacheEnabled")}
|
||||
description={t("modalityBridgeCacheEnabledDesc")}
|
||||
/>
|
||||
</div>
|
||||
<NumberField
|
||||
testId="modality-bridge-cache-ttl"
|
||||
label={t("modalityBridgeCacheTtlMinutes")}
|
||||
min={1}
|
||||
max={1440}
|
||||
value={settings.modalityBridgeCacheTtlMinutes}
|
||||
onChange={(value) => setLocal({ modalityBridgeCacheTtlMinutes: value })}
|
||||
onBlur={(raw) =>
|
||||
commitNumber(
|
||||
"modalityBridgeCacheTtlMinutes",
|
||||
raw,
|
||||
1,
|
||||
1440,
|
||||
MODALITY_BRIDGE_DEFAULTS.cacheTtlMinutes
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumberField
|
||||
testId="modality-bridge-cache-max-entries"
|
||||
label={t("modalityBridgeCacheMaxEntries")}
|
||||
min={10}
|
||||
max={5000}
|
||||
value={settings.modalityBridgeCacheMaxEntries}
|
||||
onChange={(value) => setLocal({ modalityBridgeCacheMaxEntries: value })}
|
||||
onBlur={(raw) =>
|
||||
commitNumber(
|
||||
"modalityBridgeCacheMaxEntries",
|
||||
raw,
|
||||
10,
|
||||
5000,
|
||||
MODALITY_BRIDGE_DEFAULTS.cacheMaxEntries
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<ModalityBridgeStatsRow kind="vision" />
|
||||
<ModalityBridgeTestButton />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function asSettingsRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
interface NumberFieldProps {
|
||||
testId: string;
|
||||
label: string;
|
||||
min: number;
|
||||
max: number;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
onBlur: (raw: string) => void;
|
||||
}
|
||||
|
||||
function NumberField({ testId, label, min, max, value, onChange, onBlur }: NumberFieldProps) {
|
||||
return (
|
||||
<label className="block text-sm font-medium">
|
||||
{label}
|
||||
<input
|
||||
type="number"
|
||||
data-testid={testId}
|
||||
min={min}
|
||||
max={max}
|
||||
value={value}
|
||||
onChange={(event) => onChange(Number.parseInt(event.currentTarget.value, 10) || 0)}
|
||||
onBlur={(event) => onBlur(event.currentTarget.value)}
|
||||
className="mt-1 w-full rounded-control border border-border bg-surface px-3 py-2 text-sm"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
"use client";
|
||||
|
||||
import { Suspense, useMemo } from "react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import ModalityBridgeComingSoonTab from "@/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeComingSoonTab";
|
||||
import ModalityBridgeAudioTab from "@/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeAudioTab";
|
||||
import ModalityBridgeVisionTab from "@/app/(dashboard)/dashboard/settings/components/modalityBridge/ModalityBridgeVisionTab";
|
||||
|
||||
type TabId = "vision" | "audio" | "video";
|
||||
|
||||
const TABS: ReadonlyArray<{ id: TabId; labelKey: string; fallback: string }> = [
|
||||
{ id: "vision", labelKey: "modalityBridgeVisionTab", fallback: "Vision" },
|
||||
{ id: "audio", labelKey: "modalityBridgeAudioTab", fallback: "Audio" },
|
||||
{ id: "video", labelKey: "modalityBridgeVideoTab", fallback: "Video" },
|
||||
];
|
||||
|
||||
function ModalityBridgePageContent() {
|
||||
const t = useTranslations("settings");
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const translateOrFallback = (key: string, fallback: string) =>
|
||||
typeof t.has === "function" && !t.has(key) ? fallback : t(key);
|
||||
|
||||
const activeTab = useMemo<TabId>(() => {
|
||||
const requested = searchParams.get("tab") as TabId | null;
|
||||
return requested && TABS.some((tab) => tab.id === requested) ? requested : "vision";
|
||||
}, [searchParams]);
|
||||
|
||||
const handleTabChange = (tab: TabId) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set("tab", tab);
|
||||
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-sm text-text-muted">{t("modalityBridgeIntro")}</p>
|
||||
<div
|
||||
className="flex gap-1 overflow-x-auto border-b border-border"
|
||||
role="tablist"
|
||||
aria-label={translateOrFallback("modalityBridgeSubTabsAria", "Modality Bridge sections")}
|
||||
>
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab.id}
|
||||
aria-controls="modality-bridge-tabpanel"
|
||||
onClick={() => handleTabChange(tab.id)}
|
||||
className={`whitespace-nowrap border-b-2 px-4 py-2 text-sm font-medium transition-colors ${
|
||||
activeTab === tab.id
|
||||
? "border-primary text-primary"
|
||||
: "border-transparent text-text-muted hover:text-text"
|
||||
}`}
|
||||
>
|
||||
{translateOrFallback(tab.labelKey, tab.fallback)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div id="modality-bridge-tabpanel" role="tabpanel">
|
||||
{activeTab === "vision" && <ModalityBridgeVisionTab />}
|
||||
{activeTab === "audio" && <ModalityBridgeAudioTab />}
|
||||
{activeTab === "video" && (
|
||||
<ModalityBridgeComingSoonTab bodyKey="modalityBridgeVideoComingSoon" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ModalityBridgePage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<ModalityBridgePageContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,8 @@ const LEGACY_TAB_ROUTES: Record<string, string> = {
|
||||
"feature-flags": "/dashboard/settings/feature-flags",
|
||||
cache: "/dashboard/settings/cache",
|
||||
general: "/dashboard/settings/general",
|
||||
modalityBridge: "/dashboard/settings/modality-bridge",
|
||||
"modality-bridge": "/dashboard/settings/modality-bridge",
|
||||
resilience: "/dashboard/settings/resilience",
|
||||
routing: "/dashboard/settings/routing",
|
||||
security: "/dashboard/settings/security",
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
// Import through the module index, NOT "@/lib/memory/manager" directly: the index's
|
||||
// import-time side effect is what calls memoryManager.register(sqliteBackend). Importing
|
||||
// the bare manager gives an EMPTY registry, so every handler here threw
|
||||
// `Primary backend "sqlite" not registered` and returned 500 (#8752).
|
||||
import { memoryManager } from "@/lib/memory";
|
||||
import { memoryManager } from "@/lib/memory/manager";
|
||||
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
|
||||
import { MemoryUpdatePutSchema } from "@/shared/schemas/memory";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
|
||||
|
||||
@@ -38,6 +38,7 @@ export async function GET(request: Request) {
|
||||
id: model.id,
|
||||
name: model.name || model.root || model.id,
|
||||
type: model.type || "chat",
|
||||
...(typeof model.subtype === "string" ? { subtype: model.subtype } : {}),
|
||||
custom: model.custom === true,
|
||||
...(model.free === true ? { free: true } : {}),
|
||||
...(model.capabilities ? { capabilities: model.capabilities } : {}),
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
import { CORS_HEADERS, handleCorsOptions } from "@/shared/utils/cors";
|
||||
import { buildErrorBody } from "@omniroute/open-sse/utils/error";
|
||||
import { installMarketplacePlugin } from "@/lib/plugins/marketplace";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
const InstallBodySchema = z.object({
|
||||
name: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
export async function OPTIONS() {
|
||||
return handleCorsOptions();
|
||||
}
|
||||
@@ -20,14 +15,15 @@ export async function POST(request: NextRequest) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
try {
|
||||
const parsed = InstallBodySchema.safeParse(await request.json());
|
||||
if (!parsed.success) {
|
||||
const body = await request.json();
|
||||
const { name } = body as { name?: string };
|
||||
if (!name || typeof name !== "string") {
|
||||
return NextResponse.json(buildErrorBody(400, "Missing or invalid 'name' field"), {
|
||||
status: 400,
|
||||
headers: CORS_HEADERS,
|
||||
});
|
||||
}
|
||||
const result = await installMarketplacePlugin(parsed.data.name);
|
||||
const result = await installMarketplacePlugin(name);
|
||||
return NextResponse.json(result, { status: 201, headers: CORS_HEADERS });
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : "Failed to install marketplace plugin";
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import {
|
||||
discoverAdobeFireflyModels,
|
||||
resolveAdobeAccessToken,
|
||||
} from "@omniroute/open-sse/services/adobeFireflyClient.ts";
|
||||
import {
|
||||
getAdobeFireflyFallbackCatalog,
|
||||
mapDiscoveredToCatalog,
|
||||
toAdobeMediaCapabilitiesApi,
|
||||
type AdobeFireflyCatalogModel,
|
||||
} from "@omniroute/open-sse/services/adobeFireflyModels.ts";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
|
||||
|
||||
type AdobeProviderData = { cookie?: unknown; access_token?: unknown; accessToken?: unknown };
|
||||
|
||||
interface AdobeProviderModelsResult {
|
||||
models: Array<Record<string, unknown>>;
|
||||
source: "api" | "local_catalog";
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
function toModelResponse(model: AdobeFireflyCatalogModel): Record<string, unknown> {
|
||||
const endpoint = model.modality === "image" ? "images" : "videos";
|
||||
return {
|
||||
id: model.id,
|
||||
name: model.name,
|
||||
owned_by: "adobe-firefly",
|
||||
apiFormat: endpoint,
|
||||
supportedEndpoints: [endpoint],
|
||||
type: model.modality,
|
||||
input_modalities: model.inputModalities,
|
||||
output_modalities: [model.modality],
|
||||
supported_sizes: model.capabilities.supportedSizes,
|
||||
media_capabilities: toAdobeMediaCapabilitiesApi(model),
|
||||
};
|
||||
}
|
||||
|
||||
function fallback(warning: string): AdobeProviderModelsResult {
|
||||
return {
|
||||
models: getAdobeFireflyFallbackCatalog().map(toModelResponse),
|
||||
source: "local_catalog",
|
||||
warning,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAdobeModels(
|
||||
apiKey: string | undefined,
|
||||
accessToken: string | undefined,
|
||||
providerData: unknown,
|
||||
fetchImpl: typeof fetch = fetch
|
||||
): Promise<AdobeProviderModelsResult> {
|
||||
const providerSpecificData =
|
||||
providerData && typeof providerData === "object" ? (providerData as AdobeProviderData) : {};
|
||||
try {
|
||||
const token = await resolveAdobeAccessToken(
|
||||
{
|
||||
apiKey,
|
||||
accessToken,
|
||||
providerSpecificData,
|
||||
},
|
||||
fetchImpl
|
||||
);
|
||||
const models = mapDiscoveredToCatalog(await discoverAdobeFireflyModels(token, fetchImpl));
|
||||
return models.length > 0
|
||||
? { models: models.map(toModelResponse), source: "api" }
|
||||
: fallback("Adobe Firefly discovery returned no callable image or video models");
|
||||
} catch (error) {
|
||||
return fallback(
|
||||
`Adobe Firefly discovery unavailable: ${sanitizeErrorMessage(
|
||||
error instanceof Error ? error.message : String(error)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -84,8 +84,10 @@ import {
|
||||
isAutoFetchModelsEnabled,
|
||||
persistDiscoveredModels,
|
||||
} from "@/lib/providerModels/modelDiscovery";
|
||||
import { buildProviderModelsUrl, getDiscoveryClientVersionOptions } from "./discoveryClientVersion";
|
||||
import { getAdobeModels } from "./adobeFireflyDiscovery";
|
||||
import {
|
||||
buildProviderModelsUrl,
|
||||
getDiscoveryClientVersionOptions,
|
||||
} from "./discoveryClientVersion";
|
||||
import {
|
||||
parseGeminiModelsList,
|
||||
type GeminiDiscoveryModel,
|
||||
@@ -420,7 +422,10 @@ export async function GET(
|
||||
// #6267 — a models-endpoint redirect (307/308) is not a fixable-config
|
||||
// error. safeOutboundFetch throws REDIRECT_BLOCKED which
|
||||
// getSafeOutboundFetchErrorStatus maps to 503, but unlike the other 503
|
||||
// Redirect blocks degrade to the local/cached catalog; invalid URLs remain hard errors.
|
||||
// cases (URL_GUARD_BLOCKED / INVALID_URL, which are genuinely
|
||||
// unrecoverable and stay hard errors) a blocked redirect should degrade to
|
||||
// the local/cached catalog OmniRoute ships instead of surfacing a raw 503.
|
||||
// General fix — covers any config-driven provider that 307s (e.g. qwen-web).
|
||||
if (error instanceof SafeOutboundFetchError && error.code === "REDIRECT_BLOCKED") {
|
||||
return buildDiscoveryFallbackResponse(warnings);
|
||||
}
|
||||
@@ -429,11 +434,6 @@ export async function GET(
|
||||
return buildDiscoveryFallbackResponse(warnings);
|
||||
};
|
||||
|
||||
if (provider === "adobe-firefly") {
|
||||
const discovery = await getAdobeModels(apiKey, accessToken, connection.providerSpecificData);
|
||||
return buildResponse({ provider, connectionId, ...discovery });
|
||||
}
|
||||
|
||||
const maybeReturnCachedDiscovery = () => {
|
||||
if (!refresh && cachedDiscoveryModels.length > 0) {
|
||||
return buildCachedDiscoveryResponse();
|
||||
|
||||
@@ -701,17 +701,6 @@ export async function testSingleConnection(connectionId: string, validationModel
|
||||
? makeDiagnosis("ok", "local", null, null)
|
||||
: classifyFailure({ error: result.error, statusCode: result.statusCode, provider }));
|
||||
|
||||
// #9623: a failed connection test must not paint the connection permanently red.
|
||||
// Previously a non-terminal failure wrote `testStatus: "error"` with
|
||||
// `rateLimitedUntil: null` — since the cooldown filter only ever skips entries
|
||||
// whose rateLimitedUntil is in the future, a null cooldown left the connection
|
||||
// permanently unavailable after a transient outage. Give non-terminal test
|
||||
// failures a short cooldown so the lazy-recovery path retries them.
|
||||
const terminalTestStatuses = new Set(["banned", "expired", "credits_exhausted"]);
|
||||
const isTerminalFailure =
|
||||
!result.valid && terminalTestStatuses.has(String(diagnosis.code ?? diagnosis.type ?? "").toLowerCase());
|
||||
const testFailureCooldownMs = result.valid ? 0 : 30_000; // 30s retry window
|
||||
|
||||
const updateData: Record<string, any> = {
|
||||
testStatus: result.valid ? "active" : "error",
|
||||
lastError: result.valid ? null : result.error,
|
||||
@@ -720,12 +709,7 @@ export async function testSingleConnection(connectionId: string, validationModel
|
||||
lastErrorType: result.valid ? null : diagnosis.type,
|
||||
lastErrorSource: result.valid ? null : diagnosis.source,
|
||||
errorCode: result.valid ? null : diagnosis.code || result.statusCode || null,
|
||||
rateLimitedUntil:
|
||||
result.valid || isTerminalFailure
|
||||
? result.valid
|
||||
? null
|
||||
: connection.rateLimitedUntil || null
|
||||
: new Date(Date.now() + testFailureCooldownMs).toISOString(),
|
||||
rateLimitedUntil: result.valid ? null : connection.rateLimitedUntil || null,
|
||||
};
|
||||
|
||||
if (result.valid) {
|
||||
|
||||
@@ -73,7 +73,9 @@ export async function PATCH(request: Request, { params }: RouteParams): Promise<
|
||||
// helpers. Without the pre-update removal, a group/provider switch would leave
|
||||
// orphan qtSd/ combos a quota key still sees. Guarded + non-fatal.
|
||||
const combosNeedResync =
|
||||
body !== null && typeof body === "object" && ("connectionIds" in body || "groupId" in body);
|
||||
body !== null &&
|
||||
typeof body === "object" &&
|
||||
("connectionIds" in body || "groupId" in body);
|
||||
if (combosNeedResync) {
|
||||
try {
|
||||
const { removeQuotaCombosForPool } = await import("@/lib/quota/quotaCombos");
|
||||
@@ -104,7 +106,7 @@ export async function PATCH(request: Request, { params }: RouteParams): Promise<
|
||||
id,
|
||||
prevApiKeyIds,
|
||||
nextApiKeyIds,
|
||||
parsed.data.exclusive ?? false
|
||||
parsed.data.exclusive ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,7 +132,7 @@ export async function DELETE(request: Request, { params }: RouteParams): Promise
|
||||
|
||||
try {
|
||||
const { id } = await params;
|
||||
const existed = await deletePool(id);
|
||||
const existed = deletePool(id);
|
||||
if (!existed) {
|
||||
return NextResponse.json(buildErrorBody(404, "Pool not found"), { status: 404 });
|
||||
}
|
||||
|
||||
@@ -11,15 +11,9 @@
|
||||
* and never reaches the browser.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
import { forwardToDarioAdmin, requireAdminAuth } from "../_lib";
|
||||
import { createErrorResponse } from "@/lib/api/errorResponse";
|
||||
|
||||
const DeleteAccountBodySchema = z.object({
|
||||
alias: z.string().trim().min(1).optional(),
|
||||
});
|
||||
|
||||
export async function GET(request: Request): Promise<Response> {
|
||||
const authResponse = await requireAdminAuth(request);
|
||||
if (authResponse) return authResponse;
|
||||
@@ -35,9 +29,9 @@ export async function DELETE(request: Request): Promise<Response> {
|
||||
|
||||
if (!alias && request.body !== null) {
|
||||
try {
|
||||
const parsed = DeleteAccountBodySchema.safeParse(await request.json());
|
||||
if (parsed.success && parsed.data.alias) {
|
||||
alias = parsed.data.alias;
|
||||
const parsed = await request.json();
|
||||
if (parsed && typeof parsed === "object" && typeof (parsed as { alias?: unknown }).alias === "string") {
|
||||
alias = (parsed as { alias: string }).alias.trim();
|
||||
}
|
||||
} catch {
|
||||
/* fall through to the missing-alias error below */
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
* pickup, rather than relying on any undocumented hot-reload behavior.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { NextResponse } from "next/server";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
@@ -40,11 +39,6 @@ import { getDarioHomeDir } from "@/lib/services/installers/dario";
|
||||
import { createErrorResponse } from "@/lib/api/errorResponse";
|
||||
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";
|
||||
|
||||
const ImportBodySchema = z.object({
|
||||
connectionId: z.string().trim().min(1).optional(),
|
||||
alias: z.string().trim().min(1).optional(),
|
||||
});
|
||||
|
||||
const ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_\-.]{0,63}$/;
|
||||
|
||||
function safeAliasFromSource(email: string | null | undefined, connectionId: string): string {
|
||||
@@ -88,16 +82,15 @@ export async function POST(request: Request): Promise<Response> {
|
||||
const authResponse = await requireAdminAuth(request);
|
||||
if (authResponse) return authResponse;
|
||||
|
||||
let raw: unknown;
|
||||
let body: unknown;
|
||||
try {
|
||||
raw = await request.json();
|
||||
body = await request.json();
|
||||
} catch {
|
||||
return createErrorResponse({ status: 400, message: "Invalid JSON body" });
|
||||
}
|
||||
|
||||
const parsed = ImportBodySchema.safeParse(raw ?? {});
|
||||
const b = parsed.success ? parsed.data : {};
|
||||
const connectionId = b.connectionId ?? null;
|
||||
const b = (body || {}) as Record<string, unknown>;
|
||||
const connectionId = typeof b.connectionId === "string" ? b.connectionId : null;
|
||||
if (!connectionId) {
|
||||
return createErrorResponse({ status: 400, message: "connectionId is required" });
|
||||
}
|
||||
@@ -119,7 +112,10 @@ export async function POST(request: Request): Promise<Response> {
|
||||
});
|
||||
}
|
||||
|
||||
let alias = b.alias || safeAliasFromSource(conn.email as string | null, connectionId);
|
||||
let alias =
|
||||
typeof b.alias === "string" && b.alias.trim()
|
||||
? b.alias.trim()
|
||||
: safeAliasFromSource(conn.email as string | null, connectionId);
|
||||
if (!ALIAS_PATTERN.test(alias)) {
|
||||
alias = safeAliasFromSource(conn.email as string | null, connectionId);
|
||||
}
|
||||
|
||||
@@ -8,29 +8,23 @@
|
||||
* posts the displayed code to /login-complete.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import { forwardToDarioAdmin, requireAdminAuth } from "../_lib";
|
||||
import { createErrorResponse } from "@/lib/api/errorResponse";
|
||||
|
||||
const LoginStartBodySchema = z.object({
|
||||
alias: z.string().trim().min(1).optional(),
|
||||
});
|
||||
type LoginStartBody = z.infer<typeof LoginStartBodySchema>;
|
||||
|
||||
export async function POST(request: Request): Promise<Response> {
|
||||
const authResponse = await requireAdminAuth(request);
|
||||
if (authResponse) return authResponse;
|
||||
|
||||
let body: LoginStartBody = {};
|
||||
let body: { alias?: string } = {};
|
||||
try {
|
||||
if (request.body !== null) {
|
||||
const parsed = LoginStartBodySchema.safeParse(await request.json());
|
||||
if (parsed.success) body = parsed.data;
|
||||
const parsed = await request.json();
|
||||
if (parsed && typeof parsed === "object") body = parsed as { alias?: string };
|
||||
}
|
||||
} catch {
|
||||
return createErrorResponse({ status: 400, message: "Invalid JSON body" });
|
||||
}
|
||||
|
||||
const forwardBody = body.alias ? { alias: body.alias } : {};
|
||||
const forwardBody = typeof body.alias === "string" && body.alias.trim() ? { alias: body.alias.trim() } : {};
|
||||
return forwardToDarioAdmin({ method: "POST", path: "/admin/login/start", body: forwardBody });
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import { getCachedProviderNodes } from "@/lib/db/readCache";
|
||||
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
|
||||
import {
|
||||
buildDynamicAudioProvider,
|
||||
isLoopbackNodeHost,
|
||||
type AudioProvider,
|
||||
type ProviderNodeRow,
|
||||
} from "@omniroute/open-sse/config/audioRegistry.ts";
|
||||
@@ -36,7 +35,19 @@ export const AUDIO_REMOTE_NODES_FLAG = "AUDIO_REMOTE_PROVIDER_NODES";
|
||||
* Loopback / private-range hosts that never leave the operator's machine or
|
||||
* Docker network. `::1` stays excluded, matching the previous SSRF hardening.
|
||||
*/
|
||||
export { isLoopbackNodeHost as isLocalAudioNodeHost };
|
||||
export function isLocalAudioNodeHost(baseUrl: string): boolean {
|
||||
try {
|
||||
const hostname = new URL(baseUrl).hostname;
|
||||
return (
|
||||
hostname === "localhost" ||
|
||||
hostname === "127.0.0.1" ||
|
||||
// Strictly 172.16.0.0/12 (Docker/local)
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure selection step — no DB, no flag lookup, so the policy is directly testable.
|
||||
@@ -61,7 +72,7 @@ export function selectAudioProviderNodes(
|
||||
return false;
|
||||
}
|
||||
if (!node.baseUrl) return false;
|
||||
return isLoopbackNodeHost(node.baseUrl) || allowRemote;
|
||||
return isLocalAudioNodeHost(node.baseUrl) || allowRemote;
|
||||
});
|
||||
|
||||
const providers: AudioProvider[] = [];
|
||||
|
||||
@@ -1113,7 +1113,6 @@ async function buildUnifiedModelsResponseCore(
|
||||
input_modalities: imgModel.inputModalities || ["text"],
|
||||
output_modalities: ["image"],
|
||||
...(imgModel.description ? { description: imgModel.description } : {}),
|
||||
...(imgModel.mediaCapabilities ? { media_capabilities: imgModel.mediaCapabilities } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1179,12 +1178,6 @@ async function buildUnifiedModelsResponseCore(
|
||||
created: timestamp,
|
||||
owned_by: videoModel.provider,
|
||||
type: "video",
|
||||
supported_sizes: videoModel.supportedSizes,
|
||||
input_modalities: ["text"],
|
||||
output_modalities: ["video"],
|
||||
...(videoModel.mediaCapabilities
|
||||
? { media_capabilities: videoModel.mediaCapabilities }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1383,7 +1376,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
continue;
|
||||
}
|
||||
|
||||
// #8958/#9034: honor the compatible-provider node prefix (as the synced/custom
|
||||
// #8958: honor the compatible-provider node prefix (as the synced/custom
|
||||
// loops do) so an alias-backed entry publishes `prefix/model` instead of the
|
||||
// raw provider-node UUID. Without the providerIdToPrefix lookup, `alias` fell
|
||||
// through to `providerKey` (the UUID) and the dedupe below — which only checks
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
// happen once.
|
||||
|
||||
export type UsableChatModelCandidate = {
|
||||
id?: string;
|
||||
root?: string;
|
||||
name?: string;
|
||||
owned_by?: string;
|
||||
parent?: string | null;
|
||||
type?: string;
|
||||
@@ -47,19 +44,9 @@ function excludesTextOutputModality(model: UsableChatModelCandidate) {
|
||||
);
|
||||
}
|
||||
|
||||
function isBuiltinAutoModel(model: UsableChatModelCandidate): boolean {
|
||||
const id = model.id || model.root || model.name || "";
|
||||
const normalized = id.trim().toLowerCase();
|
||||
return normalized === "auto" || normalized.startsWith("auto/");
|
||||
}
|
||||
|
||||
export function isUsableChatModel(model: UsableChatModelCandidate) {
|
||||
if (typeof model.owned_by === "string" && model.owned_by.trim().toLowerCase() === "combo") {
|
||||
// Allow built-in auto-routing models (e.g. auto, auto/best-coding)
|
||||
// while still excluding operator-created combos.
|
||||
if (!isBuiltinAutoModel(model)) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (typeof model.parent === "string" && model.parent.length > 0) return false;
|
||||
if (typeof model.type === "string" && model.type !== "chat") return false;
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "معطل",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "توجيه الطلبات التي استنفدت الميزانية إلى موفر/نموذج الاحتياط المجاني للطوارئ.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "تمكين المزامنة الدورية لتصنيف ELO للوحة صدارة Arena AI لتصنيفات ذكاء النماذج.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "قم بالإعلان عن معرفات مرآة claude/<provider>/<model> على /v1/models حتى تظهر قوائم اكتشاف نموذج بوابة Claude Code نماذج غير Claude. تحذير: يؤدي تفعيل ذلك عالميًا إلى مضاعفة إدخالات الكتالوج لجميع العملاء.",
|
||||
"sidebar": {
|
||||
"home": "الصفحة الرئيسية",
|
||||
"dashboard": "لوحة القيادة",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "استوديو المجموعات",
|
||||
"combosLiveSubtitle": "تتالي التوجيه المباشر",
|
||||
"compressionStudio": "استوديو الضغط",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "الاستثناءات",
|
||||
"contextSettingsSubtitle": "القيم الافتراضية العالمية",
|
||||
"contextHeadroomSubtitle": "الضغط الجدولي",
|
||||
"contextSessionDedupSubtitle": "إزالة التكرار عبر الأدوار",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "التقليم الاستكشافي",
|
||||
"contextOmniglyphSubtitle": "السياق كصور",
|
||||
"compressionStudioSubtitle": "تتالي المحرك المباشر",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "تجاوز لكل نموذج/نقطة نهاية",
|
||||
"chaosConfigSubtitle": "التنفيذ المتوازي متعدد النماذج",
|
||||
"routingSection": "التوجيه",
|
||||
"protocolsSection": "البروتوكولات",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "مخصصات الرموز المجانية الشهرية",
|
||||
"freeProviderRankings": "تصنيفات الموفرين المجانيين",
|
||||
"freeProviderRankingsSubtitle": "أفضل الموفرين المجانيين مرتبين حسب درجات ELO للنموذج",
|
||||
"radar": "كتالوج الرادار",
|
||||
"radarSubtitle": "كتالوج نموذج مجاني معزز بالمجتمع",
|
||||
"costsQuotaShare": "مشاركة الحصص",
|
||||
"costsPricing": "التسعير",
|
||||
"logsProxy": "سجلات بروكسي",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "عام",
|
||||
"settingsAppearance": "المظهر",
|
||||
"settingsAi": "إعدادات الذكاء الاصطناعي",
|
||||
"settingsModalityBridge": "جسر الوضعية",
|
||||
"settingsSecurity": "الأمان",
|
||||
"settingsAccessTokens": "رموز الوصول",
|
||||
"settingsFeatureFlags": "أعلام مميزة",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "ذاكرة التخزين المؤقت",
|
||||
"settingsAuthz": "التخويل",
|
||||
"settingsRouting": "التوجيه",
|
||||
"settingsResilience": "المرونة",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "وقت التشغيل",
|
||||
"consoleLogs": "سجلات وحدة التحكم",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "جدول زمني مرئي للطلبات",
|
||||
"globalRouting": "التوجيه العام",
|
||||
"mitmProxy": "بروكسي MITM",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "أساسيات التطبيق",
|
||||
"settingsAppearanceSubtitle": "السمة والتخطيط",
|
||||
"settingsAiSubtitle": "سلوك الذكاء الاصطناعي الافتراضي",
|
||||
"settingsModalityBridgeSubtitle": "تحويل الصورة/الصوت إلى نص للنماذج التي تدعم النص فقط",
|
||||
"globalRoutingSubtitle": "قواعد التوجيه العامة",
|
||||
"settingsResilienceSubtitle": "إعادات المحاولة وقواطع الدائرة",
|
||||
"settingsAdvancedSubtitle": "خيارات المستخدم المتقدم",
|
||||
"settingsSecuritySubtitle": "المصادقة والتشفير",
|
||||
"settingsAccessTokensSubtitle": "رموز CLI محددة النطاق للوضع البعيد",
|
||||
"settingsFeatureFlagsSubtitle": "تبديل قدرات النظام",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "دليل النموذج وتخزين الاستجابة في الذاكرة",
|
||||
"settingsSidebar": "الشريط الجانبي",
|
||||
"settingsSidebarSubtitle": "تخصيص تخطيط الشريط الجانبي",
|
||||
"settingsAuthzSubtitle": "قائمة المسارات وسياسة التجاوز",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "مرئي دائمًا",
|
||||
"groupSeparatorLabel": "فاصل",
|
||||
"discovery": "اكتشاف",
|
||||
"discoverySubtitle": "فحص المزودين للوصول المجاني",
|
||||
"radar": "كتالوج الرادار",
|
||||
"radarSubtitle": "كتالوج نموذج مجاني معزز بالمجتمع"
|
||||
"discoverySubtitle": "فحص المزودين للوصول المجاني"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "خطافات الويب",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "التخزين وقاعدة البيانات وتكوين المثيل العام",
|
||||
"settingsAppearanceDescription": "الموضوع والعلامة التجارية والتخصيص المرئي",
|
||||
"settingsAiDescription": "سلوكيات الذكاء الاصطناعي وميزانيات التفكير وإعدادات الرؤية والذاكرة",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "TTL لمدخلات ذاكرة التخزين المؤقت لكتالوج النموذج",
|
||||
"settingsSecurityDescription": "إعدادات المصادقة والترخيص والتحكم في الوصول",
|
||||
"featureFlags": "الميزات تجريبية",
|
||||
"featureFlagsDescription": "قدرات نظام التحكم والميزات التجريبية",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "توليد الصور",
|
||||
"imageToText": "تحويل الصورة إلى نص",
|
||||
"imageToTextComingSoon": "ستكون ساحة تجربة تحويل الصورة إلى نص المضمنة متاحة عند تنفيذ <code>/api/v1/images/understanding</code>.",
|
||||
"imageToTextBridgeCta": "قم بتكوين جسر الصورة→النص في إعدادات جسر الوضعية",
|
||||
"sttBridgeCta": "قم بتكوين جسر الكلام→النص في إعدادات جسر الوضعية",
|
||||
"disabled": "معطل",
|
||||
"videoGeneration": "توليد الفيديو",
|
||||
"musicGeneration": "توليد الموسيقى",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "تلقائي",
|
||||
"always": "دائمًا"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "كيفية تمكين الاكتشاف في Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "قم بالإعلان عن النماذج غير الخاصة بـ Claude تحت معرفات المرآة claude/<provider>/<model> حتى يتمكن نموذج اكتشاف بوابة Claude Code من إدراجها. يضاعف إدخالات الكتالوج لجميع العملاء عند تفعيلها عالميًا.",
|
||||
"ccDiscoveryInfoLink": "فتح علامات الميزات",
|
||||
"ccOnboardingTitle": "settings.json لاكتشاف نموذج البوابة",
|
||||
"ccOnboardingCopy": "نسخ",
|
||||
"ccOnboardingCopied": "تم النسخ",
|
||||
"ccOnboardingKeyPlaceholder": "<مفتاح واجهة برمجة تطبيقات OmniRoute الخاص بك>",
|
||||
"ccOnboardingWindowNote": "يفترض Claude Code نافذة سياق تبلغ 200K لأي معرف نموذج لا يتعرف عليه. بالنسبة لنموذج ذو نافذة حقيقية مختلفة، أضف CLAUDE_CODE_AUTO_COMPACT_WINDOW مباشرة أسفلها حتى لا يتم تشغيل الضغط التلقائي في وقت مبكر جدًا.",
|
||||
"failedSave": "فشل الحفظ",
|
||||
"profileSyncTitle": "المزامنة التلقائية لملفات تعريف CLI",
|
||||
"profileSyncDescription": "بعد مزامنة نماذج الموفر، قم بإعادة إنشاء ملفات تعريف أداة CLI تلقائيًا من الكتالوج المباشر. معطل افتراضيًا — يتم كتابة ملفات التعريف فقط؛ لا يتم تغيير التكوين النشط/الافتراضي مطلقًا.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "الاستدلال على المهارات واستخدام الأدوات",
|
||||
"hermesRoleApproval": "الموافقة",
|
||||
"hermesRoleApprovalDesc": "قرارات السلامة والموافقة",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "استدعاءات أداة خادم MCP",
|
||||
"hermesRoleTitleGeneration": "توليد العنوان",
|
||||
"hermesRoleTitleGenerationDesc": "توليد عنوان الجلسة",
|
||||
"hermesRoleMemoryQueryRewrite": "إعادة كتابة استعلام الذاكرة",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "إعادة كتابة استعلام بحث الذاكرة",
|
||||
"hermesRoleTtsAudioTags": "علامات الصوت TTS",
|
||||
"hermesRoleTtsAudioTagsDesc": "توليد علامة الصوت TTS",
|
||||
"hermesRoleTriageSpecifier": "محدد تصنيف الأولويات",
|
||||
"hermesRoleTriageSpecifierDesc": "مواصفات تصنيف القضايا وطلبات السحب",
|
||||
"hermesRoleKanbanDecomposer": "مفكك كانبان",
|
||||
"hermesRoleKanbanDecomposerDesc": "تحليل مهام كانبان",
|
||||
"hermesRoleProfileDescriber": "وصف الملف الشخصي",
|
||||
"hermesRoleProfileDescriberDesc": "وصف ملف المستخدم",
|
||||
"hermesRoleGoalJudge": "قاضي الهدف",
|
||||
"hermesRoleGoalJudgeDesc": "تقييم إكمال الهدف",
|
||||
"hermesRoleCurator": "المنسق",
|
||||
"hermesRoleCuratorDesc": "تنسيق المهارات والذاكرة",
|
||||
"hermesRoleMonitor": "شاشة",
|
||||
"hermesRoleMonitorDesc": "مراقبة الخلفية",
|
||||
"hermesRoleBackgroundReview": "مراجعة الخلفية",
|
||||
"hermesRoleBackgroundReviewDesc": "مراجعة كود الخلفية",
|
||||
"hermesSelectBeforePreview": "حدد النماذج للأدوار، أو تأكد من تحميل الأدوار، قبل المعاينة.",
|
||||
"hermesPreviewFailed": "فشل إنشاء المعاينة",
|
||||
"hermesSavedTo": "تم الحفظ في {path}",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "لصق في:",
|
||||
"copilotReloadInstruction": "ثم أعد تحميل VS Code وعيّن مفتاح API في موجه الإدخال.",
|
||||
"wireApiChatCompletions": "مكتملات الدردشة (/chat/مكتملات)",
|
||||
"wireApiResponses": "واجهة برمجة تطبيقات الاستجابات (/ الاستجابات)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "واجهة برمجة تطبيقات الاستجابات (/ الاستجابات)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "المجموعات",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "النماذج: {count, plural, one {نموذج واحد} other {# نماذج}}",
|
||||
"sectionTitle": "سطح التكامل",
|
||||
"sectionDescription": "واجهات برمجة التطبيقات المتوافقة مع OpenAI ونقاط نهاية البروتوكول التشغيلية",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "واجهات برمجة التطبيقات",
|
||||
"tabProtocols": "البروتوكولات",
|
||||
"tabsAria": "أقسام نقطة النهاية",
|
||||
"protocolsTitle": "البروتوكولات",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "لم تتم إضافة أي نماذج جديدة (كلها موجودة بالفعل).",
|
||||
"importDoneCount": "✓ تم! {count, plural, one {استُورد نموذج واحد.} other {استُورد # من النماذج.}}",
|
||||
"unexpectedErrorOccurred": "حدث خطأ غير متوقع",
|
||||
"getApiKey": "احصل على مفتاح API",
|
||||
"getApiKeyDescription": "سجل أو اشترك للحصول على مفتاح API",
|
||||
"connectionCountLabel": "عدد الاتصالات: {count, plural, one {اتصال واحد} other {# اتصالات}}",
|
||||
"messagesPath": "الرسائل",
|
||||
"responsesPath": "الردود",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "إعادة كتابة استدعاءات أداة web_fetch الأصلية إلى /v1/web/fetch الخاصة بـ OmniRoute.",
|
||||
"interceptionLoadError": "فشل تحميل إعدادات الاعتراض: {error}",
|
||||
"interceptionSaveError": "فشل حفظ إعدادات الاعتراض: {error}",
|
||||
"ccAliasSectionTitle": "Expose في كود كلود (claude/…)",
|
||||
"ccAliasSectionHint": "قم بالإعلان عن نماذج هذا المزود تحت معرفات المرآة claude/<provider>/<model> حتى يتمكن نموذج اكتشاف البوابة في Claude Code من إدراجها. معطلة بشكل افتراضي - تمكين هذا يضاعف إدخالات الكتالوج لجميع العملاء.",
|
||||
"ccAliasProviderLevelLabel": "موفر افتراضي",
|
||||
"ccAliasModelOverridesLabel": "تجاوزات لكل نموذج",
|
||||
"ccAliasModelOverrideAriaLabel": "تجاوز لـ {modelId}",
|
||||
"ccAliasStateInherit": "وراثة",
|
||||
"ccAliasStateOn": "تشغيل",
|
||||
"ccAliasStateOff": "إيقاف",
|
||||
"ccAliasAddModelPlaceholder": "معرف النموذج (مثل gpt-4o)",
|
||||
"ccAliasAddModelButton": "أضف تجاوز",
|
||||
"ccAliasLoadError": "فشل في تحميل إعدادات discovery-alias: {error}",
|
||||
"ccAliasSaveError": "فشل في حفظ إعداد alias الاكتشاف: {error}",
|
||||
"compatUpstreamHeadersLabel": "ترويسات المنبع الإضافية",
|
||||
"compatUpstreamHeadersHint": "إعداد عالي الصلاحيات — يعامل معاملة بيانات اعتماد API الخاصة بالمزود، لذا لا ينبغي استخدامه إلا من مسؤولين موثوقين. تُدمج الترويسات بعد أن يضيف OmniRoute المصادقة. إذا استخدمت ترويسة مخصصة الاسم نفسه لترويسة موجودة (مثل Authorization)، فستستبدل قيمتك الترويسة المنشأة تلقائيًا بالكامل، بما فيها رمز Bearer. قد يؤدي الإعداد الخاطئ إلى خطأ 401 أو تعطّل مصادقة المنبع. أضف ترويسة واحدة في كل صف. تُحفظ القيمة عند فقدان التركيز أو إغلاق اللوحة.",
|
||||
"compatUpstreamHeaderName": "اسم الترويسة",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "معرّف مستخدم New-API",
|
||||
"newApiUserIdPlaceholder": "مثال: 12345",
|
||||
"newApiUserIdHint": "قيمة New-Api-User الخاص بـ AgentRouter، وبالتالي مع مفتاح API لوحة التحكم لجلب رصيد الحصة.",
|
||||
"newApiAggregatorToggleLabel": "بوابة المجمع",
|
||||
"newApiAggregatorToggleHint": "قم بتمكين اكتشاف الرصيد لعقد مجمع New-API / One-API / Sub2API. ستظهر لوحة المعلومات شارة الرصيد وسيتجاوز توجيه ما قبل الرحلة للحصة الحسابات المستنفدة.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "رمز وصول النظام لنقطة نهاية /api/user/self الخاصة بالموحد. ليس مفتاح واجهة برمجة التطبيقات للتوجيه.",
|
||||
"newApiAggregatorUserIdHint": "قيمة رأس New-Api-User المستخدمة لاسترداد رصيد حصة مستخدم المجمع.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "الحصة لكل وحدة",
|
||||
"newApiAggregatorQuotaPerUnitHint": "وحدات الائتمان New-API لكل 1 دولار (افتراضي: 500000). قم بتجاوز ذلك إذا كان المجمع الخاص بك يستخدم معدلًا مختلفًا.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "تفعيل كشف الرصيد لعقد متوافقة مع New-API / One-API / Sub2API",
|
||||
"cpaModeDisabledTitle": "وضع CPA معطل",
|
||||
"cpaModeEnabledTitle": "وضع CPA مفعّل",
|
||||
"customUserAgentHint": "اتركه فارغًا لاستخدام وكيل المستخدم الافتراضي",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "مثال: production",
|
||||
"testModel": "نموذج الاختبار",
|
||||
"testingModel": "جارٍ اختبار النموذج",
|
||||
"modelTestQuotaTooltip": "تم استنفاد الحصة — تعود غدًا أو تحتاج إلى تعبئة",
|
||||
"toggleOffShort": "إيقاف",
|
||||
"toggleOnShort": "تشغيل",
|
||||
"tokenExpiredBadge": "منتهي الصلاحية",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "احصل على مفتاح API من ai.360.cn",
|
||||
"agentrouter": "احصل على رصيد مجاني بقيمة $200 على https://agentrouter.org/register — لا يتطلب بطاقة ائتمان.",
|
||||
"unorouter": "قم بإنشاء مفتاح API على https://unorouter.ai، ثم ألصقه هنا كرمز Bearer.",
|
||||
"agnes": "احصل على مفتاح API من agnes-ai.com",
|
||||
"aimlapi": "تم إيقاف الفئة المجانية مؤقتًا (2026) — أصبحت واجهة برمجة تطبيقات الذكاء الاصطناعي/تعلم الآلة (AI/ML API) مدفوعة حسب الاستخدام فقط (الحد الأدنى للشحن $20)؛ لا توجد أرصدة مجانية متكررة.",
|
||||
"ai21": "رصيد تجريبي بقيمة $10 عند التسجيل (صالح لمدة 3 أشهر)، لا يتطلب بطاقة ائتمان",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "ربط Alibaba Coding Plan بمفتاح API.",
|
||||
"bedrock": "تكامل Bedrock الأصلي: يستخدم اكتشاف النماذج نماذج Bedrock الأساسية وملفات تعريف الاستدلال، بينما تستخدم الدردشة واجهات برمجة تطبيقات Bedrock Runtime Converse/ConverseStream الإقليمية.",
|
||||
"anthropic": "ربط Anthropic بمفتاح API.",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "سجل وأنشئ مفتاح API في وحدة تحكم API الخاصة بـ Ant Ling (https://chat.ant-ling.com/open)، ثم ألصقه هنا. يقوم OmniRoute بتوجيه حركة الدردشة إلى https://api.ant-ling.com/v1/chat/completions؛ المزود متوافق مع OpenAI ويعرض أيضًا واجهة متوافقة مع Anthropic.",
|
||||
"api-airforce": "احصل على مفتاح API الخاص بك من https://panel.api.airforce — نقطة نهاية متوافقة مع OpenAI على https://api.airforce/v1",
|
||||
"arcee-ai": "احصل على مفتاح API من arcee.ai",
|
||||
"azure-ai": "يستخدم Foundry واجهة OpenAI v1 مع أسماء النشر كنماذج. يقوم OmniRoute بتوحيد عناوين URL للموارد الجذرية إلى نقاط النهاية v1 chat و /models.",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "مفتاح Bearer API لبوابة Chutes المتوافقة مع OpenAI.",
|
||||
"clarifai": "توفر Clarifai دردشة واستجابات و /models متوافقة مع OpenAI على /v2/ext/openai/v1. تتطلب النماذج العامة/المجتمعية عادةً رمز PAT؛ بينما تعمل المفاتيح المخصصة للتطبيق فقط مع الموارد الموجودة داخل هذا التطبيق.",
|
||||
"cloudflare-ai": "يتطلب رمز API ومعرف الحساب (يمكن العثور عليه في dash.cloudflare.com)",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) متوافق مع OpenAI على /v1/openai. يقوم OmniRoute بفحص /v1/openai/models ويوجه حركة الدردشة إلى /v1/openai/chat/completions. يستخدم المضيف الحالي clovastudio.stream.ntruss.com — يتم إلغاء دعم نقطة النهاية القديمة clovastudio.apigw.ntruss.com.",
|
||||
"codestral": "ربط Codestral بمفتاح API.",
|
||||
"cohere": "تجربة مجانية: 1,000 مكالمة API شهريًا للاختبار، لا تتطلب بطاقة ائتمان",
|
||||
"command-code": "أنشئ أو انسخ مفتاح API من Command Code، ثم الصقه هنا كرمز Bearer.",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "تعرض بوابة نماذج watsonx واجهات متوافقة مع OpenAI لـ /chat/completions و /models تحت المسار /ml/gateway/v1.",
|
||||
"ideogram": "احصل على مفتاح API من ideogram.ai/docs/api",
|
||||
"iflytek": "احصل على مفتاح API من console.xfyun.cn",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs متوافقة مع OpenAI على https://api.inceptionlabs.ai/v1. mercury-2 هو أول نموذج LLM للتشتت (dLLM) في الكتالوج - أسرع من 5-10 مرات في التوليد مقارنة بالنماذج التلقائية المماثلة، مع استدعاء الأدوات، json_mode، والمخرجات المنظمة.",
|
||||
"inference-net": "رصيد مجاني بقيمة 25 دولارًا عند التسجيل بالإضافة إلى توفر منح بحثية",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "حصة مجانية شهرية ~1M إدخال / 3M إخراج توكن (~10 RPM)",
|
||||
"jina-ai": "مفتاح API من نوع Bearer لواجهة برمجة تطبيقات إعادة الترتيب من Jina AI.",
|
||||
"jina-reader": "ربط Jina Reader بمفتاح API.",
|
||||
"kenari": "توفر Kenari نقطة نهاية لإكمال الدردشة متوافقة مع OpenAI على https://kenari.id/v1/chat/completions، بالإضافة إلى كتالوج مباشر لـ /v1/models يغطي Claude و GPT و DeepSeek و GLM و Kimi والمزيد. تستخدم OmniRoute بروتوكول OpenAI وتعرض النماذج عبر التمرير المباشر.",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "ربط Perplexity بمفتاح API.",
|
||||
"piapi": "ربط PiAPI بمفتاح API.",
|
||||
"pioneer": "رصيد استخدام مجاني بقيمة 75$ — لا يتطلب بطاقة ائتمان",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo متوافق مع OpenAI على https://api.platform.preferredai.jp/v1. تم بناؤه بواسطة Preferred Networks ومُحسّن للغة اليابانية. الوثائق متاحة بشكل أساسي باللغة اليابانية.",
|
||||
"poe": "يوفر Poe دردشة واستجابات متوافقة مع OpenAI على https://api.poe.com/v1، مع التحقق من الرصيد المصادق عليه على /usage/current_balance.",
|
||||
"pollinations": "الفئة المجانية بدون مفتاح: openai، وopenai-fast، وopenai-large، وqwen-coder، وmistral، وdeepseek، وgrok، وgemini-flash-lite-3.1، وperplexity-fast، وperplexity-reasoning. تتطلب النماذج المميزة (claude، وgemini، وmidijourney) مفتاح Pollinations API من enter.pollinations.ai.",
|
||||
"publicai": "يتطلب مفتاح API — رصيد تسجيل لمرة واحدة، ثم مدفوع",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "يعتمد توليد الفيديو في Runway على المهام. يرسل OmniRoute وظائف تحويل النص إلى فيديو أو الصورة إلى فيديو، ويستعلم من /v1/tasks/[id]، ويقوم بتطبيع مخرجات الفيديو النهائية مرة أخرى إلى استجابة /v1/videos/generations الشبيهة بـ OpenAI.",
|
||||
"sambanova": "رصيد مجاني بقيمة 5$ عند التسجيل (صلاحية 30 يومًا)، لا يتطلب بطاقة ائتمان",
|
||||
"sap": "يستخدم اكتشاف النماذج /v2/lm/scenarios/foundation-models/models على AI_API_URL. تستخدم طلبات الدردشة deploymentUrl/chat/completions وتتطلب AI-Resource-Group.",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "سارفام AI متوافق مع OpenAI على /v1. يقوم OmniRoute بفحص /v1/models ويوجه حركة الدردشة إلى /v1/chat/completions. تم ضبط النماذج للغات الهندية.",
|
||||
"scaleway": "1 مليون رمز مميز مجاني للحسابات الجديدة — متوافق مع الاتحاد الأوروبي/GDPR (باريس)، Qwen3 235B وLlama 70B",
|
||||
"sensenova": "احصل على مفتاح API من platform.sensenova.cn",
|
||||
"siliconflow": "رصيد مجاني بقيمة 1$ بالإضافة إلى نماذج مجانية بشكل دائم بعد التحقق من الهوية",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "ربط Together AI بمفتاح API.",
|
||||
"tokenrouter": "يوفر TokenRouter نقطة نهاية لإكمال الدردشة متوافقة مع OpenAI على https://api.tokenrouter.com/v1/chat/completions، بالإضافة إلى كتالوج /v1/models يعمل. يستخدم OmniRoute بروتوكول OpenAI.",
|
||||
"topaz": "ربط Topaz بمفتاح API.",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "تايفون متوافق مع OpenAI على /v1. تم بناؤه بواسطة SCB 10X (تايلاند)؛ typhoon-v2.5-30b-a3b-instruct هو نموذج متعدد اللغات يركز على اللغة التايلاندية.",
|
||||
"udio": "الصق ملف تعريف ارتباط الجلسة (session cookie) من udio.com (مصادقة Supabase)",
|
||||
"uncloseai": "لا يتطلب مصادقة. تقبل واجهة برمجة التطبيقات (API) أي سلسلة غير فارغة كمفتاح للتعريف.",
|
||||
"upstage": "ربط Upstage بمفتاح API.",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "مفتاح API من نوع Bearer لواجهات برمجة تطبيقات Voyage AI embeddings و rerank.",
|
||||
"wafer": "مفتاح API من https://wafer.ai",
|
||||
"wandb": "ربط Weights & Biases Inference بمفتاح API.",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra متوافق مع OpenAI على https://api.writer.com/v1. palmyra-x5 يوفر نافذة سياق بحجم 1M-token.",
|
||||
"x5lab": "توفر X5Lab نقطة نهاية لإكمال الدردشة متوافقة مع OpenAI على https://api.x5lab.dev/v1/chat/completions، بالإضافة إلى كتالوج /v1/models مباشر. يستخدم OmniRoute بروتوكول OpenAI ويعرض النماذج عبر passthrough.",
|
||||
"xai": "ربط xAI (Grok) بمفتاح API.",
|
||||
"xiaomi-mimo": "ربط Xiaomi MiMo بمفتاح API.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "دردشة الذكاء الاصطناعي من ByteDance عبر dola.com",
|
||||
"overrideBaseUrlAdvanced": "متقدم: تجاوز عنوان URL الأساسي",
|
||||
"overrideBaseUrlHint": "متقدم: توجيه هذا المزود المدمج إلى نقطة نهاية مخصصة. اتركه فارغًا لاستخدام الافتراضي.",
|
||||
"apiProtocolLabel": "بروتوكول API",
|
||||
"apiProtocolDefault": "متوافق مع OpenAI (افتراضي)",
|
||||
"apiProtocolHint": "بعض المزودين ينشرون نفس النماذج عبر أكثر من بروتوكول. اترك الإعداد الافتراضي ما لم تكن بحاجة إلى البديل.",
|
||||
"bulkAddFormatHintCloudflare": "مفتاح واحد لكل سطر. التنسيق: name|accountId|apiKey (معرف حساب Cloudflare + رمز API).",
|
||||
"lmarenaWebCookieHint": "افتح arena.ai، وسجل الدخول، ثم انسخ ترويسة Cookie الكاملة من طلب الشبكة. قم بتضمين arena-auth-prod-v1.0 و arena-auth-prod-v1.1 (وأي أجزاء أخرى إن وجدت)، ويفضل مع cf_clearance. لا تقم بلصق ملف تعريف الارتباط الفارغ arena-auth-prod-v1 فقط. اختياري: providerSpecificData.recaptchaV3Token إذا كان create-evaluation لا يزال يرجع 403.",
|
||||
"kimiOfficialSupporterBadge": "الصديق المؤسس",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) هو الصديق المؤسس للمصادر المفتوحة لـ OmniRoute",
|
||||
"cheaperInferenceSupporterBadge": "صديق مفتوح المصدر",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference تدعم OmniRoute كصديق للمصادر المفتوحة",
|
||||
"kimiPartnerLinkNote": "رابط شريك — يدعم OmniRoute دون أي تكلفة إضافية عليك",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "رابط شريك — يدعم OmniRoute دون أي تكلفة إضافية عليك"
|
||||
},
|
||||
"settings": {
|
||||
"title": "الإعدادات",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "جارٍ الحفظ...",
|
||||
"requestBodyLimitSave": "حفظ",
|
||||
"requestBodyLimitCurrent": "الحالي: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "فشل في تحميل إعدادات التخزين المؤقت",
|
||||
"cacheConfigSaveSuccess": "تم حفظ إعدادات التخزين المؤقت",
|
||||
"cacheConfigSaveFailed": "فشل في حفظ إعدادات التخزين المؤقت",
|
||||
"modelCatalogTtlWholeNumberError": "استخدم عددًا صحيحًا",
|
||||
"modelCatalogTtlMinimumError": "الحد الأدنى هو {min} مللي ثانية",
|
||||
"modelCatalogTtlMaximumError": "الحد الأقصى هو {max} مللي ثانية",
|
||||
"modelCatalogCacheTtl": "مدة صلاحية ذاكرة التخزين المؤقت لكتالوج النموذج",
|
||||
"modelCatalogCacheTtlDescription": "مدة تخزين استجابات كتالوج النموذج قبل التحديث",
|
||||
"modelCatalogCacheTtlLabel": "مدة صلاحية ذاكرة التخزين المؤقت لكتالوج النموذج بالمللي ثانية",
|
||||
"modelCatalogCacheTtlSaving": "جاري الحفظ...",
|
||||
"modelCatalogCacheTtlSave": "احفظ",
|
||||
"modelCatalogCacheTtlCurrent": "الحالي: {value} مللي ثانية",
|
||||
"mitmProxy": "بروكسي MITM",
|
||||
"pricing": "التسعير",
|
||||
"storage": "التخزين",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "كيف يعمل التسعير",
|
||||
"cacheWrite": "كتابة ذاكرة التخزين المؤقت",
|
||||
"unsaved": "غير محفوظ",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "إعادة تعيين الافتراضيات",
|
||||
"saveProvider": "حفظ المزود",
|
||||
"model": "نموذج",
|
||||
"models": "نماذج",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "أبدًا",
|
||||
"compressionLiveZoneTitle": "المنطقة المباشرة المتوافقة مع التخزين المؤقت",
|
||||
"compressionLiveZoneDesc": "الحفاظ على استقرار بادئة المحادثة المضغوطة ومعالجة العناصر المضافة حديثًا فقط.",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "استثناءات الضغط",
|
||||
"compressionExclusionsDesc": "معرفات النماذج أو أنماط المزود/النموذج التي يجب ألا يتم ضغطها أبدًا. `*` هو الوحيدة المستخدمة كحرف بدل (مثل `openai/*`، `*embedding*`). تمر الطلبات المطابقة بدون تغيير في البايتات — لا يعمل محرك الضغط.",
|
||||
"compressionExclusionsPlaceholder": "نمط واحد لكل سطر، مثل \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "احفظ",
|
||||
"compressionExclusionsSaved": "تم الحفظ",
|
||||
"compressionExclusionsCount": "تم تكوين {count, plural, one {# استبعاد} other {# استبعادات}}",
|
||||
"compressionExclusionsEmpty": "لا توجد استثناءات مُهيأة - كل نموذج/نقطة نهاية مؤهلة للضغط (السلوك الافتراضي).",
|
||||
"compressionCavemanConfig": "تكوين محرك Caveman",
|
||||
"compressionCavemanConfigDesc": "ضبط محرك الضغط القائم على القواعد",
|
||||
"compressionCavemanPanelHint": "يتم ضبط تشغيله/إيقافه ومستواه في اللوحة:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "يتم إرساله إلى نموذج الرؤية قبل إعادة إدخال الوصف المستخرج في الطلب الأصلي.",
|
||||
"visionBridgeTimeoutMs": "المهلة (مللي ثانية)",
|
||||
"visionBridgeMaxImagesPerRequest": "ماكس الصور لكل طلب",
|
||||
"modalityBridgeIntro": "قم بربط المحتوى متعدد الوسائط بالنص قبل أن يصل إلى النماذج النصية فقط. الرؤية حية؛ الصوت يصل مع AudioBridge؛ الفيديو في خارطة الطريق.",
|
||||
"modalityBridgeVisionTab": "رؤية",
|
||||
"modalityBridgeAudioTab": "صوت",
|
||||
"modalityBridgeVideoTab": "فيديو",
|
||||
"modalityBridgeSubTabsAria": "أقسام جسر الوضعية",
|
||||
"modalityBridgeVisionTitle": "جسر الرؤية",
|
||||
"modalityBridgeVisionDesc": "وصف الصور باستخدام نموذج الرؤية واستكمال ذلك باستخدام نموذج النص الذي اختاره المستخدم.",
|
||||
"modalityBridgeAudioTitle": "جسر الصوت",
|
||||
"modalityBridgeAudioDesc": "قم بنسخ الصوت باستخدام نموذج تحويل الكلام إلى نص قبل المتابعة مع نموذج النص المختار.",
|
||||
"modalityBridgeAudioEnabled": "تفعيل جسر الصوت",
|
||||
"modalityBridgeAudioEnabledDesc": "استبدل أجزاء الصوت بالنصوص عندما لا يمكن للنموذج المستهدف معالجة الصوت.",
|
||||
"modalityBridgeAudioModel": "نموذج تحويل الكلام إلى نص",
|
||||
"modalityBridgeAudioModelAuto": "تلقائي (أول مزود STT متصل)",
|
||||
"modalityBridgeAudioMaxClips": "أقصى عدد لمقاطع الصوت لكل طلب",
|
||||
"modalityBridgeMode": "وضع",
|
||||
"modalityBridgeModeAuto": "تلقائي (موصى به)",
|
||||
"modalityBridgeModeAutoHint": "النهج القديم: إعادة توجيه النماذج الفردية بدون بيانات الاعتماد؛ وصف خلاف ذلك.",
|
||||
"modalityBridgeModeDescribe": "وصف دائمًا",
|
||||
"modalityBridgeModeDescribeHint": "النموذج الذي اخترته يجيب دائمًا؛ يتم استبدال الصور بوصف نصي.",
|
||||
"modalityBridgeModeReroute": "إعادة التوجيه دائمًا",
|
||||
"modalityBridgeModeRerouteHint": "أرسل الطلب بالكامل إلى أفضل نموذج قادر على الرؤية (يتراجع إلى الوصف عندما لا يكون هناك نموذج قابل للاستخدام).",
|
||||
"modalityBridgeVisionModel": "نموذج الرؤية",
|
||||
"modalityBridgeVisionModelAuto": "تلقائي (أفضل خيار متاح)",
|
||||
"modalityBridgeTaskAware": "وصف مدرك للمهمة",
|
||||
"modalityBridgeTaskAwareDesc": "قم بتضمين سؤال المستخدم كتركيز حتى يصف نموذج الرؤية ما هو مهم وينقل النص المرئي.",
|
||||
"modalityBridgePrompt": "وصف المطالبة",
|
||||
"modalityBridgeAdvanced": "متقدم",
|
||||
"modalityBridgeTimeoutMs": "مهلة (مللي ثانية)",
|
||||
"modalityBridgeMaxImages": "أقصى عدد من الصور لكل طلب",
|
||||
"modalityBridgeCacheEnabled": "وصف التخزين المؤقت",
|
||||
"modalityBridgeCacheEnabledDesc": "إعادة استخدام الأوصاف للصور المتطابقة (مفتاح SHA-256، في الذاكرة).",
|
||||
"modalityBridgeCacheTtlMinutes": "مدة صلاحية التخزين المؤقت (دقائق)",
|
||||
"modalityBridgeCacheMaxEntries": "عدد الإدخالات القصوى في الذاكرة المؤقتة",
|
||||
"modalityBridgeStatsBridged": "مربوط",
|
||||
"modalityBridgeStatsCacheHits": "ضربات التخزين المؤقت",
|
||||
"modalityBridgeStatsFailures": "الإخفاقات",
|
||||
"modalityBridgeStatsLastUsed": "آخر استخدام",
|
||||
"modalityBridgeStatsNever": "أبداً",
|
||||
"modalityBridgeTestButton": "اختبار باستخدام صورة عينة",
|
||||
"modalityBridgeTestRunning": "اختبار…",
|
||||
"modalityBridgeTestOk": "الجسر OK — {count} صورة(صور) موصوفة بواسطة {model}",
|
||||
"modalityBridgeTestReroute": "تم إعادة توجيه الطلب إلى {model}",
|
||||
"modalityBridgeTestNoop": "لم يتم تفعيل الجسر (قد يدعم النموذج الرؤية بشكل أصلي أو أن الجسر معطل)",
|
||||
"modalityBridgeTestError": "فشل الاختبار: {message}",
|
||||
"modalityBridgeAudioTestButton": "اختبر باستخدام صوت عينة",
|
||||
"modalityBridgeAudioTestRunning": "اختبار الصوت…",
|
||||
"modalityBridgeAudioTestOk": "جسر الصوت OK — تم نسخ {count} مقطع(ات) بواسطة {model}",
|
||||
"modalityBridgeAudioTestNoop": "لم يتم تفعيل جسر الصوت (قد يدعم الهدف الصوت، لم يتم الاتصال بمزود STT، أو أن الجسر معطل)",
|
||||
"modalityBridgeAudioTestError": "فشل اختبار الصوت: {message}",
|
||||
"modalityBridgeAudioComingSoon": "جسر الصوت (الكلام → النص عبر /v1/audio/transcriptions) سيتم شحنه في الإصدار التالي. تم حجز مفاتيح إعداداته بالفعل.",
|
||||
"modalityBridgeVideoComingSoon": "تجسير الفيديو (عينة الإطار + الترجمة) في قائمة الانتظار - راجع المشكلة #9760.",
|
||||
"modalityBridgeMovedTitle": "تم نقل Vision Bridge",
|
||||
"modalityBridgeMovedBody": "إعدادات Vision Bridge الآن موجودة في صفحة Modality Bridge المخصصة.",
|
||||
"modalityBridgeMovedCta": "فتح إعدادات جسر الوضعية",
|
||||
"resilienceMaxBackoffSteps": "أقصى خطوات التراجع",
|
||||
"resilienceProviderBreakerTitle": "قواطع دوائر لكل مزود",
|
||||
"resilienceFailureThreshold": "عتبة الفشل",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "الاستخدام",
|
||||
"grokExtraUsageCredits": "رصيد استخدام إضافي",
|
||||
"grokAutoTopUp": "إعادة تعبئة تلقائية",
|
||||
"grokAutoTopUpUnavailable": "غير متوفر",
|
||||
"grokAutoTopUpEnabled": "مفعل",
|
||||
"grokAutoTopUpDisabled": "معطل",
|
||||
"grokAutoTopUpAt": "في",
|
||||
"grokAutoTopUpAdd": "أضف",
|
||||
"grokAutoTopUpMax": "الأقصى",
|
||||
"grokAutoTopUpMonth": "شهر",
|
||||
"grokAdditionalCredits": "أرصدة إضافية",
|
||||
"loggerTab": "المسجل",
|
||||
"proxyTab": "بروكسي",
|
||||
"budgetManagement": "إدارة الميزانية",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "عنوان URL للتحقق",
|
||||
"deviceCodeYourCode": "الرمز الخاص بك",
|
||||
"deviceCodeWaiting": "في انتظار الترخيص...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "لا يمكن إكمال تسجيل الدخول إلى Google من هذا العنوان",
|
||||
"googleLoopbackWhatHappens": "تقوم Google بإصدار رمز التفويض مرة واحدة فقط عندما يكون <code>{redirectUri}</code> قابلاً للوصول من المتصفح الذي يوافق على تسجيل الدخول. هنا، تشير هذه العنوان إلى هذا الكمبيوتر، وليس إلى خادم OmniRoute — لذا فإن شاشة الموافقة تتعطل بدلاً من إعادة التوجيه، ولا يوجد عنوان URL للعودة لنسخه.",
|
||||
"googleLoopbackRecommended": "موصى به — قم بتشغيل هذا على جهاز الكمبيوتر الخاص بك، ثم الصق النتيجة أدناه:",
|
||||
"googleLoopbackHelperNote": "يفتح موافقة Google محليًا (حيث يعمل 127.0.0.1) ويطبع كائن omniroute-cred-v1.… في سطر واحد. الصق هذا الكائن في حقل الخطوة 2 أدناه - فهو يقبل كائن اعتماد بالإضافة إلى عنوان URL للرد.",
|
||||
"googleLoopbackTunnelLabel": "أو قم بإعادة توجيه منفذ لوحة التحكم عبر SSH وأعد تحميل OmniRoute من خلال النفق:",
|
||||
"googleLoopbackTunnelNote": "استبدل {userPlaceholder} باسم مستخدم SSH الخاص بك، احتفظ بالترمينال مفتوحًا، ثم افتح {localUrl} واتصل مرة أخرى من هناك.",
|
||||
"googleLoopbackHeadlessAlt": "للاستخدام الكامل بدون واجهة مع عدم وجود ردود محلية على الإطلاق، <a>قم بتكوين بيانات اعتماد Google OAuth الخاصة بك</a> بالإضافة إلى عنوان URL أساسي عام.",
|
||||
"remoteAccessInfo": "الوصول عن بعد: نظرًا لأنك تصل إلى OmniRoute عن بُعد، فبعد الحصول على الترخيص، ستظهر لك صفحة خطأ (لم يتم العثور على المضيف المحلي). وهذا أمر طبيعي — ما عليك سوى نسخ عنوان URL الكامل من شريط عنوان المتصفح لديك ولصقه أدناه.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "لا يمكن إكمال تسجيل الدخول من هذا العنوان",
|
||||
"loopbackMismatchWhatHappened": "ماذا يحدث",
|
||||
"loopbackMismatchExplanation": "بعد أن توافق على تسجيل الدخول، يقوم {providerName} دائمًا بإعادة المتصفح إلى <code>{redirectUri}</code>. تلك العنوان يشير إلى الكمبيوتر الذي يعمل على هذا المتصفح، وليس إلى خادم OmniRoute — لذا فإن رمز التفويض لا يصل أبدًا إلى OmniRoute ويفشل المزود في تسجيل الدخول دون عرض خطأ.",
|
||||
"loopbackMismatchHowToFix": "كيف يمكن إصلاح ذلك",
|
||||
"loopbackMismatchStep1": "على هذا الكمبيوتر، افتح نافذة الأوامر وابدأ نفق SSH إلى خادم OmniRoute:",
|
||||
"loopbackMismatchStep1Note": "استبدل {userPlaceholder} باسم مستخدم SSH الخاص بك. احتفظ بهذا الطرفية مفتوحة حتى تظهر الحالة كنشطة — كلا المنفذين مطلوبان: أحدهما يقدم لوحة التحكم، والآخر يستقبل الاستدعاء.",
|
||||
"loopbackMismatchStep2": "في هذا المتصفح، أعد فتح OmniRoute من خلال النفق:",
|
||||
"loopbackMismatchStep3": "ثم قم بتوصيل {providerName} مرة أخرى من التبويب الجديد. الآن تصل الاستجابة إلى الخادم ويكتمل تسجيل الدخول بشكل طبيعي.",
|
||||
"loopbackMismatchAlternative": "لا يوجد وصول SSH؟ إذا كان هذا المزود يقدم علامة استيراد الرمز، اتصل باستخدام رمز بدلاً من ذلك - هذا المسار لا يستخدم رد نداء الحلقة.",
|
||||
"step1OpenUrl": "الخطوة 1: افتح عنوان URL هذا في متصفحك",
|
||||
"copy": "نسخ",
|
||||
"step2PasteCallback": "الخطوة 2: الصق عنوان URL لرد الاتصال أو رمز التفويض هنا",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "النموذج المصدر (الأصلي للوكيل)",
|
||||
"targetModel": "النموذج المستهدف (OmniRoute)",
|
||||
"noMappings": "لم يتم تكوين أي تعيينات للنماذج. قم بتشغيل معالج الإعداد لاكتشاف النماذج تلقائيًا.",
|
||||
"noMappingsDesc": "لم يتم تكوين أي تعيينات نموذج حتى الآن. أضف تعيينات لتوجيه طلبات الوكيل عبر OmniRoute.",
|
||||
"addMapping": "إضافة خريطة",
|
||||
"selectModel": "تحديد…",
|
||||
"saveMappings": "حفظ التعيينات",
|
||||
"setupWizard": "معالج الإعداد",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "لم يتم العثور على خوادم بروكسي محفوظة. أضف خوادم بروكسي في الإعدادات → البروكسي أولاً.",
|
||||
"updateProviderFailed": "فشل تحديث المزود",
|
||||
"providerEnabled": "تم تفعيل {provider}",
|
||||
"providerDisabled": "تم تعطيل {provider}"
|
||||
"providerDisabled": "تم تعطيل {provider}",
|
||||
"providerAdded": "تم إضافة {provider}",
|
||||
"add": "أضف",
|
||||
"manualApiKey": "استخدم مفتاح API يدوي"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "خطر",
|
||||
"requiresRestart": "يتطلب إعادة التشغيل",
|
||||
"source": "المصدر",
|
||||
"ccDiscoveryAliasesEnvWarning": "مفعل عبر متغير البيئة (EXPOSE_CC_DISCOVERY_ALIASES) — هذا يتجاوز أي تبديل لوحة تحكم أدناه.",
|
||||
"resetFlag": "إعادة تعيين {label} إلى الافتراضي",
|
||||
"reset": "إعادة تعيين",
|
||||
"loadFailed": "فشل تحميل علامات الميزات",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "تمكين الوصول إلى الشبكة في بيئة اختبار المهارات المعزولة."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "مركز تحكم Combo",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "رابط شريك",
|
||||
"dismissAriaLabel": "تجاهل"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "قم بالإعلان عن معرفات مرآة <gateway-alias>/<model> على /v1/models للنماذج التي ليس لديها مالك قانوني لديه بيانات اعتماد نشطة ولكن بوابة تمرير مع بيانات اعتماد نشطة تقوم بتوجيهها. تحذير: يضيف إدخالات الكتالوج لجميع العملاء عند تمكينه عالميًا.",
|
||||
"radarPage": {
|
||||
"title": "كتالوج الرادار",
|
||||
"subtitle": "كتالوج نموذج مجاني معزز بذكاء المجتمع",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "Deaktiv",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Büdcəsi tükənmiş sorğuları təcili pulsuz ehtiyat təminatçıya/modelə yönləndirin.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Model intellekti reytinqləri üçün dövri Arena AI liderlər cədvəli ELO sinxronizasiyasını aktivləşdirin.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "/v1/models üzərində claude/<provider>/<model> güzgü id-lərini reklam edin ki, Claude Code keçid modeli kəşfiyyatı qeyri-Claude modellərini siyahıya alsın. Diqqət: qlobal olaraq aktiv edildikdə bütün müştərilər üçün kataloq girişlərini ikiqat artırır.",
|
||||
"sidebar": {
|
||||
"home": "Home",
|
||||
"dashboard": "Dashboard",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "Combo Studio",
|
||||
"combosLiveSubtitle": "Canlı marşrutlaşdırma kaskadı",
|
||||
"compressionStudio": "Compression Studio",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "İstisnalar",
|
||||
"contextSettingsSubtitle": "Qlobal standartlar",
|
||||
"contextHeadroomSubtitle": "Cədvəl sıxlaşdırması",
|
||||
"contextSessionDedupSubtitle": "Dönüşlərarası təkrarların təmizlənməsi",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "Evristik budama",
|
||||
"contextOmniglyphSubtitle": "Kontekst şəkillər kimi",
|
||||
"compressionStudioSubtitle": "Canlı mühərrik kaskadı",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "Model/endpoint üzrə keçid",
|
||||
"chaosConfigSubtitle": "Çoxmodelli paralel icra",
|
||||
"routingSection": "Marşrutlaşdırma",
|
||||
"protocolsSection": "Protokollar",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "Aylıq pulsuz token limitləri",
|
||||
"freeProviderRankings": "Pulsuz Təminatçı Reytinqləri",
|
||||
"freeProviderRankingsSubtitle": "Model ELO xallarına görə sıralanmış ən yaxşı pulsuz təminatçılar",
|
||||
"radar": "Radar Kataloqu",
|
||||
"radarSubtitle": "İcma ilə zənginləşdirilmiş pulsuz model kataloqu",
|
||||
"costsQuotaShare": "Kvota Paylaşımı",
|
||||
"costsPricing": "Qiymətləndirmə",
|
||||
"logsProxy": "Proksi qeydləri",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Görünüş",
|
||||
"settingsAi": "AI Parametrləri",
|
||||
"settingsModalityBridge": "Modallıq Körpüsü",
|
||||
"settingsSecurity": "Təhlükəsizlik",
|
||||
"settingsAccessTokens": "Giriş Tokenləri",
|
||||
"settingsFeatureFlags": "Xüsusiyyət Bayraqları",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "Keş",
|
||||
"settingsAuthz": "Authz",
|
||||
"settingsRouting": "Marşrutlaşdırma",
|
||||
"settingsResilience": "Dözümlülük",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "İcra müddəti",
|
||||
"consoleLogs": "Konsol qeydləri",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "Vizual tələb zaman cədvəli",
|
||||
"globalRouting": "Qlobal marşrutlaşdırma",
|
||||
"mitmProxy": "MITM Proksi",
|
||||
"oneProxy": "1 Proksi",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "App basics",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "Şəkil/səs → mətn yalnız model üçün mətnə keçid",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
"settingsSecuritySubtitle": "Auth and encryption",
|
||||
"settingsAccessTokensSubtitle": "Uzaq rejim üçün məhdud əhatəli CLI tokenləri",
|
||||
"settingsFeatureFlagsSubtitle": "Sistem imkanlarını dəyişdirin",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "Model kataloqu və cavab keşlənməsi",
|
||||
"settingsSidebar": "Sidebar",
|
||||
"settingsSidebarSubtitle": "Customize sidebar layout",
|
||||
"settingsAuthzSubtitle": "Marşrut inventarı və yan keçmə siyasəti",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "Həmişə görünən",
|
||||
"groupSeparatorLabel": "Ayırıcı",
|
||||
"discovery": "Kəşf",
|
||||
"discoverySubtitle": "Pulsuz giriş üçün provayderləri skan edin",
|
||||
"radar": "Radar Kataloqu",
|
||||
"radarSubtitle": "İcma ilə zənginləşdirilmiş pulsuz model kataloqu"
|
||||
"discoverySubtitle": "Pulsuz giriş üçün provayderləri skan edin"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "Yaddaş, verilənlər bazası və ümumi nümunə konfiqurasiyası",
|
||||
"settingsAppearanceDescription": "Mövzu, brendinq və vizual fərdiləşdirmə",
|
||||
"settingsAiDescription": "AI davranışları, düşünmə büdcələri, görmə və yaddaş parametrləri",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "Model kataloqu keşb girişləri üçün TTL",
|
||||
"settingsSecurityDescription": "Doğrulama, avtorizasiya və girişə nəzarət parametrləri",
|
||||
"featureFlags": "Xüsusiyyət Bayraqları",
|
||||
"featureFlagsDescription": "İdarəetmə sisteminin imkanları və eksperimental xüsusiyyətləri",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "Şəkil generasiyası",
|
||||
"imageToText": "Şəkildən mətnə",
|
||||
"imageToTextComingSoon": "Daxili Şəkildən Mətnə sınaq mühiti <code>/api/v1/images/understanding</code> tətbiq edildikdə əlçatan olacaq.",
|
||||
"imageToTextBridgeCta": "Modallıq Körpüsü parametrlərində Şəkil→Mətn körpüsünü konfiqurasiya edin",
|
||||
"sttBridgeCta": "Modality Bridge parametrlərində Səs→Mətn körpüsünü konfiqurasiya edin",
|
||||
"disabled": "Deaktiv edilib",
|
||||
"videoGeneration": "Video generasiyası",
|
||||
"musicGeneration": "Musiqi generasiyası",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "Avtomatik",
|
||||
"always": "Həmişə"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Claude Kodunda kəşfi necə aktivləşdirmək olar",
|
||||
"ccDiscoveryInfoTooltip": "Claude olmayan modelləri claude/<provider>/<model> güzgü ID-ləri altında reklam edin ki, Claude Code-un qapı modeli kəşfi onları siyahıya ala bilsin. Qlobal olaraq aktiv edildikdə bütün müştərilər üçün kataloq girişlərini ikiqat artırır.",
|
||||
"ccDiscoveryInfoLink": "Xüsusiyyət Bayraqlarını Açın",
|
||||
"ccOnboardingTitle": "gateway modeli kəşfi üçün settings.json",
|
||||
"ccOnboardingCopy": "Kopyala",
|
||||
"ccOnboardingCopied": "Kopyalandı",
|
||||
"ccOnboardingKeyPlaceholder": "<your OmniRoute API açarınız>",
|
||||
"ccOnboardingWindowNote": "Claude Code tanımadığı hər hansı model id üçün 200K kontekst pəncərəsi qəbul edir. Fərqli real pəncərəyə malik bir model üçün, avtomatik sıxılmanın çox tez başlamaması üçün onun altına CLAUDE_CODE_AUTO_COMPACT_WINDOW əlavə edin.",
|
||||
"failedSave": "Yadda saxlamaq mümkün olmadı",
|
||||
"profileSyncTitle": "CLI profilinin avtomatik sinxronizasiyası",
|
||||
"profileSyncDescription": "Təminatçı modelləri sinxronlaşdırıldıqdan sonra canlı kataloqdan CLI alət profillərini avtomatik olaraq yenidən yaradın. Standart olaraq qeyri-aktivdir — yalnız profil faylları yazılır; aktiv/standart konfiqurasiya heç vaxt dəyişdirilmir.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "Bacarıqlar və alətlərdən istifadə mühakiməsi",
|
||||
"hermesRoleApproval": "Təsdiqləmə",
|
||||
"hermesRoleApprovalDesc": "Təhlükəsizlik və təsdiqləmə qərarları",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "MCP server alət çağırışları",
|
||||
"hermesRoleTitleGeneration": "Başlıq Yaratma",
|
||||
"hermesRoleTitleGenerationDesc": "Seans başlığı yaradılması",
|
||||
"hermesRoleMemoryQueryRewrite": "Yaddaş Sorğusunu Yenidən Yazın",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "Yaddaş axtarış sorğusunun yenidən yazılması",
|
||||
"hermesRoleTtsAudioTags": "TTS Səs Etiketləri",
|
||||
"hermesRoleTtsAudioTagsDesc": "TTS audio etiketi yaradılması",
|
||||
"hermesRoleTriageSpecifier": "Triage Təyin Edici",
|
||||
"hermesRoleTriageSpecifierDesc": "Məsələ və PR triage spesifikasiyası",
|
||||
"hermesRoleKanbanDecomposer": "Kanban Decomposer",
|
||||
"hermesRoleKanbanDecomposerDesc": "Kanban tapşırıq parçalanması",
|
||||
"hermesRoleProfileDescriber": "Profil Təsvirçisi",
|
||||
"hermesRoleProfileDescriberDesc": "İstifadəçi profili təsviri",
|
||||
"hermesRoleGoalJudge": "Məqsəd Məhkəməsi",
|
||||
"hermesRoleGoalJudgeDesc": "Məqsədin tamamlanmasının qiymətləndirilməsi",
|
||||
"hermesRoleCurator": "Kürator",
|
||||
"hermesRoleCuratorDesc": "Bacarıq və yaddaşın tənzimlənməsi",
|
||||
"hermesRoleMonitor": "Monitor",
|
||||
"hermesRoleMonitorDesc": "Arxa plan monitorinqi",
|
||||
"hermesRoleBackgroundReview": "Arxa Planın İcmalı",
|
||||
"hermesRoleBackgroundReviewDesc": "Arxa plan kodu icmalı",
|
||||
"hermesSelectBeforePreview": "Önbaxışdan əvvəl rollar üçün modelləri seçin və ya rolların yükləndiyindən əmin olun.",
|
||||
"hermesPreviewFailed": "Önbaxış yaradıla bilmədi",
|
||||
"hermesSavedTo": "{path} ünvanında saxlanıldı",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "Yapışdırın:",
|
||||
"copilotReloadInstruction": "Sonra VS Code-u yenidən yükləyin və daxiletmə sorğusunda API açarını təyin edin.",
|
||||
"wireApiChatCompletions": "Söhbət Tamamlamaları (/chat/tamamlamalar)",
|
||||
"wireApiResponses": "Responses API (/cavablar)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "Responses API (/cavablar)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Combos",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"sectionTitle": "Integration Surface",
|
||||
"sectionDescription": "OpenAI-compatible APIs and operational protocol endpoints",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "API-lər",
|
||||
"tabProtocols": "Protocols",
|
||||
"tabsAria": "Endpoint sections",
|
||||
"protocolsTitle": "Protocols",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "No new models were added (all already exist).",
|
||||
"importDoneCount": "✓ Done! {count, plural, one {# model imported.} other {# models imported.}}",
|
||||
"unexpectedErrorOccurred": "An unexpected error occurred",
|
||||
"getApiKey": "API açarını alın",
|
||||
"getApiKeyDescription": "API açarı üçün qeydiyyatdan keçin və ya qeydiyyatdan keçin",
|
||||
"connectionCountLabel": "{count, plural, one {# connection} other {# connections}}",
|
||||
"messagesPath": "messages",
|
||||
"responsesPath": "responses",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "Yerli web_fetch alət çağırışlarını OmniRoute-un /v1/web/fetch ünvanına yenidən yazın.",
|
||||
"interceptionLoadError": "Ələ keçirmə parametrlərini yükləmək mümkün olmadı: {error}",
|
||||
"interceptionSaveError": "Ələ keçirmə parametrlərini yadda saxlamaq mümkün olmadı: {error}",
|
||||
"ccAliasSectionTitle": "Claude Kodunda (claude/…) açıq et",
|
||||
"ccAliasSectionHint": "Bu provayderin modellərini claude/<provider>/<model> güzgü ID-ləri altında reklam edin ki, Claude Code-un qapı modeli kəşfi onları siyahıya ala bilsin. Varsayılan olaraq deaktivdir — bunu aktivləşdirmək bütün müştərilər üçün kataloq girişlərini ikiqat artırır.",
|
||||
"ccAliasProviderLevelLabel": "Təchizatçı standart",
|
||||
"ccAliasModelOverridesLabel": "Model üzrə üst-üstə düşmələr",
|
||||
"ccAliasModelOverrideAriaLabel": "{nameId} üçün üst-üstə düşmə",
|
||||
"ccAliasStateInherit": "İrsən al",
|
||||
"ccAliasStateOn": "İşdə",
|
||||
"ccAliasStateOff": "Söndürüldü",
|
||||
"ccAliasAddModelPlaceholder": "Model id (məsələn, gpt-4o)",
|
||||
"ccAliasAddModelButton": "Override əlavə et",
|
||||
"ccAliasLoadError": "Kəşf-alias parametrləri yüklənmədi: {error}",
|
||||
"ccAliasSaveError": "discovery-alias parametrlərini saxlamaq mümkün olmadı: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
@@ -5413,8 +5453,8 @@
|
||||
"t3ChatWebCookieHint": "t3.chat → DevTools → Proqram → Yerli Yaddaş → https://t3.chat açın, 'convex-session-id' kopyalayın. Sonra DevTools → Network açın, istənilən söhbət sorğusundan tam kuki başlığını kopyalayın. Hər iki dəyəri aşağıdakı sahələrə yapışdırın.",
|
||||
"t3ChatWebCookiePlaceholder": "konveks-sessiya-id=abc123...",
|
||||
"grokWebCookieHint": "Grok Web Cookie Hint",
|
||||
"blockClaudeExtraUsageDescription": "__MISSING__:When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.",
|
||||
"blockClaudeExtraUsageLabel": "__MISSING__:Block extra Claude usage",
|
||||
"blockClaudeExtraUsageDescription": "Aktiv edildikdə, OmniRoute bu Claude bağlantısını istifadənin API-si sıralanmış əlavə istifadəni bildirdiyi anda istifadəyə yararsız olaraq işarələyir, beləliklə, ehtiyat başqa bir bağlantıya keçid edir, ödənişə görə əlavə ödəniş etməyə davam etmək əvəzinə.",
|
||||
"blockClaudeExtraUsageLabel": "Əlavə Claude istifadəsini bloklayın",
|
||||
"disableCoolingDescription": "Müvəqqəti soyuma müddətini atlayın ki, bu bağlantı bərpa edilə bilən xətalardan sonra belə uyğun qalsın (bloklanmış/vaxtı keçmiş kimi terminal vəziyyətlər hələ də tətbiq olunur).",
|
||||
"disableCoolingLabel": "Bu bağlantı üçün soyuma müddətini söndürün",
|
||||
"bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "New-API User ID",
|
||||
"newApiUserIdPlaceholder": "e.g. 12345",
|
||||
"newApiUserIdHint": "AgentRouter New-Api-User header value, used together with the console API key to fetch quota balance.",
|
||||
"newApiAggregatorToggleLabel": "Toplayıcı Qapısı",
|
||||
"newApiAggregatorToggleHint": "Yeni-API / Bir-API / Sub2API toplayıcı düyünləri üçün balans aşkar etməni aktivləşdirin. İdarəetmə paneli balans nişanını göstərəcək və kvota-öncəsi yönləndirmə tükənmiş hesabları atlayacaq.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "Toplanıcı üçün /api/user/self son nöqtəsi üçün Sistem Giriş Tokeni. Marşrutlaşdırma API açarı deyil.",
|
||||
"newApiAggregatorUserIdHint": "Yeni-Api-Istifadəçi başlıq dəyəri toplayıcı istifadəçinin kvota balansını əldə etmək üçün istifadə olunur.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "Vahid Başına Kotа",
|
||||
"newApiAggregatorQuotaPerUnitHint": "Yeni-API kredit vahidləri $1 üçün (default: 500000). Əgər sizin toplayıcınız fərqli bir dərəcə istifadə edirsə, üstələyin.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "Yeni-API / Bir-API / Sub2API toplayıcı ilə uyğun düyünlər üçün balans aşkar etməni aktivləşdirin",
|
||||
"cpaModeDisabledTitle": "CLIProxyAPI compatibility mode is disabled",
|
||||
"cpaModeEnabledTitle": "CLIProxyAPI compatibility mode is enabled",
|
||||
"customUserAgentHint": "Custom User Agent Hint",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Tag Group Placeholder",
|
||||
"testModel": "Test Model",
|
||||
"testingModel": "Testing Model",
|
||||
"modelTestQuotaTooltip": "Kvota bitdi — sabah sıfırlanır və ya əlavə maliyyə tələb olunur",
|
||||
"toggleOffShort": "Off",
|
||||
"toggleOnShort": "On",
|
||||
"tokenExpiredBadge": "Token Expired Badge",
|
||||
@@ -5593,13 +5641,13 @@
|
||||
"importGrokAuth": "Grok Build kimlik doğrulaması idxal et",
|
||||
"zedImportTitle": "Zed Keychain-dən idxal et",
|
||||
"zedImportDescription": "Zed IDE tərəfindən OS keychain-də saxlanılan AI provayder etibarnamələrini (OpenAI, Anthropic, Google, Mistral, xAI) aşkar edin və onları bağlantı kimi idxal edin. Zed IDE bu cihazda quraşdırılmalıdır.",
|
||||
"zedImportButton": "__MISSING__:Import from Zed",
|
||||
"zedImportFailed": "__MISSING__:Zed import failed",
|
||||
"zedImportButton": "Zed-dən İdxal Et",
|
||||
"zedImportFailed": "Zed idxalı uğursuz oldu",
|
||||
"zedImportHint": "Zed Import Hint",
|
||||
"zedImportNetworkError": "Zed Import Network Error",
|
||||
"zedImportNone": "Zed Import None",
|
||||
"zedImportSuccess": "__MISSING__:Imported {credentials} credential(s) from Zed for {providers} provider(s)",
|
||||
"zedImporting": "__MISSING__:Importing…",
|
||||
"zedImportSuccess": "{providers} provayder(ler) üçün Zed-dən {credentials} etibarnamə(lar) idxal edildi",
|
||||
"zedImporting": "İdxal edilir…",
|
||||
"zedNoCredentials": "Keychain-də heç bir Zed etibarnaməsi tapılmadı",
|
||||
"zedUnsupportedCredentials": "{count} keychain etibarnaməsi tapıldı, lakin heç biri dəstəklənən provayderlərə uyğun gəlmədi",
|
||||
"zedManualTitle": "Əl ilə token idxalı",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "API açarını ai.360.cn ünvanından əldə edin",
|
||||
"agentrouter": "https://agentrouter.org/register ünvanından $200 pulsuz kredit əldə edin — kredit kartı tələb olunmur.",
|
||||
"unorouter": "https://unorouter.ai saytında API açarı yaradın, sonra onu burada Bearer token kimi yapışdırın.",
|
||||
"agnes": "API açarını agnes-ai.com ünvanından əldə edin",
|
||||
"aimlapi": "Pulsuz paket dayandırılıb (2026) — AI/ML API artıq yalnız istifadə etdikcə ödə modelindədir (min. $20 balans artırma); təkrarlanan pulsuz kreditlər yoxdur.",
|
||||
"ai21": "Qeydiyyat zamanı $10 sınaq krediti (3 ay etibarlıdır), kredit kartı tələb olunmur",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "Alibaba Coding Plan-ı API açarı ilə qoşun.",
|
||||
"bedrock": "Yerli Bedrock inteqrasiyası: model kəşfi Bedrock baza modellərindən və nəticə çıxarma profillərindən istifadə edir, söhbət isə regional Bedrock Runtime Converse/ConverseStream API-lərindən istifadə edir.",
|
||||
"anthropic": "Anthropic-i API açarı ilə qoşun.",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "Ant Ling API konsolunda (https://chat.ant-ling.com/open) qeydiyyatdan keçin və API açarı yaradın, sonra onu buraya yapışdırın. OmniRoute, söhbət trafikini https://api.ant-ling.com/v1/chat/completions ünvanına yönləndirir; təminatçı OpenAI ilə uyğun gəlir və həmçinin Anthropic ilə uyğun bir interfeys təqdim edir.",
|
||||
"api-airforce": "API açarınızı https://panel.api.airforce ünvanından əldə edin — OpenAI ilə uyğun gələn son nöqtə: https://api.airforce/v1",
|
||||
"arcee-ai": "API açarını arcee.ai ünvanından əldə edin",
|
||||
"azure-ai": "Foundry model kimi yerləşdirmə adları ilə OpenAI v1 interfeysindən istifadə edir. OmniRoute kök resurs URL-lərini v1 chat və /models son nöqtələrinə normallaşdırır.",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "Chutes OpenAI ilə uyğun gələn şlüz üçün Bearer API açarı.",
|
||||
"clarifai": "Clarifai /v2/ext/openai/v1 üzərində OpenAI ilə uyğun söhbət, cavablar və /models təqdim edir. İctimai/icma modelləri adətən PAT tələb edir; tətbiq miqyaslı açarlar yalnız həmin tətbiqin daxilindəki resurslar üçün işləyir.",
|
||||
"cloudflare-ai": "API Tokeni VƏ Hesab ID-si (dash.cloudflare.com ünvanında tapıla bilər) tələb olunur",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) OpenAI ilə uyğun gəlir /v1/openai. OmniRoute /v1/openai/models-a baxış keçirir və söhbət trafikini /v1/openai/chat/completions-a yönləndirir. Hal-hazırda clovastudio.stream.ntruss.com hostundan istifadə edir — köhnə clovastudio.apigw.ntruss.com son nöqtəsi ləğv edilir.",
|
||||
"codestral": "Codestral-ı API açarı ilə qoşun.",
|
||||
"cohere": "Pulsuz Sınaq: Sınaq üçün ayda 1,000 API çağırışı, kredit kartı tələb olunmur",
|
||||
"command-code": "Command Code-dan API açarı yaradın və ya kopyalayın, sonra onu bura Bearer tokeni kimi yapışdırın.",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "watsonx model şlüzü /ml/gateway/v1 altında OpenAI ilə uyğun /chat/completions və /models təqdim edir.",
|
||||
"ideogram": "API açarını ideogram.ai/docs/api ünvanından əldə edin",
|
||||
"iflytek": "API açarını console.xfyun.cn ünvanından əldə edin",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs OpenAI ilə uyğun gəlir https://api.inceptionlabs.ai/v1. mercury-2 kataloqda ilk diffuziya LLM (dLLM) dir — müqayisə edilə bilən avto-regressiv modellərdən 5-10x daha sürətli yaradılma, alət çağırma, json_mode və strukturlaşdırılmış çıxışlarla.",
|
||||
"inference-net": "Qeydiyyatdan keçdikdə $25 pulsuz kredit, həmçinin tədqiqat qrantları mövcuddur",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "Pulsuz aylıq kvota ~1M giriş / 3M çıxış tokenləri (~10 RPM)",
|
||||
"jina-ai": "Jina AI rerank API-si üçün Bearer API açarı.",
|
||||
"jina-reader": "Jina Reader-i API açarı ilə qoşun.",
|
||||
"kenari": "Kenari https://kenari.id/v1/chat/completions ünvanında OpenAI ilə uyğun söhbət tamamlama son nöqtəsini, həmçinin Claude, GPT, DeepSeek, GLM, Kimi və daha çoxunu əhatə edən canlı /v1/models kataloqunu təqdim edir. OmniRoute OpenAI protokolundan istifadə edir və modelləri birbaşa ötürmə (passthrough) vasitəsilə siyahıya salır.",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "Perplexity-ni API açarı ilə qoşun.",
|
||||
"piapi": "PiAPI-ni API açarı ilə qoşun.",
|
||||
"pioneer": "$75 pulsuz istifadə krediti — kredit kartı tələb olunmur",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo OpenAI ilə uyğundur https://api.platform.preferredai.jp/v1. Preferred Networks tərəfindən hazırlanmışdır və Yapon dilinə optimallaşdırılmışdır. Sənədlər əsasən Yapon dilindədir.",
|
||||
"poe": "Poe https://api.poe.com/v1 ünvanında OpenAI ilə uyğun söhbət və cavabları təqdim edir, /usage/current_balance ünvanında isə autentifikasiya edilmiş balans yoxlamalarını dəstəkləyir.",
|
||||
"pollinations": "Pulsuz açarsız səviyyə: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning. Premium modellər (claude, gemini, midijourney) enter.pollinations.ai saytından Pollinations API açarı tələb edir.",
|
||||
"publicai": "API açarı tələb olunur — qeydiyyat zamanı birdəfəlik kredit, sonra isə ödənişli",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "Runway video yaradılması tapşırıq əsaslıdır. OmniRoute mətndən-videoya və ya şəkildən-videoya tapşırıqlarını təqdim edir, /v1/tasks/[id] ünvanını sorğulayır və tamamlanmış video çıxışlarını yenidən OpenAI tipli /v1/videos/generations cavabına normallaşdırır.",
|
||||
"sambanova": "Qeydiyyatdan keçdikdə $5 pulsuz kredit (30 gün etibarlılıq müddəti), kredit kartı tələb olunmur",
|
||||
"sap": "Model kəşfi AI_API_URL üzərində /v2/lm/scenarios/foundation-models/models istifadə edir. Söhbət sorğuları deploymentUrl/chat/completions istifadə edir və AI-Resource-Group tələb edir.",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "Sarvam AI OpenAI ilə uyğun gəlir /v1. OmniRoute /v1/models-i yoxlayır və söhbət trafikini /v1/chat/completions-a yönləndirir. Modellər Hind dilləri üçün tənzimlənmişdir.",
|
||||
"scaleway": "Yeni hesablar üçün 1M pulsuz token — Aİ/GDPR uyğun (Paris), Qwen3 235B və Llama 70B",
|
||||
"sensenova": "API açarını platform.sensenova.cn ünvanından əldə edin",
|
||||
"siliconflow": "Şəxsiyyətin təsdiqlənməsindən sonra $1 pulsuz kredit və daimi pulsuz modellər",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "Together AI-ı API açarı ilə qoşun.",
|
||||
"tokenrouter": "TokenRouter https://api.tokenrouter.com/v1/chat/completions ünvanında OpenAI ilə uyğun söhbət tamamlama son nöqtəsini və əlavə olaraq işlək /v1/models kataloqunu təqdim edir. OmniRoute OpenAI protokolundan istifadə edir.",
|
||||
"topaz": "Topaz-ı API açarı ilə qoşun.",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "Typhoon OpenAI ilə uyğun gəlir /v1. SCB 10X (Tayland) tərəfindən hazırlanmışdır; typhoon-v2.5-30b-a3b-instruct tayca birinci, çoxdilli modeldir.",
|
||||
"udio": "udio.com (Supabase auth) saytından sessiya kukisini yapışdırın",
|
||||
"uncloseai": "Autentifikasiya tələb olunmur. API identifikasiya üçün açar kimi istənilən boş olmayan sətri qəbul edir.",
|
||||
"upstage": "Upstage-i API açarı ilə qoşun.",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "Voyage AI embeddings və rerank API-ləri üçün Bearer API açarı.",
|
||||
"wafer": "https://wafer.ai saytından API açarı",
|
||||
"wandb": "Weights & Biases Inference-i API açarı ilə qoşun.",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra OpenAI ilə uyğun gəlir https://api.writer.com/v1. palmyra-x5 1M-token kontekst pəncərəsi təklif edir.",
|
||||
"x5lab": "X5Lab https://api.x5lab.dev/v1/chat/completions ünvanında OpenAI ilə uyğun gələn chat completions son nöqtəsini, həmçinin canlı /v1/models kataloqunu təqdim edir. OmniRoute OpenAI protokolundan istifadə edir və modelləri passthrough vasitəsilə siyahıya salır.",
|
||||
"xai": "xAI (Grok)-u API açarı ilə qoşun.",
|
||||
"xiaomi-mimo": "Xiaomi MiMo-nu API açarı ilə qoşun.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "ByteDance AI chat via dola.com",
|
||||
"overrideBaseUrlAdvanced": "Advanced: override base URL",
|
||||
"overrideBaseUrlHint": "Advanced: point this built-in provider at a custom endpoint. Leave blank to use the default.",
|
||||
"apiProtocolLabel": "API protokolu",
|
||||
"apiProtocolDefault": "OpenAI-uyğun (default)",
|
||||
"apiProtocolHint": "Bəzi provayderlər eyni modelləri bir neçə protokol üzrə dərc edirlər. Alternativə ehtiyacınız olmadıqca, standartı saxlayın.",
|
||||
"bulkAddFormatHintCloudflare": "One key per line. Format: name|accountId|apiKey (Cloudflare account ID + API token).",
|
||||
"lmarenaWebCookieHint": "Open arena.ai, sign in, then copy the full Cookie header from a Network request. Include arena-auth-prod-v1.0 and arena-auth-prod-v1.1 (and further chunks if present), preferably with cf_clearance. Do not paste only the empty arena-auth-prod-v1 cookie. Optional: providerSpecificData.recaptchaV3Token if create-evaluation still returns 403.",
|
||||
"kimiOfficialSupporterBadge": "Təsisçi Dost",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) OmniRoute-un təsisçi Açıq Mənbə Dostudur",
|
||||
"cheaperInferenceSupporterBadge": "Açıq Mənbə Dostu",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference OmniRoute-u Açıq Mənbə Dostu kimi dəstəkləyir",
|
||||
"kimiPartnerLinkNote": "Tərəfdaş linki — sizə heç bir əlavə xərc olmadan OmniRoute-u dəstəkləyir",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "Tərəfdaş linki — sizə heç bir əlavə xərc olmadan OmniRoute-u dəstəkləyir"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "Saving...",
|
||||
"requestBodyLimitSave": "Save",
|
||||
"requestBodyLimitCurrent": "Current: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "Keş parametrlərini yükləmək mümkün olmadı",
|
||||
"cacheConfigSaveSuccess": "Keş parametrləri saxlanıldı",
|
||||
"cacheConfigSaveFailed": "Keş parametrlərini saxlamaq mümkün olmadı",
|
||||
"modelCatalogTtlWholeNumberError": "Tam ədəd istifadə edin",
|
||||
"modelCatalogTtlMinimumError": "Minimum {min} ms-dir",
|
||||
"modelCatalogTtlMaximumError": "Maksimum {max} ms-dir",
|
||||
"modelCatalogCacheTtl": "Model Kataloqu Keş TTL",
|
||||
"modelCatalogCacheTtlDescription": "Model kataloqu cavabları yeniləmədən əvvəl nə qədər müddət önbelleğe alınır",
|
||||
"modelCatalogCacheTtlLabel": "Model kataloqu keş cache TTL millisekundlarla",
|
||||
"modelCatalogCacheTtlSaving": "Saxlanılır...",
|
||||
"modelCatalogCacheTtlSave": "Saxla",
|
||||
"modelCatalogCacheTtlCurrent": "Cari: {value} ms",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"pricing": "Pricing",
|
||||
"storage": "Storage",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "How Pricing Works",
|
||||
"cacheWrite": "Cache Write",
|
||||
"unsaved": "unsaved",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "Sıfırlama standartları",
|
||||
"saveProvider": "Save Provider",
|
||||
"model": "Model",
|
||||
"models": "models",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "Heç vaxt",
|
||||
"compressionLiveZoneTitle": "Keşlə düzləndirilmiş Canlı Zona",
|
||||
"compressionLiveZoneDesc": "Sıxılmış söhbət prefiksini sabit saxlayın və yalnız yeni əlavə edilmiş elementləri emal edin.",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "Sıxma İstisnaları",
|
||||
"compressionExclusionsDesc": "Sıxılmamalı olan model id-ləri və ya provayder/model nümunələri. `*` yeganə yer tutucudur (məsələn, `openai/*`, `*embedding*`). Uyğun gələn bir tələb byte-identik olaraq keçir — sıxılma mühərriki işləmir.",
|
||||
"compressionExclusionsPlaceholder": "Bir nümunə bir sətirdə, məsələn: \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "Saxla",
|
||||
"compressionExclusionsSaved": "Saxlanıldı",
|
||||
"compressionExclusionsCount": "{count, plural, one {# istisna} other {# istisnalar}} konfiqurasiya edilib",
|
||||
"compressionExclusionsEmpty": "Heç bir istisna konfiqurasiya edilməyib — hər bir model/son nöqtə sıxılma üçün uyğundur (standart davranış).",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "Onun aktiv/deaktiv edilməsi və səviyyəsi paneldə təyin olunur:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Çıxarılmış təsvir orijinal sorğuya yenidən daxil edilməzdən əvvəl görüntü modelinə göndərilir.",
|
||||
"visionBridgeTimeoutMs": "Zaman aşımı (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "İstək Başına Maksimum Şəkillər",
|
||||
"modalityBridgeIntro": "Multimodal məzmunu mətnə köçürün, mətnə yalnız modellərə çatmadan əvvəl. Görmə aktivdir; Səs AudioBridge ilə gəlir; Video isə yol xəritəsindədir.",
|
||||
"modalityBridgeVisionTab": "Görmə",
|
||||
"modalityBridgeAudioTab": "Səs",
|
||||
"modalityBridgeVideoTab": "Video",
|
||||
"modalityBridgeSubTabsAria": "Modallıq Körpüsü bölmələri",
|
||||
"modalityBridgeVisionTitle": "Görmə Körpüsü",
|
||||
"modalityBridgeVisionDesc": "Şəkilləri bir görmə modeli ilə təsvir edin və istifadəçinin seçdiyi mətn modeli ilə davam edin.",
|
||||
"modalityBridgeAudioTitle": "Səs Körpüsü",
|
||||
"modalityBridgeAudioDesc": "Seçilmiş mətn modelinə davam etməzdən əvvəl səsdən mətbə çevirmək üçün nitqdən mətbə modelindən istifadə edin.",
|
||||
"modalityBridgeAudioEnabled": "Səs Körpüsünü Aktivləşdirin",
|
||||
"modalityBridgeAudioEnabledDesc": "Hədəf model audio emal edə bilmədikdə audio hissələri transkriptlərlə əvəz edin.",
|
||||
"modalityBridgeAudioModel": "Nitqdan mətnə model",
|
||||
"modalityBridgeAudioModelAuto": "Avtomatik (ilk qoşulmuş STT təminatçısı)",
|
||||
"modalityBridgeAudioMaxClips": "Tələb başına maksimum audio kliplər",
|
||||
"modalityBridgeMode": "Rejim",
|
||||
"modalityBridgeModeAuto": "Avtomatik (tövsiyə olunur)",
|
||||
"modalityBridgeModeAutoHint": "Köhnə heuristika: fərdi modelləri kimlik məlumatları olmadan yönləndir; əks halda təsvir et.",
|
||||
"modalityBridgeModeDescribe": "Həmişə təsvir et",
|
||||
"modalityBridgeModeDescribeHint": "Seçdiyiniz model həmişə cavab verir; şəkillər mətn təsvirləri ilə əvəz olunur.",
|
||||
"modalityBridgeModeReroute": "Həmişə yönləndir",
|
||||
"modalityBridgeModeRerouteHint": "Bütün sorğunu ən yaxşı görmə qabiliyyətinə malik modelə göndərin (istifadə oluna bilən olmadıqda təsvir etməyə geri dönür).",
|
||||
"modalityBridgeVisionModel": "Görmə modeli",
|
||||
"modalityBridgeVisionModelAuto": "Avtomatik (ən yaxşı mövcud)",
|
||||
"modalityBridgeTaskAware": "Tapşırığa uyğun təsvir",
|
||||
"modalityBridgeTaskAwareDesc": "İstifadəçinin sualını diqqət mərkəzində saxlayın ki, görmə modeli vacib olanı təsvir etsin və görünən mətni transkribə etsin.",
|
||||
"modalityBridgePrompt": "Təsvir təklifi",
|
||||
"modalityBridgeAdvanced": "İrəliləmiş",
|
||||
"modalityBridgeTimeoutMs": "Vaxt bitməsi (ms)",
|
||||
"modalityBridgeMaxImages": "Tələb başına maksimum şəkil sayı",
|
||||
"modalityBridgeCacheEnabled": "Keş təsvirləri",
|
||||
"modalityBridgeCacheEnabledDesc": "Eyniləşən şəkillər üçün təsvirləri yenidən istifadə edin (SHA-256 açarına əsaslanan, yaddaşda).",
|
||||
"modalityBridgeCacheTtlMinutes": "Keş TTL (dəqiqə)",
|
||||
"modalityBridgeCacheMaxEntries": "Keş maksimum girişlər",
|
||||
"modalityBridgeStatsBridged": "köprülenmiş",
|
||||
"modalityBridgeStatsCacheHits": "keş vurğuları",
|
||||
"modalityBridgeStatsFailures": "xətalar",
|
||||
"modalityBridgeStatsLastUsed": "sonuncu istifadə olunan",
|
||||
"modalityBridgeStatsNever": "heç vaxt",
|
||||
"modalityBridgeTestButton": "Nümunə şəkil ilə test edin",
|
||||
"modalityBridgeTestRunning": "Test edilir…",
|
||||
"modalityBridgeTestOk": "Körpü OK — {count} şəkil(ler) {model} tərəfindən təsvir edilib",
|
||||
"modalityBridgeTestReroute": "Körpü sorğunu {model} yönləndirdi",
|
||||
"modalityBridgeTestNoop": "Körpü aktivləşmədi (model yerli olaraq görünüşü dəstəkləyə bilər və ya körpü deaktivdir)",
|
||||
"modalityBridgeTestError": "Test uğursuz oldu: {message}",
|
||||
"modalityBridgeAudioTestButton": "Nümunə audio ilə test edin",
|
||||
"modalityBridgeAudioTestRunning": "Səsi test edir…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} klip(ler) {model} tərəfindən transkribasiya edildi",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge aktivləşmədi (hədəf audio dəstəkləyə bilər, STT provayderi qoşulmayıb, ya da körpü deaktivdir)",
|
||||
"modalityBridgeAudioTestError": "Səs testi uğursuz oldu: {message}",
|
||||
"modalityBridgeAudioComingSoon": "Audio körpüsü (söz → mətn /v1/audio/transcriptions vasitəsilə) növbəti buraxılışda təqdim ediləcək. Onun parametrləri üçün açarlar artıq ayrılıb.",
|
||||
"modalityBridgeVideoComingSoon": "Video bridging (frame sampling + captioning) arxa planda var — məsələyə baxın #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge köçürüldü",
|
||||
"modalityBridgeMovedBody": "Vision Bridge parametrləri indi xüsusi Modality Bridge səhifəsində mövcuddur.",
|
||||
"modalityBridgeMovedCta": "Modallıq Körpüsü parametrlərini açın",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Usage",
|
||||
"grokExtraUsageCredits": "Əlavə İstifadə Krediti",
|
||||
"grokAutoTopUp": "Avtomatik Yükləmə",
|
||||
"grokAutoTopUpUnavailable": "Mövcud Değil",
|
||||
"grokAutoTopUpEnabled": "Aktivdir",
|
||||
"grokAutoTopUpDisabled": "Deaktiv edilmiş",
|
||||
"grokAutoTopUpAt": "at",
|
||||
"grokAutoTopUpAdd": "əlavə et",
|
||||
"grokAutoTopUpMax": "maksimum",
|
||||
"grokAutoTopUpMonth": "ay",
|
||||
"grokAdditionalCredits": "Əlavə Kreditlər",
|
||||
"loggerTab": "Logger",
|
||||
"proxyTab": "Proxy",
|
||||
"budgetManagement": "Budget Management",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "Verification URL",
|
||||
"deviceCodeYourCode": "Your code",
|
||||
"deviceCodeWaiting": "Waiting for authorization...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "Google girişini bu ünvandan tamamlamaq mümkün deyil",
|
||||
"googleLoopbackWhatHappens": "Google yalnızca avtorizasiya kodunu <code>{redirectUri}</code> brauzerdən daxil olan giriş təsdiq edildikdə buraxır. Burada bu ünvan bu kompüterə, OmniRoute serverinə deyil — buna görə də razılıq ekranı yönləndirilmir və kopyalanacaq geri çağırma URL-si yoxdur.",
|
||||
"googleLoopbackRecommended": "Tövsiyə olunur — bunu öz kompüterinizdə işlədin, sonra nəticəni aşağıda yapışdırın:",
|
||||
"googleLoopbackHelperNote": "Bu, Google razılığını yerli olaraq açır (127.0.0.1-in işlədiyi yerdə) və bir sətirlik omniroute-cred-v1.… blob-u çap edir. O blob-u aşağıdakı Addım 2 sahəsinə yapışdırın — bu, həm bir etimadnamə blob-u, həm də bir geri çağırma URL-sini qəbul edir.",
|
||||
"googleLoopbackTunnelLabel": "Yoxsa SSH vasitəsilə dashboard portunu irəlilədin və OmniRoute-u tuneldən yenidən yükləyin:",
|
||||
"googleLoopbackTunnelNote": "{userPlaceholder} yerinə SSH istifadəçi adınızı daxil edin, terminalı açıq saxlayın, sonra {localUrl} ünvanını açın və oradan yenidən qoşulun.",
|
||||
"googleLoopbackHeadlessAlt": "Tamamilə başsız istifadə üçün heç bir yerli geri çağırma olmadan, <a>öz Google OAuth kredensiallarınızı konfiqurasiya edin</a> və əlavə olaraq bir ictimai əsas URL.",
|
||||
"remoteAccessInfo": "Remote access: Since you're accessing OmniRoute remotely, after authorization you'll see an error page (localhost not found). This is normal — just copy the full URL from your browser address bar and paste it below.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "Bu ünvandan daxil olma tamamlanmır.",
|
||||
"loopbackMismatchWhatHappened": "Nə baş verir",
|
||||
"loopbackMismatchExplanation": "Girişinizi təsdiqlədikdən sonra, {providerName} həmişə brauzeri <code>{redirectUri}</code> ünvanına geri göndərir. Bu ünvan bu brauzeri işlədən kompüterə işarə edir, OmniRoute serverinə deyil — buna görə də, avtorizasiya kodu OmniRoute-a çatmır və təminatçı səhv mesajı göstərmədən daxil olmağı uğursuz edir.",
|
||||
"loopbackMismatchHowToFix": "Bunu necə düzəltmək olar",
|
||||
"loopbackMismatchStep1": "Bu kompüterdə terminalı açın və OmniRoute serverinə SSH tunelini başlayın:",
|
||||
"loopbackMismatchStep1Note": "{userPlaceholder} yerinə SSH istifadəçi adınızı daxil edin. Bağlantı aktiv olaraq göstərilənə qədər bu terminalı açıq saxlayın — hər iki port lazımdır: biri paneli təqdim edir, digəri isə geri çağırmanı alır.",
|
||||
"loopbackMismatchStep2": "Bu brauzerdə OmniRoute-u tunel vasitəsilə yenidən açın:",
|
||||
"loopbackMismatchStep3": "Sonra yeni tabdan {providerName} ilə yenidən qoşulun. İndi geri çağırma serverə çatır və giriş normal şəkildə tamamlanır.",
|
||||
"loopbackMismatchAlternative": "SSH girişi yoxdur? Əgər bu təminatçı token idxal tabı təklif edirsə, token ilə qoşulun — bu yol döngə geri çağırma istifadə etmir.",
|
||||
"step1OpenUrl": "Step 1: Open this URL in your browser",
|
||||
"copy": "Copy",
|
||||
"step2PasteCallback": "Step 2: Paste callback URL or authorization code here",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "Mənbə modeli (agent yerli)",
|
||||
"targetModel": "Hədəf modeli (OmniRoute)",
|
||||
"noMappings": "Heç bir model uyğunlaşdırması konfiqurasiya edilməyib. Modelləri avtomatik aşkar etmək üçün quraşdırma sehrbazını işə salın.",
|
||||
"noMappingsDesc": "Hələ heç bir model xəritəsi konfiqurasiya edilməyib. Agent tələblərini OmniRoute vasitəsilə yönləndirmək üçün xəritələr əlavə edin.",
|
||||
"addMapping": "Xəritə əlavə et",
|
||||
"selectModel": "Seçin…",
|
||||
"saveMappings": "Uyğunlaşdırmaları saxla",
|
||||
"setupWizard": "Quraşdırma sehrbazı",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "Yadda saxlanılmış proksi tapılmadı. Əvvəlcə Parametrlər → Proksi bölməsində proksilər əlavə edin.",
|
||||
"updateProviderFailed": "Provayder yenilənə bilmədi",
|
||||
"providerEnabled": "{provider} aktivləşdirildi",
|
||||
"providerDisabled": "{provider} deaktiv edildi"
|
||||
"providerDisabled": "{provider} deaktiv edildi",
|
||||
"providerAdded": "{provider} əlavə edildi",
|
||||
"add": "Əlavə et",
|
||||
"manualApiKey": "İnterfeys açarını əl ilə istifadə et"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "Təhlükə",
|
||||
"requiresRestart": "Yenidən başlatma tələb olunur",
|
||||
"source": "Mənbə",
|
||||
"ccDiscoveryAliasesEnvWarning": "Mühit dəyişəni (EXPOSE_CC_DISCOVERY_ALIASES) vasitəsilə aktivdir — bu, aşağıdakı hər hansı bir panel açarını üstələyir.",
|
||||
"resetFlag": "{label} dəyərini ilkin vəziyyətinə sıfırla",
|
||||
"reset": "Sıfırla",
|
||||
"loadFailed": "Funksiya bayraqlarını yükləmək mümkün olmadı",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "Bacarıqlar sandbox-unda şəbəkəyə girişi aktivləşdirin."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "Kombo İdarəetmə Mərkəzi",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "Tərəfdaş linki",
|
||||
"dismissAriaLabel": "Bağla"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "<gateway-alias>/<model> güzgü id-lərini /v1/models-da reklam edin, əgər modelin kanonik sahibi aktiv etibarnaməyə malik deyilsə, lakin bir keçid qapısı aktiv etibarnamə ilə onları yönləndirirsə. Xəbərdarlıq: qlobal olaraq aktiv edildikdə bütün müştərilər üçün kataloq qeydləri əlavə edir.",
|
||||
"radarPage": {
|
||||
"title": "Radar Kataloqu",
|
||||
"subtitle": "İcma intellekti ilə zənginləşdirilmiş pulsuz model kataloqu",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "Деактивирано",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Маршрутизиране на заявки с изчерпан бюджет към аварийния безплатен резервен доставчик/модел.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Активиране на периодична синхронизация на ELO от класацията на Arena AI за класиране на интелигентността на моделите.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "Рекламирайте claude/<provider>/<model> mirror идентификатори на /v1/models, така че списъкът за откриване на модели на Claude Code gateway да включва неклаудови модели. Внимание: удвоява записите в каталога за всички клиенти, когато е активирано глобално.",
|
||||
"sidebar": {
|
||||
"home": "Начало",
|
||||
"dashboard": "Табло",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "Combo Studio",
|
||||
"combosLiveSubtitle": "Каскада на маршрутизиране в реално време",
|
||||
"compressionStudio": "Compression Studio",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "Изключения",
|
||||
"contextSettingsSubtitle": "Глобални настройки по подразбиране",
|
||||
"contextHeadroomSubtitle": "Таблично уплътняване",
|
||||
"contextSessionDedupSubtitle": "Дедупликация между ходовете",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "Евристично подрязване",
|
||||
"contextOmniglyphSubtitle": "Контекст като изображения",
|
||||
"compressionStudioSubtitle": "Каскада на двигатели в реално време",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "Заобикаляне по модел/крайна точка",
|
||||
"chaosConfigSubtitle": "Паралелно изпълнение на множество модели",
|
||||
"routingSection": "Routing",
|
||||
"protocolsSection": "Protocols",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "Месечни лимити за безплатни токени",
|
||||
"freeProviderRankings": "Класация на безплатните доставчици",
|
||||
"freeProviderRankingsSubtitle": "Най-добрите безплатни доставчици, класирани по ELO резултати на моделите",
|
||||
"radar": "Каталог на радара",
|
||||
"radarSubtitle": "Безплатен модел каталог, обогатен от общността",
|
||||
"costsQuotaShare": "Quota Sharing",
|
||||
"costsPricing": "Pricing",
|
||||
"logsProxy": "Proxy Logs",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAi": "AI Settings",
|
||||
"settingsModalityBridge": "Модалностен мост",
|
||||
"settingsSecurity": "Security",
|
||||
"settingsAccessTokens": "Токени за достъп",
|
||||
"settingsFeatureFlags": "Флагове за функции",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "Кеш",
|
||||
"settingsAuthz": "Авторизация",
|
||||
"settingsRouting": "Routing",
|
||||
"settingsResilience": "Resilience",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "Runtime",
|
||||
"consoleLogs": "Console Logs",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "Визуален времеви график на заявките",
|
||||
"globalRouting": "Global Routing",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "App basics",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "Fallback за текст само модели за изображение/аудио → текст",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
"settingsSecuritySubtitle": "Auth and encryption",
|
||||
"settingsAccessTokensSubtitle": "Ограничени CLI токени за отдалечен режим",
|
||||
"settingsFeatureFlagsSubtitle": "Превключване на системните възможности",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "Каталог на модели и кеширане на отговори",
|
||||
"settingsSidebar": "Sidebar",
|
||||
"settingsSidebarSubtitle": "Customize sidebar layout",
|
||||
"settingsAuthzSubtitle": "Инвентар на маршрутите и политика за заобикаляне",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "Винаги видим",
|
||||
"groupSeparatorLabel": "Разделител",
|
||||
"discovery": "Откриване",
|
||||
"discoverySubtitle": "Сканиране на доставчици за безплатен достъп",
|
||||
"radar": "Каталог на радара",
|
||||
"radarSubtitle": "Безплатен модел каталог, обогатен от общността"
|
||||
"discoverySubtitle": "Сканиране на доставчици за безплатен достъп"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Уеб кукички",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "Storage, database, and general instance configuration",
|
||||
"settingsAppearanceDescription": "Theme, branding, and visual customization",
|
||||
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "TTL за кеш записи на моделния каталог",
|
||||
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
|
||||
"featureFlags": "Флагове за функции",
|
||||
"featureFlagsDescription": "Възможности на системата за управление и експериментални функции",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "Генериране на изображения",
|
||||
"imageToText": "Изображение към текст",
|
||||
"imageToTextComingSoon": "Интегрираната среда за тестване на Изображение към текст ще бъде налична, когато бъде внедрен <code>/api/v1/images/understanding</code>.",
|
||||
"imageToTextBridgeCta": "Конфигурирайте моста Image→Text в настройките на Modality Bridge",
|
||||
"sttBridgeCta": "Конфигурирайте моста Speech→Text в настройките на Modality Bridge",
|
||||
"disabled": "Деактивиран",
|
||||
"videoGeneration": "Генериране на видео",
|
||||
"musicGeneration": "Генериране на музика",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "Автоматично",
|
||||
"always": "Винаги"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Как да активирате откритие в Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "Рекламирайте модели, различни от Claude, под claude/<provider>/<model> mirror ids, за да може откритията на модели в Claude Code да ги изброява. Удвоява каталоговите записи за всички клиенти, когато е активирано глобално.",
|
||||
"ccDiscoveryInfoLink": "Отворете Флаговете на Функции",
|
||||
"ccOnboardingTitle": "settings.json за откриване на модел на шлюз",
|
||||
"ccOnboardingCopy": "Копирай",
|
||||
"ccOnboardingCopied": "Копирано",
|
||||
"ccOnboardingKeyPlaceholder": "<вашият ключ за OmniRoute API>",
|
||||
"ccOnboardingWindowNote": "Claude Code предполага, че контекстният прозорец е 200K за всяко идентификатор на модел, който не разпознава. За модел с различен реален прозорец добавете CLAUDE_CODE_AUTO_COMPACT_WINDOW точно под него, за да не се задейства автоматичното компресиране твърде рано.",
|
||||
"failedSave": "Неуспешно запазване",
|
||||
"profileSyncTitle": "Автоматично синхронизиране на CLI профили",
|
||||
"profileSyncDescription": "След като моделите на доставчиците бъдат синхронизирани, автоматично регенерирайте профилите на CLI инструментите от каталога на живо. Изключено по подразбиране — записват се само профилни файлове; активната/подразбиращата се конфигурация никога не се променя.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "Логическо мислене за умения и използване на инструменти",
|
||||
"hermesRoleApproval": "Одобрение",
|
||||
"hermesRoleApprovalDesc": "Решения за безопасност и одобрение",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "MCP сървърен инструмент за повиквания",
|
||||
"hermesRoleTitleGeneration": "Генериране на заглавие",
|
||||
"hermesRoleTitleGenerationDesc": "Генериране на заглавие на сесия",
|
||||
"hermesRoleMemoryQueryRewrite": "Пренаписване на запитване за памет",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "Пренаписване на запитване за търсене в паметта",
|
||||
"hermesRoleTtsAudioTags": "TTS Аудио Тагове",
|
||||
"hermesRoleTtsAudioTagsDesc": "Генериране на TTS аудио тагове",
|
||||
"hermesRoleTriageSpecifier": "Спецификатор на триажа",
|
||||
"hermesRoleTriageSpecifierDesc": "Спецификация за триаж на проблеми и PR",
|
||||
"hermesRoleKanbanDecomposer": "Канбан Декомпозитор",
|
||||
"hermesRoleKanbanDecomposerDesc": "Декомпозиция на Kanban задачи",
|
||||
"hermesRoleProfileDescriber": "Описание на профила",
|
||||
"hermesRoleProfileDescriberDesc": "Описание на потребителския профил",
|
||||
"hermesRoleGoalJudge": "Цел Съдия",
|
||||
"hermesRoleGoalJudgeDesc": "Оценка на завършването на целта",
|
||||
"hermesRoleCurator": "Куратор",
|
||||
"hermesRoleCuratorDesc": "Кураторство на умения и памет",
|
||||
"hermesRoleMonitor": "Монитор",
|
||||
"hermesRoleMonitorDesc": "Фоново наблюдение",
|
||||
"hermesRoleBackgroundReview": "Преглед на фона",
|
||||
"hermesRoleBackgroundReviewDesc": "Преглед на кода на заден план",
|
||||
"hermesSelectBeforePreview": "Изберете модели за ролите или се уверете, че ролите са заредени, преди да прегледате.",
|
||||
"hermesPreviewFailed": "Неуспешно генериране на предварителен преглед",
|
||||
"hermesSavedTo": "Запазено в {path}",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "Поставете в:",
|
||||
"copilotReloadInstruction": "След това рестартирайте VS Code и задайте API ключа в полето за въвеждане.",
|
||||
"wireApiChatCompletions": "Завършвания на чат (/chat/completions)",
|
||||
"wireApiResponses": "API за отговори (/отговори)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "API за отговори (/отговори)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Комбота",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"sectionTitle": "Интеграционна повърхност",
|
||||
"sectionDescription": "Съвместими с OpenAI API и крайни точки на оперативния протокол",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "API-та",
|
||||
"tabProtocols": "Протоколи",
|
||||
"tabsAria": "Раздели на крайните точки",
|
||||
"protocolsTitle": "Протоколи",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "Не са добавени нови модели (всички вече съществуват).",
|
||||
"importDoneCount": "✓ Готово! {count, plural, one {# model imported.} other {# models imported.}}",
|
||||
"unexpectedErrorOccurred": "Възникна неочаквана грешка",
|
||||
"getApiKey": "Вземете API ключа",
|
||||
"getApiKeyDescription": "Регистрирайте се или се запишете за API ключ",
|
||||
"connectionCountLabel": "{count, plural, one {# connection} other {# connections}}",
|
||||
"messagesPath": "съобщения",
|
||||
"responsesPath": "отговори",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "Пренаписване на повикванията на вградения инструмент web_fetch към /v1/web/fetch на OmniRoute.",
|
||||
"interceptionLoadError": "Неуспешно зареждане на настройките за прихващане: {error}",
|
||||
"interceptionSaveError": "Неуспешно запазване на настройките за прихващане: {error}",
|
||||
"ccAliasSectionTitle": "Изложи в Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "Рекламирайте моделите на този доставчик под claude/<provider>/<model> mirror ids, за да може откритията на модели на Claude Code да ги изброява. По подразбиране е изключено — активирането на това удвоява записите в каталога за всички клиенти.",
|
||||
"ccAliasProviderLevelLabel": "Дефолтен доставчик",
|
||||
"ccAliasModelOverridesLabel": "Пре Overrides За Всеки Модел",
|
||||
"ccAliasModelOverrideAriaLabel": "Презапис за {modelId}",
|
||||
"ccAliasStateInherit": "Наследи",
|
||||
"ccAliasStateOn": "Включено",
|
||||
"ccAliasStateOff": "Изключено",
|
||||
"ccAliasAddModelPlaceholder": "Идентификатор на модела (напр. gpt-4o)",
|
||||
"ccAliasAddModelButton": "Добави замяна",
|
||||
"ccAliasLoadError": "Неуспешно зареждане на настройки за discovery-alias: {error}",
|
||||
"ccAliasSaveError": "Неуспешно запазване на настройката discovery-alias: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
@@ -5413,8 +5453,8 @@
|
||||
"t3ChatWebCookieHint": "Отворете t3.chat → DevTools → Приложение → Локално хранилище → https://t3.chat, копирайте 'convex-session-id'. След това отворете DevTools → Network, копирайте пълния хедър на Cookie от всяка заявка за чат. Поставете и двете стойности в полетата по-долу.",
|
||||
"t3ChatWebCookiePlaceholder": "convex-session-id=abc123...",
|
||||
"grokWebCookieHint": "Grok Web Cookie Hint",
|
||||
"blockClaudeExtraUsageDescription": "__MISSING__:When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.",
|
||||
"blockClaudeExtraUsageLabel": "__MISSING__:Block extra Claude usage",
|
||||
"blockClaudeExtraUsageDescription": "Когато е активирано, OmniRoute маркира това Claude свързване като недостъпно веднага щом API-то за използване докладва опашка от допълнително използване, така че резервното копие да премине към друга връзка вместо да продължи с допълнителното таксуване на база \"плащай, колкото ползваш\".",
|
||||
"blockClaudeExtraUsageLabel": "Блокирайте допълнителната употреба на Claude",
|
||||
"disableCoolingDescription": "Пропускане на временния период на изчакване, така че тази връзка да остане допустима дори след възстановими грешки (терминалните състояния като блокиран/изтекъл все още се прилагат).",
|
||||
"disableCoolingLabel": "Деактивиране на периода на изчакване за тази връзка",
|
||||
"bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "New-API User ID",
|
||||
"newApiUserIdPlaceholder": "e.g. 12345",
|
||||
"newApiUserIdHint": "AgentRouter New-Api-User header value, used together with the console API key to fetch quota balance.",
|
||||
"newApiAggregatorToggleLabel": "Агрегаторен шлюз",
|
||||
"newApiAggregatorToggleHint": "Активирайте откритие на баланс за New-API / One-API / Sub2API агрегаторски възли. Таблото за управление ще показва значка за баланс, а маршрутизацията за предварителна проверка на квотата ще пропуска изчерпаните акаунти.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "Токен за достъп до системата за крайна точка /api/user/self на агрегатора. Не е ключът за маршрутизиране на API.",
|
||||
"newApiAggregatorUserIdHint": "New-Api-User заглавна стойност, използвана за извличане на баланса на квотата на агрегаторския потребител.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "Квота на единица",
|
||||
"newApiAggregatorQuotaPerUnitHint": "Нов-API кредитни единици на $1 (по подразбиране: 500000). Презапишете, ако вашият агрегатор използва различна ставка.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "Активирайте откритие на баланс за съвместими възли с New-API / One-API / Sub2API агрегатор",
|
||||
"cpaModeDisabledTitle": "Cpa Mode Disabled Title",
|
||||
"cpaModeEnabledTitle": "Cpa Mode Enabled Title",
|
||||
"customUserAgentHint": "Custom User Agent Hint",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Tag Group Placeholder",
|
||||
"testModel": "Test Model",
|
||||
"testingModel": "Testing Model",
|
||||
"modelTestQuotaTooltip": "Изчерпан лимит — нулира се утре или е необходимо допълване",
|
||||
"toggleOffShort": "Toggle Off Short",
|
||||
"toggleOnShort": "Toggle On Short",
|
||||
"tokenExpiredBadge": "Token Expired Badge",
|
||||
@@ -5593,13 +5641,13 @@
|
||||
"importGrokAuth": "Импортиране на Grok Build оторизация",
|
||||
"zedImportTitle": "Импортиране от Zed Keychain",
|
||||
"zedImportDescription": "Открийте идентификационни данни за AI доставчици (OpenAI, Anthropic, Google, Mistral, xAI), съхранени от Zed IDE в ключодържателя на ОС, и ги импортирайте като връзки. Zed IDE трябва да бъде инсталиран на тази машина.",
|
||||
"zedImportButton": "__MISSING__:Import from Zed",
|
||||
"zedImportFailed": "__MISSING__:Zed import failed",
|
||||
"zedImportButton": "Импорт от Zed",
|
||||
"zedImportFailed": "Неуспешен импорт на Zed",
|
||||
"zedImportHint": "Zed Import Hint",
|
||||
"zedImportNetworkError": "Zed Import Network Error",
|
||||
"zedImportNone": "Zed Import None",
|
||||
"zedImportSuccess": "__MISSING__:Imported {credentials} credential(s) from Zed for {providers} provider(s)",
|
||||
"zedImporting": "__MISSING__:Importing…",
|
||||
"zedImportSuccess": "Импортирaни {credentials} удостоверение(я) от Zed за {providers} доставчик(и)",
|
||||
"zedImporting": "Импортиране…",
|
||||
"zedNoCredentials": "В ключодържателя не са намерени идентификационни данни за Zed",
|
||||
"zedUnsupportedCredentials": "Намерени са {count} идентификационни данни в ключодържателя, но нито едни не съвпадат с поддържаните доставчици",
|
||||
"zedManualTitle": "Ръчно импортиране на токен",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "Вземете API ключ на ai.360.cn",
|
||||
"agentrouter": "Вземете $200 безплатни кредити на https://agentrouter.org/register — не се изисква кредитна карта.",
|
||||
"unorouter": "Създайте API ключ на https://unorouter.ai, след което го поставете тук като Bearer токен.",
|
||||
"agnes": "Вземете API ключ на agnes-ai.com",
|
||||
"aimlapi": "Безплатният план е спрян (2026) — AI/ML API вече е само с разплащане според потреблението (мин. $20 презареждане); без периодични безплатни кредити.",
|
||||
"ai21": "$10 пробни кредити при регистрация (валидни 3 месеца), не се изисква кредитна карта",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "Свържете Alibaba Coding Plan с API ключ.",
|
||||
"bedrock": "Вградена интеграция с Bedrock: откриването на модели използва базовите модели и inference profiles на Bedrock, докато чатът използва регионалните API-та Bedrock Runtime Converse/ConverseStream.",
|
||||
"anthropic": "Свържете Anthropic с API ключ.",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "Регистрирайте се и създайте API ключ в конзолата на Ant Ling API (https://chat.ant-ling.com/open), след което го поставете тук. OmniRoute маршрутизира чат трафика към https://api.ant-ling.com/v1/chat/completions; доставчикът е съвместим с OpenAI и също така предлага интерфейс, съвместим с Anthropic.",
|
||||
"api-airforce": "Вземете своя API ключ от https://panel.api.airforce — съвместима с OpenAI крайна точка на https://api.airforce/v1",
|
||||
"arcee-ai": "Вземете API ключ на arcee.ai",
|
||||
"azure-ai": "Foundry използва интерфейса на OpenAI v1 с имена на внедряванията като модели. OmniRoute нормализира базовите URL адреси на ресурсите към крайните точки v1 chat и /models.",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "Bearer API ключ за съвместимия с OpenAI шлюз на Chutes.",
|
||||
"clarifai": "Clarifai предоставя съвместими с OpenAI чат, отговори и /models на /v2/ext/openai/v1. Публичните/общностните модели обикновено изискват PAT; ключовете с обхват на приложението работят само за ресурси в рамките на това приложение.",
|
||||
"cloudflare-ai": "Изисква API токен И Account ID (намира се на dash.cloudflare.com)",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) е съвместим с OpenAI на /v1/openai. OmniRoute проверява /v1/openai/models и маршрутизира чат трафика към /v1/openai/chat/completions. Използва текущия хост clovastudio.stream.ntruss.com — наследеният крайна точка clovastudio.apigw.ntruss.com ще бъде деактивиран.",
|
||||
"codestral": "Свържете Codestral с API ключ.",
|
||||
"cohere": "Безплатен пробен период: 1,000 API извиквания/месец за тестване, не се изисква кредитна карта",
|
||||
"command-code": "Създайте или копирайте API ключ от Command Code, след което го поставете тук като Bearer токен.",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "watsonx model gateway предоставя съвместими с OpenAI /chat/completions и /models под /ml/gateway/v1.",
|
||||
"ideogram": "Вземете API ключ на ideogram.ai/docs/api",
|
||||
"iflytek": "Вземете API ключ на console.xfyun.cn",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs е съвместим с OpenAI на https://api.inceptionlabs.ai/v1. mercury-2 е първият дифузионен LLM (dLLM) в каталога — 5-10x по-бързо генериране от сравними авторегресивни модели, с извикване на инструменти, json_mode и структурирани изходи.",
|
||||
"inference-net": "$25 безплатни кредити при регистрация плюс налични изследователски грантове",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "Безплатна месечна квота ~1M входящи / 3M изходящи токена (~10 RPM)",
|
||||
"jina-ai": "Bearer API ключ за Jina AI rerank API.",
|
||||
"jina-reader": "Свържете Jina Reader с API ключ.",
|
||||
"kenari": "Kenari предоставя съвместима с OpenAI крайна точка за chat completions на https://kenari.id/v1/chat/completions, плюс каталог /v1/models в реално време, покриващ Claude, GPT, DeepSeek, GLM, Kimi и други. OmniRoute използва протокола на OpenAI и изброява моделите чрез passthrough.",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "Свържете Perplexity с API ключ.",
|
||||
"piapi": "Свържете PiAPI с API ключ.",
|
||||
"pioneer": "$75 безплатни кредити за използване — не се изисква кредитна карта",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo е съвместим с OpenAI на https://api.platform.preferredai.jp/v1. Създаден от Preferred Networks и оптимизиран за японски. Документацията е предимно на японски.",
|
||||
"poe": "Poe предоставя съвместими с OpenAI чат и отговори на https://api.poe.com/v1, с автентикирани проверки на баланса на /usage/current_balance.",
|
||||
"pollinations": "Безплатно ниво без ключ: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning. Премиум моделите (claude, gemini, midijourney) изискват API ключ за Pollinations от enter.pollinations.ai.",
|
||||
"publicai": "Изисква API ключ — еднократен кредит при регистрация, след което е платено",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "Генерирането на видео в Runway е базирано на задачи. OmniRoute изпраща задачи за text-to-video или image-to-video, проверява периодично /v1/tasks/[id] и нормализира готовите видео резултати обратно в наподобяващ OpenAI отговор на /v1/videos/generations.",
|
||||
"sambanova": "$5 безплатни кредити при регистрация (валидност 30 дни), не се изисква кредитна карта",
|
||||
"sap": "Откриването на модели използва /v2/lm/scenarios/foundation-models/models на AI_API_URL. Заявките за чат използват deploymentUrl/chat/completions и изискват AI-Resource-Group.",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "Sarvam AI е съвместим с OpenAI на /v1. OmniRoute проучва /v1/models и маршрутизира чат трафика към /v1/chat/completions. Моделите са настроени за индийски езици.",
|
||||
"scaleway": "1M безплатни токена за нови акаунти — съвместимо с EU/GDPR (Париж), Qwen3 235B и Llama 70B",
|
||||
"sensenova": "Вземете API ключ на platform.sensenova.cn",
|
||||
"siliconflow": "$1 безплатни кредити плюс постоянно безплатни модели след потвърждаване на самоличността",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "Свържете Together AI с API ключ.",
|
||||
"tokenrouter": "TokenRouter предоставя съвместима с OpenAI крайна точка за chat completions на https://api.tokenrouter.com/v1/chat/completions, плюс работещ каталог за /v1/models. OmniRoute използва протокола на OpenAI.",
|
||||
"topaz": "Свържете Topaz с API ключ.",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "Тайфун е съвместим с OpenAI на /v1. Създаден от SCB 10X (Тайланд); typhoon-v2.5-30b-a3b-instruct е модел с приоритет на тайския език и многоезичен.",
|
||||
"udio": "Поставете сесийната бисквитка от udio.com (Supabase auth)",
|
||||
"uncloseai": "Не се изисква удостоверяване. API приема всеки непразен низ като ключ за идентификация.",
|
||||
"upstage": "Свържете Upstage с API ключ.",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "Bearer API ключ за Voyage AI embeddings и rerank API-та.",
|
||||
"wafer": "API ключ от https://wafer.ai",
|
||||
"wandb": "Свържете Weights & Biases Inference с API ключ.",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra е съвместим с OpenAI на https://api.writer.com/v1. palmyra-x5 предлага контекстен прозорец от 1M токена.",
|
||||
"x5lab": "X5Lab предоставя съвместима с OpenAI крайна точка за chat completions на https://api.x5lab.dev/v1/chat/completions, плюс каталог на живо /v1/models. OmniRoute използва протокола на OpenAI и изброява моделите чрез passthrough.",
|
||||
"xai": "Свържете xAI (Grok) с API ключ.",
|
||||
"xiaomi-mimo": "Свържете Xiaomi MiMo с API ключ.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "Чат с ИИ на ByteDance чрез dola.com",
|
||||
"overrideBaseUrlAdvanced": "Разширени: презаписване на базовия URL адрес",
|
||||
"overrideBaseUrlHint": "Разширени: насочване на този вграден доставчик към персонализирана крайна точка. Оставете празно за използване на стойността по подразбиране.",
|
||||
"apiProtocolLabel": "API протокол",
|
||||
"apiProtocolDefault": "Съвместим с OpenAI (по подразбиране)",
|
||||
"apiProtocolHint": "Някои доставчици публикуват същите модели по повече от един протокол. Оставете по подразбиране, освен ако не се нуждаете от алтернативата.",
|
||||
"bulkAddFormatHintCloudflare": "По един ключ на ред. Формат: name|accountId|apiKey (Cloudflare ID на акаунт + API токен).",
|
||||
"lmarenaWebCookieHint": "Отворете arena.ai, влезте в профила си, след което копирайте пълната заглавка Cookie от мрежова заявка (Network request). Включете arena-auth-prod-v1.0 и arena-auth-prod-v1.1 (и следващите части, ако има такива), за предпочитане с cf_clearance. Не поставяйте само празната бисквитка arena-auth-prod-v1. По избор: providerSpecificData.recaptchaV3Token, ако create-evaluation все още връща 403.",
|
||||
"kimiOfficialSupporterBadge": "Учредяващ приятел",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) е основаващият приятел на отворения код на OmniRoute",
|
||||
"cheaperInferenceSupporterBadge": "Приятел на отворения код",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference подкрепя OmniRoute като приятел на отворения код",
|
||||
"kimiPartnerLinkNote": "Партньорска връзка — поддържа OmniRoute без допълнителни разходи за вас",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "Партньорска връзка — поддържа OmniRoute без допълнителни разходи за вас"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "Saving...",
|
||||
"requestBodyLimitSave": "Save",
|
||||
"requestBodyLimitCurrent": "Current: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "Неуспешно зареждане на настройки за кеша",
|
||||
"cacheConfigSaveSuccess": "Настройки на кеша запазени",
|
||||
"cacheConfigSaveFailed": "Неуспешно запазване на настройките за кеша",
|
||||
"modelCatalogTtlWholeNumberError": "Използвайте цяло число",
|
||||
"modelCatalogTtlMinimumError": "Минимумът е {min} ms",
|
||||
"modelCatalogTtlMaximumError": "Максимумът е {max} ms",
|
||||
"modelCatalogCacheTtl": "TTL на кеша на каталога на моделите",
|
||||
"modelCatalogCacheTtlDescription": "Колко дълго отговорите на каталога на моделите се кешират преди обновяване",
|
||||
"modelCatalogCacheTtlLabel": "TTL на кеша на каталога на модела в милисекунди",
|
||||
"modelCatalogCacheTtlSaving": "Записване...",
|
||||
"modelCatalogCacheTtlSave": "Запази",
|
||||
"modelCatalogCacheTtlCurrent": "Текущ: {value} ms",
|
||||
"mitmProxy": "MITM прокси",
|
||||
"pricing": "Ценообразуване",
|
||||
"storage": "Съхранение",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "Как работи ценообразуването",
|
||||
"cacheWrite": "Кеш запис",
|
||||
"unsaved": "незаписан",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "Нулиране на настройки по подразбиране",
|
||||
"saveProvider": "Запазване на доставчика",
|
||||
"model": "Модел",
|
||||
"models": "модели",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "Никога",
|
||||
"compressionLiveZoneTitle": "Кеш-подравнена активна зона",
|
||||
"compressionLiveZoneDesc": "Запазване на компресирания префикс на разговора стабилен и обработване само на новодобавените елементи.",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "Изключения за компресия",
|
||||
"compressionExclusionsDesc": "Идентификатите на моделите или шаблоните на доставчика/модела, които никога не трябва да се компресират. `*` е единственият символ за подмяна (например `openai/*`, `*embedding*`). Съответстваща заявка преминава без промяна на байтовете — не се изпълнява компресионен двигател.",
|
||||
"compressionExclusionsPlaceholder": "Един шаблон на ред, напр. \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "Запази",
|
||||
"compressionExclusionsSaved": "Запазено",
|
||||
"compressionExclusionsCount": "Конфигурирани {count, plural, one {# изключение} other {# изключения}}",
|
||||
"compressionExclusionsEmpty": "Няма конфигурирани изключения — всеки модел/крайна точка е подходящ за компресия (по подразбиране).",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "Неговото включване/изключване и ниво се настройват в панела:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Изпраща се към модела за зрение, преди извлеченото описание да бъде инжектирано обратно в първоначалната заявка.",
|
||||
"visionBridgeTimeoutMs": "Изчакване (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "Макс. изображения на заявка",
|
||||
"modalityBridgeIntro": "Свържете мултимодално съдържание с текст, преди да достигне модели само за текст. Визията е активна; Аудиото пристига с AudioBridge; Видеото е в плана.",
|
||||
"modalityBridgeVisionTab": "Визия",
|
||||
"modalityBridgeAudioTab": "Аудио",
|
||||
"modalityBridgeVideoTab": "Видео",
|
||||
"modalityBridgeSubTabsAria": "Секции на модула за свързване",
|
||||
"modalityBridgeVisionTitle": "Визионен Мост",
|
||||
"modalityBridgeVisionDesc": "Опишете изображения с модел за визия и продължете с избрания от потребителя текстов модел.",
|
||||
"modalityBridgeAudioTitle": "Аудио Мост",
|
||||
"modalityBridgeAudioDesc": "Транскрибирайте аудио с модел за разпознаване на реч, преди да продължите с избрания текстов модел.",
|
||||
"modalityBridgeAudioEnabled": "Активирайте Audio Bridge",
|
||||
"modalityBridgeAudioEnabledDesc": "Заменете аудио частите с транскрипции, когато целевият модел не може да обработва аудио.",
|
||||
"modalityBridgeAudioModel": "Модел за преобразуване на реч в текст",
|
||||
"modalityBridgeAudioModelAuto": "Авто (първият свързан доставчик на STT)",
|
||||
"modalityBridgeAudioMaxClips": "Максимален брой аудио клипове на заявка",
|
||||
"modalityBridgeMode": "Режим",
|
||||
"modalityBridgeModeAuto": "Авто (препоръчително)",
|
||||
"modalityBridgeModeAutoHint": "Наследствена хевристика: пренасочете отделни модели без удостоверения; опишете в противен случай.",
|
||||
"modalityBridgeModeDescribe": "Винаги описвайте",
|
||||
"modalityBridgeModeDescribeHint": "Моделът, който избрахте, винаги отговаря; изображенията са заменени с текстови описания.",
|
||||
"modalityBridgeModeReroute": "Винаги пренасочвайте",
|
||||
"modalityBridgeModeRerouteHint": "Изпратете цялото запитване до най-добрия модел с възможности за визуализация (преминава към описание, когато няма наличен).",
|
||||
"modalityBridgeVisionModel": "Модел на визията",
|
||||
"modalityBridgeVisionModelAuto": "Авто (най-добро налично)",
|
||||
"modalityBridgeTaskAware": "Описание, съобразено с задачата",
|
||||
"modalityBridgeTaskAwareDesc": "Включете въпроса на потребителя като фокус, така че моделът на визията да опише какво е важно и да транскрибира видимия текст.",
|
||||
"modalityBridgePrompt": "Описание на подканата",
|
||||
"modalityBridgeAdvanced": "Разширен",
|
||||
"modalityBridgeTimeoutMs": "Таймаут (мс)",
|
||||
"modalityBridgeMaxImages": "Максимален брой изображения на заявка",
|
||||
"modalityBridgeCacheEnabled": "Кеш описания",
|
||||
"modalityBridgeCacheEnabledDesc": "Повторно използване на описания за идентични изображения (с ключ SHA-256, в паметта).",
|
||||
"modalityBridgeCacheTtlMinutes": "Кеш TTL (минути)",
|
||||
"modalityBridgeCacheMaxEntries": "Максимален брой записи в кеша",
|
||||
"modalityBridgeStatsBridged": "свързан",
|
||||
"modalityBridgeStatsCacheHits": "cache удари",
|
||||
"modalityBridgeStatsFailures": "неуспехи",
|
||||
"modalityBridgeStatsLastUsed": "последно използвано",
|
||||
"modalityBridgeStatsNever": "никога",
|
||||
"modalityBridgeTestButton": "Тест с примерна снимка",
|
||||
"modalityBridgeTestRunning": "Тестване…",
|
||||
"modalityBridgeTestOk": "Bridge OK — {count} изображение(я), описано(и) от {model}",
|
||||
"modalityBridgeTestReroute": "Мостът пренасочи заявката към {model}",
|
||||
"modalityBridgeTestNoop": "Мостът не се активира (моделът може да поддържа визия нативно или мостът е деактивиран)",
|
||||
"modalityBridgeTestError": "Тестът не успя: {message}",
|
||||
"modalityBridgeAudioTestButton": "Тест с примерен аудио файл",
|
||||
"modalityBridgeAudioTestRunning": "Тестване на аудио…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} клип(а) транскрибирани от {model}",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge не беше активиран (целта може да поддържа аудио, няма свързан STT доставчик или моста е деактивиран)",
|
||||
"modalityBridgeAudioTestError": "Тестът на звука не успя: {message}",
|
||||
"modalityBridgeAudioComingSoon": "Аудио мостът (говор → текст чрез /v1/audio/transcriptions) ще бъде включен в следващото издание. Ключовете за настройките му вече са резервирани.",
|
||||
"modalityBridgeVideoComingSoon": "Видео свързване (извадка на кадри + надписи) е в списъка със задачи — вижте проблема #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge преместен",
|
||||
"modalityBridgeMovedBody": "Настройките на Vision Bridge вече са налични на специализираната страница Modality Bridge.",
|
||||
"modalityBridgeMovedCta": "Отворете настройките на Modality Bridge",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Използване",
|
||||
"grokExtraUsageCredits": "Допълнителни кредити за употреба",
|
||||
"grokAutoTopUp": "Автоматично попълване",
|
||||
"grokAutoTopUpUnavailable": "Недостъпен",
|
||||
"grokAutoTopUpEnabled": "Активиран",
|
||||
"grokAutoTopUpDisabled": "Деактивиран",
|
||||
"grokAutoTopUpAt": "в",
|
||||
"grokAutoTopUpAdd": "добави",
|
||||
"grokAutoTopUpMax": "макс",
|
||||
"grokAutoTopUpMonth": "месец",
|
||||
"grokAdditionalCredits": "Допълнителни кредити",
|
||||
"loggerTab": "Дървосекач",
|
||||
"proxyTab": "Прокси",
|
||||
"budgetManagement": "Управление на бюджета",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "URL адрес за проверка",
|
||||
"deviceCodeYourCode": "Вашият код",
|
||||
"deviceCodeWaiting": "Чака се оторизация...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "Google входът не може да бъде завършен от този адрес",
|
||||
"googleLoopbackWhatHappens": "Google освобождава кода за авторизация само когато <code>{redirectUri}</code> е достъпен от браузъра, който одобрява влизането. Тук този адрес сочи към този компютър, а не към сървъра на OmniRoute — така че екрана за съгласие засяда вместо да пренасочва, и няма URL за обратен повик, който да копирате.",
|
||||
"googleLoopbackRecommended": "Препоръчително — стартирайте това на собствения си компютър, след което поставете резултата по-долу:",
|
||||
"googleLoopbackHelperNote": "Отваря локално Google съгласие (където 127.0.0.1 работи) и отпечатва едноредов omniroute-cred-v1.… blob. Поставете този blob в полето Стъпка 2 по-долу — то приема както blob с удостоверение, така и URL за обратен повик.",
|
||||
"googleLoopbackTunnelLabel": "Или пренасочете порта на таблото през SSH и презаредете OmniRoute през тунела:",
|
||||
"googleLoopbackTunnelNote": "Заменете {userPlaceholder} с вашето SSH потребителско име, оставете терминала отворен, след това отворете {localUrl} и се свържете отново оттам.",
|
||||
"googleLoopbackHeadlessAlt": "За напълно безглаво използване без локален обратен повикване, <a>конфигурирайте собствените си Google OAuth идентификационни данни</a> плюс публичен основен URL.",
|
||||
"remoteAccessInfo": "Отдалечен достъп: Тъй като осъществявате достъп до OmniRoute дистанционно, след упълномощаване ще видите страница за грешка (localhost не е намерен). Това е нормално — просто копирайте пълния URL адрес от адресната лента на браузъра си и го поставете по-долу.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "Входът не може да бъде завършен от този адрес",
|
||||
"loopbackMismatchWhatHappened": "Какво се случва",
|
||||
"loopbackMismatchExplanation": "След като одобрите входа, {providerName} винаги изпраща браузъра обратно на <code>{redirectUri}</code>. Този адрес сочи към компютъра, на който работи този браузър, а не към сървъра на OmniRoute — така че кодът за удостоверяване никога не достига до OmniRoute и доставчикът не успява да извърши входа, без да покаже грешка.",
|
||||
"loopbackMismatchHowToFix": "Как да го поправим",
|
||||
"loopbackMismatchStep1": "На този компютър отворете терминал и стартирайте SSH тунел към сървъра OmniRoute:",
|
||||
"loopbackMismatchStep1Note": "Заменете {userPlaceholder} с вашето SSH потребителско име. Дръжте този терминал отворен, докато връзката не се покаже като активна — нужни са и двата порта: единият обслужва таблото, а другият получава обратния повик.",
|
||||
"loopbackMismatchStep2": "В този браузър, отворете отново OmniRoute през тунела:",
|
||||
"loopbackMismatchStep3": "След това свържете {providerName} отново от новия раздел. Обратният повик сега достига до сървъра и входът завършва нормално.",
|
||||
"loopbackMismatchAlternative": "Няма SSH достъп? Ако този доставчик предлага раздел за импортиране на токени, свържете се с токен вместо това — този път не използва обратен колбек.",
|
||||
"step1OpenUrl": "Стъпка 1: Отворете този URL във вашия браузър",
|
||||
"copy": "копие",
|
||||
"step2PasteCallback": "Стъпка 2: Поставете URL адреса за обратно извикване или кода за оторизация тук",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "Изходен модел (нативен за агента)",
|
||||
"targetModel": "Целеви модел (OmniRoute)",
|
||||
"noMappings": "Няма конфигурирани съпоставяния на модели. Стартирайте съветника за настройка за автоматично откриване на модели.",
|
||||
"noMappingsDesc": "Все още няма конфигурирани модели. Добавете мапинги, за да маршрутизирате заявките на агента чрез OmniRoute.",
|
||||
"addMapping": "Добавяне на картографиране",
|
||||
"selectModel": "Изберете…",
|
||||
"saveMappings": "Запазване на съпоставянията",
|
||||
"setupWizard": "Съветник за настройка",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "Не са намерени запазени проксита. Първо добавете проксита в Настройки → Прокси.",
|
||||
"updateProviderFailed": "Неуспешно обновяване на доставчика",
|
||||
"providerEnabled": "{provider} е активиран",
|
||||
"providerDisabled": "{provider} е деактивиран"
|
||||
"providerDisabled": "{provider} е деактивиран",
|
||||
"providerAdded": "{provider} добавен",
|
||||
"add": "Добави",
|
||||
"manualApiKey": "Използвайте ръчен API ключ"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "Опасност",
|
||||
"requiresRestart": "Изисква рестартиране",
|
||||
"source": "Източник",
|
||||
"ccDiscoveryAliasesEnvWarning": "Активирано чрез променлива на средата (EXPOSE_CC_DISCOVERY_ALIASES) — това отменя всяко превключване на таблото по-долу.",
|
||||
"resetFlag": "Възстановяване на {label} по подразбиране",
|
||||
"reset": "Нулиране",
|
||||
"loadFailed": "Неуспешно зареждане на функционалните флагове",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "Активиране на мрежов достъп в пясъчника за умения."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "Комбо контролен център",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "Партньорска връзка",
|
||||
"dismissAriaLabel": "Затваряне"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "Рекламирайте <gateway-alias>/<model> mirror ids на /v1/models за модели, чийто каноничен собственик няма активна идентификация, но пасивен шлюз с активна идентификация ги маршрутизира. Внимание: добавя записи в каталога за всички клиенти, когато е активирано глобално.",
|
||||
"radarPage": {
|
||||
"title": "Каталог на радара",
|
||||
"subtitle": "Безплатен модел каталог, обогатен с информация от общността",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "নিষ্ক্রিয়",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "বাজেট শেষ হয়ে যাওয়া অনুরোধগুলো জরুরি ফ্রি ফলব্যাক প্রোভাইডার/মডেলে রুট করুন।",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "মডেল ইন্টেলিজেন্স র্যাঙ্কিংয়ের জন্য পর্যায়ক্রমিক Arena AI লিডারবোর্ড ELO সিঙ্ক সক্ষম করুন।",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "/v1/models এ claude/<provider>/<model> মিরর আইডি বিজ্ঞাপন দিন যাতে Claude Code গেটওয়ে মডেল আবিষ্কার non-Claude মডেল তালিকা করে। সতর্কতা: এটি বিশ্বব্যাপী সক্ষম হলে সমস্ত ক্লায়েন্টের জন্য ক্যাটালগ এন্ট্রি দ্বিগুণ করে।",
|
||||
"sidebar": {
|
||||
"home": "Home",
|
||||
"dashboard": "Dashboard",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "Combo Studio",
|
||||
"combosLiveSubtitle": "লাইভ রাউটিং ক্যাসকেড",
|
||||
"compressionStudio": "Compression Studio",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "অব্যাহতি",
|
||||
"contextSettingsSubtitle": "গ্লোবাল ডিফল্ট",
|
||||
"contextHeadroomSubtitle": "ট্যাবুলার কম্প্যাকশন",
|
||||
"contextSessionDedupSubtitle": "ক্রস-টার্ন ডিডুপ্লিকেশন",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "হিউরিস্টিক প্রুনিং",
|
||||
"contextOmniglyphSubtitle": "ছবি হিসেবে কনটেক্সট",
|
||||
"compressionStudioSubtitle": "লাইভ ইঞ্জিন ক্যাসকেড",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "প্রতি-মডেল/এন্ডপয়েন্ট বাইপাস",
|
||||
"chaosConfigSubtitle": "মাল্টি-মডেল প্যারালাল এক্সিকিউশন",
|
||||
"routingSection": "Routing",
|
||||
"protocolsSection": "Protocols",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "মাসিক ফ্রি-টোকেন বরাদ্দ",
|
||||
"freeProviderRankings": "ফ্রি প্রোভাইডার র্যাঙ্কিং",
|
||||
"freeProviderRankingsSubtitle": "মডেল ELO স্কোর অনুযায়ী র্যাঙ্ক করা সেরা ফ্রি প্রোভাইডারসমূহ",
|
||||
"radar": "রাডার ক্যাটালগ",
|
||||
"radarSubtitle": "কমিউনিটি-সমৃদ্ধ ফ্রি মডেল ক্যাটালগ",
|
||||
"costsQuotaShare": "Quota Sharing",
|
||||
"costsPricing": "Pricing",
|
||||
"logsProxy": "Proxy Logs",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAi": "AI Settings",
|
||||
"settingsModalityBridge": "মোডালিটি ব্রিজ",
|
||||
"settingsSecurity": "Security",
|
||||
"settingsAccessTokens": "অ্যাক্সেস টোকেন",
|
||||
"settingsFeatureFlags": "বৈশিষ্ট্য পতাকা",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "ক্যাশে",
|
||||
"settingsAuthz": "Authz",
|
||||
"settingsRouting": "Routing",
|
||||
"settingsResilience": "Resilience",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "Runtime",
|
||||
"consoleLogs": "Console Logs",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "ভিজ্যুয়াল রিকোয়েস্ট টাইমলাইন",
|
||||
"globalRouting": "Global Routing",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "App basics",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "ছবি/অডিও → টেক্সট-শুধু মডেলের জন্য টেক্সট ফ্যালব্যাক",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
"settingsSecuritySubtitle": "Auth and encryption",
|
||||
"settingsAccessTokensSubtitle": "রিমোট মোডের জন্য স্কোপড CLI টোকেন",
|
||||
"settingsFeatureFlagsSubtitle": "সিস্টেমের ক্ষমতা টগল করুন",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "মডেল ক্যাটালগ এবং প্রতিক্রিয়া ক্যাশিং",
|
||||
"settingsSidebar": "Sidebar",
|
||||
"settingsSidebarSubtitle": "Customize sidebar layout",
|
||||
"settingsAuthzSubtitle": "রুট ইনভেন্টরি এবং বাইপাস পলিসি",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "সর্বদা দৃশ্যমান",
|
||||
"groupSeparatorLabel": "বিভাজক",
|
||||
"discovery": "ডিসকভারি",
|
||||
"discoverySubtitle": "ফ্রি অ্যাক্সেসের জন্য প্রোভাইডার স্ক্যান করুন",
|
||||
"radar": "রাডার ক্যাটালগ",
|
||||
"radarSubtitle": "কমিউনিটি-সমৃদ্ধ ফ্রি মডেল ক্যাটালগ"
|
||||
"discoverySubtitle": "ফ্রি অ্যাক্সেসের জন্য প্রোভাইডার স্ক্যান করুন"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "ওয়েবহুক",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "Storage, database, and general instance configuration",
|
||||
"settingsAppearanceDescription": "Theme, branding, and visual customization",
|
||||
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "মডেল ক্যাটালগ ক্যাশ এন্ট্রির জন্য TTL",
|
||||
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
|
||||
"featureFlags": "বৈশিষ্ট্য পতাকা",
|
||||
"featureFlagsDescription": "নিয়ন্ত্রণ সিস্টেম ক্ষমতা এবং পরীক্ষামূলক বৈশিষ্ট্য",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "ইমেজ জেনারেশন",
|
||||
"imageToText": "ইমেজ টু টেক্সট",
|
||||
"imageToTextComingSoon": "<code>/api/v1/images/understanding</code> বাস্তবায়িত হলে ইনলাইন Image-to-Text প্লেগ্রাউন্ড উপলব্ধ হবে।",
|
||||
"imageToTextBridgeCta": "Modality Bridge সেটিংসে Image→Text ব্রিজ কনফিগার করুন",
|
||||
"sttBridgeCta": "Modality Bridge সেটিংসে Speech→Text ব্রিজ কনফিগার করুন",
|
||||
"disabled": "নিষ্ক্রিয়",
|
||||
"videoGeneration": "ভিডিও জেনারেশন",
|
||||
"musicGeneration": "মিউজিক জেনারেশন",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "অটো",
|
||||
"always": "সর্বদা"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Claude কোডে ডিসকভারি কীভাবে সক্ষম করবেন",
|
||||
"ccDiscoveryInfoTooltip": "Claude-এর অধীনে non-Claude মডেলগুলি বিজ্ঞাপন দিন /claude/<provider>/<model> মিরর আইডি যাতে Claude Code-এর গেটওয়ে মডেল আবিষ্কার সেগুলি তালিকাভুক্ত করতে পারে। এটি বিশ্বব্যাপী সক্ষম হলে সমস্ত ক্লায়েন্টের জন্য ক্যাটালগ এন্ট্রি দ্বিগুণ করে।",
|
||||
"ccDiscoveryInfoLink": "ফিচার ফ্ল্যাগগুলি খুলুন",
|
||||
"ccOnboardingTitle": "gateway মডেল আবিষ্কারের জন্য settings.json",
|
||||
"ccOnboardingCopy": "কপি করুন",
|
||||
"ccOnboardingCopied": "কপি করা হয়েছে",
|
||||
"ccOnboardingKeyPlaceholder": "<আপনার OmniRoute API কী>",
|
||||
"ccOnboardingWindowNote": "Claude Code একটি 200K প্রসঙ্গ উইন্ডো গ্রহণ করে যেকোন মডেল আইডির জন্য যা এটি চিনতে পারে না। একটি ভিন্ন বাস্তব উইন্ডো সহ মডেলের জন্য, এর নিচে CLAUDE_CODE_AUTO_COMPACT_WINDOW যোগ করুন যাতে অটো-কোম্প্যাকশন খুব তাড়াতাড়ি শুরু না হয়।",
|
||||
"failedSave": "সংরক্ষণ করতে ব্যর্থ হয়েছে",
|
||||
"profileSyncTitle": "CLI প্রোফাইল অটো-সিঙ্ক",
|
||||
"profileSyncDescription": "প্রদানকারী মডেলগুলি সিঙ্ক্রোনাইজ করার পরে, লাইভ ক্যাটালগ থেকে স্বয়ংক্রিয়ভাবে CLI টুল প্রোফাইলগুলি পুনরায় তৈরি করুন। ডিফল্টভাবে বন্ধ — শুধুমাত্র প্রোফাইল ফাইলগুলি লেখা হয়; সক্রিয়/ডিফল্ট কনফিগারেশন কখনই পরিবর্তন করা হয় না।",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "দক্ষতা এবং টুল ব্যবহারের যুক্তি",
|
||||
"hermesRoleApproval": "অনুমোদন",
|
||||
"hermesRoleApprovalDesc": "নিরাপত্তা এবং অনুমোদন সংক্রান্ত সিদ্ধান্ত",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "MCP সার্ভার টুল কলগুলি",
|
||||
"hermesRoleTitleGeneration": "শিরোনাম তৈরি করা",
|
||||
"hermesRoleTitleGenerationDesc": "সেশন শিরোনাম তৈরি করা",
|
||||
"hermesRoleMemoryQueryRewrite": "মেমরি কোয়েরি পুনর্লিখন",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "মেমরি অনুসন্ধান প্রশ্ন পুনর্লিখন",
|
||||
"hermesRoleTtsAudioTags": "টিটিএস অডিও ট্যাগস",
|
||||
"hermesRoleTtsAudioTagsDesc": "TTS অডিও ট্যাগ তৈরি করা",
|
||||
"hermesRoleTriageSpecifier": "ট্রায়েজ স্পেসিফায়ার",
|
||||
"hermesRoleTriageSpecifierDesc": "ইস্যু এবং পিআর ট্রায়েজ স্পেসিফিকেশন",
|
||||
"hermesRoleKanbanDecomposer": "কানবান ডিকম্পোজার",
|
||||
"hermesRoleKanbanDecomposerDesc": "কানবান টাস্ক ডিকম্পোজিশন",
|
||||
"hermesRoleProfileDescriber": "প্রোফাইল বর্ণনাকারী",
|
||||
"hermesRoleProfileDescriberDesc": "ব্যবহারকারীর প্রোফাইল বর্ণনা",
|
||||
"hermesRoleGoalJudge": "গোল জাজ",
|
||||
"hermesRoleGoalJudgeDesc": "লক্ষ্য সম্পন্ন বিচার",
|
||||
"hermesRoleCurator": "কিউরেটর",
|
||||
"hermesRoleCuratorDesc": "দক্ষতা এবং স্মৃতি কিউরেশন",
|
||||
"hermesRoleMonitor": "মোনিটর",
|
||||
"hermesRoleMonitorDesc": "পটভূমি পর্যবেক্ষণ",
|
||||
"hermesRoleBackgroundReview": "পটভূমি পর্যালোচনা",
|
||||
"hermesRoleBackgroundReviewDesc": "পটভূমি কোড পর্যালোচনা",
|
||||
"hermesSelectBeforePreview": "প্রিভিউ করার আগে, রোলের জন্য মডেল নির্বাচন করুন অথবা রোলগুলো লোড করা হয়েছে তা নিশ্চিত করুন।",
|
||||
"hermesPreviewFailed": "প্রিভিউ তৈরি করতে ব্যর্থ হয়েছে",
|
||||
"hermesSavedTo": "{path}-এ সংরক্ষিত হয়েছে",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "এতে পেস্ট করুন:",
|
||||
"copilotReloadInstruction": "তারপর VS Code রিলোড করুন এবং ইনপুট প্রম্পটে API কী সেট করুন।",
|
||||
"wireApiChatCompletions": "চ্যাট সমাপ্তি (/চ্যাট/সম্পূর্ণতা)",
|
||||
"wireApiResponses": "প্রতিক্রিয়া API (/প্রতিক্রিয়া)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "প্রতিক্রিয়া API (/প্রতিক্রিয়া)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Combos",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"sectionTitle": "Integration Surface",
|
||||
"sectionDescription": "OpenAI-compatible APIs and operational protocol endpoints",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "এপিআইগুলি",
|
||||
"tabProtocols": "Protocols",
|
||||
"tabsAria": "Endpoint sections",
|
||||
"protocolsTitle": "Protocols",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "No new models were added (all already exist).",
|
||||
"importDoneCount": "✓ Done! {count, plural, one {# model imported.} other {# models imported.}}",
|
||||
"unexpectedErrorOccurred": "An unexpected error occurred",
|
||||
"getApiKey": "এপিআই কী পান",
|
||||
"getApiKeyDescription": "একটি API কী-এর জন্য নিবন্ধন করুন বা সাইন আপ করুন",
|
||||
"connectionCountLabel": "{count, plural, one {# connection} other {# connections}}",
|
||||
"messagesPath": "messages",
|
||||
"responsesPath": "responses",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "নেটিভ web_fetch টুল কলগুলিকে OmniRoute-এর /v1/web/fetch-এ রিরাইট করুন।",
|
||||
"interceptionLoadError": "ইন্টারসেপশন সেটিংস লোড করতে ব্যর্থ হয়েছে: {error}",
|
||||
"interceptionSaveError": "ইন্টারসেপশন সেটিংস সংরক্ষণ করতে ব্যর্থ হয়েছে: {error}",
|
||||
"ccAliasSectionTitle": "Claude কোডে প্রকাশ করুন (claude/…)",
|
||||
"ccAliasSectionHint": "এই প্রদানকারীর মডেলগুলি claude/<provider>/<model> মিরর আইডির অধীনে বিজ্ঞাপন দিন যাতে Claude Code-এর গেটওয়ে মডেল আবিষ্কার সেগুলি তালিকাভুক্ত করতে পারে। ডিফল্টভাবে বন্ধ — এটি সক্রিয় করলে সমস্ত ক্লায়েন্টের জন্য ক্যাটালগের এন্ট্রি দ্বিগুণ হয়।",
|
||||
"ccAliasProviderLevelLabel": "প্রদানকারী ডিফল্ট",
|
||||
"ccAliasModelOverridesLabel": "প্রতি-মডেল ওভাররাইডস",
|
||||
"ccAliasModelOverrideAriaLabel": "{name} এর জন্য ওভাররাইড",
|
||||
"ccAliasStateInherit": "উত্তরাধিকারী",
|
||||
"ccAliasStateOn": "চালু",
|
||||
"ccAliasStateOff": "বন্ধ",
|
||||
"ccAliasAddModelPlaceholder": "মডেল আইডি (যেমন gpt-4o)",
|
||||
"ccAliasAddModelButton": "অভাররাইড যোগ করুন",
|
||||
"ccAliasLoadError": "ডিসকভারি-অ্যালিয়াস সেটিংস লোড করতে ব্যর্থ: {error}",
|
||||
"ccAliasSaveError": "ডিসকভারি-অ্যালিয়াস সেটিং সংরক্ষণ করতে ব্যর্থ: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
@@ -5413,8 +5453,8 @@
|
||||
"t3ChatWebCookieHint": "t3.chat → DevTools → Application → Local Storage → https://t3.chat খুলুন, 'convex-session-id' কপি করুন। তারপর DevTools → Network খুলুন, যেকোনো চ্যাট অনুরোধ থেকে সম্পূর্ণ কুকি হেডারটি কপি করুন। নীচের ক্ষেত্রগুলিতে উভয় মান আটকান।",
|
||||
"t3ChatWebCookiePlaceholder": "উত্তল-সেশন-id=abc123...",
|
||||
"grokWebCookieHint": "Grok Web Cookie Hint",
|
||||
"blockClaudeExtraUsageDescription": "__MISSING__:When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.",
|
||||
"blockClaudeExtraUsageLabel": "__MISSING__:Block extra Claude usage",
|
||||
"blockClaudeExtraUsageDescription": "যখন সক্ষম করা হয়, OmniRoute এই Claude সংযোগটিকে অপ্রাপ্য হিসেবে চিহ্নিত করে যত তাড়াতাড়ি ব্যবহারের API কিউ করা অতিরিক্ত ব্যবহারের রিপোর্ট করে, তাই ব্যাকআপ অন্য সংযোগে স্যুইচ করে পে-অ্যাস-ইউ-গো অতিরিক্ত বিলিং চালিয়ে যাওয়ার পরিবর্তে।",
|
||||
"blockClaudeExtraUsageLabel": "অতিরিক্ত Claude ব্যবহারের ব্লক করুন",
|
||||
"disableCoolingDescription": "অস্থায়ী কুলডাউন এড়িয়ে যান যাতে পুনরুদ্ধারযোগ্য ত্রুটির পরেও এই সংযোগটি যোগ্য থাকে (নিষিদ্ধ/মেয়াদোত্তীর্ণের মতো টার্মিনাল অবস্থাগুলি এখনও প্রযোজ্য)।",
|
||||
"disableCoolingLabel": "এই সংযোগের জন্য কুলডাউন নিষ্ক্রিয় করুন",
|
||||
"bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "New-API User ID",
|
||||
"newApiUserIdPlaceholder": "e.g. 12345",
|
||||
"newApiUserIdHint": "AgentRouter New-Api-User header value, used together with the console API key to fetch quota balance.",
|
||||
"newApiAggregatorToggleLabel": "এগ্রিগেটর গেটওয়ে",
|
||||
"newApiAggregatorToggleHint": "নতুন-এপিআই / ওয়ান-এপিআই / সাব2এপিআই অ্যাগ্রিগেটর নোডগুলির জন্য ব্যালেন্স সনাক্তকরণ সক্ষম করুন। ড্যাশবোর্ড ব্যালেন্স ব্যাজ দেখাবে এবং কোটা-পূর্ব ফ্লাইট রাউটিং ক্লান্ত অ্যাকাউন্টগুলি বাদ দেবে।",
|
||||
"newApiAggregatorConsoleApiKeyHint": "এগ্রিগেটরের /api/user/self এন্ডপয়েন্টের জন্য সিস্টেম অ্যাক্সেস টোকেন। রাউটিং API কী নয়।",
|
||||
"newApiAggregatorUserIdHint": "নতুন-এপিআই-ব্যবহারকারী হেডার মান যা সমন্বয়কারী ব্যবহারকারীর কোটা ব্যালেন্স পেতে ব্যবহৃত হয়।",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "প্রতি ইউনিট কোটা",
|
||||
"newApiAggregatorQuotaPerUnitHint": "নতুন-এপিআই ক্রেডিট ইউনিট প্রতি $1 (ডিফল্ট: 500000)। যদি আপনার অ্যাগ্রিগেটর ভিন্ন হার ব্যবহার করে তবে ওভাররাইড করুন।",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "নতুন-এপিআই / ওয়ান-এপিআই / সাব2এপিআই অ্যাগ্রিগেটর সামঞ্জস্যপূর্ণ নোডগুলির জন্য ব্যালেন্স শনাক্তকরণ সক্ষম করুন",
|
||||
"cpaModeDisabledTitle": "Cpa Mode Disabled Title",
|
||||
"cpaModeEnabledTitle": "Cpa Mode Enabled Title",
|
||||
"customUserAgentHint": "Custom User Agent Hint",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Tag Group Placeholder",
|
||||
"testModel": "Test Model",
|
||||
"testingModel": "Testing Model",
|
||||
"modelTestQuotaTooltip": "কোটার সীমা শেষ — আগামীকাল পুনরায় সেট হবে অথবা একটি টপ-আপ প্রয়োজন",
|
||||
"toggleOffShort": "Toggle Off Short",
|
||||
"toggleOnShort": "Toggle On Short",
|
||||
"tokenExpiredBadge": "Token Expired Badge",
|
||||
@@ -5593,13 +5641,13 @@
|
||||
"importGrokAuth": "Grok Build auth ইম্পোর্ট করুন",
|
||||
"zedImportTitle": "Zed Keychain থেকে ইম্পোর্ট করুন",
|
||||
"zedImportDescription": "OS কীচেইনে Zed IDE দ্বারা সংরক্ষিত AI প্রোভাইডার ক্রেডেনশিয়াল (OpenAI, Anthropic, Google, Mistral, xAI) খুঁজুন এবং সেগুলিকে কানেকশন হিসেবে ইম্পোর্ট করুন। এই মেশিনে Zed IDE ইনস্টল করা থাকতে হবে।",
|
||||
"zedImportButton": "__MISSING__:Import from Zed",
|
||||
"zedImportFailed": "__MISSING__:Zed import failed",
|
||||
"zedImportButton": "Zed থেকে আমদানি করুন",
|
||||
"zedImportFailed": "Zed আমদানি ব্যর্থ হয়েছে",
|
||||
"zedImportHint": "Zed Import Hint",
|
||||
"zedImportNetworkError": "Zed Import Network Error",
|
||||
"zedImportNone": "Zed Import None",
|
||||
"zedImportSuccess": "__MISSING__:Imported {credentials} credential(s) from Zed for {providers} provider(s)",
|
||||
"zedImporting": "__MISSING__:Importing…",
|
||||
"zedImportSuccess": "{providers} প্রদানকারী(দের) জন্য Zed থেকে {credentials} শংসাপত্র(গুলি) আমদানি করা হয়েছে",
|
||||
"zedImporting": "আমদানি হচ্ছে…",
|
||||
"zedNoCredentials": "কীচেইনে কোনো Zed ক্রেডেনশিয়াল পাওয়া যায়নি",
|
||||
"zedUnsupportedCredentials": "{count}টি কীচেইন ক্রেডেনশিয়াল পাওয়া গেছে, কিন্তু কোনোটিই সমর্থিত প্রোভাইডারের সাথে মেলেনি",
|
||||
"zedManualTitle": "ম্যানুয়াল টোকেন ইম্পোর্ট",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "ai.360.cn থেকে API কী পান",
|
||||
"agentrouter": "https://agentrouter.org/register-এ $200 ফ্রি ক্রেডিট পান — কোনো ক্রেডিট কার্ডের প্রয়োজন নেই।",
|
||||
"unorouter": "https://unorouter.ai তে একটি API কী তৈরি করুন, তারপর এটি এখানে Bearer টোকেন হিসেবে পেস্ট করুন।",
|
||||
"agnes": "agnes-ai.com থেকে API কী পান",
|
||||
"aimlapi": "ফ্রি টিয়ার স্থগিত করা হয়েছে (২০২৬) — AI/ML API এখন শুধুমাত্র পে-অ্যাজ-ইউ-গো (সর্বনিম্ন $২০ টপ-আপ); কোনো পুনরাবৃত্ত ফ্রি ক্রেডিট নেই।",
|
||||
"ai21": "সাইনআপে $১০ ট্রায়াল ক্রেডিট (৩ মাসের জন্য বৈধ), কোনো ক্রেডিট কার্ডের প্রয়োজন নেই",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "একটি API কী দিয়ে Alibaba Coding Plan কানেক্ট করুন।",
|
||||
"bedrock": "নেটিভ Bedrock ইন্টিগ্রেশন: মডেল ডিসকভারি Bedrock ফাউন্ডেশন মডেল এবং ইনফারেন্স প্রোফাইল ব্যবহার করে, যেখানে চ্যাট আঞ্চলিক Bedrock Runtime Converse/ConverseStream API ব্যবহার করে।",
|
||||
"anthropic": "একটি API কী দিয়ে Anthropic কানেক্ট করুন।",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "Ant Ling API কনসোলে (https://chat.ant-ling.com/open) একটি API কী নিবন্ধন করুন এবং তৈরি করুন, তারপর এটি এখানে পেস্ট করুন। OmniRoute চ্যাট ট্রাফিককে https://api.ant-ling.com/v1/chat/completions এ রাউট করে; প্রদানকারী OpenAI-সঙ্গত এবং একটি Anthropic-সঙ্গত সারফেসও প্রকাশ করে।",
|
||||
"api-airforce": "https://panel.api.airforce থেকে আপনার API কী পান — OpenAI-সামঞ্জস্যপূর্ণ এন্ডপয়েন্ট https://api.airforce/v1-এ",
|
||||
"arcee-ai": "arcee.ai থেকে API কী পান",
|
||||
"azure-ai": "Foundry মডেল হিসেবে ডিপ্লয়মেন্ট নাম সহ OpenAI v1 সারফেস ব্যবহার করে। OmniRoute রুট রিসোর্স URL-গুলোকে v1 চ্যাট এবং /models এন্ডপয়েন্টে নরমালাইজ করে।",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "Chutes OpenAI-সামঞ্জস্যপূর্ণ গেটওয়ের জন্য Bearer API কী।",
|
||||
"clarifai": "Clarifai /v2/ext/openai/v1-এ OpenAI-সামঞ্জস্যপূর্ণ চ্যাট, রেসপন্স এবং /models প্রকাশ করে। পাবলিক/কমিউনিটি মডেলগুলোর জন্য সাধারণত একটি PAT প্রয়োজন হয়; অ্যাপ-স্কোপড কীগুলো শুধুমাত্র সেই অ্যাপের ভেতরের রিসোর্সগুলোর জন্য কাজ করে।",
|
||||
"cloudflare-ai": "API টোকেন এবং অ্যাকাউন্ট আইডি (dash.cloudflare.com-এ পাওয়া যাবে) প্রয়োজন",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) OpenAI-সঙ্গত /v1/openai তে। OmniRoute /v1/openai/models তে প্রোব করে এবং চ্যাট ট্রাফিককে /v1/openai/chat/completions এ রাউট করে। বর্তমান clovastudio.stream.ntruss.com হোস্ট ব্যবহার করে — পুরানো clovastudio.apigw.ntruss.com এন্ডপয়েন্টটি বাতিল করা হচ্ছে।",
|
||||
"codestral": "একটি API কী দিয়ে Codestral কানেক্ট করুন।",
|
||||
"cohere": "ফ্রি ট্রায়াল: পরীক্ষার জন্য প্রতি মাসে 1,000টি API কল, কোনো ক্রেডিট কার্ডের প্রয়োজন নেই",
|
||||
"command-code": "Command Code থেকে একটি API কী তৈরি বা কপি করুন, তারপর সেটি এখানে Bearer টোকেন হিসেবে পেস্ট করুন।",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "watsonx মডেল গেটওয়ে /ml/gateway/v1-এর অধীনে OpenAI-সামঞ্জস্যপূর্ণ /chat/completions এবং /models প্রকাশ করে।",
|
||||
"ideogram": "ideogram.ai/docs/api-এ API কী পান",
|
||||
"iflytek": "console.xfyun.cn-এ API কী পান",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs OpenAI-সঙ্গতিপূর্ণ https://api.inceptionlabs.ai/v1 এ। mercury-2 ক্যাটালগে প্রথম ডিফিউশন LLM (dLLM) — তুলনামূলক অটোরেগ্রেসিভ মডেলের চেয়ে ৫-১০ গুণ দ্রুত উৎপাদন, টুল কলিং, json_mode, এবং কাঠামোবদ্ধ আউটপুট সহ।",
|
||||
"inference-net": "সাইন আপে $25 ফ্রি ক্রেডিট এবং সাথে গবেষণা অনুদান উপলব্ধ",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "মাসিক ফ্রি কোটা ~1M ইনপুট / 3M আউটপুট টোকেন (~10 RPM)",
|
||||
"jina-ai": "Jina AI rerank API-এর জন্য Bearer API কী।",
|
||||
"jina-reader": "একটি API কী দিয়ে Jina Reader কানেক্ট করুন।",
|
||||
"kenari": "Kenari https://kenari.id/v1/chat/completions-এ একটি OpenAI-সামঞ্জস্যপূর্ণ চ্যাট কমপ্লিশন এন্ডপয়েন্ট প্রকাশ করে, সাথে Claude, GPT, DeepSeek, GLM, Kimi এবং আরও অনেক কিছু কভার করে একটি লাইভ /v1/models ক্যাটালগ প্রদান করে। OmniRoute OpenAI প্রোটোকল ব্যবহার করে এবং passthrough-এর মাধ্যমে মডেল তালিকাভুক্ত করে।",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "একটি API কী দিয়ে Perplexity কানেক্ট করুন।",
|
||||
"piapi": "একটি API কী দিয়ে PiAPI কানেক্ট করুন।",
|
||||
"pioneer": "$75 ফ্রি ব্যবহারের ক্রেডিট — কোনো ক্রেডিট কার্ডের প্রয়োজন নেই",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo হল OpenAI-সঙ্গত https://api.platform.preferredai.jp/v1 এ। এটি Preferred Networks দ্বারা নির্মিত এবং জাপানি ভাষার জন্য অপ্টিমাইজ করা হয়েছে। ডকস প্রধানত জাপানি ভাষায় রয়েছে।",
|
||||
"poe": "Poe https://api.poe.com/v1-এ OpenAI-সামঞ্জস্যপূর্ণ চ্যাট এবং রেসপন্স প্রকাশ করে, সাথে /usage/current_balance-এ অথেন্টিকেটেড ব্যালেন্স চেক করার সুবিধা রয়েছে।",
|
||||
"pollinations": "ফ্রি কী-হীন টিয়ার: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning। প্রিমিয়াম মডেলগুলোর (claude, gemini, midijourney) জন্য enter.pollinations.ai থেকে একটি Pollinations API কী প্রয়োজন।",
|
||||
"publicai": "একটি API কী প্রয়োজন — এককালীন সাইনআপ ক্রেডিট, তারপর পেইড",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "Runway ভিডিও জেনারেশন টাস্ক-ভিত্তিক। OmniRoute টেক্সট-টু-ভিডিও বা ইমেজ-টু-ভিডিও জব সাবমিট করে, /v1/tasks/[id] পোল করে এবং সমাপ্ত ভিডিও আউটপুটগুলোকে আবার OpenAI-এর মতো /v1/videos/generations রেসপন্সে নরমালাইজ করে।",
|
||||
"sambanova": "সাইন আপ করার সময় $5 ফ্রি ক্রেডিট (৩০ দিনের মেয়াদ), কোনো ক্রেডিট কার্ডের প্রয়োজন নেই",
|
||||
"sap": "মডেল ডিসকভারি AI_API_URL-এ /v2/lm/scenarios/foundation-models/models ব্যবহার করে। চ্যাট রিকোয়েস্টগুলো deploymentUrl/chat/completions ব্যবহার করে এবং এর জন্য AI-Resource-Group প্রয়োজন।",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "Sarvam AI OpenAI-সঙ্গত /v1-এ। OmniRoute /v1/models-এ প্রোব করে এবং চ্যাট ট্রাফিক /v1/chat/completions-এ রাউট করে। মডেলগুলি ইন্ডিক ভাষার জন্য টিউন করা হয়েছে।",
|
||||
"scaleway": "নতুন অ্যাকাউন্টের জন্য 1M ফ্রি টোকেন — EU/GDPR কমপ্লায়েন্ট (প্যারিস), Qwen3 235B এবং Llama 70B",
|
||||
"sensenova": "platform.sensenova.cn থেকে API কী পান",
|
||||
"siliconflow": "পরিচয় যাচাইকরণের পর $1 ফ্রি ক্রেডিট এবং স্থায়ীভাবে ফ্রি মডেল",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "একটি API কী দিয়ে Together AI কানেক্ট করুন।",
|
||||
"tokenrouter": "TokenRouter https://api.tokenrouter.com/v1/chat/completions-এ একটি OpenAI-সামঞ্জস্যপূর্ণ চ্যাট কমপ্লিশন এন্ডপয়েন্ট প্রকাশ করে, সাথে একটি কার্যকর /v1/models ক্যাটালগ রয়েছে। OmniRoute OpenAI প্রোটোকল ব্যবহার করে।",
|
||||
"topaz": "একটি API কী দিয়ে Topaz কানেক্ট করুন।",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "টাইফুন OpenAI-সঙ্গত /v1 এ। নির্মিত SCB 10X (থাইল্যান্ড); typhoon-v2.5-30b-a3b-instruct একটি থাই-প্রথম, বহুভাষিক মডেল।",
|
||||
"udio": "udio.com (Supabase auth) থেকে সেশন কুকি পেস্ট করুন",
|
||||
"uncloseai": "কোনো auth-এর প্রয়োজন নেই। API শনাক্তকরণের জন্য কী হিসেবে যেকোনো অ-খালি স্ট্রিং গ্রহণ করে।",
|
||||
"upstage": "একটি API কী দিয়ে Upstage সংযুক্ত করুন।",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "Voyage AI এম্বেডিংস এবং রির্যাঙ্ক API-গুলির জন্য Bearer API কী।",
|
||||
"wafer": "https://wafer.ai থেকে API কী",
|
||||
"wandb": "একটি API কী দিয়ে Weights & Biases Inference সংযুক্ত করুন।",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra OpenAI-সঙ্গত https://api.writer.com/v1-এ। palmyra-x5 একটি 1M-token প্রসঙ্গ উইন্ডো প্রদান করে।",
|
||||
"x5lab": "X5Lab https://api.x5lab.dev/v1/chat/completions-এ একটি OpenAI-সামঞ্জস্যপূর্ণ চ্যাট কমপ্লিশন এন্ডপয়েন্ট এবং একটি লাইভ /v1/models ক্যাটালগ প্রদান করে। OmniRoute OpenAI প্রোটোকল ব্যবহার করে এবং passthrough-এর মাধ্যমে মডেল তালিকাভুক্ত করে।",
|
||||
"xai": "একটি API কী দিয়ে xAI (Grok) সংযুক্ত করুন।",
|
||||
"xiaomi-mimo": "একটি API কী দিয়ে Xiaomi MiMo সংযুক্ত করুন।",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "dola.com-এর মাধ্যমে ByteDance AI চ্যাট",
|
||||
"overrideBaseUrlAdvanced": "উন্নত: বেস URL ওভাররাইড করুন",
|
||||
"overrideBaseUrlHint": "উন্নত: এই বিল্ট-ইন প্রোভাইডারটিকে একটি কাস্টম এন্ডপয়েন্টে নির্দেশ করুন। ডিফল্ট ব্যবহার করতে ফাঁকা রাখুন।",
|
||||
"apiProtocolLabel": "এপিআই প্রোটোকল",
|
||||
"apiProtocolDefault": "OpenAI-সঙ্গত (ডিফল্ট)",
|
||||
"apiProtocolHint": "কিছু প্রদানকারী একাধিক প্রোটোকলের মাধ্যমে একই মডেল প্রকাশ করে। বিকল্প প্রয়োজন না হলে ডিফল্টটি রেখে দিন।",
|
||||
"bulkAddFormatHintCloudflare": "প্রতি লাইনে একটি কী। ফরম্যাট: name|accountId|apiKey (Cloudflare অ্যাকাউন্ট ID + API টোকেন)।",
|
||||
"lmarenaWebCookieHint": "arena.ai খুলুন, সাইন ইন করুন, তারপর একটি Network রিকোয়েস্ট থেকে সম্পূর্ণ Cookie হেডার কপি করুন। arena-auth-prod-v1.0 এবং arena-auth-prod-v1.1 (এবং উপস্থিত থাকলে পরবর্তী অংশগুলি) অন্তর্ভুক্ত করুন, বিশেষ করে cf_clearance সহ। শুধুমাত্র খালি arena-auth-prod-v1 কুকি পেস্ট করবেন না। ঐচ্ছিক: create-evaluation এখনও 403 রিটার্ন করলে providerSpecificData.recaptchaV3Token।",
|
||||
"kimiOfficialSupporterBadge": "প্রতিষ্ঠাতা বন্ধু",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) হল OmniRoute-এর প্রতিষ্ঠাতা ওপেন সোর্স বন্ধু",
|
||||
"cheaperInferenceSupporterBadge": "ওপেন সোর্স বন্ধু",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference একজন ওপেন সোর্স বন্ধু হিসেবে OmniRoute-কে সমর্থন করে",
|
||||
"kimiPartnerLinkNote": "পার্টনার লিঙ্ক — আপনার কোনো অতিরিক্ত খরচ ছাড়াই OmniRoute-কে সমর্থন করে",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "পার্টনার লিঙ্ক — আপনার কোনো অতিরিক্ত খরচ ছাড়াই OmniRoute-কে সমর্থন করে"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "Saving...",
|
||||
"requestBodyLimitSave": "Save",
|
||||
"requestBodyLimitCurrent": "Current: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "ক্যাশ সেটিংস লোড করতে ব্যর্থ হয়েছে",
|
||||
"cacheConfigSaveSuccess": "ক্যাশ সেটিংস সংরক্ষিত হয়েছে",
|
||||
"cacheConfigSaveFailed": "ক্যাশ সেটিংস সংরক্ষণ করতে ব্যর্থ হয়েছে",
|
||||
"modelCatalogTtlWholeNumberError": "একটি পূর্ণ সংখ্যা ব্যবহার করুন",
|
||||
"modelCatalogTtlMinimumError": "ন্যূনতম {min} ms",
|
||||
"modelCatalogTtlMaximumError": "সর্বাধিক {max} ms",
|
||||
"modelCatalogCacheTtl": "মডেল ক্যাটালগ ক্যাশ TTL",
|
||||
"modelCatalogCacheTtlDescription": "মডেল ক্যাটালগ প্রতিক্রিয়া রিফ্রেশ করার আগে কতক্ষণ ক্যাশে করা হয়",
|
||||
"modelCatalogCacheTtlLabel": "মডেল ক্যাটালগ ক্যাশ TTL মিলিসেকেন্ডে",
|
||||
"modelCatalogCacheTtlSaving": "সংরক্ষণ হচ্ছে...",
|
||||
"modelCatalogCacheTtlSave": "সংরক্ষণ করুন",
|
||||
"modelCatalogCacheTtlCurrent": "বর্তমান: {value} ms",
|
||||
"mitmProxy": "এমআইটিএম প্রক্সি",
|
||||
"pricing": "Pricing",
|
||||
"storage": "Storage",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "How Pricing Works",
|
||||
"cacheWrite": "Cache Write",
|
||||
"unsaved": "unsaved",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "ডিফল্ট রিসেট করুন",
|
||||
"saveProvider": "Save Provider",
|
||||
"model": "Model",
|
||||
"models": "models",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "কখনোই নয়",
|
||||
"compressionLiveZoneTitle": "ক্যাশ-অ্যালাইনড লাইভ জোন",
|
||||
"compressionLiveZoneDesc": "সংকুচিত কথোপকথনের প্রিফিক্স স্থিতিশীল রাখুন এবং শুধুমাত্র নতুন যুক্ত হওয়া আইটেমগুলো প্রসেস করুন।",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "সংকোচন বাদ দেওয়া",
|
||||
"compressionExclusionsDesc": "মডেল আইডি বা প্রদানকারী/মডেল প্যাটার্ন যা কখনোই সংকুচিত করা উচিত নয়। `*` হল একমাত্র ওয়াইল্ডকার্ড (যেমন `openai/*`, `*embedding*`)। একটি মেলানো অনুরোধ বাইট-আইডেন্টিক্যাল পাস করে — কোন সংকোচন ইঞ্জিন চলে না।",
|
||||
"compressionExclusionsPlaceholder": "প্রতি লাইনে একটি প্যাটার্ন, যেমন \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "সংরক্ষণ করুন",
|
||||
"compressionExclusionsSaved": "সংরক্ষিত",
|
||||
"compressionExclusionsCount": "{count, plural, one {# বর্জন} other {# বর্জনসমূহ}} কনফিগার করা হয়েছে",
|
||||
"compressionExclusionsEmpty": "কোনো বাদ দেওয়া কনফিগার করা হয়নি — প্রতিটি মডেল/এন্ডপয়েন্ট সংকোচনের জন্য যোগ্য (ডিফল্ট আচরণ)।",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "এর অন/অফ এবং লেভেল প্যানেলে সেট করা আছে:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "এক্সট্র্যাক্ট করা বিবরণটি মূল অনুরোধে পুনরায় ইনজেক্ট করার আগে ভিশন মডেলে পাঠানো হয়েছে।",
|
||||
"visionBridgeTimeoutMs": "টাইমআউট (মিসে)",
|
||||
"visionBridgeMaxImagesPerRequest": "অনুরোধ প্রতি সর্বোচ্চ ছবি",
|
||||
"modalityBridgeIntro": "বহুমাত্রিক কন্টেন্টকে টেক্সটে সংযুক্ত করুন যাতে এটি টেক্সট-শুধু মডেলগুলিতে পৌঁছানোর আগে। ভিশন লাইভ; অডিও অডিওব্রিজের সাথে আসে; ভিডিও রোডম্যাপে রয়েছে।",
|
||||
"modalityBridgeVisionTab": "দৃষ্টি",
|
||||
"modalityBridgeAudioTab": "অডিও",
|
||||
"modalityBridgeVideoTab": "ভিডিও",
|
||||
"modalityBridgeSubTabsAria": "মোডালিটি ব্রিজ সেকশনসমূহ",
|
||||
"modalityBridgeVisionTitle": "ভিশন ব্রিজ",
|
||||
"modalityBridgeVisionDesc": "দৃষ্টি মডেলের সাথে চিত্র বর্ণনা করুন এবং ব্যবহারকারীর নির্বাচিত টেক্সট মডেলের সাথে চালিয়ে যান।",
|
||||
"modalityBridgeAudioTitle": "অডিও ব্রিজ",
|
||||
"modalityBridgeAudioDesc": "নির্বাচিত টেক্সট মডেলের সাথে এগিয়ে যাওয়ার আগে একটি স্পিচ-টু-টেক্সট মডেলের সাহায্যে অডিও ট্রান্সক্রাইব করুন।",
|
||||
"modalityBridgeAudioEnabled": "অডিও ব্রিজ সক্রিয় করুন",
|
||||
"modalityBridgeAudioEnabledDesc": "লক্ষ্য মডেল অডিও প্রক্রিয়া করতে না পারলে অডিও অংশগুলি ট্রান্সক্রিপ্টের সাথে প্রতিস্থাপন করুন।",
|
||||
"modalityBridgeAudioModel": "স্পিচ-টু-টেক্সট মডেল",
|
||||
"modalityBridgeAudioModelAuto": "অটো (প্রথম সংযুক্ত STT প্রদানকারী)",
|
||||
"modalityBridgeAudioMaxClips": "প্রতি অনুরোধে সর্বাধিক অডিও ক্লিপ",
|
||||
"modalityBridgeMode": "মোড",
|
||||
"modalityBridgeModeAuto": "অটো (সুপারিশকৃত)",
|
||||
"modalityBridgeModeAutoHint": "লিগ্যাসি হিউরিস্টিক: শংসাপত্র ছাড়া পৃথক মডেলগুলি পুনঃনির্দেশ করুন; অন্যথায় বর্ণনা করুন।",
|
||||
"modalityBridgeModeDescribe": "সর্বদা বর্ণনা করুন",
|
||||
"modalityBridgeModeDescribeHint": "আপনি যে মডেলটি বেছে নিয়েছেন তা সর্বদা উত্তর দেয়; চিত্রগুলি পাঠ্য বর্ণনার দ্বারা প্রতিস্থাপিত হয়।",
|
||||
"modalityBridgeModeReroute": "সর্বদা পুনঃনির্দেশ করুন",
|
||||
"modalityBridgeModeRerouteHint": "সর্বোত্তম ভিশন-সক্ষম মডেলে পুরো অনুরোধ পাঠান (যখন কোনটি ব্যবহারযোগ্য নয় তখন বর্ণনা করতে ফিরে যায়)।",
|
||||
"modalityBridgeVisionModel": "ভিশন মডেল",
|
||||
"modalityBridgeVisionModelAuto": "অটো (সেরা উপলব্ধ)",
|
||||
"modalityBridgeTaskAware": "কাজ-সচেতন বর্ণনা",
|
||||
"modalityBridgeTaskAwareDesc": "ব্যবহারকারীর প্রশ্নকে ফোকাস হিসেবে অন্তর্ভুক্ত করুন যাতে ভিশন মডেলটি গুরুত্বপূর্ণ বিষয়গুলি বর্ণনা করে এবং দৃশ্যমান টেক্সটটি ট্রান্সক্রাইব করে।",
|
||||
"modalityBridgePrompt": "বর্ণনা প্রম্পট",
|
||||
"modalityBridgeAdvanced": "অগ্রসর",
|
||||
"modalityBridgeTimeoutMs": "টাইমআউট (মি.সে.)",
|
||||
"modalityBridgeMaxImages": "প্রতি অনুরোধে সর্বাধিক ছবি",
|
||||
"modalityBridgeCacheEnabled": "ক্যাশের বর্ণনা",
|
||||
"modalityBridgeCacheEnabledDesc": "একই চিত্রের জন্য বর্ণনা পুনরায় ব্যবহার করুন (SHA-256 কীযুক্ত, মেমরিতে)।",
|
||||
"modalityBridgeCacheTtlMinutes": "ক্যাশে TTL (মিনিট)",
|
||||
"modalityBridgeCacheMaxEntries": "ক্যাশের সর্বাধিক এন্ট্রি",
|
||||
"modalityBridgeStatsBridged": "ব্রিজড",
|
||||
"modalityBridgeStatsCacheHits": "ক্যাশ হিটস",
|
||||
"modalityBridgeStatsFailures": "ব্যর্থতা",
|
||||
"modalityBridgeStatsLastUsed": "শেষ ব্যবহার করা হয়েছে",
|
||||
"modalityBridgeStatsNever": "কখনো না",
|
||||
"modalityBridgeTestButton": "নমুনা চিত্রের সাথে পরীক্ষা করুন",
|
||||
"modalityBridgeTestRunning": "পরীক্ষা চলছে…",
|
||||
"modalityBridgeTestOk": "ব্রিজ ঠিক আছে — {count} চিত্র(গুলি) {model} দ্বারা বর্ণিত",
|
||||
"modalityBridgeTestReroute": "ব্রিজ অনুরোধটি {model} এ পুনঃনির্দেশিত করেছে",
|
||||
"modalityBridgeTestNoop": "ব্রিজ সক্রিয় হয়নি (মডেলটি নেটিভভাবে ভিশন সমর্থন করতে পারে অথবা ব্রিজ অক্ষম করা হয়েছে)",
|
||||
"modalityBridgeTestError": "পরীক্ষা ব্যর্থ: {message}",
|
||||
"modalityBridgeAudioTestButton": "নমুনা অডিওর সাথে পরীক্ষা করুন",
|
||||
"modalityBridgeAudioTestRunning": "অডিও পরীক্ষা করা হচ্ছে…",
|
||||
"modalityBridgeAudioTestOk": "অডিও ব্রিজ ঠিক আছে — {count} ক্লিপ(গুলি) {model} দ্বারা ট্রান্সক্রাইব করা হয়েছে",
|
||||
"modalityBridgeAudioTestNoop": "অডিও ব্রিজ সক্রিয় হয়নি (লক্ষ্য অডিও সমর্থন করতে পারে, কোন STT প্রদানকারী সংযুক্ত নয়, অথবা ব্রিজ নিষ্ক্রিয় রয়েছে)",
|
||||
"modalityBridgeAudioTestError": "অডিও পরীক্ষা ব্যর্থ: {message}",
|
||||
"modalityBridgeAudioComingSoon": "অডিও ব্রিজ (স্পিচ → টেক্সট মাধ্যমে /v1/audio/transcriptions) পরবর্তী রিলিজে আসছে। এর সেটিংস কী ইতিমধ্যেই সংরক্ষিত।",
|
||||
"modalityBridgeVideoComingSoon": "ভিডিও ব্রিজিং (ফ্রেম স্যাম্পলিং + ক্যাপশনিং) ব্যাকলগে রয়েছে — সমস্যা #9760 দেখুন।",
|
||||
"modalityBridgeMovedTitle": "ভিশন ব্রিজ স্থানান্তরিত হয়েছে",
|
||||
"modalityBridgeMovedBody": "ভিশন ব্রিজের সেটিংস এখন নির্দিষ্ট মডালিটি ব্রিজ পৃষ্ঠায় লাইভ।",
|
||||
"modalityBridgeMovedCta": "মোডালিটি ব্রিজ সেটিংস খুলুন",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Usage",
|
||||
"grokExtraUsageCredits": "অতিরিক্ত ব্যবহার ক্রেডিট",
|
||||
"grokAutoTopUp": "অটো টপ-আপ",
|
||||
"grokAutoTopUpUnavailable": "অপ্রাপ্য",
|
||||
"grokAutoTopUpEnabled": "সক্রিয়",
|
||||
"grokAutoTopUpDisabled": "অক্ষম",
|
||||
"grokAutoTopUpAt": "এট্",
|
||||
"grokAutoTopUpAdd": "যোগ করুন",
|
||||
"grokAutoTopUpMax": "সর্বাধিক",
|
||||
"grokAutoTopUpMonth": "মাস",
|
||||
"grokAdditionalCredits": "অতিরিক্ত ক্রেডিটস",
|
||||
"loggerTab": "Logger",
|
||||
"proxyTab": "Proxy",
|
||||
"budgetManagement": "Budget Management",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "Verification URL",
|
||||
"deviceCodeYourCode": "Your code",
|
||||
"deviceCodeWaiting": "Waiting for authorization...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "এই ঠিকানা থেকে Google সাইন-ইন সম্পন্ন করা যাচ্ছে না",
|
||||
"googleLoopbackWhatHappens": "গুগল শুধুমাত্র অনুমোদন কোডটি প্রকাশ করে যখন <code>{redirectUri}</code> ব্রাউজার থেকে পৌঁছানো যায় যা সাইন-ইন অনুমোদন করে। এখানে সেই ঠিকানা এই কম্পিউটারের দিকে নির্দেশ করে, OmniRoute সার্ভারের দিকে নয় — তাই সম্মতি স্ক্রীন ঝুলে থাকে পরিবর্তে পুনঃনির্দেশ করার, এবং কপি করার জন্য কোন কলব্যাক URL নেই।",
|
||||
"googleLoopbackRecommended": "প্রস্তাবিত — এটি আপনার নিজের কম্পিউটারে চালান, তারপর ফলাফলটি নিচে পেস্ট করুন:",
|
||||
"googleLoopbackHelperNote": "এটি স্থানীয়ভাবে গুগল সম্মতি খুলে (যেখানে 127.0.0.1 কাজ করে) এবং একটি একলাইন omniroute-cred-v1.… ব্লব মুদ্রণ করে। সেই ব্লবটি নিচের পদক্ষেপ 2 এর ক্ষেত্রে পেস্ট করুন — এটি একটি শংসাপত্র ব্লব এবং একটি কলব্যাক URL উভয়ই গ্রহণ করে।",
|
||||
"googleLoopbackTunnelLabel": "অথবা SSH এর মাধ্যমে ড্যাশবোর্ড পোর্ট ফরওয়ার্ড করুন এবং টানেলের মাধ্যমে OmniRoute পুনরায় লোড করুন:",
|
||||
"googleLoopbackTunnelNote": "আপনার SSH ব্যবহারকারীর নামের সাথে {userPlaceholder} প্রতিস্থাপন করুন, টার্মিনাল খোলা রাখুন, তারপর {localUrl} খুলুন এবং সেখান থেকে আবার সংযোগ করুন।",
|
||||
"googleLoopbackHeadlessAlt": "সম্পূর্ণ হেডলেস ব্যবহারের জন্য, কোন স্থানীয় কলব্যাক ছাড়াই, <a>আপনার নিজস্ব Google OAuth শংসাপত্র কনফিগার করুন</a> এবং একটি পাবলিক বেস URL।",
|
||||
"remoteAccessInfo": "Remote access: Since you're accessing OmniRoute remotely, after authorization you'll see an error page (localhost not found). This is normal — just copy the full URL from your browser address bar and paste it below.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "এই ঠিকানা থেকে সাইন-ইন সম্পন্ন করা যাচ্ছে না",
|
||||
"loopbackMismatchWhatHappened": "কি ঘটছে",
|
||||
"loopbackMismatchExplanation": "লগইন অনুমোদন করার পর, {providerName} সবসময় ব্রাউজারকে <code>{redirectUri}</code> এ ফিরিয়ে পাঠায়। সেই ঠিকানা এই ব্রাউজারটি চালানো কম্পিউটারের দিকে নির্দেশ করে, OmniRoute সার্ভারের দিকে নয় — তাই অনুমোদন কোড কখনো OmniRoute এ পৌঁছায় না এবং প্রদানকারী একটি ত্রুটি ছাড়াই সাইন-ইন ব্যর্থ হয়।",
|
||||
"loopbackMismatchHowToFix": "এটি কিভাবে ঠিক করবেন",
|
||||
"loopbackMismatchStep1": "এই কম্পিউটারে, একটি টার্মিনাল খুলুন এবং OmniRoute সার্ভারে একটি SSH টানেল শুরু করুন:",
|
||||
"loopbackMismatchStep1Note": "আপনার SSH ব্যবহারকারীর নামের সাথে {userPlaceholder} প্রতিস্থাপন করুন। সংযোগ সক্রিয় হিসাবে প্রদর্শিত না হওয়া পর্যন্ত এই টার্মিনালটি খোলা রাখুন — উভয় পোর্ট প্রয়োজন: একটি ড্যাশবোর্ড পরিবেশন করে, অন্যটি কলব্যাক গ্রহণ করে।",
|
||||
"loopbackMismatchStep2": "এই ব্রাউজারে, টানেলের মাধ্যমে OmniRoute পুনরায় খুলুন:",
|
||||
"loopbackMismatchStep3": "এরপর নতুন ট্যাব থেকে আবার {providerName} এর সাথে সংযোগ করুন। কলব্যাক এখন সার্ভারে পৌঁছায় এবং লগইন স্বাভাবিকভাবে সম্পন্ন হয়।",
|
||||
"loopbackMismatchAlternative": "SSH অ্যাক্সেস নেই? যদি এই প্রদানকারী একটি টোকেন আমদানি ট্যাব অফার করে, তাহলে টোকেনের মাধ্যমে সংযোগ করুন — সেই পথটি লুপব্যাক কলব্যাক ব্যবহার করে না।",
|
||||
"step1OpenUrl": "Step 1: Open this URL in your browser",
|
||||
"copy": "Copy",
|
||||
"step2PasteCallback": "Step 2: Paste callback URL or authorization code here",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "উৎস মডেল (এজেন্ট নেটিভ)",
|
||||
"targetModel": "টার্গেট মডেল (OmniRoute)",
|
||||
"noMappings": "কোনো মডেল ম্যাপিং কনফিগার করা নেই। মডেলগুলো অটো-ডিটেক্ট করতে সেটআপ উইজার্ড চালান।",
|
||||
"noMappingsDesc": "এখনো কোনো মডেল ম্যাপিং কনফিগার করা হয়নি। এজেন্টের অনুরোধগুলি OmniRoute এর মাধ্যমে রাউট করার জন্য ম্যাপিং যোগ করুন।",
|
||||
"addMapping": "ম্যাপিং যোগ করুন",
|
||||
"selectModel": "নির্বাচন করুন…",
|
||||
"saveMappings": "ম্যাপিং সংরক্ষণ করুন",
|
||||
"setupWizard": "সেটআপ উইজার্ড",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "No saved proxies found. Add proxies in Settings → Proxy first.",
|
||||
"updateProviderFailed": "Failed to update provider",
|
||||
"providerEnabled": "{provider} enabled",
|
||||
"providerDisabled": "{provider} disabled"
|
||||
"providerDisabled": "{provider} disabled",
|
||||
"providerAdded": "{provider} যোগ করা হয়েছে",
|
||||
"add": "যোগ করুন",
|
||||
"manualApiKey": "একটি ম্যানুয়াল API কী ব্যবহার করুন"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "বিপদ",
|
||||
"requiresRestart": "রিস্টার্ট প্রয়োজন",
|
||||
"source": "উৎস",
|
||||
"ccDiscoveryAliasesEnvWarning": "পরিবেশ ভেরিয়েবল (EXPOSE_CC_DISCOVERY_ALIASES) দ্বারা সক্রিয় — এটি নিচের যেকোনো ড্যাশবোর্ড টগলকে অতিক্রম করে।",
|
||||
"resetFlag": "{label} ডিফল্টে রিসেট করুন",
|
||||
"reset": "রিসেট করুন",
|
||||
"loadFailed": "ফিচার ফ্ল্যাগ লোড করতে ব্যর্থ হয়েছে",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "স্কিল স্যান্ডবক্সে নেটওয়ার্ক অ্যাক্সেস সক্ষম করুন।"
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "কম্বো কন্ট্রোল সেন্টার",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "পার্টনার লিঙ্ক",
|
||||
"dismissAriaLabel": "খারিজ করুন"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "/gateway-alias/<model> মিরর আইডি গুলি /v1/models এ বিজ্ঞাপন দিন তাদের জন্য মডেলগুলির যা ক্যানোনিকাল মালিকের কোন সক্রিয় শংসাপত্র নেই কিন্তু একটি পাসথ্রু গেটওয়ে তাদের সক্রিয় শংসাপত্রের সাথে রাউট করে। সতর্কতা: এটি বৈশ্বিকভাবে সক্ষম হলে সমস্ত ক্লায়েন্টের জন্য ক্যাটালগ এন্ট্রি যোগ করে।",
|
||||
"radarPage": {
|
||||
"title": "রাডার ক্যাটালগ",
|
||||
"subtitle": "কমিউনিটি বুদ্ধিমত্তা সমৃদ্ধ ফ্রি মডেল ক্যাটালগ",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "Zakázáno",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Směrovat požadavky s vyčerpaným rozpočtem na nouzového bezplatného záložního poskytovatele/model.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Povolit periodickou synchronizaci ELO z žebříčku Arena AI pro hodnocení inteligence modelů.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "Inzerujte claude/<provider>/<model> zrcadlové ID na /v1/models, aby seznam objevování modelů brány Claude Code obsahoval modely, které nejsou Claude. Upozornění: při globálním povolení zdvojuje katalogové položky pro všechny klienty.",
|
||||
"sidebar": {
|
||||
"home": "Domov",
|
||||
"dashboard": "Nástěnka",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "Combo Studio",
|
||||
"combosLiveSubtitle": "Živá kaskáda směrování",
|
||||
"compressionStudio": "Compression Studio",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "Vyloučení",
|
||||
"contextSettingsSubtitle": "Globální výchozí nastavení",
|
||||
"contextHeadroomSubtitle": "Tabulková komprimace",
|
||||
"contextSessionDedupSubtitle": "Deduplikace mezi tahy",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "Heuristické prořezávání",
|
||||
"contextOmniglyphSubtitle": "Kontext jako obrázky",
|
||||
"compressionStudioSubtitle": "Živá kaskáda enginů",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "Obcházení na základě modelu/koncového bodu",
|
||||
"chaosConfigSubtitle": "Paralelní spouštění více modelů",
|
||||
"routingSection": "Routing",
|
||||
"protocolsSection": "Protocols",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "Měsíční příděly bezplatných tokenů",
|
||||
"freeProviderRankings": "Žebříčky bezplatných poskytovatelů",
|
||||
"freeProviderRankingsSubtitle": "Nejlepší bezplatní poskytovatelé seřazení podle ELO skóre modelů",
|
||||
"radar": "Radar katalog",
|
||||
"radarSubtitle": "Bezplatný modelový katalog obohacený komunitou",
|
||||
"costsQuotaShare": "Quota Sharing",
|
||||
"costsPricing": "Pricing",
|
||||
"logsProxy": "Proxy Logs",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAi": "AI Settings",
|
||||
"settingsModalityBridge": "Modální Most",
|
||||
"settingsSecurity": "Security",
|
||||
"settingsAccessTokens": "Přístupové tokeny",
|
||||
"settingsFeatureFlags": "Příznaky funkcí",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "Mezipaměť",
|
||||
"settingsAuthz": "Authz",
|
||||
"settingsRouting": "Routing",
|
||||
"settingsResilience": "Resilience",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "Runtime",
|
||||
"consoleLogs": "Console Logs",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "Vizualizace časové osy požadavků",
|
||||
"globalRouting": "Global Routing",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "App basics",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "Obrázek/audio → textový fallback pro modely pouze s textem",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
"settingsSecuritySubtitle": "Auth and encryption",
|
||||
"settingsAccessTokensSubtitle": "CLI tokeny s omezeným rozsahem pro vzdálený režim",
|
||||
"settingsFeatureFlagsSubtitle": "Přepínání možností systému",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "Katalog modelů a mezipaměť odpovědí",
|
||||
"settingsSidebar": "Sidebar",
|
||||
"settingsSidebarSubtitle": "Customize sidebar layout",
|
||||
"settingsAuthzSubtitle": "Inventář tras a zásady obcházení",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "Vždy viditelné",
|
||||
"groupSeparatorLabel": "Oddělovač",
|
||||
"discovery": "Objevování",
|
||||
"discoverySubtitle": "Skenovat poskytovatele pro bezplatný přístup",
|
||||
"radar": "Radar katalog",
|
||||
"radarSubtitle": "Bezplatný modelový katalog obohacený komunitou"
|
||||
"discoverySubtitle": "Skenovat poskytovatele pro bezplatný přístup"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooky",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "Storage, database, and general instance configuration",
|
||||
"settingsAppearanceDescription": "Theme, branding, and visual customization",
|
||||
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "TTL pro položky mezipaměti katalogu modelů",
|
||||
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
|
||||
"featureFlags": "Příznaky funkcí",
|
||||
"featureFlagsDescription": "Možnosti řídicího systému a experimentální funkce",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "Generování obrázků",
|
||||
"imageToText": "Obrázek na text",
|
||||
"imageToTextComingSoon": "Integrované testovací prostředí pro převod obrázku na text bude k dispozici, jakmile bude implementováno <code>/api/v1/images/understanding</code>.",
|
||||
"imageToTextBridgeCta": "Nakonfigurujte most Image→Text v nastavení Modality Bridge",
|
||||
"sttBridgeCta": "Nastavte most Řeč→Text v nastavení Modality Bridge",
|
||||
"disabled": "Zakázáno",
|
||||
"videoGeneration": "Generování videa",
|
||||
"musicGeneration": "Generování hudby",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "Auto",
|
||||
"always": "Vždy"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Jak povolit objevování v Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "Inzerujte modely, které nejsou Claude, pod claude/<provider>/<model> zrcadlovými ID, aby modelová objevovací brána Claude Code mohla tyto modely zobrazit. Dvojnásobí katalogové položky pro všechny klienty, když je globálně povoleno.",
|
||||
"ccDiscoveryInfoLink": "Otevřít Funkční Pásky",
|
||||
"ccOnboardingTitle": "settings.json pro objevování modelu brány",
|
||||
"ccOnboardingCopy": "Kopírovat",
|
||||
"ccOnboardingCopied": "Zkopírováno",
|
||||
"ccOnboardingKeyPlaceholder": "<váš klíč API OmniRoute>",
|
||||
"ccOnboardingWindowNote": "Claude Code předpokládá kontextové okno 200K pro jakékoli ID modelu, které nezná. Pro model s jiným skutečným oknem přidejte CLAUDE_CODE_AUTO_COMPACT_WINDOW těsně pod něj, aby automatická komprese nenastala příliš brzy.",
|
||||
"failedSave": "Nepodařilo se uložit",
|
||||
"profileSyncTitle": "Automatická synchronizace profilů CLI",
|
||||
"profileSyncDescription": "Po synchronizaci modelů poskytovatelů automaticky regenerovat profily nástrojů CLI z živého katalogu. Ve výchozím nastavení vypnuto — zapisují se pouze soubory profilů; aktivní/výchozí konfigurace se nikdy nemění.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "Uvažování nad dovednostmi a používáním nástrojů",
|
||||
"hermesRoleApproval": "Schválení",
|
||||
"hermesRoleApprovalDesc": "Rozhodování o bezpečnosti a schválení",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "Volání nástrojů serveru MCP",
|
||||
"hermesRoleTitleGeneration": "Generování Titulů",
|
||||
"hermesRoleTitleGenerationDesc": "Generování názvu relace",
|
||||
"hermesRoleMemoryQueryRewrite": "Přepis dotazu na paměť",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "Přepisování dotazu pro vyhledávání v paměti",
|
||||
"hermesRoleTtsAudioTags": "TTS Audio Tagy",
|
||||
"hermesRoleTtsAudioTagsDesc": "Generování audio tagu TTS",
|
||||
"hermesRoleTriageSpecifier": "Triage Specifier",
|
||||
"hermesRoleTriageSpecifierDesc": "Specifikace triáže problémů a PR",
|
||||
"hermesRoleKanbanDecomposer": "Kanban Decomposer",
|
||||
"hermesRoleKanbanDecomposerDesc": "Dekompozice úkolů Kanban",
|
||||
"hermesRoleProfileDescriber": "Popisovač profilu",
|
||||
"hermesRoleProfileDescriberDesc": "Popis uživatelského profilu",
|
||||
"hermesRoleGoalJudge": "Cílový soudce",
|
||||
"hermesRoleGoalJudgeDesc": "Hodnocení dokončení cíle",
|
||||
"hermesRoleCurator": "Kurátor",
|
||||
"hermesRoleCuratorDesc": "Kurátorství dovedností a paměti",
|
||||
"hermesRoleMonitor": "Monitor",
|
||||
"hermesRoleMonitorDesc": "Sledování na pozadí",
|
||||
"hermesRoleBackgroundReview": "Pozadí Kontroly",
|
||||
"hermesRoleBackgroundReviewDesc": "Kontrola kódu na pozadí",
|
||||
"hermesSelectBeforePreview": "Před zobrazením náhledu vyberte modely pro role nebo se ujistěte, že jsou role načteny.",
|
||||
"hermesPreviewFailed": "Nepodařilo se vygenerovat náhled",
|
||||
"hermesSavedTo": "Uloženo do {path}",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "Vložit do:",
|
||||
"copilotReloadInstruction": "Poté znovu načtěte VS Code a nastavte klíč API ve vstupním poli.",
|
||||
"wireApiChatCompletions": "Dokončení chatu (/chat/completions)",
|
||||
"wireApiResponses": "Responses API (/responses)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "Responses API (/responses)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Komba",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "{count, plural, one {# model} other {# modelů}}",
|
||||
"sectionTitle": "Integrační plocha",
|
||||
"sectionDescription": "OpenAI-compatible API operační protokoly koncového bodu",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "API",
|
||||
"tabProtocols": "Protokoly",
|
||||
"tabsAria": "Koncové sekce",
|
||||
"protocolsTitle": "Protokoly",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "Nebyly přidány žádné nové modely (všechny již existují).",
|
||||
"importDoneCount": "✓ Hotovo! {count, plural, one {# importován model.} other {# importováno modelů.}}",
|
||||
"unexpectedErrorOccurred": "Došlo k neočekávané chybě",
|
||||
"getApiKey": "Získat API klíč",
|
||||
"getApiKeyDescription": "Zaregistrujte se nebo se přihlaste pro API klíč",
|
||||
"connectionCountLabel": "{count, plural, one {# spojení} other {# spojení}}",
|
||||
"messagesPath": "messages",
|
||||
"responsesPath": "responses",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "Přepisovat nativní volání nástroje web_fetch na /v1/web/fetch v OmniRoute.",
|
||||
"interceptionLoadError": "Nepodařilo se načíst nastavení zachytávání: {error}",
|
||||
"interceptionSaveError": "Nepodařilo se uložit nastavení zachytávání: {error}",
|
||||
"ccAliasSectionTitle": "Expose v Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "Inzerujte modely tohoto poskytovatele pod claude/<provider>/<model> zrcadlovými ID, aby mohl objevovací modelový systém Claude Code tyto modely zobrazit. Ve výchozím nastavení vypnuto — povolení tohoto nastavení zdvojnásobí položky katalogu pro všechny klienty.",
|
||||
"ccAliasProviderLevelLabel": "Výchozí poskytovatel",
|
||||
"ccAliasModelOverridesLabel": "Přepsání na úrovni modelu",
|
||||
"ccAliasModelOverrideAriaLabel": "Přepsání pro {modelId}",
|
||||
"ccAliasStateInherit": "Dědit",
|
||||
"ccAliasStateOn": "Zapnuto",
|
||||
"ccAliasStateOff": "Vypnuto",
|
||||
"ccAliasAddModelPlaceholder": "ID modelu (např. gpt-4o)",
|
||||
"ccAliasAddModelButton": "Přidat přepsání",
|
||||
"ccAliasLoadError": "Nepodařilo se načíst nastavení discovery-alias: {error}",
|
||||
"ccAliasSaveError": "Nepodařilo se uložit nastavení discovery-alias: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream hlavičky",
|
||||
"compatUpstreamHeadersHint": "Nastavení s vysokými oprávněními — stejná úroveň důvěryhodnosti jako při úpravách přihlašovacích údajů API poskytovatele; měli by jej používat pouze důvěryhodní administrátoři. Sloučeno poté, co OmniRoute přidá ověření z klíče API poskytovatele. Pokud vlastní záhlaví používá stejný název jako existující (např. Authorization), vaše hodnota zcela nahradí automaticky vygenerované záhlaví (včetně tokenu Bearer) — upstream vidí pouze to, co jste zadali, nikoli klíč z nastavení. Nesprávná nastavení může způsobit chybu 401 nebo nefunkční upstream ověření. Jeden řádek na jedno záhlaví (např. extra ověření pro některé brány). Pro náhled najděte na hodnotu nebo ji označte. Uloží se při odklonu, kliknutí mimo nebo zavření tohoto panelu.",
|
||||
"compatUpstreamHeaderName": "Název hlavičky",
|
||||
@@ -5413,8 +5453,8 @@
|
||||
"t3ChatWebCookieHint": "Otevřete t3.chat → DevTools → Aplikace → Místní úložiště → https://t3.chat, zkopírujte 'convex-session-id'. Poté otevřete DevTools → Network, zkopírujte celou hlavičku cookie z libovolné žádosti o chat. Obě hodnoty vložte do polí níže.",
|
||||
"t3ChatWebCookiePlaceholder": "convex-session-id=abc123...",
|
||||
"grokWebCookieHint": "Grok Web Cookie Hint",
|
||||
"blockClaudeExtraUsageDescription": "__MISSING__:When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.",
|
||||
"blockClaudeExtraUsageLabel": "__MISSING__:Block extra Claude usage",
|
||||
"blockClaudeExtraUsageDescription": "Když je povoleno, OmniRoute označí toto připojení Claude jako nedostupné, jakmile API pro používání nahlásí zařazené dodatečné použití, takže přepnutí na záložní připojení proběhne místo pokračování v dodatečném účtování podle skutečné spotřeby.",
|
||||
"blockClaudeExtraUsageLabel": "Zablokovat nadměrné používání Claude",
|
||||
"disableCoolingDescription": "Přeskočit přechodný cooldown, aby toto připojení zůstalo způsobilé i po obnovitelných chybách (konečné stavy jako zablokováno/vypršelo stále platí).",
|
||||
"disableCoolingLabel": "Zakázat cooldown pro toto připojení",
|
||||
"bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "New-API User ID",
|
||||
"newApiUserIdPlaceholder": "e.g. 12345",
|
||||
"newApiUserIdHint": "AgentRouter New-Api-User header value, used together with the console API key to fetch quota balance.",
|
||||
"newApiAggregatorToggleLabel": "Agregátorová Brána",
|
||||
"newApiAggregatorToggleHint": "Povolit detekci zůstatku pro uzly agregátoru New-API / One-API / Sub2API. Řídicí panel zobrazí odznak zůstatku a směrování předkontroly kvót bude přeskočit vyčerpané účty.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "Token pro přístup k systému pro endpoint agregátoru /api/user/self. Ne klíč API pro směrování.",
|
||||
"newApiAggregatorUserIdHint": "Hodnota hlavičky New-Api-User použita k získání zůstatku kvóty agregátorového uživatele.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "Kvóta Na Jednotku",
|
||||
"newApiAggregatorQuotaPerUnitHint": "Nové-API kreditní jednotky za 1 $ (výchozí: 500000). Přepište, pokud váš agregátor používá jinou sazbu.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "Povolit detekci zůstatku pro uzly kompatibilní s New-API / One-API / Sub2API agregátorem",
|
||||
"cpaModeDisabledTitle": "Cpa Mode Disabled Title",
|
||||
"cpaModeEnabledTitle": "Cpa Mode Enabled Title",
|
||||
"customUserAgentHint": "Custom User Agent Hint",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Tag Group Placeholder",
|
||||
"testModel": "Test Model",
|
||||
"testingModel": "Testing Model",
|
||||
"modelTestQuotaTooltip": "Kvóta vyčerpána — resetuje se zítra nebo je potřeba doplnění",
|
||||
"toggleOffShort": "Toggle Off Short",
|
||||
"toggleOnShort": "Toggle On Short",
|
||||
"tokenExpiredBadge": "Token Expired Badge",
|
||||
@@ -5593,13 +5641,13 @@
|
||||
"importGrokAuth": "Importovat ověření Grok Build",
|
||||
"zedImportTitle": "Importovat z klíčenky Zed",
|
||||
"zedImportDescription": "Vyhledejte přihlašovací údaje poskytovatelů AI (OpenAI, Anthropic, Google, Mistral, xAI) uložené editorem Zed IDE v klíčence operačního systému a importujte je jako připojení. Na tomto počítači musí být nainstalován Zed IDE.",
|
||||
"zedImportButton": "__MISSING__:Import from Zed",
|
||||
"zedImportFailed": "__MISSING__:Zed import failed",
|
||||
"zedImportButton": "Importovat ze Zed",
|
||||
"zedImportFailed": "Import Zed se nezdařil",
|
||||
"zedImportHint": "Zed Import Hint",
|
||||
"zedImportNetworkError": "Zed Import Network Error",
|
||||
"zedImportNone": "Zed Import None",
|
||||
"zedImportSuccess": "__MISSING__:Imported {credentials} credential(s) from Zed for {providers} provider(s)",
|
||||
"zedImporting": "__MISSING__:Importing…",
|
||||
"zedImportSuccess": "Importováno {credentials} pověření z Zed pro {providers} poskytovatele(ů)",
|
||||
"zedImporting": "Importuji…",
|
||||
"zedNoCredentials": "V klíčence nebyly nalezeny žádné přihlašovací údaje pro Zed",
|
||||
"zedUnsupportedCredentials": "Nalezeno {count} přihlašovacích údajů v klíčence, ale žádný neodpovídá podporovaným poskytovatelům",
|
||||
"zedManualTitle": "Ruční import tokenu",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "Získejte API klíč na ai.360.cn",
|
||||
"agentrouter": "Získejte bezplatný kredit 200 $ na https://agentrouter.org/register — není vyžadována platební karta.",
|
||||
"unorouter": "Vytvořte API klíč na https://unorouter.ai, poté jej sem vložte jako Bearer token.",
|
||||
"agnes": "Získejte API klíč na agnes-ai.com",
|
||||
"aimlapi": "Bezplatný tarif pozastaven (2026) — AI/ML API je nyní pouze pay-as-you-go (min. dobití 20 $); žádné opakující se bezplatné kredity.",
|
||||
"ai21": "Zkušební kredit 10 $ při registraci (platnost 3 měsíce), není vyžadována platební karta",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "Připojte Alibaba Coding Plan pomocí API klíče.",
|
||||
"bedrock": "Nativní integrace Bedrock: zjišťování modelů využívá základní modely Bedrock a profily odvozování, zatímco chat využívá regionální API Bedrock Runtime Converse/ConverseStream.",
|
||||
"anthropic": "Připojte Anthropic pomocí API klíče.",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "Zaregistrujte se a vytvořte API klíč v konzoli Ant Ling API (https://chat.ant-ling.com/open), poté jej sem vložte. OmniRoute směruje chatový provoz na https://api.ant-ling.com/v1/chat/completions; poskytovatel je kompatibilní s OpenAI a také nabízí rozhraní kompatibilní s Anthropic.",
|
||||
"api-airforce": "Získejte svůj API klíč z https://panel.api.airforce — koncový bod kompatibilní s OpenAI na https://api.airforce/v1",
|
||||
"arcee-ai": "Získejte API klíč na arcee.ai",
|
||||
"azure-ai": "Foundry používá rozhraní OpenAI v1 s názvy nasazení jako modely. OmniRoute normalizuje kořenové URL prostředků na koncové body v1 chat a /models.",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "Bearer API klíč pro bránu Chutes kompatibilní s OpenAI.",
|
||||
"clarifai": "Clarifai zpřístupňuje chat, odpovědi a /models kompatibilní s OpenAI na /v2/ext/openai/v1. Veřejné/komunitní modely obvykle vyžadují PAT; klíče s rozsahem aplikace fungují pouze pro prostředky v rámci dané aplikace.",
|
||||
"cloudflare-ai": "Vyžaduje API token A ID účtu (najdete na dash.cloudflare.com)",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) je kompatibilní s OpenAI na /v1/openai. OmniRoute prozkoumává /v1/openai/models a směruje chatový provoz na /v1/openai/chat/completions. Používá aktuální hostitele clovastudio.stream.ntruss.com — zastaralý koncový bod clovastudio.apigw.ntruss.com bude vyřazen.",
|
||||
"codestral": "Připojte Codestral pomocí API klíče.",
|
||||
"cohere": "Bezplatná zkušební verze: 1 000 volání API/měsíc pro testování, není vyžadována platební karta",
|
||||
"command-code": "Vytvořte nebo zkopírujte API klíč z Command Code a poté jej sem vložte jako Bearer token.",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "Brána modelů watsonx zpřístupňuje koncové body /chat/completions a /models kompatibilní s OpenAI pod /ml/gateway/v1.",
|
||||
"ideogram": "Získejte API klíč na ideogram.ai/docs/api",
|
||||
"iflytek": "Získejte API klíč na console.xfyun.cn",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs je kompatibilní s OpenAI na https://api.inceptionlabs.ai/v1. mercury-2 je první difuzní LLM (dLLM) v katalogu — 5-10x rychlejší generace než srovnatelné autoregresivní modely, s voláním nástrojů, json_mode a strukturovanými výstupy.",
|
||||
"inference-net": "Bezplatný kredit 25 $ při registraci plus možnost získat výzkumné granty",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "Bezplatná měsíční kvóta ~1M vstupních / 3M výstupních tokenů (~10 RPM)",
|
||||
"jina-ai": "Bearer API klíč pro Jina AI rerank API.",
|
||||
"jina-reader": "Připojte Jina Reader pomocí API klíče.",
|
||||
"kenari": "Kenari zpřístupňuje koncový bod pro doplňování chatu kompatibilní s OpenAI na adrese https://kenari.id/v1/chat/completions a také živý katalog /v1/models zahrnující modely Claude, GPT, DeepSeek, GLM, Kimi a další. OmniRoute používá protokol OpenAI a vypisuje modely prostřednictvím passthrough.",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "Připojte Perplexity pomocí API klíče.",
|
||||
"piapi": "Připojte PiAPI pomocí API klíče.",
|
||||
"pioneer": "Bezplatný kredit na používání ve výši 75 $ — není vyžadována platební karta",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo je kompatibilní s OpenAI na https://api.platform.preferredai.jp/v1. Vytvořeno společností Preferred Networks a optimalizováno pro japonštinu. Dokumentace je převážně v japonštině.",
|
||||
"poe": "Poe poskytuje chat a odpovědi kompatibilní s OpenAI na adrese https://api.poe.com/v1, s ověřenou kontrolou zůstatku na /usage/current_balance.",
|
||||
"pollinations": "Bezplatná úroveň bez klíče: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning. Prémiové modely (claude, gemini, midijourney) vyžadují API klíč Pollinations z enter.pollinations.ai.",
|
||||
"publicai": "Vyžaduje API klíč — jednorázový kredit při registraci, poté placené",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "Generování videa v Runway je založeno na úlohách. OmniRoute odesílá úlohy typu text-na-video nebo obrázek-na-video, dotazuje se na /v1/tasks/[id] a normalizuje hotové video výstupy zpět do odpovědi typu /v1/videos/generations podobné OpenAI.",
|
||||
"sambanova": "Bezplatný kredit 5 $ při registraci (platnost 30 dní), není vyžadována platební karta",
|
||||
"sap": "Vyhledávání modelů používá /v2/lm/scenarios/foundation-models/models na AI_API_URL. Požadavky na chat používají deploymentUrl/chat/completions a vyžadují AI-Resource-Group.",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "Sarvam AI je kompatibilní s OpenAI na /v1. OmniRoute prozkoumává /v1/models a směruje chatový provoz na /v1/chat/completions. Modely jsou laděny pro indické jazyky.",
|
||||
"scaleway": "1 milion bezplatných tokenů pro nové účty — v souladu s EU/GDPR (Paříž), Qwen3 235B & Llama 70B",
|
||||
"sensenova": "Získejte API klíč na platform.sensenova.cn",
|
||||
"siliconflow": "Bezplatný kredit 1 $ a trvale bezplatné modely po ověření totožnosti",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "Připojte Together AI pomocí API klíče.",
|
||||
"tokenrouter": "TokenRouter poskytuje koncový bod pro doplňování chatu kompatibilní s OpenAI na adrese https://api.tokenrouter.com/v1/chat/completions a také funkční katalog /v1/models. OmniRoute používá protokol OpenAI.",
|
||||
"topaz": "Připojte Topaz pomocí API klíče.",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "Typhoon je kompatibilní s OpenAI na /v1. Vytvořil SCB 10X (Thajsko); typhoon-v2.5-30b-a3b-instruct je thajsko-první, vícejazyčný model.",
|
||||
"udio": "Vložte session cookie z udio.com (Supabase auth)",
|
||||
"uncloseai": "Není vyžadováno žádné ověření. API přijímá jakýkoli neprázdný řetězec jako klíč pro identifikaci.",
|
||||
"upstage": "Připojte Upstage pomocí API klíče.",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "Bearer API klíč pro Voyage AI embeddings a rerank API.",
|
||||
"wafer": "API klíč z https://wafer.ai",
|
||||
"wandb": "Připojte Weights & Biases Inference pomocí API klíče.",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra je kompatibilní s OpenAI na https://api.writer.com/v1. palmyra-x5 nabízí kontextové okno o velikosti 1M tokenů.",
|
||||
"x5lab": "X5Lab poskytuje koncový bod pro dokončování chatu kompatibilní s OpenAI na adrese https://api.x5lab.dev/v1/chat/completions a také živý katalog /v1/models. OmniRoute používá protokol OpenAI a vypisuje modely prostřednictvím passthrough.",
|
||||
"xai": "Připojte xAI (Grok) pomocí API klíče.",
|
||||
"xiaomi-mimo": "Připojte Xiaomi MiMo pomocí API klíče.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "ByteDance AI chat přes dola.com",
|
||||
"overrideBaseUrlAdvanced": "Pokročilé: přepsat základní URL",
|
||||
"overrideBaseUrlHint": "Pokročilé: nasměrovat tohoto vestavěného poskytovatele na vlastní koncový bod. Ponechte prázdné pro použití výchozího.",
|
||||
"apiProtocolLabel": "API protokol",
|
||||
"apiProtocolDefault": "Kompatibilní s OpenAI (výchozí)",
|
||||
"apiProtocolHint": "Někteří poskytovatelé publikují stejné modely přes více než jeden protokol. Nechte výchozí nastavení, pokud nepotřebujete alternativu.",
|
||||
"bulkAddFormatHintCloudflare": "Jeden klíč na řádek. Formát: name|accountId|apiKey (Cloudflare account ID + API token).",
|
||||
"lmarenaWebCookieHint": "Otevřete arena.ai, přihlaste se a poté zkopírujte celou hlavičku Cookie ze síťového požadavku (Network request). Zahrňte arena-auth-prod-v1.0 a arena-auth-prod-v1.1 (a další části, pokud jsou přítomny), nejlépe s cf_clearance. Nevkládejte pouze prázdný cookie arena-auth-prod-v1. Volitelně: providerSpecificData.recaptchaV3Token, pokud create-evaluation stále vrací 403.",
|
||||
"kimiOfficialSupporterBadge": "Zakládající přítel",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) je zakládající open source přítel OmniRoute",
|
||||
"cheaperInferenceSupporterBadge": "Přítel open source",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference podporuje OmniRoute jako přítel open source",
|
||||
"kimiPartnerLinkNote": "Partnerský odkaz — podporuje OmniRoute bez jakýchkoli dalších nákladů pro vás",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "Partnerský odkaz — podporuje OmniRoute bez jakýchkoli dalších nákladů pro vás"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Nastavení",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "Saving...",
|
||||
"requestBodyLimitSave": "Save",
|
||||
"requestBodyLimitCurrent": "Current: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "Nepodařilo se načíst nastavení cache",
|
||||
"cacheConfigSaveSuccess": "Nastavení cache uloženo",
|
||||
"cacheConfigSaveFailed": "Nepodařilo se uložit nastavení cache",
|
||||
"modelCatalogTtlWholeNumberError": "Použijte celé číslo",
|
||||
"modelCatalogTtlMinimumError": "Minimum je {min} ms",
|
||||
"modelCatalogTtlMaximumError": "Maximum je {max} ms",
|
||||
"modelCatalogCacheTtl": "TTL mezipaměti katalogu modelů",
|
||||
"modelCatalogCacheTtlDescription": "Jak dlouho jsou odpovědi katalogu modelů cachovány před obnovením",
|
||||
"modelCatalogCacheTtlLabel": "TTL mezipaměti katalogu modelů v milisekundách",
|
||||
"modelCatalogCacheTtlSaving": "Ukládání...",
|
||||
"modelCatalogCacheTtlSave": "Uložit",
|
||||
"modelCatalogCacheTtlCurrent": "Aktuální: {value} ms",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"pricing": "Ceny",
|
||||
"storage": "Skladování",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "Jak funguje stanovování cen",
|
||||
"cacheWrite": "Zápis do mezipaměti",
|
||||
"unsaved": "neuloženo",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "Obnovit výchozí nastavení",
|
||||
"saveProvider": "Uložit poskytovatele",
|
||||
"model": "Model",
|
||||
"models": "modely",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "Nikdy",
|
||||
"compressionLiveZoneTitle": "Živá zóna zarovnaná s mezipamětí",
|
||||
"compressionLiveZoneDesc": "Udržovat komprimovaný prefix konverzace stabilní a zpracovávat pouze nově připojené položky.",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "Vyloučení komprese",
|
||||
"compressionExclusionsDesc": "Model ids nebo vzory poskytovatele/modelu, které nesmí být nikdy komprimovány. `*` je jediný zástupný znak (např. `openai/*`, `*embedding*`). Shodný požadavek prochází beze změny — žádný kompresní engine neběží.",
|
||||
"compressionExclusionsPlaceholder": "Jeden vzor na řádek, např. \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "Uložit",
|
||||
"compressionExclusionsSaved": "Uloženo",
|
||||
"compressionExclusionsCount": "{count, plural, one {# vyloučení} other {# vyloučení}} nakonfigurováno",
|
||||
"compressionExclusionsEmpty": "Žádné vyloučení není nakonfigurováno — každý model/koncový bod je způsobilý pro kompresi (výchozí chování).",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "Jeho zapnutí/vypnutí a úroveň se nastavují v panelu:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Odesílá se do modelu vision předtím, než je extrahovaný popis vložen zpět do původního požadavku.",
|
||||
"visionBridgeTimeoutMs": "Časový limit (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "Maximální počet obrázků na žádost",
|
||||
"modalityBridgeIntro": "Propojte multimodální obsah s textem, než dosáhne modelů pouze pro text. Vize je živá; Zvuk přichází s AudioBridge; Video je na cestě.",
|
||||
"modalityBridgeVisionTab": "Vize",
|
||||
"modalityBridgeAudioTab": "Zvuk",
|
||||
"modalityBridgeVideoTab": "Video",
|
||||
"modalityBridgeSubTabsAria": "Sekce Modality Bridge",
|
||||
"modalityBridgeVisionTitle": "Vision Bridge",
|
||||
"modalityBridgeVisionDesc": "Popište obrázky pomocí modelu pro rozpoznávání obrazu a pokračujte s uživatelovým vybraným textovým modelem.",
|
||||
"modalityBridgeAudioTitle": "Audio Bridge",
|
||||
"modalityBridgeAudioDesc": "Přepište audio pomocí modelu převodu řeči na text, než pokračujete s vybraným textovým modelem.",
|
||||
"modalityBridgeAudioEnabled": "Povolit Audio Bridge",
|
||||
"modalityBridgeAudioEnabledDesc": "Nahraďte audio části přepisy, když cílový model nemůže zpracovat audio.",
|
||||
"modalityBridgeAudioModel": "Model pro převod řeči na text",
|
||||
"modalityBridgeAudioModelAuto": "Auto (první připojený poskytovatel STT)",
|
||||
"modalityBridgeAudioMaxClips": "Maximální počet audio klipů na požadavek",
|
||||
"modalityBridgeMode": "Režim",
|
||||
"modalityBridgeModeAuto": "Auto (doporučeno)",
|
||||
"modalityBridgeModeAutoHint": "Zastaralá heuristika: přesměrovat jednotlivé modely bez přihlašovacích údajů; popsat jinak.",
|
||||
"modalityBridgeModeDescribe": "Vždy popisujte",
|
||||
"modalityBridgeModeDescribeHint": "Model, který jste zvolili, vždy odpovídá; obrázky jsou nahrazeny textovými popisy.",
|
||||
"modalityBridgeModeReroute": "Vždy přesměrovat",
|
||||
"modalityBridgeModeRerouteHint": "Odešlete celou žádost nejlepšímu modelu s vizuálními schopnostmi (přepne na popis, když žádný není použitelný).",
|
||||
"modalityBridgeVisionModel": "Model Vize",
|
||||
"modalityBridgeVisionModelAuto": "Auto (nejlepší dostupné)",
|
||||
"modalityBridgeTaskAware": "Popis zaměřený na úkol",
|
||||
"modalityBridgeTaskAwareDesc": "Zahrňte otázku uživatele jako zaměření, aby model pro vizi popsal, co je důležité, a přepsal viditelný text.",
|
||||
"modalityBridgePrompt": "Popisový prompt",
|
||||
"modalityBridgeAdvanced": "Pokročilé",
|
||||
"modalityBridgeTimeoutMs": "Časový limit (ms)",
|
||||
"modalityBridgeMaxImages": "Max obrázků na požadavek",
|
||||
"modalityBridgeCacheEnabled": "Popisy mezipaměti",
|
||||
"modalityBridgeCacheEnabledDesc": "Znovu použít popisy pro identické obrázky (SHA-256 klíčované, v paměti).",
|
||||
"modalityBridgeCacheTtlMinutes": "Cache TTL (minuty)",
|
||||
"modalityBridgeCacheMaxEntries": "Maximální počet položek v cache",
|
||||
"modalityBridgeStatsBridged": "propojený",
|
||||
"modalityBridgeStatsCacheHits": "cache zásahy",
|
||||
"modalityBridgeStatsFailures": "selhání",
|
||||
"modalityBridgeStatsLastUsed": "naposledy použito",
|
||||
"modalityBridgeStatsNever": "nikdy",
|
||||
"modalityBridgeTestButton": "Test s ukázkovým obrázkem",
|
||||
"modalityBridgeTestRunning": "Testování…",
|
||||
"modalityBridgeTestOk": "Bridge OK — {count} obrázek(ů) popsáno {model}",
|
||||
"modalityBridgeTestReroute": "Most přesměroval požadavek na {model}",
|
||||
"modalityBridgeTestNoop": "Most nebyl aktivován (model může nativně podporovat zrak nebo je most deaktivován)",
|
||||
"modalityBridgeTestError": "Test selhal: {message}",
|
||||
"modalityBridgeAudioTestButton": "Test s ukázkovým zvukem",
|
||||
"modalityBridgeAudioTestRunning": "Testování zvuku…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} klip(y) přepsány pomocí {model}",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge se nepodařilo aktivovat (cílové zařízení může podporovat audio, žádný poskytovatel STT není připojen, nebo je most deaktivován)",
|
||||
"modalityBridgeAudioTestError": "Test zvuku selhal: {message}",
|
||||
"modalityBridgeAudioComingSoon": "Audio most (řeč → text přes /v1/audio/transcriptions) bude součástí další verze. Jeho klíče nastavení jsou již rezervovány.",
|
||||
"modalityBridgeVideoComingSoon": "Video bridging (frame sampling + captioning) je na backlogu — viz problém #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge přesunuto",
|
||||
"modalityBridgeMovedBody": "Nastavení Vision Bridge nyní žije na vyhrazené stránce Modality Bridge.",
|
||||
"modalityBridgeMovedCta": "Otevřít nastavení Modality Bridge",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Použití",
|
||||
"grokExtraUsageCredits": "Další kredit na použití",
|
||||
"grokAutoTopUp": "Automatické dobíjení",
|
||||
"grokAutoTopUpUnavailable": "Nedostupné",
|
||||
"grokAutoTopUpEnabled": "Povoleno",
|
||||
"grokAutoTopUpDisabled": "Zakázáno",
|
||||
"grokAutoTopUpAt": "na",
|
||||
"grokAutoTopUpAdd": "přidat",
|
||||
"grokAutoTopUpMax": "max",
|
||||
"grokAutoTopUpMonth": "měsíc",
|
||||
"grokAdditionalCredits": "Další kredity",
|
||||
"loggerTab": "Zapisovač",
|
||||
"proxyTab": "Proxy",
|
||||
"budgetManagement": "Správa rozpočtu",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "Ověřovací URL",
|
||||
"deviceCodeYourCode": "Váš kód",
|
||||
"deviceCodeWaiting": "Čekání na autorizaci...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "Přihlášení pomocí Google se z této adresy nemůže dokončit",
|
||||
"googleLoopbackWhatHappens": "Google uvolní autorizační kód pouze tehdy, když je <code>{redirectUri}</code> dostupná z prohlížeče, který schvaluje přihlášení. Zde tato adresa směřuje na tento počítač, nikoli na server OmniRoute — takže se obrazovka souhlasu zasekne místo přesměrování a není žádná URL pro zpětné volání, kterou by bylo možné zkopírovat.",
|
||||
"googleLoopbackRecommended": "Doporučeno — spusťte to na svém vlastním počítači a poté vložte výsledek níže:",
|
||||
"googleLoopbackHelperNote": "Otevře místní Google souhlas (kde funguje 127.0.0.1) a vytiskne jednorázový omniroute-cred-v1.… blob. Vložte tento blob do pole Krok 2 níže — přijímá jak blob s pověřením, tak URL zpětného volání.",
|
||||
"googleLoopbackTunnelLabel": "Nebo přesměrujte port řídicího panelu přes SSH a znovu načtěte OmniRoute přes tunel:",
|
||||
"googleLoopbackTunnelNote": "Nahraďte {userPlaceholder} svým SSH uživatelským jménem, nechte terminál otevřený, poté otevřete {localUrl} a znovu se odtud připojte.",
|
||||
"googleLoopbackHeadlessAlt": "Pro plně bezhlavé použití bez jakéhokoli místního zpětného volání, <a>nastavte si vlastní Google OAuth přihlašovací údaje</a> a veřejnou základní URL.",
|
||||
"remoteAccessInfo": "Vzdálený přístup: Vzhledem k tomu, že k OmniRoute přistupujete vzdáleně, po autorizaci se zobrazí chybová stránka (localhost nenalezen). To je normální – stačí zkopírovat celou adresu URL z adresního řádku prohlížeče a vložit ji níže.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "Přihlášení nelze dokončit z této adresy",
|
||||
"loopbackMismatchWhatHappened": "Co se děje",
|
||||
"loopbackMismatchExplanation": "Po schválení přihlášení {providerName} vždy vrátí prohlížeč zpět na <code>{redirectUri}</code>. Tato adresa směřuje na počítač, který tento prohlížeč používá, nikoli na server OmniRoute — takže autorizační kód se nikdy nedostane k OmniRoute a poskytovatel přihlášení selže, aniž by zobrazil chybu.",
|
||||
"loopbackMismatchHowToFix": "Jak to opravit",
|
||||
"loopbackMismatchStep1": "Na tomto počítači otevřete terminál a spusťte SSH tunel k serveru OmniRoute:",
|
||||
"loopbackMismatchStep1Note": "Nahraďte {userPlaceholder} svým uživatelským jménem SSH. Nechte tento terminál otevřený, dokud se připojení neukáže jako aktivní — potřebné jsou oba porty: jeden slouží pro dashboard, druhý přijímá zpětné volání.",
|
||||
"loopbackMismatchStep2": "V tomto prohlížeči znovu otevřete OmniRoute přes tunel:",
|
||||
"loopbackMismatchStep3": "Poté se znovu připojte k {providerName} z nového panelu. Callback nyní dosáhne serveru a přihlášení probíhá normálně.",
|
||||
"loopbackMismatchAlternative": "Žádný přístup přes SSH? Pokud tento poskytovatel nabízí kartu pro import tokenu, připojte se místo toho pomocí tokenu — tato cesta nepoužívá zpětné volání smyčky.",
|
||||
"step1OpenUrl": "Krok 1: Otevřete tuto adresu URL ve svém prohlížeči",
|
||||
"copy": "Kopírovat",
|
||||
"step2PasteCallback": "Krok 2: Sem vložte adresu URL zpětného volání nebo autorizační kód",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "Zdrojový model (nativní pro agenta)",
|
||||
"targetModel": "Cílový model (OmniRoute)",
|
||||
"noMappings": "Nejsou nakonfigurována žádná mapování modelů. Spusťte průvodce nastavením pro automatickou detekci modelů.",
|
||||
"noMappingsDesc": "Žádné mapování modelů zatím není nakonfigurováno. Přidejte mapování pro směrování požadavků agenta přes OmniRoute.",
|
||||
"addMapping": "Přidat mapování",
|
||||
"selectModel": "Vybrat…",
|
||||
"saveMappings": "Uložit mapování",
|
||||
"setupWizard": "Průvodce nastavením",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "Nebyly nalezeny žádné uložené proxy. Nejprve přidejte proxy v Nastavení → Proxy.",
|
||||
"updateProviderFailed": "Nepodařilo se aktualizovat poskytovatele",
|
||||
"providerEnabled": "{provider} povolen",
|
||||
"providerDisabled": "{provider} zakázán"
|
||||
"providerDisabled": "{provider} zakázán",
|
||||
"providerAdded": "{provider} přidán",
|
||||
"add": "Přidat",
|
||||
"manualApiKey": "Použijte ruční API klíč"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "Nebezpečí",
|
||||
"requiresRestart": "Vyžaduje restart",
|
||||
"source": "Zdroj",
|
||||
"ccDiscoveryAliasesEnvWarning": "Aktivní prostřednictvím proměnné prostředí (EXPOSE_CC_DISCOVERY_ALIASES) — toto přepisuje jakýkoli přepínač na panelu níže.",
|
||||
"resetFlag": "Obnovit {label} na výchozí",
|
||||
"reset": "Obnovit",
|
||||
"loadFailed": "Načtení příznaků funkcí se nezdařilo",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "Povolit přístup k síti v sandboxu dovedností."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "Combo Control Center",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "Partnerský odkaz",
|
||||
"dismissAriaLabel": "Zavřít"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "Inzerujte <gateway-alias>/<model> zrcadlové ID na /v1/models pro modely, jejichž kanonický vlastník nemá aktivní pověření, ale pasivní brána s aktivním pověřením je směruje. Upozornění: při globálním povolení přidává katalogové položky pro všechny klienty.",
|
||||
"radarPage": {
|
||||
"title": "Radar katalog",
|
||||
"subtitle": "Bezplatný modelový katalog obohacený komunitními informacemi",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "Deaktiveret",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Diriger budgetudtømte anmodninger til den gratis nød-fallback-udbyder/-model.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Aktivér periodisk ELO-synkronisering fra Arena AI-førertavlen til rangering af modelintelligens.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "Reklamer claude/<provider>/<model> spejl-id'er på /v1/models, så Claude Code gateway modelopdagelse viser ikke-Claude modeller. Advarsel: fordobler katalogposter for alle klienter, når det er aktiveret globalt.",
|
||||
"sidebar": {
|
||||
"home": "Hjem",
|
||||
"dashboard": "Dashboard",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "Combo Studio",
|
||||
"combosLiveSubtitle": "Live routing-kaskade",
|
||||
"compressionStudio": "Compression Studio",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "Udelukkelser",
|
||||
"contextSettingsSubtitle": "Globale standarder",
|
||||
"contextHeadroomSubtitle": "Tabellarisk komprimering",
|
||||
"contextSessionDedupSubtitle": "Deduplikering på tværs af ture",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "Heuristisk beskæring",
|
||||
"contextOmniglyphSubtitle": "Kontekst som billeder",
|
||||
"compressionStudioSubtitle": "Live motorkaskade",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "Per-model/endpoint omgåelse",
|
||||
"chaosConfigSubtitle": "Parallel udførelse med flere modeller",
|
||||
"routingSection": "Routing",
|
||||
"protocolsSection": "Protocols",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "Månedlige gratis token-tildelinger",
|
||||
"freeProviderRankings": "Rangeringer af gratis udbydere",
|
||||
"freeProviderRankingsSubtitle": "Bedste gratis udbydere rangeret efter model-ELO-scorer",
|
||||
"radar": "Radar Katalog",
|
||||
"radarSubtitle": "Samfundsberiget gratis modelkatalog",
|
||||
"costsQuotaShare": "Quota Sharing",
|
||||
"costsPricing": "Pricing",
|
||||
"logsProxy": "Proxy Logs",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAi": "AI Settings",
|
||||
"settingsModalityBridge": "Modality Bro",
|
||||
"settingsSecurity": "Security",
|
||||
"settingsAccessTokens": "Adgangstokens",
|
||||
"settingsFeatureFlags": "Feature Flag",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "Cache",
|
||||
"settingsAuthz": "Authz",
|
||||
"settingsRouting": "Routing",
|
||||
"settingsResilience": "Resilience",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "Runtime",
|
||||
"consoleLogs": "Console Logs",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "Visuel anmodnings tidslinje",
|
||||
"globalRouting": "Global Routing",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "App basics",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "Billede/lyd → tekst fallback for tekst-only modeller",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
"settingsSecuritySubtitle": "Auth and encryption",
|
||||
"settingsAccessTokensSubtitle": "Afgrænsede CLI-tokens til fjerntilstand",
|
||||
"settingsFeatureFlagsSubtitle": "Skift systemfunktioner",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "Modelkatalog og responscache",
|
||||
"settingsSidebar": "Sidebar",
|
||||
"settingsSidebarSubtitle": "Customize sidebar layout",
|
||||
"settingsAuthzSubtitle": "Ruteoversigt og bypass-politik",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "Altid synlig",
|
||||
"groupSeparatorLabel": "Separator",
|
||||
"discovery": "Opdagelse",
|
||||
"discoverySubtitle": "Scan udbydere for gratis adgang",
|
||||
"radar": "Radar Katalog",
|
||||
"radarSubtitle": "Samfundsberiget gratis modelkatalog"
|
||||
"discoverySubtitle": "Scan udbydere for gratis adgang"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "Storage, database, and general instance configuration",
|
||||
"settingsAppearanceDescription": "Theme, branding, and visual customization",
|
||||
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "TTL for modelkatalog cacheposter",
|
||||
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
|
||||
"featureFlags": "Feature Flag",
|
||||
"featureFlagsDescription": "Styresystemfunktioner og eksperimentelle funktioner",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "Billedgenerering",
|
||||
"imageToText": "Billede til tekst",
|
||||
"imageToTextComingSoon": "Det indbyggede Billede til tekst-playground vil være tilgængeligt, når <code>/api/v1/images/understanding</code> er implementeret.",
|
||||
"imageToTextBridgeCta": "Konfigurer Image→Text broen i Modality Bridge indstillinger",
|
||||
"sttBridgeCta": "Konfigurer Speech→Text broen i Modality Bridge indstillinger",
|
||||
"disabled": "Deaktiveret",
|
||||
"videoGeneration": "Videogenerering",
|
||||
"musicGeneration": "Musikgenerering",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "Auto",
|
||||
"always": "Altid"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Sådan aktiveres opdagelse i Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "Reklamer for non-Claude modeller under claude/<provider>/<model> spejl-id'er, så Claude Code's gateway modelopdagelse kan liste dem. Dobbelt katalogposter for alle klienter, når det er aktiveret globalt.",
|
||||
"ccDiscoveryInfoLink": "Åbn Funktionsflag",
|
||||
"ccOnboardingTitle": "settings.json til gateway modelopdagelse",
|
||||
"ccOnboardingCopy": "Kopier",
|
||||
"ccOnboardingCopied": "Kopieret",
|
||||
"ccOnboardingKeyPlaceholder": "<din OmniRoute API-nøgle>",
|
||||
"ccOnboardingWindowNote": "Claude Code antager et 200K kontekstvindue for enhver model-id, den ikke genkender. For en model med et andet reelt vindue, tilføj CLAUDE_CODE_AUTO_COMPACT_WINDOW lige under den, så auto-komprimering ikke aktiveres for tidligt.",
|
||||
"failedSave": "Kunne ikke gemme",
|
||||
"profileSyncTitle": "Automatisk synkronisering af CLI-profil",
|
||||
"profileSyncDescription": "Efter udbydermodeller er synkroniseret, regenereres CLI-værktøjsprofiler automatisk fra det aktive katalog. Deaktiveret som standard — kun profilfiler skrives; den aktive/standardkonfiguration ændres aldrig.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "Ræsonnering om færdigheder og værktøjsbrug",
|
||||
"hermesRoleApproval": "Godkendelse",
|
||||
"hermesRoleApprovalDesc": "Sikkerheds- og godkendelsesbeslutninger",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "MCP server værktøjsopkald",
|
||||
"hermesRoleTitleGeneration": "Titelgenerering",
|
||||
"hermesRoleTitleGenerationDesc": "Generering af sessionstitel",
|
||||
"hermesRoleMemoryQueryRewrite": "Hukommelsesforespørgselsomskrivning",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "Hukommelsessøgning forespørgselsomskrivning",
|
||||
"hermesRoleTtsAudioTags": "TTS Lydmærker",
|
||||
"hermesRoleTtsAudioTagsDesc": "TTS lydtaggenerering",
|
||||
"hermesRoleTriageSpecifier": "Triage Specifier",
|
||||
"hermesRoleTriageSpecifierDesc": "Specifikation for issue- og PR-triage",
|
||||
"hermesRoleKanbanDecomposer": "Kanban Decomposer",
|
||||
"hermesRoleKanbanDecomposerDesc": "Kanban opgave nedbrydning",
|
||||
"hermesRoleProfileDescriber": "Profilbeskriver",
|
||||
"hermesRoleProfileDescriberDesc": "Brugerprofilbeskrivelse",
|
||||
"hermesRoleGoalJudge": "Mål Dommer",
|
||||
"hermesRoleGoalJudgeDesc": "Mål fuldførelsesbedømmelse",
|
||||
"hermesRoleCurator": "Kurator",
|
||||
"hermesRoleCuratorDesc": "Færdigheds- og hukommelseskuratering",
|
||||
"hermesRoleMonitor": "Overvågning",
|
||||
"hermesRoleMonitorDesc": "Baggrundsovervågning",
|
||||
"hermesRoleBackgroundReview": "Baggrundsundersøgelse",
|
||||
"hermesRoleBackgroundReviewDesc": "Baggrundskodegennemgang",
|
||||
"hermesSelectBeforePreview": "Vælg modeller til roller, eller sørg for, at rollerne er indlæst, før du forhåndsviser.",
|
||||
"hermesPreviewFailed": "Kunne ikke generere forhåndsvisning",
|
||||
"hermesSavedTo": "Gemt i {path}",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "Indsæt i:",
|
||||
"copilotReloadInstruction": "Genstart derefter VS Code, og angiv API-nøglen i inputfeltet.",
|
||||
"wireApiChatCompletions": "Chatafslutninger (/chat/afslutninger)",
|
||||
"wireApiResponses": "Responses API (/responses)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "Responses API (/responses)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Combos",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"sectionTitle": "Integrationsoverflade",
|
||||
"sectionDescription": "OpenAI-kompatible API'er og operationelle protokolendepunkter",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "API'er",
|
||||
"tabProtocols": "Protokoller",
|
||||
"tabsAria": "Endepunktsafsnit",
|
||||
"protocolsTitle": "Protokoller",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "Ingen nye modeller blev tilføjet (alle findes allerede).",
|
||||
"importDoneCount": "✓ Færdig! {count, plural, one {# model imported.} other {# models imported.}}",
|
||||
"unexpectedErrorOccurred": "Der opstod en uventet fejl",
|
||||
"getApiKey": "Få API-nøgle",
|
||||
"getApiKeyDescription": "Registrer dig eller tilmeld dig for en API-nøgle",
|
||||
"connectionCountLabel": "{count, plural, one {# connection} other {# connections}}",
|
||||
"messagesPath": "beskeder",
|
||||
"responsesPath": "svar",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "Omskriv oprindelige web_fetch-værktøjskald til OmniRoutes /v1/web/fetch.",
|
||||
"interceptionLoadError": "Kunne ikke indlæse indstillinger for opsnapning: {error}",
|
||||
"interceptionSaveError": "Kunne ikke gemme indstillinger for opsnapning: {error}",
|
||||
"ccAliasSectionTitle": "Eksponer i Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "Reklamer for denne udbyders modeller under claude/<provider>/<model> spejl-id'er, så Claude Codes gateway-modelopdagelse kan liste dem. Slået fra som standard — aktivering af dette fordobler katalogindgange for alle klienter.",
|
||||
"ccAliasProviderLevelLabel": "Udbyder standard",
|
||||
"ccAliasModelOverridesLabel": "Per-model overskrivninger",
|
||||
"ccAliasModelOverrideAriaLabel": "Overskrivning for {modelId}",
|
||||
"ccAliasStateInherit": "Arv",
|
||||
"ccAliasStateOn": "Tændt",
|
||||
"ccAliasStateOff": "Slukket",
|
||||
"ccAliasAddModelPlaceholder": "Model id (f.eks. gpt-4o)",
|
||||
"ccAliasAddModelButton": "Tilføj overskrivning",
|
||||
"ccAliasLoadError": "Fejl ved indlæsning af discovery-alias indstillinger: {error}",
|
||||
"ccAliasSaveError": "Fejl ved gemning af discovery-alias indstilling: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
@@ -5413,8 +5453,8 @@
|
||||
"t3ChatWebCookieHint": "Åbn t3.chat → DevTools → Application → Local Storage → https://t3.chat, kopier 'convex-session-id'. Åbn derefter DevTools → Netværk, kopier den fulde Cookie-header fra enhver chatanmodning. Indsæt begge værdier i felterne nedenfor.",
|
||||
"t3ChatWebCookiePlaceholder": "convex-session-id=abc123...",
|
||||
"grokWebCookieHint": "Grok Web Cookie Hint",
|
||||
"blockClaudeExtraUsageDescription": "__MISSING__:When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.",
|
||||
"blockClaudeExtraUsageLabel": "__MISSING__:Block extra Claude usage",
|
||||
"blockClaudeExtraUsageDescription": "Når det er aktiveret, markerer OmniRoute denne Claude-forbindelse som utilgængelig, så snart brug API'et rapporterer køede ekstra brug, så fallback skifter til en anden forbindelse i stedet for at fortsætte med pay-as-you-go ekstra fakturering.",
|
||||
"blockClaudeExtraUsageLabel": "Bloker ekstra Claude-brug",
|
||||
"disableCoolingDescription": "Skip the transient cooldown so this connection stays eligible even after recoverable errors (terminal states like banned/expired still apply).",
|
||||
"disableCoolingLabel": "Deaktiver afkøling for denne forbindelse",
|
||||
"bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "New-API User ID",
|
||||
"newApiUserIdPlaceholder": "e.g. 12345",
|
||||
"newApiUserIdHint": "AgentRouter New-Api-User header value, used together with the console API key to fetch quota balance.",
|
||||
"newApiAggregatorToggleLabel": "Aggregator Gateway",
|
||||
"newApiAggregatorToggleHint": "Aktivér balancedetektion for New-API / One-API / Sub2API aggregator-noder. Dashboardet vil vise balance-badge, og quota-preflight-routing vil springe over udtømte konti.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "System Access Token til aggregatorens /api/user/self endpoint. Ikke routing API-nøglen.",
|
||||
"newApiAggregatorUserIdHint": "New-Api-User header værdi brugt til at hente aggregator brugerens kvote saldo.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "Kvota Pr. Enhed",
|
||||
"newApiAggregatorQuotaPerUnitHint": "New-API kredit enheder pr. $1 (standard: 500000). Overskriv hvis din aggregator bruger en anden sats.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "Aktivér balancedetektion for New-API / One-API / Sub2API aggregator-kompatible noder",
|
||||
"cpaModeDisabledTitle": "Cpa Mode Disabled Title",
|
||||
"cpaModeEnabledTitle": "Cpa Mode Enabled Title",
|
||||
"customUserAgentHint": "Custom User Agent Hint",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Tag Group Placeholder",
|
||||
"testModel": "Test Model",
|
||||
"testingModel": "Testing Model",
|
||||
"modelTestQuotaTooltip": "Quota opbrugt — nulstilles i morgen eller kræver en opfyldning",
|
||||
"toggleOffShort": "Toggle Off Short",
|
||||
"toggleOnShort": "Toggle On Short",
|
||||
"tokenExpiredBadge": "Token Expired Badge",
|
||||
@@ -5593,13 +5641,13 @@
|
||||
"importGrokAuth": "Importer Grok Build-godkendelse",
|
||||
"zedImportTitle": "Importer fra Zed Keychain",
|
||||
"zedImportDescription": "Find AI-udbyderoplysninger (OpenAI, Anthropic, Google, Mistral, xAI) gemt af Zed IDE i OS-nøgleringen og importer dem som forbindelser. Zed IDE skal være installeret på denne maskine.",
|
||||
"zedImportButton": "__MISSING__:Import from Zed",
|
||||
"zedImportFailed": "__MISSING__:Zed import failed",
|
||||
"zedImportButton": "Importer fra Zed",
|
||||
"zedImportFailed": "Zed import fejlede",
|
||||
"zedImportHint": "Zed Import Hint",
|
||||
"zedImportNetworkError": "Zed Import Network Error",
|
||||
"zedImportNone": "Zed Import None",
|
||||
"zedImportSuccess": "__MISSING__:Imported {credentials} credential(s) from Zed for {providers} provider(s)",
|
||||
"zedImporting": "__MISSING__:Importing…",
|
||||
"zedImportSuccess": "Importerede {credentials} legitimationsoplysninger fra Zed for {providers} udbyder(e)",
|
||||
"zedImporting": "Importer…",
|
||||
"zedNoCredentials": "Ingen Zed-legitimationsoplysninger fundet i nøgleringen",
|
||||
"zedUnsupportedCredentials": "Fandt {count} legitimationsoplysning(er) i nøgleringen, men ingen matchede understøttede udbydere",
|
||||
"zedManualTitle": "Manuel token-import",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "Få API-nøgle på ai.360.cn",
|
||||
"agentrouter": "Få $200 gratis kreditter på https://agentrouter.org/register — intet kreditkort påkrævet.",
|
||||
"unorouter": "Opret en API-nøgle på https://unorouter.ai, og indsæt den derefter her som en Bearer-token.",
|
||||
"agnes": "Få API-nøgle på agnes-ai.com",
|
||||
"aimlapi": "Gratis niveau sat på pause (2026) — AI/ML API er nu kun pay-as-you-go (min. $20 optankning); ingen tilbagevendende gratis kreditter.",
|
||||
"ai21": "$10 prøvekreditter ved tilmelding (gyldig i 3 måneder), intet kreditkort påkrævet",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "Forbind Alibaba Coding Plan med en API-nøgle.",
|
||||
"bedrock": "Nativ Bedrock-integration: modelopdagelse bruger Bedrock-fundamentmodeller og inferensprofiler, mens chat bruger de regionale Bedrock Runtime Converse/ConverseStream-API'er.",
|
||||
"anthropic": "Forbind Anthropic med en API-nøgle.",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "Registrer dig og opret en API-nøgle i Ant Ling API-konsollen (https://chat.ant-ling.com/open), og indsæt den her. OmniRoute dirigerer chattrafik til https://api.ant-ling.com/v1/chat/completions; udbyderen er OpenAI-kompatibel og tilbyder også en Anthropic-kompatibel overflade.",
|
||||
"api-airforce": "Få din API-nøgle fra https://panel.api.airforce — OpenAI-kompatibelt slutpunkt på https://api.airforce/v1",
|
||||
"arcee-ai": "Få API-nøgle på arcee.ai",
|
||||
"azure-ai": "Foundry bruger OpenAI v1-overfladen med implementeringsnavne som modeller. OmniRoute normaliserer rodressource-URL'er til v1-chat- og /models-slutpunkterne.",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "Bearer API-nøgle til den Chutes OpenAI-kompatible gateway.",
|
||||
"clarifai": "Clarifai eksponerer OpenAI-kompatibel chat, svar og /models på /v2/ext/openai/v1. Offentlige/community-modeller kræver typisk en PAT; app-scopede nøgler virker kun for ressourcer i den app.",
|
||||
"cloudflare-ai": "Kræver API-token OG konto-id (findes på dash.cloudflare.com)",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) er OpenAI-kompatibel på /v1/openai. OmniRoute undersøger /v1/openai/models og ruter chattrafik til /v1/openai/chat/completions. Bruger den nuværende clovastudio.stream.ntruss.com vært — den ældre clovastudio.apigw.ntruss.com endpoint bliver udfaset.",
|
||||
"codestral": "Forbind Codestral med en API-nøgle.",
|
||||
"cohere": "Gratis prøveperiode: 1.000 API-kald/måned til test, intet kreditkort påkrævet",
|
||||
"command-code": "Opret eller kopier en API-nøgle fra Command Code, og indsæt den derefter her som et Bearer-token.",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "watsonx-modelgatewayen eksponerer OpenAI-kompatible /chat/completions og /models under /ml/gateway/v1.",
|
||||
"ideogram": "Få API-nøgle på ideogram.ai/docs/api",
|
||||
"iflytek": "Få API-nøgle på console.xfyun.cn",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs er OpenAI-kompatibel på https://api.inceptionlabs.ai/v1. mercury-2 er den første diffusion LLM (dLLM) i katalogen — 5-10x hurtigere generation end sammenlignelige autoregressive modeller, med værktøjsopkald, json_mode og strukturerede output.",
|
||||
"inference-net": "$25 gratis kredit ved tilmelding samt forskningsbevillinger tilgængelige",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "Gratis månedlig kvote ~1M input / 3M output tokens (~10 RPM)",
|
||||
"jina-ai": "Bearer API-nøgle til Jina AI rerank-API'en.",
|
||||
"jina-reader": "Forbind Jina Reader med en API-nøgle.",
|
||||
"kenari": "Kenari eksponerer et OpenAI-kompatibelt chat completions-endpoint på https://kenari.id/v1/chat/completions, plus et live /v1/models-katalog, der dækker Claude, GPT, DeepSeek, GLM, Kimi med flere. OmniRoute bruger OpenAI-protokollen og viser modeller via passthrough.",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "Forbind Perplexity med en API-nøgle.",
|
||||
"piapi": "Forbind PiAPI med en API-nøgle.",
|
||||
"pioneer": "$75 i gratis forbrugskredit — intet kreditkort påkrævet",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo er OpenAI-kompatibel på https://api.platform.preferredai.jp/v1. Bygget af Preferred Networks og optimeret til japansk. Dokumentationen er primært på japansk.",
|
||||
"poe": "Poe eksponerer OpenAI-kompatibel chat og responses på https://api.poe.com/v1 med godkendte saldotjek på /usage/current_balance.",
|
||||
"pollinations": "Gratis nøglefrit niveau: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning. Premium-modeller (claude, gemini, midijourney) kræver en Pollinations API-nøgle fra enter.pollinations.ai.",
|
||||
"publicai": "Kræver en API-nøgle — engangskredit ved tilmelding, derefter betalt",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "Runway-videogenerering er opgavebaseret. OmniRoute indsender tekst-til-video- eller billede-til-video-job, poller /v1/tasks/[id] og normaliserer de færdige videooutput tilbage til det OpenAI-lignende /v1/videos/generations-svar.",
|
||||
"sambanova": "$5 i gratis kredit ved tilmelding (30 dages gyldighed), intet kreditkort påkrævet",
|
||||
"sap": "Modelfindelse bruger /v2/lm/scenarios/foundation-models/models på AI_API_URL. Chatanmodninger bruger deploymentUrl/chat/completions og kræver AI-Resource-Group.",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "Sarvam AI er OpenAI-kompatibel på /v1. OmniRoute undersøger /v1/models og dirigerer chattrafik til /v1/chat/completions. Modellerne er tilpasset til indiske sprog.",
|
||||
"scaleway": "1M gratis tokens til nye konti — EU/GDPR-kompatibel (Paris), Qwen3 235B & Llama 70B",
|
||||
"sensenova": "Hent API-nøgle på platform.sensenova.cn",
|
||||
"siliconflow": "$1 i gratis kredit plus permanent gratis modeller efter identitetsbekræftelse",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "Forbind Together AI med en API-nøgle.",
|
||||
"tokenrouter": "TokenRouter eksponerer et OpenAI-kompatibelt chat completions-slutpunkt på https://api.tokenrouter.com/v1/chat/completions plus et fungerende /v1/models-katalog. OmniRoute bruger OpenAI-protokollen.",
|
||||
"topaz": "Forbind Topaz med en API-nøgle.",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "Typhoon er OpenAI-kompatibel på /v1. Bygget af SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct er en thai-første, flersproget model.",
|
||||
"udio": "Indsæt sessionscookie fra udio.com (Supabase-godkendelse)",
|
||||
"uncloseai": "Ingen godkendelse påkrævet. API'en accepterer enhver ikke-tom streng som nøgle til identifikation.",
|
||||
"upstage": "Forbind Upstage med en API-nøgle.",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "Bearer API-nøgle til Voyage AI-embeddings og rerank-API'er.",
|
||||
"wafer": "API-nøgle fra https://wafer.ai",
|
||||
"wandb": "Forbind Weights & Biases Inference med en API-nøgle.",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra er OpenAI-kompatibel på https://api.writer.com/v1. palmyra-x5 tilbyder et 1M-token kontekstvindue.",
|
||||
"x5lab": "X5Lab eksponerer et OpenAI-kompatibelt chat completions-slutpunkt på https://api.x5lab.dev/v1/chat/completions samt et live /v1/models-katalog. OmniRoute bruger OpenAI-protokollen og viser modeller via passthrough.",
|
||||
"xai": "Forbind xAI (Grok) med en API-nøgle.",
|
||||
"xiaomi-mimo": "Forbind Xiaomi MiMo med en API-nøgle.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "ByteDance AI-chat via dola.com",
|
||||
"overrideBaseUrlAdvanced": "Avanceret: tilsidesæt base-URL",
|
||||
"overrideBaseUrlHint": "Avanceret: peg denne indbyggede udbyder mod et brugerdefineret slutpunkt. Lad feltet stå tomt for at bruge standarden.",
|
||||
"apiProtocolLabel": "API-protokol",
|
||||
"apiProtocolDefault": "OpenAI-kompatibel (standard)",
|
||||
"apiProtocolHint": "Nogle udbydere offentliggør de samme modeller over mere end ét protokol. Lad standarden være, medmindre du har brug for alternativet.",
|
||||
"bulkAddFormatHintCloudflare": "Én nøgle pr. linje. Format: name|accountId|apiKey (Cloudflare-konto-id + API-token).",
|
||||
"lmarenaWebCookieHint": "Åbn arena.ai, log ind, og kopiér derefter den fulde Cookie-header fra en netværksanmodning. Inkluder arena-auth-prod-v1.0 og arena-auth-prod-v1.1 (og yderligere bidder, hvis de findes), helst med cf_clearance. Indsæt ikke kun den tomme arena-auth-prod-v1-cookie. Valgfrit: providerSpecificData.recaptchaV3Token hvis create-evaluation stadig returnerer 403.",
|
||||
"kimiOfficialSupporterBadge": "Grundlæggende ven",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) er OmniRoutes grundlæggende open source-ven",
|
||||
"cheaperInferenceSupporterBadge": "Open source-ven",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference støtter OmniRoute som open source-ven",
|
||||
"kimiPartnerLinkNote": "Partnerlink — understøtter OmniRoute uden ekstra omkostninger for dig",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "Partnerlink — understøtter OmniRoute uden ekstra omkostninger for dig"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Indstillinger",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "Saving...",
|
||||
"requestBodyLimitSave": "Save",
|
||||
"requestBodyLimitCurrent": "Current: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "Kunne ikke indlæse cacheindstillinger",
|
||||
"cacheConfigSaveSuccess": "Cacheindstillinger gemt",
|
||||
"cacheConfigSaveFailed": "Fejl ved gemning af cacheindstillinger",
|
||||
"modelCatalogTtlWholeNumberError": "Brug et helt tal",
|
||||
"modelCatalogTtlMinimumError": "Minimum er {min} ms",
|
||||
"modelCatalogTtlMaximumError": "Maksimum er {max} ms",
|
||||
"modelCatalogCacheTtl": "Model Katalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "Hvor længe caches modelkatalogsvar, før de opdateres",
|
||||
"modelCatalogCacheTtlLabel": "Modelkatalog cache TTL i millisekunder",
|
||||
"modelCatalogCacheTtlSaving": "Gemmer...",
|
||||
"modelCatalogCacheTtlSave": "Gem",
|
||||
"modelCatalogCacheTtlCurrent": "Aktuel: {value} ms",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"pricing": "Prissætning",
|
||||
"storage": "Opbevaring",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "Hvordan prissætning fungerer",
|
||||
"cacheWrite": "Cache skriv",
|
||||
"unsaved": "ikke gemt",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "Nulstil standardindstillinger",
|
||||
"saveProvider": "Gem udbyder",
|
||||
"model": "Model",
|
||||
"models": "modeller",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "Aldrig",
|
||||
"compressionLiveZoneTitle": "Cache-justeret Live Zone",
|
||||
"compressionLiveZoneDesc": "Hold det komprimerede samtale-præfiks stabilt og behandl kun nyligt tilføjede elementer.",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "Komprimeringsundtagelser",
|
||||
"compressionExclusionsDesc": "Model-id'er eller leverandør/model mønstre, der aldrig må komprimeres. `*` er det eneste wildcard (f.eks. `openai/*`, `*embedding*`). En matchende anmodning passerer igennem byte-identisk — ingen komprimeringsmotor kører.",
|
||||
"compressionExclusionsPlaceholder": "Én mønster pr. linje, f.eks. \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "Gem",
|
||||
"compressionExclusionsSaved": "Gemte",
|
||||
"compressionExclusionsCount": "{count, plural, one {# udeladelse} other {# udeladelser}} konfigureret",
|
||||
"compressionExclusionsEmpty": "Ingen udelukkelser konfigureret — hver model/endpoint er berettiget til kompression (standardadfærd).",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "Dets til/fra og niveau indstilles i panelet:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Sendt til vision-modellen før den udtrukne beskrivelse indsættes tilbage i den oprindelige anmodning.",
|
||||
"visionBridgeTimeoutMs": "Timeout (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "Max billeder pr. anmodning",
|
||||
"modalityBridgeIntro": "Bro forbind multimodal indhold til tekst, før det når tekst-only modeller. Vision er live; Lyd ankommer med AudioBridge; Video er på køreplanen.",
|
||||
"modalityBridgeVisionTab": "Vision",
|
||||
"modalityBridgeAudioTab": "Lyd",
|
||||
"modalityBridgeVideoTab": "Video",
|
||||
"modalityBridgeSubTabsAria": "Modality Bridge sektioner",
|
||||
"modalityBridgeVisionTitle": "Vision Bridge",
|
||||
"modalityBridgeVisionDesc": "Beskriv billeder med en visionsmodel og fortsæt med brugerens valgte tekstmodel.",
|
||||
"modalityBridgeAudioTitle": "Audio Bro",
|
||||
"modalityBridgeAudioDesc": "Transskriber lyd med en tale-til-tekst model, før du fortsætter med den valgte tekstmodel.",
|
||||
"modalityBridgeAudioEnabled": "Aktivér Audio Bridge",
|
||||
"modalityBridgeAudioEnabledDesc": "Erstat lyddele med transkriptioner, når målmodellen ikke kan behandle lyd.",
|
||||
"modalityBridgeAudioModel": "Tale-til-tekst model",
|
||||
"modalityBridgeAudioModelAuto": "Auto (første tilsluttede STT-udbyder)",
|
||||
"modalityBridgeAudioMaxClips": "Maksimalt antal lydklip pr. anmodning",
|
||||
"modalityBridgeMode": "Tilstand",
|
||||
"modalityBridgeModeAuto": "Auto (anbefalet)",
|
||||
"modalityBridgeModeAutoHint": "Legacy heuristik: omdiriger individuelle modeller uden legitimationsoplysninger; beskriv ellers.",
|
||||
"modalityBridgeModeDescribe": "Beskriv altid",
|
||||
"modalityBridgeModeDescribeHint": "Den model, du valgte, svarer altid; billeder erstattes af tekstbeskrivelser.",
|
||||
"modalityBridgeModeReroute": "Altid omdirigere",
|
||||
"modalityBridgeModeRerouteHint": "Send hele anmodningen til den bedste vision-kapable model (fald tilbage til beskrivelse, når ingen er brugbar).",
|
||||
"modalityBridgeVisionModel": "Vision model",
|
||||
"modalityBridgeVisionModelAuto": "Auto (bedst tilgængelig)",
|
||||
"modalityBridgeTaskAware": "Opgavebevidst beskrivelse",
|
||||
"modalityBridgeTaskAwareDesc": "Inkluder brugerens spørgsmål som fokus, så visionsmodellen beskriver, hvad der betyder noget, og transkriberer synlig tekst.",
|
||||
"modalityBridgePrompt": "Beskrivelse prompt",
|
||||
"modalityBridgeAdvanced": "Avanceret",
|
||||
"modalityBridgeTimeoutMs": "Timeout (ms)",
|
||||
"modalityBridgeMaxImages": "Maks billeder pr. anmodning",
|
||||
"modalityBridgeCacheEnabled": "Cache beskrivelser",
|
||||
"modalityBridgeCacheEnabledDesc": "Genbrug beskrivelser for identiske billeder (SHA-256 nøgle, i hukommelsen).",
|
||||
"modalityBridgeCacheTtlMinutes": "Cache TTL (minutter)",
|
||||
"modalityBridgeCacheMaxEntries": "Cache maks. poster",
|
||||
"modalityBridgeStatsBridged": "broet",
|
||||
"modalityBridgeStatsCacheHits": "cache hits",
|
||||
"modalityBridgeStatsFailures": "fejl",
|
||||
"modalityBridgeStatsLastUsed": "sidst brugt",
|
||||
"modalityBridgeStatsNever": "aldrig",
|
||||
"modalityBridgeTestButton": "Test med prøvebillede",
|
||||
"modalityBridgeTestRunning": "Testning…",
|
||||
"modalityBridgeTestOk": "Bro OK — {count} billede(r) beskrevet af {model}",
|
||||
"modalityBridgeTestReroute": "Broen omdirigerede anmodningen til {model}",
|
||||
"modalityBridgeTestNoop": "Broen blev ikke aktiveret (modellen kan muligvis understøtte vision nativt, eller broen er deaktiveret)",
|
||||
"modalityBridgeTestError": "Testet mislykkedes: {message}",
|
||||
"modalityBridgeAudioTestButton": "Test med prøveaudio",
|
||||
"modalityBridgeAudioTestRunning": "Tester lyd…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} klip(klip) transskriberet af {model}",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge blev ikke aktiveret (målet kan understøtte lyd, ingen STT-udbyder er tilsluttet, eller broen er deaktiveret)",
|
||||
"modalityBridgeAudioTestError": "Lydtest mislykkedes: {message}",
|
||||
"modalityBridgeAudioComingSoon": "Audio broen (tale → tekst via /v1/audio/transcriptions) leveres i den næste udgivelse. Dens indstillingsnøgler er allerede reserveret.",
|
||||
"modalityBridgeVideoComingSoon": "Video brokering (rammesampling + undertekster) er på backloggen — se problem #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge flyttet",
|
||||
"modalityBridgeMovedBody": "Vision Bridge-indstillinger er nu live på den dedikerede Modality Bridge-side.",
|
||||
"modalityBridgeMovedCta": "Åbn Modality Bridge-indstillinger",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Brug",
|
||||
"grokExtraUsageCredits": "Ekstra Brugs Kreditter",
|
||||
"grokAutoTopUp": "Auto Opladning",
|
||||
"grokAutoTopUpUnavailable": "Utilgængelig",
|
||||
"grokAutoTopUpEnabled": "Aktiveret",
|
||||
"grokAutoTopUpDisabled": "Deaktiveret",
|
||||
"grokAutoTopUpAt": "ved",
|
||||
"grokAutoTopUpAdd": "tilføj",
|
||||
"grokAutoTopUpMax": "maksimum",
|
||||
"grokAutoTopUpMonth": "måned",
|
||||
"grokAdditionalCredits": "Yderligere Krediteringer",
|
||||
"loggerTab": "Logger",
|
||||
"proxyTab": "Fuldmagt",
|
||||
"budgetManagement": "Budgetstyring",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "Bekræftelses-URL",
|
||||
"deviceCodeYourCode": "Din kode",
|
||||
"deviceCodeWaiting": "Venter på godkendelse...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "Google-login kan ikke fuldføres fra denne adresse",
|
||||
"googleLoopbackWhatHappens": "Google frigiver kun autorisationskoden, når <code>{redirectUri}</code> er tilgængelig fra den browser, der godkender login. Her peger den adresse på denne computer, ikke på OmniRoute-serveren — så samtykkeskærmen hænger i stedet for at omdirigere, og der er ingen callback-URL at kopiere.",
|
||||
"googleLoopbackRecommended": "Anbefalet — kør dette på din egen computer, og indsæt derefter resultatet nedenfor:",
|
||||
"googleLoopbackHelperNote": "Det åbner Google-samtykket lokalt (hvor 127.0.0.1 fungerer) og udskriver en en-linjers omniroute-cred-v1.… blob. Indsæt den blob i feltet Trin 2 nedenfor — det accepterer en legitimationsblob samt en callback-URL.",
|
||||
"googleLoopbackTunnelLabel": "Eller videresend dashboard-porten over SSH og genindlæs OmniRoute gennem tunnelen:",
|
||||
"googleLoopbackTunnelNote": "Erstat {userPlaceholder} med dit SSH-brugernavn, hold terminalen åben, åbn derefter {localUrl} og forbind igen derfra.",
|
||||
"googleLoopbackHeadlessAlt": "For fuldt headless brug uden lokale callback overhovedet, <a>konfigurer dine egne Google OAuth legitimationsoplysninger</a> plus en offentlig basis-URL.",
|
||||
"remoteAccessInfo": "Fjernadgang: Da du fjernadgang til OmniRoute, vil du efter godkendelse se en fejlside (lokal vært ikke fundet). Dette er normalt - bare kopier den fulde URL fra din browsers adresselinje og indsæt den nedenfor.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "Log ind kan ikke fuldføres fra denne adresse",
|
||||
"loopbackMismatchWhatHappened": "Hvad sker der",
|
||||
"loopbackMismatchExplanation": "Når du godkender login, sender {providerName} altid browseren tilbage til <code>{redirectUri}</code>. Den adresse peger på computeren, der kører denne browser, ikke på OmniRoute-serveren — så autorisationskoden når aldrig OmniRoute, og udbyderen fejler login uden at vise en fejl.",
|
||||
"loopbackMismatchHowToFix": "Hvordan man løser det",
|
||||
"loopbackMismatchStep1": "På denne computer skal du åbne et terminalvindue og starte en SSH-tunnel til OmniRoute-serveren:",
|
||||
"loopbackMismatchStep1Note": "Erstat {userPlaceholder} med dit SSH-brugernavn. Hold dette terminalvindue åbent, indtil forbindelsen vises som aktiv — begge porte er nødvendige: den ene server dashboardet, den anden modtager callbacken.",
|
||||
"loopbackMismatchStep2": "I denne browser, genåbn OmniRoute gennem tunnelen:",
|
||||
"loopbackMismatchStep3": "Forbind {providerName} igen fra den nye fane. Callback'en når nu serveren, og login'en afsluttes normalt.",
|
||||
"loopbackMismatchAlternative": "Ingen SSH-adgang? Hvis denne udbyder tilbyder en token-import-fane, så forbind med en token i stedet - den sti bruger ikke en loopback-callback.",
|
||||
"step1OpenUrl": "Trin 1: Åbn denne URL i din browser",
|
||||
"copy": "Kopiér",
|
||||
"step2PasteCallback": "Trin 2: Indsæt tilbagekalds-URL eller autorisationskode her",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "Kildemodel (agent-nativ)",
|
||||
"targetModel": "Målmodel (OmniRoute)",
|
||||
"noMappings": "Ingen model-tilknytninger konfigureret. Kør opsætningsguiden for at registrere modeller automatisk.",
|
||||
"noMappingsDesc": "Ingen modelkortlægninger er endnu konfigureret. Tilføj kortlægninger for at rute agentanmodninger gennem OmniRoute.",
|
||||
"addMapping": "Tilføj kortlægning",
|
||||
"selectModel": "Vælg…",
|
||||
"saveMappings": "Gem tilknytninger",
|
||||
"setupWizard": "Opsætningsguide",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "Ingen gemte proxyer fundet. Tilføj proxyer i Indstillinger → Proxy først.",
|
||||
"updateProviderFailed": "Kunne ikke opdatere udbyder",
|
||||
"providerEnabled": "{provider} aktiveret",
|
||||
"providerDisabled": "{provider} deaktiveret"
|
||||
"providerDisabled": "{provider} deaktiveret",
|
||||
"providerAdded": "{provider} tilføjet",
|
||||
"add": "Tilføj",
|
||||
"manualApiKey": "Brug en manuel API-nøgle"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "Fare",
|
||||
"requiresRestart": "Kræver genstart",
|
||||
"source": "Kilde",
|
||||
"ccDiscoveryAliasesEnvWarning": "Aktiv via miljøvariabel (EXPOSE_CC_DISCOVERY_ALIASES) — dette overskriver enhver dashboard-omskifter nedenfor.",
|
||||
"resetFlag": "Nulstil {label} til standard",
|
||||
"reset": "Nulstil",
|
||||
"loadFailed": "Kunne ikke indlæse feature flags",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "Aktivér netværksadgang i skills-sandkassen."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "Combo Kontrolcenter",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "Partnerlink",
|
||||
"dismissAriaLabel": "Afvis"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "Reklamer <gateway-alias>/<model> spejl-id'er på /v1/models for modeller, hvis kanoniske ejer ikke har nogen aktiv legitimationsoplysninger, men en passthrough gateway med aktive legitimationsoplysninger ruter dem. Advarsel: tilføjer katalogposter for alle klienter, når det er aktiveret globalt.",
|
||||
"radarPage": {
|
||||
"title": "Radar Katalog",
|
||||
"subtitle": "Gratis modelkatalog beriget med samfundsintelligens",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "Deaktiviert",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Anfragen mit erschöpftem Budget an den kostenlosen Notfall-Fallback-Anbieter/das Notfall-Fallback-Modell weiterleiten.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Periodischen ELO-Abgleich der Arena AI-Bestenliste für Modell-Intelligenz-Rankings aktivieren.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "Bewerben Sie claude/<provider>/<model> Spiegel-IDs auf /v1/models, damit die Claude Code-Gateway-Modellentdeckung Nicht-Claude-Modelle auflistet. Warnung: Verdoppelt Katalogeinträge für alle Clients, wenn global aktiviert.",
|
||||
"sidebar": {
|
||||
"home": "Zuhause",
|
||||
"dashboard": "Dashboard",
|
||||
@@ -1055,7 +1055,7 @@
|
||||
"combosLive": "Combo Studio",
|
||||
"combosLiveSubtitle": "Live-Routing-Kaskade",
|
||||
"compressionStudio": "Compression Studio",
|
||||
"compressionExclusions": "__MISSING__:Exclusions",
|
||||
"compressionExclusions": "Ausschlüsse",
|
||||
"contextSettingsSubtitle": "Globale Standardeinstellungen",
|
||||
"contextHeadroomSubtitle": "Tabellarische Kompaktierung",
|
||||
"contextSessionDedupSubtitle": "Rundenübergreifende Deduplizierung",
|
||||
@@ -1066,7 +1066,7 @@
|
||||
"contextUltraSubtitle": "Heuristisches Pruning",
|
||||
"contextOmniglyphSubtitle": "Kontext als Bilder",
|
||||
"compressionStudioSubtitle": "Live-Engine-Kaskade",
|
||||
"compressionExclusionsSubtitle": "__MISSING__:Per-model/endpoint bypass",
|
||||
"compressionExclusionsSubtitle": "Pro-Modell/Endpoint-Umgehung",
|
||||
"chaosConfigSubtitle": "Parallele Ausführung mehrerer Modelle",
|
||||
"routingSection": "Routing",
|
||||
"protocolsSection": "Protocols",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "Monatliche kostenlose Token-Guthaben",
|
||||
"freeProviderRankings": "Rankings kostenloser Anbieter",
|
||||
"freeProviderRankingsSubtitle": "Die besten kostenlosen Anbieter, bewertet nach Modell-ELO-Scores",
|
||||
"radar": "Radar-Katalog",
|
||||
"radarSubtitle": "Kostenloser Modellkatalog, angereichert mit Community-Intelligenz",
|
||||
"costsQuotaShare": "Quota Sharing",
|
||||
"costsPricing": "Pricing",
|
||||
"logsProxy": "Proxy Logs",
|
||||
@@ -1104,10 +1106,11 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAi": "AI Settings",
|
||||
"settingsModalityBridge": "Modalitätsbrücke",
|
||||
"settingsSecurity": "Security",
|
||||
"settingsAccessTokens": "Zugriffstoken",
|
||||
"settingsFeatureFlags": "Feature-Flags",
|
||||
"settingsCache": "__MISSING__:Cache",
|
||||
"settingsCache": "Cache",
|
||||
"settingsAuthz": "Authz",
|
||||
"settingsRouting": "Routing",
|
||||
"settingsResilience": "Resilience",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "Runtime",
|
||||
"consoleLogs": "Console Logs",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "Visuelle Anforderungszeitleiste",
|
||||
"globalRouting": "Global Routing",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,13 +1191,14 @@
|
||||
"settingsGeneralSubtitle": "App basics",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "Bild-/Audio-→Text-Fallback für textbasierte Modelle",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
"settingsSecuritySubtitle": "Auth and encryption",
|
||||
"settingsAccessTokensSubtitle": "CLI-Token mit eingeschränktem Gültigkeitsbereich für den Remote-Modus",
|
||||
"settingsFeatureFlagsSubtitle": "Systemfunktionen umschalten",
|
||||
"settingsCacheSubtitle": "__MISSING__:Model catalog and response caching",
|
||||
"settingsCacheSubtitle": "Modellkatalog und Antwort-Cache",
|
||||
"settingsSidebar": "Sidebar",
|
||||
"settingsSidebarSubtitle": "Customize sidebar layout",
|
||||
"settingsAuthzSubtitle": "Routenbestand und Bypass-Richtlinie",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "Immer sichtbar",
|
||||
"groupSeparatorLabel": "Trennlinie",
|
||||
"discovery": "Discovery",
|
||||
"discoverySubtitle": "Anbieter auf kostenlosen Zugang scannen",
|
||||
"radar": "Radar-Katalog",
|
||||
"radarSubtitle": "Kostenloser Modellkatalog, angereichert mit Community-Intelligenz"
|
||||
"discoverySubtitle": "Anbieter auf kostenlosen Zugang scannen"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
@@ -1559,7 +1561,7 @@
|
||||
"settingsGeneralDescription": "Storage, database, and general instance configuration",
|
||||
"settingsAppearanceDescription": "Theme, branding, and visual customization",
|
||||
"settingsAiDescription": "AI behaviors, thinking budgets, vision, and memory settings",
|
||||
"settingsCacheDescription": "__MISSING__:TTL for model catalog cache entries",
|
||||
"settingsCacheDescription": "TTL für Modellkatalog-Cacheeinträge",
|
||||
"settingsSecurityDescription": "Authentication, authorization, and access control settings",
|
||||
"featureFlags": "Feature-Flags",
|
||||
"featureFlagsDescription": "Steuerungssystemfunktionen und experimentelle Funktionen",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "Bildgenerierung",
|
||||
"imageToText": "Bild zu Text",
|
||||
"imageToTextComingSoon": "Der Inline-Bild-zu-Text-Playground wird verfügbar sein, sobald <code>/api/v1/images/understanding</code> implementiert ist.",
|
||||
"imageToTextBridgeCta": "Konfigurieren Sie die Bild→Text-Brücke in den Modality Bridge-Einstellungen",
|
||||
"sttBridgeCta": "Konfigurieren Sie die Speech→Text-Brücke in den Modality Bridge-Einstellungen",
|
||||
"disabled": "Deaktiviert",
|
||||
"videoGeneration": "Videogenerierung",
|
||||
"musicGeneration": "Musikgenerierung",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "Auto",
|
||||
"always": "Immer"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Wie man die Entdeckung in Claude Code aktiviert",
|
||||
"ccDiscoveryInfoTooltip": "Bewerben Sie Nicht-Claude-Modelle unter claude/<provider>/<model> Spiegel-IDs, damit die Gateway-Modellentdeckung von Claude Code sie auflisten kann. Verdoppelt Katalogeinträge für alle Clients, wenn global aktiviert.",
|
||||
"ccDiscoveryInfoLink": "Feature-Flags Öffnen",
|
||||
"ccOnboardingTitle": "settings.json für die Entdeckung des Gateway-Modells",
|
||||
"ccOnboardingCopy": "Kopieren",
|
||||
"ccOnboardingCopied": "Kopiert",
|
||||
"ccOnboardingKeyPlaceholder": "<dein OmniRoute API-Schlüssel>",
|
||||
"ccOnboardingWindowNote": "Claude Code geht von einem Kontextfenster von 200K für jede Modell-ID aus, die es nicht erkennt. Für ein Modell mit einem anderen tatsächlichen Fenster fügen Sie CLAUDE_CODE_AUTO_COMPACT_WINDOW direkt darunter hinzu, damit die automatische Komprimierung nicht zu früh ausgelöst wird.",
|
||||
"failedSave": "Speichern fehlgeschlagen",
|
||||
"profileSyncTitle": "Automatische Synchronisierung von CLI-Profilen",
|
||||
"profileSyncDescription": "Nachdem die Anbietermodelle synchronisiert wurden, werden die CLI-Tool-Profile automatisch aus dem Live-Katalog neu generiert. Standardmäßig deaktiviert — es werden nur Profildateien geschrieben; die aktive/Standardkonfiguration wird nie geändert.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "Reasoning für Skills und Tool-Nutzung",
|
||||
"hermesRoleApproval": "Genehmigung",
|
||||
"hermesRoleApprovalDesc": "Sicherheits- und Genehmigungsentscheidungen",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "MCP-Server-Toolaufrufe",
|
||||
"hermesRoleTitleGeneration": "Titelgenerierung",
|
||||
"hermesRoleTitleGenerationDesc": "Generierung des Sitzungstitels",
|
||||
"hermesRoleMemoryQueryRewrite": "Speicherabfrage Umschreibung",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "Speicherabfrage-Neuschreibung",
|
||||
"hermesRoleTtsAudioTags": "TTS-Audio-Tags",
|
||||
"hermesRoleTtsAudioTagsDesc": "TTS-Audio-Tag-Generierung",
|
||||
"hermesRoleTriageSpecifier": "Triage-Spezifizierer",
|
||||
"hermesRoleTriageSpecifierDesc": "Spezifikation für die Bearbeitung von Issues und PRs",
|
||||
"hermesRoleKanbanDecomposer": "Kanban-Zerleger",
|
||||
"hermesRoleKanbanDecomposerDesc": "Kanban-Aufgabenzerlegung",
|
||||
"hermesRoleProfileDescriber": "Profilbeschreiber",
|
||||
"hermesRoleProfileDescriberDesc": "Benutzerprofilbeschreibung",
|
||||
"hermesRoleGoalJudge": "Zielrichter",
|
||||
"hermesRoleGoalJudgeDesc": "Zielvervollständigung bewerten",
|
||||
"hermesRoleCurator": "Kurator",
|
||||
"hermesRoleCuratorDesc": "Fähigkeits- und Gedächtnis-Kuration",
|
||||
"hermesRoleMonitor": "Monitor",
|
||||
"hermesRoleMonitorDesc": "Hintergrundüberwachung",
|
||||
"hermesRoleBackgroundReview": "Hintergrundüberprüfung",
|
||||
"hermesRoleBackgroundReviewDesc": "Hintergrund-Codeüberprüfung",
|
||||
"hermesSelectBeforePreview": "Wählen Sie Modelle für Rollen aus oder stellen Sie sicher, dass die Rollen geladen sind, bevor Sie eine Vorschau anzeigen.",
|
||||
"hermesPreviewFailed": "Vorschau konnte nicht generiert werden",
|
||||
"hermesSavedTo": "Gespeichert unter {path}",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "Einfügen in:",
|
||||
"copilotReloadInstruction": "Laden Sie dann VS Code neu und legen Sie den API-Schlüssel in der Eingabeaufforderung fest.",
|
||||
"wireApiChatCompletions": "Chat-Abschlüsse (/chat/completions)",
|
||||
"wireApiResponses": "Antwort-API (/responses)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "Antwort-API (/responses)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Kombinationen",
|
||||
@@ -3706,7 +3732,7 @@
|
||||
"modelsCount": "{count, plural, one {# model} other {# models}}",
|
||||
"sectionTitle": "Integrationsoberfläche",
|
||||
"sectionDescription": "OpenAI-kompatible APIs und Betriebsprotokoll-Endpunkte",
|
||||
"tabApis": "__MISSING__:APIs",
|
||||
"tabApis": "APIs",
|
||||
"tabProtocols": "Protokolle",
|
||||
"tabsAria": "Endpunktabschnitte",
|
||||
"protocolsTitle": "Protokolle",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "Es wurden keine neuen Modelle hinzugefügt (alle bereits vorhanden).",
|
||||
"importDoneCount": "✓ Fertig! {count, plural, one {# model imported.} other {# models imported.}}",
|
||||
"unexpectedErrorOccurred": "Es ist ein unerwarteter Fehler aufgetreten",
|
||||
"getApiKey": "API-Schlüssel abrufen",
|
||||
"getApiKeyDescription": "Registrieren oder für einen API-Schlüssel anmelden",
|
||||
"connectionCountLabel": "{count, plural, one {# connection} other {# connections}}",
|
||||
"messagesPath": "Nachrichten",
|
||||
"responsesPath": "Antworten",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "Native web_fetch-Tool-Aufrufe auf /v1/web/fetch von OmniRoute umschreiben.",
|
||||
"interceptionLoadError": "Fehler beim Laden der Interzeptionseinstellungen: {error}",
|
||||
"interceptionSaveError": "Fehler beim Speichern der Interzeptionseinstellungen: {error}",
|
||||
"ccAliasSectionTitle": "Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "Bewerben Sie die Modelle dieses Anbieters unter claude/<provider>/<model> Spiegel-IDs, damit das Gateway-Modellentdeckung von Claude Code sie auflisten kann. Standardmäßig deaktiviert — das Aktivieren verdoppelt die Katalogeinträge für alle Kunden.",
|
||||
"ccAliasProviderLevelLabel": "Anbieter standardmäßig",
|
||||
"ccAliasModelOverridesLabel": "Pro-Modell-Überschreibungen",
|
||||
"ccAliasModelOverrideAriaLabel": "Überschreibung für {modelId}",
|
||||
"ccAliasStateInherit": "Erben",
|
||||
"ccAliasStateOn": "Ein",
|
||||
"ccAliasStateOff": "Aus",
|
||||
"ccAliasAddModelPlaceholder": "Modell-ID (z.B. gpt-4o)",
|
||||
"ccAliasAddModelButton": "Überschreibung hinzufügen",
|
||||
"ccAliasLoadError": "Fehler beim Laden der discovery-alias-Einstellungen: {error}",
|
||||
"ccAliasSaveError": "Fehler beim Speichern der discovery-alias-Einstellung: {error}",
|
||||
"compatUpstreamHeadersLabel": "Extra upstream headers",
|
||||
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
|
||||
"compatUpstreamHeaderName": "Header name",
|
||||
@@ -5413,8 +5453,8 @@
|
||||
"t3ChatWebCookieHint": "Öffnen Sie t3.chat → DevTools → Anwendung → Lokaler Speicher → https://t3.chat, kopieren Sie „convex-session-id“. Öffnen Sie dann DevTools → Netzwerk und kopieren Sie den vollständigen Cookie-Header aus jeder Chat-Anfrage. Fügen Sie beide Werte in die Felder unten ein.",
|
||||
"t3ChatWebCookiePlaceholder": "convex-session-id=abc123...",
|
||||
"grokWebCookieHint": "Grok Web Cookie Hint",
|
||||
"blockClaudeExtraUsageDescription": "__MISSING__:When enabled, OmniRoute marks this Claude connection unavailable as soon as the usage API reports queued extra usage, so fallback switches to another connection instead of continuing on pay-as-you-go extra billing.",
|
||||
"blockClaudeExtraUsageLabel": "__MISSING__:Block extra Claude usage",
|
||||
"blockClaudeExtraUsageDescription": "Wenn aktiviert, markiert OmniRoute diese Claude-Verbindung als nicht verfügbar, sobald die Usage-API gemeldete zusätzliche Nutzung in der Warteschlange anzeigt, sodass der Fallback auf eine andere Verbindung umschaltet, anstatt mit der Abrechnung für zusätzliche Nutzung nach dem Pay-as-you-go-Prinzip fortzufahren.",
|
||||
"blockClaudeExtraUsageLabel": "Blockiere zusätzliche Claude-Nutzung",
|
||||
"disableCoolingDescription": "Vorübergehende Abklingzeit überspringen, damit diese Verbindung auch nach behebbaren Fehlern verfügbar bleibt (endgültige Zustände wie gesperrt/abgelaufen gelten weiterhin).",
|
||||
"disableCoolingLabel": "Abklingzeit für diese Verbindung deaktivieren",
|
||||
"bulkPasteAdded": "{count, plural, one {1 key added} other {# keys added}}",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "New-API-Benutzer-ID",
|
||||
"newApiUserIdPlaceholder": "z. B. 12345",
|
||||
"newApiUserIdHint": "Wert des New-Api-User-Headers von AgentRouter, zusammen mit dem Konsolen-API-Schlüssel verwendet, um das Kontingentguthaben abzurufen.",
|
||||
"newApiAggregatorToggleLabel": "Aggregator-Gateway",
|
||||
"newApiAggregatorToggleHint": "Aktivieren Sie die Saldenüberwachung für New-API / One-API / Sub2API-Aggregator-Knoten. Das Dashboard zeigt das Saldenabzeichen an und die Quoten-Vorfeld-Routing überspringt erschöpfte Konten.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "Systemzugangstoken für den Endpunkt /api/user/self des Aggregators. Nicht den Routing-API-Schlüssel.",
|
||||
"newApiAggregatorUserIdHint": "Neuer-Api-Benutzer-Headerwert, der verwendet wird, um das Kontingentguthaben des Aggregator-Benutzers abzurufen.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "Kontingent Pro Einheit",
|
||||
"newApiAggregatorQuotaPerUnitHint": "Neue-API-Gutschriften pro $1 (Standard: 500000). Überschreiben, wenn Ihr Aggregator einen anderen Satz verwendet.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "Aktivieren Sie die Balancerkennung für New-API / One-API / Sub2API-Aggregator-kompatible Knoten",
|
||||
"cpaModeDisabledTitle": "Cpa Mode Disabled Title",
|
||||
"cpaModeEnabledTitle": "Cpa Mode Enabled Title",
|
||||
"customUserAgentHint": "Custom User Agent Hint",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Tag Group Placeholder",
|
||||
"testModel": "Test Model",
|
||||
"testingModel": "Testing Model",
|
||||
"modelTestQuotaTooltip": "Kontingent erschöpft — wird morgen zurückgesetzt oder benötigt eine Auffüllung",
|
||||
"toggleOffShort": "Toggle Off Short",
|
||||
"toggleOnShort": "Toggle On Short",
|
||||
"tokenExpiredBadge": "Token Expired Badge",
|
||||
@@ -5593,13 +5641,13 @@
|
||||
"importGrokAuth": "Grok Build-Authentifizierung importieren",
|
||||
"zedImportTitle": "Aus Zed-Schlüsselbund importieren",
|
||||
"zedImportDescription": "Von der Zed IDE im Betriebssystem-Schlüsselbund gespeicherte KI-Anbieter-Anmeldedaten (OpenAI, Anthropic, Google, Mistral, xAI) erkennen und als Verbindungen importieren. Die Zed IDE muss auf diesem Computer installiert sein.",
|
||||
"zedImportButton": "__MISSING__:Import from Zed",
|
||||
"zedImportFailed": "__MISSING__:Zed import failed",
|
||||
"zedImportButton": "Importieren von Zed",
|
||||
"zedImportFailed": "Zed-Import fehlgeschlagen",
|
||||
"zedImportHint": "Zed Import Hint",
|
||||
"zedImportNetworkError": "Zed Import Network Error",
|
||||
"zedImportNone": "Zed Import None",
|
||||
"zedImportSuccess": "__MISSING__:Imported {credentials} credential(s) from Zed for {providers} provider(s)",
|
||||
"zedImporting": "__MISSING__:Importing…",
|
||||
"zedImportSuccess": "{credentials} Anmeldeinformationen von Zed für {providers} Anbieter importiert",
|
||||
"zedImporting": "Importiere…",
|
||||
"zedNoCredentials": "Keine Zed-Anmeldedaten im Schlüsselbund gefunden",
|
||||
"zedUnsupportedCredentials": "{count} Schlüsselbund-Anmeldedaten gefunden, aber keine stimmten mit unterstützten Anbietern überein",
|
||||
"zedManualTitle": "Manueller Token-Import",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "API-Schlüssel auf ai.360.cn abrufen",
|
||||
"agentrouter": "Erhalten Sie 200 $ Gratisguthaben unter https://agentrouter.org/register – keine Kreditkarte erforderlich.",
|
||||
"unorouter": "Erstellen Sie einen API-Schlüssel unter https://unorouter.ai und fügen Sie ihn dann hier als Bearer-Token ein.",
|
||||
"agnes": "API-Schlüssel auf agnes-ai.com abrufen",
|
||||
"aimlapi": "Kostenlose Stufe pausiert (2026) – AI/ML API ist jetzt nur noch Pay-as-you-go (mind. 20 $ Aufladung); keine wiederkehrenden Gratisguthaben.",
|
||||
"ai21": "10 $ Testguthaben bei Registrierung (3 Monate gültig), keine Kreditkarte erforderlich",
|
||||
@@ -5745,7 +5794,7 @@
|
||||
"bailian-coding-plan": "Alibaba Coding Plan mit einem API-Schlüssel verbinden.",
|
||||
"bedrock": "Native Bedrock-Integration: Die Modellerkennung nutzt Bedrock-Foundation-Modelle und Inferenzprofile, während der Chat die regionalen Bedrock Runtime Converse/ConverseStream-APIs verwendet.",
|
||||
"anthropic": "Anthropic mit einem API-Schlüssel verbinden.",
|
||||
"ant-ling": "__MISSING__:Register and create an API key at the Ant Ling API console (https://chat.ant-ling.com/open), then paste it here. OmniRoute routes chat traffic to https://api.ant-ling.com/v1/chat/completions; the provider is OpenAI-compatible and also exposes an Anthropic-compatible surface.",
|
||||
"ant-ling": "Registrieren Sie sich und erstellen Sie einen API-Schlüssel in der Ant Ling API-Konsole (https://chat.ant-ling.com/open), und fügen Sie ihn dann hier ein. OmniRoute leitet den Chat-Verkehr an https://api.ant-ling.com/v1/chat/completions weiter; der Anbieter ist OpenAI-kompatibel und bietet auch eine Anthropic-kompatible Oberfläche an.",
|
||||
"api-airforce": "Holen Sie sich Ihren API-Schlüssel von https://panel.api.airforce – OpenAI-kompatibler Endpunkt unter https://api.airforce/v1",
|
||||
"arcee-ai": "API-Schlüssel auf arcee.ai abrufen",
|
||||
"azure-ai": "Foundry verwendet die OpenAI v1-Oberfläche mit Deployment-Namen als Modelle. OmniRoute normalisiert Root-Ressourcen-URLs auf die v1-Chat- und /models-Endpunkte.",
|
||||
@@ -5766,7 +5815,7 @@
|
||||
"chutes": "Bearer-API-Schlüssel für das OpenAI-kompatible Gateway von Chutes.",
|
||||
"clarifai": "Clarifai stellt OpenAI-kompatiblen Chat, Antworten und /models unter /v2/ext/openai/v1 bereit. Öffentliche/Community-Modelle erfordern in der Regel ein PAT; App-bezogene Schlüssel funktionieren nur für Ressourcen innerhalb dieser App.",
|
||||
"cloudflare-ai": "Erfordert API-Token UND Account-ID (zu finden unter dash.cloudflare.com)",
|
||||
"clova-studio": "__MISSING__:CLOVA Studio (HyperCLOVA X) is OpenAI-compatible on /v1/openai. OmniRoute probes /v1/openai/models and routes chat traffic to /v1/openai/chat/completions. Uses the current clovastudio.stream.ntruss.com host — the legacy clovastudio.apigw.ntruss.com endpoint is being deprecated.",
|
||||
"clova-studio": "CLOVA Studio (HyperCLOVA X) ist OpenAI-kompatibel unter /v1/openai. OmniRoute prüft /v1/openai/models und leitet den Chat-Verkehr zu /v1/openai/chat/completions. Verwendet den aktuellen Host clovastudio.stream.ntruss.com — der veraltete Endpunkt clovastudio.apigw.ntruss.com wird eingestellt.",
|
||||
"codestral": "Codestral mit einem API-Schlüssel verbinden.",
|
||||
"cohere": "Kostenlose Testversion: 1.000 API-Aufrufe/Monat zum Testen, keine Kreditkarte erforderlich",
|
||||
"command-code": "Erstellen oder kopieren Sie einen API-Schlüssel von Command Code und fügen Sie ihn hier als Bearer-Token ein.",
|
||||
@@ -5811,9 +5860,9 @@
|
||||
"watsonx": "Das watsonx-Modell-Gateway stellt OpenAI-kompatible /chat/completions und /models unter /ml/gateway/v1 bereit.",
|
||||
"ideogram": "API-Schlüssel unter ideogram.ai/docs/api anfordern",
|
||||
"iflytek": "API-Schlüssel unter console.xfyun.cn anfordern",
|
||||
"inception": "__MISSING__:Inception Labs is OpenAI-compatible at https://api.inceptionlabs.ai/v1. mercury-2 is the first diffusion LLM (dLLM) in the catalog — 5-10x faster generation than comparable autoregressive models, with tool calling, json_mode, and structured outputs.",
|
||||
"inception": "Inception Labs ist OpenAI-kompatibel unter https://api.inceptionlabs.ai/v1. mercury-2 ist das erste Diffusions-LLM (dLLM) im Katalog — 5-10x schnellere Generierung als vergleichbare autoregressive Modelle, mit Toolaufrufen, json_mode und strukturierten Ausgaben.",
|
||||
"inference-net": "25 $ kostenloses Guthaben bei der Registrierung plus Forschungsstipendien verfügbar",
|
||||
"internlm": "__MISSING__:Free monthly quota ~1M input / 3M output tokens (~10 RPM)",
|
||||
"internlm": "Kostenloses monatliches Kontingent ~1M Eingabe / 3M Ausgabe-Token (~10 RPM)",
|
||||
"jina-ai": "Bearer-API-Schlüssel für die Jina AI Rerank-API.",
|
||||
"jina-reader": "Jina Reader mit einem API-Schlüssel verbinden.",
|
||||
"kenari": "Kenari stellt einen OpenAI-kompatiblen Chat-Vervollständigungsendpunkt unter https://kenari.id/v1/chat/completions bereit, sowie einen Live-Katalog unter /v1/models, der Claude, GPT, DeepSeek, GLM, Kimi und weitere abdeckt. OmniRoute verwendet das OpenAI-Protokoll und listet Modelle per Passthrough auf.",
|
||||
@@ -5860,7 +5909,7 @@
|
||||
"perplexity": "Perplexity mit einem API-Schlüssel verbinden.",
|
||||
"piapi": "PiAPI mit einem API-Schlüssel verbinden.",
|
||||
"pioneer": "75 $ kostenloses Nutzungsguthaben — keine Kreditkarte erforderlich",
|
||||
"plamo": "__MISSING__:PLaMo is OpenAI-compatible at https://api.platform.preferredai.jp/v1. Built by Preferred Networks and optimized for Japanese. Docs are primarily in Japanese.",
|
||||
"plamo": "PLaMo ist OpenAI-kompatibel unter https://api.platform.preferredai.jp/v1. Entwickelt von Preferred Networks und optimiert für Japanisch. Die Dokumentation ist hauptsächlich auf Japanisch.",
|
||||
"poe": "Poe stellt OpenAI-kompatible Chat- und Responses-Endpunkte unter https://api.poe.com/v1 bereit, mit authentifizierten Guthabenabfragen auf /usage/current_balance.",
|
||||
"pollinations": "Kostenlose schlüssellose Stufe: openai, openai-fast, openai-large, qwen-coder, mistral, deepseek, grok, gemini-flash-lite-3.1, perplexity-fast, perplexity-reasoning. Premium-Modelle (claude, gemini, midijourney) erfordern einen Pollinations-API-Schlüssel von enter.pollinations.ai.",
|
||||
"publicai": "Erfordert einen API-Schlüssel — einmaliges Startguthaben bei Registrierung, danach kostenpflichtig",
|
||||
@@ -5872,7 +5921,7 @@
|
||||
"runwayml": "Die Runway-Videogenerierung ist aufgabenbasiert. OmniRoute übermittelt Text-to-Video- oder Image-to-Video-Jobs, fragt /v1/tasks/[id] ab und normalisiert die fertigen Videoausgaben zurück in die OpenAI-ähnliche Antwort von /v1/videos/generations.",
|
||||
"sambanova": "5 $ kostenloses Guthaben bei Registrierung (30 Tage Gültigkeit), keine Kreditkarte erforderlich",
|
||||
"sap": "Die Modellerkennung verwendet /v2/lm/scenarios/foundation-models/models auf AI_API_URL. Chat-Anfragen verwenden deploymentUrl/chat/completions und erfordern AI-Resource-Group.",
|
||||
"sarvam": "__MISSING__:Sarvam AI is OpenAI-compatible on /v1. OmniRoute probes /v1/models and routes chat traffic to /v1/chat/completions. Models are tuned for Indic languages.",
|
||||
"sarvam": "Sarvam AI ist OpenAI-kompatibel auf /v1. OmniRoute prüft /v1/models und leitet den Chat-Verkehr zu /v1/chat/completions. Die Modelle sind auf indische Sprachen abgestimmt.",
|
||||
"scaleway": "1 Mio. kostenlose Token für neue Konten — EU-DSGVO-konform (Paris), Qwen3 235B & Llama 70B",
|
||||
"sensenova": "API-Schlüssel unter platform.sensenova.cn abrufen",
|
||||
"siliconflow": "$1 Gratisguthaben plus dauerhaft kostenlose Modelle nach Identitätsprüfung",
|
||||
@@ -5889,7 +5938,7 @@
|
||||
"together": "Verbinden Sie Together AI mit einem API-Schlüssel.",
|
||||
"tokenrouter": "TokenRouter stellt einen OpenAI-kompatiblen Chat-Vervollständigungsendpunkt unter https://api.tokenrouter.com/v1/chat/completions sowie einen funktionierenden Katalog unter /v1/models bereit. OmniRoute verwendet das OpenAI-Protokoll.",
|
||||
"topaz": "Verbinden Sie Topaz mit einem API-Schlüssel.",
|
||||
"typhoon": "__MISSING__:Typhoon is OpenAI-compatible on /v1. Built by SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct is a thai-first, multilingual model.",
|
||||
"typhoon": "Typhoon ist OpenAI-kompatibel auf /v1. Entwickelt von SCB 10X (Thailand); typhoon-v2.5-30b-a3b-instruct ist ein thailändisch-first, mehrsprachiges Modell.",
|
||||
"udio": "Sitzungs-Cookie von udio.com einfügen (Supabase-Authentifizierung)",
|
||||
"uncloseai": "Keine Authentifizierung erforderlich. Die API akzeptiert jede nicht leere Zeichenfolge als Schlüssel zur Identifikation.",
|
||||
"upstage": "Verbinden Sie Upstage mit einem API-Schlüssel.",
|
||||
@@ -5902,7 +5951,7 @@
|
||||
"voyage-ai": "Bearer-API-Schlüssel für Voyage AI-Embeddings und Rerank-APIs.",
|
||||
"wafer": "API-Schlüssel von https://wafer.ai",
|
||||
"wandb": "Verbinden Sie Weights & Biases Inference mit einem API-Schlüssel.",
|
||||
"writer": "__MISSING__:Writer Palmyra is OpenAI-compatible at https://api.writer.com/v1. palmyra-x5 offers a 1M-token context window.",
|
||||
"writer": "Writer Palmyra ist OpenAI-kompatibel unter https://api.writer.com/v1. palmyra-x5 bietet ein 1M-Token-Kontextfenster.",
|
||||
"x5lab": "X5Lab stellt einen OpenAI-kompatiblen Chat-Vervollständigungsendpunkt unter https://api.x5lab.dev/v1/chat/completions sowie einen Live-Katalog unter /v1/models bereit. OmniRoute verwendet das OpenAI-Protokoll und listet Modelle per Passthrough auf.",
|
||||
"xai": "Verbinden Sie xAI (Grok) mit einem API-Schlüssel.",
|
||||
"xiaomi-mimo": "Verbinden Sie Xiaomi MiMo mit einem API-Schlüssel.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "ByteDance KI-Chat über dola.com",
|
||||
"overrideBaseUrlAdvanced": "Erweitert: Basis-URL überschreiben",
|
||||
"overrideBaseUrlHint": "Erweitert: Diesen integrierten Anbieter auf einen benutzerdefinierten Endpunkt verweisen lassen. Leer lassen, um den Standardwert zu verwenden.",
|
||||
"apiProtocolLabel": "API-Protokoll",
|
||||
"apiProtocolDefault": "OpenAI-kompatibel (Standard)",
|
||||
"apiProtocolHint": "Einige Anbieter veröffentlichen dieselben Modelle über mehr als ein Protokoll. Lassen Sie die Standardeinstellung, es sei denn, Sie benötigen die Alternative.",
|
||||
"bulkAddFormatHintCloudflare": "Ein Schlüssel pro Zeile. Format: name|accountId|apiKey (Cloudflare-Konto-ID + API-Token).",
|
||||
"lmarenaWebCookieHint": "Öffnen Sie arena.ai, melden Sie sich an und kopieren Sie dann den vollständigen Cookie-Header aus einer Netzwerkanfrage. Fügen Sie arena-auth-prod-v1.0 und arena-auth-prod-v1.1 (und weitere Chunks, falls vorhanden) hinzu, vorzugsweise mit cf_clearance. Fügen Sie nicht nur das leere arena-auth-prod-v1-Cookie ein. Optional: providerSpecificData.recaptchaV3Token, falls create-evaluation weiterhin 403 zurückgibt.",
|
||||
"kimiOfficialSupporterBadge": "Gründungsfreund",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) ist OmniRoutes Gründungs-Open-Source-Freund",
|
||||
"cheaperInferenceSupporterBadge": "Open-Source-Freund",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference unterstützt OmniRoute als Open-Source-Freund",
|
||||
"kimiPartnerLinkNote": "Partnerlink — unterstützt OmniRoute ohne zusätzliche Kosten für Sie",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "Partnerlink — unterstützt OmniRoute ohne zusätzliche Kosten für Sie"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
@@ -6089,18 +6129,18 @@
|
||||
"requestBodyLimitSaving": "Saving...",
|
||||
"requestBodyLimitSave": "Save",
|
||||
"requestBodyLimitCurrent": "Current: {value}",
|
||||
"cacheConfigLoadFailed": "__MISSING__:Failed to load cache settings",
|
||||
"cacheConfigSaveSuccess": "__MISSING__:Cache settings saved",
|
||||
"cacheConfigSaveFailed": "__MISSING__:Failed to save cache settings",
|
||||
"modelCatalogTtlWholeNumberError": "__MISSING__:Use a whole number",
|
||||
"modelCatalogTtlMinimumError": "__MISSING__:Minimum is {min} ms",
|
||||
"modelCatalogTtlMaximumError": "__MISSING__:Maximum is {max} ms",
|
||||
"modelCatalogCacheTtl": "__MISSING__:Model Catalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "__MISSING__:How long model catalog responses are cached before refreshing",
|
||||
"modelCatalogCacheTtlLabel": "__MISSING__:Model catalog cache TTL in milliseconds",
|
||||
"modelCatalogCacheTtlSaving": "__MISSING__:Saving...",
|
||||
"modelCatalogCacheTtlSave": "__MISSING__:Save",
|
||||
"modelCatalogCacheTtlCurrent": "__MISSING__:Current: {value} ms",
|
||||
"cacheConfigLoadFailed": "Fehler beim Laden der Cache-Einstellungen",
|
||||
"cacheConfigSaveSuccess": "Cache-Einstellungen gespeichert",
|
||||
"cacheConfigSaveFailed": "Fehler beim Speichern der Cache-Einstellungen",
|
||||
"modelCatalogTtlWholeNumberError": "Verwenden Sie eine ganze Zahl",
|
||||
"modelCatalogTtlMinimumError": "Minimum ist {min} ms",
|
||||
"modelCatalogTtlMaximumError": "Maximal sind {max} ms",
|
||||
"modelCatalogCacheTtl": "Modellkatalog Cache TTL",
|
||||
"modelCatalogCacheTtlDescription": "Wie lange werden die Antworten des Modellkatalogs zwischengespeichert, bevor sie aktualisiert werden?",
|
||||
"modelCatalogCacheTtlLabel": "TTL des Modells Katalog Cache in Millisekunden",
|
||||
"modelCatalogCacheTtlSaving": "Speichern...",
|
||||
"modelCatalogCacheTtlSave": "Speichern",
|
||||
"modelCatalogCacheTtlCurrent": "Aktuell: {value} ms",
|
||||
"mitmProxy": "MITM-Proxy",
|
||||
"pricing": "Preise",
|
||||
"storage": "Lagerung",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "So funktioniert die Preisgestaltung",
|
||||
"cacheWrite": "Cache-Schreiben",
|
||||
"unsaved": "nicht gespeichert",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "Standardwerte zurücksetzen",
|
||||
"saveProvider": "Anbieter speichern",
|
||||
"model": "Modell",
|
||||
"models": "Modelle",
|
||||
@@ -6934,13 +6974,13 @@
|
||||
"compressionPreserveSystemNever": "Nie",
|
||||
"compressionLiveZoneTitle": "Cache-ausgerichtete Live Zone",
|
||||
"compressionLiveZoneDesc": "Hält den komprimierten Gesprächspräfix stabil und verarbeitet nur neu angehängte Einträge.",
|
||||
"compressionExclusionsTitle": "__MISSING__:Compression Exclusions",
|
||||
"compressionExclusionsDesc": "__MISSING__:Model ids or provider/model patterns that must never be compressed. `*` is the only wildcard (e.g. `openai/*`, `*embedding*`). A matching request passes through byte-identical — no compression engine runs.",
|
||||
"compressionExclusionsPlaceholder": "__MISSING__:One pattern per line, e.g.\nopenai/text-embedding-3-large\nanthropic/*",
|
||||
"compressionExclusionsSave": "__MISSING__:Save",
|
||||
"compressionExclusionsSaved": "__MISSING__:Saved",
|
||||
"compressionExclusionsCount": "__MISSING__:{count, plural, one {# exclusion} other {# exclusions}} configured",
|
||||
"compressionExclusionsEmpty": "__MISSING__:No exclusions configured — every model/endpoint is eligible for compression (default behavior).",
|
||||
"compressionExclusionsTitle": "Kompression Ausschlüsse",
|
||||
"compressionExclusionsDesc": "Modell-IDs oder Anbieter-/Modellmuster, die niemals komprimiert werden dürfen. `*` ist das einzige Platzhalterzeichen (z. B. `openai/*`, `*embedding*`). Eine übereinstimmende Anfrage wird byte-identisch durchgeleitet — es wird keine Komprimierungsengine ausgeführt.",
|
||||
"compressionExclusionsPlaceholder": "Ein Muster pro Zeile, z.B. \nopenai/text-embedding-3-large \nanthropic/*",
|
||||
"compressionExclusionsSave": "Speichern",
|
||||
"compressionExclusionsSaved": "Gespeichert",
|
||||
"compressionExclusionsCount": "{count, plural, one {# Ausschluss} other {# Ausschlüsse}} konfiguriert",
|
||||
"compressionExclusionsEmpty": "Keine Ausschlüsse konfiguriert — jedes Modell/jeder Endpunkt ist für die Komprimierung berechtigt (Standardverhalten).",
|
||||
"compressionCavemanConfig": "Caveman Engine Configuration",
|
||||
"compressionCavemanConfigDesc": "Fine-tune the rule-based compression engine",
|
||||
"compressionCavemanPanelHint": "Ein/Aus-Status und Stufe werden im Panel festgelegt:",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Wird an das Vision-Modell gesendet, bevor die extrahierte Beschreibung wieder in die ursprüngliche Anfrage eingefügt wird.",
|
||||
"visionBridgeTimeoutMs": "Timeout (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "Maximale Bilder pro Anfrage",
|
||||
"modalityBridgeIntro": "Bringen Sie multimodale Inhalte in Textform, bevor sie textbasierten Modellen zugeführt werden. Vision ist live; Audio kommt mit dem AudioBridge; Video steht auf der Roadmap.",
|
||||
"modalityBridgeVisionTab": "Vision",
|
||||
"modalityBridgeAudioTab": "Audio",
|
||||
"modalityBridgeVideoTab": "Video",
|
||||
"modalityBridgeSubTabsAria": "Modality Bridge-Bereiche",
|
||||
"modalityBridgeVisionTitle": "Vision Bridge",
|
||||
"modalityBridgeVisionDesc": "Beschreibe Bilder mit einem Vision-Modell und fahre mit dem vom Benutzer gewählten Textmodell fort.",
|
||||
"modalityBridgeAudioTitle": "Audio-Brücke",
|
||||
"modalityBridgeAudioDesc": "Transkribiere Audio mit einem Sprach-zu-Text-Modell, bevor du mit dem gewählten Textmodell fortfährst.",
|
||||
"modalityBridgeAudioEnabled": "Audio-Brücke aktivieren",
|
||||
"modalityBridgeAudioEnabledDesc": "Ersetzen Sie Audioabschnitte durch Transkripte, wenn das Zielmodell Audio nicht verarbeiten kann.",
|
||||
"modalityBridgeAudioModel": "Spracherkennungsmodell",
|
||||
"modalityBridgeAudioModelAuto": "Auto (erster verbundener STT-Anbieter)",
|
||||
"modalityBridgeAudioMaxClips": "Maximale Audio-Clips pro Anfrage",
|
||||
"modalityBridgeMode": "Modus",
|
||||
"modalityBridgeModeAuto": "Auto (empfohlen)",
|
||||
"modalityBridgeModeAutoHint": "Legacy-Heuristik: Einzelne Modelle ohne Anmeldeinformationen umleiten; andernfalls beschreiben.",
|
||||
"modalityBridgeModeDescribe": "Immer beschreiben",
|
||||
"modalityBridgeModeDescribeHint": "Das von Ihnen gewählte Modell antwortet immer; Bilder werden durch Textbeschreibungen ersetzt.",
|
||||
"modalityBridgeModeReroute": "Immer umleiten",
|
||||
"modalityBridgeModeRerouteHint": "Sende die gesamte Anfrage an das beste vision-fähige Modell (fällt auf beschreiben zurück, wenn keins verwendbar ist).",
|
||||
"modalityBridgeVisionModel": "Visionsmodell",
|
||||
"modalityBridgeVisionModelAuto": "Auto (beste verfügbare)",
|
||||
"modalityBridgeTaskAware": "Aufgabenbewusste Beschreibung",
|
||||
"modalityBridgeTaskAwareDesc": "Fügen Sie die Frage des Benutzers als Fokus hinzu, damit das Vision-Modell beschreibt, was wichtig ist, und sichtbaren Text transkribiert.",
|
||||
"modalityBridgePrompt": "Beschreibung Aufforderung",
|
||||
"modalityBridgeAdvanced": "Erweitert",
|
||||
"modalityBridgeTimeoutMs": "Zeitüberschreitung (ms)",
|
||||
"modalityBridgeMaxImages": "Maximale Bilder pro Anfrage",
|
||||
"modalityBridgeCacheEnabled": "Cache-Beschreibungen",
|
||||
"modalityBridgeCacheEnabledDesc": "Wiederverwendung von Beschreibungen für identische Bilder (SHA-256-verschlüsselt, im Speicher).",
|
||||
"modalityBridgeCacheTtlMinutes": "Cache TTL (Minuten)",
|
||||
"modalityBridgeCacheMaxEntries": "Cache maximale Einträge",
|
||||
"modalityBridgeStatsBridged": "überbrückt",
|
||||
"modalityBridgeStatsCacheHits": "Cache-Treffer",
|
||||
"modalityBridgeStatsFailures": "Fehler",
|
||||
"modalityBridgeStatsLastUsed": "zuletzt verwendet",
|
||||
"modalityBridgeStatsNever": "nie",
|
||||
"modalityBridgeTestButton": "Testen mit Beispielbild",
|
||||
"modalityBridgeTestRunning": "Testen…",
|
||||
"modalityBridgeTestOk": "Bridge OK — {count} Bild(er) beschrieben von {model}",
|
||||
"modalityBridgeTestReroute": "Der Bridge hat die Anfrage an {model} umgeleitet.",
|
||||
"modalityBridgeTestNoop": "Bridge wurde nicht aktiviert (das Modell unterstützt möglicherweise Vision nativ oder Bridge ist deaktiviert)",
|
||||
"modalityBridgeTestError": "Test fehlgeschlagen: {message}",
|
||||
"modalityBridgeAudioTestButton": "Testen mit Beispielaudio",
|
||||
"modalityBridgeAudioTestRunning": "Audio wird getestet…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} Clip(s) von {model} transkribiert",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge wurde nicht aktiviert (das Ziel unterstützt möglicherweise Audio, kein STT-Anbieter ist verbunden oder die Brücke ist deaktiviert)",
|
||||
"modalityBridgeAudioTestError": "Audiotest fehlgeschlagen: {message}",
|
||||
"modalityBridgeAudioComingSoon": "Die Audio-Brücke (Sprache → Text über /v1/audio/transcriptions) wird in der nächsten Version ausgeliefert. Ihre Einstellungsschlüssel sind bereits reserviert.",
|
||||
"modalityBridgeVideoComingSoon": "Video-Bridging (Frame-Sampling + Untertitelung) steht auf der Warteliste – siehe Issue #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge verschoben",
|
||||
"modalityBridgeMovedBody": "Die Vision Bridge-Einstellungen sind jetzt auf der speziellen Modality Bridge-Seite verfügbar.",
|
||||
"modalityBridgeMovedCta": "Modality Bridge-Einstellungen öffnen",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Nutzung",
|
||||
"grokExtraUsageCredits": "Zusätzliche Nutzungsguthaben",
|
||||
"grokAutoTopUp": "Automatische Aufladung",
|
||||
"grokAutoTopUpUnavailable": "Nicht verfügbar",
|
||||
"grokAutoTopUpEnabled": "Aktiviert",
|
||||
"grokAutoTopUpDisabled": "Deaktiviert",
|
||||
"grokAutoTopUpAt": "bei",
|
||||
"grokAutoTopUpAdd": "hinzufügen",
|
||||
"grokAutoTopUpMax": "max",
|
||||
"grokAutoTopUpMonth": "Monat",
|
||||
"grokAdditionalCredits": "Zusätzliche Anerkennungen",
|
||||
"loggerTab": "Logger",
|
||||
"proxyTab": "Stellvertreter",
|
||||
"budgetManagement": "Budgetverwaltung",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "Verifizierungs-URL",
|
||||
"deviceCodeYourCode": "Ihr Code",
|
||||
"deviceCodeWaiting": "Warten auf Autorisierung...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "Die Google-Anmeldung kann von dieser Adresse aus nicht abgeschlossen werden.",
|
||||
"googleLoopbackWhatHappens": "Google gibt den Autorisierungscode nur einmal frei, wenn <code>{redirectUri}</code> vom Browser, der die Anmeldung genehmigt, erreicht werden kann. Hier zeigt diese Adresse auf diesen Computer und nicht auf den OmniRoute-Server — daher bleibt der Zustimmungsbildschirm hängen, anstatt weiterzuleiten, und es gibt keine Callback-URL zum Kopieren.",
|
||||
"googleLoopbackRecommended": "Empfohlen — führen Sie dies auf Ihrem eigenen Computer aus und fügen Sie das Ergebnis unten ein:",
|
||||
"googleLoopbackHelperNote": "Es öffnet die Google-Zustimmung lokal (wo 127.0.0.1 funktioniert) und druckt einen einzeiligen omniroute-cred-v1.… Blob. Fügen Sie diesen Blob in das Feld Schritt 2 unten ein – es akzeptiert sowohl einen Anmelde-Blob als auch eine Callback-URL.",
|
||||
"googleLoopbackTunnelLabel": "Oder leiten Sie den Dashboard-Port über SSH weiter und laden Sie OmniRoute durch das Tunnel neu:",
|
||||
"googleLoopbackTunnelNote": "Ersetzen Sie {userPlaceholder} durch Ihren SSH-Benutzernamen, halten Sie das Terminal geöffnet, öffnen Sie dann {localUrl} und verbinden Sie sich von dort erneut.",
|
||||
"googleLoopbackHeadlessAlt": "Für die vollständig headless Nutzung ohne lokale Rückrufe, <a>konfigurieren Sie Ihre eigenen Google OAuth-Anmeldeinformationen</a> sowie eine öffentliche Basis-URL.",
|
||||
"remoteAccessInfo": "Fernzugriff: Da Sie aus der Ferne auf OmniRoute zugreifen, wird nach der Autorisierung eine Fehlerseite angezeigt (localhost nicht gefunden). Das ist normal – kopieren Sie einfach die vollständige URL aus der Adressleiste Ihres Browsers und fügen Sie sie unten ein.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "Anmeldung kann von dieser Adresse aus nicht abgeschlossen werden",
|
||||
"loopbackMismatchWhatHappened": "Was passiert gerade",
|
||||
"loopbackMismatchExplanation": "Nachdem Sie die Anmeldung genehmigt haben, sendet {providerName} den Browser immer zurück zu <code>{redirectUri}</code>. Diese Adresse verweist auf den Computer, der diesen Browser ausführt, nicht auf den OmniRoute-Server — daher erreicht der Autorisierungscode OmniRoute nie und der Anbieter schlägt die Anmeldung fehl, ohne einen Fehler anzuzeigen.",
|
||||
"loopbackMismatchHowToFix": "Wie man es behebt",
|
||||
"loopbackMismatchStep1": "Öffnen Sie auf diesem Computer ein Terminal und starten Sie einen SSH-Tunnel zum OmniRoute-Server:",
|
||||
"loopbackMismatchStep1Note": "Ersetzen Sie {userPlaceholder} durch Ihren SSH-Benutzernamen. Lassen Sie dieses Terminal geöffnet, bis die Verbindung als aktiv angezeigt wird – beide Ports werden benötigt: einer dient dem Dashboard, der andere empfängt den Callback.",
|
||||
"loopbackMismatchStep2": "In diesem Browser OmniRoute über das Tunnel erneut öffnen:",
|
||||
"loopbackMismatchStep3": "Verbinden Sie {providerName} dann erneut über den neuen Tab. Der Callback erreicht jetzt den Server und die Anmeldung wird normal abgeschlossen.",
|
||||
"loopbackMismatchAlternative": "Kein SSH-Zugriff? Wenn dieser Anbieter einen Token-Import-Tab anbietet, verbinden Sie sich stattdessen mit einem Token — dieser Pfad verwendet keinen Loopback-Callback.",
|
||||
"step1OpenUrl": "Schritt 1: Öffnen Sie diese URL in Ihrem Browser",
|
||||
"copy": "Kopieren",
|
||||
"step2PasteCallback": "Schritt 2: Fügen Sie hier die Rückruf-URL oder den Autorisierungscode ein",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "Source model (agent native)",
|
||||
"targetModel": "Target model (OmniRoute)",
|
||||
"noMappings": "No model mappings configured. Run setup wizard to auto-detect models.",
|
||||
"noMappingsDesc": "Noch keine Modellzuordnungen konfiguriert. Fügen Sie Zuordnungen hinzu, um Agentenanfragen über OmniRoute zu leiten.",
|
||||
"addMapping": "Mapping hinzufügen",
|
||||
"selectModel": "Select…",
|
||||
"saveMappings": "Save mappings",
|
||||
"setupWizard": "Setup wizard",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "Keine gespeicherten Proxys gefunden. Fügen Sie zuerst Proxys unter Einstellungen → Proxy hinzu.",
|
||||
"updateProviderFailed": "Anbieter konnte nicht aktualisiert werden",
|
||||
"providerEnabled": "{provider} aktiviert",
|
||||
"providerDisabled": "{provider} deaktiviert"
|
||||
"providerDisabled": "{provider} deaktiviert",
|
||||
"providerAdded": "{provider} hinzugefügt",
|
||||
"add": "Hinzufügen",
|
||||
"manualApiKey": "Verwenden Sie einen manuellen API-Schlüssel"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "Gefahr",
|
||||
"requiresRestart": "Erfordert Neustart",
|
||||
"source": "Quelle",
|
||||
"ccDiscoveryAliasesEnvWarning": "Aktiv über Umgebungsvariable (EXPOSE_CC_DISCOVERY_ALIASES) — dies überschreibt jeden Dashboard-Schalter unten.",
|
||||
"resetFlag": "{label} auf Standardwert zurücksetzen",
|
||||
"reset": "Zurücksetzen",
|
||||
"loadFailed": "Feature-Flags konnten nicht geladen werden",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "Netzwerkzugriff in der Skills-Sandbox aktivieren."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "Combo-Kontrollzentrum",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "Partnerlink",
|
||||
"dismissAriaLabel": "Schließen"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "Bewerben Sie <gateway-alias>/<model> Spiegel-IDs auf /v1/models für Modelle, deren kanonischer Eigentümer keine aktiven Anmeldeinformationen hat, aber ein Durchgangsgateway mit aktiven Anmeldeinformationen sie weiterleitet. Warnung: Fügt Katalogeinträge für alle Clients hinzu, wenn global aktiviert.",
|
||||
"radarPage": {
|
||||
"title": "Radar-Katalog",
|
||||
"subtitle": "Kostenloser Modellkatalog, angereichert mit Community-Intelligenz",
|
||||
|
||||
@@ -1106,6 +1106,7 @@
|
||||
"settingsGeneral": "Storage",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAi": "AI Settings",
|
||||
"settingsModalityBridge": "Modality Bridge",
|
||||
"settingsSecurity": "Security",
|
||||
"settingsAccessTokens": "Access Tokens",
|
||||
"settingsFeatureFlags": "Feature Flags",
|
||||
@@ -1190,6 +1191,7 @@
|
||||
"settingsGeneralSubtitle": "Database and backups",
|
||||
"settingsAppearanceSubtitle": "Theme and layout",
|
||||
"settingsAiSubtitle": "AI behavior defaults",
|
||||
"settingsModalityBridgeSubtitle": "Image/audio → text fallback for text-only models",
|
||||
"globalRoutingSubtitle": "Global routing rules",
|
||||
"settingsResilienceSubtitle": "Retries and breakers",
|
||||
"settingsAdvancedSubtitle": "Power user options",
|
||||
@@ -2293,7 +2295,9 @@
|
||||
"suggestedModels": "Suggested models from provider",
|
||||
"imageGeneration": "Image Generation",
|
||||
"imageToText": "Image to Text",
|
||||
"imageToTextComingSoon": "The inline Image-to-Text playground will be available when <code>/api/v1/images/understanding</code> is implemented.",
|
||||
"imageToTextComingSoon": "Modality Bridge can describe images for text-only models now. The inline playground will be available when <code>/api/v1/images/understanding</code> is implemented.",
|
||||
"imageToTextBridgeCta": "Configure the Image→Text bridge in Modality Bridge settings",
|
||||
"sttBridgeCta": "Configure the Speech→Text bridge in Modality Bridge settings",
|
||||
"disabled": "Disabled",
|
||||
"videoGeneration": "Video Generation",
|
||||
"musicGeneration": "Music Generation",
|
||||
@@ -7141,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Sent to the vision model before the extracted description is injected back into the original request.",
|
||||
"visionBridgeTimeoutMs": "Timeout (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "Max Images Per Request",
|
||||
"modalityBridgeIntro": "Bridge multimodal content to text before it reaches text-only models. Vision is live; Audio arrives with the AudioBridge; Video is on the roadmap.",
|
||||
"modalityBridgeVisionTab": "Vision",
|
||||
"modalityBridgeAudioTab": "Audio",
|
||||
"modalityBridgeVideoTab": "Video",
|
||||
"modalityBridgeSubTabsAria": "Modality Bridge sections",
|
||||
"modalityBridgeVisionTitle": "Vision Bridge",
|
||||
"modalityBridgeVisionDesc": "Describe images with a vision model and continue with the user's chosen text model.",
|
||||
"modalityBridgeAudioTitle": "Audio Bridge",
|
||||
"modalityBridgeAudioDesc": "Transcribe audio with a speech-to-text model before continuing with the chosen text model.",
|
||||
"modalityBridgeAudioEnabled": "Enable Audio Bridge",
|
||||
"modalityBridgeAudioEnabledDesc": "Replace audio parts with transcripts when the target model cannot process audio.",
|
||||
"modalityBridgeAudioModel": "Speech-to-text model",
|
||||
"modalityBridgeAudioModelAuto": "Auto (first connected STT provider)",
|
||||
"modalityBridgeAudioMaxClips": "Max audio clips per request",
|
||||
"modalityBridgeMode": "Mode",
|
||||
"modalityBridgeModeAuto": "Auto (recommended)",
|
||||
"modalityBridgeModeAutoHint": "Legacy heuristic: reroute individual models without credentials; describe otherwise.",
|
||||
"modalityBridgeModeDescribe": "Always describe",
|
||||
"modalityBridgeModeDescribeHint": "The model you chose always answers; images are replaced by text descriptions.",
|
||||
"modalityBridgeModeReroute": "Always reroute",
|
||||
"modalityBridgeModeRerouteHint": "Send the whole request to the best vision-capable model (falls back to describe when none is usable).",
|
||||
"modalityBridgeVisionModel": "Vision model",
|
||||
"modalityBridgeVisionModelAuto": "Auto (best available)",
|
||||
"modalityBridgeTaskAware": "Task-aware description",
|
||||
"modalityBridgeTaskAwareDesc": "Include the user's question as focus so the vision model describes what matters and transcribes visible text.",
|
||||
"modalityBridgePrompt": "Description prompt",
|
||||
"modalityBridgeAdvanced": "Advanced",
|
||||
"modalityBridgeTimeoutMs": "Timeout (ms)",
|
||||
"modalityBridgeMaxImages": "Max images per request",
|
||||
"modalityBridgeCacheEnabled": "Cache descriptions",
|
||||
"modalityBridgeCacheEnabledDesc": "Reuse descriptions for identical images (SHA-256 keyed, in-memory).",
|
||||
"modalityBridgeCacheTtlMinutes": "Cache TTL (minutes)",
|
||||
"modalityBridgeCacheMaxEntries": "Cache max entries",
|
||||
"modalityBridgeStatsBridged": "bridged",
|
||||
"modalityBridgeStatsCacheHits": "cache hits",
|
||||
"modalityBridgeStatsFailures": "failures",
|
||||
"modalityBridgeStatsLastUsed": "last used",
|
||||
"modalityBridgeStatsNever": "never",
|
||||
"modalityBridgeTestButton": "Test with sample image",
|
||||
"modalityBridgeTestRunning": "Testing…",
|
||||
"modalityBridgeTestOk": "Bridge OK — {count} image(s) described by {model}",
|
||||
"modalityBridgeTestReroute": "Bridge rerouted the request to {model}",
|
||||
"modalityBridgeTestNoop": "Bridge did not activate (model may support vision natively or bridge is disabled)",
|
||||
"modalityBridgeTestError": "Test failed: {message}",
|
||||
"modalityBridgeAudioTestButton": "Test with sample audio",
|
||||
"modalityBridgeAudioTestRunning": "Testing audio…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} clip(s) transcribed by {model}",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge did not activate (the target may support audio, no STT provider is connected, or the bridge is disabled)",
|
||||
"modalityBridgeAudioTestError": "Audio test failed: {message}",
|
||||
"modalityBridgeAudioComingSoon": "The Audio bridge (speech → text via /v1/audio/transcriptions) ships in the next release. Its settings keys are already reserved.",
|
||||
"modalityBridgeVideoComingSoon": "Video bridging (frame sampling + captioning) is on the backlog — see issue #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge moved",
|
||||
"modalityBridgeMovedBody": "Vision Bridge settings now live in the dedicated Modality Bridge page.",
|
||||
"modalityBridgeMovedCta": "Open Modality Bridge settings",
|
||||
"resilienceMaxBackoffSteps": "Max backoff steps",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker per Provider",
|
||||
"resilienceFailureThreshold": "Failure threshold",
|
||||
@@ -10191,8 +10249,6 @@
|
||||
"copied": "Copied!",
|
||||
"run": "Run",
|
||||
"running": "Running...",
|
||||
"loading": "Loading...",
|
||||
"retry": "Retry",
|
||||
"response": "Response",
|
||||
"tunnel": "Tunnel",
|
||||
"send": "Send",
|
||||
|
||||
@@ -937,7 +937,7 @@
|
||||
"disabled": "Desactivado",
|
||||
"featureFlagOmnirouteEmergencyFallbackDescription": "Enruta las solicitudes que hayan agotado el presupuesto al proveedor/modelo fallback gratuito de emergencia.",
|
||||
"featureFlagArenaEloSyncEnabledDescription": "Activa la sincronización periódica del ELO de la clasificación de Arena AI para los rankings de inteligencia de modelos.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "__MISSING__:Advertise claude/<provider>/<model> mirror ids on /v1/models so Claude Code gateway model discovery lists non-Claude models. Warning: doubles catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeCcDiscoveryAliasesDescription": "Anunciar los ids de espejo claude/<provider>/<model> en /v1/models para que la lista de descubrimiento de modelos del gateway de Claude Code incluya modelos que no son de Claude. Advertencia: duplica las entradas del catálogo para todos los clientes cuando se habilita globalmente.",
|
||||
"sidebar": {
|
||||
"home": "Inicio",
|
||||
"dashboard": "Panel de control",
|
||||
@@ -1094,6 +1094,8 @@
|
||||
"costsFreeTiersSubtitle": "Asignaciones mensuales de tokens gratuitos",
|
||||
"freeProviderRankings": "Ranking de proveedores gratuitos",
|
||||
"freeProviderRankingsSubtitle": "Mejores proveedores gratuitos clasificados por puntuación ELO de modelo",
|
||||
"radar": "Catálogo de Radar",
|
||||
"radarSubtitle": "Catálogo de modelos gratuito enriquecido por la comunidad",
|
||||
"costsQuotaShare": "Compartición de cuotas",
|
||||
"costsPricing": "Precios",
|
||||
"logsProxy": "Logs del proxy",
|
||||
@@ -1104,6 +1106,7 @@
|
||||
"settingsGeneral": "General",
|
||||
"settingsAppearance": "Apariencia",
|
||||
"settingsAi": "Ajustes de IA",
|
||||
"settingsModalityBridge": "Puente de Modalidad",
|
||||
"settingsSecurity": "Seguridad",
|
||||
"settingsAccessTokens": "Tokens de acceso",
|
||||
"settingsFeatureFlags": "Banderas de características",
|
||||
@@ -1119,7 +1122,7 @@
|
||||
"runtime": "Runtime",
|
||||
"consoleLogs": "Logs de consola",
|
||||
"logsTimeline": "Timeline",
|
||||
"logsTimelineSubtitle": "__MISSING__:Visual request timeline",
|
||||
"logsTimelineSubtitle": "Línea de tiempo de solicitudes visuales",
|
||||
"globalRouting": "Enrutamiento global",
|
||||
"mitmProxy": "MITM Proxy",
|
||||
"oneProxy": "1Proxy",
|
||||
@@ -1188,6 +1191,7 @@
|
||||
"settingsGeneralSubtitle": "Base de datos y copias de seguridad",
|
||||
"settingsAppearanceSubtitle": "Tema y diseño",
|
||||
"settingsAiSubtitle": "Valores predeterminados de comportamiento de IA",
|
||||
"settingsModalityBridgeSubtitle": "Fallback de imagen/audio → texto para modelos solo de texto",
|
||||
"globalRoutingSubtitle": "Reglas de enrutamiento global",
|
||||
"settingsResilienceSubtitle": "Reintentos y disyuntores",
|
||||
"settingsAdvancedSubtitle": "Opciones para usuarios avanzados",
|
||||
@@ -1230,9 +1234,7 @@
|
||||
"alwaysVisible": "Siempre visible",
|
||||
"groupSeparatorLabel": "Separador",
|
||||
"discovery": "Descubrimiento",
|
||||
"discoverySubtitle": "Escanear proveedores para acceso gratuito",
|
||||
"radar": "Catálogo de Radar",
|
||||
"radarSubtitle": "Catálogo de modelos gratuito enriquecido por la comunidad"
|
||||
"discoverySubtitle": "Escanear proveedores para acceso gratuito"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "Webhooks",
|
||||
@@ -2294,6 +2296,8 @@
|
||||
"imageGeneration": "Generación de imágenes",
|
||||
"imageToText": "Imagen a texto",
|
||||
"imageToTextComingSoon": "El playground integrado de imagen a texto estará disponible cuando se implemente <code>/api/v1/images/understanding</code>.",
|
||||
"imageToTextBridgeCta": "Configura el puente de Imagen→Texto en la configuración del Puente de Modalidad",
|
||||
"sttBridgeCta": "Configura el puente de Voz→Texto en la configuración del Puente de Modalidad",
|
||||
"disabled": "Desactivado",
|
||||
"videoGeneration": "Generación de vídeo",
|
||||
"musicGeneration": "Generación de música",
|
||||
@@ -2513,6 +2517,14 @@
|
||||
"auto": "Automático",
|
||||
"always": "Siempre"
|
||||
},
|
||||
"ccDiscoveryInfoButton": "Cómo habilitar el descubrimiento en Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "Anunciar modelos no Claude bajo claude/<provider>/<model> IDs de espejo para que el descubrimiento de modelos de la puerta de enlace de Claude Code pueda listarlos. Duplica las entradas del catálogo para todos los clientes cuando está habilitado globalmente.",
|
||||
"ccDiscoveryInfoLink": "Abrir Banderas de Características",
|
||||
"ccOnboardingTitle": "settings.json para el descubrimiento del modelo de gateway",
|
||||
"ccOnboardingCopy": "Copiar",
|
||||
"ccOnboardingCopied": "Copiado",
|
||||
"ccOnboardingKeyPlaceholder": "<tu clave API de OmniRoute>",
|
||||
"ccOnboardingWindowNote": "Claude Code asume una ventana de contexto de 200K para cualquier ID de modelo que no reconozca. Para un modelo con una ventana real diferente, añade CLAUDE_CODE_AUTO_COMPACT_WINDOW justo debajo para que la auto-compresión no se active demasiado pronto.",
|
||||
"failedSave": "No se pudo guardar",
|
||||
"profileSyncTitle": "Sincronización automática de perfiles CLI",
|
||||
"profileSyncDescription": "Una vez sincronizados los modelos de los proveedores, regenera automáticamente los perfiles de las herramientas CLI a partir del catálogo en vivo. Desactivado por defecto: solo se escriben archivos de perfil; la configuración activa/predeterminada nunca se modifica.",
|
||||
@@ -2914,6 +2926,28 @@
|
||||
"hermesRoleSkillsHubDesc": "Razonamiento de habilidades y uso de herramientas",
|
||||
"hermesRoleApproval": "Aprobación",
|
||||
"hermesRoleApprovalDesc": "Decisiones de seguridad y aprobación",
|
||||
"hermesRoleMcp": "MCP",
|
||||
"hermesRoleMcpDesc": "Llamadas a la herramienta del servidor MCP",
|
||||
"hermesRoleTitleGeneration": "Generación de Títulos",
|
||||
"hermesRoleTitleGenerationDesc": "Generación de título de sesión",
|
||||
"hermesRoleMemoryQueryRewrite": "Reescritura de Consulta de Memoria",
|
||||
"hermesRoleMemoryQueryRewriteDesc": "Reescritura de consulta de búsqueda en memoria",
|
||||
"hermesRoleTtsAudioTags": "Etiquetas de Audio TTS",
|
||||
"hermesRoleTtsAudioTagsDesc": "Generación de etiquetas de audio TTS",
|
||||
"hermesRoleTriageSpecifier": "Especificador de Triage",
|
||||
"hermesRoleTriageSpecifierDesc": "Especificación de triaje de problemas y PR",
|
||||
"hermesRoleKanbanDecomposer": "Descomponedor Kanban",
|
||||
"hermesRoleKanbanDecomposerDesc": "Descomposición de tareas Kanban",
|
||||
"hermesRoleProfileDescriber": "Describidor de Perfil",
|
||||
"hermesRoleProfileDescriberDesc": "Descripción del perfil del usuario",
|
||||
"hermesRoleGoalJudge": "Juez de Objetivos",
|
||||
"hermesRoleGoalJudgeDesc": "Evaluación de la finalización de objetivos",
|
||||
"hermesRoleCurator": "Curador",
|
||||
"hermesRoleCuratorDesc": "Curación de habilidades y memoria",
|
||||
"hermesRoleMonitor": "Monitor",
|
||||
"hermesRoleMonitorDesc": "Monitoreo en segundo plano",
|
||||
"hermesRoleBackgroundReview": "Revisión de Antecedentes",
|
||||
"hermesRoleBackgroundReviewDesc": "Revisión de código en segundo plano",
|
||||
"hermesSelectBeforePreview": "Seleccione modelos para los roles, o asegúrese de que los roles estén cargados, antes de previsualizar.",
|
||||
"hermesPreviewFailed": "Error al generar la vista previa",
|
||||
"hermesSavedTo": "Guardado en {path}",
|
||||
@@ -2947,15 +2981,7 @@
|
||||
"copilotPasteInto": "Pegar en:",
|
||||
"copilotReloadInstruction": "Luego, recarga VS Code y establece la clave de API en el prompt de entrada.",
|
||||
"wireApiChatCompletions": "Chat Completions (/chat/completions)",
|
||||
"wireApiResponses": "API de respuestas (/responses)",
|
||||
"ccDiscoveryInfoButton": "__MISSING__:How to enable discovery in Claude Code",
|
||||
"ccDiscoveryInfoTooltip": "__MISSING__:Advertise non-Claude models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Doubles catalog entries for all clients when enabled globally.",
|
||||
"ccDiscoveryInfoLink": "__MISSING__:Open Feature Flags",
|
||||
"ccOnboardingTitle": "__MISSING__:settings.json for gateway model discovery",
|
||||
"ccOnboardingCopy": "__MISSING__:Copy",
|
||||
"ccOnboardingCopied": "__MISSING__:Copied",
|
||||
"ccOnboardingKeyPlaceholder": "__MISSING__:<your OmniRoute API key>",
|
||||
"ccOnboardingWindowNote": "__MISSING__:Claude Code assumes a 200K context window for any model id it does not recognize. For a model with a different real window, add CLAUDE_CODE_AUTO_COMPACT_WINDOW just under it so auto-compaction does not fire too early."
|
||||
"wireApiResponses": "API de respuestas (/responses)"
|
||||
},
|
||||
"combos": {
|
||||
"title": "Combos",
|
||||
@@ -5049,6 +5075,8 @@
|
||||
"noNewModelsAddedExisting": "No se ha añadido ningún modelo nuevo (todos ya existen).",
|
||||
"importDoneCount": "✓ ¡Listo! {count, plural, one {# modelo importado.} other {# modelos importados.}}",
|
||||
"unexpectedErrorOccurred": "Ha ocurrido un error inesperado",
|
||||
"getApiKey": "Obtener clave API",
|
||||
"getApiKeyDescription": "Regístrate o crea una cuenta para obtener una clave API",
|
||||
"connectionCountLabel": "{count, plural, one {# conexión} other {# conexiones}}",
|
||||
"messagesPath": "messages",
|
||||
"responsesPath": "responses",
|
||||
@@ -5179,6 +5207,18 @@
|
||||
"interceptFetchHint": "Reescribir las llamadas a la herramienta nativa web_fetch a /v1/web/fetch de OmniRoute.",
|
||||
"interceptionLoadError": "No se pudieron cargar los ajustes de intercepción: {error}",
|
||||
"interceptionSaveError": "No se pudieron guardar los ajustes de intercepción: {error}",
|
||||
"ccAliasSectionTitle": "Exponer en Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "Anunciar los modelos de este proveedor bajo claude/<provider>/<model> IDs de espejo para que el descubrimiento de modelos de la puerta de enlace de Claude Code pueda listarlos. Desactivado por defecto; habilitar esto duplica las entradas del catálogo para todos los clientes.",
|
||||
"ccAliasProviderLevelLabel": "Proveedor predeterminado",
|
||||
"ccAliasModelOverridesLabel": "Sobrescrituras por modelo",
|
||||
"ccAliasModelOverrideAriaLabel": "Sobrescribir para {modelId}",
|
||||
"ccAliasStateInherit": "Heredar",
|
||||
"ccAliasStateOn": "Encendido",
|
||||
"ccAliasStateOff": "Apagar",
|
||||
"ccAliasAddModelPlaceholder": "ID del modelo (p. ej. gpt-4o)",
|
||||
"ccAliasAddModelButton": "Agregar anulación",
|
||||
"ccAliasLoadError": "Error al cargar la configuración de discovery-alias: {error}",
|
||||
"ccAliasSaveError": "Error al guardar la configuración de alias de descubrimiento: {error}",
|
||||
"compatUpstreamHeadersLabel": "Cabeceras upstream adicionales",
|
||||
"compatUpstreamHeadersHint": "Ajuste de privilegios elevados: mismo nivel de confianza que editar las credenciales de la API del proveedor; solo deben usarlo administradores de confianza. Se fusiona después de que OmniRoute añada la autenticación de la clave de API del proveedor. Si una cabecera personalizada usa el mismo nombre que una existente (p. ej., Authorization), tu valor reemplaza completamente la cabecera autogenerada (incluido el token Bearer); el upstream solo ve lo que escribiste, no la clave de los ajustes. Una configuración incorrecta puede causar errores 401 o romper la autenticación upstream. Una fila por cabecera (p. ej., Authentication adicional para algunas pasarelas). Pasa el ratón o enfoca el valor para previsualizar. Se guarda al perder el foco, hacer clic fuera o cerrar este panel.",
|
||||
"compatUpstreamHeaderName": "Nombre de la cabecera",
|
||||
@@ -5453,6 +5493,13 @@
|
||||
"newApiUserIdLabel": "ID de usuario de New-API",
|
||||
"newApiUserIdPlaceholder": "p. ej. 12345",
|
||||
"newApiUserIdHint": "Valor de la cabecera New-Api-User de AgentRouter, usado junto con la clave de API de la consola para consultar el saldo de cuota.",
|
||||
"newApiAggregatorToggleLabel": "Puerta de Enlace del Agregador",
|
||||
"newApiAggregatorToggleHint": "Habilitar la detección de saldo para nodos agregadores New-API / One-API / Sub2API. El panel mostrará la insignia de saldo y el enrutamiento de pre-vuelo de cuota omitirá las cuentas agotadas.",
|
||||
"newApiAggregatorConsoleApiKeyHint": "Token de acceso del sistema para el endpoint /api/user/self del agregador. No es la clave API de enrutamiento.",
|
||||
"newApiAggregatorUserIdHint": "Valor del encabezado New-Api-User utilizado para obtener el saldo de cuota del usuario agregador.",
|
||||
"newApiAggregatorQuotaPerUnitLabel": "Cuota Por Unidad",
|
||||
"newApiAggregatorQuotaPerUnitHint": "Unidades de crédito de New-API por $1 (predeterminado: 500000). Sobrescriba si su agregador utiliza una tasa diferente.",
|
||||
"featureFlagNewApiAggregatorBalanceDescription": "Habilitar la detección de balance para nodos compatibles con New-API / One-API / Sub2API",
|
||||
"cpaModeDisabledTitle": "El modo de compatibilidad de CLIProxyAPI está desactivado",
|
||||
"cpaModeEnabledTitle": "El modo de compatibilidad de CLIProxyAPI está activado",
|
||||
"customUserAgentHint": "Define una cadena de User-Agent personalizada para enviar en las peticiones HTTP.",
|
||||
@@ -5568,6 +5615,7 @@
|
||||
"tagGroupPlaceholder": "Grupo de etiquetas",
|
||||
"testModel": "Probar modelo",
|
||||
"testingModel": "Probando modelo",
|
||||
"modelTestQuotaTooltip": "Cuota agotada — se restablece mañana o necesita una recarga",
|
||||
"toggleOffShort": "Desactivado",
|
||||
"toggleOnShort": "Activado",
|
||||
"tokenExpiredBadge": "Insignia de token caducado",
|
||||
@@ -5737,6 +5785,7 @@
|
||||
"onboardingProviderDescriptions": {
|
||||
"360ai": "Obtén tu clave de API en ai.360.cn",
|
||||
"agentrouter": "Obtén 200 $ en créditos gratuitos en https://agentrouter.org/register; no se requiere tarjeta de crédito.",
|
||||
"unorouter": "Crea una clave API en https://unorouter.ai, luego pégala aquí como un token Bearer.",
|
||||
"agnes": "Obtén tu clave de API en agnes-ai.com",
|
||||
"aimlapi": "Nivel gratuito en pausa (2026). AI/ML API solo funciona bajo demanda (recarga mínima de 20 $); no hay créditos gratuitos recurrentes.",
|
||||
"ai21": "10 $ en créditos de prueba al registrarte (válidos durante 3 meses), no se requiere tarjeta de crédito.",
|
||||
@@ -5976,25 +6025,16 @@
|
||||
"doubaoWebDesc": "Chat de IA de ByteDance vía dola.com",
|
||||
"overrideBaseUrlAdvanced": "Avanzado: sobrescribir URL base",
|
||||
"overrideBaseUrlHint": "Avanzado: apunte este proveedor integrado a un endpoint personalizado. Déjelo en blanco para usar el predeterminado.",
|
||||
"apiProtocolLabel": "Protocolo API",
|
||||
"apiProtocolDefault": "Compatible con OpenAI (predeterminado)",
|
||||
"apiProtocolHint": "Algunos proveedores publican los mismos modelos a través de más de un protocolo. Deja el valor predeterminado a menos que necesites la alternativa.",
|
||||
"bulkAddFormatHintCloudflare": "Una clave por línea. Formato: name|accountId|apiKey (ID de cuenta de Cloudflare + clave de API).",
|
||||
"lmarenaWebCookieHint": "Abra arena.ai, inicie sesión y copie el encabezado Cookie completo de una solicitud de red. Incluya arena-auth-prod-v1.0 y arena-auth-prod-v1.1 (y fragmentos adicionales si los hay), preferiblemente con cf_clearance. No pegue solo la cookie vacía arena-auth-prod-v1. Opcional: providerSpecificData.recaptchaV3Token si create-evaluation sigue devolviendo 403.",
|
||||
"kimiOfficialSupporterBadge": "Colaborador fundador",
|
||||
"kimiOfficialSupporterTooltip": "Kimi (Moonshot AI) es un Open Source Friend fundador de OmniRoute",
|
||||
"cheaperInferenceSupporterBadge": "Amigo del código abierto",
|
||||
"cheaperInferenceSupporterTooltip": "Cheaper Inference apoya a OmniRoute como amigo del código abierto",
|
||||
"kimiPartnerLinkNote": "Enlace de socio: apoya a OmniRoute sin coste adicional para usted",
|
||||
"ccAliasSectionTitle": "__MISSING__:Expose in Claude Code (claude/…)",
|
||||
"ccAliasSectionHint": "__MISSING__:Advertise this provider's models under claude/<provider>/<model> mirror ids so Claude Code's gateway model discovery can list them. Off by default — enabling this doubles catalog entries for all clients.",
|
||||
"ccAliasProviderLevelLabel": "__MISSING__:Provider default",
|
||||
"ccAliasModelOverridesLabel": "__MISSING__:Per-model overrides",
|
||||
"ccAliasModelOverrideAriaLabel": "__MISSING__:Override for {modelId}",
|
||||
"ccAliasStateInherit": "__MISSING__:Inherit",
|
||||
"ccAliasStateOn": "__MISSING__:On",
|
||||
"ccAliasStateOff": "__MISSING__:Off",
|
||||
"ccAliasAddModelPlaceholder": "__MISSING__:Model id (e.g. gpt-4o)",
|
||||
"ccAliasAddModelButton": "__MISSING__:Add override",
|
||||
"ccAliasLoadError": "__MISSING__:Failed to load discovery-alias settings: {error}",
|
||||
"ccAliasSaveError": "__MISSING__:Failed to save discovery-alias setting: {error}"
|
||||
"kimiPartnerLinkNote": "Enlace de socio: apoya a OmniRoute sin coste adicional para usted"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes",
|
||||
@@ -6754,7 +6794,7 @@
|
||||
"howPricingWorks": "Cómo funcionan los precios",
|
||||
"cacheWrite": "Escritura en caché",
|
||||
"unsaved": "sin guardar",
|
||||
"resetDefaults": "__MISSING__:Reset defaults",
|
||||
"resetDefaults": "Restablecer valores predeterminados",
|
||||
"saveProvider": "Guardar proveedor",
|
||||
"model": "Modelo",
|
||||
"models": "Modelos",
|
||||
@@ -7105,6 +7145,60 @@
|
||||
"visionBridgePromptHint": "Se envía al modelo de visión antes de que la descripción extraída se inyecte de nuevo en la solicitud original.",
|
||||
"visionBridgeTimeoutMs": "Tiempo de espera (ms)",
|
||||
"visionBridgeMaxImagesPerRequest": "Máximo de imágenes por solicitud",
|
||||
"modalityBridgeIntro": "Conecte contenido multimodal a texto antes de que llegue a modelos solo de texto. La visión está activa; el audio llega con AudioBridge; el video está en la hoja de ruta.",
|
||||
"modalityBridgeVisionTab": "Visión",
|
||||
"modalityBridgeAudioTab": "Audio",
|
||||
"modalityBridgeVideoTab": "Vídeo",
|
||||
"modalityBridgeSubTabsAria": "Secciones del Puente de Modalidad",
|
||||
"modalityBridgeVisionTitle": "Puente de Visión",
|
||||
"modalityBridgeVisionDesc": "Describe imágenes con un modelo de visión y continúa con el modelo de texto elegido por el usuario.",
|
||||
"modalityBridgeAudioTitle": "Puente de Audio",
|
||||
"modalityBridgeAudioDesc": "Transcribe audio con un modelo de reconocimiento de voz antes de continuar con el modelo de texto elegido.",
|
||||
"modalityBridgeAudioEnabled": "Habilitar Puente de Audio",
|
||||
"modalityBridgeAudioEnabledDesc": "Reemplace las partes de audio con transcripciones cuando el modelo objetivo no pueda procesar audio.",
|
||||
"modalityBridgeAudioModel": "Modelo de texto a voz",
|
||||
"modalityBridgeAudioModelAuto": "Automático (primer proveedor STT conectado)",
|
||||
"modalityBridgeAudioMaxClips": "Máx. clips de audio por solicitud",
|
||||
"modalityBridgeMode": "Modo",
|
||||
"modalityBridgeModeAuto": "Automático (recomendado)",
|
||||
"modalityBridgeModeAutoHint": "Heurística heredada: redirigir modelos individuales sin credenciales; describir de otra manera.",
|
||||
"modalityBridgeModeDescribe": "Siempre describe",
|
||||
"modalityBridgeModeDescribeHint": "El modelo que elegiste siempre responde; las imágenes son reemplazadas por descripciones de texto.",
|
||||
"modalityBridgeModeReroute": "Siempre redirigir",
|
||||
"modalityBridgeModeRerouteHint": "Envía toda la solicitud al mejor modelo capaz de visión (vuelve a describir cuando ninguno sea utilizable).",
|
||||
"modalityBridgeVisionModel": "Modelo de visión",
|
||||
"modalityBridgeVisionModelAuto": "Automático (mejor disponible)",
|
||||
"modalityBridgeTaskAware": "Descripción consciente de la tarea",
|
||||
"modalityBridgeTaskAwareDesc": "Incluya la pregunta del usuario como enfoque para que el modelo de visión describa lo que importa y transcriba el texto visible.",
|
||||
"modalityBridgePrompt": "Descripción del aviso",
|
||||
"modalityBridgeAdvanced": "Avanzado",
|
||||
"modalityBridgeTimeoutMs": "Tiempo de espera (ms)",
|
||||
"modalityBridgeMaxImages": "Máx. imágenes por solicitud",
|
||||
"modalityBridgeCacheEnabled": "Descripciones de caché",
|
||||
"modalityBridgeCacheEnabledDesc": "Reutilizar descripciones para imágenes idénticas (clave SHA-256, en memoria).",
|
||||
"modalityBridgeCacheTtlMinutes": "TTL de caché (minutos)",
|
||||
"modalityBridgeCacheMaxEntries": "Máximo de entradas en caché",
|
||||
"modalityBridgeStatsBridged": "puenteado",
|
||||
"modalityBridgeStatsCacheHits": "aciertos de caché",
|
||||
"modalityBridgeStatsFailures": "fallos",
|
||||
"modalityBridgeStatsLastUsed": "último usado",
|
||||
"modalityBridgeStatsNever": "nunca",
|
||||
"modalityBridgeTestButton": "Prueba con imagen de muestra",
|
||||
"modalityBridgeTestRunning": "Probando…",
|
||||
"modalityBridgeTestOk": "Puente OK — {count} imagen(es) descrita(s) por {model}",
|
||||
"modalityBridgeTestReroute": "El puente redirigió la solicitud a {model}",
|
||||
"modalityBridgeTestNoop": "El puente no se activó (el modelo puede soportar visión de forma nativa o el puente está deshabilitado)",
|
||||
"modalityBridgeTestError": "La prueba falló: {message}",
|
||||
"modalityBridgeAudioTestButton": "Prueba con audio de muestra",
|
||||
"modalityBridgeAudioTestRunning": "Probando audio…",
|
||||
"modalityBridgeAudioTestOk": "Audio Bridge OK — {count} clip(s) transcritos por {model}",
|
||||
"modalityBridgeAudioTestNoop": "Audio Bridge no se activó (el destino puede soportar audio, no hay proveedor de STT conectado, o el puente está deshabilitado)",
|
||||
"modalityBridgeAudioTestError": "La prueba de audio falló: {message}",
|
||||
"modalityBridgeAudioComingSoon": "El puente de audio (voz → texto a través de /v1/audio/transcriptions) se incluirá en la próxima versión. Sus claves de configuración ya están reservadas.",
|
||||
"modalityBridgeVideoComingSoon": "El puenteo de video (muestreo de fotogramas + subtitulado) está en la lista de tareas pendientes — consulta el problema #9760.",
|
||||
"modalityBridgeMovedTitle": "Vision Bridge movido",
|
||||
"modalityBridgeMovedBody": "La configuración de Vision Bridge ahora está disponible en la página dedicada de Modality Bridge.",
|
||||
"modalityBridgeMovedCta": "Abrir la configuración del puente de modalidad",
|
||||
"resilienceMaxBackoffSteps": "Pasos máximos de backoff",
|
||||
"resilienceProviderBreakerTitle": "Circuit Breaker por proveedor",
|
||||
"resilienceFailureThreshold": "Umbral de fallos",
|
||||
@@ -8442,6 +8536,16 @@
|
||||
},
|
||||
"usage": {
|
||||
"title": "Uso",
|
||||
"grokExtraUsageCredits": "Créditos Adicionales de Uso",
|
||||
"grokAutoTopUp": "Recarga Automática",
|
||||
"grokAutoTopUpUnavailable": "No disponible",
|
||||
"grokAutoTopUpEnabled": "Habilitado",
|
||||
"grokAutoTopUpDisabled": "Deshabilitado",
|
||||
"grokAutoTopUpAt": "en",
|
||||
"grokAutoTopUpAdd": "agregar",
|
||||
"grokAutoTopUpMax": "máx",
|
||||
"grokAutoTopUpMonth": "mes",
|
||||
"grokAdditionalCredits": "Créditos Adicionales",
|
||||
"loggerTab": "Logger",
|
||||
"proxyTab": "Proxy",
|
||||
"budgetManagement": "Gestión presupuestaria",
|
||||
@@ -9725,23 +9829,23 @@
|
||||
"deviceCodeVerificationUrl": "URL de verificación",
|
||||
"deviceCodeYourCode": "Tu código",
|
||||
"deviceCodeWaiting": "Esperando autorización...",
|
||||
"googleLoopbackTitle": "__MISSING__:Google sign-in can't complete from this address",
|
||||
"googleLoopbackWhatHappens": "__MISSING__:Google only releases the authorization code once <code>{redirectUri}</code> is reachable from the browser that approves the sign-in. Here that address points at this computer, not at the OmniRoute server — so the consent screen hangs instead of redirecting, and there is no callback URL to copy.",
|
||||
"googleLoopbackRecommended": "__MISSING__:Recommended — run this on your own computer, then paste the result below:",
|
||||
"googleLoopbackHelperNote": "__MISSING__:It opens the Google consent locally (where 127.0.0.1 works) and prints a one-line omniroute-cred-v1.… blob. Paste that blob into the Step 2 field below — it accepts a credential blob as well as a callback URL.",
|
||||
"googleLoopbackTunnelLabel": "__MISSING__:Or forward the dashboard port over SSH and reload OmniRoute through the tunnel:",
|
||||
"googleLoopbackTunnelNote": "__MISSING__:Replace {userPlaceholder} with your SSH username, keep the terminal open, then open {localUrl} and connect again from there.",
|
||||
"googleLoopbackHeadlessAlt": "__MISSING__:For fully headless use with no local callback at all, <a>configure your own Google OAuth credentials</a> plus a public base URL.",
|
||||
"googleLoopbackTitle": "No se puede completar el inicio de sesión de Google desde esta dirección",
|
||||
"googleLoopbackWhatHappens": "Google solo libera el código de autorización una vez que <code>{redirectUri}</code> es accesible desde el navegador que aprueba el inicio de sesión. Aquí esa dirección apunta a esta computadora, no al servidor de OmniRoute — por lo que la pantalla de consentimiento se queda colgada en lugar de redirigir, y no hay ninguna URL de callback para copiar.",
|
||||
"googleLoopbackRecommended": "Recomendado: ejecuta esto en tu propia computadora, luego pega el resultado a continuación:",
|
||||
"googleLoopbackHelperNote": "Abre el consentimiento de Google localmente (donde 127.0.0.1 funciona) y imprime un blob omniroute-cred-v1.… de una línea. Pega ese blob en el campo Paso 2 a continuación — acepta un blob de credenciales así como una URL de callback.",
|
||||
"googleLoopbackTunnelLabel": "O reenvía el puerto del panel a través de SSH y recarga OmniRoute a través del túnel:",
|
||||
"googleLoopbackTunnelNote": "Reemplaza {userPlaceholder} con tu nombre de usuario SSH, mantén la terminal abierta, luego abre {localUrl} y conéctate de nuevo desde allí.",
|
||||
"googleLoopbackHeadlessAlt": "Para un uso completamente sin cabeza sin ningún callback local, <a>configura tus propias credenciales de Google OAuth</a> más una URL base pública.",
|
||||
"remoteAccessInfo": "Acceso remoto: dado que estás accediendo a OmniRoute de forma remota, después de la autorización verás una página de error (localhost no encontrado). Esto es normal; simplemente copia la URL completa de la barra de direcciones de tu navegador y pégala a continuación.",
|
||||
"loopbackMismatchTitle": "__MISSING__:Sign-in can't complete from this address",
|
||||
"loopbackMismatchWhatHappened": "__MISSING__:What's happening",
|
||||
"loopbackMismatchExplanation": "__MISSING__:After you approve the login, {providerName} always sends the browser back to <code>{redirectUri}</code>. That address points at the computer running this browser, not at the OmniRoute server — so the authorization code never reaches OmniRoute and the provider fails the sign-in without showing an error.",
|
||||
"loopbackMismatchHowToFix": "__MISSING__:How to fix it",
|
||||
"loopbackMismatchStep1": "__MISSING__:On this computer, open a terminal and start an SSH tunnel to the OmniRoute server:",
|
||||
"loopbackMismatchStep1Note": "__MISSING__:Replace {userPlaceholder} with your SSH username. Keep this terminal open until the connection shows as active — both ports are needed: one serves the dashboard, the other receives the callback.",
|
||||
"loopbackMismatchStep2": "__MISSING__:In this browser, reopen OmniRoute through the tunnel:",
|
||||
"loopbackMismatchStep3": "__MISSING__:Then connect {providerName} again from the new tab. The callback now reaches the server and the login completes normally.",
|
||||
"loopbackMismatchAlternative": "__MISSING__:No SSH access? If this provider offers a token import tab, connect with a token instead — that path doesn't use a loopback callback.",
|
||||
"loopbackMismatchTitle": "No se puede completar el inicio de sesión desde esta dirección",
|
||||
"loopbackMismatchWhatHappened": "¿Qué está pasando?",
|
||||
"loopbackMismatchExplanation": "Después de que apruebes el inicio de sesión, {providerName} siempre envía el navegador de vuelta a <code>{redirectUri}</code>. Esa dirección apunta a la computadora que ejecuta este navegador, no al servidor de OmniRoute, por lo que el código de autorización nunca llega a OmniRoute y el proveedor falla el inicio de sesión sin mostrar un error.",
|
||||
"loopbackMismatchHowToFix": "Cómo solucionarlo",
|
||||
"loopbackMismatchStep1": "En este ordenador, abre una terminal y comienza un túnel SSH al servidor OmniRoute:",
|
||||
"loopbackMismatchStep1Note": "Reemplace {userPlaceholder} con su nombre de usuario SSH. Mantenga esta terminal abierta hasta que la conexión se muestre como activa: se necesitan ambos puertos: uno sirve el panel y el otro recibe la devolución de llamada.",
|
||||
"loopbackMismatchStep2": "En este navegador, vuelve a abrir OmniRoute a través del túnel:",
|
||||
"loopbackMismatchStep3": "Luego conecta {providerName} nuevamente desde la nueva pestaña. La devolución de llamada ahora llega al servidor y el inicio de sesión se completa normalmente.",
|
||||
"loopbackMismatchAlternative": "¿No hay acceso SSH? Si este proveedor ofrece una pestaña de importación de tokens, conéctate con un token en su lugar; ese camino no utiliza una devolución de llamada de bucle invertido.",
|
||||
"step1OpenUrl": "Paso 1: Abre esta URL en tu navegador",
|
||||
"copy": "Copiar",
|
||||
"step2PasteCallback": "Paso 2: Pega aquí la URL de callback o el código de autorización",
|
||||
@@ -10744,6 +10848,8 @@
|
||||
"sourceModel": "Modelo de origen (nativo del agente)",
|
||||
"targetModel": "Modelo de destino (OmniRoute)",
|
||||
"noMappings": "No hay mapeos de modelos configurados. Ejecuta el asistente de configuración para detectar modelos automáticamente.",
|
||||
"noMappingsDesc": "No hay asignaciones de modelo configuradas aún. Agrega asignaciones para enrutar solicitudes de agentes a través de OmniRoute.",
|
||||
"addMapping": "Agregar mapeo",
|
||||
"selectModel": "Seleccionar…",
|
||||
"saveMappings": "Guardar asignaciones",
|
||||
"setupWizard": "Asistente de configuración",
|
||||
@@ -11491,7 +11597,10 @@
|
||||
"noSavedProxiesError": "No se encontraron proxies guardados. Añade proxies primero en Ajustes → Proxy.",
|
||||
"updateProviderFailed": "Error al actualizar el proveedor",
|
||||
"providerEnabled": "{provider} habilitado",
|
||||
"providerDisabled": "{provider} deshabilitado"
|
||||
"providerDisabled": "{provider} deshabilitado",
|
||||
"providerAdded": "{provider} añadido",
|
||||
"add": "Agregar",
|
||||
"manualApiKey": "Usar una clave API manual"
|
||||
},
|
||||
"gamification": {
|
||||
"leaderboardScopes": {
|
||||
@@ -11682,6 +11791,7 @@
|
||||
"danger": "Peligro",
|
||||
"requiresRestart": "Requiere reinicio",
|
||||
"source": "Origen",
|
||||
"ccDiscoveryAliasesEnvWarning": "Activo a través de la variable de entorno (EXPOSE_CC_DISCOVERY_ALIASES) — esto anula cualquier interruptor del panel a continuación.",
|
||||
"resetFlag": "Restablecer {label} a los valores predeterminados",
|
||||
"reset": "Restablecer",
|
||||
"loadFailed": "No se pudieron cargar los feature flags",
|
||||
@@ -11838,8 +11948,7 @@
|
||||
"SKILLS_SANDBOX_NETWORK_ENABLED": {
|
||||
"description": "Habilitar el acceso a la red en el sandbox de habilidades."
|
||||
}
|
||||
},
|
||||
"ccDiscoveryAliasesEnvWarning": "__MISSING__:Active via environment variable (EXPOSE_CC_DISCOVERY_ALIASES) — this overrides any dashboard toggle below."
|
||||
}
|
||||
},
|
||||
"comboControl": {
|
||||
"title": "Centro de control de combos",
|
||||
@@ -12187,7 +12296,7 @@
|
||||
"partnerLinkNote": "Enlace de socio",
|
||||
"dismissAriaLabel": "Descartar"
|
||||
},
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "__MISSING__:Advertise <gateway-alias>/<model> mirror ids on /v1/models for models whose canonical owner has no active credential but a passthrough gateway with an active credential routes them. Warning: adds catalog entries for all clients when enabled globally.",
|
||||
"featureFlagExposeFunctionalGatewayMirrorsDescription": "Anunciar IDs de espejo <gateway-alias>/<model> en /v1/models para modelos cuyo propietario canónico no tiene credenciales activas pero un gateway de passthrough con credenciales activas los enruta. Advertencia: agrega entradas de catálogo para todos los clientes cuando se habilita globalmente.",
|
||||
"radarPage": {
|
||||
"title": "Catálogo de Radar",
|
||||
"subtitle": "Catálogo de modelos gratuito enriquecido con inteligencia de la comunidad",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user