mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 05:02:15 +03:00
Merge remote-tracking branch 'origin/release/v3.8.49' into fix-7251-complexity
This commit is contained in:
18
README.md
18
README.md
@@ -255,14 +255,7 @@ All **18** strategies — mix & match per combo step:
|
||||
| 🚦 **Policy** | `hard` (block over share) · `soft` (deprioritize) · `burst` (use idle headroom) |
|
||||
| 🧱 **Cap** | absolute ceiling per key, independent of mode |
|
||||
|
||||
```
|
||||
Pool "team-codex" · 1 Codex Pro account · 3 keys · 5-hour window
|
||||
├─ alice weight 50 ██████████░░░░░░░░░░ ≤ 50% of the shared 5h quota
|
||||
├─ bob weight 30 ██████░░░░░░░░░░░░░░ ≤ 30%
|
||||
└─ ci-bot weight 20 ████░░░░░░░░░░░░░░░░ ≤ 20%
|
||||
Generous mode (<50% pool used) → idle shares are lent out
|
||||
Strict mode (≥50% pool used) → each key held to its fair share
|
||||
```
|
||||
<img src="./docs/diagrams/pool-fair-share.svg" width="100%" alt="OmniRoute key pool 'team-codex': one Codex Pro account shared by 3 keys over a 5-hour window. alice weight 50 (up to 50% of the shared 5h quota), bob weight 30, ci-bot weight 20. In generous mode (under 50% pool used) idle shares are lent out; once the pool crosses 50% strict mode holds each key to its fair share."/>
|
||||
|
||||
<sub>Enforced in the hot path **before** the request leaves OmniRoute, with per-(key, model) caps + session stickiness for prompt-cache integrity (now with a per-combo / global disable toggle). 📖 [Quota Sharing Engine](docs/routing/QUOTA_SHARE.md)</sub>
|
||||
|
||||
@@ -276,14 +269,7 @@ Strict mode (≥50% pool used) → each key held to its fair share
|
||||
| 💤 **Connection cooldown** | one account / key | Skips a rate-limited key while other keys keep serving |
|
||||
| 🎯 **Model lockout** | provider + model | Quarantines just one quota-limited model, not the whole connection |
|
||||
|
||||
```
|
||||
Combo: "always-on" Strategy: priority
|
||||
1. cc/claude-opus-4-7 ← subscription (use it fully)
|
||||
2. cx/gpt-5.5 ← second subscription
|
||||
3. glm/glm-5.1 ← cheap backup ($0.5/1M)
|
||||
4. kr/claude-sonnet-4.5 ← FREE, unlimited (never fails)
|
||||
Result: 4 layers of fallback = zero downtime
|
||||
```
|
||||
<img src="./docs/diagrams/combo-always-on.svg" width="100%" alt="OmniRoute combo 'always-on' with priority strategy: requests go to 1. cc/claude-opus-4-7 (subscription, use it fully); on failure fall through to 2. cx/gpt-5.5 (second subscription), then 3. glm/glm-5.1 (cheap backup at $0.5/1M), then 4. kr/claude-sonnet-4.5 (free, unlimited, never fails). Result: 4 layers of fallback = zero downtime."/>
|
||||
|
||||
<sub>📖 [Auto-Combo Engine](docs/routing/AUTO-COMBO.md) · [Resilience Guide](docs/architecture/RESILIENCE_GUIDE.md)</sub>
|
||||
|
||||
|
||||
1
changelog.d/maintenance/7626-readme-pool-combo-svg.md
Normal file
1
changelog.d/maintenance/7626-readme-pool-combo-svg.md
Normal file
@@ -0,0 +1 @@
|
||||
- **README animated diagrams** (`docs/diagrams/pool-fair-share.svg`, `docs/diagrams/combo-always-on.svg`): the last two ASCII blocks in the root README — the `"team-codex"` key-pool fair-share example and the `"always-on"` priority combo — are now hand-authored SMIL SVG animations (16s loops, DESIGN_SYSTEM.md palette, GitHub `<img>`-sandbox safe), completing the set started by tier-cascade.svg (#7615). Full block copy preserved verbatim in the `img` alt text; both registered in `docs/diagrams/README.md`.
|
||||
1
changelog.d/maintenance/v3849-campaign-basereds.md
Normal file
1
changelog.d/maintenance/v3849-campaign-basereds.md
Normal file
@@ -0,0 +1 @@
|
||||
- **maintenance(quality):** clear v3.8.49 owner-PR-campaign base-reds on `release/v3.8.49` (accumulated because fast-gates PR→release skip the complexity/golden/file-size ratchets, surfaced only on re-sync): regenerate the `provider/translate-path` golden snapshot for the new providers; re-point the `#6772` connId-prefix test off the now-reserved `fta` alias (claimed by freetheai #7602 — built-in aliases shadow custom-node prefixes by design); bump the complexity baseline 2056→2058 for the new provider dispatch handlers. No behavior change.
|
||||
@@ -1,6 +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.",
|
||||
"count": 2056,
|
||||
"count": 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.",
|
||||
"_rebaseline_2026_07_10_v3847_merge_burst": "2053->2054 (+1). Drift herdado do merge burst do dia em release/v3.8.47 (campanha /implement-prs: ~36 PRs mergeados — órfãos, features do dono, ports). O check:complexity NÃO roda no fast-path PR->release, então o ramo acumulou o +1 sem rebaselinar (mesma família de todos os rebaselines abaixo). Trust-but-verify: medido 2054 no tip da release pós-burst; a única função flagada nova é pré-existente (getResolvedModelCapabilities em modelCapabilities.ts, já >teto antes de #6714). Nenhum PR órfão/feature introduz violação NOVA — os fixes deste ciclo são complexity-net-zero. Rebaseline aprovado pelo dono (2026-07-10) para destravar o FQG dos ~7 órfãos verdes-exceto-complexity. Tighten via --update next cycle.",
|
||||
"_rebaseline_2026_07_10_gcf_v3_2": "2054->2056 (+2). PR feat/headroom-gcf-v3.2-nested-flattening: own growth from re-vendoring the GCF (Headroom) codec to spec v3.2 (nested flattening). The 2 new over-threshold functions are the v3.2 `>`-path flatten/unflatten walk in the vendored generic-profile encode/decode paths (open-sse/services/compression/engines/headroom/gcf/{generic,decode_generic}.ts). This is imported third-party code kept byte-faithful to upstream gcf-typescript, not extractable without diverging from the vendored source; local measures 2055 on the merged tree; frozen at 2056 = the base's CI-observed 2054 + this PR's 2 new functions, matching the documented local-vs-CI off-by-one convention (see _rebaseline_2026_07_02_v3844_ci_observed) so the GitHub runner stays green. Round-trip guarded by tests/unit/compression/headroom-smartcrusher.test.ts (deep-nested case). Structural shrink belongs upstream in gcf, not here.",
|
||||
"_rebaseline_2026_07_08_6556_inherited_drift": "2052->2053 (+1). PR #6556 (omniglyph engine): drift herdado do merge burst da base (a catraca nao roda no fast-path PR->release, mesmo padrao dos rebaselines v3.8.44/46). Trust-but-verify: o proprio codigo do PR e complexity-net-zero — as 2 violacoes que ele introduzia (runCompressionAsync complexity 17 apos o branch do modo omniglyph; OmniglyphContextPageClient 161 linhas) foram CORRIGIDAS por extracao real (engines/omniglyphSingleMode.ts + split do componente em section components), medido: 2055->2053 local; base pura origin/release/v3.8.47 mede 2053 identico. Tighten via --update next cycle.",
|
||||
|
||||
@@ -30,6 +30,8 @@ inside GitHub's `<img>` sandbox:
|
||||
| File | Used in | Notes |
|
||||
| ---------------------------------------- | ---------------- | ---------------------------------------------------------------------------- |
|
||||
| [tier-cascade.svg](./tier-cascade.svg) | README.md (root) | Animated 4-tier auto-fallback cascade (16s loop, 4 acts). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [pool-fair-share.svg](./pool-fair-share.svg) | README.md (root) | Animated key-pool fair-share quota (generous → strict, 16s loop). Edit the SVG directly — there is no `.mmd` source. |
|
||||
| [combo-always-on.svg](./combo-always-on.svg) | README.md (root) | Animated priority-combo fallback (4 layers, 16s loop). Edit the SVG directly — there is no `.mmd` source. |
|
||||
|
||||
## How to update
|
||||
|
||||
|
||||
230
docs/diagrams/combo-always-on.svg
Normal file
230
docs/diagrams/combo-always-on.svg
Normal file
@@ -0,0 +1,230 @@
|
||||
<svg viewBox="0 0 1200 650" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute combo 'always-on' with priority strategy: requests go to 1. cc/claude-opus-4-7 (subscription, use it fully); on failure fall through to 2. cx/gpt-5.5 (second subscription), then 3. glm/glm-5.1 (cheap backup at $0.5/1M), then 4. kr/claude-sonnet-4.5 (free, unlimited, never fails). Result: 4 layers of fallback, zero downtime.">
|
||||
<defs>
|
||||
<radialGradient id="hubGlowC" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#e54d5e" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="#e54d5e" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="bgGlowC" cx="42%" cy="35%" r="60%">
|
||||
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.12"/>
|
||||
<stop offset="60%" stop-color="#6366f1" stop-opacity="0.04"/>
|
||||
<stop offset="100%" stop-color="#6366f1" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="gradBrandC" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#e54d5e"/>
|
||||
<stop offset="100%" stop-color="#a855f7"/>
|
||||
</linearGradient>
|
||||
<pattern id="gridPaperC" 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>
|
||||
<!-- request feed into the combo -->
|
||||
<path id="pInC" d="M 26,295 L 86,295"/>
|
||||
<!-- combo → each priority card -->
|
||||
<path id="pF1" d="M 420,270 C 472,270 468,128 520,128"/>
|
||||
<path id="pF2" d="M 420,290 C 472,290 468,246 520,246"/>
|
||||
<path id="pF3" d="M 420,310 C 472,310 468,364 520,364"/>
|
||||
<path id="pF4" d="M 420,330 C 472,330 468,482 520,482"/>
|
||||
<!-- fall-through drops between cards -->
|
||||
<path id="pD1" d="M 534,150 L 534,246"/>
|
||||
<path id="pD2" d="M 534,268 L 534,364"/>
|
||||
<path id="pD3" d="M 534,386 L 534,482"/>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="650" fill="#0b0e14"/>
|
||||
<rect width="1200" height="650" fill="url(#gridPaperC)"/>
|
||||
<rect width="1200" height="650" fill="url(#bgGlowC)"/>
|
||||
|
||||
<!-- ═══ Combo card ═══ -->
|
||||
<rect x="90" y="210" width="330" height="170" rx="14" fill="#161b22" stroke="#8b5cf6" stroke-width="1.2" stroke-opacity="0.45"/>
|
||||
<g transform="translate(112, 254)">
|
||||
<circle cx="42" cy="42" r="34" fill="none" stroke="#e54d5e" stroke-width="1" opacity="0.35" stroke-dasharray="3 7">
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 42 42" to="360 42 42" dur="30s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle cx="42" cy="42" r="24" fill="url(#hubGlowC)">
|
||||
<animate attributeName="r" values="22;30;22" dur="3.2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<g stroke="#e54d5e" stroke-width="5" stroke-linecap="round" fill="none">
|
||||
<line x1="22" y1="42" x2="38" y2="42"/>
|
||||
<path d="M 39 42 C 52 42, 54 26, 66 24"/>
|
||||
<path d="M 39 42 C 54 42, 56 42, 68 42"/>
|
||||
<path d="M 39 42 C 52 42, 54 58, 66 60"/>
|
||||
</g>
|
||||
<circle cx="39" cy="42" r="5" fill="#f7f6fc">
|
||||
<animate attributeName="opacity" values="1;0.7;1" dur="1.8s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<text x="212" y="252" font-family="Consolas, 'Courier New', monospace" font-size="10" letter-spacing="2.5" font-weight="700" fill="#a78bfa">COMBO</text>
|
||||
<text x="212" y="278" font-family="Consolas, 'Courier New', monospace" font-size="17" font-weight="700" fill="url(#gradBrandC)">"always-on"</text>
|
||||
<text x="212" y="304" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#a1a1aa">Strategy: <tspan fill="#8b5cf6" font-weight="700">priority</tspan></text>
|
||||
<text x="212" y="326" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#71717a">try 1 first · fall through</text>
|
||||
<text x="212" y="344" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#71717a">on failure</text>
|
||||
|
||||
<!-- incoming requests -->
|
||||
<text x="26" y="280" font-family="Consolas, 'Courier New', monospace" font-size="9.5" letter-spacing="1.5" fill="#71717a">REQUESTS</text>
|
||||
<use href="#pInC" stroke="#6366f1" stroke-width="1.2" stroke-dasharray="3 5" opacity="0.4"/>
|
||||
|
||||
<!-- fan-out topology (always faintly visible) -->
|
||||
<g stroke="#6366f1" stroke-width="1.2" fill="none" stroke-dasharray="3 5" opacity="0.35">
|
||||
<use href="#pF1"/><use href="#pF2"/><use href="#pF3"/><use href="#pF4"/>
|
||||
</g>
|
||||
<!-- fall-through rails -->
|
||||
<g stroke="#6366f1" stroke-width="1" fill="none" stroke-dasharray="2 4" opacity="0.25">
|
||||
<use href="#pD1"/><use href="#pD2"/><use href="#pD3"/>
|
||||
</g>
|
||||
|
||||
<!-- ═══ Priority cards ═══ -->
|
||||
<!-- 1 · cc/claude-opus-4-7 -->
|
||||
<g transform="translate(520, 80)">
|
||||
<rect width="560" height="96" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect width="560" height="96" rx="14" fill="none" stroke="#22c55e" stroke-width="1.4" stroke-opacity="0.9">
|
||||
<animate attributeName="stroke-opacity" values="0.9;0.9;0;0;0.9" keyTimes="0;0.24;0.27;0.985;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="30" y="56" font-family="Consolas, 'Courier New', monospace" font-size="20" font-weight="800" fill="#71717a">1</text>
|
||||
<text x="70" y="44" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC"><tspan fill="#8b5cf6">cc/</tspan>claude-opus-4-7</text>
|
||||
<text x="70" y="68" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#a1a1aa">subscription · use it fully</text>
|
||||
<circle cx="524" cy="48" r="4.5" fill="#22c55e">
|
||||
<animate attributeName="fill" values="#22c55e;#22c55e;#ef4444;#ef4444;#22c55e" keyTimes="0;0.24;0.28;0.985;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="1;0.45;1" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- 2 · cx/gpt-5.5 -->
|
||||
<g transform="translate(520, 198)">
|
||||
<rect width="560" height="96" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect width="560" height="96" rx="14" fill="none" stroke="#22c55e" stroke-width="1.4" stroke-opacity="0">
|
||||
<animate attributeName="stroke-opacity" values="0;0;0.9;0.9;0;0" keyTimes="0;0.28;0.31;0.48;0.51;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="30" y="56" font-family="Consolas, 'Courier New', monospace" font-size="20" font-weight="800" fill="#71717a">2</text>
|
||||
<text x="70" y="44" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC"><tspan fill="#8b5cf6">cx/</tspan>gpt-5.5</text>
|
||||
<text x="70" y="68" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#a1a1aa">second subscription</text>
|
||||
<circle cx="524" cy="48" r="4.5" fill="#71717a">
|
||||
<animate attributeName="fill" values="#71717a;#71717a;#22c55e;#22c55e;#ef4444;#ef4444;#71717a" keyTimes="0;0.28;0.31;0.48;0.52;0.985;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="1;0.45;1" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- 3 · glm/glm-5.1 -->
|
||||
<g transform="translate(520, 316)">
|
||||
<rect width="560" height="96" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect width="560" height="96" rx="14" fill="none" stroke="#22c55e" stroke-width="1.4" stroke-opacity="0">
|
||||
<animate attributeName="stroke-opacity" values="0;0;0.9;0.9;0;0" keyTimes="0;0.53;0.56;0.73;0.76;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="30" y="56" font-family="Consolas, 'Courier New', monospace" font-size="20" font-weight="800" fill="#71717a">3</text>
|
||||
<text x="70" y="44" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC"><tspan fill="#8b5cf6">glm/</tspan>glm-5.1</text>
|
||||
<text x="70" y="68" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#a1a1aa">cheap backup · <tspan fill="#f59e0b">$0.5/1M</tspan></text>
|
||||
<circle cx="524" cy="48" r="4.5" fill="#71717a">
|
||||
<animate attributeName="fill" values="#71717a;#71717a;#22c55e;#22c55e;#ef4444;#ef4444;#71717a" keyTimes="0;0.53;0.56;0.73;0.77;0.985;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="1;0.45;1" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- 4 · kr/claude-sonnet-4.5 -->
|
||||
<g transform="translate(520, 434)">
|
||||
<rect width="560" height="96" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect width="560" height="96" rx="14" fill="none" stroke="#22c55e" stroke-width="1.4" stroke-opacity="0">
|
||||
<animate attributeName="stroke-opacity" values="0;0;0.9;0.9;0" keyTimes="0;0.78;0.81;0.985;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="30" y="56" font-family="Consolas, 'Courier New', monospace" font-size="20" font-weight="800" fill="#71717a">4</text>
|
||||
<text x="70" y="44" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC"><tspan fill="#8b5cf6">kr/</tspan>claude-sonnet-4.5</text>
|
||||
<text x="70" y="68" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#a1a1aa"><tspan fill="#22c55e" font-weight="700">FREE</tspan> · unlimited · never fails</text>
|
||||
<circle cx="524" cy="48" r="4.5" fill="#71717a">
|
||||
<animate attributeName="fill" values="#71717a;#71717a;#22c55e;#22c55e;#71717a" keyTimes="0;0.78;0.81;0.985;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="1;0.45;1" dur="2s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- ═══ Traffic dots (act-windowed) ═══ -->
|
||||
<!-- incoming feed, steady except at the loop seam -->
|
||||
<g fill="#a78bfa">
|
||||
<animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.945;0.965;0.99;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="1.2s" repeatCount="indefinite" begin="0s"><mpath href="#pInC"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.2s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="1.2s" repeatCount="indefinite" begin="0.6s"><mpath href="#pInC"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.2s" begin="0.6s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- act 1: combo → priority 1 -->
|
||||
<g fill="#8b5cf6">
|
||||
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.235;0.245;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0s"><mpath href="#pF1"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0.7s"><mpath href="#pF1"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0.7s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- act 2: combo → priority 2 -->
|
||||
<g fill="#8b5cf6">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.28;0.30;0.475;0.485;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0s"><mpath href="#pF2"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0.7s"><mpath href="#pF2"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0.7s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- act 3: combo → priority 3 -->
|
||||
<g fill="#8b5cf6">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.53;0.55;0.725;0.735;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0s"><mpath href="#pF3"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0.7s"><mpath href="#pF3"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0.7s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- act 4: combo → priority 4 -->
|
||||
<g fill="#8b5cf6">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.78;0.80;0.945;0.955;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0s"><mpath href="#pF4"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.4">
|
||||
<animateMotion dur="1.4s" repeatCount="indefinite" begin="0.7s"><mpath href="#pF4"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.4s" begin="0.7s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- ═══ Fall-through hand-offs (brand coral) ═══ -->
|
||||
<g fill="#e54d5e">
|
||||
<circle r="4">
|
||||
<animateMotion dur="16s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.24;0.275;1" calcMode="linear"><mpath href="#pD1"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.24;0.245;0.27;0.28;1" dur="16s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="4">
|
||||
<animateMotion dur="16s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.485;0.52;1" calcMode="linear"><mpath href="#pD2"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.485;0.49;0.515;0.525;1" dur="16s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="4">
|
||||
<animateMotion dur="16s" repeatCount="indefinite" keyPoints="0;0;1;1" keyTimes="0;0.735;0.77;1" calcMode="linear"><mpath href="#pD3"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.735;0.74;0.765;0.775;1" dur="16s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<!-- transition labels -->
|
||||
<text x="552" y="196" font-family="Consolas, 'Courier New', monospace" font-size="9.5" fill="#ef4444" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.235;0.25;0.30;0.32;1" dur="16s" repeatCount="indefinite"/>fails → next</text>
|
||||
<text x="552" y="314" font-family="Consolas, 'Courier New', monospace" font-size="9.5" fill="#ef4444" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.48;0.495;0.545;0.565;1" dur="16s" repeatCount="indefinite"/>fails → next</text>
|
||||
<text x="552" y="432" font-family="Consolas, 'Courier New', monospace" font-size="9.5" fill="#ef4444" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.73;0.745;0.795;0.815;1" dur="16s" repeatCount="indefinite"/>fails → next</text>
|
||||
|
||||
<!-- ═══ Footer: the uptime bar that never drops ═══ -->
|
||||
<text x="90" y="588" font-family="Consolas, 'Courier New', monospace" font-size="9.5" letter-spacing="2" font-weight="700" fill="#71717a">UPTIME</text>
|
||||
<rect x="90" y="596" width="1020" height="6" rx="3" fill="#22c55e" opacity="0.8">
|
||||
<animate attributeName="opacity" values="0.8;1;0.8" dur="3s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<g transform="translate(986, 566)">
|
||||
<rect width="124" height="24" rx="12" fill="#161b22" stroke="#22c55e" stroke-width="1" stroke-opacity="0.6"/>
|
||||
<circle cx="16" cy="12" r="3" fill="#22c55e">
|
||||
<animate attributeName="opacity" values="1;0.35;1" dur="1.6s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text x="70" y="16" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="9.5" letter-spacing="2" font-weight="700" fill="#22c55e">ALWAYS ON</text>
|
||||
</g>
|
||||
<text x="600" y="634" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="12.5" fill="#a1a1aa">Result: 4 layers of fallback = <tspan fill="#22c55e" font-weight="700">zero downtime</tspan></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
204
docs/diagrams/pool-fair-share.svg
Normal file
204
docs/diagrams/pool-fair-share.svg
Normal file
@@ -0,0 +1,204 @@
|
||||
<svg viewBox="0 0 1200 650" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="OmniRoute key pool 'team-codex': one Codex Pro account shared by 3 keys over a 5-hour window. alice weight 50, bob weight 30, ci-bot weight 20. In generous mode (under 50% pool used) idle shares are lent out; once the pool crosses 50% strict mode holds each key to its fair share.">
|
||||
<defs>
|
||||
<radialGradient id="bgGlowP" cx="50%" cy="30%" r="60%">
|
||||
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.12"/>
|
||||
<stop offset="60%" stop-color="#6366f1" stop-opacity="0.04"/>
|
||||
<stop offset="100%" stop-color="#6366f1" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="gradBrandP" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#e54d5e"/>
|
||||
<stop offset="100%" stop-color="#a855f7"/>
|
||||
</linearGradient>
|
||||
<pattern id="gridPaperP" 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>
|
||||
<!-- request feed paths, one per lane -->
|
||||
<path id="pReqA" d="M 236,192 L 352,192"/>
|
||||
<path id="pReqB" d="M 236,324 L 352,324"/>
|
||||
<path id="pReqC" d="M 236,456 L 352,456"/>
|
||||
<!-- rejected request: travels to ci-bot's cap line and stops -->
|
||||
<path id="pRejC" d="M 236,456 L 414,456"/>
|
||||
<!-- idle share of bob lent down to ci-bot's borrowed segment -->
|
||||
<path id="pLend" d="M 430,324 C 430,392 446,390 446,440"/>
|
||||
</defs>
|
||||
|
||||
<rect width="1200" height="650" fill="#0b0e14"/>
|
||||
<rect width="1200" height="650" fill="url(#gridPaperP)"/>
|
||||
<rect width="1200" height="650" fill="url(#bgGlowP)"/>
|
||||
|
||||
<!-- ═══ Pool header card ═══ -->
|
||||
<rect x="90" y="28" width="1020" height="84" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<text x="120" y="60" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="19" font-weight="800" fill="#F7F6FC">Pool <tspan font-family="Consolas, 'Courier New', monospace" fill="url(#gradBrandP)">"team-codex"</tspan></text>
|
||||
<text x="120" y="84" font-family="Consolas, 'Courier New', monospace" font-size="11.5" fill="#a1a1aa">1 Codex Pro account · <tspan fill="#8b5cf6">3 keys</tspan> · 5-hour window</text>
|
||||
|
||||
<!-- pool usage meter -->
|
||||
<text x="520" y="50" font-family="Consolas, 'Courier New', monospace" font-size="9.5" letter-spacing="2" font-weight="700" fill="#71717a">POOL USED</text>
|
||||
<rect x="520" y="58" width="280" height="14" rx="7" fill="#1c2230" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="521" y="59" height="12" rx="6" width="0" fill="#22c55e">
|
||||
<animate attributeName="width" values="0;92;129;176;190;190;0" keyTimes="0;0.25;0.50;0.59;0.80;0.965;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="fill" values="#22c55e;#22c55e;#f59e0b;#f59e0b" keyTimes="0;0.52;0.55;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<!-- 50% threshold mark -->
|
||||
<line x1="660" y1="52" x2="660" y2="78" stroke="#a1a1aa" stroke-width="1.2" stroke-dasharray="2 2" stroke-opacity="0.6">
|
||||
<animate attributeName="stroke" values="#a1a1aa;#a1a1aa;#f59e0b;#f59e0b;#a1a1aa;#a1a1aa" keyTimes="0;0.52;0.53;0.60;0.63;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="stroke-opacity" values="0.6;0.6;1;1;0.6;0.6" keyTimes="0;0.52;0.53;0.60;0.63;1" dur="16s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<text x="660" y="90" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="9" fill="#71717a">50%</text>
|
||||
<text x="520" y="90" font-family="Consolas, 'Courier New', monospace" font-size="9" fill="#71717a">0</text>
|
||||
<text x="800" y="90" text-anchor="end" font-family="Consolas, 'Courier New', monospace" font-size="9" fill="#71717a">100%</text>
|
||||
|
||||
<!-- mode pills (swap at the 50% crossing) -->
|
||||
<g>
|
||||
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.52;0.545;1" dur="16s" repeatCount="indefinite"/>
|
||||
<rect x="852" y="52" width="228" height="30" rx="15" fill="#161b22" stroke="#22c55e" stroke-width="1" stroke-opacity="0.6"/>
|
||||
<circle cx="872" cy="67" r="3.5" fill="#22c55e">
|
||||
<animate attributeName="opacity" values="1;0.35;1" dur="1.6s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text x="886" y="71" font-family="Consolas, 'Courier New', monospace" font-size="10" letter-spacing="1.5" font-weight="700" fill="#22c55e">GENEROUS · <50% USED</text>
|
||||
</g>
|
||||
<g opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;1" keyTimes="0;0.53;0.555;1" dur="16s" repeatCount="indefinite"/>
|
||||
<rect x="852" y="52" width="228" height="30" rx="15" fill="#161b22" stroke="#f59e0b" stroke-width="1" stroke-opacity="0.7"/>
|
||||
<circle cx="872" cy="67" r="3.5" fill="#f59e0b">
|
||||
<animate attributeName="opacity" values="1;0.35;1" dur="1s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text x="886" y="71" font-family="Consolas, 'Courier New', monospace" font-size="10" letter-spacing="1.5" font-weight="700" fill="#f59e0b">STRICT · ≥50% USED</text>
|
||||
</g>
|
||||
|
||||
<!-- ═══ Key lanes ═══ -->
|
||||
<!-- lane cards -->
|
||||
<rect x="90" y="140" width="1020" height="104" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="90" y="272" width="1020" height="104" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="90" y="404" width="1020" height="104" rx="14" fill="#161b22" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
|
||||
<!-- key names + weights -->
|
||||
<text x="124" y="188" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC">alice</text>
|
||||
<text x="124" y="208" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#71717a">weight 50</text>
|
||||
<text x="124" y="320" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC">bob</text>
|
||||
<text x="124" y="340" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#71717a">weight 30</text>
|
||||
<text x="124" y="452" font-family="Consolas, 'Courier New', monospace" font-size="15" font-weight="700" fill="#F7F6FC">ci-bot</text>
|
||||
<text x="124" y="472" font-family="Consolas, 'Courier New', monospace" font-size="10.5" fill="#71717a">weight 20</text>
|
||||
|
||||
<!-- usage tracks (full track = 100% of the shared quota) -->
|
||||
<rect x="310" y="183" width="560" height="18" rx="9" fill="#1c2230" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="310" y="315" width="560" height="18" rx="9" fill="#1c2230" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
<rect x="310" y="447" width="560" height="18" rx="9" fill="#1c2230" stroke="#ffffff" stroke-opacity="0.08" stroke-width="1"/>
|
||||
|
||||
<!-- fair-share regions (tinted) -->
|
||||
<rect x="310" y="183" width="280" height="18" rx="9" fill="#6366f1" opacity="0.10"/>
|
||||
<rect x="310" y="315" width="168" height="18" rx="9" fill="#6366f1" opacity="0.10"/>
|
||||
<rect x="310" y="447" width="112" height="18" rx="9" fill="#6366f1" opacity="0.10"/>
|
||||
|
||||
<!-- cap lines (turn amber when strict mode enforces) -->
|
||||
<g stroke-width="1.6">
|
||||
<line x1="590" y1="176" x2="590" y2="208" stroke="#6366f1" stroke-opacity="0.65">
|
||||
<animate attributeName="stroke" values="#6366f1;#6366f1;#f59e0b;#f59e0b" keyTimes="0;0.53;0.56;1" dur="16s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<line x1="478" y1="308" x2="478" y2="340" stroke="#6366f1" stroke-opacity="0.65">
|
||||
<animate attributeName="stroke" values="#6366f1;#6366f1;#f59e0b;#f59e0b" keyTimes="0;0.53;0.56;1" dur="16s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
<line x1="422" y1="440" x2="422" y2="472" stroke="#6366f1" stroke-opacity="0.65">
|
||||
<animate attributeName="stroke" values="#6366f1;#6366f1;#f59e0b;#f59e0b" keyTimes="0;0.53;0.56;1" dur="16s" repeatCount="indefinite"/>
|
||||
</line>
|
||||
</g>
|
||||
|
||||
<!-- right-hand share labels (verbatim from the README block) -->
|
||||
<text x="890" y="196" font-family="Consolas, 'Courier New', monospace" font-size="11" fill="#a1a1aa">≤ 50% of the shared 5h quota</text>
|
||||
<text x="890" y="328" font-family="Consolas, 'Courier New', monospace" font-size="11" fill="#a1a1aa">≤ 30%</text>
|
||||
<text x="890" y="460" font-family="Consolas, 'Courier New', monospace" font-size="11" fill="#a1a1aa">≤ 20%</text>
|
||||
|
||||
<!-- ═══ Animated usage (seam-dip group) ═══ -->
|
||||
<g>
|
||||
<animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.94;0.965;0.99;1" dur="16s" repeatCount="indefinite"/>
|
||||
|
||||
<!-- usage bars -->
|
||||
<rect x="311" y="184" height="16" rx="8" width="0" fill="#8b5cf6">
|
||||
<animate attributeName="width" values="0;38;67;67;140;168;168;0" keyTimes="0;0.12;0.25;0.50;0.59;0.80;0.965;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="311" y="316" height="16" rx="8" width="0" fill="#8b5cf6">
|
||||
<animate attributeName="width" values="0;19;34;34;56;56;0" keyTimes="0;0.15;0.25;0.50;0.59;0.965;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<rect x="311" y="448" height="16" rx="8" width="0" fill="#8b5cf6">
|
||||
<animate attributeName="width" values="0;56;84;112;112;112;0" keyTimes="0;0.12;0.25;0.36;0.50;0.965;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<!-- ci-bot borrowed segment: grows while generous, stays (already spent) but dims under strict -->
|
||||
<rect x="420" y="448" height="16" rx="8" width="0" fill="#22c55e">
|
||||
<animate attributeName="width" values="0;0;47;47;47;0" keyTimes="0;0.36;0.47;0.50;0.965;1" dur="16s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="1;1;0.45;0.45" keyTimes="0;0.53;0.56;1" dur="16s" repeatCount="indefinite"/>
|
||||
</rect>
|
||||
<text x="444" y="440" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="9.5" fill="#22c55e" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0.35;0.35;0" keyTimes="0;0.37;0.42;0.52;0.56;0.94;1" dur="16s" repeatCount="indefinite"/>lent</text>
|
||||
|
||||
<!-- pool meter lives with the bars so the seam resets together -->
|
||||
|
||||
<!-- ── request dots · alice (steady all along) ── -->
|
||||
<g fill="#a78bfa">
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="1.5s" repeatCount="indefinite" begin="0s"><mpath href="#pReqA"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.5s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="1.5s" repeatCount="indefinite" begin="0.75s"><mpath href="#pReqA"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.5s" begin="0.75s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- ── request dots · bob (goes idle during the burst window) ── -->
|
||||
<g fill="#a78bfa">
|
||||
<animate attributeName="opacity" values="1;1;0;0;1;1" keyTimes="0;0.25;0.28;0.50;0.53;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="2.6s" repeatCount="indefinite" begin="0.4s"><mpath href="#pReqB"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="2.6s" begin="0.4s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
<text x="240" y="310" font-family="Consolas, 'Courier New', monospace" font-size="9.5" fill="#71717a" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.28;0.32;0.48;0.52;1" dur="16s" repeatCount="indefinite"/>idle</text>
|
||||
|
||||
<!-- ── request dots · ci-bot: normal → burst (generous) ── -->
|
||||
<g fill="#a78bfa">
|
||||
<animate attributeName="opacity" values="1;1;0;0" keyTimes="0;0.50;0.53;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="0.8s" repeatCount="indefinite" begin="0s"><mpath href="#pReqC"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="0.8s" begin="0s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<circle r="3.2">
|
||||
<animateMotion dur="0.8s" repeatCount="indefinite" begin="0.4s"><mpath href="#pReqC"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="0.8s" begin="0.4s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- ── idle share lent: green dot dropping from bob's spare share into ci-bot's borrowed segment ── -->
|
||||
<g fill="#22c55e">
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.36;0.38;0.48;0.50;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3">
|
||||
<animateMotion dur="1.3s" repeatCount="indefinite"><mpath href="#pLend"/></animateMotion>
|
||||
<animate attributeName="opacity" values="0;1;1;0" dur="1.3s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
</g>
|
||||
|
||||
<!-- ── strict mode: ci-bot requests held at the cap line ── -->
|
||||
<g>
|
||||
<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.59;0.61;0.92;0.94;1" dur="16s" repeatCount="indefinite"/>
|
||||
<circle r="3.2" fill="#a78bfa">
|
||||
<animateMotion dur="2.4s" repeatCount="indefinite" keyPoints="0;0.9;1;1" keyTimes="0;0.55;0.7;1" calcMode="linear"><mpath href="#pRejC"/></animateMotion>
|
||||
<animate attributeName="fill" values="#a78bfa;#a78bfa;#ef4444;#ef4444" keyTimes="0;0.6;0.7;1" dur="2.4s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0;1;1;1;0" keyTimes="0;0.1;0.6;0.82;1" dur="2.4s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text x="422" y="434" text-anchor="middle" font-family="Consolas, 'Courier New', monospace" font-size="12" font-weight="700" fill="#ef4444" opacity="0">
|
||||
<animate attributeName="opacity" values="0;0;1;0;0" keyTimes="0;0.68;0.78;0.95;1" dur="2.4s" repeatCount="indefinite"/>✕</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ═══ Footer: the two modes, verbatim, active one highlighted ═══ -->
|
||||
<circle cx="130" cy="556" r="3.5" fill="#22c55e">
|
||||
<animate attributeName="opacity" values="1;1;0.25;0.25" keyTimes="0;0.52;0.56;1" dur="16s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text x="146" y="561" font-family="Consolas, 'Courier New', monospace" font-size="12.5" fill="#F7F6FC">
|
||||
<animate attributeName="fill" values="#F7F6FC;#F7F6FC;#71717a;#71717a" keyTimes="0;0.52;0.56;1" dur="16s" repeatCount="indefinite"/>Generous mode (<50% pool used) → idle shares are lent out</text>
|
||||
<circle cx="130" cy="586" r="3.5" fill="#f59e0b" opacity="0.25">
|
||||
<animate attributeName="opacity" values="0.25;0.25;1;1" keyTimes="0;0.52;0.56;1" dur="16s" repeatCount="indefinite"/>
|
||||
</circle>
|
||||
<text x="146" y="591" font-family="Consolas, 'Courier New', monospace" font-size="12.5" fill="#71717a">
|
||||
<animate attributeName="fill" values="#71717a;#71717a;#F7F6FC;#F7F6FC" keyTimes="0;0.52;0.56;1" dur="16s" repeatCount="indefinite"/>Strict mode (≥50% pool used) → each key held to its fair share</text>
|
||||
<text x="600" y="622" text-anchor="middle" font-family="Inter, 'Segoe UI', Arial, Helvetica, system-ui, sans-serif" font-size="11" fill="#71717a">Enforced in the hot path — before the request leaves OmniRoute · per-(key, model) caps · session stickiness</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
@@ -1600,6 +1600,29 @@
|
||||
"stream": "https://api.freemodel.dev/v1/chat/completions"
|
||||
}
|
||||
},
|
||||
"freetheai": {
|
||||
"format": "openai",
|
||||
"headers": {
|
||||
"apiKey": {
|
||||
"Accept": "text/event-stream",
|
||||
"Authorization": "Bearer <TOK>",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"nonStream": {
|
||||
"Authorization": "Bearer <TOK>",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"oauth": {
|
||||
"Accept": "text/event-stream",
|
||||
"Authorization": "Bearer <TOK>",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"nonStream": "https://api.freetheai.xyz/v1/chat/completions",
|
||||
"stream": "https://api.freetheai.xyz/v1/chat/completions"
|
||||
}
|
||||
},
|
||||
"friendliai": {
|
||||
"format": "openai",
|
||||
"headers": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* PROBE for issue #6772 — custom OpenAI-compat <connId>/<listedModelId> 400s when the
|
||||
* connection has a user-defined `prefix` and the listed model id (from /api/models)
|
||||
* already carries that prefix baked in ("fta/vova/gpt-5.5").
|
||||
* already carries that prefix baked in ("custpfx6772/vova/gpt-5.5").
|
||||
*
|
||||
* Root cause hypothesis: in src/sse/services/model.ts getModelInfo(), when a client
|
||||
* addresses the connection by its raw internal node id (`<connId>/...`), the matching
|
||||
@@ -25,7 +25,7 @@ const modelsDb = await import("../../src/lib/db/models.ts");
|
||||
const { getModelInfo } = await import("../../src/sse/services/model.ts");
|
||||
|
||||
const CONN_ID = "openai-compatible-chat-97b0e595-probe6772";
|
||||
const PREFIX = "fta";
|
||||
const PREFIX = "custpfx6772"; // was "fta"; freetheai (#7602) claimed the "fta" built-in alias, which by design shadows custom-node prefixes
|
||||
const RAW_MODEL_ID = "vova/gpt-5.5"; // upstream's own model id already has a slash
|
||||
|
||||
test.before(async () => {
|
||||
@@ -53,7 +53,7 @@ test.after(() => {
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("#6772 baseline: bare alias form `fta/vova/gpt-5.5` resolves to the raw model id", async () => {
|
||||
test("#6772 baseline: bare alias form `custpfx6772/vova/gpt-5.5` resolves to the raw model id", async () => {
|
||||
const info = (await getModelInfo(`${PREFIX}/${RAW_MODEL_ID}`)) as {
|
||||
provider?: string;
|
||||
model?: string;
|
||||
|
||||
Reference in New Issue
Block a user