Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3f67761
test: specify ACP client provider bridge for pydantic AI v2
arthrod Jul 1, 2026
1796f56
feat: add ACP provider bridge for pydantic AI v2
arthrod Jul 2, 2026
13c8d57
build: require pydantic AI v2 for pydantic-acp
arthrod Jul 2, 2026
f979b11
feat: export ACP client provider bridge
arthrod Jul 2, 2026
4994131
build: align dev dependency with pydantic AI v2
arthrod Jul 2, 2026
663c673
test: tighten ACP client provider observable contract
arthrod Jul 4, 2026
a5d78a3
refactor: polish ACP provider v2 type surface
arthrod Jul 4, 2026
f3c2f78
docs: document ACP client provider bridge
arthrod Jul 4, 2026
6c4d0bc
Merge branch 'main' into arthur/acp-client-provider-v2-20260701
arthrod Jul 4, 2026
b0c55ee
🔧 CodeRabbit Recipe: Add custom finishing touch type-mania
coderabbitai[bot] Jul 4, 2026
536e728
🔧 CodeRabbit Recipe: Add custom TDD red-green finishing touch
coderabbitai[bot] Jul 4, 2026
c4549ac
Merge pull request #4 from arthrod/coderabbitai/recipe3/6c4d0bc
arthrod Jul 7, 2026
fbfe64c
Merge pull request #2 from arthrod/coderabbitai/recipe2/6c4d0bc
arthrod Jul 7, 2026
0bf499a
🔧 CodeRabbit Recipe: Add custom finishing touch tdd-red-green
coderabbitai[bot] Jul 7, 2026
8c73122
🔧 CodeRabbit Recipe: Deduplicate custom finishing touches
coderabbitai[bot] Jul 7, 2026
48e605a
lossless bug
opencode-agent[bot] Jul 7, 2026
32656ad
Merge branch 'arthur/acp-client-provider-v2-20260701' into coderabbit…
arthrod Jul 7, 2026
665da6b
Merge pull request #14 from arthrod/coderabbitai/recipe1/fbfe64c
arthrod Jul 7, 2026
39adf48
Merge branch 'arthur/acp-client-provider-v2-20260701' into coderabbit…
arthrod Jul 7, 2026
4161748
Merge pull request #11 from arthrod/coderabbitai/recipe3/fbfe64c
arthrod Jul 7, 2026
672c9ee
lossless bug
opencode-agent[bot] Jul 7, 2026
077e6fd
📝 CodeRabbit Chat: Add unit tests for PR changes
coderabbitai[bot] Jul 7, 2026
624e18f
📝 CodeRabbit Chat: Add unit tests for PR changes
coderabbitai[bot] Jul 7, 2026
de8db3f
📝 CodeRabbit Chat: Add unit tests for PR changes
coderabbitai[bot] Jul 7, 2026
9860b1f
📝 CodeRabbit Chat: Add unit tests for PR changes
coderabbitai[bot] Jul 7, 2026
5afc0d0
lossless bug
opencode-agent[bot] Jul 7, 2026
d143c19
Refactor AcpProvider and related classes from dataclasses to manual _…
opencode-agent[bot] Jul 7, 2026
65f1390
Remove blank lines after function definitions across multiple files
opencode-agent[bot] Jul 7, 2026
e338cd1
Add type annotations and cast for improved type safety in AcpProvider…
opencode-agent[bot] Jul 7, 2026
e188820
formatting
opencode-agent[bot] Jul 7, 2026
bfb34d2
Add empty message handling and refactor test assertions for clarity
opencode-agent[bot] Jul 7, 2026
ec26651
finalizing
opencode-agent[bot] Jul 7, 2026
6811e5b
Fix AcpProvider type safety and prompt rendering
opencode-agent[bot] Jul 7, 2026
d6fab16
Add type annotation for delegate.on_connect and make model_profile st…
opencode-agent[bot] Jul 7, 2026
4fb37cc
Remove unused Callable import and use setattr for dynamic attribute a…
opencode-agent[bot] Jul 7, 2026
429f684
Fix trailing commas and use string literals for forward-reference typ…
opencode-agent[bot] Jul 8, 2026
8d12d6a
Bump version to 1.1.0 and update CHANGELOG for Pydantic AI v2 provide…
opencode-agent[bot] Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

def describe_codex_surface() -> str:
"""Describe the LangChain-facing Codex helper seam."""

