mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 18:02:17 +03:00
Compare commits
4 Commits
fix/comp-e
...
feat/plugi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f96a97e33 | ||
|
|
e6d1f4b048 | ||
|
|
4919aa89a9 | ||
|
|
7c1e228353 |
31
.env.example
31
.env.example
@@ -1027,7 +1027,7 @@ GITHUB_OAUTH_CLIENT_ID=Iv1.b507a08c87ecfe98
|
||||
# Used by: open-sse/executors/base.ts — buildHeaders() dynamic lookup.
|
||||
# Update these when providers release new CLI versions to avoid blocks.
|
||||
|
||||
CLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)"
|
||||
CLAUDE_USER_AGENT="claude-cli/2.1.207 (external, cli)"
|
||||
|
||||
# Disable the deterministic tool-name cloak applied on both Anthropic-bound paths
|
||||
# (executors/base.ts native OAuth + executors/cliproxyapi.ts CLIProxyAPI) —
|
||||
@@ -1176,20 +1176,6 @@ CURSOR_USER_AGENT="Cursor/3.4"
|
||||
# OMNIROUTE_GROK_TLS_TIMEOUT_MS=60000
|
||||
# OMNIROUTE_GROK_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Notion web TLS sidecar (Chrome-fingerprinted client) ──
|
||||
# Used by: open-sse/services/notionTlsClient.ts — wire-level timeout for the
|
||||
# bogdanfinn/tls-client koffi binding and the JS-side grace window layered on
|
||||
# top of it when the native library is wedged. The notion-web executor raises
|
||||
# the wire timeout per-request to 180000 for long generations.
|
||||
# OMNIROUTE_NOTION_TLS_TIMEOUT_MS=30000
|
||||
# OMNIROUTE_NOTION_TLS_GRACE_MS=10000
|
||||
|
||||
# ── Grok web quota fetcher (auth.json override) ──
|
||||
# Used by: open-sse/services/grokQuotaFetcher.ts — path of the Grok CLI
|
||||
# auth.json used to fetch the grok-web weekly quota. Defaults to
|
||||
# ~/.grok/auth.json; override for tests or a non-standard CLI install.
|
||||
# GROK_AUTH_PATH=
|
||||
|
||||
# ── Browser-backed web-cookie chat (Playwright shared pool) ──
|
||||
# Used by: open-sse/services/browserPool.ts + browserBackedChat.ts. The shared
|
||||
# browser pool warms a headless context for web-cookie providers (e.g. claude-web)
|
||||
@@ -2099,15 +2085,6 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# the full (unfiltered) pool with a warning. Source: open-sse/services/autoCombo/virtualFactory.ts
|
||||
# OMNIROUTE_AUTO_FREE_FALLBACK_TO_FULL_POOL=false
|
||||
|
||||
# ─── Auto-Combo chaos panel (broadcast variant) ────────────────────────────
|
||||
# Tuning for the `auto/*:chaos` variant, which fans a single request out to a
|
||||
# panel of provider-diverse models. Panel size is clamped to 1..10 (default 5);
|
||||
# min-panel and the panel hard-timeout fall back to the engine defaults when
|
||||
# unset. Source: open-sse/services/autoCombo/virtualFactory.ts
|
||||
# OMNIROUTE_CHAOS_MAX_PANEL=5
|
||||
# OMNIROUTE_CHAOS_MIN_PANEL=
|
||||
# OMNIROUTE_CHAOS_PANEL_TIMEOUT_MS=
|
||||
|
||||
# ─── OpenCode config regeneration (scripts/ad-hoc/regen-opencode-config.ts) ───
|
||||
# Base URL of the OmniRoute instance to query for /v1/models when regenerating
|
||||
# an opencode.json with accurate limit.context values. Used by:
|
||||
@@ -2275,8 +2252,7 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# or lifecycle tuning.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# OMNIROUTE_VNC_IMAGE=omniroute-vnc-chromium:local
|
||||
# OMNIROUTE_DOCKER_BIN=docker
|
||||
# OMNIROUTE_VNC_CONTAINER_VNC_PORT=3000
|
||||
# OMNIROUTE_DOCKER_BIN=docker# OMNIROUTE_VNC_CONTAINER_VNC_PORT=3000
|
||||
# OMNIROUTE_VNC_CONTAINER_CDP_PORT=9223
|
||||
# OMNIROUTE_VNC_CONTAINER_PROFILE_DIR=/config
|
||||
# OMNIROUTE_VNC_PROFILE_DIR=
|
||||
@@ -2290,6 +2266,5 @@ QUOTA_STORE_DRIVER=sqlite # sqlite | redis
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Data-dir alias (optional — open-sse/services/notionThreadSessions.ts)
|
||||
# Legacy fallback for DATA_DIR, checked only after DATA_DIR and
|
||||
# OMNIROUTE_DATA_DIR are both unset. Locates the Notion web-thread session cache.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# OMNIROUTE_DATA_DIR are both unset. Locates the Notion web-thread session cache.# ─────────────────────────────────────────────────────────────────────────────
|
||||
# VIBEPROXY_DATA_DIR=
|
||||
|
||||
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@@ -9,12 +9,10 @@
|
||||
|
||||
## Validation
|
||||
|
||||
Run only the focused loop for what you changed — the full unit suite, Vitest, the
|
||||
60% coverage gate, and the production build all run in CI on this PR (#8329):
|
||||
|
||||
- [ ] Focused tests for the change: `node --import tsx/esm --test tests/unit/<file>.test.ts`
|
||||
- [ ] `npm run lint`
|
||||
- [ ] Production-code changes include a new or updated automated test in this PR
|
||||
- [ ] `npm run test:unit`
|
||||
- [ ] `npm run test:coverage`
|
||||
- [ ] Coverage is still `>= 60%` for statements, lines, functions, and branches
|
||||
- [ ] SonarQube PR analysis is green or any remaining issues are explicitly documented below
|
||||
|
||||
## Tests Added Or Updated
|
||||
|
||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -477,7 +477,6 @@ jobs:
|
||||
cache: npm
|
||||
- uses: ./.github/actions/npm-ci-retry
|
||||
- run: node scripts/i18n/check-glossary-consistency.mjs --locale=zh-CN
|
||||
- run: node scripts/i18n/check-glossary-consistency.mjs --locale=zh-TW
|
||||
|
||||
# D4 (plano mestre testes+CI): a matrix de ~40 jobs de <1min por idioma saturava sozinha
|
||||
# a concorrência de jobs da conta (Free = 20 slots, compartilhados entre TODOS os repos)
|
||||
@@ -497,7 +496,7 @@ jobs:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v7
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Validate all languages
|
||||
@@ -908,7 +907,7 @@ jobs:
|
||||
# (if-no-files-found: warn) — Sonar consumes the same file.
|
||||
- name: Upload coverage to Codecov (informational)
|
||||
if: always()
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5
|
||||
with:
|
||||
files: coverage/lcov.info
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -22,10 +22,10 @@ jobs:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
- uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
queries: security-extended
|
||||
- uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
- uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
14
.github/workflows/dast-smoke.yml
vendored
14
.github/workflows/dast-smoke.yml
vendored
@@ -2,20 +2,8 @@ name: DAST smoke (PR)
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["main", "release/**"]
|
||||
# Runner-cost guard (#8084): the CLI-bundle build alone is 6-11min; a docs-only PR
|
||||
# cannot change DAST behavior, so skip the whole workflow for pure docs/markdown
|
||||
# changes. Any code path in the diff still runs the full smoke.
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
- "**/*.md"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Superseded runs on the same PR must not stack 25-minute advisory builds
|
||||
# (force-push storms were holding 2-3 runners each). Same group rule as quality.yml.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
dast-smoke:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -53,7 +41,7 @@ jobs:
|
||||
if curl -sf http://localhost:20128/api/monitoring/health >/dev/null; then echo up; break; fi
|
||||
sleep 2
|
||||
done
|
||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- run: pip install schemathesis
|
||||
|
||||
2
.github/workflows/nightly-llm-security.yml
vendored
2
.github/workflows/nightly-llm-security.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
if curl -sf http://localhost:20128/api/monitoring/health >/dev/null; then echo up; break; fi
|
||||
sleep 2
|
||||
done
|
||||
- uses: actions/setup-python@v7
|
||||
- uses: actions/setup-python@v6
|
||||
if: steps.gate.outputs.run == 'true'
|
||||
with: { python-version: "3.12" }
|
||||
- run: pip install garak
|
||||
|
||||
2
.github/workflows/nightly-schemathesis.yml
vendored
2
.github/workflows/nightly-schemathesis.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
if curl -sf http://localhost:20128/api/monitoring/health >/dev/null; then echo "server up"; break; fi
|
||||
sleep 2
|
||||
done
|
||||
- uses: actions/setup-python@v7
|
||||
- uses: actions/setup-python@v6
|
||||
with: { python-version: "3.12" }
|
||||
- name: Install schemathesis
|
||||
run: pip install schemathesis
|
||||
|
||||
21
.github/workflows/quality.yml
vendored
21
.github/workflows/quality.yml
vendored
@@ -177,26 +177,17 @@ jobs:
|
||||
git fetch --no-tags origin "$GITHUB_BASE_REF" || true
|
||||
node scripts/quality/build-test-impact-map.mjs
|
||||
SEL="$(node scripts/quality/select-impacted-tests.mjs)"
|
||||
# Shadow evidence (#8084): persist every selection so TIA false negatives can
|
||||
# be measured against fast-unit's full-suite verdict across releases BEFORE
|
||||
# any gate authority moves off ordinary PRs. Artifact uploaded below.
|
||||
printf '%s\n' "$SEL" > tia-selection.txt
|
||||
if [ -z "$SEL" ]; then
|
||||
echo "TIA selection: empty (no source/test changes)" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "No source/test changes — skipping unit tests"; exit 0
|
||||
fi
|
||||
if [ -z "$SEL" ]; then echo "No source/test changes — skipping unit tests"; exit 0; fi
|
||||
# CI runners are 4-vCPU; run at --test-concurrency=4 (matching the ci.yml unit
|
||||
# job) rather than test:unit's local-tuned concurrency=20. Oversubscribing the
|
||||
# runner makes timing-sensitive tests (db-backup, upstream-timeout, ...) flake,
|
||||
# which must not happen on a blocking gate. DATA_DIR isolation keeps the parallel
|
||||
# run race-free regardless of concurrency.
|
||||
if echo "$SEL" | grep -q "__RUN_ALL__"; then
|
||||
echo "TIA selection: __RUN_ALL__ (fail-safe) — full-suite authority stays with fast-unit" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Fail-safe: __RUN_ALL__ — deferring FULL unit suite to fast-unit (4-shard)."
|
||||
echo "Not re-running unsharded test:unit:ci here (duplicate of fast-unit coverage)."
|
||||
exit 0
|
||||
fi
|
||||
echo "TIA selection: $(grep -c . tia-selection.txt) impacted test file(s) — full suite still runs in fast-unit (shadow-evidence phase, #8084)" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Running impacted tests:"; echo "$SEL"
|
||||
mapfile -t FILES <<< "$SEL"
|
||||
# Loader parity with test:unit:ci:shard (#6787): tests/unit/dashboard/** runs
|
||||
@@ -219,16 +210,6 @@ jobs:
|
||||
node --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 "${DASH[@]}" || RC=$?
|
||||
fi
|
||||
exit $RC
|
||||
# #8084 shadow evidence: keep the raw selection downloadable so TIA misses can be
|
||||
# audited against fast-unit failures on the same run (gate moves need this data).
|
||||
- name: Upload TIA selection (shadow evidence)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: tia-selection
|
||||
path: tia-selection.txt
|
||||
if-no-files-found: ignore
|
||||
retention-days: 30
|
||||
|
||||
fast-vitest:
|
||||
name: Vitest (fast-path)
|
||||
|
||||
2
.github/workflows/scorecard.yml
vendored
2
.github/workflows/scorecard.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@v2.4.4
|
||||
uses: ossf/scorecard-action@v2.4.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
6
.github/workflows/semgrep.yml
vendored
6
.github/workflows/semgrep.yml
vendored
@@ -6,12 +6,6 @@ on:
|
||||
branches: ["main"]
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Cancel superseded PR runs (same rule as quality.yml). No paths filter on purpose:
|
||||
# p/secrets must keep scanning docs-only diffs too — credentials leak in .md files.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
semgrep:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -10,8 +10,7 @@ This plugin solves that by:
|
||||
|
||||
- Fetching `/v1/models` and `/api/combos` **at OpenCode startup, in Node.js** — no CORS, no WebView restrictions
|
||||
- Emitting the provider block **dynamically** in the plugin's `config`/`provider` hook — so `opencode.json` only needs the plugin entry, not a static `provider.omniroute`
|
||||
- Re-fetching on a configurable TTL (default 5 min) **and** background auto-discovery while OpenCode is running (`autoSyncIntervalMs`, default 5 min), so new models / combo changes appear without restarting OpenCode
|
||||
- Exposing a force-refresh path (`omniroute_sync_models` tool + `/omni-sync` command template) equivalent to Pi `/omni sync`
|
||||
- Re-fetching on a configurable TTL (default 5 min), so new models / combo changes in the OmniRoute UI appear without restarting OpenCode
|
||||
- Computing `limit.context` for combos as `min(member.context_length)` from the live catalog (no more `null` values that cause 4K-token truncation)
|
||||
- **Auto-pickup of `interleaved` capability** for thinking models (merged via PR #3138)
|
||||
|
||||
@@ -74,9 +73,6 @@ Peer dep: `@opencode-ai/plugin` (managed by your OpenCode install).
|
||||
{
|
||||
"providerId": "omniroute",
|
||||
"baseURL": "https://or.example.com",
|
||||
// Background re-discovery while OpenCode is running (Pi parity).
|
||||
// Default 300000 (5 min). Minimum 60000. Set 0 to disable.
|
||||
"autoSyncIntervalMs": 300000,
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -92,27 +88,6 @@ opencode auth login --provider omniroute
|
||||
|
||||
Restart OpenCode. `/models` lists the full live catalog. Variants (`-low`, `-medium`, `-high`, `-thinking`) and combos appear as first-class IDs — OmniRoute is the source of truth, no client-side synthesis.
|
||||
|
||||
### Live catalog refresh (auto + force)
|
||||
|
||||
While OpenCode is running, the plugin keeps the model catalog fresh in two ways:
|
||||
|
||||
| Mechanism | Default | What it does |
|
||||
| --- | --- | --- |
|
||||
| `modelCacheTtl` | `300000` (5 min) | On-demand TTL: next provider/models hook after expiry re-fetches `/v1/models` |
|
||||
| `autoSyncIntervalMs` | `300000` (5 min) | Background timer: proactively invalidates + re-fetches while the harness is running. Min `60000`. Set `0` to disable background polling (TTL still applies) |
|
||||
|
||||
**Force sync now** (Pi `/omni sync` equivalent) — OpenCode has no Pi-style slash-command registration API, so the plugin wires both a tool and command templates:
|
||||
|
||||
1. **Tool:** `omniroute_sync_models` — invalidates in-memory + disk caches, re-fetches `GET /v1/models` (and combos/enrichment when enabled), returns `{ ok, count, ... }`.
|
||||
2. **Command templates** (type these in OpenCode):
|
||||
- `/omni-sync` — asks the agent to call `omniroute_sync_models` and report the result
|
||||
- `/omni-autosync` — asks the agent to report current `autoSyncIntervalMs` / `modelCacheTtl` status
|
||||
|
||||
```text
|
||||
/omni-sync
|
||||
/omni-autosync
|
||||
```
|
||||
|
||||
## Multi-instance (prod + preprod side-by-side)
|
||||
|
||||
> ⚠ OC ≤1.15.5 dedupes plugin loads by absolute module path. Two `plugin:` entries pointing at the same `dist/index.js` collapse into one (last-listed options win). Workaround: install the plugin twice into separate directories so each entry resolves to a distinct module file. v0.2.x will introduce an `instances: [...]` shape that registers N providers from a single load.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@omniroute/opencode-plugin",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.0",
|
||||
"description": "OpenCode plugin for the OmniRoute AI Gateway. Drives dynamic model discovery, /connect auth flow, and multi-instance OmniRoute providers via the official @opencode-ai/plugin contract.",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
@@ -23,7 +23,7 @@
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"clean": "rm -rf dist",
|
||||
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts tests/auto-sync.test.ts",
|
||||
"test": "node --import tsx/esm --test tests/scaffold.test.ts tests/auth.test.ts tests/options-schema.test.ts tests/multi-instance.test.ts tests/fetch-interceptor.test.ts tests/provider.test.ts tests/gemini-sanitize.test.ts tests/combos.test.ts tests/config-shim.test.ts tests/features.test.ts tests/usable-combo.test.ts tests/disk-snapshot-perms.test.ts tests/fork-features.test.ts tests/auto-combo-context.test.ts tests/provider-id-routing.test.ts tests/management-read-token.test.ts",
|
||||
"prepublishOnly": "npm run clean && npm run build && npm test"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
*/
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
@@ -54,7 +54,6 @@ import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { AuthHook, Config, Plugin, PluginOptions, ProviderHook } from "@opencode-ai/plugin";
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
import type { Model as ModelV2 } from "@opencode-ai/sdk/v2";
|
||||
import { z } from "zod";
|
||||
import { logger as _logger, setLogLevel, type LogLevel as _LogLevel } from "./logger.js";
|
||||
@@ -89,10 +88,6 @@ import {
|
||||
* from providerId.
|
||||
* - `modelCacheTtl` `/v1/models` TTL cache duration in milliseconds.
|
||||
* Default: 300_000 (5 min).
|
||||
* - `autoSyncIntervalMs` Background catalog re-discovery while OpenCode is
|
||||
* running. Default: 300_000 (5 min). Minimum: 60_000.
|
||||
* Set `0` to disable background auto-sync (TTL on-demand
|
||||
* discovery still applies via `modelCacheTtl`).
|
||||
* - `baseURL` Override base URL for this OmniRoute instance. When
|
||||
* absent, the loader falls back to a credential-attached
|
||||
* baseURL set by `/connect`.
|
||||
@@ -195,12 +190,6 @@ const optionsSchema = z
|
||||
.optional(),
|
||||
displayName: z.string().min(1).optional(),
|
||||
modelCacheTtl: z.number().positive().optional(),
|
||||
/**
|
||||
* Background auto-discovery interval while the harness is running.
|
||||
* `0` disables background polling. Values in (0, 60000) are clamped up
|
||||
* to 60000. Default when unset: 300000.
|
||||
*/
|
||||
autoSyncIntervalMs: z.number().int().nonnegative().optional(),
|
||||
baseURL: z.string().url().optional(),
|
||||
managementReadToken: z.string().min(1).optional(),
|
||||
features: featuresSchema.optional(),
|
||||
@@ -228,29 +217,6 @@ export const PLUGIN_GIT_HASH: string =
|
||||
|
||||
export const DEFAULT_MODEL_CACHE_TTL_MS = 300_000 as const;
|
||||
|
||||
/** Default background auto-discovery interval (matches modelCacheTtl default). */
|
||||
export const DEFAULT_AUTO_SYNC_INTERVAL_MS = 300_000 as const;
|
||||
|
||||
/** Minimum positive background auto-discovery interval. */
|
||||
export const MIN_AUTO_SYNC_INTERVAL_MS = 60_000 as const;
|
||||
|
||||
/**
|
||||
* Sanitize background auto-sync interval.
|
||||
* - unset/invalid → default 300_000
|
||||
* - explicit 0 → disabled
|
||||
* - (0, 60000) → clamped to 60000
|
||||
* - ≥ 60000 → kept as integer ms
|
||||
*/
|
||||
export function sanitizeAutoSyncIntervalMs(value: unknown): number {
|
||||
if (value === undefined || value === null) return DEFAULT_AUTO_SYNC_INTERVAL_MS;
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_AUTO_SYNC_INTERVAL_MS;
|
||||
const n = Math.trunc(value);
|
||||
if (n === 0) return 0;
|
||||
if (n < 0) return DEFAULT_AUTO_SYNC_INTERVAL_MS;
|
||||
if (n < MIN_AUTO_SYNC_INTERVAL_MS) return MIN_AUTO_SYNC_INTERVAL_MS;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Manual trim helpers avoid polynomial-regex CodeQL warnings on
|
||||
// user-supplied baseURL strings (string.replace(/\/+$/, "")). The same
|
||||
// behaviour, no backtracking.
|
||||
@@ -277,7 +243,7 @@ function trimLeadingDashes(value: string): string {
|
||||
* sees a consistent identifier.
|
||||
*/
|
||||
export function resolveOmniRoutePluginOptions(opts?: OmniRoutePluginOptions): Required<
|
||||
Pick<OmniRoutePluginOptions, "providerId" | "displayName" | "modelCacheTtl" | "autoSyncIntervalMs">
|
||||
Pick<OmniRoutePluginOptions, "providerId" | "displayName" | "modelCacheTtl">
|
||||
> & {
|
||||
/**
|
||||
* #6859: the UNPREFIXED provider id ("omniroute", "omniroute-preprod", …).
|
||||
@@ -311,22 +277,17 @@ export function resolveOmniRoutePluginOptions(opts?: OmniRoutePluginOptions): Re
|
||||
typeof opts?.modelCacheTtl === "number" && opts.modelCacheTtl > 0
|
||||
? opts.modelCacheTtl
|
||||
: DEFAULT_MODEL_CACHE_TTL_MS;
|
||||
const autoSyncIntervalMs = sanitizeAutoSyncIntervalMs(opts?.autoSyncIntervalMs);
|
||||
return {
|
||||
providerId,
|
||||
omnirouteProviderId,
|
||||
displayName,
|
||||
modelCacheTtl,
|
||||
autoSyncIntervalMs,
|
||||
baseURL: opts?.baseURL,
|
||||
managementReadToken: opts?.managementReadToken,
|
||||
features: opts?.features,
|
||||
};
|
||||
}
|
||||
|
||||
/** Fully resolved plugin options (defaults applied). */
|
||||
export type ResolvedOmniRoutePluginOptions = ReturnType<typeof resolveOmniRoutePluginOptions>;
|
||||
|
||||
/**
|
||||
* Strip a leading "opencode-" prefix (added only for the OC native-adapter
|
||||
* gate — see `resolveOmniRoutePluginOptions`) so the returned id is safe to
|
||||
@@ -562,378 +523,8 @@ export function createOmniRouteAuthHook(opts?: OmniRoutePluginOptions): AuthHook
|
||||
* opencode.json), NOT as a closure binding. Multi-instance support follows
|
||||
* from each plugin tuple invoking the factory with its own opts.
|
||||
*/
|
||||
/**
|
||||
* Invalidate in-memory fetch cache entries for a baseURL (all credential keys).
|
||||
* Returns number of entries removed.
|
||||
*/
|
||||
export function invalidateOmniRouteFetchCache(
|
||||
cache: OmniRouteFetchCache,
|
||||
baseURL?: string,
|
||||
): number {
|
||||
if (!baseURL) {
|
||||
const n = cache.size;
|
||||
cache.clear();
|
||||
return n;
|
||||
}
|
||||
const prefix = `${baseURL}::`;
|
||||
let removed = 0;
|
||||
for (const key of [...cache.keys()]) {
|
||||
if (key.startsWith(prefix) || key === baseURL) {
|
||||
cache.delete(key);
|
||||
removed += 1;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve API credentials for force-sync / background refresh without
|
||||
* depending on the provider.models auth context.
|
||||
*/
|
||||
export async function resolveOmniRouteRuntimeAuth(
|
||||
resolved: ResolvedOmniRoutePluginOptions,
|
||||
readAuthJson?: OmniRouteReadAuthJson,
|
||||
): Promise<{ apiKey: string; baseURL: string; managementReadToken: string } | null> {
|
||||
const reader = readAuthJson ?? defaultReadAuthJson;
|
||||
let authJson: AuthJsonShape | undefined | null;
|
||||
try {
|
||||
authJson = await reader();
|
||||
} catch {
|
||||
authJson = undefined;
|
||||
}
|
||||
if (authJson === null) authJson = undefined;
|
||||
|
||||
const bareKey = resolved.providerId.startsWith("opencode-")
|
||||
? resolved.providerId.slice("opencode-".length)
|
||||
: resolved.providerId;
|
||||
const lookupKeys = [resolved.providerId];
|
||||
if (bareKey !== resolved.providerId) lookupKeys.push(bareKey);
|
||||
if (resolved.omnirouteProviderId && !lookupKeys.includes(resolved.omnirouteProviderId)) {
|
||||
lookupKeys.push(resolved.omnirouteProviderId);
|
||||
}
|
||||
|
||||
let entry: AuthJsonApiEntry | undefined;
|
||||
for (const k of lookupKeys) {
|
||||
const e = authJson?.[k];
|
||||
if (
|
||||
e &&
|
||||
(e as { type?: unknown }).type === "api" &&
|
||||
typeof (e as { key?: unknown }).key === "string" &&
|
||||
((e as { key: string }).key).length > 0
|
||||
) {
|
||||
entry = e as AuthJsonApiEntry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const apiKey = entry?.key ?? "";
|
||||
if (!apiKey) return null;
|
||||
|
||||
const authBaseURL =
|
||||
entry && typeof (entry as { baseURL?: unknown }).baseURL === "string"
|
||||
? (entry as { baseURL: string }).baseURL
|
||||
: "";
|
||||
const baseURL = resolved.baseURL ?? (authBaseURL || "");
|
||||
if (!baseURL) return null;
|
||||
const managementReadToken = resolved.managementReadToken ?? apiKey;
|
||||
return { apiKey, baseURL, managementReadToken };
|
||||
}
|
||||
|
||||
/**
|
||||
* Force-refresh OmniRoute catalog: clear memory + disk cache, re-fetch /v1/models
|
||||
* (and optional management endpoints), and repopulate the shared cache.
|
||||
* OpenCode equivalent of Pi `/omni sync`.
|
||||
*/
|
||||
export async function forceSyncOmniRouteModels(args: {
|
||||
resolved: ResolvedOmniRoutePluginOptions;
|
||||
cache: OmniRouteFetchCache;
|
||||
readAuthJson?: OmniRouteReadAuthJson;
|
||||
fetcher?: OmniRouteModelsFetcher;
|
||||
combosFetcher?: OmniRouteCombosFetcher;
|
||||
autoCombosFetcher?: OmniRouteAutoCombosFetcher;
|
||||
enrichmentFetcher?: OmniRouteEnrichmentFetcher;
|
||||
compressionMetaFetcher?: OmniRouteCompressionMetaFetcher;
|
||||
providersFetcher?: OmniRouteProvidersFetcher;
|
||||
now?: () => number;
|
||||
}): Promise<{
|
||||
ok: boolean;
|
||||
count: number;
|
||||
combos: number;
|
||||
provider: string;
|
||||
baseURL?: string;
|
||||
clearedMemory: number;
|
||||
clearedDisk: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
const resolved = args.resolved;
|
||||
const cache = args.cache;
|
||||
const now = args.now ?? Date.now;
|
||||
const fetcher = args.fetcher ?? defaultOmniRouteModelsFetcher;
|
||||
const combosFetcher = args.combosFetcher ?? defaultOmniRouteCombosFetcher;
|
||||
const autoCombosFetcher = args.autoCombosFetcher ?? defaultOmniRouteAutoCombosFetcher;
|
||||
const enrichmentFetcher = args.enrichmentFetcher ?? defaultOmniRouteEnrichmentFetcher;
|
||||
const compressionMetaFetcher =
|
||||
args.compressionMetaFetcher ?? defaultOmniRouteCompressionMetaFetcher;
|
||||
const providersFetcher = args.providersFetcher ?? defaultOmniRouteProvidersFetcher;
|
||||
const features = resolved.features ?? {};
|
||||
const wantCombos = features.combos !== false;
|
||||
const wantAutoCombos = features.autoCombos !== false;
|
||||
const wantEnrichment = features.enrichment !== false;
|
||||
const wantCompressionMeta = features.compressionMetadata === true;
|
||||
const wantUsableOnly = features.usableOnly === true;
|
||||
const wantDiskCache = features.diskCache !== false;
|
||||
|
||||
const auth = await resolveOmniRouteRuntimeAuth(
|
||||
resolved,
|
||||
args.readAuthJson ?? defaultReadAuthJson,
|
||||
);
|
||||
if (!auth) {
|
||||
return {
|
||||
ok: false,
|
||||
count: 0,
|
||||
combos: 0,
|
||||
provider: resolved.omnirouteProviderId,
|
||||
clearedMemory: 0,
|
||||
clearedDisk: false,
|
||||
error:
|
||||
"No OmniRoute credentials/baseURL available. Run `opencode connect omniroute` or set plugin baseURL.",
|
||||
};
|
||||
}
|
||||
|
||||
const clearedMemory = invalidateOmniRouteFetchCache(cache, auth.baseURL);
|
||||
// Clear residual entries from prior baseURL history as well.
|
||||
const clearedAll = invalidateOmniRouteFetchCache(cache);
|
||||
let clearedDisk = false;
|
||||
if (wantDiskCache) {
|
||||
clearedDisk = await clearDiskSnapshot(resolved.providerId);
|
||||
if (resolved.omnirouteProviderId !== resolved.providerId) {
|
||||
clearedDisk = (await clearDiskSnapshot(resolved.omnirouteProviderId)) || clearedDisk;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const rawModels = await fetcher(auth.baseURL, auth.apiKey, 10_000);
|
||||
let rawCombos: OmniRouteRawCombo[] = [];
|
||||
if (wantCombos) {
|
||||
try {
|
||||
rawCombos = await combosFetcher(auth.baseURL, auth.managementReadToken, 10_000);
|
||||
} catch (err) {
|
||||
console.warn("[omniroute-plugin] force sync: combos fetch failed", err);
|
||||
}
|
||||
}
|
||||
let rawAutoCombos: OmniRouteRawAutoCombo[] = [];
|
||||
if (wantAutoCombos) {
|
||||
try {
|
||||
rawAutoCombos = await autoCombosFetcher(auth.baseURL, auth.managementReadToken, 5_000);
|
||||
} catch {
|
||||
/* soft-fail */
|
||||
}
|
||||
}
|
||||
let rawEnrichment: OmniRouteEnrichmentMap = new Map();
|
||||
if (wantEnrichment) {
|
||||
try {
|
||||
rawEnrichment = await enrichmentFetcher(auth.baseURL, auth.managementReadToken, 10_000);
|
||||
} catch {
|
||||
rawEnrichment = new Map();
|
||||
}
|
||||
}
|
||||
let rawCompressionCombos: OmniRouteCompressionCombo[] = [];
|
||||
if (wantCompressionMeta) {
|
||||
try {
|
||||
rawCompressionCombos = await compressionMetaFetcher(
|
||||
auth.baseURL,
|
||||
auth.managementReadToken,
|
||||
10_000,
|
||||
);
|
||||
} catch {
|
||||
rawCompressionCombos = [];
|
||||
}
|
||||
}
|
||||
let rawConnections: OmniRouteProviderConnection[] = [];
|
||||
if (wantUsableOnly) {
|
||||
try {
|
||||
rawConnections = await providersFetcher(auth.baseURL, auth.managementReadToken, 10_000);
|
||||
} catch {
|
||||
rawConnections = [];
|
||||
}
|
||||
}
|
||||
|
||||
const t = now();
|
||||
const entry = {
|
||||
rawModels,
|
||||
rawCombos,
|
||||
rawAutoCombos,
|
||||
rawEnrichment,
|
||||
rawCompressionCombos,
|
||||
rawConnections,
|
||||
expiresAt: t + resolved.modelCacheTtl,
|
||||
};
|
||||
const cacheKey = modelsCacheKey(
|
||||
auth.baseURL,
|
||||
`${auth.apiKey}\0${auth.managementReadToken}`,
|
||||
);
|
||||
cache.set(cacheKey, entry);
|
||||
|
||||
if (wantDiskCache) {
|
||||
try {
|
||||
const fingerprint = diskSnapshotIdentityFingerprint(
|
||||
auth.baseURL,
|
||||
auth.apiKey,
|
||||
auth.managementReadToken,
|
||||
);
|
||||
const { expiresAt: _expiresAt, ...diskEntry } = entry;
|
||||
await defaultDiskSnapshotWriter(resolved.providerId, diskEntry, fingerprint);
|
||||
} catch {
|
||||
/* soft-fail disk write */
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`[omniroute-plugin] force sync ok providerId=${resolved.providerId} ` +
|
||||
`models=${rawModels.length} combos=${rawCombos.length} ` +
|
||||
`clearedMemory=${clearedMemory + clearedAll} disk=${clearedDisk}`,
|
||||
);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
count: rawModels.length,
|
||||
combos: rawCombos.length,
|
||||
provider: resolved.omnirouteProviderId,
|
||||
baseURL: auth.baseURL,
|
||||
clearedMemory: clearedMemory + clearedAll,
|
||||
clearedDisk,
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
return {
|
||||
ok: false,
|
||||
count: 0,
|
||||
combos: 0,
|
||||
provider: resolved.omnirouteProviderId,
|
||||
baseURL: auth.baseURL,
|
||||
clearedMemory: clearedMemory + clearedAll,
|
||||
clearedDisk,
|
||||
error: message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function createOmniRouteSyncModelsTool(args: {
|
||||
resolved: ResolvedOmniRoutePluginOptions;
|
||||
cache: OmniRouteFetchCache;
|
||||
}): ReturnType<typeof tool> {
|
||||
const { resolved, cache } = args;
|
||||
return tool({
|
||||
description:
|
||||
"Force-refresh the OmniRoute model catalog (OpenCode equivalent of Pi `/omni sync`). " +
|
||||
"Invalidates in-memory and disk caches, then re-fetches GET /v1/models (and combos when enabled).",
|
||||
args: {
|
||||
reason: tool.schema
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional reason for the sync (logging only)"),
|
||||
},
|
||||
async execute(toolArgs) {
|
||||
const result = await forceSyncOmniRouteModels({ resolved, cache });
|
||||
const reason = toolArgs.reason ? ` reason=${toolArgs.reason}` : "";
|
||||
if (!result.ok) {
|
||||
return {
|
||||
title: "OmniRoute sync failed",
|
||||
output:
|
||||
`OmniRoute model sync failed for provider=${result.provider}.${reason}\n` +
|
||||
`${result.error ?? "unknown error"}`,
|
||||
metadata: result,
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: `OmniRoute sync: ${result.count} models`,
|
||||
output:
|
||||
`OmniRoute models synced.` +
|
||||
`\nprovider: ${result.provider}` +
|
||||
`\nbaseURL: ${result.baseURL}` +
|
||||
`\nmodels: ${result.count}` +
|
||||
`\ncombos: ${result.combos}` +
|
||||
`\nclearedMemoryEntries: ${result.clearedMemory}` +
|
||||
`\nclearedDiskSnapshot: ${result.clearedDisk}` +
|
||||
`\nTTL: ${resolved.modelCacheTtl}ms` +
|
||||
`\nautoSyncIntervalMs: ${resolved.autoSyncIntervalMs}` +
|
||||
reason,
|
||||
metadata: result,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start background auto-discovery while the harness is running.
|
||||
* Quiet: only logs when the model count changes or on errors.
|
||||
* Returns a stop function.
|
||||
*/
|
||||
export function startOmniRouteAutoSync(args: {
|
||||
resolved: ResolvedOmniRoutePluginOptions;
|
||||
cache: OmniRouteFetchCache;
|
||||
intervalMs?: number;
|
||||
}): () => void {
|
||||
const resolved = args.resolved;
|
||||
const cache = args.cache;
|
||||
const intervalMs = args.intervalMs ?? resolved.autoSyncIntervalMs;
|
||||
if (!intervalMs || intervalMs <= 0) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
let stopped = false;
|
||||
let inFlight: Promise<void> | null = null;
|
||||
let lastCount: number | undefined;
|
||||
|
||||
const tick = () => {
|
||||
if (stopped) return;
|
||||
if (inFlight) return;
|
||||
inFlight = (async () => {
|
||||
const result = await forceSyncOmniRouteModels({ resolved, cache });
|
||||
if (!result.ok) {
|
||||
console.warn(
|
||||
`[omniroute-plugin] auto-sync failed providerId=${resolved.providerId}: ${result.error}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (lastCount === undefined) {
|
||||
lastCount = result.count;
|
||||
return;
|
||||
}
|
||||
if (result.count !== lastCount) {
|
||||
console.warn(
|
||||
`[omniroute-plugin] auto-sync catalog size changed ${lastCount} → ${result.count} ` +
|
||||
`(providerId=${resolved.providerId})`,
|
||||
);
|
||||
lastCount = result.count;
|
||||
}
|
||||
})()
|
||||
.catch((err) => {
|
||||
console.warn("[omniroute-plugin] auto-sync tick error", err);
|
||||
})
|
||||
.finally(() => {
|
||||
inFlight = null;
|
||||
});
|
||||
};
|
||||
|
||||
// Delay first background tick by one interval so session start is not doubled
|
||||
// with the normal provider.models cold fetch. Manual tool remains immediate.
|
||||
const timer = setInterval(tick, intervalMs);
|
||||
if (typeof timer === "object" && timer && "unref" in timer && typeof timer.unref === "function") {
|
||||
timer.unref();
|
||||
}
|
||||
|
||||
console.warn(
|
||||
`[omniroute-plugin] auto-sync enabled intervalMs=${intervalMs} providerId=${resolved.providerId}`,
|
||||
);
|
||||
|
||||
return () => {
|
||||
stopped = true;
|
||||
clearInterval(timer);
|
||||
};
|
||||
}
|
||||
|
||||
export const OmniRoutePlugin: Plugin = async (_input, options) => {
|
||||
const resolved = resolveOmniRoutePluginOptions(coercePluginOptions(options));
|
||||
const resolved = coercePluginOptions(options);
|
||||
// T-07: a single per-plugin-instance cache shared between the provider
|
||||
// hook (T-03/T-05) and the config-shim hook (T-07). On OC ≥1.14.49 both
|
||||
// hooks fire within the same Plugin invocation, so a shared cache keeps
|
||||
@@ -962,53 +553,10 @@ export const OmniRoutePlugin: Plugin = async (_input, options) => {
|
||||
|
||||
// Wire log level: startupDebug:true → "debug", explicit logLevel wins.
|
||||
setLogLevel(resolved.features?.startupDebug ? "debug" : (resolved.features?.logLevel ?? "warn"));
|
||||
|
||||
// Background auto-discovery while the harness is running (Pi parity).
|
||||
// Interval 0 disables. TTL on-demand discovery still works via modelCacheTtl.
|
||||
startOmniRouteAutoSync({ resolved, cache: sharedCache });
|
||||
|
||||
const syncTool = createOmniRouteSyncModelsTool({ resolved, cache: sharedCache });
|
||||
const bareProviderId = resolved.omnirouteProviderId;
|
||||
|
||||
// Config hook: keep existing catalog shim, and register slash command
|
||||
// templates that ask the agent to call the force-sync tool (OpenCode has no
|
||||
// Pi-style registerCommand API; tools + command templates are the native path).
|
||||
const baseConfigHook = createOmniRouteConfigHook(resolved, { cache: sharedCache });
|
||||
const configWithSyncCommand = async (input: Config) => {
|
||||
await baseConfigHook(input);
|
||||
const cfg = input as Config & {
|
||||
command?: Record<
|
||||
string,
|
||||
{ template: string; description?: string; agent?: string; model?: string; subtask?: boolean }
|
||||
>;
|
||||
};
|
||||
if (!cfg.command) cfg.command = {};
|
||||
if (!cfg.command["omni-sync"]) {
|
||||
cfg.command["omni-sync"] = {
|
||||
description: "Force-refresh OmniRoute model catalog (like Pi /omni sync)",
|
||||
template:
|
||||
`Force-refresh the OmniRoute model catalog now using the omniroute_sync_models tool ` +
|
||||
`(provider ${bareProviderId}). After the tool returns, briefly report model count and whether the sync succeeded.`,
|
||||
};
|
||||
}
|
||||
if (!cfg.command["omni-autosync"]) {
|
||||
cfg.command["omni-autosync"] = {
|
||||
description: "Show OmniRoute auto-sync / cache status",
|
||||
template:
|
||||
`Report OmniRoute discovery status for provider ${bareProviderId}: ` +
|
||||
`autoSyncIntervalMs=${resolved.autoSyncIntervalMs}, modelCacheTtl=${resolved.modelCacheTtl}. ` +
|
||||
`If the user asked to refresh now, call omniroute_sync_models.`,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
auth: createOmniRouteAuthHook(resolved),
|
||||
provider: createOmniRouteProviderHook(resolved, { cache: sharedCache }),
|
||||
config: configWithSyncCommand,
|
||||
tool: {
|
||||
omniroute_sync_models: syncTool,
|
||||
},
|
||||
config: createOmniRouteConfigHook(resolved, { cache: sharedCache }),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4513,19 +4061,6 @@ export function diskSnapshotPath(providerId: string): string {
|
||||
return path.join(dir, "plugins", `omniroute-${providerId}.json`);
|
||||
}
|
||||
|
||||
/** Best-effort delete of the disk snapshot for a provider (force-sync). */
|
||||
export async function clearDiskSnapshot(providerId: string): Promise<boolean> {
|
||||
const file = diskSnapshotPath(providerId);
|
||||
try {
|
||||
await unlink(file);
|
||||
return true;
|
||||
} catch (err) {
|
||||
const code = (err as NodeJS.ErrnoException | undefined)?.code;
|
||||
if (code === "ENOENT") return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export type OmniRouteDiskSnapshotWriter = (
|
||||
providerId: string,
|
||||
entry: Omit<OmniRouteFetchCacheEntry, "expiresAt">,
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* Auto-discovery + force-sync (OpenCode parity with Pi `/omni sync`).
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
sanitizeAutoSyncIntervalMs,
|
||||
DEFAULT_AUTO_SYNC_INTERVAL_MS,
|
||||
MIN_AUTO_SYNC_INTERVAL_MS,
|
||||
parseOmniRoutePluginOptions,
|
||||
resolveOmniRoutePluginOptions,
|
||||
invalidateOmniRouteFetchCache,
|
||||
forceSyncOmniRouteModels,
|
||||
type OmniRouteFetchCache,
|
||||
} from "../src/index.js";
|
||||
|
||||
test("sanitizeAutoSyncIntervalMs: unset → default 300000", () => {
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(undefined), DEFAULT_AUTO_SYNC_INTERVAL_MS);
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(null), DEFAULT_AUTO_SYNC_INTERVAL_MS);
|
||||
});
|
||||
|
||||
test("sanitizeAutoSyncIntervalMs: 0 disables", () => {
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(0), 0);
|
||||
});
|
||||
|
||||
test("sanitizeAutoSyncIntervalMs: clamps below min to 60000", () => {
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(1), MIN_AUTO_SYNC_INTERVAL_MS);
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(59_999), MIN_AUTO_SYNC_INTERVAL_MS);
|
||||
});
|
||||
|
||||
test("sanitizeAutoSyncIntervalMs: keeps valid values", () => {
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(60_000), 60_000);
|
||||
assert.equal(sanitizeAutoSyncIntervalMs(300_000), 300_000);
|
||||
});
|
||||
|
||||
test("parseOmniRoutePluginOptions accepts autoSyncIntervalMs including 0", () => {
|
||||
assert.equal(parseOmniRoutePluginOptions({ autoSyncIntervalMs: 0 }).autoSyncIntervalMs, 0);
|
||||
assert.equal(parseOmniRoutePluginOptions({ autoSyncIntervalMs: 120_000 }).autoSyncIntervalMs, 120_000);
|
||||
});
|
||||
|
||||
test("resolveOmniRoutePluginOptions defaults autoSyncIntervalMs to 300000", () => {
|
||||
const r = resolveOmniRoutePluginOptions({});
|
||||
assert.equal(r.autoSyncIntervalMs, DEFAULT_AUTO_SYNC_INTERVAL_MS);
|
||||
});
|
||||
|
||||
test("resolveOmniRoutePluginOptions clamps low positive autoSyncIntervalMs", () => {
|
||||
const r = resolveOmniRoutePluginOptions({ autoSyncIntervalMs: 5000 });
|
||||
assert.equal(r.autoSyncIntervalMs, MIN_AUTO_SYNC_INTERVAL_MS);
|
||||
});
|
||||
|
||||
test("invalidateOmniRouteFetchCache clears by baseURL prefix", () => {
|
||||
const cache: OmniRouteFetchCache = new Map();
|
||||
cache.set("https://a.example/v1::abc", {
|
||||
rawModels: [],
|
||||
rawCombos: [],
|
||||
rawAutoCombos: [],
|
||||
rawEnrichment: new Map(),
|
||||
rawCompressionCombos: [],
|
||||
rawConnections: [],
|
||||
expiresAt: Date.now() + 1000,
|
||||
});
|
||||
cache.set("https://b.example/v1::def", {
|
||||
rawModels: [],
|
||||
rawCombos: [],
|
||||
rawAutoCombos: [],
|
||||
rawEnrichment: new Map(),
|
||||
rawCompressionCombos: [],
|
||||
rawConnections: [],
|
||||
expiresAt: Date.now() + 1000,
|
||||
});
|
||||
const removed = invalidateOmniRouteFetchCache(cache, "https://a.example/v1");
|
||||
assert.equal(removed, 1);
|
||||
assert.equal(cache.size, 1);
|
||||
assert.equal(cache.has("https://b.example/v1::def"), true);
|
||||
});
|
||||
|
||||
test("forceSyncOmniRouteModels: fetches, populates cache, returns count", async () => {
|
||||
const cache: OmniRouteFetchCache = new Map();
|
||||
const resolved = resolveOmniRoutePluginOptions({
|
||||
providerId: "omniroute",
|
||||
baseURL: "https://omniroute.example/v1",
|
||||
autoSyncIntervalMs: 0,
|
||||
features: {
|
||||
combos: false,
|
||||
autoCombos: false,
|
||||
enrichment: false,
|
||||
compressionMetadata: false,
|
||||
usableOnly: false,
|
||||
diskCache: false,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await forceSyncOmniRouteModels({
|
||||
resolved,
|
||||
cache,
|
||||
readAuthJson: async () => ({
|
||||
omniroute: { type: "api", key: "test-key" },
|
||||
}),
|
||||
fetcher: async () => [
|
||||
{ id: "model-a", object: "model" },
|
||||
{ id: "model-b", object: "model" },
|
||||
],
|
||||
now: () => 1_000_000,
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.count, 2);
|
||||
assert.equal(result.provider, "omniroute");
|
||||
assert.equal(cache.size, 1);
|
||||
const entry = [...cache.values()][0];
|
||||
assert.equal(entry.rawModels.length, 2);
|
||||
assert.equal(entry.expiresAt, 1_000_000 + resolved.modelCacheTtl);
|
||||
});
|
||||
|
||||
test("forceSyncOmniRouteModels: missing auth returns error", async () => {
|
||||
const cache: OmniRouteFetchCache = new Map();
|
||||
const resolved = resolveOmniRoutePluginOptions({
|
||||
providerId: "omniroute",
|
||||
baseURL: "https://omniroute.example/v1",
|
||||
autoSyncIntervalMs: 0,
|
||||
features: { diskCache: false },
|
||||
});
|
||||
const result = await forceSyncOmniRouteModels({
|
||||
resolved,
|
||||
cache,
|
||||
readAuthJson: async () => ({}),
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
assert.match(result.error ?? "", /credentials|baseURL|connect/i);
|
||||
});
|
||||
10
AGENTS.md
10
AGENTS.md
@@ -570,16 +570,12 @@ This repository is a fork of `diegosouzapw/OmniRoute`. Keep fork-only operationa
|
||||
changes (for example GHCR image publishing, personal deployment workflows, or local
|
||||
automation) out of upstream contribution PRs.
|
||||
|
||||
When preparing a PR for upstream, always start the work branch from the upstream
|
||||
**default branch** — the active `release/vX.Y.Z` line (today `release/v3.8.49`).
|
||||
Never branch from `main`: `main` only receives release squash-merges, so a branch
|
||||
cut there is weeks behind and produces conflict-heavy PRs
|
||||
(see `CONTRIBUTING.md` and `docs/ops/BRANCHING_MODEL.md`):
|
||||
When preparing a PR for upstream, always start the work branch from `upstream/main`,
|
||||
not from this fork's `main`:
|
||||
|
||||
```bash
|
||||
git fetch upstream
|
||||
# the default branch is the active release line, e.g. release/v3.8.49
|
||||
git switch -c <branch-name> upstream/release/vX.Y.Z
|
||||
git switch -c <branch-name> upstream/main
|
||||
```
|
||||
|
||||
Only cherry-pick or reapply the changes intended for the upstream PR.
|
||||
|
||||
@@ -198,14 +198,11 @@ Coverage notes:
|
||||
|
||||
### Pull Request Requirements
|
||||
|
||||
Before opening a PR, run the focused loop for what you changed. The full unit suite
|
||||
(4 CI shards), Vitest, the **60%+** coverage gate, and the production build are CI's
|
||||
responsibility — running them locally adds no signal the PR checks will not already
|
||||
give you, and on smaller machines it can saturate the host (#8084):
|
||||
Before opening or merging a PR:
|
||||
|
||||
- Run the test files that cover your change: `node --import tsx/esm --test tests/unit/<file>.test.ts`
|
||||
- Run `npm run lint`
|
||||
- Include or update automated tests in the same PR whenever production code changes
|
||||
- Run `npm run test:unit`
|
||||
- Run `npm run test:coverage`
|
||||
- Ensure the coverage gate stays at **60%+** statements/lines/functions/branches
|
||||
- Include the changed or added test files in the PR description when production code changed
|
||||
- Check the SonarQube result on the PR when the project secrets are configured in CI
|
||||
|
||||
@@ -231,31 +228,6 @@ Current test status: **122 unit test files** covering:
|
||||
- **Zod validation** — Use Zod v4 schemas for all API input validation
|
||||
- **Naming**: Files = camelCase/kebab-case, components = PascalCase, constants = UPPER_SNAKE
|
||||
|
||||
### Error handling / empty catch blocks
|
||||
|
||||
Never leave a `catch` unexplained. Classify it into one of two buckets (operationalizes
|
||||
the hard rule "never silently swallow errors in SSE streams"):
|
||||
|
||||
- **Intentional (our own best-effort cleanup/telemetry)** — a failure here is expected and
|
||||
harmless; add a one-line rationale comment, no logging (logging on every request is the
|
||||
noise this convention avoids).
|
||||
|
||||
```ts
|
||||
} catch {} // closing an already-closed controller after client disconnect is expected
|
||||
```
|
||||
|
||||
- **Should log (external/caller-supplied code, or the swallow changes control flow)** — keep
|
||||
the catch (never let it break the stream) but emit a contextual `console.debug`/`warn` so the
|
||||
failure is discoverable.
|
||||
|
||||
```ts
|
||||
} catch (e) {
|
||||
console.debug("[STREAM] onFailure callback error:", e);
|
||||
}
|
||||
```
|
||||
|
||||
See `open-sse/utils/stream.ts` and `open-sse/utils/streamHandler.ts` for applied examples.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -27,7 +27,7 @@ When creating _any_ validation tests or one-off logic scripts, default to using
|
||||
7. **Never bypass Husky hooks** (`--no-verify`, `--no-gpg-sign`) without explicit operator approval.
|
||||
8. **Always validate inputs with Zod schemas** from `src/shared/validation/schemas.ts`.
|
||||
9. **Always include tests when changing production code** (`src/`, `open-sse/`, `electron/`, `bin/`).
|
||||
10. **Coverage must stay** ≥ 60 % statements / lines / functions / branches — the official CI gate (`npm run test:coverage`). The ratchet baseline in `quality-baseline.json` may freeze a higher floor; never regress it.
|
||||
10. **Coverage must stay** ≥ 75 % statements / 75 % lines / 75 % functions / 70 % branches (real measured: ~82 %).
|
||||
|
||||
## 3. Codebase navigation
|
||||
|
||||
|
||||
@@ -78,15 +78,18 @@ export function registerBackup(program) {
|
||||
if (exitCode !== 0) process.exit(exitCode);
|
||||
});
|
||||
|
||||
// Legacy: `omniroute backup` without a subcommand still creates a backup
|
||||
// (documented as the canonical usage in USER_GUIDE.md / CLI-TOOLS.md /
|
||||
// AGENT-SKILLS.md). No flags are declared here — declaring the same
|
||||
// option names as `create`/`auto enable` here previously shadowed them
|
||||
// (#8512), and no doc shows `omniroute backup` invoked with flags.
|
||||
// Legacy: `omniroute backup` without subcommand still creates a backup
|
||||
backup.action(async (opts) => {
|
||||
const exitCode = await runBackupCommand(opts);
|
||||
if (exitCode !== 0) process.exit(exitCode);
|
||||
});
|
||||
backup
|
||||
.option("--name <name>", t("backup.nameOpt"))
|
||||
.option("--cloud", t("backup.cloudOpt"))
|
||||
.option("--encrypt", t("backup.encryptOpt"))
|
||||
.option("--key-file <path>", t("backup.keyFileOpt"))
|
||||
.option("--exclude <pattern>", t("backup.excludeOpt"), (v, prev = []) => [...prev, v], [])
|
||||
.option("--retention <n>", t("backup.retentionOpt"), parseInt);
|
||||
}
|
||||
|
||||
export function registerRestore(program) {
|
||||
|
||||
@@ -26,7 +26,6 @@ function wantsProviderSetup(opts) {
|
||||
|
||||
async function resolvePassword(opts, prompt, nonInteractive) {
|
||||
if (opts.password) return opts.password;
|
||||
if (process.env.INITIAL_PASSWORD) return process.env.INITIAL_PASSWORD;
|
||||
if (nonInteractive) return "";
|
||||
|
||||
const answer = await prompt.ask("Set an admin password now? [y/N]", "N");
|
||||
|
||||
@@ -5,32 +5,13 @@ import { ensureSettingsSchema, hashManagementPassword, updateSettings } from "./
|
||||
|
||||
async function loadSqlite() {
|
||||
if (process.versions.bun) {
|
||||
return { Database: (await import("bun:sqlite")).Database };
|
||||
return (await import("bun:sqlite")).Database;
|
||||
}
|
||||
try {
|
||||
return { Database: (await import("better-sqlite3")).default };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
}
|
||||
|
||||
// #7586: unlike the real server (src/lib/db/adapters/driverFactory.ts::tryOpenSync),
|
||||
// this CLI helper historically had NO fallback beyond better-sqlite3 — so on any
|
||||
// machine where better-sqlite3's native binary is unavailable (Windows without a
|
||||
// prebuilt addon, etc.), every `omniroute doctor` DB check reported a false FAIL
|
||||
// even when the actual server was healthy via its own (correct) driver cascade.
|
||||
// Reuse that same cascade here instead of re-deriving it.
|
||||
async function openWithSyncDriverFallback(dbPath, options, importError) {
|
||||
try {
|
||||
const { tryOpenSync } = await import("../../src/lib/db/adapters/driverFactory.ts");
|
||||
const adapter = tryOpenSync(dbPath, options);
|
||||
if (adapter) {
|
||||
return adapter;
|
||||
}
|
||||
return (await import("better-sqlite3")).default;
|
||||
} catch {
|
||||
// fall through to the original better-sqlite3 error below
|
||||
throw new Error("better-sqlite3 is not installed. Run npm install before using setup.");
|
||||
}
|
||||
throw createSqliteNativeError(importError);
|
||||
}
|
||||
|
||||
function openBunSqlite(Database, dbPath, options) {
|
||||
@@ -110,25 +91,19 @@ export function createSqliteNativeError(error) {
|
||||
}
|
||||
|
||||
async function openSqliteDatabase(dbPath, options = {}) {
|
||||
const loaded = await loadSqlite();
|
||||
const Database = await loadSqlite();
|
||||
if (process.versions.bun) {
|
||||
if (options.fileMustExist && !fs.existsSync(dbPath)) {
|
||||
throw new Error(`SQLite file does not exist: ${dbPath}`);
|
||||
}
|
||||
const bunOptions = options.readonly
|
||||
options = options.readonly
|
||||
? { readonly: true }
|
||||
: { readwrite: true, create: options.fileMustExist !== true };
|
||||
try {
|
||||
return openBunSqlite(loaded.Database, dbPath, bunOptions);
|
||||
} catch (error) {
|
||||
throw createSqliteNativeError(error);
|
||||
}
|
||||
}
|
||||
if (loaded.error) {
|
||||
return openWithSyncDriverFallback(dbPath, options, loaded.error);
|
||||
}
|
||||
try {
|
||||
return new loaded.Database(dbPath, options);
|
||||
return process.versions.bun
|
||||
? openBunSqlite(Database, dbPath, options)
|
||||
: new Database(dbPath, options);
|
||||
} catch (error) {
|
||||
throw createSqliteNativeError(error);
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
- refactor(sse): classify SSE critical-path empty catches + add CONTRIBUTING convention (#8142)
|
||||
@@ -1 +0,0 @@
|
||||
- feat(db): persist caller session tag into call_logs for per-session cost attribution (#8249)
|
||||
@@ -1 +0,0 @@
|
||||
- feat(api): quota-aware fallback routing for web-fetch providers (#8297)
|
||||
@@ -1 +0,0 @@
|
||||
- feat(providers): map upstream reasoning-level metadata in openai-compatible discovery (#8347)
|
||||
@@ -1 +0,0 @@
|
||||
- **feat(adobe-firefly):** reference-image attach for generate + OpenAI `/v1/images/edits` support (follow-up to #8006). Uploads sources to Firefly storage (`POST /v2/storage/image`), then submits `referenceBlobs` on 3P generate-async (nano multi-ref `usage:general`; gpt-image `usage:subject`). Wire matches live `firefly.adobe.com` captures. Also routes built-in edits to the same path (up to 4 refs).
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): expose a base-URL override for Kimi/Moonshot so CN-region API keys (issued on platform.kimi.com / moonshot.cn) can be pointed at api.moonshot.cn instead of being rejected by the international host (#7447)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(sse): stop stream readiness from treating a choices-less mid-stream error frame as a successful stream, so combo can fail over instead of returning zero `choices` (#7503)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(cli): fall back to the node:sqlite driver cascade in `bin/cli/sqlite.mjs` so `omniroute doctor` no longer reports a false "FAIL Database"/"FAIL Storage/encryption" on machines without a working better-sqlite3 native binary (#7586)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): expose Responses-API-format (OpenAI/Codex) chat models on every VS Code Ollama-compatible listing route, not just `/models` (#7587)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(backend): stop `buildClientRawRequest` deep-cloning the whole request body on every chat request (#7847) — every consumer of `clientRawRequest.body` is observability and keeps at most a bounded copy, so the unbounded clone retained ~41x more than anything used it (3.19 MiB vs 0.08 MiB on a 3.05 MiB / 729-message agent request). Also makes `cloneBoundedForLog` idempotent: arrays, objects and strings all exceeded their own bounds once the truncation marker was added, so re-bounding an already bounded payload silently dropped a further item and misreported the original length
|
||||
@@ -1 +0,0 @@
|
||||
- fix(sse): copy the combo attempt body shallowly instead of deep-cloning it per target (#7847) — the deep clone cost 9.53 MiB at 3 targets and scaled linearly with the target count (31.78 MiB at 10) on a 3.05 MiB agent request, while the isolation it provided only ever needed to contain top-level scalar writes. Also fixes a real cross-target leak in round-robin, which copied the body only when the reasoning buffer changed `max_tokens` and otherwise shared the caller's object, so a Background Task Redirection on one target rewrote `body.model` for the next
|
||||
@@ -1 +0,0 @@
|
||||
- fix(sse): estimate the combo fallback-compression trigger from the request object instead of `JSON.stringify(...)` (#7847) — the string path charged an inline base64 image as if every character were prose (~50k tokens instead of ~1.2k on a 200 KB image), falsely tripping compression on requests nowhere near the context window; the same over-count #8368/#8401 fixed elsewhere. Adds `jsonLength()`, an exact serialized-length walker (property-tested against `JSON.stringify`), and uses it for the readiness-timeout and token estimates so a multi-megabyte body is no longer materialized as a string just to be measured
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): route noauth opencode-zen connections through their assigned proxy
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): repoint the zai-web executor at chat.z.ai's current v2 chat-completions endpoint, fixing model-independent 404s (#8014)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** path-shaped multimodal model ids (e.g. `cp/cline-pass/kimi-k3`) resolve native vision via leaf/registry metadata instead of triggering Vision Bridge ([#8032](https://github.com/diegosouzapw/OmniRoute/issues/8032)) — thanks @Prudhvivuda
|
||||
@@ -1 +0,0 @@
|
||||
- fix(translator): set `status: "completed"` on translated OpenAI Responses `input` items so strict Responses-compatible upstreams stop rejecting them with 400 MissingParameter input.status (#8083)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): classify HTTP 400 model-unavailable as MODEL_NOT_FOUND so Antigravity Pro fallback locks out the deprecated model
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): carve cookie-auth providers out of terminal 401 'expired' classification so one 401 cooldowns instead of killing the connection
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): classify per-model-quota 403 and DEGRADED 400 as model-unhealthy in checkFallbackError (#8247, #8248)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** reconcile Kimi K3 vision metadata when synced `attachment=false` contradicts image/video `modalities_input`, so `supportsVision`, `attachment`, and exposed modalities agree ([#8250](https://github.com/diegosouzapw/OmniRoute/issues/8250)) — thanks @Prudhvivuda
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): fold namespace into the flattened Chat tool name so cross-namespace leaves do not collide
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(runtime):** sanitize Muse Spark fetch failures before logging and resolve the sql.js WASM asset without the unexported package metadata subpath ([#8298](https://github.com/diegosouzapw/OmniRoute/pull/8298)) — thanks @backryun
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(sse):** Combo middleware preserves OpenAI Responses request bodies and maps combo system overrides to `instructions`, preventing Responses-native fallbacks from receiving an unsupported `messages` parameter. ([#8310](https://github.com/diegosouzapw/OmniRoute/pull/8310)) — thanks @ridho9
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): accept the current compatible-provider connection id scheme in the models test route (#8326)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): stop leaking the internal provider UUID in /v1/models and honor the configured prefix (#8327)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(dashboard): show custom provider_nodes providers in the Topology view instead of only AI_PROVIDERS (#8328)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): stop the 2000-token safety buffer from inflating usage.prompt_tokens in the client response (#8331)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(backend): keep combo routing from dispatching image requests to text-only targets (#8332)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(sse): strip third-party-agent signals from the Hermes system prompt that trigger Anthropic 400 extra-usage (#8350)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(i18n):** Restore brand/model proper nouns (Claude, OpenAI, Anthropic, Gemini, MiniMax, etc.) in zh-CN and zh-TW — replace Chinese phonetic/translation forms (克劳德/打开Ai/人择/双子座) with original English, unify "provider" translation to "供应商/供應商", and apply zh-TW localized terminology (網路/設定/檔案/新增/啟用/搜尋/儲存) instead of mainland defaults (#8355 — thanks @ikelvingo).
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): estimate inline base64 image tokens instead of counting the data URL as text so it does not falsely exceed the context window (#8368)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(backend): stop prompt-cache affinity from silently reordering an explicit priority combo across models (#8370)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): accept a missing status query-param on GET /api/plugins instead of rejecting null with Invalid status value (#8374)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(resilience): treat an unreachable-proxy ECONNREFUSED as a circuit-breaker event so combo fails over instead of hitting the 503 max-retry limit (#8376)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(backend): make disabling the global per-key proxy toggle override existing per-key proxy assignments (#8385)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(dashboard): persist compression engine detail settings (Headroom / session dedup / CCR) instead of dropping them on save (#8388)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(plugins): fire registered+active plugin hooks (onRequest/onResponse/onError) during proxying instead of never invoking them (#8395)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(resilience): cap the connection cooldown after a 429 burst so combo fallback is not blacked out past the real rate-limit window (#8396)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): reach synced model_capabilities rows for canonical provider ids that only appear as an alias in MODELS_DEV_PROVIDER_MAP, e.g. codex/claude (#8429)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): stop marking a multi-quota-window provider exhausted when only some windows are depleted (LIMIT-200 snapshot eviction drops idle healthy windows) (#8431)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(backend): compute AgentBridge diagnose `dnsConfigured` per-agent instead of hard-coded to Antigravity hosts (#8466)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(providers):** OpencodeExecutor honors Extra API Keys rotation via `resolveEffectiveKey` (empty primary + extras no longer omit Authorization) ([#8467](https://github.com/diegosouzapw/OmniRoute/issues/8467)) — thanks @Prudhvivuda
|
||||
@@ -1 +0,0 @@
|
||||
- fix(sse): stop combo's "all targets failed" response from attaching one target's retry-after window to an unrelated target's error message (#8486)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(providers): persist a runtime-discovered Antigravity projectId back onto the connection so it survives token refreshes and restarts instead of being rediscovered or lost (#8491)
|
||||
@@ -1 +0,0 @@
|
||||
- fix(cli): `backup create` / `backup auto enable` — option shadowing by the parent `backup` command removed; all flags (`--cloud`, `--encrypt`, `--retention`, `--name`, `--exclude`, `--key-file`) now reach the subcommand handler with their actual values instead of being silently discarded
|
||||
@@ -1 +0,0 @@
|
||||
- fix(api): surface a non-blocking warning + startup scan when a combo name shadows a real model id, instead of silently routing with zero signal (#8530)
|
||||
@@ -1 +0,0 @@
|
||||
- **fix(kiro):** support profileless Builder ID quota, preserve CLI auth identity, stabilize social OAuth polling, and use the live model catalog ([#8565](https://github.com/diegosouzapw/OmniRoute/pull/8565)) — thanks @nguyenha935
|
||||
@@ -1 +0,0 @@
|
||||
- **chore(quality):** rebaseline complexity (2130→2183) and cognitive-complexity (951→968) across the v3.8.49 `/merge-prs` queue-drain. The first step (→2169/→956) cleared inherited base drift measured on the pristine release tip (the PR→release fast-path never ratchets these). The second step (→2183/→968) absorbs the aggregate own-growth of the 41-PR merge-train (each PR under-ceiling individually; the combined batch adds +14/+12). Owner-approved (2026-07-25); structural shrink tracked in [#3501](https://github.com/diegosouzapw/OmniRoute/issues/3501).
|
||||
@@ -1 +0,0 @@
|
||||
- **chore(combo):** extract 8 pure error predicates and quota status helpers (`clampPercent`, `quotaRemainingPercentFromQuota`, `normalizeConnectionStatus`, `hasFutureRateLimitUntil`, `getConnectionStatusQuotaCutoffReason`, `isContextOverflow400`, `isParamValidation400`, `isModelScoped400`) from `open-sse/services/combo.ts` into `open-sse/services/combo/comboPredicates.ts` — pure move, zero behavior change; `combo.ts` shrinks from 3,651 to 3,554 lines while maintaining backward-compatible re-exports.
|
||||
@@ -1 +0,0 @@
|
||||
- chore(validation): decompose `src/lib/providers/validation.ts` (→ 442 lines) by extracting the web-cookie, kiro and specialty inline validators into `validation/*` leaves — behavior-preserving move; the specialty validators that captured `isLocal` from the enclosing closure now take it as an explicit parameter, with the host dispatcher passing it at each call site
|
||||
@@ -1 +0,0 @@
|
||||
- chore(token-refresh): decompose `open-sse/services/tokenRefresh.ts` (999 → 724 lines) by extracting the rotation-map, CAS guard and circuit-breaker refresh logic into `tokenRefresh/*` leaves — behavior-preserving move; `tokenRefresh.ts` still re-exports the moved symbols so the public surface is unchanged
|
||||
@@ -1 +0,0 @@
|
||||
- chore(usage): decompose `open-sse/services/usage.ts` (999 → 253 lines) by extracting the crof, nanogpt, qoder, opencode, deepseek, bailian, vertex, xiaomi-mimo, xai and github usage fetchers into `usage/*` leaves — behavior-preserving move, the file is now a thin provider→fetcher dispatcher and the public import surface is unchanged
|
||||
@@ -1 +0,0 @@
|
||||
- chore(perf): add `npm run bench:heap-body` — a deterministic benchmark that attributes retained V8 heap to each request-body copy on the chat path (entry log clone, combo per-target clone, token-estimation string), reproducing the #7847 incident shape (3.05 MiB / 729 messages / 86 tools) so the clone-amplification work can be justified and regression-guarded with numbers instead of intuition
|
||||
@@ -1 +0,0 @@
|
||||
- chore(ci): resync the stale `no-explicit-any` suppression count for `tests/unit/proxy-registry.test.ts` (frozen at 55, actual 54 since #8447) — ESLint was failing the whole run with "There are suppressions left that do not occur anymore", turning `npm run lint` red on `release/v3.8.49` for every PR branched off it
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"_comment": "Catraca de complexidade (check-complexity.mjs, ESLint core rules complexity>=15 e max-lines-per-function>80 sobre src+open-sse+electron+bin via eslint.complexity.config.mjs). Conta total de violacoes; so pode cair. --update ratcheta.",
|
||||
"_rebaseline_2026_07_25b_v3849_mergetrain_owngrowth": "Owner-approved (chat, 2026-07-25): 2169->2183 (+14). v3.8.49 /merge-prs 41-PR merge-train aggregate own-growth: measured 2183 on the combined boarded tree (tip ac15014ca7) vs 2169 on the pristine release tip. Each boarded PR sits under the ceiling individually, but the combined batch adds +14 (new branches in #8378 chatCore contextLimit / #8432 cursor native_todo / #8476 combo input-bound / #8526 combo select-all modals / etc — the pre-screen-flagged complexity-growth set). Same merge-burst-inherited-drift class as the notes below; owner chose absorbing the ceiling over per-PR helper-extraction churn. Structural shrink stays debt (#3501); tighten via --update next cycle.",
|
||||
"_rebaseline_2026_07_25_v3849_mergequeue_drain": "Owner-approved (chat, 2026-07-25): 2130->2169 (+39). v3.8.49 /merge-prs queue-drain: the cycle's merge burst (the 8 base-red slices + owner PRs + parallel-session merges #8500-8508) accrued inherited cyclomatic drift the fast-path PR->release never ratchets (check:complexity does not run on PR->release). Measured 2169 on the pristine release tip 4053e2314a alone (BEFORE any queue PR boards) — so the entire +39 is base drift already on the tip, not any queued PR's own growth. Every merge-ready PR in the queue was tripping Fast Quality Gates on this shared base-red. Owner approved raising the ceiling to the measured tip value so the ~34-PR merge-train lands without per-PR helper-extraction churn. Structural shrink stays debt (#3501); tighten via --update next cycle.",
|
||||
"count": 2183,
|
||||
"_rebaseline_2026_07_20_owner_night_drain": "Owner-approved (chat, 2026-07-20 ~00:50): 2072->2130. The day's 17 merged PRs consumed the entire slack (tip at 2069/2072); queue PRs #6973(+4)/#7662(+2)/#7719(+1) plus the #7744/#7779 reworks were collectively blocked. Owner chose a wide margin for the remainder of the v3.8.49 cycle instead of per-PR extraction.",
|
||||
"count": 2130,
|
||||
"_rebaseline_2026_07_19_v3849_fix_sweep_cluster": "2059->2072 (owner-approved, 2026-07-19). /fix-prs validation-train sweep: a cluster of otherwise-clean contributor PRs (#6973/#7683/#7662/#7672/#7633/#7767, each +1/+2 cyclomatic own-growth from new provider/auth/combo branches) collectively pushed the count from tip 2056 to 2068. Individually all but #6973 sit under the old 2059 baseline; combined they exceed it. The tip had only 3 units of slack (2056 vs 2059), so every new-feature PR was tripping the ratchet (this was the 4th such block of the day after #7695/#7747/#7768). Owner approved raising the ceiling to 2072 = combined-cluster 2068 + 4 units headroom, so the cluster lands without per-PR helper-extraction churn and near-term feature PRs have breathing room. Measured 2068 on the 9-PR combined probe tree. Structural shrink stays debt (#3501); tighten via --update next cycle.",
|
||||
"_rebaseline_2026_07_18_pr7360_quota_visibility_resync": "2058->2059 (+1 vs recorded ceiling; measured 2056 fresh on release tip cab9e5f0c alone, so this ceiling still carries 2 units of un-banked slack from prior shrinkage — real regression from this merge is 2056->2059, +3). PR #7360 (JxnLexn) release-resync: merging origin/release/v3.8.49 to resolve the 3-file conflict (ConnectionRow.tsx/ConnectionsListPanel.tsx/useProviderConnections.ts) unions two already-compliant features in the same already-oversized god-component: release's confirm-delete-account wiring (#7361) and this PR's per-connection quota-visibility wiring. Diffed release-tip-only vs merged violation lists (scripts dumped via getComplexityEslintReport): most entries are the SAME pre-existing violations shifted a few lines (ConnectionRow/getStatusPresentation/inferErrorType — no count change) or marginally bigger (ConnectionRow function complexity 85->86, ConnectionsListPanel function 498->510 lines) from the two ConnectionRow call sites each gaining both PRs' multi-line JSX props. The 2 genuinely NEW crossings are the 'no tag' and 'tagged groups' .map() render callbacks in ConnectionsListPanel.tsx (83 and 85 lines, was <=80 on both parents individually) tipping over 80 lines specifically because both PRs' props land on the same call sites. No new logic was written during the resync itself (only import-statement unions); the growth is inherent to combining the two already-reviewed feature branches. Structural shrink tracked in #3501. Tighten via --update next cycle (true floor is 2056, not 2058).",
|
||||
"_rebaseline_2026_07_17_v3849_ownerprs_providers": "2056->2058 (+2). v3.8.49 owner-PR merge campaign own-growth: the new provider handlers/dispatch branches merged this cycle (freetheai/felo/notion/segmind/deepinfra/novita/msdesigner image+video handlers, each adding a format-dispatch guard) pushed cyclomatic violations 2056->2058. Fast-gates PR->release do not run the complexity ratchet, so this surfaced only on re-sync. Spread across the new leaf handlers (not a single extractable function); measured on the release tip. Structural shrink tracked in #3501.",
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"open-sse/executors/claudeIdentity.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"open-sse/executors/cliproxyapi.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -410,6 +415,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/shared/components/KiroSocialOAuthModal.tsx": {
|
||||
"react-hooks/exhaustive-deps": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/shared/components/LanguageSelector.tsx": {
|
||||
"@next/next/no-img-element": {
|
||||
"count": 1
|
||||
@@ -880,6 +890,11 @@
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"tests/unit/claude-to-openai-think-close-5123.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"tests/unit/cli-a2a-invoke-commands.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 16
|
||||
@@ -1932,7 +1947,7 @@
|
||||
},
|
||||
"tests/unit/proxy-registry.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 54
|
||||
"count": 55
|
||||
}
|
||||
},
|
||||
"tests/unit/proxy-resolution-status-filter.test.ts": {
|
||||
@@ -2252,7 +2267,7 @@
|
||||
},
|
||||
"tests/unit/translator-claude-to-gemini.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 17
|
||||
"count": 14
|
||||
}
|
||||
},
|
||||
"tests/unit/translator-claude-to-openai.test.ts": {
|
||||
@@ -2272,7 +2287,7 @@
|
||||
},
|
||||
"tests/unit/translator-openai-to-gemini.test.ts": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 74
|
||||
"count": 68
|
||||
}
|
||||
},
|
||||
"tests/unit/translator-openai-to-kiro.test.ts": {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"_rebaseline_2026_07_25_8499_ts7_result_union_predicates": "PR #8499 (backryun, chore/ts7-types-executor-scattered) own growth: muse-spark-web.ts 1396->1405 (+9, irreducible). Under this workspace's `strictNullChecks: false`, the boolean-literal discriminant on `GraphqlResult` (`{ ok: true } | { ok: false; error: string }`) narrows the positive `.ok===true` branch but leaves `!result.ok` at the full union under TS7, making `.error` unreachable to the checker at the two call sites (warmup, mode-switch). Fixed by adding a single `isGraphqlFailure()` type-predicate helper (doc comment + 3-line body) reused at both call sites instead of duplicating the predicate inline — not extractable to a shared module without splitting a single-file executor's local narrowing helper out of its own file. Covered by the existing muse-spark-web executor test suite (no behavior change, pure narrowing fix).",
|
||||
"_rebaseline_2026_07_22_8131_windowshide_cloudflared_spawn": "PR #8167 (Dingding-leo, fix/windows-hide-child-process, #8131) own growth: src/lib/cloudflaredTunnel.ts 934->935 (+1, irreducible call-site wiring — the single `windowsHide: true` option added to the existing cloudflared spawn() options object so no transient conhost.exe/cmd console window flashes open on Windows). Covered by the pre-merge-fix regression test tests/unit/windows-hide-child-process-spawns-8131.test.ts (added for the two additional spawn() sites the PR missed: ServiceSupervisor.ts, versionManager/processManager.ts) plus the windowsHide assertion added to tests/unit/services/installers/runNpm-shell-5379.test.ts (installers/utils.ts buildNpmExecOptions).",
|
||||
"_rebaseline_2026_07_22_8006_adobe_firefly_media_provider": "PR #8006 (artickc, feat/adobe-firefly-media) own growth: adds Adobe Firefly as a media-only (image + video) provider — unofficial IMS/cookie-session bridge for firefly.adobe.com covering IMS cookie->access_token exchange, discovery-catalog fallback, credits/balance usage, and submit+poll dispatch for both image (nano-banana/gpt-image families) and video (Sora 2/Veo 3.1/Kling 3.0) generation, with 408-under-load retry handling. New leaf open-sse/services/adobeFireflyClient.ts frozen at 1958 (>>cap 800) — a single self-contained upstream client (mirrors the qoderCli.ts precedent for a new provider client that is legitimately large on day one: IMS auth, cookie/JWT normalization, payload builders for 2 media types x multiple model families, SSE-less submit/poll state machine, error sanitization); not extractable without scattering a single upstream integration across artificial module boundaries mid-PR. open-sse/config/imageRegistry.ts (existing, previously under cap) grows 800->821 (+21, the new adobe-firefly IMAGE_PROVIDERS entry + models list, additive registry data at the existing registry chokepoint). src/lib/usage/providerLimits.ts 1000->1003 (+3, adobe-firefly/firefly added to the existing apikey-usage-fetcher allowlist, irreducible call-site wiring mirroring the sibling #7994 PromptQL/HyperAgent entries in the same PR group). Covered by tests/unit/adobe-firefly.test.ts (35/35). Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_07_22_7994_hyperagent_web_provider": "PR #7994 (artickc, feat/hyperagent-web) own growth: adds HyperAgent (hyperagent.com) as a new unofficial web-cookie chat provider, reverse-engineered from live SPA captures (thread/session SSE flow, credits/usage endpoint). New leaf open-sse/executors/hyperagent.ts frozen at 937 (>cap 800) — single self-contained executor covering cookie auth, SSE parsing (text/session_start/session_end/done events), and a sticky thread/session cache for multi-turn continuity; not extractable without splitting the executor mid-request-flow (mirrors the sseParser.ts/muse-spark-web.ts precedent for new provider executors that exceed cap on day one). src/lib/usage/providerLimits.ts 1000->1003 (+3, irreducible call-site wiring adding hyperagent/ha to the existing USAGE_FETCHER_PROVIDERS-style allowlist at the chokepoint other web-cookie providers already extend). Covered by tests/unit/executor-hyperagent.test.ts (16/16). Structural shrink tracked in #3501.",
|
||||
@@ -176,7 +175,7 @@
|
||||
"open-sse/executors/duckduckgo-web.ts": 925,
|
||||
"open-sse/executors/grok-web.ts": 1873,
|
||||
"open-sse/executors/hyperagent.ts": 937,
|
||||
"open-sse/executors/muse-spark-web.ts": 1405,
|
||||
"open-sse/executors/muse-spark-web.ts": 1394,
|
||||
"open-sse/executors/perplexity-web.ts": 1032,
|
||||
"open-sse/handlers/audioSpeech.ts": 1061,
|
||||
"open-sse/handlers/chatCore.ts": 5125,
|
||||
@@ -187,18 +186,15 @@
|
||||
"open-sse/handlers/videoGeneration.ts": 1275,
|
||||
"_rebaseline_2026_07_22_8010_codex_responses_engine": "PR #8010 (@JxnLexn) own growth: open-sse/mcp-server/schemas/tools.ts 1497->1505 (+8 = threading the new \"codex-responses\" literal into the compressionConfigureInput strategy/autoTriggerMode Zod enums and setCompressionEngineInput engine enum, mirroring the existing rtk/omniglyph enum entries; no new tool). open-sse/services/compression/strategySelector.ts 1043->1054 (+11 = one new `if (mode === \"codex-responses\")` dispatch branch in runCompression that delegates 100% to the new codexResponsesEngine.apply, mirroring the existing rtk single-mode dispatch, plus threading config.codexResponsesConfig.preserveToolNames into the shared adaptBodyForCompression call at the 3 existing call sites). src/lib/db/compression.ts (untracked, new-file cap 800) 794->845 (+51 = normalizeCodexResponsesConfig, mirroring the existing normalizeRtkConfig normalizer, plus registering \"codex-responses\" in the COMPRESSION_MODES/STACKED_PIPELINE_ENGINE_IDS/SINGLE_MODE_ENGINE sets and the getCompressionSettings load/save switch) — added to the baseline at its current size. All three are cohesive dispatch/normalizer wiring at existing chokepoints (mirroring the prior compression-mode rebaselines #6534/#6556), not extractable without hiding the mode-dispatch boundary. Covered by tests/unit/compression/codex-responses.test.ts (6) + omniglyph-registries.test.ts/types.test.ts (22, updated for the new mode).",
|
||||
"_rebaseline_2026_07_22_8034_compression_exclusions_persistence": "#8034 (compression exclusions) own growth: src/lib/db/compression.ts 845->850 (+5 = threading the new compressionExclusions field through the existing getCompressionSettings/saveCompressionSettings load/save switch over the shared key_value compression namespace — no new table, no raw SQL). Mirrors the prior compression-field rebaselines (#8010 codex-responses normalizer at the same chokepoint); the load/save switch is a single dispatch boundary, not extractable without hiding it. Covered by the PR's 8 node:test + 3 vitest cases.",
|
||||
"_rebaseline_2026_07_24_8388_compression_detail_persist": "#8388 (compression engine DETAIL settings — Headroom/session-dedup/CCR — dropped on save) own growth: src/lib/db/compression.ts 866->872 (+6 = irreducible call-site wiring at the existing getCompressionSettings/updateCompressionSettings chokepoint: one import line, one `...buildDetailConfigDefaults()` spread in the seed config, and one `case \"sessionDedup\": case \"ccr\": applyDetailConfigUpdate(config, key, parsed); break;` load-switch case, mirroring the existing headroom/#8056 case immediately above it). The actual normalizer logic (normalizeSessionDedupConfig/normalizeCcrConfig, matching SESSION_DEDUP_SCHEMA/CCR_SCHEMA bounds) was EXTRACTED into a new leaf src/lib/db/compressionDetailNormalizers.ts (well under cap) so this frozen file only carries the minimal dispatch wiring. Covered by tests/unit/8388-compression-detail-persist.test.ts (schema-accept + full DB save->reload round-trip for both new sub-objects, plus a no-regression assertion on the existing headroom round-trip).",
|
||||
"src/lib/db/compression.ts": 872,
|
||||
"src/lib/db/compression.ts": 866,
|
||||
"open-sse/mcp-server/schemas/tools.ts": 1505,
|
||||
"open-sse/mcp-server/server.ts": 1555,
|
||||
"open-sse/mcp-server/tools/advancedTools.ts": 1120,
|
||||
"_rebaseline_2026_06_27_5193_antigravity_basered": "Base-red (pre-existing release drift, fast-gate PR->release skips check:file-size): accountFallback.ts 1773->1777 and src/app/api/providers/[id]/test/route.ts 924->940 were already over their frozen caps on release/v3.8.39 independent of any antigravity change. Owner chose to rebaseline (keep the documented issue-reference comments #1846/#1449/#347 etc.) rather than accept the contributor comment-stripping in #5200/#5198. Reverted #5200 to restore the comments; bumped these two frozen caps to the actual base sizes. No logic change.",
|
||||
"_rebaseline_2026_07_22_8213_gemini_tpm_quota_cooldown_wait": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/accountFallback.ts 1857->1932 on the merged tip (release 1892 incl #8050 +35, plus this PR own growth +40); measured against the PR own merge-base was 1857->1898 (+41 — the release tip separately carries an unrelated +34 from #8050's antigravity 404 model-not-found lockout scoping, which this PR's branch does not include and this entry does not cover). Own growth is the Gemini TPM-ceiling classification + cooldown-wait wiring feeding into the combo cooldown-wait state machine (rate-limit wedge recovery) introduced by this PR's commit series. Irreducible additions at the existing account-fallback/model-lockout chokepoint. Covered by the PR's own gemini-rate-limit-tracker and TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_23_8252_combo_400_advance": "#8252 (@RaviTharuma) own growth: accountFallback.ts 1932->1940 (+8) + combo.ts 3604->3630 (+26) — advance combo on model-scoped 400s wrapped as invalid/Bad-Request. Irreducible wiring at existing account-fallback + combo dispatch chokepoints. Covered by combo-model-scoped-400-advance.test.ts.",
|
||||
"_rebaseline_2026_07_23_8247_8248_model_unhealthy": "#8247+#8248 own growth: accountFallback.ts 1940->1941 (+1, irreducible import statement only — the substantive #8248 DEGRADED-pattern classifier was extracted into open-sse/config/errorConfig.ts, which has ample headroom, instead of growing this frozen file; #8247's fix is a single existing-line condition change, net zero lines). Scoping the credits-exhausted 403/429 branch to isCompatibleProvider() (per-model-quota openai/anthropic-compatible-* nicknames) so it stays model-scoped instead of terminalling the whole connection, and classifying NVIDIA NIM 'Function ... DEGRADED' 400 bodies as model-access-denied instead of a raw passthrough 400. Covered by tests/unit/8247-accountfallback-model-unhealthy.test.ts and tests/unit/8248-accountfallback-nvidia-degraded.test.ts.",
|
||||
"open-sse/services/accountFallback.ts": 1966,
|
||||
"open-sse/services/adobeFireflyClient.ts": 2317,
|
||||
"_rebaseline_2026_07_25_adobe_firefly_reference_images": "Follow-up to #8006: storage upload + referenceBlobs for image/video and /v1/images/edits dispatch. adobeFireflyClient.ts 1958->2317 (+upload helpers, extract sources, resolve blob ids). Note: 2317 not 2316 — check-file-size.mjs counts LOC via split(\"\\n\").length (counts the trailing-newline empty element), which is 1 higher than `wc -l` on a file ending in \\n; the PR's original entry (2316) was measured with wc -l and undercounted by 1 against the actual gate.",
|
||||
"open-sse/services/accountFallback.ts": 1940,
|
||||
"open-sse/services/adobeFireflyClient.ts": 1958,
|
||||
"open-sse/services/batchProcessor.ts": 915,
|
||||
"open-sse/services/browserBackedChat.ts": 850,
|
||||
"open-sse/services/claudeCodeCompatible.ts": 1202,
|
||||
@@ -207,8 +203,7 @@
|
||||
"_rebaseline_2026_06_24_quota_share_strategy": "Dedicated quota-share strategy (Phase 3 #9): combo.ts 3180->3190 (+10 = one new `else if (strategy === \"quota-share\")` dispatch branch in handleComboChat that delegates 100% to selectQuotaShareTarget + its log line, plus the import). All the new logic lives OUT of the god-file in two new leaves under open-sse/services/combo/: quotaShareInflight.ts (in-flight counter with TTL/lease, ~150 LOC <cap) and quotaShareStrategy.ts (per-model bucket gating via isBucketSaturated + DRR proportional to weight + P2C over in-flight, ~240 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the headroom/reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. ZERO existing strategy cases were modified — only this branch was added, and the qtSd/ combos switched from fill-first to quota-share in src/lib/quota/quotaCombos.ts. Covered by tests/unit/quota-share-strategy.test.ts (gating, DRR fairness, P2C in-flight, fail-open, activation). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_06_24_task_aware_routing": "Task-aware routing strategy (port PR #2045, OmniRoute #4945): combo.ts 3190->3225 (+35) = one new `else if (strategy === \"task-aware\")` dispatch branch delegating 100% to selectTaskAwareTarget + its imports/log lines. All scoring/classification logic lives OUT of the god-file in the new leaf open-sse/services/taskAwareRouting.ts (553 LOC <cap). Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors quota-share/headroom/reset-aware branches). ZERO existing strategy cases modified. Covered by tests/unit/combo-task-aware.test.ts (35 tests). Structural shrink of combo.ts tracked in #3501.",
|
||||
"_rebaseline_2026_07_22_8213_combo_cooldown_wait_recording": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: open-sse/services/combo.ts 3548->3604 (+56, entirely this PR's diff — no other commit touched this file between the PR's merge-base and the release tip). Fixes combo cooldown-wait state recording so a bogus 503 is no longer crystallized when the cooldown-wait vars reset every setTry, adds an OpenAI-format SSE error frame path for combo-exhausted rejections (capturing request body + attempted models), and gives an abandoned per-target dispatch its own timeout instead of leaking a permanent 'pending' dashboard entry. Irreducible additions at the existing handleComboChat dispatch/retry chokepoint (mirrors the prior quota-share/headroom/task-aware strategy-branch precedents already frozen in this file). Covered by the PR's own combo-config + Gemini TPM-ceiling benchmark test additions.",
|
||||
"_rebaseline_2026_07_25_8476_combo_input_bound_homogeneous_scope": "PR #8476 (herjarsa, fix/8375-8459-combo-image-fixes, #8375) own growth: open-sse/services/combo.ts 3642->3679 (+37 net: +29 the PR's own isInputBoundFailure short-circuit for deterministic context_length_exceeded/context_window_exceeded failures, +8 a /green-prs pre-merge fix scoping that short-circuit to homogeneous remainders only — the shipped code fired unconditionally on ANY target, regressing the intentional heterogeneous-combo fallback #6637/isContextOverflow400 protects, exactly as flagged by this PR's own review evidence but never actually implemented in the branch). The fix compares orderedTargets[i+1..] modelStr against the failing target's modelStr at the existing executeTarget dispatch chokepoint (mirrors the sameProviderNext precedent a few lines below) — irreducible call-site wiring, not extractable without hiding the dispatch boundary. Covered by tests/unit/combo-input-bound-failure-8375.test.ts (homogeneous pool still short-circuits) and the new tests/unit/combo-input-bound-heterogeneous-8375.test.ts (heterogeneous combo now correctly falls through to the larger-context target).",
|
||||
"open-sse/services/combo.ts": 3679,
|
||||
"open-sse/services/combo.ts": 3630,
|
||||
"_rebaseline_2026_06_26_fidelity_gate_extraction": "Milestone-B fidelity-gate wiring residual: bodyToText+gateAdvance extracted to fidelityGateStep.ts (889->854, -35), but the StackOptions.fidelityGate field, the `const fidelityGate` reads at the two stacked-loop dispatch chokepoints, and the import of FidelityGateConfig are irreducible wiring that cannot leave strategySelector without an architectural refactor of the pre-existing stacked pipeline. Net: 889->854 (+6 vs the pre-Milestone-B frozen 848). Covered by tests/unit/compression/*.test.ts (940 pass).",
|
||||
"_rebaseline_2026_06_28_5243_risk_gate_prepass": "PR #5243 (compression risk-gate pre-pass) own growth: open-sse/services/compression/strategySelector.ts 854->899 (+45). The three exported entry points (applyCompression/applyStackedCompression/applyStackedCompressionAsync) become thin wrappers over pure-extracted private bodies (runCompression/runStackedCompression/runStackedCompressionAsync) so the risk-gate mask->run->restore wrapper sits strictly OUTSIDE the per-step loop — a single universal integration point. The wrapper logic itself (resolveRiskGate/withRiskGate) lives in the new riskGate/strategyWrap.ts (<cap); the residual growth is the duplicated thin-wrapper signatures + the extracted bodies' dispatch boundary, guarded by a byte-identical parity test (riskGateIntegration). Default off (DEFAULT_COMPRESSION_CONFIG unchanged). Not extractable without hiding the dispatch boundary, mirroring prior compression rebaselines. Structural shrink tracked in #3501.",
|
||||
"_rebaseline_2026_06_29_5286_memoization": "PR #5286 own growth: strategySelector.ts 899->960 (+61 = the opt-in result-memoization branches in applyCompression/applyCompressionAsync — principal+determinism gate, makeMemoKey lookup/store with model+supportsVision folded into the key, recompute-with-memo-off). Default off (memoizeCompressionResults), so zero behavior change. The memo helpers live in the leaf resultMemo.ts (<cap); the chokepoint wiring here is not extractable. Structural shrink of this hot-path file tracked in #3501.",
|
||||
@@ -227,8 +222,7 @@
|
||||
"open-sse/translator/response/gemini-to-openai.ts": 821,
|
||||
"_rebaseline_2026_07_22_7936_namespace_roundtrip": "#7936 (@RCrushMe, Responses-Chat namespace round-trip identity seam) own growth: open-sse/translator/response/openai-responses.ts 1092->1125 (+33) and open-sse/utils/stream.ts 2814->2869 (+55) — threading the namespace-identity seam through the Responses↔Chat translation + stream paths so tool-call namespaces survive the round-trip. Cohesive translation/stream wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_22_8210_openrouter_midstream_error": "PR #8210 (hartmark, fix/openrouter-midstream-error-surfacing) own growth: open-sse/translator/response/openai-responses.ts 1137->1163 (+26) measured on the merged tip (release 1137 + this PR own growth). Adds a single new branch inside openaiToOpenAIResponsesResponse() that detects an OpenRouter-style mid-stream aggregator error (HTTP 200 SSE chunk with empty choices + a top-level error object) and surfaces it as state.upstreamError instead of silently falling through to the no-op/awaitingTrailingUsage path, which previously masked the failure as a false empty-success completion and skipped combo fallback. Irreducible call-site addition at the existing chunk-dispatch chokepoint (mirrors the Gemini-to-OpenAI translator's #4177 precedent for the same class of upstream error surfacing). Note: this baseline entry does NOT cover the separate pre-existing +11 drift already on the release tip from #8081/#8162 (1125->1136, unrelated reasoning-placeholder-stripping fix merged after this PR branched) — that drift belongs to the maintainer's rebaseline, not this PR.",
|
||||
"_rebaseline_2026_07_24_responses_toolcalls_log_summary": "hartmark, fix/responses-tool-calls-log-summary own growth: open-sse/translator/response/openai-responses.ts 1163->1174 (+11). closeToolCall() now also writes the completed tool call into the shared state.toolCalls Map (already populated by the openai-to-claude / claude-to-openai / gemini-to-openai response translators) so stream.ts's completion-log summary builder (which reads state.toolCalls, not this translator's own funcCallIds/funcNames/funcArgsBuf bookkeeping) reports finish_reason \"tool_calls\" and message.tool_calls for openai->openai-responses translated streams instead of always logging \"stop\" with no tool_calls — the actual client-facing SSE events were already correct; only the persisted call-log summary was wrong. Irreducible call-site addition at the existing tool-call-close chokepoint. Covered by the new regression test in tests/unit/translator-resp-openai-responses.test.ts.",
|
||||
"open-sse/translator/response/openai-responses.ts": 1174,
|
||||
"open-sse/translator/response/openai-responses.ts": 1163,
|
||||
"open-sse/utils/cursorAgentProtobuf.ts": 1521,
|
||||
"_rebaseline_2026_07_23_8143_empty_catch_logging": "#8143 (@chirag127) own growth: open-sse/utils/stream.ts 2869->2887 (+18) — replacing empty catch blocks in the SSE stream subsystem with console.debug logging (Rule #6 silent-swallow fix, issues #8138-#8142). Cohesive logging additions at the existing catch chokepoints, not extractable; frozen at new size. Covered by tests/unit/stream-handler-catch-logging-8143.test.ts.",
|
||||
"open-sse/utils/stream.ts": 2887,
|
||||
@@ -256,7 +250,7 @@
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderSettings.ts": 264,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/providerPageHelpers.ts": 1054,
|
||||
"src/app/(dashboard)/dashboard/providers/components/onboarding/ProviderOnboardingWizard.tsx": 948,
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 1990,
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 1927,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
|
||||
"src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx": 819,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ComboDefaultsTab.tsx": 903,
|
||||
@@ -314,10 +308,10 @@
|
||||
"_rebaseline_2026_07_09_6678_routing_strategy_9router": "#6678 (SeaXen) — 9router-parity Routing Strategy settings card + per-provider/combo sticky-round-robin override. Own growth: ProviderDetailPageClient.tsx 784->786 (single ProviderAccountRoutingCard mount + import), auth.ts 2448->2458 (providerStrategies override resolution: fallbackStrategy/stickyRoundRobinLimit per-provider cascade in getProviderCredentials). Both additive, zero unrelated refactor; new UI/logic lives in new files (ProviderAccountRoutingCard.tsx, RoutingStrategyCard.tsx, rrState.ts::resolveComboStickyRoundRobinLimit). chat.ts value below reflects the current release tip (grown by other concurrent PRs, e.g. #6640), not this PR own change.",
|
||||
"_rebaseline_2026_07_22_8213_chat_abandoned_target_abort": "PR #8213 (hartmark, fix/gemini-tpm-quota-cooldown-wait) own growth: src/sse/handlers/chat.ts 1794->1860 (+66, measured against the PR's own merge-base — the release tip separately carries an unrelated -5 net shrink from #8013's antigravity callable-catalog alignment, which this PR's branch does not include and this entry does not cover). Adds resolveDispatchClientRawRequest(): merges a per-target modelAbortSignal into clientRawRequest.signal (via mergeAbortSignals) so a combo target abandoned by comboTargetTimeoutMs actually observes its own abort and reaches its cleanup path, instead of hanging forever inside withRateLimit/acquireAccountSemaphore and leaking a permanent 'pending' dashboard entry (live incident, log id 1784418258231-14961a). Also wires combo-exhausted rejection logging to capture request body + attempted models via the new rejectedRequestUsage helper. Irreducible additions at the existing chat dispatch chokepoint. Covered by the PR's own combo-config + integration test additions.",
|
||||
"_rebaseline_2026_07_23_8127_grok_weekly_quota": "#8127 (@apoapostolov) own growth: src/sse/handlers/chat.ts 1861->1865 (+4) — weekly quota tracking for grok-web wires a quota-fetch hook at the existing dispatch chokepoint. Thin wiring mirroring adjacent provider-quota branches; not extractable. Covered by tests/unit/grok-quota-fetcher.test.ts.",
|
||||
"src/sse/handlers/chat.ts": 1866,
|
||||
"src/sse/handlers/chat.ts": 1865,
|
||||
"_rebaseline_2026_07_20_7779_routingcombo_thread": "PR #7779 own growth: chatHelpers.ts 876->877 (+1, thread routingComboId into executeChatWithBreaker for compression-combo assignment). Frozen so it can only shrink.",
|
||||
"src/sse/handlers/chatHelpers.ts": 878,
|
||||
"src/sse/services/auth.ts": 2486,
|
||||
"src/sse/services/auth.ts": 2462,
|
||||
"open-sse/executors/default.ts": 890,
|
||||
"open-sse/translator/request/openai-responses.ts": 902,
|
||||
"open-sse/executors/kiro.ts": 944,
|
||||
@@ -327,7 +321,7 @@
|
||||
"open-sse/executors/huggingchat.ts": 813,
|
||||
"_rebaseline_2026_07_01_v3843_release_5609": "Rebaseline v3.8.43 (PR #5609 release reconciliation). DRIFT dos 109 commits do ciclo: 8 god-files existentes cresceram (ApiManagerPageClient 2983->3017, combos/page 4594->4608, AddApiKeyModal 868->869, providerPageHelpers 974->996, chat.ts 1635->1647, auth.ts 2401->2403, batchProcessor 828->915, combo.ts 3368->3387) + 2 novos acima do cap (huggingchat.ts 813, tests web-cookie-providers-new 827) + 4 test files cresceram. Modularizacao deferida (blast-radius mid-release); congelado no estado atual p/ o proximo ciclo ratchetar daqui.",
|
||||
"src/lib/providers/validation/webProvidersA.ts": 809,
|
||||
"src/lib/tokenHealthCheck.ts": 843,
|
||||
"src/lib/tokenHealthCheck.ts": 832,
|
||||
"_rebaseline_2026_07_09_6587_kiro_api_key_auth": "PR #6587 (@strangersp) own growth for Kiro long-lived API-key auth, merged onto v3.8.47 tip: openai-to-kiro.ts 890->912 (+22, auth-header selection for API-key-vs-OAuth-token connections), providerLimits.ts 998->1000 (+2, API-key auth-type branch), translator-openai-to-kiro.test.ts 1234->1257 (+23), providers-page-utils.test.ts 1109->1107 (net -2 after merging with parallel release drift; connectionMatchesProviderCard api_key coverage added), provider-validation-specialty.test.ts 2856->2980 (+124 net after merge with parallel release drift; this PR also removed the file's `@typescript-eslint/no-explicit-any` eslint-suppression entry by fixing all `any` usages, adding typed replacements). Cohesive additive feature growth, well tested; not extractable without splitting the existing chokepoints mid-merge.",
|
||||
"_rebaseline_2026_07_19_7787_ic2_localdb_reexports": "PR #7787 (IC2 raw connections cache + lazy-decrypt) own growth: localDb.ts 805->807 (gate units, +2). localDb.ts is the re-export-only layer (hard rule #2 — no logic); the PR adds 4 new db/readCache re-exports (touchConnectionLastUsed, getCachedRawProviderConnections, getCachedProviderConnectionById, getCachedProviderNodes) required by existing barrel importers. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
"_rebaseline_2026_07_20_7819_autocandidateoverrides_reexport": "PR for #7819 (Level 1+2: read-only auto/* candidate transparency + per-API-key exclusions) own growth: localDb.ts 807->808 (+1). Adds a single `export * from \"./db/autoCandidateOverrides\"` barrel re-export (hard rule #2 — no logic) for the new DB module backing per-apiKey candidate exclusions. Irreducible for a re-export list; frozen so it can only shrink.",
|
||||
@@ -344,8 +338,6 @@
|
||||
},
|
||||
"testCap": 800,
|
||||
"testFrozen": {
|
||||
"_rebaseline_2026_07_25_8510_adobe_firefly_reference_images_tests": "#8510 (artickc, feat/adobe-firefly-reference-images) own test growth: tests/unit/adobe-firefly.test.ts 711->871 (+159, entirely this PR's diff — new referenceBlobs upload/dispatch coverage for handleAdobeFireflyImageGeneration, resolveAdobeSourceImageIds, and the storage-upload wire contract). Route-level /v1/images/edits coverage (credentials/rate-limit/4-ref-cap branches added to route.ts) lives in the new tests/unit/8510-adobe-firefly-edits-route.test.ts instead of growing this file further.",
|
||||
"tests/unit/adobe-firefly.test.ts": 871,
|
||||
"_rebaseline_2026_07_09_6126_clinepass_dualauth": "#6126 (ClinePass dual-auth) own test growth: oauth-providers-config.test.ts 842->845 (+3: clinepass key/config/required-fields entries reusing the Cline WorkOS flow config, needed after registering clinepass in the oauth.ts PROVIDERS enum).",
|
||||
"_rebaseline_2026_06_27_5193_antigravity_test": "#5193 own test growth: oauth-providers-config.test.ts 870->873 (+3: antigravity projectId assertion + 50ms tick for the now fire-and-forget onboarding, matching the no-PKCE/no-openid flow).",
|
||||
"_rebaseline_2026_07_02_5928_base_red": "web-cookie-providers-new.test.ts 845->850: #5928 (test(security) Kimi Web URL host parse, CodeQL #689) grew the file +5 lines and merged into release/v3.8.44 WITHOUT rebaselining, leaving a fast-gates base-red that blocked every subsequent PR->release. Test growth is legitimate (a security regression test); maintainer absorbs the drift here. Frozen at 850.",
|
||||
@@ -392,7 +384,7 @@
|
||||
"tests/unit/translator-friendly-test-bench.test.tsx": 848,
|
||||
"tests/unit/translator-helper-branches.test.ts": 870,
|
||||
"tests/unit/translator-openai-responses-req.test.ts": 1195,
|
||||
"tests/unit/translator-openai-to-gemini.test.ts": 1616,
|
||||
"tests/unit/translator-openai-to-gemini.test.ts": 1553,
|
||||
"tests/unit/translator-openai-to-kiro.test.ts": 1257,
|
||||
"tests/unit/translator-resp-gemini-to-openai.test.ts": 1234,
|
||||
"tests/unit/usage-service-hardening.test.ts": 1503,
|
||||
@@ -472,9 +464,6 @@
|
||||
"_rebaseline_2026_07_18_basereds_test_realignment": "Base-red sweep own growth (post 102-PR campaign, full-suite realignment): tests/unit/combo-routing-engine.test.ts 3209->3243 (+34 = least-used tests now prime usage through real handleComboChat calls so recordComboRequest keys by the resolved executionKey exactly as production does — #7015 keying); tests/unit/db-migration-runner.test.ts 1491->1499 (+8 = withNonTestEnvironment now also strips node --test tokens from process.execArgv, matching the #7359 isAutomatedTestProcess widening); tests/unit/executor-default-base.test.ts 1523->1527 (+4 = 1M-beta assertion updated for claude-sonnet-4-6 GA #7129). All three are test-fidelity realignments, not extractable.",
|
||||
"_rebaseline_2026_07_21_7930_pplx_quota_cooldown": "PR #7930 (@artickc) own growth, reconstructed against release/v3.8.49 base-drift: tests/unit/perplexity-web.test.ts 1192->1355 (+163 = two new regression cases — 'Live multi-step: reconstructs answer without status COMPLETED' proving RFC-6902 diff-patched plan_block goals now surface as reasoning_content the same as a materialized plan_block, and 'Advanced-model quota upsell with empty answer surfaces clear error' proving the new advanced_models_quota_low upsell_information detection maps to HTTP 429 + reset_seconds + Retry-After instead of a silent empty-content 502). Most of the PR's original 'multi-step empty content' claims were already independently fixed on release via a different mechanism (extractAnswerFromFinalText + longestMarkdownAnswer); only the two genuinely new, non-conflicting pieces (diff-block plan-goal extraction + quota cooldown) were ported. Covered by the two new tests; not extractable without splitting the whole executor test file.",
|
||||
"_rebaseline_2026_07_22_v3849_ownGrowth_merge_batch": "OAuthModal(#7735 grok chooser), muse-spark-web(#7528 WS), combo.ts+combo-routing-engine.test(#7301 cooldown-retry) — pre-existing on tip; PricingTab(#7972), ComboDefaultsTab(#8008/#7973) — this train batch. Legitimate own-growth, owner-approved rebaseline.",
|
||||
"_rebaseline_2026_07_23_v3849_merge_train_15": "Own-growth do merge-train de 15 PRs (2026-07-23), medido na tip combinada, release pura abaixo do baseline (auth.ts 2448, muse-spark 1393, translator-test 1523). auth.ts 2462->2475 (#8321 cookie-auth 401 cooldown-em-vez-de-terminal + #8324 noauth opencode-zen via proxy — wiring de classificação no chokepoint getProviderCredentials/markAccountUnavailable, não extraível), muse-spark-web.ts 1394->1396 (#8298 sanitizeErrorMessage runtime repairs isolados do #8177), tests/unit/translator-openai-to-gemini.test.ts 1553->1616 (#8312 cobertura do cap de thinking budget no path budget_tokens explícito). Owner-approved. Frozen; shrink estrutural em #3501.",
|
||||
"_rebaseline_2026_07_22_providerLimits_webcookie_chain": "providerLimits.ts 1003->1005: own-growth from web-cookie provider usage-fetcher entries (#7994/#8006/#8027 chain) landing after the prior rebaseline.",
|
||||
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size.",
|
||||
"_rebaseline_2026_07_25_v3849_basered_filesize": "Base-red unblock (2026-07-25): check:file-size was failing on release/v3.8.49 at its own HEAD (36f8fd10), so the quality.yml fast-gates job was red for EVERY PR->release regardless of content — growth inherited from already-merged PRs, with no offending PR branch left to fix (same situation as _rebaseline_2026_07_02_5798_release_green). Prod frozen raised to the current base values: src/lib/tokenHealthCheck.ts 832->841, src/sse/handlers/chat.ts 1865->1866, src/sse/services/auth.ts 2475->2486, open-sse/services/accountFallback.ts 1941->1966, open-sse/services/combo.ts 3630->3642. accountFallback.ts was first frozen here at 1960 (the base value at 36f8fd10) and re-measured to 1966 at base tip 1cafd328c a few hours later — the same inherited drift this entry exists for, since check:file-size does not run on the PR->release fast path and so accrues unmeasured between release rebaselines. These files remain frozen and cannot grow further; any in-flight PR that adds lines to them (e.g. #8482 touches accountFallback.ts and combo.ts) bumps its own entry as usual. The release captain rebaseline-at-release supersedes this note.",
|
||||
"_rebaseline_2026_07_25_v3849_basered_filesize_2": "Base-red unblock (2026-07-25, second pass): after _rebaseline_2026_07_25_v3849_basered_filesize (measured at 36f8fd10) two more already-merged PRs grew frozen files on release/v3.8.49, so check:file-size — and with it the whole Fast Quality Gates job — is red for EVERY PR->release again, with no offending PR branch left to fix. src/lib/tokenHealthCheck.ts 841->843 (#8426 4528fc455, excludes local CLI providers from expiration) and src/app/(dashboard)/dashboard/providers/page.tsx 1927->1990 (#8349 58ab8b1d2, scroll-position restore on provider-card back-navigation). Trust-but-verify: both values measured on the pristine release tip 30709255 with no working-tree changes. Same situation and remedy as _rebaseline_2026_07_02_5798_release_green. Structural reduction of providers/page.tsx stays tracked separately — it is a 1990-line page, not something to extract inside a base-repair PR."
|
||||
"_rebaseline_2026_07_22_8056_headroom_minrows": "#8056 (@RaviTharuma, persist Headroom minRows) own growth: src/lib/db/compression.ts 850->866 (+16 HeadroomConfig+DEFAULT_HEADROOM_CONFIG+normalize/store in get/updateCompressionSettings) and open-sse/services/compression/strategySelector.ts 1054->1060 (+6 merge settings.headroom into stacked stepConfig). Cohesive settings-persistence + stacked-merge wiring at existing chokepoints, frozen at new size."
|
||||
}
|
||||
|
||||
@@ -123,9 +123,7 @@
|
||||
"_rebaseline_2026_06_26_v3837_release": "343->345. v3.8.37 cycle drift surfaced by the release-green pre-flight (the Quality Ratchet does NOT run on PR->release fast-gates, so warnings/complexity accrued unmeasured across this cycle's 76 commits — provider adds DGrid/Pioneer/xAI, headroom proxy lifecycle #4649, ~50 SSE/translator fixes, Engine Combos #5062). Trust-but-verify: this release-finalize working tree touches ONLY CHANGELOG.md, docs/i18n/*/CHANGELOG.md mirrors, and these baselines — 0 production-code change, so all drift is inherited cycle drift (`any` warn-allowed in open-sse/ + tests/). Tighten via --require-tighten next cycle."
|
||||
},
|
||||
"cognitiveComplexity": {
|
||||
"value": 968,
|
||||
"_rebaseline_2026_07_25b_v3849_mergetrain_owngrowth": "Owner-approved (chat, 2026-07-25): 956->968 (+12). v3.8.49 /merge-prs 41-PR merge-train aggregate own-growth: measured 968 on the combined boarded tree (tip ac15014ca7) vs 956 on the pristine release tip. The batch's new over-threshold functions come from the pre-screen-flagged complexity-growth set (#8378/#8432/#8476/#8526 etc); each PR is under-ceiling alone, the combined batch adds +12. Same merge-burst class as the notes below; owner chose ceiling-absorb over per-PR extraction. Structural shrink tracked in #3501; tighten via --update next cycle.",
|
||||
"_rebaseline_2026_07_25_v3849_mergequeue_drain": "Owner-approved (chat, 2026-07-25): 951->956 (+5). v3.8.49 /merge-prs queue-drain: inherited cognitive-complexity drift from the cycle's merge burst (base-red slices + owner PRs + parallel-session merges #8500-8508); check:cognitive-complexity does not run on PR->release fast-gates, so it accrued unmeasured. Measured 956 on the pristine release tip 4053e2314a alone (BEFORE any queue PR boards) — the entire +5 is base drift already on the tip, reddening Fast Quality Gates for every merge-ready PR. Owner approved raising the ceiling to the measured tip value so the ~34-PR merge-train lands without per-PR extraction churn. Structural shrink tracked in #3501; tighten via --update next cycle.",
|
||||
"value": 951,
|
||||
"_rebaseline_2026_07_10_gcf_v3_2": "885->888 (+3). PR feat/headroom-gcf-v3.2-nested-flattening: own growth from re-vendoring the GCF (Headroom) codec to spec v3.2 (nested flattening). The new over-threshold functions are the vendored v3.2 flatten/unflatten walk in open-sse/services/compression/engines/headroom/gcf/{generic,decode_generic}.ts. Imported third-party code kept byte-faithful to upstream gcf-typescript; measured 888 with the update vs 885 on the pristine origin/release/v3.8.47 tip. Guarded by tests/unit/compression/headroom-smartcrusher.test.ts (deep-nested case). Structural shrink belongs upstream in gcf.",
|
||||
"_rebaseline_2026_07_12_v3847_mergetrain_burst": "885->890 (+5). v3.8.47 /merge-prs merge-train batch (23 merge-ready PRs) inherited drift: cognitive-complexity does NOT run on PR->release fast-gates, so incidental growth accrued unmeasured across the batch. Measured 890 on the combined merge-train tip (5d980352d) vs 885 on the pristine release tip 1b7a9150e. #6838 (headroom gcf codec re-vendor) accounts for +3 (its own baseline bump to 888, superseded here by this later 890 rebaseline which already covers it); the remaining +2 is parallel-batch drift across the other 22 PRs. Owner-approved rebaseline (merge-burst reconciliation, same class as the v3.8.46/v3.8.44 notes below). Tighten via --update next cycle.",
|
||||
"_rebaseline_2026_07_09_6587_kiro_api_key_auth": "884->885 (+1). PR #6587 (@strangersp) own growth: open-sse/services/usage/kiro.ts gains ONE new over-threshold function — getKiroUsage grew from a single fetch to a 3-endpoint fallback chain (codewhisperer-get / codewhisperer-post / q-get) with per-attempt auth-header selection (tokentype: API_KEY vs Bearer-only), needed so usage/quota lookups work for the new long-lived-API-key auth path in addition to the existing OAuth path (measured: 0 violations on release tip -> 1 violation, complexity 33, at open-sse/services/usage/kiro.ts). Covered by tests/unit/kiro-iam-profilearn-usage.test.ts (tokentype header selection, friendly auth-expired/rejected-token messages). Cohesive multi-endpoint-fallback logic at an existing usage chokepoint; not extractable without splitting the fallback loop mid-merge. Structural shrink tracked in #3501.",
|
||||
|
||||
@@ -36,14 +36,6 @@
|
||||
"tests/unit/free-provider-rankings-configured-filter.test.ts": {
|
||||
"replacement": "tests/unit/freeProviderRankings-filters.test.ts",
|
||||
"reason": "v3.8.45 #6251 supersede #6245: a página Free Provider Rankings migrou do toggle client-side 'Configured Only' (#6245, configuredProviderIds no cliente) para filtros server-side configuredOnly/availableOnly (#6251). O teste antigo pinava a implementação removida (7 asserts quebrados contra código que não existe); o replacement cobre o contrato novo com 11 casos (server-side, lib helper). Verificado legítimo — supersessão documentada no CHANGELOG do #6251."
|
||||
},
|
||||
"tests/unit/video-dashscope.test.ts": {
|
||||
"replacement": "tests/unit/alibaba-video-media.test.ts",
|
||||
"reason": "v3.8.49 #8266 reorganized the Alibaba video catalog: the flat wan2.7-t2v id moved out of the plain \"alibaba\" provider (which now only exposes the dated wan2.7-t2v-2026-06-12) and lives only under \"qwen-cloud\" today. All 6 tests in the deleted file built requests against alibaba/wan2.7-t2v, which the new allowlist now rejects with HTTP 400 (verified directly against VIDEO_PROVIDERS in open-sse/config/videoRegistry.ts before deletion). Coverage for this exact id already exists and was confirmed green pre-deletion in tests/unit/alibaba-video-media.test.ts (including an explicit \"Alibaba rejects video models outside its own allowlist\" case for alibaba/wan2.7-t2v) and tests/unit/qwen-cloud-video-media.test.ts (covers the same flat id under qwen-cloud/wan2.7-t2v). Verified legitimate, not masking."
|
||||
},
|
||||
"tests/unit/usage-analytics-route-extra.test.ts": {
|
||||
"replacement": "tests/unit/usage-analytics-route.test.ts",
|
||||
"reason": "v3.8.49 base-red reconcile: the file was a 100% duplicate — all 10 of its test names exist verbatim (comm -12 verified) among the 22 in tests/unit/usage-analytics-route.test.ts, created by #7700 before #7300 fixed the retention-cutoff fixture only in the main file (reads getUserDatabaseSettings().retention.usageHistory live instead of hardcoding 30d). The stale copy red-failed 'does not double-count raw and aggregated rows' (1 !== 2) against correct production behavior. Zero unique coverage lost; the maintained main file passes 22/22. Verified legitimate, not masking."
|
||||
}
|
||||
},
|
||||
"tests/unit/catalog-updates-v3x.test.ts": "v3.8.45 #6248: fix(providers) remove deprecated MiMo V2 entries — os 5 asserts removidos pinavam specs de modelos mimo-v2-* que deixaram de existir no catálogo (54→49). Asserts seguem a remoção dos modelos, não enfraquecimento. Verificado legítimo. Prune após v3.8.45 mergear para main.",
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# OmniRoute Roadmap
|
||||
|
||||
> Version-gated, not date-gated: each milestone ships when its quality gates pass.
|
||||
> Current line: **v3.8.x** (this branch). Last updated: 2026-07-23.
|
||||
|
||||
OmniRoute is heading from a monolithic router to a **modular AI platform**: a lightweight
|
||||
core engine, a typed SDK, and everything else as installable modules and plugins. The path
|
||||
runs through a stabilization rail (3.8.50 → 3.8.59), an LTS anchor (**3.9.0**), and the
|
||||
modular **4.0**.
|
||||
|
||||
## The rail at a glance
|
||||
|
||||
```
|
||||
3.8.50 ─ 3.8.54 PREPARE non-breaking structural prep (all PRs welcome)
|
||||
3.8.55 ─ 3.8.59 VALIDATE stabilization (fixes / docs / i18n / providers only)
|
||||
3.9.0 LTS stable/v3 branch · long-term support line
|
||||
4.0.0-nightly/rc MODULAR core + SDK + modules + marketplace (develop branch)
|
||||
4.0.0 GA latest switches to v4 · v3 stays supported as LTS
|
||||
```
|
||||
|
||||
## Phase 1 — Preparation (3.8.50 → 3.8.54)
|
||||
|
||||
Non-breaking structural work that de-risks the modular split. Every version closes with a
|
||||
mandatory quality-gate battery before new merges open.
|
||||
|
||||
| Version | Focus |
|
||||
| --- | --- |
|
||||
| 3.8.50 | CI safety net on release branches · dead-code cleanup · community-reported catalog/topology bug fixes · contributor "golden path" guide |
|
||||
| 3.8.51 | Executor registry (in-place) · end-to-end provider-journey contract test becomes a CI gate · official scoped-test dev loop · CI lane consolidation (shared install/setup across gate jobs, #8084) |
|
||||
| 3.8.52 | `combo.ts` decomposition · routing-strategy registry · unified model-catalog contract for `/v1/models` · one CI policy for PRs to `release/**` and `main` (#8084) |
|
||||
| 3.8.53 | `chatCore.ts` decomposition · headless mode (`OMNIROUTE_HEADLESS=1`) · local candidate build/promote loop |
|
||||
| 3.8.54 | Release infrastructure (dormant): channels, labels, PR templates, merge queue · full-regression authority moves to the merge queue once TIA shadow evidence clears (#8084) · public feature-freeze announcement |
|
||||
|
||||
## Phase 2 — Validation (3.8.55 → 3.8.59)
|
||||
|
||||
**External feature PRs pause here** (they get the `v4-feature` label and are re-targeted to
|
||||
the v4 channel when it opens). Fixes, docs, i18n, and provider updates keep flowing.
|
||||
|
||||
| Version | Focus |
|
||||
| --- | --- |
|
||||
| 3.8.55 | Characterization tests for every extraction candidate · coupling re-measurement |
|
||||
| 3.8.56 | Extended canary · performance baselines (heap, TTFB, build) |
|
||||
| 3.8.57 | Security & compliance sweep · publish provenance (OIDC) rehearsal |
|
||||
| 3.8.58 | Full dry-run of the 3.9.0 cut (branches, channels, forward-port) — includes the PR preview-artifact + build-once promotion rehearsal (#8084) |
|
||||
| 3.8.59 | Final freeze · full-suite audit · GO/NO-GO |
|
||||
|
||||
## Phase 3 — v3.9.0 LTS
|
||||
|
||||
After 3.8.59 the next version is **3.9.0** (there is no 3.8.60). It creates the long-lived
|
||||
branch model:
|
||||
|
||||
- **`stable/v3`** — the LTS line (3.9.x). Receives fixes, security patches, and provider
|
||||
updates. `npm install omniroute` (aka `latest`) stays on v3 during the whole v4 cycle.
|
||||
- **`develop`** — v4 development, published as `4.0.0-nightly.*`.
|
||||
- **`main`** — v4 release candidates (`next`) and, eventually, GA.
|
||||
- Fixes merged to `stable/v3` are automatically forward-ported to `develop` with full
|
||||
contributor credit (`Co-authored-by`).
|
||||
|
||||
New features land in the v4 channel. The LTS line is stability-first.
|
||||
|
||||
## Phase 4 — v4.0: the modular platform
|
||||
|
||||
The monolith is intentionally disassembled on `develop`:
|
||||
|
||||
- **`@omniroute/core`** (npm name stays `omniroute`) — just the engine: `/v1/*`, routing,
|
||||
combo/fallback, providers.
|
||||
- **`@omniroute/sdk`** — one typed contract: hooks, extension points, two-phase lifecycle,
|
||||
UI contributions. The five extension systems that exist today (plugins, CLI plugins,
|
||||
skills, MCP tools, A2A skills) collapse into one declarative manifest.
|
||||
- **Modules** (`@omniroute/mod-*`) — cloud agents, traffic inspection (MITM), evals,
|
||||
webhooks, memory, guardrails, observability and more move out of the core, each with its
|
||||
own version and lifecycle.
|
||||
- **Providers as plugins** — adding a provider stops touching the core.
|
||||
- **Marketplace** — one-click install with verified integrity (hash pinning, signing,
|
||||
sandbox). Free in v1; a paid tier later with revenue share for creators.
|
||||
- Ships as `4.0.0-nightly.*` → `4.0.0-rc.N` (soak in production) → **4.0.0 GA**, when
|
||||
`latest` switches to v4 and v3 enters its announced LTS support window.
|
||||
|
||||
**The core is MIT and free, forever.**
|
||||
|
||||
## For contributors
|
||||
|
||||
| You are sending... | Target today | From 3.8.55 | After 3.9.0 |
|
||||
| --- | --- | --- | --- |
|
||||
| Bug fix / security | active `release/v3.8.x` | same | `stable/v3` |
|
||||
| Provider update | active `release/v3.8.x` | same | `stable/v3` |
|
||||
| Docs / i18n | active `release/v3.8.x` | same | `stable/v3` |
|
||||
| New feature | active `release/v3.8.x` | held with `v4-feature` label | `develop` (v4) |
|
||||
|
||||
See `CONTRIBUTING.md` for the golden path per change type.
|
||||
@@ -209,13 +209,14 @@ on). Without a `max_concurrent` cap the behavior is unchanged.
|
||||
|
||||
### Combo cooldown-aware retry
|
||||
|
||||
For every combo strategy (when enabled), a request that would crystallize a 429
|
||||
for a SHORT transient cooldown waits it out and re-dispatches instead of
|
||||
returning the 429 — this covers Gemini-class TPM/RPM windows (~60s retry-after)
|
||||
on multi-model combos, e.g. both targets of a 2-model combo hitting a per-model
|
||||
rate limit. Bounded by `comboCooldownWait` (`enabled`, `maxWaitMs`, `maxAttempts`,
|
||||
`budgetMs`) in **Settings → Resilience**. It never waits on `quota_exhausted`
|
||||
(locked until midnight) or auth/not-found reasons.
|
||||
For quota-share and `auto` combos, a request that would crystallize a 429 for a
|
||||
SHORT transient cooldown waits it out and re-dispatches instead of returning
|
||||
the 429 — this covers Gemini-class TPM/RPM windows (~60s retry-after) on a
|
||||
multi-model `auto` combo, e.g. both targets of a 2-model combo hitting a
|
||||
per-model rate limit. Bounded by `comboCooldownWait` (`enabled`, `maxWaitMs`
|
||||
65s, `maxAttempts` 2, `budgetMs` 130s, hard ceiling 90s) in **Settings →
|
||||
Resilience**. It never waits on `quota_exhausted` (locked until midnight) or
|
||||
auth/not-found reasons.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<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: 290 providers, 90+ free providers built-in, 19 routing strategies, 12-engine token compression, a built-in MCP server with 104 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>
|
||||
<radialGradient id="gGlow" cx="30%" cy="10%" r="60%"><stop offset="0%" stop-color="#7ee787" stop-opacity="0.10"/><stop offset="60%" stop-color="#00cec9" stop-opacity="0.04"/><stop offset="100%" stop-color="#6366f1" stop-opacity="0"/></radialGradient>
|
||||
<g id="yes"><circle r="11" fill="#7ee787" fill-opacity="0.14"/><path d="M -5 0.3 L -1.6 3.7 L 5.4 -4" stroke="#7ee787" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/></g>
|
||||
<g id="no"><circle r="11" fill="#6e7681" fill-opacity="0.10"/><path d="M -3.8 -3.8 L 3.8 3.8 M 3.8 -3.8 L -3.8 3.8" stroke="#6e7681" stroke-width="2.1" stroke-linecap="round"/></g>
|
||||
<g id="mid"><circle r="11" fill="#fdcb6e" fill-opacity="0.13"/><path d="M -4.6 0 L 4.6 0" stroke="#fdcb6e" stroke-width="2.4" stroke-linecap="round"/></g>
|
||||
</defs>
|
||||
<rect width="1200" height="780" fill="#0b0e14"/>
|
||||
<rect width="1200" height="780" fill="url(#gC)"/>
|
||||
<rect width="1200" height="780" fill="url(#gGlow)"/>
|
||||
<text x="44" y="54" font-family="Consolas, 'Courier New', monospace" font-size="13" letter-spacing="2.5" fill="#7ee787">WHAT SETS OMNIROUTE APART</text>
|
||||
<line x1="330" y1="49" x2="1156" y2="49" stroke="#232b38" stroke-width="1.5"/>
|
||||
<text x="44" y="96" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">Every router does routing. <tspan fill="#7ee787" font-weight="800">OmniRoute does the whole platform.</tspan></text>
|
||||
<rect x="352" y="126" width="176" height="606" rx="12" fill="#7ee787" fill-opacity="0.07" stroke="#7ee787" stroke-opacity="0.5" stroke-width="1.5"/>
|
||||
<text x="440" y="150" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15.5" font-weight="800" fill="#7ee787">OmniRoute</text>
|
||||
<text x="604" y="150" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="14" font-weight="600" fill="#8b949e">9router</text>
|
||||
<text x="760" y="150" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="14" font-weight="600" fill="#8b949e">OpenRouter</text>
|
||||
<text x="916" y="150" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="14" font-weight="600" fill="#8b949e">CLIProxyAPI</text>
|
||||
<text x="1080" y="150" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="14" font-weight="600" fill="#8b949e">LiteLLM</text>
|
||||
<line x1="44" y1="162" x2="1156" y2="162" stroke="#232b38" stroke-width="1.5"/>
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.15s" fill="freeze"/>
|
||||
<text x="44" y="196" font-size="14.5" fill="#c9d1d9">Providers</text>
|
||||
<text x="440" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">290</text>
|
||||
<text x="604" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">40+</text>
|
||||
<text x="760" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">400+*</text>
|
||||
<text x="916" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">~5</text>
|
||||
<text x="1080" y="196" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">100+</text>
|
||||
</g>
|
||||
<rect x="36" y="212" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.24s" fill="freeze"/>
|
||||
<text x="44" y="238" font-size="14.5" fill="#c9d1d9">Free providers built-in</text>
|
||||
<text x="440" y="238" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">90+</text>
|
||||
<use href="#yes" x="604" y="233"/>
|
||||
<use href="#mid" x="760" y="233"/>
|
||||
<use href="#no" x="916" y="233"/>
|
||||
<use href="#no" x="1080" y="233"/>
|
||||
</g>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.33s" fill="freeze"/>
|
||||
<text x="44" y="280" font-size="14.5" fill="#c9d1d9">Routing strategies</text>
|
||||
<text x="440" y="280" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">19</text>
|
||||
<text x="604" y="280" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">2</text>
|
||||
<text x="760" y="280" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">3</text>
|
||||
<text x="916" y="280" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">2</text>
|
||||
<text x="1080" y="280" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">6</text>
|
||||
</g>
|
||||
<rect x="36" y="296" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.42s" fill="freeze"/>
|
||||
<text x="44" y="322" font-size="14.5" fill="#c9d1d9">Token compression</text>
|
||||
<text x="440" y="322" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">12 eng</text>
|
||||
<use href="#yes" x="604" y="317"/>
|
||||
<use href="#mid" x="760" y="317"/>
|
||||
<use href="#no" x="916" y="317"/>
|
||||
<use href="#no" x="1080" y="317"/>
|
||||
</g>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.51s" fill="freeze"/>
|
||||
<text x="44" y="364" font-size="14.5" fill="#c9d1d9">Built-in MCP server (own tools)</text>
|
||||
<text x="440" y="364" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">104</text>
|
||||
<use href="#no" x="604" y="359"/>
|
||||
<use href="#mid" x="760" y="359"/>
|
||||
<use href="#no" x="916" y="359"/>
|
||||
<use href="#mid" x="1080" y="359"/>
|
||||
</g>
|
||||
<rect x="36" y="380" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.60s" fill="freeze"/>
|
||||
<text x="44" y="406" font-size="14.5" fill="#c9d1d9">A2A agent protocol</text>
|
||||
<use href="#yes" x="440" y="401"/>
|
||||
<use href="#no" x="604" y="401"/>
|
||||
<use href="#no" x="760" y="401"/>
|
||||
<use href="#no" x="916" y="401"/>
|
||||
<use href="#yes" x="1080" y="401"/>
|
||||
</g>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.69s" fill="freeze"/>
|
||||
<text x="44" y="448" font-size="14.5" fill="#c9d1d9">Persistent memory</text>
|
||||
<use href="#yes" x="440" y="443"/>
|
||||
<use href="#no" x="604" y="443"/>
|
||||
<use href="#no" x="760" y="443"/>
|
||||
<use href="#no" x="916" y="443"/>
|
||||
<use href="#no" x="1080" y="443"/>
|
||||
</g>
|
||||
<rect x="36" y="464" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.78s" fill="freeze"/>
|
||||
<text x="44" y="490" font-size="14.5" fill="#c9d1d9">Guardrails (PII / injection)</text>
|
||||
<use href="#yes" x="440" y="485"/>
|
||||
<use href="#no" x="604" y="485"/>
|
||||
<use href="#yes" x="760" y="485"/>
|
||||
<use href="#no" x="916" y="485"/>
|
||||
<use href="#yes" x="1080" y="485"/>
|
||||
</g>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.87s" fill="freeze"/>
|
||||
<text x="44" y="532" font-size="14.5" fill="#c9d1d9">Cloud agents (Codex/Devin/Jules)</text>
|
||||
<use href="#yes" x="440" y="527"/>
|
||||
<use href="#no" x="604" y="527"/>
|
||||
<use href="#no" x="760" y="527"/>
|
||||
<use href="#no" x="916" y="527"/>
|
||||
<use href="#no" x="1080" y="527"/>
|
||||
</g>
|
||||
<rect x="36" y="548" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="0.96s" fill="freeze"/>
|
||||
<text x="44" y="574" font-size="14.5" fill="#c9d1d9">TLS fingerprint stealth (JA3/JA4)</text>
|
||||
<use href="#yes" x="440" y="569"/>
|
||||
<use href="#no" x="604" y="569"/>
|
||||
<use href="#no" x="760" y="569"/>
|
||||
<use href="#no" x="916" y="569"/>
|
||||
<use href="#no" x="1080" y="569"/>
|
||||
</g>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="1.05s" fill="freeze"/>
|
||||
<text x="44" y="616" font-size="14.5" fill="#c9d1d9">Desktop · Termux · PWA</text>
|
||||
<use href="#yes" x="440" y="611"/>
|
||||
<use href="#mid" x="604" y="611"/>
|
||||
<use href="#mid" x="760" y="611"/>
|
||||
<use href="#mid" x="916" y="611"/>
|
||||
<use href="#mid" x="1080" y="611"/>
|
||||
</g>
|
||||
<rect x="36" y="632" width="1128" height="42" rx="6" fill="#ffffff" fill-opacity="0.02"/>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="1.14s" fill="freeze"/>
|
||||
<text x="44" y="658" font-size="14.5" fill="#c9d1d9">i18n UI locales</text>
|
||||
<text x="440" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">43</text>
|
||||
<text x="604" y="658" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">6</text>
|
||||
<use href="#no" x="760" y="653"/>
|
||||
<use href="#no" x="916" y="653"/>
|
||||
<use href="#no" x="1080" y="653"/>
|
||||
</g>
|
||||
<g opacity="0"><animate attributeName="opacity" values="0;1" dur="0.4s" begin="1.23s" fill="freeze"/>
|
||||
<text x="44" y="700" font-size="14.5" fill="#c9d1d9">Self-hosted · 100% MIT</text>
|
||||
<text x="440" y="700" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="15" font-weight="800" fill="#7ee787">MIT</text>
|
||||
<text x="604" y="700" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">MIT</text>
|
||||
<use href="#no" x="760" y="695"/>
|
||||
<text x="916" y="700" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#8b949e">MIT</text>
|
||||
<text x="1080" y="700" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="600" fill="#fdcb6e">open-core</text>
|
||||
</g>
|
||||
</g>
|
||||
<line x1="44" y1="728" x2="1156" y2="728" stroke="#232b38" stroke-width="1.5"/>
|
||||
<text x="44" y="750" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#71717a">full • partial • none  ·  *OpenRouter counts models & is a hosted SaaS, not self-hosted.</text>
|
||||
<text x="1156" y="750" text-anchor="end" font-family="Consolas, 'Courier New', monospace" font-size="12" letter-spacing="1.5" fill="#71717a">verified from each project's docs</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,117 +0,0 @@
|
||||
<svg viewBox="0 0 1200 430" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Works the second you install it — zero config. Three steps: 1. Install — npm install -g omniroute, one command, server on localhost port 20128. 2. Point your tool at http://localhost:20128/v1 — any OpenAI-compatible tool. 3. It answers — call model auto and get an instant reply with no API key, no signup, no configuration. Keyless free providers OpenCode Free and Felo are pre-wired into the auto combo, so a fresh install responds out of the box.">
|
||||
<desc>Animated flow card: three step tiles (Install, Point your tool, It answers) fade in left to right, a dot travels along the connectors between them in a loop, and the final check pulses.</desc>
|
||||
<defs>
|
||||
<pattern id="gridW" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||
<path d="M 32 0 L 0 0 0 32" fill="none" stroke="#ffffff" stroke-opacity="0.06" stroke-width="1"/>
|
||||
</pattern>
|
||||
<radialGradient id="bgGlowW" cx="72%" cy="12%" r="60%">
|
||||
<stop offset="0%" stop-color="#7ee787" stop-opacity="0.12"/>
|
||||
<stop offset="55%" stop-color="#00cec9" stop-opacity="0.06"/>
|
||||
<stop offset="100%" stop-color="#6366f1" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="430" fill="#0b0e14"/>
|
||||
<rect width="1200" height="430" fill="url(#gridW)"/>
|
||||
<rect width="1200" height="430" fill="url(#bgGlowW)"/>
|
||||
|
||||
<!-- header -->
|
||||
<text x="40" y="56" font-family="Consolas, 'Courier New', monospace" font-size="13" letter-spacing="2.5" fill="#7ee787">WORKS THE SECOND YOU INSTALL IT</text>
|
||||
<line x1="392" y1="51" x2="1160" y2="51" stroke="#232b38" stroke-width="1.5"/>
|
||||
<text x="40" y="98" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="23" font-weight="600" fill="#c9d1d9">Fresh install → it already answers. <tspan fill="#7ee787" font-weight="800">Zero config.</tspan></text>
|
||||
|
||||
<!-- connectors (drawn under the tiles) -->
|
||||
<g stroke="#2b3543" stroke-width="2" stroke-dasharray="5 5" fill="none">
|
||||
<path id="cW1" d="M 372 218 L 432 218"/>
|
||||
<path id="cW2" d="M 767 218 L 827 218"/>
|
||||
</g>
|
||||
<!-- traveling dots along the connectors -->
|
||||
<circle r="4" fill="#a78bfa">
|
||||
<animateMotion dur="2.6s" begin="1.4s" repeatCount="indefinite" keyPoints="0;1" keyTimes="0;1" calcMode="linear">
|
||||
<mpath href="#cW1"/>
|
||||
</animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" keyTimes="0;0.15;0.85;1" dur="2.6s" begin="1.4s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="4" fill="#7ee787">
|
||||
<animateMotion dur="2.6s" begin="1.9s" repeatCount="indefinite" keyPoints="0;1" keyTimes="0;1" calcMode="linear">
|
||||
<mpath href="#cW2"/>
|
||||
</animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" keyTimes="0;0.15;0.85;1" dur="2.6s" begin="1.9s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif">
|
||||
|
||||
<!-- step 1: install (blue) -->
|
||||
<g opacity="0">
|
||||
<animate attributeName="opacity" values="0;1" dur="0.5s" begin="0.1s" fill="freeze"/>
|
||||
<rect x="40" y="132" width="332" height="172" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="40" y="148" width="4" height="140" rx="2" fill="#0984e3"/>
|
||||
<circle cx="80" cy="172" r="15" fill="#74b9ff" fill-opacity="0.12"/>
|
||||
<text x="80" y="178" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="800" fill="#74b9ff">1</text>
|
||||
<g transform="translate(108,161)" stroke="#74b9ff" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M 11 2 L 11 15 M 6 10 L 11 15 L 16 10"/>
|
||||
<path d="M 3 19 L 19 19"/>
|
||||
</g>
|
||||
<text x="140" y="179" font-size="18" font-weight="800" fill="#74b9ff">Install</text>
|
||||
<text x="66" y="222" font-family="Consolas, 'Courier New', monospace" font-size="14" fill="#e6edf3">$ npm i -g omniroute</text>
|
||||
<text x="66" y="258" font-size="13.5" fill="#a1a1aa">One command. Server boots on</text>
|
||||
<text x="66" y="279" font-family="Consolas, 'Courier New', monospace" font-size="13" fill="#8b949e">localhost:20128</text>
|
||||
</g>
|
||||
|
||||
<!-- step 2: point your tool (violet) -->
|
||||
<g opacity="0">
|
||||
<animate attributeName="opacity" values="0;1" dur="0.5s" begin="0.5s" fill="freeze"/>
|
||||
<rect x="435" y="132" width="332" height="172" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="435" y="148" width="4" height="140" rx="2" fill="#8b5cf6"/>
|
||||
<circle cx="475" cy="172" r="15" fill="#a78bfa" fill-opacity="0.12"/>
|
||||
<text x="475" y="178" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="800" fill="#a78bfa">2</text>
|
||||
<g transform="translate(503,161)" stroke="#a78bfa" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M 8 6 L 5 9 A 4 4 0 0 0 5 15 L 8 18 M 14 6 L 17 9 A 4 4 0 0 1 17 15 L 14 18 M 8.5 12 L 13.5 12"/>
|
||||
</g>
|
||||
<text x="535" y="179" font-size="18" font-weight="800" fill="#a78bfa">Point your tool</text>
|
||||
<text x="461" y="222" font-family="Consolas, 'Courier New', monospace" font-size="13" fill="#e6edf3">http://localhost:20128/v1</text>
|
||||
<text x="461" y="258" font-size="13.5" fill="#a1a1aa">Any OpenAI-compatible tool —</text>
|
||||
<text x="461" y="279" font-size="13.5" fill="#a1a1aa">Claude Code, Cursor, Cline…</text>
|
||||
</g>
|
||||
|
||||
<!-- step 3: it answers (green) -->
|
||||
<g opacity="0">
|
||||
<animate attributeName="opacity" values="0;1" dur="0.5s" begin="0.9s" fill="freeze"/>
|
||||
<rect x="830" y="132" width="330" height="172" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="830" y="132" width="330" height="172" rx="14" fill="none" stroke="#7ee787" stroke-width="1.5" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0.9;0" keyTimes="0;0.5;1" dur="2.6s" begin="3.2s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="830" y="148" width="4" height="140" rx="2" fill="#22c55e"/>
|
||||
<circle cx="870" cy="172" r="15" fill="#7ee787" fill-opacity="0.12"/>
|
||||
<text x="870" y="178" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="800" fill="#7ee787">3</text>
|
||||
<g transform="translate(898,161)" stroke="#7ee787" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M 3 11 L 9 17 L 19 5"/>
|
||||
</g>
|
||||
<text x="930" y="179" font-size="18" font-weight="800" fill="#7ee787">It answers</text>
|
||||
<text x="856" y="222" font-family="Consolas, 'Courier New', monospace" font-size="14" fill="#e6edf3">model: <tspan fill="#7ee787" font-weight="700">"auto"</tspan> → reply</text>
|
||||
<text x="856" y="258" font-size="13.5" fill="#a1a1aa">Instant. OmniRoute builds a virtual</text>
|
||||
<text x="856" y="279" font-size="13.5" fill="#a1a1aa">combo from keyless free providers.</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- "no ___" chips -->
|
||||
<g font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="13.5" font-weight="700" opacity="0">
|
||||
<animate attributeName="opacity" values="0;1" dur="0.5s" begin="1.3s" fill="freeze"/>
|
||||
<g transform="translate(40,332)">
|
||||
<rect x="0" y="0" width="168" height="34" rx="17" fill="#161b22" stroke="#7ee787" stroke-opacity="0.35" stroke-width="1"/>
|
||||
<text x="24" y="22" fill="#c9d1d9">✕ no API key</text>
|
||||
</g>
|
||||
<g transform="translate(224,332)">
|
||||
<rect x="0" y="0" width="168" height="34" rx="17" fill="#161b22" stroke="#7ee787" stroke-opacity="0.35" stroke-width="1"/>
|
||||
<text x="24" y="22" fill="#c9d1d9">✕ no signup</text>
|
||||
</g>
|
||||
<g transform="translate(408,332)">
|
||||
<rect x="0" y="0" width="188" height="34" rx="17" fill="#161b22" stroke="#7ee787" stroke-opacity="0.35" stroke-width="1"/>
|
||||
<text x="24" y="22" fill="#c9d1d9">✕ no configuration</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- footer -->
|
||||
<text x="40" y="406" font-family="Consolas, 'Courier New', monospace" font-size="12" fill="#71717a">OpenCode Free & Felo are pre-wired into <tspan fill="#7ee787">auto</tspan> — a fresh install responds out of the box.</text>
|
||||
<text x="1160" y="406" text-anchor="end" font-family="Consolas, 'Courier New', monospace" font-size="12" letter-spacing="1.5" fill="#71717a">$0 · MIT</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Claude Code CLI — Configuration with OmniRoute"
|
||||
version: 3.8.40
|
||||
lastUpdated: 2026-07-24
|
||||
lastUpdated: 2026-06-28
|
||||
---
|
||||
|
||||
# Claude Code CLI — Configuration with OmniRoute
|
||||
@@ -140,20 +140,10 @@ extra flags needed. Override per-invocation with `--remote` / `--api-key`.
|
||||
handles this for you.
|
||||
|
||||
**`/model` picker is empty / missing gateway models** — needs Claude Code
|
||||
v2.1.219+ and `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`. Only `claude*` /
|
||||
v2.1.129+ and `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`. Only `claude*` /
|
||||
`anthropic*` model IDs appear in the picker; force any other model with
|
||||
`ANTHROPIC_MODEL=<id>` (this is what profiles do).
|
||||
|
||||
**`400 Ambiguous model 'claude-…'`** — Claude Code always sends **unprefixed**
|
||||
model IDs (e.g. `claude-opus-4-8`), so when both the Claude Code (`cc/…`) and
|
||||
Claude (`claude/…`) providers are connected the bare id matches two routes and
|
||||
OmniRoute refuses to guess. Fix it either way: pin a prefixed id with
|
||||
`ANTHROPIC_MODEL=cc/claude-opus-4-8`, or enable **Prefer Claude Code for
|
||||
unprefixed Claude models** — the toggle on the Claude provider page, or
|
||||
`OMNIROUTE_PREFER_CLAUDE_CODE_FOR_UNPREFIXED_CLAUDE_MODELS=true` (default off;
|
||||
see [Environment](../reference/ENVIRONMENT.md)) — which routes bare `claude-*`
|
||||
IDs to Claude Code instead. Explicit provider prefixes always win.
|
||||
|
||||
**Auth errors** — the profile holds no token. Use `omniroute launch --profile`
|
||||
(injects it) or export `ANTHROPIC_AUTH_TOKEN`.
|
||||
|
||||
|
||||
@@ -572,7 +572,7 @@ For the full environment variable reference, see the [README](../README.md).
|
||||
|
||||
**GitHub Copilot (`gh/`)** — OAuth: `gh/gpt-5.5`, `gh/gpt-5.4`, `gh/gpt-5.4-mini`, `gh/gpt-5-mini`, `gh/gpt-5.3-codex`, `gh/claude-opus-4.7`, `gh/claude-opus-4.6`, `gh/claude-opus-4-5-20251101`, `gh/claude-sonnet-4.6`, `gh/claude-sonnet-4.5`, `gh/claude-haiku-4.5`, `gh/gemini-3.1-pro-preview`, `gh/gemini-3-flash-preview`, `gh/oswe-vscode-prime`
|
||||
|
||||
**Kiro (`kr/`)** — FREE OAuth: use the live catalog shown under **Dashboard → Providers → Kiro → Available Models**. Availability depends on the account and plan.
|
||||
**Kiro (`kr/`)** — FREE OAuth: `kr/auto-kiro`, `kr/claude-opus-4.7`, `kr/claude-opus-4.6`, `kr/claude-sonnet-4.6`, `kr/claude-sonnet-4.5`, `kr/claude-haiku-4.5`, `kr/deepseek-3.2`, `kr/minimax-m2.5`, `kr/minimax-m2.1`, `kr/glm-5`, `kr/qwen3-coder-next`
|
||||
|
||||
**Qoder (`if/`)** — FREE OAuth: `if/qwen3.8-max-preview`, `if/qwen3.7-max`, `if/qwen3.7-plus`, `if/kimi-k3`, `if/kimi-k2.7-code`, `if/glm-5.2`, `if/deepseek-v4-pro`, `if/deepseek-v4-flash`, `if/minimax-m3`
|
||||
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
@@ -336,7 +336,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
||||
|
||||
| Variable | Default Value | When to Update |
|
||||
| ------------------------ | --------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.219 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CLAUDE_USER_AGENT` | `claude-cli/2.1.145 (external, cli)` | When Anthropic releases a new CLI version |
|
||||
| `CODEX_USER_AGENT` | `codex-cli/0.132.0 (Windows 10.0.26200; x64)` | When OpenAI updates the Codex CLI |
|
||||
| `CODEX_CLIENT_VERSION` | `0.131.0` | Override Codex client version independently of full UA string |
|
||||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user