Skip to content

feat(sse): transparent stream recovery (free-claude-code port, Fase 4, opt-in) - #4131

Merged
diegosouzapw merged 5 commits into
release/v3.8.29from
feat/fcc-stream-recovery
Jun 18, 2026
Merged

diegosouzapw merged 5 commits into
release/v3.8.29from
feat/fcc-stream-recovery

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Resumo

Porta o stream recovery transparente do free-claude-code (core/anthropic/stream_recovery.py) para o OmniRoute: quando um stream SSE upstream trunca antes de qualquer byte chegar ao cliente, ele é retentado de forma invisível. É a Fase 4 do plano docs/superpowers/plans/2026-06-17-fcc-portable-features.md — o gap headline (#1 em ROI) do porte FCC→OmniRoute.

Opt-in, default OFF. O holdback segura a janela de abertura por até HOLDBACK_MS (750ms), o que adicionaria latência de time-to-first-token em toda request de streaming. Por isso é desligado por padrão e habilitado pelo operador via ResilienceSettings.streamRecovery.enabled (env STREAM_RECOVERY_ENABLED=true|1|on). Com o setting OFF, o caminho de streaming é byte-idêntico ao atual (wrapReadableStreamWithFinalize).

O que entra

  • open-sse/services/streamRecovery.ts (novo): HoldbackBuffer (segura a janela de abertura, commit por tempo OU bytes), TruncatedStreamError, isRetryableStreamError, hasTerminalMarker, e createRecoverableStream (wrapper de ReadableStream com holdback + early-retry até EARLY_RETRY_MAX=4 re-aberturas).
  • open-sse/config/constants.ts: constantes STREAM_RECOVERY (750ms / 64KB / 4 retries — espelham o FCC).
  • src/lib/resilience/settings.ts: novo streamRecovery.enabled (default lê STREAM_RECOVERY_ENABLED, espelhando o padrão de providerCooldown).
  • open-sse/handlers/chatCore.ts: wiring condicional no ponto de retorno do stream — quando ligado, embrulha o body com createRecoverableStream e re-executa o mesmo upstream (mesma conta/body, sem re-adquirir o semáforo) na truncagem precoce.
  • Docs: .env.example + docs/reference/ENVIRONMENT.md.

Semântica (verificada)

  • Pós-commit nunca re-abre: uma vez que o cliente viu bytes (holdback commitado por 750ms/64KB/terminal-marker), qualquer falha propaga ao cliente — sem replay/duplicação.
  • Truncagem precoce: erro de transporte retryável OU done sem marcador terminal ([DONE]/message_stop) com holdback não-commitado → descarta a janela + re-abre (até 4×).
  • Semáforo liberado exatamente 1× em todo caminho terminal (close/error/cancel/give-up/exhausted), guardado por flag idempotente.
  • Cancel do cliente durante o holdback não gasta uma re-abertura upstream.

Validação (Hard Rule #18)

  • TDD unit (tests/unit/stream-recovery.test.ts, 17 testes): HoldbackBuffer (commit por tempo/bytes/flush/discard), isRetryableStreamError, hasTerminalMarker, e createRecoverableStream (retry transparente, give-up best-effort, commit-then-error, clean-stream, max-retries, cancel-no-reopen).
  • Integração end-to-end (tests/integration/stream-recovery-wiring.test.ts, 2 testes): fetch#1 trunca (sem [DONE]) → fetch#2 completa; ON recupera (2 fetches, cliente recebe só o stream recuperado, PARTIAL descartado não vaza); OFF chama o upstream 1× só (zero mudança de comportamento).
  • Settings (tests/unit/resilience-settings-stream-recovery.test.ts, 4 testes): default OFF, override, normalização, merge.
  • Gates: typecheck:core exit 0, ESLint 0, check:file-size (chatCore rebaselinado 5898→5980 com justificativa), check:env-doc-sync, check:cycles, check:any-budget:t11 — todos verdes.
  • Revisão independente adversarial: 0 blockers; 3 concerns endereçados (reuso do settings já consolidado; discard() agora vivo no tryReopen; guarda de cancelled contra fetch desperdiçado no cancel).

Deferido (follow-up)

  • Task 4.4 — mid-stream continuation (continuar de onde parou após o commit, + tool-JSON repair): é a parte mais arriscada, exige validação de corte mid-stream real (VPS) e será um sub-plano dedicado.

Test Plan

  • node --import tsx/esm --test tests/unit/stream-recovery.test.ts → 17/17
  • node --import tsx/esm --test tests/unit/resilience-settings-stream-recovery.test.ts → 4/4
  • node --import tsx/esm --test tests/integration/stream-recovery-wiring.test.ts → 2/2
  • npm run typecheck:core → exit 0
  • npm run check:file-size && npm run check:env-doc-sync && npm run check:any-budget:t11 → verdes

🤖 Generated with Claude Code

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@diegosouzapw
diegosouzapw merged commit 3919661 into release/v3.8.29 Jun 18, 2026
4 checks passed
diegosouzapw added a commit that referenced this pull request Jun 18, 2026
…ask 4.4)

Extends the opt-in stream recovery (#4131) to the POST-commit case. Until now an
upstream truncation after bytes already reached the client either errored or closed
mid-response (an early-retry would replay text the client already saw). For a
plain-text OpenAI-compatible stream we can instead re-request with the partial answer
as an assistant prefill and stitch only the missing suffix.

streamRecovery.ts — new pure, unit-tested primitives:
- scanOpenAiSseText(): extract assistant text + detect tool_calls + terminal marker;
  reports parsedOpenAi:false for non-OpenAI bodies (Anthropic → safe fallback).
- makeContinuationBody(): append the partial as an assistant turn (OpenAI shape).
- trimContinuationOverlap(): drop a duplicated seam so the join is append-only.
- createRecoverableStream() gains an optional continueStream() path: on a post-commit
  truncation it tracks the text already sent, re-requests, drains the continuation,
  emits the de-duplicated suffix + a clean terminal. Bounded by maxContinuations.

Safety / scope (documented):
- OFF by default behind its own setting (STREAM_RECOVERY_MIDSTREAM_ENABLED /
  ResilienceSettings.streamRecovery.continueMidStream), independent of the early-retry
  flag — different risk profile (recovered tail arrives as one burst).
- NEVER continues once a tool call has started streaming (would corrupt tool JSON).
- Only plain-text OpenAI-compatible upstreams; Anthropic/others fall back to the
  unchanged #4131 error/close behavior.

chatCore wiring: the early-retry reopen thunk and the continuation thunk now share a
runUpstreamStream(body) helper (DRY); continuation re-runs the upstream with
makeContinuationBody(bodyToSend, …). Gated; OFF path byte-identical.

Hard Rule #18: validated by integration tests that simulate a mid-stream cut with
mocked streams (silent post-commit truncation AND a post-commit transport error),
asserting the client sees the full answer exactly once with overlap trimmed — no VPS
needed. 16 new tests (10 primitives + 4 wiring + 2 settings). 37 existing #4131 tests
stay green. chatCore.ts +29 rebaselined (logic lives in streamRecovery.ts, <cap).
diegosouzapw added a commit that referenced this pull request Jun 18, 2026
…ask 4.4) (#4147)

Mid-stream continuation for truncated streams (FCC port, Task 4.4). Opt-in (STREAM_RECOVERY_MIDSTREAM_ENABLED, default OFF); plain-text OpenAI-compat only, nunca com tool-call; OFF byte-idêntico ao #4131. Integrado em release/v3.8.29.
@diegosouzapw diegosouzapw mentioned this pull request Jun 19, 2026
@diegosouzapw
diegosouzapw deleted the feat/fcc-stream-recovery branch June 19, 2026 12:06
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
tkgo11 pushed a commit to tkgo11/OmniRoute that referenced this pull request Sep 23, 2026
…ask 4.4) (cheaper-inference#4147)

Mid-stream continuation for truncated streams (FCC port, Task 4.4). Opt-in (STREAM_RECOVERY_MIDSTREAM_ENABLED, default OFF); plain-text OpenAI-compat only, nunca com tool-call; OFF byte-idêntico ao cheaper-inference#4131. Integrado em release/v3.8.29.
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.

1 participant