return (
"This graph uses codex-auth-helper to build a LangChain ChatOpenAI model "
"backed by the local Codex login."
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ scripts/check_releases.py
# Scripts
connect*.py
expose*.py
probe*.py
pydantic_test*.py
CHANGELOG
.acprouter-state
.deepagents-graph
Expand Down
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
ACP Kit uses synchronized versions for `acpkit`, `pydantic-acp`, `langchain-acp`,
`codex-auth-helper`, and `acpremote`.

## [1.1.0] - 2026-07-07

### Added

- Pydantic AI v2 ACP client provider bridge (`AcpProvider`, `AcpModel`,
`AcpHostBridge`) in `pydantic-acp`, enabling ACP agents to be consumed as
Pydantic AI providers with model profiles, streaming, tool calls, approvals,
sessions, and projections.
- Public export of the provider bridge surface from `pydantic_acp`.

### Changed

- `pydantic-acp` now requires Pydantic AI v2.
- Dev dependency aligned with Pydantic AI v2.
- Workspace packages synchronized to `1.1.0`.

### Fixed

- Type-safety and forward-reference handling in the ACP provider bridge.

## [1.0.0] - 2026-07-03

This is the first stable release of the synchronized ACP Kit workspace.
Expand Down Expand Up @@ -46,4 +66,5 @@ This is the first stable release of the synchronized ACP Kit workspace.
failure paths close every owned resource deterministically.
- CLI target import failures retain actionable root-cause details.

[1.1.0]: https://github.com/vcoderun/acpkit/releases/tag/v1.1.0
[1.0.0]: https://github.com/vcoderun/acpkit/releases/tag/v1.0.0
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,32 @@ Host-facing utilities include:
- `BlackBoxHarness` for ACP boundary integration tests
- `CompatibilityManifest` for documenting the ACP surface an integration truly supports

## ACP Client Provider Bridge

`pydantic-acp` also includes the inverse bridge: an ACP agent can be consumed as a Pydantic AI v2 provider/model pair via `AcpProvider` and `AcpModel`.

```python
from pydantic_ai import Agent
from pydantic_acp import AcpProvider

# `remote_acp_agent` can be any object implementing the ACP Agent interface.
provider = AcpProvider(agent=remote_acp_agent, cwd="/workspace")
model = provider.model("zed-agent")
agent = Agent(model)

result = await agent.run("Summarize the current workspace state.")
print(result.output)
```

This keeps ownership boundaries explicit:

- Pydantic AI owns the outer agent run, output validation, and normal model/provider lifecycle.
- ACP owns the delegated agent session, ACP-visible updates, and any editor or host capabilities requested by that agent.
- `AcpHostBridge` records ACP `session_update` messages and can delegate filesystem, terminal, approval, and extension callbacks to a real ACP host client when one is supplied.
- Pydantic AI function tools are intentionally not executed directly by `AcpModel`; register tools on the ACP agent or expose host capabilities through ACP.

Use this bridge when the thing you have is already an ACP agent and you want it to participate in code that expects a Pydantic AI provider/model. It is not another ACP server adapter and it does not replace `create_acp_agent(...)`.

## What `langchain-acp` Supports

`langchain-acp` keeps ACP Kit's adapter seams intact while staying graph-centric on the upstream side.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0
6 changes: 2 additions & 4 deletions examples/langchain/codex_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
"MODEL_NAME",
"acp_agent",
"build_graph",
"codex_instructions",
"config",
"describe_codex_surface",
"codex_instructions",
"graph",
"graph_from_session",
"main",
Expand All @@ -48,7 +48,6 @@

def codex_instructions(*, mode_id: str) -> str:
"""Return the Codex Responses instructions used by this example graph."""

base = (
"You are a precise workspace assistant. "
"Use the available tools when they help, keep answers concise, "
Expand All @@ -63,14 +62,13 @@ def codex_instructions(*, mode_id: str) -> str:

def describe_codex_surface() -> str:
"""Summarize the Codex-backed LangChain example surface."""

return "\n".join(
(
"Codex graph features:",
"- LangChain ChatOpenAI wired through codex-auth-helper",
"- OpenAI Responses API transport through local Codex auth state",
"- ready for `langchain-acp` exposure through `run_acp(graph=...)`",
)
),
)


Expand Down
12 changes: 4 additions & 8 deletions examples/langchain/deepagents_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"MODEL_NAME",
"WORKSPACE_ROOT",
"acp_agent",
"config",
"codex_instructions",
"config",
"graph",
"graph_from_session",
"list_workspace_files",
Expand Down Expand Up @@ -74,7 +74,6 @@

def codex_instructions(*, mode_id: str) -> str:
"""Return the Codex Responses instructions used by the DeepAgents example."""

base = (
"You are a workspace agent that can inspect and update files in the current demo workspace. "
"Use tools for concrete file work, keep changes scoped, and summarize what changed."
Expand Down Expand Up @@ -126,7 +125,7 @@ def _resolve_workspace_path(path: str, *, root: Path | None = None) -> Path:
if relative_to_parent.parts
and relative_to_parent.parts[0] == workspace_root.name
else relative_to_parent.parts
)
),
)
candidate = (workspace_root / relative_to_parent).resolve()
else:
Expand All @@ -152,13 +151,11 @@ def _normalized_mock_path(path: str) -> str:

