Metadata-Version: 2.4
Name: ag_ui_adk
Version: 0.10.0
Summary: ADK Middleware for AG-UI Protocol
Author: Mark Fogle
Author-email: Mark Fogle <mark@contextable.com>
License-Expression: MIT
License-File: LICENSE
Requires-Dist: ag-ui-protocol>=1.0.0
Requires-Dist: ag-ui-a2ui-toolkit>=0.0.5
Requires-Dist: a2ui-agent-sdk==0.2.2
Requires-Dist: aiohttp>=3.14.1
Requires-Dist: asyncio>=3.4.3
Requires-Dist: fastapi>=0.115.2
Requires-Dist: google-adk>=1.28.1,<3.0.0
Requires-Dist: pydantic>=2.11.7
Requires-Dist: sse-starlette>=2.1.0
Requires-Dist: uvicorn>=0.35.0
Requires-Python: >=3.10, <3.16
Project-URL: Homepage, https://github.com/ag-ui-protocol/ag-ui/tree/main/integrations/adk-middleware/python
Project-URL: Issues, https://github.com/ag-ui-protocol/ag-ui/issues
Project-URL: Changelog, https://github.com/ag-ui-protocol/ag-ui/blob/main/integrations/adk-middleware/python/CHANGELOG.md
Description-Content-Type: text/markdown

# ADK Middleware for AG-UI Protocol

