From 8ad73ab811716539287584cb39c2512da3254812 Mon Sep 17 00:00:00 2001 From: hyson <499525549@qq.com> Date: Mon, 20 Jul 2026 11:58:06 +0800 Subject: [PATCH 1/4] docs: explain streaming timeout semantics --- docs/STREAMING_TIMEOUTS.md | 77 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/STREAMING_TIMEOUTS.md diff --git a/docs/STREAMING_TIMEOUTS.md b/docs/STREAMING_TIMEOUTS.md new file mode 100644 index 0000000..596a596 --- /dev/null +++ b/docs/STREAMING_TIMEOUTS.md @@ -0,0 +1,77 @@ +# Streaming timeouts and keep-alives + +This document describes the timeout layers used by the adapter after the streaming reliability fix in PR #11. + +## Timeout layers + +| Layer | Default | Behavior | +|---|---:|---| +| Codex provider `stream_idle_timeout_ms` | `360000` ms | Maximum time Codex waits without receiving activity from the adapter's Responses SSE stream. `init` writes this value into `~/.codex/config.toml`. | +| Adapter downstream SSE keep-alive | `15` seconds | Sends an SSE comment frame (`: keep-alive`) while no Responses event is available, so the Codex-facing connection does not remain silent. | +| Non-stream upstream request timeout | `300` seconds | `CODEX_OPENCODE_TIMEOUT_SECONDS` is a total deadline for a normal non-stream OpenCode Go request. | +| Stream upstream connect timeout | `300` seconds | `CODEX_OPENCODE_TIMEOUT_SECONDS` limits establishment of the upstream streaming connection. | +| Stream upstream read timeout | `300` seconds | The same variable limits the gap between successful upstream body reads. It is not a total stream-duration limit. | + +## Effective behavior + +A healthy streaming response may run longer than `CODEX_OPENCODE_TIMEOUT_SECONDS` as long as the upstream continues to deliver body data before each read timeout expires. + +The downstream 15-second keep-alive protects only the adapter-to-Codex connection. It does not create upstream model output and does not prevent the upstream read timeout from firing when OpenCode Go sends no bytes for the configured read-timeout period. + +The default relationship is intentional: + +```text +adapter SSE keep-alive: 15 seconds +upstream per-read timeout: 300 seconds +Codex stream idle timeout: 360 seconds +``` + +This allows the adapter to detect a truly stalled upstream before Codex treats the downstream stream as lost, while keep-alive comments prevent ordinary model thinking gaps from looking like downstream inactivity. + +## Generated Codex provider configuration + +`codex-opencode-adapter init` writes: + +```toml +[model_providers.opencode_go_adapter] +request_max_retries = 0 +stream_max_retries = 0 +stream_idle_timeout_ms = 360000 +``` + +Existing installations are not changed merely by updating the binary. If `~/.codex/config.toml` still contains `stream_idle_timeout_ms = 120000`, rerun `init` for the project or update that provider value manually. + +## `CODEX_OPENCODE_TIMEOUT_SECONDS` semantics + +The variable has different semantics by request type: + +- non-stream: total upstream request deadline; +- stream: upstream connection timeout plus per-read idle timeout; +- stream: no total response-duration deadline. + +Increasing it allows a longer gap between upstream stream reads, but also makes a genuinely stalled upstream take longer to fail. It does not control the 15-second downstream SSE keep-alive or Codex's `stream_idle_timeout_ms`. + +## Troubleshooting + +When a long stream disconnects, record: + +1. The current `stream_idle_timeout_ms` in `~/.codex/config.toml`. +2. The current `CODEX_OPENCODE_TIMEOUT_SECONDS` value. +3. Whether `: keep-alive` comment frames continued to arrive downstream. +4. The time since the last upstream body chunk. +5. The last Responses event emitted before termination. + +Interpretation: + +- no downstream keep-alive within roughly 15 seconds: inspect the adapter process or downstream response path; +- downstream keep-alives continue, but no upstream body bytes arrive for the read-timeout period: upstream stream stall or network issue; +- stream ends at a fixed total duration despite regular upstream chunks: regression in the dedicated streaming client behavior; +- Codex disconnects while keep-alives are arriving: inspect the effective Codex provider configuration and client behavior. + +## Regression coverage + +The repository includes tests that verify: + +- downstream SSE emits keep-alive comments while the Responses stream is idle; +- generated provider configuration uses the 360-second Codex idle timeout; +- a streaming upstream request can outlive the normal non-stream total timeout when chunks continue arriving. From f07d7e36e5cd8a58402689fff79e6e6ff681a72a Mon Sep 17 00:00:00 2001 From: hyson <499525549@qq.com> Date: Mon, 20 Jul 2026 11:58:21 +0800 Subject: [PATCH 2/4] docs: index streaming timeout guide --- docs/INDEX.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/INDEX.md b/docs/INDEX.md index cb577b8..a471ca0 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -1,4 +1,4 @@ -# Documentation index +# Documentation index Docs are grouped by purpose. @@ -7,6 +7,7 @@ Docs are grouped by purpose. | [USAGE.zh-CN.md](USAGE.zh-CN.md) | Daily Chinese setup, usage, and troubleshooting guide. | | [VALIDATION.zh-CN.md](VALIDATION.zh-CN.md) | Real OpenCode Go and Codex validation checklist. | | [DIAGNOSTICS.md](DIAGNOSTICS.md) | Runtime diagnostics and log interpretation. | +| [STREAMING_TIMEOUTS.md](STREAMING_TIMEOUTS.md) | Streaming timeout layers, SSE keep-alives, configuration defaults, and troubleshooting. | | [COMPATIBILITY.md](COMPATIBILITY.md) | Compatibility scope, references, implementation mapping, and non-goals. | | [ROADMAP.md](ROADMAP.md) | Current status, known gaps, and future validation plan. | @@ -20,6 +21,7 @@ Normal use: 1. Read `USAGE.zh-CN.md`. 2. Use `DIAGNOSTICS.md` when something fails. +3. Use `STREAMING_TIMEOUTS.md` for long-running stream or idle-disconnect questions. Real validation: From 0228674825a242b330f128a781982cc31fd3e72b Mon Sep 17 00:00:00 2001 From: hyson <499525549@qq.com> Date: Mon, 20 Jul 2026 11:58:50 +0800 Subject: [PATCH 3/4] docs: add streaming timeout diagnostics --- docs/DIAGNOSTICS.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/DIAGNOSTICS.md b/docs/DIAGNOSTICS.md index d01ec3e..1b1173b 100644 --- a/docs/DIAGNOSTICS.md +++ b/docs/DIAGNOSTICS.md @@ -43,6 +43,21 @@ error.code = upstream_error The adapter currently preserves the upstream HTTP status. Real Codex validation must confirm whether non-2xx HTTP status breaks subagent control flow. If it does, only `/v1/responses` upstream failures should be changed to HTTP 200 with `response.status = failed`. +## Streaming timeout layers + +The adapter has separate downstream and upstream timeout behavior: + +- downstream Responses SSE emits `: keep-alive` comments every 15 seconds while idle; +- generated Codex provider configuration uses `stream_idle_timeout_ms = 360000`; +- non-stream upstream calls use `CODEX_OPENCODE_TIMEOUT_SECONDS` as a total request deadline; +- stream upstream calls use the same value as connect and per-read timeouts, without a total stream-duration deadline. + +A healthy stream may therefore run longer than 300 seconds when upstream chunks continue arriving. The stream should fail when no upstream body bytes arrive for the configured per-read timeout, even though downstream keep-alive comments may still have kept the Codex-facing connection active before that failure. + +Existing `~/.codex/config.toml` files may still contain the old `stream_idle_timeout_ms = 120000`. Rerun `init` or update the provider value manually before treating a 120-second Codex disconnect as an adapter regression. + +See [STREAMING_TIMEOUTS.md](STREAMING_TIMEOUTS.md) for the complete timeout model and troubleshooting checklist. + ## Multimodal unsupported input Known text-only models are guarded before upstream dispatch. When media input is present, the expected Responses failure is: @@ -79,5 +94,8 @@ If the upstream ends before a normal finish reason, record: - whether `response.incomplete` or `response.failed` was emitted - whether tool-call adoption had already started - whether text was buffered because tools were enabled +- effective `stream_idle_timeout_ms` +- effective `CODEX_OPENCODE_TIMEOUT_SECONDS` +- whether downstream `: keep-alive` comments continued to arrive Add a regression test before patching any newly observed stream terminal shape. From 1b96ca49e03f41c41bc3edc7d4399493ac010328 Mon Sep 17 00:00:00 2001 From: hyson <499525549@qq.com> Date: Mon, 20 Jul 2026 11:59:36 +0800 Subject: [PATCH 4/4] docs: clarify streaming timeout defaults --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97ae0c9..af122f8 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ For available variables see the [Environment variables](#environment-variables) `run`/`start` loads all registered projects from the global registry. `check` reads config from the closest project env file. `auth print-local-token` finds a local token from the closest project env or any registered project, then signs an adapter-level token. Project routing is handled entirely by the adapter server via `model = opencode_adapter//`. + ### Sanity check ```powershell @@ -140,11 +141,13 @@ If you only need one doc, start with [docs/USAGE.zh-CN.md](docs/USAGE.zh-CN.md). | `OPENCODE_GO_BASE_URL` | `https://opencode.ai/zen/go/v1` | Upstream base URL. | | `CODEX_OPENCODE_STATE_DB` | `.codex-opencode/state.sqlite` | SQLite state database path (relative to project root). | | `CODEX_OPENCODE_STATE_TTL_SECONDS` | `21600` | State TTL, 6 hours. | -| `CODEX_OPENCODE_TIMEOUT_SECONDS` | `300` | Upstream request timeout. | +| `CODEX_OPENCODE_TIMEOUT_SECONDS` | `300` | Non-stream total upstream timeout; stream connect and per-read timeout. It does not limit total stream duration. | | `CODEX_OPENCODE_MAX_REQUEST_BYTES` | `8388608` | Max request body size, 8 MB. | | `CODEX_OPENCODE_MAX_CONCURRENCY` | `8` | Maximum concurrent upstream requests, read at startup. | | `RUST_LOG` | `codex_opencode_adapter=info` | Tracing filter. Use `codex_opencode_adapter=debug` for detailed diagnostics. | +Streaming defaults are layered: the adapter sends downstream SSE keep-alive comments every 15 seconds, while `init` writes Codex provider `stream_idle_timeout_ms = 360000`. Existing `~/.codex/config.toml` files that still contain `120000` must be regenerated with `init` or updated manually. See [docs/STREAMING_TIMEOUTS.md](docs/STREAMING_TIMEOUTS.md) for the complete timeout model. + The upstream API key and local client token must be different. The adapter never logs either token. If you see `adapter concurrency limit reached`, check the current project's `.codex-opencode-adapter.env` first. @@ -185,6 +188,7 @@ See [docs/ROADMAP.md](docs/ROADMAP.md) for status and next milestones. | [docs/REAL_VALIDATION_2026-06-25.zh-CN.md](docs/REAL_VALIDATION_2026-06-25.zh-CN.md) | Latest real upstream smoke and partial Codex validation record. | | [docs/VALIDATION.zh-CN.md](docs/VALIDATION.zh-CN.md) | Full manual validation checklist. | | [docs/DIAGNOSTICS.md](docs/DIAGNOSTICS.md) | Runtime diagnostics and log interpretation. | +| [docs/STREAMING_TIMEOUTS.md](docs/STREAMING_TIMEOUTS.md) | Streaming timeout layers, SSE keep-alives, and long-stream troubleshooting. | | [scripts/install-user-provider.ps1](scripts/install-user-provider.ps1) | Legacy wrapper that now points to `codex-opencode-adapter init`. | | [scripts/check-local-adapter.ps1](scripts/check-local-adapter.ps1) | Legacy PowerShell helper for `/health` and `/v1/models`. | | [docs/COMPATIBILITY.md](docs/COMPATIBILITY.md) | Compatibility scope and non-goals. |