def list_workspace_files() -> str:
"""List mocked workspace files exposed by the DeepAgents example."""

return "\n".join(sorted(MOCK_WORKSPACE_FILES))


def read_file(path: str) -> str:
"""Read a mocked file from the DeepAgents example workspace."""

normalized = _normalized_mock_path(path)
content = MOCK_WORKSPACE_FILES.get(normalized)
if content is None:
Expand All @@ -168,7 +165,6 @@ def read_file(path: str) -> str:

def write_file(path: str, content: str) -> str:
"""Pretend to write a file in the DeepAgents example workspace."""

normalized = _normalized_mock_path(path)
del content
return f"Mock write accepted for {normalized}"
Expand Down Expand Up @@ -212,7 +208,7 @@ def _write_file(path: str, content: str) -> str:
def graph_from_session(session: AcpSessionContext) -> CompiledAgentGraph:
if not _deepagents_available():
raise RuntimeError(
'Install the optional DeepAgents dependency first: uv add "langchain-acp[deepagents]"'
'Install the optional DeepAgents dependency first: uv add "langchain-acp[deepagents]"',
)
workspace_root = _ensure_workspace(_session_workspace_root(session)).resolve()
model_name = session.session_model_id or DEFAULT_MODEL_ID or MODEL_NAME
Expand All @@ -221,7 +217,7 @@ def graph_from_session(session: AcpSessionContext) -> CompiledAgentGraph:

tools: Sequence[DeepAgentTool] = [
*_bind_workspace_tools(workspace_root),
*cast(Sequence[DeepAgentTool], native_plan_tools()),
*cast("Sequence[DeepAgentTool]", native_plan_tools()),
]
return create_deep_agent(
model=create_codex_chat_openai(
Expand Down
9 changes: 2 additions & 7 deletions examples/langchain/workspace_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
"MODEL_NAME",
"WORKSPACE_ROOT",
"acp_agent",
"config",
"codex_instructions",
"config",
"describe_workspace_surface",
"graph",
"graph_from_session",
Expand Down Expand Up @@ -56,7 +56,6 @@

def codex_instructions(*, mode_id: str) -> str:
"""Return the Codex Responses instructions used by the workspace graph."""

base = (
"You are a careful workspace assistant operating inside a small demo workspace. "
"Prefer reading files before changing them, keep edits focused, "
Expand Down Expand Up @@ -111,27 +110,24 @@ def _workspace_surface_summary() -> str:
"- session-aware `graph_from_session(...)` for per-session graph construction",
"- file read and write projection through `FileSystemProjectionMap`",
"- a seeded workspace that keeps ACP rendering deterministic",
)
),
)


def list_workspace_files() -> str:
"""List seeded workspace files that the demo graph can read."""

root = _ensure_workspace()
files = sorted(path.relative_to(root).as_posix() for path in root.rglob("*") if path.is_file())
return "\n".join(files)


def describe_workspace_surface() -> str:
"""Summarize the ACP-facing features exposed by the workspace graph example."""

return _workspace_surface_summary()


def read_workspace_note(path: str) -> str:
"""Read a workspace note and return its text content."""

note_path = _resolve_workspace_path(path)
if not note_path.exists():
raise ValueError(f"File not found: {path}")
Expand All @@ -140,7 +136,6 @@ def read_workspace_note(path: str) -> str:

def write_workspace_note(path: str, content: str) -> str:
"""Write a workspace note and return the saved relative path."""

root = _ensure_workspace()
return _write_workspace_note(root, path, content)

Expand Down
11 changes: 3 additions & 8 deletions examples/pydantic/finance_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,35 +171,32 @@ def persist_plan_state(
@agent.tool_plain
def describe_finance_surface() -> str:
"""Summarize the ACP-facing features available in this finance example."""

return "\n".join(
(
"Finance example features:",
"- ask/plan/trade tool modes via PrepareToolsBridge",
"- structured native plan generation in plan mode",
"- approval-gated note writes with ACP file diffs",
"- persisted plan snapshots under .acpkit/plans/",
)
),
)


@agent.tool_plain(name=_WATCHLIST_TOOL)
def list_watchlist() -> str:
"""List the seeded watchlist and workspace note files."""

