npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usestratus/sdk

v1.5.0

Published

<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset=".github/logo-dark.svg"> <source media="(prefers-color-scheme: light)" srcset=".github/logo.svg"> <img src=".github/logo.svg" alt="Stratus" width="80" height="8

Readme

Stratus

usestratus.dev

npm version CI

A better TypeScript agent SDK for Azure OpenAI. Build multi-agent systems with tools, handoffs, guardrails, streaming, structured output, and more.

  • Built for Azure, not bolted on — auto-endpoint detection, Entra ID auth, content filter errors as typed exceptions, and built-in retry. No 404 config spirals, no "which SDK do I use" confusion.
  • Two API backends, one interface — Chat Completions and Responses API through the same agent, tool, and session code. Start with one, switch with a single line.
  • Multi-agent orchestration — handoffs, subagents, guardrails, and hooks compose through a single run loop. Hooks can deny or modify tool calls at runtime.
  • Human-in-the-loopcanUseTool permission callbacks, needsApproval per tool, allowedTools glob filtering, and graceful interrupt() for stopping runs mid-flight.
  • Client-side state you control — save, resume, and fork conversations as portable JSON snapshots. Hot-swap tools mid-session. No server-side threads, no opaque session IDs.
  • Type-safe from schema to output — Zod schemas drive tool parameters, structured output, and validation. Context types flow through agents, hooks, and guardrails at compile time.
  • Zero dependencies — only Zod as a peer dep. No transitive dependency sprawl, no framework lock-in.

agents tools streaming structured output handoffs subagents guardrails hooks tracing sessions abort signals code mode todo tracking cost tracking human-in-the-loop predicted output audio data sources context compaction background tasks testing utilities debug mode

Install

bun add @usestratus/sdk

Stratus requires Zod as a peer dependency:

bun add zod

Quick Start

import { z } from "zod";
import { Agent, createModel, run, tool } from "@usestratus/sdk";

const model = createModel(); // reads AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_OPENAI_DEPLOYMENT

const getWeather = tool({
  name: "get_weather",
  description: "Get the current weather for a city",
  parameters: z.object({
    city: z.string().describe("The city name"),
  }),
  execute: async (_ctx, { city }) => {
    return `72°F and sunny in ${city}`;
  },
});

const agent = new Agent({
  name: "weather-assistant",
  instructions: "You are a helpful weather assistant.",
  model,
  tools: [getWeather],
});

const result = await run(agent, "What's the weather in New York?");
console.log(result.output);

Core Concepts

Agents

Agents are the primary building block. Each agent has a name, instructions, a model, and optional tools, handoffs, guardrails, and hooks.

const agent = new Agent({
  name: "my-agent",
  instructions: "You are a helpful assistant.",
  model,
  tools: [myTool],
});

// Dynamic instructions based on context
const agent = new Agent({
  name: "my-agent",
  instructions: (ctx) => `You are helping ${ctx.userName}.`,
  model,
});

Tools

Define tools with Zod schemas for type-safe parameter validation:

const searchTool = tool({
  name: "search",
  description: "Search for information",
  parameters: z.object({
    query: z.string().describe("Search query"),
    limit: z.number().optional().describe("Max results"),
  }),
  execute: async (context, { query, limit }) => {
    // Tool logic here
    return "search results";
  },
});

Streaming

Stream responses token-by-token:

const { stream: s, result } = stream(agent, "Tell me a story");

for await (const event of s) {
  if (event.type === "content_delta") {
    process.stdout.write(event.content);
  } else if (event.type === "tool_call_start") {
    console.log(`Calling: ${event.toolCall.name}`);
  }
}

const finalResult = await result;

Structured Output

Use Zod schemas to get typed, validated output:

const PersonSchema = z.object({
  name: z.string(),
  age: z.number(),
  occupation: z.string(),
});

const agent = new Agent({
  name: "extractor",
  instructions: "Extract person information.",
  model,
  outputType: PersonSchema,
});

const result = await run(agent, "Marie Curie was a 66-year-old physicist.");
console.log(result.finalOutput); // { name: "Marie Curie", age: 66, occupation: "physicist" }

Sessions

Sessions maintain conversation history across multiple interactions:

import { createSession } from "@usestratus/sdk";

const session = createSession({ model, tools: [myTool] });

// Option 1: stream events
session.send("Hello!");
for await (const event of session.stream()) {
  // handle events
}

// Option 2: just get the result
session.send("Follow-up question");
const result = await session.wait();

// Save and resume sessions
const snapshot = session.save();
const resumed = resumeSession(snapshot, { model });

// Fork a session (new ID, same history)
const forked = forkSession(snapshot, { model });

// Cleanup
session.close();
// Or use Symbol.asyncDispose:
await using session = createSession({ model });

Handoffs

Transfer control between specialized agents:

import { handoff } from "@usestratus/sdk";

const orderAgent = new Agent({
  name: "order_specialist",
  instructions: "Help with order inquiries.",
  model,
  tools: [lookupOrder],
  handoffDescription: "Transfer for order questions",
});

