Files
OmniRoute/docs/openapi.yaml
Diego Rodrigues de Sa e Souza 1bda6c15dc Release v3.8.44 (#5925)
* fix(install): add pnpm-workspace.yaml allowBuilds + pnpm.json for pnpm 11+

pnpm 11 introduced ERR_PNPM_IGNORED_BUILDS for native addon packages.
Without explicit allowBuilds approval, these packages silently skip build scripts
and OmniRoute fails to start with missing native modules.

Changes:
- pnpm-workspace.yaml: Set allowBuilds=true for all 13 native addon packages
  (@parcel/watcher, @swc/core, better-sqlite3, core-js, esbuild, keytar, koffi,
  libxmljs2, onnxruntime-node, protobufjs, sharp, tls-client-node, unrs-resolver)
- pnpm.json: Migrate onlyBuiltDependencies from package.json (deprecated field)
  to the new pnpm.json config file per pnpm 11 spec.

Tested on: pnpm 11.9.0, Node 24, Windows 11.

Fixes: pnpm install ERR_PNPM_IGNORED_BUILDS on fresh clone with pnpm 11.

* chore(release): open v3.8.44 development cycle

* test(security): parse Kimi Web URL host instead of substring match (CodeQL #689) (#5928)

Alert js/incomplete-url-substring-sanitization: the Kimi Web executor
test asserted result.url.includes("www.kimi.com"), which a hostile host
like www.kimi.com.evil.net would also satisfy. Parse the URL and assert
on the exact hostname (new URL(result.url).hostname === "www.kimi.com"),
which is both a stronger check and clears the CodeQL warning.

* refactor(translator): extract thinking-budget fitting from openai-to-claude (#5932)

Extract the thinking-budget fitting cluster (fitThinkingToMaxTokens +
private safeCapMaxOutputTokens + MIN_* constants) verbatim into the pure
leaf openai-to-claude/thinkingBudget.ts. Host re-exports fitThinkingToMaxTokens
so external importers keep working and imports it back for internal use.

Host 822 -> 738 LOC (under the 800 cap). No behavior change: byte-identical
bodies, public export set unchanged. Adds a split-guard test; all consumer
tests stay green (translator-openai-to-claude, strip-empty, minimax-m3, passthrough).

* chore(release): pipeline hardening — test-masking pre-flight gate + contributors/uncovered helpers (#5926)

* chore(ci): add test-masking PR-context gate to release-green pre-flight

Reproduce check:test-masking (vs origin/main) inside validate-release-green so
non-allowlisted net-assert reductions surface in the local pre-flight instead of
in a ~40-min CI layer on the release PR. run() now merges a per-gate opts.env so
GITHUB_BASE_REF reaches the child. HARD gate; skipped under --quick.

Context: v3.8.43 release cost 3 CI round-trips for PR-context gates (test-masking,
file-size, pr-evidence) that check:release-green did not reproduce locally.

* chore(release): add contributors generator + uncovered-commit reconciliation helpers

- scripts/release/gen-contributors.mjs: reproducible `### 🙌 Contributors` table for a
  CHANGELOG version (parenthetical-group parser → accurate per-PR attribution, noise-handle
  denylist). v3.8.43 shipped without the section (a real miss) because it was hand-built.
  npm run release:contributors <version> [--inject].
- scripts/release/list-uncovered-commits.mjs: lists commits since the last tag with no
  CHANGELOG bullet (v3.8.43 had 123/176 uncovered at reconciliation start). Advisory,
  maintainer-side. npm run release:uncovered.
- 20 unit tests (parenthetical attribution, noise exclusion, idempotent injection, coverage window).

* chore(quality): absorb web-cookie-providers-new file-size drift from #5928 (base-red on release/v3.8.44)

* refactor(translator): split openai-responses request translator into pure leaves (#5940)

Extract the shared pure primitives and the chat->Responses direction out of the
894-line openai-responses.ts request translator:
- openai-responses/helpers.ts: pure primitives (toRecord/toString/clampCallId/
  normalizeVerbosity/etc + markers/regexes/JsonRecord), zero host imports
- openai-responses/toResponses.ts: openaiToOpenAIResponsesRequest (chat->Responses),
  imports the helpers leaf

Host keeps openaiResponsesToOpenAIRequest (Responses->chat, imported by production)
plus both register() directions, and re-exports openaiToOpenAIResponsesRequest so
external importers (tests) keep working.

Host 894 -> 529 LOC (under the 800 cap). Verbatim bodies (multiset check: leaf A 54/54,
leaf B 294 lines, fn1 intact), public export set unchanged, leaves never import the host
(no cycle). Adds a split-guard test; all consumer tests stay green (responses-translation-fixes
37, verbosity 4, reasoning-effort 4, orphaned-tool-filter 8, empty-tool-name-loop 8,
headroom-responses-format 3).

* chore(ci): pr-evidence FAIL output tells you to push (body edit does not re-run the gate) (#5944)

ci.yml ignores the 'edited' event, so adding the Evidence block to the PR body after a
push does not re-run check:pr-evidence — you need another commit. The FAIL report now
says so, at the exact place someone sees the red check. + 5 unit tests (classification +
hint-on-fail / no-hint-on-pass). Decided against a separate edited-triggered workflow:
pr-evidence is not a required check (no ruleset gates it; release PRs merge UNSTABLE, not
BLOCKED), so the gap is cosmetic and the generate-release skill already puts Evidence in
the body before the first push.

* fix(providers): Perplexity Web emits real tool_calls in streaming mode (mirror chatgpt-web toolMode) (#5927) (#5937)

Perplexity Web (Pro/Max) only converted <tool>{...}</tool> text into
OpenAI tool_calls for non-streaming requests (hasTools && !stream).
Streaming requests -- the default for agentic coding clients -- got
the raw <tool> text as plain delta.content and never emitted a
tool_calls SSE delta, so clients could not execute tools.

Reuses the provider-agnostic buildToolModeResponse()/
toolCompletionToSseStream() helpers already shipped for chatgpt-web
(#5240): when tools are requested, buffer the full completion and
convert it into either a JSON completion or a terminal SSE replay
carrying delta.tool_calls + finish_reason: tool_calls, regardless of
the caller's stream flag. Extended buildToolModeResponse()'s idSeed
to be caller-supplied (default 'cgpt', perplexity-web passes 'pplx')
so tool_call ids stay provider-specific without duplicating the
helper. Non-tool streaming is unchanged (still lives token-by-token
via buildStreamingResponse).

* fix(discovery): resolve duplicate /v1 paths and redirect aborts (#5904)

Integrated into release/v3.8.44. Thanks @hamsa0x7 for diagnosing the doubled /v1 discovery path and the REDIRECT_BLOCKED probe-loop abort (#5899). De-scoped to the discovery fix (the #5903 session-affinity work is handled by #5943) and added Rule #18 regression guards.

* docs(changelog): record #5926 + #5944 (release-pipeline hardening) under v3.8.44 Maintenance (#5952)

* docs(claude): add Hard Rule #22 — cross-session safety (git stash + in-flight PRs) (#5955)

Integrated into release/v3.8.44 — Hard Rule #22 (cross-session safety).

* refactor(translator): extract pure helpers from response/openai-responses (#5949)

Extract the 5 stateless helpers (normalizeToolName, stripEmptyOptionalToolArgs,
normalizeOutputIndex, normalizeUpstreamFailure, extractResponsesReasoningSummaryText)
verbatim into the pure leaf openai-responses/pureHelpers.ts (no stream state, no host
import). Host imports them back and re-exports normalizeUpstreamFailure for external
importers (tests).

Host 1091 -> 1001 LOC. The stateful streaming core stays in the host (out of scope).
Byte-identical bodies (multiset 73/73), no cycle. Adds a split-guard; consumer tests
stay green (responses-translation-fixes 37, combo-param-validation-fallback-4519 5).

* docs(compression): document upstream sync policy for RTK/Caveman engines (#5830) (#5948)

Integrated into release/v3.8.44 — docs-only upstream sync policy for RTK/Caveman engines (closes #5830). All 7 checks green.

* fix(sse): strip ANSI/VT100 codes from gemini-cli stream frames (#5934)

Integrated into release/v3.8.44 — ReDoS-safe ANSI/VT100 strip for gemini-cli stream frames (port of upstream #2273, thanks @anki1kr). PR test green (5/5), file-size gate OK.

* fix(translator): strict Anthropic content-block compliance in antigravity→openai request (#5935)

Integrated into release/v3.8.44 — strict Anthropic content-block compliance in antigravity→openai (port upstream #2296). PR test green (9/9). UNSTABLE red is the pre-existing environmental setup-claude base-red (opencode-plugin dist not built in fast-path), not a regression from this PR.

* fix(mcp): auto-recover stale streamable HTTP sessions on initialize (#5957)

Integrated into release/v3.8.44 — MCP stale streamable-HTTP session auto-recovery (thanks @Chewji9875).

* fix(providers): validate v0 Platform API keys via chats endpoint (#5954)

Integrated into release/v3.8.44 — v0-vercel Platform API key validation (thanks @vittoroliveira-dev).

* fix(api): relax provider-scoped chat completion validation (#5907)

Integrated into release/v3.8.44 — relaxed provider-scoped chat validation + regression test (thanks @nickwizard).

* fix(providers): strip /v1 unconditionally to avoid /v1/v1/models fetch error (#5899) (#5920)

Integrated into release/v3.8.44 — unconditional /v1 strip in both models-discovery paths + regression test (thanks @anki1kr).

* fix(resilience): per-window is_exhausted + honor quota-exhaustion preflight for priority combos (#5923) (#5941)

Integrated into release/v3.8.44.

* fix(resilience): honor active codex session affinity over per-request reset-aware re-scoring (#5903) (#5943)

Integrated into release/v3.8.44.

* fix(thinking): only inject redacted_thinking replay block when tool_use present and thinking enabled (#5945) (#5953)

Integrated into release/v3.8.44.

* feat(providers): add ClinePass API-key provider (#5942)

Integrated into release/v3.8.44 — ClinePass API-key (BYOK) provider (port upstream 9router#2304, co-authored @adentdk). Validated locally: 16 clinepass tests green; fixed the APIKEY count 158→159 + translate-path golden snapshot (clinepass is a genuine new provider). Remaining UNSTABLE red is the pre-existing environmental setup-claude base-red (opencode-plugin dist not built in fast-path). Supersedes stub #5541.

* feat(api): add /v1/ocr endpoint (Mistral OCR) + Mistral moderation (#5950)

Integrated into release/v3.8.44 — /v1/ocr endpoint (Mistral OCR) + Mistral moderation (port upstream 9router#2064, co-authored @waguriagentic). Validated locally: 14 ocr-route tests + moderation/servicekind/endpoint-category suites green (CORS→Zod→handler + no-stack-leak assertion). Reds are inherited DRIFT only: cognitive-complexity ratchet (none from OCR files — pre-existing cycle drift, rebaselined at release) + environmental setup-claude base-red.

* fix(codex): convert chat json schema to responses text format (#5933)

Integrated into release/v3.8.44 — converts Chat Completions json_schema response_format → Responses API text.format on the Codex path, and preserves existing text.format through verbosity normalization. Base redirected main→release; the openai-responses.ts split that landed this cycle was reconciled by re-applying the delta onto openai-responses/toResponses.ts. Validated locally: 48 translator-openai-responses-req + 8 codex-verbosity tests green.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(providers): add Claude Sonnet 5 support across the model pipeline (#5833)

Integrated into release/v3.8.44 — wires claude-sonnet-5 end-to-end (registries, modelSpecs, pricing ×3, cost, Sonnet-family fallback, 1M-ctx, static models). Reconciled the add/add overlap with the already-merged #5796 (kept the PR's superset test with the family-fallback assertion). Validated locally: kiro-sonnet-5 + catalog + pricing/modelSpecs/fallback suites all green. Thanks @ggiak!

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(relay): gate bifrost auto routing by provider manifest (#5870)

Integrated into release/v3.8.44 — gates Bifrost auto-routing by the provider plugin manifest (only manifest-eligible providers reach the sidecar; ineligible/unknown fall back to the TS path with explicit reasons). Superset of #5869 (carries the full manifest + registry + docs). Resolved an integration-test conflict in favor of the release (which already subsumes this PR's readiness/removeDirWithRetry improvements). Validated locally: 4 provider-plugin-manifest + 11 relay-routing-backend tests green. Thanks @KooshaPari!

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* refactor(translator): extract pure message helpers from openai-to-kiro (852→751) (#5947)

* refactor(translator): extract pure message helpers from openai-to-kiro

Extract the pure tool/message helpers (parseToolInput, normalizeKiroToolSchema,
serializeToolResultContent) verbatim into the leaf openai-to-kiro/messageHelpers.ts.
The host imports them back for convertMessages. They were module-private, so the
public export set is unchanged (no re-export needed).

Host 852 -> 751 LOC. Byte-identical bodies (multiset 99/99), leaf has zero imports
(no cycle). Adds a split-guard; consumer tests stay green (translator-openai-to-kiro 33,
translator-ai-sdk-image-parts 3).

* chore: re-trigger CI (stuck runner on 2/2 shard)

* refactor(executors): extract pure prompt + composer helpers from cursor (#5960)

Extract two pure clusters from the cursor executor into sibling leaves:
- cursor/prompt.ts: isRecordLike + toolChoiceDirectiveLine + buildCursorOutputConstraints
- cursor/composer.ts: composer thinking-as-content decoding (isComposerModel,
  visibleComposerContentFromThinking, composerReasoningRemainder + markers)

Host imports both back for internal use and re-exports the 3 composer helpers for
external importers (tests). Host 1576 -> 1451 LOC. Byte-identical bodies (verbatim
multiset prompt 65/65, composer 32/32), leaves have zero imports (no cycle). Adds a
split-guard; consumer tests stay green (cursor-composer-thinking, cursor-streaming,
cursor-agent-tool-calls, translator-openai-to-cursor, cursor-agent-system-prompt).

* refactor(executors): extract pure SSE-collect parsing from antigravity (#5962)

Extract the pure SSE-payload -> collected-stream parser (AntigravityCollectedStream,
stripZeroWidth, parseAntigravityTextualToolCall, addAntigravityTextualToolCall,
processAntigravitySSEPayload/Text, flushAntigravitySSEText) verbatim into the leaf
antigravity/sseCollect.ts. Host imports the helpers it uses and re-exports
processAntigravitySSEPayload for external importers (tests).

Host 1812 -> 1671 LOC. Byte-identical bodies (verbatim multiset 135/135), leaf does
not import the host (no cycle). Credit/quota state, auth, and HTTP dispatch untouched.
Adds a split-guard; consumer tests stay green (executor-agy 8, executor-antigravity 26,
antigravity-sse-collect-socket-release, copilot-agent-antigravity-parity 6).

* refactor(executors): extract pure model maps + resolvers from chatgpt-web (#5967)

Extract the static model maps (MODEL_MAP, MODEL_FORCED_EFFORT, THINKING_CAPABLE_SLUGS)
and the pure thinking-effort resolvers (isThinkingCapableModel, normalizeThinkingEffort,
resolveThinkingEffort, ResolvedChatGptModel, resolveChatGptModel) verbatim into the pure
leaf chatgpt-web/models.ts. Host imports the two resolvers it uses back.

Host 3205 -> 3076 LOC. Byte-identical bodies (verbatim multiset 120/120), leaf has zero
imports (no cycle). Auth/PoW/session/HTTP dispatch and all module caches untouched.
Adds a split-guard; consumer tests stay green (chatgpt-web 86, chatgpt-web-tools-5240 4,
chatgpt-web-sha3-boringssl-5531 5).

* refactor(executors): decompose grok-web into pure tool/markup leaves (#5994)

Extract the pure OpenAI<->Grok tool-translation, native-tool mapping, markup cleanup,
and NDJSON stream types out of the 1872-line grok-web executor into 4 sibling leaves:
- grok-web/types.ts: GrokStreamResponse/GrokStreamEvent (stream types)
- grok-web/tool-bridge.ts: OpenAI<->Grok tool translation + registry + classifiers
- grok-web/native-tools.ts: native-tool selection/scoring + native->OpenAI mapping
- grok-web/text-cleanup.ts: Grok markup stripping + GrokMarkupFilter

Layered, acyclic: types <- tool-bridge <- native-tools; text-cleanup <- types; host
imports the leaves. All symbols module-private (no host re-export). Host 1872 -> 887 LOC.
Byte-identical bodies (verbatim per-leaf), no cycle, all new leaves <= 800 cap
(tool-bridge split at line 753 to stay under). Auth/cookie/TLS/HTTP dispatch untouched.
Adds a split-guard; consumer tests stay green (grok-web 62, grok-cli-oauth 15,
grok-cli-strip-params 2).

* refactor(executors): extract pure quota parsing from codex (#5999)

Extract the pure Codex quota-snapshot parsing + reset/cooldown scheduling
(CodexQuotaSnapshot, parseCodexQuotaHeaders, getCodexResetTime,
getCodexDualWindowCooldownMs) verbatim into the leaf codex/quota.ts. Host re-exports
the 4 symbols so handlers/chatCore/codexQuota.ts + tests keep resolving.

Host 1539 -> 1427 LOC. Byte-identical bodies (verbatim 98/98), leaf has zero imports
(only Date, no cycle). WS transport, auth, HTTP dispatch untouched. Adds a split-guard;
consumer tests stay green (executor-codex 40, codex-quota-fetcher 7, chatcore-codex-quota 5).

* refactor(executors): extract pure stream formatters from deepseek-web (#6000)

Extract the pure content/citation formatters (isThinkingModel, isSearchModel,
cleanDeepSeekToken, formatStreamContent, DeepSeekSearchResult, appendSearchCitations)
verbatim into the leaf deepseek-web/stream-format.ts. Host imports the 5 it uses back
into transformSSE/collectSSEContent (cleanDeepSeekToken stays internal to the leaf).

Host 1147 -> 1108 LOC. Byte-identical bodies (verbatim 34/34), leaf has zero imports
(no cycle), all module-private (no re-export). PoW/auth/token-cache/HTTP dispatch
untouched. Adds a split-guard; consumer tests stay green (deepseek-web 35,
deepseek-web-rolling-window-2942 5, deepseek-web-tools-execute 3).

* refactor(api): add validatedJsonBody helper (salvage #5075) (#5931)

Fuses JSON body parsing + Zod validation into a single call that returns
either type-narrowed data or a ready-to-return 400 NextResponse with the
standard error envelope. Salvaged as the Tier 1 portable helper from the
closed refactor PR #5075; the bulk route migration is intentionally not
ported. Adds a focused 6-case regression test.

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>

* feat(qoder): drive PAT auth via qodercli, add dashboard quota, fix connection display (#5816)

Integrated into release/v3.8.44 — Qoder PAT auth via qodercli binary + dashboard quota + dual-auth connection fix. Thanks @AgentKiller45 (co-author @judy459)!

Validated locally (release-green on its own merits): lint 0, typecheck:core 0, 104 qoder/usage/UI tests green, file-size gate OK (owner-approved qoderCli.ts baseline-freeze 666→989), env-doc-sync fixed (documented QODER_CLI_CONFIG_DIR).

The 2 remaining CI reds are INHERITED base-reds, not caused by this PR: (1) LEDGER-4 minimax-m3 supportsVision (minimax-m3 base + cline-pass/minimax-m3 from the already-merged #5942); (2) mutation-test-coverage missing 3 tests in stryker.conf (#5903/#5942/#5923). Both cleaned up separately.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(providers): minimax-m3 supportsVision (LEDGER-4) + stryker tap.testFiles drift (#6012)

Release-green cleanup — clears LEDGER-4 minimax-m3 supportsVision + stryker tap.testFiles drift base-reds. Validated locally.

* fix(registry): flag cline-pass/minimax-m3 as multimodal (supportsVision) (#6003)

The cline-pass provider's minimax-m3 entry was missing supportsVision, breaking the
LEDGER-4 registry-consistency test (all minimax-m3 entries must set supportsVision to
match lite.ts — minimax-m3 is multimodal). Every other minimax-m3 registry entry
(trae, bazaarlink, cline, ollama-cloud, ...) already sets it. This was a base-red on
release/v3.8.44 inherited by every open PR.

Validated by the existing failing-then-passing guard tests/unit/review-reviews-v3814-fixes.test.ts
(LEDGER-4).

* refactor(executors): extract pure payload construction from claude-web (#6006)

Extract the pure Claude-web payload types + transforms + default tools/style
(ClaudeWebRequestPayload, ClaudeWebStreamChunk, DEFAULT_CLAUDE_MODEL,
generateMessageUUIDs, getDefaultTools, getDefaultPersonalizedStyle, transformToClaude,
transformFromClaude) verbatim into the leaf claude-web/payload.ts. Host imports the 3
it uses back (ClaudeWebRequestPayload type + the two transforms).

Host 1056 -> 835 LOC. Byte-identical bodies (verbatim 149/149), leaf imports only
randomUUID (no host import, no cycle), all module-private (no re-export). Cookie/auth/
Turnstile/TLS/HTTP dispatch untouched. Adds a split-guard; consumer tests stay green
(claude-web 13, claude-web-auto-refresh 6).

* refactor(executors): extract pure upstream-header helpers from base (#6008)

Extract the pure upstream-header helpers (mergeUpstreamExtraHeaders, getCustomUserAgent,
setUserAgentHeader, applyConfiguredUserAgent, isOpenAICompatibleEndpoint,
stripStainlessHeadersForOpenAICompat) verbatim into the leaf base/headers.ts. base.ts is
imported by ~18 executors, so the host re-exports all 6 to keep those import paths intact;
it also imports the 4 it uses internally in the BaseExecutor class. The trivial JsonRecord
type alias is redefined locally in the leaf to avoid a base<->leaf cycle.

Host 1539 -> 1451 LOC. Byte-identical bodies (verbatim 78/78), leaf does not import the
host (no cycle). typecheck:core validates all base importers still resolve via the
re-export. Adds a split-guard; consumer tests stay green (executor-base-utils 22,
executor-default-base 49, executor-strip-stainless-openai-compat 6, plus executor sanity
via typecheck).

* refactor(executors): extract pure wire protocol from perplexity-web (#6014)

Extract the pure Perplexity wire protocol (consts, SSE stream types, SSE parsing,
OpenAI<->Perplexity message translation, request/query builders, content extraction,
sseChunk) verbatim into the leaf perplexity-web/protocol.ts. Host imports back the 10
symbols it uses; everything module-private (no re-export). Session cache, TLS fetch,
auth, and the executor class stay in the host.

Host 1028 -> 534 LOC. Byte-identical bodies (verbatim), leaf imports only randomUUID
(no host import, no cycle). Adds a split-guard; consumer tests stay green
(perplexity-web 26, streaming-tools-5927 2, tls-client 6, key-validation-models 2).

* refactor(executors): extract pure URL normalizers from default (#6015)

Extract the pure per-provider chat-URL normalizers (normalizeBailianMessagesUrl,
normalizeDataRobotChatUrl, normalizeAzureAiChatUrl, normalizeWatsonxChatUrl,
normalizeOciChatUrl, normalizeSapChatUrl, normalizeXiaomiMimoChatUrl,
normalizeOpenAIChatUrl, getOpenRouterConnectionPreset) verbatim into the leaf
default/urlNormalizers.ts. Host imports them back into buildUrl/transformRequest; the
now-dead build*ChatUrl/normalizeBaseUrl imports move to the leaf. All module-private
(no re-export).

Host 864 -> 815 LOC (shrunk below its frozen baseline). Byte-identical bodies (verbatim
45/45), leaf does not import the host (no cycle). buildHeaders/execute/auth untouched.
Adds a split-guard; consumer tests stay green (executor-default-base 49,
anthropic-compatible-bearer 3, strip-client-metadata 3).

* feat(webfetch): support self-hosted FireCrawl instances (#5793)

Integrated into release/v3.8.44 — self-hosted FireCrawl support (FIRECRAWL_BASE_URL/FIRECRAWL_TIMEOUT_MS). Re-cut clean onto the release tip (branch was fossilized from a pre-v3.8.40 snapshot). Validated: 4 firecrawl tests green, env-doc-sync + docs-sync pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(xai): register XaiExecutor with reasoning-effort suffix parsing (#5800)

Integrated into release/v3.8.44 — XaiExecutor with reasoning-effort suffix parsing. Re-cut clean onto the release tip (branch was fossilized). Validated: 6 xai-executor tests green, provider-consistency OK, typecheck:core 0 errors, env-doc-sync in sync. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(discovery): Phase 2 — reporter, /api/discovery/* routes (strict loopback-only) + dashboard UI (#5939)

* feat(discovery): Phase 2 reporter — discoveryResults DB module + service wiring

Adds src/lib/db/discoveryResults.ts (CRUD over the discovery_results table
from migration 074) and wires the opt-in discovery service to persist and read
findings through it: persistDiscoveryResult / getDiscoveryResults /
getDiscoveryResultById / markVerified / deleteDiscoveryResult, with
(provider, method, endpoint) upsert de-duplication. Re-exported from localDb.

The service stays opt-in / default-off. The /api/discovery/* routes and the
dashboard UI tab are intentionally deferred to Phase 2b — they need the
local-only enforcement model (Hard Rules #15/#17 territory) decided first.

TDD: tests/unit/db/discovery-results.test.ts (8 cases, DB + service delegation),
isolated DATA_DIR with resetDbInstance cleanup.

* feat(discovery): Phase 2b — /api/discovery/* routes (strict loopback-only)

Adds the discovery HTTP surface on top of the reporter DB module:
  GET    /api/discovery/results            list findings (optional ?providerId)
  GET    /api/discovery/results/:id        one finding (404 if absent)
  DELETE /api/discovery/results/:id        delete a finding
  POST   /api/discovery/scan               scan a provider + persist findings
  POST   /api/discovery/verify/:id         mark a finding verified

Authorization: strict loopback-only. "/api/discovery/" is added to
LOCAL_ONLY_API_PREFIXES so the central authz pipeline (proxy.ts →
runAuthzPipeline → managementPolicy) rejects non-loopback callers with a 403
LOCAL_ONLY before any handler runs. It is deliberately NOT in
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES — no remote manage-scope bypass —
because POST /scan issues outbound probes to provider endpoints (SSRF-adjacent)
and must never be tunnel-reachable. Handlers also call requireManagementAuth
(defense in depth) and return sanitized errors via createErrorResponse.

Tests:
- tests/unit/authz/discovery-routes-local-only.test.ts (8) — security guard:
  isLocalOnlyPath true + not manage-scope-bypassable for all four paths.
- tests/unit/api/discovery-routes.test.ts (6) — handler integration over an
  isolated DATA_DIR: list/filter, by-id 200/404/400, scan persist + 400 on
  empty/malformed body, verify 200/404, delete 200/404, no stack-trace leak.

* feat(discovery): Phase 2c — dashboard UI tab (Tools → Discovery)

Adds the /dashboard/discovery page (DiscoveryPageClient) that consumes the
Phase 2b /api/discovery/* routes: scan a provider, list findings, verify or
delete them. Registered in the sidebar under the Tools group (icon
travel_explore) and given a "discovery" i18n namespace + sidebar keys in
en.json (other locales fall back to en via next-intl until synced — the
locale files are in a pre-existing coverage deficit unrelated to this change).

Registers the UI test path in vitest.config.ts (advisory ui suite).

Tests: src/app/(dashboard)/dashboard/discovery/__tests__/DiscoveryPageClient.test.tsx
(3 cases: loads+renders results, empty state, fetches /api/discovery/results on
mount; stable useTranslations mock to avoid the fetch-loop). NOTE: the ui vitest
suite cannot run in this workspace — @testing-library/dom (a @testing-library/
react peer dep) is absent from node_modules, which fails ALL existing ui tests
equally; the test runs in CI. Component verified locally via typecheck + lint.

* test(discovery): register discovery-routes-local-only in stryker tap.testFiles

The mutation-test-coverage gate (--strict) flags any unit test covering a
mutated module that isn't listed in stryker.conf.json tap.testFiles. This PR's
tests/unit/authz/discovery-routes-local-only.test.ts covers src/server/authz/
routeGuard.ts (a mutated module, which this PR edits by adding the
/api/discovery/ local-only prefix), so it must be registered for its mutant
kills to count. No behavior change.

* refactor(discovery): split DiscoveryPageClient to satisfy max-lines-per-function

The complexity ratchet (max-lines-per-function: 80) flagged the single
184-line DiscoveryPageClient function (+1 over baseline). Extract the data
layer into two hooks (useDiscoveryResults for list/loading/feedback,
useDiscoveryActions for scan/verify/delete), a shared callApi helper, and two
presentational sub-components (DiscoveryScanForm, DiscoveryResultCard). Every
function is now under the 80-line ceiling; complexity gate back to baseline
1995. No behavior change — same exported component, same endpoints, same props.

* test(sidebar): include discovery in omni-proxy item-order snapshot

Adding the Discovery item to the Tools group (this PR's sidebar entry) extends
the ordered omni-proxy section list. Update the exact-match deepEqual snapshot
in sidebar-visibility.test.ts to include "discovery" in its position (after
traffic-inspector). The assertion stays exact — this reflects the intentional
new item, it does not weaken the check.

* docs(changelog): restore release bullets eaten by merge auto-resolve; re-add discovery bullet additively

* chore(quality): bump testFrozen for translator-openai-responses-req.test.ts (1097 -> 1172)

Base-red inherited from #5933, which grew the test file to 1171 lines
(Hard Rule #18 regression tests) without adjusting the frozen cap. The
release tip itself fails check:file-size; this unblocks every PR into
release/v3.8.44. File untouched by this PR.

* chore(quality): restore stryker tap.testFiles entries eaten by merge auto-resolve

The merge of origin/release/v3.8.44 silently dropped the 3 entries added
on the release side (#5903, clinepass, #5923). Took the release version
verbatim and re-added only this PR's entry (discovery-routes-local-only)
in alphabetical order. check:mutation-test-coverage green locally.

* chore(quality): reconcile inherited v3.8.44 merge-burst drift + include discovery in tools-group order test

- complexity 1995->2003 and cognitive 856->859: both measure IDENTICAL on
  the pristine release tip (3a3d618fe) and this PR's merged HEAD — the PR
  is complexity-net-zero; drift is from the 2026-07-02 merge burst
  (notes added to both baselines, same family as prior reconciliations).
- sidebar-tools-group.test.ts: append 'discovery' to the expected
  TOOLS_GROUP order — the intentional new sidebar item this PR adds
  (same expected-value update already made in sidebar-visibility.test.ts).

* feat(providers): custom icon URL for compatible provider nodes (#5815)

Integrated into release/v3.8.44 — custom icon URL for compatible provider nodes (DB migration 113 + nodes.ts + Zod schema + API routes + catalog + ProviderIcon UI). Re-cut onto the release tip (branch was fossilized ~13 real files); reconciled icon_url into the release's evolved nodes.ts/routes via 3-way. Validated: 14 backend + 5 frontend(vitest) + 24 page-utils tests green, typecheck:core 0, provider-consistency OK, file-size/env-doc-sync pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(api): add /v1/audio/translations endpoint (#5809)

Integrated into release/v3.8.44 — /v1/audio/translations endpoint (Whisper-style audio translation) + audioTranslation handler + translation providers in audioRegistry. Re-cut clean onto the release tip (branch was fossilized). Validated: 8 route tests (incl. no-stack-leak), typecheck:core 0, route-guard-membership OK, docs gates pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* feat(dashboard): wildcard-CORS runtime warning + CORS security doc (#5602) (#5759)

Integrated into release/v3.8.44 — wildcard-CORS runtime warning banner + docs/security/CORS.md security guide (#5602). Re-cut clean onto the release tip (branch was fossilized). Validated: 20+9 backend + 2 banner(vitest) tests green, typecheck:core 0, docs-sync/symbols/fabricated/doc-links pass. UNSTABLE red is the inherited environmental setup-claude base-red.

* refactor(executors): extract pure JSONL stream translation from huggingchat (#6016)

Extract the pure JSONL->OpenAI-SSE translation (sseChunk, parseJsonlLine,
streamJsonlToOpenAi, readJsonlResponse) verbatim into the leaf huggingchat/jsonlStream.ts.
They consume a passed-in ReadableStream (no fetch/network/state). Host imports back the
two it uses; all module-private (no re-export).

Host 812 -> 594 LOC. Byte-identical bodies (verbatim), leaf has zero imports (no cycle).
Cookie/auth/multipart/execute untouched. Adds a split-guard; consumer tests stay green
(executor-huggingchat 6, huggingchat-model-catalog 3).

* refactor(executors): extract pure Meta AI response parser from muse-spark-web (#6017)

Extract the pure Meta AI SSE/JSON response parsing + content/reasoning/error extraction
(parseMetaSseFrames, readMetaJsonPayloads, collect*/extract*/classify* helpers,
parseMetaAiResponseText, isRecord, the reasoning/renderer key arrays, MetaSseFrame/
ParsedMetaAiResponse types) verbatim into the leaf muse-spark-web/response-parser.ts.
Host imports back the 3 it uses; all module-private (no re-export).

Host 1301 -> 925 LOC. Byte-identical bodies (verbatim), leaf has zero imports (no cycle).
Conversation cache, cookie/auth, fetch, executor class untouched. Adds a split-guard;
consumer tests stay green (muse-spark-cookie-copy-5449 2, muse-spark-web-continuation 6).

* refactor(executors): extract pure EventStream framing from kiro (#6018)

Extract the pure AWS EventStream binary framing (ByteQueue, CRC32 table + crc32,
TEXT_ENCODER/TEXT_DECODER, KIRO_VERIFY_FULL_CRC, parseEventFrame, EventFrame type)
verbatim into the self-contained leaf kiro/eventstream.ts (local JsonRecord alias to avoid
a cycle). Host imports back the 3 it uses (ByteQueue, TEXT_ENCODER, parseEventFrame).

Host 943 -> 758 LOC. Byte-identical bodies (verbatim 145/145), leaf has zero host imports
(no cycle). Auth/token-refresh/streaming-state/executor class untouched; the test-imported
flushBufferedToolArgs/resolveKiroRegion/kiroRuntimeHost stay exported on the host. Adds a
split-guard; consumer tests stay green (executor-kiro 9, kiro-tool-args-streaming 7,
kiro-iam-region 10).

* refactor(executors): extract challenge solver from duckduckgo-web (#6020)

Extract the DuckDuckGo anti-abuse challenge solver + FE signals (CHALLENGE_STUBS,
countHtmlElements, buildHtmlLookup, sha256Base64, solveDuckDuckGoChallenge,
makeDuckDuckGoFeSignals) verbatim into the leaf duckduckgo-web/challenge.ts. The vm
sandbox + 5s timeout (SECURITY note) are preserved. Host imports back the two it uses.

Host 924 -> 788 LOC. Byte-identical bodies (verbatim 132/132), leaf does not import the
host (no cycle). The now-dead createHash/parse5 host imports are removed; vm stays (still
used in host). Auth/cookie/warm/seed/executor untouched. Adds a split-guard; consumer
tests stay green (duckduckgo-web-executor 15, duckduckgo-domain-4037 8).

* test(cli): deflake setup-claude.test.ts — silence console to stop stdout/report interleaving (#5959) (#6019)

Integrated into release/v3.8.44. Deflakes tests/unit/cli/setup-claude.test.ts (#5959) — verified in CI: setup-claude now passes in Unit Tests fast-path (2/2).

Merged with --admin over two PRE-EXISTING base-reds proven independent of this test-only change (this PR only touches setup-claude.test.ts + CHANGELOG):
- Fast Quality Gates → check:test-discovery: tests/unit/executors/{firecrawl-fetch,xai-executor}.test.ts are orphaned on release/v3.8.44 (added by #5793/#5800); the shard glob 'tests/unit/{api,...,ui}/**' omits 'executors'. Both blobs exist on the pristine base.
- Unit Tests fast-path (2/2): tests/unit/settings-i18n-keys.test.ts → 'direct translation calls have English messages' fails on the pristine base too (unrelated i18n base-red).

* fix(cli): stabilize setup-claude.test.ts flake — inject dry-run log sink (#6021)

* fix(cli): stabilize setup-claude.test.ts flake — inject dry-run log sink (#5959)

Root cause (isolated empirically, 5/10 fail on the pristine base): the
dry-run path of syncClaudeProfilesFromModels console.log's a multi-byte
box-drawing heading ("── [dry-run] … ──"). Under the node:test runner
that write lands on the test child's stdout and corrupts the runner's
V8-serialized event stream ~50% of the time ("Unable to deserialize
cloned data due to invalid or unsupported version"), killing the file at
the first logging test. ASCII-only logging never reproduced it (0/20);
the unicode heading alone reproduced it (10/20).

Fix: syncClaudeProfilesFromModels accepts an injectable log sink
(opts.log, CLI default unchanged: console.log). The dry-run test injects
a collector — keeping unicode off the child's stdout — and gains
assertions on the dry-run report (path + parsed settings content), which
FAIL on the old code (log ignored) and PASS on the new one.

Validation: 0/30 failures post-fix vs 5/10 pre-fix on the same tree.

Baselines: complexity 2003->2006 and cognitive 859->860 are inherited
post-3a3d618fe release drift — measured identical on the pristine base
with and without this change (notes added in both files).

* test(ci): collect the orphaned tests/unit/executors/ directory (base-red unblock)

#5800 created tests/unit/executors/ outside every unit-runner brace glob,
so its 2 test files (firecrawl-fetch, xai-executor) never ran anywhere and
check:test-discovery flags them as NEW orphans on the pristine base,
red-flagging every PR into release/v3.8.44. Added 'executors' to the
runner globs in package.json (7 scripts), ci.yml unit shards, quality.yml
TIA glob, build-test-impact-map.mjs, and the test-discovery gate's
COLLECTORS (the gate enforces those stay in sync). Both files pass when
actually collected (10/10); cli+executors under suite flags: 99/99.

* chore(quality): complexity baseline 2006 -> 2007 (CI-observed value)

The GitHub fast-gates runner measures 2007 where local measures 2006 —
the same local-vs-CI off-by-one documented in the 2026-06-26 note. Pin
the CI-observed value so the gate is deterministic where it runs.

* fix(i18n): add the 6 missing en.json keys flagged by settings-i18n-keys (base-red unblock)

providers.iconUrlLabel/iconUrlHint (referenced by AddCompatibleProviderModal
and EditCompatibleNodeModal) and settings.authz.cors.wildcard.title/desc
(the #5602 CORS_ALLOW_ALL banner in AuthzSection) shipped without their
en.json messages — 'direct translation calls have English messages' fails
on the pristine release tip, red-flagging every PR. git log -S proves the
keys never existed (not a merge-eat). Scanner test: 10/10 green.

* refactor(executors): extract reasoning-effort (base) + tool-normalization (codex) leaves (#6030)

Two pure-leaf follow-ups closing the Block H tail:

- base/reasoningEffort.ts: provider-aware reasoning_effort sanitation
  (MISTRAL/GITHUB reject patterns, supportsMaxEffortForProvider,
  sanitizeReasoningEffortForProvider). Deps are config/services only
  (PROVIDER_CLAUDE, isClaudeCodeCompatible, supportsClaudeMaxEffort/supportsXHighEffort)
  so the leaf never imports the host — no cycle. base.ts re-exports
  sanitizeReasoningEffortForProvider for its external importers (mimoThinking + tests).
  base.ts 1466 -> 1312 LOC.

- codex/tools.ts: Responses-API tool normalization (CODEX_HOSTED_TOOL_TYPES hosted-tool
  passthrough, isCodexFreePlan gating, normalizeCodexTools). Self-contained
  (console.debug only). codex.ts re-exports isCodexFreePlan + normalizeCodexTools for
  external importers (tests + provider services). codex.ts 1430 -> 1268 LOC.

Byte-identical bodies (verbatim: base 100/100, codex 126/126); both leaves have zero host
imports. Adds two split-guards asserting the leaf owns the symbol and both import paths
resolve to the same function. Consumer tests stay green (base-executor-sanitize-effort 34,
executor-codex 40, mimoThinking 9, codex-free-plan-image-generation 3, issue-fixes 6).

* test(ci): move orphaned executor tests to top-level so a runner collects them (#6027)

Integrated into release/v3.8.44 — collect orphaned executor tests (check:test-discovery base-red).

* test(cli): deflake cli-setup-opencode.test.ts — silence console (#5959-class landmine) (#6033)

The command under test prints CLI progress with multi-byte glyphs
(printSuccess "✔" in the happy paths, printError "✖" in the dist-missing
path that test 4 exercises) via console.log. Under the node:test runner
those child-stdout writes interleave with the V8-serialized report frames
and can corrupt the stream — the exact #5959 mechanism proven for
setup-claude.test.ts; this file's ✖ line was already visible entangled in
red CI runs. No test here asserts on stdout, so silence console.log/info/
warn for the file (same pattern as #6019/#6021, restored in after()).

Validation: pre-fix the ✖/✔ lines reach stdout every run (grep-able);
post-fix stdout is clean, 4/4 tests green, 0/20 failures across 20 runs.

* feat(agy): support Google Cloud project ID settings (#5905)

* feat(agy): support Antigravity project ID settings

* refactor(agy): collapse Antigravity family project gate

---------

Co-authored-by: Nikolay Alafuzov <alafuzov_nn@rusklimat.ru>

* feat(proxy): add Webshare proxy pool import and sync (#5993)

* feat(proxy): add Webshare proxy pool import and sync

Adds Webshare (https://proxy.webshare.io) as a fourth source in the
free-proxy provider framework alongside 1proxy, Proxifly, and IPLocate.
WebshareProvider paginates the account's `/api/v2/proxy/list/` endpoint
(Authorization: Token <key>), upserts proxies into the shared
`free_proxies` table via the existing db/freeProxies.ts helpers, and
tombstones proxies the account no longer lists (recycled/retired IDs)
while never touching rows already promoted into the live proxy pool.

Unlike the other sources, Webshare is a paid per-account list, so it is
gated on FREE_PROXY_WEBSHARE_API_KEY rather than a plain on/off flag.
No DB migration needed — reuses the existing free_proxies table and
proxy_registry-on-promote path.

Co-authored-by: ricatix <d.enistraju155@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1176

* chore(changelog): restore release entries + add webshare bullet

---------

Co-authored-by: ricatix <d.enistraju155@gmail.com>

* feat(api-keys): add per-key device/connection tracking (#5998)

* feat(api-keys): add per-key device/connection tracking

Tracks distinct client devices (SHA-256 fingerprint of IP + User-Agent)
seen with each API key, with a 30-minute TTL and per-key/global caps. The
tracker is in-memory only (module-scoped Map, same pattern as
sessionManager.ts — no global.* singleton) and never stores the raw IP:
it is masked before being written.

Hooked into open-sse/handlers/chatCore.ts (the real chat entry) rather
than the legacy src/sse/handlers path. New GET /api/keys/[id]/devices
management route exposes masked device details for a key, and the
API Keys dashboard tab gets a "Devices" count badge alongside the
existing Sessions badge.

This is a new granularity distinct from the existing maxSessions cap
(src/lib/db/apiKeys.ts), which limits concurrent sticky-routing sessions
rather than tracking device identity.

Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>
Inspired-by: https://github.com/decolua/9router/pull/931

* chore(changelog): restore release entries + add api-keys device-tracking bullet

---------

Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>

* fix(providers): only apply openai-family model inference fallback when no cataloged provider serves the id (#5852) (#5938)

resolveModelByProviderInference() in open-sse/services/model.ts had an
unconditional /^gpt-/i heuristic that hijacked any model id starting with
gpt-/o1/o3 into provider openai, even when the id is cataloged under other
providers. This broke bare (non-combo) requests for open-weight models like
gpt-oss-120b (served by fireworks/cerebras/scaleway/byteplus/sambanova/
heroku), which don't exist on openai's catalog, producing a 404 with no
fallback.

Gate the heuristic on providers.length === 0 so it only fires for genuinely
uncataloged openai-family ids, letting cataloged ids fall through to the
existing single-candidate / ambiguous-candidate resolution paths.

Regression guard: tests/unit/gptoss-provider-inference-5852.test.ts

* fix(cc-compatible): send SSE accept for streamed requests (#5958)

Integrated into release/v3.8.44 — SSE Accept header for streamed cc-compatible requests (thanks @rdself).

* fix: deepseek-web reliability — auto-refresh on 401/403, refresh v2.0.0 client headers, fix token-kind bulk import (#5988)

Integrated into release/v3.8.44 — deepseek-web auto-refresh + v2.0.0 headers + token-kind bulk import (thanks @backryun).

* feat(providers): support Vercel AI Gateway embeddings and images (#5968)

* feat(providers): support Vercel AI Gateway embeddings and images

Extends the existing vercel-ai-gateway (alias vag) provider — currently
chat-only — with embeddings and image generation support, since the
gateway's OpenAI-compatible /v1 API also exposes /embeddings and
/images/generations. Adds entries to EMBEDDING_PROVIDERS
(embeddingRegistry.ts) and IMAGE_PROVIDERS (imageRegistry.ts) modeled
on the existing openai entries.

Out of scope for this PR (tracked as follow-ups): the /v1/credits
usage reader, retry:{429:2} tuning, and claude->reasoning_effort
mapping.

Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>
Inspired-by: https://github.com/decolua/9router/pull/1704

* chore(changelog): restore release entries + add vercel-gateway media bullet

---------

Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>

* feat(cli-tools): add Crush CLI tool to the dashboard (#5970)

* feat(cli-tools): add Crush CLI tool to the dashboard

Add a `crush` entry to the dashboard CLI-Tools catalog and a new
`/api/cli-tools/crush-settings` route (GET/POST/DELETE), cloned from the
`pi` tool's route as a template. OmniRoute already ships a `crush` CLI
command path (bin/cli/commands/setup-crush.mjs) but the dashboard catalog
had no matching entry.

The new route writes the real Crush config shape (providers.omniroute as
an openai-compat provider block) to the same canonical config path
(~/.config/crush/crush.json) that setup-crush.mjs's resolveCrushTarget()
already writes to, so the dashboard and the CLI command agree on one
location. Adds CLI_TOOL_RUNTIME_CONFIG.crush for detection/status, and
bumps EXPECTED_CODE_COUNT (18 -> 19) plus the catalog-count/schema tests
that enumerate the full tool list.

Co-authored-by: dopaemon <polarisdp@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1233

* chore(changelog): restore release entries + add crush cli bullet

---------

Co-authored-by: dopaemon <polarisdp@gmail.com>

* feat(dashboard): suggest HuggingFace Hub media models (#5990)

* feat(dashboard): suggest HuggingFace Hub media models

MVP scope:
- imageRegistry.ts: add an image kind entry for the huggingface provider
  (HF Inference API text-to-image), with a dedicated "huggingface-image"
  format since the endpoint returns raw image bytes rather than JSON.
- New handler open-sse/handlers/imageGeneration/providers/huggingface.ts,
  wired into imageGeneration.ts's format dispatch.
- New pure helper module open-sse/services/hfModelSuggestions.ts: maps a
  dashboard media kind to an HF Hub pipeline_tag and sorts/limits raw HF
  Hub search results (unit-tested directly).
- New route GET /api/v1/providers/suggested-models proxies the public HF
  Hub models search API server-side (Zod-validated query, buildErrorBody
  on every error path, no HF token exposed client-side — this project has
  no server-side HF search token config, so it calls unauthenticated).
- UI: ImageExampleCard now fetches suggested HF Hub models for the
  huggingface provider and merges them into the model picker as a
  selectable chip row, alongside the existing static provider models list.
- i18n: adds media.suggestedModels to en.json only.

Co-authored-by: yicone <yicone@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1633

* chore(changelog): restore release entries + add hf-hub media suggest bullet

---------

Co-authored-by: yicone <yicone@gmail.com>

* feat(dashboard): collapse and sort provider quota rows by remaining (#5977)

* feat(dashboard): collapse and sort provider quota rows by remaining

Sort the expanded quota list by remaining percentage (highest first)
and collapse it to the first 3 rows by default, with a "Show N more" /
"Show less" toggle when a connection reports more than 3 quotas. This
keeps the most at-risk quotas out of view below a long list of
healthy ones.

Extracts the sort/slice logic into pure helpers
(sortQuotasByRemaining, getVisibleQuotas) exported from
QuotaCardExpanded.tsx and unit-tests them directly.

Co-authored-by: CườngNH <j2.cuong@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1919

* chore(changelog): restore release entries + add quota collapse/sort bullet

---------

Co-authored-by: CườngNH <j2.cuong@gmail.com>

* feat(providers): refresh The Old LLM (Free) model catalog (#5181)

* feat(dashboard): add tool-source diagnostics settings toggle (#5978)

* feat(dashboard): add tool-source diagnostics settings toggle

Adds a Settings > Advanced card (cloned from DebugModeCard) that lets
operators flip the existing `logToolSources` flag from the UI instead
of editing the DB row directly. The backend gate (chatCore.ts) and DB
default were already present but had no toggle. Also adds
`logToolSources` to the /api/settings Zod PATCH schema (it is `.strict()`,
so the key was previously rejected) and en-only i18n strings.

Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1825

* chore(changelog): restore release entries + add tool-source toggle bullet

---------

Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>

* feat(oauth): import Codex connection from a raw ChatGPT access token (#5995)

* feat(oauth): import Codex connection from a raw ChatGPT access token

OmniRoute's only Codex import path (/api/oauth/codex/import) required both
access_token and refresh_token, leaving no import path for a user who only
has a bare ChatGPT website access token (no refresh token).

- src/lib/db/providers.ts: createProviderConnection gains an explicit
  authType "access_token" branch — intentionally never deduped (no stable
  long-lived identity to match on) — and derives the connection name from
  email/name the same way "oauth" does.
- src/lib/oauth/services/codexImport.ts: export extractCodexAccountInfo so
  the new import path reuses the existing JWT decode instead of duplicating
  one.
- New route POST /api/oauth/codex/import-token (Zod-validated body
  { accessToken, name? }); errors routed through buildErrorBody /
  sanitizeErrorMessage. The executor's refreshCredentials() already
  degrades safely to null when there is no refresh token, forcing re-auth
  on expiry instead of a refresh exchange.
- OAuthModal.tsx: the callback-URL manual-paste path for codex now detects
  an eyJ-prefixed pasted token and posts it to the new endpoint, mirroring
  the existing grok-cli raw-token paste pattern.

Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1290

* chore(changelog): restore release entries + add codex token-import bullet

---------

Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>

* fix(resilience): parse Retry-After from 429 JSON body for cooldown (#5974)

Integrated into release/v3.8.44 — parse Retry-After from 429 JSON body for cooldown (incl. #6013 retry-after-json extraction by @KooshaPari).

* fix(embeddings): forward connection-level proxy to embedding requests (#5975)

Integrated into release/v3.8.44 — forward connection-level proxy to embedding requests.

* fix(api): guard shared API client against non-JSON error responses (#5973)

Integrated into release/v3.8.44 — guard shared API client against non-JSON error responses.

* feat(dashboard): surface Codex banked reset credits per account (#5199)

* feat(providers): add NVIDIA NIM image generation (#5971)

* feat(providers): add NVIDIA NIM image generation

NVIDIA already exists as a chat provider (integrate.api.nvidia.com,
OpenAI-compatible) but image generation is served on a different host
(ai.api.nvidia.com/v1/genai/<model>) with a native NIM body shape, so it
gets a dedicated `nvidia-nim` image format and handler rather than reusing
the OpenAI image path.

Adds the 4 FLUX models (flux.1-dev, flux.1-schnell, flux.1-kontext-dev,
flux.2-klein-4b) to IMAGE_PROVIDERS, plus handleNvidiaNimImageGeneration()
which shapes the per-model NIM request body (flux.1-dev's mode/cfg_scale
and 768-1344px/64px-increment dimension validation, flux.1-kontext-dev's
required input image + aspect_ratio, schnell/klein's optional array-form
edit image) and normalizes the NIM response (artifacts[]/images[]/data[]/
single-value shapes) into the OpenAI `{created, data}` shape.

Co-authored-by: eng2007 <aleksey.semenov@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1195

* chore(changelog): restore release entries + add nvidia-nim image bullet

---------

Co-authored-by: eng2007 <aleksey.semenov@gmail.com>

* feat(providers): add Augment (Auggie CLI) local provider (#5972)

* feat(providers): add Augment (Auggie CLI) local provider

Adds a new local, no-auth provider that spawns the user's local `auggie`
CLI (`auggie --print --quiet --model <m> --`) and pipes a flattened prompt
via stdin, wrapping stdout as an OpenAI-compatible SSE stream or a single
chat.completion JSON body depending on the request's `stream` flag.

Auth is delegated entirely to `auggie login` outside OmniRoute — the
connection is registered `noAuth: true` and `refreshCredentials()` is a
no-op, matching the existing `NOAUTH_PROVIDERS` credential-less flow
(synthetic connection, no DB row required). An optional connection row is
still admitted via `FREE_APIKEY_PROVIDER_IDS` for display/priority
tracking, consistent with `opencode`. The dashboard "Test Connection"
flow spawns `auggie --version` to confirm the CLI is installed and
runnable, since there is no API key to validate upstream.

Security hardening (spawn is an untrusted-input sink):
- Command injection: spawn no longer passes `shell: true` on Windows. The
  binary is resolved to a concrete path/name and argv is handed straight to
  the OS loader, so no cmd.exe metacharacter interpretation is possible.
- Argument injection (flag smuggling): `model` is validated against the
  registry allowlist (`auggieProvider.models`) before any spawn — a model
  that is unknown or starts with "-" is rejected with a sanitized error and
  the subprocess is never started. A trailing `--` marks end-of-options in
  the argv as belt-and-suspenders.

Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1200

* test(golden): regenerate translate-path for auggie provider

---------

Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>

* feat(providers): add ModelScope OpenAI-compatible provider (#5965)

* feat(providers): add ModelScope OpenAI-compatible provider

Ports ModelScope (Alibaba 魔搭) as a new API-key, OpenAI-compatible
provider — upstream 9router PR #1764. The upstream PR hardcoded
`https://api-inference.modelscope.ai/...` (`.ai` TLD); verified against
ModelScope's own API-Inference docs and third-party integration guides
that the real production domain is `api-inference.modelscope.cn`
(`.cn` TLD) and shipped that instead. Also drops the PR's static
5-model snapshot in favor of `passthroughModels: true` with an empty
seed list + `modelsUrl`, since ModelScope's open-model catalog moves
fast.

Updates the providers-constants-split characterization test's hardcoded
APIKEY_PROVIDERS count (159 -> 160) to match the new entry.

Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/1764

* chore(changelog): restore release entries + add modelscope bullet

* test(golden): regenerate translate-path for modelscope provider

---------

Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>

* feat(providers): add Qiniu OpenAI-compatible provider (#5966)

* feat(providers): add Qiniu OpenAI-compatible provider

Wires Qiniu (七牛云) AI inference gateway as a BYOK API-key provider.
Qiniu proxies many upstream models (DeepSeek V3/V4, Claude, Kimi and
more) behind a single key, so it ships with an empty static seed and
relies on passthroughModels + the live /v1/models catalog instead of a
single stale hardcoded model id.

- metadata: src/shared/constants/providers/apikey/gateways.ts
- registry entry: open-sse/config/providers/registry/qiniu/index.ts
  (format openai, executor default, bearer auth, baseUrl
  https://api.qnaigc.com/v1/chat/completions, modelsUrl
  https://api.qnaigc.com/v1/models)
- added to NAMED_OPENAI_STYLE_PROVIDERS so model import serves the live
  catalog and falls back to the (empty) local catalog on error, same
  pattern as the existing dgrid/zenmux/orcarouter gateways
- tests: tests/unit/qiniu-provider.test.ts (metadata, registry
  resolution, passthrough validation, live /v1/models fetch + fallback)

Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>
Inspired-by: https://github.com/decolua/9router/pull/911

* chore(changelog): restore release entries + add qiniu bullet

* test(golden): regenerate translate-path for qiniu provider

* test(providers): bump APIKEY count 160→161 for qiniu

---------

Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>

* feat(providers): add b.ai OpenAI-compatible provider (#5969)

* feat(providers): add b.ai OpenAI-compatible provider

Adds bai as a new OpenAI-compatible BYOK provider, distinct from the
existing thebai/theb.ai provider, using passthrough model discovery
(no hardcoded model list, live catalog served from api.b.ai/v1/models).

Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
Inspired-by: https://github.com/decolua/9router/pull/963

* test(golden): regenerate translate-path for b.ai provider

* test(providers): bump APIKEY count 161→162 for b.ai

---------

Co-authored-by: Delynn Assistant <zhen@dkzhen.org>

* feat(providers): add Nube.sh OpenAI-compatible provider (#5936)

* feat(providers): add Nube.sh OpenAI-compatible provider

Nube.sh is a live BYOK OpenAI-compatible gateway (LiteLLM proxy) at
https://ai.nube.sh/api/v1, Bearer/API-key auth. Registered as an apikey
inference-host with an OpenAI-format, default-executor registry entry.

Its live model catalog is only reachable with a valid key
(/api/v1/models returns 401 unauthenticated), so no model IDs are
hardcoded — the entry uses passthroughModels + modelsUrl for live
enumeration instead of shipping unverifiable IDs.

Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>
Inspired-by: https://github.com/decolua/9router/pull/2294

* test(golden): regenerate translate-path for nube provider

* test(providers): bump APIKEY count 162→163 for nube

---------

Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>

* feat(providers): add Charm Hyper OpenAI-compatible provider (#5961)

* feat(providers): add Charm Hyper OpenAI-compatible provider

Registers Charm Hyper (hyper.charm.land) as a new API-key gateway
provider: OpenAI-compatible chat completions format, bearer auth,
free tier (100 monthly Hypercredits). Models are resolved via
passthrough (modelsUrl + live /v1/models import) instead of a
hardcoded upstream model list, since the specific model catalog is
not publicly documented.

Co-authored-by: whale <admin@dyntech.cc>
Inspired-by: https://github.com/decolua/9router/pull/2006

* test(golden): regenerate translate-path for charm-hyper provider

* test(providers): bump APIKEY count 163→164 for charm-hyper

---------

Co-authored-by: whale <admin@dyntech.cc>

* feat(providers): add SumoPod and X5Lab OpenAI-compatible providers (#5963)

* feat(providers): add SumoPod and X5Lab OpenAI-compatible providers

Both are OpenAI-compatible BYOK aggregator gateways, wired via the
default executor with bearer API-key auth. Neither ships a hardcoded
model list — both use passthroughModels with an empty seed list and a
live /v1/models fetcher, so the catalog always reflects what each
gateway actually serves instead of speculative model IDs.

- SumoPod: https://ai.sumopod.com/v1/chat/completions (sk- keys)
- X5Lab: https://api.x5lab.dev/v1/chat/completions (x5- keys)

Regression guard: tests/unit/sumopod-x5lab-provider.test.ts.

Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>
Inspired-by: https://github.com/decolua/9router/pull/1288

* chore(changelog): restore release entries + add sumopod/x5lab bullet

* test(golden): regenerate translate-path for sumopod + x5lab providers

* test(providers): bump APIKEY count 164→166 for sumopod + x5lab

---------

Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>

* feat(server): support reverse-proxy basePath deployment (#5992)

* feat(server): support reverse-proxy basePath deployment

Adds OMNIROUTE_BASE_PATH (opt-in, empty by default) to next.config.mjs
using Next.js's native basePath support so a deployment behind a
reverse-proxy subpath (e.g. https://host/omniroute/) works without
manual header stripping. Next.js strips the configured prefix from
nextUrl.pathname before route classification, so classifyRoute() and
isLocalOnlyPath() keep matching un-prefixed paths.

The two hardcoded auth redirect targets in
src/server/authz/pipeline.ts (root "/" -> "/dashboard" and
unauthenticated dashboard -> "/login") now prefix with
request.nextUrl.basePath so they stay inside the deployed subpath.
Default empty basePath is a no-op for existing root-path deployments.

Co-authored-by: zocomputer <help@zocomputer.com>
Inspired-by: https://github.com/decolua/9router/pull/1810

* docs(env): document OMNIROUTE_BASE_PATH in .env.example + ENVIRONMENT.md; restore changelog

* docs(env): document AUGGIE_BIN + CLI_AUGGIE_BIN (base-red from #5972 auggie)

---------

Co-authored-by: zocomputer <help@zocomputer.com>

* refactor(combo): extract buildTargetTimeoutRunner from handleComboChat (#6036)

Bloco J (hot-path decomposition), Task 1. Extract the per-target-timeout dispatch wrapper
(handleComboChat's handleSingleModelWithTimeout closure) verbatim into the leaf
combo/targetTimeoutRunner.ts as a factory buildTargetTimeoutRunner({handleSingleModel,
comboTargetTimeoutMs, log}). The per-model abort still comes from target.modelAbortSignal,
so the outer request signal is intentionally not a dependency. Host call-sites unchanged.

combo.ts shrinks ~60 LOC; leaf is 91 LOC (<800). Body byte-identical (verbatim), no cycle.
This is the first slice toward extracting the shared attempt-loop/success/error handlers
(Tasks 3-4) that de-duplicate handleComboChat and handleRoundRobinCombo. Adds a dedicated
test (5) so the failover path can be mutated independently. Consumer tests stay green
(combo-strategy-fallbacks 24, combo-499-abort 5, empty-content-failover 3, body-400-stop 1,
priority-quota-exhaustion 2, rr-streaming-lock 1, rr-session-stickiness 2).

Plan: _tasks/superpowers/plans/2026-07-03-blocoJ-combo-hotpath-decomposition.md

* feat(cli-tools): add CodeWhale CLI tool (#5996)

CodeWhale (https://github.com/Hmbown/CodeWhale) is the actively-maintained
successor to DeepSeek TUI — same author, renamed project. Added as a dual
entry alongside the existing "deepseek-tui" catalog entry (rather than a
hard rename) so users who still run the old DeepSeek TUI binary keep a
working dashboard card, while new users are steered to "codewhale".

New /api/cli-tools/codewhale-settings route writes the primary
~/.codewhale/config.toml and keeps an existing legacy
~/.deepseek/config.toml in sync (read fallback + best-effort write sync),
mirroring deepseek-tui-settings/route.ts. CLI_TOOLS and cliRuntime catalogs
updated; catalog cardinality tests/constants bumped accordingly (18→19
visible code tools, 28→29 total).


Inspired-by: https://github.com/decolua/9router/pull/1761

Co-authored-by: aristorinjuang <aristorinjuang@gmail.com>

* feat(i18n): auto-detect browser language on first visit (#5979)

* feat(i18n): auto-detect browser language on first visit

Adds a pure detectBrowserLocale() matcher (exact match, zh-HK/zh-MO
folded to zh-TW, language-prefix match, else null) plus a client-only
LocaleAutoDetect component mounted once in the root layout. On first
visit (no locale cookie set), it reads navigator.languages, computes a
match against the supported locales, and persists it via the same
cookie/localStorage writer LanguageSelector already used for manual
selection (now extracted to shared/lib/persistLocale.ts) before
refreshing the router.

Co-authored-by: anmingwei <anmingwei@dobest.com>
Inspired-by: https://github.com/decolua/9router/pull/1324

* chore(changelog): restore release entries + add browser-lang-detect bullet

---------

Co-authored-by: anmingwei <anmingwei@dobest.com>

* fix(dashboard): render Update-now API errors as text, not the raw envelope object (#5991) (#6028)

Integrated into release/v3.8.44 — fix(dashboard) render Update-now API errors as text, not the raw envelope object (#5991).

Merged with --admin: the fix is a one-line frontend change funneling the error body through the already-tested extractApiErrorMessage() helper, guarded by tests/unit/ui/home-update-error-render-5991.test.ts (3/3 pass, 3/3 fail on pre-fix source). The release branch is under a heavy parallel-merge storm (tip advanced ~6× mid-CI), so the branch is synced to the latest tip and landed atomically to avoid perpetual CONFLICTING; unit-shard reds seen earlier were pre-existing base-reds/flakes unrelated to this source-scan-only change.

* feat(api): expose provider plugin manifest (#6001)

* feat(api): expose provider plugin manifest

* test(translator): split responses chat request coverage

* test(mutation): register provider coverage tests

* feat(api): expose provider plugin manifest

* fix(ci): fail closed for prerelease latest promotion

* chore(ci): reconcile provider manifest complexity gate

* feat(api): expose provider plugin manifest

* test(translator): split responses chat request coverage

* test(mutation): register provider coverage tests

* fix(ci): fail closed for prerelease latest promotion

* chore: rebase onto release tip; drop out-of-scope translator test split + promote-script tweak

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* docs(changelog): add provider plugin manifest entry

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(stryker): register account-fallback-retry-after-json test (base-red)

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: kooshapari <kooshapari@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(providers): add CN sign-up geo-restriction notices for SenseNova & StepFun (#5462)

* feat(sidecar): advertise provider manifest url (#6007)

* feat(sidecar): advertise provider manifest url via X-OmniRoute-Provider-Manifest-Url header

Re-cut onto release tip: manifest-url feature only (dropped stale-base noise).

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* docs(changelog): add sidecar manifest-url entry

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* chore(complexity): rebaseline 2009->2015 (inherited release-tip drift; feature adds 0)

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(autoCombo): latency/speed-optimized routing mode + omniroute_pick_fastest_model MCP tool (#6011)

* feat(autoCombo): latency/speed-optimized routing mode + omniroute_pick_fastest_model MCP tool

* test(translator): split responses chat request coverage

* refactor(mcp): extract fastest-model tool modules

* fix(i18n): cover provider icon and cors labels

* test(mutation): register latency coverage files

* test(ci): collect executor unit tests

* refactor(ci): reduce latency path complexity

* fix(mcp): include models catalog module

* feat(autoCombo): latency/speed-optimized routing + omniroute_pick_fastest_model MCP tool

Re-cut onto release tip: keep speed-routing + MCP tool + supporting catalog split;
drop out-of-scope translator split, en.json/ci.yml/package.json orphans, and unrelated
proxyFetch/responsesStreamHelpers/tokenLimitCounter refactors.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: kooshapari <kooshapari@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* docs(changelog): restore #5181/#5199/#5462 feature bullets eaten by merge

* feat(usage): on-demand period-scoped usage-data reset (re-cut onto release tip) (#5831)

* chore(quality): rebaseline eslintWarnings 4199->4256 + cognitiveComplexity 860->861 (v3.8.44 cycle drift)

Inherited v3.8.44 cycle drift measured on release tip 72ee80649 by the release-green
pre-flight during the /review-prs fix-batch round. The Quality Ratchet does NOT run on
PR->release fast-gates, so eslint warnings + cognitive complexity accrue unmeasured
across the cycle. Cyclomatic complexity is already green (2012 < baseline 2015) and
needs no bump. Each value carries a dated justification note; no production code touched.

* feat(claude-code): opt-in auto-permission classifier compat mode (re-cut onto release tip) (#5810)

* feat(providers): client-identity header profiles for compatible nodes (re-cut) + forbid cookie in custom headers (#5812)

* docs(openapi): document 9 newly-added routes to restore coverage ratchet (v3.8.44)

Documents the routes added this cycle that dropped openapiCoverage 36.9%->36.2%
below the ratchet baseline: 2 public v1 endpoints (/v1/ocr Mistral-OCR-compatible,
/v1/audio/translations Whisper-compatible) with full request/response specs, plus 7
dashboard/CLI-local routes marked x-internal:true (suggested-models, provider-plugin-
manifest, keys/{id}/devices, settings/purge-usage-history, oauth/codex/import-token,
cli-tools crush-settings + codewhale-settings). Coverage 36.2%->37.8% (207/547),
above baseline 36.9. check:openapi-routes/security-tiers/fabricated-docs all pass.

* refactor(sse): decompose handleComboChat auto-strategy region (Block J Task 2 — parseAutoConfig + resolveAutoStrategyOrder) (#6049)

* refactor(sse): extract pure parseAutoConfig leaf from handleComboChat

Block J Task 2 (safe slice): the auto-strategy config-resolution block in
handleComboChat is a pure function of (combo, eligibleTargets) with no side
effects, no early returns and no mutation. Extract it verbatim into
open-sse/services/combo/autoConfig.ts::parseAutoConfig so the god-function
shrinks and the derivation is independently unit-testable.

Behavior is byte-identical (verbatim-audited); combo.ts 3309->3280 LOC.
Adds tests/unit/combo-auto-config-split.test.ts (5 cases) pinning the
strategy-precedence, candidate-pool, weights and fallback derivations.

* refactor(sse): extract resolveAutoStrategyOrder leaf from handleComboChat

Block J Task 2 (coupled slice): the ~215-line `if (strategy === "auto")`
branch of handleComboChat is extracted into
open-sse/services/combo/resolveAutoStrategy.ts::resolveAutoStrategyOrder.

The branch is a control-flow region (mutates orderedTargets +
autoUsedExplicitRouter, early-returns 429, side-effect _registerExecutionCandidates),
so it is not a pure byte-identical move: the two `return unavailableResponse(...)`
exits become `{ earlyResponse }` and the mutated locals are returned instead of
closed over. Every other logic line is verbatim (semantic diff = only those
wrappers + the deeper getLKGP import path). `buildAutoCandidates` lives in
combo.ts, so it is injected via deps to keep the leaf acyclic (same DI pattern as
buildTargetTimeoutRunner) — which also makes the branch independently testable.

combo.ts 3280->3065 LOC. typecheck:core + check:cycles clean; dead host imports
removed. 60/60 consumer tests (router-strategies / auto-combo-engine /
combo-strategy-fallbacks / scoring-clamp / candidate-expansion / hidden-models)
cover the routable path end-to-end; new tests/unit/combo-resolve-auto-strategy-split.test.ts
pins the DI contract + the early-429 and default-ordering exits.

* test(sse): point quota-bypass source scan at resolveAutoStrategy leaf

The 'auto combo disables hard provider quota cutoffs when relay requests bypass'
source scan asserted combo.ts contains the bypass logic
(relayOptions?.bypassProviderQuotaPolicy === true + quotaPreflight enabled:false).
That block was extracted verbatim into combo/resolveAutoStrategy.ts (Block J
Task 2), so the scan now reads the leaf. Behavior unchanged.

* fix(ci): release-green base-reds — #5695 test regex + file-size rebaseline (#6093)

- tests/unit/ui/quick-start-api-keys-link-5695.test.ts: tolerate Prettier
  splitting <Link href=...> across lines (\s+) so the step1Desc regex matches
  the multi-line /dashboard/api-manager Link instead of skipping to step2's
  single-line /dashboard/providers Link. Code is correct; the test was brittle.
- config/quality/file-size-baseline.json: rebaseline 5 files that grew via
  already-merged PRs on the release tip (ApiManagerPageClient 3017->3058,
  OAuthModal 969->989, cliRuntime 1090->1100, webProvidersA 805->809,
  deepseek-web.test 1081->1092). Dated note added; shrink tracked in #3501.

* fix(translator): wrap Kiro system prompt in <system-reminder> (port from 9router#2306) (#6053)

Kiro/CodeWhisperer has no system role, so system messages were normalized to a
user turn with no wrapper — the full Claude Code system prompt then appeared as
raw user text, polluting the model context. Wrap system-origin content in
<system-reminder> tags before merging it into the Kiro user message. Real user
turns are unaffected. Existing history-merge tests aligned to the wrapped value.

Reported-by: VitzS7 (https://github.com/decolua/9router/issues/2306)

* fix(translator): strip multipleOf from antigravity/gemini tool schemas (port from 9router#2309) (#6052)

`multipleOf` is not part of the Gemini/antigravity OpenAPI 3.0 schema subset, so
leaving it in function_declaration parameters triggered a hard upstream 400
("Unknown name multipleOf"). Add it to GEMINI_UNSUPPORTED_SCHEMA_KEYS so it is
stripped at every schema level; minimum/maximum stay (Gemini accepts them).

Reported-by: abil0321 (https://github.com/decolua/9router/issues/2309)

* fix(kimi-web, qwen-web): align model catalog with live /models + map scenario per model (#5915)

* fix(kimi-web): align catalog with live models

Update the kimi-web catalog and request scenario selection to match
www.kimi.com's live GetAvailableModels response.

* fix(qwen-web): stop aliasing qwen3-coder-plus

Keep qwen3-coder-plus as its own model because it is present in the
live Qwen web models catalog.

* feat(minimax): extract M3 <think> to reasoning_content on OpenAI-format tiers (#6050)

MiniMax M3 is registered with format:"openai" on 8 provider tiers (trae,
huggingchat, bazaarlink, ollama-cloud, opencode, cline, opencode-zen,
codebuddy-cn), where its raw <think>...</think> tags leaked directly into
`content` instead of surfacing as a separate `reasoning_content` field.

OmniRoute already has the extraction primitive
(extractThinkingFromContent in responseSanitizer/reasoning.ts); it was just
gated to deepseek-r1/r1-distill/qwq. Extend the allowlist
(isTextualReasoningTagNativeRoute) with a minimax-m3-only pattern, excluding
the two direct minimax/minimax-cn tiers, which stay on Anthropic's Messages
format (targetFormat: "claude") and already surface reasoning natively.


Inspired-by: https://github.com/decolua/9router/pull/2231

Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: zmf963 <19422469+zmf963@users.noreply.github.com>

* fix: unwrap Cline response envelope (#6046)

Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>

* refactor(sse): extract applyStrategyOrdering leaf from handleComboChat (Block J Task 3) (#6063)

* refactor(sse): extract applyStrategyOrdering leaf from handleComboChat

Block J Task 3: the ~177-line else-if chain covering every non-auto combo
strategy (lkgp / strict-random / random / fill-first / p2c / least-used /
cost-optimized / reset-aware / reset-window / context-optimized / headroom /
quota-share) is extracted into
open-sse/services/combo/applyStrategyOrdering.ts::applyStrategyOrdering.

Each branch only reorders orderedTargets (no early returns, no other mutable
state), so the extraction is a clean verbatim move returning the reordered list;
the host replaces the chain with `else { orderedTargets = await
applyStrategyOrdering(strategy, orderedTargets, deps); }`. Semantic diff vs the
original chain = only the leading `if` (was `} else if`), the trailing return
and the deeper getLKGP import path — no logic line changed. None of the 13 strategy
helpers live in combo.ts, so no DI/cycle (unlike the auto branch).

combo.ts 3065->2883 LOC (3309->2883 across Task 2+3). typecheck:core + check:cycles
clean; 9 dead host imports removed (targetSorters block emptied). 47/47 consumer
tests (router-strategies / combo-strategy-fallbacks / rr-session-stickiness /
tag-routing) cover the DB-backed branches end-to-end; new
tests/unit/combo-apply-strategy-ordering-split.test.ts pins random / fill-first /
unknown exits.

* test(sse): point #2359 modelStr-guard scans at applyStrategyOrdering leaf

The LKGP fallback + non-auto strategy ordering (the two target.modelStr string-
method call sites) were extracted verbatim from combo.ts into the
applyStrategyOrdering leaf (Block J Task 3). The #2359 source scans now read the
leaf that owns those usages; the guard and the no-unguarded-usage assertions are
unchanged in intent.

* chore(ci): scan combo strategy leaves in check:known-symbols

Block J decomposed the combo dispatch: the `strategy === "..."` branches for
the 12 non-auto strategies moved to combo/applyStrategyOrdering.ts and the auto
branch to combo/resolveAutoStrategy.ts. The known-symbols gate previously scanned
only combo.ts, so it would report those strategies as canonicalNotHandled. Scan
all three dispatch files. Verified: 18/18 canonical strategies via dispatch.

* fix(combo): fallback to sibling model on 500 for per-model-quota providers (#5976)

* fix(combo): fallback to sibling model on 500 for per-model-quota providers

Two issues prevented combo fallback when gemini/gemma-4-31b-it returned 500:

1. targetExhaustion: connection-level exhaustion marked the shared gemini
   connection as exhausted, skipping the sibling model (gemma-4-26b-a4b-it).
   Skip markConnectionLevelExhaustion for per-model-quota providers (gemini,
   github, passthrough, compatible) since a model-level 500 does not mean
   the connection is bad.

2. combo retry loop: the auth layer records a model lockout on 500, but the
   retry loop did not check isModelLocked before retrying — it retried the
   same locked model instead of falling back. Add isModelLocked guard before
   the transient-retry decision.

* fix tests timeout

* fix: clear quota fallback CI gates

* quality-gate: extract test SSE stream helpers

* drop scope creep

* fix(combo): retry sibling models only on 500 errors

* fix(combo): reconcile onto release/v3.8.44 — keep targetExhaustion 500 fix, drop slow integration test

Reconciled by maintainer onto the current release tip:
- kept the core fix (targetExhaustion.ts model-500 guard for per-model-quota
  providers + the isModelLocked retry early-return in combo.ts) and its unit test
- dropped tests/integration/combo-concurrent-failure-recovery.test.ts +
  _sseTestHelpers.ts: they use Math.random()-based delays and 30s timeouts, run
  >3min and are flake-prone in the test:integration CI job; the unit test
  (tests/unit/combo/combo-target-exhaustion.test.ts, 21 cases) fully covers the fix
- CHANGELOG entry added

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

---------

Co-authored-by: Koosha Pari <kooshapari@gmail.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@outlook.com>
Co-authored-by: hartmark <hartmark@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(xai): surface Grok usage on quota dashboard via local usageHistory aggregation (#5806)

xAI has no public per-account quota API (the billing console requires a
session cookie, not an API key). Add getXaiUsage(connectionId), mirroring
the existing Xiaomi MiMo self-track pattern: sum tokens routed to the
connection from usage_history via getMonthlyProviderTokensForConnection
and surface them as a cumulative, uncapped quota (unlimited: true,
remaining: 100 — xAI has no fixed monthly cap). Register 'xai' in
USAGE_FETCHER_PROVIDERS and wire a switch case in getUsageForProvider.


Inspired-by: https://github.com/decolua/9router/pull/2150

Co-authored-by: ron <devestacion@gmail.com>

* feat(services): add Mux managed embedded service (#6034)

Adds Mux (coder/mux — local agent-orchestration daemon) as a fourth-tier
embedded service built on the existing ServiceSupervisor framework, the
same shape as 9Router and CLIProxyAPI:

- Installer (src/lib/services/installers/mux.ts): npm install/update via
  runNpm (array args + env-based prefix, no shell interpolation), modeled
  on ninerouter.ts. Mux ships an npm package (`mux`) with a documented
  headless `mux server --host <host> --port <port>` mode, so no
  git-clone+build path was needed.
- Registered in bootstrap.ts (SERVICES[] + buildSpawnArgsFactory).
- DB seed migration 113 (version_manager row, not_installed/auto_start=0).
- 7 API endpoints under /api/services/mux/ (install/start/stop/restart/
  update/status/auto-start) plus the shared [name]/logs SSE endpoint,
  mirroring the cliproxy route shape and delegating errors through
  createErrorResponse().
- Dashboard tab (MuxServiceTab) reusing ServiceStatusCard,
  ServiceLifecycleButtons, AutoStartToggle, ServiceLogsPanel.
- Docs: EMBEDDED-SERVICES.md (service table, architecture diagram, API
  reference, key-injection section), openapi.yaml, ENVIRONMENT.md,
  .env.example.

Security:
- Every /api/services/mux/* route is covered by the existing
  LOCAL_ONLY_API_PREFIXES "/api/services/" prefix (Hard Rule #17);
  added an explicit isLocalOnlyPath regression test for all 8 routes.
- Mux binds to 127.0.0.1 explicitly (never 0.0.0.0) as defense-in-depth,
  since it orchestrates AI agents that can execute host commands.
- The bearer token is generated the same way as 9Router's key
  (getOrCreateApiKey) and injected via MUX_SERVER_AUTH_TOKEN (mux's
  documented env form) rather than a CLI flag, so it never appears in
  `ps`/process listings.
- No shell interpolation anywhere in the installer (Hard Rule #13): all
  npm/spawn args are static arrays; the install prefix and auth token
  travel via the env option.


Inspired-by: https://github.com/decolua/9router/pull/1802

Co-authored-by: Ansh7473 <Ansh7473@users.noreply.github.com>

* feat(services): promote Bifrost to embedded/supervised service (#5670) (#5817)

Promotes Bifrost (@maximhq/bifrost — Go AI-gateway) from an env-only relay
sidecar to a first-class embedded/supervised service, matching the existing
cliproxy/9router model. Implements item #2 of #5670; the broader RouterBackend
contract (items #1, #3-#5) stays out of scope.

- Installer (npm-style, ninerouter model): install/update/getInstalledVersion/
  getLatestVersion (1h cache)/resolveSpawnArgs (Go single-dash flags, pinned
  BIFROST_TRANSPORT_VERSION), needsApiKey=false
- Bootstrap SERVICES entry (healthPath /v1/models) + spawn-args factory branch
- Migration 113 seeds the version_manager row (not_installed, port 8080,
  auto_update=1, provider_expose=1)
- 7 lifecycle API routes under /api/services/bifrost/ (verbatim from cliproxy,
  errors sanitized) — loopback-only via existing LOCAL_ONLY_API_PREFIXES
- Shared [name]/logs branch for bifrost
- Dashboard tab + registration in the services page shell
- Relay auto-wiring: getBifrostRoutingConfig defaults BIFROST_BASE_URL to the
  supervised port when the instance is running; explicit env still wins; the
  env-only relay path (/v1/relay/.../bifrost) stays unchanged (compat layer)
- Docs (EMBEDDED-SERVICES, openapi) + unit tests (installer/route-guard/routing,
  19 tests) + RUN_SERVICES_INT-gated integration lifecycle

Note: the actual Go-binary install/start/health path requires a documented VPS
live-test before merge (Hard Rule #18 / spec section 7); the gated integration
harness is the vehicle for that run.

* fix(ci): document BIFROST_PORT to clear env-doc-sync base-red

The Bifrost embedded-service merge referenced process.env.BIFROST_PORT
(src/lib/services/bootstrap.ts, default 8080) without adding it to
.env.example / ENVIRONMENT.md, so check:env-doc-sync failed on the release
tip and reddened Fast Quality Gates for every open PR->release. Docs-only.

* fix(providers): emulate OpenAI tool_calls in GitLab Duo executor (#6051) (#6111)

Co-authored-by: felssxs <felssxs@users.noreply.github.com>

* fix(providers): strip orphan tool_result on Antigravity MITM path (#6026) (#6115)

* fix(registry): update grok-cli model context lengths (#5913)

grok-build 128k→256k, grok-composer-2.5-fast 128k→200k to match actual Grok CLI /context capacities so context-aware routing stops filtering these models out. Registry-only.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(proxy): batch delete, auto-test, health scheduler + transitive alias fix (#5918)

Proxy-registry batch management (batch-delete, auto-test, background health scheduler) + fix resolveProviderAlias to follow the alias chain transitively (oc -> opencode -> opencode-zen). Probe target now operator-configurable via PROXY_HEALTH_TEST_URL. Scope-creep files from the original branch dropped.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* feat(minimax): extract M3 reasoning_content on OpenAI-format tiers (#6073)

MiniMax M3 leaks raw <think>...</think> into content on 8 OpenAI-format provider tiers; extract it into reasoning_content, leaving the direct minimax/minimax-cn (Claude-format) tiers untouched. Replacement for the stale #5804 branch.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(ci): harden provider translate-path golden across CI runners (#6076)

Normalize OS/arch-derived request headers (X-Stainless-Os/Arch, (OS;arch) UAs, and Antigravity's os.platform()-derived platform substring) in the golden so the test is runner-independent. Fixes the Mac-literal Antigravity UA that would have failed on Linux CI. Supersedes stale #6002.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* test(embeddings): pin seeded connection to direct egress in route-edge-coverage (#5975 collateral)

#5975 made the embeddings service honor the connection-level proxy. The pre-existing
route-edge-coverage embeddings edge-case tests seed an openai connection while the
settings-proxy suite has left a provider-level proxy (provider.local:8080) in the shared
DATA_DIR that resetStorage() does not clear — inert before #5975, but now the leaked
proxy fast-fails the embedding upstream with PROXY_UNREACHABLE.

These tests do not exercise proxying, so seedOpenAIConnection now pins the connection to
proxyEnabled:false, making resolveProxyForConnection return a direct egress regardless of
leaked global proxyConfig. No assertions weakened; 16/16 in the file pass. Regression
surfaced by the concurrency=1 full-suite run; passes on #5975's parent, red after it.

* fix(config): externalize ws for copilot-m365-web executor (#6130, closes #6062)

Re-lands the #6098 ws-externalization fix onto release/v3.8.44 (it had merged to main by mistake and was reverted). Externalize ws/bufferutil/utf-8-validate so the copilot-m365-web WebSocket masking path works at runtime.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(providers): update Perplexity Web models (#6106)

Refresh the Perplexity Web model catalog + mode/model_preference mappings to the current live set. Regression guard: perplexity-web.test.ts.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(providers): update Gemini Web cookies and models (#6095)

Refresh Gemini Web cookie handling + model catalog. Regression guard: gemini-web.test.ts.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(models): normalize GLM-5.2 provider context (#6091)

Hosted GLM-5.2 provider aliases now respect their declared context caps instead of inheriting the native 1M; native/bare + verified OpenCode/ZenMux routes stay at 1M. Regression guards added.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix(combo): prefer known context capacity over unknown (#6088)

When a combo filters a target for exceeding a known context limit, prefer remaining known-compatible targets over unknown-metadata ones. Regression guard: combo-context-window-filter.test.ts.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>

* fix: keep Claude tool results adjacent (#6035)

Reattach OpenAI tool_result adjacent to tool_use before Claude send (#6026). Integrated into release/v3.8.44.

* fix(security): persist IP filter config + enforce it in the authz pipeline (#6131) (#6132)

Integrated into release/v3.8.44 — IP filter persistence + authz-pipeline enforcement (closes #6131). HARD-neutro: validate-release-green on the merge shows the same 3 pre-existing base-reds as the release baseline (test-masking cycle-wide, unit red-herring, integration batch-E2E env); #6131's own tests + ip-filter/pipeline suites all green.

* fix(codex): use access_token.exp instead of id_token.exp for import expiresAt (#6075) (#6084)

Prefer access_token.exp over id_token.exp for Codex auth import (#6075). Integrated into release/v3.8.44.

* fix(compression): send patch-only to PUT /api/settings/compression in CompressionHub (#6039) (#6077)

Send patch-only to PUT /api/settings/compression in CompressionHub (#6039). Integrated into release/v3.8.44.

* fix: reqId ReferenceError in safety-net redirect, dead code, filename typo (#6097)

Fix reqId ReferenceError in safety-net combo redirect + dead-code + DESING→DESIGN rename. Integrated into release/v3.8.44.

* fix(combo): expand fingerprint-based providers into per-fingerprint combo targets (#6082)

Expand fingerprint-based providers into per-fingerprint combo targets. Integrated into release/v3.8.44.

* fix(auth): persist quota preflight account lockouts (#6090)

Persist quota preflight account lockouts until reset window. Integrated into release/v3.8.44.

* fix(combos): expand OpenCode/MiMo fingerprint accounts in combo builder (#6087) (#6092)

Expand OpenCode/MiMo fingerprint accounts in combo builder (#6087). Integrated into release/v3.8.44.

* chore(quality): rebaseline v3.8.44 release-green drift (eslint/cognitive/cyclomatic/file-size)

Measured on release tip 32e4c906e during the #6131/#5975 release-green pass:
eslintWarnings 4256->4270 (+14), cognitiveComplexity 861->867 (+6), cyclomatic
count 2015->2026 (+11), and testFrozen caps for models-catalog-route (1507->1600),
perplexity-web (959->999), route-edge-coverage (1234->1241, my #5975 comment +7).
Inherited cycle drift (the Quality Ratchet does not run on PR->release fast-gates);
compression 'bun not found' is a local-env false and codeql is within baseline, so
neither is rebaselined. No production code touched.

* fix(accountFallback): persist per-account 429 cascade + classify 'Monthly usage limit. Resets in N days.' (#6061)

Persist per-account 429 cascade + classify 'Monthly usage limit. Resets in N days'. Integrated into release/v3.8.44.

* feat(build): backend-only fast build (skip the dashboard frontend) (#6119)

Backend-only fast build (skip dashboard frontend). Integrated into release/v3.8.44.

* fix(provider-limits): clear transient rate-limit state when quota recovers (#6128)

Clear transient rate-limit state when quota recovers. Integrated into release/v3.8.44.

* docs: Normalize mixed-language documentation content (#6105)

Normalize mixed-language documentation to English. Integrated into release/v3.8.44.

* chore docs

* i18n(zh-CN): translate CHANGELOG entries and section headings (#6043)

Adopt zh-CN as a translated locale: translate CHANGELOG + supporting docs. Integrated into release/v3.8.44.

* chore(quality): rebaseline residual eslint + file-size drift (v3.8.44)

Residual drift on release tip 716041223 (moving target): eslintWarnings 4270->4279
(+9 as the branch advanced past the prior rebaseline) and testFrozen/frozen file-size
caps for providerLimits.ts (955->982), accountFallback.ts (1790->1864) and
sse-auth.test.ts (1553->1600). All inherited from parallel-session merges (e.g. #6128);
the two production god-files ideally warrant decomposition rather than a bump (tracked
as debt). No production code touched.

* fix(repo): remove Windows case-conflicting DESIGN duplicate (#6140)

Remove stale root DESIGN.md (Windows case-conflict with design.md). Integrated into release/v3.8.44.

* fix(provider-limits): close TOCTOU race in quota recovery clear (I2) (#6139)

Close TOCTOU race in quota recovery clear via CAS primitive (I2 from #6128). Integrated into release/v3.8.44.

* fix(glm): suppress </think> close marker leak in GLM Anthropic transport (#6133)

Suppress </think> close-marker leak in GLM Anthropic transport. Integrated into release/v3.8.44.

* fix(cli): give setup-claude a fallback profile generator like setup-codex (#6138)

Give setup-claude a fallback profile generator like setup-codex. Integrated into release/v3.8.44.

* fix(onboarding): route provider-details link by node id, not provider slug (#6145) (#6145)

Route onboarding provider-details link by node id (#6145). Integrated into release/v3.8.44.

* fix(translator): strip Responses-only truncation field before Chat Completions forwarding (#6109)

Strip Responses-only truncation field before Chat Completions forwarding (#2311). Integrated into release/v3.8.44.

* fix(mitm): guard against concurrent MITM server starts (#6107)

Guard against concurrent MITM server starts (#2316). Integrated into release/v3.8.44.

* feat(models): add claude-sonnet-5 to Antigravity catalog (#6103)

Add claude-sonnet-5 to Antigravity catalog. Integrated into release/v3.8.44.

* fix(providers): strip thinking param for minimax-m2.7 on NVIDIA NIM (#6102)

Strip unsupported thinking param for minimax-m2.7 on NVIDIA NIM. Integrated into release/v3.8.44.

* feat(providers): add Kenari OpenAI-compatible gateway (#6104)

Add Kenari OpenAI-compatible gateway (BYOK). Integrated into release/v3.8.44.

* feat(sse): per-request Auto-Combo controls (X-OmniRoute-Mode / X-OmniRoute-Budget) — closes #6023 #6024 #6025 (#6057)

Per-request Auto-Combo controls (X-OmniRoute-Mode / X-OmniRoute-Budget). Integrated into release/v3.8.44.

* feat(resilience): throttle concurrent upstream quota fetches — closes #6009 (#6058)

Throttle concurrent upstream quota fetches (#6009). Integrated into release/v3.8.44.

* fix(oauth): graceful 400 for keychain-import-only providers (zed) (#6041) (#6054)

Graceful 400 for keychain-import-only providers on OAuth route (zed, #6041). Integrated into release/v3.8.44.

* fix(dashboard): resolve broken Card import breaking next build (base-red from #6061) (#6155)

* fix(dashboard): resolve broken Card import breaking next build (base-red from #6061)

CoolingConnectionsPanel imported `Card` from `@/components/ui/card`, a path
that does not exist in this repo (there is no shadcn-style `src/components/ui/`).
The PR->release fast-gates do not run `next build`, so the broken import slipped
in and `next build` failed with:

  Module not found: Can't resolve '@/components/ui/card'

Fix: the <Card> here was only a styled container, so replace it with a <div>
carrying the equivalent Tailwind classes (border/bg/padding + rounded-card
shadow-sm). Also normalize the file from CRLF to LF (it shipped with CRLF).

Adds a vitest/jsdom regression test (tests/unit/ui/CoolingConnectionsPanel.test.tsx)
that fails-without-fix (Vite: 'Failed to resolve import @/components/ui/card')
and passes with it, plus renders/empty-state coverage. Rule #18.

* fix(dashboard): stop client CoolingConnectionsPanel dragging server DB barrel into browser bundle

Second base-red from #6061, surfaced once the broken Card import was fixed:

  ./node_modules/ioredis/built/connectors/StandaloneConnector.js
  Module not found: Can't resolve 'net'
  Import trace: ioredis <- rateLimiter.ts <- apiKeys.ts <- @/lib/localDb
                <- CoolingConnectionsPanel.tsx (a "use client" component)

The client panel imported `formatResetCountdown` from `@/lib/localDb` — the
server-side DB re-export barrel — which transitively pulls better-sqlite3/ioredis
(node:net) into the browser bundle. That violates the CLAUDE.md rule 'never
barrel-import from localDb'.

`formatResetCountdown` is a pure date-formatting function, so move its
implementation to the client-safe `@/shared/utils/formatting` (alongside
formatTime/formatDuration) and re-export it from db/providers/rateLimit.ts for the
existing server callers + barrel. The panel now imports it directly from the
shared util — no server code in the client bundle.

Tests (Rule #18):
- tests/unit/format-reset-countdown.test.ts (node:test, blocking test:unit) —
  pure-function coverage: null/past/invalid, s, m+s, h+m, ISO string.
- tests/unit/ui/CoolingConnectionsPanel.test.tsx mock updated to the new module.

* fix(release): v3.8.44 Phase-0 pre-flight — base-red sweep + ratchet absorption

- fix(models): stop resolveProviderAlias at registered provider ids so oc/
  reaches the no-auth opencode provider again (#2901 contract, regressed by
  #5918's transitive chain; transitivity kept across alias-only hops)
- fix(auggie): handle async EPIPE 'error' events on child stdin so a
  fast-exiting CLI surfaces a sanitized error instead of crashing (both
  spawn sites); deflakes auggie-executor tests
- test: align provider family count 166->167 (Kenari #6104), regenerate
  translate-path golden on Linux (+kenari), opencode quota scope
  provider->connection (#6061)
- quality(test-masking): add _deletedWithReplacement allowlist support to
  check-test-masking.mjs (deletion exempt ONLY when the declared replacement
  test exists in HEAD; 5 new gate unit tests) + reduction allowlist entries
  for the verified #5958/#6088/#5816 migrations + targetExhaustion->
  combo-target-exhaustion replacement (#5976, 21 cases/52 asserts vs 13/37)
- quality(file-size): absorb v3.8.44 cycle drift (oauth route 960,
  providerLimits 998, chat 1662, auth 2426) with justification; #6158 will
  restore the oauth-route freeze
- changelog: bullets for the above + the #6155 cooling-panel build fix

* chore(release): v3.8.44 — 2026-07-04

Release reconciliation + close (generate-release Phases 0a/1):
- CHANGELOG [3.8.44]: 21 PR refs added to existing bullets, 62 new bullets
  (incl. restoration of ~10 bullets erased by the stale-branch merge in
  1f6ec5bc8), 3 Maintenance rollups, #6061/#6130 credit fixes, 🙌 Contributors
  table (35 external contributors) — coverage 144/153 cycle commits by #ref
- 42 docs/i18n CHANGELOG mirrors synced (EN content; i18n workflow translates)
- README: What's New refreshed for v3.8.44 highlights
- build scope: exclude electron/node_modules + electron/dist-electron + .build
  from tsconfig (local build-output leak poisoned next build with 8GB OOM —
  same class as the 2026-06-25 incident; scope 14765→5207, gate green)
- quality: cyclomatic baseline 2026→2028 (+2 inherited end-of-cycle drift;
  verified the release-captain code fixes add 0 new violations)

* fix(release): v3.8.44 one-pass release-PR CI sweep

- fix(dashboard): /dashboard/system/proxy 500'd on EVERY render — #5918 put
  useProxyBatchOperations(load) before the const load declaration (TDZ
  ReferenceError, digest 539380095). Hook block moved after load; SSR
  renderToString regression test added (the exact crash mode).
- fix(server): TRACE/TRACK/CONNECT crashed Next's middleware adapter
  (undici cannot represent them) into a raw 500 on every route — the raw
  HTTP method guard now answers 405 + Allow up-front (dast-smoke
  Schemathesis finding on /api/keys/{id}/devices); guard test added.
- fix(api): restore Zod validation on the provider-scoped chat route via a
  .passthrough() schema preserving #5907's relaxed semantics (t06 gate).
- docs(openapi): /api/keys/{id}/devices 401 now refs the management error
  envelope (Schemathesis schema-conformance).
- quality: rebaseline i18nUiCoverage 77.5->76.8 (+~1352 new en.json UI keys
  from the cycle await the async translation workflow; v3.8.39 precedent).
- CodeQL: dismissed 2 incomplete-url-substring FPs on unit-test asserts
  (v3.8.35 precedent) with Hard Rule #14 justifications.
- changelog: bullets for the above + 42 i18n mirrors re-synced

* fix(release): round-2 CI findings — LocaleAutoDetect refresh gating + ratchet tighten

- fix(i18n): LocaleAutoDetect (#5979) refreshed the router on EVERY cookie-less
  first visit, even when the detected locale matched the server-rendered
  <html lang> — re-navigating mid-interaction (flaky e2e 'execution context
  destroyed' + visible flash for new visitors). Refresh now only fires when
  the locale actually differs; regression test added.
- quality: tighten openapiCoverage.pct 36.9->39.3 (require-tighten gate on the
  release PR; value measured by the CI Quality Ratchet on 00c55afcb)
- quality(file-size): shrink the ProxyRegistryManager TDZ note to fit the
  1117-line freeze (prettier reflow added a line at commit time)
- changelog bullet + 42 i18n mirrors re-synced

* test(release): collect the #6082 fingerprint-expansion ghost test

check:test-discovery (Lint job, layered behind the round-1 t06 fix) flagged
tests/e2e/fingerprint-expansion.test.ts as a NEW orphan — it is a node:test
server-boot test that no runner collected, so it had never run. Moved to
tests/integration/ (the collector for this shape), fixed the helper import,
and verified it actually passes (3/3 on first-ever run). CHANGELOG ref updated.

---------

Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com>
Co-authored-by: Hamsa_M <116961508+hamsa0x7@users.noreply.github.com>
Co-authored-by: Chewji <126886556+Chewji9875@users.noreply.github.com>
Co-authored-by: Vittor Guilherme Borges de Oliveira <vittoroliveira.dev@gmail.com>
Co-authored-by: nickwizard <35692452+nickwizard@users.noreply.github.com>
Co-authored-by: Ankit <177378174+anki1kr@users.noreply.github.com>
Co-authored-by: Fadhil Yusuf <33994304+yusufrahadika@users.noreply.github.com>
Co-authored-by: Giorgos Giakoumettis <giorgos@yiakoumettis.gr>
Co-authored-by: KooshaPari <42529354+KooshaPari@users.noreply.github.com>
Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com>
Co-authored-by: AgentKiller45 <jamalzzj45@gmail.com>
Co-authored-by: Nikolay Alafuzov <alafuzov_nn@rusklimat.ru>
Co-authored-by: ricatix <d.enistraju155@gmail.com>
Co-authored-by: Muhammad Mugni Hadi <mugni@rukita.co>
Co-authored-by: Randi <55005611+rdself@users.noreply.github.com>
Co-authored-by: backryun <bakryun0718@proton.me>
Co-authored-by: Ngô Tấn Tài <tantai@newnol.io.vn>
Co-authored-by: dopaemon <polarisdp@gmail.com>
Co-authored-by: yicone <yicone@gmail.com>
Co-authored-by: CườngNH <j2.cuong@gmail.com>
Co-authored-by: DuyPrX <93126969+DuyPrX@users.noreply.github.com>
Co-authored-by: ryanngit <74137224+ryanngit@users.noreply.github.com>
Co-authored-by: eng2007 <aleksey.semenov@gmail.com>
Co-authored-by: chamdanilukman <16629923+chamdanilukman@users.noreply.github.com>
Co-authored-by: Umar Javed <114807145+tn5052@users.noreply.github.com>
Co-authored-by: JiangZhuo <jiangzhuo@qiniu.com>
Co-authored-by: Delynn Assistant <zhen@dkzhen.org>
Co-authored-by: whale9820 <87256750+whale9820@users.noreply.github.com>
Co-authored-by: whale <admin@dyntech.cc>
Co-authored-by: Rigel Ramadhani Waloni <rigel8911@gmail.com>
Co-authored-by: zocomputer <help@zocomputer.com>
Co-authored-by: aristorinjuang <aristorinjuang@gmail.com>
Co-authored-by: anmingwei <anmingwei@dobest.com>
Co-authored-by: janeza2 <49841619+janeza2@users.noreply.github.com>
Co-authored-by: zmf963 <19422469+zmf963@users.noreply.github.com>
Co-authored-by: Markus Hartung <mail@hartmark.se>
Co-authored-by: Koosha Pari <kooshapari@gmail.com>
Co-authored-by: hartmark <hartmark@users.noreply.github.com>
Co-authored-by: ron <devestacion@gmail.com>
Co-authored-by: Ansh7473 <Ansh7473@users.noreply.github.com>
Co-authored-by: felssxs <felssxs@users.noreply.github.com>
Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com>
Co-authored-by: Arthur Bodera <abodera@gmail.com>
Co-authored-by: Semianchuk Vitalii <fix20152@gmail.com>
Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com>
Co-authored-by: NOXX - Commiter <artur1992123@mail.ru>
Co-authored-by: Milan Soni <123074437+Iammilansoni@users.noreply.github.com>
Co-authored-by: Devin <studyzy@gmail.com>
Co-authored-by: Raxxoor <manker_lol@hotmail.com>
Co-authored-by: derhornspieler <15236687+derhornspieler@users.noreply.github.com>
2026-07-04 13:00:30 -03:00

7172 lines
200 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.8.44
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,
failover, and usage tracking.
## Base URLs
- **Local**: `http://localhost:20128`
## Authentication
All proxy endpoints require a Bearer token (API key managed via the dashboard).
Management endpoints are protected when `requireLogin` is enabled.
contact:
name: OmniRoute
license:
name: MIT
servers:
- url: http://localhost:20128
description: Local development
tags:
- name: Playground
description: Playground Studio — preset management and prompt improvement
- name: Memory
description: Conversational memory management — CRUD, engine status, playground preview, summarization, reindex, and Qdrant settings (plan 21 — v3.8.6). All routes require management auth.
- name: Chat
description: OpenAI-compatible chat completions
- name: Messages
description: Anthropic-compatible messages
- name: Responses
description: OpenAI Responses API
- name: Embeddings
description: Text embedding generation
- name: Images
description: Image generation
- name: Audio
description: Audio speech and transcription
- name: Moderations
description: Content moderation
- name: Rerank
description: Document reranking
- name: Models
description: Available model listing
- name: Providers
description: Provider connection management
- name: Provider Nodes
description: Provider node configuration
- name: API Keys
description: API key management
- name: Combos
description: Routing combo management
- name: Settings
description: Application settings
- name: Compression
description: Prompt compression, RTK filters, Caveman rules, and compression combos
- name: Usage
description: Usage analytics and logs
- name: Translator
description: Format translation debug & testing
- name: CLI Tools
description: CLI tool configuration management
- name: Embedded Services
description: >-
Install, start, stop, and monitor locally-running embedded services (9Router, CLIProxyAPI).
All routes are LOCAL_ONLY — accessible from loopback only (hard rule #17).
- name: OAuth
description: OAuth flows for provider authentication
- name: System
description: System management (restart, shutdown, backup)
- name: Pricing
description: Model pricing configuration
- name: Cloud
description: Cloud worker authentication and sync
- name: Fallback
description: Fallback chain management
- name: Telemetry
description: Telemetry and token health monitoring
- name: Agent Skills
description: >-
Agent Skills catalog — 42 SKILL.md files (22 REST API + 20 CLI) for external agents,
MCP clients, and A2A orchestrators to discover OmniRoute capabilities.
- name: AgentBridge
description: >-
MITM proxy manager for 9 IDE agents (Antigravity, Kiro, Copilot, Codex, Cursor, Zed,
Claude Code, Open Code, Trae). Controls server lifecycle, DNS/model mappings, bypass list,
and cert management. All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17).
See docs/frameworks/AGENTBRIDGE.md.
- name: Traffic Inspector
description: >-
LLM-aware HTTPS traffic debugger with 4 capture modes (AgentBridge, Custom Hosts,
HTTP_PROXY :8080, System-wide). Provides real-time WebSocket stream, session recording,
HAR export, SSE merge, and conversation normalization.
All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17).
See docs/frameworks/TRAFFIC_INSPECTOR.md.
paths:
# --- Playground + Search Tools (plans 17+18) ---
/api/playground/improve-prompt:
post:
tags:
- Playground
summary: Improve prompt via LLM
description: |
Rewrites the supplied system prompt and/or user prompt using a meta-prompt
(inspired by Anthropic Console Prompt Improver). Internally calls
`/v1/chat/completions` with the model specified in the request body.
Quota is consumed from the caller's account.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- model
properties:
system:
type: string
maxLength: 50000
description: System prompt to improve (at least one of system/prompt required)
prompt:
type: string
maxLength: 50000
description: User prompt to improve
model:
type: string
description: Model to use for the improvement call (e.g. openai/gpt-4o)
tone:
type: string
enum:
- concise
- detailed
default: concise
responses:
"200":
description: Improved prompt(s)
content:
application/json:
schema:
type: object
properties:
improvedSystem:
type: string
improvedPrompt:
type: string
tokensIn:
type: integer
tokensOut:
type: integer
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/playground/presets:
get:
tags:
- Playground
summary: List playground presets
description: Returns all saved playground presets ordered by creation date (newest first).
security:
- BearerAuth: []
responses:
"200":
description: Preset list
content:
application/json:
schema:
type: object
properties:
presets:
type: array
items:
$ref: "#/components/schemas/PlaygroundPreset"
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags:
- Playground
summary: Create playground preset
description: Saves the current playground configuration as a named preset in the database.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPresetCreate"
responses:
"201":
description: Created preset
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPreset"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/playground/presets/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
get:
tags:
- Playground
summary: Get playground preset
security:
- BearerAuth: []
responses:
"200":
description: Preset found
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPreset"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Preset not found
put:
tags:
- Playground
summary: Update playground preset
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPresetCreate"
responses:
"200":
description: Updated preset
content:
application/json:
schema:
$ref: "#/components/schemas/PlaygroundPreset"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Preset not found
delete:
tags:
- Playground
summary: Delete playground preset
security:
- BearerAuth: []
responses:
"204":
description: Deleted
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Preset not found
# --- Memory Engine (plan 21) ---
/api/memory:
get:
tags:
- Memory
summary: List memory entries
security:
- ManagementSessionAuth: []
parameters:
- name: apiKeyId
in: query
schema:
type: string
- name: type
in: query
schema:
type: string
enum:
- factual
- episodic
- procedural
- semantic
- name: sessionId
in: query
schema:
type: string
- name: q
in: query
schema:
type: string
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
- name: offset
in: query
schema:
type: integer
minimum: 0
responses:
"200":
description: Paginated list of memories with stats
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/MemoryEntry"
total:
type: integer
totalPages:
type: integer
stats:
type: object
properties:
total:
type: integer
tokensUsed:
type: integer
hitRate:
type: number
cacheStats:
type: object
properties:
hits:
type: integer
misses:
type: integer
"401":
$ref: "#/components/responses/Unauthorized"
post:
tags:
- Memory
summary: Create a memory entry
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- content
- key
properties:
content:
type: string
minLength: 1
key:
type: string
minLength: 1
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
default: factual
sessionId:
type: string
nullable: true
apiKeyId:
type: string
metadata:
type: object
additionalProperties: true
expiresAt:
type: string
format: date-time
nullable: true
responses:
"201":
description: Created memory entry
content:
application/json:
schema:
$ref: "#/components/schemas/MemoryEntry"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
description: Memory UUID
get:
tags:
- Memory
summary: Get a single memory entry
security:
- ManagementSessionAuth: []
responses:
"200":
description: Memory entry
content:
application/json:
schema:
$ref: "#/components/schemas/MemoryEntry"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Memory not found
put:
tags:
- Memory
summary: Update a memory entry
description: Update `type`, `key`, `content`, and/or `metadata` of an existing memory. If an embedding source is available, the vector in `vec_memories` is also regenerated. Corresponds to `MemoryUpdatePutSchema`.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
key:
type: string
minLength: 1
content:
type: string
minLength: 1
metadata:
type: object
additionalProperties: true
additionalProperties: false
responses:
"200":
description: Updated memory entry
content:
application/json:
schema:
$ref: "#/components/schemas/MemoryEntry"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Memory not found
delete:
tags:
- Memory
summary: Delete a memory entry
description: Deletes the SQLite row, removes the vector from `vec_memories`, and best-effort deletes the point from Qdrant.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Deleted
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
"401":
$ref: "#/components/responses/Unauthorized"
"404":
description: Memory not found
/api/memory/health:
get:
tags:
- Memory
summary: Memory store health check
description: Round-trip create→list→delete to verify the store is alive.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Health result
content:
application/json:
schema:
type: object
properties:
working:
type: boolean
latencyMs:
type: number
error:
type: string
nullable: true
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/retrieve-preview:
post:
tags:
- Memory
summary: Dry-run memory retrieval (Playground)
description: Simulates `retrieveMemories()` for a given query and returns the ranked results with score, tier, and token count. Does NOT modify any memory. Corresponds to `RetrievePreviewSchema`.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- query
properties:
query:
type: string
minLength: 1
strategy:
type: string
enum:
- exact
- semantic
- hybrid
default: hybrid
maxTokens:
type: integer
minimum: 1
maximum: 16000
default: 2000
apiKeyId:
type: string
description: Optional — tests global pool when omitted
limit:
type: integer
minimum: 1
maximum: 100
default: 20
additionalProperties: false
responses:
"200":
description: Preview results
content:
application/json:
schema:
type: object
properties:
memories:
type: array
items:
type: object
properties:
id:
type: string
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
key:
type: string
content:
type: string
score:
type: number
tokens:
type: integer
tier:
type: string
enum:
- fts5
- vector
- hybrid-rrf
- qdrant
vecScore:
type: number
nullable: true
ftsScore:
type: number
nullable: true
resolution:
type: object
properties:
embeddingSource:
type: string
enum:
- remote
- static
- transformers
nullable: true
embeddingModel:
type: string
nullable: true
vectorStore:
type: string
enum:
- sqlite-vec
- qdrant
- none
strategyUsed:
type: string
enum:
- exact
- semantic
- hybrid
rerankApplied:
type: boolean
fallbackReason:
type: string
nullable: true
totalTokensUsed:
type: integer
budgetMaxTokens:
type: integer
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/embedding-providers:
get:
tags:
- Memory
summary: List embedding providers
description: Returns all providers that have embedding-capable models, indicating which have an active API key configured.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Provider list
content:
application/json:
schema:
type: object
properties:
providers:
type: array
items:
type: object
properties:
provider:
type: string
hasKey:
type: boolean
models:
type: array
items:
type: object
properties:
id:
type: string
description: "Format: provider/model"
name:
type: string
dimensions:
type: integer
nullable: true
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/engine-status:
get:
tags:
- Memory
summary: Memory engine status
description: Returns the full engine status including keyword tier availability, embedding resolution, vector store statistics (sqlite-vec), Qdrant health, and rerank configuration. Corresponds to `MemoryEngineStatusSchema`.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Engine status
content:
application/json:
schema:
type: object
properties:
keyword:
type: object
properties:
available:
type: boolean
backend:
type: string
enum:
- FTS5
embedding:
type: object
properties:
source:
type: string
enum:
- remote
- static
- transformers
nullable: true
model:
type: string
nullable: true
dimensions:
type: integer
nullable: true
available:
type: boolean
reason:
type: string
cacheStats:
type: object
properties:
hits:
type: integer
misses:
type: integer
size:
type: integer
vectorStore:
type: object
properties:
backend:
type: string
enum:
- sqlite-vec
- qdrant
- none
available:
type: boolean
rowCount:
type: integer
needsReindex:
type: integer
reason:
type: string
qdrant:
type: object
properties:
enabled:
type: boolean
healthy:
type: boolean
nullable: true
latencyMs:
type: number
nullable: true
error:
type: string
nullable: true
rerank:
type: object
properties:
enabled:
type: boolean
provider:
type: string
nullable: true
model:
type: string
nullable: true
available:
type: boolean
reason:
type: string
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/summarize:
post:
tags:
- Memory
summary: Compact old memories
description: "Manually triggers memory compaction for memories older than `olderThanDays`. Use `dryRun: true` to preview candidates. Corresponds to `MemorySummarizeSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
olderThanDays:
type: integer
minimum: 1
maximum: 365
default: 30
apiKeyId:
type: string
description: Optional — compacts all keys when omitted
dryRun:
type: boolean
default: false
additionalProperties: false
responses:
"200":
description: Summarization result
content:
application/json:
schema:
type: object
properties:
candidates:
type: integer
tokensSaved:
type: integer
dryRun:
type: boolean
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/memory/reindex:
post:
tags:
- Memory
summary: Trigger vector reindex
description: "Starts background reindexing of memories with `needs_reindex = 1`. Use `force: true` to regenerate ALL vectors regardless of index status. Corresponds to `MemoryReindexSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
force:
type: boolean
default: false
description: When true, marks all memories needs_reindex=1 before running.
additionalProperties: false
responses:
"200":
description: Reindex started
content:
application/json:
schema:
type: object
properties:
started:
type: boolean
pending:
type: integer
description: Memories still pending after this batch
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/memory:
get:
tags:
- Memory
- Settings
summary: Get memory settings
description: Returns the extended memory settings including 7 new fields added in plan 21 (embeddingSource, embeddingProviderModel, transformersEnabled, staticEnabled, rerankEnabled, rerankProviderModel, vectorStore).
security:
- ManagementSessionAuth: []
responses:
"200":
description: Extended memory settings
content:
application/json:
schema:
$ref: "#/components/schemas/MemorySettingsExtended"
"401":
$ref: "#/components/responses/Unauthorized"
put:
tags:
- Memory
- Settings
summary: Update memory settings
description: "Update any subset of the extended memory settings. All fields are optional; only provided fields are updated. Schema: `MemorySettingsExtendedSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MemorySettingsExtended"
responses:
"200":
description: Updated memory settings
content:
application/json:
schema:
$ref: "#/components/schemas/MemorySettingsExtended"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/qdrant:
get:
tags:
- Memory
- Settings
summary: Get Qdrant settings
description: Returns current Qdrant configuration. The `apiKey` field is never returned raw — use `hasApiKey` / `apiKeyMasked` instead.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Qdrant settings
content:
application/json:
schema:
$ref: "#/components/schemas/QdrantSettings"
"401":
$ref: "#/components/responses/Unauthorized"
put:
tags:
- Memory
- Settings
summary: Update Qdrant settings
description: 'Update Qdrant configuration. Pass `apiKey: ""` to remove the stored key. Schema: `QdrantSettingsUpdateSchema`.'
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
host:
type: string
port:
type: integer
minimum: 1
maximum: 65535
collection:
type: string
minLength: 1
embeddingModel:
type: string
minLength: 1
apiKey:
type: string
description: Empty string removes the key
additionalProperties: false
responses:
"200":
description: Updated Qdrant settings
content:
application/json:
schema:
$ref: "#/components/schemas/QdrantSettings"
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/qdrant/health:
get:
tags:
- Memory
summary: Qdrant health probe
description: Performs a liveness check against the configured Qdrant instance. Returns latency and any connection error (sanitized — no stack traces).
security:
- ManagementSessionAuth: []
responses:
"200":
description: Health result
content:
application/json:
schema:
$ref: "#/components/schemas/QdrantHealthResult"
"401":
$ref: "#/components/responses/Unauthorized"
/api/settings/qdrant/search:
post:
tags:
- Memory
summary: Qdrant semantic search test
description: "Performs a test semantic search against the Qdrant collection. Useful for validating that the integration works end-to-end. Schema: `QdrantSearchSchema`."
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- query
properties:
query:
type: string
minLength: 1
topK:
type: integer
minimum: 1
maximum: 50
default: 5
additionalProperties: false
responses:
"200":
description: Search results
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
type: object
"400":
description: Validation error
"401":
$ref: "#/components/responses/Unauthorized"
"503":
description: Qdrant unavailable (structured error, no stack trace)
/api/settings/qdrant/cleanup:
post:
tags:
- Memory
summary: Clean up expired Qdrant points
description: Removes Qdrant points for memories that have expired or exceeded the configured retention window.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Cleanup result
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
checked:
type: integer
"401":
$ref: "#/components/responses/Unauthorized"
"503":
description: Qdrant unavailable (structured error, no stack trace)
/api/settings/qdrant/embedding-models:
get:
tags:
- Memory
summary: List Qdrant embedding models
description: Returns the list of embedding models available for use with Qdrant.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Embedding models list
content:
application/json:
schema:
type: object
properties:
models:
type: array
items:
type: string
"401":
$ref: "#/components/responses/Unauthorized"
# ─── Proxy Endpoints ──────────────────────────────────────────
/api/v1/chat/completions:
post:
tags: [Chat]
summary: Create chat completion
description: OpenAI-compatible chat completions endpoint. Routes to configured providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChatCompletionRequest"
responses:
"200":
description: Chat completion response (or SSE stream)
headers:
X-OmniRoute-Response-Cost:
schema:
type: string
description: Request cost in USD, fixed 10 decimals (e.g. `0.0001234500`; `0.0000000000` for free/unpriced).
X-OmniRoute-Tokens-In:
schema:
type: string
description: Input (prompt) token count.
X-OmniRoute-Tokens-Out:
schema:
type: string
description: Output (completion) token count.
X-OmniRoute-Model:
schema:
type: string
description: Resolved model.
X-OmniRoute-Provider:
schema:
type: string
description: Resolved provider alias.
X-OmniRoute-Latency-Ms:
schema:
type: string
description: Handler latency in milliseconds.
X-OmniRoute-Cache-Hit:
schema:
type: string
enum: ["true", "false"]
description: Whether the response was served from cache.
X-OmniRoute-Fallback-Attempts:
schema:
type: string
description: Number of fallback attempts (only present when > 0).
X-OmniRoute-Request-Id:
schema:
type: string
description: Request correlation id (present when known).
X-OmniRoute-Version:
schema:
type: string
description: OmniRoute build version (always present).
X-OmniRoute-Cost-Saved:
schema:
type: string
description: >-
On a semantic-cache HIT, the original (would-have-been) cost in USD that
the cache avoided (fixed 10 decimals). Present only on cache hits;
X-OmniRoute-Response-Cost is 0 for the same response (incremental cost).
content:
application/json:
schema:
$ref: "#/components/schemas/ChatCompletionResponse"
text/event-stream:
schema:
type: string
"401":
$ref: "#/components/responses/Unauthorized"
"502":
description: All upstream providers failed
/api/v1/ws:
get:
tags: [Chat]
summary: Chat completion over WebSocket (handshake + upgrade)
description: >-
OpenAI-compatible chat over a WebSocket connection. `GET` with
`?handshake=1` returns the connection descriptor (auth path, message
protocol and live-event channels) as JSON; a plain `GET` without an
Upgrade returns `426 Upgrade Required`. After upgrading, the client
exchanges JSON frames — `{type:"request", id, payload:{model, messages}}`
to start a completion and `{type:"cancel", id}` to abort it. A separate
live channel (default port `LIVE_WS_PORT=20129`, path `/live`) streams
dashboard events on the `requests`, `combo` and `credentials` topics with
a 15s heartbeat. Requires an API key.
security:
- BearerAuth: []
parameters:
- name: handshake
in: query
description: Set to `1` to receive the JSON connection descriptor instead of upgrading.
required: false
schema:
type: string
enum: ["1"]
responses:
"101":
description: WebSocket upgrade successful
"200":
description: Handshake descriptor (auth path, message protocol, live channels)
"401":
description: WebSocket auth required (no credential supplied)
"403":
description: Invalid WebSocket credential
"426":
description: Upgrade Required — connect via WebSocket or use `?handshake=1`
/api/v1/providers/{provider}/chat/completions:
post:
tags: [Chat]
summary: Create chat completion (provider-specific)
description: Routes to a specific provider by name.
security:
- BearerAuth: []
parameters:
- name: provider
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChatCompletionRequest"
responses:
"200":
description: Chat completion response
"401":
$ref: "#/components/responses/Unauthorized"
/api/v1/api/chat:
post:
tags: [Chat]
summary: Ollama-compatible chat endpoint
description: Provides compatibility with Ollama's /api/chat format.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Chat response (JSON or streaming)
/api/v1/messages:
post:
tags: [Messages]
summary: Create message (Anthropic-compatible)
description: Anthropic Messages API endpoint. Routes to Claude providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MessagesRequest"
responses:
"200":
description: >-
Message response (or SSE stream). Non-streaming success responses
carry the `X-OmniRoute-*` cost-telemetry headers (see
`POST /api/v1/chat/completions`), including `X-OmniRoute-Request-Id`
and `X-OmniRoute-Version`.
"401":
$ref: "#/components/responses/Unauthorized"
/api/v1/messages/count_tokens:
post:
tags: [Messages]
summary: Count tokens for a message
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Token count
/api/v1/responses:
post:
tags: [Responses]
summary: Create response (OpenAI Responses API)
description: OpenAI Responses API endpoint.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: >-
Response object or SSE stream. Non-streaming success responses carry
the `X-OmniRoute-*` cost-telemetry headers (see
`POST /api/v1/chat/completions`), including `X-OmniRoute-Request-Id`
and `X-OmniRoute-Version`.
"401":
$ref: "#/components/responses/Unauthorized"
/api/v1/embeddings:
post:
tags: [Embeddings]
summary: Create embeddings
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [input, model]
properties:
input:
oneOf:
- type: string
- type: array
items:
type: string
model:
type: string
responses:
"200":
description: >-
Embedding vectors. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); media
cost is computed per modality when pricing is available, otherwise
`0` (fail-open).
/api/v1/providers/{provider}/embeddings:
post:
tags: [Embeddings]
summary: Create embeddings (provider-specific)
security:
- BearerAuth: []
parameters:
- name: provider
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Embedding vectors
/api/v1/images/generations:
post:
tags: [Images]
summary: Generate images
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [prompt]
properties:
prompt:
type: string
model:
type: string
n:
type: integer
default: 1
size:
type: string
default: 1024x1024
responses:
"200":
description: >-
Generated images. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); image
cost is computed per image when pricing is available, otherwise `0`
(fail-open).
/api/v1/providers/{provider}/images/generations:
post:
tags: [Images]
summary: Generate images (provider-specific)
security:
- BearerAuth: []
parameters:
- name: provider
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Generated images
/api/v1/audio/speech:
post:
tags: [Audio]
summary: Generate speech audio
description: Text-to-speech endpoint. Routes to configured TTS providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [input]
properties:
input:
type: string
model:
type: string
voice:
type: string
responses:
"200":
description: >-
Audio data. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); speech
cost is computed per character when pricing is available, otherwise
`0` (fail-open).
/api/v1/audio/transcriptions:
post:
tags: [Audio]
summary: Transcribe audio
description: Audio-to-text transcription endpoint.
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
model:
type: string
responses:
"200":
description: >-
Transcription result. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`);
transcription cost is computed per second when pricing is available,
otherwise `0` (fail-open).
/api/v1/moderations:
post:
tags: [Moderations]
summary: Create moderation
description: Content moderation endpoint. Routes to configured moderation providers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [input]
properties:
input:
oneOf:
- type: string
- type: array
items:
type: string
responses:
"200":
description: >-
Moderation result. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`);
moderations are always cost `0` (free).
/api/v1/rerank:
post:
tags: [Rerank]
summary: Rerank documents
description: Document reranking endpoint.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [query, documents]
properties:
query:
type: string
documents:
type: array
items:
type: string
model:
type: string
responses:
"200":
description: >-
Reranked documents. Success responses carry the `X-OmniRoute-*`
cost-telemetry headers (see `POST /api/v1/chat/completions`); rerank
cost is computed per search-unit when pricing is available,
otherwise `0` (fail-open).
/api/v1:
get:
tags: [System]
summary: API v1 root endpoint
description: Returns basic API info and status.
security:
- BearerAuth: []
responses:
"200":
description: API info
/api/v1/models:
get:
tags: [Models]
summary: List available models
description: Returns all models available across configured providers.
security:
- BearerAuth: []
responses:
"200":
description: Model list
content:
application/json:
schema:
type: object
properties:
object:
type: string
example: list
data:
type: array
items:
$ref: "#/components/schemas/Model"
/api/v1/providers/{provider}/models:
get:
tags: [Models]
summary: List models for a specific provider
description: Returns only models for the selected provider with provider prefix removed from each model id.
security:
- BearerAuth: []
parameters:
- in: path
name: provider
required: true
schema:
type: string
description: Provider id or alias (for example `openai`, `claude`, `cc`).
responses:
"200":
description: Provider-scoped model list
content:
application/json:
schema:
type: object
properties:
object:
type: string
example: list
data:
type: array
items:
$ref: "#/components/schemas/Model"
"400":
description: Unknown provider
/api/models:
get:
tags: [Models]
summary: List models (management)
responses:
"200":
description: Internal model list with aliases
/api/models/alias:
post:
tags: [Models]
summary: Create or update a model alias
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Alias created/updated
/api/models/catalog:
get:
tags: [Models]
summary: Get full model catalog
responses:
"200":
description: Complete catalog with all providers
# ─── Management Endpoints ──────────────────────────────────────
/api/providers:
get:
tags: [Providers]
summary: List provider connections
responses:
"200":
description: Provider connection list
content:
application/json:
schema:
type: object
properties:
connections:
type: array
items:
$ref: "#/components/schemas/ProviderConnection"
post:
tags: [Providers]
summary: Create provider connection
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderConnectionCreate"
responses:
"201":
description: Created provider connection
/api/providers/{id}:
get:
tags: [Providers]
summary: Get provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Provider connection details
"404":
description: Provider not found
patch:
tags: [Providers]
summary: Update provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProviderConnectionCreate"
responses:
"200":
description: Updated provider
delete:
tags: [Providers]
summary: Delete provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Provider deleted
/api/providers/{id}/test:
post:
tags: [Providers]
summary: Test provider connection
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Test result
/api/providers/{id}/models:
get:
tags: [Providers]
summary: List models for a provider
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Provider model list
/api/providers/test-batch:
post:
tags: [Providers]
summary: Test multiple providers at once
responses:
"200":
description: Batch test results
/api/providers/validate:
post:
tags: [Providers]
summary: Validate provider credentials
responses:
"200":
description: Validation result
/api/providers/client:
get:
tags: [Providers]
summary: Get client-side provider info
responses:
"200":
description: Provider info for frontend
/api/providers/agy-auth/import:
post:
tags: [Providers]
summary: Import an Antigravity CLI (agy) token file as an `agy` connection
responses:
"200":
description: Created or updated provider connection
/api/providers/agy-auth/import-bulk:
post:
tags: [Providers]
summary: Bulk-import multiple Antigravity CLI (agy) token files (up to 50)
responses:
"200":
description: Per-entry import results (success/failed counts)
/api/providers/agy-auth/zip-extract:
post:
tags: [Providers]
summary: Extract `.json` token files from an uploaded ZIP for agy bulk import
responses:
"200":
description: Extracted token-file entries
/api/providers/agy-auth/apply-local:
post:
tags: [Providers]
summary: Auto-detect and import the local Antigravity CLI (agy) login from disk
responses:
"200":
description: Created or updated provider connection
"404":
description: No local agy login found
/api/provider-nodes:
get:
tags: [Provider Nodes]
summary: List provider nodes
responses:
"200":
description: Provider node list
post:
tags: [Provider Nodes]
summary: Create provider node
responses:
"201":
description: Created node
/api/provider-nodes/{id}:
patch:
tags: [Provider Nodes]
summary: Update provider node
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Updated node
delete:
tags: [Provider Nodes]
summary: Delete provider node
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Node deleted
/api/provider-nodes/validate:
post:
tags: [Provider Nodes]
summary: Validate a provider node
responses:
"200":
description: Validation result
/api/provider-models:
get:
tags: [Provider Nodes]
summary: List provider models
responses:
"200":
description: Provider model list
/api/keys:
get:
tags: [API Keys]
summary: List API keys
responses:
"200":
description: API key list
content:
application/json:
schema:
type: object
properties:
keys:
type: array
items:
$ref: "#/components/schemas/ApiKey"
"401":
description: Authentication required
post:
tags: [API Keys]
summary: Create API key
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [label]
properties:
label:
type: string
responses:
"201":
description: Created API key (includes full key value)
"401":
description: Authentication required
/api/keys/{id}:
get:
tags: [API Keys]
summary: Get API key
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: API key metadata
"401":
description: Authentication required
"404":
description: Key not found
patch:
tags: [API Keys]
summary: Update API key
parameters:
- $ref: "#/components/parameters/ResourceId"
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
"200":
description: API key settings updated
"400":
description: Invalid update request
"401":
description: Authentication required
"404":
description: Key not found
delete:
tags: [API Keys]
summary: Delete API key
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Key deleted
"401":
description: Authentication required
"404":
description: Key not found
/api/combos:
get:
tags: [Combos]
summary: List routing combos
responses:
"200":
description: Combo list
post:
tags: [Combos]
summary: Create routing combo
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ComboCreate"
responses:
"201":
description: Created combo
/api/combos/{id}:
patch:
tags: [Combos]
summary: Update combo
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Updated combo
delete:
tags: [Combos]
summary: Delete combo
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Combo deleted
/api/combos/metrics:
get:
tags: [Combos]
summary: Get combo metrics
responses:
"200":
description: Metrics for combos
/api/combos/test:
post:
tags: [Combos]
summary: Test a combo configuration
responses:
"200":
description: Test result
/api/settings:
get:
tags: [Settings]
summary: Get application settings
responses:
"200":
description: Current settings
patch:
tags: [Settings]
summary: Update settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated settings
/api/settings/purge-request-history:
post:
tags: [Settings]
summary: Clear request log history
description: Deletes `call_logs`, legacy `request_detail_logs`, and local request artifact files under `DATA_DIR/call_logs`.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Request history cleared
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
deletedArtifacts:
type: integer
deletedDetailedLogs:
type: integer
errors:
type: integer
"401":
description: Unauthorized
"500":
description: Cleanup failed or reported errors
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
deletedArtifacts:
type: integer
deletedDetailedLogs:
type: integer
errors:
type: integer
error:
type: object
/api/settings/compression:
get:
tags: [Compression]
summary: Get global compression settings
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current compression settings
put:
tags: [Compression]
summary: Update global compression settings
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
defaultMode:
type: string
enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
autoTriggerMode:
type: string
enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
autoTriggerTokens:
type: integer
minimum: 0
rtkConfig:
type: object
additionalProperties: true
stackedPipeline:
type: array
items:
type: object
responses:
"200":
description: Updated compression settings
/api/settings/compression/mcp-accessibility:
get:
tags: [Compression]
summary: Get the MCP tool-output accessibility (trimming) config
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current mcpAccessibility config
put:
tags: [Compression]
summary: Update the MCP tool-output accessibility (trimming) config
description: >-
Partial-merge update. Numeric floors (e.g. a maxTextChars below the truncation-tail
reserve) are folded back to the safe defaults server-side, so the response reflects the
effective config.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
maxTextChars:
type: integer
minimum: 1
collapseThreshold:
type: integer
minimum: 1
collapseKeepHead:
type: integer
minimum: 0
collapseKeepTail:
type: integer
minimum: 0
minLengthToProcess:
type: integer
minimum: 1
responses:
"200":
description: Updated mcpAccessibility config (numeric floors applied)
/api/compression/preview:
post:
tags: [Compression]
summary: Preview compression for a message payload
security:
- BearerAuth: []
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [messages, mode]
properties:
mode:
type: string
enum: [off, lite, standard, aggressive, ultra, rtk, stacked]
messages:
type: array
items:
type: object
required: [role, content]
properties:
role:
type: string
content:
oneOf:
- type: string
- type: array
items: {}
config:
type: object
additionalProperties: true
responses:
"200":
description: Compression preview with diff, validation, and stats
/api/compression/language-packs:
get:
tags: [Compression]
summary: List Caveman compression language packs
security:
- BearerAuth: []
- ManagementSessionAuth: []
responses:
"200":
description: Available languages and rule-pack metadata
/api/compression/rules:
get:
tags: [Compression]
summary: List Caveman compression rule metadata
security:
- BearerAuth: []
- ManagementSessionAuth: []
responses:
"200":
description: Caveman rule metadata
/api/context/rtk/config:
get:
tags: [Compression]
summary: Get RTK compression settings
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current RTK config
put:
tags: [Compression]
summary: Update RTK compression settings
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
intensity:
type: string
enum: [minimal, standard, aggressive]
customFiltersEnabled:
type: boolean
trustProjectFilters:
type: boolean
rawOutputRetention:
type: string
enum: [never, failures, always]
rawOutputMaxBytes:
type: integer
responses:
"200":
description: Updated RTK config
/api/context/rtk/filters:
get:
tags: [Compression]
summary: List RTK filters and load diagnostics
security:
- ManagementSessionAuth: []
responses:
"200":
description: RTK filter catalog and diagnostics
/api/context/rtk/test:
post:
tags: [Compression]
summary: Run RTK compression preview for text
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text]
properties:
text:
type: string
command:
type: string
config:
type: object
additionalProperties: true
responses:
"200":
description: Detection and RTK compression result
/api/context/rtk/raw-output/{id}:
get:
tags: [Compression]
summary: Read retained redacted RTK raw output
security:
- ManagementSessionAuth: []
parameters:
- in: path
name: id
required: true
schema:
type: string
pattern: "^[a-f0-9]{24}$"
responses:
"200":
description: Raw output text
"404":
description: Raw output not found
/api/settings/payload-rules:
get:
tags: [Settings]
summary: Get payload rules configuration
description: |
Returns the current payload rules used to mutate outgoing request payloads before they
are sent upstream.
Requires a dashboard management session cookie when management auth is enabled.
security:
- ManagementSessionAuth: []
responses:
"200":
description: Current payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/PayloadRulesConfig"
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"403":
$ref: "#/components/responses/ManagementInvalidToken"
"500":
description: Failed to read payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
put:
tags: [Settings]
summary: Update payload rules configuration
description: |
Persists and hot reloads payload rules. The legacy input field `default-raw` is accepted
on writes and normalized to `defaultRaw` in responses/runtime state.
Requires a dashboard management session cookie when management auth is enabled.
security:
- ManagementSessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdatePayloadRulesRequest"
responses:
"200":
description: Updated payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/PayloadRulesConfig"
"400":
$ref: "#/components/responses/ValidationError"
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"403":
$ref: "#/components/responses/ManagementInvalidToken"
"500":
description: Failed to update payload rules configuration
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
/api/settings/combo-defaults:
get:
tags: [Settings]
summary: Get combo default settings
responses:
"200":
description: Default combo settings
/api/settings/proxy:
get:
tags: [Settings]
summary: Get proxy settings
responses:
"200":
description: Current proxy settings
patch:
tags: [Settings]
summary: Update proxy settings
responses:
"200":
description: Updated proxy settings
/api/settings/proxy/test:
post:
tags: [Settings]
summary: Test proxy connection
responses:
"200":
description: Test result
/api/settings/require-login:
post:
tags: [Settings]
summary: Toggle login requirement
responses:
"200":
description: Updated
/api/settings/ip-filter:
get:
tags: [Settings]
summary: Get IP filter configuration
description: Returns the current IP filter settings including blacklist, whitelist, and temp bans.
responses:
"200":
description: IP filter configuration
put:
tags: [Settings]
summary: Update IP filter configuration
description: |
Configure IP filtering with blacklist/whitelist modes, add/remove individual IPs, and manage temp bans.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
mode:
type: string
enum: [blacklist, whitelist]
blacklist:
type: array
items:
type: string
whitelist:
type: array
items:
type: string
addBlacklist:
type: string
removeBlacklist:
type: string
addWhitelist:
type: string
removeWhitelist:
type: string
tempBan:
type: object
properties:
ip:
type: string
durationMs:
type: integer
reason:
type: string
removeBan:
type: string
responses:
"200":
description: Updated IP filter configuration
/api/settings/system-prompt:
get:
tags: [Settings]
summary: Get system prompt configuration
description: Returns the current system prompt injection settings.
responses:
"200":
description: System prompt configuration
put:
tags: [Settings]
summary: Update system prompt configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
prompt:
type: string
enabled:
type: boolean
responses:
"200":
description: Updated system prompt configuration
/api/settings/thinking-budget:
get:
tags: [Settings]
summary: Get thinking budget configuration
description: Returns the current thinking/reasoning budget settings for AI models.
responses:
"200":
description: Thinking budget configuration
put:
tags: [Settings]
summary: Update thinking budget configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
mode:
type: string
description: Thinking mode (e.g., auto, manual, disabled)
customBudget:
type: integer
minimum: 0
maximum: 131072
effortLevel:
type: string
enum: [none, low, medium, high]
responses:
"200":
description: Updated thinking budget configuration
/api/rate-limit:
get:
tags: [Settings]
summary: Get rate limit configuration
responses:
"200":
description: Rate limit settings
post:
tags: [Settings]
summary: Update rate limit configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated rate limit settings
/api/tags:
get:
tags: [System]
summary: List Ollama-compatible model tags
description: Returns models in Ollama /api/tags format for Ollama client compatibility
responses:
"200":
description: Ollama model tags
# ─── Usage & Analytics ─────────────────────────────────────────
/api/usage/analytics:
get:
tags: [Usage]
summary: Get usage analytics
parameters:
- name: period
in: query
schema:
type: string
enum: [day, week, month]
default: day
responses:
"200":
description: Usage analytics data
/api/usage/call-logs:
get:
tags: [Usage]
summary: Get call logs
parameters:
- name: limit
in: query
schema:
type: integer
default: 50
- name: offset
in: query
schema:
type: integer
default: 0
responses:
"200":
description: Paginated call logs
/api/usage/call-logs/{id}:
get:
tags: [Usage]
summary: Get a specific call log
parameters:
- $ref: "#/components/parameters/ResourceId"
responses:
"200":
description: Call log detail
/api/usage/{connectionId}:
get:
tags: [Usage]
summary: Get usage for a specific connection
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
responses:
"200":
description: Connection usage data
/api/usage/history:
get:
tags: [Usage]
summary: Get usage history
responses:
"200":
description: Historical usage data
/api/usage/logs:
get:
tags: [Usage]
summary: Get usage logs
responses:
"200":
description: Usage log entries
/api/usage/proxy-logs:
get:
tags: [Usage]
summary: Get proxy logs
responses:
"200":
description: Proxy log entries
/api/usage/request-logs:
get:
tags: [Usage]
summary: Get request logs
responses:
"200":
description: Request log entries
/api/usage/budget:
get:
tags: [Usage]
summary: Get usage budget status
description: Returns current budget limits and consumption.
responses:
"200":
description: Budget status
post:
tags: [Usage]
summary: Configure usage budget
description: Set or update budget limits for usage tracking.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated budget configuration
# ─── Pricing ───────────────────────────────────────────────────
/api/pricing:
get:
tags: [Pricing]
summary: Get model pricing
responses:
"200":
description: Current pricing configuration
post:
tags: [Pricing]
summary: Set model pricing
responses:
"200":
description: Updated pricing
/api/pricing/defaults:
get:
tags: [Pricing]
summary: Get default pricing
responses:
"200":
description: Default pricing data
/api/pricing/models:
get:
tags: [Pricing]
summary: Get pricing per model
description: Returns pricing information organized by model.
responses:
"200":
description: Per-model pricing data
# ─── Translator ────────────────────────────────────────────────
/api/translator/detect:
post:
tags: [Translator]
summary: Detect request format
description: Detects the API format of a request body (OpenAI, Claude, Gemini, etc.)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [body]
properties:
body:
type: object
responses:
"200":
description: Detected format
/api/translator/translate:
post:
tags: [Translator]
summary: Translate between formats
description: Converts a request between API formats (e.g. Claude → OpenAI)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [sourceFormat, targetFormat, body]
properties:
step:
type: string
sourceFormat:
type: string
targetFormat:
type: string
provider:
type: string
body:
type: object
responses:
"200":
description: Translated request
/api/translator/send:
post:
tags: [Translator]
summary: Send translated request to provider
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [provider, body]
properties:
provider:
type: string
body:
type: object
responses:
"200":
description: Provider response (may be SSE stream)
/api/translator/history:
get:
tags: [Translator]
summary: Get translation history
description: Returns recent translation events for the Live Monitor
responses:
"200":
description: Translation history entries
# ─── CLI Remote Mode ───────────────────────────────────────────
/api/cli/connect:
post:
tags: [CLI Remote Mode]
summary: Exchange the management password for a scoped CLI access token
description: >
Remote-mode bootstrap. Public (password-gated) route: verifies the
management password with brute-force lockout, then mints an `oma_`
access token. The plaintext token is returned once.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password: { type: string }
name: { type: string }
scope: { type: string, enum: [read, write, admin] }
expiresInDays: { type: integer, minimum: 1, maximum: 3650 }
responses:
"200":
description: Token minted (token returned once)
"401":
description: Invalid password
"429":
description: Too many failed attempts
/api/cli/whoami:
get:
tags: [CLI Remote Mode]
summary: Report the current credential (scope, name, expiry)
responses:
"200":
description: Authenticated; access-token details when applicable
"401":
description: Authentication required
/api/cli/tokens:
get:
tags: [CLI Remote Mode]
summary: List access tokens (masked) — admin scope
responses:
"200":
description: Masked token list
"403":
description: Insufficient scope
post:
tags: [CLI Remote Mode]
summary: Create a scoped access token — admin scope
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
scope: { type: string, enum: [read, write, admin] }
expiresInDays: { type: integer, minimum: 1, maximum: 3650 }
responses:
"200":
description: Token created (token returned once)
"403":
description: Insufficient scope
/api/cli/tokens/{id}:
delete:
tags: [CLI Remote Mode]
summary: Revoke an access token by id or display prefix — admin scope
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
"200":
description: Token revoked
"403":
description: Insufficient scope
"404":
description: Token not found or already revoked
# ─── CLI Tools ─────────────────────────────────────────────────
/api/cli-tools/backups:
get:
tags: [CLI Tools]
summary: List CLI tool backups
responses:
"200":
description: Backup list
post:
tags: [CLI Tools]
summary: Create CLI tool backup
responses:
"200":
description: Backup created
/api/cli-tools/runtime/{toolId}:
get:
tags: [CLI Tools]
summary: Get runtime status for a CLI tool
parameters:
- name: toolId
in: path
required: true
schema:
type: string
responses:
"200":
description: Runtime status
/api/cli-tools/guide-settings/{toolId}:
get:
tags: [CLI Tools]
summary: Get guide settings for a tool
parameters:
- name: toolId
in: path
required: true
schema:
type: string
responses:
"200":
description: Guide settings
/api/cli-tools/antigravity-mitm:
get:
tags: [CLI Tools]
summary: Get Antigravity MITM proxy settings
responses:
"200":
description: MITM proxy configuration
post:
tags: [CLI Tools]
summary: Update Antigravity MITM proxy settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated MITM proxy configuration
delete:
tags: [CLI Tools]
summary: Reset Antigravity MITM proxy settings
responses:
"200":
description: MITM proxy settings reset
/api/cli-tools/antigravity-mitm/alias:
get:
tags: [CLI Tools]
summary: Get Antigravity MITM alias configuration
responses:
"200":
description: Alias configuration
put:
tags: [CLI Tools]
summary: Update Antigravity MITM alias configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated alias configuration
/api/cli-tools/claude-settings:
get:
tags: [CLI Tools]
summary: Get Claude CLI settings
responses:
"200":
description: Claude CLI configuration
post:
tags: [CLI Tools]
summary: Apply Claude CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Claude CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Claude CLI settings
responses:
"200":
description: Claude CLI settings reset
/api/cli-tools/cline-settings:
get:
tags: [CLI Tools]
summary: Get Cline CLI settings
responses:
"200":
description: Cline CLI configuration
post:
tags: [CLI Tools]
summary: Apply Cline CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Cline CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Cline CLI settings
responses:
"200":
description: Cline CLI settings reset
/api/cli-tools/codex-profiles:
get:
tags: [CLI Tools]
summary: Get Codex profiles
responses:
"200":
description: Codex profile list
post:
tags: [CLI Tools]
summary: Create Codex profile
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Profile created
put:
tags: [CLI Tools]
summary: Update Codex profile
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Profile updated
delete:
tags: [CLI Tools]
summary: Delete Codex profile
responses:
"200":
description: Profile deleted
/api/cli-tools/codex-settings:
get:
tags: [CLI Tools]
summary: Get Codex CLI settings
responses:
"200":
description: Codex CLI configuration
post:
tags: [CLI Tools]
summary: Apply Codex CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Codex CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Codex CLI settings
responses:
"200":
description: Codex CLI settings reset
/api/cli-tools/droid-settings:
get:
tags: [CLI Tools]
summary: Get Droid CLI settings
responses:
"200":
description: Droid CLI configuration
post:
tags: [CLI Tools]
summary: Apply Droid CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Droid CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Droid CLI settings
responses:
"200":
description: Droid CLI settings reset
/api/cli-tools/kilo-settings:
get:
tags: [CLI Tools]
summary: Get Kilo CLI settings
responses:
"200":
description: Kilo CLI configuration
post:
tags: [CLI Tools]
summary: Apply Kilo CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Kilo CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset Kilo CLI settings
responses:
"200":
description: Kilo CLI settings reset
/api/cli-tools/openclaw-settings:
get:
tags: [CLI Tools]
summary: Get OpenClaw CLI settings
responses:
"200":
description: OpenClaw CLI configuration
post:
tags: [CLI Tools]
summary: Apply OpenClaw CLI settings
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: OpenClaw CLI settings applied
delete:
tags: [CLI Tools]
summary: Reset OpenClaw CLI settings
responses:
"200":
description: OpenClaw CLI settings reset
# ─── Embedded Services ─────────────────────────────────────────
# All routes LOCAL_ONLY (loopback only) — hard rule #17.
# See docs/frameworks/EMBEDDED-SERVICES.md for full reference.
/api/services/9router/install:
post:
tags: [Embedded Services]
summary: Install 9Router from npm
description: >-
Installs the `9router` npm package under DATA_DIR/services/9router/.
Uses execFile (no shell interpolation — hard rule #13).
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
description: npm version tag or semver to install
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
path:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/9router/start:
post:
tags: [Embedded Services]
summary: Start 9Router
description: >-
Spawns the 9Router process. Idempotent if already running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started (or already running)
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: 9Router is not installed
"503":
description: Start failed
/api/services/9router/stop:
post:
tags: [Embedded Services]
summary: Stop 9Router
description: >-
Gracefully stops 9Router (SIGTERM → 15 s → SIGKILL). Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"503":
description: Stop failed
/api/services/9router/restart:
post:
tags: [Embedded Services]
summary: Restart 9Router
description: >-
Equivalent to stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/9router/update:
post:
tags: [Embedded Services]
summary: Update 9Router to a newer npm version
description: >-
Stops the service (if running), installs the newer npm version, then restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
previousVersion:
type: string
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: Update failed
/api/services/9router/rotate-key:
post:
tags: [Embedded Services]
summary: Rotate the 9Router API key
description: >-
Generates a new API key, encrypts it at-rest, and restarts the service to
apply it. The plaintext key is never returned.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Key rotated
content:
application/json:
schema:
type: object
properties:
keyRotated:
type: boolean
restarted:
type: boolean
"500":
description: Rotation failed
/api/services/9router/status:
get:
tags: [Embedded Services]
summary: Get 9Router status
description: >-
Returns combined live supervisor state and DB metadata.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatusExtended"
"500":
description: Status read failed
/api/services/9router/auto-start:
post:
tags: [Embedded Services]
summary: Toggle 9Router auto-start
description: >-
When enabled, 9Router starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/cliproxy/install:
post:
tags: [Embedded Services]
summary: Install CLIProxyAPI from npm
description: >-
Installs the CLIProxyAPI package under DATA_DIR/services/cliproxy/.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/cliproxy/start:
post:
tags: [Embedded Services]
summary: Start CLIProxyAPI
description: >-
Spawns the CLIProxyAPI process. Idempotent if already running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: CLIProxyAPI is not installed
"503":
description: Start failed
/api/services/cliproxy/stop:
post:
tags: [Embedded Services]
summary: Stop CLIProxyAPI
description: >-
Gracefully stops CLIProxyAPI. Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/cliproxy/restart:
post:
tags: [Embedded Services]
summary: Restart CLIProxyAPI
description: >-
stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/cliproxy/update:
post:
tags: [Embedded Services]
summary: Update CLIProxyAPI to a newer npm version
description: >-
Stops, installs newer version, restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"500":
description: Update failed
/api/services/cliproxy/status:
get:
tags: [Embedded Services]
summary: Get CLIProxyAPI status
description: >-
Returns live supervisor state and DB metadata (no apiKeyMasked — CLIProxyAPI
does not use an injected API key).
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/cliproxy/auto-start:
post:
tags: [Embedded Services]
summary: Toggle CLIProxyAPI auto-start
description: >-
When enabled, CLIProxyAPI starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/mux/install:
post:
tags: [Embedded Services]
summary: Install Mux from npm
description: >-
Installs the `mux` npm package (coder/mux — local agent-orchestration
daemon) under DATA_DIR/services/mux/. **LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Install succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"400":
description: Invalid request body
"500":
description: npm install failed
/api/services/mux/start:
post:
tags: [Embedded Services]
summary: Start Mux
description: >-
Spawns `mux server --host 127.0.0.1 --port <port>`. Idempotent if
already running. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service started
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
"409":
description: Mux is not installed
"503":
description: Start failed
/api/services/mux/stop:
post:
tags: [Embedded Services]
summary: Stop Mux
description: >-
Gracefully stops Mux. Idempotent.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service stopped
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/restart:
post:
tags: [Embedded Services]
summary: Restart Mux
description: >-
stop() then start() under the operation lock.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service restarted
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/update:
post:
tags: [Embedded Services]
summary: Update Mux to a newer npm version
description: >-
Stops, installs newer version, restarts.
**LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Update succeeded
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
"500":
description: Update failed
/api/services/mux/status:
get:
tags: [Embedded Services]
summary: Get Mux status
description: >-
Returns live supervisor state and DB metadata.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Status response
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceStatus"
/api/services/mux/auto-start:
post:
tags: [Embedded Services]
summary: Toggle Mux auto-start
description: >-
When enabled, Mux starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Auto-start flag updated
content:
application/json:
schema:
type: object
properties:
autoStart:
type: boolean
"400":
description: Invalid request body
/api/services/bifrost/install:
post:
tags: [Embedded Services]
summary: Install Bifrost
description: >-
Installs the `@maximhq/bifrost` npm package under DATA_DIR/services/bifrost/.
The package downloads the Go binary on first run. Accepts an optional `version`
field (semver or `latest`). **LOCAL_ONLY** — loopback only.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
version:
type: string
default: latest
responses:
"200":
description: Installation result
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
installedVersion:
type: string
installPath:
type: string
durationMs:
type: number
/api/services/bifrost/start:
post:
tags: [Embedded Services]
summary: Start Bifrost
description: Starts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after start
"409":
description: Bifrost is not installed
/api/services/bifrost/stop:
post:
tags: [Embedded Services]
summary: Stop Bifrost
description: Stops the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after stop
/api/services/bifrost/restart:
post:
tags: [Embedded Services]
summary: Restart Bifrost
description: Restarts the supervised Bifrost process. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Service status after restart
"409":
description: Bifrost is not installed
/api/services/bifrost/update:
post:
tags: [Embedded Services]
summary: Update Bifrost
description: >-
Updates Bifrost to the latest npm version. Stops the running process,
installs the new version, and restarts if it was previously running.
**LOCAL_ONLY** — loopback only.
responses:
"200":
description: Update result
/api/services/bifrost/status:
get:
tags: [Embedded Services]
summary: Get Bifrost status
description: Returns live and DB status for the supervised Bifrost service. **LOCAL_ONLY** — loopback only.
responses:
"200":
description: Bifrost service status
/api/services/bifrost/auto-start:
post:
tags: [Embedded Services]
summary: Toggle Bifrost auto-start
description: >-
When enabled, Bifrost starts automatically on the next OmniRoute boot.
**LOCAL_ONLY** — loopback only.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"204":
description: Auto-start flag updated
"400":
description: Invalid request body
/api/services/{name}/logs:
get:
tags: [Embedded Services]
summary: Stream service logs via SSE
description: >-
Returns a Server-Sent Events stream from the service's in-memory ring buffer
(5 MB, circular). Sends a `snapshot` event with historical lines first, then
live `log` events, plus a `heartbeat` every 15 s.
**LOCAL_ONLY** — loopback only.
parameters:
- name: name
in: path
required: true
schema:
type: string
enum: [9router, cliproxy]
- name: tail
in: query
schema:
type: integer
default: 200
maximum: 1000
description: Number of historical lines to include in the initial snapshot
- name: filter
in: query
schema:
type: string
maxLength: 200
description: >-
Case-insensitive substring filter applied to log lines.
No regex — ReDoS-safe by design.
responses:
"200":
description: SSE log stream
content:
text/event-stream:
schema:
type: string
description: >-
Events: `snapshot` (LogLine[]), `log` (LogLine), `heartbeat` ({})
"400":
description: filter parameter exceeds maximum length
"404":
description: Service not found
# ─── OAuth ─────────────────────────────────────────────────────
/api/oauth/{provider}/{action}:
get:
tags: [OAuth]
summary: OAuth flow handler
description: Handles OAuth authorization and callback for providers
parameters:
- name: provider
in: path
required: true
schema:
type: string
- name: action
in: path
required: true
schema:
type: string
enum: [authorize, callback, refresh, status]
responses:
"200":
description: OAuth flow response
"302":
description: Redirect to provider auth page
/api/oauth/cursor/auto-import:
get:
tags: [OAuth]
summary: Auto-import Cursor OAuth credentials
description: Automatically detects and imports Cursor credentials from local config.
responses:
"200":
description: Import result
/api/oauth/cursor/import:
get:
tags: [OAuth]
summary: Get Cursor import status
responses:
"200":
description: Current import status
post:
tags: [OAuth]
summary: Import Cursor OAuth credentials
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Credentials imported
/api/oauth/kiro/auto-import:
get:
tags: [OAuth]
summary: Auto-import Kiro OAuth credentials
description: Automatically detects and imports Kiro credentials from local config.
responses:
"200":
description: Import result
/api/oauth/kiro/import:
get:
tags: [OAuth]
summary: Get Kiro import status
responses:
"200":
description: Current import status
post:
tags: [OAuth]
summary: Import Kiro OAuth credentials
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Credentials imported
/api/oauth/kiro/social-authorize:
get:
tags: [OAuth]
summary: Initiate Kiro social OAuth authorization
description: Starts the social OAuth flow for Kiro.
responses:
"302":
description: Redirect to OAuth provider
/api/oauth/kiro/social-exchange:
post:
tags: [OAuth]
summary: Exchange Kiro social OAuth token
description: Exchanges the authorization code for access tokens.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Token exchange result
# ─── Cloud ─────────────────────────────────────────────────────
/api/cloud/auth:
post:
tags: [Cloud]
summary: Authenticate with cloud worker
description: Authenticates with the OmniRoute cloud worker for remote access.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Authentication result
/api/cloud/credentials/update:
put:
tags: [Cloud]
summary: Update cloud worker credentials
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Credentials updated
/api/cloud/model/resolve:
post:
tags: [Cloud]
summary: Resolve model via cloud
description: Resolves a model request through the cloud worker.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Resolved model info
/api/cloud/models/alias:
get:
tags: [Cloud]
summary: Get cloud model aliases
responses:
"200":
description: Cloud model alias list
put:
tags: [Cloud]
summary: Update cloud model alias
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Alias updated
# ─── Fallback ──────────────────────────────────────────────────
/api/fallback/chains:
get:
tags: [Fallback]
summary: List fallback chains
description: Returns all registered fallback chains for model routing.
responses:
"200":
description: Fallback chain list
post:
tags: [Fallback]
summary: Create fallback chain
description: Registers a fallback routing chain for a model.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [model, chain]
properties:
model:
type: string
chain:
type: array
items:
type: object
properties:
provider:
type: string
priority:
type: integer
enabled:
type: boolean
responses:
"200":
description: Fallback chain created
delete:
tags: [Fallback]
summary: Delete fallback chain
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [model]
properties:
model:
type: string
responses:
"200":
description: Fallback chain deleted
# ─── System ────────────────────────────────────────────────────
/api/auth/login:
post:
tags: [System]
summary: Authenticate user
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password:
type: string
minLength: 1
responses:
"200":
description: JWT token returned
"400":
description: Invalid login request
"401":
description: Invalid password
"403":
description: Password setup required
"429":
description: Too many failed attempts
/api/auth/logout:
post:
tags: [System]
summary: Log out
responses:
"200":
description: Session cleared
/api/init:
get:
tags: [System]
summary: Initialize application
responses:
"200":
description: Init status
/api/restart:
post:
tags: [System]
summary: Restart the application
responses:
"200":
description: Restart initiated
/api/shutdown:
post:
tags: [System]
summary: Shutdown the application
x-always-protected: true
responses:
"200":
description: Shutdown initiated
/api/db-backups:
get:
tags: [System]
summary: List database backups
responses:
"200":
description: Backup list
post:
tags: [System]
summary: Create database backup
responses:
"200":
description: Backup created
patch:
tags: [System]
summary: Save database backup retention settings
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
keepLatest:
type: integer
minimum: 1
maximum: 200
retentionDays:
type: integer
minimum: 0
maximum: 3650
responses:
"200":
description: Backup retention settings saved
/api/storage/health:
get:
tags: [System]
summary: Check storage health
responses:
"200":
description: Storage health status
/api/sync/cloud:
post:
tags: [System]
summary: Sync with cloud
responses:
"200":
description: Sync result
/api/sync/initialize:
post:
tags: [System]
summary: Initialize cloud sync
responses:
"200":
description: Sync initialized
# ─── Resilience & Monitoring ────────────────────────────────────
/api/resilience:
get:
tags: [System]
summary: Get resilience configuration
responses:
"200":
description: Request queue, connection cooldown, provider breaker, and wait settings
patch:
tags: [System]
summary: Update resilience configuration
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Updated resilience configuration
/api/resilience/reset:
post:
tags: [System]
summary: Reset circuit breakers
responses:
"200":
description: Circuit breakers reset
/api/monitoring/health:
get:
tags: [System]
summary: System health check
description: Returns system health including uptime, memory, circuit breakers, rate limits
responses:
"200":
description: Health status
/api/rate-limits:
get:
tags: [System]
summary: Get per-account rate limit status
responses:
"200":
description: Rate limit status by account
/api/sessions:
get:
tags: [System]
summary: Get active sessions
responses:
"200":
description: Active session list
/api/cache:
get:
tags: [System]
summary: Get cache statistics
responses:
"200":
description: Semantic cache and idempotency stats
delete:
tags: [System]
summary: Clear all caches
responses:
"200":
description: Caches cleared
/api/cache/stats:
get:
tags: [System]
summary: Get detailed cache statistics
description: Returns detailed statistics for all cache layers.
responses:
"200":
description: Detailed cache stats
delete:
tags: [System]
summary: Clear cache statistics
responses:
"200":
description: Cache stats cleared
# ─── Telemetry & Token Health ───────────────────────────────────
/api/telemetry/summary:
get:
tags: [Telemetry]
summary: Get telemetry summary
description: Returns aggregated telemetry data including request metrics and performance stats.
responses:
"200":
description: Telemetry summary data
/api/token-health:
get:
tags: [Telemetry]
summary: Get token health status
description: Returns health status of OAuth tokens across all providers.
responses:
"200":
description: Token health status
# ─── Evals & Policies ──────────────────────────────────────────
/api/evals:
get:
tags: [System]
summary: List eval suites
responses:
"200":
description: Eval suite list
post:
tags: [System]
summary: Run evaluation
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Eval results
/api/evals/{suiteId}:
get:
tags: [System]
summary: Get eval suite details
parameters:
- name: suiteId
in: path
required: true
schema:
type: string
responses:
"200":
description: Eval suite details
/api/policies:
get:
tags: [System]
summary: List routing policies
responses:
"200":
description: Policy list
post:
tags: [System]
summary: Create routing policy
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"201":
description: Created policy
delete:
tags: [System]
summary: Delete routing policy
responses:
"200":
description: Policy deleted
/api/compliance/audit-log:
get:
tags: [System]
summary: Get compliance audit log
description: >
Returns paginated audit log entries. Use `level=high` to filter to
high-level actions only (powers the Activity feed). Use `level=all`
(default) for full compliance table.
security:
- bearerAuth: []
parameters:
- name: level
in: query
schema:
type: string
enum: [high, all]
default: all
description: "high = Activity feed events only; all = all audit events"
- name: action
in: query
schema:
type: string
description: Filter by exact action string (e.g. "provider.added")
- name: actor
in: query
schema:
type: string
description: Filter by actor identifier
- name: limit
in: query
schema:
type: integer
default: 50
maximum: 500
- name: offset
in: query
schema:
type: integer
default: 0
responses:
"200":
description: Audit log entries
"401":
description: Unauthorized
"500":
description: Internal server error
# ─── Quota Sharing (Group B, plan 22) ────────────────────────────
/api/quota/pools:
get:
tags: [Quota]
summary: List quota pools
security:
- bearerAuth: []
responses:
"200":
description: Array of QuotaPool objects
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/QuotaPool"
"401":
description: Unauthorized
"500":
description: Internal server error
post:
tags: [Quota]
summary: Create quota pool
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PoolCreate"
responses:
"201":
description: Pool created
content:
application/json:
schema:
$ref: "#/components/schemas/QuotaPool"
"400":
description: Validation error (Zod)
"401":
description: Unauthorized
"500":
description: Internal server error
/api/quota/pools/{id}:
get:
tags: [Quota]
summary: Get quota pool by ID
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: QuotaPool object
content:
application/json:
schema:
$ref: "#/components/schemas/QuotaPool"
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
patch:
tags: [Quota]
summary: Update quota pool (name or allocations)
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PoolUpdate"
responses:
"200":
description: Updated pool
"400":
description: Validation error
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
delete:
tags: [Quota]
summary: Delete quota pool
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"204":
description: Deleted
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
/api/quota/pools/{id}/usage:
get:
tags: [Quota]
summary: Get pool usage snapshot (per-key consumption + burn rate)
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: PoolUsageSnapshot
content:
application/json:
schema:
$ref: "#/components/schemas/PoolUsageSnapshot"
"401":
description: Unauthorized
"404":
description: Pool not found
"500":
description: Internal server error
/api/quota/plans:
get:
tags: [Quota]
summary: List resolved provider plans (catalog + manual overrides)
security:
- bearerAuth: []
responses:
"200":
description: Array of ProviderPlan
"401":
description: Unauthorized
"500":
description: Internal server error
/api/quota/plans/{connectionId}:
get:
tags: [Quota]
summary: Get resolved plan for a connection
security:
- bearerAuth: []
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
responses:
"200":
description: ProviderPlan (source = auto | manual)
"401":
description: Unauthorized
"404":
description: Connection not found
"500":
description: Internal server error
put:
tags: [Quota]
summary: Upsert manual plan override for a connection
security:
- bearerAuth: []
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/PlanUpsert"
responses:
"200":
description: Updated plan
"400":
description: Validation error (Zod)
"401":
description: Unauthorized
"500":
description: Internal server error
delete:
tags: [Quota]
summary: Delete manual plan override (reverts to catalog/auto)
security:
- bearerAuth: []
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
responses:
"204":
description: Override deleted
"401":
description: Unauthorized
"404":
description: Override not found
"500":
description: Internal server error
/api/quota/preview:
get:
tags: [Quota]
summary: Dry-run quota enforcement check (preview only, no consumption recorded)
security:
- bearerAuth: []
parameters:
- name: apiKeyId
in: query
required: true
schema:
type: string
- name: poolId
in: query
required: true
schema:
type: string
- name: estimatedTokens
in: query
schema:
type: number
- name: estimatedUsd
in: query
schema:
type: number
- name: estimatedRequests
in: query
schema:
type: integer
responses:
"200":
description: EnforceDecision (allow/block + reason)
"400":
description: Validation error (Zod)
"401":
description: Unauthorized
"500":
description: Internal server error
/api/settings/quota-store:
get:
tags: [Settings]
summary: Get current quota store driver settings
description: Redis URL is masked in the response (shows only scheme+host).
security:
- bearerAuth: []
responses:
"200":
description: QuotaStoreSettings (driver + masked redisUrl)
"401":
description: Unauthorized
"500":
description: Internal server error
put:
tags: [Settings]
summary: Update quota store driver settings
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/QuotaStoreSettings"
responses:
"200":
description: Settings updated
"400":
description: Validation error (Zod) — e.g. driver=redis without valid URL
"401":
description: Unauthorized
"500":
description: Internal server error
# ─── v1beta (Gemini-Compatible) ─────────────────────────────────
/api/v1beta/models:
get:
tags: [Models]
summary: List models (Gemini format)
description: Returns models in Gemini v1beta format for native SDK compatibility
security:
- BearerAuth: []
responses:
"200":
description: Model list in Gemini format
/api/v1beta/models/{path}:
post:
tags: [Models]
summary: Gemini generateContent
description: Gemini-compatible generateContent endpoint
security:
- BearerAuth: []
parameters:
- name: path
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Generated content
# ─── AgentBridge ──────────────────────────────────────────────
/api/tools/agent-bridge/agents:
get:
tags: [AgentBridge]
summary: List all 9 IDE agents with current state
description: >-
Returns the state (dns_enabled, cert_trusted, setup_completed, last_started_at,
last_error) for all 9 configured IDE agents. LOCAL_ONLY.
responses:
"200":
description: Array of agent state rows
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AgentBridgeAgentState"
"403":
description: Loopback-only — request came from a non-loopback address
/api/tools/agent-bridge/state:
get:
tags: [AgentBridge]
summary: Get global AgentBridge server state
description: Returns running status, port, cert info, and intercepted request count.
responses:
"200":
description: Server state
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeServerState"
/api/tools/agent-bridge/server:
post:
tags: [AgentBridge]
summary: Control AgentBridge MITM server
description: Start, stop, restart, trust-cert, or regenerate-cert. SPAWN_CAPABLE.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeServerAction"
responses:
"200":
description: Action executed
"400":
description: Invalid action
"409":
description: Port 443 conflict
/api/tools/agent-bridge/agents/{agentId}/dns:
post:
tags: [AgentBridge]
summary: Enable or disable DNS for one agent
description: Adds or removes /etc/hosts entries for the agent's host list. SPAWN_CAPABLE.
parameters:
- name: agentId
in: path
required: true
schema:
$ref: "#/components/schemas/AgentId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeDnsAction"
responses:
"200":
description: DNS updated
"400":
description: Validation error
/api/tools/agent-bridge/agents/{agentId}/mappings:
get:
tags: [AgentBridge]
summary: Get model mappings for one agent
parameters:
- name: agentId
in: path
required: true
schema:
$ref: "#/components/schemas/AgentId"
responses:
"200":
description: Array of source→target model mappings
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AgentBridgeMappingRow"
put:
tags: [AgentBridge]
summary: Update model mappings for one agent
parameters:
- name: agentId
in: path
required: true
schema:
$ref: "#/components/schemas/AgentId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeMappingPut"
responses:
"200":
description: Mappings updated
/api/tools/agent-bridge/bypass:
get:
tags: [AgentBridge]
summary: List bypass patterns (hosts never decrypted)
responses:
"200":
description: Bypass patterns
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AgentBridgeBypassRow"
put:
tags: [AgentBridge]
summary: Update user bypass patterns
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeBypassUpsert"
responses:
"200":
description: Patterns updated
/api/tools/agent-bridge/cert:
post:
tags: [AgentBridge]
summary: Download or regenerate the AgentBridge CA certificate
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [action]
properties:
action:
type: string
enum: [download, regenerate]
responses:
"200":
description: CA certificate PEM (download) or regeneration confirmation
/api/tools/agent-bridge/upstream-ca:
get:
tags: [AgentBridge]
summary: Get configured upstream CA cert path
responses:
"200":
description: Upstream CA configuration
content:
application/json:
schema:
type: object
properties:
path:
type: string
nullable: true
post:
tags: [AgentBridge]
summary: Set upstream CA cert path for corporate TLS environments
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AgentBridgeUpstreamCaPost"
responses:
"200":
description: Upstream CA configured
"400":
description: Path does not exist or is not readable
# ─── Traffic Inspector ─────────────────────────────────────────
/api/tools/traffic-inspector/requests:
get:
tags: [Traffic Inspector]
summary: List intercepted requests (filterable)
parameters:
- name: profile
in: query
schema:
type: string
enum: [llm, custom, all]
- name: host
in: query
schema:
type: string
- name: agent
in: query
schema:
$ref: "#/components/schemas/AgentId"
- name: status
in: query
schema:
type: string
enum: ["2xx", "3xx", "4xx", "5xx", error]
- name: source
in: query
schema:
$ref: "#/components/schemas/CaptureSource"
- name: sessionId
in: query
schema:
type: string
format: uuid
responses:
"200":
description: Array of intercepted requests
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InterceptedRequest"
delete:
tags: [Traffic Inspector]
summary: Clear the in-memory traffic buffer
responses:
"204":
description: Buffer cleared
/api/tools/traffic-inspector/requests/{id}:
get:
tags: [Traffic Inspector]
summary: Get a single intercepted request by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Intercepted request details
content:
application/json:
schema:
$ref: "#/components/schemas/InterceptedRequest"
"404":
description: Request not found in buffer
/api/tools/traffic-inspector/requests/{id}/replay:
post:
tags: [Traffic Inspector]
summary: Replay a captured request through OmniRoute router
description: Re-executes the original request body against /v1/chat/completions. Consumes quota.
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Replay response (streaming or JSON)
"404":
description: Request not found
/api/tools/traffic-inspector/requests/{id}/annotation:
put:
tags: [Traffic Inspector]
summary: Save or update annotation on a request
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorAnnotationPut"
responses:
"200":
description: Annotation saved
/api/tools/traffic-inspector/ws:
get:
tags: [Traffic Inspector]
summary: Live WebSocket stream of intercepted requests
description: >-
Upgrade to WebSocket. On connect, server sends `{type:"snapshot", data:[...]}`.
Subsequent events: `{type:"new", data:{...}}`, `{type:"update", data:{...}}`,
`{type:"clear"}`. LOCAL_ONLY.
responses:
"101":
description: WebSocket upgrade successful
"403":
description: Non-loopback origin rejected
/api/tools/traffic-inspector/export.har:
get:
tags: [Traffic Inspector]
summary: Export current filtered request list as HAR 1.2
parameters:
- name: profile
in: query
schema:
type: string
enum: [llm, custom, all]
- name: sessionId
in: query
schema:
type: string
format: uuid
responses:
"200":
description: HAR file (JSON)
content:
application/json:
schema:
type: object
description: HAR 1.2 format
/api/tools/traffic-inspector/hosts:
get:
tags: [Traffic Inspector]
summary: List custom capture hosts
responses:
"200":
description: Custom hosts list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InspectorCustomHost"
post:
tags: [Traffic Inspector]
summary: Add a custom capture host (edits /etc/hosts)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorCustomHostCreate"
responses:
"201":
description: Host added
"409":
description: Host already exists
/api/tools/traffic-inspector/hosts/{host}:
delete:
tags: [Traffic Inspector]
summary: Remove a custom capture host
parameters:
- name: host
in: path
required: true
schema:
type: string
responses:
"204":
description: Host removed
patch:
tags: [Traffic Inspector]
summary: Toggle enabled state of a custom host
parameters:
- name: host
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [enabled]
properties:
enabled:
type: boolean
responses:
"200":
description: Host updated
/api/tools/traffic-inspector/capture-modes:
get:
tags: [Traffic Inspector]
summary: Get state of all 4 capture modes
responses:
"200":
description: Capture modes state
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorCaptureModesState"
/api/tools/traffic-inspector/capture-modes/http-proxy:
post:
tags: [Traffic Inspector]
summary: Start or stop the HTTP_PROXY listener (port 8080)
description: SPAWN_CAPABLE — spawns a net.Server listener.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorCaptureModeAction"
responses:
"200":
description: Action executed
"409":
description: Port conflict (EADDRINUSE) when starting
/api/tools/traffic-inspector/capture-modes/system-proxy:
post:
tags: [Traffic Inspector]
summary: Apply or revert system-wide proxy settings
description: SPAWN_CAPABLE — executes networksetup/gsettings/netsh. Requires admin.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSystemProxyAction"
responses:
"200":
description: System proxy updated
"500":
description: OS command failed (permission error)
/api/tools/traffic-inspector/capture-modes/tls-intercept:
post:
tags: [Traffic Inspector]
summary: Toggle TLS body decryption in HTTP_PROXY mode
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorTlsInterceptToggle"
responses:
"200":
description: TLS intercept mode updated
/api/tools/traffic-inspector/sessions:
get:
tags: [Traffic Inspector]
summary: List all saved recording sessions
responses:
"200":
description: Sessions list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/InspectorSession"
post:
tags: [Traffic Inspector]
summary: Start a new recording session
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSessionStart"
responses:
"201":
description: Session started
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSession"
/api/tools/traffic-inspector/sessions/{id}:
get:
tags: [Traffic Inspector]
summary: Get session snapshot (all captured requests)
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Session with embedded requests
"404":
description: Session not found
patch:
tags: [Traffic Inspector]
summary: Stop or rename a recording session
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InspectorSessionPatch"
responses:
"200":
description: Session updated
delete:
tags: [Traffic Inspector]
summary: Delete a recording session
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: Session deleted
/api/tools/traffic-inspector/sessions/{id}/export.har:
get:
tags: [Traffic Inspector]
summary: Export a recorded session as HAR 1.2
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: HAR file for this session
content:
application/json:
schema:
type: object
description: HAR 1.2 format
"404":
description: Session not found
/api/tools/traffic-inspector/internal/ingest:
post:
tags: [Traffic Inspector]
summary: Internal ingest endpoint for server.cjs passthrough path
description: >-
Accepts a serialized InterceptedRequest from the CJS MITM server for requests
that do not go through TypeScript handlers (e.g., passthrough hosts). Requires
INSPECTOR_INTERNAL_INGEST_TOKEN header. LOCAL_ONLY.
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/InterceptedRequest"
responses:
"204":
description: Ingested
"401":
description: Invalid or missing ingest token
# ─── OpenAPI Spec ──────────────────────────────────────────────
/api/openapi/spec:
get:
tags: [System]
summary: Get OpenAPI specification catalog
description: >-
Returns a structured JSON catalog parsed from this `openapi.yaml`,
including info, servers, tags, schemas, and a flat list of endpoints
(method, path, tags, summary, security, parameters, responses).
Used by the in-app API explorer.
responses:
"200":
description: Parsed OpenAPI catalog
content:
application/json:
schema:
type: object
properties:
info:
type: object
servers:
type: array
items:
type: object
tags:
type: array
items:
type: object
endpoints:
type: array
items:
type: object
properties:
method:
type: string
path:
type: string
tags:
type: array
items:
type: string
summary:
type: string
description:
type: string
security:
type: boolean
parameters:
type: array
items:
type: object
requestBody:
type: boolean
responses:
type: array
items:
type: string
schemas:
type: array
items:
type: string
"404":
description: openapi.yaml file not found on disk
"500":
description: Failed to parse OpenAPI spec
# ─── Agent Skills Catalog ────────────────────────────────────────────────────
/api/agent-skills:
get:
tags: [Agent Skills]
summary: List agent skills catalog
description: |
Returns the full 42-entry Agent Skills catalog with optional filtering.
Skills describe how to use OmniRoute's REST API and CLI — they are structured
SKILL.md documentation files discoverable by external agents, MCP clients, and
A2A orchestrators. No authentication required.
parameters:
- name: category
in: query
required: false
schema:
type: string
enum: [api, cli]
description: Filter by category (api = REST API skills, cli = CLI skills)
- name: area
in: query
required: false
schema:
type: string
description: Filter by area slug (e.g. "providers", "models", "cli-serve")
responses:
"200":
description: Catalog list
content:
application/json:
schema:
type: object
required: [skills, count, coverage]
properties:
skills:
type: array
items:
$ref: "#/components/schemas/AgentSkill"
count:
type: integer
coverage:
$ref: "#/components/schemas/SkillCoverage"
"400":
$ref: "#/components/responses/BadRequest"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/{id}:
get:
tags: [Agent Skills]
summary: Get a single agent skill
description: |
Returns metadata for a single agent skill by its canonical ID
(e.g. `omni-providers`, `cli-serve`). No authentication required.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID
example: omni-providers
responses:
"200":
description: Agent skill metadata
content:
application/json:
schema:
$ref: "#/components/schemas/AgentSkill"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/{id}/raw:
get:
tags: [Agent Skills]
summary: Get raw SKILL.md content
description: |
Returns the SKILL.md content for a skill as `text/markdown`.
Resolution order: local filesystem `skills/{id}/SKILL.md` → GitHub raw URL (1-hour cache).
No authentication required.
parameters:
- name: id
in: path
required: true
schema:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID
example: omni-providers
responses:
"200":
description: SKILL.md content as Markdown
headers:
X-Skill-Source:
schema:
type: string
enum: [filesystem, github, generated]
description: Where the content was loaded from
X-Skill-Fetched-At:
schema:
type: string
format: date-time
description: ISO timestamp of when the content was fetched
Cache-Control:
schema:
type: string
description: "public, max-age=3600"
content:
text/markdown:
schema:
type: string
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"502":
description: Upstream GitHub fetch failed
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/coverage:
get:
tags: [Agent Skills]
summary: Get SKILL.md coverage stats
description: |
Returns how many of the 22 API skills and 20 CLI skills have SKILL.md
files on the local filesystem vs the catalog totals. No authentication required.
responses:
"200":
description: Coverage stats
content:
application/json:
schema:
$ref: "#/components/schemas/SkillCoverage"
"500":
$ref: "#/components/responses/InternalError"
/api/agent-skills/generate:
post:
tags: [Agent Skills]
summary: Trigger SKILL.md generator
description: |
Runs the Agent Skills generator which writes `skills/{id}/SKILL.md` for
all 42 catalog entries (or a subset via `onlyIds`). Preserves
`<!-- skill:custom-start --> ... <!-- skill:custom-end -->` blocks.
**Requires management authentication.**
security:
- BearerAuth: []
- ManagementSessionAuth: []
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
dryRun:
type: boolean
default: true
description: "If true, reports what would be generated without writing files"
prune:
type: boolean
default: false
description: "If true, deletes skill directories not in the catalog"
onlyIds:
type: array
items:
type: string
description: "If provided, only regenerate these skill IDs"
responses:
"200":
description: Generator report
content:
application/json:
schema:
type: object
required: [generated, unchanged, pruned, orphansDetected, errors]
properties:
generated:
type: array
items:
type: string
description: IDs that got new/updated SKILL.md
unchanged:
type: array
items:
type: string
description: IDs whose content was already up to date
pruned:
type: array
items:
type: string
description: IDs whose directories were deleted (prune mode)
orphansDetected:
type: array
items:
type: string
description: Directories found in skills/ not in the catalog
errors:
type: array
items:
type: object
required: [id, error]
properties:
id:
type: string
error:
type: string
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
"503":
description: Generator module not available
/api/v1/ocr:
post:
tags:
- Images
summary: Document OCR
description: >-
Mistral OCRcompatible document OCR endpoint. Accepts a JSON body
referencing a document/image and returns extracted text. Success
responses carry the `X-OmniRoute-*` cost-telemetry headers.
security:
- BearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
model:
type: string
document:
type: object
responses:
"200":
description: OCR result with extracted text.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/audio/translations:
post:
tags:
- Audio
summary: Translate audio to English
description: >-
OpenAI Whispercompatible audio translation (multipart/form-data).
Unlike `/api/v1/audio/transcriptions`, output is always English
regardless of the source language. Success responses carry the
`X-OmniRoute-*` cost-telemetry headers.
security:
- BearerAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- file
properties:
file:
type: string
format: binary
model:
type: string
responses:
"200":
description: English translation of the audio.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/providers/suggested-models:
get:
tags:
- Providers
summary: Suggested media models
description: >-
Read-only server-side proxy to the public HuggingFace Hub models search
API, used by the dashboard to suggest models for a media provider kind
without exposing an HF token client-side. Never accepts or returns
credentials.
parameters:
- name: type
in: query
schema:
type: string
description: Media kind to search for (e.g. `image`, `audio`, `video`).
responses:
"200":
description: List of suggested HuggingFace Hub models.
"500":
$ref: "#/components/responses/InternalError"
/api/v1/provider-plugin-manifest:
get:
tags:
- Providers
summary: Provider plugin manifest
description: Returns the manifest describing installed provider plugins.
responses:
"200":
description: Provider plugin manifest.
"500":
$ref: "#/components/responses/InternalError"
/api/keys/{id}/devices:
get:
tags:
- API Keys
summary: List devices for an API key
description: >-
Lists the distinct devices (masked IP + User-Agent fingerprints)
tracked for an API key by the in-memory device tracker. IPs are masked
before storage; the route never sees the raw client IP.
x-internal: true
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
"200":
description: Distinct devices seen for the API key.
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"404":
$ref: "#/components/responses/NotFound"
/api/settings/purge-usage-history:
post:
tags:
- Settings
summary: Purge usage history
description: Dashboard-only. Purges stored usage-history records.
x-internal: true
responses:
"200":
description: Usage history purged.
"401":
$ref: "#/components/responses/Unauthorized"
/api/oauth/codex/import-token:
post:
tags:
- OAuth
summary: Import a Codex connection from a bare access token
description: >-
Dashboard-only. Creates a Codex (ChatGPT/OpenAI) connection from a raw
access token with no refresh token (authType `access_token`).
x-internal: true
responses:
"200":
description: Connection imported.
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/api/cli-tools/crush-settings:
get:
tags:
- CLI Tools
summary: Read Crush CLI OmniRoute config
description: Local-only. Reads the OmniRoute provider block in Crush's config.
x-internal: true
responses:
"200":
description: Current Crush config state.
post:
tags:
- CLI Tools
summary: Write Crush CLI OmniRoute config
description: Local-only. Registers OmniRoute as an `openai-compat` provider in Crush's config.
x-internal: true
responses:
"200":
description: Crush config updated.
delete:
tags:
- CLI Tools
summary: Remove OmniRoute from Crush CLI config
description: Local-only. Removes the OmniRoute provider block from Crush's config.
x-internal: true
responses:
"200":
description: Crush config entry removed.
/api/cli-tools/codewhale-settings:
get:
tags:
- CLI Tools
summary: Read CodeWhale CLI OmniRoute config
description: >-
Local-only. Reads the OmniRoute config block from
`~/.codewhale/config.toml` (with `~/.deepseek/config.toml` legacy
fallback).
x-internal: true
responses:
"200":
description: Current CodeWhale config state.
post:
tags:
- CLI Tools
summary: Write CodeWhale CLI OmniRoute config
description: Local-only. Writes the OmniRoute config block in CodeWhale TOML format.
x-internal: true
responses:
"200":
description: CodeWhale config updated.
delete:
tags:
- CLI Tools
summary: Remove OmniRoute from CodeWhale CLI config
description: Local-only. Removes the OmniRoute config block from CodeWhale's config.
x-internal: true
responses:
"200":
description: CodeWhale config entry removed.
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
description: API key obtained from the OmniRoute dashboard
ManagementSessionAuth:
type: apiKey
in: cookie
name: auth_token
description: Dashboard management session cookie for protected management routes
parameters:
ResourceId:
name: id
in: path
required: true
schema:
type: string
responses:
Unauthorized:
description: Missing or invalid API key
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Unauthorized
ManagementAuthenticationRequired:
description: Authentication required for management routes
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Authentication required
type: invalid_request
requestId: 3f9f6f5a-509a-4b35-b0a7-2d2d99d73a01
ManagementInvalidToken:
description: Bearer tokens are not accepted for management routes
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Invalid management token
type: invalid_request
requestId: 1b6a6ff8-d60c-4900-8d0a-25f81749f0a3
ValidationError:
description: Request body failed validation
content:
application/json:
schema:
$ref: "#/components/schemas/ValidationErrorResponse"
BadRequest:
description: The request was malformed or failed validation
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Invalid request
type: invalid_request_error
requestId: 8c2b1d44-7a3e-4c91-9b0f-1e2d3c4b5a60
NotFound:
description: The requested resource was not found
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Resource not found
type: not_found_error
requestId: 4d5e6f70-1a2b-3c4d-5e6f-7a8b9c0d1e2f
InternalError:
description: An unexpected server error occurred
content:
application/json:
schema:
$ref: "#/components/schemas/ApiErrorResponse"
example:
error:
message: Internal server error
type: api_error
requestId: 0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d
schemas:
PlaygroundPreset:
type: object
required:
- id
- name
- endpoint
- model
- params
- created_at
properties:
id:
type: string
format: uuid
name:
type: string
maxLength: 100
endpoint:
type: string
description: Playground endpoint key (e.g. "chat.completions")
model:
type: string
system:
type: string
nullable: true
params:
type: object
additionalProperties: true
description: Serialized parameter values (temperature, max_tokens, etc.)
created_at:
type: string
format: date-time
PlaygroundPresetCreate:
type: object
required:
- name
- endpoint
- model
properties:
name:
type: string
minLength: 1
maxLength: 100
endpoint:
type: string
minLength: 1
model:
type: string
minLength: 1
system:
type: string
nullable: true
params:
type: object
additionalProperties: true
default: {}
MemoryEntry:
type: object
description: A single persisted memory entry
properties:
id:
type: string
description: UUID
apiKeyId:
type: string
sessionId:
type: string
nullable: true
type:
type: string
enum:
- factual
- episodic
- procedural
- semantic
key:
type: string
description: Stable upsert key (e.g. preference:i_prefer_python)
content:
type: string
metadata:
type: object
additionalProperties: true
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
nullable: true
needsReindex:
type: integer
description: 1 if the vector for this memory is stale or missing
MemorySettingsExtended:
type: object
description: Extended memory settings including 7 new fields from plan 21. All fields are optional for PUT (patch semantics).
properties:
enabled:
type: boolean
maxTokens:
type: integer
minimum: 0
maximum: 16000
retentionDays:
type: integer
minimum: 1
maximum: 365
strategy:
type: string
enum:
- recent
- semantic
- hybrid
skillsEnabled:
type: boolean
embeddingSource:
type: string
enum:
- remote
- static
- transformers
- auto
description: Which embedding source to use. "auto" = remote > static > transformers.
embeddingProviderModel:
type: string
nullable: true
description: Embedding provider/model in "provider/model" format (e.g. openai/text-embedding-3-small).
transformersEnabled:
type: boolean
description: Opt-in for Transformers.js local MiniLM model (~400MB RAM)
staticEnabled:
type: boolean
description: Opt-in for static potion-base-8M local model
rerankEnabled:
type: boolean
description: Enable reranking step (+200-500ms/req)
rerankProviderModel:
type: string
nullable: true
description: Rerank provider/model in "provider/model" format
vectorStore:
type: string
enum:
- sqlite-vec
- qdrant
- auto
description: Which vector backend to use
QdrantSettings:
type: object
description: Qdrant vector database configuration (read shape — no raw apiKey)
properties:
enabled:
type: boolean
host:
type: string
port:
type: integer
minimum: 1
maximum: 65535
collection:
type: string
embeddingModel:
type: string
hasApiKey:
type: boolean
apiKeyMasked:
type: string
nullable: true
description: First 4 chars of the configured API key, or null
QdrantHealthResult:
type: object
description: Result of a Qdrant liveness probe
properties:
ok:
type: boolean
latencyMs:
type: number
error:
type: string
nullable: true
description: Sanitized error message (no stack traces)
AgentSkill:
type: object
description: >-
Single entry in the Agent Skills catalog. Describes one OmniRoute REST API surface
(category: api) or CLI subcommand group (category: cli) with a canonical ID and a
link to its SKILL.md documentation file.
required: [id, name, description, category, area, rawUrl, githubUrl]
properties:
id:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: Canonical skill ID (e.g. "omni-providers", "cli-serve")
example: omni-providers
name:
type: string
minLength: 1
maxLength: 100
description: Human-readable skill name
example: Provider Management
description:
type: string
minLength: 1
maxLength: 2000
description: One-paragraph description of what the skill covers
category:
type: string
enum: [api, cli]
description: "api = REST API skill; cli = CLI subcommand skill"
area:
type: string
minLength: 1
maxLength: 50
description: Functional area slug (e.g. "providers", "combos-routing", "cli-serve")
example: providers
endpoints:
type: array
items:
type: string
description: REST API endpoints (present for api-category skills only)
example: ["POST /api/providers", "GET /api/providers/:id"]
cliCommands:
type: array
items:
type: string
description: CLI subcommand names (present for cli-category skills only)
example: ["providers list", "providers test", "providers rotate"]
icon:
type: string
description: Material symbol icon name for dashboard display
isEntry:
type: boolean
description: Whether this is a recommended starting point
isNew:
type: boolean
description: Whether this skill was added in a recent release
rawUrl:
type: string
format: uri
description: GitHub raw URL of the SKILL.md file
example: "https://raw.githubusercontent.com/diegosouzapw/OmniRoute/refs/heads/main/skills/omni-providers/SKILL.md"
githubUrl:
type: string
format: uri
description: GitHub blob URL for viewing the SKILL.md in the browser
example: "https://github.com/diegosouzapw/OmniRoute/blob/main/skills/omni-providers/SKILL.md"
SkillCoverage:
type: object
description: >-
Coverage statistics for the Agent Skills catalog: how many of the 22 REST API
skills and 20 CLI skills have generated SKILL.md files on the local filesystem.
required: [api, cli, totalSkills, generatedAt]
properties:
api:
type: object
required: [have, total]
properties:
have:
type: integer
minimum: 0
maximum: 22
description: Number of API skills with SKILL.md on disk
total:
type: integer
enum: [22]
description: Canonical API skill count (always 22)
cli:
type: object
required: [have, total]
properties:
have:
type: integer
minimum: 0
maximum: 20
description: Number of CLI skills with SKILL.md on disk
total:
type: integer
enum: [20]
description: Canonical CLI skill count (always 20)
totalSkills:
type: integer
minimum: 0
maximum: 42
description: Sum of api.have + cli.have
generatedAt:
type: string
format: date-time
description: ISO datetime when coverage was last computed
ErrorResponse:
type: object
description: Standard error response body
required: [error]
properties:
error:
type: object
required: [message]
properties:
message:
type: string
description: Human-readable error message (never includes stack traces)
code:
type: string
description: Machine-readable error code
# ─── AgentBridge Schemas ────────────────────────────────────────
AgentId:
type: string
enum:
- antigravity
- kiro
- copilot
- codex
- cursor
- zed
- claude-code
- open-code
- trae
description: One of the 9 supported IDE agents
AgentBridgeAgentState:
type: object
description: Per-agent MITM state
properties:
agent_id:
$ref: "#/components/schemas/AgentId"
dns_enabled:
type: boolean
cert_trusted:
type: boolean
setup_completed:
type: boolean
last_started_at:
type: string
format: date-time
nullable: true
last_error:
type: string
nullable: true
AgentBridgeServerState:
type: object
description: Global AgentBridge MITM server state
properties:
running:
type: boolean
port:
type: integer
example: 443
certReady:
type: boolean
interceptedCount:
type: integer
activeConnections:
type: integer
lastStartedAt:
type: string
format: date-time
nullable: true
AgentBridgeServerAction:
type: object
required: [action]
properties:
action:
type: string
enum: [start, stop, restart, trust-cert, regenerate-cert]
AgentBridgeDnsAction:
type: object
required: [enabled]
properties:
enabled:
type: boolean
AgentBridgeMappingRow:
type: object
properties:
agent_id:
$ref: "#/components/schemas/AgentId"
source_model:
type: string
example: gpt-4o
target_model:
type: string
example: claude-sonnet-4.7
updated_at:
type: string
format: date-time
AgentBridgeMappingPut:
type: object
required: [mappings]
properties:
mappings:
type: array
items:
type: object
required: [source, target]
properties:
source:
type: string
example: gpt-4o
target:
type: string
example: claude-sonnet-4.7
AgentBridgeBypassRow:
type: object
properties:
pattern:
type: string
example: "*.bank.*"
source:
type: string
enum: [default, user]
created_at:
type: string
format: date-time
AgentBridgeBypassUpsert:
type: object
required: [patterns]
properties:
patterns:
type: array
items:
type: string
example: ["*.bank.*", "*.gov.*"]
AgentBridgeUpstreamCaPost:
type: object
required: [path]
properties:
path:
type: string
description: Absolute path to a PEM file for corporate upstream CA
example: "/etc/ssl/certs/corporate-ca.pem"
# ─── Traffic Inspector Schemas ──────────────────────────────────
CaptureSource:
type: string
enum: [agent-bridge, custom-host, http-proxy, system-proxy]
DetectedKind:
type: string
enum: [llm, app, unknown]
InterceptedRequest:
type: object
description: A single intercepted HTTP request captured by the Traffic Inspector
required:
[
id,
source,
timestamp,
method,
host,
path,
requestHeaders,
requestSize,
responseHeaders,
responseSize,
status,
]
properties:
id:
type: string
format: uuid
source:
$ref: "#/components/schemas/CaptureSource"
agent:
$ref: "#/components/schemas/AgentId"
timestamp:
type: string
format: date-time
method:
type: string
example: POST
host:
type: string
example: api.githubcopilot.com
path:
type: string
example: /v1/chat/completions
requestHeaders:
type: object
additionalProperties:
type: string
requestBody:
type: string
nullable: true
description: Masked (secrets replaced with ***)
requestSize:
type: integer
responseHeaders:
type: object
additionalProperties:
type: string
responseBody:
type: string
nullable: true
responseSize:
type: integer
status:
oneOf:
- type: integer
- type: string
enum: [in-flight, error]
proxyLatencyMs:
type: number
nullable: true
upstreamLatencyMs:
type: number
nullable: true
totalLatencyMs:
type: number
nullable: true
error:
type: string
nullable: true
description: Sanitized error message (no stack traces)
sourceModel:
type: string
nullable: true
mappedModel:
type: string
nullable: true
detectedKind:
$ref: "#/components/schemas/DetectedKind"
contextKey:
type: string
nullable: true
description: 12-char SHA-256 hex of the system prompt (for conversation grouping)
example: a3f9c2b1d5e4
annotation:
type: string
nullable: true
sessionId:
type: string
format: uuid
nullable: true
note:
type: string
nullable: true
description: Informational note (e.g. TLS tunnel metadata)
InspectorCustomHost:
type: object
properties:
host:
type: string
example: api.openai.com
enabled:
type: boolean
label:
type: string
nullable: true
kind:
type: string
enum: [llm, app, custom]
added_at:
type: string
format: date-time
last_seen_at:
type: string
format: date-time
nullable: true
InspectorCustomHostCreate:
type: object
required: [host]
properties:
host:
type: string
minLength: 1
example: my-internal-llm.company.com
enabled:
type: boolean
default: true
label:
type: string
nullable: true
kind:
type: string
enum: [llm, app, custom]
default: custom
InspectorCaptureModesState:
type: object
properties:
agentBridge:
type: object
properties:
active:
type: boolean
customHosts:
type: object
properties:
active:
type: boolean
count:
type: integer
httpProxy:
type: object
properties:
active:
type: boolean
port:
type: integer
example: 8080
systemProxy:
type: object
properties:
active:
type: boolean
guardMinutes:
type: integer
InspectorCaptureModeAction:
type: object
required: [action]
properties:
action:
type: string
enum: [start, stop]
InspectorSystemProxyAction:
type: object
required: [action]
properties:
action:
type: string
enum: [apply, revert]
port:
type: integer
minimum: 1
maximum: 65535
example: 8080
guardMinutes:
type: integer
minimum: 1
example: 30
InspectorTlsInterceptToggle:
type: object
required: [enabled]
properties:
enabled:
type: boolean
InspectorAnnotationPut:
type: object
required: [annotation]
properties:
annotation:
type: string
maxLength: 10000
InspectorSession:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
nullable: true
started_at:
type: string
format: date-time
ended_at:
type: string
format: date-time
nullable: true
request_count:
type: integer
profile:
type: string
enum: [llm, custom, all]
nullable: true
InspectorSessionStart:
type: object
properties:
name:
type: string
example: "Antigravity test run #1"
InspectorSessionPatch:
type: object
required: [action]
properties:
action:
type: string
enum: [stop, rename]
name:
type: string
QuotaPool:
type: object
description: A quota sharing pool — binds a provider connection to allocation rules.
required: [id, connectionId, name, createdAt, allocations]
properties:
id:
type: string
connectionId:
type: string
name:
type: string
createdAt:
type: string
format: date-time
allocations:
type: array
items:
$ref: "#/components/schemas/PoolAllocation"
PoolAllocation:
type: object
required: [apiKeyId, weight, policy]
properties:
apiKeyId:
type: string
weight:
type: number
minimum: 0
maximum: 100
description: Share percentage (0100)
capValue:
type: number
nullable: true
description: Absolute cap value (optional)
capUnit:
type: string
enum: [percent, requests, tokens, usd]
nullable: true
policy:
type: string
enum: [hard, soft, burst]
PoolCreate:
type: object
required: [connectionId, name]
properties:
connectionId:
type: string
name:
type: string
maxLength: 120
allocations:
type: array
items:
$ref: "#/components/schemas/PoolAllocation"
default: []
PoolUpdate:
type: object
properties:
name:
type: string
maxLength: 120
allocations:
type: array
items:
$ref: "#/components/schemas/PoolAllocation"
PoolUsageSnapshot:
type: object
required: [poolId, generatedAt, dimensions]
properties:
poolId:
type: string
generatedAt:
type: string
format: date-time
dimensions:
type: array
items:
type: object
properties:
unit:
type: string
enum: [percent, requests, tokens, usd]
window:
type: string
enum: ["5h", hourly, daily, weekly, monthly]
limit:
type: number
consumedTotal:
type: number
perKey:
type: array
items:
type: object
properties:
apiKeyId:
type: string
consumed:
type: number
fairShare:
type: number
deficit:
type: number
description: "Negative = surplus; positive = over-allocation"
borrowing:
type: boolean
burnRate:
type: object
nullable: true
properties:
tokensPerSecond:
type: number
timeToExhaustionMs:
type: number
nullable: true
QuotaDimension:
type: object
required: [unit, window, limit]
properties:
unit:
type: string
enum: [percent, requests, tokens, usd]
window:
type: string
enum: ["5h", hourly, daily, weekly, monthly]
limit:
type: number
minimum: 0
PlanUpsert:
type: object
required: [dimensions]
properties:
dimensions:
type: array
minItems: 1
items:
$ref: "#/components/schemas/QuotaDimension"
QuotaStoreSettings:
type: object
required: [driver]
properties:
driver:
type: string
enum: [sqlite, redis]
redisUrl:
type: string
format: uri
nullable: true
description: Redis connection URL (write-only; masked in GET responses)
ServiceStatus:
type: object
description: Live supervisor state for an embedded service
properties:
tool:
type: string
example: 9router
state:
type: string
enum: [not_installed, stopped, starting, running, stopping, error]
pid:
type: integer
nullable: true
port:
type: integer
example: 20130
health:
type: string
enum: [unknown, healthy, degraded]
startedAt:
type: string
format: date-time
nullable: true
lastError:
type: string
nullable: true
ServiceStatusExtended:
allOf:
- $ref: "#/components/schemas/ServiceStatus"
- type: object
description: >-
Extended status including version metadata and (for 9Router) API key preview.
properties:
installedVersion:
type: string
nullable: true
latestVersion:
type: string
nullable: true
updateAvailable:
type: boolean
apiKeyMasked:
type: string
nullable: true
description: >-
Masked API key preview (e.g. "nr_****abcd").
Present only for services that use an injected API key (9Router).
autoStart:
type: boolean
providerExpose:
type: boolean
description: >-
Whether models from this service are exposed as a routing provider.
9Router only.
ApiErrorResponse:
type: object
properties:
error:
type: object
properties:
message:
type: string
type:
type: string
details:
description: Optional additional error details
requestId:
type: string
format: uuid
ValidationErrorResponse:
type: object
properties:
error:
type: object
required: [message, details]
properties:
message:
type: string
example: Invalid request
details:
type: array
items:
type: object
required: [field, message]
properties:
field:
type: string
message:
type: string
PayloadRuleModelSpec:
type: object
additionalProperties: false
required: [name]
properties:
name:
type: string
minLength: 1
protocol:
type: string
minLength: 1
PayloadMutationRule:
type: object
additionalProperties: false
required: [models, params]
properties:
models:
type: array
minItems: 1
items:
$ref: "#/components/schemas/PayloadRuleModelSpec"
params:
type: object
minProperties: 1
additionalProperties: true
PayloadFilterRule:
type: object
additionalProperties: false
required: [models, params]
properties:
models:
type: array
minItems: 1
items:
$ref: "#/components/schemas/PayloadRuleModelSpec"
params:
type: array
minItems: 1
items:
type: string
minLength: 1
PayloadRulesConfig:
type: object
additionalProperties: false
required: [default, override, filter, defaultRaw]
properties:
default:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
override:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
filter:
type: array
items:
$ref: "#/components/schemas/PayloadFilterRule"
defaultRaw:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
UpdatePayloadRulesRequest:
type: object
additionalProperties: false
description: At least one payload-rules section must be present in the request body.
properties:
default:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
override:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
filter:
type: array
items:
$ref: "#/components/schemas/PayloadFilterRule"
defaultRaw:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
default-raw:
type: array
items:
$ref: "#/components/schemas/PayloadMutationRule"
anyOf:
- required: [default]
- required: [override]
- required: [filter]
- required: [defaultRaw]
- required: [default-raw]
ChatCompletionRequest:
type: object
required: [model, messages]
properties:
model:
type: string
example: gpt-4o
messages:
type: array
items:
type: object
required: [role]
properties:
role:
type: string
description: >-
Message role. The proxy accepts any non-empty string; common values
include system, user, assistant, tool, function, and developer.
example: user
content:
description: >-
Message content. May be a plain string, an array of content parts
for multimodal inputs (text, image, audio, etc.), or null when the
message only carries tool/function calls.
oneOf:
- type: string
- type: array
items:
type: object
- type: "null"
name:
type: string
tool_call_id:
type: string
tool_calls:
type: array
items:
type: object
function_call:
type: object
stream:
type: boolean
default: false
temperature:
type: number
minimum: 0
maximum: 2
max_tokens:
type: integer
top_p:
type: number
minimum: 0
maximum: 1
n:
type: integer
minimum: 1
default: 1
stop:
description: Up to 4 stop sequences (string or array of strings).
oneOf:
- type: string
- type: array
items:
type: string
maxItems: 4
frequency_penalty:
type: number
minimum: -2
maximum: 2
presence_penalty:
type: number
minimum: -2
maximum: 2
seed:
type: integer
logprobs:
type: boolean
top_logprobs:
type: integer
minimum: 0
maximum: 20
response_format:
type: object
description: Output format constraint (e.g. JSON mode or JSON Schema).
properties:
type:
type: string
example: json_object
tools:
type: array
description: Tool definitions available to the model.
items:
type: object
tool_choice:
description: Controls which tool (if any) is invoked by the model.
oneOf:
- type: string
example: auto
- type: object
parallel_tool_calls:
type: boolean
default: true
service_tier:
type: string
example: auto
user:
type: string
description: Stable end-user identifier for abuse monitoring.
ChatCompletionResponse:
type: object
properties:
id:
type: string
object:
type: string
example: chat.completion
choices:
type: array
items:
type: object
properties:
index:
type: integer
message:
type: object
properties:
role:
type: string
content:
type: string
finish_reason:
type: string
usage:
type: object
properties:
prompt_tokens:
type: integer
completion_tokens:
type: integer
total_tokens:
type: integer
MessagesRequest:
type: object
required: [model, messages, max_tokens]
properties:
model:
type: string
example: claude-sonnet-4-5-20250514
messages:
type: array
items:
type: object
required: [role, content]
properties:
role:
type: string
enum: [user, assistant]
content:
type: string
max_tokens:
type: integer
stream:
type: boolean
default: false
system:
type: string
Model:
type: object
properties:
id:
type: string
object:
type: string
example: model
owned_by:
type: string
ProviderConnection:
type: object
properties:
id:
type: string
provider:
type: string
name:
type: string
url:
type: string
isActive:
type: boolean
maxConcurrent:
type: integer
nullable: true
minimum: 0
priority:
type: integer
testStatus:
type: string
enum: [active, error, untested]
createdAt:
type: string
format: date-time
ProviderConnectionCreate:
type: object
required: [provider, url]
properties:
provider:
type: string
example: openai
name:
type: string
url:
type: string
apiKey:
type: string
isActive:
type: boolean
default: true
maxConcurrent:
type: integer
nullable: true
minimum: 0
ApiKey:
type: object
properties:
id:
type: string
label:
type: string
keyPreview:
type: string
description: Last 4 characters of the key
isActive:
type: boolean
createdAt:
type: string
format: date-time
ComboCreate:
type: object
required: [name, model]
properties:
name:
type: string
model:
type: string
strategy:
type: string
enum:
- priority
- weighted
- round-robin
- context-relay
- fill-first
- p2c
- random
- least-used
- cost-optimized
- reset-aware
- reset-window
- headroom
- strict-random
- auto
- lkgp
- context-optimized
- fusion
default: priority
nodes:
type: array
items:
type: object
properties:
connectionId:
type: string
weight:
type: integer
priority:
type: integer