finance_root = _finance_root(Path.cwd())
return "\n\n".join(
(
"Seeded symbols:\n" + "\n".join(f"- {symbol}" for symbol in sorted(_QUOTE_BOOK)),
"Workspace files:\n" + _list_market_files(finance_root),
)
),
)


@agent.tool_plain(name=_QUOTE_TOOL)
def quote_symbol(symbol: str) -> str:
"""Return a deterministic demo quote for a watchlist symbol."""

normalized_symbol = symbol.strip().upper()
try:
return _QUOTE_BOOK[normalized_symbol]
Expand All @@ -210,14 +207,12 @@ def quote_symbol(symbol: str) -> str:
@agent.tool_plain(name=_READ_NOTE_TOOL)
def read_market_note(path: str, max_chars: int = _READ_PREVIEW_CHARS) -> str:
"""Read a finance workspace note relative to the current working directory."""

return _read_market_note(_finance_root(Path.cwd()), path, max_chars=max_chars)


@agent.tool_plain(name=_WRITE_NOTE_TOOL, requires_approval=True)
def save_market_note(path: str, content: str) -> str:
"""Write a finance note inside the local workspace."""

return _save_market_note(_finance_root(Path.cwd()), path, content)


Expand Down Expand Up @@ -258,7 +253,7 @@ def save_market_note(path: str, content: str) -> str:
FileSystemProjectionMap(
default_read_tool=_READ_NOTE_TOOL,
default_write_tool=_WRITE_NOTE_TOOL,
)
),
],
)
acp_agent = create_acp_agent(agent=agent, config=config)
Expand Down
3 changes: 1 addition & 2 deletions examples/pydantic/mock_harness_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def _harness_model(*, instructions: str = _INSTRUCTIONS) -> str | Model[Any]:
except ModuleNotFoundError as exc:
raise ModuleNotFoundError(
"Install `codex-auth-helper`, unset `ACP_HARNESS_CODEX_MODEL`, or set "
"`ACP_HARNESS_MODEL` to a pydantic-ai model name before running the harness example."
"`ACP_HARNESS_MODEL` to a pydantic-ai model name before running the harness example.",
) from exc
return create_codex_responses_model(
configured_codex_model or _DEFAULT_CODEX_MODEL,
Expand All @@ -130,7 +130,6 @@ def _build_config(*, include_code_mode: bool) -> AdapterConfig:

def _build_agent(session: AcpSessionContext, *, include_code_mode: bool) -> Agent[None, str]:
"""Build a real model-backed agent wired with pydantic-ai-harness capabilities."""

_ensure_workspace()
harness_bridges = _build_harness_bridges(
_WORKSPACE_ROOT,
Expand Down
11 changes: 4 additions & 7 deletions examples/pydantic/travel_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ def _prompt_has_binary_media(prompt: Sequence[AgentPromptBlock]) -> bool:
return True
continue
if isinstance(block, EmbeddedResourceContentBlock) and isinstance(
block.resource, BlobResourceContents
block.resource,
BlobResourceContents,
):
return True
return False
Expand Down Expand Up @@ -204,29 +205,26 @@ async def observe_write_result(
@agent.tool_plain
def describe_travel_surface() -> str:
"""Summarize the ACP-facing surfaces available in this travel example."""

return "\n".join(
(
"Travel example features:",
"- existing Hooks capability introspection rendered through HookProjectionMap",
"- approval-gated file writes with ACP diffs",
"- prompt-model override provider for image and audio prompts",
)
),
)


@agent.tool_plain
def list_trip_files() -> str:
"""List the demo travel files available in the local workspace."""

_ensure_travel_workspace()
return "\n".join(sorted(path.name for path in _TRAVEL_ROOT.iterdir() if path.is_file()))


@agent.tool_plain(name=_READ_TOOL)
def read_trip_file(path: str, max_chars: int = 4000) -> str:
"""Read a travel file and return a bounded preview."""

if max_chars <= 0:
raise ValueError("max_chars must be positive.")
_ensure_travel_workspace()
Expand All @@ -239,7 +237,6 @@ def read_trip_file(path: str, max_chars: int = 4000) -> str:
@agent.tool_plain(name=_WRITE_TOOL, requires_approval=True)
def write_trip_file(path: str, content: str) -> str:
"""Write a travel file inside the local demo workspace."""

_ensure_travel_workspace()
file_path = _resolve_trip_path(Path(path).name if Path(path).is_absolute() else path)
file_path.write_text(content, encoding="utf-8")
Expand All @@ -261,7 +258,7 @@ def write_trip_file(path: str, content: str) -> str:
FileSystemProjectionMap(
default_read_tool=_READ_TOOL,
default_write_tool=_WRITE_TOOL,
)
),
],
)
acp_agent = create_acp_agent(agent=agent, config=config)
Expand Down
Loading