Metadata-Version: 2.4
Name: acpkit
Version: 0.4.2
Summary: ACP Kit provides a common adapter for Agent Frameworks.
Project-URL: Homepage, https://github.com/vcoderun/acpkit
Project-URL: Issues, https://github.com/vcoderun/acpkit/issues
Project-URL: Repository, https://github.com/vcoderun/acpkit
License: MIT
License-File: LICENSE
Keywords: acp,agents,protocol,pydantic-ai
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: click>=8.1.8
Requires-Dist: typing-extensions>=4.12.0
Provides-Extra: all
Requires-Dist: agent-client-protocol>=0.9.0; extra == 'all'
Requires-Dist: basedpyright; extra == 'all'
Requires-Dist: codex-auth-helper; extra == 'all'
Requires-Dist: mkdocs-material; extra == 'all'
Requires-Dist: mkdocstrings[python]; extra == 'all'
Requires-Dist: pre-commit; extra == 'all'
Requires-Dist: pydantic-acp; extra == 'all'
Requires-Dist: pydantic-ai-slim; extra == 'all'
Requires-Dist: pydantic-graph; extra == 'all'
Requires-Dist: pydantic>=2.7; extra == 'all'
Requires-Dist: pytest; extra == 'all'
Requires-Dist: pytest-asyncio; extra == 'all'
Requires-Dist: pytest-cov; extra == 'all'
Requires-Dist: python-dotenv; extra == 'all'
Requires-Dist: ruff; extra == 'all'
Requires-Dist: ty; extra == 'all'
Requires-Dist: typing-extensions>=4.12.0; extra == 'all'
Requires-Dist: uv>=0.8.3; extra == 'all'
Provides-Extra: codex
Requires-Dist: codex-auth-helper; extra == 'codex'
Provides-Extra: dev
Requires-Dist: agent-client-protocol>=0.9.0; extra == 'dev'
Requires-Dist: basedpyright; extra == 'dev'
Requires-Dist: codex-auth-helper; extra == 'dev'
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pydantic-acp; extra == 'dev'
Requires-Dist: pydantic-ai-slim; extra == 'dev'
Requires-Dist: pydantic-graph; extra == 'dev'
Requires-Dist: pydantic>=2.7; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-asyncio; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: python-dotenv; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Requires-Dist: ty; extra == 'dev'
Requires-Dist: typing-extensions>=4.12.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material; extra == 'docs'
Requires-Dist: mkdocstrings[python]; extra == 'docs'
Provides-Extra: launch
Requires-Dist: uv>=0.8.3; extra == 'launch'
Provides-Extra: pydantic
Requires-Dist: pydantic-acp; extra == 'pydantic'
Description-Content-Type: text/markdown

# ACP Kit

ACP Kit is a monorepo for ACP-facing agent runtime packages.

- `acpkit` is the root CLI and target resolver
- `pydantic-acp` adapts `pydantic_ai.Agent` instances to ACP
- `codex-auth-helper` turns a local Codex login into a `pydantic-ai` Responses model

The core workflow is simple:

1. build a normal `pydantic_ai.Agent`
2. expose it through ACP with `run_acp(...)` or `create_acp_agent(...)`
3. optionally add session stores, approvals, providers, projection maps, and bridges

## Installation

Production:

```bash
uv pip install "acpkit[pydantic]"
```

```bash
pip install "acpkit[pydantic]"
```

To use `acpkit launch ...`, install the optional launch extra:

```bash
uv pip install "acpkit[launch]"
```

```bash
pip install "acpkit[launch]"
```

Development:

```bash
uv sync --extra dev --extra docs --extra pydantic
```

```bash
pip install -e ".[dev,docs,pydantic]"
```

## CLI

Run a supported agent target through ACP:

```bash
acpkit run strong_agent
acpkit run strong_agent:agent
acpkit run strong_agent:agent -p ./examples
```

`acpkit` resolves `module` or `module:attribute` targets, auto-detects `pydantic_ai.Agent`
instances, and dispatches them to the installed adapter package. If only the module is given, it
selects the last defined `pydantic_ai.Agent` instance in that module.

If the matching adapter extra is not installed, `acpkit` fails with an install hint such as
`uv pip install "acpkit[pydantic]"`.

Launch a target through Toad ACP:

```bash
acpkit launch strong_agent
acpkit launch strong_agent:agent -p ./examples
```

`acpkit launch TARGET` mirrors the resolved target to:

```bash
toad acp "acpkit run TARGET [-p PATH]..."
```

