Documentation
¶
Overview ¶
Package acp provides an Agent Client Protocol (ACP) engine for agentrun.
Unlike CLI backends that parse per-tool stdout formats, ACP uses JSON-RPC 2.0 over stdin/stdout with a persistent subprocess. The subprocess stays alive across turns — MCP servers boot once, subsequent turns are instant.
This implementation targets ACP spec v0.10.8 (protocol version 1).
ACP is a standardized protocol supported by OpenCode, Goose, OpenHands, and other agent runtimes. One engine handles all ACP-speaking agents, parameterized by binary name.
engine := acp.NewEngine(acp.WithBinary("opencode"), acp.WithArgs("acp"))
proc, err := engine.Start(ctx, session)
update.go maps ACP session/update notifications to agentrun.Message values.
ACP session/update notifications arrive as a two-level envelope:
outer: {"sessionId":"...", "update": <inner>}
inner: {"sessionUpdate":"agent_message_chunk", "content":{...}}
makeUpdateHandler (in process.go) unpacks the outer sessionNotification, then calls parseSessionUpdate(inner) which dispatches on the "sessionUpdate" discriminator field via the updateParsers map.
Adding a new update type = one map entry + one function. Delta types that need accumulation also add a case in turnAccumulator.observe().
Index ¶
- Constants
- type Conn
- func (c *Conn) Call(ctx context.Context, method string, params, result any) error
- func (c *Conn) Done() <-chan struct{}
- func (c *Conn) Err() error
- func (c *Conn) Notify(method string, params any) error
- func (c *Conn) OnMethod(method string, h func(json.RawMessage) (any, error))
- func (c *Conn) OnNotification(method string, h func(json.RawMessage))
- func (c *Conn) ReadLoop()
- type Engine
- type EngineOption
- func WithArgs(args ...string) EngineOption
- func WithBinary(binary string) EngineOption
- func WithGracePeriod(d time.Duration) EngineOption
- func WithHandshakeTimeout(d time.Duration) EngineOption
- func WithMaxMessageSize(size int) EngineOption
- func WithOutputBuffer(size int) EngineOption
- func WithPermissionHandler(h PermissionHandler) EngineOption
- func WithPermissionTimeout(d time.Duration) EngineOption
- func WithStderrWriter(w io.Writer) EngineOption
- type EngineOptions
- type PermissionHandler
- type PermissionRequest
- type RPCError
Constants ¶
const ( MethodInitialize = "initialize" MethodSessionNew = "session/new" MethodSessionLoad = "session/load" MethodSessionPrompt = "session/prompt" MethodSessionUpdate = "session/update" MethodSessionCancel = "session/cancel" MethodSessionSetMode = "session/set_mode" MethodSessionSetConfig = "session/set_config_option" MethodRequestPerm = "session/request_permission" MethodShutdown = "shutdown" )
JSON-RPC 2.0 method constants for the Agent Client Protocol.
const ErrCodeToolCallFailed = "tool_call_failed"
ErrCodeToolCallFailed is the ErrorCode for failed tool calls. Library-defined (not from ACP wire format) — the ACP protocol has no structured error code on tool_call_update failures.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a bidirectional JSON-RPC 2.0 multiplexer over newline-delimited JSON.
Conn serializes outbound messages (Call, Notify) via a mutex-protected encoder and dispatches inbound messages (responses, notifications, method calls) in ReadLoop. All handlers must be registered before ReadLoop starts.
The synchronization model uses sync.Mutex + map[int64]chan for pending calls. On ReadLoop exit, all pending channels receive an error — preventing goroutine leaks.
func (*Conn) Call ¶
Call sends a JSON-RPC request and blocks until the response arrives or ctx expires.
func (*Conn) Done ¶
func (c *Conn) Done() <-chan struct{}
Done returns a channel that is closed when ReadLoop exits.
func (*Conn) Err ¶
Err returns the ReadLoop error after it exits. Returns nil if ReadLoop hasn't finished or exited cleanly (reader closed with no scanner error).
func (*Conn) OnMethod ¶
OnMethod registers a handler for JSON-RPC method calls (has id field, expects response). The handler runs in a dedicated goroutine to avoid blocking ReadLoop. Must be called before ReadLoop starts.
func (*Conn) OnNotification ¶
func (c *Conn) OnNotification(method string, h func(json.RawMessage))
OnNotification registers a handler for JSON-RPC notifications (no id field). Must be called before ReadLoop starts.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is an ACP engine that communicates with agents via JSON-RPC 2.0 over a persistent subprocess's stdin/stdout.
func NewEngine ¶
func NewEngine(opts ...EngineOption) *Engine
NewEngine creates an ACP engine. Use EngineOption functions to customize the binary, arguments, buffer sizes, and permission handling.
type EngineOption ¶
type EngineOption func(*EngineOptions)
EngineOption configures an Engine at construction time.
func WithArgs ¶
func WithArgs(args ...string) EngineOption
WithArgs sets additional arguments passed to the binary.
func WithBinary ¶
func WithBinary(binary string) EngineOption
WithBinary sets the ACP agent executable name or path.
func WithGracePeriod ¶
func WithGracePeriod(d time.Duration) EngineOption
WithGracePeriod sets the duration to wait after SIGTERM before sending SIGKILL. Values <= 0 are ignored.
func WithHandshakeTimeout ¶
func WithHandshakeTimeout(d time.Duration) EngineOption
WithHandshakeTimeout sets the deadline for the initialize + session handshake. Values <= 0 are ignored.
func WithMaxMessageSize ¶ added in v0.3.0
func WithMaxMessageSize(size int) EngineOption
WithMaxMessageSize sets the maximum JSON-RPC message size in bytes. The default is 4 MB. Zero or negative means unlimited.
func WithOutputBuffer ¶
func WithOutputBuffer(size int) EngineOption
WithOutputBuffer sets the channel buffer size for process output messages. Values <= 0 are ignored.
func WithPermissionHandler ¶
func WithPermissionHandler(h PermissionHandler) EngineOption
WithPermissionHandler sets the callback for agent permission requests.
func WithPermissionTimeout ¶
func WithPermissionTimeout(d time.Duration) EngineOption
WithPermissionTimeout sets the deadline for the permission handler callback. Values <= 0 are ignored.
func WithStderrWriter ¶ added in v0.4.0
func WithStderrWriter(w io.Writer) EngineOption
WithStderrWriter sets a writer to receive subprocess stderr output. The writer's lifetime must span the entire engine lifecycle. Nil is a no-op (stderr is discarded via os.DevNull).
type EngineOptions ¶
type EngineOptions struct {
// Binary is the ACP agent executable name or path.
Binary string
// Args are additional arguments passed to the binary (e.g., ["acp"]).
Args []string
// OutputBuffer is the channel buffer size for process output messages.
OutputBuffer int
// GracePeriod is the duration to wait after SIGTERM before sending SIGKILL.
GracePeriod time.Duration
// HandshakeTimeout is the deadline for initialize + session/new during Start().
HandshakeTimeout time.Duration
// MaxMessageSize is the maximum JSON-RPC message size in bytes for the scanner.
MaxMessageSize int
// PermissionTimeout is the deadline for the PermissionHandler callback.
PermissionTimeout time.Duration
// PermissionHandler is called when the agent requests client-side permission.
PermissionHandler PermissionHandler
// StderrWriter receives subprocess stderr output when non-nil.
// The writer's lifetime must span the entire engine lifecycle
// (ACP uses a single persistent subprocess). When nil, subprocess
// stderr is discarded (connected to os.DevNull by exec.Cmd).
//
// Concurrency: the subprocess stderr goroutine writes to this writer
// concurrently with engine operations. Implementations must be safe
// for concurrent use if the caller reads or resets the writer between
// turns.
StderrWriter io.Writer
}
EngineOptions holds resolved construction-time configuration for an ACP engine.
type PermissionHandler ¶
type PermissionHandler func(ctx context.Context, req PermissionRequest) (approved bool, err error)
PermissionHandler is called when the agent requests client-side permission. Runs in a dedicated goroutine (not blocking ReadLoop). Return true to approve. The engine maps the boolean to the ACP option-based wire format internally: true → allow_once (prefer) or allow_always; false → reject_once or reject_always.
This collapses ACP's richer option-based outcomes to binary approve/deny. A future version may expose the full option set via PermissionRequest if consumers need to distinguish allow_once from allow_always. If nil, permission requests are auto-denied (unless HITL is off).
type PermissionRequest ¶
type PermissionRequest struct {
SessionID string
ToolName string // from toolCallUpdate.Title
ToolCallID string // from toolCallUpdate.ToolCallID
Description string // from toolCallUpdate.Kind
}
PermissionRequest carries the agent's permission request to the handler. The handler returns a simple approve/deny decision; the engine maps this to the ACP option-based wire format internally.