diff --git a/sdk/arch/llm.mdx b/sdk/arch/llm.mdx
index 66feaf460..bd99677ab 100644
--- a/sdk/arch/llm.mdx
+++ b/sdk/arch/llm.mdx
@@ -74,6 +74,21 @@ flowchart TB
| **Configuration Loaders** | Config hydration | `load_from_env()`, `load_from_json()` |
| **Telemetry** | Usage tracking | Token counts, costs, latency |
+### Provider Boundaries
+
+Private adapters normalize provider data before the core LLM pipeline consumes
+it. Stream events use typed contracts; message conversion accepts typed provider
+objects, generic LiteLLM objects, and dictionaries. It preserves tool-call IDs,
+Responses item IDs, and reasoning metadata used to replay conversation history.
+
+When `custom_tokenizer` is configured, the SDK attempts to load a chat-template
+tokenizer through the optional Transformers package. Token counts include the
+chat template and tool schemas when supported. Missing Transformers, tokenizer
+loading failures, or unsupported template results fall back to LiteLLM token
+counting. Transformers is not required to import or use the SDK.
+
+See [LLM Streaming](/sdk/guides/llm-streaming) for callback and completion behavior.
+
## Configuration
See [`LLM` source](https://github.com/OpenHands/software-agent-sdk/blob/main/openhands-sdk/openhands/sdk/llm/llm.py) for complete list of supported fields.
diff --git a/sdk/guides/llm-streaming.mdx b/sdk/guides/llm-streaming.mdx
index 9253d3009..5201c6be5 100644
--- a/sdk/guides/llm-streaming.mdx
+++ b/sdk/guides/llm-streaming.mdx
@@ -5,9 +5,8 @@ description: Stream LLM responses token-by-token for real-time display and inter
import RunExampleCode from "/sdk/shared-snippets/how-to-run-example.mdx";
-
-This is currently only supported for the chat completion endpoint.
-
+Streaming is supported by both Chat Completions (`completion()` / `acompletion()`)
+and Responses (`responses()` / `aresponses()`).
> A ready-to-run example is available [here](#ready-to-run-example)!
@@ -76,6 +75,43 @@ complete response. This creates a more responsive user experience, especially fo
+## Responses Streaming
+
+For direct LLM calls, pass `stream=True` and an `on_token` callback. The call
+returns a complete `LLMResponse` after the stream finishes; callbacks receive
+`ModelResponseStream` chunks while it is being read.
+
+```python
+from openhands.sdk.llm import Message, TextContent
+from openhands.sdk.llm.streaming import ModelResponseStream
+
+def show_text(chunk: ModelResponseStream) -> None:
+ for choice in chunk.choices:
+ if choice.delta.content:
+ print(choice.delta.content, end="", flush=True)
+
+response = llm.responses(
+ [Message(role="user", content=[TextContent(text="Say hello")])],
+ stream=True,
+ on_token=show_text,
+)
+```
+
+Use `await llm.aresponses(...)` for the asynchronous equivalent. It accepts
+synchronous or asynchronous callbacks.
+
+Without a callback, the SDK normally falls back to a non-streaming request.
+Endpoints that require streaming, such as ChatGPT subscription endpoints, still
+drain the stream and return the complete response without a callback.
+
+Instrumentation wrappers do not need to inherit from LiteLLM's stream classes.
+The SDK accepts synchronous iterables and asynchronous iterables on the async
+path, including synchronous wrappers returned to an async caller. A completed
+Responses event yielded by the stream remains valid if the wrapper's
+`completed_response` attribute is absent or `None`. A non-null wrapper completion
+takes precedence after the stream has been read. A stream with no completion
+raises `LLMNoResponseError` and follows the configured retry policy.
+
## Ready-to-run Example