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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,7 @@ CURSOR_USER_AGENT="Cursor/3.4"
# OPENCODE_PARK_AND_RESUME=false # #13924 feature flag (Settings → Feature Flags wins): park the request with a heartbeat after repeated transient 429s, then replay one capped leg of up to 3 accounts
#OPENCODE_POOL_STRAIN_MARKER_PATH=/tmp/opencode-pool-strain.json # #13924: pool-strain marker path (JSON {since, reason, ttl_s}); fresh marker parks without recounting
# RESPONSES_FIRST_BYTE_TIMEOUT_MS=15000 # #13484: OpenCode Responses first-byte window, only used when the OPENCODE_RESPONSES_STALL_ROTATION flag is on (0 disables)
# FLUSH_EMPTY_RETRY_ENABLED=false # #14213 feature flag (Settings → Feature Flags wins): retry empty translated streaming turns through the normal credential path (up to STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX retries)

# ── API Bridge (/v1 proxy server) ──
# API_BRIDGE_PROXY_TIMEOUT_MS=600000 # Proxy hop timeout (default: 10min)
Expand Down
1 change: 1 addition & 0 deletions changelog.d/fixes/14213-flush-empty-retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **fix(sse):** retry empty translated streaming turns through the normal credential path (up to `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX` retries) instead of exposing an empty 200 or an empty-content 502, and a stream that drops before anything reaches the client takes the same retry path; a stream that answers and then stops producing without closing is replayed the same way once its stall budget runs out, instead of buffering forever; off by default behind `FLUSH_EMPTY_RETRY_ENABLED` ([#14213](https://github.com/diegosouzapw/OmniRoute/pull/14213))
3 changes: 2 additions & 1 deletion config/quality/file-size-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"_rebaseline_2026_09_21_14250_member_egress_lines": "PR #14250 own growth: src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx 1477->1479 (+2 = the PoolMemberEgressLines import and its one-line mount under the pool members label, next to PoolEgressObservation). The member-egress observation itself lives outside the frozen file, all under cap: PoolMemberEgressLines.tsx, the dedicated GET /api/settings/proxies/pool/member-egress route, readPoolMemberEgressObservation in src/lib/proxyPoolEgressObservation.ts and getRecentEgressIpForProxy in src/lib/db/proxyLogs.ts. Only the mount point is irreducible. Covered by tests/unit/proxy-pool-member-egress-route.test.ts and tests/unit/ui/PoolMemberEgressLines.test.tsx.",
"_rebaseline_2026_09_22_opencode_train10c_drift": "Release-tip drift: open-sse/executors/opencode.ts 1247->1251, left by the train-10c merge wave (2026-09-22) — the PR->release fast-gates do not run check:file-size, so the tip went red for every train boarding afterwards. Absorbed once at the tip under the owner-approved train-rebaseline policy (see _rebaseline_2026_09_18_merge_train_8_frozen_growth). Structural shrink tracked in #3501.",
"_rebaseline_2026_09_21_14290_stack_handover_growth": "Stacked on #13924 (rewritten/squash-merged as 893fef9c on release/v3.8.51). PR #14290's own growth on top of that parent: open-sse/executors/opencode.ts 1233->1247 (+14 irreducible seam: settle429Arm park arm forcing burstStreak to threshold + handover comment; park/replay block owned by #13924, throttle leaf opencodeEgressThrottle.ts 543 lines under cap). Covered by tests/unit/opencode-429-park-resume.test.ts handover case (8/8) + tests/unit/opencode-egress-throttle.test.ts (22/22).",
"_rebaseline_2026_09_21_14213_empty_turn_retry_growth": "PR #14213 own growth: empty translated streaming turn retry through the normal credential path (classifier + replay parity + bounded reader in new open-sse/utils/emptyTurnRetry.ts, hook wiring in open-sse/handlers/chatCore.ts, streamEmptyChoices extraction). open-sse/handlers/chatCore.ts 6287->6400 (+113 gate count on the reconciled release tip 30f7088c, whose own chatCore.ts already sits at the frozen 6287; irreducible call-site wiring at the insertion point; logic lives in the new 422-line module under the cap). Irreducible spec wiring, additive and flag-off inert. Covered by flush-empty-retry.test.ts + flush-empty-retry-hook.test.ts (46 tests).",
"_rebaseline_2026_09_20_14226_core_overrides_column": "own growth 1788->1800 (+12) src/lib/db/core.ts: rate_limit_overrides_json column in reimport INSERT + preservation SELECT, irreducible spec growth, covered by rate-limit-overrides-startup/reimport tests",
"_rebaseline_2026_09_18_merge_train_8_frozen_growth": "Owner-approved train rebaseline (2026-09-18, /merge-prs; precedent _rebaseline_2026_07_23_v3849_merge_train_15). Own growth of 32 merge-ready contributor PRs that each add irreducible call-site/plumbing lines to an already-frozen file, measured on the combined merge-train tip 04cf8095 (release tip green before boarding). Per-file (old->new, contributing PRs): src/app/(dashboard)/dashboard/combos/page.tsx 5080->5091 (#13951); src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx 2491->2493 (#13533); src/app/api/v1/models/catalog.ts 2117->2127 (#13994); src/lib/db/apiKeys.ts 1659->1671 (#12952, #13861); src/lib/tokenHealthCheck.ts 1221->1254 (#13444, #13874); src/shared/components/RequestLoggerDetail.tsx 1200->1210 (#13373); src/shared/components/RequestLoggerV2.tsx 1718->1748 (#13373); src/shared/middleware/chatBodyAdmission.ts 1200->1206 (#13823); src/sse/handlers/chat.ts 2541->2547 (combined growth); src/sse/services/auth.ts 3582->3592 (combined growth); open-sse/executors/antigravity.ts 1665->1717 (#13125, #13318, #13659); open-sse/executors/codex.ts 1553->1570 (#13708); open-sse/executors/cursor.ts 1847->1868 (#13125); open-sse/executors/deepseek-web.ts 1200->1224 (#13226); open-sse/executors/default.ts 1200->1205 (#11828); open-sse/handlers/imageGeneration.ts 3304->3334 (#12982); open-sse/services/accountFallback.ts 2507->2515 (#13008); open-sse/services/combo/executeTargetAttempt.ts 1228->1258 (#12235); open-sse/services/combo/roundRobinCombo.ts 1221->1261 (#12235); open-sse/services/rateLimitManager.ts 1200->1329 (#13895); open-sse/translator/response/openai-responses.ts 1466->1518 (#12841, #13956); open-sse/utils/cursorAgentProtobuf.ts 1547->1588 (#13125); open-sse/utils/stream.ts 3140->3239 (#12688, #12855); tests/integration/chat-pipeline.test.ts 1740->1756 (#12966); tests/unit/account-fallback-service.test.ts 2056->2072 (#13040); tests/unit/chatcore-translation-paths.test.ts 3449->3546 (#13856, #13972); tests/unit/token-refresh-service.test.ts 1407->1408 (combined growth); tests/unit/translator-openai-to-gemini.test.ts 1625->1809 (#13318, #13848). Files previously under the 1200 cap that crossed it are frozen at the measured size. Structural shrink of these god-files stays tracked in #3501; the ceilings never move up again outside a documented entry. ADJUST (train 8d re-measure after #13548 ejection and #14101 landing): open-sse/services/accountFallback.ts 2515->2517 (#13008 +22, #13350 +7, #13984 +2, #13040 +2 on a tip at 2499).",
"_rebaseline_2026_09_18_14065_codex_reasoning_whitelist": "Release-tip drift: open-sse/executors/codex.ts 1552->1553 (+1) from #14065 (fix(codex): whitelist reasoning object keys before the wire, #13643), merged 2026-09-18 without its own rebaseline — the PR->release fast-gates do not run check:file-size, so the tip went red for every train boarding afterwards. Absorbed at the release tip by the /merge-prs captain session (owner-approved train-rebaseline policy, 2026-09-18). Structural shrink tracked in #3501.",
Expand Down Expand Up @@ -480,7 +481,7 @@
"open-sse/executors/codex.ts": 1570,
"open-sse/executors/cursor.ts": 1868,
"open-sse/executors/muse-spark-web.ts": 1405,
"open-sse/handlers/chatCore.ts": 6287,
"open-sse/handlers/chatCore.ts": 6400,
"open-sse/handlers/imageGeneration.ts": 3334,
"open-sse/handlers/search.ts": 1789,
"open-sse/mcp-server/schemas/tools.ts": 1621,
Expand Down
1 change: 1 addition & 0 deletions docs/reference/ENVIRONMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,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. |
| `FLUSH_EMPTY_RETRY_ENABLED` | `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`) retries empty translated streaming turns through the normal credential path (up to `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX` retries) instead of exposing an empty 200 or an empty-content 502. |
| `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`). |
Expand Down
5 changes: 3 additions & 2 deletions docs/reference/FEATURE_FLAGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ A boolean flag is considered **enabled** when its effective value is `"true"`,

