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

claude-team-agent-sdk

v0.9.10

Published

[agent-like]Lightweight Claude agent SDK with custom tools and streaming events

Readme

Claude Agent SDK

A lightweight npm package for building Claude-powered agents without installing the Claude Code CLI runtime.

This first version supports Anthropic direct API calls, an in-memory agent loop, custom tools, permission callbacks, and stable SDK-style events.

Install

npm install claude-team-agent-sdk zod

Minimal Usage

The examples use DeepSeek's Anthropic-compatible endpoint.

import { createAgent } from "claude-team-agent-sdk";

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
});

for await (const message of agent.query("Say hello")) {
  console.log(message);
}

Pass { stream: false } to disable model streaming for a query:

const result = await agent.prompt("Say hello", { stream: false });

Multimodal Input

Pass Anthropic-compatible content blocks for image or document prompts:

const result = await agent.prompt([
  { type: "text", text: "Summarize this screenshot." },
  {
    type: "image",
    source: {
      type: "base64",
      media_type: "image/png",
      data: imageBase64,
    },
  },
]);

console.log(result.result);

JSONL Context Tracing

Pass a ContextTracer to observe an agent run without changing the agent loop. The built-in JSONL tracer writes one structured event per line:

import { createAgent, createJsonlContextTracer } from "claude-team-agent-sdk";

const tracer = createJsonlContextTracer({
  path: ".agent-runs/session.jsonl",
});

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tracer,
});

await agent.prompt("Remember that my name is Ada.");

Each JSONL entry includes session_id, run_id, seq, source, type, and data. Agent runs record transcript and context events such as run_start, user_message, model_request, assistant_message, tool_use, tool_result, and result. For team runners, pass the tracer per query to propagate it into delegated agents:

for await (const event of team.query("Ask engineering to investigate.", {
  tracer,
})) {
  console.log(event);
}

LangSmith Context Tracing

Pass LangSmith's RunTree constructor to the SDK tracer. The SDK depends on langsmith directly and uses its official RunTree / RunTreeConfig types for this adapter.

Configure LangSmith with its standard environment variables:

LANGSMITH_TRACING=true
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGSMITH_API_KEY=<your-langsmith-api-key>
LANGSMITH_PROJECT=<your-langsmith-project>
# Required only for org-scoped or multi-workspace API keys.
LANGSMITH_WORKSPACE_ID=<your-langsmith-workspace-id>
import { RunTree } from "langsmith/run_trees";
import {
  createAgent,
  createCompositeContextTracer,
  createJsonlContextTracer,
  createLangSmithContextTracer,
} from "claude-team-agent-sdk";

const tracer = createCompositeContextTracer([
  createJsonlContextTracer({ path: ".agent-runs/session.jsonl" }),
  createLangSmithContextTracer({
    RunTree,
    projectName: process.env.LANGSMITH_PROJECT,
    workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
    tags: ["local-debug"],
  }),
]);

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tracer,
});

If you prefer explicit values over environment variables, pass them to the SDK tracer. workspaceId is optional and only selects a LangSmith workspace; it is not the tracing project name.

import { RunTree } from "langsmith/run_trees";
import { createLangSmithContextTracer } from "claude-team-agent-sdk";

const tracer = createLangSmithContextTracer({
  RunTree,
  apiKey: process.env.LANGSMITH_API_KEY,
  apiUrl: process.env.LANGSMITH_ENDPOINT,
  projectName: process.env.LANGSMITH_PROJECT,
  // Optional: only when LangSmith requires an explicit workspace.
  workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
});

LangSmith receives one root chain run per SDK query, child llm runs for model turns, child tool runs for SDK tool calls, and run events for auxiliary trace events.

Custom sinks can implement the same interface for SQLite, OpenTelemetry, object storage, or host-specific observability. The functions below are application code you provide, not SDK exports:

const tracer = {
  async onEvent(event) {
    // TODO: Replace with your own storage/logging code.
  },
};

