cd /news/ai-agents/show-hn-anyclaude-sdk-claude-code-st… · home topics ai-agents article
[ARTICLE · art-74683] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Show HN: Anyclaude-SDK – Claude Code-Style SDK for OpenAI/Anthropic Endpoints

Anyclaude-SDK, a new open-source SDK that brings Claude Code-style agent capabilities to any OpenAI- or Anthropic-compatible LLM endpoint, has been released on npm. The SDK supports multi-agent teams, MCP servers, and runs in the browser via WebContainer, Node, and Bun without requiring a backend or OAuth. It exposes the same interface as the official Anthropic Claude agent SDK, allowing code written for that SDK to work unchanged.

read15 min views1 publishedJul 26, 2026
Show HN: Anyclaude-SDK – Claude Code-Style SDK for OpenAI/Anthropic Endpoints
Image: source

Claude Code agent capabilities — tools, the tool loop, multi-turn conversations, MCP, sub-agents, multi-agent teams, sessions — against any OpenAI- or Anthropic-compatible LLM endpoint, running in the browser (WebContainer), Node, and Bun. No backend required, no OAuth, no native binaries.

Live demo:[a full IDE running in your browser]·Docs:[anyclaude-docs.puter.site]·React UI kit:anyclaude-react

It exposes the same query()

async-generator interface and the same SDKMessage

envelope as @anthropic-ai/claude-agent-sdk

, so code written against the official SDK can iterate our output unchanged.

Multi-agent teams go beyond one agent: a coordinator delegates board tasks to worker sub-agents in parallel, you can dispatch a message to a running worker and have it land on its next tool round (push delivery, like the message queue), supervise them live with background dispatch, block for the next finished worker with

(event-driven — integrate each result as it lands, no busy-polling), and even run agents in

wait_for_worker

separate Web Workers or browser tabs that share one mailbox via

BroadcastChannelMailbox

. See Teams & sub-agents.

npm install anyclaude-sdk @webcontainer/api

@webcontainer/api

is an optional peer dependency — only needed if you use WebContainerWorkspace

. You can supply your own FileSystem

/CommandExecutor

.

import { WebContainer } from '@webcontainer/api'
import {
  query,
  WebContainerWorkspace,
  createOpenAIClient,
  ALL_CLAUDE_CODE_TOOLS,
} from 'anyclaude-sdk'

// 1. Boot a WebContainer and wrap it as a workspace.
const wc = await WebContainer.boot()
const workspace = new WebContainerWorkspace(wc)

// 2. Point at any OpenAI-compatible endpoint.
const llm = createOpenAIClient({
  apiKey: import.meta.env.VITE_OPENAI_API_KEY,
  baseUrl: 'https://api.openai.com/v1', // or Groq, Together, OpenRouter, local…
  model: 'gpt-4o',
})

// 3. Run the agent — same shape as the official SDK.
for await (const msg of query({ prompt: 'List the files and summarize the project', workspace, llm })) {
  if (msg.type === 'assistant') {
    for (const block of msg.message.content) {
      if (block.type === 'text') console.log(block.text)
    }
  } else if (msg.type === 'result' && msg.subtype === 'success') {
    console.log('Done:', msg.result)
  }
}

Connect external MCP servers or define in-process tools. Because browsers block direct cross-origin MCP fetches (CORS), pass a mcpProxy

for remote servers:

import { createSdkMcpServer, tool } from 'anyclaude-sdk'

const calc = createSdkMcpServer({
  name: 'calc',
  tools: [tool('add', 'Add two numbers',
    { type: 'object', properties: { a: { type: 'number' }, b: { type: 'number' } }, required: ['a', 'b'] },
    (args) => ({ content: [{ type: 'text', text: String(args.a + args.b) }] }))],
})

query({
  prompt, workspace, llm,
  mcpServers: {
    calc,                                                   // in-process, no network
    docs: { type: 'http', url: 'https://mcp.example.com' }, // remote
  },
  // Route remote MCP through a CORS proxy (function, `{url}`/`{rawUrl}` template, or bare prefix):
  mcpProxy: 'https://my-proxy.example/?url={url}',
})

Remote tools are exposed as mcp__<server>__<tool>

.

Three transport clients, all implementing the same LLMClient

interface:

import { createOpenAIClient, createAnthropicClient, createResponsesClient } from 'anyclaude-sdk'

// OpenAI-compatible Chat Completions (OpenAI, Groq, Together, OpenRouter, xAI, Kilo, local…)
const a = createOpenAIClient({ apiKey, baseUrl: 'https://api.x.ai/v1', model: 'grok-build-0.1' })

// Anthropic Messages API
const b = createAnthropicClient({ apiKey, model: 'claude-sonnet-4-6' })

// OpenAI Responses API (POST /v1/responses)
const c = createResponsesClient({ apiKey, model: 'gpt-4o' })

// AgentRouter gateway (sponsor) — one-line preset over createOpenAIClient; key
// falls back to AGENTROUTER_API_KEY. Reaches Claude/GPT/Gemini/DeepSeek/GLM/… via one base URL.
import { createAgentRouterClient } from 'anyclaude-sdk'
const d = createAgentRouterClient({ model: 'claude-sonnet-4-5-20250929' })

All three normalize tool calls, streaming, and usage to the same StreamResult

, and include a fallback parser for models that emit tool calls as inline text. Every client also accepts extraHeaders

/ extraBody

(merge provider-specific headers/params without a custom client) and surfaces retries

on the result when transient failures were retried.

Use a PromptStream

to push user turns over time:

import { query, PromptStream } from 'anyclaude-sdk'

const prompts = new PromptStream()
const session = query({ prompt: prompts, workspace, llm, model: 'gpt-4o' })

prompts.push('Create a hello.txt with a greeting')
// …later, based on UI input:
prompts.push('Now translate it to French')
prompts.end() // close the conversation

for await (const msg of session) {
  // render msg…
}

ALL_CLAUDE_CODE_TOOLS

includes:

Tool Purpose
bash
Run shell commands via jsh (2>&1 //dev/null redirects are stripped)
read_file
Read text (numbered lines, offset/limit), images (auto-downsampled base64), PDFs (document block), and notebooks (.ipynb cells + outputs); binary files are rejected with guidance
write_file
Write a file, creating parent dirs
edit_file
Exact-match string replace (requires a prior read)
multi_edit
Apply a sequence of edits to one file atomically
notebook_edit
Replace/insert/delete cells in a .ipynb
delete_file
Remove a file/dir
glob
Find files by glob pattern (** , * , ? )
grep
Regex search across files
list_files
List a directory
todo_write
Track a multi-step task list across turns
web_fetch
Fetch a URL → clean Markdown via the Jina Reader (CORS-free, JS-rendered)
web_search
Web search via Jina + DuckDuckGo HTML; returns top-N title/URL/snippet
render_page
Render a public page in a headless browser: Playwright-style actions (click/fill/scroll/press), accessibility snapshot , evaluate JS, console+network logs , and a screenshot (as a viewable image) or extracted markdown/html. Refuses localhost/private/WebContainer hosts.

read_file

dispatches by file type. Image and PDF bytes are forwarded to the model automatically as a follow-up user turn (Anthropic gets native image

/document

blocks; OpenAI-compatible endpoints get image_url

/file

parts), so the model can actually see the file, not just a text summary. Tune the caps via limits

:

query({ prompt, workspace, llm, limits: { maxTokens: 25000, maxImageBytes: 3_750_000, maxPdfPages: 20 } })

Pass a subset, or your own Tool[]

, via tools:

:

import { readFile, writeFile, editFile } from 'anyclaude-sdk'

query({ prompt, workspace, llm, tools: [readFile, writeFile, editFile] })

A user turn beginning with /

is intercepted. Built-ins: /help

, /clear

, /compact [focus]

(summarizes history to free context), /tools

, /cost

, /model

. Define your own prompt-template commands:

import { query, promptCommand } from 'anyclaude-sdk'

query({
  prompt: promptStream, workspace, llm,
  commands: [promptCommand('review', 'Review the diff', 'Review this code and list issues: $ARGUMENTS')],
})
// user types: /review src/app.ts

Enable with background: true

to run sub-agents or long work off the critical path. The task

tool gains run_in_background

(returns a task id immediately), and task_list

/ task_output

/ task_stop

tools let the agent poll them. Optional off-main-thread execution via a Comlink worker harness (exposeBackgroundWorker

/ wrapWorker

); the in-thread manager works without it.

query({ prompt, workspace, llm, agents: {}, background: true })

Two halves: Comlink for main→worker control (wrapWorker

/ exposeBackgroundWorker

, above), and ** BroadcastChannelMailbox** so agents in

differentworkers gossip mailbox-style. It's a drop-in

Mailbox

, so the existing team

tools (send_message

/ dispatch_tasks

) work unchanged across workers:

import { BroadcastChannelMailbox } from 'anyclaude-sdk'

// inside each Web Worker / tab / worker_thread, same channel name:
const mailbox = new BroadcastChannelMailbox({ channelName: 'team', origin: 'planner' })
query({ prompt, workspace, llm, team: true, mailbox })
// messages sent by one worker land in the addressed agent's inbox in another.

Uses the global BroadcastChannel

by default. For durable cross-tab delivery (IndexedDB/localStorage fallbacks, older browsers, Node) use the one-call helper — it's backed by the bundled broadcast-channel package, lazy-imported so it stays out of bundles that don't use it:

const mailbox = await BroadcastChannelMailbox.crossTab({ channelName: 'team', origin: 'planner' })
query({ prompt, workspace, llm, team: true, mailbox })

Push delivery to a running agent. Messages addressed to an agent are auto-injected into its transcript at the next turn boundary — same model as the message queue, but from the shared mailbox. So a coordinator (or peer, or another worker) can redirect a running sub-agent mid-task and it lands on the sub-agent's next tool round, no polling tool needed. dispatch_tasks

names each worker worker:<taskId>

so you can target a specific one:

mailbox.send('coordinator', 'worker:task_1', 'while you work: also add logging')
// worker:task_1 sees "[Team messages] - from coordinator: ..." on its next step.

On by default with team: true

; opt out via query({ deliverTeamMessages: false })

.

You aren't tied to WebContainer. A Sandbox

is just a FileSystem

plus a CommandExecutor

, and you can mix and match.

Adapters wrap each provider's client structurally (no hard dependency on their SDKs — install only the one you use):

import { E2BSandbox, VercelSandbox, DaytonaSandbox, CloudflareSandbox } from 'anyclaude-sdk'

// e.g. E2B
import { Sandbox } from 'e2b'
const sbx = await Sandbox.create()
const workspace = new E2BSandbox(sbx)

query({ prompt, workspace, llm })

Supported: WebContainer, E2B, Vercel Sandbox, Daytona, Cloudflare Sandbox, and LocalSandbox (real OS). All implement the same Sandbox

interface.

Run the agent directly against the host machine's filesystem and shell — like Claude Code — with automatic platform detection (Windows / macOS / Linux):

import { LocalSandbox, createAnthropicClient, query } from 'anyclaude-sdk'

const workspace = new LocalSandbox({ cwd: '/path/to/project' }) // defaults to process.cwd()
const llm = createAnthropicClient({ baseUrl, model: 'claude-sonnet-4-6', apiKey })

for await (const msg of query({ prompt: 'add a CLI flag and run the tests', workspace, llm })) { /* … */ }

The agent's working directory is taken from the sandbox automatically. See examples/local-agent.mjs

for a runnable headless demo. On Windows it uses cmd.exe

; elsewhere $SHELL

//bin/sh

(override via shell

/shellArgs

).

For a durable local filesystem in the browser, use a DB-backed FS and seed a standard Linux tree. DexieFileSystem

(IndexedDB) is the recommended default — persistent across reloads, indexed for fast readdir

/glob

, with metadata (mode, mtime, symlinks):

import {
  DexieFileSystem, OpfsFileSystem, seedLinuxTree, composeWorkspace, NoopCommandExecutor,
} from 'anyclaude-sdk'

const fs = new DexieFileSystem('my-project-fs')   // or: new OpfsFileSystem()
await seedLinuxTree(fs)                            // /bin /etc /home/user /tmp /usr …

// File-only agent (no shell):
const workspace = composeWorkspace(fs, new NoopCommandExecutor(), '/home/user')

// …or pair a persistent FS with a remote shell:
// const workspace = composeWorkspace(fs, new E2BSandbox(sbx), '/home/user')

OpfsFileSystem

(Origin Private File System) is offered alongside Dexie for large-binary / native-handle scenarios; use OpfsFileSystem.isSupported()

to feature-detect.

A MemoryFileSystem

also ships for tests:

import { MemoryFileSystem, NoopCommandExecutor, composeWorkspace } from 'anyclaude-sdk'

const fs = new MemoryFileSystem()
await fs.writeFile('/app/index.ts', 'export const x = 1')
const workspace = composeWorkspace(fs, new NoopCommandExecutor())

Declare reusable prompt-skills inline — each becomes a /name

slash command and is invokable by the agent through the skill

tool. $ARGUMENTS

is substituted at call time:

import { query, defineSkill } from 'anyclaude-sdk'

query({
  prompt, workspace, llm,
  skills: [
    defineSkill({
      name: 'changelog',
      description: 'Summarize git changes into a changelog entry',
      instructions: 'Write a concise changelog entry for: $ARGUMENTS',
      argumentHint: '<since>',
    }),
  ],
})

You can also pass plain Skill

objects, or skills: true

to load .claude/skills/*.md

from the workspace.

Run query()

in a serverless function and stream SDKMessage

s to the browser. For runs longer than the platform's time cap, checkpoint at a turn boundary and continue transparently in a fresh invocation:

//  near the deadline, persist to the store, emit a `d` message
query({ prompt, workspace, llm, sessionStore, maxDurationMs: 20_000 })
// later — resume + continue the tool loop with NO new user message
query({ workspace, llm, sessionStore, resume: true, continueRun: true })

Pluggable SessionStore

adapters (all implement SessionStoreLike

): SessionStore

(IndexedDB), MemorySessionStore

, KVSessionStore

(Vercel KV / Upstash), RedisSessionStore

, PostgresSessionStore

(Neon / pg / postgres.js), SupabaseSessionStore

.

Declare tools the host executes — e.g. run bash

in the user's browser WebContainer while the agent loop runs on your server. The run s with a client_tool_request

; the client executes it and you resume with the result:

import { WORKSPACE_TOOL_NAMES } from 'anyclaude-sdk'
query({ prompt, llm, workspace, sessionId, clientTools: WORKSPACE_TOOL_NAMES })  // → emits client_tool_request + s
query({ llm, workspace, sessionId, resume: true, continueRun: true, clientToolResults })  // → continues

On the browser side, anyclaude-react

turns those into a ready executor map backed by any workspace — a WebContainer (real shell + files), the user's IndexedDB (DexieFileSystem

), OPFS, or memory:

import { createWebContainerClientTools, createWorkspaceClientTools } from 'anyclaude-react'
useAgent({ endpoint: '/api/agent', clientTools: createWebContainerClientTools(wc) })           // files + bash
useAgent({ endpoint: '/api/agent', clientTools: createWorkspaceClientTools(new DexieFileSystem('my-db')) }) // IndexedDB

Provide onAskUser

and the agent gains an ask_user_question

tool to put a decision to the user — multiple-choice or free-text (inputType: 'choice' | 'text' | 'textarea' | 'number'

, plus allowOther

). Hosts that only render choices can ignore the new fields:

query({ prompt, workspace, llm, onAskUser: async ({ question, options, inputType }) =>
  inputType && inputType !== 'choice' ? promptText(question) : pickOne(question, options) })

The agent loop runs server-side, so your system prompt, tool instructions, and retrieved context live in the server→LLM request and never reach the browser. To also strip sensitive artifacts (reasoning, raw tool output / RAG, model identity) from the streamed messages, wrap the stream — a pure, opt-in output transform:

import { projectMessages } from 'anyclaude-sdk'
for await (const m of projectMessages(query({ /* ... */ }), { preset: 'public' }))
  res.write(JSON.stringify(m) + '\n')

