Metadata-Version: 2.4
Name: ag-ui-claude-sdk-fiqros
Version: 0.1.7
Summary: AG-UI integration for the Anthropic Claude Agent SDK, with multimodal attachment support (images and documents)
Author-email: Ambient Code Platform <gkrumbac@redhat.com>
Maintainer: Fiqros Team
License-Expression: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ag-ui-protocol>=0.1.15
Requires-Dist: claude-agent-sdk>=0.1.12
Requires-Dist: anthropic>=0.68.0
Requires-Dist: fastapi>=0.100.0
Requires-Dist: uvicorn[standard]>=0.23.0
Requires-Dist: pydantic>=2.0.0
Dynamic: license-file

# ag-ui-claude-sdk-fiqros

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-sdk`](https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/claude-agent-sdk)
> from the AG-UI project (MIT), maintained by the **Fiqros team**. It tracks
> upstream and adds the attachment fix described in
> [Attachment support](#attachment-support-fork-changes) below, contributed by
> the Fiqros team. The import name is unchanged, so it is a drop-in replacement
> for the upstream package.

## Installation

```bash
pip install ag-ui-claude-sdk-fiqros
```

Or from a checkout:

```bash
pip install -e .
```

## Usage

The adapter manages the SDK lifecycle internally — just call `adapter.run(input_data)`:

```python
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")
```

## Attachment support (fork changes)

*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:

```json
{"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:**

- An attachment-only message (no text) previously produced an *empty* prompt.
- A message split into several text blocks lost everything after the first.
- Unsupported or malformed blocks are now skipped **with a warning** instead of
  in silence.

**Supported:** images and documents, both inline base64 and URL sources. Audio
and video blocks are skipped with a warning — Claude does not accept them.

### Test coverage

**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.

```bash
uv run --group dev python -m pytest tests/ -q
```

Full analysis, evidence and rejected alternatives: [BUG_REPORT_attachments_dropped.md](BUG_REPORT_attachments_dropped.md).

## Features

- **User attachments** - Images and documents in user messages are passed through to the model
- **Full lifecycle management** - Handles client pooling, message extraction, and event translation internally
- **Interrupt support** - Call `adapter.interrupt()` to stop a running query
- **Dynamic frontend tools** - Client-provided tools automatically added as MCP server with auto-granted permissions
- **Frontend tool halting** - Streams pause after frontend tool calls for client-side execution (human-in-the-loop)
- **Streaming tool arguments** - Real-time TOOL_CALL_ARGS emission as JSON arguments stream in
- **Bidirectional state sync** - Shared state management via ag_ui_update_state tool
- **Context injection** - Context and state injected into prompts for agent awareness
- **Event cleanup** - Hanging events (tool calls, reasoning blocks) automatically closed on stream end
- **Custom tools via MCP** - Define custom tools using Claude SDK's @tool decorator
- **Forwarded props** - Per-run option overrides with security whitelist

## Examples

The 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 |

## Running the Examples

```bash
# 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)"**

## Session Persistence

Claude SDK maintains conversation state in the `.claude/` directory. For production deployments:

- **Development**: Sessions persist locally in `.claude/{session_id}/`
- **Production**: Mount `.claude/` as a persistent volume in your container
- **Resumption**: Pass `resume=<session_id>` via the options dict or `forwarded_props`

See [Claude SDK Hosting Guide](https://platform.claude.com/docs/en/agent-sdk/hosting) for deployment patterns.

## Links

- [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/python)
- [AG-UI Documentation](https://docs.ag-ui.com/)
- [AG-UI State Management](https://docs.ag-ui.com/concepts/state)
