acp

package
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package acp implements the Agent Communication Protocol — a WebSocket-based protocol that lets any client interact with an OK agent.

Handles WebSocket upgrade per RFC 6455: SHA1 key challenge → 101 response → bidirectional JSON frames. Falls back to SSE for read-only streaming.

Package acp implements the Agent Client Protocol (https://agentclientprotocol.com) transport: a stdio JSON-RPC 2.0 agent that editors and other host clients speak to drive OK. Many tools integrated with the v1 (main-branch) agent over ACP, so v2 keeps the wire contract identical — the wire types in this file are a faithful port of main's src/acp/protocol.ts (ACP protocol version 1).

The package is an adapter layer over the v2 kernel and depends only on stable contracts: it maps the agent's typed event.Event stream onto session/update notifications (see dispatch.go), bridges permission.Approver onto session/request_permission round-trips (see permission.go), and exposes the whole thing over NDJSON JSON-RPC (see server.go). How a per-session agent is actually assembled — provider, tools rooted at the session cwd, per-session MCP — is left to a Factory the composition root supplies (see service.go), so this package stays independent of the cli wiring.

Index

Constants

View Source
const (
	MsgSubmit  = "submit"
	MsgCancel  = "cancel"
	MsgApprove = "approve"
	MsgEvent   = "event"
)

Message types

View Source
const (
	ErrParse          = -32700
	ErrInvalidRequest = -32600
	ErrMethodNotFound = -32601
	ErrInvalidParams  = -32602
	ErrInternal       = -32603
)

JSON-RPC 2.0 error codes (subset used on the wire). Mirrors protocol.ts.

View Source
const ProtocolVersion = 1

ProtocolVersion is the ACP version this agent implements. Matches main.

Variables

This section is empty.

Functions

func FlattenPrompt

func FlattenPrompt(blocks []ContentBlock) string

FlattenPrompt extracts the user-visible prompt text out of ACP content blocks. Text blocks contribute their text; resource blocks contribute their inline text when present (embeddedContext). Other block kinds are dropped. Ported from protocol.ts flattenPrompt.

func Serve

func Serve(ctx context.Context, r io.Reader, w io.Writer, factory Factory, info AgentInfo) error

Serve runs an ACP agent on r/w (stdin/stdout in production) until the input ends or ctx is canceled. It owns the JSON-RPC connection and the session registry; the Factory supplies the kernel wiring. This is the single entry point the `ok acp` command calls.

stdout is the JSON-RPC channel: callers must keep all other output (logs, diagnostics) off w and on stderr, or the wire corrupts.

Types

type AgentCapabilities

type AgentCapabilities struct {
	LoadSession        bool               `json:"loadSession"`
	PromptCapabilities PromptCapabilities `json:"promptCapabilities"`
	MCPCapabilities    MCPCapabilities    `json:"mcpCapabilities"`
}

AgentCapabilities is the agentCapabilities object in InitializeResult.

type AgentInfo

type AgentInfo struct {
	Name    string
	Version string
}

AgentInfo identifies this agent to clients in the initialize reply.

type ClientMsg

type ClientMsg struct {
	Type     string `json:"type"`
	Input    string `json:"input,omitempty"`
	ID       string `json:"id,omitempty"`
	Allow    bool   `json:"allow,omitempty"`
	Remember bool   `json:"remember,omitempty"`
}

type Conn

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

Conn is one NDJSON JSON-RPC 2.0 connection over a reader/writer pair (stdin/ stdout in production). It dispatches inbound requests and notifications to registered handlers, and can itself send outbound notifications (session/update) and requests (session/request_permission), correlating replies by id.

Writes are serialized by a mutex, so handlers running on separate goroutines (a long session/prompt alongside a session/cancel) never interleave a line. It implements notifier, the dependency the dispatch sink takes.

func NewConn

func NewConn(r io.Reader, w io.Writer) *Conn

NewConn wires a connection over r (inbound) and w (outbound). Register handlers with Handle / HandleNotify before calling Serve. The encoder disables HTML escaping so payloads match main's JSON.stringify output byte-for-byte.

func (*Conn) Handle

func (c *Conn) Handle(method string, h RequestHandler)

Handle registers a request handler for method. Not safe to call concurrently with Serve; wire all handlers up first.

func (*Conn) HandleNotify

func (c *Conn) HandleNotify(method string, h NotificationHandler)

HandleNotify registers a notification handler for method.

func (*Conn) Notify

func (c *Conn) Notify(method string, params any) error

Notify sends a fire-and-forget notification. Satisfies notifier.

func (*Conn) Request

func (c *Conn) Request(ctx context.Context, method string, params any) (json.RawMessage, error)

Request sends an outbound request and blocks until the peer responds, ctx is canceled, or the connection closes. Satisfies notifier.

func (*Conn) Serve

func (c *Conn) Serve(ctx context.Context) error

Serve reads inbound frames until the reader ends or ctx is canceled. Each inbound request/notification runs on its own goroutine so a long-running prompt does not block cancellation or permission replies. When the read loop ends it cancels in-flight handlers (so prompts abort) and waits for them to return — flushing fast responses and unwinding aborted ones — before failing any outstanding outbound requests. Returns nil on clean EOF.

type ContentBlock

type ContentBlock struct {
	Type     string            `json:"type"`
	Text     string            `json:"text,omitempty"`
	Resource *ResourceContents `json:"resource,omitempty"`
	MimeType string            `json:"mimeType,omitempty"`
	Data     string            `json:"data,omitempty"`
}

ContentBlock is one piece of a prompt. The agent reads text blocks and the inline text of resource blocks (embeddedContext); image/audio are accepted on the wire but ignored, matching the advertised capabilities.

type Factory

type Factory interface {
	NewSession(ctx context.Context, p SessionParams) (*control.Controller, error)
}

Factory builds the per-session controller. The composition root (the cli's `ok acp` command) implements it by reusing setup()'s assembly: a Provider for Model, a tool Registry rooted at Cwd via builtin.Workspace, a per-session MCP host from MCPServers, the event Sink, all wired into a control.Controller. The returned controller owns its own cleanup (Close stops MCP subprocesses), so the service calls ctrl.Close() on teardown.

type Implementation

type Implementation struct {
	Name    string `json:"name"`
	Title   string `json:"title,omitempty"`
	Version string `json:"version,omitempty"`
}

Implementation names a participant (client or agent) on the wire.

type InitializeParams

type InitializeParams struct {
	ProtocolVersion int             `json:"protocolVersion"`
	ClientInfo      *Implementation `json:"clientInfo,omitempty"`
}

InitializeParams is the client's handshake. We accept and ignore its capabilities/info — the agent advertises a fixed capability set in reply.

type InitializeResult

type InitializeResult struct {
	ProtocolVersion   int               `json:"protocolVersion"`
	AgentCapabilities AgentCapabilities `json:"agentCapabilities"`
	AgentInfo         Implementation    `json:"agentInfo"`
	AuthMethods       []any             `json:"authMethods"`
}

InitializeResult advertises what this agent supports. The capability flags match main exactly: sessions are created via session/new (no loadSession), prompts may carry inline resource text (embeddedContext) but not image/audio, and MCP is stdio-only (no http/sse).

type MCPCapabilities

type MCPCapabilities struct {
	HTTP bool `json:"http"`
	SSE  bool `json:"sse"`
}

MCPCapabilities reports which MCP transports session/new accepts.

type MCPServerSpec

type MCPServerSpec struct {
	Name    string            `json:"name"`
	Command string            `json:"command,omitempty"`
	Args    []string          `json:"args,omitempty"`
	Env     map[string]string `json:"env,omitempty"`
}

MCPServerSpec describes one stdio MCP server the client asks the agent to run.

type NotificationHandler

type NotificationHandler func(ctx context.Context, params json.RawMessage)

NotificationHandler reacts to an inbound notification. It cannot reply, so it returns nothing — errors have nowhere to go on the wire (stderr would corrupt stdout, which is the JSON-RPC channel).

type PermissionOption

type PermissionOption struct {
	OptionID string               `json:"optionId"`
	Name     string               `json:"name"`
	Kind     PermissionOptionKind `json:"kind"`
}

PermissionOption is one choice offered to the user for a permission request.

type PermissionOptionKind

type PermissionOptionKind string

PermissionOptionKind classifies an option for host UI styling. Matches main.

const (
	OptAllowOnce    PermissionOptionKind = "allow_once"
	OptAllowAlways  PermissionOptionKind = "allow_always"
	OptRejectOnce   PermissionOptionKind = "reject_once"
	OptRejectAlways PermissionOptionKind = "reject_always"
)

type PermissionOutcome

type PermissionOutcome struct {
	Outcome  string `json:"outcome"`
	OptionID string `json:"optionId,omitempty"`
}

PermissionOutcome is "selected" (with optionId) or "canceled".

type PermissionRequestParams

type PermissionRequestParams struct {
	SessionID string             `json:"sessionId"`
	ToolCall  PermissionToolCall `json:"toolCall"`
	Options   []PermissionOption `json:"options"`
}

PermissionRequestParams asks the client to approve a pending tool call.

type PermissionRequestResult

type PermissionRequestResult struct {
	Outcome PermissionOutcome `json:"outcome"`
}

PermissionRequestResult is the client's reply to a permission request.

type PermissionToolCall

type PermissionToolCall struct {
	ToolCallID string          `json:"toolCallId"`
	Title      string          `json:"title,omitempty"`
	Kind       string          `json:"kind,omitempty"`
	Status     string          `json:"status,omitempty"`
	RawInput   json.RawMessage `json:"rawInput,omitempty"`
}

PermissionToolCall describes the call awaiting approval.

type PromptCapabilities

type PromptCapabilities struct {
	Image           bool `json:"image"`
	Audio           bool `json:"audio"`
	EmbeddedContext bool `json:"embeddedContext"`
}

PromptCapabilities reports which content-block kinds prompts may carry.

type RPCError

type RPCError struct {
	Code    int
	Message string
}

RPCError lets a handler choose the JSON-RPC error code returned to the client.

func (*RPCError) Error

func (e *RPCError) Error() string

type RequestHandler

type RequestHandler func(ctx context.Context, params json.RawMessage) (any, error)

RequestHandler answers an inbound JSON-RPC request. The returned value is marshaled as the response result. To control the error code, return a *RPCError; any other error becomes ErrInternal.

type ResourceContents

type ResourceContents struct {
	URI      string `json:"uri"`
	MimeType string `json:"mimeType,omitempty"`
	Text     string `json:"text,omitempty"`
}

ResourceContents is the embedded resource of a "resource" content block.

type Server

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

func NewServer

func NewServer(ctrl *control.Controller) *Server

func (*Server) Broadcast

func (s *Server) Broadcast(msg ServerMsg)

func (*Server) HandleSSE

func (s *Server) HandleSSE(w http.ResponseWriter, r *http.Request)

func (*Server) Handler

func (s *Server) Handler() http.Handler

type ServerMsg

type ServerMsg struct {
	Type    string `json:"type"`
	Kind    string `json:"kind,omitempty"`
	Content string `json:"content,omitempty"`
	Name    string `json:"name,omitempty"`
	Args    any    `json:"args,omitempty"`
	Result  string `json:"result,omitempty"`
	Error   string `json:"error,omitempty"`
}

type SessionCancelParams

type SessionCancelParams struct {
	SessionID string `json:"sessionId"`
}

SessionCancelParams cancels an in-progress turn.

type SessionLoadParams

type SessionLoadParams struct {
	SessionID  string          `json:"sessionId"`
	Cwd        string          `json:"cwd,omitempty"`
	MCPServers []MCPServerSpec `json:"mcpServers,omitempty"`
}

SessionLoadParams resumes a session saved under sessionId (the id a prior session/new returned), optionally re-rooting it at cwd with fresh MCP servers. The agent replays the stored conversation as session/update notifications before the request returns.

type SessionLoadResult

type SessionLoadResult struct{}

SessionLoadResult is the empty ack; the conversation has already arrived as a burst of session/update notifications by the time it is sent.

type SessionNewParams

type SessionNewParams struct {
	Cwd        string          `json:"cwd,omitempty"`
	MCPServers []MCPServerSpec `json:"mcpServers,omitempty"`
}

SessionNewParams opens a session rooted at cwd, optionally with stdio MCP servers the agent should connect for the session's lifetime.

type SessionNewResult

type SessionNewResult struct {
	SessionID string `json:"sessionId"`
}

SessionNewResult returns the opaque id used to address the session thereafter.

type SessionParams

type SessionParams struct {
	Cwd        string
	MCPServers []plugin.Spec
	Sink       event.Sink
}

SessionParams is everything a Factory needs to assemble one ACP session's controller. Sink is owned by this package (an updateSink bound to the session id) and must be wired into the controller's event sink; the controller's interactive approval (see control.Controller.EnableInteractiveApproval) then routes "ask" decisions back through that sink as ApprovalRequest events, which the sink forwards to the client over session/request_permission.

The Factory picks the model (ACP's session/new carries no model selection). Cwd roots the session's file tools and bash (built via builtin.Workspace). MCPServers are the stdio MCP servers the client asked the agent to connect for this session.

type SessionPromptParams

type SessionPromptParams struct {
	SessionID string         `json:"sessionId"`
	Prompt    []ContentBlock `json:"prompt"`
}

SessionPromptParams sends a turn's prompt to a session.

type SessionPromptResult

type SessionPromptResult struct {
	StopReason     StopReason `json:"stopReason"`
	TranscriptPath *string    `json:"transcriptPath,omitempty"`
}

SessionPromptResult ends a session/prompt. TranscriptPath is reserved for a future on-disk transcript pointer; omitted (null) for now.

type SessionUpdateParams

type SessionUpdateParams struct {
	SessionID string `json:"sessionId"`
	Update    any    `json:"update"`
}

SessionUpdateParams wraps one update for a session.

type StopReason

type StopReason string

StopReason tells the client why a turn ended. Values match main's wire.

const (
	StopEndTurn   StopReason = "end_turn"
	StopCancelled StopReason = "cancelled"
	StopError     StopReason = "error"
)

Jump to

Keyboard shortcuts

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