Skip to content

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 to ws:// / 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 until stop() is called or the embedder aborts the process; the helper backoffDelayMs(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 socket

The 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:

DirectionMessagePurpose
SDK -> platformtool_registerFull tool list after every (re)connect.
SDK -> platformheartbeatEvery 30s.
SDK -> platformtool_resultSuccess - includes latency_ms.
SDK -> platformtool_errorError - includes message + optional stack.
platform -> SDKwelcomeHandshake ack.
platform -> SDKheartbeat_ackHeartbeat ack.
platform -> SDKtool_callcallId, 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