const triageAgent = new Agent({
  name: "triage",
  instructions: "Route to the right specialist.",
  model,
  handoffs: [
    orderAgent, // shorthand
    handoff({    // with options
      agent: refundAgent,
      onHandoff: () => console.log("Transferring..."),
    }),
  ],
});

const result = await run(triageAgent, "Where is my order?");
console.log(result.lastAgent.name); // "order_specialist"

Subagents

Delegate subtasks to child agents that run independently:

import { subagent } from "@usestratus/sdk";

const researcher = new Agent({
  name: "researcher",
  instructions: "Research topics thoroughly.",
  model,
});

const parentAgent = new Agent({
  name: "parent",
  instructions: "Use the researcher for deep dives.",
  model,
  subagents: [
    subagent({
      agent: researcher,
      inputSchema: z.object({ topic: z.string() }),
      mapInput: ({ topic }) => `Research: ${topic}`,
    }),
  ],
});

Guardrails

Validate inputs and outputs with guardrails:

import type { InputGuardrail, OutputGuardrail } from "@usestratus/sdk";

const profanityFilter: InputGuardrail = {
  name: "profanity_filter",
  execute: (input) => ({
    tripwireTriggered: containsProfanity(input),
    outputInfo: "Blocked by profanity filter",
  }),
};

const piiFilter: OutputGuardrail = {
  name: "pii_filter",
  execute: (output) => ({
    tripwireTriggered: /\d{3}-\d{2}-\d{4}/.test(output),
    outputInfo: "Output contained PII",
  }),
};

const agent = new Agent({
  name: "guarded",
  model,
  inputGuardrails: [profanityFilter],
  outputGuardrails: [piiFilter],
});

Guardrails run in parallel. When a tripwire is triggered, an InputGuardrailTripwireTriggered or OutputGuardrailTripwireTriggered error is thrown.

Hooks

Lifecycle hooks for observability and control:

import type { AgentHooks } from "@usestratus/sdk";

const hooks: AgentHooks = {
  beforeRun: ({ agent, input }) => { /* ... */ },
  afterRun: ({ agent, result }) => { /* ... */ },

  // Return a decision to allow, deny, or modify tool calls
  beforeToolCall: ({ toolCall }) => {
    if (toolCall.function.name === "dangerous_tool") {
      return { decision: "deny", reason: "Not allowed" };
    }
    return { decision: "allow" };
  },
  afterToolCall: ({ toolCall, result }) => { /* ... */ },

  // Allow or deny handoffs
  beforeHandoff: ({ fromAgent, toAgent }) => {
    return { decision: "allow" };
  },
};

Tracing

Opt-in tracing with zero overhead when inactive:

import { withTrace } from "@usestratus/sdk";

const { result, trace } = await withTrace("my-workflow", () =>
  run(agent, "Hello"),
);

console.log(trace.id);
console.log(trace.duration);
for (const span of trace.spans) {
  console.log(`[${span.type}] ${span.name} (${span.duration}ms)`);
  // span.type: "model_call" | "tool_execution" | "handoff" | "guardrail" | "subagent" | "custom"
}

Abort Signals

Cancel runs with AbortSignal:

const controller = new AbortController();

setTimeout(() => controller.abort(), 5000);

try {
  const result = await run(agent, "Long task...", {
    signal: controller.signal,
  });
} catch (error) {
  if (error instanceof RunAbortedError) {
    console.log("Run was cancelled");
  }
}

Todo Tracking

Track task progress during agent execution:

import { todoTool, TodoList } from "@usestratus/sdk";

const todos = new TodoList();
todos.onUpdate((items) => {
  for (const item of items) {
    const icon = item.status === "completed" ? "+" : item.status === "in_progress" ? ">" : "-";
    console.log(`${icon} ${item.content}`);
  }
});

const agent = new Agent({
  name: "planner",
  instructions: "Break tasks into steps and track progress with todo_write.",
  model,
  tools: [todoTool(todos)],
});

await run(agent, "Set up a new TypeScript project");

Usage & Cost Tracking

Track token usage and estimate costs:

import { createCostEstimator } from "@usestratus/sdk";

const estimator = createCostEstimator({
  inputTokenCostPer1k: 0.01,
  outputTokenCostPer1k: 0.03,
});

const result = await run(agent, "Hello", { costEstimator: estimator });
console.log(result.usage.totalTokens); // token counts
console.log(result.totalCostUsd);      // estimated cost
console.log(result.numTurns);          // model call count

// Set budget limits
const result = await run(agent, "Hello", {
  costEstimator: estimator,
  maxBudgetUsd: 0.50, // throws MaxBudgetExceededError if exceeded
});

Tool Choice & Tool Use Behavior

Control how the model uses tools:

const agent = new Agent({
  name: "my-agent",
  model,
  tools: [myTool],
  modelSettings: {
    // "auto" | "none" | "required" | { type: "function", function: { name: "..." } }
    toolChoice: "required",
  },
  // "run_llm_again" (default) | "stop_on_first_tool" | { stopAtToolNames: ["..."] }
  toolUseBehavior: "stop_on_first_tool",
});

