Skip to main content

Filesystem API Reference

Complete API documentation for AG-Kit filesystem tools, including method signatures, input/output schemas, and practical usage examples.

API Overview

The filesystem API provides tools that work across multiple execution backends with consistent interfaces and type safety.

Available Tools

Individual Tools

  • ReadTool - Read file contents with encoding support
  • WriteTool - Write content to files with directory creation
  • EditTool - Make targeted edits to existing files
  • GlobTool - Find files using glob patterns
  • GrepTool - Search file contents with regex patterns
  • LsTool - List directory contents with detailed information

Comprehensive Tool

  • StrReplaceEditor - Advanced multi-operation file editor for AI coding workflows

Quick Comparison

FeatureLocalIn-MemorySandbox
Performance✅ Maximum✅ High⚠️ Network overhead
Security⚠️ Full access✅ Isolated✅ Sandboxed
Persistence✅ Permanent❌ Temporary✅ Session-based
Testing⚠️ Side effects✅ Perfect⚠️ Complex setup

Setup

Local File Operator

import { LocalFileOperator } from '@ag-kit/tools/fs';

const context = {
  workingDirectory: process.cwd(),
  fsOperator: new LocalFileOperator()
};

In-Memory File Operator

import { InMemoryFileOperator } from '@ag-kit/tools/fs';
import { fs } from 'memfs';

const context = {
  workingDirectory: '/virtual',
  fsOperator: new InMemoryFileOperator(fs)
};

Sandbox File Operator

import { SandboxFileOperator } from '@ag-kit/tools/fs';

const context = {
  workingDirectory: '/home/user',
  fsOperator: new SandboxFileOperator({ sandbox })
};

Transaction Support

Transaction support allows you to group multiple file operations and roll them back if needed. The TransactionFileOperator wraps any base operator (Local, In-Memory, or Sandbox) to provide atomic operations.

Basic Usage

import { TransactionFileOperator, TransactionExecutor } from '@ag-kit/tools/fs/transaction';
import { LocalFileOperator } from '@ag-kit/tools/fs';

// Wrap any base operator with transaction support
const baseOperator = new LocalFileOperator(); // or InMemoryFileOperator, SandboxFileOperator
const transactionOperator = new TransactionFileOperator(baseOperator);

const context = {
  workingDirectory: process.cwd(),
  fsOperator: transactionOperator
};

Manual Transaction Control

await transactionOperator.beginTransaction();

try {
  await writeTool.invoke({
    file_path: 'config.json',
    content: '{"version": "2.0"}'
  });
  
  await editTool.invoke({
    file_path: 'package.json',
    old_string: '"version": "1.0.0"',
    new_string: '"version": "2.0.0"'
  });
  
  await transactionOperator.commitTransaction();
} catch (error) {
  await transactionOperator.rollbackTransaction(); // All changes reverted
}
import { TransactionExecutor, createTransactionExecutor, withTransaction } from '@ag-kit/tools/fs/transaction';

// Method 1: Using TransactionExecutor class
const executor = new TransactionExecutor(baseOperator);

const result = await executor.executeInTransaction(async (transactionOperator) => {
  // All operations are automatically transactional
  await writeTool.invoke({
    file_path: 'data.json',
    content: JSON.stringify(data)
  });

  await editTool.invoke({
    file_path: 'config.ts',
    old_string: 'DEBUG = false',
    new_string: 'DEBUG = true'
  });

  // If this throws, all operations are automatically rolled back
  if (!validateData(data)) {
    throw new Error('Validation failed');
  }

  return { filesModified: 2 };
});

// Method 2: Using convenience function
const result2 = await withTransaction(baseOperator, async (transactionOperator) => {
  // Same operations as above
  return { success: true };
});

// Method 3: Using builder pattern
const executor2 = createTransactionExecutor(baseOperator);
const builderResult = await executor2
  .writeFile('config.json', JSON.stringify(config))
  .mkdir('logs', { recursive: true })
  .custom(async (op) => {
    // Custom operation
    await op.writeFile('logs/app.log', 'Application started');
  })
  .execute();

