`agent/anthropic_adapter.py` was 3,423 lines and this PR adds another auth
boundary to it. Split along the seams that were already there, so the
credential surface this PR changes has a single owner instead of being
interleaved with request building:
- `agent/anthropic_endpoints.py` (258) — base-URL/endpoint-family predicates.
Pure functions over a URL string, which is what lets both of the modules
below depend on it without a cycle.
- `agent/anthropic_message_convert.py` (1,225) — OpenAI-style to Anthropic
Messages payload conversion: model ids, tool schemas, content/thinking
blocks, tool_use pairing, cache_control, screenshot eviction, blank-block
scrubbing.
- `agent/anthropic_credentials.py` (910) — credential sources, the OAuth
flows, and the refresh commit (`CredentialPersistError` and both singleton
writers).
- `agent/anthropic_adapter.py` (1,215) — client construction and the Messages
API call, re-exporting every name from the three modules above so existing
`from agent.anthropic_adapter import ...` imports keep resolving. The
re-export surface was diffed against the pre-split module: nothing dropped.
Call sites that read a moved name through the adapter's namespace at runtime
(`credential_pool._refresh_entry_impl`, `auxiliary_client`) now import it from
the defining module, so there is one patchable seam rather than two bindings
that can disagree. The tests that monkeypatched those seams were retargeted to
match; no assertion was changed.
No behavior change.
Dashboard PKCE login reused the code_verifier as the OAuth state (leaking
it and disabling CSRF validation) and never checked state on callback --
the same class of bug already fixed for the CLI flow. Credential-pool
refresh excluded "anthropic" from the cross-process lock Codex/xAI already
get, so concurrent Hermes processes racing a single-use refresh token could
leave the loser stuck exhausted with no recovery for hermes_pkce/dashboard
sources. The dashboard OAuth save also never cleared a stale
ANTHROPIC_API_KEY, which resolve_anthropic_token() prioritizes over the
OAuth pool entry by design -- so a leftover key silently kept billing
pay-per-token after a Claude Pro/Max login.
A concurrency stress test written to validate the refresh-race fix under
load surfaced a fifth, unrelated bug: _auth_store_lock()'s Windows
lock-file "ensure content" write was unguarded and could raise an uncaught
PermissionError under real contention -- affecting every single-use-token
provider sharing that lock, not just Anthropic.
Fixes#87887, #87888, #87889.