The gateway ships in the
praisonai-bot package. praisonai serve gateway works exactly as documented here; for a standalone install see praisonai-bot Migration.decode_client_frame is the single validating decode step at the WebSocket boundary. Instead of each handler hand-parsing data.get(...), you decode a raw JSON frame once into a typed, discriminated ClientFrame — or get a FrameDecodeError carrying a structured HelloError that is safe to serialise straight back to the client.
decode_client_frame validates it once and dispatches on type to a typed handler, or rejects it with a structured error.
Quick Start
Decode once at the boundary
Swap per-handler
data.get(...) for a single validating decode. Every advertised inbound method — hello, message, leave, and the legacy join — is decoded here into an already-typed object.Inbound Frames
decode_client_frame returns one of four typed frames, discriminated on the wire type field:
HelloParams is documented on the Gateway Handshake Protocol page (the hello half of the same union). The codec adds two conveniences to the hello path: it tolerates both the flat protocol_min / protocol_max shape and the legacy nested protocol: {min, max} shape, and it accepts a capabilities / caps alias.
MessageParams
The validatedmessage frame — previously hand-parsed per handler.
| Field | Type | Default | Description |
|---|---|---|---|
content | Union[str, Dict[str, Any]] | required | The message body — text, or a structured payload |
session_id | Optional[str] | None | Optional session the message belongs to |
message_id | Optional[str] | None | Optional client-supplied idempotency / correlation id |
metadata | Dict[str, Any] | {} | Optional additional message metadata |
type | str | "message" | Wire-type discriminant (set automatically) |
content is required and also accepts the legacy text alias. Empty-string content is rejected, and content must be a str or dict. Non-dict metadata is silently coerced to {}.LeaveParams
The validatedleave frame.
| Field | Type | Default | Description |
|---|---|---|---|
session_id | Optional[str] | None | Optional session the client is leaving |
reason | Optional[str] | None | Optional human-readable reason (display / logging only) |
type | str | "leave" | Wire-type discriminant (set automatically) |
JoinParams
The legacyjoin handshake. New clients should prefer hello, but the codec keeps join in the same single validating decode step so the wrapper can share validation.
| Field | Type | Default | Description |
|---|---|---|---|
agent_id | str | required | The agent to connect to |
min_version | int | 1 (MIN_CLIENT_PROTOCOL_VERSION) | Minimum protocol version the client supports |
max_version | int | 1 (GATEWAY_PROTOCOL_VERSION) | Maximum protocol version the client supports |
session_id | Optional[str] | None | Optional session to resume |
type | str | "join" | Wire-type discriminant (set automatically) |
How It Works
Field coercion and validation happen once, at the WebSocket boundary. Handlers receive already-typed objects; the transport gets a deterministic rejection. Every rejection returns aFrameDecodeError whose .error is a HelloError with:
code—ConnectErrorCode.CONFIGURATION_ERRORfor a schema failure, orConnectErrorCode.PROTOCOL_UNSUPPORTEDfor a version-range failurenext_step—ConnectRecoveryStep.DO_NOT_RETRYfor schema failures, orUPGRADE_CLIENTfor version failures
HelloError fields and full recovery-step vocabulary are documented on the Gateway Handshake Protocol and Gateway Error Handling pages — error.to_dict() produces the same wire shape used there.
Best Practices
Decode once at the boundary
Decode once at the boundary
Call
decode_client_frame in one place — the WebSocket receive loop — and pass typed frames to handlers. Validation and coercion happen exactly once, so downstream code never re-checks types or defaults.Return the attached HelloError on FrameDecodeError
Return the attached HelloError on FrameDecodeError
On
FrameDecodeError, send e.error.to_dict() straight back to the client. It is a safe, structured hello_error / error frame carrying (code, next_step, retry_after_seconds) — no per-handler try/except or ad-hoc isinstance checks needed.Prefer decode_client_frame over per-handler data.get(...)
Prefer decode_client_frame over per-handler data.get(...)
Hand-parsing each frame with
data.get(...) drifts from the wire contract and duplicates validation. decode_client_frame is the single source of truth: it decodes every advertised inbound method plus the legacy join in one validating step.Related
Gateway Handshake Protocol
The
hello half of the same union — HelloParams, HelloResult, and HelloError.Gateway Error Handling
The
code / next_step recovery-step vocabulary carried by FrameDecodeError.Session Protocol
How
session_id on message and leave frames maps to gateway sessions.Gateway Overview
The full gateway architecture the frame codec plugs into.

