@smooai/smooth-operator
v1.23.0
Published
TypeScript SDK for the smooth-operator WebSocket protocol: the native client (`.`), React bindings (`./react`), and the embeddable web-component chat widget (`./widget`). Generated from the language-neutral JSON Schemas in spec/.
Downloads
11,561
Maintainers
Readme
What is this?
The native TypeScript client for the smooth-operator WebSocket protocol — and the one the smooai monorepo dogfoods. It connects to a running smooth-operator service (create a session, send a message, stream the agent's events back) — not the agent engine itself. Types are generated from the language-neutral JSON Schemas in spec/ (and committed, so consumers don't need the generator), with an ergonomic layer — discriminated unions + type guards — on top. It's Lambda-native and transport-injectable, so it runs in a browser, on Node, or inside a Lambda handler unchanged.
30-second quickstart
pnpm add @smooai/smooth-operatorRequires Node ≥ 22, ESM only.
One package, three subpath exports
@smooai/smooth-operator is the whole TypeScript SDK — install once, import the
layer you need. react / react-dom are optional peer deps (only the
./react subpath needs them), so a client-only or widget-only consumer never
pulls React into their bundle.
| Import | What |
| --- | --- |
| @smooai/smooth-operator | the protocol client (SmoothAgentClient, streaming turns, HITL) |
| @smooai/smooth-operator/react | React bindings — useConversation hook + <SmoothChat> (see the guide) |
| @smooai/smooth-operator/react/styles.css | default stylesheet for the React components |
| @smooai/smooth-operator/widget | the embeddable web-component chat widget (mountChatWidget, <smooth-agent-chat>) |
| @smooai/smooth-operator/widget/standalone | the prebuilt IIFE bundle for a no-build <script> embed |
| @smooai/smooth-operator/validate | the Node-only ProtocolValidator (pulls ajv + node:fs; keep it off the browser path) |
import { SmoothAgentClient } from '@smooai/smooth-operator';
const client = new SmoothAgentClient({ url: 'ws://127.0.0.1:8787/ws' });
await client.connect();
const session = await client.createConversationSession({ agentId, userName: 'Alice' });
const turn = client.sendMessage({ sessionId: session.sessionId, message: 'How long is your return window?' });
const final = await turn; // EventualResponse — cost, tokens, messageId
console.log(final.data.payload.messageId);(Point url at your own smooth-operator-server, or at the hosted endpoint.)
Watch it stream
sendMessage returns a MessageTurn that is both an async-iterable of events and awaitable for the authoritative terminal state. Iterate tokens as they arrive; await the same handle for the final response.
const turn = client.sendMessage({ sessionId: session.sessionId, message: 'Where is my order?' });
for await (const ev of turn) {
if (ev.type === 'stream_chunk') console.error(` ↳ node: ${ev.node}`); // workflow node boundary
if (ev.type === 'stream_token') process.stdout.write(ev.token ?? ''); // tokens, live
if (ev.type === 'write_confirmation_required') {
// HITL: a tool wants to write. Approve, and the resumed stream flows back into THIS turn.
client.confirmToolAction({ sessionId: session.sessionId, requestId: turn.requestId, approved: true });
}
}
const final = await turn; // EventualResponse — the authoritative terminal state%%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','actorBkg':'#0b1426','actorBorder':'#2b3a52','actorTextColor':'#e6edf6','signalColor':'#7c8aa0','signalTextColor':'#e6edf6','noteBkgColor':'#f49f0a','noteTextColor':'#1a0f00','noteBorderColor':'#ff6b6c','fontFamily':'ui-sans-serif, system-ui, sans-serif'}}}%%
sequenceDiagram
participant App
participant C as SmoothAgentClient
participant S as Service
App->>C: sendMessage(...)
C->>S: { action: send_message }
S-->>C: immediate_response (202)
S-->>C: stream_token "Our" "return" "window" …
S-->>C: stream_chunk { node: response_gen }
S-->>C: eventual_response (200)
C-->>App: for-await yields events · await resolves finalTransport injection
The client never touches a real socket directly — it talks to an injectable Transport. The default uses the global WebSocket. On Node, inject the ws package; in tests, inject a mock — which is how the conformance suite exercises real client code (correlation, parsing, HITL routing) without a network.
import WebSocket from 'ws';
new SmoothAgentClient({ url, webSocketFactory: (u) => new WebSocket(u) });Runtime validation (optional, Node-only)
import { ProtocolValidator } from '@smooai/smooth-operator';
const v = await ProtocolValidator.load();
v.validateEvent(incomingEvent); // { valid, errors } — ajv-compiled from the spec schemasPolyglot — one spec, five clients
This is one of five native clients generated from the same protocol. Need C# / Microsoft.Extensions.AI? The IChatClient facade lives in the .NET client (it's a .NET-ecosystem feature). This TypeScript package is the native streaming client.
%%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
SPEC["spec/ (JSON Schema)"] --> TS["TypeScript<br/>@smooai/smooth-operator"]
SPEC --> GO["Go"]
SPEC --> NET[".NET (+ MEAI IChatClient facade)"]
SPEC --> PY["Python"]
SPEC --> RS["Rust"]Test-driven by default
Nothing here is vibe-coded — it's verified against a real LLM gateway.
%%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart TD
J["🎯 LLM-as-judge quality evals (Rust harness)"]
E["🌐 Live cross-language E2E — this client boots the real server + drives a real claude-haiku-4-5 turn"]
C["🧪 Conformance fixtures (shared across all 5 clients)"]
U["⚡ Unit + type-level tests (discrimination, guards, correlation)"]
J --> E --> C --> U16 tests cover the conformance fixtures, the client (with a mock transport so real parsing/correlation/HITL run), and type-level checks. In the live cross-language E2E, this client boots a real smooth-operator-server subprocess (KB seeded), drives a real claude-haiku-4-5 turn over WebSocket, and asserts ≥1 streamed event, a knowledge-grounded "17", and per-session memory.
The proof story: an LLM-as-judge scored a multi-turn answer 1/5 (the runtime forgot turn 1's context); the failing eval drove a per-session-memory fix; it now scores 5/5 — a regression a substring test would have missed. See docs/EVALS.md.
Live tests are gated, never silently skipped: they run with SMOOTH_AGENT_E2E=1 + SMOOAI_GATEWAY_KEY and skip cleanly otherwise.
pnpm test # conformance + client + type-level — no creds
pnpm test:e2e # live cross-language E2E (needs gateway key)Scripts
| Script | Purpose |
| --- | --- |
| pnpm generate | Regenerate src/generated/types.ts from ../spec. |
| pnpm build | tsc → dist/. |
| pnpm typecheck | Type-check src/ + test/ without emitting. |
| pnpm test | Vitest (conformance + client + type-level). |
The generated types are committed; CI runs pnpm generate + git diff --exit-code to catch schemas that changed without a regenerate.
Smoo-powered or bring-your-own
Point the client at the hosted lom.smoo.ai endpoint, or at your own self-hosted smooth-operator-server (AWS Lambda or k8s) — same protocol, same client, same code.
🧩 Part of Smoo AI
@smooai/smooth-operator is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product. It's the TypeScript member of the polyglot SDK set (TypeScript · Python · Go · .NET · Rust) for the smooth-operator service.
- 🌐 The service — smooth-operator (protocol, server, the five clients, AWS/k8s deploy)
- 🧰 More open source from Smoo AI — smoo.ai/open-source
- ☁️ Hosted — lom.smoo.ai runs smooth-operator for you, managed and multi-tenant
🔗 Links
- 📦 npm —
@smooai/smooth-operator - 🛰️ Protocol —
docs/PROTOCOL.md - 🧪 Evals —
docs/EVALS.md - 💬 Issues — github.com/SmooAI/smooth-operator/issues
📄 License
MIT © 2026 Smoo AI. See LICENSE.
