acp

package module
v0.0.0-...-77db8d3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: MIT Imports: 11 Imported by: 0

README

go-acp

Go SDK for the Agent Client Protocol (ACP)

  • generated ACP schema types and method constants (schema package, pinned to an upstream schema release)
  • typed, chainable handler registration for agents and clients
  • streaming session helpers (ActiveSession) with guaranteed update/response ordering
  • protocol-level cancellation ($/cancel_request) in both directions
  • stdio (newline-delimited JSON) and WebSocket transports, backed by github.com/Aqothy/jsonrpc2

HTTP and SSE transports are not included (yet).

Agent

agent := acp.Agent().
	OnInitialize(func(ctx context.Context, req acp.AgentRequest[schema.InitializeRequest]) (schema.InitializeResponse, error) {
		return schema.InitializeResponse{ProtocolVersion: schema.CurrentProtocolVersion}, nil
	}).
	OnNewSession(func(ctx context.Context, req acp.AgentRequest[schema.NewSessionRequest]) (schema.NewSessionResponse, error) {
		return schema.NewSessionResponse{SessionID: newSessionID()}, nil
	}).
	OnPrompt(func(ctx context.Context, req acp.AgentRequest[schema.PromptRequest]) (schema.PromptResponse, error) {
		// stream output back to the client
		err := req.Client.SessionUpdate(ctx, schema.SessionNotification{
			SessionID: req.Params.SessionID,
			Update: schema.SessionUpdate{
				SessionUpdate: schema.SessionUpdateAgentMessageChunk,
				Content:       schema.TextBlock("hello!"),
			},
		})
		if err != nil {
			return schema.PromptResponse{}, err
		}
		return schema.PromptResponse{StopReason: schema.StopReasonEndTurn}, nil
	}).
	OnCancel(func(ctx context.Context, req acp.AgentNotification[schema.CancelNotification]) error {
		return nil
	})

conn, err := agent.Connect(ctx, acp.Stdio())
if err != nil {
	log.Fatal(err)
}
<-conn.Done()