DeepSeek Anthropic-compatible API

DeepSeek exposes an Anthropic-compatible endpoint. Configure baseURL and use a DeepSeek model name:

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
});

Custom Tool

import { createAgent, tool } from "claude-team-agent-sdk";
import { z } from "zod/v4";

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tools: [
    tool(
      "calculator",
      "Evaluate a simple arithmetic expression",
      z.object({ expr: z.string() }),
      async input => ({ content: String(Function(`return ${input.expr}`)()) }),
    ),
  ],
});

const result = await agent.prompt("What is 2+2?");
console.log(result.result);

Permission Callback

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tools: [dangerousTool],
  permission: async request => {
    if (request.toolName === "danger") {
      return { behavior: "deny", message: "Blocked by policy" };
    }
    return { behavior: "allow" };
  },
});

Denied tools are returned to Claude as error tool_result blocks so the model can explain or choose another path.

Skills

Skills are reusable instruction bundles. They are lighter than Claude Code runtime plugins: the SDK reads skill instructions and injects matching skills into the model request, but it does not depend on the Claude Code runtime.

import { createAgent, loadSkill, skill } from "claude-team-agent-sdk";

const codeReview = skill({
  name: "code-review",
  description: "Review code changes and pull requests",
  instructions: "Always list bugs and risks before summaries.",
});

const pdf = await loadSkill("./skills/pdf");

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  skills: [codeReview, pdf],
});

loadSkill(path) expects a SKILL.md file:

---
name: pdf
description: Read and inspect PDF documents
---

Render pages before claiming layout is correct.

MCP Tools

The SDK can expose MCP server tools as agent tools. The first version supports stdio MCP servers, remote Streamable HTTP servers, OAuth providers, and a generic MCPClient adapter.

import {
  connectMCPStdioServer,
  createAgent,
} from "claude-team-agent-sdk";

const mcp = await connectMCPStdioServer(
  {
    command: "node",
    args: ["./mcp-server.js"],
  },
  {
    namePrefix: "docs",
  },
);

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tools: mcp.tools,
});

try {
  const result = await agent.prompt("Search the docs for installation steps.");
  console.log(result.result);
} finally {
  await mcp.close();
}

Use createMCPTools(client) if your host application already manages an MCP client connection.

Connect a remote Streamable HTTP MCP server:

import {
  connectMCPStreamableHTTPServer,
  createAgent,
} from "claude-team-agent-sdk";

const mcp = await connectMCPStreamableHTTPServer("https://mcp.example.com/mcp", {
  namePrefix: "remote",
  requestInit: {
    headers: {
      "X-Workspace": "demo",
    },
  },
});

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tools: mcp.tools,
});

Pass an official MCP OAuthClientProvider when the remote server requires OAuth:

const mcp = await connectMCPStreamableHTTPServer("https://mcp.example.com/mcp", {
  authProvider,
});

AgentLike Composition

Agent and Team satisfy the same AgentLike shape:

type AgentLike = {
  query(prompt, options?): AsyncGenerator<SDKMessage | TeamRunnerMessage>;
  prompt(prompt, options?): Promise<SDKResultMessage>;
};

That means a team can be used anywhere a callable agent is expected. From the outside, a team is an agent; inside, it can contain a whole organization.

Team Mailbox Collaboration

Use createTeam() when you want to talk to one AgentLike while it coordinates with named members internally. The team automatically injects member agentTool() tools and drives the mailbox runtime when you call team.query() or team.prompt().

import {
  createAgent,
  createMemoryMailbox,
  createTeam,
  teamMember,
} from "claude-team-agent-sdk";

const researcher = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  systemPrompt: "You research agent SDK architecture and report concise findings.",
});

