@northflare/agent
v0.1.3
Published
A TypeScript SDK for creating coding agents that can interact with various sandboxed execution environments - built on top of the AI SDK
Maintainers
Readme
AI Code Agents
A TypeScript SDK for creating AI agents that interact with sandboxed code execution environments. Built on the Vercel AI SDK, 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: trueorDEBUG=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+
npmor another package manager- AI SDK v5+ and zod v4+ (see below)
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 modelsThe SDK expects a LanguageModel instance at runtime; bring your own provider
package or construct the model in whatever way your application prefers.
Quick Start
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 contentswrite_file- Write or create filesdelete_file- Delete filesedit_file- Edit files with search/replace operationsmulti_edit_file- Multiple atomic edits to a single file with array of search/replace operationsmove_file- Move or rename filescopy_file- Copy filesread_many_files- Batch read multiple files
Directory & Search:
get_project_file_structure- Get complete project tree structureglob- Pattern-based file searchlist_directory- List directory contents
Task Management:
todo_read- Read and filter todo items with statistics and current task trackingtodo_write- Write/update todo items with status and priority tracking
Execution & External:
run_command- Execute shell commandsurl_fetcher- Fetch content from URLs and analyze with prompts (includes caching)
Control:
submit- Submits the current task with the requiredresultstring that is sent to the user as the final replytask- Launch specialized sub-agents for complex, multi-step tasks
Usage Examples
Basic SDK Interface Usage
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
// 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:
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:
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:
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 (
inheritto 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:
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
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.
