Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2237,6 +2237,11 @@ APP_LOG_TO_FILE=true
# proxy — only the operator sets active/inactive (a flaky probe must not strand an
# assigned proxy; #6246). Set "true" to restore the legacy test-and-set behaviour.
# PROXY_HEALTH_AUTO_DEACTIVATE=false
# Opt-in feature flag (default off; a dashboard DB override wins over this value): show,
# under a proxy pool in the dashboard, how many observed egress IPs served its members over
# the last 24 h and how many connections used them (read-only, computed from the proxy log,
# never used for routing). "true" (or 1, yes) enables it.
# PROXY_POOL_EGRESS_OBSERVATION=false

# Allow OAuth and provider validation flows to bypass a pinned proxy and connect
# directly when proxy reachability pre-checks fail. Default: false.
Expand Down
1 change: 1 addition & 0 deletions changelog.d/features/13581-pool-egress-observation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **feat(proxies):** the proxy pool editor shows, for the last 24 h, how many distinct egress IPs actually served the pool's members, how many connections went through them and the most seen behind one IP, read from the proxy log through a separate route so it can never break the pool screen; opt-in with the `PROXY_POOL_EGRESS_OBSERVATION` feature flag (default off) ([#13581](https://github.com/diegosouzapw/OmniRoute/pull/13581)) — thanks @maxmad64bis
1 change: 1 addition & 0 deletions changelog.d/features/13602-pool-skips-refused-member.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **feat(proxies):** a proxy pool stops re-serving a member the provider just refused through it and tries another member instead, reusing the existing skip cooldown; a later success through the member clears it. Opt-in with the `PROXY_SKIP_RECENTLY_FAILED` feature flag (default off: pool selection unchanged) ([#13602](https://github.com/diegosouzapw/OmniRoute/pull/13602)) — thanks @maxmad64bis
6 changes: 4 additions & 2 deletions config/quality/file-size-baseline.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"_rebaseline_2026_09_15_13602_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1214. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.",
"_rebaseline_2026_09_15_13580_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/sse/handlers/chatHelpers.ts->1202. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.",
"_rebaseline_2026_09_13_13581_pool_egress_observation": "PR #13581 own growth: src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx 1475->1477 (+2 = the PoolEgressObservation import and its one-line mount under the pool members label). The observation itself lives outside the frozen file, all under cap: PoolEgressObservation.tsx, the dedicated GET /api/settings/proxies/pool/egress-observation route, src/lib/proxyPoolEgressObservation.ts and getPoolEgressObservation in src/lib/db/proxyLogs.ts. Only the mount point is irreducible. Covered by tests/unit/proxy-pool-egress-observation.test.ts, tests/unit/proxy-pool-egress-observation-route.test.ts and tests/unit/ui/PoolEgressObservation.test.tsx.",
"_rebaseline_2026_09_15_13439_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/combo/executeTargetAttempt.ts->1228. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.",
"_rebaseline_2026_09_15_13672_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): open-sse/services/combo/executeTargetAttempt.ts->1223; open-sse/services/combo/roundRobinCombo.ts->1213. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.",
"_rebaseline_2026_09_15_13441_combined_growth": "Combined growth of the 2026-09-15 maxmad64bis uplift batch (each PR rebaselined its own growth; the merged sum is larger): src/lib/db/core.ts->1770. Every hunk is flag-gated or a verified fix covered by that PR's tests; see the batch report.",
Expand Down Expand Up @@ -342,7 +344,7 @@
"_rebaseline_2026_07_27_3850_relax_filesize_cap": "OWNER-APPROVED TEMPORARY relax for v3.8.50-3.8.54 PREPARE phase (docs/ROADMAP.md). cap 800->900 (+100), testCap 800->900 (+100). Targets: decompose-existing-frozen unchanged (frozen still only-shrink); this only relaxes the cap for NEW files in the decompose/extract-while-PREPARE phase (.51='executor registry in-place' and .52='combo.ts decomposition' create new leaf modules above 800). RE-TIGHTENING MANDATORY in v3.8.51: cap target 850 = 850 once decomposition wave stabilizes. SUPERSEDED by _rebaseline_2026_07_27_3850_relax_filesize_cap_v2_20pct (v1 +20% buffer) — retained for audit. Tracked via same roadmap issue.",
"_rebaseline_2026_07_27_v3849_train1h": "Merge-train 1H (31 PRs) — owner-approved 2026-07-27. Two distinct causes, kept separate on purpose: (1) GENUINE irreducible growth at existing chokepoints — providerLimits/auth (#8632 Kimi quota-reset recovery), rateLimitManager (#8616 idle wedged limiters), models-catalog-route.test (#8610 OpenCode Go effort aliases); (2) COLLISION with #8585, which banked shrinks measured on the pre-train release tip while 30 sibling PRs in the SAME train grew those files again — chat/accountFallback (#8628), chatCore (#8613), videoGeneration (#8581), imageGeneration. The zero-headroom frozen entries cannot absorb either. Ceilings re-pinned to the post-merge tip; #8612 (also in this train) automates shrink-banking so this self-inflicted drift stops recurring. Detail: src/lib/usage/providerLimits.ts 1006->1013 (#8632); src/sse/services/auth.ts 2492->2508 (#8632); open-sse/services/rateLimitManager.ts 1014->1060 (#8616); src/sse/handlers/chat.ts 1842->1845 (#8628); open-sse/handlers/chatCore.ts 4939->4955 (#8613); open-sse/handlers/imageGeneration.ts 3100->3101 ((sem PR — teto do #8585)); open-sse/handlers/videoGeneration.ts 1038->1063 (#8581); open-sse/services/accountFallback.ts 1965->1966 (#8628); tests/unit/models-catalog-route.test.ts 1608->1636 (#8610)",
"frozen": {
"src/sse/handlers/chatHelpers.ts": 1202,
"src/sse/handlers/chatHelpers.ts": 1214,
"_rebaseline_2026_06_22_4644_deepseek_web_tools": "PR #4644 (BugsBag/robust deepseek-web tool-call parsing): open-sse/executors/deepseek-web.ts 1117->1125 (+8). The new agentic tool-call path emits surrounding text + reasoning before tool_calls and swaps to the dedicated deepseekWebTools.ts parser; the +8 lines are cohesive wiring at the existing transformSSE chokepoint (the parser itself lives in the new deepseekWebTools.ts file, already under cap). The PR's own fast-gate (PR->release) does not run check:file-size, so this surfaced only at release reconcile. Covered by tests/unit/deepseek-web-tools-variants.test.ts + deepseek-web-tools-execute.test.ts.",
"_rebaseline_2026_06_23_4712_deepseek_web_tool_results": "PR for #4712 (deepseek-web drops role:tool): open-sse/executors/deepseek-web.ts 1125->1148 (+23). messagesToPrompt() now folds role:\"tool\" results into the single-prompt transcript (recovering the tool name from the preceding assistant tool_calls by tool_call_id) instead of silently dropping them; the lines are cohesive wiring inside the existing function. Covered by tests/unit/deepseek-web-tool-result-prompt-4712.test.ts.",
"_rebaseline_2026_06_24_headroom_strategy": "Headroom-aware connection selection (dario technique): combo.ts 3168->3180 (+12 = a new `else if (strategy === \"headroom\")` dispatch branch in handleComboChat that delegates to orderTargetsByHeadroom + its log line, plus the import). The actual logic lives OUT of the god-file: the pure ranker rankByHeadroom/computeHeadroom is the new leaf open-sse/services/combo/headroomRanking.ts (91 LOC, <cap) and the async orderer orderTargetsByHeadroom is appended to the existing open-sse/services/combo/quotaStrategies.ts (<cap) next to its sibling reset-aware/reset-window orderers (reuses their connection-expansion machinery). headroom = 1 - max(util_5h, util_7d) from getSaturation (src/lib/quota/saturationSignals.ts), prefers the connection with the most free capacity. Only the dispatch wiring is irreducible at the existing combo strategy chokepoint (mirrors the reset-aware/reset-window/context-optimized branches); not extractable without hiding the call site. fill-first stays default; all existing strategies untouched. Covered by tests/unit/combo-headroom-ranking.test.ts (pure helper) + tests/unit/combo-headroom-strategy.test.ts (orderer, saturation injected). Structural shrink of combo.ts tracked in #3501.",
Expand Down Expand Up @@ -471,7 +473,7 @@
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631,
"src/app/(dashboard)/dashboard/providers/page.tsx": 2025,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1477,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1597,
Expand Down
67 changes: 67 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11543,6 +11543,73 @@ paths:
responses:
"200":
description: OK
/api/settings/proxies/pool/egress-observation:
get:
tags:
- Settings
summary: "GET settings › proxies › pool › egress observation"
description: >-
Read-only observation of how many distinct egress IPs actually served the members of a
proxy pool over the last 24 h, read from the proxy log (numbers only, never used for
routing). Opt-in through the PROXY_POOL_EGRESS_OBSERVATION feature flag: while it is
off, or when the read fails, the body is JSON null. Results are cached for 30 seconds
per normalized scope (key is read as account, global as the stored global pool).
security:
- ManagementSessionAuth: []
parameters:
- name: scope
in: query
required: true
schema:
type: string
enum:
- global
- provider
- account
- combo
- key
- name: scopeId
in: query
required: false
description: Required for every scope except global; ignored for global.
schema:
type: string
maxLength: 256
responses:
"200":
description: The observation, or null when the feature flag is off or the read failed.
content:
application/json:
schema:
type: object
nullable: true
required:
- connections
- distinctExits
- maxConnectionsOnOneExit
- windowHours
properties:
connections:
type: integer
minimum: 0
description: Distinct OmniRoute connections logged through the pool members.
distinctExits:
type: integer
minimum: 0
description: Distinct egress IPs observed for those members.
maxConnectionsOnOneExit:
type: integer
minimum: 0
description: Most connections seen behind one egress IP over the window.
windowHours:
type: integer
example: 24
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/ManagementAuthenticationRequired"
"500":
$ref: "#/components/responses/InternalError"
/api/settings/proxy/cloudflare-deploy:
post:
tags:
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@ Anthropic-compatible provider instead.
| `PROXY_HEALTH_TEST_STAGGER_MS` | `100` | `src/lib/proxyHealth/probeTarget.ts` | Delay in ms between two probe departures inside a batch. Without it the whole batch leaves at the same moment and a shared egress IP can trip a rate-limited target. Set to `0` to disable the spacing; capped at 5000. |
| `PROXY_HEALTH_USE_PROVIDER_TARGET` | `true` | `src/lib/proxyHealth/providerProbeTarget.ts` | Set "false" to stop probing the real host of a proxy's assigned provider (`GET /models`, no API key) and always use `PROXY_HEALTH_TEST_URL` instead. |
| `PROXY_HEALTH_AUTO_DEACTIVATE` | `false` | `src/lib/proxyHealth/statusPolicy.ts` | When `false` (default), automated reachability probes (the scheduler + the `/api/settings/proxies/auto-test` "Test All" button) are **read-only** and never write a proxy's status — only the operator sets active/inactive, so a flaky probe can't strand an assigned proxy (#6246). Set `true` to restore the legacy test-and-set behaviour. |
| `PROXY_POOL_EGRESS_OBSERVATION` | `false` | `src/shared/utils/featureFlags.ts` | Opt-in feature flag (see [FEATURE_FLAGS.md](./FEATURE_FLAGS.md); a dashboard DB override wins). `true` (or `1`, `yes`) shows the read-only pool egress observation under a proxy pool in the dashboard (distinct egress IPs, connections and the most seen behind one IP over the last 24 h, from the proxy log). Never used for routing. |
| `PROXY_AUTO_REMOVE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler auto-remove proxies after repeated consecutive failures. |
| `PROXY_AUTO_REMOVE_AFTER` | `3` | `src/lib/proxyHealth/scheduler.ts` | Consecutive failures before the scheduler auto-removes a proxy (when `PROXY_AUTO_REMOVE=true`). |
| `PROXY_AUTO_DISABLE` | `false` | `src/lib/proxyHealth/scheduler.ts` | Set `true` to let the scheduler soft-disable (status `dead`, never deleted) a proxy after repeated consecutive failures, instead of removing it. Non-destructive alternative to `PROXY_AUTO_REMOVE`: the proxy drops out of pool/rotation resolution immediately (the alive-status filter used by scope-pool resolution already excludes it) and is automatically re-activated once it starts passing probes again. Shares the `PROXY_AUTO_REMOVE_AFTER` threshold. If both flags are `true`, `PROXY_AUTO_REMOVE` wins. |
Expand Down
Loading
Loading