Review fixes for the Task 3 contract layer:
- build_chat_model now accepts http_async_client alongside http_client
(at least one required) and wires it into ChatOpenAI
(http_async_client), ChatAnthropic (seeded _async_client), and
ChatOllama (async_client_kwargs transport), closing the unsafe
default-async-client gap.
- ChatOllama safe transports move from the shared client_kwargs to
sync_client_kwargs/async_client_kwargs; langchain-ollama merges shared
kwargs into both clients, which poisoned the async client with a sync
transport and crashed ainvoke.
- Unsupported parameters now actually execute the contract-declared
normalizer (reject_non_auto) instead of a hardcoded raise, with a
fallback rejection if a normalizer would let a value through.
- build_chat_model rejects overlapping client_options/request_options
keys instead of silently overwriting.
Add the Task 3 parameter contract layer (design doc 6.1-6.4):
- adapters.py: versioned built-in contracts for the five phase-1
adapters plus the openai-compatible/glm-5.2 model-specific contract
(verbatim section 6.2 values); exact > longest glob > generic
matching with spec_revision pinning; resolve_parameters implementing
the section 6.1 inherit/omit semantics, contract validation with
stable error codes, and named normalizers (identity,
clamp_to_model_limit, omit_when_none, omit_when_auto,
reject_non_auto); Adapter.build_request as the single entry point
mapping ResolvedModelConfig to {client_options, request_options};
compute_effective_capabilities (protocol AND declared AND verified).
- factory.py: build_chat_model(resolved_config, http_client, *,
credential=None) with no **kwargs and no setdefault merging; injects
the safe HTTP client into ChatOpenAI/ChatAnthropic/ChatOllama, never
reads provider API-key environment variables, and strips the
OLLAMA_API_KEY authorization header for mode=none adapters.
- tests: per-adapter request-capturing fakes plus an httpx.MockTransport
outbound capture proving registry resolution matches the wire request.