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
41 changes: 41 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,47 @@ are available as `TerminalAuthMethod`, replacing the incorrect `EnvVarAuthMethod
name. Accepted elicitation content validates scalar values and string lists;
nested objects are not valid form values.

## Additional v1 protocol methods

The SDK exposes all agent and client methods in the bundled `schema-v1.23.0`
method tables. `session/delete` and `logout` are stable and do not require
`use_unstable_protocol=True`. The other methods in the table below are unstable;
enable that flag on the receiving connection (or `run_agent`) to route them.
Check the peer's advertised capabilities before calling these methods.

| Wire method | Python method | Receiver |
| --- | --- | --- |
| `session/delete` | `delete_session(session_id=...)` | Agent |
| `providers/list`, `providers/set`, `providers/disable` | `list_providers`, `set_provider`, `disable_provider` | Agent |
| `logout` | `logout()` | Agent |
| `mcp/connect`, `mcp/disconnect` | `connect_mcp`, `disconnect_mcp` | Client |
| `mcp/message` (request) | `mcp_message` | Either peer |
| `mcp/message` (notification) | `notify_mcp` | Either peer |
| `nes/start`, `nes/suggest`, `nes/close` | `start_nes`, `suggest_nes`, `close_nes` | Agent |
| `nes/accept`, `nes/reject` | `accept_nes`, `reject_nes` | Agent |
| `document/didOpen`, `document/didChange`, `document/didClose`, `document/didSave`, `document/didFocus` | `did_open`, `did_change`, `did_close`, `did_save`, `did_focus` | Agent |

For example, an agent that advertises session deletion implements:

```python
from acp import DeleteSessionResponse

