// Package acp speaks the Agent Client Protocol: JSON-RPC 2.0 framed as one // compact object per line, over a pair of streams that are usually this // process's stdin and stdout. // // It is a peer, not a client or a server. The other end calls initialize, // opens sessions and sends prompts; this end calls back to ask permission for // a tool. Both directions run over the same connection at the same time, which // is why the read loop dispatches every inbound message onto its own goroutine // — a permission request answered on the read loop would be a deadlock with // the peer doing the same thing. // // The package knows nothing about agents, models or tools. It is the wire and // the vocabulary, and [Handler] is where somebody else's meaning attaches. package acp import ( "encoding/json" "fmt" ) // jsonrpcVersion is required on every frame in both directions. The clients // that matter validate it as a literal, so omitting it is not a shortcut, it // is a decode error at the far end. const jsonrpcVersion = "2.0" // Message is one frame. The four payload fields are mutually exclusive in // practice — a request has Method, a response has Result or Error — but they // share a struct because that is how they share a line. type Message struct { JSONRPC string `json:"jsonrpc"` // ID is kept raw so a string id and a numeric one both come back exactly // as they were sent. A response whose id does not match byte for byte is a // response the peer cannot pair up. ID *json.RawMessage `json:"id,omitempty"` Method string `json:"method,omitempty"` Params json.RawMessage `json:"params,omitempty"` Result json.RawMessage `json:"result,omitempty"` Error *Error `json:"error,omitempty"` } // The JSON-RPC error codes this package produces. const ( CodeParse = -32700 CodeInvalidRequest = -32600 CodeMethodNotFound = -32601 CodeInvalidParams = -32602 CodeInternal = -32603 ) // Error is a JSON-RPC error object, and also a Go error: a [Handler] that // returns one chooses the code the peer sees, and anything else becomes // CodeInternal. type Error struct { Code int `json:"code"` Message string `json:"message"` Data any `json:"data,omitempty"` } func (e *Error) Error() string { return fmt.Sprintf("acp: %s (%d)", e.Message, e.Code) } // Errorf builds an [Error] with a formatted message. func Errorf(code int, format string, args ...any) *Error { return &Error{Code: code, Message: fmt.Sprintf(format, args...)} } // asError turns whatever a handler returned into something the peer can be // told. An error that already chose its code keeps it. func asError(err error) *Error { if e, ok := err.(*Error); ok { return e } return &Error{Code: CodeInternal, Message: err.Error()} } // decodeParams unmarshals a request's params, reporting a bad shape as // CodeInvalidParams rather than as a server fault. Absent params decode as the // zero value: a method whose arguments are all optional may be called with // none. func decodeParams(raw json.RawMessage, v any) error { if len(raw) == 0 || string(raw) == "null" { return nil } if err := json.Unmarshal(raw, v); err != nil { return Errorf(CodeInvalidParams, "cannot decode params: %v", err) } return nil }