On* methods cover the stable ACP surface. Unstable methods (session/fork, providers/*, nes/*, document/*, elicitation/*) register through acp.RegisterAgentRequest/acp.RegisterClientRequest with any schema.AgentMethod*/schema.ClientMethod* constant — the same mechanism the On* sugar uses.

Client

client := acp.Client().
	OnRequestPermission(handlePermission).
	OnSessionUpdate(logUpdate) // optional: ActiveSession streaming works without it

conn, err := client.Connect(ctx, acp.Combine(agentStdout, agentStdin))
if err != nil {
	log.Fatal(err)
}
defer conn.Close()

if _, err := conn.Agent().Initialize(ctx, schema.InitializeRequest{ProtocolVersion: schema.CurrentProtocolVersion}); err != nil {
	log.Fatal(err)
}

err = conn.Agent().BuildSession("/path/to/project").WithSession(ctx, func(ctx context.Context, session *acp.ActiveSession) error {
	prompt := session.PromptAsync(ctx, "Summarize this repo")
	text, err := session.ReadText(ctx) // or session.NextUpdate in a loop
	if err != nil {
		return err
	}
	fmt.Println(text)
	_, err = prompt.Await(ctx)
	return err
})

Transports

  • app.Connect(ctx, rwc) — newline-delimited JSON over any io.ReadWriteCloser. acp.Stdio() serves an agent on stdin/stdout; acp.Combine(r, w) adapts a reader/writer pair (e.g. a subprocess's pipes).

  • app.ConnectWebSocket(ctx, ws) — one JSON-RPC message per text frame over anything implementing jsonrpc2.WebSocket (ReadMessage/WriteMessage/Close). Any WebSocket library adapts with a few lines; e.g. github.com/coder/websocket:

    type coderWS struct{ conn *websocket.Conn }
    
    func (w coderWS) ReadMessage(ctx context.Context) ([]byte, error) {
        typ, data, err := w.conn.Read(ctx)
        if err != nil {
            return nil, err
        }
        if typ != websocket.MessageText {
            return nil, fmt.Errorf("unexpected binary websocket frame")
        }
        return data, nil
    }
    func (w coderWS) WriteMessage(ctx context.Context, data []byte) error {
        return w.conn.Write(ctx, websocket.MessageText, data)
    }
    func (w coderWS) Close() error { return w.conn.Close(websocket.StatusNormalClosure, "") }
    
  • clientApp.ConnectAgent(ctx, agentApp) — wires both apps in process, for tests or embedding an agent in the client binary.

Update / generate schema

One command downloads the configured upstream ACP schema release and regenerates Go types and method constants:

make schema

To pin a newer release:

make schema RELEASE=schema-v1.18.0
make check

make schema-local regenerates from the checked-in files without downloading.

Documentation

Overview

Package acp provides the core Agent Client Protocol app/session helpers over JSON-RPC.

It intentionally stays transport-light: use AgentApp.Connect or ClientApp.Connect with any io.ReadWriteCloser carrying newline-delimited JSON-RPC messages (acp.Stdio for stdio agents), ConnectWebSocket with anything implementing jsonrpc2.WebSocket, or ConnectAgent for in-process tests.

Index

Constants

View Source
const (
	CodeParseError       int64 = -32700
	CodeInvalidRequest   int64 = -32600
	CodeMethodNotFound   int64 = -32601
	CodeInvalidParams    int64 = -32602
	CodeInternalError    int64 = -32603
	CodeRequestCancelled int64 = -32800
	CodeAuthentication   int64 = -32000
	CodeResourceNotFound int64 = -32002
	CodeURLElicitation   int64 = -32042
)

Variables

View Source
var ErrActiveSessionClosed = errors.New("active ACP session closed")

Functions

func AuthenticationRequiredError

func AuthenticationRequiredError(data any) error

func Combine

func Combine(r io.Reader, w io.Writer) io.ReadWriteCloser

Combine joins a reader and a writer into the io.ReadWriteCloser expected by AgentApp.Connect and ClientApp.Connect. Close closes whichever halves implement io.Closer.

func InvalidParamsError

func InvalidParamsError(err error) error

func RPCError

func RPCError(code int64, message string, data any) error

RPCError creates a JSON-RPC wire error with optional JSON-marshaled data.

func RequestCancelledError

func RequestCancelledError(data any) error

func ResourceNotFoundError

func ResourceNotFoundError(uri string) error

func Stdio

func Stdio() io.ReadWriteCloser

Stdio is the standard agent transport: JSON-RPC read from stdin, written to stdout.

Types

type ActiveSession

type ActiveSession struct {
	// contains filtered or unexported fields
}

func (*ActiveSession) Dispose

func (s *ActiveSession) Dispose()

func (*ActiveSession) NewSessionResponse

func (s *ActiveSession) NewSessionResponse() schema.NewSessionResponse

func (*ActiveSession) NextUpdate

func (s *ActiveSession) NextUpdate(ctx context.Context) (ActiveSessionMessage, error)

func (*ActiveSession) Prompt

func (s *ActiveSession) Prompt(ctx context.Context, prompt any) (schema.PromptResponse, error)

func (*ActiveSession) PromptAsync

func (s *ActiveSession) PromptAsync(ctx context.Context, prompt any) *PromptCall

func (*ActiveSession) PushMarker

func (s *ActiveSession) PushMarker(tag string)

PushMarker enqueues an application-defined marker delivered in order after every update already routed to this session. Because updates are routed synchronously on the read loop before any later message is read, pushing a marker right after a request (session/load, session/resume) resolves gives an in-stream barrier: everything the agent sent before responding is guaranteed to be dequeued before the marker.

func (*ActiveSession) ReadText

func (s *ActiveSession) ReadText(ctx context.Context) (string, error)

func (*ActiveSession) SessionID

func (s *ActiveSession) SessionID() schema.SessionId

type ActiveSessionMessage

type ActiveSessionMessage struct {
	Kind         ActiveSessionMessageKind
	Notification schema.SessionNotification
	Update       schema.SessionUpdate
	Response     schema.PromptResponse
	StopReason   schema.StopReason
	// Err is set on a Stop message whose prompt failed. A NextUpdate error, by
	// contrast, always means the stream itself terminated (disposed, connection
	// closed, or ctx done).
	Err error
	// Marker is the tag passed to PushMarker for Marker messages.
	Marker string
}

type ActiveSessionMessageKind

type ActiveSessionMessageKind string
const (
	ActiveSessionMessageUpdate ActiveSessionMessageKind = "session_update"
	ActiveSessionMessageStop   ActiveSessionMessageKind = "stop"
	ActiveSessionMessageMarker ActiveSessionMessageKind = "marker"
)

type AgentApp

type AgentApp struct {
	// contains filtered or unexported fields
}

func Agent

func Agent(options ...AppOptions) *AgentApp

Agent creates an agent app; register handlers on it and Connect it to a client.

func NewAgentApp

func NewAgentApp(options ...AppOptions) *AgentApp

func RegisterAgentNotification

func RegisterAgentNotification[P any](app *AgentApp, method schema.Method, handler AgentNotificationHandler[P]) *AgentApp

RegisterAgentNotification registers a typed handler for an agent-bound notification.

func RegisterAgentRequest

func RegisterAgentRequest[P any, R any](app *AgentApp, method schema.Method, handler AgentRequestHandler[P, R]) *AgentApp

RegisterAgentRequest registers a typed handler for an agent-bound request method. Free functions are used for registration because Go methods cannot introduce type parameters; the On* methods cover the stable ACP surface.

func (*AgentApp) Connect

func (app *AgentApp) Connect(ctx context.Context, rwc io.ReadWriteCloser) (*AgentConnection, error)

Connect serves the app over newline-delimited JSON-RPC, e.g. acp.Stdio().

func (*AgentApp) ConnectWebSocket

func (app *AgentApp) ConnectWebSocket(ctx context.Context, ws jsonrpc2.WebSocket) (*AgentConnection, error)

ConnectWebSocket serves the app over a message-oriented WebSocket, one JSON-RPC message per text frame.

func (*AgentApp) ConnectWith

func (app *AgentApp) ConnectWith(ctx context.Context, rwc io.ReadWriteCloser, op func(context.Context, *AgentConnection) error) error

ConnectWith connects over rwc, runs op, and closes the connection when op returns.

func (*AgentApp) OnCancel

func (*AgentApp) OnConnect

func (app *AgentApp) OnConnect(handler func(context.Context, *AgentConnection) error) *AgentApp

OnConnect runs handler once a connection is established, before Connect returns.

func (*AgentApp) OnInitialize

func (*AgentApp) OnLogout

func (*AgentApp) OnNewSession

func (*AgentApp) OnPrompt

type AgentConnection

type AgentConnection struct {
	// contains filtered or unexported fields
}

func (*AgentConnection) Client

func (c *AgentConnection) Client() *ClientPeer

Client is the handle for calling into the connected client.

func (AgentConnection) Close

func (c AgentConnection) Close() error

func (AgentConnection) Done

func (c AgentConnection) Done() <-chan struct{}

func (AgentConnection) Wait

func (c AgentConnection) Wait() error

type AgentNotification

type AgentNotification[P any] struct {
	Params P
	Client *ClientPeer
}

type AgentNotificationHandler

type AgentNotificationHandler[P any] func(context.Context, AgentNotification[P]) error

type AgentPeer

type AgentPeer struct {
	// contains filtered or unexported fields
}

AgentPeer is the client's handle for calling into the agent.

func (*AgentPeer) AttachSession

func (c *AgentPeer) AttachSession(id schema.SessionId) *ActiveSession

AttachSession attaches to an existing session by id, subscribing to its session/update stream. Use it for sessions the agent already knows about (session/load, session/resume): attach BEFORE issuing the load/resume call so notifications replayed while the call is in flight are queued in order.

func (*AgentPeer) Authenticate

func (*AgentPeer) BuildSession

func (c *AgentPeer) BuildSession(cwd string) *SessionBuilder

func (*AgentPeer) BuildSessionRequest

func (c *AgentPeer) BuildSessionRequest(request schema.NewSessionRequest) *SessionBuilder

func (*AgentPeer) Cancel

func (c *AgentPeer) Cancel(ctx context.Context, notification schema.CancelNotification) error

Cancel asks the agent to stop the session's current prompt turn. The agent flushes pending session/update notifications and finishes the in-flight session/prompt with StopReason "cancelled".

func (*AgentPeer) CloseSession

func (*AgentPeer) DeleteSession

func (*AgentPeer) Initialize

func (*AgentPeer) ListSessions

func (*AgentPeer) LoadSession

func (*AgentPeer) Logout

func (*AgentPeer) NewSession

func (*AgentPeer) Notify

func (c *AgentPeer) Notify(ctx context.Context, method schema.Method, params any) error

Notify sends an arbitrary agent-bound notification.

func (*AgentPeer) Prompt

func (*AgentPeer) Request

func (c *AgentPeer) Request(ctx context.Context, method schema.Method, params any, result any) error

Request sends an arbitrary agent-bound request; use it for extension or unstable methods that have no typed wrapper.

func (*AgentPeer) RequestID

func (c *AgentPeer) RequestID() (jsonrpc2.ID, bool)

RequestID returns the inbound request id when this peer handle was passed to a request handler.

func (*AgentPeer) ResumeSession

func (*AgentPeer) SetSessionMode

type AgentRequest

type AgentRequest[P any] struct {
	Params    P
	Client    *ClientPeer
	RequestID jsonrpc2.ID
}

type AgentRequestHandler

type AgentRequestHandler[P any, R any] func(context.Context, AgentRequest[P]) (R, error)

type AppOptions

type AppOptions struct {
	OnInternalError     func(error)
	OnNotificationError func(error)
}

type ClientApp

type ClientApp struct {
	// contains filtered or unexported fields
}

func Client

func Client(options ...AppOptions) *ClientApp

Client creates a client app; register handlers on it and Connect it to an agent.

func NewClientApp

func NewClientApp(options ...AppOptions) *ClientApp

func RegisterClientNotification

func RegisterClientNotification[P any](app *ClientApp, method schema.Method, handler ClientNotificationHandler[P]) *ClientApp

RegisterClientNotification registers a typed handler for a client-bound notification.

func RegisterClientRequest

func RegisterClientRequest[P any, R any](app *ClientApp, method schema.Method, handler ClientRequestHandler[P, R]) *ClientApp

RegisterClientRequest registers a typed handler for a client-bound request method.

func (*ClientApp) Connect

func (app *ClientApp) Connect(ctx context.Context, rwc io.ReadWriteCloser) (*ClientConnection, error)

Connect serves the app over newline-delimited JSON-RPC.

func (*ClientApp) ConnectAgent

func (app *ClientApp) ConnectAgent(ctx context.Context, agent *AgentApp) (*ClientConnection, *AgentConnection, error)

ConnectAgent wires the client and agent apps together in process, useful for tests and embedding an agent in the client binary.

func (*ClientApp) ConnectWebSocket

func (app *ClientApp) ConnectWebSocket(ctx context.Context, ws jsonrpc2.WebSocket) (*ClientConnection, error)

ConnectWebSocket serves the app over a message-oriented WebSocket, one JSON-RPC message per text frame.

func (*ClientApp) ConnectWith

func (app *ClientApp) ConnectWith(ctx context.Context, rwc io.ReadWriteCloser, op func(context.Context, *ClientConnection) error) error

ConnectWith connects over rwc, runs op, and closes the connection when op returns.

func (*ClientApp) ConnectWithAgent

func (app *ClientApp) ConnectWithAgent(ctx context.Context, agent *AgentApp, op func(context.Context, *AgentPeer) error) error

ConnectWithAgent runs op against an in-process agent and tears both ends down after.

func (*ClientApp) OnConnect

func (app *ClientApp) OnConnect(handler func(context.Context, *ClientConnection) error) *ClientApp

OnConnect runs handler once a connection is established, before Connect returns.

func (*ClientApp) OnSessionUpdate

type ClientConnection

type ClientConnection struct {
	// contains filtered or unexported fields
}

func (*ClientConnection) Agent

func (c *ClientConnection) Agent() *AgentPeer

Agent is the handle for calling into the connected agent.

func (ClientConnection) Close

func (c ClientConnection) Close() error

func (ClientConnection) Done

func (c ClientConnection) Done() <-chan struct{}

func (ClientConnection) Wait

func (c ClientConnection) Wait() error

type ClientNotification

type ClientNotification[P any] struct {
	Params P
	Agent  *AgentPeer
}

type ClientNotificationHandler

type ClientNotificationHandler[P any] func(context.Context, ClientNotification[P]) error

type ClientPeer

type ClientPeer struct {
	// contains filtered or unexported fields
}

ClientPeer is the agent's handle for calling back into the client.

func (*ClientPeer) CreateTerminal

func (*ClientPeer) KillTerminal

func (*ClientPeer) Notify

func (c *ClientPeer) Notify(ctx context.Context, method schema.Method, params any) error

Notify sends an arbitrary client-bound notification.

func (*ClientPeer) ReadTextFile

func (*ClientPeer) ReleaseTerminal

func (*ClientPeer) Request

func (c *ClientPeer) Request(ctx context.Context, method schema.Method, params any, result any) error

Request sends an arbitrary client-bound request; use it for extension or unstable methods that have no typed wrapper.

func (*ClientPeer) RequestID

func (c *ClientPeer) RequestID() (jsonrpc2.ID, bool)

RequestID returns the inbound request id when this peer handle was passed to a request handler.

func (*ClientPeer) RequestPermission

func (*ClientPeer) SessionUpdate

func (c *ClientPeer) SessionUpdate(ctx context.Context, notification schema.SessionNotification) error

func (*ClientPeer) TerminalOutput

func (*ClientPeer) WaitForTerminalExit

func (*ClientPeer) WriteTextFile

type ClientRequest

type ClientRequest[P any] struct {
	Params    P
	Agent     *AgentPeer
	RequestID jsonrpc2.ID
}

type ClientRequestHandler

type ClientRequestHandler[P any, R any] func(context.Context, ClientRequest[P]) (R, error)

type PromptCall

type PromptCall struct {
	// contains filtered or unexported fields
}

func (*PromptCall) Await

type SessionBuilder

type SessionBuilder struct {
	// contains filtered or unexported fields
}

func (*SessionBuilder) Request

func (*SessionBuilder) Start

func (b *SessionBuilder) Start(ctx context.Context) (*ActiveSession, error)

func (*SessionBuilder) WithAdditionalDirectories

func (b *SessionBuilder) WithAdditionalDirectories(dirs ...string) *SessionBuilder

func (*SessionBuilder) WithMCPServer

func (b *SessionBuilder) WithMCPServer(server schema.McpServer) *SessionBuilder

func (*SessionBuilder) WithSession

func (b *SessionBuilder) WithSession(ctx context.Context, op func(context.Context, *ActiveSession) error) error

WithSession starts the session, runs op, and disposes the session when op returns.

Directories

Path Synopsis
cmd
acp-schema-gen command
Package schema contains generated Go representations of the Agent Client Protocol schema.
Package schema contains generated Go representations of the Agent Client Protocol schema.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL