Introduction
TypeScript agent SDK purpose-built for Azure OpenAI
Stratus is a TypeScript agent SDK purpose-built for Azure OpenAI.
One run() call handles the entire tool loop. The types are strict. The API is small.
- One line to start —
createModel()reads your env vars. No config objects, no API version guessing. - One interface, two backends — Chat Completions and Responses API through the same agent, tool, and session code.
- Agents that compose — handoffs, subagents, guardrails, and hooks in a single run loop. Deny or modify tool calls at runtime.
- Human-in-the-loop — permission callbacks, per-tool approval, glob-filtered tools, and graceful mid-run interrupts.
- State you own — save, resume, and fork conversations as JSON. No server-side threads.
- Type-safe end to end — Zod schemas drive tool parameters, structured output, and validation. Types flow through agents, hooks, and guardrails at compile time.
- Zero dep — only Zod as a peer dep.
Why this exists
Azure's v1 API lets you use the standard OpenAI() client, point it at your endpoint, and things mostly work. But "mostly" breaks down fast in production.
The OpenAI SDK gives you chat.completions.create(). Everything else is on you:
- Tool calling is a manual loop. You call the model, check for tool calls, execute them, append the results, call the model again. Stratus does all of that in one
run()call with parallel execution and error recovery. - No agent abstraction. You're passing around message arrays. Stratus gives you agents — instructions, tools, guardrails, and handoffs in a single config object.
- Streaming is bare. You get raw SSE chunks. Stratus gives you typed stream events —
content_delta,tool_call_start,tool_call_done— with aRunResultat the end. - Content filter errors are buried. Azure nests them inside
inner_error.content_filter_results. Stratus throws a typedContentFilterError. - Multi-agent orchestration doesn't exist. Handoffs, subagents, guardrails, and hooks are first-class in Stratus.
- Both APIs, one interface. Chat Completions and Responses API through the same
Modelinterface. Swap with one line.
And these are things other agent SDKs don't do at all:
- Budget enforcement. Set
maxBudgetUsdand the run stops before you get a surprise bill. Not after. - Hook modify. Intercept tool calls, rewrite their arguments, or deny them entirely. Per-tool pattern matching included.
- Todo tracking. Agents report structured progress in real-time via
TodoList. Your UI updates as they work, not after. - Session fork. Branch a conversation with one call. Try a different strategy without losing the original.
- Typed context end-to-end. One context type flows through tools, hooks, guardrails, and subagents. TypeScript generics, not
any. - Test utilities built in.
createMockModel()and response builders ship as@usestratus/sdk/testing. No reverse-engineering the mock pattern. - Debug mode.
{ debug: true }logs model calls, tool executions, and handoffs to stderr. Zero overhead when off.
Features
Agents
Define agents with instructions, tools, and model settings
Sessions
Multi-turn conversations with save/resume/fork
Tools
Type-safe tool definitions with Zod schema validation
Built-in Tools
Server-side web search, code interpreter, MCP, and image generation
Subagents
Delegate work to child agents as tool calls
Streaming
Real-time streaming with abort signal cancellation
Structured Output
Parse model output into typed objects via Zod
Handoffs
Route conversations between specialized agents
Hooks
Lifecycle callbacks with permission control (allow/deny/modify)
Guardrails
Input and output validation with tripwire support
Tracing
Built-in span-based tracing via AsyncLocalStorage
Usage & Cost Tracking
Built-in cost estimation, budget limits, and reasoning token tracking
Quick Example
import { createModel, createSession, tool } from "@usestratus/sdk";
import { z } from "zod";
const model = createModel(); // reads AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT
const getWeather = tool({
name: "get_weather",
description: "Get current weather for a city",
parameters: z.object({ city: z.string() }),
execute: async (_ctx, { city }) => `72°F and sunny in ${city}`,
});
await using session = createSession({
model,
instructions: "You are a weather assistant.",
tools: [getWeather],
});
session.send("What's the weather in NYC?");
for await (const event of session.stream()) {
if (event.type === "content_delta") process.stdout.write(event.content);
}
// Multi-turn: context persists automatically
session.send("What about London?");
const result = await session.wait(); // no need to drain the stream manually
console.log(result.output);Architecture
Stratus is organized into two layers:
| Import path | Description |
|---|---|
@usestratus/sdk | Re-exports core + Azure OpenAI implementation |
@usestratus/sdk/core | Provider-agnostic: Agent, Session, run loop, tools, handoffs, guardrails, hooks, tracing |
@usestratus/sdk/azure | Azure models + createModel() factory |
@usestratus/sdk/testing | Mock model, response builders — keep out of production bundles |
The core layer defines the Model interface. Azure is the built-in implementation, but you can plug in any provider by implementing Model.
Guides
End-to-end examples showing how to combine features into real agents:
Agentic Tool Use
Tool loops, parallel calls, context, streaming, and control
Real-Time Streaming
Stream to CLI, SSE endpoints, and multi-turn sessions
Customer Support Agent
Multi-agent triage with handoffs, hooks, and guardrails
Research Agent
Orchestrate subagents for web research and data analysis
Data Extraction Pipeline
Structured output with validation guardrails and batch processing
Project Structure
Last updated on