Table-driven JSON-RPC method routing for ACP: decode/4 classifies and
type-decodes an inbound wire method + params into a tagged tuple,
dispatch/4 calls the matching handler callback, and result_marker/1
tells the sending side how to decode a pending outbound request's
response.
Every function clause here is generated at compile time from
Raxol.AgentClientProtocol.MethodTable.rows/0 by the internal
Router.Codegen module (not part of the public docs; @moduledoc false) -- editing the table is the only way to change routing behavior;
there is no hand-maintained parallel list of method strings anywhere in
this module.
What this module does NOT do (by design)
- No capability gating.
ConnectionconsultsMethodTable'scapabilityfield and must reject a gated-and-unnegotiated method with -32601 BEFORE ever callingdecode/4--Routerhas no capability awareness at all. - No
$/cancel_requestorsession/cancelclauses. Both areConnection-routed session-control paths pre-filtered on the wire method string beforedecode/4is called. Callingdecode(side, :notification, "$/cancel_request", _)ordecode(side, :notification, "session/cancel", _)falls through to the catch-all like any other unrecognized method -- callers must pre-filter, not rely onRouterto special-case them. - No pending-map bookkeeping.
result_marker/1is a pure lookup; storingpending[id] = %{marker: ..., from: ...}and reading it back on response arrival (markers computed once at send time; ids includenil) isConnection's job.
Decoding
iex> Raxol.AgentClientProtocol.Router.decode(:agent, :request, "session/prompt", %{"sessionId" => "s1", "prompt" => []})
{:ok, {:prompt, %Raxol.AgentClientProtocol.Schema.AgentTypes.PromptRequest{session_id: "s1", prompt: []}}}
iex> Raxol.AgentClientProtocol.Router.decode(:agent, :request, "_vendor/thing", %{"x" => 1})
{:ok, {:ext_request, "_vendor/thing", %{"x" => 1}}}
iex> Raxol.AgentClientProtocol.Router.decode(:agent, :request, "no/such/method", %{})
{:error, :method_not_found}
Summary
Types
A decoded, dispatchable inbound message: {callback_tag, decoded_params_or_nil}.
What Connection should do with a pending outbound request's response.
Which side is decoding/dispatching: the side that HANDLES the row (see MethodTable.rows_for_side/1).
Functions
Classify and decode an inbound JSON-RPC method + params for side.
Dispatch a decoded message (as produced by decode/4) to handler's
matching callback. Request callbacks are called with (params, ctx) (or
(ctx) alone for params: nil rows); notification callbacks
are called with (params, ctx) and this function always returns :ok
regardless of what the callback itself returns.
Look up how the sending side should decode the response to an outbound
request for wire, computed once at send time -- never re-derive
this from the wire string when the response actually arrives; store the
returned marker in the pending-request entry instead.
Types
@type decoded() :: {atom(), struct() | nil} | {:ext_request, String.t(), map()} | {:ext_notification, String.t(), map()}
A decoded, dispatchable inbound message: {callback_tag, decoded_params_or_nil}.
@type result_marker() :: {:decode, module()} | :ext | :unknown
What Connection should do with a pending outbound request's response.
@type side() :: :agent | :client
Which side is decoding/dispatching: the side that HANDLES the row (see MethodTable.rows_for_side/1).
Functions
@spec decode(side(), Raxol.AgentClientProtocol.MethodTable.kind(), String.t(), term()) :: {:ok, decoded()} | {:error, term()}
Classify and decode an inbound JSON-RPC method + params for side.
Returns {:ok, decoded} for a recognized core/registered row or an
"_"-prefixed ext passthrough, {:error, :method_not_found} for an
unrecognized method (⇒ -32601 at the caller), or {:error, reason} when a
recognized row's params fail to decode (⇒ -32602 at the caller).
Never creates an atom from wire -- unknown and ext methods stay
binaries end-to-end.
Dispatch a decoded message (as produced by decode/4) to handler's
matching callback. Request callbacks are called with (params, ctx) (or
(ctx) alone for params: nil rows); notification callbacks
are called with (params, ctx) and this function always returns :ok
regardless of what the callback itself returns.
@spec result_marker(String.t()) :: result_marker()
Look up how the sending side should decode the response to an outbound
request for wire, computed once at send time -- never re-derive
this from the wire string when the response actually arrives; store the
returned marker in the pending-request entry instead.