Files
turnstone/docs/sdk.md
T
Patrick Buckley 5ee539c983 Add Python and TypeScript client SDKs for server and console APIs (#19)
* Add Python and TypeScript client SDKs for server and console APIs

Python SDK (turnstone/sdk/) with sync + async clients for both server
and console APIs. Returns Pydantic models directly, streams SSE events
as typed dataclasses. 27 event types with registry-based deserialization.
High-level send_and_wait() for request-response patterns.

TypeScript SDK (sdk/typescript/) with zero browser dependencies. Uses
fetch + ReadableStream for SSE parsing. Discriminated union event types
with type guards. Same API surface as Python SDK.

63 Python tests, 21 TypeScript tests (vitest). Comprehensive docs at
docs/sdk.md with SDK architecture diagram.

* Address PR #19 review feedback + fix lint

- Fix consume_task leak in send_and_wait when send() raises (try/finally)
- Fix TS sendAndWait: open SSE before send, plumb AbortSignal for timeout
- Add signal param to TS streamSSE for cancellation support
- Fix SSE parser: join multi-line data: fields with \n per spec, handle CRLF
- Fix generate-types.py sys.path (parents[3] not parents[2])
- Document token ignored when httpx_client provided
- Document TS timeout units as milliseconds
- Fix stale docstring in test_sdk_sse.py
- Fix import sorting (ruff I001)
2026-03-03 21:22:24 -08:00

8.6 KiB

Turnstone Client SDK

See also: API Reference | Architecture | SDK Class Diagram

Typed HTTP client libraries for programmatic access to the turnstone server and console APIs. Available in Python (sync + async) and TypeScript.


Python SDK

The Python SDK is included in the turnstone package — no extra install required. It wraps the REST and SSE endpoints with typed methods that return Pydantic models directly.

Quick Start

from turnstone.sdk import TurnstoneServer

# Synchronous client
with TurnstoneServer("http://localhost:8080", token="tok_xxx") as client:
    # Create a workstream
    ws = client.create_workstream(name="Analysis")

    # Send a message and wait for the full response
    result = client.send_and_wait("Summarize this codebase.", ws.ws_id)
    print(result.content)

    # Stream events in real time
    for event in client.stream_events(ws.ws_id):
        if event.type == "content":
            print(event.text, end="", flush=True)

    # Close when done
    client.close_workstream(ws.ws_id)

Async Client

import asyncio
from turnstone.sdk import AsyncTurnstoneServer

async def main():
    async with AsyncTurnstoneServer("http://localhost:8080", token="tok_xxx") as client:
        ws = await client.create_workstream(name="demo")
        async for event in client.stream_events(ws.ws_id):
            if event.type == "content":
                print(event.text, end="", flush=True)

asyncio.run(main())

Server Client API

Both TurnstoneServer (sync) and AsyncTurnstoneServer (async) expose:

Category Method Returns
Workstreams list_workstreams() ListWorkstreamsResponse
dashboard() DashboardResponse
create_workstream(*, name, model, auto_approve) CreateWorkstreamResponse
close_workstream(ws_id) StatusResponse
Chat send(message, ws_id) SendResponse
approve(*, ws_id, approved, feedback, always) StatusResponse
plan_feedback(*, ws_id, feedback) StatusResponse
command(*, ws_id, command) StatusResponse
Streaming stream_events(ws_id) Iterator[ServerEvent]
stream_global_events() Iterator[ServerEvent]
High-level send_and_wait(message, ws_id, *, timeout, on_event) TurnResult
Sessions list_sessions() ListSessionsResponse
Auth login(token) AuthLoginResponse
logout() StatusResponse
Health health() HealthResponse

Console Client API

Both TurnstoneConsole (sync) and AsyncTurnstoneConsole (async) expose:

Category Method Returns
Cluster overview() ClusterOverviewResponse
nodes(*, sort, limit, offset) ClusterNodesResponse
workstreams(*, state, node, search, sort, page, per_page) ClusterWorkstreamsResponse
node_detail(node_id) NodeDetailResponse
create_workstream(*, node_id, name, model) ConsoleCreateWsResponse
Streaming stream_cluster_events() Iterator[ClusterEvent]
Auth login(token) / logout() AuthLoginResponse / StatusResponse
Health health() ConsoleHealthResponse

Event Types

SSE events are deserialized into typed dataclasses. Use event.type to discriminate.

Per-workstream events (from stream_events(ws_id)):

Type Class Key Fields
connected ConnectedEvent model, model_alias, skip_permissions
history HistoryEvent messages
content ContentEvent text
reasoning ReasoningEvent text
tool_info ToolInfoEvent items
approve_request ApproveRequestEvent items
tool_result ToolResultEvent call_id, name, output
tool_output_chunk ToolOutputChunkEvent call_id, chunk
status StatusEvent prompt_tokens, total_tokens, pct, effort
plan_review PlanReviewEvent content
error ErrorEvent message
info InfoEvent message
stream_end StreamEndEvent

Global events (from stream_global_events()):

Type Class Key Fields
ws_state WsStateEvent ws_id, state, tokens, activity
ws_activity WsActivityEvent ws_id, activity, activity_state
ws_rename WsRenameEvent ws_id, name
ws_closed WsClosedEvent ws_id

Cluster events (from stream_cluster_events()):

Type Class Key Fields
node_joined NodeJoinedEvent node_id
node_lost NodeLostEvent node_id
cluster_state ClusterStateEvent ws_id, node_id, state, tokens
ws_created ClusterWsCreatedEvent ws_id, node_id, name

TurnResult

The send_and_wait() method returns a TurnResult that aggregates the full response:

result = client.send_and_wait("Hello", ws_id, timeout=60)
result.content      # Full text response
result.reasoning    # Chain-of-thought (if shown)
result.tool_results # List of (tool_name, output) tuples
result.errors       # Any error messages
result.ok           # True if no errors and not timed out
result.timed_out    # True if timeout expired

Error Handling

Non-2xx responses raise TurnstoneAPIError:

from turnstone.sdk import TurnstoneServer, TurnstoneAPIError

try:
    client.send("hi", "bad_ws_id")
except TurnstoneAPIError as e:
    print(e.status_code)  # 404
    print(e.message)      # "Unknown workstream"

TypeScript SDK

Located at sdk/typescript/. Zero runtime dependencies for browsers; uses native fetch and ReadableStream for SSE parsing.

Quick Start

import { TurnstoneServer } from "@turnstone/sdk";

const client = new TurnstoneServer({
  baseUrl: "http://localhost:8080",
  token: "tok_xxx",
});

// Create workstream and send message
const ws = await client.createWorkstream({ name: "demo" });
const result = await client.sendAndWait("Hello!", ws.ws_id);
console.log(result.content);

// Stream events
for await (const event of client.streamEvents(ws.ws_id)) {
  if (event.type === "content") {
    process.stdout.write(event.text);
  }
}

Console Client

import { TurnstoneConsole } from "@turnstone/sdk";

const console = new TurnstoneConsole({
  baseUrl: "http://localhost:8081",
  token: "tok_xxx",
});

const overview = await console.overview();
console.log(`Nodes: ${overview.nodes}, Workstreams: ${overview.workstreams}`);

// Stream cluster events
for await (const event of console.clusterEvents()) {
  console.log(event.type, event);
}

Type Safety

All event types are modeled as a discriminated union:

import { isContentEvent, isErrorEvent } from "@turnstone/sdk";
import type { ServerEvent } from "@turnstone/sdk";

function handleEvent(event: ServerEvent) {
  if (isContentEvent(event)) {
    // event is narrowed to ContentEvent
    console.log(event.text);
  } else if (isErrorEvent(event)) {
    console.error(event.message);
  }
}

Custom Fetch

The client accepts a custom fetch implementation for testing or Node.js environments:

const client = new TurnstoneServer({
  baseUrl: "http://localhost:8080",
  fetch: myCustomFetch,
});

Architecture

turnstone/sdk/               Python SDK (sub-package)
  _base.py                   Shared httpx async client, auth, error handling
  _sync.py                   Background event loop for sync wrappers
  _types.py                  TurnResult + TurnstoneAPIError
  events.py                  27 SSE event dataclasses with type registry
  server.py                  AsyncTurnstoneServer + TurnstoneServer
  console.py                 AsyncTurnstoneConsole + TurnstoneConsole

sdk/typescript/              TypeScript SDK (npm package)
  src/base.ts                fetch wrapper, auth, SSE streaming
  src/server.ts              TurnstoneServer class
  src/console.ts             TurnstoneConsole class
  src/events.ts              Discriminated union events + type guards
  src/sse.ts                 ReadableStream SSE parser
  src/types.ts               Request/response interfaces

The Python SDK reuses Pydantic models from turnstone/api/ directly — no schema duplication. The TypeScript SDK has hand-written interfaces matching those models.

Both SDKs follow the same design: typed methods for REST endpoints, async iterators for SSE streams, and a high-level send_and_wait method for simple request-response patterns.