Skip to content

fix: strict proxy propagation, client error fallback prevention, and stream gating - #3403

Open
v01dgrace wants to merge 7 commits into
decolua:masterfrom
v01dgrace:fix/strict-proxy-propagation
Open

fix: strict proxy propagation, client error fallback prevention, and stream gating#3403
v01dgrace wants to merge 7 commits into
decolua:masterfrom
v01dgrace:fix/strict-proxy-propagation

Conversation

@v01dgrace

@v01dgrace v01dgrace commented Aug 18, 2026

Copy link
Copy Markdown

📝 Description

Fixes #2951

This PR addresses the root causes behind multi-key pool exhaustion (observed in multi-key NVIDIA NIM and generic provider setups) caused by three interacting reliability issues identified in #2951:

  1. strictProxy Flag Dropping: Proxy pools configured with strictProxy: true lost this flag during credential assembly, causing proxyAwareFetch() to silently fall back to direct unproxied connections when the proxy failed.
  2. Deterministic Client Errors Poisoning Multi-Key Pools: HTTP 400 errors (e.g., malformed payloads, prompt context-length exceeded, invalid parameter names) were treated as credential-level errors, repeatedly rotating through all available API keys and placing persistent modelLock on each valid key until 0 accounts remained available.
  3. Premature Streaming Success & Empty Stream Masking: Streaming requests invoked onRequestSuccess() and cleared account error states before reading the first upstream byte. Empty streams (0 bytes) or JSON error payloads disguised within HTTP 200 responses were falsely treated as completed requests without giving outer router layers the opportunity to fall over.

🔍 Key Changes

1. End-to-End strictProxy Propagation (Finding 2)

  • src/sse/services/auth.js: Explicitly propagate strictProxy: resolvedProxy.strictProxy === true into providerSpecificData for both standard credential resolution and the virtual no-auth connection path.
  • open-sse/handlers/chatCore.js: Forward strictProxy into proxyOptions consumed by proxyAwareFetch().

2. Client-Side Request Error Classification & Fallback Protection (Finding 1)

  • open-sse/config/errorConfig.js: Introduced { pass: true } rule mappings for status 400 as well as deterministic error signatures (invalid_request_error, bad request, improperly formed request, unsupported parameter, maximum context length, context_length_exceeded, prompt is too long, exceeds the limit).
  • open-sse/services/accountFallback.js: When a rule matches { pass: true }, return { shouldFallback: false, cooldownMs: 0 }. This halts key rotation immediately and avoids writing persistent modelLock_* entries for client-induced errors.

3. Streaming First-Valid-Event Gate (Finding 3)

  • open-sse/handlers/chatCore/streamingHandler.js:
    • Implemented an early-chunk reader gate prior to invoking onRequestSuccess().
    • Checks for empty bodies (0 bytes) and structured JSON error envelopes (error, error_code, or unexpected detail).
    • If upstream ends prematurely or returns an error, emits success: false (HTTP 502/4xx), enabling outer retry/fallback loops.
    • Reconstitutes the stream via prefixedBody preserving the initial chunk so zero stream bytes or events are lost when data is valid.
    • Ensures reader.cancel(reason) promise is propagated on client disconnects to prevent socket leakage.

4. NVIDIA Reasoning Timeout & Model Registry

  • open-sse/providers/registry/nvidia.js: Added stallTimeoutMs: 600000 (10 minutes) to accommodate long silent thinking phases on reasoning models, and added stepfun-ai/step-3.7-flash model definition.

🏗️ Architecture & Control Flow

flowchart TD
    A[Incoming Request] --> B{Stream or JSON?}
    
    %% Error classification flow
    B -->|Error Occurs| C{checkFallbackError}
    C -->|Client-side 400 / Context Length| D[pass: true -> Return 400 Immediately]
    D --> D1[No Key Rotation / No modelLock]
    C -->|401/403/429/5xx Provider Error| E[shouldFallback: true]
    E --> E1[Rotate Next Account / Backoff]

    %% Stream gate flow
    B -->|Stream: true| F[Fetch Provider Upstream]
    F --> G{First Chunk Gate}
    G -->|0 bytes / Disguised JSON Error| H[Return 502 / Failover]
    G -->|Valid Event / Content| I[Trigger onRequestSuccess]
    I --> J[Prepend Buffered Chunk -> Stream to Client]
Loading

🧪 Test Coverage & Validation

Added comprehensive unit tests covering all edge cases:

  • tests/unit/strict-proxy-propagation.test.js:
    • Validates strictProxy=true propagation through normal and no-auth credential resolution.
    • Confirms zero fallback calls to direct fetch when strictProxy=true encounters proxy failures.
    • Confirms direct fallback occurs when strictProxy=false.
  • tests/unit/account-fallback-rules.test.js:
    • Verifies HTTP 400, invalid_request_error, unsupported parameter, and context-length errors return { shouldFallback: false, cooldownMs: 0 }.
    • Verifies rate limits and authentication errors preserve exponential backoff and cooldowns.
  • tests/unit/stream-first-valid-event-gate.test.js:
    • Tests empty streams (0 bytes), non-SSE HTML pages, raw string JSON errors, and FastAPI detail errors.
    • Verifies normal streaming containing the word "error" in assistant content is safely delivered without false positives.
    • Validates null body graceful handling.

Test Results

✓ tests/unit/stream-first-valid-event-gate.test.js (8 tests)
✓ tests/unit/account-fallback-rules.test.js (7 tests)
✓ tests/unit/strict-proxy-propagation.test.js (5 tests)
✓ tests/unit/ollama-ndjson-stream.test.js (1 test)
✓ tests/unit/fetch-success-clears-account.test.js (1 test)

Test Files  5 passed (5)
Tests       22 passed (22)

📋 Checklist

  • Tested against latest master (v0.5.55) with zero merge conflicts.
  • Full production build passes cleanly (npm run build).
  • All new unit test suites pass (vitest run).
  • 100% backward compatible: default non-strict proxy and provider failovers remain unchanged.
  • No sensitive logs or credentials leaked in debug output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NVIDIA multi-key pool can be exhausted by request/provider/proxy failures; strictProxy is dropped and empty streams are marked successful

2 participants