if (builderResult.success) {
  console.log('All operations completed successfully');
} else {
  console.error('Transaction failed:', builderResult.error);
}

Practical Examples

Safe Configuration Update

async function updateConfig(newConfig: Config) {
  const executor = new TransactionExecutor(transactionOperator);
  
  return await executor.execute(async () => {
    // Update main config
    await writeTool.invoke({
      file_path: 'config.json',
      content: JSON.stringify(newConfig, null, 2)
    });
    
    // Update package.json version
    await editTool.invoke({
      file_path: 'package.json',
      old_string: `"version": "${oldConfig.version}"`,
      new_string: `"version": "${newConfig.version}"`
    });
    
    // Validate - rolls back if this fails
    if (!isValidConfig(newConfig)) {
      throw new Error('Invalid configuration');
    }
    
    return { updated: true };
  });
}

Batch File Processing

async function processFiles(files: string[]) {
  const executor = new TransactionExecutor(transactionOperator);
  
  return await executor.execute(async () => {
    for (const file of files) {
      const content = await readTool.invoke({ file_path: file });
      const processed = processContent(content);
      
      await writeTool.invoke({
        file_path: file,
        content: processed
      });
    }
    
    return { processed: files.length };
  });
}

Individual Tools Usage

ReadTool

import { createReadTool } from '@ag-kit/tools/fs';

const readTool = createReadTool(context);

// Read entire file
const result = await readTool.invoke({
  file_path: 'src/config.json'
});

// Read with line range
const partialResult = await readTool.invoke({
  file_path: 'large-file.txt',
  start_line: 100,
  end_line: 200
});

WriteTool

import { createWriteTool } from '@ag-kit/tools/fs';

const writeTool = createWriteTool(context);

const result = await writeTool.invoke({
  file_path: 'output/data.json',
  content: JSON.stringify({ message: 'Hello World' }, null, 2),
  create_directories: true
});

EditTool

import { createEditTool } from '@ag-kit/tools/fs';

const editTool = createEditTool(context);

const result = await editTool.invoke({
  file_path: 'src/config.ts',
  old_string: 'const API_URL = "http://localhost"',
  new_string: 'const API_URL = "https://api.production.com"',
  expected_replacements: 1 // Optional: validate exact number of replacements
});

GlobTool

import { createGlobTool } from '@ag-kit/tools/fs';

const globTool = createGlobTool(context);

const result = await globTool.invoke({
  pattern: '**/*.ts',
  exclude_patterns: ['node_modules/**', 'dist/**']
});

GrepTool

import { createGrepTool } from '@ag-kit/tools/fs';

const grepTool = createGrepTool(context);

const result = await grepTool.invoke({
  pattern: 'TODO|FIXME',
  file_pattern: '**/*.{ts,js}',
  case_sensitive: false
});

LsTool

import { createLsTool } from '@ag-kit/tools/fs';

const lsTool = createLsTool(context);

const result = await lsTool.invoke({
  directory_path: 'src',
  recursive: true,
  show_hidden: false
});

StrReplaceEditor

Advanced file editing tool designed for AI coding workflows:
import { createStrReplaceEditor } from '@ag-kit/tools/fs';

const editor = createStrReplaceEditor(context);

// Replace text
await editor.invoke({
  command: 'str_replace',
  path: 'src/app.ts',
  old_str: 'old implementation',
  new_str: 'new implementation'
});

// View file contents
await editor.invoke({
  command: 'view',
  path: 'file.ts',
  view_range: [1, 50] // Optional line range
});

// Create new file
await editor.invoke({
  command: 'create',
  path: 'new-file.ts',
  file_text: 'export const config = {};'
});

API Reference

Common Interfaces

interface ExecutionContext {
  workingDirectory: string;
  fsOperator: BaseFileOperator;
  messageHandler?: any;
}

