v0.5.0MIT

Ship the agent. Keep the pager quiet.

A TypeScript agent SDK for teams putting an agent in front of real users. Define an agent, add tools, run a loop that cannot hang, get a typed result — with the production problems already solved, and nothing in your dependency tree.

$pnpm add just-another-sdk
just-another-sdk — demo
$

Why another one

The demo is the easy half. The bill, the breach, and the outage are the other one.

Every agent framework demos beautifully. Then you put one in front of real users, and the interesting problems turn out to be the unglamorous ones: a model that loops until your bill spikes, a tool exception that takes down a request, an API key that ends up in a CI log, a filesystem tool that reads one directory too far, a vendor outage with no second path, and no way to see what your agent actually did.

just-another-sdk treats those as the product, not the appendix. Every one of them is a default, a type, or a test — not something you remember to add on the Friday before launch.

Not adjectives

Every claim on this page is something you can run.

Real output, copied from actual runs in the repository. All three run offline, with no API key at all.

A vendor goes down. The run does not.

Anthropic returns 529. The retry policy backs off, gives up, and the same run continues on the next provider — one run id, one usage total, one transcript. Failing over is a line in your trace instead of a page in your incident channel.

anthropic 529 → fallbacks: [google(…)]
▶ run_msbtg147_qsa69f assistant · claude-opus-5
⟳ retry 1/2 · provider_error · waiting 181ms
⇄ fallback → gemini-2.5-pro · after provider_error
↳ get_weather {"city":"Paris"}
→ {"city":"Paris","tempC":18,"summary":"clear"} 1ms
✔ finish · 2 turns · 20 in / 10 out · 296ms

Zero dependencies

One package, and npm ls proves it. Every provider is a plain fetch call — no vendor SDK to keep in sync, nothing transitive to audit, and it runs on Node, Bun, Deno, and the edge unchanged.

A loop that cannot hang

Every exit path sets a stopReason. A model that calls tools forever costs you maxTurns requests, not your afternoon — and that budget is shared across a whole chain of agents.

Batteries, not a shopping list

Maths, time, and reasoning tools are on every agent with no import. Weather, Wikipedia, geocoding, and currency need no API key. The calculator is a real parser, so there is no eval behind it.

Dangerous things refused by default

Filesystem tools cannot leave their root, not even through a symlink. HTTP refuses private and cloud-metadata addresses even when you allow every host. Secrets never reach a log.

Delegation that cannot loop

Hand a conversation to a specialist and it stays one run — one id, one usage total, one transcript. A cycle is refused rather than followed, and the route is in the result.

Observable by construction

One typed event stream feeds tracing, metrics, and progress UIs. Every turn is recorded as it happens, so a trace is a formatter over data you already have rather than a parallel logging path.

The whole API

Four concepts. No ceremony.

An Agent is immutable configuration, so one instance safely serves every concurrent request. Run state is separate; sessions are separate again. Tools are plain functions with a schema — any Standard Schema validator, so Zod is your choice and never our dependency.

Agent
immutable config: model, tools, handoffs, policy
tool()
schema in, typed handler out
RunResult
output, steps, usage, agentPath
ModelProvider
one method: generate()
support-agent.ts
import { Agent } from 'just-another-sdk'
import { anthropic, google } from 'just-another-sdk/providers'
import { webTools } from 'just-another-sdk/tools'

const support = new Agent({
  name: 'support',
  instructions: 'Help the customer. Use your tools.',

  // Native Claude. If Anthropic is having a day,
  // the same run continues on Gemini.
  model: anthropic('claude-opus-5'),
  fallbacks: [google('gemini-2.5-pro')],

  tools: [...webTools(), issueRefund],

  // A person approves anything that moves money.
  toolGuardrails: [
    {
      name: 'confirm-refunds',
      tools: ['issue_refund'],
      check: () => ({ requireApproval: true }),
    },
  ],

  // Escalate to a specialist when it needs one.
  handoffs: [billing, technical],
})

const result = await support.run(message, { sessionId: userId })

result.output      // the answer, typed
result.agentPath   // ['support', 'billing']
result.usage       // { inputTokens: 412, … }

Three vendors, natively. The rest for free.

Claude speaks the Messages API, Gemini speaks generateContent, and OpenAI speaks Chat Completions — each a direct fetch call, not a vendor SDK. OpenRouter reaches hundreds more through one key, and the same compatible transport covers anything that speaks the OpenAI shape. Writing your own means implementing one method.

anthropic
openai
gemini
openrouter
groq
together
fireworks
deepseek
xai
ollama
vllm
lm studio

■native transport  · ■ OpenAI-compatible

Running in about two minutes.

Runnable examples ship with the repository, and several of them need no API key.

$pnpm add just-another-sdk