d

and client_tool_request

control messages are always preserved. (Note: anything that runs in the browsercreateAgentClient

mode — necessarily exposes its request; use the server/endpoint path when the prompt is proprietary.)

npm install anyclaude-react

useAgent()

plus restylable components — chat (AgentChat

, ChatPanel

, Transcript

, MarkdownMessage

, Composer

, Working

, ToolCall

) and an IDE set (Terminal

, FileExplorer

, CodeEditor

, AskUser

). createAgentClient

/ createEndpointClient

auto-stitch d

continuations and run clientTools

in the browser.

Stand up an Anthropic Messages API-compatible endpoint backed by any OpenAI-compatible model, so Claude Code itself (or any Anthropic-Messages client) runs against DeepSeek / Qwen / GLM / Kimi / local Ollama. Unlike a naive proxy, inline tool-call dialects are recovered into proper tool_use blocks, so tool use actually works on cheap models.

import { createOpenAIClient } from 'anyclaude-sdk/llm'
import { anthropicToChat, anthropicSSE } from 'anyclaude-sdk/anthropic-endpoint'

const llm = createOpenAIClient({ baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat', apiKey })
// POST /v1/messages:
for await (const evt of anthropicSSE(llm, anthropicToChat(body), { model: 'deepseek-chat' })) res.write(evt)
// then: ANTHROPIC_BASE_URL=http://localhost:8787 claude

Runnable: examples/claude-code-router.

Frontier models emit clean native function-calls; cheaper ones often don't. Three layers (in anyclaude-sdk/llm

) close the gap: tool-call dialects (parseToolCalls

— xml-function / hermes / json-fence), auto-detected model profiles (profileForModel

— qwen/deepseek/moonshot/zhipu/mistral/llama), and self-healing argument repair (query({ repairToolCalls })

, on by default — validates args and feeds the model a corrective tool_result instead of running with garbage). Prove it on your endpoints with scripts/compat-matrix.mjs

COMPATIBILITY.md.

npm create anyclaude-app@latest my-app   # template: bolt — WebContainer + chat + live preview, no backend

The bolt

template wires useWebContainerPreview({ wc })

(boot a dev server → live preview URL) + a browser-side query()

Keep a large pool of rarely-used tools out of the per-turn payload (big savings on weak/uncached models) while staying discoverable + callable. Mark them deferred; tool_search

indexes them and the loop arms a tool (sends its schema on subsequent turns) once search surfaces it — then it executes normally.

query({ prompt, workspace, llm,
  extraTools: [deploy, ...integrationTools],   // e.g. 35 integration tools
  deferredTools: ['stripe_charge', 'supabase_query', /* … the niche ones */],
})
// or per-tool: defineTool({ name, description, parameters, run, defer: true })

Only the lean core + tool_search

are sent each turn; the model searches when it needs a niche tool, the SDK arms it, and the call goes through. Register 35, send ~10.

Opt-in knobs for token cost and latency — especially on weak / uncached models:

query({
  prompt, workspace, llm,
  systemPromptPreset: 'lean',      // ~70% shorter built-in prompt — saved every turn on uncached models
  keepToolResults: 6,              // context editing: stub tool_results older than the last 6 (caps transcript growth)
  parallelToolExecution: true,     // run a turn's read-only tool calls concurrently (~2× faster on multi-read turns)
  deferredTools: [/* niche tools */], // keep rarely-used tools out of the payload until tool_search arms them
})
// custom read tool opting into parallelism:
defineTool({ name: 'get_logs', description: '…', parameters, run, parallelSafe: true })

Mutating tools / bash

/ delegated client tools always execute serially; keepToolResults

and parallelToolExecution

preserve correctness, just trim cost/latency.

Live compaction markerautoCompact

emits acompact_boundary

withstatus: 'start'

beforesummarizing (for a live "compacting…" shimmer) andstatus: 'end'

after withpost_tokens

.Cancel a queued messageMessageQueue.push()

returns a stable id;remove(id)

cancels a single pending message (per-pill ✕ in a UI).BYO LLM client— reuse the SDK's wire codec:toOpenAIMessages

,consumeSSE

, and the LLM types fromanyclaude-sdk/llm

(no bare-root import in browser bundles).

Runnable Vite projects in examples/:

(WebContainer IDE — real shell + Node in the tab),

browser-ide

browser-chat

, claude-code-router

, vercel-kv-survivor

, vercel-supabase-survivor

, vercel-indexeddb-survivor

, (server brain / browser hands). Try the

vercel-clienttools

.

live demoquery(options): AsyncGenerator<SDKMessage>

— main entry.prompt: string | AsyncIterable<SDKUserMessage>

workspace: FileSystem & CommandExecutor

llm: LLMClient

tools?

,extraTools?

,allowedTools?

/disallowedTools?

,deferredTools?

(lazy-load),model?

,systemPrompt?

/appendSystemPrompt?

,maxTurns?

(default 50),cwd?

,abortController?

  • serverless: sessionStore?

,resume?

,maxDurationMs?

,continueRun?

  • client tools: clientTools?

,clientToolResults?

; interactive:onAskUser?

  • also: mcpServers?

,agents?

,commands?

,hooks?

,background?

,team?

,memory?

,permissionMode?

/canUseTool?

,messageQueue?

createOpenAIClient

/createAnthropicClient

/createResponsesClient

WebContainerWorkspace

,MemoryFileSystem

,NoopCommandExecutor

,LocalSandbox

,composeWorkspace

defineTool

(custom tools),projectMessages

(server-side stream redaction)ALL_CLAUDE_CODE_TOOLS

, individual tools,toolDefs

,toolByName

  • browser-clean subpaths: anyclaude-sdk/{query,loop,llm,fs,workspace,tools,session,memory,compact,permissions,skills,queue,prompt,anthropic-endpoint,telemetry}

anyclaude-sdk/llm

:parseToolCalls

  • dialects,profileForModel

(model profiles),validateToolArguments

(repair),toOpenAIMessages

/consumeSSE

(BYO-client codec)anyclaude-sdk/anthropic-endpoint

:anthropicToChat

,anthropicSSE

,streamResultToAnthropicMessage

(Claude-Code router)runToolLoop

(/loop

),compactWithWindow

(/compact

),track

/telemetryEnabled

(/telemetry

)- All SDK*

message types,ContentBlockParam

,LLMClient

,ToolDef

,SessionStoreLike

, etc.

Feature Official SDK anyclaude-sdk
Auth OAuth token None required
Backend claude.ai API Any OpenAI/Anthropic endpoint
Runtime Node only Browser, Node, Bun
File ops Native filesystem Pluggable (WebContainer / Memory / IndexedDB / local)
Commands Native shell jsh (WebContainer) / local / client-side tools
MCP / slash commands / background tasks / sub-agents Built-in Built-in
Serverless survivor + prompt projection Built-in

The SDK emits anonymous, opt-out usage telemetry (SDK version, runtime, a coarse model-family bucket, and which features are used) — never code, prompts, repo identity, paths, or keys. It sends to an aggregate-only collector (a Puter Worker; source in examples/telemetry-collector). Disable with

ANYCLAUDE_TELEMETRY=0

, DO_NOT_TRACK=1

, or query({ disableTelemetry: true })

; repoint with ANYCLAUDE_TELEMETRY_URL

(or set it to ''

to send nowhere). Full disclosure: TELEMETRY.md.

MIT

── more in #ai-agents 4 stories · sorted by recency
── more on @anyclaude-sdk 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-anyclaude-sd…] indexed:0 read:15min 2026-07-26 ·