## Flag Catalog

75 flags across 6 categories. **Default** is the definition default — the value
76 flags across 6 categories. **Default** is the definition default — the value
used when neither a DB override nor an environment variable is present.

### Security (10)
Expand All @@ -64,7 +64,7 @@ used when neither a DB override nor an environment variable is present.
| `AUTH_LOG_INCLUDE_ACCOUNT_ID` | boolean | `false` | Include account prefix in AUTH log lines (e.g. "Using <provider> account: abc12345..."). Disabled by default so account identifiers are redacted from shared/multi-tenant process logs. Independent from Debug Mode; flipping Debug Mode does not reveal this. |
| `OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN` | boolean | `false` | When OIDC is enabled, disable password login so users can only authenticate via OIDC Single Sign-On. When disabled (default), both password login and OIDC are available. |

### Network (17)
### Network (18)

| Key | Type | Default | Restart | Description |
| ----------------------------------------------- | ------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -80,6 +80,7 @@ used when neither a DB override nor an environment variable is present.
| `OPENCODE_USER_BLOCKED_ROTATION` | boolean | `false` | | OpenCode executor: on a 403/451 carrying a `user_blocked` refusal (not geo, not a Cloudflare fingerprint rejection), cool the refused account down and rotate to the next account at most once per request; a second refusal is returned as-is, without a success mark. Off by default: routing around an upstream user block can look like evasion and spread the flag across the fleet. |
| `OPENCODE_TRANSIENT_FAILOVER_BACKOFF` | boolean | `false` | | OpenCode rotation: after two consecutive transient upstream failures (5xx or an empty 400), pause before the next account — 1.5s doubling per further failure, capped at 6s per pause and 10s per request, skipped on client disconnect; the failed body is released before waiting. Off by default: failover stays immediate. |
| `OPENCODE_PARK_AND_RESUME` | boolean | `false` | | OpenCode rotation: park the request after repeated transient 429s (or a fresh pool-strain marker) with a heartbeat, then replay one capped leg of up to 3 sequential accounts instead of fanning out the whole fleet. Off by default: every 429 rotates to the next account exactly as before. |
| `FLUSH_EMPTY_RETRY_ENABLED` | boolean | `false` | | On translated streaming turns, when the upstream turn carries no usable content (reasoning-only completion or zero valuable chunks), issue bounded retries through the normal credential path (up to `STREAM_RECOVERY.EMPTY_TURN_RETRY_MAX`) before anything is exposed to the client. Off by default: empty turns keep the current behavior (empty 200 or empty-content 502). |
| `OPENCODE_RATE_LIMITED_429_EARLY_STOP` | boolean | `false` | | OpenCode rotation: stop the account wave at the first 429 classified as a real rate limit (parseable `Retry-After`, or a body naming a rate/usage limit) and return that upstream 429 unchanged. Unclassified 429s keep rotating. Off by default: the free tier is limited per egress IP (#9611), so every 429 rotates and an exhausted wave returns the last upstream 429. |
| `MITM_DISABLE_TLS_VERIFY` | boolean | `false` | ✓ | Disable TLS certificate verification for the MITM proxy. **Danger.** |
| `OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS` | boolean | `false` | | Allow provider URLs pointing to private/internal networks. |
Expand Down
4 changes: 4 additions & 0 deletions open-sse/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,11 +365,15 @@ export const CREDENTIAL_HEALTH_CACHE_TTL = (() => {
* as soon as this many bytes accumulate, regardless of the timer.
* - EARLY_RETRY_MAX: max transparent re-opens of the upstream stream while the
* holdback is still uncommitted (free-claude-code uses 5 total attempts = 4 retries).
* - EMPTY_TURN_RETRY_MAX: max bounded retries of a translated stream turn that ends
* with no usable content (same family: bounded retries of a failing stream
* before anything is exposed to the client).
*/
export const STREAM_RECOVERY = {
HOLDBACK_MS: 750,
BUFFER_MAX_BYTES: 65536,
EARLY_RETRY_MAX: 4,
EMPTY_TURN_RETRY_MAX: 4,
/**
* Minimum character overlap `trimContinuationOverlap` must find between the
* already-emitted text and a mid-stream continuation for the continuation to be
Expand Down
Loading
Loading