anoman
SDK · TypeScript

anoman-ai · TypeScript

Zero-dependency client for browser, Node, Bun, Deno, and Edge runtimes. Async iterators for streaming, AbortController-aware cancellation, batch poll helper, typed _anoman metadata.

Install

Any package manager

npm install anoman-ai

Zero runtime dependencies. ESM + CJS dual export. npm ↗

Client init

Same client, every runtime

import { AnomanClient } from "anoman-ai";

const client = new AnomanClient({
  apiKey: process.env.ANOMAN_API_KEY!,
  // baseURL defaults to https://api.anoman.io
  timeout: 120_000,    // ms
  maxRetries: 3,       // retried only on 429 / 503 / 504
});

For Cloudflare Workers / Vercel Edge / Deno, use the anoman-ai/edge entrypoint — no Node APIs.

Chat completions

Same shape as OpenAI + typed _anoman

const response = await client.chat.completions.create({
  model: "claude-sonnet-4-6",
  messages: [
    { role: "system", content: "You are a helpful assistant." },
    { role: "user", content: "What's the capital of Indonesia?" },
  ],
  temperature: 0.7,
  max_tokens: 200,
});

// Standard fields — identical to OpenAI SDK
console.log(response.choices[0].message.content);
console.log(response.usage.total_tokens);

// Anoman extension — fully typed
console.log(response.anoman.cost_usd);                  // "0.000041"
console.log(response.anoman.guardrails.injection);      // { status: "pass", score: 0.02 }
console.log(response.anoman.cache.hit);                 // false
console.log(response.anoman.routing.region);            // "id"

Full endpoint reference at /docs/endpoints/chat-completions.

Streaming

Native async iteration

const stream = await client.chat.completions.create({
  model: "claude-sonnet-4-6",
  messages: [{ role: "user", content: "Tell me a short story" }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

// The final _anoman frame is exposed as a promise on the stream
const meta = await stream.anomanMeta;
console.log(`Cost: ${meta.cost_usd}`);

Batch

Enqueue + poll in one call

const job = await client.chat.completions.create({
  model: "deepseek-v3",
  messages: [{ role: "user", content: "Summarize this 50-page doc..." }],
  preferBatch: true,        // x-anoman-prefer-batch
});

console.log(`Queued ${job.id}, SLA ${job.sla_minutes}m`);

// Poll to completion — SDK handles 202/200 + Retry-After
const result = await client.pollBatch(job.id, {
  deadlineMinutes: 30,
  onProgress: (r) => console.log(`  remaining ${r.sla_remaining_minutes}m`),
});

console.log(result.choices[0].message.content);
console.log(`Saved: $${result.anoman.savings_usd}`);

Full batch lifecycle at /docs/endpoints/batch.

Errors

Typed exception hierarchy

All errors extend AnomanError. Use instanceof to branch.

import {
  AnomanError,
  AuthError,            // 401, 403 auth_*
  BudgetExceededError,  // 402
  GuardrailError,       // 403 guardrail_*
  RateLimitError,       // 429
  ProviderError,        // 503, 504
} from "anoman-ai";

try {
  const response = await client.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: "Hi" }],
  });
} catch (err) {
  if (err instanceof GuardrailError) {
    console.log(`Blocked: ${err.code} — ${err.message}`);
  } else if (err instanceof RateLimitError) {
    console.log(`Wait ${err.retryAfter}s`);
  } else if (err instanceof ProviderError) {
    console.log(`Upstream ${err.statusCode}: ${err.message}`);
  } else if (err instanceof BudgetExceededError) {
    console.log("Out of budget");
  } else if (err instanceof AuthError) {
    console.log("Auth failed");
  } else if (err instanceof AnomanError) {
    console.log(`Unexpected: ${err}`);
  } else {
    throw err;  // not from Anoman
  }
}

Anoman-specific options

camelCase kwargs for headers

Common x-anoman-* headers exposed as typed kwargs.

const response = await client.chat.completions.create({
  model: "claude-sonnet-4-6",
  messages: [/* ... */],
  sessionId: "conv-7k4mP",      // x-anoman-session-id
  agentId: "support-bot-v3",    // x-anoman-agent-id
  metadata: { customer_tier: "enterprise" },  // surfaces in traces
});

Python instead?

Same surface area. Sync + async clients.