interface ToolResponse {
  success: boolean;
  error?: string;
  error_type?: 'validation' | 'security' | 'file_not_found' | 'permission' | 'execution';
  [key: string]: any;
}

FilesystemToolkit Configuration

/**
 * Configuration options for FilesystemToolkit
 */
interface FilesystemToolkitConfig {
  /**
   * Mode for file editing operations
   * - 'integrated': Use str-replace-editor tool (single tool for read/write/edit operations)
   * - 'atomic': Use separate read, write, and edit tools
   * - 'enhanced': Use atomic tools plus multi-edit tool for batch operations
   */
  mode: "integrated" | "atomic" | "enhanced";
}

interface FilesystemToolkitOptions extends IToolkitOptions {
  context: ExecutionContext;
  config?: FilesystemToolkitConfig;
}

class FilesystemToolkit extends BaseToolkit {
  constructor(options: FilesystemToolkitOptions);

  // Get tools by category
  getFileOperationTools(): BaseTool[];
  getUtilityTools(): BaseTool[];

  // Configuration management
  getConfig(): FilesystemToolkitConfig;
  switchMode(mode: "integrated" | "atomic"): void;

  // Statistics
  getStats(): { mode: string };
}

// Usage examples
const toolkit = new FilesystemToolkit({
  context: {
    workingDirectory: process.cwd(),
    fsOperator: new LocalFileOperator()
  },
  config: {
    mode: "enhanced" // Use atomic tools + multi-edit
  }
});

// Switch modes dynamically
toolkit.switchMode("integrated"); // Switch to str-replace-editor
toolkit.switchMode("enhanced");   // Switch back to enhanced mode

Tool Parameters

// ReadTool
interface ReadToolParams {
  file_path: string;
  start_line?: number;
  end_line?: number;
  encoding?: BufferEncoding;
}

// WriteTool
interface WriteToolParams {
  file_path: string;
  content: string;
  create_directories?: boolean;
  encoding?: BufferEncoding;
}

// EditTool
interface EditToolParams {
  file_path: string;
  old_string: string;
  new_string: string;
  expected_replacements?: number;
}

// GlobTool
interface GlobToolParams {
  pattern: string;
  exclude_patterns?: string[];
  include_directories?: boolean;
  max_results?: number;
}

// GrepTool
interface GrepToolParams {
  pattern: string;
  file_pattern?: string;
  case_sensitive?: boolean;
  max_results?: number;
  context_lines?: number;
}

// LsTool
interface LsToolParams {
  directory_path: string;
  recursive?: boolean;
  show_hidden?: boolean;
  include_stats?: boolean;
}

Transaction Interfaces

// Transaction wrapper for any base operator
class TransactionFileOperator extends BaseFileOperator {
  constructor(baseOperator: BaseFileOperator, operatorType?: EOperatorType);

  // Transaction management
  beginTransaction(): Promise<void>;
  commitTransaction(): Promise<void>;
  rollbackTransaction(): Promise<void>;
  resetTransactionState(): void;

  // Status and configuration
  isTransactionActive(): boolean;
  getTransactionOperations(): readonly TransactionOperation[];
  setConfig(config: Partial<ITransactionFileOperatorConfig>): void;
  getConfig(): ITransactionFileOperatorConfig;

  // All BaseFileOperator methods are available
  // (readFile, writeFile, mkdir, unlink, etc.)
}

// Transaction executor for convenient operation management
class TransactionExecutor {
  constructor(baseOperator: BaseFileOperator);

  // Get the underlying transaction operator
  getOperator(): TransactionFileOperator;

  // Builder pattern methods
  writeFile(path: string, data: string | Buffer, options?: any): TransactionExecutor;
  mkdir(path: string, options?: any): TransactionExecutor;
  custom(operation: (op: TransactionFileOperator) => Promise<void>): TransactionExecutor;

