From 720154175219ae6ced7a9e09db2693fa2adce4eb Mon Sep 17 00:00:00 2001 From: Tommaso Bolis Date: Sun, 25 Oct 2026 17:19:33 +0100 Subject: [PATCH] =?UTF-8?q?docs(verified-apis):=20record=20=C2=A78=20re-ch?= =?UTF-8?q?ecks=20for=20the=20#1206=20lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Offline re-checks of the framework releases that #1206 moved into the PR CI lock (BG §1.8, §0.3): langgraph (Refs #1204, #1211), Google ADK and native Gemini (Refs #1215, #1207), MS Agent Framework and its middleware (Refs #1203), OpenAI Agents SDK (Refs #1205), Anthropic SDK (Refs #1209), CrewAI (Refs #1210). Statuses and live dates are unchanged; no --live round-trip was run. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/verified-apis.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/verified-apis.md b/docs/verified-apis.md index 8cb75ddb..3b55065b 100644 --- a/docs/verified-apis.md +++ b/docs/verified-apis.md @@ -709,14 +709,14 @@ regardless of which issue/PR produced the offline pass. | Framework | Symbol / kwarg | Status | Verified value | Date | Source | |---|---|---|---|---|---| -| LangGraph | `langchain_openai.ChatOpenAI(model, base_url, api_key, default_headers, http_async_client, http_client, max_retries=0, callbacks, use_responses_api=False)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `langgraph==1.2.15`, `langchain-openai==1.7.1`, `langchain-core==1.6.10`, `openai==3.29.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: `ChatOpenAI.ainvoke` on the default `use_responses_api=True` (`POST /responses`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`), and, in acceptance group 7, a one-node `StateGraph` turn with `use_responses_api=False` on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `langgraph==1.2.12`, `langchain-openai==1.6.6`, `langchain-core==1.6.5`. Re-checked offline 2026-10-14 (#1128, #1129, #1130) on the PR lock `langgraph==1.2.15`, `langchain-openai==1.7.1`, `langchain-core==1.6.10`, `openai==3.31.0`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok; the `interrupt()` composition tests behind §8.2 pass; `nox -s langgraph-demo` (3.11) passes with conformance 4/4. langchain-openai 1.7.1 only adds `mid_conversation_*` fields to the resolved model profile (`mid_conversation_tools` is forced off unless the Responses API is used); the adapter reads no profile and passes the same constructor kwargs. The deep adapter (#198) defaults to `use_responses_api=False` (`/chat/completions`) since #1043, the one route every upstream behind an OpenAI-format proxy serves (§2, route matrix #894); `chat_model(..., use_responses_api=True)` opts into the live-verified `/responses` data plane. Also tested offline with `openai==2.54.0`. Re-checked offline 2026-10-16 (#1162) on the PR lock `langgraph==1.2.15`, `langchain-openai==1.7.2`, `langchain-core==1.6.11`, `openai==3.31.0`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok. The `interrupt()` composition tests and `nox -s langgraph-demo` were not re-run for this bump. Re-checked offline 2026-10-19 (#1166, #1167) on the PR lock `langgraph==1.2.16`, `langchain-openai==1.7.2`, `langchain-core==1.6.12`, `openai==3.35.1`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok; the whole suite passes (3300 passed, 271 skipped) and `smoke_example.py langgraph` passes the conformance kit; the `interrupt()` composition tests behind §8.2 pass (27 passed, 2 skipped in the langgraph-demo venv on Python 3.11, where the opentelemetry-gated ones run); `nox -s langgraph-demo` (3.11) passes with conformance 4/4. langgraph 1.2.16 leaves `interrupt()`, `Command` and `GraphInterrupt` byte-identical to 1.2.15 (diff of the installed source); its resume fixes (#9322, #9265) touch pending-write bookkeeping and the invalid-resume handling for `response_schema` validation, which `approval_interrupt()` does not pass, so the adapter is unchanged. langchain-core 1.6.12 (content-block-start extras, #41272) is in `language_models/_compat_bridge.py`; the adapter imports only `langchain_core.callbacks.BaseCallbackHandler`. This was not re-checked live; the live round-trip above is unchanged. | 2026-10-13 | #34; `python/scripts/verify_frameworks.py --only langgraph` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only langgraph --live`; acceptance group 7 | -| Google ADK | `google.adk.models.lite_llm.LiteLlm(model="openai/…", api_base, api_key, extra_headers, max_retries=0, client, llm_client)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `google-adk==2.11.0`, `litellm==1.105.0`, `openai==2.54.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's `LiteLlm`, one turn through `InMemoryRunner.run_async` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `google-adk==2.10.0`, `litellm==1.102.1`. Re-checked offline 2026-10-14 with `google-adk==2.12.0`, `litellm==1.105.1`, `openai==2.54.0`, `google-genai==2.30.1`, Python 3.12 (#1139, #1140): `verify_frameworks.py --only adk` gives signature ok and expected-class ok; `LiteLlm` still has exactly `model` and `llm_client`, `LiteLLMClient.acompletion(self, model, messages, tools, **kwargs)` is unchanged and still awaited as `llm_client.acompletion(...)` for streamed and non-streamed requests; the adk/litellm tests pass, including the `max_retries=0` request-count and typed-refusal-bridge tests. The 2.12.0 `LiteLlm` fixes (tool-call argument parsing, streamed tool-call thought signatures, tool-call id handling) and litellm 1.105.x's HTTP-pool refactor (BerriAI/litellm#43245, proxy-server side) were read from the release notes only, not exercised live. The pre-built OpenAI `client` sends through the SDK's shared client (#740), so every call carries the run's correlation id and `donkey.last_call` is `OBSERVED` after a call; the live turn above confirms the consumer-auth headers reach the proxy. A refusal surfaces as a LiteLLM error (`litellm.exceptions.APIError` for a PII 403, `InternalServerError` for a transport-raised `ModelSubstituted` or `GatewayUnavailable`) with no gateway headers and no `__cause__`; confirmed offline with `google-adk==2.11.0`, `litellm==1.104.2`, `openai==2.54.0` (#969). `LiteLlm.llm_client` (a pydantic field typed `LiteLLMClient`, present from `google-adk==2.4.0`) is awaited as `llm_client.acompletion(...)` for every model request, streamed or not, so `model()` passes a `LiteLLMClient` subclass that tags LiteLLM's error with the refusal the transport recorded for that call, and the typed-refusal bridge raises it typed (`tests/conformance/test_typed_refusal_bridge.py`). Also tested offline with `openai==2.54.0`. `max_retries=0` goes to LiteLLM, which sets it on `client` per call (default 2); one send per budget 429 confirmed by request count against a loopback proxy with `litellm==1.103.2` (#734, `tests/unit/test_framework_retries.py`). Re-checked offline 2026-10-19 with `google-adk==2.12.0`, `litellm==1.106.0`, `openai==2.54.0`, `google-genai==2.31.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` (#1170): `verify_frameworks.py --only adk` gives signature ok and expected-class ok for `adk` and `adk.gemini` (live not run); the whole suite passes (3286 passed, 228 skipped for extras not installed on this leg), including the adk/litellm tests, the `max_retries=0` request-count tests and the typed-refusal-bridge tests. litellm 1.106.0's attribution-header change (BerriAI/litellm#44291) was read in the installed source: `BaseConfig.get_attribution_headers()` returns an empty mapping by default and only the Novita and Perplexity configs override it, `with_attribution_headers` lets caller headers win on a name clash and never mutates its input, and the adapter's `extra_headers` goes to the request through `optional_params["extra_headers"]`, outside that merge; no wire capture was taken and no live round-trip was run on 1.106.0. Re-checked offline 2026-10-22 with `google-adk==2.12.0`, `litellm==1.106.2`, `openai==2.54.0`, `google-genai==2.32.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` (#1194): `verify_frameworks.py --only adk` gives signature ok and expected-class ok for `adk` and `adk.gemini` (live not run); the whole suite passes (3411 passed, 228 skipped for extras not installed on this leg), including the adk/litellm tests, the `max_retries=0` request-count tests and the typed-refusal-bridge tests. No live round-trip was run on 1.106.2. Re-checked offline 2026-10-24 with `google-adk==2.13.0`, `litellm==1.106.2`, `openai==2.54.0`, `google-genai==2.32.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` with only `google-adk` moved to 2.13.0 (#1207): 2.13.0 iterates the model's response generator in a copy of the flow's context (`google.adk.utils._runner_utils._stream_in_caller_scope`, read in the installed source), so the record the transport sets stayed in that copy and an `after_model_callback` read `UNOBSERVED` (`test_adk_runner_is_unobserved_but_after_model_callback_sees_it` failed). `model()` and `gemini()` now return a subclass (`GovernedLiteLlm`, `GovernedGemini`, from `_last_call_bridged_class`) whose `generate_content_async`, a plain `def`, opens a `LastCallBridge` in the flow's context before ADK copies it, and closes it before the first complete (non-partial) response, because ADK runs that response's function calls (in tasks of their own on 2.13.0) before it resumes the generator. `verify_frameworks.py --only adk --emit-verified` gives signature ok and expected-class ok for `adk` and `adk.gemini` (the class check is `isinstance`; actual class `donkey_kit.integrations.adk.GovernedLiteLlm` / `GovernedGemini`), live not run; the same at the `google-adk==2.4.0` floor. The whole suite passes on 2.13.0 and on the 2.12.0 lock (3468 passed, 228 skipped each), and `scripts/smoke_example.py adk` passes on 2.13.0. A `LiteLlm` built from `connection_kwargs()` is not bridged, so on 2.13.0 its callback reads `UNOBSERVED`. No live round-trip was run on 2.13.0. | 2026-10-13 | #34; `python/scripts/verify_frameworks.py --only adk` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only adk --live`; acceptance group 7 | -| Google ADK (native Gemini) | `google.adk.models.Gemini(model, base_url, client_kwargs={api_key, http_options={base_url, api_version="", headers, timeout, httpx_async_client}})` | VERIFIED (LIVE) | Round-trip through a `Format=Gemini` proxy with `google-adk==2.10.0`, `google-genai==2.25.0`: `generate_content_async` → 200, SSE streaming, and a tool-calling agent run. ADK builds its genai `Client` from `client_kwargs`, and through google-adk 2.12.0 `client_kwargs['http_options']` **replaces** ADK's default `http_options` wholesale, so the adapter passes the full set (from 2.13.0 ADK merges into it instead; see the 2.13.0 re-check below); an injected `httpx_async_client` disables genai's aiohttp path, so buffered and streamed calls both go through the shared `DonkeyAsyncClient`. `api_key` is required by genai and sent as `x-goog-api-key` (gateway-ignored placeholder). `timeout` is milliseconds (`None` would disable it). genai raises its own `google.genai.errors.APIError` whose `.response` is the httpx response, so `classify(exc.response)` works. `Gemini.client_kwargs` first appears in google-adk **2.4.0** (absent in 2.0.0 and 2.3.0, checked 2026-10-01); ADK's pydantic config ignores unknown fields, so on older versions the governed client is dropped silently. The `adk` extra floors at `google-adk>=2.4`, `gemini()` refuses a `Gemini` lacking either field with `_verify.blocked(...)`, and the round-trip test runs in CI at 2.4.0 and the newest release (#735). ADK caches one genai `Client` per event loop, and the shared `DonkeyAsyncClient` keeps one connection pool per loop, so one Donkey serves repeated `asyncio.run(...)` calls (#807). Signature re-checked offline 2026-10-14 with `google-adk==2.12.0`, `google-genai==2.30.1` (#1139): `Gemini` still has `base_url` and `client_kwargs`, `verify_frameworks.py --only adk` reports `adk.gemini` signature ok, and `test_adapter_contract.py::test_streamed_call[adk.gemini]` passes; no live Gemini round-trip was run on 2.12.0. Re-checked offline 2026-10-24 with `google-adk==2.13.0`, `google-genai==2.32.0`, Python 3.12 (#1207): `Gemini` still has `base_url` and `client_kwargs`, and `verify_frameworks.py --only adk --emit-verified` reports `adk.gemini` signature ok and expected-class ok (`isinstance`; actual class `donkey_kit.integrations.adk.GovernedGemini`), live not run. Two 2.13.0 changes were read in the installed source. (1) `Gemini._with_adk_http_options` no longer lets `client_kwargs['http_options']` replace ADK's options wholesale: it fills only the `api_version`, `base_url` and `retry_options` the caller left unset, so the caller's fields win, and it merges ADK's tracking values into the caller's `headers` (`merge_tracking_headers`, which keeps every other header). The adapter sets `base_url` and `api_version` itself, so neither is filled. 2.12.0 already merged the tracking values per request in `generate_content_async`, so the wire is unchanged: an offline capture through `MockTransport` on 2.12.0 and 2.13.0 shows the same `POST /models/gemini-2.5-flash:generateContent` with `client_id`, `client_secret`, `x-goog-api-key` and the `x-goog-api-client`/`user-agent` tracking values, differing only in the `google-adk/` token. (2) `_stream_in_caller_scope` iterates the response generator in a copy of the flow's context, so `gemini()` returns the bridged subclass described in the Google ADK row; `test_adk_runner_is_unobserved_but_after_model_callback_sees_it` and `test_adk_streamed_run_after_model_callback_sees_every_response` fail on 2.13.0 without it and pass with it, as they do on 2.12.0 and 2.4.0. A `Gemini` built from `gemini_connection_kwargs()` is not bridged, so on 2.13.0 its callback reads `UNOBSERVED`. Offline, with the recorded `responses.stream.*` fixture through `MockTransport`, `donkey.last_call.input_tokens` stays `None` for a streamed call on 2.12.0 and 2.13.0, with or without the bridge, both in the callbacks and after the generator ends. The bridge does not change it; the cause was not investigated here, and the adk page's "usage lands on the last one" for streaming is not confirmed by this check. No live Gemini round-trip was run on 2.13.0. | 2026-10-01 | #691, #735; `python/scripts/verify_frameworks.py --only adk.gemini --live` against `ddk-gemini-inbound` | -| MS Agent Framework | `agent_framework.openai.OpenAIChatClient(model, base_url, api_key, default_headers)` — the **Responses API** client (`POST /responses`), built by `chat_client(..., api="responses")`; `agent_framework.openai.OpenAIChatCompletionClient(…)` with the same kwargs — the **Chat Completions** client (`POST /chat/completions`), built by `chat_client()` by default (#1043) | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `agent-framework==1.21.0`, `agent-framework-openai==1.16.0`, `openai==3.29.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: one `Agent.run` turn on each client — the default Chat Completions client (`POST /chat/completions`) and the `api="responses"` client (`POST /responses`) — against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route (Chat Completions client). Offline: construction and `isinstance` against both recorded paths confirmed offline with `agent-framework==1.19.0` / `agent-framework-openai==1.14.4`, and re-checked at `agent-framework==1.21.0` / `agent-framework-openai==1.16.0` / `openai==3.31.0` (#1126): `verify_frameworks.py --only agent_framework` still resolves `OpenAIChatCompletionClient` and `OpenAIChatClient` with signature ok, both accept `model`, `base_url`, `api_key`, `default_headers`, `async_client` and neither accepts `model_id`. The constructor kwarg is `model=` — `model_id` is **not** accepted. `base_url`/`api_key`/`default_headers`/`async_client` are accepted by both, so `connection_kwargs()` serves either. Both chain the openai error as `__cause__` of `ChatClientException` (`OpenAIContentFilterException` for a content-filter 400), so `policy_middleware()` covers both. The default is `/chat/completions` (#1043), the only route every upstream serves (§2 per-upstream route matrix, #894); `/responses` is the opt-in for OpenAI-routed proxies. The switch traces back to an Azure OpenAI route that answered `/responses` with **404** `Resource not found` and `/chat/completions` with 200 in one live probe of `ddk-azure-openai-model-routing` (2026-09-30, #826); re-captured 2026-10-07 as the §2 fixture `src/donkey_kit/simulator/_fixtures/anypoint/azure_openai_routing/` (#896, moved in #895); the `/chat/completions` route itself is recorded in §2 (Chat Completions row). Also confirmed offline at the `agent-framework==1.13.0` extras floor, where the call reaches the proxy (`test_framework_redirects.py`). 1.12.0 constructs but sends elsewhere (§8.3, #743). (Reclassified from a bare `VERIFIED` per #681 — same offline-only evidence class as its peer rows; §0.3 reserves `VERIFIED` for a real-sandbox round-trip.) Re-checked offline 2026-10-16 at `agent-framework==1.22.0` / `agent-framework-openai==1.17.0` / `openai==3.31.0` (#1156): `verify_frameworks.py --only agent_framework` resolves `agent_framework_openai._chat_completion_client.OpenAIChatCompletionClient` and `agent_framework_openai._chat_client.OpenAIChatClient` with signature ok and expected-class ok; the constructor kwarg set was not re-listed. The live round-trip was not re-run. | 2026-10-13 | #34, #520, #826 (`verify_frameworks.py --only agent_framework`); `python/scripts/verify_frameworks.py --only agent_framework --live`; acceptance group 7 | -| MS Agent Framework (middleware) | `agent_framework.chat_middleware` decorator; `ChatContext.stream` / `.result`; `ResponseStream.with_pull_context_manager(cm_factory)`; `agent_framework.exceptions.ChatClientException` chaining the openai error as `__cause__` | UNVERIFIED (signature confirmed; pending maintainer `--live` + sign-off) | Confirmed offline with `agent-framework==1.19.0`: `Agent(middleware=[...])` accepts a `@chat_middleware`-marked function (an undecorated `(context, next)` function whose first parameter is not annotated `ChatContext` raises `MiddlewareException: Cannot determine middleware type`). `OpenAIChatClient._handle_request_error` re-raises every openai error as `ChatClientException` from the original, so `policy_middleware()` runs the call inside the typed-refusal bridge (`core/refusals.py`, #724), whose chain walk reaches the `openai.APIStatusError` and classifies its response in core; the adapter's `refusal_translator` sees through `agent_framework.exceptions.AgentFrameworkException` (the base of `ChatClientException`, confirmed offline with `agent-framework-core==1.20.0`), so an unreachable gateway surfaces as `GatewayUnavailable`. A streamed refusal surfaces on the caller's pull, after the middleware has returned; `with_pull_context_manager` wraps each pull, so the same conversion applies. Pinned by `test_agent_framework_policy_middleware.py` on a real `Agent` (one send, typed `PIIDetected` with the run's correlation id, streaming and non-streaming). Re-checked at `agent-framework==1.21.0` / `agent-framework-core==1.21.0` (#1126): the `agent-framework-middleware` session passes, `ResponseStream.with_pull_context_manager(cm_factory)` and `chat_middleware` are unchanged, `ChatClientException` still subclasses `AgentFrameworkException`, and the two streaming fixes in 1.21.0 (delegated provider streams close on release, microsoft/agent-framework#9100; streamed logprobs kept across empty chunks, #8997) change cleanup ordering and one metadata field, not the pull-context wrapping or the exception chain. No live round-trip. Re-checked at `agent-framework==1.22.0` / `agent-framework-core==1.22.0` (#1156): the `agent-framework-middleware` session's test files pass (96 passed, 55 skipped for other frameworks, 0 failed); the 1.22.0 release notes and API surface were not otherwise inspected. | 2026-10-16 | #739, #1126, #1156 | -| OpenAI Agents SDK | `agents.OpenAIChatCompletionsModel(model, openai_client=openai.AsyncOpenAI(base_url, api_key, default_headers, http_client, max_retries=0))` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `openai-agents==0.23.1`, `openai==3.29.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's model, one turn through `Runner.run` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `openai-agents==0.20.0`. Also tested offline with `openai==2.54.0`. Re-checked offline 2026-10-14 (#1142) at `openai==3.31.0` with `openai-agents==0.23.1` (the PR lock): `verify_frameworks.py --only openai_agents` resolves `agents.models.openai_chatcompletions.OpenAIChatCompletionsModel` with the signature ok; `openai.AsyncOpenAI.__init__` still takes `base_url`, `api_key`, `default_headers`, `http_client` (typed `httpx2.AsyncClient | None`) and `max_retries`; the adapter-contract suite and the strict example smoke pass. openai 3.30.0 drains an async HTTP/1.1 stream for at most 0.1 s after `[DONE]` (openai/openai-python#4041); through the bridge the forwarded stream is already consumed, so the drain hits `httpx.StreamConsumed`, which openai catches, and the response is closed rather than pooled. Streams complete with no added latency or hang, as at 3.24.0. The live round-trip was not re-run. Re-checked offline 2026-10-16 (#223, promotion to a deep, conformance-gated adapter) at `openai-agents==0.24.0` with `openai==3.31.0` (the new `openai-agents-demo` lock): `verify_frameworks.py --only openai_agents` resolves the class with the signature ok; the installed constructor is `OpenAIChatCompletionsModel(model, openai_client, should_replay_reasoning_content=None, strict_feature_validation=False, buffer_streamed_tool_calls=False)`, and the adapter passes only `model` and `openai_client`. The demo additionally relies on the framework's own documented `agents.Agent(name, instructions, model, tools)`, `agents.Runner.run(starting_agent, input, *, run_config=…)`, `agents.RunConfig(tracing_disabled=True)` and `agents.RunResult.final_output`, all confirmed present at 0.24.0; `Agent.instructions` accepts a `(RunContextWrapper, Agent) -> str` callable. The four public conformance scenarios and the Runner-level correlation test (`tests/unit/test_openai_agents_correlation.py`) pass against it. The live round-trip was not re-run. Re-checked offline 2026-10-19 (#1171) at `openai==3.35.1` with `openai-agents==0.24.0` (the PR lock, Python 3.12, installed with the adapter-contract recipe): `verify_frameworks.py --only openai_agents` resolves `agents.models.openai_chatcompletions.OpenAIChatCompletionsModel` with the signature ok; `openai.AsyncOpenAI.__init__` still takes `base_url`, `api_key`, `default_headers`, `http_client` (typed `httpx2.AsyncClient | None`) and `max_retries`, and `openai.DefaultAsyncHttpxClient` is still built on `httpx2.AsyncClient`; the full suite (3266 passed, 273 skipped for other frameworks' extras) and the strict example smoke pass. The live round-trip was not re-run. | 2026-10-13 | #34; #223; `python/scripts/verify_frameworks.py --only openai_agents` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only openai_agents --live`; acceptance group 7 | -| Anthropic SDK | `anthropic.AsyncAnthropic(base_url, api_key, default_headers, http_client, max_retries=0)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `anthropic==1.13.0`, `httpx2==2.13.1`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: `messages.create` (`POST /v1/messages`) against the `Format=Anthropic` proxy `ddk-anthropic-inbound` (`claude-haiku-4-5-20251001`), in the harness and in acceptance group 7. Offline: construction and `isinstance` against the recorded path confirmed offline with `anthropic==0.116.0`, and again with `anthropic==0.125.0` and `anthropic==1.9.0` (`httpx2==2.13.1`) after #701. Re-checked offline on 2026-10-14 against the PR lock at `anthropic==1.13.0` (`httpx2==2.13.1`, `openai==3.31.0`, Python 3.12, #1127): `verify_frameworks.py --only anthropic` reports the signature ok; the constructor still takes `base_url`, `api_key`, `default_headers`, `http_client` and `max_retries`; `DefaultAsyncHttpxClient` is still an `httpx2.AsyncClient`; an `httpx` `http_client` is still rejected with `TypeError`; the default `anthropic-version` is still `2023-06-01` (now `ANTHROPIC_API_VERSION` in `_constants.py`, a rename). 1.13.0 also tracks an explicit timeout in a write-only `_timeout_overridden` flag behind a `timeout` property, which changes nothing for this adapter, as it passes no `timeout`. `http_client` is the shared `DonkeyAsyncClient`'s non-owning view on `anthropic<1` (#733) and a bridged `httpx2.AsyncClient` on `anthropic>=1.0` (§8.1). Model id remains per-call; the separately live-verified Anthropic Messages ingress (§2, #304) requires a `Format=Anthropic` proxy. Also tested offline with `openai==2.54.0` (0.116.0) and `openai==3.22.1` (1.9.0). Re-checked offline 2026-10-16 against the PR lock at `anthropic==1.15.0` (`httpx2==2.13.1`, `openai==3.31.0`, Python 3.12, #1161): `verify_frameworks.py --only anthropic` reports the signature ok; the constructor still takes `base_url`, `api_key`, `default_headers`, `http_client` and `max_retries` (it also accepts `auth_token`, `credentials`, `config`, `profile`, `webhook_key`, `open_telemetry` and `middleware`, none of which the adapter passes); `DefaultAsyncHttpxClient` is still an `httpx2.AsyncClient`; an `httpx` `http_client` is still rejected with `TypeError`; the default `anthropic-version` is still `2023-06-01` (`ANTHROPIC_API_VERSION` in `_constants.py`). The live round-trip was not re-run. Re-checked offline 2026-10-22 against the PR lock at `anthropic==2.1.0` (`httpx2==2.16.0`, `openai==3.36.1`, Python 3.12, #1195): `verify_frameworks.py --only anthropic` reports the signature ok and expected-class ok; the constructor still takes `base_url`, `api_key`, `default_headers`, `http_client` and `max_retries`; `DefaultAsyncHttpxClient` is still an `httpx2.AsyncClient`; an `httpx` `http_client` is still rejected with `TypeError`; the whole suite passes (3318 passed, 299 skipped for extras not installed on this leg). **The default `anthropic-version` changed:** 2.0.0 ("add support for new API version 2026-10-21", marked breaking) sets `ANTHROPIC_API_VERSION` to `2026-10-21`, so the client now sends `anthropic-version: 2026-10-21` unless the caller overrides it in `default_headers`. The live round-trip above, and the §2 native-ingress row, were taken with `2023-06-01`. **Live round-trip 2026-10-22** (#1195) with `anthropic==2.1.0`, `httpx2==2.16.0`, Python 3.12, from source on the PR lock: `verify_frameworks.py --only anthropic --live` sends `messages.create` (`POST /v1/messages`) with the new default `anthropic-version: 2026-10-21` through the `Format=Anthropic` proxy `ddk-anthropic-inbound` (`claude-haiku-4-5-20251001`) and gets a completion back, so the gateway passes the new version through. Acceptance group 7 was not re-run. | 2026-10-22 | #34, #701, #1127, #1161, #1195; `python/scripts/verify_frameworks.py --only anthropic` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only anthropic --live`; acceptance group 7 | -| CrewAI | `crewai.LLM(model="openai/…", base_url, api_key, extra_headers, max_retries=0, interceptor)` → returns `crewai.BaseLLM` (concretely `OpenAICompletion`) | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `crewai==1.15.27`, `openai==2.54.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's LLM, one `Task` through `Crew.kickoff` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` confirmed offline with `crewai==1.15.22`, `openai==2.54.0` — against the concrete `OpenAICompletion` class the factory returns, **not** `crewai.LLM`. `crewai.LLM.__new__` is a documented factory: an `openai/`-prefixed model with an explicit `base_url` (which `connection_kwargs()` always supplies) routes to and returns `crewai.llms.providers.openai.completion.OpenAICompletion`, CrewAI's native OpenAI provider and a sibling `crewai.BaseLLM` subclass, not a `crewai.LLM` instance (`isinstance(obj, crewai.LLM)` is false; `isinstance(obj, crewai.BaseLLM)` is true). That provider strips the `openai/` prefix and does not go through LiteLLM, which is only the factory's fallback route. `crewai.BaseLLM` (`crewai.llms.base_llm.BaseLLM`, a public top-level export) is the correct common return type and the adapter's annotation, but every provider is one, so the harness pins the concrete `OpenAICompletion` class: a routing change fails the check instead of passing as another `BaseLLM` (#640). The prior `crewai.LLM` expectation and its `CLASS RENAMED` verdict were an artifact of checking against the wrong class, not a real incompatibility. `extra_headers` is not a named `OpenAICompletion` field: `BaseLLM` collects it into `additional_params`, which `OpenAICompletion` merges into its request parameters, so the proxy auth headers arrive by that passthrough rather than a named parameter — the silently-absorbed-kwarg case above (seen on the wire against a local stub server, sync and streaming; not a sandbox round-trip). `max_retries=0` reaches the provider's OpenAI client (a 503 is sent once); CrewAI's own `BaseLLM` rate-limit retry still sends a budget 429 3 times and has no off switch, an asserted exemption confirmed with `crewai==1.15.23` (#734, `tests/unit/test_framework_retries.py`). Re-confirmed 2026-10-12 with `crewai==1.15.27`, `openai==2.54.0` (#1116): the signature check still resolves to `OpenAICompletion`, and the `adapter-contract(crewai)` suite, retry exemption included, passes. Re-confirmed 2026-10-14 with `crewai==1.15.28`, `openai==2.54.0` (#1141): the signature check still resolves to `OpenAICompletion`, and the crewai conformance, retry-exemption and adapter tests pass. The 1.15.28 change the AI impact review flagged, context-window sizes now read from a bundled LiteLLM catalog (crewAIInc/crewAI#8077), does not touch this row: `crewai/llms/providers/openai/completion.py`, `crewai/llms/base_llm.py`, `crewai/llms/retry.py` and `crewai/llms/hooks/transport.py` are byte-identical to 1.15.27 (diff of the two wheels), the `crewai.LLM.__new__` routing is unchanged (docstring-only edit), and the adapter sets no context-window option. The live round-trip above is still the 1.15.27 one; no `--live` run was made at 1.15.28. | 2026-10-13 | #34, #640, #1116, #1141; `python/scripts/verify_frameworks.py --only crewai` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only crewai --live`; acceptance group 7 | +| LangGraph | `langchain_openai.ChatOpenAI(model, base_url, api_key, default_headers, http_async_client, http_client, max_retries=0, callbacks, use_responses_api=False)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `langgraph==1.2.15`, `langchain-openai==1.7.1`, `langchain-core==1.6.10`, `openai==3.29.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: `ChatOpenAI.ainvoke` on the default `use_responses_api=True` (`POST /responses`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`), and, in acceptance group 7, a one-node `StateGraph` turn with `use_responses_api=False` on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `langgraph==1.2.12`, `langchain-openai==1.6.6`, `langchain-core==1.6.5`. Re-checked offline 2026-10-14 (#1128, #1129, #1130) on the PR lock `langgraph==1.2.15`, `langchain-openai==1.7.1`, `langchain-core==1.6.10`, `openai==3.31.0`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok; the `interrupt()` composition tests behind §8.2 pass; `nox -s langgraph-demo` (3.11) passes with conformance 4/4. langchain-openai 1.7.1 only adds `mid_conversation_*` fields to the resolved model profile (`mid_conversation_tools` is forced off unless the Responses API is used); the adapter reads no profile and passes the same constructor kwargs. The deep adapter (#198) defaults to `use_responses_api=False` (`/chat/completions`) since #1043, the one route every upstream behind an OpenAI-format proxy serves (§2, route matrix #894); `chat_model(..., use_responses_api=True)` opts into the live-verified `/responses` data plane. Also tested offline with `openai==2.54.0`. Re-checked offline 2026-10-16 (#1162) on the PR lock `langgraph==1.2.15`, `langchain-openai==1.7.2`, `langchain-core==1.6.11`, `openai==3.31.0`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok. The `interrupt()` composition tests and `nox -s langgraph-demo` were not re-run for this bump. Re-checked offline 2026-10-19 (#1166, #1167) on the PR lock `langgraph==1.2.16`, `langchain-openai==1.7.2`, `langchain-core==1.6.12`, `openai==3.35.1`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok; the whole suite passes (3300 passed, 271 skipped) and `smoke_example.py langgraph` passes the conformance kit; the `interrupt()` composition tests behind §8.2 pass (27 passed, 2 skipped in the langgraph-demo venv on Python 3.11, where the opentelemetry-gated ones run); `nox -s langgraph-demo` (3.11) passes with conformance 4/4. langgraph 1.2.16 leaves `interrupt()`, `Command` and `GraphInterrupt` byte-identical to 1.2.15 (diff of the installed source); its resume fixes (#9322, #9265) touch pending-write bookkeeping and the invalid-resume handling for `response_schema` validation, which `approval_interrupt()` does not pass, so the adapter is unchanged. langchain-core 1.6.12 (content-block-start extras, #41272) is in `language_models/_compat_bridge.py`; the adapter imports only `langchain_core.callbacks.BaseCallbackHandler`. This was not re-checked live; the live round-trip above is unchanged. Re-checked offline 2026-10-25 (#1204, #1211) on the PR lock `langgraph==1.2.17`, `langchain-openai==1.7.2`, `langchain-core==1.7.0`, `openai==3.37.0`, `pydantic==2.14.1`, Python 3.12: `verify_frameworks.py --only langgraph` reports the signature ok; the whole suite passes (3481 passed, 279 skipped) and `smoke_example.py langgraph` passes the conformance kit; the `interrupt()`/`Command(resume=...)` approval tests pass (19 passed, 10 skipped) and `nox -s langgraph-demo` (3.11) passes with conformance 4/4. langchain-core 1.7.0 changes only `language_models/_compat_bridge.py` (per-slice `tool-call-delta` events replace `block-delta` snapshots; diff of the installed source), which the adapter does not use; langgraph 1.2.17 leaves `interrupt()`, `Command` and `GraphInterrupt` byte-identical to 1.2.16. This was not re-checked live; the live round-trip above is unchanged. | 2026-10-13 | #34; `python/scripts/verify_frameworks.py --only langgraph` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only langgraph --live`; acceptance group 7 | +| Google ADK | `google.adk.models.lite_llm.LiteLlm(model="openai/…", api_base, api_key, extra_headers, max_retries=0, client, llm_client)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `google-adk==2.11.0`, `litellm==1.105.0`, `openai==2.54.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's `LiteLlm`, one turn through `InMemoryRunner.run_async` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `google-adk==2.10.0`, `litellm==1.102.1`. Re-checked offline 2026-10-14 with `google-adk==2.12.0`, `litellm==1.105.1`, `openai==2.54.0`, `google-genai==2.30.1`, Python 3.12 (#1139, #1140): `verify_frameworks.py --only adk` gives signature ok and expected-class ok; `LiteLlm` still has exactly `model` and `llm_client`, `LiteLLMClient.acompletion(self, model, messages, tools, **kwargs)` is unchanged and still awaited as `llm_client.acompletion(...)` for streamed and non-streamed requests; the adk/litellm tests pass, including the `max_retries=0` request-count and typed-refusal-bridge tests. The 2.12.0 `LiteLlm` fixes (tool-call argument parsing, streamed tool-call thought signatures, tool-call id handling) and litellm 1.105.x's HTTP-pool refactor (BerriAI/litellm#43245, proxy-server side) were read from the release notes only, not exercised live. The pre-built OpenAI `client` sends through the SDK's shared client (#740), so every call carries the run's correlation id and `donkey.last_call` is `OBSERVED` after a call; the live turn above confirms the consumer-auth headers reach the proxy. A refusal surfaces as a LiteLLM error (`litellm.exceptions.APIError` for a PII 403, `InternalServerError` for a transport-raised `ModelSubstituted` or `GatewayUnavailable`) with no gateway headers and no `__cause__`; confirmed offline with `google-adk==2.11.0`, `litellm==1.104.2`, `openai==2.54.0` (#969). `LiteLlm.llm_client` (a pydantic field typed `LiteLLMClient`, present from `google-adk==2.4.0`) is awaited as `llm_client.acompletion(...)` for every model request, streamed or not, so `model()` passes a `LiteLLMClient` subclass that tags LiteLLM's error with the refusal the transport recorded for that call, and the typed-refusal bridge raises it typed (`tests/conformance/test_typed_refusal_bridge.py`). Also tested offline with `openai==2.54.0`. `max_retries=0` goes to LiteLLM, which sets it on `client` per call (default 2); one send per budget 429 confirmed by request count against a loopback proxy with `litellm==1.103.2` (#734, `tests/unit/test_framework_retries.py`). Re-checked offline 2026-10-19 with `google-adk==2.12.0`, `litellm==1.106.0`, `openai==2.54.0`, `google-genai==2.31.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` (#1170): `verify_frameworks.py --only adk` gives signature ok and expected-class ok for `adk` and `adk.gemini` (live not run); the whole suite passes (3286 passed, 228 skipped for extras not installed on this leg), including the adk/litellm tests, the `max_retries=0` request-count tests and the typed-refusal-bridge tests. litellm 1.106.0's attribution-header change (BerriAI/litellm#44291) was read in the installed source: `BaseConfig.get_attribution_headers()` returns an empty mapping by default and only the Novita and Perplexity configs override it, `with_attribution_headers` lets caller headers win on a name clash and never mutates its input, and the adapter's `extra_headers` goes to the request through `optional_params["extra_headers"]`, outside that merge; no wire capture was taken and no live round-trip was run on 1.106.0. Re-checked offline 2026-10-22 with `google-adk==2.12.0`, `litellm==1.106.2`, `openai==2.54.0`, `google-genai==2.32.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` (#1194): `verify_frameworks.py --only adk` gives signature ok and expected-class ok for `adk` and `adk.gemini` (live not run); the whole suite passes (3411 passed, 228 skipped for extras not installed on this leg), including the adk/litellm tests, the `max_retries=0` request-count tests and the typed-refusal-bridge tests. No live round-trip was run on 1.106.2. Re-checked offline 2026-10-24 with `google-adk==2.13.0`, `litellm==1.106.2`, `openai==2.54.0`, `google-genai==2.32.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` with only `google-adk` moved to 2.13.0 (#1207): 2.13.0 iterates the model's response generator in a copy of the flow's context (`google.adk.utils._runner_utils._stream_in_caller_scope`, read in the installed source), so the record the transport sets stayed in that copy and an `after_model_callback` read `UNOBSERVED` (`test_adk_runner_is_unobserved_but_after_model_callback_sees_it` failed). `model()` and `gemini()` now return a subclass (`GovernedLiteLlm`, `GovernedGemini`, from `_last_call_bridged_class`) whose `generate_content_async`, a plain `def`, opens a `LastCallBridge` in the flow's context before ADK copies it, and closes it before the first complete (non-partial) response, because ADK runs that response's function calls (in tasks of their own on 2.13.0) before it resumes the generator. `verify_frameworks.py --only adk --emit-verified` gives signature ok and expected-class ok for `adk` and `adk.gemini` (the class check is `isinstance`; actual class `donkey_kit.integrations.adk.GovernedLiteLlm` / `GovernedGemini`), live not run; the same at the `google-adk==2.4.0` floor. The whole suite passes on 2.13.0 and on the 2.12.0 lock (3468 passed, 228 skipped each), and `scripts/smoke_example.py adk` passes on 2.13.0. A `LiteLlm` built from `connection_kwargs()` is not bridged, so on 2.13.0 its callback reads `UNOBSERVED`. No live round-trip was run on 2.13.0. Re-checked offline 2026-10-25 with `google-adk==2.13.0`, `litellm==1.106.4`, `openai==2.54.0`, `google-genai==2.33.0`, Python 3.12, on the PR lock `adapter-contract-adk-py3.12.txt` (#1215, with #1207's 2.13.0 now in the lock): `verify_frameworks.py --only adk` gives signature ok and expected-class ok for `adk` and `adk.gemini` (actual classes `GovernedLiteLlm` / `GovernedGemini`; live not run); the whole suite passes (3475 passed, 228 skipped for extras not installed on this leg), including `test_adapter_async_last_call.py -k adk` (6 passed), and `scripts/smoke_example.py adk` passes. Per the release notes, litellm 1.106.3 changes the image cosign key and drops the eMudhra root CA from its trust bundles, and 1.106.4 fixes a proxy startup `ImportError`; none touches the `LiteLlm` kwargs the adapter sets, and `model()` sends TLS through the pre-built `AsyncOpenAI` client. No live round-trip was run on 1.106.4. | 2026-10-13 | #34; `python/scripts/verify_frameworks.py --only adk` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only adk --live`; acceptance group 7 | +| Google ADK (native Gemini) | `google.adk.models.Gemini(model, base_url, client_kwargs={api_key, http_options={base_url, api_version="", headers, timeout, httpx_async_client}})` | VERIFIED (LIVE) | Round-trip through a `Format=Gemini` proxy with `google-adk==2.10.0`, `google-genai==2.25.0`: `generate_content_async` → 200, SSE streaming, and a tool-calling agent run. ADK builds its genai `Client` from `client_kwargs`, and through google-adk 2.12.0 `client_kwargs['http_options']` **replaces** ADK's default `http_options` wholesale, so the adapter passes the full set (from 2.13.0 ADK merges into it instead; see the 2.13.0 re-check below); an injected `httpx_async_client` disables genai's aiohttp path, so buffered and streamed calls both go through the shared `DonkeyAsyncClient`. `api_key` is required by genai and sent as `x-goog-api-key` (gateway-ignored placeholder). `timeout` is milliseconds (`None` would disable it). genai raises its own `google.genai.errors.APIError` whose `.response` is the httpx response, so `classify(exc.response)` works. `Gemini.client_kwargs` first appears in google-adk **2.4.0** (absent in 2.0.0 and 2.3.0, checked 2026-10-01); ADK's pydantic config ignores unknown fields, so on older versions the governed client is dropped silently. The `adk` extra floors at `google-adk>=2.4`, `gemini()` refuses a `Gemini` lacking either field with `_verify.blocked(...)`, and the round-trip test runs in CI at 2.4.0 and the newest release (#735). ADK caches one genai `Client` per event loop, and the shared `DonkeyAsyncClient` keeps one connection pool per loop, so one Donkey serves repeated `asyncio.run(...)` calls (#807). Signature re-checked offline 2026-10-14 with `google-adk==2.12.0`, `google-genai==2.30.1` (#1139): `Gemini` still has `base_url` and `client_kwargs`, `verify_frameworks.py --only adk` reports `adk.gemini` signature ok, and `test_adapter_contract.py::test_streamed_call[adk.gemini]` passes; no live Gemini round-trip was run on 2.12.0. Re-checked offline 2026-10-24 with `google-adk==2.13.0`, `google-genai==2.32.0`, Python 3.12 (#1207): `Gemini` still has `base_url` and `client_kwargs`, and `verify_frameworks.py --only adk --emit-verified` reports `adk.gemini` signature ok and expected-class ok (`isinstance`; actual class `donkey_kit.integrations.adk.GovernedGemini`), live not run. Two 2.13.0 changes were read in the installed source. (1) `Gemini._with_adk_http_options` no longer lets `client_kwargs['http_options']` replace ADK's options wholesale: it fills only the `api_version`, `base_url` and `retry_options` the caller left unset, so the caller's fields win, and it merges ADK's tracking values into the caller's `headers` (`merge_tracking_headers`, which keeps every other header). The adapter sets `base_url` and `api_version` itself, so neither is filled. 2.12.0 already merged the tracking values per request in `generate_content_async`, so the wire is unchanged: an offline capture through `MockTransport` on 2.12.0 and 2.13.0 shows the same `POST /models/gemini-2.5-flash:generateContent` with `client_id`, `client_secret`, `x-goog-api-key` and the `x-goog-api-client`/`user-agent` tracking values, differing only in the `google-adk/` token. (2) `_stream_in_caller_scope` iterates the response generator in a copy of the flow's context, so `gemini()` returns the bridged subclass described in the Google ADK row; `test_adk_runner_is_unobserved_but_after_model_callback_sees_it` and `test_adk_streamed_run_after_model_callback_sees_every_response` fail on 2.13.0 without it and pass with it, as they do on 2.12.0 and 2.4.0. A `Gemini` built from `gemini_connection_kwargs()` is not bridged, so on 2.13.0 its callback reads `UNOBSERVED`. Offline, with the recorded `responses.stream.*` fixture through `MockTransport`, `donkey.last_call.input_tokens` stays `None` for a streamed call on 2.12.0 and 2.13.0, with or without the bridge, both in the callbacks and after the generator ends. The bridge does not change it; the cause was not investigated here, and the adk page's "usage lands on the last one" for streaming is not confirmed by this check. No live Gemini round-trip was run on 2.13.0. Re-checked offline 2026-10-25 with `google-adk==2.13.0`, `google-genai==2.33.0`, Python 3.12 (#1215 lock): `verify_frameworks.py --only adk` reports `adk.gemini` signature ok and expected-class ok (`GovernedGemini`), live not run. No live Gemini round-trip was run on `google-genai` 2.33.0. | 2026-10-01 | #691, #735; `python/scripts/verify_frameworks.py --only adk.gemini --live` against `ddk-gemini-inbound` | +| MS Agent Framework | `agent_framework.openai.OpenAIChatClient(model, base_url, api_key, default_headers)` — the **Responses API** client (`POST /responses`), built by `chat_client(..., api="responses")`; `agent_framework.openai.OpenAIChatCompletionClient(…)` with the same kwargs — the **Chat Completions** client (`POST /chat/completions`), built by `chat_client()` by default (#1043) | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `agent-framework==1.21.0`, `agent-framework-openai==1.16.0`, `openai==3.29.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: one `Agent.run` turn on each client — the default Chat Completions client (`POST /chat/completions`) and the `api="responses"` client (`POST /responses`) — against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route (Chat Completions client). Offline: construction and `isinstance` against both recorded paths confirmed offline with `agent-framework==1.19.0` / `agent-framework-openai==1.14.4`, and re-checked at `agent-framework==1.21.0` / `agent-framework-openai==1.16.0` / `openai==3.31.0` (#1126): `verify_frameworks.py --only agent_framework` still resolves `OpenAIChatCompletionClient` and `OpenAIChatClient` with signature ok, both accept `model`, `base_url`, `api_key`, `default_headers`, `async_client` and neither accepts `model_id`. The constructor kwarg is `model=` — `model_id` is **not** accepted. `base_url`/`api_key`/`default_headers`/`async_client` are accepted by both, so `connection_kwargs()` serves either. Both chain the openai error as `__cause__` of `ChatClientException` (`OpenAIContentFilterException` for a content-filter 400), so `policy_middleware()` covers both. The default is `/chat/completions` (#1043), the only route every upstream serves (§2 per-upstream route matrix, #894); `/responses` is the opt-in for OpenAI-routed proxies. The switch traces back to an Azure OpenAI route that answered `/responses` with **404** `Resource not found` and `/chat/completions` with 200 in one live probe of `ddk-azure-openai-model-routing` (2026-09-30, #826); re-captured 2026-10-07 as the §2 fixture `src/donkey_kit/simulator/_fixtures/anypoint/azure_openai_routing/` (#896, moved in #895); the `/chat/completions` route itself is recorded in §2 (Chat Completions row). Also confirmed offline at the `agent-framework==1.13.0` extras floor, where the call reaches the proxy (`test_framework_redirects.py`). 1.12.0 constructs but sends elsewhere (§8.3, #743). (Reclassified from a bare `VERIFIED` per #681 — same offline-only evidence class as its peer rows; §0.3 reserves `VERIFIED` for a real-sandbox round-trip.) Re-checked offline 2026-10-16 at `agent-framework==1.22.0` / `agent-framework-openai==1.17.0` / `openai==3.31.0` (#1156): `verify_frameworks.py --only agent_framework` resolves `agent_framework_openai._chat_completion_client.OpenAIChatCompletionClient` and `agent_framework_openai._chat_client.OpenAIChatClient` with signature ok and expected-class ok; the constructor kwarg set was not re-listed. The live round-trip was not re-run. Re-checked offline 2026-10-25 with `agent-framework==1.23.0`, `agent-framework-core==1.23.0`, `agent-framework-openai==1.17.0`, `openai==3.37.0`, `mcp==2.3.0`, `pydantic==2.14.1`, Python 3.12, on the PR lock `adapter-contract-agent_framework-py3.12.txt` (#1203, which also moved `mcp` 1.30.0 → 2.3.0): `verify_frameworks.py --only agent_framework agent_framework.responses --require-installed` resolves `OpenAIChatCompletionClient` and `OpenAIChatClient` with signature ok and expected-class ok (live not run), and both still accept `model`, `base_url`, `api_key`, `default_headers` and `async_client`; the whole suite passes (3588 passed, 182 skipped) and `smoke_example.py agent_framework` passes the conformance kit. `agent-framework-openai` is unchanged at 1.17.0. The live round-trip was not re-run. | 2026-10-13 | #34, #520, #826 (`verify_frameworks.py --only agent_framework`); `python/scripts/verify_frameworks.py --only agent_framework --live`; acceptance group 7 | +| MS Agent Framework (middleware) | `agent_framework.chat_middleware` decorator; `ChatContext.stream` / `.result`; `ResponseStream.with_pull_context_manager(cm_factory)`; `agent_framework.exceptions.ChatClientException` chaining the openai error as `__cause__` | UNVERIFIED (signature confirmed; pending maintainer `--live` + sign-off) | Confirmed offline with `agent-framework==1.19.0`: `Agent(middleware=[...])` accepts a `@chat_middleware`-marked function (an undecorated `(context, next)` function whose first parameter is not annotated `ChatContext` raises `MiddlewareException: Cannot determine middleware type`). `OpenAIChatClient._handle_request_error` re-raises every openai error as `ChatClientException` from the original, so `policy_middleware()` runs the call inside the typed-refusal bridge (`core/refusals.py`, #724), whose chain walk reaches the `openai.APIStatusError` and classifies its response in core; the adapter's `refusal_translator` sees through `agent_framework.exceptions.AgentFrameworkException` (the base of `ChatClientException`, confirmed offline with `agent-framework-core==1.20.0`), so an unreachable gateway surfaces as `GatewayUnavailable`. A streamed refusal surfaces on the caller's pull, after the middleware has returned; `with_pull_context_manager` wraps each pull, so the same conversion applies. Pinned by `test_agent_framework_policy_middleware.py` on a real `Agent` (one send, typed `PIIDetected` with the run's correlation id, streaming and non-streaming). Re-checked at `agent-framework==1.21.0` / `agent-framework-core==1.21.0` (#1126): the `agent-framework-middleware` session passes, `ResponseStream.with_pull_context_manager(cm_factory)` and `chat_middleware` are unchanged, `ChatClientException` still subclasses `AgentFrameworkException`, and the two streaming fixes in 1.21.0 (delegated provider streams close on release, microsoft/agent-framework#9100; streamed logprobs kept across empty chunks, #8997) change cleanup ordering and one metadata field, not the pull-context wrapping or the exception chain. No live round-trip. Re-checked at `agent-framework==1.22.0` / `agent-framework-core==1.22.0` (#1156): the `agent-framework-middleware` session's test files pass (96 passed, 55 skipped for other frameworks, 0 failed); the 1.22.0 release notes and API surface were not otherwise inspected. Re-checked offline 2026-10-25 at `agent-framework==1.23.0` / `agent-framework-core==1.23.0` (#1203): `_middleware.py` (`chat_middleware`, `ChatContext`) and `exceptions.py` are byte-identical to 1.22.0 (diff of the two wheels), `ResponseStream.with_pull_context_manager(cm_factory)` keeps its signature, and the middleware and typed-refusal-bridge tests pass on the contract lock (50 passed, 42 skipped for other frameworks, 0 failed); the `agent-framework-middleware` CI session passed on the #1206 lock. The 1.23.0 release notes were not otherwise inspected. | 2026-10-16 | #739, #1126, #1156 | +| OpenAI Agents SDK | `agents.OpenAIChatCompletionsModel(model, openai_client=openai.AsyncOpenAI(base_url, api_key, default_headers, http_client, max_retries=0))` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `openai-agents==0.23.1`, `openai==3.29.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's model, one turn through `Runner.run` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `openai-agents==0.20.0`. Also tested offline with `openai==2.54.0`. Re-checked offline 2026-10-14 (#1142) at `openai==3.31.0` with `openai-agents==0.23.1` (the PR lock): `verify_frameworks.py --only openai_agents` resolves `agents.models.openai_chatcompletions.OpenAIChatCompletionsModel` with the signature ok; `openai.AsyncOpenAI.__init__` still takes `base_url`, `api_key`, `default_headers`, `http_client` (typed `httpx2.AsyncClient | None`) and `max_retries`; the adapter-contract suite and the strict example smoke pass. openai 3.30.0 drains an async HTTP/1.1 stream for at most 0.1 s after `[DONE]` (openai/openai-python#4041); through the bridge the forwarded stream is already consumed, so the drain hits `httpx.StreamConsumed`, which openai catches, and the response is closed rather than pooled. Streams complete with no added latency or hang, as at 3.24.0. The live round-trip was not re-run. Re-checked offline 2026-10-16 (#223, promotion to a deep, conformance-gated adapter) at `openai-agents==0.24.0` with `openai==3.31.0` (the new `openai-agents-demo` lock): `verify_frameworks.py --only openai_agents` resolves the class with the signature ok; the installed constructor is `OpenAIChatCompletionsModel(model, openai_client, should_replay_reasoning_content=None, strict_feature_validation=False, buffer_streamed_tool_calls=False)`, and the adapter passes only `model` and `openai_client`. The demo additionally relies on the framework's own documented `agents.Agent(name, instructions, model, tools)`, `agents.Runner.run(starting_agent, input, *, run_config=…)`, `agents.RunConfig(tracing_disabled=True)` and `agents.RunResult.final_output`, all confirmed present at 0.24.0; `Agent.instructions` accepts a `(RunContextWrapper, Agent) -> str` callable. The four public conformance scenarios and the Runner-level correlation test (`tests/unit/test_openai_agents_correlation.py`) pass against it. The live round-trip was not re-run. Re-checked offline 2026-10-19 (#1171) at `openai==3.35.1` with `openai-agents==0.24.0` (the PR lock, Python 3.12, installed with the adapter-contract recipe): `verify_frameworks.py --only openai_agents` resolves `agents.models.openai_chatcompletions.OpenAIChatCompletionsModel` with the signature ok; `openai.AsyncOpenAI.__init__` still takes `base_url`, `api_key`, `default_headers`, `http_client` (typed `httpx2.AsyncClient | None`) and `max_retries`, and `openai.DefaultAsyncHttpxClient` is still built on `httpx2.AsyncClient`; the full suite (3266 passed, 273 skipped for other frameworks' extras) and the strict example smoke pass. The live round-trip was not re-run. Re-checked offline 2026-10-25 with `openai-agents==0.24.0`, `openai==3.37.0`, `pydantic==2.14.1`, Python 3.12, on the PR lock `adapter-contract-openai-agents-py3.12.txt` (#1205): `verify_frameworks.py --only openai_agents` gives signature ok and expected-class ok (live not run); the whole suite passes (3447 passed, 281 skipped for other frameworks' extras) and `smoke_example.py openai_agents` passes the conformance kit. openai 3.37.0's breaking change is confined to the Decisions and Live types (release notes), which the adapter does not use. The live round-trip was not re-run. | 2026-10-13 | #34; #223; `python/scripts/verify_frameworks.py --only openai_agents` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only openai_agents --live`; acceptance group 7 | +| Anthropic SDK | `anthropic.AsyncAnthropic(base_url, api_key, default_headers, http_client, max_retries=0)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `anthropic==1.13.0`, `httpx2==2.13.1`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: `messages.create` (`POST /v1/messages`) against the `Format=Anthropic` proxy `ddk-anthropic-inbound` (`claude-haiku-4-5-20251001`), in the harness and in acceptance group 7. Offline: construction and `isinstance` against the recorded path confirmed offline with `anthropic==0.116.0`, and again with `anthropic==0.125.0` and `anthropic==1.9.0` (`httpx2==2.13.1`) after #701. Re-checked offline on 2026-10-14 against the PR lock at `anthropic==1.13.0` (`httpx2==2.13.1`, `openai==3.31.0`, Python 3.12, #1127): `verify_frameworks.py --only anthropic` reports the signature ok; the constructor still takes `base_url`, `api_key`, `default_headers`, `http_client` and `max_retries`; `DefaultAsyncHttpxClient` is still an `httpx2.AsyncClient`; an `httpx` `http_client` is still rejected with `TypeError`; the default `anthropic-version` is still `2023-06-01` (now `ANTHROPIC_API_VERSION` in `_constants.py`, a rename). 1.13.0 also tracks an explicit timeout in a write-only `_timeout_overridden` flag behind a `timeout` property, which changes nothing for this adapter, as it passes no `timeout`. `http_client` is the shared `DonkeyAsyncClient`'s non-owning view on `anthropic<1` (#733) and a bridged `httpx2.AsyncClient` on `anthropic>=1.0` (§8.1). Model id remains per-call; the separately live-verified Anthropic Messages ingress (§2, #304) requires a `Format=Anthropic` proxy. Also tested offline with `openai==2.54.0` (0.116.0) and `openai==3.22.1` (1.9.0). Re-checked offline 2026-10-16 against the PR lock at `anthropic==1.15.0` (`httpx2==2.13.1`, `openai==3.31.0`, Python 3.12, #1161): `verify_frameworks.py --only anthropic` reports the signature ok; the constructor still takes `base_url`, `api_key`, `default_headers`, `http_client` and `max_retries` (it also accepts `auth_token`, `credentials`, `config`, `profile`, `webhook_key`, `open_telemetry` and `middleware`, none of which the adapter passes); `DefaultAsyncHttpxClient` is still an `httpx2.AsyncClient`; an `httpx` `http_client` is still rejected with `TypeError`; the default `anthropic-version` is still `2023-06-01` (`ANTHROPIC_API_VERSION` in `_constants.py`). The live round-trip was not re-run. Re-checked offline 2026-10-22 against the PR lock at `anthropic==2.1.0` (`httpx2==2.16.0`, `openai==3.36.1`, Python 3.12, #1195): `verify_frameworks.py --only anthropic` reports the signature ok and expected-class ok; the constructor still takes `base_url`, `api_key`, `default_headers`, `http_client` and `max_retries`; `DefaultAsyncHttpxClient` is still an `httpx2.AsyncClient`; an `httpx` `http_client` is still rejected with `TypeError`; the whole suite passes (3318 passed, 299 skipped for extras not installed on this leg). **The default `anthropic-version` changed:** 2.0.0 ("add support for new API version 2026-10-21", marked breaking) sets `ANTHROPIC_API_VERSION` to `2026-10-21`, so the client now sends `anthropic-version: 2026-10-21` unless the caller overrides it in `default_headers`. The live round-trip above, and the §2 native-ingress row, were taken with `2023-06-01`. **Live round-trip 2026-10-22** (#1195) with `anthropic==2.1.0`, `httpx2==2.16.0`, Python 3.12, from source on the PR lock: `verify_frameworks.py --only anthropic --live` sends `messages.create` (`POST /v1/messages`) with the new default `anthropic-version: 2026-10-21` through the `Format=Anthropic` proxy `ddk-anthropic-inbound` (`claude-haiku-4-5-20251001`) and gets a completion back, so the gateway passes the new version through. Acceptance group 7 was not re-run. Re-checked offline 2026-10-25 with `anthropic==2.3.0`, `httpx2==2.16.0`, Python 3.12, on the PR lock `adapter-contract-anthropic-py3.12.txt` (#1209): `verify_frameworks.py --only anthropic` gives signature ok and expected-class ok (live not run); the whole suite passes (3438 passed, 299 skipped) and `smoke_example.py anthropic` passes the conformance kit. Per the 2.2.0/2.3.0 changelog, the request-layer changes are limited to beta routes; nothing touches the `AsyncAnthropic` kwargs, `messages.create` or the error classes the adapter uses. No live round-trip was run on 2.3.0. | 2026-10-22 | #34, #701, #1127, #1161, #1195; `python/scripts/verify_frameworks.py --only anthropic` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only anthropic --live`; acceptance group 7 | +| CrewAI | `crewai.LLM(model="openai/…", base_url, api_key, extra_headers, max_retries=0, interceptor)` → returns `crewai.BaseLLM` (concretely `OpenAICompletion`) | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `crewai==1.15.27`, `openai==2.54.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's LLM, one `Task` through `Crew.kickoff` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` confirmed offline with `crewai==1.15.22`, `openai==2.54.0` — against the concrete `OpenAICompletion` class the factory returns, **not** `crewai.LLM`. `crewai.LLM.__new__` is a documented factory: an `openai/`-prefixed model with an explicit `base_url` (which `connection_kwargs()` always supplies) routes to and returns `crewai.llms.providers.openai.completion.OpenAICompletion`, CrewAI's native OpenAI provider and a sibling `crewai.BaseLLM` subclass, not a `crewai.LLM` instance (`isinstance(obj, crewai.LLM)` is false; `isinstance(obj, crewai.BaseLLM)` is true). That provider strips the `openai/` prefix and does not go through LiteLLM, which is only the factory's fallback route. `crewai.BaseLLM` (`crewai.llms.base_llm.BaseLLM`, a public top-level export) is the correct common return type and the adapter's annotation, but every provider is one, so the harness pins the concrete `OpenAICompletion` class: a routing change fails the check instead of passing as another `BaseLLM` (#640). The prior `crewai.LLM` expectation and its `CLASS RENAMED` verdict were an artifact of checking against the wrong class, not a real incompatibility. `extra_headers` is not a named `OpenAICompletion` field: `BaseLLM` collects it into `additional_params`, which `OpenAICompletion` merges into its request parameters, so the proxy auth headers arrive by that passthrough rather than a named parameter — the silently-absorbed-kwarg case above (seen on the wire against a local stub server, sync and streaming; not a sandbox round-trip). `max_retries=0` reaches the provider's OpenAI client (a 503 is sent once); CrewAI's own `BaseLLM` rate-limit retry still sends a budget 429 3 times and has no off switch, an asserted exemption confirmed with `crewai==1.15.23` (#734, `tests/unit/test_framework_retries.py`). Re-confirmed 2026-10-12 with `crewai==1.15.27`, `openai==2.54.0` (#1116): the signature check still resolves to `OpenAICompletion`, and the `adapter-contract(crewai)` suite, retry exemption included, passes. Re-confirmed 2026-10-14 with `crewai==1.15.28`, `openai==2.54.0` (#1141): the signature check still resolves to `OpenAICompletion`, and the crewai conformance, retry-exemption and adapter tests pass. The 1.15.28 change the AI impact review flagged, context-window sizes now read from a bundled LiteLLM catalog (crewAIInc/crewAI#8077), does not touch this row: `crewai/llms/providers/openai/completion.py`, `crewai/llms/base_llm.py`, `crewai/llms/retry.py` and `crewai/llms/hooks/transport.py` are byte-identical to 1.15.27 (diff of the two wheels), the `crewai.LLM.__new__` routing is unchanged (docstring-only edit), and the adapter sets no context-window option. The live round-trip above is still the 1.15.27 one; no `--live` run was made at 1.15.28. Re-checked offline 2026-10-25 with `crewai==1.15.30`, `openai==2.54.0`, `mcp==1.30.0`, Python 3.12, on the PR lock `adapter-contract-crewai-py3.12.txt` (#1210): `verify_frameworks.py --only crewai` gives signature ok and expected-class ok (`OpenAICompletion`; live not run); the whole suite passes (3489 passed, 256 skipped) and `smoke_example.py crewai` passes the conformance kit. 1.15.29's LiteLLM-compatible kwargs enter `crewai.LLM.__new__` only through an active `llm_overlay` (`_overlay_mapped`, read in the installed source); without one, `openai/…` still routes to the native `OpenAICompletion` with the adapter's kwargs. No `--live` run was made at 1.15.30. | 2026-10-13 | #34, #640, #1116, #1141; `python/scripts/verify_frameworks.py --only crewai` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only crewai --live`; acceptance group 7 | | LlamaIndex | `llama_index.llms.openai_like.OpenAILike(model, api_base, api_key, default_headers, is_chat_model=True, is_function_calling_model=True)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `llama-index-core==0.14.25`, `llama-index-llms-openai-like==0.8.1`, `llama-index-llms-openai==0.8.3`, `openai==2.54.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: a `FunctionAgent` on the adapter's LLM, one `run` turn (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route. Offline: construction and `isinstance` against the recorded path confirmed offline with `llama-index-llms-openai-like==0.8.0`. `verify_frameworks.py --only llamaindex` re-run 2026-10-14 on 0.8.1: signature ok, resolves to `llama_index.llms.openai_like.base.OpenAILike`. Also tested offline with `openai==2.54.0`. `http_client` / `async_http_client` carry the SDK's shared client (#740), so every call carries the run's correlation id and `donkey.last_call` is `OBSERVED` after a call. LlamaIndex raises the openai `APIStatusError` on a refusal; `typed_refusals()` converts it to the SDK's typed error. Caveat (#829): LlamaIndex keys reasoning-model handling (`llama_index.llms.openai.utils.O1_MODELS`) and `context_window` (`openai_modelname_to_contextsize`) on the exact bare OpenAI name, and `OpenAILike` defaults `context_window` to 3,900. `LlamaIndexAdapter.llm()` resolves both from the name after one `/` prefix. A prefixed reasoning model gets `temperature=1.0`, plus `max_completion_tokens` / `reasoning_effort` via `additional_kwargs`. Re-confirmed offline 2026-10-14 against `llama-index-llms-openai==0.8.3` (with `llama-index-workflows==2.26.0`): `O1_MODELS` still keys on the exact bare name (`gpt-5-mini` is in it, `openai/gpt-5-mini` is not), `openai_modelname_to_contextsize` still raises `ValueError` for a prefixed name, and `OpenAILike` still defaults `context_window` to 3,900 and `is_chat_model` to `False`. A prefixed `openai/gpt-5-mini` still resolves to `context_window=400000`, `temperature=1.0`, and `max_tokens` / `reasoning_effort` in `additional_kwargs`. 0.8.3 adds `gpt-6.1-sol` (1,050,000) to `O1_MODELS`, which the adapter picks up from the table. `connection_kwargs()` carries no model, so it does not apply these. | 2026-10-13 | #34; #829; `python/scripts/verify_frameworks.py --only llamaindex` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only llamaindex --live`; acceptance group 7 | | Strands | `strands.models.openai.OpenAIModel(model_id, client_args={base_url, api_key, default_headers, http_client, max_retries=0}, stream=False)` | VERIFIED (LIVE) | **Live round-trip 2026-10-13** (#34) with `strands-agents==1.60.0`, `openai==2.54.0`, on the published `donkey-kit==0.1.3.dev0` (TestPyPI), Python 3.12: an `Agent` on the adapter's model, one turn through `invoke_async` (`POST /chat/completions`) against `ddk-openai-model-routing` (`openai/gpt-5-mini`); acceptance group 7 also ran it on `/chat/completions` against `ddk-pii-masking`, a single `azureopenai/gpt-5-mini` route, two turns followed by a `donkey.llm` call on the same `Donkey` (#852). Offline: construction and `isinstance` against the recorded path confirmed offline with `strands-agents==1.60.0` (`verify_frameworks.py --only strands`: signature ok, `OpenAIModel`, with `openai==2.54.0`, #1132; earlier `1.57.1`). `client_args.max_retries=0` reaches the OpenAI client; one send per budget 429 from the model and from `Agent(retry_strategy=None)` confirmed by request count with `strands-agents==1.57.1`, `openai==3.22.1` (#734, `tests/unit/test_framework_retries.py`); re-run with `strands-agents==1.60.0`, `openai==2.54.0` (`strands-agents[openai]` pins `openai<3`): `test_framework_retries.py` and `test_strands_terminal_refusal.py` pass (the `openai==3.22.1` leg was not re-run). Error wrappers, for the typed-refusal bridge (#724): `strands.types.exceptions.ModelThrottledException`, `ContextWindowOverflowException` and `EventLoopException` are each raised `from` the original error and carry no request or response, so the adapter's `refusal_translator` hands their `__cause__` back to `core.refusals.translate()`; confirmed offline with `strands-agents==1.57.2`, re-checked with `1.60.0` (`tests/unit/test_refusal_bridge.py`, passes). Agent throttle retry (#951), read from the installed source of `strands-agents==1.57.2`, and re-read from `1.60.0`, with `openai==2.54.0` (the `openai` extra pins `openai<3`): `OpenAIModel.stream` (`strands/models/openai.py`) raises `ModelThrottledException(str(error)) from error` for an openai error that `classify_openai_error` calls throttling, a 429 included; `Agent(retry_strategy=...)` defaults to `strands.ModelRetryStrategy` (`strands/event_loop/_retry.py`), whose public `is_retryable()` is `isinstance(exception, ModelThrottledException)` and which sets `AfterModelCallEvent.retry` up to `max_attempts=6`; `_handle_model_execution` (`strands/event_loop/event_loop.py`) re-raises a non-retried exception as it is (`raise e`), still so at 1.60.0, which changed `OpenAIModel.stream` (usage read from the `finish_reason` chunk, strands-agents#4851; the `reasoningContent` warning logged once per request, #5016) but not the throttle path (`classify_openai_error`, then `ModelThrottledException(str(error)) from error`), and `event_loop_cycle` re-raises a model-call error without wrapping it in `EventLoopException`. So `model()` returns an `OpenAIModel` subclass whose `stream` (the public `Model.stream` interface) raises `TokenBudgetExceeded` in place of the throttle a budget 429 becomes: a default `Agent` sends it once, and `invoke_async()` and `__call__` raise that same object; a throttle the transport did not send passes through and is retried 3 times under `ModelRetryStrategy(max_attempts=3)`. Confirmed offline by request count with `simulate()` and a loopback proxy (`tests/unit/test_strands_terminal_refusal.py`, `tests/unit/test_framework_retries.py`). Re-checked offline 2026-10-16 with `strands-agents==1.61.1`, `openai==2.54.0` (#1163): `verify_frameworks.py --only strands` reports the signature ok and resolves `OpenAIModel`. The retry, refusal-bridge and throttle-path checks above were not re-run at 1.61.1. The live round-trip was not re-run. | 2026-10-13 | #34; `python/scripts/verify_frameworks.py --only strands` (also `--emit-verified`); `python/scripts/verify_frameworks.py --only strands --live`; acceptance group 7 |