/** * JSON-RPC 2.0 envelope + canonical method names. Used by `bridge.cts` and * any non-TypeScript adapter (e.g. Hermes' Python client). * * Adding a method here is non-breaking. Renaming or removing one is breaking * (see ARCHITECTURE.md §8). */ import type { ErrorCode, SerializedMemosError } from "./errors.js"; // ─── Envelope ──────────────────────────────────────────────────────────────── export type JsonRpcId = number | string; export interface JsonRpcRequest

{ jsonrpc: "2.0"; id: JsonRpcId; method: string; params?: P; } export interface JsonRpcNotification

{ jsonrpc: "2.0"; method: string; params?: P; } export interface JsonRpcSuccess { jsonrpc: "2.0"; id: JsonRpcId; result: R; } export interface JsonRpcFailure { jsonrpc: "2.0"; id: JsonRpcId | null; error: { /** Numeric code per JSON-RPC 2.0; we always use -32000 for app errors. */ code: number; message: string; /** Our stable application-level error. */ data?: SerializedMemosError; }; } export type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure; // JSON-RPC 2.0 reserved codes export const JSONRPC_PARSE_ERROR = -32700; export const JSONRPC_INVALID_REQUEST = -32600; export const JSONRPC_METHOD_NOT_FOUND = -32601; export const JSONRPC_INVALID_PARAMS = -32602; export const JSONRPC_INTERNAL_ERROR = -32603; export const JSONRPC_APPLICATION_ERROR = -32000; // ─── Method names ──────────────────────────────────────────────────────────── /** * The complete method registry. Group prefixes match `core/` modules so the * `bridge/methods.ts` dispatcher can route mechanically. */ export const RPC_METHODS = { // ── lifecycle ── CORE_INIT: "core.init", CORE_SHUTDOWN: "core.shutdown", CORE_HEALTH: "core.health", // ── session / episode ── SESSION_OPEN: "session.open", SESSION_CLOSE: "session.close", EPISODE_OPEN: "episode.open", EPISODE_CLOSE: "episode.close", // ── pipeline (per turn) ── TURN_START: "turn.start", TURN_END: "turn.end", FEEDBACK_SUBMIT: "feedback.submit", // ── memory queries ── MEMORY_SEARCH: "memory.search", MEMORY_GET_TRACE: "memory.get_trace", MEMORY_GET_POLICY: "memory.get_policy", MEMORY_GET_WORLD: "memory.get_world", MEMORY_LIST_EPISODES: "memory.list_episodes", MEMORY_TIMELINE: "memory.timeline", MEMORY_LIST_TRACES: "memory.list_traces", MEMORY_LIST_WORLDS: "memory.list_world_models", // ── skills ── SKILL_LIST: "skill.list", SKILL_GET: "skill.get", SKILL_ARCHIVE: "skill.archive", // ── retrieval ── RETRIEVAL_QUERY: "retrieval.query", // ── subagents ── SUBAGENT_RECORD: "subagent.record", // ── config ── CONFIG_GET: "config.get", CONFIG_PATCH: "config.patch", // ── hub ── HUB_STATUS: "hub.status", HUB_PUBLISH: "hub.publish", HUB_PULL: "hub.pull", // ── logs ── LOGS_TAIL: "logs.tail", /** Notification: forward a log line from a non-TS adapter back into our sinks. */ LOGS_FORWARD: "logs.forward", // ── events ── /** Notification: subscribe; the server then sends `events.notify` notifications. */ EVENTS_SUBSCRIBE: "events.subscribe", EVENTS_UNSUBSCRIBE: "events.unsubscribe", EVENTS_NOTIFY: "events.notify", } as const; export type RpcMethodName = (typeof RPC_METHODS)[keyof typeof RPC_METHODS]; export function isRpcMethodName(s: string): s is RpcMethodName { return Object.values(RPC_METHODS).includes(s as RpcMethodName); } /** Map an internal `MemosError.code` to a numeric JSON-RPC code we'll report. */ export function rpcCodeForError(code: ErrorCode | undefined): number { if (!code) return JSONRPC_INTERNAL_ERROR; switch (code) { case "invalid_argument": case "config_invalid": case "protocol_error": return JSONRPC_INVALID_PARAMS; case "unknown_method": return JSONRPC_METHOD_NOT_FOUND; default: return JSONRPC_APPLICATION_ERROR; } }