Compare commits
27 Commits
feat/opper
...
fix/codeql
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528fba953d | ||
|
|
1ee4818224 | ||
|
|
6afc7ddab0 | ||
|
|
71fd806bb0 | ||
|
|
adedc351d7 | ||
|
|
1d476e4ced | ||
|
|
18d5b75335 | ||
|
|
00d385b498 | ||
|
|
7b9b36836c | ||
|
|
ae8ab27294 | ||
|
|
9237a0562f | ||
|
|
c4d149c2eb | ||
|
|
a0422b9af8 | ||
|
|
5f5d1101ba | ||
|
|
5b8fe4d8c7 | ||
|
|
d1768de716 | ||
|
|
c11f661a8a | ||
|
|
0481f61750 | ||
|
|
86c03f1fed | ||
|
|
8abcd639d1 | ||
|
|
9f1f5ecf1f | ||
|
|
e616130496 | ||
|
|
7a10d3856a | ||
|
|
5984c43f5b | ||
|
|
331580b499 | ||
|
|
3a7ed20bcd | ||
|
|
9c34e88f6b |
11
.env.example
@@ -925,6 +925,11 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
|
||||
# web_fetch). Default: 60000. Used by: open-sse/mcp-server/fetchTimeout.ts
|
||||
# OMNIROUTE_MCP_UPSTREAM_TIMEOUT_MS=60000
|
||||
|
||||
# Maximum number of local-corpus index instances cached in memory.
|
||||
# Used by: src/lib/localCorpus/configured.ts — bounds the LRU cache of
|
||||
# LocalCorpusIndex objects (one per indexed root directory). Default: 5.
|
||||
# OMNIROUTE_CORPUS_CACHE_SIZE=5
|
||||
|
||||
# Model catalog sync interval in hours.
|
||||
# Used by: src/shared/services/modelSyncScheduler.ts — periodic model refresh.
|
||||
# Default: 24
|
||||
@@ -3062,3 +3067,9 @@ QUOTA_STORE_DRIVER=sqlite
|
||||
# without a configured budget are always considered affordable. Requires the
|
||||
# provider_quota_state table (migration 148).
|
||||
# OMNIROUTE_QUOTA_AWARE_ROUTING=0
|
||||
|
||||
# ─── LOCAL CORPUS (opt-in document index) ───
|
||||
# Size of the in-memory LRU index cache for the local document corpus used by
|
||||
# corpus-aware retrieval. Higher values keep more index entries hot.
|
||||
# Used by: src/lib/localCorpus/configured.ts
|
||||
# OMNIROUTE_CORPUS_CACHE_SIZE=5
|
||||
|
||||
@@ -46,7 +46,7 @@ Repository map and Reference Documentation sections below.
|
||||
|
||||
## Project at a Glance
|
||||
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 353 LLM providers, auto-fallback.
|
||||
**OmniRoute** — unified AI proxy/router. One endpoint, 354 LLM providers, auto-fallback.
|
||||
|
||||
| Layer | Location | Purpose |
|
||||
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
1
changelog.d/features/nimble-search-and-fetch.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(providers):** add **Nimble** as a web-search and web-fetch provider (`nimble-search`) — `/v1/search` routes to Nimble's search API at `lite` depth (locale, freshness and include/exclude domain filters mapped onto the shared request contract), and `/v1/web/fetch` routes to Nimble Extract, which covers all four fetch formats (`markdown`, `html`, `links`, `screenshot`) from a single call. One API key serves both surfaces.
|
||||
1
changelog.d/features/opper-provider.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(providers):** Add **Opper** as an API-key gateway provider — EU-hosted AI gateway with 700+ models from 30+ providers behind one OpenAI-compatible API and one key (`OPPER_API_KEY`); model ids use `provider/model` format (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-5`); live model catalog at `https://api.opper.ai/v3/compat/models`; entry mirrors `requesty` (same shape, `passthroughModels: true`, no static seed)
|
||||
1
changelog.d/fixes/11602-provider-allowlist.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(handoff): enforce provider allowlist for universal handoff (#11602) — universal handoff now skips summarization when the selected summary model's provider is not included in the configured provider allowlist.
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** Show a stable error state when Search Analytics returns an HTTP error or malformed data ([#11603](https://github.com/diegosouzapw/OmniRoute/pull/11603)) — thanks @pacocartones
|
||||
1
changelog.d/fixes/11604-profile-xp-consistency.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** Keep the Profile level and progress aligned with aggregate XP, including bounded handling for invalid totals ([#11604](https://github.com/diegosouzapw/OmniRoute/pull/11604)) — thanks @pacocartones
|
||||
1
changelog.d/fixes/11605-profile-hidden-badge-details.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** Prevent locked hidden badges from revealing their icon or opening private badge details before they are earned ([#11605](https://github.com/diegosouzapw/OmniRoute/pull/11605)) — thanks @pacocartones
|
||||
1
changelog.d/fixes/11607-modal-focus-restoration.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** Restore keyboard focus after shared modals close and cancel delayed autofocus during cleanup ([#11607](https://github.com/diegosouzapw/OmniRoute/pull/11607)) — thanks @pacocartones
|
||||
1
changelog.d/fixes/11610-datatable-keyboard-activation.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** Enable Enter and Space activation for clickable data-table rows without hijacking nested controls ([#11610](https://github.com/diegosouzapw/OmniRoute/pull/11610)) — thanks @pacocartones
|
||||
1
changelog.d/fixes/11625-retired-pool.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(autoCombo,sse):** vendor-retired catalog ids are dropped from the auto-combo candidate pool and no longer win on leftover `arena_elo` / `user_override` rows; `getModelLifecycleDecision` consults `model-lifecycle.json` (prefix-stripped) so aggregator traffic is not `untracked` for ids the snapshot already knows ([#11625](https://github.com/diegosouzapw/OmniRoute/issues/11625))
|
||||
@@ -3093,11 +3093,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/sse/services/auth.ts": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/sse/services/model.ts": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
"_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)",
|
||||
"frozen": {
|
||||
"_rebaseline_2026_08_20_10878_10799_provider_health_probes": "PRs #10878 (unsupported OpenAI-like validation probes stay neutral) + #10799 (preserve credential health on inconclusive NVIDIA-timeout/Antigravity-400 probes) own growth: src/app/api/providers/[id]/test/route.ts 946->1025 (+79, sum of both boarded together). Both add narrowly-scoped classification branches at the existing test-route dispatch chokepoint (unsupported-capability skip, credential-inconclusive detection) rather than new files, mirroring the prior 2026_06_27_5193 rebaseline of the same file. Covered by tests/unit/provider-validation-unsupported-neutral.test.ts + tests/unit/provider-health-inconclusive-probes.test.ts.",
|
||||
"src/app/api/providers/[id]/test/route.ts": 1237,
|
||||
"src/app/api/providers/[id]/test/route.ts": 1262,
|
||||
"_rebaseline_2026_08_23_11141_oauth_400_recovery": "PR #11141 (HouMinXi) own growth: test/route.ts 1025->1215 (+190, the reactive-400 recovery path — a fully rebuilt probe for refresh+retry on refreshable non-rotating connections, with inconclusive-status preservation and rotating-provider exclusion; all growth is the new probe builder + guards at the existing test-route dispatch, extraction would split the retry flow mid-logic). Covered by tests/unit/oauth-400-recovery.test.ts (8, bug-injection proof). Owner pre-authorized baseline bumps 2026-08-22.",
|
||||
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
|
||||
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
|
||||
@@ -435,7 +435,7 @@
|
||||
"src/shared/components/analytics/charts.tsx": 1346,
|
||||
"src/shared/services/cliRuntime.ts": 1459,
|
||||
"src/sse/handlers/chat.ts": 2493,
|
||||
"src/sse/services/auth.ts": 3346,
|
||||
"src/sse/services/auth.ts": 3432,
|
||||
"_rebaseline_2026_08_24_lasterror_provider_error_detail": "PR (ntdat812) own growth: src/sse/services/auth.ts 3344->3346 (+2). One line is the import of describeUpstreamFailure from @/shared/utils/upstreamError, which replaces the string-only collapse `typeof errorText === \"string\" ? errorText.slice(0, 100) : \"Provider error\"` at the single markAccountUnavailable chokepoint (net 0 lines there) — the logic itself lives in upstreamError.ts, next to the extractErrorMessage it reuses, so nothing else moved into this file. The second line is the repo's own lint-staged prettier pass splitting a pre-existing two-statements-on-one-line at getProviderCredentials (`invalidateManagedLease(...); log.warn(...)`); it re-applies on any commit that touches this file, so it is not separable from the change. Covered by tests/unit/provider-error-detail-lastError.test.ts.",
|
||||
"_rebaseline_2026_08_23_11186_synced_inventory_routing": "PR #11186 (pacocartones) own growth: src/sse/services/auth.ts 3260->3337 (+77, loadAdvertisedModelsForSelfHostedConnections + the modelNotAdvertised candidate-filter predicate — pins chat routing to the connection whose synced inventory actually advertises the model, fixing spurious model-not-found on multi-host self-hosted setups; at the existing credential-selection chokepoint, not extractable without splitting the selection flow). Covered by tests/unit/chat-routing-synced-inventory-11089.test.ts. Owner pre-authorized baseline bumps 2026-08-22.",
|
||||
"tests/unit/account-fallback-service.test.ts": 2044,
|
||||
@@ -459,7 +459,7 @@
|
||||
"_rebaseline_2026_08_22_11156_enter_check_disabled": "PR #11156 (rqzbeh) own growth: AddApiKeyModal.tsx 1080->1082 (+2, Enter keydown handler now mirrors the isCheckDisabled condition — owner-requested post-merge polish from #11056; the rest of the diff is Prettier reflow). Covered by tests/unit/ui/add-api-key-modal-enter-key.test.tsx (jsdom render test, Enter dispatch assertions).",
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
|
||||
"src/shared/components/ModelSelectModal.tsx": 1138,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1321,
|
||||
"src/shared/constants/providers/apikey/gateways.ts": 1330,
|
||||
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1387,
|
||||
"_rebaseline_2026_08_11_v3850_merge_storm_provider_registry": "DRIFT do merge-storm 2026-08-11 (99 PRs mergeados no release/v3.8.50). AddApiKeyModal.tsx (PR #8949 ChatGPT Web provider) e useProviderConnections.ts/ModelSelectModal.tsx (PRs #9011 combo test-all, #9499 image combos) = UI nova legitima acima do cap; gateways.ts = god-file de catalogo de providers que cresceu com PRs #9009/#9421/#9468/#9594 (qualquer split arriscaria corromper o merge de novo — o proprio PR #9421 quebrou o arquivo); bridge.ts (PR #8949) = ponte Chromium vendored; proxyFetch.ts 1207->1220 = drift herdado de merges. Owner autorizou rebaseline com anotacao (2026-08-11).",
|
||||
"src/lib/modelCapabilities.ts": 1072,
|
||||
@@ -479,7 +479,9 @@
|
||||
"_rebaseline_2026_08_24_11355_cooldown_recovery_guards": "PR #11355 own growth: test/route.ts 1215->1237, +22 (startup crash-recovery guard: clearStaleCrashCooldowns() now parses the persisted rate_limited_until deadline and skips clearing rows still genuinely in the future, instead of clearing every non-terminal cooldown unconditionally). Cohesive fix at the existing test-route dispatch chokepoint alongside the #11141 probe builder. Covered by tests/unit/startup-stale-cooldown-recovery.test.ts + tests/unit/repro-zai-cooldown-cleared-by-connection-test.test.ts.",
|
||||
"src/lib/guardrails/videoBridgeRuntime.ts": 1009,
|
||||
"_rebaseline_2026_08_24_video_bridge_fu02_fu07_sampler": "PRs #11344 (FU-02 one-frame scene-aware determinism) + #11381 (FU-07 opt-in segment_aware structural sampling) own growth: videoBridgeRuntime.ts <1000->1009, +9 (sum of both boarded together in the same merge-batch). #11344 adds the deterministic one-frame midpoint fallback + policyEffective=uniform report at the existing scene_aware seam; #11381 adds the bounded local-only FFmpeg structural pre-analysis pass (scene/freeze/blur/exposure/SI-TI) and its budget-reallocation logic. Covered by tests/unit/guardrails/videoBridgeSampler.test.ts, tests/unit/guardrails/videoBridgeFu07StructuralSampling.test.ts, tests/integration/video-bridge-sampler-ffmpeg.test.ts. Owner pre-authorized rebaseline for legitimate PR growth (2026-08-19 directive).",
|
||||
"open-sse/services/autoCombo/virtualFactory.ts": 1130
|
||||
"open-sse/services/autoCombo/virtualFactory.ts": 1130,
|
||||
"src/lib/cloudflaredTunnel.ts": 1078,
|
||||
"src/shared/components/RequestLoggerDetail.tsx": 1018
|
||||
},
|
||||
"_rebaseline_base_2026_08_10_proxyfetch": "Base-red fix (green-prs sweep, issue #9985): open-sse/utils/proxyFetch.ts 1207 > cap 1000 — new proxied-TLS fetch helper introduced by the Fal reference-image work. Owner-authorized quick rebaseline to green; structural slim tracked for v3.9.0.",
|
||||
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
|
||||
@@ -648,5 +650,7 @@
|
||||
"_rebaseline_2026_08_20_v3850_merge_train_batch1": "Merge-train batch1 (2026-08-19/20, 30 PRs boarded onto release/v3.8.50): gateways.ts 1255->1268 = PR #10722 (Token Kiosk OpenAI-compatible provider gateway catalog entry, +13 declarative lines, same god-file no-split rationale as prior gateways.ts rebaselines); chatHelpers.ts (uncapped, not previously frozen) new 1017 = PR #10797 (relay/bifrost error normalization, +23/-2, own-PR growth, existing file already near cap from accumulated chokepoint wiring per its own rebaseline history above); chatBodyAdmission.ts (uncapped) new 1005 = pre-existing base-red on the pure release tip (1004>1000 before this train boarded anything, no PR in this batch touches this file) — frozen here at its current size, not authorizing further growth. Owner-authorized rebaseline (2026-08-19 merge-prs session).",
|
||||
"_rebaseline_2026_08_20_8338_cursor_image_provider": "PR (reimplementation of #8338, @valvesss): imageRegistry.ts 1019->1033 = new cursor IMAGE_PROVIDERS entry (Cursor plan image generation via Agent CLI), +14 lines of declarative provider metadata. Same god-registry no-split rationale as prior imageRegistry/gateways rebaselines.",
|
||||
"_rebaseline_2026_08_20_imageregistry_1034": "imageRegistry.ts 1033->1034: +1 line drift between #10842 (cursor image provider, froze at 1033) and its actual merged state on release (measured 1034) — trivial rebaseline, not a new feature.",
|
||||
"_rebaseline_2026_08_25_11146_subscription_first_auto": "PR #11146 (@yourspraveen, subscription-first auto groupings auto/subscription+auto/thrifty): open-sse/services/autoCombo/virtualFactory.ts is a NEW file in this PR landing at 1128 lines (+2 margin) — two opt-in flat auto ids built on the established auto/best-free pattern (connectionBillingCatalog + subscriptionLadder pure functions). Frozen at merge size per owner-authorized rebaseline directive (2026-08-19, merge-batch Step 4); no further growth without split rationale."
|
||||
"_rebaseline_2026_08_25_11146_subscription_first_auto": "PR #11146 (@yourspraveen, subscription-first auto groupings auto/subscription+auto/thrifty): open-sse/services/autoCombo/virtualFactory.ts is a NEW file in this PR landing at 1128 lines (+2 margin) — two opt-in flat auto ids built on the established auto/best-free pattern (connectionBillingCatalog + subscriptionLadder pure functions). Frozen at merge size per owner-authorized rebaseline directive (2026-08-19, merge-batch Step 4); no further growth without split rationale.",
|
||||
"_rebaseline_2026_08_26_mergebatch_v3851_batch1": "/merge-batch 2026-08-26 (v3.8.51): three legitimate growths from this batch. #11448 src/app/api/providers/[id]/test/route.ts 1237->1262 (auto-test-on-create wiring). #11495 src/sse/services/auth.ts 3346->3376 (web-cookie health-sweep verify-only path). #11561 src/lib/cloudflaredTunnel.ts new named-tunnel mode, lands at 1078 (+78 over the 1000 new-file cap) for the CLOUDFLARED_CONFIG named-tunnel flow (login->create->route dns config parsing + readiness detection). Owner-authorized rebaseline per merge-batch Step 4 (2026-08-19 directive); no further growth without split rationale.",
|
||||
"_rebaseline_2026_08_26_mergebatch_v3851_batch2": "/merge-batch 2026-08-26 (v3.8.51) batch 2: three legitimate growths. #11083 src/shared/components/RequestLoggerDetail.tsx new-file cap, lands at 1018 (+18 over 1000) — copy-all button for request detail modal. #11631 src/shared/constants/providers/apikey/gateways.ts 1321->1330 (1min.ai gateway entry). #11628 src/sse/services/auth.ts 3376->3432 (credential-health isolation from model failures). Owner-authorized rebaseline per merge-batch Step 4 (2026-08-19 directive); no further growth without split rationale."
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (353 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 85 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
|
||||
<svg viewBox="0 0 1200 350" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Animated terminal demoing the OmniRoute CLI: omniroute providers list (354 providers registered, anthropic, codex, glm, kimi shown active), omniroute combo list (always-on priority, cost-saver, fusion-panel, context-relay) and omniroute health (healthy, 18412 requests in 24h, p95 412ms, circuit breakers 24 closed, 1 half-open, 0 open), cycling over 85 top-level commands: providers, oauth, keys, combo, nodes, models, cache, compression, cost, usage, quota, health, resilience, telemetry, logs, audit, mcp, a2a, cloud, memory, skills, eval, doctor, repl, tunnel, backup, sync, webhooks, policy, pricing, translator, simulate and more.">
|
||||
<desc>Compact animated terminal cycling three real OmniRoute CLI commands with a typewriter effect and a scrolling subcommand ticker; the first frame shows the completed providers-list screen.</desc>
|
||||
<defs><clipPath id="tickerClip"><rect x="12" y="304" width="1176" height="40"/></clipPath><clipPath id="tw0"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;31;61;92;122;153;184;214;245;245" keyTimes="0;0.012;0.018;0.024;0.030;0.036;0.042;0.048;0.054;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw1"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;26;51;76;102;128;153;178;204;204" keyTimes="0;0.348;0.351;0.357;0.363;0.369;0.375;0.381;0.387;1" dur="18s" repeatCount="indefinite"/></rect></clipPath><clipPath id="tw2"><rect x="64" y="46" height="26" width="0"><animate attributeName="width" calcMode="discrete" values="0;20;41;61;82;102;122;143;163;163" keyTimes="0;0.678;0.684;0.690;0.696;0.702;0.708;0.714;0.720;1" dur="18s" repeatCount="indefinite"/></rect></clipPath></defs>
|
||||
<rect width="1200" height="350" fill="#0d1117"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 353 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
|
||||
<svg viewBox="0 0 1200 780" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Comparison table: OmniRoute versus 9router, OpenRouter, CLIProxyAPI and LiteLLM across 13 capabilities. OmniRoute is the only one with the full set: 354 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 110 tools, A2A protocol, persistent memory, guardrails, cloud agents, TLS fingerprint stealth, desktop/Termux/PWA, 43 UI locales and 100% MIT self-hosted. 9router has free providers, RTK compression and translation but no MCP, A2A, memory, guardrails, cloud agents or stealth. OpenRouter is a hosted SaaS with 400+ models, guardrails and a hosted MCP but is not self-hosted and lacks A2A, memory, cloud agents and stealth. CLIProxyAPI is a light OAuth proxy with two routing strategies. LiteLLM has 100+ providers, A2A and extensive guardrails but no memory, compression, free tier, stealth or cloud agents.">
|
||||
<desc>Static-header comparison table where each capability row fades in top to bottom; the OmniRoute column is highlighted and shows a check or a leading value in every row, while competitors show a mix of checks, partials and crosses.</desc>
|
||||
<defs>
|
||||
<pattern id="gC" width="32" height="32" patternUnits="userSpaceOnUse"><path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.05" stroke-width="1"/></pattern>
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 353 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier and 56 recurring or keyless free-forever providers. Every tool works: 35 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
|
||||
<svg viewBox="0 0 1200 540" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="The OmniRoute promise: one endpoint and 354 providers. Six pillars. Resilient fallback: automatic routing continues while another healthy target is available. Save up to 95 percent of eligible tokens: RTK plus Caveman stacked compression averages about 89 percent on tool-heavy sessions. Zero dollars to start: 90+ providers with a free tier and 56 recurring or keyless free-forever providers. Every tool works: 35 CLI and agent integration records, including Claude Code, Codex, Cursor, Cline, Copilot and Antigravity, through one config. One endpoint: OpenAI, Claude, Gemini and Responses API translation at /v1. Production controls: circuit breakers, TLS stealth, MCP with 110 tools, A2A, memory, guardrails, evals, and 39,000+ static test declarations across 5,100+ tracked test files.">
|
||||
<desc>Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperP" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
@@ -21,7 +21,7 @@
|
||||
<line x1="150" y1="53" x2="1160" y2="53" stroke="#232b38" stroke-width="1.5"/>
|
||||
</g>
|
||||
<g>
|
||||
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">353 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
|
||||
<text x="40" y="100" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">One endpoint. <tspan fill="#a78bfa" font-weight="800">354 providers.</tspan> Never stop building — OmniRoute picks <tspan fill="#7ee787" font-weight="700">the cheapest one that works</tspan>.</text>
|
||||
</g>
|
||||
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
@@ -38,7 +38,7 @@
|
||||
<line x1="3.9" y1="3.9" x2="18.1" y2="18.1"/>
|
||||
</g>
|
||||
<text x="102" y="170" font-size="18" font-weight="800" fill="#74b9ff">Never hit limits</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 353 providers in</text>
|
||||
<text x="66" y="204" font-size="13.5" fill="#a1a1aa">Auto-fallback across 354 providers in</text>
|
||||
<text x="66" y="226" font-size="13.5" fill="#a1a1aa">milliseconds. Quota out? The next provider</text>
|
||||
<text x="66" y="248" font-size="13.5" fill="#a1a1aa">takes over while a healthy target remains.</text>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 353 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 353 AI providers, 90+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
|
||||
<svg viewBox="0 0 1200 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute hero: Never stop coding. Every AI tool to 354 providers — 90+ free — through one endpoint. Claude Code, Codex, Cursor, Cline, Copilot and Antigravity into free Claude, GPT and Gemini with auto-fallback. RTK + Caveman stacked compression saves 15 to 95 percent of tokens — about 89 percent average on tool-heavy sessions — so you never hit limits. Stats: 354 AI providers, 90+ free tiers, about 1.51B free tokens per month, 15 to 95 percent token savings, 19 routing strategies, zero dollars to start.">
|
||||
<desc>Animated hero card: a pulse travels the divider line and a compression bar demo repeatedly shrinks a prompt by up to 95 percent; all headline content is static and readable on the first frame.</desc>
|
||||
<defs>
|
||||
<pattern id="gridPaperH" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
@@ -28,7 +28,7 @@
|
||||
<text x="48" y="138" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="60" font-weight="800" fill="#e9edf3">Never stop coding<tspan fill="#a855f7">.</tspan></text>
|
||||
|
||||
<!-- subheadline -->
|
||||
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">353 providers</tspan> — <tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
|
||||
<text x="48" y="184" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="25" font-weight="600" fill="#c9d1d9">Every AI tool → <tspan fill="#a78bfa" font-weight="800">354 providers</tspan> — <tspan fill="#7ee787" font-weight="800">90+ free</tspan> — through one endpoint.</text>
|
||||
|
||||
<!-- plug line -->
|
||||
<text x="48" y="222" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="16.5" fill="#a1a1aa">Claude Code · Codex · Cursor · Cline · Copilot · Antigravity  →  <tspan fill="#7ee787" font-weight="700">FREE</tspan> Claude / GPT / Gemini · auto-fallback</text>
|
||||
|
||||
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -57,7 +57,7 @@ New tab for extracting content from a URL via `POST /v1/web/fetch` (created in p
|
||||
- Submit → fetch → render `ScrapeResult.tsx`.
|
||||
- `ScrapeResult` renders markdown preview + raw toggle.
|
||||
- Cap: if response body > **256 KB**, UI shows `(truncated, view raw)` and opens raw in a Monaco modal (D21).
|
||||
- Metadata panel: provider (firecrawl/jina-reader/tavily-search/tinyfish), latency, cost, response size, links count.
|
||||
- Metadata panel: provider (firecrawl/jina-reader/tavily-search/tinyfish/nimble-search), latency, cost, response size, links count.
|
||||
- Uses `useScrapeFetch.ts` hook.
|
||||
|
||||
### Compare Tab
|
||||
@@ -108,7 +108,7 @@ Runs the same query/URL across up to **4 providers in parallel** (D22):
|
||||
| Field | Source |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| `id`, `name` | `searchRegistry.ts` |
|
||||
| `kind` | `"search"` (12 providers) or `"fetch"` (firecrawl, jina-reader, tavily-search, tinyfish) |
|
||||
| `kind` | `"search"` or `"fetch"` (firecrawl, jina-reader, tavily-search, tinyfish, nimble-search) |
|
||||
| `costPerQuery` | Registry data |
|
||||
| `freeMonthlyQuota` | Registry data |
|
||||
| `searchTypes` / `fetchFormats` | Registry data |
|
||||
@@ -136,7 +136,7 @@ Only one backend change was needed for this feature:
|
||||
|
||||
`src/app/api/search/providers/route.ts` was extended to:
|
||||
|
||||
- Include all 4 fetch providers (`firecrawl`, `jina-reader`, `tavily-search`, `tinyfish`) in the array.
|
||||
- Include every fetch provider (`firecrawl`, `jina-reader`, `tavily-search`, `tinyfish`, `nimble-search`) in the array.
|
||||
- Add `kind: "search" | "fetch"` to every item.
|
||||
- Add `status: "configured" | "missing" | "rate_limited"` derived from live credential state.
|
||||
- Maintain backward compatibility — existing fields (`id`, `name`, etc.) unchanged.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
---
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -281,7 +281,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -479,7 +479,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
@@ -529,7 +529,7 @@ Web/search provider abstraction (Tavily, Brave, Exa, Serper, etc.).
|
||||
## Web Fetch API
|
||||
|
||||
Extract content from a URL via a configured web-fetch provider (Firecrawl, Jina
|
||||
Reader, Tavily Extract, TinyFish Fetch).
|
||||
Reader, Tavily Extract, TinyFish Fetch, Nimble Extract).
|
||||
|
||||
| Method | Path | Description |
|
||||
| ------ | --------------- | --------------------------------------------------------- |
|
||||
@@ -538,7 +538,8 @@ Reader, Tavily Extract, TinyFish Fetch).
|
||||
**Auth:** Bearer API key (`extractApiKey` + `isValidApiKey`). Policy enforced via `enforceApiKeyPolicy`.
|
||||
|
||||
**Quota-aware fallback (#8297):** when no explicit `provider` is given, the pool
|
||||
(`firecrawl` → `jina-reader` → `tavily-search` → `tinyfish`) is walked in fixed
|
||||
(`firecrawl` → `jina-reader` → `tavily-search` → `tinyfish` → `nimble-search`) is
|
||||
walked in fixed
|
||||
priority order (fill-first) — a rate-limited-but-configured provider is skipped
|
||||
instead of short-circuiting the request, and a retryable/quota upstream failure
|
||||
(HTTP 429 always; 402/403 for Firecrawl/Tavily/TinyFish quota-style free tiers —
|
||||
@@ -661,11 +662,21 @@ refusal. On success:
|
||||
{
|
||||
"allowed": true,
|
||||
// present only when the key opted into per-key usage limits (daily/weekly USD):
|
||||
"personal": { "dailySpentUsd": 1.25, "dailyLimitUsd": 5, "dailyResetAtIso": "…", "weeklySpentUsd": 8, "weeklyLimitUsd": 20, "weeklyResetAtIso": "…" /* … */ },
|
||||
"personal": {
|
||||
"dailySpentUsd": 1.25,
|
||||
"dailyLimitUsd": 5,
|
||||
"dailyResetAtIso": "…",
|
||||
"weeklySpentUsd": 8,
|
||||
"weeklyLimitUsd": 20,
|
||||
"weeklyResetAtIso": "…" /* … */,
|
||||
},
|
||||
// the selected provider quota snapshot, or null when nothing is cached yet:
|
||||
"provider": { "connectionId": "…", "provider": "claude", "plan": "…", "quotas": { /* … */ } },
|
||||
"provider": { "connectionId": "…", "provider": "claude", "plan": "…", "quotas": {/* … */} },
|
||||
// every connection's snapshot, so a UI can render several providers side by side:
|
||||
"providers": [ { "connectionId": "…", "provider": "claude", /* … */ }, { "provider": "codex", /* … */ } ]
|
||||
"providers": [
|
||||
{ "connectionId": "…", "provider": "claude" /* … */ },
|
||||
{ "provider": "codex" /* … */ },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
@@ -674,7 +685,7 @@ On refusal (`401` bad key / `403` not allowed) the same route returns
|
||||
(key allowed, nothing learned yet) is a different state from a refusal, and only the JSON form
|
||||
distinguishes them.
|
||||
|
||||
**Auth:** the caller's own Bearer API key, validated with `isValidApiKey` — this is *not* the
|
||||
**Auth:** the caller's own Bearer API key, validated with `isValidApiKey` — this is _not_ the
|
||||
management surface (`/api/keys/…`), which stays behind `requireManagementAuth`.
|
||||
|
||||
---
|
||||
|
||||
@@ -512,6 +512,7 @@ detection above).
|
||||
| `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION` | `rtk` | `open-sse/mcp-server/descriptionCompressor.ts` | Compression algorithm/profile. Disable values: `0`, `false`, `off`. |
|
||||
| `OMNIROUTE_MCP_FETCH_TIMEOUT_MS` | `10000` | `open-sse/mcp-server/fetchTimeout.ts` | Abort budget (ms) for MCP-server internal management reads (health, resilience, combos, quota, usage). |
|
||||
| `OMNIROUTE_MCP_UPSTREAM_TIMEOUT_MS` | `60000` | `open-sse/mcp-server/fetchTimeout.ts` | Abort budget (ms) for MCP hops that wait on a provider (`route_request`, `web_search`, `web_fetch`). |
|
||||
| `OMNIROUTE_CORPUS_CACHE_SIZE` | `5` | `src/lib/localCorpus/configured.ts` | Maximum number of local-corpus index instances cached in memory (LRU, one per indexed root directory). Clamped to a minimum of `1`. |
|
||||
| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. |
|
||||
| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/lib/usage/providerLimits.ts` | Provider rate-limit and quota polling interval. |
|
||||
| `PROVIDER_LIMITS_SYNC_SPACING_MS` | `1500` | `src/lib/usage/providerLimits.ts` | Gap (ms) between consecutive OAuth quota fetches in a bulk sync; OAuth connections are fetched one at a time to avoid bursting an upstream. `0` opts out (concurrent). |
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
title: "Provider Reference"
|
||||
version: 3.8.51
|
||||
lastUpdated: 2026-08-25
|
||||
lastUpdated: 2026-08-26
|
||||
---
|
||||
|
||||
# Provider Reference
|
||||
|
||||
> **Auto-generated** from `src/shared/constants/providers.ts` — do not edit by hand.
|
||||
> Regenerate with: `npm run gen:provider-reference`
|
||||
> **Last generated:** 2026-08-25
|
||||
> **Last generated:** 2026-08-26
|
||||
|
||||
Total providers: **353**. See category breakdown below.
|
||||
Total providers: **354**. See category breakdown below.
|
||||
|
||||
## Categories
|
||||
|
||||
@@ -98,7 +98,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `deepseek-web` | `ds-web` | DeepSeek Web | Web cookie | [link](https://chat.deepseek.com) | Paste your userToken from chat.deepseek.com — DevTools → Application → Local Storage → userToken | emulated |
|
||||
| `doubao-web` | `db` | Dola Web (ByteDance) | Web cookie | [link](https://www.dola.com) | Paste the full Cookie header from www.dola.com. It should include sessionid, ttwid, and s_v_web_id. If s_v_web_id is unavailable, fp=verify_... from a chat/completion request URL can be used as a fallback. | — |
|
||||
| `gemini-business` | `gembiz` | Gemini Business (Enterprise) | Web cookie | [link](https://business.gemini.google) | From your enterprise account: open business.gemini.google/home/cid/{your-cid}, then copy __Secure-1PSID and __Secure-1PSIDTS cookies from DevTools → Application → Cookies. Paste as a cookie header below. | — |
|
||||
| `gemini-web` | `gweb` | Gemini Web (Free) | Web cookie | [link](https://gemini.google.com) | Paste your __Secure-1PSID cookie value from gemini.google.com. Optionally add __Secure-1PSIDTS separated by semicolon. | emulated |
|
||||
| `gemini-web` | `gweb` | Gemini Web (Free) | Web cookie | [link](https://gemini.google.com) | Paste the full cookie header, the __Secure-1PSID value, or the JSON export containing cookies from gemini.google.com. Include __Secure-1PSIDTS and __Secure-1PSIDCC when available. | emulated |
|
||||
| `grok-web` | `gw` | Grok Web (Subscription) | Web cookie | [link](https://grok.com) | Paste the full grok.com cookie line from DevTools → Application → Cookies. Include both `sso` and `sso-rw` (e.g. `sso=...; sso-rw=...`) — Grok's anti-bot rejects `sso` on its own. | — |
|
||||
| `hailuo-web` | `hailuo-web` | Hailuo Web (MiniMax) | Web cookie | [link](https://chat.minimax.io) | Open hailuo.ai, log in, then open DevTools → Application → Local Storage → copy the "_token" value. device_id/uuid fingerprint fields are derived automatically; if requests fail, re-capture _token (sessions can expire). | — |
|
||||
| `huggingchat` | `huggingchat` | HuggingChat (Free) | Web cookie | [link](https://huggingface.co/chat) | Paste the full Cookie header from huggingface.co/chat (DevTools → Network → /chat/conversation → Request Headers → Cookie). It should include hf-chat and may also include token / aws-waf-token. | — |
|
||||
@@ -122,7 +122,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `zai-web` | `zw` | Z.ai Web | Web cookie | [link](https://chat.z.ai) | Copy the "token" value from chat.z.ai → DevTools → Application → Local Storage. Do not copy cookies; OmniRoute handles the per-request CAPTCHA through its browser transport. | — |
|
||||
| `zenmux-free` | `zmf` | ZenMux Free (Web) | Web cookie | [link](https://zenmux.ai) | Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days. | — |
|
||||
|
||||
## API Key Providers (paid / paid-with-free-credits) (233)
|
||||
## API Key Providers (paid / paid-with-free-credits) (234)
|
||||
|
||||
| ID | Alias | Name | Tags | Website | Notes |
|
||||
|----|-------|------|------|---------|-------|
|
||||
@@ -284,6 +284,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `openference-api` | `ofa` | Openference API | API key | [link](https://openference.com) | Free plan: 3-day trial with open-source models — no credit card required |
|
||||
| `openrouter` | `openrouter` | OpenRouter | API key, aggregator | [link](https://openrouter.ai) | Free models at $0/token with :free suffix - 20 RPM / 200 RPD |
|
||||
| `openvecta` | `openvecta` | OpenVecta | API key | [link](https://openvecta.com) | Free credits on signup for OpenAI-compatible inference across LLMs, embeddings, and reasoning models |
|
||||
| `opper` | `opper` | Opper | API key, aggregator | [link](https://opper.ai) | — |
|
||||
| `orcarouter` | `orcarouter` | OrcaRouter | API key | [link](https://www.orcarouter.ai) | — |
|
||||
| `ovhcloud` | `ovh` | OVHcloud AI | API key | [link](https://www.ovhcloud.com) | — |
|
||||
| `perplexity` | `pplx` | Perplexity | API key | [link](https://www.perplexity.ai) | — |
|
||||
@@ -379,7 +380,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `vllm` | `vllm` | vLLM | Local, self-hosted | [link](https://github.com/vllm-project/vllm) | API key optional. Configure the local vLLM OpenAI-compatible base URL (default: http://localhost:8000/v1). |
|
||||
| `xinference` | `xinference` | XInference | Local, self-hosted | [link](https://inference.readthedocs.io) | API key optional. Configure the local XInference OpenAI-compatible base URL (default: http://localhost:9997/v1). |
|
||||
|
||||
## Search Providers (15)
|
||||
## Search Providers (16)
|
||||
|
||||
| ID | Alias | Name | Tags | Website | Notes |
|
||||
|----|-------|------|------|---------|-------|
|
||||
@@ -389,6 +390,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
| `firecrawl` | `fc` | Firecrawl | Search | [link](https://firecrawl.dev) | API key from firecrawl.dev/app/api-keys (or set your self-hosted Firecrawl base URL) |
|
||||
| `google-pse-search` | `google-pse` | Google Programmable Search | Search | [link](https://developers.google.com/custom-search/v1/overview) | Requires a Google API key and your Programmable Search Engine ID (cx) |
|
||||
| `linkup-search` | `linkup` | Linkup Search | Search | [link](https://docs.linkup.so) | Bearer API key from the Linkup dashboard |
|
||||
| `nimble-search` | `nimble` | Nimble Search | Search | [link](https://docs.nimbleway.com/nimble-sdk/web-tools/search) | Bearer API key from the Nimble dashboard |
|
||||
| `ollama-search` | `ollama-search` | Ollama Search | Search | [link](https://ollama.com/settings/keys) | Same API key as Ollama Cloud (from ollama.com/settings/keys) |
|
||||
| `perplexity-search` | `pplx-search` | Perplexity Search | Search | [link](https://docs.perplexity.ai/guides/search-quickstart) | Same API key as Perplexity (pplx-...) |
|
||||
| `searchapi-search` | `searchapi` | SearchAPI | Search | [link](https://www.searchapi.io/docs/google) | API key from SearchAPI (query param or Bearer auth) |
|
||||
@@ -441,7 +443,7 @@ Use the dashboard at `/dashboard/providers` to enable, configure, and test each
|
||||
|
||||
- Catalog: [`src/shared/constants/providers.ts`](../../src/shared/constants/providers.ts)
|
||||
- Registry (per-model details): [`open-sse/config/providerRegistry.ts`](../../open-sse/config/providerRegistry.ts)
|
||||
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (109 implementations)
|
||||
- Executors: [`open-sse/executors/`](../../open-sse/executors/) (110 implementations)
|
||||
- Translators: [`open-sse/translator/`](../../open-sse/translator/)
|
||||
|
||||
## See Also
|
||||
|
||||
6
llm.txt
@@ -1,6 +1,6 @@
|
||||
# OmniRoute
|
||||
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 353 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
> OmniRoute is a free, open-source AI Gateway that acts as a universal API proxy for multi-provider LLMs. It provides smart routing, automatic fallback, load balancing, and format translation across 354 AI providers — all through a single OpenAI-compatible endpoint. Includes a built-in MCP Server (110 tools), A2A v0.3 protocol, Memory/Skills systems, Cloud Agents (codex, cursor, devin, jules), Guardrails framework, and an Electron desktop app.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -277,7 +277,7 @@ OmniRoute solves the problem of managing multiple AI provider subscriptions, quo
|
||||
## Key Features (v3.8.50)
|
||||
|
||||
### Core Proxy
|
||||
- **353 AI providers** with automatic format translation
|
||||
- **354 AI providers** with automatic format translation
|
||||
- **Provider categories**: Free (90+ free tiers), OAuth, API Key, Self-Hosted, Custom (OpenAI/Anthropic-compatible)
|
||||
- **18 routing strategies**: priority, weighted, round-robin, fill-first, p2c, random, least-used, cost-optimized, reset-aware, reset-window, headroom, strict-random, auto, lkgp, context-optimized, context-relay, fusion, pipeline
|
||||
- **4-tier fallback**: Subscription → API Key → Cheap → Free
|
||||
@@ -475,7 +475,7 @@ diagnostics) plus **memory**, **skill**, **agentSkill**, **githubSkill**, **pool
|
||||
|
||||
## v3.8.x Highlights
|
||||
|
||||
- **353-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **354-provider catalog** with 90+ free tiers, one-click account imports, and bulk key add
|
||||
- **19 routing strategies** — including `fusion` (parallel panel + judge synthesis), `pipeline`, `reset-aware`, `reset-window`, `headroom`, and `context-relay`
|
||||
- **14-factor Auto-Combo scoring** with bandit exploration and progressive cooldown
|
||||
- **MCP server expanded to 110 tools / 33 scopes** (canonical + memory/skill/agentSkill/githubSkill/pool/notion/obsidian/localCorpus/gamification/plugin modules)
|
||||
|
||||
17
open-sse/config/nimble.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Nimble shared client constants.
|
||||
*
|
||||
* Nimble asks every integration to identify itself with a stable client-source
|
||||
* header so calls can be attributed to the host product. Both Nimble surfaces
|
||||
* in OmniRoute — search (`POST /v1/search`, wired in open-sse/handlers/search.ts)
|
||||
* and fetch (`POST /v1/extract`, open-sse/executors/nimble-fetch.ts) — send it,
|
||||
* and both read the value from here so the two can never drift apart.
|
||||
*
|
||||
* Docs: https://docs.nimbleway.com/api-reference/introduction
|
||||
*/
|
||||
|
||||
/** Header Nimble uses to attribute a request to the calling product. */
|
||||
export const NIMBLE_CLIENT_SOURCE_HEADER = "X-Client-Source";
|
||||
|
||||
/** The value OmniRoute sends. Do not vary it per surface or per request. */
|
||||
export const NIMBLE_CLIENT_SOURCE = "omniroute";
|
||||
@@ -63,6 +63,7 @@ import { nubeProvider } from "./registry/nube/index.ts";
|
||||
import { clinepassProvider } from "./registry/clinepass/index.ts";
|
||||
import { sparkdeskProvider } from "./registry/sparkdesk/index.ts";
|
||||
import { nlpcloudProvider } from "./registry/nlpcloud/index.ts";
|
||||
import { oneminaiProvider } from "./registry/oneminai/index.ts";
|
||||
import { nvidiaProvider } from "./registry/nvidia/index.ts";
|
||||
import { api_airforceProvider } from "./registry/api-airforce/index.ts";
|
||||
import { mistralProvider } from "./registry/mistral/index.ts";
|
||||
@@ -229,6 +230,7 @@ import { x5labProvider } from "./registry/x5lab/index.ts";
|
||||
import { kenariProvider } from "./registry/kenari/index.ts";
|
||||
import { navyProvider } from "./registry/navy/index.ts";
|
||||
import { naraProvider } from "./registry/nara/index.ts";
|
||||
import { opperProvider } from "./registry/opper/index.ts";
|
||||
import { requestyProvider } from "./registry/requesty/index.ts";
|
||||
import { sealionProvider } from "./registry/sealion/index.ts";
|
||||
import { routewayProvider } from "./registry/routeway/index.ts";
|
||||
@@ -331,6 +333,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
clinepass: clinepassProvider,
|
||||
sparkdesk: sparkdeskProvider,
|
||||
nlpcloud: nlpcloudProvider,
|
||||
oneminai: oneminaiProvider,
|
||||
nvidia: nvidiaProvider,
|
||||
"api-airforce": api_airforceProvider,
|
||||
mistral: mistralProvider,
|
||||
@@ -500,6 +503,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
|
||||
kenari: kenariProvider,
|
||||
navy: navyProvider,
|
||||
nara: naraProvider,
|
||||
opper: opperProvider,
|
||||
requesty: requestyProvider,
|
||||
sealion: sealionProvider,
|
||||
routeway: routewayProvider,
|
||||
|
||||
30
open-sse/config/providers/registry/oneminai/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
|
||||
// 1min.ai (docs.1min.ai) — a chat aggregator exposing many upstream models
|
||||
// through one custom API. Not OpenAI-compatible at the wire level (single
|
||||
// `promptObject.prompt` string instead of a `messages` array, real SSE with
|
||||
// event:/data: framing instead of raw text deltas, "API-KEY" auth header
|
||||
// instead of Authorization: Bearer) — see open-sse/executors/oneminai.ts for
|
||||
// the request/response translation. `format: "openai"` here describes the
|
||||
// client-facing surface OmniRoute exposes, not 1min.ai's actual wire format.
|
||||
export const oneminaiProvider: RegistryEntry = {
|
||||
id: "oneminai",
|
||||
alias: "1min",
|
||||
format: "openai",
|
||||
executor: "default",
|
||||
baseUrl: "https://api.1min.ai/api/chat-with-ai",
|
||||
authType: "apikey",
|
||||
authHeader: "api-key",
|
||||
// The model catalog is loaded dynamically per-account/plan on 1min.ai's own
|
||||
// dashboard rather than published as a stable public list, so only the
|
||||
// model shown in every one of 1min.ai's own docs examples is statically
|
||||
// catalogued; passthroughModels lets any other slug the account has access
|
||||
// to be used by id.
|
||||
passthroughModels: true,
|
||||
liveCatalogAuthoritative: false,
|
||||
// No tool/function-calling, JSON mode, or vision support is wired up by the
|
||||
// executor's translation (1min.ai's attachments.images/files feature would
|
||||
// need separate Asset API upload plumbing this provider doesn't implement).
|
||||
unsupportedParams: ["tools", "tool_choice", "functions", "function_call", "response_format"],
|
||||
models: [{ id: "gpt-4o-mini", name: "GPT-4o Mini" }],
|
||||
};
|
||||
11
open-sse/config/providers/registry/opper/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { RegistryEntry } from "../../shared.ts";
|
||||
import { buildOpenAiCompatibleRegistryEntry } from "../../shared.ts";
|
||||
|
||||
export const opperProvider: RegistryEntry = buildOpenAiCompatibleRegistryEntry({
|
||||
id: "opper",
|
||||
alias: "opper",
|
||||
baseUrl: "https://api.opper.ai/v3/compat/chat/completions",
|
||||
modelsUrl: "https://api.opper.ai/v3/compat/models",
|
||||
models: [],
|
||||
passthroughModels: true,
|
||||
});
|
||||
@@ -116,6 +116,27 @@ export const SEARCH_PROVIDERS: Record<string, SearchProviderConfig> = {
|
||||
cacheTTLMs: 5 * 60 * 1000,
|
||||
},
|
||||
|
||||
// Nimble also serves POST /v1/web/fetch through the same credential — see
|
||||
// open-sse/executors/nimble-fetch.ts.
|
||||
"nimble-search": {
|
||||
id: "nimble-search",
|
||||
name: "Nimble Search",
|
||||
baseUrl: "https://sdk.nimbleway.com/v1/search",
|
||||
method: "POST",
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
costPerQuery: 0.005,
|
||||
freeMonthlyQuota: 0,
|
||||
searchTypes: ["web", "news"],
|
||||
defaultMaxResults: 5,
|
||||
maxMaxResults: 100,
|
||||
// Kept below GLOBAL_TIMEOUT_MS (handlers/search.ts) so a stalled Nimble call
|
||||
// still leaves budget for the failover provider instead of burning the whole
|
||||
// request window.
|
||||
timeoutMs: 10_000,
|
||||
cacheTTLMs: 5 * 60 * 1000,
|
||||
},
|
||||
|
||||
firecrawl: {
|
||||
id: "firecrawl",
|
||||
name: "Firecrawl",
|
||||
|
||||
@@ -60,6 +60,8 @@ const lazyExecutors: Record<string, () => Promise<BaseExecutor>> = {
|
||||
gitlab: () => import("./gitlab.ts").then((m) => new m.GitlabExecutor()),
|
||||
"gitlab-duo": () => import("./gitlab.ts").then((m) => new m.GitlabExecutor("gitlab-duo")),
|
||||
nlpcloud: () => import("./nlpcloud.ts").then((m) => new m.NlpCloudExecutor()),
|
||||
oneminai: () => import("./oneminai.ts").then((m) => new m.OneMinAiExecutor()),
|
||||
"1min": () => import("./oneminai.ts").then((m) => new m.OneMinAiExecutor()), // Alias
|
||||
pollinations: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()),
|
||||
pol: () => import("./pollinations.ts").then((m) => new m.PollinationsExecutor()), // Alias
|
||||
"cloudflare-ai": () => import("./cloudflare-ai.ts").then((m) => new m.CloudflareAIExecutor()),
|
||||
|
||||
@@ -243,6 +243,14 @@ export function resolveKiroRegion(
|
||||
// kiroRuntimeHost from this executor keep working.
|
||||
export { kiroRuntimeHost };
|
||||
|
||||
/**
|
||||
* Status codes for which trying the next candidate endpoint may succeed where the
|
||||
* current one failed (auth/profile mismatch, not a payload problem). Mirrors
|
||||
* 9router's KIRO_ENDPOINT_FALLBACK_STATUSES — a 400 (malformed body) is deliberately
|
||||
* excluded since resending the same body to another host cannot fix it.
|
||||
*/
|
||||
const KIRO_ENDPOINT_FALLBACK_STATUSES = new Set([401, 403, 404]);
|
||||
|
||||
/**
|
||||
* KiroExecutor - Executor for Kiro AI (AWS CodeWhisperer)
|
||||
* Uses AWS CodeWhisperer streaming API with AWS EventStream binary format
|
||||
@@ -334,17 +342,47 @@ export class KiroExecutor extends BaseExecutor {
|
||||
// Center accounts (e.g. eu-central-1) are rejected by the default us-east-1 host; only the
|
||||
// regional endpoint accepts the region-bound token + profileArn.
|
||||
const region = resolveKiroRegion(credentials);
|
||||
const url = `${kiroRuntimeHost(region)}/generateAssistantResponse`;
|
||||
const regionalUrl = `${kiroRuntimeHost(region)}/generateAssistantResponse`;
|
||||
|
||||
// The Kiro IDE's own branded gateway (runtime.*.kiro.dev) only exists for
|
||||
// us-east-1 and only accepts Kiro OIDC/social tokens — it rejects
|
||||
// TokenType=API_KEY and external-IdP/IdC SSO tokens outright (403 "bearer
|
||||
// token invalid"), so those auth methods go straight to the region-resolved
|
||||
// CodeWhisperer/Amazon Q surface (mirrors 9router's getOrderedBaseUrls in
|
||||
// open-sse/executors/kiro.js). For everything else, try the branded gateway
|
||||
// first — it is the surface the native Kiro IDE itself talks to — and fall
|
||||
// back to the raw AWS host on an auth/profile-shaped failure.
|
||||
const authMethod =
|
||||
typeof credentials.providerSpecificData?.authMethod === "string"
|
||||
? credentials.providerSpecificData.authMethod
|
||||
: undefined;
|
||||
const isCodeWhispererOnly =
|
||||
authMethod === "api_key" || authMethod === "idc" || isExternalIdpAuthMethod(authMethod);
|
||||
const candidateUrls =
|
||||
region === "us-east-1" && !isCodeWhispererOnly
|
||||
? ["https://runtime.us-east-1.kiro.dev/generateAssistantResponse", regionalUrl]
|
||||
: [regionalUrl];
|
||||
|
||||
const headers = this.buildHeaders(credentials, stream);
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
const transformedBody = await this.transformRequest(model, body, stream, credentials);
|
||||
const requestBody = JSON.stringify(transformedBody);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(transformedBody),
|
||||
signal,
|
||||
});
|
||||
let response!: Response;
|
||||
let url = candidateUrls[0];
|
||||
for (let i = 0; i < candidateUrls.length; i++) {
|
||||
url = candidateUrls[i];
|
||||
response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: requestBody,
|
||||
signal,
|
||||
});
|
||||
const hasFallback = i + 1 < candidateUrls.length;
|
||||
if (response.ok || !hasFallback || !KIRO_ENDPOINT_FALLBACK_STATUSES.has(response.status)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return { response, url, headers, transformedBody };
|
||||
|
||||
222
open-sse/executors/nimble-fetch.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Nimble Web Fetch Executor
|
||||
*
|
||||
* Fetches content from a URL using the Nimble Extract API.
|
||||
* POST https://sdk.nimbleway.com/v1/extract
|
||||
*
|
||||
* Extract returns the requested formats side by side under `data`, so one call
|
||||
* covers every OmniRoute fetch format: `markdown`, `html`, `links` and
|
||||
* `screenshot` (base64 PNG, surfaced as a data URL).
|
||||
*
|
||||
* Docs: https://docs.nimbleway.com/nimble-sdk/web-tools/extract/quickstart
|
||||
*/
|
||||
|
||||
import { sanitizeErrorMessage, buildErrorBody } from "../utils/error.ts";
|
||||
import { NIMBLE_CLIENT_SOURCE, NIMBLE_CLIENT_SOURCE_HEADER } from "../config/nimble.ts";
|
||||
import type { WebFetchResult, WebFetchFormat, WebFetchCredentials } from "../handlers/webFetch.ts";
|
||||
|
||||
const NIMBLE_EXTRACT_URL = "https://sdk.nimbleway.com/v1/extract";
|
||||
const NIMBLE_TIMEOUT_MS = 30_000;
|
||||
|
||||
/** Max characters kept from an HTML <title> / meta description. */
|
||||
const META_MAX_CHARS = 500;
|
||||
|
||||
// These run over untrusted upstream HTML, so every quantifier is over a character
|
||||
// class that cannot cross its own terminator — no backtracking (see AGENTS.md →
|
||||
// "Regex Security"). Length is capped by META_MAX_CHARS after the match, not by the
|
||||
// quantifier: bounding the capture instead would make an over-long title fail to
|
||||
// match at all rather than truncate.
|
||||
const TITLE_RE = /<title[^>]{0,200}>([^<]*)<\/title>/i;
|
||||
// One pattern per quote style. A shared ["'] class for the closing delimiter would
|
||||
// cut a double-quoted description at its first apostrophe ("Don't miss…" → "Don").
|
||||
const META_DESCRIPTION_DOUBLE_RE =
|
||||
/<meta[^>]{0,200}name=["']description["'][^>]{0,200}content="([^"]*)"/i;
|
||||
const META_DESCRIPTION_SINGLE_RE =
|
||||
/<meta[^>]{0,200}name=["']description["'][^>]{0,200}content='([^']*)'/i;
|
||||
|
||||
/** Map an OmniRoute fetch format onto the Nimble Extract format name. */
|
||||
function mapFormat(format: WebFetchFormat): string {
|
||||
switch (format) {
|
||||
case "html":
|
||||
return "html";
|
||||
case "links":
|
||||
return "links";
|
||||
case "screenshot":
|
||||
return "screenshot";
|
||||
case "markdown":
|
||||
default:
|
||||
return "markdown";
|
||||
}
|
||||
}
|
||||
|
||||
function readString(source: Record<string, unknown>, key: string): string {
|
||||
const value = source[key];
|
||||
return value != null ? String(value) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull a title / description out of raw HTML.
|
||||
* Extract has no dedicated metadata format, so metadata is only available when
|
||||
* the caller asked for it and we requested `html` alongside their format.
|
||||
*/
|
||||
function parseHtmlMetadata(html: string): { title: string | null; description: string | null } {
|
||||
if (!html) return { title: null, description: null };
|
||||
|
||||
const title = TITLE_RE.exec(html)?.[1]?.trim().slice(0, META_MAX_CHARS);
|
||||
const rawDescription =
|
||||
META_DESCRIPTION_DOUBLE_RE.exec(html)?.[1] ?? META_DESCRIPTION_SINGLE_RE.exec(html)?.[1];
|
||||
const description = rawDescription?.trim().slice(0, META_MAX_CHARS);
|
||||
|
||||
return {
|
||||
title: title ? title : null,
|
||||
description: description ? description : null,
|
||||
};
|
||||
}
|
||||
|
||||
interface NimbleFetchOptions {
|
||||
url: string;
|
||||
format: WebFetchFormat;
|
||||
includeMetadata: boolean;
|
||||
credentials: WebFetchCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Nimble Extract request.
|
||||
*/
|
||||
export async function nimbleFetch(opts: NimbleFetchOptions): Promise<WebFetchResult> {
|
||||
const { url, format, includeMetadata, credentials } = opts;
|
||||
|
||||
if (!credentials.apiKey) {
|
||||
const body = buildErrorBody(401, "Nimble API key required");
|
||||
return { success: false, status: 401, error: body.error.message };
|
||||
}
|
||||
|
||||
const requested = mapFormat(format);
|
||||
|
||||
// `links` always comes back so WebFetchResponse.links is populated; `html` is
|
||||
// added only when the caller asked for metadata, since it is the sole source
|
||||
// of a page title/description.
|
||||
const formats = [...new Set([requested, "links", ...(includeMetadata ? ["html"] : [])])];
|
||||
|
||||
const requestBody: Record<string, unknown> = {
|
||||
url,
|
||||
formats,
|
||||
// Extract types `render` as `boolean | "auto"`; "auto" lets Nimble select the
|
||||
// driver per target domain rather than forcing a browser on every static page.
|
||||
render: "auto",
|
||||
};
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => {
|
||||
const err = new Error(`nimble-fetch timeout after ${NIMBLE_TIMEOUT_MS}ms`);
|
||||
err.name = "TimeoutError";
|
||||
controller.abort(err);
|
||||
}, NIMBLE_TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
const response = await fetch(NIMBLE_EXTRACT_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${credentials.apiKey}`,
|
||||
[NIMBLE_CLIENT_SOURCE_HEADER]: NIMBLE_CLIENT_SOURCE,
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Nimble returns a plain-text body on auth failures, so this must be
|
||||
// sanitized before it can reach a response.
|
||||
const rawError = await response.text().catch(() => `HTTP ${response.status}`);
|
||||
const msg = sanitizeErrorMessage(`Nimble error ${response.status}: ${rawError}`);
|
||||
const body = buildErrorBody(response.status, msg);
|
||||
return { success: false, status: response.status, error: body.error.message };
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as Record<string, unknown>;
|
||||
|
||||
// A 200 can still carry a failed extraction — Extract reports the target's own
|
||||
// outcome in the envelope. Without this the caller would get an empty document
|
||||
// marked successful, and the pool in /v1/web/fetch would never fall through to
|
||||
// the next provider.
|
||||
const taskStatus = typeof payload.status === "string" ? payload.status : "";
|
||||
const taskStatusCode = typeof payload.status_code === "number" ? payload.status_code : null;
|
||||
if (
|
||||
(taskStatus && taskStatus !== "success") ||
|
||||
(taskStatusCode !== null && taskStatusCode >= 400)
|
||||
) {
|
||||
const detail = taskStatus || `status ${taskStatusCode}`;
|
||||
const msg = sanitizeErrorMessage(`Nimble extraction did not succeed: ${detail}`);
|
||||
const body = buildErrorBody(502, msg);
|
||||
return { success: false, status: 502, error: body.error.message };
|
||||
}
|
||||
|
||||
const data = (payload.data as Record<string, unknown> | null) ?? {};
|
||||
|
||||
const rawLinks = data.links;
|
||||
const links: string[] = Array.isArray(rawLinks) ? rawLinks.map((l) => String(l)) : [];
|
||||
|
||||
const screenshot = readString(data, "screenshot");
|
||||
const screenshotUrl =
|
||||
format === "screenshot" && screenshot
|
||||
? screenshot.startsWith("data:")
|
||||
? screenshot
|
||||
: `data:image/png;base64,${screenshot}`
|
||||
: null;
|
||||
|
||||
// The requested format must actually be present. An absent key means Extract
|
||||
// succeeded but produced nothing for what the caller asked for; returning an
|
||||
// empty document as a success would stop /v1/web/fetch from trying the next
|
||||
// provider. An empty *value* is legitimate (a genuinely blank page) and passes.
|
||||
if (!(requested in data)) {
|
||||
const msg = sanitizeErrorMessage(`Nimble returned no ${requested} content for the request`);
|
||||
const body = buildErrorBody(502, msg);
|
||||
return { success: false, status: 502, error: body.error.message };
|
||||
}
|
||||
|
||||
let content: string;
|
||||
switch (format) {
|
||||
case "html":
|
||||
content = readString(data, "html");
|
||||
break;
|
||||
case "links":
|
||||
content = JSON.stringify(links);
|
||||
break;
|
||||
case "screenshot":
|
||||
content = "";
|
||||
break;
|
||||
case "markdown":
|
||||
default:
|
||||
content = readString(data, "markdown");
|
||||
break;
|
||||
}
|
||||
|
||||
const metadata = includeMetadata ? parseHtmlMetadata(readString(data, "html")) : null;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
provider: "nimble-search",
|
||||
url,
|
||||
content,
|
||||
links,
|
||||
metadata,
|
||||
screenshot_url: screenshotUrl,
|
||||
},
|
||||
};
|
||||
} catch (err: unknown) {
|
||||
// The abort reason above is named "TimeoutError"; an external abort surfaces as
|
||||
// "AbortError". Both must reach the 504 branch.
|
||||
if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
|
||||
const body = buildErrorBody(504, "Nimble request timed out");
|
||||
return { success: false, status: 504, error: body.error.message };
|
||||
}
|
||||
const msg =
|
||||
err instanceof Error ? sanitizeErrorMessage(err.message) : sanitizeErrorMessage(String(err));
|
||||
const body = buildErrorBody(502, msg);
|
||||
return { success: false, status: 502, error: body.error.message };
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
313
open-sse/executors/oneminai.ts
Normal file
@@ -0,0 +1,313 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
import {
|
||||
BaseExecutor,
|
||||
mergeUpstreamExtraHeaders,
|
||||
type ExecuteInput,
|
||||
type ProviderCredentials,
|
||||
} from "./base.ts";
|
||||
import { PROVIDERS } from "../config/constants.ts";
|
||||
import { buildErrorBody } from "../utils/error.ts";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
type OpenAIMessage = {
|
||||
role?: string;
|
||||
content?: unknown;
|
||||
};
|
||||
|
||||
const CHAT_URL = "https://api.1min.ai/api/chat-with-ai";
|
||||
const ROLE_LABELS: Record<string, string> = {
|
||||
system: "System",
|
||||
developer: "System",
|
||||
user: "User",
|
||||
assistant: "Assistant",
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): JsonRecord {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
|
||||
}
|
||||
|
||||
function extractTextContent(content: unknown): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (!Array.isArray(content)) return "";
|
||||
return content
|
||||
.map((part) => {
|
||||
if (!part || typeof part !== "object") return "";
|
||||
const item = part as Record<string, unknown>;
|
||||
return item.type === "text" && typeof item.text === "string" ? item.text : "";
|
||||
})
|
||||
.filter((text) => text.length > 0)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 1min.ai's Chat with AI API takes one `promptObject.prompt` string, not an
|
||||
* OpenAI `messages` array — multi-turn context is normally carried server-side
|
||||
* via `promptObject.conversationId` (see docs.1min.ai/docs/api/chat-with-ai-api),
|
||||
* which requires a prior POST /api/conversations call and a stable conversation
|
||||
* identity that stateless OpenAI-compatible clients don't provide. Rather than
|
||||
* half-implement that, a single user message passes through unchanged and
|
||||
* multi-turn history is flattened into a labeled transcript.
|
||||
*/
|
||||
export function buildPrompt(messages: OpenAIMessage[] | undefined): string {
|
||||
const list = Array.isArray(messages) ? messages : [];
|
||||
if (list.length === 1 && list[0]?.role === "user") {
|
||||
return extractTextContent(list[0].content);
|
||||
}
|
||||
return list
|
||||
.map((message) => {
|
||||
const role = String(message?.role || "user").toLowerCase();
|
||||
const text = extractTextContent(message?.content);
|
||||
const label = ROLE_LABELS[role] || role;
|
||||
return `${label}: ${text}`;
|
||||
})
|
||||
.filter((line) => line.length > 0)
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
function buildSseChunk(data: unknown): string {
|
||||
return `data: ${JSON.stringify(data)}\n\n`;
|
||||
}
|
||||
|
||||
function buildOpenAiJsonCompletion(content: string, model: string, id: string, created: number): Response {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
id,
|
||||
object: "chat.completion",
|
||||
created,
|
||||
model,
|
||||
choices: [{ index: 0, message: { role: "assistant", content }, finish_reason: "stop" }],
|
||||
// 1min.ai's response shape carries no token-usage fields.
|
||||
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
function toOpenAiErrorResponse(status: number, message: string, upstreamDetails?: unknown): Response {
|
||||
return new Response(JSON.stringify(buildErrorBody(status, message, upstreamDetails)), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse 1min.ai's real Server-Sent Events (event: content|result|done|error,
|
||||
* data: {...}) from the upstream Response body and re-emit them as standard
|
||||
* OpenAI chat.completion.chunk SSE.
|
||||
*/
|
||||
function translateSseStream(upstreamBody: ReadableStream<Uint8Array>, model: string, id: string, created: number): ReadableStream<Uint8Array> {
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
return new ReadableStream<Uint8Array>({
|
||||
async start(controller) {
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
buildSseChunk({
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model,
|
||||
choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }],
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const reader = upstreamBody.getReader();
|
||||
let buffer = "";
|
||||
let finished = false;
|
||||
|
||||
const finish = () => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
buildSseChunk({
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model,
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
})
|
||||
)
|
||||
);
|
||||
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
||||
controller.close();
|
||||
};
|
||||
|
||||
const emitContent = (text: string) => {
|
||||
if (!text) return;
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
buildSseChunk({
|
||||
id,
|
||||
object: "chat.completion.chunk",
|
||||
created,
|
||||
model,
|
||||
choices: [{ index: 0, delta: { content: text }, finish_reason: null }],
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// SSE event framing: "event:"/"data:" lines, blank-line separated records.
|
||||
const processEvent = (eventText: string) => {
|
||||
let eventType = "message";
|
||||
const dataLines: string[] = [];
|
||||
for (const rawLine of eventText.split("\n")) {
|
||||
if (rawLine.startsWith("event:")) {
|
||||
eventType = rawLine.slice(6).trim();
|
||||
} else if (rawLine.startsWith("data:")) {
|
||||
dataLines.push(rawLine.slice(5).trim());
|
||||
}
|
||||
}
|
||||
const data = dataLines.join("\n");
|
||||
if (eventType === "content") {
|
||||
try {
|
||||
const parsed = asRecord(JSON.parse(data));
|
||||
if (typeof parsed.content === "string") emitContent(parsed.content);
|
||||
} catch {
|
||||
// Ignore malformed content events rather than surfacing partial JSON.
|
||||
}
|
||||
} else if (eventType === "error") {
|
||||
emitContent(`\n[1min.ai error: ${data}]`);
|
||||
finish();
|
||||
} else if (eventType === "done") {
|
||||
finish();
|
||||
}
|
||||
// "result" carries the final full aiRecord, redundant with the content
|
||||
// events already streamed — intentionally ignored.
|
||||
};
|
||||
|
||||
try {
|
||||
while (!finished) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
let separatorIndex = buffer.indexOf("\n\n");
|
||||
while (separatorIndex !== -1) {
|
||||
processEvent(buffer.slice(0, separatorIndex));
|
||||
buffer = buffer.slice(separatorIndex + 2);
|
||||
separatorIndex = buffer.indexOf("\n\n");
|
||||
}
|
||||
}
|
||||
if (!finished && buffer.trim()) processEvent(buffer);
|
||||
finish();
|
||||
} catch (error) {
|
||||
controller.error(error);
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export class OneMinAiExecutor extends BaseExecutor {
|
||||
constructor() {
|
||||
super("oneminai", PROVIDERS["oneminai"] || { format: "openai", baseUrl: CHAT_URL });
|
||||
}
|
||||
|
||||
buildUrl(_model: string, stream: boolean): string {
|
||||
return stream ? `${CHAT_URL}?isStreaming=true` : CHAT_URL;
|
||||
}
|
||||
|
||||
buildHeaders(credentials: ProviderCredentials | null): Record<string, string> {
|
||||
const key = credentials?.apiKey || credentials?.accessToken || "";
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"API-KEY": key,
|
||||
};
|
||||
}
|
||||
|
||||
transformRequest(model: string, body: unknown): JsonRecord {
|
||||
const payload = asRecord(body);
|
||||
const messages = Array.isArray(payload.messages) ? (payload.messages as OpenAIMessage[]) : [];
|
||||
return {
|
||||
type: "UNIFY_CHAT_WITH_AI",
|
||||
model,
|
||||
promptObject: { prompt: buildPrompt(messages) },
|
||||
};
|
||||
}
|
||||
|
||||
async execute({ model, body, stream, credentials, signal, upstreamExtraHeaders }: ExecuteInput) {
|
||||
const url = this.buildUrl(model, stream);
|
||||
const headers = this.buildHeaders(credentials);
|
||||
mergeUpstreamExtraHeaders(headers, upstreamExtraHeaders);
|
||||
const payload = this.transformRequest(model, body);
|
||||
|
||||
const id = `chatcmpl-oneminai-${randomUUID()}`;
|
||||
const created = Math.floor(Date.now() / 1000);
|
||||
|
||||
try {
|
||||
this.assertOutboundUrlAllowed(url);
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify(payload),
|
||||
signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
let message = `1min.ai API failed with status ${response.status}`;
|
||||
try {
|
||||
const parsed = asRecord(JSON.parse(errorText));
|
||||
const err = asRecord(parsed.error);
|
||||
if (typeof err.message === "string") message = err.message;
|
||||
} catch {
|
||||
if (errorText) message = errorText;
|
||||
}
|
||||
return {
|
||||
response: toOpenAiErrorResponse(response.status, message),
|
||||
url,
|
||||
headers,
|
||||
transformedBody: payload,
|
||||
};
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
if (!response.body) {
|
||||
return {
|
||||
response: toOpenAiErrorResponse(502, "1min.ai returned an empty stream"),
|
||||
url,
|
||||
headers,
|
||||
transformedBody: payload,
|
||||
};
|
||||
}
|
||||
return {
|
||||
response: new Response(translateSseStream(response.body, model, id, created), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
}),
|
||||
url,
|
||||
headers,
|
||||
transformedBody: payload,
|
||||
};
|
||||
}
|
||||
|
||||
const json = asRecord(await response.json());
|
||||
const aiRecord = asRecord(json.aiRecord);
|
||||
const detail = asRecord(aiRecord.aiRecordDetail);
|
||||
const resultObject = Array.isArray(detail.resultObject) ? detail.resultObject : [];
|
||||
const content = resultObject.filter((part): part is string => typeof part === "string").join("");
|
||||
|
||||
return {
|
||||
response: buildOpenAiJsonCompletion(content, model, id, created),
|
||||
url,
|
||||
headers,
|
||||
transformedBody: payload,
|
||||
};
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || "Unknown error");
|
||||
return {
|
||||
response: toOpenAiErrorResponse(502, `1min.ai fetch error: ${message}`),
|
||||
url,
|
||||
headers,
|
||||
transformedBody: payload,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default OneMinAiExecutor;
|
||||
@@ -704,8 +704,9 @@ export async function handleChatCore({
|
||||
const recordKeyHealthStatus = (
|
||||
status: number,
|
||||
creds: Record<string, unknown> | null | undefined,
|
||||
transport?: string
|
||||
): void => recordKeyHealthStatusFor(status, creds, log, transport);
|
||||
transport?: string,
|
||||
failureDetail?: string
|
||||
): void => recordKeyHealthStatusFor(status, creds, log, transport, failureDetail);
|
||||
// ── Phase 9.2: Idempotency check ──
|
||||
// Resolve the idempotency key once here and reuse it at the Phase 9.2 save site below,
|
||||
// rather than re-deriving it. (#3821-review LEDGER-6)
|
||||
@@ -3174,11 +3175,20 @@ export async function handleChatCore({
|
||||
});
|
||||
|
||||
if (
|
||||
res.response.status === 401 &&
|
||||
stream &&
|
||||
(res.response.ok ||
|
||||
res.response.status === HTTP_STATUS.UNAUTHORIZED ||
|
||||
res.response.status === HTTP_STATUS.FORBIDDEN) &&
|
||||
executionConnectionId &&
|
||||
!(await shouldIsolateProbeFailures())
|
||||
) {
|
||||
recordKeyHealthStatus(401, execCreds);
|
||||
const failureDetail = res.response.ok
|
||||
? ""
|
||||
: await res.response
|
||||
.clone()
|
||||
.text()
|
||||
.catch(() => "");
|
||||
recordKeyHealthStatus(res.response.status, execCreds, res.transport, failureDetail);
|
||||
}
|
||||
|
||||
if (isModelScope() && res.response.status === 429 && attempts < maxAttempts - 1) {
|
||||
@@ -3529,13 +3539,6 @@ export async function handleChatCore({
|
||||
// Non-stream: release semaphore immediately after reading full response body.
|
||||
const status = rawResult.response.status;
|
||||
|
||||
// Use execution credentials captured during request processing
|
||||
if (
|
||||
rawResult._executionCredentials?.connectionId &&
|
||||
rawResult._executionCredentials?.apiKey
|
||||
) {
|
||||
recordKeyHealthStatus(status, rawResult._executionCredentials, rawResult.transport);
|
||||
}
|
||||
releaseRawResultAccountSemaphore =
|
||||
typeof rawResult._accountSemaphoreRelease === "function"
|
||||
? rawResult._accountSemaphoreRelease
|
||||
@@ -3561,6 +3564,19 @@ export async function handleChatCore({
|
||||
contentType,
|
||||
upstreamStream
|
||||
);
|
||||
// Use the exact execution credential selected for this request. Model capability
|
||||
// failures stay in routing telemetry; authoritative success only recovers this key.
|
||||
if (
|
||||
rawResult._executionCredentials?.connectionId &&
|
||||
(rawResult._executionCredentials.apiKey || rawResult._executionCredentials.accessToken)
|
||||
) {
|
||||
recordKeyHealthStatus(
|
||||
status,
|
||||
rawResult._executionCredentials,
|
||||
rawResult.transport,
|
||||
status >= 400 ? payload : ""
|
||||
);
|
||||
}
|
||||
releaseRawResultAccountSemaphore();
|
||||
releaseRawResultAccountSemaphore = () => {};
|
||||
|
||||
@@ -4241,79 +4257,84 @@ export async function handleChatCore({
|
||||
`[provider] Node ${errorConnectionId} probe ${errorType} (${statusCode}) — connection stays active`
|
||||
);
|
||||
} else {
|
||||
// Kimi's 403 says "billing cycle" for both an exhausted subscription and a
|
||||
// temporary request window. Read its official usage endpoint before making
|
||||
// the connection terminal: a non-zero Weekly quota plus an empty Ratelimit
|
||||
// window must recover automatically at the reported reset time.
|
||||
let kimiRateLimitResetAt: string | null = null;
|
||||
if (provider === "kimi-coding") {
|
||||
try {
|
||||
const { fetchAndPersistProviderLimits } =
|
||||
await import("@/lib/usage/providerLimits");
|
||||
const { usage } = await fetchAndPersistProviderLimits(errorConnectionId, "manual");
|
||||
kimiRateLimitResetAt = getKimiTemporaryRateLimitResetAt(usage);
|
||||
} catch {
|
||||
// Preserve the existing quota handling when Kimi's usage endpoint is unavailable.
|
||||
// Kimi's 403 says "billing cycle" for both an exhausted subscription and a
|
||||
// temporary request window. Read its official usage endpoint before making
|
||||
// the connection terminal: a non-zero Weekly quota plus an empty Ratelimit
|
||||
// window must recover automatically at the reported reset time.
|
||||
let kimiRateLimitResetAt: string | null = null;
|
||||
if (provider === "kimi-coding") {
|
||||
try {
|
||||
const { fetchAndPersistProviderLimits } =
|
||||
await import("@/lib/usage/providerLimits");
|
||||
const { usage } = await fetchAndPersistProviderLimits(
|
||||
errorConnectionId,
|
||||
"manual"
|
||||
);
|
||||
kimiRateLimitResetAt = getKimiTemporaryRateLimitResetAt(usage);
|
||||
} catch {
|
||||
// Preserve the existing quota handling when Kimi's usage endpoint is unavailable.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Providers with per-model quotas — lock the model only, not the connection
|
||||
const quotaCooldownMs = kimiRateLimitResetAt
|
||||
? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0)
|
||||
: retryAfterMs || COOLDOWN_MS.rateLimit;
|
||||
const accountSemaphoreKey = resolveAccountSemaphoreKey({
|
||||
provider,
|
||||
model: currentModel,
|
||||
connectionId: errorConnectionId,
|
||||
credentials,
|
||||
});
|
||||
if (accountSemaphoreKey) {
|
||||
markAccountSemaphoreBlocked(accountSemaphoreKey, quotaCooldownMs);
|
||||
}
|
||||
if (kimiRateLimitResetAt) {
|
||||
await updateProviderConnection(errorConnectionId, {
|
||||
testStatus: "unavailable",
|
||||
rateLimitedUntil: kimiRateLimitResetAt,
|
||||
backoffLevel: 0,
|
||||
lastErrorType: PROVIDER_ERROR_TYPES.RATE_LIMITED,
|
||||
lastError: message,
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} Kimi request window exhausted (${statusCode}) — retrying after ${kimiRateLimitResetAt}`
|
||||
);
|
||||
} else if (isModelScope() && errorConnectionId) {
|
||||
const lockFn = provider === "antigravity" ? lockExactModel : lockModel;
|
||||
lockFn(provider, errorConnectionId, model, "quota_exhausted", quotaCooldownMs);
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} ModelScope model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (connection stays active)`
|
||||
);
|
||||
} else if (
|
||||
lockModelIfPerModelQuota(
|
||||
// Providers with per-model quotas — lock the model only, not the connection
|
||||
const quotaCooldownMs = kimiRateLimitResetAt
|
||||
? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0)
|
||||
: retryAfterMs || COOLDOWN_MS.rateLimit;
|
||||
const accountSemaphoreKey = resolveAccountSemaphoreKey({
|
||||
provider,
|
||||
errorConnectionId,
|
||||
model,
|
||||
"quota_exhausted",
|
||||
quotaCooldownMs
|
||||
)
|
||||
) {
|
||||
const quotaScope = getQuotaScopeLabelForProvider(provider, model);
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} ${quotaScope}-only quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (cooldown_scope=${quotaScope}, ttl_source=${retryAfterMs ? "upstream" : "inferred"}, connection stays active)`
|
||||
);
|
||||
} else {
|
||||
await writeTerminalStatus(
|
||||
errorConnectionId,
|
||||
{
|
||||
testStatus: "credits_exhausted",
|
||||
model: currentModel,
|
||||
connectionId: errorConnectionId,
|
||||
credentials,
|
||||
});
|
||||
if (accountSemaphoreKey) {
|
||||
markAccountSemaphoreBlocked(accountSemaphoreKey, quotaCooldownMs);
|
||||
}
|
||||
if (kimiRateLimitResetAt) {
|
||||
await updateProviderConnection(errorConnectionId, {
|
||||
testStatus: "unavailable",
|
||||
rateLimitedUntil: kimiRateLimitResetAt,
|
||||
backoffLevel: 0,
|
||||
lastErrorType: PROVIDER_ERROR_TYPES.RATE_LIMITED,
|
||||
lastError: message,
|
||||
lastErrorType: errorType,
|
||||
errorCode: String(statusCode),
|
||||
},
|
||||
"production"
|
||||
);
|
||||
console.warn(`[provider] Node ${errorConnectionId} exhausted quota (${statusCode})`);
|
||||
}
|
||||
errorCode: statusCode,
|
||||
});
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} Kimi request window exhausted (${statusCode}) — retrying after ${kimiRateLimitResetAt}`
|
||||
);
|
||||
} else if (isModelScope() && errorConnectionId) {
|
||||
const lockFn = provider === "antigravity" ? lockExactModel : lockModel;
|
||||
lockFn(provider, errorConnectionId, model, "quota_exhausted", quotaCooldownMs);
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} ModelScope model quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (connection stays active)`
|
||||
);
|
||||
} else if (
|
||||
lockModelIfPerModelQuota(
|
||||
provider,
|
||||
errorConnectionId,
|
||||
model,
|
||||
"quota_exhausted",
|
||||
quotaCooldownMs
|
||||
)
|
||||
) {
|
||||
const quotaScope = getQuotaScopeLabelForProvider(provider, model);
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} ${quotaScope}-only quota exhausted (${statusCode}) for ${model} - ${Math.ceil(quotaCooldownMs / 1000)}s (cooldown_scope=${quotaScope}, ttl_source=${retryAfterMs ? "upstream" : "inferred"}, connection stays active)`
|
||||
);
|
||||
} else {
|
||||
await writeTerminalStatus(
|
||||
errorConnectionId,
|
||||
{
|
||||
testStatus: "credits_exhausted",
|
||||
lastError: message,
|
||||
lastErrorType: errorType,
|
||||
errorCode: String(statusCode),
|
||||
},
|
||||
"production"
|
||||
);
|
||||
console.warn(
|
||||
`[provider] Node ${errorConnectionId} exhausted quota (${statusCode})`
|
||||
);
|
||||
}
|
||||
} // close probeIsolated3 else
|
||||
}
|
||||
} else if (errorType === PROVIDER_ERROR_TYPES.UNAUTHORIZED) {
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
* handleChatCore. Translates an upstream HTTP status into the in-memory key-health state
|
||||
* (apiKeyRotator) for the connection's currently-selected key, and persists the change to the
|
||||
* provider connection so it survives process restarts:
|
||||
* - 401 → record a failure (warning, then invalid at the threshold), always persisted.
|
||||
* - genuine 401/403 credential rejection → record a failure (warning, then invalid at the
|
||||
* threshold), always persisted.
|
||||
* - 402 → terminal (insufficient balance); mark the current key invalid immediately (#5239),
|
||||
* persisted on the active→invalid transition.
|
||||
* - 2xx → record a success, persisted only when recovering from a warning/invalid state.
|
||||
* Any other status only refreshes the tracked extra-key set. The handler binds its `log` once and
|
||||
* delegates here, keeping the existing call sites unchanged.
|
||||
* Model availability failures remain model/routing telemetry even when an upstream reports them
|
||||
* with 401/403. Any other status only refreshes the tracked extra-key set.
|
||||
*/
|
||||
|
||||
import {
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
trackConnectionExtraKeys,
|
||||
type KeyHealth,
|
||||
} from "../../services/apiKeyRotator.ts";
|
||||
import { isModelUnavailableError } from "../../services/modelFamilyFallback.ts";
|
||||
import { updateProviderConnection } from "@/lib/db/providers";
|
||||
|
||||
type KeyHealthLog = {
|
||||
@@ -28,11 +30,38 @@ type KeyHealthLog = {
|
||||
error?: (tag: string, message: string) => void;
|
||||
} | null;
|
||||
|
||||
const CREDENTIAL_FAILURE_PATTERNS = [
|
||||
/\b(?:invalid|incorrect|expired|missing|revoked)\s+api[\s_-]?key\b/i,
|
||||
/\bapi[\s_-]?key\s+(?:is\s+)?(?:invalid|incorrect|expired|missing|revoked|not\s+valid)\b/i,
|
||||
/\bauthentication[\s_-]+(?:failed|error|required)\b/i,
|
||||
/\b(?:invalid|expired|missing|revoked)\s+(?:token|credentials?|bearer)\b/i,
|
||||
/\bunauthorized\b/i,
|
||||
/\bnot\s+authenticated\b/i,
|
||||
/\bforbidden\b/i,
|
||||
/\baccess\s+denied\b/i,
|
||||
];
|
||||
|
||||
function isModelCapabilityFailure(status: number, failureDetail: string): boolean {
|
||||
if (!failureDetail) return false;
|
||||
const normalizedDetail = failureDetail.replace(/[_-]+/g, " ");
|
||||
// Model-family fallback already owns these phrases. Use a model-capable status for
|
||||
// classification because some aggregators misreport the same model rejection as 401.
|
||||
return isModelUnavailableError(status === 401 ? 403 : status, normalizedDetail);
|
||||
}
|
||||
|
||||
function isCredentialFailure(status: number, failureDetail: string): boolean {
|
||||
if (status !== 401 && status !== 403) return false;
|
||||
if (isModelCapabilityFailure(status, failureDetail)) return false;
|
||||
if (status === 401) return true;
|
||||
return CREDENTIAL_FAILURE_PATTERNS.some((pattern) => pattern.test(failureDetail));
|
||||
}
|
||||
|
||||
export function recordKeyHealthStatus(
|
||||
status: number,
|
||||
creds: Record<string, unknown> | null | undefined,
|
||||
log?: KeyHealthLog,
|
||||
transport?: string
|
||||
transport?: string,
|
||||
failureDetail = ""
|
||||
): void {
|
||||
// CLIProxyAPI owns a shared external credential pool. Its auth failures cannot be
|
||||
// attributed to the native OmniRoute connection selected before proxy dispatch.
|
||||
@@ -55,11 +84,11 @@ export function recordKeyHealthStatus(
|
||||
|
||||
trackConnectionExtraKeys(connId, extraKeys);
|
||||
|
||||
if (status === 401) {
|
||||
if (isCredentialFailure(status, failureDetail)) {
|
||||
const updatedHealth = recordKeyFailure(connId, currentKeyId);
|
||||
log?.warn?.(
|
||||
"AUTH",
|
||||
`401 on connection ${connId.slice(0, 8)} - key marked as failed (failure #${updatedHealth.failures})`
|
||||
`${status} on connection ${connId.slice(0, 8)} - key marked as failed (failure #${updatedHealth.failures})`
|
||||
);
|
||||
|
||||
// Persist health status to DB on every failure (not just invalid transitions)
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
isUnconfiguredLoopbackSearchProvider,
|
||||
type SearchProviderConfig,
|
||||
} from "../config/searchRegistry.ts";
|
||||
import { NIMBLE_CLIENT_SOURCE, NIMBLE_CLIENT_SOURCE_HEADER } from "../config/nimble.ts";
|
||||
import { buildPerplexityRequest, parsePerplexitySearchOptions } from "./search/perplexitySearch.ts";
|
||||
import * as fcSearch from "./search/firecrawlSearch.ts";
|
||||
import { type FirecrawlSearchEnvelope } from "./search/firecrawlSearch.ts";
|
||||
@@ -475,6 +476,38 @@ function buildTavilyRequest(
|
||||
};
|
||||
}
|
||||
|
||||
function buildNimbleRequest(
|
||||
config: SearchProviderConfig,
|
||||
params: SearchRequestParams
|
||||
): { url: string; init: RequestInit } {
|
||||
if (!params.token) throw new Error("Nimble Search requires an API key");
|
||||
const { includes, excludes } = parseDomainFilter(params.domainFilter);
|
||||
const body: Record<string, unknown> = {
|
||||
query: params.query,
|
||||
max_results: Math.min(params.maxResults, config.maxMaxResults),
|
||||
search_depth: "lite",
|
||||
output_format: "plain_text",
|
||||
focus: params.searchType === "news" ? "news" : "general",
|
||||
};
|
||||
if (params.country) body.country = params.country.toUpperCase();
|
||||
if (params.language) body.locale = params.language;
|
||||
if (params.timeRange && params.timeRange !== "any") body.time_range = params.timeRange;
|
||||
if (includes.length) body.include_domains = includes.slice(0, 50);
|
||||
if (excludes.length) body.exclude_domains = excludes.slice(0, 50);
|
||||
return {
|
||||
url: resolveSearchBaseUrl(config, params),
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${params.token}`,
|
||||
[NIMBLE_CLIENT_SOURCE_HEADER]: NIMBLE_CLIENT_SOURCE,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildGooglePseRequest(
|
||||
config: SearchProviderConfig,
|
||||
params: SearchRequestParams
|
||||
@@ -707,6 +740,7 @@ const requestBuilders: Record<string, SearchRequestBuilder> = {
|
||||
"perplexity-search": buildPerplexityRequest,
|
||||
"exa-search": buildExaRequest,
|
||||
"tavily-search": buildTavilyRequest,
|
||||
"nimble-search": buildNimbleRequest,
|
||||
firecrawl: fcSearch.buildFirecrawlSearchRequest,
|
||||
"google-pse-search": buildGooglePseRequest,
|
||||
"linkup-search": buildLinkupRequest,
|
||||
@@ -828,6 +862,47 @@ function normalizeTavilyResponse(
|
||||
return { results, totalResults: results.length };
|
||||
}
|
||||
|
||||
interface NimbleSearchItem {
|
||||
title?: string;
|
||||
url?: string;
|
||||
description?: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
interface NimbleSearchEnvelope {
|
||||
results?: NimbleSearchItem[];
|
||||
total_results?: number;
|
||||
}
|
||||
|
||||
function normalizeNimbleResponse(
|
||||
data: unknown,
|
||||
_query: string,
|
||||
_searchType: string
|
||||
): { results: SearchResult[]; totalResults: number | null } {
|
||||
const now = new Date().toISOString();
|
||||
const envelope = (data ?? {}) as NimbleSearchEnvelope;
|
||||
if (!Array.isArray(envelope.results)) return { results: [], totalResults: null };
|
||||
const results = envelope.results.map((item, idx) =>
|
||||
makeResult(
|
||||
"nimble-search",
|
||||
{
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
snippet: item.description || item.content?.slice(0, 300) || "",
|
||||
full_text: item.content || undefined,
|
||||
text_format: "text",
|
||||
},
|
||||
idx,
|
||||
now
|
||||
)
|
||||
);
|
||||
return {
|
||||
results,
|
||||
totalResults:
|
||||
typeof envelope.total_results === "number" ? envelope.total_results : results.length,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeGooglePseResponse(
|
||||
data: any,
|
||||
_query: string,
|
||||
@@ -1283,6 +1358,7 @@ const responseNormalizers: Record<string, SearchResponseNormalizer> = {
|
||||
"perplexity-search": normalizePerplexityResponse,
|
||||
"exa-search": normalizeExaResponse,
|
||||
"tavily-search": normalizeTavilyResponse,
|
||||
"nimble-search": normalizeNimbleResponse,
|
||||
firecrawl: (data: FirecrawlSearchEnvelope, _query: string, searchType: string) =>
|
||||
fcSearch.normalizeFirecrawlSearchResponse(data, searchType, makeResult),
|
||||
"google-pse-search": normalizeGooglePseResponse,
|
||||
|
||||
@@ -21,12 +21,14 @@ import { firecrawlFetch } from "../executors/firecrawl-fetch.ts";
|
||||
import { jinaReaderFetch } from "../executors/jina-reader-fetch.ts";
|
||||
import { tavilyFetch } from "../executors/tavily-fetch.ts";
|
||||
import { tinyfishFetch } from "../executors/tinyfish-fetch.ts";
|
||||
import { nimbleFetch } from "../executors/nimble-fetch.ts";
|
||||
|
||||
export type WebFetchFormat = "markdown" | "html" | "links" | "screenshot";
|
||||
|
||||
export interface WebFetchRequest {
|
||||
url: string;
|
||||
provider?: "firecrawl" | "jina-reader" | "tavily-search" | "tinyfish" | "context7";
|
||||
provider?:
|
||||
"firecrawl" | "jina-reader" | "tavily-search" | "tinyfish" | "context7" | "nimble-search";
|
||||
format?: WebFetchFormat;
|
||||
depth?: 0 | 1 | 2;
|
||||
wait_for_selector?: string;
|
||||
@@ -61,6 +63,7 @@ export const WEB_FETCH_PROVIDERS = Object.freeze([
|
||||
"tavily-search",
|
||||
"tinyfish",
|
||||
"context7",
|
||||
"nimble-search",
|
||||
] as const);
|
||||
// Derived from the array — adding a provider to WEB_FETCH_PROVIDERS
|
||||
// automatically widens the union; they cannot drift apart.
|
||||
@@ -139,6 +142,14 @@ export async function handleWebFetch(
|
||||
credentials,
|
||||
});
|
||||
|
||||
case "nimble-search":
|
||||
return await nimbleFetch({
|
||||
url: req.url,
|
||||
format,
|
||||
includeMetadata,
|
||||
credentials,
|
||||
});
|
||||
|
||||
case "context7":
|
||||
// Context7 returns llms.txt text only: html/links/screenshot formats are
|
||||
// unsupported, and the format field is validated/ignored below.
|
||||
|
||||
@@ -557,7 +557,7 @@ export const webFetchInput = z.object({
|
||||
.min(1, "URL is required")
|
||||
.describe("The URL to fetch content from"),
|
||||
provider: z
|
||||
.enum(["firecrawl", "jina-reader", "tavily-search", "tinyfish", "context7"])
|
||||
.enum(["firecrawl", "jina-reader", "tavily-search", "tinyfish", "context7", "nimble-search"])
|
||||
.optional()
|
||||
.describe(
|
||||
"Specific fetch provider to use (default: first available). " +
|
||||
|
||||
@@ -694,7 +694,8 @@ async function handleXSearch(args: {
|
||||
|
||||
async function handleWebFetch(args: {
|
||||
url: string;
|
||||
provider?: "firecrawl" | "jina-reader" | "tavily-search" | "tinyfish" | "context7";
|
||||
provider?:
|
||||
"firecrawl" | "jina-reader" | "tavily-search" | "tinyfish" | "context7" | "nimble-search";
|
||||
format?: "markdown" | "html" | "links" | "screenshot";
|
||||
include_metadata?: boolean;
|
||||
depth?: number;
|
||||
|
||||
@@ -30,6 +30,16 @@ const BOOTSTRAP_TIMEOUT_MS = 8_000;
|
||||
const ONBOARD_TIMEOUT_MS = 15_000;
|
||||
const DEFAULT_TIER_ID = "legacy-tier";
|
||||
|
||||
// onboardUser is a Long-Running Operation: Google frequently answers the
|
||||
// first call with {"done": false} (no cloudaicompanionProject field yet) and
|
||||
// expects the SAME request re-sent every couple of seconds until the
|
||||
// operation settles with {"done": true, response: {...}}. Treating the
|
||||
// first "done:false" response as "no project" (BYOP) misclassifies a normal
|
||||
// in-progress onboarding as "bring your own project" and permanently caches
|
||||
// that wrong verdict. Poll bounded, matching 9router's onboardUser().
|
||||
const ONBOARD_POLL_MAX_ATTEMPTS = 5;
|
||||
const ONBOARD_POLL_INTERVAL_MS = 2_000;
|
||||
|
||||
/** Ordered list of loadCodeAssist endpoint URLs. */
|
||||
export function getAntigravityLoadCodeAssistUrls(): string[] {
|
||||
return ANTIGRAVITY_BOOTSTRAP_BASE_URLS.map((base) => `${base}${LOAD_CODE_ASSIST_PATH}`);
|
||||
@@ -162,10 +172,31 @@ async function tryLoadCodeAssist(
|
||||
return { projectId: null, tierId: DEFAULT_TIER_ID };
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the project id from a settled ({done:true}) onboardUser response body.
|
||||
* The documented LRO shape nests it under `response.cloudaicompanionProject`
|
||||
* (matches 9router's onboardUser and Google's Operation envelope), but some
|
||||
* observed responses put it at the top level — check both.
|
||||
*/
|
||||
function extractProjectIdFromOnboardResponse(data: Record<string, unknown> | null): string | null {
|
||||
const nested = (data?.response as Record<string, unknown> | undefined)?.cloudaicompanionProject;
|
||||
const project = nested ?? data?.cloudaicompanionProject;
|
||||
if (typeof project === "string") {
|
||||
const id = project.trim();
|
||||
return id || null;
|
||||
}
|
||||
if (project && typeof project === "object") {
|
||||
const id = (project as Record<string, unknown>).id;
|
||||
if (typeof id === "string" && id.trim()) return id.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt onboardUser to create a Cloud Code project for the account.
|
||||
* Called when loadCodeAssist returns no project — the account has never
|
||||
* been onboarded. Returns true if any endpoint reports success.
|
||||
* been onboarded. Polls the same endpoint on {done:false} responses (an
|
||||
* in-progress LRO) before concluding anything about the account.
|
||||
*/
|
||||
async function tryOnboardUser(
|
||||
accessToken: string,
|
||||
@@ -176,47 +207,74 @@ async function tryOnboardUser(
|
||||
): Promise<AntigravityOnboardStatus> {
|
||||
const urls = getAntigravityOnboardUrls();
|
||||
const headers = getAntigravityContentHeaders(clientProfile, accessToken);
|
||||
const body = JSON.stringify({
|
||||
tier_id: tierId,
|
||||
metadata: getAntigravityLoadCodeAssistMetadata(),
|
||||
});
|
||||
|
||||
for (const url of urls) {
|
||||
if (signal?.aborted) throw signal.reason;
|
||||
try {
|
||||
const timeoutSignal = AbortSignal.timeout(ONBOARD_TIMEOUT_MS);
|
||||
const response = await fetchImpl(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
tier_id: tierId,
|
||||
metadata: getAntigravityLoadCodeAssistMetadata(),
|
||||
}),
|
||||
signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
|
||||
});
|
||||
for (let attempt = 1; attempt <= ONBOARD_POLL_MAX_ATTEMPTS; attempt++) {
|
||||
if (signal?.aborted) throw signal.reason;
|
||||
try {
|
||||
const timeoutSignal = AbortSignal.timeout(ONBOARD_TIMEOUT_MS);
|
||||
const response = await fetchImpl(url, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body,
|
||||
signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Accounts Google expects to Bring Their Own Project: onboardUser
|
||||
// returns 200 without a `cloudaicompanionProject` in the body — no
|
||||
// automatic project creation for standard-tier/personal accounts
|
||||
// (tracked in #8491). Detect that so we can fail fast with a clear
|
||||
// instruction instead of retrying forever or fabricating an id that
|
||||
// Google later rejects with a delayed 429 RESOURCE_EXHAUSTED.
|
||||
const body = await response.text().catch(() => "");
|
||||
if (body && !/cloudaicompanionProject/.test(body)) {
|
||||
if (!response.ok) {
|
||||
console.warn(
|
||||
`[models] antigravity onboardUser done but no project in response at ${url} — Google BYOP (user-defined GCP project) required`
|
||||
`[models] antigravity onboardUser failed at ${url} (${response.status}) — trying next`
|
||||
);
|
||||
return "requires_manual_project";
|
||||
break;
|
||||
}
|
||||
return "onboarded";
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`[models] antigravity onboardUser failed at ${url} (${response.status}) — trying next`
|
||||
);
|
||||
} catch (error) {
|
||||
if (signal?.aborted || (error instanceof Error && error.name === "AbortError")) {
|
||||
throw signal?.reason ?? error;
|
||||
const data = (await response.json().catch(() => null)) as Record<string, unknown> | null;
|
||||
|
||||
// Only an EXPLICIT `done: false` means "in-progress LRO, poll again".
|
||||
// A proper Google Operation always carries `done` when it is one; a
|
||||
// response with `done` absent entirely (e.g. `{}`) is not an LRO in
|
||||
// progress — it's Google's immediate, settled "no project" answer for
|
||||
// BYOP accounts (#8491) and must fall through to that classification
|
||||
// on the first attempt, same as before this polling was added.
|
||||
if (data?.done === false) {
|
||||
// In-progress LRO — Google hasn't decided (project created, or
|
||||
// BYOP required) yet. Re-send the same request after a short wait.
|
||||
if (attempt < ONBOARD_POLL_MAX_ATTEMPTS) {
|
||||
console.warn(
|
||||
`[models] antigravity onboardUser at ${url} not done yet (attempt ${attempt}/${ONBOARD_POLL_MAX_ATTEMPTS}) — waiting`
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, ONBOARD_POLL_INTERVAL_MS));
|
||||
continue;
|
||||
}
|
||||
console.warn(
|
||||
`[models] antigravity onboardUser at ${url} still not done after ${ONBOARD_POLL_MAX_ATTEMPTS} attempts — treating as failed`
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// done:true — Google has settled the operation. Accounts Google
|
||||
// expects to Bring Their Own Project answer with done:true and no
|
||||
// cloudaicompanionProject — no automatic project creation for
|
||||
// standard-tier/personal accounts (tracked in #8491). Only now is it
|
||||
// safe to draw that conclusion.
|
||||
if (extractProjectIdFromOnboardResponse(data)) {
|
||||
return "onboarded";
|
||||
}
|
||||
console.warn(
|
||||
`[models] antigravity onboardUser done but no project in response at ${url} — Google BYOP (user-defined GCP project) required`
|
||||
);
|
||||
return "requires_manual_project";
|
||||
} catch (error) {
|
||||
if (signal?.aborted || (error instanceof Error && error.name === "AbortError")) {
|
||||
throw signal?.reason ?? error;
|
||||
}
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`[models] antigravity onboardUser threw for ${url}: ${msg} — trying next`);
|
||||
break;
|
||||
}
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
console.warn(`[models] antigravity onboardUser threw for ${url}: ${msg} — trying next`);
|
||||
}
|
||||
}
|
||||
return "failed";
|
||||
|
||||
@@ -31,7 +31,10 @@ const MAX_CONNECTION_EXTRA_KEYS = 500;
|
||||
*/
|
||||
export function trackConnectionExtraKeys(connectionId: string, extraKeys: string[]): void {
|
||||
const validExtras = extraKeys.filter((k) => typeof k === "string" && k.trim().length > 0);
|
||||
if (!_connectionExtraKeys.has(connectionId) && _connectionExtraKeys.size >= MAX_CONNECTION_EXTRA_KEYS) {
|
||||
if (
|
||||
!_connectionExtraKeys.has(connectionId) &&
|
||||
_connectionExtraKeys.size >= MAX_CONNECTION_EXTRA_KEYS
|
||||
) {
|
||||
const oldest = _connectionExtraKeys.keys().next().value;
|
||||
if (oldest !== undefined) _connectionExtraKeys.delete(oldest);
|
||||
}
|
||||
@@ -308,6 +311,29 @@ export function syncHealthFromDB(connectionId: string, health?: Record<string, K
|
||||
}
|
||||
}
|
||||
|
||||
/** Recover one authoritatively validated key without changing sibling-key health. */
|
||||
export function recoverKeyHealth(
|
||||
connectionId: string,
|
||||
keyId: string,
|
||||
providerSpecificData: unknown
|
||||
): Record<string, unknown> | undefined {
|
||||
const data =
|
||||
providerSpecificData && typeof providerSpecificData === "object"
|
||||
? (providerSpecificData as Record<string, unknown>)
|
||||
: {};
|
||||
const health = data.apiKeyHealth as Record<string, KeyHealth> | undefined;
|
||||
const currentHealth = health?.[keyId];
|
||||
if (!currentHealth || (currentHealth.status === "active" && currentHealth.failures === 0)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
syncHealthFromDB(connectionId, health);
|
||||
return {
|
||||
...data,
|
||||
apiKeyHealth: { ...health, [keyId]: recordKeySuccess(connectionId, keyId) },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the rotation index for a connection.
|
||||
* Call this when a key fails (401/403) to skip the bad key next time.
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
* 2b. Layers 1-2 retried against the base model this id inherits quality scores
|
||||
* from, when `resolveScoresAs` resolves one (#11489). Reported as
|
||||
* `<source>:inherited`.
|
||||
* 2c. If the request id or its scoresAs base is vendor-retired (#11625), layers
|
||||
* 1–3 are skipped so a leftover arena row cannot short-circuit the layer-3
|
||||
* veto, and a dead *codex id cannot keep the coding wildcard boost.
|
||||
* 3. Models.dev tier — derived from `model_capabilities` table capability data,
|
||||
* with a vendor-lifecycle veto (#11508: a retired id never earns a tier
|
||||
* score) and the same base-model inheritance as layers 1–2
|
||||
@@ -43,6 +46,7 @@ import {
|
||||
} from "../../../src/lib/db/modelIntelligence.ts";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolveScoresAs } from "./scoresAs.ts";
|
||||
import { isVendorRetiredId } from "../modelLifecycle.ts";
|
||||
|
||||
// #11508 — vendor lifecycle snapshot (#11507). An id the vendor has retired
|
||||
// must never earn a capability-derived tier score: models.dev keeps listing
|
||||
@@ -463,48 +467,60 @@ export function getTaskFitness(model: string, taskType: string): number {
|
||||
return getTaskFitnessWithSource(model, taskType).score;
|
||||
}
|
||||
|
||||
function isFitnessRetired(modelId: string): boolean {
|
||||
if (isVendorRetiredId(modelId)) return true;
|
||||
const { base, via } = resolveScoresAs(modelId);
|
||||
return via !== null && isVendorRetiredId(base);
|
||||
}
|
||||
|
||||
export function getTaskFitnessWithSource(
|
||||
model: string,
|
||||
taskType: string
|
||||
): { score: number; source: string } {
|
||||
const normalizedModel = model.toLowerCase();
|
||||
const normalizedTask = taskType.toLowerCase();
|
||||
const fitnessRetired = isFitnessRetired(normalizedModel);
|
||||
|
||||
const userOverride = queryModelIntelligence(normalizedModel, normalizedTask, "user_override");
|
||||
if (userOverride !== null) {
|
||||
return { score: userOverride, source: "user_override" };
|
||||
if (!fitnessRetired) {
|
||||
const userOverride = queryModelIntelligence(normalizedModel, normalizedTask, "user_override");
|
||||
if (userOverride !== null) {
|
||||
return { score: userOverride, source: "user_override" };
|
||||
}
|
||||
|
||||
const arenaElo = queryModelIntelligence(normalizedModel, normalizedTask, "arena_elo");
|
||||
if (arenaElo !== null) {
|
||||
return { score: arenaElo, source: "arena_elo" };
|
||||
}
|
||||
|
||||
// Layers 1-2, retried against the base model this id inherits quality from
|
||||
// (#11489). Every DB-backed source publishes scores for BASE models only, so
|
||||
// a variant id — an effort suffix (`gpt-5.6-sol-xhigh`), a vendor alias
|
||||
// (`gpt-5.6`), a `-free` tier marker (`mimo-v2.5-free`, #4517) — misses both
|
||||
// literal lookups and used to fall all the way to the wildcard 0.5, losing
|
||||
// every comparison against a base model that happens to be benchmarked.
|
||||
// The score is inherited VERBATIM: the 12-factor scoring already prices cost
|
||||
// and latency per variant, so there is no basis for inventing an effort
|
||||
// delta. `:inherited` keeps the indirection visible to callers.
|
||||
const inherited = lookupInheritedFitness(normalizedModel, normalizedTask);
|
||||
if (inherited !== null) {
|
||||
return inherited;
|
||||
}
|
||||
|
||||
const tierScore = getModelsDevTierFitness(normalizedModel, normalizedTask);
|
||||
if (tierScore !== null) {
|
||||
return { score: tierScore, source: "models_dev_tier" };
|
||||
}
|
||||
|
||||
const staticScore = lookupStaticFitnessTable(normalizedModel, normalizedTask);
|
||||
if (staticScore !== null) {
|
||||
return { score: staticScore, source: "fitness_table" };
|
||||
}
|
||||
|
||||
return { score: lookupWildcardBoosts(normalizedModel, normalizedTask), source: "wildcard_boost" };
|
||||
}
|
||||
|
||||
const arenaElo = queryModelIntelligence(normalizedModel, normalizedTask, "arena_elo");
|
||||
if (arenaElo !== null) {
|
||||
return { score: arenaElo, source: "arena_elo" };
|
||||
}
|
||||
|
||||
// Layers 1-2, retried against the base model this id inherits quality from
|
||||
// (#11489). Every DB-backed source publishes scores for BASE models only, so
|
||||
// a variant id — an effort suffix (`gpt-5.6-sol-xhigh`), a vendor alias
|
||||
// (`gpt-5.6`), a `-free` tier marker (`mimo-v2.5-free`, #4517) — misses both
|
||||
// literal lookups and used to fall all the way to the wildcard 0.5, losing
|
||||
// every comparison against a base model that happens to be benchmarked.
|
||||
// The score is inherited VERBATIM: the 12-factor scoring already prices cost
|
||||
// and latency per variant, so there is no basis for inventing an effort
|
||||
// delta. `:inherited` keeps the indirection visible to callers.
|
||||
const inherited = lookupInheritedFitness(normalizedModel, normalizedTask);
|
||||
if (inherited !== null) {
|
||||
return inherited;
|
||||
}
|
||||
|
||||
const tierHit = getModelsDevTierFitnessWithSource(normalizedModel, normalizedTask);
|
||||
if (tierHit !== null) {
|
||||
return tierHit;
|
||||
}
|
||||
|
||||
const staticScore = lookupStaticFitnessTable(normalizedModel, normalizedTask);
|
||||
if (staticScore !== null) {
|
||||
return { score: staticScore, source: "fitness_table" };
|
||||
}
|
||||
|
||||
return { score: lookupWildcardBoosts(normalizedModel, normalizedTask), source: "wildcard_boost" };
|
||||
// Retired: 0.5 is "no evidence", never a quality claim and never a *codex boost.
|
||||
return { score: 0.5, source: "wildcard_boost" };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -524,6 +540,7 @@ function lookupInheritedFitness(
|
||||
const { base, via } = resolveScoresAs(normalizedModel);
|
||||
if (via === null || base === normalizedModel) return null;
|
||||
const normalizedBase = base.toLowerCase();
|
||||
if (isVendorRetiredId(normalizedBase)) return null;
|
||||
|
||||
for (const source of ["user_override", "arena_elo"] as const) {
|
||||
const score = queryModelIntelligence(normalizedBase, normalizedTask, source);
|
||||
|
||||
@@ -70,6 +70,7 @@ import { resolveProviderId } from "../../src/shared/constants/providers.ts";
|
||||
import * as semaphore from "./rateLimitSemaphore.ts";
|
||||
import { getCircuitBreaker } from "../../src/shared/utils/circuitBreaker";
|
||||
import { parseModel } from "./model.ts";
|
||||
import { rejectRetiredAutoComboCandidates } from "./modelLifecycle.ts";
|
||||
import { createComboContext } from "./combo/context.ts";
|
||||
import { phaseComboSetup } from "./combo/comboSetup.ts";
|
||||
import { checkCredentialGate, logCredentialSkip } from "./credentialGate.ts";
|
||||
@@ -627,11 +628,14 @@ export async function buildAutoCandidates(
|
||||
})
|
||||
);
|
||||
|
||||
// Filter out candidates whose model is hidden by the user in the dashboard
|
||||
return candidates.filter((c) => {
|
||||
const hiddenModels = hiddenModelsMap.get(c.provider);
|
||||
return !hiddenModels?.has(c.model);
|
||||
});
|
||||
// Filter out candidates whose model is hidden by the user in the dashboard,
|
||||
// then drop vendor-retired ids so auto-combo cannot pick them (#11625).
|
||||
return rejectRetiredAutoComboCandidates(
|
||||
candidates.filter((c) => {
|
||||
const hiddenModels = hiddenModelsMap.get(c.provider);
|
||||
return !hiddenModels?.has(c.model);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Context-cache pin health gate — moved to combo/dispatchPrelude.ts alongside the
|
||||
|
||||
@@ -230,7 +230,10 @@ function formatMessagesForPrompt(messages: MessageLike[]): string {
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
export function selectMessagesForSummary(messages: MessageLike[], maxMessages: number): MessageLike[] {
|
||||
export function selectMessagesForSummary(
|
||||
messages: MessageLike[],
|
||||
maxMessages: number
|
||||
): MessageLike[] {
|
||||
const validMessages = messages.filter((m) => m && typeof m === "object");
|
||||
const system = validMessages.filter(
|
||||
(m) => typeof m.role === "string" && (m.role === "system" || m.role === "developer")
|
||||
@@ -626,6 +629,15 @@ async function generateUniversalHandoffAsync(options: {
|
||||
|
||||
const summaryPrompt = HANDOFF_PROMPT_TEMPLATE.replace("{HISTORY}", historyText);
|
||||
const summaryModel = options.handoffModel || options.currModel;
|
||||
|
||||
if (options.providerAllowlist.length > 0) {
|
||||
const slashIdx = summaryModel.indexOf("/");
|
||||
const modelProvider = slashIdx > 0 ? summaryModel.slice(0, slashIdx) : "";
|
||||
if (modelProvider && !options.providerAllowlist.includes(modelProvider)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const summaryBody: Record<string, unknown> = {
|
||||
model: summaryModel,
|
||||
messages: [{ role: "user", content: summaryPrompt }],
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
* Replacement model IDs are migration guidance only. This module never rewrites a
|
||||
* request: shutdown models are rejected, deprecated models remain callable until
|
||||
* their shutdown date, and untracked models pass through unchanged.
|
||||
*
|
||||
* Dated OpenAI rows in MODEL_LIFECYCLE_RECORDS stay provider-scoped (a warn-before-
|
||||
* shutdown date on `openai` must not leak onto an aggregator that still serves the
|
||||
* id). Snapshot `status: "retired"` ids from config/quality/model-lifecycle.json
|
||||
* are id-scoped and prefix-stripped (#11625): `openai/gpt-5.2-codex` on openrouter
|
||||
* is the same retired vendor id as `gpt-5.2-codex`.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
export const OPENAI_MODEL_DEPRECATIONS_URL = "https://developers.openai.com/api/docs/deprecations";
|
||||
|
||||
@@ -128,6 +135,67 @@ function shutdownTimestamp(shutdownAt: string): number {
|
||||
return Date.parse(`${shutdownAt}T00:00:00.000Z`);
|
||||
}
|
||||
|
||||
const SNAPSHOT_URL = new URL("../../config/quality/model-lifecycle.json", import.meta.url);
|
||||
const SNAPSHOT_SOURCE = "config/quality/model-lifecycle.json";
|
||||
|
||||
type VendorRetiredEntry = {
|
||||
vendor?: string;
|
||||
status?: string;
|
||||
retiredOn?: string | null;
|
||||
replacement?: string | null;
|
||||
};
|
||||
|
||||
let _retiredIds: Set<string> | null = null;
|
||||
let _retiredEntries: Map<string, VendorRetiredEntry> | null = null;
|
||||
|
||||
function loadVendorRetiredSnapshot(): {
|
||||
ids: Set<string>;
|
||||
entries: Map<string, VendorRetiredEntry>;
|
||||
} {
|
||||
if (_retiredIds && _retiredEntries) return { ids: _retiredIds, entries: _retiredEntries };
|
||||
const ids = new Set<string>();
|
||||
const entries = new Map<string, VendorRetiredEntry>();
|
||||
try {
|
||||
const parsed = JSON.parse(readFileSync(SNAPSHOT_URL, "utf8")) as {
|
||||
retired?: Record<string, VendorRetiredEntry>;
|
||||
};
|
||||
for (const [id, entry] of Object.entries(parsed.retired ?? {})) {
|
||||
if (entry?.status !== "retired") continue;
|
||||
const key = id.toLowerCase();
|
||||
ids.add(key);
|
||||
entries.set(key, entry);
|
||||
}
|
||||
} catch {
|
||||
// Snapshot missing → no id-scoped veto. Dated OpenAI rows still apply.
|
||||
}
|
||||
_retiredIds = ids;
|
||||
_retiredEntries = entries;
|
||||
return { ids, entries };
|
||||
}
|
||||
|
||||
/** True when `modelId` or its last `vendor/` path segment is `status: "retired"` in the snapshot. */
|
||||
export function isVendorRetiredId(modelId: string | null | undefined): boolean {
|
||||
if (typeof modelId !== "string" || modelId.length === 0) return false;
|
||||
const lower = modelId.toLowerCase();
|
||||
const { ids } = loadVendorRetiredSnapshot();
|
||||
if (ids.has(lower)) return true;
|
||||
const slash = lower.lastIndexOf("/");
|
||||
return slash !== -1 && ids.has(lower.slice(slash + 1));
|
||||
}
|
||||
|
||||
function lookupVendorRetiredEntry(modelId: string): VendorRetiredEntry | null {
|
||||
const lower = modelId.toLowerCase();
|
||||
const { entries } = loadVendorRetiredSnapshot();
|
||||
return entries.get(lower) ?? entries.get(lower.slice(lower.lastIndexOf("/") + 1)) ?? null;
|
||||
}
|
||||
|
||||
/** Drop auto-combo candidates whose model id the vendor has retired (#11625). */
|
||||
export function rejectRetiredAutoComboCandidates<T extends { model: string }>(
|
||||
candidates: readonly T[]
|
||||
): T[] {
|
||||
return candidates.filter((candidate) => !isVendorRetiredId(candidate.model));
|
||||
}
|
||||
|
||||
export function getModelLifecycleDecision(
|
||||
provider: string | null | undefined,
|
||||
model: string | null | undefined,
|
||||
@@ -138,6 +206,24 @@ export function getModelLifecycleDecision(
|
||||
const record = RECORDS_BY_KEY.get(lifecycleKey(normalizedProvider, normalizedModel));
|
||||
|
||||
if (!record) {
|
||||
if (isVendorRetiredId(normalizedModel)) {
|
||||
const entry = lookupVendorRetiredEntry(normalizedModel);
|
||||
const replacementId =
|
||||
typeof entry?.replacement === "string" && entry.replacement.length > 0
|
||||
? entry.replacement
|
||||
: null;
|
||||
return {
|
||||
provider: normalizedProvider,
|
||||
model: normalizedModel,
|
||||
status: "shutdown",
|
||||
action: "reject",
|
||||
shutdownAt: typeof entry?.retiredOn === "string" ? entry.retiredOn : null,
|
||||
replacement: replacementId
|
||||
? { provider: entry?.vendor ?? "", model: replacementId }
|
||||
: null,
|
||||
source: SNAPSHOT_SOURCE,
|
||||
};
|
||||
}
|
||||
return {
|
||||
provider: normalizedProvider,
|
||||
model: normalizedModel,
|
||||
@@ -170,7 +256,8 @@ export function formatModelLifecycleMessage(decision: ModelLifecycleDecision): s
|
||||
? ` Use "${decision.replacement.provider}/${decision.replacement.model}" instead.`
|
||||
: "";
|
||||
if (decision.status === "shutdown") {
|
||||
return `Model "${modelRef}" was shut down on ${decision.shutdownAt} and cannot be routed automatically.${replacement}`;
|
||||
const when = decision.shutdownAt ? ` was shut down on ${decision.shutdownAt}` : " has been retired by its vendor";
|
||||
return `Model "${modelRef}"${when} and cannot be routed automatically.${replacement}`;
|
||||
}
|
||||
return `Model "${modelRef}" is deprecated and is scheduled to shut down on ${decision.shutdownAt}.${replacement}`;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { register } from "../registry.ts";
|
||||
import { FORMATS } from "../formats.ts";
|
||||
import {
|
||||
DEFAULT_SAFETY_SETTINGS,
|
||||
tryParseJSON,
|
||||
cleanJSONSchemaForAntigravity,
|
||||
} from "../helpers/geminiHelper.ts";
|
||||
import { buildGeminiTools, sanitizeGeminiToolName } from "../helpers/geminiToolsSanitizer.ts";
|
||||
@@ -186,13 +185,6 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) {
|
||||
.map((c) => (c.type === "text" ? c.text : JSON.stringify(c)))
|
||||
.join("\n");
|
||||
}
|
||||
let parsedContent = tryParseJSON(content);
|
||||
if (parsedContent === null) {
|
||||
parsedContent = { result: content };
|
||||
} else if (typeof parsedContent !== "object") {
|
||||
parsedContent = { result: parsedContent };
|
||||
}
|
||||
|
||||
const toolUseId = block.tool_use_id;
|
||||
const name = toolUseNames[toolUseId] || "unknown";
|
||||
|
||||
@@ -210,7 +202,7 @@ export function claudeToGeminiRequest(model, body, stream, credentials = null) {
|
||||
functionResponse: {
|
||||
...(stripFunctionCallId ? {} : { id: toolUseId }),
|
||||
name,
|
||||
response: { result: parsedContent },
|
||||
response: { result: content },
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -511,18 +511,12 @@ function openaiToGeminiBase(
|
||||
name = sanitizeToolName(name);
|
||||
|
||||
const resp = toolResponses[fid];
|
||||
let parsedResp = tryParseJSON(resp);
|
||||
if (parsedResp === null) {
|
||||
parsedResp = { result: resp };
|
||||
} else if (typeof parsedResp !== "object") {
|
||||
parsedResp = { result: parsedResp };
|
||||
}
|
||||
|
||||
toolParts.push({
|
||||
functionResponse: {
|
||||
...(toolNameOptions.stripFunctionCallId ? {} : { id: fid }),
|
||||
name: name,
|
||||
response: { result: parsedResp },
|
||||
response: { result: resp },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
121
package-lock.json
generated
@@ -86,7 +86,6 @@
|
||||
"undici": "^8.10.0",
|
||||
"update-notifier": "^7.3.1",
|
||||
"uuid": "^14.0.0",
|
||||
"wreq-js": "3.1.0",
|
||||
"ws": "^8.21.3",
|
||||
"xxhash-wasm": "^1.1.0",
|
||||
"yazl": "^3.3.1",
|
||||
@@ -110,7 +109,7 @@
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.6",
|
||||
"@types/better-sqlite3": "^9.6.0",
|
||||
"@types/bun": "*",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.4",
|
||||
@@ -161,7 +160,7 @@
|
||||
"better-sqlite3": "^13.0.2",
|
||||
"js-tiktoken": "^1.0.20",
|
||||
"keytar": "^7.9.0",
|
||||
"onnxruntime-node": "1.27.0",
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"sqlite-vec": "^0.1.9",
|
||||
"tls-client-node": "^0.2.0",
|
||||
"wreq-js": "^3.1.0"
|
||||
@@ -15121,6 +15120,14 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/boolean": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
|
||||
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/bottleneck": {
|
||||
"version": "2.19.5",
|
||||
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
||||
@@ -18206,6 +18213,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-node": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
|
||||
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
@@ -19051,6 +19065,13 @@
|
||||
"benchmarks"
|
||||
]
|
||||
},
|
||||
"node_modules/es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/es6-promisify": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-7.0.0.tgz",
|
||||
@@ -21857,16 +21878,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/global-agent": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-4.1.3.tgz",
|
||||
"integrity": "sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
|
||||
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"globalthis": "^1.0.2",
|
||||
"matcher": "^4.0.0",
|
||||
"semver": "^7.3.5",
|
||||
"serialize-error": "^8.1.0"
|
||||
"boolean": "^3.0.1",
|
||||
"es6-error": "^4.1.1",
|
||||
"matcher": "^3.0.0",
|
||||
"roarr": "^2.15.3",
|
||||
"semver": "^7.3.2",
|
||||
"serialize-error": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
@@ -25424,6 +25447,13 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
@@ -27292,9 +27322,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/matcher": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-4.0.0.tgz",
|
||||
"integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
|
||||
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -27302,9 +27332,6 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/material-symbols": {
|
||||
@@ -30277,16 +30304,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/onnxruntime-common": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.27.0.tgz",
|
||||
"integrity": "sha512-3KxL5wIVqa8Ex08jxSzncm9CMgw8CjOFyOQ7SxvG9o0cVLlhTNKXyIQuTbtX4tGPJEf73OER2xrjt4HJSBL4ow==",
|
||||
"version": "1.24.3",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz",
|
||||
"integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/onnxruntime-node": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.27.0.tgz",
|
||||
"integrity": "sha512-QEzGwrvNBgv4uPVdnbHsOGG4G6T96mdlcFI8aAKPjMU8wOPpVocPXb6k3QGkaZagVTv2G9Bnnbo6Z3JdXr1fQw==",
|
||||
"version": "1.24.3",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.24.3.tgz",
|
||||
"integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -30297,8 +30324,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.5.16",
|
||||
"global-agent": "^4.1.3",
|
||||
"onnxruntime-common": "1.27.0"
|
||||
"global-agent": "^3.0.0",
|
||||
"onnxruntime-common": "1.24.3"
|
||||
}
|
||||
},
|
||||
"node_modules/onnxruntime-web": {
|
||||
@@ -33975,6 +34002,24 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/roarr": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
|
||||
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"boolean": "^3.0.1",
|
||||
"detect-node": "^2.0.4",
|
||||
"globalthis": "^1.0.1",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"semver-compare": "^1.0.0",
|
||||
"sprintf-js": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/robot3": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/robot3/-/robot3-0.4.1.tgz",
|
||||
@@ -34334,6 +34379,13 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/semver-compare": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
|
||||
@@ -34361,13 +34413,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-error": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
|
||||
"integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
|
||||
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
"type-fest": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -34377,9 +34429,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-error/node_modules/type-fest": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
@@ -35149,6 +35201,13 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
|
||||
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/sql.js": {
|
||||
"version": "1.14.2",
|
||||
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.2.tgz",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.8.51",
|
||||
"description": "Unified AI router with 353 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
|
||||
"description": "Unified AI router with 354 providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"omniroute": "bin/omniroute.mjs",
|
||||
@@ -252,7 +252,7 @@
|
||||
"postinstall": "node scripts/build/postinstall.mjs",
|
||||
"uninstall": "node scripts/build/uninstall.mjs",
|
||||
"uninstall:full": "node scripts/build/uninstall.mjs --full",
|
||||
"prepare": "husky",
|
||||
"prepare": "node -e \"try{require.resolve('husky')}catch(e){process.exit(0)};require('child_process').execSync('husky',{stdio:'inherit'})\"",
|
||||
"system-info": "node scripts/dev/system-info.mjs",
|
||||
"build:cli-api": "node --import tsx/esm scripts/cli/generate-api-commands.mjs",
|
||||
"postbuild": "node scripts/build/colocate-standalone.mjs",
|
||||
@@ -347,7 +347,7 @@
|
||||
"better-sqlite3": "^13.0.2",
|
||||
"js-tiktoken": "^1.0.20",
|
||||
"keytar": "^7.9.0",
|
||||
"onnxruntime-node": "1.27.0",
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"sqlite-vec": "^0.1.9",
|
||||
"tls-client-node": "^0.2.0",
|
||||
"wreq-js": "^3.1.0"
|
||||
@@ -436,7 +436,7 @@
|
||||
"unrs-resolver": true
|
||||
},
|
||||
"overrides": {
|
||||
"onnxruntime-node": "1.27.0",
|
||||
"onnxruntime-node": "1.24.3",
|
||||
"fast-xml-parser": "^5.10.1",
|
||||
"sharp": "^0.35.3",
|
||||
"postcss": "^8.5.18",
|
||||
|
||||
7
public/providers/nimble-search.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Nimble">
|
||||
<rect width="24" height="24" rx="5" fill="#6D28D9" />
|
||||
<path
|
||||
d="M7 17V7h2.1l5.8 6.6V7H17v10h-2.1L9.1 10.4V17H7z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 240 B |
1
public/providers/opper.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 315 315"><title>Opper</title><circle cx="157.5" cy="157.5" r="157.5" fill="#1B2E40"/><g transform="translate(63 63) scale(0.6)" fill="#FFFFFF"><path fill-rule="evenodd" clip-rule="evenodd" d="M159.78 315C71.53 315 0 244.49 0 157.5C0 -18.9499 159.78 0.650075 159.78 0.650075C159.78 87.2201 88.36 157.4 0.2 157.5C149.8 157.64 159.78 315 159.78 315ZM160.52 217.98C160.52 217.98 156.94 161.65 105.04 157.52C120.6 157.34 160.52 151.54 160.52 96.5601C160.52 151.54 200.44 157.34 216 157.52C164.1 161.65 160.52 217.98 160.52 217.98Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 592 B |
@@ -19,12 +19,20 @@
|
||||
*/
|
||||
|
||||
const KEY = process.env.NVIDIA_API_KEY ?? "";
|
||||
const BASE_URL = process.env.NVIDIA_BASE_URL || "https://integrate.api.nvidia.com/v1/chat/completions";
|
||||
const BASE_URL =
|
||||
process.env.NVIDIA_BASE_URL || "https://integrate.api.nvidia.com/v1/chat/completions";
|
||||
const MODEL = process.env.NVIDIA_MODEL || "openai/gpt-oss-120b";
|
||||
|
||||
// Neutralize CR/LF before logging so env-derived values (NVIDIA_MODEL, etc.)
|
||||
// cannot forge extra log lines (S5145 log injection).
|
||||
const line = (s = "") => console.log(String(s).replace(/[\r\n]+/g, " "));
|
||||
// cannot forge extra log lines (S5145 log injection). Also strip any raw
|
||||
// occurrence of the API key so an upstream error/response that echoes it
|
||||
// back (e.g. inside err.stack or a validation result) never reaches the
|
||||
// terminal in clear text (js/clear-text-logging, CWE-312/532).
|
||||
const line = (s = "") => {
|
||||
let out = String(s).replace(/[\r\n]+/g, " ");
|
||||
if (KEY) out = out.split(KEY).join("[REDACTED]");
|
||||
console.log(out);
|
||||
};
|
||||
const hr = () => line("─".repeat(72));
|
||||
|
||||
function show(label: string, value: unknown) {
|
||||
@@ -52,8 +60,13 @@ async function partA() {
|
||||
});
|
||||
line(" ✅ validateProviderApiKey retornou (sem crash):");
|
||||
show("resultado", result);
|
||||
if (typeof (result as any)?.error === "string" && (result as any).error.includes("startsWith")) {
|
||||
line(" ⚠️ A mensagem de erro contém 'startsWith' → crash CAPTURADO dentro do try/catch da validação.");
|
||||
if (
|
||||
typeof (result as any)?.error === "string" &&
|
||||
(result as any).error.includes("startsWith")
|
||||
) {
|
||||
line(
|
||||
" ⚠️ A mensagem de erro contém 'startsWith' → crash CAPTURADO dentro do try/catch da validação."
|
||||
);
|
||||
}
|
||||
} catch (err: any) {
|
||||
line(" ❌ validateProviderApiKey LANÇOU (crash não tratado):");
|
||||
|
||||
@@ -22,6 +22,52 @@ interface SearchStats {
|
||||
avgDurationMs: number;
|
||||
}
|
||||
|
||||
function isFiniteNumber(value: unknown): value is number {
|
||||
return typeof value === "number" && Number.isFinite(value);
|
||||
}
|
||||
|
||||
function isSearchStats(value: unknown): value is SearchStats {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
|
||||
const candidate = value as Partial<SearchStats>;
|
||||
return (
|
||||
isFiniteNumber(candidate.total) &&
|
||||
isFiniteNumber(candidate.today) &&
|
||||
isFiniteNumber(candidate.cached) &&
|
||||
isFiniteNumber(candidate.errors) &&
|
||||
isFiniteNumber(candidate.totalCostUsd) &&
|
||||
isFiniteNumber(candidate.cacheHitRate) &&
|
||||
isFiniteNumber(candidate.avgDurationMs) &&
|
||||
!!candidate.byProvider &&
|
||||
typeof candidate.byProvider === "object" &&
|
||||
!Array.isArray(candidate.byProvider) &&
|
||||
Object.values(candidate.byProvider).every(
|
||||
(provider) =>
|
||||
!!provider &&
|
||||
typeof provider === "object" &&
|
||||
isFiniteNumber(provider.count) &&
|
||||
isFiniteNumber(provider.costUsd)
|
||||
) &&
|
||||
Array.isArray(candidate.last24h) &&
|
||||
candidate.last24h.every(
|
||||
(point) => typeof point.hour === "string" && isFiniteNumber(point.count)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function readSearchStats(response: Response): Promise<SearchStats> {
|
||||
const body: unknown = await response.json().catch(() => null);
|
||||
if (!response.ok) {
|
||||
const message =
|
||||
body && typeof body === "object" && "error" in body && typeof body.error === "string"
|
||||
? body.error
|
||||
: null;
|
||||
throw new Error(message ?? "searchAnalyticsNoData");
|
||||
}
|
||||
if (!isSearchStats(body)) throw new Error("searchAnalyticsNoData");
|
||||
return body;
|
||||
}
|
||||
|
||||
function StatCard({
|
||||
icon,
|
||||
label,
|
||||
@@ -85,16 +131,25 @@ export default function SearchAnalyticsTab() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/v1/search/analytics")
|
||||
.then((r) => r.json())
|
||||
.then((d) => {
|
||||
setStats(d);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
setError(e.message);
|
||||
setLoading(false);
|
||||
});
|
||||
let cancelled = false;
|
||||
const controller = new AbortController();
|
||||
async function loadStats() {
|
||||
try {
|
||||
const response = await fetch("/api/v1/search/analytics", { signal: controller.signal });
|
||||
const nextStats = await readSearchStats(response);
|
||||
if (!cancelled) setStats(nextStats);
|
||||
} catch (cause) {
|
||||
const message = cause instanceof Error ? cause.message : "searchAnalyticsNoData";
|
||||
if (!cancelled) setError(message);
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
}
|
||||
void loadStats();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
controller.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -249,21 +249,23 @@ export default function ProfilePage() {
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
||||
{allBadges.map((badge) => {
|
||||
const isEarned = earnedIds.has(badge.id);
|
||||
const isHiddenAndLocked = Boolean(badge.hidden) && !isEarned;
|
||||
const earnedInfo = earnedBadges.find((b) => b.badgeId === badge.id);
|
||||
const rarityColor = RARITY_COLORS[badge.rarity] || RARITY_COLORS.common;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={badge.id}
|
||||
onClick={() => setSelectedBadge(badge)}
|
||||
onClick={() => !isHiddenAndLocked && setSelectedBadge(badge)}
|
||||
disabled={isHiddenAndLocked}
|
||||
className={`relative p-4 rounded-xl border transition-all text-left ${
|
||||
isEarned
|
||||
? `${rarityColor} bg-surface hover:shadow-md`
|
||||
: "border-border/50 bg-surface/50 opacity-50 grayscale hover:opacity-70"
|
||||
: "border-border/50 bg-surface/50 opacity-50 grayscale enabled:hover:opacity-70 disabled:cursor-default"
|
||||
}`}
|
||||
>
|
||||
<div className="text-3xl mb-2">
|
||||
<BadgeIcon icon={badge.icon} earned={isEarned} />
|
||||
<BadgeIcon icon={isHiddenAndLocked ? null : badge.icon} earned={isEarned} />
|
||||
</div>
|
||||
<p className="font-semibold text-sm truncate">
|
||||
{badge.hidden && !isEarned ? "???" : translateBadge(badge, "name")}
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
"use client";
|
||||
|
||||
import { Button, Input, Select, Toggle } from "@/shared/components";
|
||||
import {
|
||||
PEAK_HOUR_PROTECTION_DAYS,
|
||||
type PeakHourProtectionConfig,
|
||||
type PeakHourProtectionDay,
|
||||
type PeakHourProtectionMode,
|
||||
type PeakHourWindow,
|
||||
} from "@/lib/providers/peakHourProtection";
|
||||
import { providerText, type ProviderMessageTranslator } from "../providerPageHelpers";
|
||||
|
||||
export const EMPTY_PEAK_HOUR_PROTECTION: PeakHourProtectionConfig = {
|
||||
enabled: false,
|
||||
mode: "block",
|
||||
windows: [],
|
||||
};
|
||||
|
||||
const DAY_LABELS: Record<PeakHourProtectionDay, string> = {
|
||||
mon: "Mon",
|
||||
tue: "Tue",
|
||||
wed: "Wed",
|
||||
thu: "Thu",
|
||||
fri: "Fri",
|
||||
sat: "Sat",
|
||||
sun: "Sun",
|
||||
};
|
||||
|
||||
function cloneConfig(value: unknown): PeakHourProtectionConfig {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return { ...EMPTY_PEAK_HOUR_PROTECTION, windows: [] };
|
||||
}
|
||||
const record = value as Record<string, unknown>;
|
||||
const mode: PeakHourProtectionMode = record.mode === "avoid" ? "avoid" : "block";
|
||||
const windows = Array.isArray(record.windows)
|
||||
? record.windows
|
||||
.filter((entry): entry is PeakHourWindow => !!entry && typeof entry === "object")
|
||||
.map((entry) => ({
|
||||
id: typeof entry.id === "string" ? entry.id : crypto.randomUUID(),
|
||||
name: typeof entry.name === "string" ? entry.name : "",
|
||||
days: Array.isArray(entry.days)
|
||||
? entry.days.filter((day): day is PeakHourProtectionDay =>
|
||||
(PEAK_HOUR_PROTECTION_DAYS as readonly string[]).includes(day)
|
||||
)
|
||||
: [],
|
||||
startUtc: typeof entry.startUtc === "string" ? entry.startUtc : "06:00",
|
||||
endUtc: typeof entry.endUtc === "string" ? entry.endUtc : "10:00",
|
||||
}))
|
||||
: [];
|
||||
return { enabled: record.enabled === true, mode, windows };
|
||||
}
|
||||
|
||||
function newWindow(): PeakHourWindow {
|
||||
return { id: crypto.randomUUID(), name: "", days: [], startUtc: "06:00", endUtc: "10:00" };
|
||||
}
|
||||
|
||||
function weekdayWindow(startUtc: string, endUtc: string): PeakHourWindow {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
name: "Weekday peak",
|
||||
days: ["mon", "tue", "wed", "thu", "fri"],
|
||||
startUtc,
|
||||
endUtc,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizePeakHourProtectionForSave(
|
||||
value: PeakHourProtectionConfig
|
||||
): PeakHourProtectionConfig | null {
|
||||
const windows = value.windows
|
||||
.map((window) => ({
|
||||
...(window.name?.trim() ? { name: window.name.trim() } : {}),
|
||||
...(window.days && window.days.length > 0 ? { days: window.days } : {}),
|
||||
startUtc: window.startUtc,
|
||||
endUtc: window.endUtc,
|
||||
}))
|
||||
.filter(
|
||||
(window) => /^\d{2}:\d{2}$/.test(window.startUtc) && /^\d{2}:\d{2}$/.test(window.endUtc)
|
||||
);
|
||||
if (!value.enabled && windows.length === 0) return null;
|
||||
return { enabled: value.enabled, mode: value.mode, windows };
|
||||
}
|
||||
|
||||
export function formatPeakHourSummary(value: unknown): string | null {
|
||||
const config = cloneConfig(value);
|
||||
if (!config.enabled || config.windows.length === 0) return null;
|
||||
const mode = config.mode === "avoid" ? "Avoid" : "Block";
|
||||
return `${mode} during ${config.windows.length} peak window${config.windows.length === 1 ? "" : "s"}`;
|
||||
}
|
||||
|
||||
export default function PeakHourProtectionEditor({
|
||||
value,
|
||||
onChange,
|
||||
t,
|
||||
}: {
|
||||
value: PeakHourProtectionConfig;
|
||||
onChange: (next: PeakHourProtectionConfig) => void;
|
||||
t: ProviderMessageTranslator;
|
||||
}) {
|
||||
const updateWindow = (id: string | undefined, patch: Partial<PeakHourWindow>) => {
|
||||
onChange({
|
||||
...value,
|
||||
windows: value.windows.map((window) => (window.id === id ? { ...window, ...patch } : window)),
|
||||
});
|
||||
};
|
||||
|
||||
const toggleDay = (window: PeakHourWindow, day: PeakHourProtectionDay) => {
|
||||
const days = new Set(window.days || []);
|
||||
if (days.has(day)) days.delete(day);
|
||||
else days.add(day);
|
||||
updateWindow(window.id, { days: Array.from(days) });
|
||||
};
|
||||
|
||||
const applyPreset = (provider: "deepseek" | "zai") => {
|
||||
const windows =
|
||||
provider === "deepseek"
|
||||
? [weekdayWindow("01:00", "04:00"), weekdayWindow("06:00", "10:00")]
|
||||
: [{ ...newWindow(), name: "Daily peak", startUtc: "06:00", endUtc: "10:00" }];
|
||||
onChange({ enabled: true, mode: value.mode, windows });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded-lg border border-amber-500/30 bg-amber-500/5 p-4">
|
||||
<Toggle
|
||||
checked={value.enabled}
|
||||
onChange={(enabled) => onChange({ ...value, enabled })}
|
||||
label={providerText(t, "peakHourProtectionLabel", "Peak-hour protection")}
|
||||
description={providerText(
|
||||
t,
|
||||
"peakHourProtectionDescription",
|
||||
"Block this connection during configured UTC peak-hour windows. This avoids uncertain peak multipliers instead of trying to price them."
|
||||
)}
|
||||
/>
|
||||
<Select
|
||||
label={providerText(t, "peakHourProtectionModeLabel", "Protection mode")}
|
||||
value={value.mode}
|
||||
options={[
|
||||
{
|
||||
value: "block",
|
||||
label: providerText(t, "peakHourProtectionModeBlock", "Block requests"),
|
||||
},
|
||||
{
|
||||
value: "avoid",
|
||||
label: providerText(t, "peakHourProtectionModeAvoid", "Avoid in routing"),
|
||||
},
|
||||
]}
|
||||
onChange={(event) =>
|
||||
onChange({ ...value, mode: event.target.value === "avoid" ? "avoid" : "block" })
|
||||
}
|
||||
hint={providerText(
|
||||
t,
|
||||
"peakHourProtectionModeHint",
|
||||
"Direct requests fail while active; combo/auto routing skips protected connections when alternatives exist."
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button size="sm" variant="secondary" onClick={() => applyPreset("deepseek")}>
|
||||
{providerText(t, "peakHourDeepSeekPreset", "Use DeepSeek preset")}
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" onClick={() => applyPreset("zai")}>
|
||||
{providerText(t, "peakHourZaiPreset", "Use Z.ai preset")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
icon="add"
|
||||
onClick={() => onChange({ ...value, windows: [...value.windows, newWindow()] })}
|
||||
>
|
||||
{providerText(t, "peakHourAddWindow", "Add window")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{value.windows.length === 0 ? (
|
||||
<p className="text-xs text-text-muted">
|
||||
{providerText(t, "peakHourNoWindows", "No peak-hour windows configured.")}
|
||||
</p>
|
||||
) : (
|
||||
value.windows.map((window) => (
|
||||
<div key={window.id} className="rounded-lg border border-border/70 bg-surface/50 p-3">
|
||||
<div className="mb-3 flex items-center justify-between gap-2">
|
||||
<Input
|
||||
label={providerText(t, "peakHourWindowName", "Window name")}
|
||||
value={window.name || ""}
|
||||
onChange={(event) => updateWindow(window.id, { name: event.target.value })}
|
||||
placeholder={providerText(
|
||||
t,
|
||||
"peakHourWindowNamePlaceholder",
|
||||
"e.g. weekday peak"
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
icon="delete"
|
||||
onClick={() =>
|
||||
onChange({
|
||||
...value,
|
||||
windows: value.windows.filter((entry) => entry.id !== window.id),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<Input
|
||||
label={providerText(t, "peakHourStartUtc", "Start UTC")}
|
||||
type="time"
|
||||
value={window.startUtc}
|
||||
onChange={(event) => updateWindow(window.id, { startUtc: event.target.value })}
|
||||
/>
|
||||
<Input
|
||||
label={providerText(t, "peakHourEndUtc", "End UTC")}
|
||||
type="time"
|
||||
value={window.endUtc}
|
||||
onChange={(event) => updateWindow(window.id, { endUtc: event.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="mb-2 text-xs font-medium text-text-muted">
|
||||
{providerText(t, "peakHourDays", "Days (empty = every day)")}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{PEAK_HOUR_PROTECTION_DAYS.map((day) => {
|
||||
const active = (window.days || []).includes(day);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={day}
|
||||
onClick={() => toggleDay(window, day)}
|
||||
className={`rounded-full px-2 py-1 text-xs font-medium transition-colors ${
|
||||
active
|
||||
? "bg-amber-500 text-white"
|
||||
: "bg-muted/60 text-text-muted hover:bg-muted"
|
||||
}`}
|
||||
>
|
||||
{DAY_LABELS[day]}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -59,6 +59,12 @@ import AgentrouterConsoleFields from "./AgentrouterConsoleFields";
|
||||
import QuotaScrapingFields, { EMPTY_QUOTA_SCRAPING_FIELDS } from "./QuotaScrapingFields";
|
||||
import GlmTeamQuotaFields, { EMPTY_GLM_TEAM_QUOTA_FIELDS } from "./GlmTeamQuotaFields";
|
||||
import ProviderRegionField, { getProviderRegionConfig } from "./AlibabaProviderRegionField";
|
||||
import PeakHourProtectionEditor, {
|
||||
EMPTY_PEAK_HOUR_PROTECTION,
|
||||
formatPeakHourSummary,
|
||||
normalizePeakHourProtectionForSave,
|
||||
} from "../PeakHourProtectionEditor";
|
||||
import type { PeakHourProtectionConfig } from "@/lib/providers/peakHourProtection";
|
||||
export interface EditConnectionModalConnection {
|
||||
id?: string;
|
||||
name?: string;
|
||||
@@ -154,6 +160,7 @@ export default function EditConnectionModal({
|
||||
runtimeKey: "",
|
||||
connectorName: stringField(connectionProviderSpecificData?.connectorName) || "OmniRoute Codex",
|
||||
m365Tier: normalizeM365TierValue(connectionProviderSpecificData?.tier) as M365TierValue,
|
||||
peakHourProtection: { ...EMPTY_PEAK_HOUR_PROTECTION, windows: [] } as PeakHourProtectionConfig,
|
||||
});
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState(null);
|
||||
@@ -391,6 +398,22 @@ export default function EditConnectionModal({
|
||||
connectorName:
|
||||
stringField(connection.providerSpecificData?.connectorName) || "OmniRoute Codex",
|
||||
m365Tier: normalizeM365TierValue(connection.providerSpecificData?.tier) as M365TierValue,
|
||||
peakHourProtection: {
|
||||
...EMPTY_PEAK_HOUR_PROTECTION,
|
||||
...((connection.providerSpecificData?.peakHourProtection as PeakHourProtectionConfig) ||
|
||||
{}),
|
||||
windows: Array.isArray(
|
||||
(
|
||||
connection.providerSpecificData?.peakHourProtection as
|
||||
PeakHourProtectionConfig | undefined
|
||||
)?.windows
|
||||
)
|
||||
? [
|
||||
...(connection.providerSpecificData?.peakHourProtection as PeakHourProtectionConfig)
|
||||
.windows,
|
||||
]
|
||||
: [],
|
||||
},
|
||||
});
|
||||
const existing = connection.providerSpecificData?.extraApiKeys;
|
||||
setExtraApiKeys(Array.isArray(existing) ? existing : []);
|
||||
@@ -699,6 +722,9 @@ export default function EditConnectionModal({
|
||||
}
|
||||
if (updates.providerSpecificData) {
|
||||
updates.providerSpecificData.disableCooling = formData.disableCooling ? true : undefined;
|
||||
updates.providerSpecificData.peakHourProtection = normalizePeakHourProtectionForSave(
|
||||
formData.peakHourProtection
|
||||
);
|
||||
// Explicit `null`, not `undefined`: the PUT route merges
|
||||
// { ...existing, ...incoming }, so omitting the key would keep the previous
|
||||
// choice and switching back to the default would never take effect.
|
||||
@@ -844,6 +870,16 @@ export default function EditConnectionModal({
|
||||
label={t("disableCoolingLabel")}
|
||||
description={t("disableCoolingDescription")}
|
||||
/>
|
||||
<PeakHourProtectionEditor
|
||||
value={formData.peakHourProtection}
|
||||
onChange={(peakHourProtection) => setFormData({ ...formData, peakHourProtection })}
|
||||
t={t}
|
||||
/>
|
||||
{formatPeakHourSummary(formData.peakHourProtection) && (
|
||||
<p className="text-xs text-text-muted">
|
||||
{formatPeakHourSummary(formData.peakHourProtection)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<QuotaScrapingFields
|
||||
provider={provider}
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import { providerAllowsOptionalApiKey } from "@/shared/constants/providers";
|
||||
import { shouldUseApiKeyConnectionTest } from "./webSessionTestDispatch";
|
||||
import { testCodexAppServerConnection, makeDiagnosis } from "./codexAppServerHealth";
|
||||
import { removeConnectionHealth } from "@omniroute/open-sse/services/apiKeyRotator.ts";
|
||||
import { recoverKeyHealth } from "@omniroute/open-sse/services/apiKeyRotator.ts";
|
||||
import { shouldClearErrorStateOnValidProbe } from "@/lib/usage/providerLimits";
|
||||
import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
|
||||
import { classifyAmbiguousOrAuthError, type ClassifyFailureArgs } from "./mistralAmbiguousAuth";
|
||||
@@ -1125,7 +1125,11 @@ export async function testSingleConnection(connectionId: string, validationModel
|
||||
lastError: clearErrorState ? null : result.valid ? connection.lastError : result.error,
|
||||
lastErrorAt: clearErrorState ? null : result.valid ? connection.lastErrorAt : now,
|
||||
lastTested: now,
|
||||
lastErrorType: clearErrorState ? null : result.valid ? connection.lastErrorType : diagnosis.type,
|
||||
lastErrorType: clearErrorState
|
||||
? null
|
||||
: result.valid
|
||||
? connection.lastErrorType
|
||||
: diagnosis.type,
|
||||
lastErrorSource: clearErrorState
|
||||
? null
|
||||
: result.valid
|
||||
@@ -1147,16 +1151,11 @@ export async function testSingleConnection(connectionId: string, validationModel
|
||||
|
||||
if (clearErrorState) {
|
||||
updateData.backoffLevel = 0;
|
||||
}
|
||||
|
||||
const psd = connection?.providerSpecificData as Record<string, unknown> | undefined;
|
||||
updateData.providerSpecificData = {
|
||||
...(psd || {}),
|
||||
apiKeyHealth: {},
|
||||
};
|
||||
|
||||
try {
|
||||
removeConnectionHealth(connectionId);
|
||||
} catch {}
|
||||
if (result.valid && (connection.apiKey || connection.accessToken)) {
|
||||
const recovered = recoverKeyHealth(connectionId, "primary", connection.providerSpecificData);
|
||||
if (recovered) updateData.providerSpecificData = recovered;
|
||||
}
|
||||
|
||||
// If token was refreshed, update tokens in DB
|
||||
|
||||
@@ -54,6 +54,13 @@ const FETCH_PROVIDERS: FetchProviderDef[] = [
|
||||
freeMonthlyQuota: 0,
|
||||
fetchFormats: ["markdown", "html"],
|
||||
},
|
||||
{
|
||||
id: "nimble-search",
|
||||
name: "Nimble Extract",
|
||||
costPerQuery: 0.005,
|
||||
freeMonthlyQuota: 0,
|
||||
fetchFormats: ["markdown", "html", "links", "screenshot"],
|
||||
},
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -11314,7 +11314,9 @@
|
||||
"noPayload": "لا توجد بيانات حمولة متاحة لهذه السجل.",
|
||||
"correlationId": "معرف الترابط",
|
||||
"correlationIdValue": "معرّف الارتباط: {id}",
|
||||
"detailedPayloadInfo": "قم بتمكين تسجيل الدخول التفصيلي أولاً إذا كنت ترغب في عرض الحمولة من أربعة مراحل للعميل/المزود للطلبات الجديدة."
|
||||
"detailedPayloadInfo": "قم بتمكين تسجيل الدخول التفصيلي أولاً إذا كنت ترغب في عرض الحمولة من أربعة مراحل للعميل/المزود للطلبات الجديدة.",
|
||||
"copyAll": "Copy all",
|
||||
"copiedAll": "Copied all"
|
||||
}
|
||||
},
|
||||
"proxyLogger": {
|
||||
|
||||
@@ -11314,7 +11314,9 @@
|
||||
"noPayload": "Bu log girişi üçün heç bir yük məlumatı mövcud deyil.",
|
||||
"correlationId": "Korrelyasiya ID-si",
|
||||
"correlationIdValue": "Korrelyasiya ID: {id}",
|
||||
"detailedPayloadInfo": "Əvvəlcə ətraflı qeydiyyatı aktiv edin, əgər yeni tələblər üçün dörd mərhələli müştəri/təchizatçı yük görünüşünü istəyirsinizsə."
|
||||
"detailedPayloadInfo": "Əvvəlcə ətraflı qeydiyyatı aktiv edin, əgər yeni tələblər üçün dörd mərhələli müştəri/təchizatçı yük görünüşünü istəyirsinizsə.",
|
||||
"copyAll": "Copy all",
|
||||
"copiedAll": "Copied all"
|
||||
}
|
||||
},
|
||||
"proxyLogger": {
|
||||
|
||||