async def delete_session(self, session_id: str, **kwargs) -> DeleteSessionResponse:
await self.session_store.delete(session_id)
return DeleteSessionResponse()
```

A client then calls `await connection.delete_session(session_id=session_id)`.
Deletion removes stored session data; `close_session` only closes the active
session. The SDK dispatches these calls to your implementation; it does not
provide session storage or provider management itself. Missing request handlers
return the JSON-RPC method-not-found error.

MCP requests return the inner JSON result unchanged, including `null`. Use
`notify_mcp` for one-way MCP messages. Both APIs accept `connection_id`, `method`,
and optional `params`. These methods share the same connections and routers
across stdio, HTTP, and WebSocket transports.

## Optional — Talk to the Gemini CLI

_Have the Gemini CLI installed? Run the bridge to exercise permission flows._
Expand Down
19 changes: 15 additions & 4 deletions scripts/gen_signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,8 @@ def _format_annotation(self, annotation: t.Any) -> ast.expr:
for argument in rest:
formatted = ast.BinOp(left=formatted, op=ast.BitOr(), right=self._format_annotation(argument))
return formatted
if origin is t.Literal and annotation in self._literals.values():
name = next(name for name, value in self._literals.items() if value is annotation)
self._add_schema_import(name)
return ast.Name(id=name)
if origin is t.Literal:
return self._format_literal(annotation)
elif (
inspect.isclass(annotation)
and issubclass(annotation, BaseModel)
Expand Down Expand Up @@ -166,6 +164,19 @@ def _format_annotation(self, annotation: t.Any) -> ast.expr:
self._add_typing_import("Any")
return ast.Name(id="Any")

def _format_literal(self, annotation: t.Any) -> ast.expr:
if annotation in self._literals.values():
name = next(name for name, value in self._literals.items() if value is annotation)
self._add_schema_import(name)
return ast.Name(id=name)
self._add_typing_import("Literal")
values = [ast.Constant(value=value) for value in t.get_args(annotation)]
return ast.Subscript(
value=ast.Name(id="Literal"),
slice=values[0] if len(values) == 1 else ast.Tuple(elts=values, ctx=ast.Load()),
ctx=ast.Load(),
)


def gen_signature(source_dir: Path) -> None:
global schema
Expand Down
58 changes: 58 additions & 0 deletions src/acp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
)
from .schema import (
AcceptElicitationResponse,
AcceptNesNotification,
AuthenticateRequest,
AuthenticateResponse,
CancelElicitationResponse,
CancelNotification,
CloseNesRequest,
CloseNesResponse,
CompleteElicitationNotification,
ConnectMcpRequest,
ConnectMcpResponse,
CreateElicitationRequest,
CreateElicitationResponse,
CreateFormElicitationRequest,
Expand All @@ -31,6 +36,17 @@
CreateUrlRequestElicitationRequest,
CreateUrlSessionElicitationRequest,
DeclineElicitationResponse,
DeleteSessionRequest,
DeleteSessionResponse,
DidChangeDocumentNotification,
DidCloseDocumentNotification,
DidFocusDocumentNotification,
DidOpenDocumentNotification,
DidSaveDocumentNotification,
DisableProviderRequest,
DisableProviderResponse,
DisconnectMcpRequest,
DisconnectMcpResponse,
ElicitationBooleanPropertySchema,
ElicitationCapabilities,
ElicitationFormCapabilities,
Expand All @@ -50,24 +66,37 @@
InitializeResponse,
KillTerminalRequest,
KillTerminalResponse,
ListProvidersRequest,
ListProvidersResponse,
LoadSessionRequest,
LoadSessionResponse,
LogoutRequest,
LogoutResponse,
MessageMcpNotification,
MessageMcpRequest,
NewSessionRequest,
NewSessionResponse,
OtherElicitationResponse,
PromptRequest,
PromptResponse,
ReadTextFileRequest,
ReadTextFileResponse,
RejectNesNotification,
ReleaseTerminalRequest,
ReleaseTerminalResponse,
RequestPermissionRequest,
RequestPermissionResponse,
SessionNotification,
SetProviderRequest,
SetProviderResponse,
SetSessionConfigOptionResponse,
SetSessionConfigOptionSelectRequest,
SetSessionModeRequest,
SetSessionModeResponse,
StartNesRequest,
StartNesResponse,
SuggestNesRequest,
SuggestNesResponse,
TerminalOutputRequest,
TerminalOutputResponse,
WaitForTerminalExitRequest,
Expand Down Expand Up @@ -97,6 +126,35 @@
"AGENT_METHODS",
"CLIENT_METHODS",
# types
"AcceptNesNotification",
"CloseNesRequest",
"CloseNesResponse",
"ConnectMcpRequest",
"ConnectMcpResponse",
"DeleteSessionRequest",
"DeleteSessionResponse",
"DidChangeDocumentNotification",
"DidCloseDocumentNotification",
"DidFocusDocumentNotification",
"DidOpenDocumentNotification",
"DidSaveDocumentNotification",
"DisableProviderRequest",
"DisableProviderResponse",
"DisconnectMcpRequest",
"DisconnectMcpResponse",
"ListProvidersRequest",
"ListProvidersResponse",
"LogoutRequest",
"LogoutResponse",
"MessageMcpNotification",
"MessageMcpRequest",
"RejectNesNotification",
"SetProviderRequest",
"SetProviderResponse",
"StartNesRequest",
"StartNesResponse",
"SuggestNesRequest",
"SuggestNesResponse",
"InitializeRequest",
"InitializeResponse",
"NewSessionRequest",
Expand Down
57 changes: 56 additions & 1 deletion src/acp/agent/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
CancelElicitationResponse,
CompleteElicitationNotification,
ConfigOptionUpdate,
ConnectMcpRequest,
ConnectMcpResponse,
CreateElicitationResponse,
CreateFormElicitationRequest,
CreateFormRequestElicitationRequest,
Expand All @@ -32,6 +34,8 @@
CreateUrlSessionElicitationRequest,
CurrentModeUpdate,
DeclineElicitationResponse,
DisconnectMcpRequest,
DisconnectMcpResponse,
ElicitationFormRequestMode,
ElicitationFormSessionMode,
ElicitationMode,
Expand All @@ -40,6 +44,8 @@
EnvVariable,
KillTerminalRequest,
KillTerminalResponse,
MessageMcpNotification,
MessageMcpRequest,
PermissionOption,
ReadTextFileRequest,
ReadTextFileResponse,
Expand All @@ -64,7 +70,15 @@
WriteTextFileRequest,
WriteTextFileResponse,
)
from ..utils import compatible_class, notify_model, param_model, request_model, request_optional_model, serialize_params
from ..utils import (
compatible_class,
notify_model,
param_model,
request_model,
request_model_from_dict,
request_optional_model,
serialize_params,
)
from .router import build_agent_router

__all__ = ["AgentSideConnection"]
Expand Down Expand Up @@ -278,6 +292,47 @@ async def complete_elicitation(self, elicitation_id: str, **kwargs: Any) -> None
CompleteElicitationNotification(elicitation_id=elicitation_id, field_meta=kwargs or None),
)

@param_model(ConnectMcpRequest)
async def connect_mcp(self, server_id: str, **kwargs: Any) -> ConnectMcpResponse:
return await request_model(
self._conn,
CLIENT_METHODS["mcp_connect"],
ConnectMcpRequest(server_id=server_id, field_meta=kwargs or None),
ConnectMcpResponse,
)

@param_model(DisconnectMcpRequest)
async def disconnect_mcp(self, connection_id: str, **kwargs: Any) -> DisconnectMcpResponse:
return await request_model_from_dict(
self._conn,
CLIENT_METHODS["mcp_disconnect"],
DisconnectMcpRequest(connection_id=connection_id, field_meta=kwargs or None),
DisconnectMcpResponse,
)

@param_model(MessageMcpRequest)
async def mcp_message(
self, connection_id: str, method: str, params: dict[str, Any] | None = None, **kwargs: Any
) -> Any:
return await self._conn.send_request(
CLIENT_METHODS["mcp_message"],
serialize_params(
MessageMcpRequest(connection_id=connection_id, method=method, params=params, field_meta=kwargs or None)
),
)

@param_model(MessageMcpNotification)
async def notify_mcp(
self, connection_id: str, method: str, params: dict[str, Any] | None = None, **kwargs: Any
) -> None:
await notify_model(
self._conn,
CLIENT_METHODS["mcp_message"],
MessageMcpNotification(
connection_id=connection_id, method=method, params=params, field_meta=kwargs or None
),
)

async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
return await self._conn.send_request(f"_{method}", params)

Expand Down
67 changes: 67 additions & 0 deletions src/acp/agent/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,36 @@
from ..meta import AGENT_METHODS
from ..router import MessageRouter, Route, _resolve_handler, _warn_legacy_handler
from ..schema import (
AcceptNesNotification,
AuthenticateRequest,
CancelNotification,
CloseNesRequest,
CloseSessionRequest,
DeleteSessionRequest,
DidChangeDocumentNotification,
DidCloseDocumentNotification,
DidFocusDocumentNotification,
DidOpenDocumentNotification,
DidSaveDocumentNotification,
DisableProviderRequest,
ForkSessionRequest,
InitializeRequest,
ListProvidersRequest,
ListSessionsRequest,
LoadSessionRequest,
LogoutRequest,
MessageMcpNotification,
MessageMcpRequest,
NewSessionRequest,
PromptRequest,
RejectNesNotification,
ResumeSessionRequest,
SetProviderRequest,
SetSessionConfigOptionBooleanRequest,
SetSessionConfigOptionSelectRequest,
SetSessionModeRequest,
StartNesRequest,
SuggestNesRequest,
)
from ..utils import model_to_kwargs, normalize_result

Expand Down Expand Up @@ -102,6 +119,56 @@ def build_agent_router(agent: Agent, use_unstable_protocol: bool = False) -> Mes

router.route_notification(AGENT_METHODS["session_cancel"], CancelNotification, agent, "cancel")

router.route_request(
AGENT_METHODS["session_delete"],
DeleteSessionRequest,
agent,
"delete_session",
adapt_result=normalize_result,
)
router.route_request(AGENT_METHODS["providers_list"], ListProvidersRequest, agent, "list_providers", unstable=True)
router.route_request(
AGENT_METHODS["providers_set"],
SetProviderRequest,
agent,
"set_provider",
unstable=True,
adapt_result=normalize_result,
)
router.route_request(
AGENT_METHODS["providers_disable"],
DisableProviderRequest,
agent,
"disable_provider",
unstable=True,
adapt_result=normalize_result,
)
router.route_request(AGENT_METHODS["logout"], LogoutRequest, agent, "logout", adapt_result=normalize_result)
router.route_request(AGENT_METHODS["mcp_message"], MessageMcpRequest, agent, "mcp_message", unstable=True)
router.route_notification(AGENT_METHODS["mcp_message"], MessageMcpNotification, agent, "notify_mcp", unstable=True)
router.route_request(AGENT_METHODS["nes_start"], StartNesRequest, agent, "start_nes", unstable=True)
router.route_request(AGENT_METHODS["nes_suggest"], SuggestNesRequest, agent, "suggest_nes", unstable=True)
router.route_request(
AGENT_METHODS["nes_close"], CloseNesRequest, agent, "close_nes", unstable=True, adapt_result=normalize_result
)
router.route_notification(AGENT_METHODS["nes_accept"], AcceptNesNotification, agent, "accept_nes", unstable=True)
router.route_notification(AGENT_METHODS["nes_reject"], RejectNesNotification, agent, "reject_nes", unstable=True)
router.route_notification(
AGENT_METHODS["document_did_open"], DidOpenDocumentNotification, agent, "did_open", unstable=True
)
router.route_notification(
AGENT_METHODS["document_did_change"], DidChangeDocumentNotification, agent, "did_change", unstable=True
)
router.route_notification(
AGENT_METHODS["document_did_close"], DidCloseDocumentNotification, agent, "did_close", unstable=True
)
router.route_notification(
AGENT_METHODS["document_did_save"], DidSaveDocumentNotification, agent, "did_save", unstable=True
)
router.route_notification(
AGENT_METHODS["document_did_focus"], DidFocusDocumentNotification, agent, "did_focus", unstable=True
)

@router.handle_extension_request
async def _handle_extension_request(name: str, payload: dict[str, Any]) -> Any:
ext = getattr(agent, "ext_method", None)
Expand Down
Loading
Loading