npm package: @northflare/agent
Description: A TypeScript SDK for creating coding agents that can interact with various sandboxed execution environments - built on top of the AI SDK
Latest version: 0.1.3

# AI Code Agents

A TypeScript SDK for creating AI agents that interact with sandboxed code execution environments. Built on the [Vercel AI SDK](https://ai-sdk.dev/), it provides a flexible, type-safe framework for building AI coding agents with comprehensive tool support and environment abstraction.

## Key Features

- **🔓 No Vendor Lock-in**: Environment abstraction layer works across any execution environment (local, Docker, cloud sandboxes). Model-agnostic architecture supports any AI provider through the Vercel AI SDK.
- **🛡️ Type-Safe**: Full TypeScript support with strict typing and comprehensive Zod schemas for all tool inputs/outputs.
- **🔧 Flexible Tool System**: Several built-in tools with configurable safety levels (`readonly`, `basic`, `all`). Easy to extend with custom tools.
- **🌍 Environment Abstraction**: Write tools once, run anywhere. All tools work seamlessly across different environment implementations.
- **📦 Multiple Environments**: Support for single or multiple environments per agent, enabling complex multi-context workflows.
- **🎯 Step-by-Step Execution**: Built-in step tracking with optional verbose debug logging (`debug: true` or `DEBUG=1`) for transparent agent behavior.
- **🔌 MCP Server Support**: Full integration with Model Context Protocol (MCP) servers for extending tool capabilities.
- **💾 Session Management**: Built-in session persistence and resumption with external storage support.

## Installation

You will need:

- Node.js 20+
- `npm` or another package manager
- AI SDK v5+ and zod v4+ (see below)

```bash
npm install @northflare/agent ai zod

# Install the provider you plan to use (the SDK no longer bundles any providers):
npm install @ai-sdk/anthropic  # For Claude models
# or
npm install @ai-sdk/openai     # For OpenAI models
```

The SDK expects a `LanguageModel` instance at runtime; bring your own provider
package or construct the model in whatever way your application prefers.

## Quick Start

```typescript
import { query } from "@northflare/agent";
import { openai } from "@ai-sdk/openai"; // Alternative: OpenAI models

// Create an agent using the SDK interface
const response = query({
  prompt: "Create a simple Node.js HTTP server in server.js",
  options: {
    model: openai("gpt-5.1"),
    maxTurns: 10,
    cwd: "/workspace",
    allowedTools: ["read_file", "write_file", "run_command", "todo_write"],
  },
});

// Process streaming messages
for await (const message of response) {
  if (message.type === "system" && message.subtype === "init") {
    console.log(`Session ID: ${message.session_id}`);
  }

  if (message.type === "assistant") {
    console.log(`Assistant: ${message.message.content}`);
  }

  if (message.type === "result") {
    console.log(`Result: ${message.result}`);
    break;
  }
}
```

### Debug logging

Set `debug: true` in `options` or export `DEBUG=1` to emit detailed, prefixed console logs (`[agent-sdk:agent]`, `[agent-sdk:mcp]`) that cover session creation/resume, tool filtering, MCP initialization/status, agent creation/invocation, and cleanup.

### Tools

Tools enable agents to interact with their environments. Each tool has a well-defined purpose with comprehensive input/output validation.

**File Operations:**

- `read_file` - Read file contents
- `write_file` - Write or create files
- `delete_file` - Delete files
- `edit_file` - Edit files with search/replace operations
- `multi_edit_file` - Multiple atomic edits to a single file with array of search/replace operations
- `move_file` - Move or rename files
- `copy_file` - Copy files
- `read_many_files` - Batch read multiple files

**Directory & Search:**

- `get_project_file_structure` - Get complete project tree structure
- `glob` - Pattern-based file search
- `list_directory` - List directory contents

**Task Management:**

- `todo_read` - Read and filter todo items with statistics and current task tracking
- `todo_write` - Write/update todo items with status and priority tracking

**Execution & External:**

- `run_command` - Execute shell commands
- `url_fetcher` - Fetch content from URLs and analyze with prompts (includes caching)

**Control:**

- `submit` - Submits the current task with the required `result` string that is sent to the user as the final reply
- `task` - Launch specialized sub-agents for complex, multi-step tasks

## Usage Examples

### Basic SDK Interface Usage

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Create a Python script that calculates fibonacci numbers",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    maxTurns: 5,
    allowedTools: ["read_file", "write_file", "run_command"],
  },
});

