PlatoolsClient (TypeScript)
platools.connect() is a thin wrapper around PlatoolsClient. The TypeScript client follows the same contract as the Python client - same backoff curve, same heartbeat interval, same wire protocol - so a single Platos platform can serve mixed-language SDK deployments without any runtime awareness of which language a given tool is implemented in.
Connection model
- Outbound WebSocket to
${PLATOS_URL}/ws/sdk.http:///https://URLs rewrite tows:///wss://automatically. - JWT auth via
Authorization: Bearer ${PLATOS_SECRET}on the WebSocket upgrade. - Heartbeat every 30s (
HEARTBEAT_INTERVAL_MS = 30_000). - Exponential backoff reconnect -
BACKOFF_BASE_MS = 1_000,BACKOFF_MAX_MS = 60_000.runForever()reconnects untilstop()is called or the embedder aborts the process; the helperbackoffDelayMs(attempt)is exported so tests can assert the curve. - Registration on reconnect. The full tool list is resent after every successful reconnect.
These match the Python SDK’s wire protocol bit-for-bit - see PRD §5.2. The one shape difference: the Python client caps at MAX_BACKOFF_ATTEMPTS = 10 per session, while the TypeScript client retries indefinitely until stop() is called. Long-running Node hosts typically wrap the connect loop in their own supervisor (PM2, systemd) and don’t notice; pick whichever matches your runtime story.
Basic usage
import { platools } from "./platools.js";
await platools.connect(); // runs until the platform closes the socketThe process stays alive as long as connect() is pending. For a real deployment you’ll want to wrap it in your own runtime (PM2, systemd, Node cluster worker) - the SDK doesn’t register process signal handlers, that’s left to the embedder.
Graceful shutdown
import { Platools } from "@platools/sdk";
const platools = new Platools();// ... register tools ...
const connectPromise = platools.connect();
process.on("SIGTERM", () => { // PlatoolsClient's stop() closes the socket and lets run_forever() exit. // Wire this through whatever handle your app keeps on the client.});
await connectPromise;The Platools wrapper hides the client handle by design - if you need more control, use PlatoolsClient directly and call client.stop() from your signal handler.
Direct PlatoolsClient
import { PlatoolsClient, ToolRegistry } from "@platools/sdk";
const registry = new ToolRegistry();// register tools ...
const client = new PlatoolsClient({ url: "https://platform.example.com", secret: "platos_agent_...", registry,});
await client.runForever();// Later: await client.stop();The constructor validates url and secret - missing or empty values throw immediately at construction time, never at first-send time.
Wire protocol
Identical to the Python SDK. See packages/platools-js/src/transport/protocol.ts for the Zod schemas. The envelope messages are:
| Direction | Message | Purpose |
|---|---|---|
| SDK -> platform | tool_register | Full tool list after every (re)connect. |
| SDK -> platform | heartbeat | Every 30s. |
| SDK -> platform | tool_result | Success - includes latency_ms. |
| SDK -> platform | tool_error | Error - includes message + optional stack. |
| platform -> SDK | welcome | Handshake ack. |
| platform -> SDK | heartbeat_ack | Heartbeat ack. |
| platform -> SDK | tool_call | callId, toolName, params. |
Unknown inbound messages are logged and ignored so rolling platform upgrades can’t crash older SDKs.
Error handling
When a tool handler throws (or returns a rejected promise), the client catches the error, serializes the message and stack, and sends a tool_error back over the WebSocket. The connection stays up. One bad handler never kills the session. Socket-level failures fall through to the reconnect loop.
Next steps
- Tool factory - register tools before connecting.
- Python Client - same wire protocol in Python.
- Local mode -
platools servefor development without a platform.