Skip to main content
Use Warp Grep as a tool in your AI agent workflows. The SDK provides adapters for Anthropic, OpenAI, and Vercel AI SDK.

Quick Start

  • MorphClient
  • Anthropic
  • OpenAI
  • Vercel AI SDK
  • Standalone Client
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });

// Simple - defaults to LocalRipgrepProvider
const result = await morph.warpGrep.execute({
  query: 'Find authentication middleware',
  repoRoot: '.'
});

if (result.success) {
  for (const ctx of result.contexts) {
    console.log(`File: ${ctx.file}`);
    console.log(ctx.content);
  }
}

Response Types

Both direct usage and tool usage return the same response structure:
  • Success Response
  • Error Response
interface WarpGrepResult {
  success: true;
  contexts: Array<{
    file: string;    // File path relative to repo root
    content: string; // Content of the relevant code section
  }>;
  summary: string;   // Summary of what was found
}
All types are exported from the SDK root:
import type { 
  // Result types
  WarpGrepResult, 
  WarpGrepContext,
  WarpGrepInput,
  
  // Config types
  WarpGrepClientConfig,
  WarpGrepToolConfig,
  
  // Provider types for custom implementations
  WarpGrepProvider,
  GrepResult,
  ReadResult,
  AnalyseEntry,
  
  // Agent types (advanced usage)
  AgentRunResult,
  SessionConfig,
  ChatMessage,
} from '@morphllm/morphsdk';

Handling Results

const result = await morph.warpGrep.execute({
  query: 'Find authentication middleware',
  repoRoot: '.'
});

if (result.success) {
  console.log(`Found ${result.contexts.length} relevant code sections`);
  
  for (const ctx of result.contexts) {
    console.log(`\n--- ${ctx.file} ---`);
    console.log(ctx.content);
  }
  
  console.log(`\nSummary: ${result.summary}`);
} else {
  console.error(`Search failed: ${result.error}`);
}

Customize Tool Name and Description

Override the default tool name and description to tailor it for your use case:
  • MorphClient
  • Standalone
import { MorphClient } from '@morphllm/morphsdk';

const morph = new MorphClient({ apiKey: process.env.MORPH_API_KEY });

const grepTool = morph.openai.createWarpGrepTool({
  repoRoot: '.',
  name: 'my_custom_grep', // override the default tool name
  description: 'Use this tool when you know what you are looking for in the codebase.' // override the default tool description
});
OptionDefaultDescription
namewarpgrep_codebase_searchThe tool name exposed to the LLM
description(see SDK)The tool description explaining when/how to use it

Remote Sandboxes

How Warp Grep Works

Warp Grep runs an agent loop: it sends a query to a specialized search model, the model outputs tool calls (grep, read, analyse), those tools execute on your codebase, and the results go back to the model. This repeats until the model finds the relevant code. For local development, this just works — the tools run on your machine. But when your code lives in a remote sandbox (E2B, Modal, Daytona), you need to tell the SDK how to run those tools remotely.

The Solution: Implement WarpGrepProvider

You provide three functions that execute in your sandbox:
import type { WarpGrepProvider, GrepResult, ReadResult, AnalyseEntry } from '@morphllm/morphsdk';

const provider: WarpGrepProvider = {
  // Search for regex pattern, return "path:line:content" format
  grep: async ({ pattern, path }): Promise<GrepResult> => {
    // Run ripgrep in your sandbox
    return { lines: ['src/auth.ts:10:function login()'] };
  },
  
  // Read file lines, return "lineNumber|content" format
  read: async ({ path, start, end }): Promise<ReadResult> => {
    // Read from your sandbox filesystem
    return { lines: ['1|const x = 1;', '2|const y = 2;'] };
  },
  
  // List directory contents
  analyse: async ({ path, pattern, maxResults, maxDepth }): Promise<AnalyseEntry[]> => {
    // List files in your sandbox
    return [{ name: 'src', path: 'src', type: 'dir', depth: 0 }];
  }
};

// Pass to warp grep
const result = await runWarpGrep({
  query: 'Find authentication middleware',
  repoRoot: '/path/in/sandbox',
  provider,
  apiKey: process.env.MORPH_API_KEY,
});

Full Examples

  • E2B
  • Daytona
/**
 * E2B + Warp Grep Example
 * 
 * Demonstrates using warp-grep with a remote E2B sandbox.
 * We implement the WarpGrepProvider interface with three functions
 * that execute in the sandbox.
 */

import { Sandbox } from "@e2b/code-interpreter";
import { runWarpGrep } from "@morphllm/morphsdk/tools/warp-grep";
import type { WarpGrepProvider, GrepResult, ReadResult, AnalyseEntry } from "@morphllm/morphsdk/tools/warp-grep";

const MORPH_API_KEY = process.env.MORPH_API_KEY;
const REMOTE_DIR = "/home/user/repo";
const TEST_REPO = "https://github.com/google-gemini/gemini-cli.git";

