AGENTS.md - Agent SDK Development Guide
This file provides instructions for AI coding agents working in this repository.
Project Overview
Agent SDK is a TypeScript library for building AI agents with multi-model support (createModel / per-provider factories), MCP integration (MCPClient, MCPAdapter, optional mcp_config.json), skill system, long-term memory via MemoryManager, streaming helpers under src/streaming/, and JSONL or in-memory session storage.
Optional environment: set TAVILY_API_KEY to enable built-in WebSearch via the Tavily API (src/tools/builtin/tavily-search.ts). Optional tuning: TAVILY_SEARCH_DEPTH, TAVILY_MAX_RESULTS.
Zod: zod is a peerDependency (^4). It remains in devDependencies for this repo’s tests and builds; consumers must install zod themselves (see root README.md Installation).
Build/Lint/Test Commands
# Install dependencies
pnpm install
# Build the project (ESM + CJS + type declarations) via tsup
pnpm build
# Watch mode rebuild during development
pnpm dev
# Type checking (no emit)
pnpm lint
# Run all tests once
pnpm test:run
# Run tests in watch mode
pnpm test
# Run a single test file
pnpm vitest run tests/unit/tools.test.ts
# Run tests matching a pattern
pnpm vitest run -t "should register a tool"
# Clean build artifacts
pnpm clean
Requires Node.js >= 18.
Code Style Guidelines
Imports
// 1. External libraries first
import { z } from 'zod';
import { Command } from 'commander';
// 2. Internal imports - use relative paths with .js extension
import { ToolRegistry } from '../tools/registry.js';
import type { Message, StreamEvent } from '../core/types.js';
// 3. Use `import type` for type-only imports
import type { ModelAdapter, ToolDefinition } from '../core/types.js';
File Structure
Each module should follow this pattern:
src/module/
├── index.ts # Re-exports (barrel file)
├── types.ts # Type definitions (optional)
├── main-file.ts # Core implementation
└── helper.ts # Helper functions
Naming Conventions
- Files:
kebab-case.ts(e.g.,tool-registry.ts,stream-transform.ts) - Classes:
PascalCase(e.g.,Agent,ToolRegistry,MCPClient) - Interfaces/Types:
PascalCase(e.g.,AgentConfig,ToolDefinition) - Functions:
camelCase(e.g.,createTool,zodToJsonSchema) - Constants:
UPPER_SNAKE_CASE(e.g.,DEFAULT_SYSTEM_PROMPT) - Private members: prefix with
_for unused, otherwise no prefix
Type Definitions
// Use interfaces for object shapes
export interface AgentConfig {
model: ModelAdapter;
systemPrompt?: string;
tools?: ToolDefinition[];
}
// Use type aliases for unions/intersections
export type StreamEvent = TextEvent | ToolCallEvent | ErrorEvent;
// Export types from dedicated types.ts files
export * from './types.js';
Function Signatures
// Async functions that return streams use AsyncIterable
stream(params: ModelParams): AsyncIterable<StreamChunk>
// Factory functions use create* prefix
export function createTool(config: ToolConfig): ToolDefinition
export function createModel(config: CreateModelConfig): ModelAdapter
// Static methods for default instances
export function createAgent(config: AgentConfig): Agent
Error Handling
// Always catch and wrap errors with context
try {
const content = await fs.readFile(path, 'utf-8');
return { content };
} catch (error) {
return {
content: `Error reading file: ${error instanceof Error ? error.message : String(error)}`,
isError: true
};
}
// Throw descriptive errors for invalid configuration
if (!this.apiKey) {
throw new Error('OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass apiKey in config.');
}
Zod Schemas
// Use Zod for tool parameter validation (names should match built-in style, e.g. Read / Write)
export const readFileTool = createTool({
name: 'Read',
description: 'Read the contents of a file',
parameters: z.object({
file_path: z.string().describe('Absolute path to the file to read')
}),
handler: async ({ file_path }) => { ... }
});
JSDoc Comments
/**
* Short description of what the function does
* @param param - Description of parameter
* @returns Description of return value
*/
export function myFunction(param: string): Result { ... }
Testing
import { describe, it, expect } from 'vitest';
describe('FeatureName', () => {
it('should do something specific', () => {
const result = myFunction();
expect(result).toBe(expected);
});
it('should handle errors', async () => {
const result = await riskyOperation();
expect(result.isError).toBe(true);
});
});
Architecture Patterns
- Factory Functions: Use
create*pattern for complex objects (createTool,createModel,createMCPAdapter, etc.) - Interface Segregation: Keep interfaces focused and composable
- Async Iterables: Use
AsyncIterable<T>for streaming operations - Adapter Pattern: Model adapters implement
ModelAdapter; MCP usesMCPAdapter/MCPClient - Registry Pattern: Central registries for tools and skills
- Memory:
MemoryManagerloads optional long-term instructions from CLAUDE.md paths; distinct from sessionSessionManagerstorage
Third-party documentation policy
Public docs (docs/**/*.md, including docs/sdk-*.md and topical guides such as docs/tool-hook-mechanism.md) treat Agent as the only supported integration surface for application code. The repository root README.md is for installation and navigation only; factual content lives under docs/. Model factories (createOpenAI, etc.) configure AgentConfig.model; do not document or encourage calling ModelAdapter.stream / complete directly in product code. Chunk→event helpers (transformStream, toAgentStream, etc.) are not part of the public API. Keep this aligned with docs/sdk-overview.md section 3.
Module Exports
Each module's index.ts should:
// Export classes and functions
export { ToolRegistry } from './registry.js';
export { createTool } from './registry.js';
// Export types separately
export type { ToolConfig, ToolResult } from './types.js';
The root src/index.ts is the main public API; package.json also exposes @ddlqhd/agent-sdk/models and @ddlqhd/agent-sdk/tools.
Commit Messages
Follow conventional commits:
feat:for new featuresfix:for bug fixesdocs:for documentationtest:for testsrefactor:for refactoring
Example: feat: add MCP integration with stdio transport