The command is dispatched through `uvx --python 3.14 --from batrachian-toad`, so Toad runs in a
separate Python 3.14 tool environment and does not replace your project Python.

If the script already starts its own ACP server and should be launched directly, use `--command`:

```bash
acpkit launch -c "python3.11 strong_agent.py"
```

`launch TARGET` and `launch --command ...` are mutually exclusive. `-p/--path` only applies to
`TARGET` mode.

## run_acp

Use `run_acp(...)` when you want to start an ACP server directly from a Pydantic AI agent:

```python
from pydantic_ai import Agent
from pydantic_acp import run_acp

agent = Agent("openai:gpt-5", name="weather-agent")

@agent.tool_plain
def get_weather(city: str) -> str:
    return f"Weather in {city}: sunny"

run_acp(agent=agent)
```

## create_acp_agent

Use `create_acp_agent(...)` when you want the ACP agent object without starting the server
immediately:

```python
import asyncio

from acp import run_agent
from pydantic_ai import Agent
from pydantic_acp import AdapterConfig, MemorySessionStore, create_acp_agent

agent = Agent("openai:gpt-5", name="composable-agent")

acp_agent = create_acp_agent(
    agent=agent,
    config=AdapterConfig(
        agent_name="my-service",
        agent_title="My Service Agent",
        session_store=MemorySessionStore(),
    ),
)

asyncio.run(run_agent(acp_agent))
```

## AdapterConfig

`AdapterConfig` is the main runtime configuration surface. Common fields include:

- agent metadata: `agent_name`, `agent_title`, `agent_version`
- persistence: `session_store`
- model selection: `allow_model_selection`, `available_models`, `models_provider`
- mode and config state: `modes_provider`, `config_options_provider`
- plans: `plan_provider`, or native plan state via `PrepareToolsMode(plan_mode=True)`
- approvals: `approval_bridge`, `approval_state_provider`
- bridges: `capability_bridges`
- projection and classification: `projection_maps`, `tool_classifier`, `enable_generic_tool_projection`

Configured adapter example:

```python
from pathlib import Path

from pydantic_ai import Agent
from pydantic_acp import (
    AdapterConfig,
    AdapterModel,
    FileSessionStore,
    NativeApprovalBridge,
    run_acp,
)

agent = Agent("openai:gpt-5", name="configured-agent")

config = AdapterConfig(
    agent_name="configured-agent",
    agent_title="Configured Agent",
    allow_model_selection=True,
    available_models=[
        AdapterModel(
            model_id="fast",
            name="Fast",
            description="Lower-latency responses",
            override="openai:gpt-5-mini",
        ),
        AdapterModel(
            model_id="smart",
            name="Smart",
            description="Higher-quality responses",
            override="openai:gpt-5",
        ),
    ],
    session_store=FileSessionStore(base_dir=Path(".acp-sessions")),
    approval_bridge=NativeApprovalBridge(enable_persistent_choices=True),
)

run_acp(agent=agent, config=config)
```

## Native Plan State

When `plan_provider` is not configured, the adapter can manage ACP plan state natively. Enable it
by marking one `PrepareToolsMode` with `plan_mode=True` inside a `PrepareToolsBridge`:

```python
from pydantic_ai import Agent
from pydantic_ai.tools import RunContext, ToolDefinition
from pydantic_acp import AdapterConfig, PrepareToolsBridge, PrepareToolsMode, run_acp


def plan_tools(
    ctx: RunContext[None], tool_defs: list[ToolDefinition]
) -> list[ToolDefinition]:
    del ctx
    return list(tool_defs)


def agent_tools(
    ctx: RunContext[None], tool_defs: list[ToolDefinition]
) -> list[ToolDefinition]:
    del ctx
    return list(tool_defs)


agent = Agent("openai:gpt-5", name="plan-agent")

run_acp(
    agent=agent,
    config=AdapterConfig(
        capability_bridges=[
            PrepareToolsBridge(
                default_mode_id="agent",
                modes=[
                    PrepareToolsMode(
                        id="plan",
                        name="Plan",
                        description="Inspect and write plans.",
                        prepare_func=plan_tools,
                        plan_mode=True,
                    ),
                    PrepareToolsMode(
                        id="agent",
                        name="Agent",
                        description="Full tool surface.",
                        prepare_func=agent_tools,
                    ),
                ],
            ),
        ],
    ),
)
```

When the session is in `plan` mode, the adapter:

- injects `acp_get_plan` and `acp_set_plan` as hidden tools on the agent
- extends `output_type` with `NativePlanGeneration` so the agent can emit a structured plan in a
  single response

