Skip to content

ant_ai.acp.commands

ACPCommandContext dataclass

Everything a code-dispatched command handler may need about the live session.

history is the same list the adapter keeps for the session, so a handler that wants to change the transcript (e.g. /compact) mutates it in place::

ctx.history[:] = [Message(role="user", content=summary)]

replace_agent swaps the agent used for the rest of the session (e.g. /skill installs a skill by handing back a freshly built agent).

Source code in src/ant_ai/acp/commands.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@dataclass
class ACPCommandContext:
    """Everything a code-dispatched command handler may need about the live session.

    ``history`` is the *same* list the adapter keeps for the session, so a handler
    that wants to change the transcript (e.g. ``/compact``) mutates it in place::

        ctx.history[:] = [Message(role="user", content=summary)]

    ``replace_agent`` swaps the agent used for the rest of the session (e.g.
    ``/skill`` installs a skill by handing back a freshly built agent).
    """

    args: str
    session_id: str
    client: Client | None
    capabilities: ClientCapabilities | None
    cwd: str | None
    history: list[Message]
    agent: Agent
    replace_agent: Callable[[Agent], None]

ACPCommand dataclass

A slash command advertised to the ACP client, plus how it is handled.

  • kind="code": handler runs against an :class:ACPCommandContext; no model turn happens. Use for concrete operations (/compact, /skill).
  • kind="prompt": the command expands via template ("{args}" is replaced with the text after the command) and the normal workflow runs. With template=None this is just "advertise it and pass the raw text to the agent" -- the pre-existing behaviour.
Source code in src/ant_ai/acp/commands.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@dataclass(frozen=True)
class ACPCommand:
    """A slash command advertised to the ACP client, plus how it is handled.

    - ``kind="code"``: ``handler`` runs against an :class:`ACPCommandContext`; no
      model turn happens. Use for concrete operations (``/compact``, ``/skill``).
    - ``kind="prompt"``: the command expands via ``template`` (``"{args}"`` is
      replaced with the text after the command) and the normal workflow runs.
      With ``template=None`` this is just "advertise it and pass the raw text to
      the agent" -- the pre-existing behaviour.
    """

    name: str
    description: str
    input_hint: str | None = None
    kind: Literal["code", "prompt"] = "prompt"
    handler: CommandHandler | None = None
    template: str | None = None

    def __post_init__(self) -> None:
        if self.kind == "code" and self.handler is None:
            raise ValueError(
                f"ACPCommand {self.name!r}: kind='code' requires a handler"
            )

    def expand(self, args: str) -> str:
        # str.replace, not str.format: `args` may legitimately contain braces.
        return (self.template or "{args}").replace("{args}", args)

    def to_available_command(self) -> AvailableCommand:
        return AvailableCommand(
            name=self.name,
            description=self.description,
            input=AvailableCommandInput(
                root=UnstructuredCommandInput(hint=self.input_hint)
            )
            if self.input_hint
            else None,
        )

parse_slash_command

parse_slash_command(text: str) -> tuple[str | None, str]

Split a leading slash command off the prompt text.

"/skill ~/s/foo" -> ("skill", "~/s/foo"); "/compact" -> ("compact", ""); anything else (plain text, a bare path like /etc/hosts) -> (None, "").

Source code in src/ant_ai/acp/commands.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def parse_slash_command(text: str) -> tuple[str | None, str]:
    """Split a leading slash command off the prompt text.

    ``"/skill ~/s/foo"`` -> ``("skill", "~/s/foo")``; ``"/compact"`` ->
    ``("compact", "")``; anything else (plain text, a bare path like
    ``/etc/hosts``) -> ``(None, "")``.
    """
    match = _SLASH_RE.match(text.lstrip())
    if match is None:
        return None, ""
    return match.group(1), (match.group(2) or "").strip()