Skip to content

feat(ts): native TypeScript server — parity with the Rust/C# server#60

Merged
brentrager merged 1 commit into
mainfrom
server-ts
Jun 24, 2026
Merged

feat(ts): native TypeScript server — parity with the Rust/C# server#60
brentrager merged 1 commit into
mainfrom
server-ts

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

What this builds

A native TypeScript server for the smooth-operator wire protocol — the missing polyglot-server piece (until now only Rust + C# had servers; TS/Py/Go were clients only). New package typescript/server/ (@smooai/smooth-operator-server); the published client in typescript/ is untouched.

It speaks the same protocol (spec/) and runs the published @smooai/smooth-operator-core engine in-process, one SmoothAgent per turn.

Ported file-by-file from the C# server, behavior verified against the Rust reference:

TypeScript ← ported from (C#) ← verified against (Rust)
protocol.ts ProtocolEvents.cs protocol.rs
sessionStore.ts SessionStore.cs StorageAdapter session surface
auth.ts Auth.cs verifier seam + LocalTokenVerifier
turnRunner.ts TurnRunner.cs run_streaming_turn (runner.rs)
frameDispatcher.ts FrameDispatcher.cs handle_frame (handler.rs)
server.ts SmoothOperatorWebSocketExtensions.cs server.rs loop + local.rs
backplane.ts backplane attach/detach seam

Done

  • WS transport (ws): per-connection read loop + a single outbound writer (one socket = one writer; ws.send never concurrent). A level-triggered signal makes the writer/reader lost-wakeup-safe.
  • FrameDispatcher — validates + routes create_conversation_session → SessionStore, send_message (with requestId) → TurnRunner, ping → pong. Unknown/invalid frames error without dropping the connection.
  • SessionStore — in-memory sessions + conversation message logs (async interface), with prior-history replay onto the engine thread.
  • TurnRunner — runs SmoothAgent.runStream, maps StreamEvents (textstream_token, tool_call/tool_resultstream_chunk with the isError convention) onto protocol events, and returns the final reply + auto-context citations.
  • Auth verifier seamnone (default) / trusted (base64url JSON) / jwt (HS256). Fail-closed: anything missing/malformed/expired → anonymous.
  • Graceful SIGTERM/SIGINT drain — a shared AbortController on server state (single source, default uncancelled); each connection's read loop races "cancel fired" vs "next inbound frame" with the turn dispatch awaited inside the frame branch so an in-flight turn finishes (prefer cancel on ties); detach-after-loop always runs the backplane deregister.
  • serveLocal() embeddable entrypoint + a main.ts binary (in-memory local flavor; SMOOAI_GATEWAY_URL/SMOOAI_GATEWAY_KEY enable live turns, keyless errors cleanly — mirrors the Rust path).

Stubbed / follow-on

  • HITL tool-confirm / OTP / write-confirmation events — the turn seam exists; the confirm round-trip is follow-on.
  • Reranker stage on the citation path — the engine supports a reranker; not yet surfaced as a server knob.
  • ACL-filtered knowledge (AccessKnowledge.forAccess) — seam present; the MVP provider is unscoped, a group-filtered view drops in.
  • Cross-pod backplane (Redis/NATS) — in-memory attach/detach stub; the seam is wired.
  • Durable (Postgres/Dynamo) SessionStore — implement the SessionStore interface.

Tests — 27, all green

  • Protocol conformance (8) — round-trips the spec/conformance/fixtures.json golden messages (create/response, send, stream_chunk, eventual_response with + without citations) through the protocol builders; matches the contract every server speaks.
  • Boot (3) — serveLocal() starts on an ephemeral port + accepts a connection; unknown-action and invalid-JSON error without dropping the socket.
  • Turn round-trip over a real WebSocket (5) — ping→create→send_message→202 ack→stream_token(s)→eventual_response; tool-call + tool-result chunks; citations from scoped knowledge (url present/omitted); multi-turn history replay; unknown-session error. (Mirrors WebSocketProtocolIntegrationTests.cs.)
  • Graceful drain (2) — an in-flight turn finishes after cancel fires, then the loop exits and detach runs; an idle connection drains immediately.
  • Auth seam (9) — none/trusted/jwt, fail-closed on bad signature/expiry/malformed.
pnpm --filter @smooai/smooth-operator-server typecheck
pnpm --filter @smooai/smooth-operator-server test

Workspace-wide pnpm -r typecheck and pnpm -r test are green (the published client's 26 tests still pass). The package is private (not published); version pinned to the lockstep 1.2.0.

🤖 Generated with Claude Code

…consuming @smooai/smooth-operator-core

Adds typescript/server/ (@smooai/smooth-operator-server): a WebSocket service
that speaks the smooth-operator wire protocol and runs the published
@smooai/smooth-operator-core engine in-process per turn. The TS sibling of
rust/smooth-operator-server and dotnet/server; the published client in
typescript/ is untouched.

Ported file-by-file from the C# server, verified against the Rust reference:
- protocol.ts        ← ProtocolEvents.cs / protocol.rs (byte-for-byte shapes,
                       incl. the triple-nested eventual_response.data.data)
- sessionStore.ts    ← SessionStore.cs (in-memory sessions + message logs)
- auth.ts            ← Auth.cs + Rust LocalTokenVerifier (none/trusted/jwt,
                       fail-closed)
- turnRunner.ts      ← TurnRunner.cs / run_streaming_turn (SmoothAgent.runStream
                       → stream_token + stream_chunk; auto-context citations)
- frameDispatcher.ts ← FrameDispatcher.cs / handle_frame
- server.ts          ← SmoothOperatorWebSocketExtensions.cs + Rust server loop +
                       local.rs: per-connection read loop, single outbound writer,
                       graceful SIGTERM/SIGINT drain (shared AbortController; turn
                       awaited inside the frame branch so in-flight turns finish;
                       detach-after-loop), serveLocal() embeddable entrypoint
- backplane.ts       ← Rust backplane attach/detach seam (in-memory stub)

Tests (27, all green): protocol conformance round-tripping the
spec/conformance/fixtures.json golden messages; boot; turn round-trip over a real
WebSocket (tokens, tool chunks, citations, multi-turn history replay); graceful
drain (in-flight turn finishes after cancel, then detach runs); the auth seam.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7Mn93HpqhSgEmX6tRdPAv
@changeset-bot

changeset-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0fee670

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentrager
brentrager merged commit 0f58009 into main Jun 24, 2026
brentrager added a commit that referenced this pull request Jun 24, 2026
… shared parity corpus (#69)

The TS/Python/Go servers landed (#59/#60/#61) and all five now run the shared
spec/conformance/scenarios corpus (#62-#67) — tested protocol parity. Updates the
servers row + tests section from 'in progress' to the real state.


Claude-Session: https://claude.ai/code/session_01U7Mn93HpqhSgEmX6tRdPAv

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant