Skip to main content
The qckfx Agent SDK provides a powerful tool system that allows agents to interact with their environment. Tools can perform file operations, execute shell commands, search content, and much more.

Tool Architecture

Tool Interface

All tools implement the Tool interface:
interface Tool<Res extends ToolResult = ToolResult> {
  id: string;
  name: string;
  description: string;
  requiresPermission: boolean;
  parameters: Record<string, ParameterSchema>;
  requiredParameters: string[];
  category?: ToolCategory | ToolCategory[];
  alwaysRequirePermission?: boolean;
  execute: (args: Record<string, unknown>, context: ToolContext) => Promise<Res>;
}

Tool Categories

Tools are categorized for permission management and feature grouping:
enum ToolCategory {
  FILE_OPERATION = 'file_operation',
  SHELL_EXECUTION = 'shell_execution',
  READONLY = 'readonly',
  NETWORK = 'network'
}

Creating Custom Tools

Using createTool()

The createTool function provides a standardized way to create tools with built-in validation and permission handling.
import { createTool, ToolCategory } from '@qckfx/agent';

const customTool = createTool({
  id: 'custom_calculator',
  name: 'Calculator',
  description: 'Performs basic mathematical calculations',
  requiresPermission: false,
  category: ToolCategory.READONLY,
  parameters: {
    expression: {
      type: 'string',
      description: 'Mathematical expression to evaluate'
    },
    precision: {
      type: 'number',
      description: 'Number of decimal places for the result'
    }
  },
  requiredParameters: ['expression'],
  execute: async (args, context) => {
    const { expression, precision = 2 } = args;
    
    try {
      // Simple expression evaluation (use a proper math library in production)
      const result = eval(expression as string);
      return {
        ok: true,
        data: {
          expression,
          result: Number(result.toFixed(precision)),
          precision
        }
      };
    } catch (error) {
      return {
        ok: false,
        error: `Invalid expression: ${error.message}`
      };
    }
  }
});

Parameter Schema

Define tool parameters using JSON Schema-like objects:
interface ParameterSchema {
  type: string;
  description?: string;
  items?: ParameterSchema;
  properties?: Record<string, ParameterSchema>;
  required?: string[];
  [key: string]: unknown;
}
Supported Types:
  • 'string' - Text values
  • 'number' - Numeric values
  • 'integer' - Integer values
  • 'boolean' - True/false values
  • 'array' - Array of values
  • 'object' - Object with properties
Example:
parameters: {
  filePath: {
    type: 'string',
    description: 'Path to the file to process'
  },
  options: {
    type: 'object',
    description: 'Processing options',
    properties: {
      recursive: { type: 'boolean' },
      maxDepth: { type: 'integer' }
    }
  },
  tags: {
    type: 'array',
    description: 'List of tags to apply',
    items: { type: 'string' }
  }
}

Tool Context

Tools receive a context object with execution environment and utilities:
interface ToolContext {
  executionId: string;
  permissionManager?: PermissionManager;
  logger?: Logger;
  executionAdapter: ExecutionAdapter;
  toolRegistry?: {
    getAllTools: () => Tool[];
    getTool: (toolId: string) => Tool | undefined;
  };
  sessionState: SessionState;
  abortSignal?: AbortSignal;
  [key: string]: unknown;
}
Key Context Properties:
  • executionId - Unique identifier for this tool execution
  • executionAdapter - Interface for file operations and shell commands
  • sessionState - Current session state and context
  • abortSignal - Signal for cooperative cancellation
  • logger - Logging interface

Advanced Tool Example

const fileAnalyzerTool = createTool({
  id: 'file_analyzer',
  name: 'File Analyzer',
  description: 'Analyzes file content and provides statistics',
  requiresPermission: false,
  category: ToolCategory.READONLY,
  parameters: {
    path: {
      type: 'string',
      description: 'Path to the file to analyze'
    },
    includeContent: {
      type: 'boolean',
      description: 'Whether to include file content in analysis'
    }
  },
  requiredParameters: ['path'],
  validateArgs: (args) => {
    const { path } = args;
    if (typeof path !== 'string' || !path.trim()) {
      return { valid: false, reason: 'Path must be a non-empty string' };
    }
    return { valid: true };
  },
  execute: async (args, context) => {
    const { path, includeContent = false } = args;
    const { executionAdapter, executionId, abortSignal } = context;
    
    try {
      // Check if operation was aborted
      if (abortSignal?.aborted) {
        throw new Error('Operation aborted');
      }
      
      // Read file content
      const fileResult = await executionAdapter.readFile(
        executionId,
        path as string
      );
      
      if (!fileResult.ok) {
        return {
          ok: false,
          error: `Failed to read file: ${fileResult.error}`
        };
      }
      
      const content = fileResult.data.content;
      const lines = content.split('\n');
      
      const analysis = {
        path,
        size: fileResult.data.size,
        encoding: fileResult.data.encoding,
        lineCount: lines.length,
        characterCount: content.length,
        wordCount: content.split(/\s+/).filter(word => word.length > 0).length,
        isEmpty: content.trim().length === 0,
        ...(includeContent && { content })
      };
      
      return {
        ok: true,
        data: analysis
      };
    } catch (error) {
      return {
        ok: false,
        error: error.message
      };
    }
  }
});

Tool Registration

Registering Tools

Register custom tools with an agent instance:
// Create agent
const agent = await Agent.create({
  config: {
    defaultModel: 'claude-3-5-sonnet-20241022'
  }
});

// Register custom tools
agent.registerTool(customTool);
agent.registerTool(fileAnalyzerTool);

Tool Registry

