Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .agents/skills/pydantic-acp/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,20 @@ agent = Agent(model)
Do not pass arbitrary CLI commands here; the process must be an ACP server. Each command-backed
model owns one child ACP process and should be reused for repeated subagent calls.

`create_acp_model(...)` and `AcpProvider(...)` recover from an ACP
`auth_required` response by authenticating with the first advertised
`AuthMethodAgent` and retrying `session/new` once. Use `auth_method_id=...` only
when a specific method has already been prepared. `EnvVarAuthMethod` and
`TerminalAuthMethod` require host-owned credential injection or terminal
execution and must not be selected automatically.

Use `AcpProvider.ensure_session()` to initialize and create a session without a
prompt. Use `AcpProvider.set_session_mode(...)` to bootstrap that session and
select a mode before the first model turn. Set `raise_on_empty_turn=True` on the
provider or factory when a silent text turn must raise
`UnexpectedModelBehavior`; the backward-compatible default returns an empty
response.

Use `AcpProvider(acp_agent=...)` directly only when provider ownership, host delegation, or lower
level lifecycle control matters.

Expand Down Expand Up @@ -464,6 +478,11 @@ Stay in this skill when the main issue is:
`plan`; the runtime must fall back to full updates otherwise.
- Accept and persist `AcpMcpServer` session payloads, but do not advertise ACP
MCP transport capability until the SDK exposes a public router.
- Automatically authenticate only with `AuthMethodAgent`. Environment-variable
and terminal auth methods require client-owned setup before `authenticate`
can be called.
- Unwrap only single-child groups whose message identifies an anyio TaskGroup;
preserve unrelated exception groups and cancellation.

- Do not describe `pydantic-acp` as transport.
- Do not promise ACP state the active `pydantic_ai.Agent` cannot honor.
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ ACP Kit uses synchronized versions for `acpkit`, `pydantic-acp`, `langchain-acp`

## [Unreleased]

## [1.5.1] - 2026-07-24

### Added

- `pydantic-acp`'s `AcpProvider` recovers from an `auth_required` (`-32000`)
rejection of `session/new` by running the ACP `authenticate` flow — using an
advertised agent-managed method or an explicit `auth_method_id=` — and
retrying session creation once. Environment-variable and terminal methods
remain client-owned because they require credential injection or an
interactive process.
- Public `AcpProvider.ensure_session()` and `AcpProvider.set_session_mode()`
bootstrap a session and select a session mode without sending a prompt turn,
so callers no longer reach into the private `_ensure_session`.
- Opt-in `AcpProvider(raise_on_empty_turn=True)` raises
`UnexpectedModelBehavior` with an ACP-specific diagnostic when a text-output
turn yields no visible text. The default remains `False`, preserving the
empty-response contract.

### Fixed

- `pydantic-acp`'s `request_prompt` propagates the ACP agent's real error
(rate limit, auth rejection, upstream API failure) by unwrapping single-child
anyio TaskGroup exception wrappers and dropping matching TaskGroup
`__context__` noise, while preserving unrelated aggregate errors and
cancellation.

## [1.5.0] - 2026-07-24

### Changed
Expand Down
4 changes: 2 additions & 2 deletions COVERAGE
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Line coverage: 100.00% (10329 / 10329)
Branch coverage: 100.00% (3434 / 3434)
Line coverage: 100.00% (10398 / 10398)
Branch coverage: 100.00% (3456 / 3456)
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,17 @@ model = create_acp_model(
acp_command=("npx", "@zed-industries/codex-acp"),
cwd="/workspace",
stderr_mode="inherit",
raise_on_empty_turn=True,
)
agent = Agent(model)
```

When `session/new` reports `auth_required`, the provider authenticates with the
first agent-managed method advertised by the ACP agent and retries once. Pass
`auth_method_id="..."` to `create_acp_model(...)` or `AcpProvider(...)` to
select a specific prepared method. Environment-variable and terminal methods
still require their client-side credential or terminal setup.

For lower-level ownership, construct the provider directly:

```python
Expand All @@ -315,6 +322,8 @@ from pydantic_acp import AcpProvider

