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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<project_key>/<real_model>`.

### Sanity check

```powershell
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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. |
Expand Down
18 changes: 18 additions & 0 deletions docs/DIAGNOSTICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
4 changes: 3 additions & 1 deletion docs/INDEX.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Documentation index
# Documentation index

Docs are grouped by purpose.

Expand All @@ -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. |

Expand All @@ -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:

Expand Down
77 changes: 77 additions & 0 deletions docs/STREAMING_TIMEOUTS.md
Original file line number Diff line number Diff line change
@@ -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.