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

effect-claude-agent-sdk

v0.4.0

Published

Effect-native bindings for the Claude Agent SDK - build type-safe AI agents with Effect

Readme

effect-claude-agent-sdk

Effect-native bindings for the Claude Agent SDK. Build type-safe AI agents with Effect's powerful service composition, streaming, and error handling.

Features

  • Effect Services - AgentSdk and AgentRuntime as composable Effect services
  • Type-Safe Tools - Define MCP tools with Effect Schema, automatic Zod conversion
  • Stream-Based I/O - Query results as Effect Streams with backpressure
  • Lifecycle Hooks - Effect-based hook handlers for SDK events
  • Query Supervision - Concurrency limits, timeouts, and automatic cleanup
  • Layered Configuration - Environment-aware config with sensible defaults

Installation

bun add effect-claude-agent-sdk effect @effect/platform @effect/platform-bun

Requirements

  • Bun 1.0+ (peer dependency)
  • ANTHROPIC_API_KEY environment variable

Quick Start

import * as Console from "effect/Console"
import * as Effect from "effect/Effect"
import * as Stream from "effect/Stream"
import { AgentSdk } from "effect-claude-agent-sdk"

const program = Effect.scoped(
  Effect.gen(function* () {
    const sdk = yield* AgentSdk
    const handle = yield* sdk.query("What is 2 + 2?")

    yield* handle.stream.pipe(
      Stream.tap((message) => Console.log(message)),
      Stream.runDrain
    )
  }).pipe(Effect.provide(AgentSdk.layerDefault))
)

Effect.runPromise(program)

Core Concepts

AgentSdk

The low-level service wrapping the Claude Agent SDK. Use this when you need direct control over queries.

import { AgentSdk, AgentSdkConfig } from "effect-claude-agent-sdk"

// Default layer (uses environment variables)
AgentSdk.layerDefault

// Custom configuration
AgentSdk.layer.pipe(
  Layer.provide(
    AgentSdkConfig.layerFromEnv("MY_PREFIX") // reads MY_PREFIX_API_KEY, etc.
  )
)

AgentRuntime

A higher-level service that adds supervision, retries, and timeouts on top of AgentSdk.

import * as Effect from "effect/Effect"
import * as Stream from "effect/Stream"
import { AgentRuntime } from "effect-claude-agent-sdk"

const program = Effect.gen(function* () {
  const runtime = yield* AgentRuntime

  // Get stats about active queries
  const stats = yield* runtime.stats
  console.log(`Active queries: ${stats.active}`)

  // Stream responses directly
  yield* runtime
    .stream("Explain quantum computing")
    .pipe(Stream.runForEach((msg) => Effect.log(msg)))

  // Interrupt all active queries
  yield* runtime.interruptAll
}).pipe(Effect.provide(AgentRuntime.layerDefault))

QueryHandle

Both services return a QueryHandle for controlling the query:

interface QueryHandle {
  readonly stream: Stream.Stream<SDKMessage, AgentSdkError>
  readonly send: (message: SDKUserMessage) => Effect.Effect<void, AgentSdkError>
  readonly sendAll: (messages: Iterable<SDKUserMessage>) => Effect.Effect<void, AgentSdkError>
  readonly sendForked: (message: SDKUserMessage) => Effect.Effect<void, AgentSdkError, Scope.Scope>
  readonly closeInput: Effect.Effect<void, AgentSdkError>
  readonly interrupt: Effect.Effect<void, AgentSdkError>
}

Defining Tools

Define type-safe MCP tools using Effect Schema:

import * as Effect from "effect/Effect"
import * as Schema from "effect/Schema"
import { Tools } from "effect-claude-agent-sdk"

// Define a tool with typed parameters and return value
const Calculator = Tools.Tool.make("calculator", {
  description: "Perform arithmetic operations",
  parameters: {
    operation: Schema.Literal("add", "subtract", "multiply", "divide"),
    a: Schema.Number,
    b: Schema.Number
  },
  success: Schema.Number,
  failure: Schema.String
})

// Create a toolkit from multiple tools
const toolkit = Tools.Toolkit.make(Calculator)

// Implement handlers
const handlers = {
  calculator: ({ operation, a, b }) =>
    Effect.gen(function* () {
      switch (operation) {
        case "add":
          return a + b
        case "subtract":
          return a - b
        case "multiply":
          return a * b
        case "divide":
          if (b === 0) return yield* Effect.fail("Division by zero")
          return a / b
      }
    })
}

Tool Annotations

Add metadata to tools for better documentation:

const ReadFile = Tools.Tool.make("read_file", {
  description: "Read contents of a file",
  parameters: { path: Schema.String },
  success: Schema.String
})
  .annotate(Tools.Tool.Readonly, true)
  .annotate(Tools.Tool.OpenWorld, true)

MCP Servers

Create in-process MCP servers with your tools:

import * as Effect from "effect/Effect"
import { AgentSdk, Mcp, Tools } from "effect-claude-agent-sdk"

const program = Effect.scoped(
  Effect.gen(function* () {
    const sdk = yield* AgentSdk
    const toolkit = Tools.Toolkit.make(Calculator)
    const tools = yield* Mcp.toolsFromToolkit(toolkit, handlers)

    const server = yield* sdk.createSdkMcpServer({
      name: "my-tools",
      version: "1.0.0",
      tools
    })

    const handle = yield* sdk.query("Calculate 15 * 7", {
      tools: ["calculator"],
      mcpServers: { "my-tools": server }
    })

    // ...
  })
)

Hooks

Handle SDK lifecycle events with Effect:

import * as Effect from "effect/Effect"
import { Hooks } from "effect-claude-agent-sdk"

const myHook = Hooks.Hook.callback((input, context) =>
  Effect.gen(function* () {
    if (input.type === "PreToolUse") {
      yield* Effect.log(`Tool ${input.tool_name} about to be called`)
    }
    return {} // Hook output
  })
)

// Create a matcher for specific events
const matcher = Hooks.Hook.matcher({
  matcher: "PreToolUse",
  timeout: "30 seconds",
  hooks: [yield* myHook]
})

Logging & Observability

Match-based logging helpers with Effect-native loggers:

import * as Effect from "effect/Effect"
import * as Stream from "effect/Stream"
import { AgentRuntime, Logging } from "effect-claude-agent-sdk"

const program = Effect.gen(function* () {
  const runtime = yield* AgentRuntime

  yield* runtime
    .stream("Explain quantum computing")
    .pipe(Logging.tapSdkLogs, Stream.runDrain)

  yield* Logging.logQueryEventStream(runtime.events)
}).pipe(
  Effect.provide(AgentRuntime.layerDefault),
  Effect.provide(Logging.layerDefault)
)

Configuration

Environment Variables

| Variable | Description | Default | |----------|-------------|---------| | ANTHROPIC_API_KEY | API key for Claude | Required | | AGENTSDK_MODEL | Model to use | claude-sonnet-4-20250514 | | AGENTSDK_MAX_TURNS | Maximum conversation turns | 100 | | AGENTSDK_SYSTEM_PROMPT | System prompt | None | | AGENTSDK_LOG_FORMAT | Logger format (pretty, structured, json, logfmt, string) | pretty | | AGENTSDK_LOG_LEVEL | Minimum log level (trace, debug, info, warn, error, fatal, none) | info | | AGENTSDK_LOG_SPANS | Include span annotations in logs | false | | AGENTSDK_LOG_MESSAGES | Enable SDK message logging | true | | AGENTSDK_LOG_QUERY_EVENTS | Enable query event logging | true | | AGENTSDK_LOG_HOOKS | Enable hook input logging | true |

Runtime Configuration

import { AgentRuntimeConfig } from "effect-claude-agent-sdk"

const config = AgentRuntimeConfig.layer({
  queryTimeout: "5 minutes",
  firstMessageTimeout: "30 seconds",
  retryMaxRetries: 3,
  retryBaseDelay: "1 second",
  maxConcurrentQueries: 10
})

Experimental Features

Rate-Limited Tool Handlers

import { Experimental, Tools } from "effect-claude-agent-sdk"

const handlers = {
  echo: ({ text }) => Effect.succeed(text)
}

const limited = Experimental.RateLimiter.rateLimitHandlers(
  handlers,
  { limit: 10, window: "1 minute" },
  { keyPrefix: "tools" }
)

// Provide the rate limiter layer
Effect.provide(Experimental.RateLimiter.layerMemory)