const team = createTeam({
  name: "engineering",
  lead: createAgent({
    apiKey: process.env.DEEPSEEK_API_KEY,
    baseURL: "https://api.deepseek.com/anthropic",
    model: "deepseek-v4-flash",
    systemPrompt: "You lead engineering work. Delegate research tasks to researcher.",
  }),
  members: [
    teamMember({
      name: "researcher",
      role: "executor",
      focus: "Research agent architecture",
      agent: researcher,
    }),
  ],
  mailbox: createMemoryMailbox(),
});

for await (const event of team.query("Ask the researcher to inspect the SDK design.")) {
  console.log(event);
}

team.query() streams both the lead agent's normal SDK messages and team runtime events such as team_message, team_agent, and nested agent_message events. team.prompt() consumes that stream and returns only the final result.

createTeam() injects member AgentLike tools into the lead. Those tools expose an explicit action contract: mode: "ask" waits for the member result, mode: "handoff" returns an acceptance receipt to the lead while the team runtime continues the accepted mailbox work, and mode: "observe" reports unsupported unless a host runtime provides observation support. In the default team.query() and team.prompt() path, a handoff receipt is not the final delivery: the runtime waits for the member's upstream reply, feeds it back to the lead, and keeps going until the root lead returns the final result or the run terminates.

Team member tools can also request explicit shared workspace write grants:

for await (const event of team.query(
  "Ask backend to implement the API in the shared repo.",
  {
    permissions: {
      workspaceGrants: [{
        root: "/work/shared/txt-notebook-app",
        access: ["write"],
        reason: "Project shared workspace",
      }],
    },
  },
)) {
  console.log(event);
}

When the lead calls a member tool, it may include workspaceGrants scoped to that member, for example /work/shared/txt-notebook-app/backend. The runtime only accepts write grants that are covered by the caller's current permissions. The accepted grants are written to mailbox metadata, included in the child agent's task/system context, and enforced by the built-in write tools. Read-only tools such as Read, LS, Glob, and Grep can inspect any path the host process can read and do not require workspace grants. If a write tool is denied, the model receives a structured permission_denied tool result with the requested path, allowed roots, and a deterministic suggested next step. Grant access values are operation categories, not tool names; write covers Write, Edit, and obvious Bash writes.

Managers should choose one workspace strategy explicitly when delegating: ask the member to write deliverables in its own private workspace and report paths, or provide workspaceGrants: [{ root, access: ["write"], reason }] for every shared or manager-owned root named as a write destination.

Advanced mailbox controls remain available through team.send(), team.drain(), and team.mailbox. Member agents that can accept tools receive team_send, team_inbox, team_read, team_reply, team_followup, and team_status so they can process assigned mailbox work. The lead does not receive raw mailbox tools by default; pass exposeLeadMailboxTools: true only when the lead should manually operate the team mailbox.

For durable local storage, pass a SQLite-like database. better-sqlite3 works without the SDK taking a hard dependency on it:

import Database from "better-sqlite3";
import {
  createAgent,
  createSQLiteMailbox,
  createTeam,
} from "claude-team-agent-sdk";

const mailbox = createSQLiteMailbox({
  database: new Database("team-mailbox.db"),
});

const team = createTeam({
  name: "engineering",
  lead: createAgent({
    apiKey: process.env.DEEPSEEK_API_KEY,
    baseURL: "https://api.deepseek.com/anthropic",
    model: "deepseek-v4-flash",
  }),
  members: [],
  mailbox,
});

Hosts can also provide their own TeamMailbox adapter for Redis, Cloudflare D1, Durable Objects, or another queue/storage backend.

Nested teams

Because teamMember().agent accepts any AgentLike, a Team can be a member of another Team:

import {
  createAgent,
  createTeam,
  teamMember,
} from "claude-team-agent-sdk";

const createDeepSeekAgent = (systemPrompt: string) => createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  systemPrompt,
});

