mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-11-12 04:12:45 +00:00
## Summary `session/new` and switching models via `configOptions` regressed to ~15s each in v0.59.0. This removes the blocking call responsible and seeds the context window synchronously instead, restoring sub-second behavior on both paths. ## Root cause v0.59.0 added an inline `await query.getContextUsage()` on the session-creation and model-switch paths. That SDK control request is not serviced by the CLI until the session's first prompt turn has run — before then it stalls ~15s. And because SDK control requests are serialized over a single stdin/stdout channel, an awaited `setModel` queued behind the wedged `getContextUsage` inherited the full stall. So both paths blocked ~15s before returning. Verified with the real CLI (setModel alone pre-prompt: ~1ms; getContextUsage alone pre-prompt: ~16s; getContextUsage-then-setModel: both resolve together at ~16s, confirming the serialization). ## Fix Drop `getContextUsage` from both hot paths. The context window is seeded synchronously by `immediateContextWindow(...)`: 1. an authoritative value from a per-(provider, model) cache, if learned; else 2. a synchronous text heuristic (`\b1m\b` → 1,000,000); else 3. `DEFAULT_CONTEXT_WINDOW` (200,000). The authoritative window is learned the moment it's actually available — from each turn's `result.modelUsage` — and written into the cache, so later sessions/switches to the same model seed the correct window immediately with no IPC. A model that has never run a turn shows the heuristic/default window until its first response, matching pre-0.59.0 behavior. ### Cache design - **Keyed on `(provider, resolved model id)`.** The resolved id (`ModelInfo.resolvedModel`, e.g. `claude-sonnet-5[1m]`) matches the `result.modelUsage` keys, so the write key and the read keys line up. Keying on the resolved id (not the picker value) dedups aliases that resolve to the same model; keying on the provider keeps two providers that reuse an id string for different windows from crossing wires (per-session provider config, #853). The provider is captured at session creation, since the effective provider can change process-wide over the adapter's lifetime. - **Non-positive/NaN windows are ignored** — they neither overwrite the session's learned window nor enter the cache. ## Impact (measured, real CLI) | Path | Before | After | | --- | --- | --- | | `session/new` | ~17.4s | ~0.9s | | model switch (before first prompt) | ~15s | ~3ms | | model switch (after a turn) | ~14.7s | <1ms | ## Tests - New end-to-end cache test: a turn's authoritative window (learned under the resolved id even when the assistant message reports the bare id) is served on a later switch with no `getContextUsage`. - New provider-isolation test: the same resolved id on a different provider does not read another provider's cached window; the same provider does. - New non-positive-window test: a nonsensical `contextWindow` is ignored and never poisons the cache. - Rewrote the existing context-window tests to the new contract (assert no `getContextUsage` on session/new and switch; window from cache/inference/default). Closes #886 Closes #880 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>