for await (const message of response) {
  if (message.type === "assistant") {
    console.log(message.message.content);
  }
  if (message.type === "result") {
    console.log("Final result:", message.result);
  }
}
```

### With Session Management

Resume previous conversations with session IDs:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

// First conversation
const response1 = query({
  prompt: "Create a React component for a todo list",
  options: { model: anthropic("MODEL_ID_PLACEHOLDER") },
});

let sessionId: string;
for await (const message of response1) {
  if (message.type === "system" && message.subtype === "init") {
    sessionId = message.session_id;
  }
}

// Resume the conversation later
const response2 = query({
  prompt: "Now add localStorage persistence to the todo list",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    resume: sessionId,
  },
});
```

### With External Session Storage

Persist sessions to a database or filesystem:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";
import fs from "fs/promises";

const response = query({
  prompt: "Build a web application",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    // Load session from storage
    loadSession: async (sessionId) => {
      try {
        const data = await fs.readFile(`sessions/${sessionId}.json`, "utf-8");
        return JSON.parse(data);
      } catch {
        return null;
      }
    },
    // Save session to storage
    saveSession: async (sessionId, session) => {
      await fs.writeFile(
        `sessions/${sessionId}.json`,
        JSON.stringify(session, null, 2)
      );
    },
  },
});
```

### With MCP Servers

Extend agent capabilities with MCP servers:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Search for information about TypeScript decorators",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    mcpServers: {
      "web-search": {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-google-search"],
        env: {
          GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
          GOOGLE_CSE_ID: process.env.GOOGLE_CSE_ID,
        },
      },
    },
  },
});
```

### With Custom Tool Restrictions

Control which tools the agent can use:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Analyze the project structure and create a report",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    // Only allow read operations
    allowedTools: [
      "read_file",
      "read_many_files",
      "get_project_file_structure",
      "glob",
      "list_directory",
    ],
    // Or explicitly disallow dangerous operations
    disallowedTools: ["run_command", "delete_file"],
  },
});
```

### With Task Management Tools

Use the todo tools for complex multi-step tasks:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Refactor the authentication module with proper error handling",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    allowedTools: [
      "read_file",
      "write_file",
      "edit_file",
      "multi_edit_file", // For batch edits
      "todo_read", // Track task progress
      "todo_write", // Update task status
      "run_command",
    ],
  },
});
```

### With External Data Fetching

Fetch and analyze external content:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Fetch the latest TypeScript documentation and create a summary",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    allowedTools: [
      "url_fetcher", // Fetch and analyze web content
      "write_file", // Save the summary
    ],
  },
});
```

### With Vision Support (Image Input)

The SDK supports image inputs in streaming mode with three different vision modes:

#### Direct Vision Mode (`visionModel: true`)

Images are sent directly to a vision-capable model:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: [
    { type: "text", text: "What do you see in this image?" },
    { type: "image", image: new URL("https://example.com/image.jpg") },
  ],
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"), // Must be vision-capable
    visionModel: true, // Enable direct vision mode
  },
});
```

#### Tool-Based Vision Mode (`visionModel: string`)

Use a separate vision model as a tool for analysis:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";
import {
  saveImage,
  loadImage,
} from "@northflare/agent/image-storage/filesystem";

const response = query({
  prompt: [
    { type: "text", text: "Analyze this image and write a haiku about it" },
    { type: "image", image: "data:image/jpeg;base64,..." }, // Base64 image
  ],
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"), // Main model
    visionModel: "openai/gpt-4-vision-preview", // Vision model as tool
    saveImage, // Required: image storage function
    loadImage, // Required: image retrieval function
  },
});
```

#### No Vision Support (`visionModel: false` or undefined)

Images are not supported and will throw an error if provided:

```typescript
const response = query({
  prompt: "Process text only",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    visionModel: false, // Explicitly disable vision support
  },
});
```

#### Image Storage Adapters

The SDK provides built-in image storage adapters for tool-based vision:

```typescript
// Filesystem storage (default)
import {
  saveImage,
  loadImage,
} from "@northflare/agent/image-storage/filesystem";

