// jsonrpc.go 实现 ACP 用到的最小 JSON-RPC 2.0 codec。spec §9.5 选定自写最小 // 实现,理由:ACP 协议简单,无需引入完整 JSON-RPC 库。 // // 关键设计: // - id 类型同时支持 number(int64) 和 string(spec §5.2 命名空间约定) // - DecodeMessage 返回统一 *Message,调用方按 IsRequest/IsResponse/IsNotification 分发 // - 单条消息长度上限:DefaultMaxMessageBytes(防 OOM) package acp import ( "bufio" "encoding/json" "errors" "fmt" "io" "sync" ) // JSONRPC 错误码(标准 + ACP 自定义) const ( JSONRPCParseError = -32700 JSONRPCInvalidRequest = -32600 JSONRPCMethodNotFound = -32601 JSONRPCInvalidParams = -32602 JSONRPCInternalError = -32603 // ACP 自定义(-32000 到 -32099 区间为 server 自定义) ACPFsPathOutsideWorktree = -32001 ) // DefaultMaxMessageBytes 单条 JSON-RPC message 最大字节数(防 OOM)。 const DefaultMaxMessageBytes = 8 * 1024 * 1024 // 8 MB // Message 是 JSON-RPC 2.0 envelope 统一表示。Request 含 ID + Method; // Notification 含 Method 但 ID = nil;Response 含 ID + (Result 或 Error)。 type Message struct { JSONRPC string `json:"jsonrpc"` ID json.RawMessage `json:"id,omitempty"` Method string `json:"method,omitempty"` Params json.RawMessage `json:"params,omitempty"` Result json.RawMessage `json:"result,omitempty"` Error *JSONRPCError `json:"error,omitempty"` } // JSONRPCError 是 JSON-RPC 错误对象。 type JSONRPCError struct { Code int `json:"code"` Message string `json:"message"` Data json.RawMessage `json:"data,omitempty"` } func (e *JSONRPCError) Error() string { return fmt.Sprintf("jsonrpc error %d: %s", e.Code, e.Message) } // IsRequest reports whether m is a request (has ID + Method). func (m *Message) IsRequest() bool { return m.Method != "" && len(m.ID) > 0 && m.Result == nil && m.Error == nil } // IsNotification reports whether m is a notification (has Method but no ID). func (m *Message) IsNotification() bool { return m.Method != "" && len(m.ID) == 0 } // IsResponse reports whether m is a response (has ID, no Method). func (m *Message) IsResponse() bool { return m.Method == "" && len(m.ID) > 0 } // IDInt64 attempts to extract a numeric ID. Returns (n, true) if it's a JSON // number; (0, false) otherwise. func (m *Message) IDInt64() (int64, bool) { if len(m.ID) == 0 { return 0, false } var n int64 if err := json.Unmarshal(m.ID, &n); err == nil { return n, true } return 0, false } // IDString attempts to extract a string ID. func (m *Message) IDString() (string, bool) { if len(m.ID) == 0 { return "", false } var s string if err := json.Unmarshal(m.ID, &s); err == nil { return s, true } return "", false } // Decoder reads newline-delimited JSON-RPC messages from a stream. type Decoder struct { r *bufio.Reader maxSize int } // NewDecoder constructs a Decoder. maxSize <= 0 → DefaultMaxMessageBytes. func NewDecoder(r io.Reader, maxSize int) *Decoder { if maxSize <= 0 { maxSize = DefaultMaxMessageBytes } br := bufio.NewReaderSize(r, 64*1024) return &Decoder{r: br, maxSize: maxSize} } // DecodeMessage reads one LF-delimited JSON message. func (d *Decoder) DecodeMessage() (*Message, error) { line, err := d.r.ReadBytes('\n') if err != nil { if errors.Is(err, io.EOF) && len(line) == 0 { return nil, io.EOF } if !errors.Is(err, io.EOF) { return nil, err } } if len(line) > d.maxSize { return nil, fmt.Errorf("acp.jsonrpc: message exceeds max size %d", d.maxSize) } var m Message if err := json.Unmarshal(line, &m); err != nil { return nil, fmt.Errorf("acp.jsonrpc: parse: %w", err) } if m.JSONRPC != "2.0" { return nil, fmt.Errorf("acp.jsonrpc: unsupported jsonrpc version %q", m.JSONRPC) } return &m, nil } // Encoder serializes Messages to a writer with LF separator. type Encoder struct { w io.Writer mu sync.Mutex } // NewEncoder constructs an Encoder. Writes are mutex-protected so concurrent // callers (e.g. response handler + scheduled prompt) don't interleave bytes. func NewEncoder(w io.Writer) *Encoder { return &Encoder{w: w} } // Encode marshals m as JSON and writes it followed by '\n'. func (e *Encoder) Encode(m *Message) error { if m.JSONRPC == "" { m.JSONRPC = "2.0" } b, err := json.Marshal(m) if err != nil { return fmt.Errorf("acp.jsonrpc: marshal: %w", err) } e.mu.Lock() defer e.mu.Unlock() if _, err := e.w.Write(b); err != nil { return err } if _, err := e.w.Write([]byte{'\n'}); err != nil { return err } return nil } // NewRequestInt builds a Message with an integer id (server-initiated calls). func NewRequestInt(id int64, method string, params any) (*Message, error) { idJSON, err := json.Marshal(id) if err != nil { return nil, err } pb, err := json.Marshal(params) if err != nil { return nil, err } return &Message{JSONRPC: "2.0", ID: idJSON, Method: method, Params: pb}, nil } // NewNotification builds a Message without id. func NewNotification(method string, params any) (*Message, error) { pb, err := json.Marshal(params) if err != nil { return nil, err } return &Message{JSONRPC: "2.0", Method: method, Params: pb}, nil } // NewResponseOK builds a success response. func NewResponseOK(id json.RawMessage, result any) (*Message, error) { rb, err := json.Marshal(result) if err != nil { return nil, err } return &Message{JSONRPC: "2.0", ID: id, Result: rb}, nil } // NewResponseErr builds an error response. func NewResponseErr(id json.RawMessage, code int, msg string) *Message { return &Message{JSONRPC: "2.0", ID: id, Error: &JSONRPCError{Code: code, Message: msg}} }