Observed since 21 Oct, 09:32 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
AG-UI integration for the Anthropic Claude Agent SDK, with multimodal attachment support (images and documents)
Implementation of the AG-UI protocol for the Anthropic Claude Agent SDK (Python), with support for user attachments — images and documents sent by the user now reach the model instead of being silently discarded.
This is a fork of
integrations/claude-agent-sdkfrom the AG-UI project (MIT), maintained by the Fiqros team. It tracks upstream and adds the attachment fix described in Attachment support below, contributed by the Fiqros team. The import name is unchanged, so it is a drop-in replacement for the upstream package.
pip install ag-ui-claude-sdk-fiqros
Or from a checkout:
pip install -e .
The adapter manages the SDK lifecycle internally — just call adapter.run(input_data):
from ag_ui_claude_sdk import ClaudeAgentAdapter, add_claude_fastapi_endpoint
adapter = ClaudeAgentAdapter(name="my_agent", options={"model": "claude-haiku-4-5"})
add_claude_fastapi_endpoint(app=app, adapter=adapter, path="/my_agent")
Investigated, fixed and documented by the Fiqros team.
Upstream, a user message carrying an attachment never reached the model. Claude would reply "It looks like you forgot to attach the image!" — with no error and nothing in the logs.
Cause. When a user attaches a file, AG-UI sends the message content as a
list of blocks rather than a plain string:
{"role": "user", "content": [
{"type": "text", "text": "what is in this image?"},
{"type": "image", "source": {"type": "data", "value": "<base64>", "mime_type": "image/png"}}
]}
process_messages() walked that list looking for the first block with a .text
attribute, took it, and stopped. Image, document, audio and video blocks have a
.source rather than .text, so they matched nothing and fell out of the loop
unreferenced. Claude was asked about an image it was never shown.
Fix. AG-UI and Anthropic describe attachments with the same structure under different field names, so the fix is a rename, not a conversion — the base64 payload is passed through untouched, with no decode/re-encode:
AG-UI {"type": "data", "value": "<b64>", "mime_type": "image/png"}
Anthropic {"type": "base64", "data": "<b64>", "media_type": "image/png"}
Two changes implement it:
utils.py — convert_content_blocks() renames every block and returns the
full list. Messages with no attachment still return a plain string, so the
common path is byte-for-byte unchanged.session.py — content blocks require ClaudeSDKClient.query()'s streaming
form (AsyncIterable[dict]), since its str form cannot carry them. A small
async generator wraps the blocks in a user-message envelope.Also fixed along the way:
Supported: images and documents, both inline base64 and URL sources. Audio and video blocks are skipped with a warning — Claude does not accept them.
44 tests cover the attachment path (suite total: 110 → 154, with every pre-existing test passing unmodified):
| Suite | Tests | Covers |
|---|---|---|
TestConvertContentBlocks | 13 | Image and document renaming, URL sources, text-only fast path, attachment-only messages, malformed and unknown blocks, base64 passthrough, pydantic models |
TestDocumentBlocks | 15 | MIME passthrough across 6 document types, multiple documents, mixed image+document, large PDFs, missing MIME |
TestProcessMessagesWithAttachments | 6 | Block-list vs plain-string return, camelCase mimeType from TypeScript clients |
TestAttachmentReachesTheClient | 5 | Envelope shape and payload integrity through the real SessionWorker |
TestDocumentDelivery | 5 | Document delivery, 20 KB PDF unmangled, ordering |
The delivery suites run the real SessionWorker with only ClaudeSDKClient
substituted, so they assert on exactly what would reach the CLI. Both halves of
the fix were also mutation-tested — reverting them fails 7 and 17 tests
respectively.
uv run --group dev python -m pytest tests/ -q
Full analysis, evidence and rejected alternatives: BUG_REPORT_attachments_dropped.md.
adapter.interrupt() to stop a running queryThe integration includes 5 example agents:
| Route | Description | Features |
|---|---|---|
/agentic_chat | Basic conversational assistant | Simple chat |
/backend_tool_rendering | Weather tool (backend MCP) | Backend tool execution, tool rendering |
/shared_state | Recipe collaboration | Bidirectional state sync, ag_ui_update_state |
/human_in_the_loop | Task planning with approval | Frontend tools, step tracking, approval workflow |
/tool_based_generative_ui | Frontend tool rendering | Dynamic frontend tools, generative UI |
# Install dependencies
cd integrations/claude-agent-sdk/python
pip install -e .
# Start server (port 8019)
cd examples
ANTHROPIC_API_KEY=sk-ant-xxx python server.py
# Start Dojo (in another terminal)
cd apps/dojo
pnpm dev
Visit http://localhost:3000 and select "Claude Agent SDK (Python)"
Claude SDK maintains conversation state in the .claude/ directory. For production deployments:
.claude/{session_id}/.claude/ as a persistent volume in your containerresume=<session_id> via the options dict or forwarded_propsSee Claude SDK Hosting Guide for deployment patterns.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.7 | 31 Jul 2026 | 6 |
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 09:32 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:d22eab817a99cf5fbfb59ec427b8297973862a46a2a3c7221caf91c0be0ecd45