// Memory storage (for testing)
import { MemoryImageStorage } from "@northflare/agent";
const storage = new MemoryImageStorage();

// Custom storage implementation
const customSaveImage = async (id: string, data: string | URL) => {
  // Your custom storage logic (e.g., S3, database)
};

const customLoadImage = async (id: string) => {
  // Your custom retrieval logic
  return storedImageData;
};
```

### With Streaming Input (Interactive Sessions)

Use streaming input for multi-turn conversations with dynamic user input and interruptions:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";
import { v4 as uuidv4 } from "uuid";

// Create an async generator for streaming messages
async function* generateMessages() {
  const sessionId = uuidv4();

  // Initial message - analyze the codebase
  yield {
    type: "user" as const,
    uuid: uuidv4(),
    session_id: sessionId,
    message: {
      role: "user" as const,
      content: "Analyze this codebase for potential performance issues",
    },
    parent_tool_use_id: null,
  };

  // Wait for some condition or user input
  await new Promise((resolve) => setTimeout(resolve, 2000));

  // Follow-up message after some processing
  yield {
    type: "user" as const,
    uuid: uuidv4(),
    session_id: sessionId,
    message: {
      role: "user" as const,
      content: "Now focus on optimizing the database queries",
    },
    parent_tool_use_id: null,
  };

  // Add more messages based on dynamic conditions
  const userDecision = await getUserInput(); // Your custom input function
  if (userDecision === "refactor") {
    yield {
      type: "user" as const,
      uuid: uuidv4(),
      session_id: sessionId,
      message: {
        role: "user" as const,
        content: "Refactor the identified bottlenecks and create unit tests",
      },
      parent_tool_use_id: null,
    };
  }
}

// Process the streaming session
const response = query({
  prompt: generateMessages(), // Pass the async generator
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    maxTurns: 10,
    allowedTools: [
      "read_file",
      "write_file",
      "edit_file",
      "multi_edit_file",
      "get_project_file_structure",
      "run_command",
      "todo_write",
      "todo_read",
    ],
  },
});

// Handle streaming responses with interruption capability
const timeoutId = setTimeout(() => {
  console.log("Interrupting after timeout...");
  response.interrupt();
}, 30000); // Interrupt after 30 seconds if needed

try {
  for await (const message of response) {
    if (message.type === "system" && message.subtype === "init") {
      console.log(`Session started: ${message.session_id}`);
    }

    if (message.type === "assistant") {
      // Stream assistant responses as they come
      console.log("Assistant:", message.message.content);
    }

    if (message.type === "stream_event") {
      // Stream events for tool usage
      console.log(`Tool event: ${message.event.type}`);
    }

    if (message.type === "result") {
      if (message.subtype === "success") {
        console.log("Final result:", message.result);
      }
      clearTimeout(timeoutId);
      break;
    }
  }
} catch (error) {
  console.error("Error during streaming:", error);
  clearTimeout(timeoutId);
}

// Helper function for getting user input (example)
async function getUserInput(): Promise<string> {
  // In a real application, this would get actual user input
  // For example, from readline, a web UI, or other input source
  return "refactor";
}
```

**Key features of streaming input:**

- **Dynamic Message Generation**: Use async generators to yield messages based on runtime conditions
- **Multi-turn Conversations**: Maintain context across multiple user inputs
- **Session Persistence**: Messages automatically maintain session context
- **Interruption Support**: Cancel operations at any time using `response.interrupt()`
- **Real-time Feedback**: Process responses as they stream, not just final results
- **Flexible Control Flow**: Add messages based on conditions, user input, or external events

### With Output Format

