From 5d1ad576a1ff09e61d537d4375992f53c3db5728 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Sun, 19 Jul 2026 19:07:44 -0300 Subject: [PATCH] feat(quality): gate the free-tier headline so it can never silently drift again (#7798) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(free-tiers): correct the headline to the 1.37B the catalog actually computes The 2026-06-17 honesty correction landed 1.54B, but v3.8.42 reclassified longcat from a 150M/mo recurring grant to a one-time 10M signup credit and the doc was never resynced. Verified against computeFreeModelTotals() at every release tag from 3.8.13 to HEAD: no free provider was lost by mistake. * feat(quality): gate the free-tier headline against the live catalog The README headlined ~1.6B free tokens/mo for seven releases after the catalog had already been corrected down to 1.37B. No gate watched that number, so the drift was invisible — check:docs-counts only covered providers, locales, executors, strategies, oauth, a2a skills and cloud agents. Adds a STRICT check that runs computeFreeModelTotals() (the same function behind /api/free-tier/summary) and fails the build when README.md or FREE_TIERS.md publish a headline that no longer rounds to it. Degrades to a skip if tsx is unavailable rather than going falsely red. The extractor is a whitelist: the theoretical ceiling (~10B), the historical ~1.94B and per-model rows (~1.00B) are legitimate figures that must never trip the gate. Also adds the biweekly-audit note under the README headline, so readers know the number moves both ways and is what the catalog computes rather than a rounded-up best case. * feat(quality): extend the counts gate to engines, MCP tools/scopes and CLI tools The v3.8.49 audit found four more numbers that had silently drifted, all invisible to CI because check:docs-counts only watched providers/locales/executors/strategies/oauth/ a2a/cloud-agents: 10->11 compression engines, 94->104 MCP tools, 30->31 scopes, 26->33 CLI tools. Adds a generic makeNumberClaimValidator that reads every fact in ONE tsx subprocess via the same functions the app serves (ENGINE_IDS, countUniqueMcpTools, the live scope union, CLI_TOOLS) — never a hardcoded copy — with DATA_DIR redirected to a throwaway dir so importing the MCP tool modules can't touch the operator's SQLite. Each check declares a skip pattern so legitimate non-aggregate figures never trip it: per-module tool counts ('Memory tool definitions (3 tools)') and the CLI catalog total sitting next to the MCP total. Degrades to a skip when tsx is unavailable rather than a false red. 7 new unit tests (all pass) covering the exact stale values this audit found and proving per-module counts are ignored. * docs(diagrams): sync the animated cards and mermaid sources to the audited v3.8.49 numbers The README text was fixed in #7795 but the SVG cards and mermaid sources kept the old numbers baked in — exactly the drift the readers see first. - compression-pipeline.svg: 10 -> 11 engine cells (Omniglyph added as #8, matching the README alt text), re-spaced 51px cells, highlight cascade re-timed, the Caveman kill-dot repositioned inside its cell, default-stack bracket recentered - free-tier-budget.svg: bar and grid rebuilt from computeFreeModelTotals() — 21 -> 19 countable pools (LongCat-2.0 moved to one-time credit, Inclusion provider removed), huggingchat entry is now ERNIE 4.5 VL, kiro shows Claude Sonnet 4.5, signup credits ~616M -> ~626M (+longcat 10M pill), aria said 'about 1.6 billion' -> 1.4/2.0, lower sections shifted up 30px (viewBox 872 -> 842) - promise-pillars.svg: 26 -> 33 coding agents - mcp-tools-94.mmd -> mcp-tools-104.mmd: real per-collection unique contributions (42 base + memory 3 + skill 4 + githubSkill 3 + pool 6 + gamification 8 + plugin 8 + notion 6 + obsidian 22 + compression 2), exported SVG regenerated, zh-CN ref synced - request-pipeline.mmd: 17 -> 18 strategies, exported SVG regenerated - README free-tier alt + docs/diagrams/README.md synced to the same numbers Both edited cards pass validate-svg.sh and were render-verified at 4 timestamps (animation runs; first frame is the finished composition). * docs(env): register the 4 env vars missing from the .env.example contract (base-red unblock) FREE_PROXY_AUTO_SYNC_ENABLED / FREE_PROXY_AUTO_SYNC_INTERVAL_MS (scheduler.ts) and MITM_ROOT_CA_ENABLED / MITM_CERT_MODE (mitm manager/server, #6684) landed on release/v3.8.49 without their .env.example + ENVIRONMENT.md entries, turning the docs-gates job red for every PR on the branch. Documented with their real defaults and the set-by-manager caveat for MITM_CERT_MODE. * fix(dashboard): narrow the Codex session ParseResult with an equality check (base-red unblock) #7725 landed 'if (!result.ok)' in OAuthModal — under this repo's strict:false, tsc 6 only narrows a discriminated union on the equality form, so the negation raised TS2339 (Property 'error' does not exist on ParseResult) and turned the dashboard-typecheck gate red for every PR on release/v3.8.49. Runtime semantics are identical (ok is a strict boolean). Also ratchets the frozen baseline down 260 -> 259: the real fix here plus 3 baselined errors that other merges already fixed (CostOverviewTab TS2304, SidebarTab TS2322, FreePoolTab TS2304). Baseline diff is deletions-only. * fix(dashboard): keep OAuthModal within the frozen file-size cap The narrowing comment pushed the file to 1032 > 1030 frozen; the rationale lives in the previous commit message and the dashboard-typecheck gate itself guards the '=== false' form from being refactored back to '!result.ok'. * test(providers): align the grok-web credential assertion with the #7567 hint (base-red unblock) #7713 added hintKey/hintFallback (proactive cf_clearance/User-Agent guidance) to the grok-web web-session metadata without touching this test's deepEqual, turning unit shard 2/4 red for every PR on release/v3.8.49. Rewritten in the same contract-only style the file already uses for lmarena: structural fields stay strictly asserted, the hint asserts key + intent (cf_clearance / User-Agent) without freezing operator copy. Net stronger than before — the old assertion never checked the hint at all. * test(golden): regenerate translate-path snapshot for the notion-web endpoint move (base-red unblock) #7768 switched notion-web to app.notion.com without regenerating the golden, turning unit shard 3/4 red for every PR on release/v3.8.49. Two-line regen, reflects the deliberate production change. --- .env.example | 17 ++ README.md | 4 +- .../quality/dashboard-typecheck-baseline.json | 9 - docs/diagrams/README.md | 4 +- docs/diagrams/compression-pipeline.svg | 133 ++++++------ docs/diagrams/exported/mcp-tools-104.svg | 1 + docs/diagrams/exported/mcp-tools-94.svg | 1 - docs/diagrams/exported/request-pipeline.svg | 2 +- docs/diagrams/free-tier-budget.svg | 180 ++++++++-------- .../{mcp-tools-94.mmd => mcp-tools-104.mmd} | 18 +- docs/diagrams/promise-pillars.svg | 4 +- docs/diagrams/request-pipeline.mmd | 2 +- docs/i18n/zh-CN/docs/frameworks/MCP-SERVER.md | 4 +- docs/reference/ENVIRONMENT.md | 4 + docs/reference/FREE_TIERS.md | 8 +- scripts/check/check-docs-counts-sync.mjs | 201 ++++++++++++++++++ src/shared/components/OAuthModal.tsx | 2 +- tests/snapshots/provider/translate-path.json | 4 +- tests/unit/check-docs-counts-sync.test.ts | 98 +++++++++ tests/unit/web-session-credentials.test.ts | 21 +- 20 files changed, 525 insertions(+), 192 deletions(-) create mode 100644 docs/diagrams/exported/mcp-tools-104.svg delete mode 100644 docs/diagrams/exported/mcp-tools-94.svg rename docs/diagrams/{mcp-tools-94.mmd => mcp-tools-104.mmd} (52%) diff --git a/.env.example b/.env.example index 032dabfd57..9f2869678c 100644 --- a/.env.example +++ b/.env.example @@ -1806,6 +1806,15 @@ APP_LOG_TO_FILE=true # Skip writing to the hosts file when adding/removing DNS entries (e.g. sandboxed # or read-only test environments). Used by: src/mitm/dns/dnsConfig.ts. # OMNIROUTE_SKIP_DNS_WRITE=1 +# Opt in to the root-CA + per-host-leaf cert model for the MITM proxy (#6684). +# Fresh installs and installs with this set to "true" get a persisted root CA that +# signs per-host leaves; installs with a pre-existing trusted legacy leaf keep the +# legacy fixed-SAN cert unless opted in. Used by: src/mitm/manager.ts. +# MITM_ROOT_CA_ENABLED=true +# Set BY the MITM manager for the spawned proxy process ("root-ca" | "legacy") — +# reflects the migration decision above; not meant to be set manually. +# Read by: src/mitm/server.cjs. +# MITM_CERT_MODE=legacy # ── Test/CI-only guards (never needed in production) ── # Set automatically by tests/_setup/isolateDataDir.ts and the CI workflows: the @@ -1825,6 +1834,14 @@ APP_LOG_TO_FILE=true # ONEPROXY_MAX_PROXIES=500 # ONEPROXY_MIN_QUALITY_THRESHOLD=50 +# ── Free Proxy Pool (auto-sync scheduler) ── +# Background refresh of the free-proxy pool. Opt-in, OFF by default (parallels +# Hard Rule #20's default-off posture for data-mutating background features). +# Used by: src/lib/freeProxyProviders/scheduler.ts +# FREE_PROXY_AUTO_SYNC_ENABLED=true +# Sync interval in ms (default: 1800000 = 30 min). +# FREE_PROXY_AUTO_SYNC_INTERVAL_MS=1800000 + # ── Free Proxy Pool (1proxy source) ── # Used by: src/lib/freeProxyProviders/oneproxy.ts # Set FREE_PROXY_1PROXY_ENABLED=false to disable this source. diff --git a/README.md b/README.md index 3ebc463ab4..e53aadc503 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ > Stacking free tiers by hand is painful — dozens of SDKs, dozens of rate limits, and no idea how much you actually have. OmniRoute aggregates the **documented** free tiers of **39 provider pools / 460+ models** into one honest number and shows it live on the dashboard (`/dashboard/free-tiers`). -OmniRoute free-tier budget card: ~1.4B free tokens per month steady, up to ~2.0B in the first month with signup credits, from the documented free tiers of 39 provider pools / 460+ models behind one endpoint. Honest pool-deduped math — each shared pool counted once (counting every rate limit 24/7 would read ~10B; not published), 15 providers ToS-flagged so you decide. Budget bar of the 21 countable free pools with per-model grid (Mistral Large 3 1B, GPT-4o mini 150M, LongCat 150M, Gemini 2.5 Flash 60M … Auto 25K), ~616M one-time first-month signup credits (vertex 300M, agentrouter 200M, predibase 25M, together 25M, glm-cn 20M, doubao 15M, ai21 10M, deepseek 5M, hyperbolic 5M), plus permanently-free no-token-cap providers (SiliconFlow, Z.AI GLM-Flash, Kilo, OpenCode Zen, baidu …) and a $10 OpenRouter top-up unlocking +24M/mo — surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers. +OmniRoute free-tier budget card: ~1.4B free tokens per month steady, up to ~2.0B in the first month with signup credits, from the documented free tiers of 39 provider pools / 460+ models behind one endpoint. Honest pool-deduped math — each shared pool counted once (counting every rate limit 24/7 would read ~10B; not published), 15 providers ToS-flagged so you decide. Budget bar of the 19 countable free pools with per-model grid (Mistral Large 3 1B, GPT-4o mini 150M, Gemini 2.5 Flash 60M … Claude Sonnet 4.5 25K), ~626M one-time first-month signup credits (vertex 300M, agentrouter 200M, predibase 25M, together 25M, glm-cn 20M, doubao 15M, ai21 10M, longcat 10M, deepseek 5M, hyperbolic 5M, nscale 5M), plus permanently-free no-token-cap providers (SiliconFlow, Z.AI GLM-Flash, Kilo, OpenCode Zen, baidu …) and a $10 OpenRouter top-up unlocking +24M/mo — surfaced separately so they never inflate the headline. Live used/remaining on /dashboard/free-tiers. > Animated summary of the live `/dashboard/free-tiers` page. Full methodology (pool dedupe, credit tiers, provider terms): **[docs/reference/FREE_TIERS.md](docs/reference/FREE_TIERS.md)**. +> +> These figures are re-audited every two weeks against the live catalog and **move both ways** — a provider ends a free tier and the number drops; a new one lands and it climbs. We publish what the catalog actually computes, never a rounded-up best case. A CI gate (`check:docs-counts`) fails the build if this headline drifts from the code.
diff --git a/config/quality/dashboard-typecheck-baseline.json b/config/quality/dashboard-typecheck-baseline.json index 4f7857d6ce..20617731af 100644 --- a/config/quality/dashboard-typecheck-baseline.json +++ b/config/quality/dashboard-typecheck-baseline.json @@ -63,9 +63,6 @@ "src/app/(dashboard)/dashboard/compression/studio/EncoderComparisonTable.tsx": { "TS2322": 1 }, - "src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": { - "TS2304": 1 - }, "src/app/(dashboard)/dashboard/costs/quota-share/QuotaSharePageClient.tsx": { "TS2551": 7, "TS2322": 2, @@ -184,12 +181,6 @@ "src/app/(dashboard)/dashboard/settings/components/RedisLauncherPanel.tsx": { "TS2345": 11 }, - "src/app/(dashboard)/dashboard/settings/components/SidebarTab.tsx": { - "TS2322": 1 - }, - "src/app/(dashboard)/dashboard/settings/components/proxy/FreePoolTab.tsx": { - "TS2304": 1 - }, "src/app/(dashboard)/dashboard/tools/traffic-inspector/components/CustomHostsManager.tsx": { "TS2339": 1 }, diff --git a/docs/diagrams/README.md b/docs/diagrams/README.md index dee34594f5..0fa9f35bca 100644 --- a/docs/diagrams/README.md +++ b/docs/diagrams/README.md @@ -16,7 +16,7 @@ Mermaid sources (`.mmd`) and exported SVGs for OmniRoute v3.8.0 architecture flo | [auto-combo-12factor.mmd](./auto-combo-12factor.mmd) | [SVG](./exported/auto-combo-12factor.svg) | docs/routing/AUTO-COMBO.md | | [resilience-3layers.mmd](./resilience-3layers.mmd) | [SVG](./exported/resilience-3layers.svg) | docs/architecture/RESILIENCE_GUIDE.md, CLAUDE.md | | [i18n-flow.mmd](./i18n-flow.mmd) | [SVG](./exported/i18n-flow.svg) | docs/guides/I18N.md | -| [mcp-tools-94.mmd](./mcp-tools-94.mmd) | [SVG](./exported/mcp-tools-94.svg) | docs/frameworks/MCP-SERVER.md | +| [mcp-tools-104.mmd](./mcp-tools-104.mmd) | [SVG](./exported/mcp-tools-104.svg) | docs/frameworks/MCP-SERVER.md | | [cloud-agent-flow.mmd](./cloud-agent-flow.mmd) | [SVG](./exported/cloud-agent-flow.svg) | docs/frameworks/CLOUD_AGENT.md | | [authz-pipeline.mmd](./authz-pipeline.mmd) | [SVG](./exported/authz-pipeline.svg) | docs/architecture/AUTHZ_GUIDE.md | | [db-schema-overview.mmd](./db-schema-overview.mmd) | [SVG](./exported/db-schema-overview.svg) | docs/architecture/CODEBASE_DOCUMENTATION.md | @@ -34,7 +34,7 @@ inside GitHub's `` sandbox: | [combo-always-on.svg](./combo-always-on.svg) | style reference | Animated priority-combo fallback (4 layers, 16s loop). Edit the SVG directly — there is no `.mmd` source. | | [cli-terminal.svg](./cli-terminal.svg) | README.md (root) | Compact half-height animated terminal (1200×350): 3 real CLI commands cycling with typewriter + scrolling subcommand ticker; first frame = completed providers screen. Edit the SVG directly — there is no `.mmd` source. | | [compression-pipeline.svg](./compression-pipeline.svg) | README.md (root) | Animated 10-engine compression funnel (8s loop). Edit the SVG directly — there is no `.mmd` source. | -| [free-tier-budget.svg](./free-tier-budget.svg) | README.md (root) | Animated free-tier budget card (~1.6B/mo headline, 21-pool budget bar, per-model grid, signup credits, 10s loop). Edit the SVG directly — there is no `.mmd` source. | +| [free-tier-budget.svg](./free-tier-budget.svg) | README.md (root) | Animated free-tier budget card (~1.4B/mo headline, 19-pool budget bar, per-model grid, signup credits, 10s loop). Edit the SVG directly — there is no `.mmd` source. | | [readme-hero.svg](./readme-hero.svg) | README.md (root) | Animated hero card (tagline, 268-provider/90+ free headline, full-width compression bar demo, 6 stat chips). Edit the SVG directly — there is no `.mmd` source. | | [promise-pillars.svg](./promise-pillars.svg) | README.md (root) | Animated "The Promise" 6-pillar card (12s border-highlight sweep). Edit the SVG directly — there is no `.mmd` source. | | [why-pain-fix.svg](./why-pain-fix.svg) | README.md (root) | Animated "Why OmniRoute" 10-row pain-vs-fix ledger (15s green row sweep). Edit the SVG directly — there is no `.mmd` source. | diff --git a/docs/diagrams/compression-pipeline.svg b/docs/diagrams/compression-pipeline.svg index e03d5293a5..72a12994e4 100644 --- a/docs/diagrams/compression-pipeline.svg +++ b/docs/diagrams/compression-pipeline.svg @@ -1,4 +1,4 @@ - + @@ -30,7 +30,7 @@ - OMNIROUTE COMPRESSION — 10 ENGINES + OMNIROUTE COMPRESSION — 11 ENGINES @@ -53,10 +53,10 @@ - + - + @@ -77,105 +77,114 @@ - + - - - + + + - 1 - Session - Dedup + 1 + Session + Dedup - - - + + + - 2 - CCR + 2 + CCR - - - + + + - 3 - RTK + 3 + RTK - - - + + + - 4 - Headroom + 4 + Headroom - - - + + + - 5 - Relevance + 5 + Relevance - - - + + + - 6 - Caveman + 6 + Caveman - - - + + + - 7 - LLM - Lingua-2 + 7 + LLM + Lingua-2 - - - - + + + + - 8 - Lite + 8 + Omniglyph - - - - + + + + - 9 - Aggressive + 9 + Lite - - - - + + + + - 10 - Ultra + 10 + Aggressive + + + + + + + + 11 + Ultra - - default stack: RTK → Caveman + + default stack: RTK → Caveman { } code · URLs · JSON — always preserved byte-perfect diff --git a/docs/diagrams/exported/mcp-tools-104.svg b/docs/diagrams/exported/mcp-tools-104.svg new file mode 100644 index 0000000000..3f750ae1fd --- /dev/null +++ b/docs/diagrams/exported/mcp-tools-104.svg @@ -0,0 +1 @@ +

MCP Server
104 tools total

Core (42)
routing, cache, compression,
quota, proxy, tunnels, search, web_fetch

Memory (3)
memory_search, memory_save,
memory_delete

Skills (4)
skill_invoke, skill_list,
skill_diagnose, skill_uninstall
+ agent-skill cli-registry discovery

GitHub-Skills (3)
marketplace search,
install, update

Pool (6)
pool_status, pool_sessions, pool_reset,
pool_warm, pool_health, browser_pool_status

Gamification (8)

Plugins (8)

Notion (6)
search, get_page, query_database,
get_database, list_block_children,
append_blocks

Obsidian (22)
vault read/write, search,
frontmatter, daily notes, …

Compression Studios (+2)
beyond the 11 in Core

\ No newline at end of file diff --git a/docs/diagrams/exported/mcp-tools-94.svg b/docs/diagrams/exported/mcp-tools-94.svg deleted file mode 100644 index e4edb1d769..0000000000 --- a/docs/diagrams/exported/mcp-tools-94.svg +++ /dev/null @@ -1 +0,0 @@ -

MCP Server
94 tools total

Core (34)
routing, cache, compression,
quota, proxy, tunnels, search, web_fetch

Memory (3)
memory_search, memory_save,
memory_delete

Skills (4)
skill_invoke, skill_list,
skill_diagnose, skill_uninstall

Agent-Skills (3)
cli-registry discovery

Pool (6)
pool_status, pool_sessions, pool_reset,
pool_warm, pool_health, browser_pool_status

Gamification (8)

Plugins (8)

Notion (6)
search, get_page, query_database,
get_database, list_block_children,
append_blocks

Obsidian (22)
vault read/write, search,
frontmatter, daily notes, …

\ No newline at end of file diff --git a/docs/diagrams/exported/request-pipeline.svg b/docs/diagrams/exported/request-pipeline.svg index f64c7903fe..9de7fb82a0 100644 --- a/docs/diagrams/exported/request-pipeline.svg +++ b/docs/diagrams/exported/request-pipeline.svg @@ -1 +1 @@ -

yes

no

combo

single

Client
(IDE/CLI/SDK)

Next.js Route
/v1/chat/completions

CORS preflight

Zod validation
(request body)

AuthZ pipeline
(extractApiKey +
isValidApiKey)

API key policy
(allowlist + scopes)

Prompt-injection
guardrail

handleChatCore

Cache hit?

Return cached

Rate limit
(per-key, per-IP)

Combo
target?

resolveComboTargets
(14 strategies)

handleSingleModel
(per target)

translateRequest
(OpenAI↔Claude↔Gemini)

getExecutor
(31 executors)

Upstream Provider
(177 providers)

SSE / JSON

responsesTransformer
(Responses↔Chat)

\ No newline at end of file +

yes

no

combo

single

Client
(IDE/CLI/SDK)

Next.js Route
/v1/chat/completions

CORS preflight

Zod validation
(request body)

AuthZ pipeline
(extractApiKey +
isValidApiKey)

API key policy
(allowlist + scopes)

Prompt-injection
guardrail

handleChatCore

Cache hit?

Return cached

Rate limit
(per-key, per-IP)

Combo
target?

resolveComboTargets
(18 strategies)

handleSingleModel
(per target)

translateRequest
(OpenAI↔Claude↔Gemini)

getExecutor
(31 executors)

Upstream Provider
(177 providers)

SSE / JSON

responsesTransformer
(Responses↔Chat)

\ No newline at end of file diff --git a/docs/diagrams/free-tier-budget.svg b/docs/diagrams/free-tier-budget.svg index 570a9abc75..fece1c76f6 100644 --- a/docs/diagrams/free-tier-budget.svg +++ b/docs/diagrams/free-tier-budget.svg @@ -1,4 +1,4 @@ - + @@ -27,9 +27,9 @@ - - - + + + FREE-TIER BUDGET · LIVE ON /dashboard/free-tiers @@ -79,32 +79,30 @@ counted once ✓ 15 providers ToS-flagged — we flag it · you decide - - WHERE IT COMES FROM · 21 COUNTABLE FREE POOLS + + WHERE IT COMES FROM · 19 COUNTABLE FREE POOLS - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -113,86 +111,88 @@ each segment = one free pool · widths floored so every provider shows · honest numbers below - + Mistral Large 3 1.00B GPT-4o mini 150M - LongCat-2.0-Preview 150M - Gemini 2.5 Flash 60M - GLM 4.7 30M - Llama 3.3 70B 30M - Grok-3 24M - DeepSeek V4 Pro 20M - GPT-4.1 18M - Llama 4 Scout 15M - Inclusion Model 15M - GPT-4o 7M - MiniMax-M2.7 6M - Arcee Trinity Large Prev 5M - Auto Free 4M - Auto 1M - Command A Reasoning 800K - Llama 3.3 70B 500K - morph-v3-large 400K - Llama 3.1 8B 200K - Auto 25K + Gemini 2.5 Flash 60M + GLM 4.7 30M + Llama 3.3 70B 30M + Grok-3 24M + DeepSeek V4 Pro 20M + GPT-4.1 18M + Llama 4 Scout 15M + GPT-4o 7M + MiniMax-M2.7 6M + Arcee Trinity Large Prev 5M + Auto Free 4M + Auto 1M + Command A Reasoning 800K + ERNIE 4.5 VL 424B 500K + morph-v3-large 400K + Llama 3.1 8B 200K + Claude Sonnet 4.5 25K - + FIRST MONTH · ONE-TIME SIGNUP CREDITS ~616M + + FIRST MONTH · ONE-TIME SIGNUP CREDITS ~626M - - vertex 300M - - agentrouter 200M - - predibase 25M - - together 25M - - glm-cn 20M - - doubao 15M - - ai21 10M - - deepseek 5M - - hyperbolic 5M + + vertex 300M + + agentrouter 200M + + predibase 25M + + together 25M + + glm-cn 20M + + doubao 15M + + ai21 10M + + deepseek 5M + + hyperbolic 5M + + longcat 10M + + - - PLUS THE UN-COUNTABLE — PERMANENTLY FREE · NO TOKEN CAP + + PLUS THE UN-COUNTABLE — PERMANENTLY FREE · NO TOKEN CAP - - SiliconFlow - - Z.AI GLM-Flash - - Kilo - - OpenCode Zen - - baidu - - + + SiliconFlow + + Z.AI GLM-Flash + + Kilo + + OpenCode Zen + + baidu + + - $10 OpenRouter top-up → +24M/mo - surfaced separately — never inflates the headline + $10 OpenRouter top-up → +24M/mo + surfaced separately — never inflates the headline - - CURRENT MONTH - - + + CURRENT MONTH + + - + - + - LIVE - used / remaining · per-model breakdown · transparent terms flag per provider + LIVE + used / remaining · per-model breakdown · transparent terms flag per provider diff --git a/docs/diagrams/mcp-tools-94.mmd b/docs/diagrams/mcp-tools-104.mmd similarity index 52% rename from docs/diagrams/mcp-tools-94.mmd rename to docs/diagrams/mcp-tools-104.mmd index bb629d4050..27967da552 100644 --- a/docs/diagrams/mcp-tools-94.mmd +++ b/docs/diagrams/mcp-tools-104.mmd @@ -1,16 +1,20 @@ %% MCP Server tool inventory by category -%% Reflects: open-sse/mcp-server/ — TOTAL_MCP_TOOL_COUNT in server.ts -%% (MCP_TOOLS 34 + memory 3 + skill 4 + agentSkill 3 + pool 6 + gamification 8 + plugin 8 + notion 6 + obsidian 22 = 94) +%% Reflects: open-sse/mcp-server/ — TOTAL_MCP_TOOL_COUNT in server.ts (countUniqueMcpTools) +%% Unique contributions: MCP_TOOLS 42 + memory 3 + skill 4 + githubSkill 3 + pool 6 +%% + gamification 8 + plugin 8 + notion 6 + obsidian 22 + compression 2 = 104 +%% (agentSkillTools fully overlaps the skill registry; 11 of the 13 compression +%% tools live in the base registry — duplicates counted once) %% Regenerate the SVG with: npm run docs:render-diagrams -%% v3.8.40 +%% v3.8.49 flowchart LR - MCP["MCP Server

94 tools total"] - MCP --> Core["Core (34)
routing, cache, compression,
quota, proxy, tunnels, search, web_fetch"] + MCP["MCP Server
104 tools total"] + MCP --> Core["Core (42)
routing, cache, compression,
quota, proxy, tunnels, search, web_fetch"] MCP --> Mem["Memory (3)
memory_search, memory_save,
memory_delete"] - MCP --> Skl["Skills (4)
skill_invoke, skill_list,
skill_diagnose, skill_uninstall"] - MCP --> ASkl["Agent-Skills (3)
cli-registry discovery"] + MCP --> Skl["Skills (4)
skill_invoke, skill_list,
skill_diagnose, skill_uninstall
+ agent-skill cli-registry discovery"] + MCP --> GSkl["GitHub-Skills (3)
marketplace search,
install, update"] MCP --> Pool["Pool (6)
pool_status, pool_sessions, pool_reset,
pool_warm, pool_health, browser_pool_status"] MCP --> Gam["Gamification (8)"] MCP --> Plg["Plugins (8)"] MCP --> Notion["Notion (6)
search, get_page, query_database,
get_database, list_block_children,
append_blocks"] MCP --> Obs["Obsidian (22)
vault read/write, search,
frontmatter, daily notes, …"] + MCP --> Cmp["Compression Studios (+2)
beyond the 11 in Core"] diff --git a/docs/diagrams/promise-pillars.svg b/docs/diagrams/promise-pillars.svg index 23d0e59143..e687bfb652 100644 --- a/docs/diagrams/promise-pillars.svg +++ b/docs/diagrams/promise-pillars.svg @@ -1,4 +1,4 @@ - + Animated promise card: six pillar tiles fade in in reading order, then a soft colored border highlight sweeps from tile to tile in a continuous cycle. @@ -91,7 +91,7 @@ Every tool works - 26 coding agents — Claude Code, Codex, + 33 coding agents — Claude Code, Codex, Cursor, Cline, Copilot, Antigravity — through one config. diff --git a/docs/diagrams/request-pipeline.mmd b/docs/diagrams/request-pipeline.mmd index 88261da1b5..2933c645c3 100644 --- a/docs/diagrams/request-pipeline.mmd +++ b/docs/diagrams/request-pipeline.mmd @@ -13,7 +13,7 @@ flowchart LR Cache -->|yes| Return["Return cached"] Cache -->|no| Rate["Rate limit
(per-key, per-IP)"] Rate --> Combo{"Combo
target?"} - Combo -->|combo| Resolve["resolveComboTargets
(17 strategies)"] + Combo -->|combo| Resolve["resolveComboTargets
(18 strategies)"] Resolve --> Single["handleSingleModel
(per target)"] Combo -->|single| Single Single --> Translate["translateRequest
(OpenAI↔Claude↔Gemini)"] diff --git a/docs/i18n/zh-CN/docs/frameworks/MCP-SERVER.md b/docs/i18n/zh-CN/docs/frameworks/MCP-SERVER.md index 9508bf8304..ef7f577df8 100644 --- a/docs/i18n/zh-CN/docs/frameworks/MCP-SERVER.md +++ b/docs/i18n/zh-CN/docs/frameworks/MCP-SERVER.md @@ -10,9 +10,9 @@ lastUpdated: 2026-06-28 > > 数据来源:`open-sse/mcp-server/schemas/tools.ts`(34 个基础工具)+ `memoryTools.ts`(3)+ `skillTools.ts`(4)+ `agentSkillTools.ts`(3)+ `poolTools.ts`(6)+ `gamificationTools.ts`(8)+ `pluginTools.ts`(8)+ `notionTools.ts`(6)+ `obsidianTools.ts`(22)= **94**(`TOTAL_MCP_TOOL_COUNT`)。工具注册和权限域绑定逻辑见 `open-sse/mcp-server/server.ts`。 -![MCP tool inventory (94 tools by category)](../diagrams/exported/mcp-tools-94.svg) +![MCP tool inventory (104 tools by category)](../diagrams/exported/mcp-tools-104.svg) -> 来源:[diagrams/mcp-tools-94.mmd](../diagrams/mcp-tools-94.mmd)(通过 `npm run docs:render-diagrams` 重新生成)。 +> 来源:[diagrams/mcp-tools-104.mmd](../diagrams/mcp-tools-104.mmd)(通过 `npm run docs:render-diagrams` 重新生成)。 ## 安装 diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 57f471a554..5967da22b0 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -1029,6 +1029,8 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy), | `MITM_DISABLE_TLS_VERIFY` | `0` | `src/mitm/server.cjs` | Set `1` to disable upstream TLS verification (development only). | | `MITM_IDLE_TIMEOUT_MS` | `60000` | `src/mitm/socketTimeouts.ts`, `src/mitm/server.cjs` | Idle socket timeout (ms) for proxied connections; idle sockets past this are torn down to avoid leaking half-open tunnels. | | `MITM_VERBOSE` | `1` | `src/mitm/server.cjs`, `src/mitm/_internal/bypass.cjs` | Routing-decision log verbosity: `0` silences, higher values log more bypass/route decisions. | +| `MITM_ROOT_CA_ENABLED` | `false` | `src/mitm/manager.ts` | Set `true` to opt in to the root-CA + per-host-leaf cert model (#6684). Fresh installs get it automatically; installs with a pre-existing trusted legacy leaf keep the legacy fixed-SAN cert unless opted in. | +| `MITM_CERT_MODE` | `legacy` | `src/mitm/manager.ts`, `src/mitm/server.cjs` | Set BY the MITM manager for the spawned proxy process (`root-ca` \| `legacy`) — reflects the cert-migration decision; not meant to be set manually. | | `OMNIROUTE_NO_SUDO` | `0` | `src/mitm/systemCommands.ts` | Set `1` (truthy) to strip the leading `sudo` from MITM cert-trust commands — for root-less / user-namespaced deployments where the operator trusts the CA manually (e.g. via Node's extra-CA-certs mechanism). | | `SKIP_ANTIGRAVITY_DNS` | _(unset)_ | `src/mitm/dns/provision.ts` | Set `true` to skip provisioning `/etc/hosts` DNS entries for the Antigravity proxy hostnames entirely — for containers with no sudo/root available. | | `OMNIROUTE_SKIP_DNS_WRITE` | _(unset)_ | `src/mitm/dns/dnsConfig.ts` | Set `1` to skip writing to the hosts file when adding/removing DNS entries — for sandboxed or read-only test environments. | @@ -1039,6 +1041,8 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy), | `ONEPROXY_API_URL` | `https://1proxy-api.aitradepulse.com` | `src/lib/oneproxySync.ts` | 1Proxy service API URL override. | | `ONEPROXY_MAX_PROXIES` | `500` | `src/lib/oneproxySync.ts` | Maximum proxies imported per sync. | | `ONEPROXY_MIN_QUALITY_THRESHOLD` | `50` | `src/lib/oneproxySync.ts` | Minimum quality score for imported proxies. | +| `FREE_PROXY_AUTO_SYNC_ENABLED` | `false` | `src/lib/freeProxyProviders/scheduler.ts` | Set `true` to enable the background free-proxy pool auto-sync scheduler. Opt-in, off by default. | +| `FREE_PROXY_AUTO_SYNC_INTERVAL_MS` | `1800000` | `src/lib/freeProxyProviders/scheduler.ts` | Auto-sync interval in milliseconds (default 30 min). | | `FREE_PROXY_1PROXY_ENABLED` | `true` | `src/lib/freeProxyProviders/oneproxy.ts` | Enable the 1proxy free proxy source. Set to `false` to disable. | | `FREE_PROXY_1PROXY_API_URL` | _(see oneproxy.ts)_ | `src/lib/freeProxyProviders/oneproxy.ts` | 1proxy API URL override. | | `FREE_PROXY_1PROXY_MAX` | `500` | `src/lib/freeProxyProviders/oneproxy.ts` | Maximum proxies fetched per sync from 1proxy. | diff --git a/docs/reference/FREE_TIERS.md b/docs/reference/FREE_TIERS.md index 936af9e78c..bac9c0f725 100644 --- a/docs/reference/FREE_TIERS.md +++ b/docs/reference/FREE_TIERS.md @@ -15,15 +15,17 @@ lastUpdated: 2026-06-28 | Metric | Tokens / month | Meaning | | ------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **Documented recurring grant (steady)** | **~1.54B** | Free-tier **pools** (per-model catalog), each shared pool counted **once**. The live source behind `/api/free-tier/summary` and the dashboard's Free-Tier Budget page. **Use this number.** | -| **+ first month with signup credits** | **~2.15B** | Steady + one-time signup credits (Together $25, Z.AI 20M, DeepSeek 5M, …), deduped per account. **First month only** — does not recur. | +| **Documented recurring grant (steady)** | **~1.37B** | Free-tier **pools** (per-model catalog), each shared pool counted **once**. The live source behind `/api/free-tier/summary` and the dashboard's Free-Tier Budget page. **Use this number.** | +| **+ first month with signup credits** | **~2.00B** | Steady + one-time signup credits (Together $25, Z.AI 20M, DeepSeek 5M, …), deduped per account. **First month only** — does not recur. | | **+ permanently free, no published cap** | _un-quantifiable_ | `siliconflow`, `glm-cn` (GLM-4-Flash), `tencent`, `baidu`, `kilo-gateway`, `opencode-zen` — real recurring access, rate/concurrency-limited, **no token cap to count**. Listed, never summed (counting them at `RPM×24/7` is the inflation we reject). | | **+ deposit-unlock boost** | **+~24M** | A one-time **$10** OpenRouter top-up raises its free pool from 50 → 1000 req/day. Reported separately so it never inflates the steady number. | | Theoretical ceiling (all rate limits, 24/7) | ~10B | Sum of every provider rate limit extrapolated to non-stop use. **Not a guarantee** — do not headline this. | -**Honest headline:** _OmniRoute aggregates **~1.6B documented free tokens per month** (up to ~2.1B in your first month with signup credits) across 40+ free-tier pools — plus a long tail of permanently-free, no-cap providers — and RTK + Caveman compression (15–95% token savings) stretches that further._ +**Honest headline:** _OmniRoute aggregates **~1.37B documented free tokens per month** (up to ~2.0B in your first month with signup credits) across 39 free-tier pools — plus a long tail of permanently-free, no-cap providers — and RTK + Caveman compression (15–95% token savings) stretches that further._ > **Why this dropped from the previous ~1.94B.** The 2026-06-17 refresh is an honesty correction, not a loss: `gemini` is now pool-deduped (was inflated by counting each Flash variant separately, 462M → 60M), `cloudflare-ai` corrected to its real 10k-Neurons/day (122M → 30M), `doubao` reclassified as a one-time signup credit (not recurring), and shut-down tiers removed (`github-models` closed to new signups, `chutes`/`phind`/`kluster` discontinued). Partly offset by `llm7` (correct 5M/day → 150M) and new free providers (Kilo, OpenCode Zen, Z.AI GLM-Flash). +> +> **Further corrected to ~1.37B in v3.8.42:** `longcat` was reclassified from a 150M/mo recurring grant to a one-time 10M signup credit after its free preview ended. Same honesty rule — no provider was dropped by mistake. Biggest **documented** contributors: `mistral` 1.00B, `llm7` 150M, `groq` 117M, `gemini` 60M, `cerebras` 30M, `cloudflare-ai` 30M, `sambanova` 30M. (`longcat` is excluded — its 10M LongCat-2.0 grant is a one-time, KYC-gated signup credit, not a recurring monthly budget.) diff --git a/scripts/check/check-docs-counts-sync.mjs b/scripts/check/check-docs-counts-sync.mjs index 242a5d9b84..024cdb1cb0 100644 --- a/scripts/check/check-docs-counts-sync.mjs +++ b/scripts/check/check-docs-counts-sync.mjs @@ -23,6 +23,8 @@ // not catch it — regenerate with `npm run gen:provider-reference` before relying on it. import fs from "node:fs"; +import { spawnSync } from "node:child_process"; +import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -102,6 +104,16 @@ export function tallyDrift(checks, getContent) { } for (const f of c.files) { const content = getContent(f); + if (c.validate) { + if (content == null) continue; + const v = c.validate(content); + lines.push(` ${v.ok ? "✓" : c.strict ? "✗" : "⚠"} ${f} — ${v.detail}`); + if (!v.ok) { + if (c.strict) strict++; + else soft++; + } + continue; + } const found = content != null && content.includes(String(c.actual)); if (found) { lines.push(` ✓ ${f} mentions "${c.actual}"`); @@ -115,6 +127,131 @@ export function tallyDrift(checks, getContent) { return { strict, soft, lines }; } +// Reads every code-derived fact in ONE tsx subprocess — the same functions the app +// serves at runtime, never a hardcoded copy. DATA_DIR is redirected to a throwaway dir +// so importing the MCP tool modules cannot touch the operator's real SQLite file. +// Returns null when tsx is unavailable so the gate degrades to a skip, not a false red. +function readCodeFacts() { + const script = [ + 'import {computeFreeModelTotals} from "./open-sse/config/freeModelCatalog.ts";', + 'import {ENGINE_IDS} from "./open-sse/services/compression/engineCatalog.ts";', + 'import {CLI_TOOLS} from "./src/shared/constants/cliTools.ts";', + 'import {countUniqueMcpTools} from "./open-sse/mcp-server/toolCount.ts";', + 'import {MCP_TOOLS} from "./open-sse/mcp-server/schemas/tools.ts";', + 'import {memoryTools} from "./open-sse/mcp-server/tools/memoryTools.ts";', + 'import {skillTools} from "./open-sse/mcp-server/tools/skillTools.ts";', + 'import {agentSkillTools} from "./open-sse/mcp-server/tools/agentSkillTools.ts";', + 'import {githubSkillTools} from "./open-sse/mcp-server/tools/githubSkillTools.ts";', + 'import {poolTools} from "./open-sse/mcp-server/tools/poolTools.ts";', + 'import {gamificationTools} from "./open-sse/mcp-server/tools/gamificationTools.ts";', + 'import {pluginTools} from "./open-sse/mcp-server/tools/pluginTools.ts";', + 'import {notionTools} from "./open-sse/mcp-server/tools/notionTools.ts";', + 'import {obsidianTools} from "./open-sse/mcp-server/tools/obsidianTools.ts";', + 'import {compressionTools} from "./open-sse/mcp-server/tools/compressionTools.ts";', + "const cols={MCP_TOOLS,memoryTools,skillTools,agentSkillTools,githubSkillTools,poolTools,", + "gamificationTools,pluginTools,notionTools,obsidianTools,compressionTools};", + "const sc=new Set();", + "for(const col of Object.values(cols))for(const t of Object.values(col))", + "for(const x of (t?.scopes||[]))sc.add(x);", + "const t=computeFreeModelTotals();const cli=Object.values(CLI_TOOLS);", + "const by=(c)=>cli.filter(x=>x.category===c).length;", + 'console.log("@@"+JSON.stringify({freeSteady:t.steadyRecurringTokens,', + "freeFirst:t.firstMonthRealisticTokens,freePools:t.poolCount,engines:ENGINE_IDS.length,", + "cliTotal:cli.length,cliCode:by('code'),cliAgent:by('agent'),", + "mcpTools:countUniqueMcpTools(cols),mcpScopes:sc.size}));", + ].join(""); + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "docs-counts-")); + try { + const r = spawnSync(process.execPath, ["--import", "tsx/esm", "-e", script], { + cwd: ROOT, + encoding: "utf8", + timeout: 180000, + env: { ...process.env, DATA_DIR: tmp, APP_LOG_LEVEL: "silent" }, + }); + if (r.status !== 0 || !r.stdout) return null; + const line = r.stdout.split("\n").find((l) => l.startsWith("@@")); + return line ? JSON.parse(line.slice(2)) : null; + } catch { + return null; + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } +} + +// The docs publish a rounded aggregate headline ("~1.4B"). Accept a claim that rounds to +// the live value at one decimal place; reject a stale one. Tolerance is tight on purpose: +// this gate exists so the headline cannot drift upward unnoticed. +// +// Only the AGGREGATE headline is validated, via an explicit whitelist. These files also +// carry figures that are legitimately not the headline and must never trip the gate: +// the theoretical ceiling ("would read ~10B; not published"), the historical "previous +// ~1.94B", and per-model rows ("mistral … ~1.00B"). A whitelist keeps those safe without +// having to enumerate every contrastive phrasing. +const HEADLINE_AFTER = /^\s*(?:documented\s+)?free tokens|^\s*in (?:your|the) first month/i; +const HEADLINE_BEFORE = /(recurring grant[^|]*\|\s*\**|signup credits[^|]*\|\s*\**|up to\s*)$/i; + +export function extractHeadlineClaims(content) { + const claims = []; + for (const m of content.matchAll(/~(\d+(?:\.\d+)?)B/g)) { + const after = content.slice(m.index + m[0].length, m.index + m[0].length + 80); + const before = content.slice(Math.max(0, m.index - 80), m.index); + if (!HEADLINE_AFTER.test(after) && !HEADLINE_BEFORE.test(before)) continue; + claims.push({ value: Number(m[1]), text: m[0] }); + } + return claims; +} + +export function checkFreeTierHeadline(content, totals) { + const claims = extractHeadlineClaims(content); + if (!claims.length) return { ok: true, detail: "no aggregate free-tier headline in this file" }; + const steady = totals.s / 1e9; + const first = totals.m / 1e9; + const stale = claims.filter( + (c) => Math.abs(c.value - steady) >= 0.05 && Math.abs(c.value - first) >= 0.05 + ); + if (!stale.length) + return { ok: true, detail: `${claims.length} headline claim(s) match the live catalog` }; + return { + ok: false, + detail: + `stale headline ${[...new Set(stale.map((c) => c.text))].join(", ")} — live catalog ` + + `computes ~${steady.toFixed(2)}B steady / ~${first.toFixed(2)}B first month`, + }; +} + +// --- Generic numeric-claim gate --------------------------------------------- +// Same principle as the free-tier headline: docs legitimately carry numbers that are +// NOT the aggregate being gated (per-module tool counts like "Memory tool definitions +// (3 tools)", the CLI catalog's "33 tools (25 CLI Code's ...)" next to the MCP total). +// So every check declares what to skip rather than assuming any "N tools" is the claim. +export function extractNumberClaims(content, { pattern, skipBefore, skipAfter }) { + const claims = []; + for (const m of content.matchAll(pattern)) { + const before = content.slice(Math.max(0, m.index - 40), m.index); + const after = content.slice(m.index + m[0].length, m.index + m[0].length + 40); + if (skipBefore && skipBefore.test(before)) continue; + if (skipAfter && skipAfter.test(after)) continue; + claims.push({ value: Number(m[1]), text: m[0].trim() }); + } + return claims; +} + +export function makeNumberClaimValidator(expected, opts) { + return (content) => { + const claims = extractNumberClaims(content, opts); + if (!claims.length) return { ok: true, detail: `no ${opts.what} claim in this file` }; + const stale = claims.filter((c) => c.value !== expected); + if (!stale.length) + return { ok: true, detail: `${claims.length} ${opts.what} claim(s) match the code` }; + return { + ok: false, + detail: + `stale ${opts.what}: ${[...new Set(stale.map((c) => `"${c.text}"`))].join(", ")} — ` + + `code has ${expected}`, + }; + }; +} + export function buildChecks() { return [ { @@ -131,6 +268,70 @@ export function buildChecks() { strict: true, files: ["docs/README.md", "docs/guides/I18N.md", "AGENTS.md"], }, + ...(() => { + const f = readCodeFacts(); + if (!f) + return [ + { + label: "Code-derived counts", + actual: 0, + docKey: "code facts", + strict: false, + files: [], + }, + ]; + const claim = (expected, what, opts, files) => ({ + label: `${what} (live code)`, + actual: expected, + docKey: what, + strict: true, + files, + validate: makeNumberClaimValidator(expected, { what, ...opts }), + }); + return [ + { + label: "Free-tier headline (live catalog)", + actual: `~${(f.freeSteady / 1e9).toFixed(2)}B steady / ${f.freePools} pools`, + docKey: "free-tier headline", + strict: true, + files: ["README.md", "docs/reference/FREE_TIERS.md"], + validate: (content) => + checkFreeTierHeadline(content, { s: f.freeSteady, m: f.freeFirst }), + }, + claim( + f.engines, + "compression engines", + { pattern: /(\d+)[-\s](?:engine stack|composable engines|stacked engines)/gi }, + ["README.md"] + ), + claim( + f.mcpTools, + "MCP tools", + { + pattern: /(\d+) tools/gi, + // per-module rows ("Memory tool definitions (3 tools)") and the CLI catalog + // total ("33 tools (25 CLI Code's …)") are not the MCP aggregate + // per-module rows read "… tool definitions (N tools" / "… management tools + // (N tools" — the word tool(s)/definitions sits right before the paren. The + // aggregate ("MCP Server (104 tools", "all 104 tools") never does. + skipBefore: /(tools?|definitions?)\s*\(\s*$/i, + skipAfter: /^\s*\(\d+ CLI/, + }, + ["README.md", "CLAUDE.md", "AGENTS.md", "docs/frameworks/MCP-SERVER.md"] + ), + claim(f.mcpScopes, "MCP scopes", { pattern: /(\d+) scopes/gi }, [ + "README.md", + "CLAUDE.md", + "AGENTS.md", + ]), + claim( + f.cliTotal, + "CLI tools", + { pattern: /(\d+) tools(?=\s*\(\d+ CLI)/gi }, + ["README.md"] + ), + ]; + })(), { label: "Executors count", actual: countFiles("open-sse/executors"), diff --git a/src/shared/components/OAuthModal.tsx b/src/shared/components/OAuthModal.tsx index 7d079724b2..9daefd84e9 100644 --- a/src/shared/components/OAuthModal.tsx +++ b/src/shared/components/OAuthModal.tsx @@ -705,7 +705,7 @@ export default function OAuthModal({ // (`{user, accessToken, expires}`), not just the bare token (#6636). if (provider === "codex" && looksLikeCodexSessionJson(callbackUrl)) { const result = parseCodexSessionJson(JSON.parse(callbackUrl.trim())); - if (!result.ok) { + if (result.ok === false) { setError(result.error); return; } diff --git a/tests/snapshots/provider/translate-path.json b/tests/snapshots/provider/translate-path.json index 6d401fc86d..0656be2a47 100644 --- a/tests/snapshots/provider/translate-path.json +++ b/tests/snapshots/provider/translate-path.json @@ -3212,8 +3212,8 @@ } }, "url": { - "nonStream": "https://www.notion.so/api/v3/runInferenceTranscript", - "stream": "https://www.notion.so/api/v3/runInferenceTranscript" + "nonStream": "https://app.notion.com/api/v3/runInferenceTranscript", + "stream": "https://app.notion.com/api/v3/runInferenceTranscript" } }, "nous-research": { diff --git a/tests/unit/check-docs-counts-sync.test.ts b/tests/unit/check-docs-counts-sync.test.ts index 3c7256f6aa..040876f438 100644 --- a/tests/unit/check-docs-counts-sync.test.ts +++ b/tests/unit/check-docs-counts-sync.test.ts @@ -98,3 +98,101 @@ test("the gate exits 0 against the current (synced) repo state", () => { // Throws if exit code is non-zero; current docs are synced so this must pass. assert.doesNotThrow(() => execFileSync("node", [GATE], { encoding: "utf8", stdio: "pipe" })); }); + +// --- Free-tier headline gate ------------------------------------------------ +// Regression guard for the drift found in the v3.8.49 README audit: the README +// headlined ~1.6B for seven releases after the catalog had already been corrected +// down to 1.37B, because no gate watched that number. +import { + checkFreeTierHeadline, + extractHeadlineClaims, +} from "../../scripts/check/check-docs-counts-sync.mjs"; + +const checkHeadline = checkFreeTierHeadline as ( + content: string, + totals: { s: number; m: number; p: number } +) => { ok: boolean; detail: string }; +const extractClaims = extractHeadlineClaims as ( + content: string +) => { value: number; text: string }[]; + +const TOTALS = { s: 1_371_725_000, m: 1_998_225_000, p: 39 }; + +test("free-tier gate accepts a headline that rounds to the live catalog", () => { + assert.equal(checkHeadline("~1.4B free tokens per month", TOTALS).ok, true); + assert.equal(checkHeadline("up to ~2.0B in the first month", TOTALS).ok, true); +}); + +test("free-tier gate rejects the stale headlines this audit found", () => { + for (const stale of ["~1.6B free tokens/mo", "~1.54B free tokens per month"]) { + const r = checkHeadline(stale, TOTALS); + assert.equal(r.ok, false, `expected ${stale} to be rejected`); + assert.match(r.detail, /live catalog computes/); + } + assert.equal(checkHeadline("up to ~2.1B in the first month", TOTALS).ok, false); +}); + +test("free-tier gate ignores non-headline figures", () => { + // The theoretical ceiling, the historical value and per-model rows are legitimate + // and must never trip the gate — that is why the extractor is a whitelist. + const noise = + "counting every rate limit 24/7 would read ~10B; not published. " + + "Why this dropped from the previous ~1.94B. | `mistral` | recurring | ~1.00B |"; + assert.deepEqual(extractClaims(noise), []); + assert.equal(checkHeadline(noise, TOTALS).ok, true); +}); + +test("free-tier gate passes when a file carries no headline at all", () => { + assert.equal(checkHeadline("no figures here", TOTALS).ok, true); +}); + +// --- Generic numeric-claim gate (engines / MCP tools / scopes / CLI) -------- +// Extends the same drift guard to the counts that silently drifted in v3.8.49: +// 10→11 engines, 94→104 MCP tools, 30→31 scopes, 26→33 CLI tools. +import { makeNumberClaimValidator } from "../../scripts/check/check-docs-counts-sync.mjs"; + +const makeValidator = makeNumberClaimValidator as ( + expected: number, + opts: { what: string; pattern: RegExp; skipBefore?: RegExp; skipAfter?: RegExp } +) => (content: string) => { ok: boolean; detail: string }; + +test("MCP-tools gate accepts the aggregate and rejects a stale one", () => { + const v = makeValidator(104, { + what: "MCP tools", + pattern: /(\d+) tools/gi, + skipBefore: /(tools?|definitions?)\s*\(\s*$/i, + skipAfter: /^\s*\(\d+ CLI/, + }); + assert.equal(v("MCP Server (104 tools)").ok, true); + assert.equal(v("with 104 tools total").ok, true); + assert.equal(v("MCP Server (94 tools)").ok, false); +}); + +test("MCP-tools gate ignores per-module counts and the CLI catalog total", () => { + const v = makeValidator(104, { + what: "MCP tools", + pattern: /(\d+) tools/gi, + skipBefore: /(tools?|definitions?)\s*\(\s*$/i, + skipAfter: /^\s*\(\d+ CLI/, + }); + // "Memory tool definitions (3 tools)" and "33 tools (25 CLI Code's)" are not the MCP total + assert.equal(v("Memory tool definitions (3 tools)").ok, true); + assert.equal(v("management tools (8 tools)").ok, true); + assert.equal(v("all 33 tools (25 CLI Code's + 8 CLI Agents)").ok, true); +}); + +test("compression-engines and CLI-tools gates catch their v3.8.49 drift", () => { + const eng = makeValidator(11, { + what: "compression engines", + pattern: /(\d+)[-\s](?:engine stack|composable engines|stacked engines)/gi, + }); + assert.equal(eng("11-engine stack").ok, true); + assert.equal(eng("10-engine stack").ok, false); + + const cli = makeValidator(33, { + what: "CLI tools", + pattern: /(\d+) tools(?=\s*\(\d+ CLI)/gi, + }); + assert.equal(cli("all 33 tools (25 CLI Code's)").ok, true); + assert.equal(cli("all 26 tools (25 CLI Code's)").ok, false); +}); diff --git a/tests/unit/web-session-credentials.test.ts b/tests/unit/web-session-credentials.test.ts index 250ac5ea39..55d6fe11a9 100644 --- a/tests/unit/web-session-credentials.test.ts +++ b/tests/unit/web-session-credentials.test.ts @@ -15,14 +15,19 @@ test("web session credential metadata covers every web-cookie provider", () => { }); test("web session credential metadata identifies cookie, token, and no-auth providers", () => { - // Grok needs BOTH sso and sso-rw cookies (#3180) - assert.deepEqual(webSessionCredentials.getWebSessionCredentialRequirement("grok-web"), { - kind: "cookie", - credentialName: "sso + sso-rw", - placeholder: "sso=...; sso-rw=...", - acceptsFullCookieHeader: true, - storageKeys: ["cookie", "sso", "sso-rw"], - }); + // Grok needs BOTH sso and sso-rw cookies (#3180). #7567 added the proactive + // cf_clearance/User-Agent hint — assert its intent, don't freeze operator copy. + { + const req = webSessionCredentials.getWebSessionCredentialRequirement("grok-web"); + assert.ok(req && req.kind === "cookie"); + assert.equal(req.credentialName, "sso + sso-rw"); + assert.equal(req.placeholder, "sso=...; sso-rw=..."); + assert.equal(req.acceptsFullCookieHeader, true); + assert.deepEqual(req.storageKeys, ["cookie", "sso", "sso-rw"]); + assert.equal(req.hintKey, "grokWebCookieHint"); + assert.ok(typeof req.hintFallback === "string" && /cf_clearance/.test(req.hintFallback)); + assert.ok(/User-Agent/.test(req.hintFallback)); + } assert.deepEqual(webSessionCredentials.getWebSessionCredentialRequirement("copilot-web"), { kind: "token", credentialName: "access_token",