Code Mode (Experimental)

Let LLMs write code that orchestrates multiple tools instead of calling them one at a time. Inspired by Cloudflare's Code Mode — LLMs are better at writing code than making individual tool calls.

import { createCodeModeTool, FunctionExecutor } from "@usestratus/sdk/core";

const executor = new FunctionExecutor({ timeout: 30_000 });
const codemode = createCodeModeTool({
  tools: [getWeather, sendEmail, lookupOrder],
  executor,
});

const agent = new Agent({
  name: "assistant",
  model,
  tools: [codemode],
});

// The LLM writes code like:
// async () => {
//   const weather = await codemode.get_weather({ location: "London" });
//   if (weather.temp > 60) {
//     await codemode.send_email({ to: "[email protected]", subject: "Nice day!", body: ... });
//   }
//   return { weather, notified: true };
// }

createCodeModeTool generates TypeScript types from your tools, presents the LLM with a single execute_code tool, and runs the generated code in an executor. All tool calls happen within one invocation — no round-trips through the model between calls.

Two built-in executors:

  • FunctionExecutor — fast, same-process (NOT sandboxed)
  • WorkerExecutor — isolated via worker_threads (separate V8 context, no host access)

Implement the Executor interface for custom sandboxes (containers, Cloudflare Workers, etc.).

Imports

Stratus provides four export paths:

// Everything (core + Azure)
import { Agent, run, tool, createModel } from "@usestratus/sdk";

// Core only (provider-agnostic)
import { Agent, run, tool, validateAgent } from "@usestratus/sdk/core";

// Azure provider only
import { createModel, AzureResponsesModel } from "@usestratus/sdk/azure";

// Test utilities (keep out of production bundles)
import { createMockModel, textResponse, toolCallResponse } from "@usestratus/sdk/testing";

Configuration

Azure OpenAI

The fastest way — createModel() reads from environment variables:

import { createModel } from "@usestratus/sdk";

const model = createModel();                    // Responses API (default)
const model = createModel("chat-completions");  // Chat Completions API

Or configure explicitly:

import { AzureResponsesModel } from "@usestratus/sdk";

const model = new AzureResponsesModel({
  endpoint: "https://your-resource.openai.azure.com",
  apiKey: process.env.AZURE_OPENAI_API_KEY!,
  deployment: "gpt-5.2",
});

Both models implement the same Model interface — swap one for the other without changing any agent, tool, or session code.

Environment Variables

AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com
AZURE_OPENAI_API_KEY=your-api-key
AZURE_OPENAI_DEPLOYMENT=gpt-5.2

Testing

Stratus ships test utilities as a separate entrypoint:

import { createMockModel, textResponse, toolCallResponse } from "@usestratus/sdk/testing";

const model = createMockModel([
  toolCallResponse([{ name: "search", args: { q: "test" } }]),
  textResponse("Found 3 results"),
]);

const agent = new Agent({ name: "test", model, tools: [searchTool] });
const result = await run(agent, "Search for test");
expect(result.output).toBe("Found 3 results");

// Capture requests for assertions
const model = createMockModel([textResponse("ok")], { capture: true });
await run(agent, "Hello");
expect(model.requests[0].messages[0].content).toBe("Hello");

Debug Mode

Log model calls, tool executions, and handoffs to stderr:

const result = await run(agent, "Hello", { debug: true });
// [stratus:model] 2026-04-02T... request to assistant {"messages":2,"tools":1,"turn":0}
// [stratus:model] 2026-04-02T... response from assistant {"content":"Hi!","toolCalls":[],...}

Also works on sessions: createSession({ model, debug: true }).

Error Handling

All errors extend StratusError:

| Error | Description | |---|---| | StratusError | Base error class | | ModelError | API call failures (includes status and code) | | ContentFilterError | Content filtered by Azure's content management policy | | MaxTurnsExceededError | Agent exceeded the maxTurns limit | | OutputParseError | Structured output failed Zod validation | | RunAbortedError | Run cancelled via AbortSignal | | InputGuardrailTripwireTriggered | Input guardrail blocked the request | | OutputGuardrailTripwireTriggered | Output guardrail blocked the response |

import { ModelError, MaxTurnsExceededError, RunAbortedError } from "@usestratus/sdk";

try {
  await run(agent, input);
} catch (error) {
  if (error instanceof MaxTurnsExceededError) {
    // Agent ran too many turns
  } else if (error instanceof ModelError) {
    console.log(error.status, error.code);
  }
}

Packages

Stratus is a monorepo with two packages:

| Package | Description | |---|---| | @usestratus/sdk | Agent SDK for Azure OpenAI (this README) |

Development

bun install       # Install all workspace dependencies
bun test          # Run tests (all packages)
bun run lint      # Lint with Biome
bun run typecheck # TypeScript type checking