Define a structured output format for the agent:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Create a REST API for a blog",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    outputFormat: {
      type: "structured",
      schema: {
        type: "object",
        properties: {
          endpoints: {
            type: "array",
            items: {
              type: "object",
              properties: {
                method: { type: "string" },
                path: { type: "string" },
                description: { type: "string" },
              },
            },
          },
          filesCreated: {
            type: "array",
            items: { type: "string" },
          },
        },
      },
    },
  },
});
```

### With Sub-agents

Use specialized sub-agents for complex, multi-step tasks:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";
import type { AgentDefinition } from "@northflare/agent";

// Define specialized sub-agents
const agents: Record<string, AgentDefinition> = {
  "code-reviewer": {
    description: "Reviews code for bugs, security issues, and best practices",
    prompt:
      "You are a thorough code reviewer. Focus on finding bugs, security vulnerabilities, and code quality issues.",
    tools: ["read_file", "grep", "glob"],
    model: "MODEL_ID_PLACEHOLDER", // Use a powerful model for analysis
  },
  "test-writer": {
    description: "Writes comprehensive unit and integration tests",
    prompt:
      "You are a test writing specialist. Write thorough tests with good coverage and edge cases.",
    tools: ["read_file", "write_file", "edit_file"],
    model: "claude-3-haiku-20240307", // Use a faster model for code generation
  },
  documenter: {
    description: "Creates clear and comprehensive documentation",
    prompt:
      "You are a technical documentation expert. Write clear, concise documentation with examples.",
    tools: ["read_file", "write_file"],
    model: "inherit", // Inherit the parent agent's model
  },
};

// Main agent orchestrates sub-agents
const response = query({
  prompt:
    "Review the authentication module, write tests for any issues found, and document the changes",
  options: {
    model: anthropic("MODEL_ID_PLACEHOLDER"),
    agents, // Pass sub-agent definitions
    allowedTools: ["task", "read_file", "write_file"], // Main agent needs 'task' tool
    maxTurns: 15,
  },
});

for await (const message of response) {
  if (message.type === "tool_result" && message.tool_name === "task") {
    console.log("Sub-agent completed:", message.result);
  }
  if (message.type === "result") {
    console.log("All tasks completed:", message.result);
  }
}
```

**Sub-agent Features:**

- **Specialized Prompts**: Each sub-agent can have its own system prompt for focused behavior
- **Tool Restrictions**: Limit sub-agents to specific tools for safety and focus
- **Model Selection**: Use different models based on task complexity (`inherit` to use parent's model)
- **Isolated Context**: Sub-agents run with fresh context, preventing confusion
- **Parallel Execution**: Multiple sub-agents can run concurrently when invoked in the same message
- **Recursion Protection**: Sub-agents cannot invoke other sub-agents (Task tool filtered out)

### Interrupting Execution

Stop agent execution at any time:

```typescript
import { query } from "@northflare/agent";
import { anthropic } from "@ai-sdk/anthropic";

const response = query({
  prompt: "Create multiple components for a dashboard",
  options: { model: anthropic("claude-4-5-sonnet") },
});

// Stop after 5 seconds
setTimeout(() => {
  response.interrupt();
}, 5000);

for await (const message of response) {
  // Process messages until interrupted
}
```

## API Reference

```typescript
import type { LanguageModel } from "ai";

// Sub-agent definition
interface AgentDefinition {
  description: string; // When to use this agent
  prompt: string; // System prompt for the agent
  tools?: string[]; // Allowed tool names (omit for all)
  model?: string | "inherit"; // Model override (defaults to 'inherit')
}

interface QueryOptions {
  // Core options
  model?: LanguageModel;
  maxTurns?: number;
  abortController?: AbortController;

  // Environment options
  cwd?: string;
  env?: Record<string, string>;
  additionalDirectories?: string[];

  // Tool options
  allowedTools?: string[];
  disallowedTools?: string[];

  // Sub-agents configuration
  agents?: Record<string, AgentDefinition>;

  // Session management
  resume?: string;
  loadSession?: (sessionId: string) => Promise<SessionData | null>;
  saveSession?: (sessionId: string, session: SessionData) => Promise<void>;

  // MCP servers
  mcpServers?: Record<string, MCPServerConfig>;
  onElicitationRequest?: (request: ElicitationRequest) => Promise<any>;

  // Output options
  outputFormat?: OutputFormat;
  systemPrompt?: string | SystemPromptPreset;
  stderr?: (data: Buffer) => void;

  // Diagnostics
  debug?: boolean; // Verbose lifecycle logging (also enabled via DEBUG env)
}
```

## Copyright

This is a fork of [AI Code Agents](https://github.com/felixarntz/ai-code-agents).