  // Execution methods
  execute(): Promise<{ success: boolean; error?: Error }>;
  executeInTransaction<T>(operation: (operator: TransactionFileOperator) => Promise<T>): Promise<T>;
  createTransaction(): Promise<TransactionController>;
}

// Manual transaction control
class TransactionController {
  constructor(transactionOperator: TransactionFileOperator);

  getOperator(): TransactionFileOperator;
  commit(): Promise<void>;
  rollback(): Promise<void>;
  isTransactionCompleted(): boolean;
  getOperationHistory(): readonly TransactionOperation[];
}

// Configuration interfaces
interface ITransactionFileOperatorConfig {
  batchSize: number;
  maxConcurrency: number;
  enableLazyLoading: boolean;
}

enum EOperatorType {
  LOCAL = "local",
  MEMORY = "memory",
  SANDBOX = "sandbox"
}

interface TransactionOperation {
  type: "write" | "mkdir" | "unlink" | "rmdir" | "rename";
  path: string;
  oldPath?: string;
  timestamp: number;
}

interface FileState {
  exists: boolean;
  isDirectory: boolean;
  contentLoader?: () => Promise<Buffer>;
}

// Utility functions
function createTransactionExecutor(baseOperator: BaseFileOperator): TransactionExecutor;
function withTransaction<T>(
  baseOperator: BaseFileOperator,
  operation: (operator: TransactionFileOperator) => Promise<T>
): Promise<T>;

Advanced Usage

Multi-File Refactoring with MultiEditTool

import { createMultieditTool, createGrepTool } from '@ag-kit/tools/fs';

const multieditTool = createMultieditTool(context);
const grepTool = createGrepTool(context);

// Find all files that need refactoring
const searchResult = await grepTool.invoke({
  pattern: 'oldApiFunction|OLD_CONSTANT',
  file_pattern: '**/*.{ts,js}',
  case_sensitive: false
});

// Apply consistent refactoring to each file
for (const match of searchResult.data.matches) {
  const refactorResult = await multieditTool.invoke({
    file_path: match.file,
    edits: [
      {
        old_string: 'oldApiFunction',
        new_string: 'newApiFunction',
        expected_replacements: 1
      },
      {
        old_string: 'OLD_CONSTANT',
        new_string: 'NEW_CONSTANT',
        expected_replacements: 1
      },
      {
        old_string: '// TODO: Update this',
        new_string: '// DONE: Updated to new API',
        expected_replacements: 1
      }
    ]
  });

  if (refactorResult.success) {
    console.log(`✓ Refactored ${match.file}: ${refactorResult.data.total_replacements} changes`);
  } else {
    console.log(`✗ Failed to refactor ${match.file}`);
  }
}

Transactional File Operations

import { withTransaction, TransactionExecutor } from '@ag-kit/tools/fs/transaction';
import { LocalFileOperator } from '@ag-kit/tools/fs';

const baseOperator = new LocalFileOperator();

// Method 1: Simple transaction wrapper
const result = await withTransaction(baseOperator, async (transactionOp) => {
  // Create multiple related files atomically
  await transactionOp.mkdir('project/src', { recursive: true });
  await transactionOp.mkdir('project/tests', { recursive: true });

  await transactionOp.writeFile('project/package.json', JSON.stringify({
    name: 'my-project',
    version: '1.0.0'
  }, null, 2));

  await transactionOp.writeFile('project/src/index.ts', 'export const main = () => console.log("Hello");');
  await transactionOp.writeFile('project/tests/index.test.ts', 'import { main } from "../src/index";');

  // If any operation fails, all changes are rolled back
  return { projectCreated: true };
});

// Method 2: Manual transaction control
const executor = new TransactionExecutor(baseOperator);
const transaction = await executor.createTransaction();

