first commit
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// Package jsonrpc implements a minimal JSON-RPC 2.0 client over a subprocess's
|
||||
// stdio (US-014/#116). It is the shared transport foundation reused by the MCP
|
||||
// client (#130/#131), the plugin system (#132/#133) and process-isolated
|
||||
// sub-agents (#135): each spawns an external executable and speaks line-delimited
|
||||
// JSON-RPC 2.0 over the child's stdin/stdout.
|
||||
//
|
||||
// The wire format follows the spec: every message carries "jsonrpc":"2.0". A
|
||||
// request has an id and expects a matching response; a notification omits the id
|
||||
// and expects none. Requests and responses are correlated by id, so concurrent
|
||||
// requests from different goroutines are safe — each waits only on its own reply.
|
||||
//
|
||||
// This file defines the message envelope and (de)serialization; transport.go
|
||||
// implements the subprocess client.
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Version is the only JSON-RPC protocol version this package speaks.
|
||||
const Version = "2.0"
|
||||
|
||||
// ID is a JSON-RPC request identifier. The spec allows a string or a number;
|
||||
// this client only ever generates numeric ids, but ID round-trips whatever a
|
||||
// peer sends back so response correlation still works against servers that echo
|
||||
// string ids.
|
||||
type ID struct {
|
||||
num int64
|
||||
str string
|
||||
isStr bool
|
||||
}
|
||||
|
||||
// NumID returns a numeric request id.
|
||||
func NumID(n int64) ID { return ID{num: n} }
|
||||
|
||||
// String renders the id for use as a map key when correlating responses.
|
||||
func (id ID) String() string {
|
||||
if id.isStr {
|
||||
return "s:" + id.str
|
||||
}
|
||||
return fmt.Sprintf("n:%d", id.num)
|
||||
}
|
||||
|
||||
// MarshalJSON emits the id as its underlying JSON scalar (number or string).
|
||||
func (id ID) MarshalJSON() ([]byte, error) {
|
||||
if id.isStr {
|
||||
return json.Marshal(id.str)
|
||||
}
|
||||
return json.Marshal(id.num)
|
||||
}
|
||||
|
||||
// UnmarshalJSON accepts either a JSON number or string id.
|
||||
func (id *ID) UnmarshalJSON(data []byte) error {
|
||||
var n int64
|
||||
if err := json.Unmarshal(data, &n); err == nil {
|
||||
id.num, id.isStr, id.str = n, false, ""
|
||||
return nil
|
||||
}
|
||||
var s string
|
||||
if err := json.Unmarshal(data, &s); err == nil {
|
||||
id.str, id.isStr, id.num = s, true, 0
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("jsonrpc: id is neither number nor string: %s", data)
|
||||
}
|
||||
|
||||
// Request is an outgoing JSON-RPC request or notification. When ID is nil the
|
||||
// message is a notification (no response expected).
|
||||
type Request struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID *ID `json:"id,omitempty"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// Response is an incoming JSON-RPC response. Exactly one of Result / Error is
|
||||
// set on a well-formed reply.
|
||||
type Response struct {
|
||||
JSONRPC string `json:"jsonrpc"`
|
||||
ID *ID `json:"id,omitempty"`
|
||||
Result json.RawMessage `json:"result,omitempty"`
|
||||
Error *Error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// Error is a JSON-RPC error object.
|
||||
type Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// Error implements the error interface so a peer error can be returned directly.
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("jsonrpc: server error %d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// newRequest builds a request (id != nil) or notification (id == nil) with the
|
||||
// given params marshaled to JSON. A nil params value is omitted from the wire
|
||||
// message.
|
||||
func newRequest(id *ID, method string, params any) (*Request, error) {
|
||||
req := &Request{JSONRPC: Version, ID: id, Method: method}
|
||||
if params != nil {
|
||||
raw, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jsonrpc: marshal params for %q: %w", method, err)
|
||||
}
|
||||
req.Params = raw
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
Reference in New Issue
Block a user