/**
 * Creates a WarpGrepProvider for E2B sandbox.
 * Implements grep, read, and analyse functions that run in the sandbox.
 */
function createE2BProvider(sandbox: Sandbox, repoDir: string): WarpGrepProvider {
  
  /**
   * grep - Search for a regex pattern in files
   * Must return lines in ripgrep format: "path:lineNumber:content"
   */
  async function grep(params: { pattern: string; path: string }): Promise<GrepResult> {
    const targetPath = params.path === '.' ? repoDir : `${repoDir}/${params.path.replace(/^\.\//, '')}`;
    
    const cmd = `rg --no-config --no-heading --with-filename --line-number --color=never --trim --max-columns=400 -g '!.git' -g '!node_modules' -g '!dist' '${params.pattern}' '${targetPath}'`;
    
    const result = await sandbox.commands.run(cmd, { cwd: repoDir });
    
    // Exit code 1 = no matches (not an error)
    if (result.exitCode !== 0 && result.exitCode !== 1) {
      return { lines: [], error: result.stderr || `grep failed with exit code ${result.exitCode}` };
    }
    
    const lines = (result.stdout || '').trim().split('\n').filter(l => l.length > 0);
    return { lines };
  }

  /**
   * read - Read lines from a file
   * Must return lines in format: "lineNumber|content"
   */
  async function read(params: { path: string; start?: number; end?: number }): Promise<ReadResult> {
    const filePath = params.path.startsWith(repoDir) ? params.path : `${repoDir}/${params.path.replace(/^\.\//, '')}`;
    const start = params.start ?? 1;
    const end = params.end ?? 1_000_000;
    
    const cmd = `sed -n '${start},${end}p' '${filePath}'`;
    const result = await sandbox.commands.run(cmd, { cwd: repoDir });
    
    if (result.exitCode !== 0) {
      return { lines: [], error: result.stderr || `read failed: ${filePath}` };
    }
    
    const lines = (result.stdout || '').split('\n').map((line, idx) => `${start + idx}|${line}`);
    if (lines.length > 0 && lines[lines.length - 1] === `${start + lines.length - 1}|`) {
      lines.pop();
    }
    return { lines };
  }

  /**
   * analyse - List directory contents
   * Must return entries with: name, path, type ('file' | 'dir'), depth
   */
  async function analyse(params: { path: string; pattern?: string | null; maxResults?: number; maxDepth?: number }): Promise<AnalyseEntry[]> {
    const targetPath = params.path === '.' ? repoDir : `${repoDir}/${params.path.replace(/^\.\//, '')}`;
    const maxDepth = params.maxDepth ?? 2;
    const maxResults = params.maxResults ?? 100;
    
    const cmd = `find '${targetPath}' -maxdepth ${maxDepth} -not -path '*/\\.git/*' -not -path '*/node_modules/*' | head -${maxResults}`;
    const result = await sandbox.commands.run(cmd, { cwd: repoDir });
    
    if (result.exitCode !== 0) return [];
    
    const entries: AnalyseEntry[] = [];
    const paths = (result.stdout || '').trim().split('\n').filter(p => p.length > 0);
    
    for (const fullPath of paths) {
      if (fullPath === targetPath) continue;
      
      const name = fullPath.split('/').pop() || '';
      const relativePath = fullPath.replace(repoDir + '/', '');
      const depth = relativePath.split('/').length - 1;
      
      const statResult = await sandbox.commands.run(`test -d '${fullPath}' && echo dir || echo file`, { cwd: repoDir });
      const type = statResult.stdout.trim() === 'dir' ? 'dir' : 'file';
      
      if (params.pattern && !new RegExp(params.pattern).test(name)) continue;
      
      entries.push({ name, path: relativePath, type, depth });
    }
    
    return entries.slice(0, maxResults);
  }

  return { grep, read, analyse };
}

async function main() {
  // Create E2B sandbox
  const sandbox = await Sandbox.create();

  try {
    // Clone repo and install ripgrep
    await sandbox.commands.run(`git clone --depth 1 ${TEST_REPO} ${REMOTE_DIR}`, { cwd: "/home/user", timeoutMs: 120000 });
    await sandbox.commands.run("sudo apt-get update -qq && sudo apt-get install -y -qq ripgrep", { cwd: REMOTE_DIR, timeoutMs: 120000 });

    // Create provider and run warp-grep
    const provider = createE2BProvider(sandbox, REMOTE_DIR);
    
    const result = await runWarpGrep({
      query: "find the code that handles custom mcp integrations",
      repoRoot: REMOTE_DIR,
      provider,
      apiKey: MORPH_API_KEY,
    });

    // Use the results
    if (result.terminationReason === 'completed' && result.finish?.resolved) {
      for (const file of result.finish.resolved) {
        console.log(`Found: ${file.path}`);
        console.log(file.content);
      }
    }

  } finally {
    await sandbox.kill();
  }
}

main();
Your sandbox needs ripgrep (rg) installed for the grep function to work.