Persisted Input Queue

import { Experimental, Schema } from "effect-claude-agent-sdk"

const program = Effect.gen(function* () {
  const queue = yield* Experimental.PersistedQueue.makeUserMessageQueue()
  const adapter = yield* Experimental.PersistedQueue.makeInputAdapter(queue)

  const message: Schema.SDKUserMessage = {
    type: "user",
    session_id: "",
    message: { role: "user", content: [{ type: "text", text: "hello" }] },
    parent_tool_use_id: null
  }

  yield* adapter.send(message)
}).pipe(Effect.provide(Experimental.PersistedQueue.layerMemory))

Audit Event Log

import { Experimental } from "effect-claude-agent-sdk"

const program = Effect.scoped(
  Effect.gen(function* () {
    const log = yield* Experimental.EventLog.EventLog

    yield* log.write({
      schema: Experimental.EventLog.AuditEventSchema,
      event: "hook_event",
      payload: {
        sessionId: "session-1",
        hook: "SessionStart",
        outcome: "success"
      }
    })
  }).pipe(
    Effect.provide([
      Experimental.EventLog.layerMemory,
      Experimental.EventLog.layerAuditHandlers
    ])
  )
)

Sessions (v2)

import { SessionManager, SessionService } from "effect-claude-agent-sdk"
import * as Effect from "effect/Effect"
import * as Stream from "effect/Stream"

const program = Effect.scoped(
  Effect.gen(function* () {
    const session = yield* SessionService
    yield* session.send("hello")
    return yield* Stream.runCollect(session.stream)
  }).pipe(
    Effect.provide(SessionService.layerDefault({
      model: "claude-sonnet-4-5-20250929"
    }))
  )
)

Session configuration (via SessionManager.layerDefaultFromEnv) supports: EXECUTABLE, PATH_TO_CLAUDE_CODE_EXECUTABLE, EXECUTABLE_ARGS, PERMISSION_MODE, ALLOWED_TOOLS, DISALLOWED_TOOLS, ANTHROPIC_API_KEY/API_KEY, CLAUDE_CODE_SESSION_ACCESS_TOKEN.

Guidance:

  • Use SessionService or SessionManager for normal app usage (applies SessionConfig defaults and validates required model).
  • Use the low-level Session module when you want to manage every option explicitly; it does not read SessionConfig defaults.

API Reference

Services

| Service | Description | |---------|-------------| | AgentSdk | Low-level SDK wrapper with query() and createSdkMcpServer() | | AgentRuntime | High-level runtime with supervision, retries, timeouts | | QuerySupervisor | Manages concurrent queries and cleanup | | SessionManager | Session factory that applies SessionConfig defaults | | SessionService | Scoped Session wrapper for single-session usage |

Modules

| Module | Description | |--------|-------------| | Schema | Effect Schema definitions for SDK types | | Tools | Tool and Toolkit definitions | | Hooks | Hook handlers and matchers | | Logging | Logging config, matchers, and stream helpers | | Mcp | MCP server creation utilities | | Storage | Chat history, artifacts, and audit log persistence | | Experimental | Rate limiting, persisted queues, event logging |

Layers

| Layer | Description | |-------|-------------| | AgentSdk.layerDefault | Default SDK configuration | | AgentSdk.layerDefaultFromEnv(prefix) | SDK from environment with custom prefix | | AgentRuntime.layerDefault | Default runtime with supervision | | AgentRuntime.layerDefaultFromEnv(prefix) | Runtime from environment | | SessionManager.layerDefault | Session manager with default SessionConfig | | SessionManager.layerDefaultFromEnv(prefix) | Session manager from environment | | SessionService.layerDefault(options) | Scoped session service with defaults | | SessionService.layerDefaultFromEnv(options, prefix) | Scoped session service from environment |

Examples

See the examples/ directory:

  • agent-sdk-mcp-rate-limit.ts - Rate-limited MCP tools
  • agent-sdk-audit-log.ts - Event logging integration
  • agent-sdk-persisted-input.ts - Persisted message queue
  • agent-sdk-metadata-cache.ts - Result caching
  • agent-service-http-server.ts / agent-service-http-client.ts - HTTP API
  • agent-service-rpc-server.ts / agent-service-rpc-client.ts - RPC API

License

MIT