try {
  const operator = transaction.getOperator();

  // Perform operations
  await operator.writeFile('config.json', JSON.stringify(config));
  await operator.writeFile('backup.json', JSON.stringify(backup));

  // Validate before committing
  if (await validateConfiguration(config)) {
    await transaction.commit();
    console.log('Configuration updated successfully');
  } else {
    await transaction.rollback();
    console.log('Configuration validation failed, changes rolled back');
  }
} catch (error) {
  if (!transaction.isTransactionCompleted()) {
    await transaction.rollback();
  }
  throw error;
}

Multi-Backend Workflow

function createFileOperator() {
  const env = process.env.NODE_ENV;

  switch (env) {
    case 'test':
      return new InMemoryFileOperator(fs);
    case 'production':
      return new SandboxFileOperator({ sandbox });
    default:
      return new LocalFileOperator();
  }
}

const context = {
  workingDirectory: process.cwd(),
  fsOperator: createFileOperator()
};

Batch Operations

const files = ['config.json', 'package.json', 'tsconfig.json'];
const results = await Promise.all(
  files.map(file => readTool.invoke({ file_path: file }))
);

Error Handling

try {
  const result = await readTool.invoke({ file_path: 'nonexistent.txt' });
  
  if (!result.success) {
    switch (result.error_type) {
      case 'file_not_found':
        console.log('File does not exist');
        break;
      case 'permission':
        console.log('Permission denied');
        break;
      case 'security':
        console.log('Security violation');
        break;
      default:
        console.log('Unknown error:', result.error);
    }
  }
} catch (error) {
  console.error('Tool execution failed:', error);
}

Security Features

Path Validation

All tools automatically validate paths to prevent directory traversal attacks:
// ✅ Safe - relative paths within workspace
{ file_path: 'src/config.ts' }

// ❌ Blocked - directory traversal
{ file_path: '../../../etc/passwd' }

Production Recommendations

  • Use Sandbox File Operator for untrusted code
  • Use In-Memory File Operator for testing
  • Use Local File Operator only for trusted environments

Examples

Development Workflow

// Read configuration
const config = await readTool.invoke({ file_path: 'config.json' });

// Update configuration
await editTool.invoke({
  file_path: 'config.json',
  old_string: '"debug": false',
  new_string: '"debug": true'
});

// Find all TypeScript files
const tsFiles = await globTool.invoke({
  pattern: '**/*.ts',
  exclude_patterns: ['node_modules/**']
});

// Search for TODOs
const todos = await grepTool.invoke({
  pattern: 'TODO|FIXME',
  file_pattern: '**/*.{ts,js}'
});

Safe Configuration Migration

import { withTransaction } from '@ag-kit/tools/fs/transaction';
import { createMultieditTool, createReadTool, createWriteTool, createGrepTool } from '@ag-kit/tools/fs';

async function migrateConfiguration(oldConfigPath: string, newConfigPath: string) {
  const readTool = createReadTool(context);
  const writeTool = createWriteTool(context);
  const multieditTool = createMultieditTool(context);
  const grepTool = createGrepTool(context);

  return await withTransaction(context.fsOperator, async (transactionOp) => {
    // Read old configuration
    const oldConfig = await readTool.invoke({ file_path: oldConfigPath });
    const configData = JSON.parse(oldConfig.data.content);

    // Transform configuration structure
    const newConfigData = {
      version: '2.0',
      settings: {
        api: {
          baseUrl: configData.apiUrl,
          timeout: configData.timeout || 5000
        },
        features: {
          debug: configData.debug || false,
          logging: configData.enableLogging || true
        }
      }
    };

    // Write new configuration
    await writeTool.invoke({
      file_path: newConfigPath,
      content: JSON.stringify(newConfigData, null, 2),
      create_directories: true
    });

    // Update all files that reference the old config
    const configReferences = await grepTool.invoke({
      pattern: oldConfigPath.replace(/\./g, '\\.'),
      file_pattern: '**/*.{ts,js,json}'
    });

    for (const ref of configReferences.data.matches) {
      await multieditTool.invoke({
        file_path: ref.file,
        edits: [
          {
            old_string: oldConfigPath,
            new_string: newConfigPath,
            expected_replacements: 1
          }
        ]
      });
    }

    // Archive old configuration
    await transactionOp.rename(oldConfigPath, `${oldConfigPath}.backup`);

    return {
      migrated: true,
      newConfigPath,
      referencesUpdated: configReferences.data.matches.length
    };
  });
}