const ceoAgent = createDeepSeekAgent(
  [
    "You are the CEO agent.",
    "Clarify product goals, decide which department owns the work, and ask for concise progress reports.",
    "Do not implement engineering details yourself.",
  ].join("\n"),
);
const engineeringHeadAgent = createDeepSeekAgent(
  [
    "You are the engineering head agent.",
    "Break engineering goals into backend and frontend work, route tasks to the right executor, and report outcomes upstream.",
    "Keep architecture decisions explicit.",
  ].join("\n"),
);
const backendAgent = createDeepSeekAgent(
  [
    "You are the backend executor agent.",
    "Handle APIs, data models, storage, integrations, and server-side correctness.",
    "Escalate product or UI decisions instead of guessing.",
  ].join("\n"),
);
const frontendAgent = createDeepSeekAgent(
  [
    "You are the frontend executor agent.",
    "Handle UI flows, client state, accessibility, and browser behavior.",
    "Escalate API contract questions instead of inventing them.",
  ].join("\n"),
);

const engineeringTeam = createTeam({
  name: "engineering",
  lead: engineeringHeadAgent,
  members: [
    teamMember({ name: "backend", role: "executor", agent: backendAgent }),
    teamMember({ name: "frontend", role: "executor", agent: frontendAgent }),
  ],
});

const companyTeam = createTeam({
  name: "company",
  lead: ceoAgent,
  members: [
    teamMember({
      name: "engineering",
      role: "head",
      focus: "Own engineering delivery",
      agent: engineeringTeam,
    }),
  ],
});

Use this pattern to model CEO -> Head Team -> Executor Agent without hard-coding that hierarchy into the SDK.

Routing loops

The SDK does not block routing loops by default. A task can move from a manager to a member, back to the manager for context, and then back to the same member. That is normal organizational flow, not necessarily a runtime error.

Use maxTurns, permission callbacks, mailbox status, and host-level monitoring to control cost and risk. If your application needs a strict hierarchy, expose only the allowed members at each layer and enforce routing with permission callbacks or a host-level policy.

Team runtime drain

Mailbox routing is explicit: a pending message belongs to its to mailbox and must be handled by that member's agent. claimNext(mailboxId) only claims one pending message for that mailbox and marks it processing.

const message = await team.mailbox.claimNext("engineering::researcher");

Use team.drain() to let the runtime advance already-routed work:

const result = await team.drain({
  maxRounds: 5,
  maxMessages: 20,
});

drain() iterates members, claims pending messages from each member's own mailbox, and prompts that member agent. It does not re-route work. The member must call team_reply for a final result or team_followup for progress. If a member ends without either, the runtime marks the original message failed and sends a diagnostic follow-up to the upstream mailbox.

Claude Code-style Built-in Tools

The SDK includes an opt-in set of Claude Code-style tools:

  • Read
  • Write
  • Edit
  • LS
  • Glob
  • Grep
  • Bash
import { createAgent, createClaudeCodeTools } from "claude-team-agent-sdk";

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
  tools: createClaudeCodeTools({
    cwd: process.cwd(),
    allowedDirectories: [process.cwd()],
  }),
  permission: async request => {
    if (request.toolName === "Bash" || request.toolName === "Write" || request.toolName === "Edit") {
      return { behavior: "deny", message: "This host did not approve write or shell access." };
    }
    return { behavior: "allow" };
  },
});

These tools are not enabled by default. Read, LS, Glob, and Grep are read-only observation tools and are not gated by workspace grants. Write, Edit, and obvious Bash writes are gated to the configured workspace roots and task-scoped shared workspace grants, so production hosts should pair write and shell access with a permission callback. Shell redirects to /dev/null are treated as discard targets, not workspace writes.

Multi-turn Session

const agent = createAgent({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/anthropic",
  model: "deepseek-v4-flash",
});

await agent.prompt("My name is Ada.");
const result = await agent.prompt("What is my name?");
console.log(result.result);

The SDK stores conversation state in memory for the lifetime of the Agent instance. Persistent transcripts and resume support are intentionally out of scope for the first release.