Document the full RPC flow, message format, error codes, SDK usage, available methods (getAgentMessages), and guide for adding new methods. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7 KiB
7 KiB
Hub RPC Protocol
The Hub exposes an RPC (Remote Procedure Call) interface over the Gateway WebSocket transport. Clients can invoke methods on the Hub and receive structured responses, all routed through the same Gateway message layer used for regular chat.
Architecture Overview
Client (SDK) Gateway (WebSocket) Hub
| | |
|-- send(RequestAction) ------->|-- route to Hub ----------->|
| | |-- dispatch(method, params)
| | |-- handler executes
|<-- receive(ResponseAction) ---|<-- route to Client --------|
| | |
- The Client calls
client.request(hubDeviceId, method, params). - The SDK generates a
requestId(UUIDv7), wraps it into aRequestPayload, and sends a message withaction = "request"to the Hub via the Gateway. - The Gateway routes the message to the Hub's socket (standard device-to-device routing).
- The Hub detects
action === "request"in itsonMessagehandler and delegates toRpcDispatcher.dispatch(). - The dispatcher looks up the registered handler for the given
methodand invokes it. - The Hub sends back a message with
action = "response"containing either a success or error payload, addressed to the original sender. - The Client SDK intercepts incoming
"response"messages in itsRECEIVElistener, matches byrequestId, and resolves (or rejects) the correspondingPromise.
Message Format
All RPC messages use the standard RoutedMessage envelope:
interface RoutedMessage<T> {
id: string; // UUIDv7 message ID
uid: string | null;
from: string; // sender deviceId
to: string; // recipient deviceId
action: string; // "request" or "response"
payload: T;
}
Request Payload
interface RequestPayload<T = unknown> {
requestId: string; // UUIDv7, generated by the SDK
method: string; // RPC method name
params?: T; // method-specific parameters
}
Response Payload (Success)
interface ResponseSuccessPayload<T = unknown> {
requestId: string; // matches the request
ok: true;
payload: T; // method-specific result
}
Response Payload (Error)
interface ResponseErrorPayload {
requestId: string; // matches the request
ok: false;
error: {
code: string; // machine-readable error code
message: string; // human-readable description
};
}
Error Codes
| Code | Description |
|---|---|
METHOD_NOT_FOUND |
The requested RPC method does not exist. |
INVALID_PARAMS |
Missing or malformed parameters. |
AGENT_NOT_FOUND |
No session file found for the given agent ID. |
RPC_ERROR |
Catch-all for unexpected errors. |
Client SDK Usage
The GatewayClient provides a request() method that handles the full request/response lifecycle:
request<T = unknown>(
to: string, // target deviceId (Hub's deviceId)
method: string, // RPC method name
params?: unknown, // method parameters
timeout?: number, // timeout in ms (default: 10000)
): Promise<T>
The method:
- Generates a
requestIdinternally. - Sends a
RequestPayloadvia the Gateway. - Returns a
Promisethat resolves with the response payload on success, or rejects with anErroron failure or timeout. - Automatically cleans up pending requests on disconnect.
Example
import { GatewayClient, type GetAgentMessagesResult } from "@multica/sdk";
const client = new GatewayClient({
url: "http://localhost:3000",
deviceId: "my-client",
deviceType: "client",
});
client.connect();
client.onRegistered(async () => {
try {
const result = await client.request<GetAgentMessagesResult>(
"hub-device-id",
"getAgentMessages",
{ agentId: "019abc12-...", offset: 0, limit: 20 },
);
console.log(`Total: ${result.total}, returned: ${result.messages.length}`);
} catch (err) {
console.error("RPC failed:", err.message);
}
});
Available RPC Methods
getAgentMessages
Retrieves the message history for a given agent session. Works for both active and closed agents as long as the session file exists on disk.
Parameters:
interface GetAgentMessagesParams {
agentId: string; // required - the agent/session ID
offset?: number; // starting index (default: 0)
limit?: number; // max messages to return (default: 50)
}
Response:
interface GetAgentMessagesResult {
messages: AgentMessage[]; // array of messages
total: number; // total message count in the session
offset: number; // the offset used
limit: number; // the limit used
}
Each AgentMessage in the array is one of:
- UserMessage (
role: "user") - User input (text or multimodal content). - AssistantMessage (
role: "assistant") - LLM response, may containTextContent,ThinkingContent, orToolCallblocks. Includesusage(token counts and costs),model,provider, andstopReason. - ToolResultMessage (
role: "toolResult") - Result of a tool invocation, withtoolCallId,toolName,content, andisError.
Example request:
const result = await client.request<GetAgentMessagesResult>(
hubDeviceId,
"getAgentMessages",
{ agentId: "019abc12-3def-7000-8000-000000000001", offset: 0, limit: 10 },
);
Example success response payload:
{
"requestId": "019abc12-...",
"ok": true,
"payload": {
"messages": [
{ "role": "user", "content": "Hello", "timestamp": 1700000000000 },
{
"role": "assistant",
"content": [{ "type": "text", "text": "Hi! How can I help?" }],
"model": "claude-sonnet-4-20250514",
"provider": "anthropic",
"usage": { "input": 10, "output": 15, "totalTokens": 25 },
"stopReason": "end_turn",
"timestamp": 1700000001000
}
],
"total": 42,
"offset": 0,
"limit": 10
}
}
Example error response payload:
{
"requestId": "019abc12-...",
"ok": false,
"error": {
"code": "AGENT_NOT_FOUND",
"message": "No session found for agent: 019abc12-bad-id"
}
}
Adding New RPC Methods
- Create a handler file in
src/hub/rpc/handlers/:
// src/hub/rpc/handlers/my-method.ts
import { RpcError, type RpcHandler } from "../dispatcher.js";
export function createMyMethodHandler(): RpcHandler {
return (params: unknown) => {
if (!params || typeof params !== "object") {
throw new RpcError("INVALID_PARAMS", "params must be an object");
}
// ... validate and handle
return { /* result */ };
};
}
- Register it in
src/hub/hub.tsconstructor:
this.rpc.register("myMethod", createMyMethodHandler());
- (Optional) Add typed params/result interfaces in
packages/sdk/src/actions/rpc.tsand export them frompackages/sdk/src/actions/index.tsfor client-side type safety.