// 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
}