Anthropic Agent SDK Adapter
@openharness/adapter-anthropic-agent
Direct access to Claude with tool use, streaming, and multi-turn conversations.
Available
v0.1.0
Installation
npm install @openharness/adapter-anthropic-agent
Quick Start
import { AnthropicAgentAdapter } from "@openharness/adapter-anthropic-agent";
const adapter = new AnthropicAgentAdapter({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Simple execution
const result = await adapter.execute({
message: "What is 2 + 2?",
});
console.log(result.output);
Capabilities
This adapter implements a subset of the Open Harness API focused on core execution capabilities.
Supported
| Domain | Capability | Notes |
|---|---|---|
| Execution | execute() | Sync execution with agentic loop |
| Execution | executeStream() | Async generator streaming |
| Execution | Cancel | Via AbortSignal |
| Execution | Extended Thinking | enableThinking config |
| Tools | registerTool() | Register custom handlers |
| Tools | listTools() | List registered tools |
| Tools | invokeTool() | Direct invocation |
| Sessions | createConversation() | In-memory state |
| Sessions | sendMessage() | With history |
| Sessions | sendMessageStream() | Streaming with history |
| Models | Model switching | Per-request via options |
Not Supported
| Domain | Reason | Workaround |
|---|---|---|
| Agents | SDK is stateless | External state management |
| Skills | Not in SDK scope | Register tools directly |
| MCP | Requires separate package | Add MCP tools manually |
| Memory | SDK is stateless | External database |
| Subagents | Requires orchestration | Build custom orchestrator |
| Files | Not in SDK scope | Register file tools |
| Hooks | Requires orchestration | Wrap adapter methods |
| Planning | Not in SDK scope | Build custom layer |
Tool Registration
adapter.registerTool({
name: "get_weather",
description: "Get the current weather for a location",
inputSchema: {
type: "object",
properties: {
location: { type: "string", description: "City name" },
},
required: ["location"],
},
handler: async (input) => {
const location = input.location as string;
// Your weather API logic here
return {
success: true,
output: { temp: 72, condition: "sunny", location },
};
},
});
// Execute with tools (agentic loop handles tool calls automatically)
const result = await adapter.execute({
message: "What's the weather in San Francisco?",
});
Streaming
for await (const event of adapter.executeStream({
message: "Write a haiku about programming",
})) {
switch (event.type) {
case "text":
process.stdout.write(event.content);
break;
case "tool_call_start":
console.log(`\nUsing tool: ${event.name}`);
break;
case "tool_result":
console.log(`Tool result:`, event.output);
break;
case "done":
console.log(`\n\nTokens: ${event.usage.total_tokens}`);
break;
}
}
Event Types
| Event | Description |
|---|---|
text | Text content chunk |
tool_call_start | Tool invocation beginning |
tool_call_delta | Partial tool input (JSON streaming) |
tool_call_end | Tool invocation complete |
tool_result | Tool execution result |
progress | Iteration progress |
error | Error occurred |
done | Execution complete with usage stats |
Conversation Management
In-Memory Only
Conversations are stored in-memory and lost on process restart. For persistence, serialize conversation.messages to your database.
// Create a conversation
const conversation = adapter.createConversation({
systemPrompt: "You are a helpful coding assistant.",
});
// Send messages in the conversation
const response1 = await adapter.sendMessage(
conversation.id,
"What's a good way to handle errors in TypeScript?"
);
const response2 = await adapter.sendMessage(
conversation.id,
"Can you show me an example?"
);
// Stream a message in the conversation
for await (const event of adapter.sendMessageStream(
conversation.id,
"Now explain async/await"
)) {
if (event.type === "text") {
process.stdout.write(event.content);
}
}
Configuration
interface AnthropicAgentConfig {
// Anthropic API key (or use ANTHROPIC_API_KEY env var)
apiKey?: string;
// Model (default: "claude-sonnet-4-20250514")
model?: string;
// Max tokens (default: 4096)
maxTokens?: number;
// Base URL for API (useful for proxies)
baseUrl?: string;
// Default system prompt
systemPrompt?: string;
// Custom Anthropic client
client?: Anthropic;
// Enable extended thinking (default: false)
enableThinking?: boolean;
// Thinking budget (default: 10000)
thinkingBudget?: number;
}
Execution Options
Per-request options can override adapter defaults:
const result = await adapter.execute({
message: "Explain quantum computing",
}, {
model: "claude-opus-4-20250514",
maxTokens: 8192,
temperature: 0.7,
maxIterations: 5, // Max tool use iterations (default: 10)
abortSignal: controller.signal,
});
API Behavior
Agentic Loop
The adapter implements an automatic agentic loop for tool use:
- User message is sent to Claude
- If Claude requests tool use, the adapter automatically invokes the registered handler
- Tool results are sent back to Claude
- Loop continues until Claude produces a final text response or
maxIterationsis reached
This differs from raw SDK usage where you must manually handle tool calls.
Token Usage
// Sync execution
const result = await adapter.execute({ message: "Hello" });
console.log(result.usage);
// { inputTokens: 10, outputTokens: 50, totalTokens: 60, durationMs: 1234 }
// Streaming - usage is in the final "done" event
for await (const event of adapter.executeStream({ message: "Hello" })) {
if (event.type === "done") {
console.log(event.usage);
}
}
Extending the Adapter
Adding MCP Support
import { Client } from "@anthropic-ai/mcp";
// Connect to MCP server
const mcp = new Client();
await mcp.connect("npx -y @anthropic-ai/mcp-server-filesystem /tmp");
// Get tools from MCP server
const mcpTools = await mcp.listTools();
// Register MCP tools with the adapter
for (const tool of mcpTools) {
adapter.registerTool({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
handler: async (input) => {
const result = await mcp.callTool(tool.name, input);
return { success: true, output: result };
},
});
}
Adding File Operations
import * as fs from "fs/promises";
adapter.registerTool({
name: "read_file",
description: "Read a file from the filesystem",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "File path" },
},
required: ["path"],
},
handler: async (input) => {
try {
const content = await fs.readFile(input.path as string, "utf-8");
return { success: true, output: { content } };
} catch (error) {
return { success: false, error: error.message };
}
},
});
Adding Hooks
const originalExecute = adapter.execute.bind(adapter);
adapter.execute = async (request, options) => {
// Pre-hook
console.log("Starting execution:", request.message);
const result = await originalExecute(request, options);
// Post-hook
console.log("Completed:", result.usage.totalTokens, "tokens");
return result;
};