Build applications on top of your deployed agents. The SDK handles streaming, sessions, and error handling.
npm install @superserve/sdk
import Superserve from "@superserve/sdk"
const client = new Superserve({ apiKey: "your-api-key" })
- Node.js 18+ (uses native
fetch and ReadableStream)
- React 18+ (optional - only needed for
@superserve/sdk/react)
- Ships as both ESM and CJS - works with
import and require
Run
Send a message, get a response. Creates a session, sends the message, tears down.
const result = await client.run("code-reviewer", {
message: "Review the error handling in src/auth.ts",
})
console.log(result.text)
console.log(result.duration) // 3800
console.log(result.toolCalls) // [{ name: "read_file", input: {...}, duration: 120 }]
You can reuse an existing session instead of creating a new one:
const result = await client.run("code-reviewer", {
message: "Now fix the issues you found",
sessionId: "ses_abc123",
})
RunResult:
| Field | Type | Description |
|---|
text | string | Full response |
toolCalls | ToolCall[] | Tools invoked during the run |
duration | number | Milliseconds |
finishReason | string | "completed", "failed", or "cancelled" |
maxTurnsReached | boolean | Whether the agent hit its maximum turn limit |
RunOptions:
| Field | Type | Description |
|---|
message | string | The message to send (required) |
sessionId | string? | Reuse an existing session instead of creating a new one |
idleTimeout | number? | Session idle timeout in seconds |
Stream
Stream tokens as they arrive.
const stream = client.stream("code-reviewer", {
message: "Refactor the database connection pool",
onText: (text) => process.stdout.write(text),
onToolStart: (tool) => console.log(`-> ${tool.name}`),
onToolEnd: (tool) => console.log(`done in ${tool.duration}ms`),
onFinish: (result) => console.log(`\nDone in ${result.duration}ms`),
})
await stream.result
Or iterate over text chunks:
const stream = client.stream("code-reviewer", {
message: "Write tests for the auth module",
})
for await (const chunk of stream.textStream) {
process.stdout.write(chunk)
}
Raw event iteration
The stream is an AsyncIterable<StreamEvent>. You can iterate over all events directly for full control:
const stream = client.stream("code-reviewer", {
message: "Analyze this codebase",
})
for await (const event of stream) {
switch (event.type) {
case "text":
process.stdout.write(event.content)
break
case "tool-start":
console.log(`Using tool: ${event.name}`)
break
case "tool-end":
console.log(`Tool finished in ${event.duration}ms`)
break
case "run-completed":
console.log(`Run completed in ${event.duration}ms`)
break
case "run-failed":
console.error(`Run failed: ${event.error}`)
break
}
}
StreamEvent types:
| Type | Fields | Description |
|---|
TextEvent | content: string | A chunk of text from the agent |
ToolStartEvent | name: string, input: unknown | Agent started using a tool |
ToolEndEvent | duration: number | Tool execution completed |
RunCompletedEvent | duration: number, maxTurnsReached: boolean | Run finished successfully |
RunFailedEvent | error: string | Run failed |
AgentStream:
| Property | Type | Description |
|---|
textStream | AsyncIterable<string> | Text chunks as they arrive |
result | Promise<RunResult> | Resolves on completion |
abort() | () => void | Cancel the stream |
AgentStream can only be iterated once. If you access stream.result without iterating first, the stream is consumed automatically in the background.
Sessions
Multi-turn conversations. The agent’s workspace and memory persist between messages.
const session = await client.createSession("code-reviewer", {
title: "Auth module review",
idleTimeout: 3600, // 1 hour, in seconds
})
const r1 = await session.run("What files handle authentication?")
// "Authentication is handled in src/auth.ts and src/middleware/auth.ts..."
const r2 = await session.run("Add rate limiting to the login endpoint")
// Agent remembers context from r1
const r3 = session.stream("Now write tests for the rate limiter")
for await (const chunk of r3.textStream) {
process.stdout.write(chunk)
}
await session.end()
Session streaming also supports callbacks:
const stream = session.stream("Refactor the auth module", {
onText: (text) => process.stdout.write(text),
onToolStart: (tool) => console.log(`-> ${tool.name}`),
onToolEnd: (tool) => console.log(`done in ${tool.duration}ms`),
onFinish: (result) => console.log(`\nDone in ${result.duration}ms`),
onError: (err) => console.error(err),
})
await stream.result
SessionOptions:
| Field | Type | Description |
|---|
title | string? | A label for the session (shown in superserve sessions list) |
idleTimeout | number? | Idle timeout in seconds before the session is cleaned up |
Session:
| Property / Method | Type | Description |
|---|
id | string | Session ID |
agentId | string | The agent’s ID |
info | SessionInfo | Full session metadata |
run(message) | Promise<RunResult> | Send and wait |
stream(message, options?) | AgentStream | Send and stream (accepts callback options) |
end() | Promise<void> | End the session |
SessionInfo:
| Field | Type | Description |
|---|
id | string | Session ID |
agentId | string | Agent ID |
agentName | string? | Agent name |
status | string | "active", "idle", "completed", or "failed" |
title | string? | Session title |
messageCount | number | Number of messages in the session |
createdAt | string | ISO 8601 timestamp |
Agents
const agents = await client.agents.list()
const agent = await client.agents.get("code-reviewer")
Agent:
| Field | Type | Description |
|---|
id | string | Agent ID (e.g. agt_7c9e...) |
name | string | Agent name |
command | string | null | Start command (e.g. python agent.py) |
depsStatus | string | Dependency installation status |
depsError | string | null | Dependency error message, if any |
requiredSecrets | string[] | Secrets the agent needs |
environmentKeys | string[] | Environment variables configured |
createdAt | string | ISO 8601 timestamp |
updatedAt | string | ISO 8601 timestamp |
React
Build chat interfaces with useAgent. Manages messages, streaming, and sessions automatically.
npm install @superserve/sdk react
import { SuperserveProvider, useAgent } from "@superserve/sdk/react"
function App() {
return (
<SuperserveProvider apiKey="your-api-key">
<Chat />
</SuperserveProvider>
)
}
function Chat() {
const { messages, sendMessage, isStreaming, stop } = useAgent({
agent: "code-reviewer",
onFinish: (msg) => console.log("Agent responded:", msg.content),
onError: (err) => console.error("Error:", err),
})
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
{isStreaming && <button onClick={stop}>Stop</button>}
<form onSubmit={(e) => {
e.preventDefault()
const input = e.currentTarget.elements.namedItem("msg") as HTMLInputElement
sendMessage(input.value)
input.value = ""
}}>
<input name="msg" placeholder="Send a message..." />
</form>
</div>
)
}
useAgent options:
| Option | Type | Description |
|---|
agent | string | Agent name or ID |
apiKey | string? | API key (can also be provided via SuperserveProvider) |
baseUrl | string? | Base URL for the Superserve API |
initialMessages | Message[]? | Initial messages to populate the chat |
onFinish | (message: Message) => void | Called when the agent finishes responding |
onError | (error: Error) => void | Called on errors |
useAgent return:
| Field | Type | Description |
|---|
messages | Message[] | All messages |
sendMessage | (text: string) => void | Send a message |
status | AgentStatus | "ready", "streaming", or "error" |
isStreaming | boolean | Response in progress |
error | Error | null | Last error |
stop | () => void | Stop the current stream |
reset | () => void | Clear all messages and end the session |
Message:
| Field | Type | Description |
|---|
id | string | Unique message ID |
role | "user" | "assistant" | Who sent the message |
content | string | Message text |
toolCalls | ToolCall[]? | Tools the agent used (assistant messages only) |
createdAt | Date | When the message was created |
The hook creates the client and session lazily on the first sendMessage call. Calling sendMessage while a response is streaming is a no-op - the message is silently ignored. Calling reset() ends the underlying session and clears all messages.
Configuration
const client = new Superserve({
apiKey: "your-api-key", // required
baseUrl: "https://...", // default: https://api.superserve.ai
timeout: 30000, // ms, default: 30000
})
Errors
All errors extend SuperserveError. Network failures and API errors throw APIError with a status code.
import { APIError, SuperserveError } from "@superserve/sdk"
try {
await client.run("my-agent", { message: "Hello" })
} catch (e) {
if (e instanceof APIError) {
console.error(e.status) // HTTP status code (401, 404, 500, etc.)
console.error(e.message) // Error message from the API
console.error(e.details) // Field-level error details, if any
}
if (e instanceof SuperserveError) {
// Covers APIError and other SDK errors (e.g. aborted requests)
console.error(e.message)
}
}
Error classes:
| Class | Description |
|---|
SuperserveError | Base error class for all SDK errors |
APIError | HTTP error from the API. Has status, message, and details properties |
Common APIError status codes:
| Status | Meaning |
|---|
401 | Invalid or missing API key |
404 | Agent or session not found |
422 | Invalid request (check details for field-level info) |
0 | Network failure or request timeout |
TypeScript types
All public types are exported from @superserve/sdk:
import type {
SuperserveOptions,
RunOptions,
StreamOptions,
SessionOptions,
RunResult,
ToolCall,
StreamEvent,
TextEvent,
ToolStartEvent,
ToolEndEvent,
RunCompletedEvent,
RunFailedEvent,
Agent,
SessionInfo,
} from "@superserve/sdk"
React types are exported from @superserve/sdk/react:
import type {
Message,
AgentStatus,
UseAgentOptions,
UseAgentReturn,
SuperserveProviderProps,
} from "@superserve/sdk/react"