// Usage
const migrationResult = await migrateConfiguration('old-config.json', 'config/app.json');
if (migrationResult.migrated) {
  console.log(`Configuration migrated successfully to ${migrationResult.newConfigPath}`);
  console.log(`Updated ${migrationResult.referencesUpdated} file references`);
}

Batch File Processing with Error Recovery

import { TransactionExecutor } from '@ag-kit/tools/fs/transaction';

async function processFilesWithRecovery(filePatterns: string[]) {
  const executor = new TransactionExecutor(context.fsOperator);
  const results = [];

  for (const pattern of filePatterns) {
    const transaction = await executor.createTransaction();

    try {
      const operator = transaction.getOperator();

      // Find files matching pattern
      const files = await globTool.invoke({ pattern });

      // Process each file
      for (const file of files.data.matches) {
        const content = await readTool.invoke({ file_path: file.path });

        // Apply transformations
        const processedContent = await processFileContent(content.data.content);

        // Write back processed content
        await operator.writeFile(file.path, processedContent);
      }

      // Commit if all files processed successfully
      await transaction.commit();
      results.push({ pattern, success: true, filesProcessed: files.data.matches.length });

    } catch (error) {
      // Rollback on any error
      await transaction.rollback();
      results.push({ pattern, success: false, error: error.message });
    }
  }

  return results;
}

async function processFileContent(content: string): Promise<string> {
  // Example: Remove console.log statements and add proper logging
  return content
    .replace(/console\.log\([^)]+\);?\n?/g, '')
    .replace(/\/\/ TODO:/g, '// DONE:')
    .replace(/var /g, 'const ');
}

Key Features Summary

New in This Version

MultiEditTool

  • Purpose: Perform multiple sequential edits on a single file
  • Benefits: Atomic batch operations, consistent error handling, detailed results
  • Use Cases: Configuration updates, code refactoring, batch replacements

Transaction Support

  • Purpose: Group multiple file operations with rollback capability
  • Benefits: Data consistency, error recovery, atomic operations
  • Strategies: Local (file backup), Memory (snapshot), Sandbox (content tracking)

Enhanced Toolkit Mode

  • Purpose: Combine atomic tools with multi-edit capabilities
  • Benefits: Flexibility for different use cases, optimal tool selection
  • Configuration: mode: "enhanced" in FilesystemToolkit

Best Practices

  1. Use MultiEditTool for multiple changes to the same file
  2. Use Transactions for operations that must succeed or fail together
  3. Use Enhanced Mode when you need both atomic and batch operations
  4. Validate expected_replacements to catch unexpected changes
  5. Handle errors gracefully with proper rollback mechanisms

Migration Guide

From EditTool to MultiEditTool

// Before: Multiple EditTool calls
await editTool.invoke({ file_path: 'config.ts', old_string: 'A', new_string: 'B' });
await editTool.invoke({ file_path: 'config.ts', old_string: 'C', new_string: 'D' });

// After: Single MultiEditTool call
await multieditTool.invoke({
  file_path: 'config.ts',
  edits: [
    { old_string: 'A', new_string: 'B' },
    { old_string: 'C', new_string: 'D' }
  ]
});

Adding Transaction Support

// Before: Direct operations
await writeTool.invoke({ file_path: 'file1.txt', content: 'data1' });
await writeTool.invoke({ file_path: 'file2.txt', content: 'data2' });

// After: Transactional operations
await withTransaction(baseOperator, async (transactionOp) => {
  await transactionOp.writeFile('file1.txt', 'data1');
  await transactionOp.writeFile('file2.txt', 'data2');
  // Both succeed or both are rolled back
});