The tool registry manages all available tools:
interface ToolRegistry {
  registerTool(tool: Tool): void;
  getTool(toolId: string): Tool | undefined;
  getAllTools(): Tool[];
  getToolsByCategory(category: ToolCategory): Tool[];
  executeToolWithCallbacks(
    toolId: string,
    toolUseId: string,
    args: Record<string, unknown>,
    context: ToolContext
  ): Promise<unknown>;
}

Permission System

Permission Levels

Tools can require different permission levels:
// No permission required (safe, read-only operations)
const readTool = createTool({
  id: 'read_tool',
  requiresPermission: false,
  category: ToolCategory.READONLY,
  // ...
});

// Permission required (file modifications)
const writeTool = createTool({
  id: 'write_tool',
  requiresPermission: true,
  category: ToolCategory.FILE_OPERATION,
  // ...
});

// Always requires permission (dangerous operations)
const shellTool = createTool({
  id: 'shell_tool',
  requiresPermission: true,
  alwaysRequirePermission: true,
  category: ToolCategory.SHELL_EXECUTION,
  // ...
});

Permission Modes

Control permission behavior at the agent level:
// Normal mode - prompts for permission as needed
agent.setFastEditMode(false);
agent.setDangerMode(false);

// Fast edit mode - allows file operations without prompts
agent.setFastEditMode(true);

// Danger mode - allows all operations without prompts (use with caution!)
agent.setDangerMode(true);

Tool Execution

Manual Tool Execution

Execute tools manually while preserving agent bookkeeping:
// Execute a tool directly
const result = await agent.invokeTool('file_analyzer', {
  path: 'package.json',
  includeContent: true
});

console.log('Analysis result:', result);

Tool Execution Context

Tools are executed with full context and lifecycle management:
  1. Validation - Arguments are validated against the parameter schema
  2. Permission Check - Permission is requested if required
  3. Execution - Tool logic is executed with full context
  4. Event Emission - Execution events are emitted for monitoring
  5. Result Processing - Results are processed and returned

Error Handling

Tools should return structured results:
// Success result
return {
  ok: true,
  data: { /* result data */ }
};

// Error result
return {
  ok: false,
  error: 'Description of what went wrong'
};

// Or throw an error for unexpected failures
throw new Error('Unexpected error occurred');

Built-in Tool Integration

Accessing Built-in Tools

Custom tools can access built-in tools through the tool registry:
const composeTool = createTool({
  id: 'compose_tool',
  name: 'Compose Tool',
  description: 'Combines multiple tool operations',
  execute: async (args, context) => {
    const { toolRegistry } = context;
    
    // Use built-in file read tool
    const fileReadTool = toolRegistry?.getTool('file_read');
    if (fileReadTool) {
      const fileContent = await fileReadTool.execute(
        { path: 'config.json' },
        context
      );
      // Process file content...
    }
    
    return { ok: true, data: { /* composed result */ } };
  }
});

Execution Adapter

Use the execution adapter for common operations:
const utilityTool = createTool({
  id: 'utility_tool',
  name: 'Utility Tool',
  description: 'Demonstrates execution adapter usage',
  execute: async (args, context) => {
    const { executionAdapter, executionId } = context;
    
    // Read a file
    const fileResult = await executionAdapter.readFile(
      executionId,
      'data.txt'
    );
    
    // Execute a command
    const cmdResult = await executionAdapter.executeCommand(
      executionId,
      'ls -la',
      '/path/to/directory'
    );
    
    // Search for files
    const files = await executionAdapter.glob(
      executionId,
      '**/*.ts'
    );
    
    return {
      ok: true,
      data: {
        fileContent: fileResult.data?.content,
        commandOutput: cmdResult.stdout,
        matchingFiles: files
      }
    };
  }
});

Best Practices

1. Clear Tool Descriptions

Write descriptive tool names and descriptions:
const tool = createTool({
  id: 'json_formatter',
  name: 'JSON Formatter',
  description: 'Formats and validates JSON content with customizable indentation and sorting options',
  // ...
});

2. Comprehensive Parameter Schemas

Define clear parameter schemas with descriptions:
parameters: {
  content: {
    type: 'string',
    description: 'JSON content to format (as string)'
  },
  indent: {
    type: 'integer',
    description: 'Number of spaces for indentation (default: 2)'
  },
  sortKeys: {
    type: 'boolean',
    description: 'Whether to sort object keys alphabetically (default: false)'
  }
}

3. Proper Error Handling

Handle errors gracefully and provide meaningful messages:
execute: async (args, context) => {
  try {
    // Tool logic here
    return { ok: true, data: result };
  } catch (error) {
    context.logger?.error(`Tool ${this.name} failed:`, error);
    return {
      ok: false,
      error: `Failed to process: ${error.message}`
    };
  }
}

4. Respect Abort Signals

Check for cancellation in long-running operations:
execute: async (args, context) => {
  const { abortSignal } = context;
  
  for (const item of largeDataSet) {
    if (abortSignal?.aborted) {
      throw new Error('Operation cancelled');
    }
    
    // Process item
    await processItem(item);
  }
  
  return { ok: true, data: results };
}

5. Use Appropriate Categories

Categorize tools correctly for proper permission handling:
// Read-only operations
category: ToolCategory.READONLY

// File modifications
category: ToolCategory.FILE_OPERATION

// Shell commands
category: ToolCategory.SHELL_EXECUTION

// Network requests
category: ToolCategory.NETWORK

// Multiple categories
category: [ToolCategory.FILE_OPERATION, ToolCategory.NETWORK]

6. Validate Input Thoroughly

Implement custom validation for complex requirements:
validateArgs: (args) => {
  const { url } = args;
  
  if (typeof url !== 'string') {
    return { valid: false, reason: 'URL must be a string' };
  }
  
  try {
    new URL(url);
  } catch {
    return { valid: false, reason: 'Invalid URL format' };
  }
  
  return { valid: true };
}
I