User pointed out: the desktop chat panel already has 5 CLI agents
(Claude Code / Codex / OpenCode / Copilot / Gemini) that manage their
own auth — Claude Code is logged in by the user, Copilot rides GitHub
auth, Gemini rides gcloud. The #27 intent-gate landing required a
separate `OPENPENCIL_ANTHROPIC_API_KEY` env var to launch the
orchestrator, which broke UX consistency and was a real design defect:
the existing CLI agents already provide LLM access, the orchestrator
should reuse them instead of requiring users to wire up a parallel
direct-API key.
Root cause was a trait mismatch: `Orchestrator` needs an
`LlmClient` impl, and `DesktopLlmClient` (now deleted) was written to
take `Arc<dyn agent::Provider>` — but `agent::Provider` is only
implemented by `AnthropicProvider` / `OpenAiCompatProvider`, NOT the
CLI-backed `ChatProvider`s (`ClaudeCodeProvider` etc).
Fix is a thin `ChatProvider → LlmClient` adapter:
- New `chat_provider_llm::ChatProviderLlmClient` (~80 lines): owns
an `Arc<dyn ChatProvider>`, each `LlmClient::call` spawns a thread
that drains the provider's blocking iterator into a futures mpsc
channel and returns the receive half as a `BoxStream`. Same
async↔sync bridge `BlockingRecvIter` uses in the opposite
direction.
- `DesignSession::start` now generic over `L: LlmClient + Send +
'static` instead of taking `Arc<dyn Provider>` + default_model;
caller picks the LlmClient impl. Test e2e path
(`from_channels`) unaffected.
- `chat_session::launch_if_pending`: Design branch reads
`chat_selected_agent`, wraps the existing `provider_for_agent`
output in `ChatProviderLlmClient`, hands it to `DesignSession`.
Unwired agents (Codex / OpenCode) fall through to the chat-path
unwired-agent error bubble — same UX as a chat send to an
unwired agent.
Deletions:
- `chat_orchestrator.rs` — `DesktopLlmClient` was its last
surviving content; with the new adapter no caller needs it.
Removed the file + the `mod chat_orchestrator;` line.
- `chat_session::provider_for_design` and the
`OPENPENCIL_ANTHROPIC_API_KEY` / `ANTHROPIC_API_KEY` /
`OPENPENCIL_ORCHESTRATOR_MODEL` env reads.
- `op-host-desktop/Cargo.toml` `agent` crate's `["anthropic"]`
feature — no path inside the host needs an `agent::Provider`
impl anymore (the trait stays imported for the
`BuiltInProvider` shim in `chat_runtime.rs`, future-facing).
`op-smoke` keeps `AnthropicProvider` + `OpenAiCompatProvider`
directly because the smoke deliberately bypasses any CLI auth path
to validate the orchestrator against a raw API endpoint
independently of the host UI.
cargo test -p op-host-desktop 106 passed (unchanged). cargo fmt
--all -- --check + cargo clippy --workspace --all-targets -- -D
warnings clean.