# `remote_acp_agent` can be any object implementing the ACP Agent interface.
provider = AcpProvider(acp_agent=remote_acp_agent, cwd="/workspace")
session_id = await provider.ensure_session()
await provider.set_session_mode("review")
model = provider.model()
agent = Agent(model)

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.0
1.5.1
6 changes: 4 additions & 2 deletions docs/compatibility-matrix-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ manifest = CompatibilityManifest(
rationale='The runtime keeps the hook seam but does not expose it to ACP clients.',
),
'authenticate': SurfaceSupport(
status='planned',
rationale='No auth handshake has been added yet.',
status='implemented',
owner='provider',
mapping='AcpProvider auth_required recovery',
rationale='Agent-managed methods are automatic; env and terminal setup stays client-owned.',
),
},
)
Expand Down
32 changes: 32 additions & 0 deletions docs/pydantic-acp.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ model = create_acp_model(
acp_command=("npx", "@zed-industries/codex-acp"),
cwd="/workspace",
stderr_mode="inherit",
raise_on_empty_turn=True,
)
agent = Agent(model)
```
Expand All @@ -112,6 +113,37 @@ ACP agent exposes a selectable `"model"` `session/set_config_option` option.
`AcpProvider` and
`AcpModel` remain available when lower-level provider ownership is needed.

For command-backed and in-process ACP agents, session setup is explicit and
does not require a dummy prompt:

```python
from acp.interfaces import Agent as AcpAgent
from pydantic_acp import AcpProvider


async def prepare_provider(acp_agent: AcpAgent) -> AcpProvider:
provider = AcpProvider(
acp_agent=acp_agent,
cwd="/workspace",
raise_on_empty_turn=True,
)
await provider.ensure_session()
await provider.set_session_mode("review")
return provider
```

If `session/new` fails with ACP `auth_required`, `AcpProvider` calls
`authenticate` with the first advertised `AuthMethodAgent` and retries session
creation once. `auth_method_id="..."` selects a specific method when the host
has prepared it. `EnvVarAuthMethod` and `TerminalAuthMethod` require
client-owned credential injection or terminal execution, so the provider does
not select them automatically.

Agent errors are propagated without single-child anyio TaskGroup wrapping.
`raise_on_empty_turn=True` additionally turns a text request that produces no
visible agent text into `UnexpectedModelBehavior`; the default remains `False`
for backward compatibility.

The bridge keeps ownership explicit:

- Pydantic AI owns the outer run, output validation, and normal provider
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/langchain-acp/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.0
1.5.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__all__ = ("__version__",)

__version__ = "1.5.0"
__version__ = "1.5.1"
12 changes: 12 additions & 0 deletions packages/adapters/pydantic-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,19 @@ model = create_acp_model(
acp_command=("npx", "@zed-industries/codex-acp"),
cwd="/workspace",
stderr_mode="inherit",
raise_on_empty_turn=True,
)
agent = Agent(model)
```

`raise_on_empty_turn=True` converts a silent text turn into an ACP-specific
`UnexpectedModelBehavior` instead of returning an empty response. When
`session/new` reports `auth_required`, the provider calls `authenticate` with
the first advertised agent-managed method and retries session creation once.
Use `auth_method_id="..."` to select a specific method that has already been
prepared by the host. Environment-variable and terminal auth methods require
client-side credential or terminal setup and are not selected automatically.

For lower-level ownership, construct the provider directly:

```python
Expand All @@ -133,6 +142,8 @@ from pydantic_acp import AcpProvider

# `remote_acp_agent` can be any object implementing the ACP Agent interface.
provider = AcpProvider(acp_agent=remote_acp_agent, cwd="/workspace")
session_id = await provider.ensure_session()
await provider.set_session_mode("review")
model = provider.model()
agent = Agent(model)

Expand All @@ -144,6 +155,7 @@ 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.
- `ensure_session()` initializes and creates the ACP session without consuming a prompt turn; `set_session_mode(...)` uses that same session.
- `create_acp_model(...)` and `provider.model()` leave ACP model selection to the wrapped agent's session default; pass `model_name="zed-agent"` or `provider.model("zed-agent")` only when the ACP agent exposes a selectable `"model"` `session/set_config_option` option.
- `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.
Expand Down
2 changes: 1 addition & 1 deletion packages/adapters/pydantic-acp/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.0
1.5.1
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

__all__ = ("__version__",)

__version__ = "1.5.0"
__version__ = "1.5.1"
Loading
Loading