`NativePlanGeneration` fields:
- `plan_entries: list[PlanEntry]` — structured plan entries
- `plan_md: str` — optional markdown representation

Native plan state and `plan_provider` are mutually exclusive. See
[docs/providers.md](https://github.com/vcoderun/acpkit/blob/main/docs/providers.md) for full
details.

## Agent Factories

Use a factory or custom `AgentSource` when agent construction depends on the current session:

```python
from pydantic_ai import Agent
from pydantic_acp import AcpSessionContext, create_acp_agent

def build_agent(session: AcpSessionContext) -> Agent[None, str]:
    return Agent(
        "openai:gpt-5",
        name=f"agent-{session.cwd.name}",
        system_prompt=f"Work inside {session.cwd.name}.",
    )

acp_agent = create_acp_agent(agent_factory=build_agent)
```

`StaticAgentSource` accepts an optional `deps` field to pass typed runtime dependencies alongside
a shared agent instance without a factory:

```python
from pydantic_acp import AdapterConfig, create_acp_agent
from pydantic_acp.agent_source import StaticAgentSource
from pydantic_ai import Agent

from myapp.deps import AppDependencies

deps = AppDependencies(db=my_db, cache=my_cache)
agent = Agent("openai:gpt-5", name="deps-agent", deps_type=AppDependencies)

acp_agent = create_acp_agent(
    agent_source=StaticAgentSource(agent=agent, deps=deps),
)
```

## Session Stores

`MemorySessionStore` is the default. Use `FileSessionStore` when sessions should survive process
restarts:

```python
from pathlib import Path

from pydantic_ai import Agent
from pydantic_acp import AdapterConfig, FileSessionStore, run_acp

agent = Agent("openai:gpt-5", name="persistent-agent")

run_acp(
    agent=agent,
    config=AdapterConfig(
        session_store=FileSessionStore(base_dir=Path(".acp-sessions")),
    ),
)
```

Session lifecycle support includes create, load, list, fork, resume, close, transcript replay, and
message-history replay.

## Runtime Controls

The adapter exposes a small ACP control plane alongside normal prompts:

- `/model`
  print the current session model id
- `/model provider:model`
  switch the current session model
- `/tools`
  list registered tools
- `/hooks`
  list registered `Hooks` callbacks visible on the current agent
- `/mcp-servers`
  list MCP servers extracted from the current agent toolsets and session metadata

Codex-backed model changes must be explicit:

```text
/model codex:gpt-5.4
```

## Approval Flow

Pydantic AI approval-gated tools are bridged to ACP permission requests:

```python
from pydantic_ai import Agent
from pydantic_ai.exceptions import ApprovalRequired
from pydantic_ai.tools import RunContext
from pydantic_acp import AdapterConfig, NativeApprovalBridge, run_acp

agent = Agent("openai:gpt-5", name="approval-agent")

@agent.tool
def delete_file(ctx: RunContext[None], path: str) -> str:
    if not ctx.tool_call_approved:
        raise ApprovalRequired()
    return f"Deleted {path}"

run_acp(
    agent=agent,
    config=AdapterConfig(
        approval_bridge=NativeApprovalBridge(enable_persistent_choices=True),
    ),
)
```

## Projection Maps

`projection_maps` lets known tool families render as richer ACP content instead of raw text.

### FileSystemProjectionMap

`FileSystemProjectionMap` can project:

- read tools into ACP diff previews
- write tools into ACP file diffs
- bash tools into command previews and terminal references

```python
from pydantic_acp import FileSystemProjectionMap, run_acp

run_acp(
    agent=agent,
    projection_maps=(
        FileSystemProjectionMap(
            default_read_tool="read_file",
            default_write_tool="write_file",
            default_bash_tool="execute",
        ),
    ),
)
```

### HookProjectionMap

`HookProjectionMap` controls how existing `pydantic_ai.capabilities.Hooks` callbacks render into
ACP updates:

```python
from pydantic_ai import Agent
from pydantic_ai.capabilities import Hooks
from pydantic_acp import HookProjectionMap, run_acp

hooks = Hooks[None]()

@hooks.on.before_model_request
async def log_request(ctx, request_context):
    del ctx
    return request_context

agent = Agent("openai:gpt-5", capabilities=[hooks])

run_acp(
    agent=agent,
    projection_maps=(
        HookProjectionMap(
            hidden_event_ids=frozenset({"after_model_request"}),
            event_labels={"before_model_request": "Preparing Request"},
        ),
    ),
)
```

## Capability Bridges

Capability bridges extend ACP exposure without coupling the adapter core to one product runtime.
Built-in bridges cover:

- `HookBridge`
- `PrepareToolsBridge`
- `HistoryProcessorBridge`
- `McpBridge`
- `AgentBridgeBuilder`

See [docs/bridges.md](https://github.com/vcoderun/acpkit/blob/main/docs/bridges.md) for the full bridge model.

## Providers

Providers let the host own session state while the adapter exposes it through ACP:

- `SessionModelsProvider`
- `SessionModesProvider`
- `ConfigOptionsProvider`
- `PlanProvider`
- `ApprovalStateProvider`

See [docs/providers.md](https://github.com/vcoderun/acpkit/blob/main/docs/providers.md) for full details.

## Host Backends

`ClientHostContext` provides session-scoped access to ACP client-backed filesystem and terminal
operations:

```python
from acp.interfaces import Client as AcpClient
from pydantic_ai import Agent
from pydantic_acp import AcpSessionContext, ClientHostContext

def build_agent(client: AcpClient, session: AcpSessionContext) -> Agent[None, str]:
    host = ClientHostContext.from_session(client=client, session=session)
    agent = Agent("openai:gpt-5")

    @agent.tool
    async def read_user_file(ctx, path: str) -> str:
        del ctx
        result = await host.filesystem.read_text_file(path)
        return result.content

    return agent
```

See [docs/host-backends.md](https://github.com/vcoderun/acpkit/blob/main/docs/host-backends.md) for the filesystem
and terminal API surface.

## Codex Auth Helper

`codex-auth-helper` reads `~/.codex/auth.json`, refreshes tokens when needed, builds a Codex-aware
`AsyncOpenAI` client, and returns a ready-to-use `OpenAIResponsesModel`.

```python
from pydantic_ai import Agent
from codex_auth_helper import create_codex_responses_model

agent = Agent(create_codex_responses_model("gpt-5.4"))
```

See [docs/helpers.md](https://github.com/vcoderun/acpkit/blob/main/docs/helpers.md) for helper package details.

## Examples

Runnable and focused examples live under [examples/pydantic](https://github.com/vcoderun/acpkit/tree/main/examples/pydantic):

- [static_agent.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/static_agent.py)
  smallest direct `run_acp(agent=...)` setup
- [factory_agent.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/factory_agent.py)
  session-aware factory plus session-local model selection
- [providers.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/providers.py)
  models, modes, config options, plan updates, and approval metadata
- [bridges.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/bridges.py)
  bridge builder, prepare-tools, history processors, and MCP metadata
- [approvals.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/approvals.py)
  native deferred approval flow
- [host_context.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/host_context.py)
  `ClientHostContext` usage inside a factory-built agent
- [hook_projection.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/hook_projection.py)
  existing `Hooks` capability introspection rendered through `HookProjectionMap`
- [strong_agent.py](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/strong_agent.py)
  full-featured workspace agent example combining factories, providers, approvals, bridges, projection maps, `ask/plan/agent` modes, and host helpers

## Development

Canonical local checks:

```bash
uv run ruff check
uv run ty check
uv run basedpyright
make tests
make check
```

Preview the docs locally:

```bash
make serve
```

## Documentation Map

- [docs/index.md](https://github.com/vcoderun/acpkit/blob/main/docs/index.md): workspace overview and package map
- [docs/cli.md](https://github.com/vcoderun/acpkit/blob/main/docs/cli.md): root `acpkit` CLI behavior
- [docs/pydantic-acp.md](https://github.com/vcoderun/acpkit/blob/main/docs/pydantic-acp.md): adapter API, runtime controls, approvals, projection maps, providers, and host backends
- [docs/bridges.md](https://github.com/vcoderun/acpkit/blob/main/docs/bridges.md): capability bridges and hook rendering
- [docs/providers.md](https://github.com/vcoderun/acpkit/blob/main/docs/providers.md): provider seams and host-owned state
- [docs/host-backends.md](https://github.com/vcoderun/acpkit/blob/main/docs/host-backends.md): client filesystem and terminal helpers
- [docs/helpers.md](https://github.com/vcoderun/acpkit/blob/main/docs/helpers.md): helper packages including `codex-auth-helper`
- [docs/testing.md](https://github.com/vcoderun/acpkit/blob/main/docs/testing.md): behavioral test surface and validation commands
- [examples/pydantic/README.md](https://github.com/vcoderun/acpkit/blob/main/examples/pydantic/README.md): runnable demos and focused SDK examples

## License

Apache 2.0