This Python middleware enables [Google ADK](https://google.github.io/adk-docs/) agents to be used with the AG-UI Protocol, providing a bridge between the two frameworks.

## Prerequisites

The middleware supports CPython 3.10–3.15 (`>=3.10,<3.16`). Python 3.15
requires `ag-ui-a2ui-toolkit>=0.0.5`, which is included in the package's
dependencies. The locked environment uses Google ADK 1.35.0; the supported
dependency range remains `google-adk>=1.28.1,<3.0.0`.

The A2UI dependency is pinned to the verified `a2ui-agent-sdk==0.2.2`.
Versions 0.2.3 and 0.2.4 fail to import their upstream ADK toolset with
`NameError: name 'models' is not defined` ([#3089](https://github.com/ag-ui-protocol/ag-ui/issues/3089)).
Version 0.2.2 retains the common-types schema fix used by AG-UI's catalog
prompts. The pin remains until an explicit SDK compatibility verification
and migration; future releases require that review before adoption.
Applications importing `a2ui.adk.send_a2ui_to_client_toolset` also need
`a2a-sdk<1`: that upstream module uses A2A's legacy API. AG-UI itself imports
only A2UI's schema/parser helpers and does not constrain A2A.

CI runs the full suite on Python 3.12 and 3.15. The 3.15 job also builds the
sdist and wheel, installs the wheel with pip in a clean environment, and runs
the suite outside the source checkout. Google ADK 2.x has a separate
informational full-suite job with known failures; Python 3.15 support does
not resolve those existing compatibility limitations. Free-threaded Python
and Windows have not been verified.

The examples use ADK Agents using various Gemini models along with the AG-UI Dojo.
The separate `examples/` application currently requires Python 3.10–3.14;
its environment has not yet been updated for Python 3.15.

- A [Gemini API Key](https://makersuite.google.com/app/apikey). The examples assume that this is exported via the GOOGLE_API_KEY environment variable.

## Quick Start

To use this integration you need to:

1. Clone the [AG-UI repository](https://github.com/ag-ui-protocol/ag-ui).

    ```bash
    git clone https://github.com/ag-ui-protocol/ag-ui.git
    ```

2. Change to the `integrations/adk-middleware/python` directory.

    ```bash
    cd integrations/adk-middleware/python
    ```

3. Install the `adk-middleware` package from the local directory.  For example,

    ```bash
    pip install .
    ```

    or

    ```bash
    uv pip install .
    ```

    This installs the package from the current directory which contains:
    - `src/ag_ui_adk/` - The middleware source code
    - `examples/` - Example servers and agents
    - `tests/` - Test suite

4. Install the requirements for the `examples`, for example:

    ```bash
    uv pip install -r requirements.txt
    ```

5. Run the example fast_api server.

    ```bash
    export GOOGLE_API_KEY=<My API Key>
    cd examples
    uv sync
    uv run dev
    ```

6. Open another terminal in the root directory of the ag-ui repository clone.

7. Start the integration ag-ui dojo:

    ```bash
    pnpm install && pnpm run dev
    ```

8. Visit [http://localhost:3000/adk-middleware](http://localhost:3000/adk-middleware).

9. Select View `ADK Middleware` from the sidebar.

### Development Setup

If you want to contribute to ADK Middleware development, you'll need to take some additional steps.  You can either use the following script of the manual development setup.

```bash
# From the adk-middleware directory
chmod +x setup_dev.sh
./setup_dev.sh
```

### Manual Development Setup

```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate

# Install this package in editable mode
pip install -e .

# For development (includes testing and linting tools)
pip install -e ".[dev]"
# OR
pip install -r requirements-dev.txt
```

This installs the ADK middleware in editable mode for development.

## Testing

```bash
# Run tests (271 comprehensive tests)
pytest

# With coverage
pytest --cov=src/ag_ui_adk

# Specific test file
pytest tests/test_adk_agent.py
```
## Usage options

### Option 1: Direct Usage
```python
from ag_ui_adk import ADKAgent
from google.adk.agents import Agent

# 1. Create your ADK agent
my_agent = Agent(
    name="assistant",
    instruction="You are a helpful assistant."
    tools=[
        AGUIToolset(), # Add the tools provided by the AG-UI client
    ]
)

# 2. Create the middleware with direct agent embedding
agent = ADKAgent(
    adk_agent=my_agent,
    app_name="my_app",
    user_id="user123"
)

# 3. Use directly with AG-UI RunAgentInput
async for event in agent.run(input_data):
    print(f"Event: {event.type}")
```

### Option 2: FastAPI Server

```python
from fastapi import FastAPI
from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint
from google.adk.agents import Agent

# 1. Create your ADK agent
my_agent = Agent(
    name="assistant",
    instruction="You are a helpful assistant."
    tools=[
        AGUIToolset(), # Add the tools provided by the AG-UI client
    ]
)

# 2. Create the middleware with direct agent embedding
agent = ADKAgent(
    adk_agent=my_agent,
    app_name="my_app",
    user_id="user123"
)

# 3. Create FastAPI app
app = FastAPI()
add_adk_fastapi_endpoint(
    app, agent, path="/chat",
    extract_headers=["x-user-id", "x-tenant-id"]  # Extract HTTP headers into state.headers
)

# Run with: uvicorn your_module:app --host 0.0.0.0 --port 8000
```

For detailed configuration options, see [CONFIGURATION.md](./CONFIGURATION.md)

### Option 3: Using ADK App with ResumabilityConfig (HITL)

> **Requires `google-adk >= 1.16.0`**

For human-in-the-loop (HITL) workflows where the agent pauses for user approval and resumes afterward, use `ADKAgent.from_app()` with ADK's `ResumabilityConfig`. This enables ADK to persist `FunctionCall` events before pausing, allowing seamless resumption when the user provides tool results.

```python
from fastapi import FastAPI
from ag_ui_adk import ADKAgent, add_adk_fastapi_endpoint, AGUIToolset
from google.adk.agents import Agent
from google.adk.apps import App, ResumabilityConfig

# 1. Create your ADK agent with client-side tools
my_agent = Agent(
    name="assistant",
    instruction="You are a helpful assistant.",
    tools=[
        AGUIToolset(),  # Client-side tools for HITL workflows
    ]
)

# 2. Wrap in an ADK App with ResumabilityConfig
adk_app = App(
    name="my_app",
    root_agent=my_agent,
    resumability_config=ResumabilityConfig(is_resumable=True),
)

# 3. Create the middleware using from_app()
agent = ADKAgent.from_app(
    adk_app,
    user_id="user123",
    session_timeout_seconds=3600,
    use_in_memory_services=True,
)

# 4. Add FastAPI endpoint
app = FastAPI()
add_adk_fastapi_endpoint(app, agent, path="/chat")
```

**How it works:**

1. The agent calls a client-side tool (e.g., `generate_task_steps`) — ADK persists the `FunctionCall` event and pauses execution
2. The middleware emits `TOOL_CALL_START`, `TOOL_CALL_ARGS`, and `TOOL_CALL_END` events to the frontend
3. The user reviews and responds (approve/reject), and the frontend sends a `ToolMessage` with the result, or a `resume` entry (see [TOOLS.md](./TOOLS.md#interrupts-and-resume))
4. The middleware resumes ADK execution with the stored `invocation_id`, restoring the agent's position
5. The agent continues from where it left off with the user's response

With `emit_interrupt_outcome=True` (default `False`), a tool confirmation (`tool_context.request_confirmation()`) additionally ends its run with an interrupt outcome on `RUN_FINISHED`. Enable it only with a frontend that resumes via `RunAgentInput.resume`; see [TOOLS.md](./TOOLS.md#interrupts-and-resume).

**When to use `from_app()` vs direct `ADKAgent()`:**

| Feature | `ADKAgent(adk_agent=...)` | `ADKAgent.from_app(app)` |
|---|---|---|
| Basic HITL | ~~Yes (fire-and-forget)~~ **Deprecated** | Yes (native resumability) |
| Session persistence across pause/resume | Manual | Automatic |
| SequentialAgent sub-agent position restore | No | Yes |
| Requires `google-adk` | Any version | >= 1.16.0 |

> **Deprecation notice:** The fire-and-forget HITL flow via `ADKAgent(adk_agent=...)` is deprecated and will be removed in a future version. For human-in-the-loop workflows, use `ADKAgent.from_app()` with `ResumabilityConfig(is_resumable=True)`. The direct constructor remains fully supported for agents without client-side tools. See [USAGE.md](./USAGE.md#migrating-to-resumable-hitl) for migration instructions.

See `examples/server/api/human_in_the_loop.py` for a complete working example.

## Running the ADK Backend Server for Dojo App

To run the ADK backend server that works with the Dojo app, use the following command:

```bash
python -m examples.fastapi_server
```

This will start a FastAPI server that connects your ADK middleware to the Dojo application.

## Examples

### Simple Conversation

```python
import asyncio
from ag_ui_adk import ADKAgent
from google.adk.agents import Agent
from ag_ui.core import RunAgentInput, UserMessage

async def main():
    # Setup
    my_agent = Agent(
        name="assistant", 
        instruction="You are a helpful assistant.", 
        tools=[
            AGUIToolset(), # Add the tools provided by the AG-UI client
        ]
    )

    agent = ADKAgent(
        adk_agent=my_agent,
        app_name="demo_app",
        user_id="demo"
    )

    # Create input
    input = RunAgentInput(
        thread_id="thread_001",
        run_id="run_001",
        messages=[
            UserMessage(id="1", role="user", content="Hello!")
        ],
        context=[],
        state={},
        tools=[],
        forwarded_props={}
    )

    # Run and handle events
    async for event in agent.run(input):
        print(f"Event: {event.type}")
        if hasattr(event, 'delta'):
            print(f"Content: {event.delta}")

asyncio.run(main())
```

### Multiple AG-UI Endpoints

```python
# Create multiple ADKAgent instances with different ADK agents
general_agent_wrapper = ADKAgent(
    adk_agent=general_agent,
    app_name="demo_app",
    user_id="demo"
)

technical_agent_wrapper = ADKAgent(
    adk_agent=technical_agent,
    app_name="demo_app",
    user_id="demo"
)

creative_agent_wrapper = ADKAgent(
    adk_agent=creative_agent,
    app_name="demo_app",
    user_id="demo"
)

# Use different endpoints for each agent
from fastapi import FastAPI
from ag_ui_adk import add_adk_fastapi_endpoint

app = FastAPI()
add_adk_fastapi_endpoint(app, general_agent_wrapper, path="/agents/general")
add_adk_fastapi_endpoint(app, technical_agent_wrapper, path="/agents/technical")
add_adk_fastapi_endpoint(app, creative_agent_wrapper, path="/agents/creative")
```

## Context Support

The middleware automatically passes `context` from `RunAgentInput` to your ADK agents, following the pattern established by LangGraph. Context is stored in session state under the `_ag_ui_context` key and is accessible in both tools and instruction providers.

### In Tools via Session State

```python
from google.adk.tools import ToolContext
from ag_ui_adk import CONTEXT_STATE_KEY

def my_tool(tool_context: ToolContext) -> str:
    context_items = tool_context.state.get(CONTEXT_STATE_KEY, [])
    for item in context_items:
        print(f"{item['description']}: {item['value']}")
    return "Done"
```

### In Instruction Providers via Session State

```python
from google.adk.agents.readonly_context import ReadonlyContext
from ag_ui_adk import CONTEXT_STATE_KEY

def dynamic_instructions(ctx: ReadonlyContext) -> str:
    instructions = "You are a helpful assistant."

    context_items = ctx.state.get(CONTEXT_STATE_KEY, [])
    for item in context_items:
        instructions += f"\n- {item['description']}: {item['value']}"

    return instructions

agent = LlmAgent(
    name="assistant",
    instruction=dynamic_instructions,  # Callable instruction provider
)
```

### Alternative: Via RunConfig custom_metadata

Context is also available via `RunConfig.custom_metadata`:

```python
def dynamic_instructions(ctx: ReadonlyContext) -> str:
    # Alternative access via custom_metadata
    if ctx.run_config and ctx.run_config.custom_metadata:
        context_items = ctx.run_config.custom_metadata.get('ag_ui_context', [])
```

**Note:** Session state is the recommended approach as it works with all ADK versions.

See `examples/other/context_usage.py` for a complete demonstration.

## Tool Support

The middleware provides complete bidirectional tool support, enabling AG-UI Protocol tools to execute within Google ADK agents. All tools supplied by the client are currently implemented as long-running tools that emit events to the client for execution and can be combined with backend tools provided by the agent to create a hybrid combined toolset.

### Adk Agent Agui Tool Support

Use the AGUIToolset to expose tools from the AG-UI client to the ADK agent. By default all agui client tools are added to the context. You can filter which tools to expose using the `tool_filter` parameter and fix name conflicts with the `tool_name_prefix` parameter. In google adk tools with the same name override previously defined tools of the same name. You can order the tools array to control which tool takes precedence.

```python
from ag_ui_adk import ADKAgent, AGUIToolset
from google.adk.agents import Agent

hello_agent = LlmAgent(
    name='HelloAgent',
    model='gemini-3.5-flash',
    description="An agent that greets users",
    instruction="""
    You are a friendly assistant that greets users.
    Use the sayHello tool to greet the user.
    """,
    tools=[
        AGUIToolset(tool_filter=['sayHello']) # Add only the sayHello tool exposed by the AG-UI client
    ],
)

goodbye_agent = LlmAgent(
    name='GoodbyeAgent',
    model='gemini-3.5-flash',
    description="An agent that says goodbye",
    instruction="""
    You are a friendly assistant that says goodbye to users.
    Use the sayGoodbye tool to say goodbye to the user.
    """,
        tools=[
        AGUIToolset(tool_filter=lambda tool, readonly_context=None: tool.name.endswith('Goodbye') ) # Add tools ending with Goodbye exposed by the AG-UI client
    ],
)

# create an agent
agent = LlmAgent(
    name='QaAgent',
    model='gemini-3.5-flash',
    description="The QaAgent helps users by answering their questions.",
    instruction="""
    You are a helpful assistant. Help users by answering their questions and assisting with their needs.
    """,
    tools=[
        # This agent doesn't see any tools provided by the AG-UI client
    ],
    sub_agents=[
        hello_agent, 
        goodbye_agent,
    ],
)
```


For detailed information about tool support, see [TOOLS.md](./TOOLS.md).

## Additional Documentation

- **[CONFIGURATION.md](./CONFIGURATION.md)** - Complete configuration guide
- **[TOOLS.md](./TOOLS.md)** - Tool support documentation
- **[USAGE.md](./USAGE.md)** - Usage examples and patterns
- **[ARCHITECTURE.md](./ARCHITECTURE.md)** - Technical architecture and design details

## Migration Guide

### Migrating from v0.4.x 

If you are upgrading from version 0.4.x, please note the following changes:

- Agui tools are no longer automatically included in the root agent's toolset. You must explicitly add the `AGUIToolset` to your agent's tools list to access AG-UI client tools.

- Agui tools with names that conflict with existing agent tools will no longer be automatically removed. Use the `tool_name_prefix` and `tool_filter` parameters of `AGUIToolset` to manage tool name conflicts and filter which tools to include.

- If you want to maintain the previous behavior of only the root agent having access to AG-UI tools, and ensure no name conflicts, you can add the `AGUIToolset` with a custom filter as the first tool in the root agent like this:

    ```python
    tools=[
        AGUIToolset(
            tool_filter=lambda tool, readonly_context=None: tool.name not in [
                "transfer_to_agent", 
                "any other tools provided to this agent that overlap with agui tools...",
            ],
        ),
        ...other tools...
    ]
    ```
