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
1
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.2
Reject invalid frames deterministically
When a raw frame is not a mapping, carries an unknown or missing
type, or fails per-frame validation, decode_client_frame raises FrameDecodeError. The attached HelloError maps straight onto the outbound hello_error (or error) wire frame via error.to_dict().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.
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.
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.
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.

