Sasha Tools Development Guide
Generated: 2025-08-06 UTC
Purpose: Comprehensive guide for developing tools in Sasha's LLxprt-powered system
Audience: Developers creating new tools for Sasha
Overview
This guide explains how to develop tools for Sasha using the hybrid approach of JSON schemas for execution and markdown for documentation, powered by LLxprt's orchestration capabilities.
Tool Architecture
Tool Components
Each tool consists of three essential parts:
/tools/
βββ schemas/ # JSON definitions
β βββ my-tool.json # Tool schema with parameters
βββ handlers/ # Executable implementations
β βββ my-tool.sh # Handler script (shell/Python/JS)
βββ docs/ # Human documentation
βββ my-tool.md # User-facing documentation
Step-by-Step Tool Creation
Step 1: Define the Tool Schema
Create a JSON schema file in /tools/schemas/:
{
"name": "create_report",
"version": "1.0.0",
"description": "Creates a formatted report from data",
"author": "developer@example.com",
"created": "2025-08-06",
"parameters": {
"title": {
"type": "string",
"description": "Report title",
"required": true,
"maxLength": 200
},
"data_source": {
"type": "string",
"description": "Path to data file",
"required": true,
"pattern": "^/workspace/.*"
},
"format": {
"type": "string",
"description": "Output format",
"required": false,
"default": "pdf",
"enum": ["pdf", "html", "markdown"]
},
"include_charts": {
"type": "boolean",
"description": "Include data visualizations",
"required": false,
"default": true
}
},
"handler": {
"type": "python",
"path": "tools/handlers/create-report.py",
"timeout": 30000,
"memoryLimit": "512MB"
},
"permissions": {
"riskLevel": "low",
"requiresConfirmation": true,
"category": "creation",
"allowedPaths": ["/workspace/reports", "/workspace/data"],
"restrictedPaths": ["/workspace/config", "/workspace/secrets"],
"maxFileSizeMB": 50
},
"triggers": [
"create a report",
"generate report from",
"make a report about",
"build report for"
],
"examples": [
{
"input": "Create a report from the Q4 sales data",
"parameters": {
"title": "Q4 Sales Report",
"data_source": "/workspace/data/q4-sales.csv"
}
}
]
}
Step 2: Implement the Handler
Choose your handler type based on the task complexity:
Shell Script Handler (tools/handlers/create-report.sh)
#!/bin/bash
set -e
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--title) TITLE="$2"; shift 2 ;;
--data_source) DATA_SOURCE="$2"; shift 2 ;;
--format) FORMAT="$2"; shift 2 ;;
--include_charts) INCLUDE_CHARTS="$2"; shift 2 ;;
*) shift ;;
esac
done
# Validate inputs
if [ -z "$TITLE" ] || [ -z "$DATA_SOURCE" ]; then
echo '{"success": false, "error": "Missing required parameters"}' >&2
exit 1
fi
# Process data and create report
OUTPUT_FILE="/workspace/reports/${TITLE// /_}.${FORMAT:-pdf}"
# Your report generation logic here
echo "Generating report: $TITLE"
echo "Data source: $DATA_SOURCE"
echo "Format: ${FORMAT:-pdf}"
# Return success response
cat <<EOF
{
"success": true,
"file": "$OUTPUT_FILE",
"size": $(stat -f%z "$OUTPUT_FILE" 2>/dev/null || echo 0),
"message": "Report generated successfully"
}
EOF
Python Handler (tools/handlers/create-report.py)
#!/usr/bin/env python3
import sys
import json
import os
from datetime import datetime
def create_report(params):
"""
Creates a formatted report from data
"""
title = params['title']
data_source = params['data_source']
format_type = params.get('format', 'pdf')
include_charts = params.get('include_charts', True)
try:
# Validate data source exists
if not os.path.exists(data_source):
return {
'success': False,
'error': f'Data source not found: {data_source}'
}
# Generate report (implement your logic here)
output_path = f"/workspace/reports/{title.replace(' ', '_')}.{format_type}"
# Mock report generation
with open(output_path, 'w') as f:
f.write(f"# {title}\n\n")
f.write(f"Generated: {datetime.now().isoformat()}\n")
f.write(f"Data from: {data_source}\n")
if include_charts:
f.write("\n## Charts\n[Chart placeholders]\n")
return {
'success': True,
'file': output_path,
'size': os.path.getsize(output_path),
'format': format_type,
'message': f'Report "{title}" generated successfully'
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
if __name__ == '__main__':
# Parse JSON parameters from command line
if len(sys.argv) > 1:
params = json.loads(sys.argv[1])
else:
params = json.loads(sys.stdin.read())
# Execute and return result
result = create_report(params)
print(json.dumps(result))
JavaScript Handler (tools/handlers/create-report.js)
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
function createReport(params) {
const { title, data_source, format = 'pdf', include_charts = true } = params;
try {
// Validate data source
if (!fs.existsSync(data_source)) {
return {
success: false,
error: `Data source not found: ${data_source}`
};
}
// Generate report
const outputPath = `/workspace/reports/${title.replace(/ /g, '_')}.${format}`;
// Mock report content
const content = `
# ${title}
Generated: ${new Date().toISOString()}
Data from: ${data_source}
${include_charts ? '## Charts\n[Chart placeholders]' : ''}
`.trim();
fs.writeFileSync(outputPath, content);
return {
success: true,
file: outputPath,
size: fs.statSync(outputPath).size,
format: format,
message: `Report "${title}" generated successfully`
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// Parse parameters from command line
const params = JSON.parse(process.argv[2] || '{}');
// Execute and output result
const result = createReport(params);
console.log(JSON.stringify(result));
Step 3: Create Documentation
Write user-friendly documentation in /tools/docs/create-report.md:
# π Create Report Tool
## Purpose
Generates professional reports from your data with optional charts and multiple format options.
## What It Does
- Analyzes data from CSV, Excel, or JSON files
- Creates formatted reports with sections and visualizations
- Exports to PDF, HTML, or Markdown formats
- Includes optional charts and graphs
## How to Use It
### Natural Language Examples
- "Create a report from the Q4 sales data"
- "Generate a PDF report about customer analytics"
- "Make an HTML report from the survey results without charts"
### Parameters You Can Specify
- **Title**: What to call your report
- **Data Source**: Which file contains your data
- **Format**: PDF (default), HTML, or Markdown
- **Charts**: Whether to include visualizations (default: yes)
## Examples
### Basic Report
"Create a sales report from this month's data"
β Generates a PDF with charts from the most recent sales file
### Custom Format
"Generate an HTML report called 'User Analytics' from /workspace/data/users.csv"
β Creates an HTML report with specified title and data
### Without Charts
"Make a markdown report from the inventory data without charts"
β Creates a text-only Markdown report
## Output
Reports are saved to `/workspace/reports/` with your specified format.
## Tips
- Use descriptive titles for easy identification
- PDF format is best for sharing
- HTML format is best for web viewing
- Markdown format is best for further editing
Testing Your Tool
Unit Test Template
// test/tools/create-report.test.js
const { executeToolHandler } = require('../../services/llxprt-bridge');
describe('Create Report Tool', () => {
test('creates PDF report with valid data', async () => {
const result = await executeToolHandler('create_report', {
title: 'Test Report',
data_source: '/workspace/test-data.csv',
format: 'pdf'
});
expect(result.success).toBe(true);
expect(result.file).toContain('.pdf');
});
test('fails with missing data source', async () => {
const result = await executeToolHandler('create_report', {
title: 'Test Report',
data_source: '/nonexistent/file.csv'
});
expect(result.success).toBe(false);
expect(result.error).toContain('not found');
});
test('respects format parameter', async () => {
const result = await executeToolHandler('create_report', {
title: 'Test Report',
data_source: '/workspace/test-data.csv',
format: 'html'
});
expect(result.success).toBe(true);
expect(result.file).toContain('.html');
});
});
Integration Test Script
#!/bin/bash
# test/integration/test-create-report.sh
echo "Testing Create Report Tool"
# Test 1: Basic execution
echo "Test 1: Basic PDF generation"
RESULT=$(node tools/handlers/create-report.js '{
"title": "Integration Test Report",
"data_source": "/workspace/test-data.csv"
}')
if echo "$RESULT" | grep -q '"success": true'; then
echo "β
Test 1 passed"
else
echo "β Test 1 failed: $RESULT"
fi
# Test 2: Different format
echo "Test 2: HTML format"
RESULT=$(node tools/handlers/create-report.js '{
"title": "HTML Test Report",
"data_source": "/workspace/test-data.csv",
"format": "html"
}')
if echo "$RESULT" | grep -q '.html'; then
echo "β
Test 2 passed"
else
echo "β Test 2 failed: $RESULT"
fi
# Test 3: Error handling
echo "Test 3: Missing file error"
RESULT=$(node tools/handlers/create-report.js '{
"title": "Error Test",
"data_source": "/nonexistent.csv"
}')
if echo "$RESULT" | grep -q '"success": false'; then
echo "β
Test 3 passed"
else
echo "β Test 3 failed: $RESULT"
fi
Security Best Practices
Input Validation
function validateInput(params, schema) {
const errors = [];
// Check required parameters
for (const [key, config] of Object.entries(schema.parameters)) {
if (config.required && !params[key]) {
errors.push(`Missing required parameter: ${key}`);
}
}
// Validate types
for (const [key, value] of Object.entries(params)) {
const config = schema.parameters[key];
if (!config) continue;
if (config.type === 'string' && typeof value !== 'string') {
errors.push(`${key} must be a string`);
}
if (config.type === 'integer' && !Number.isInteger(value)) {
errors.push(`${key} must be an integer`);
}
if (config.type === 'boolean' && typeof value !== 'boolean') {
errors.push(`${key} must be a boolean`);
}
}
// Validate enums
for (const [key, value] of Object.entries(params)) {
const config = schema.parameters[key];
if (config?.enum && !config.enum.includes(value)) {
errors.push(`${key} must be one of: ${config.enum.join(', ')}`);
}
}
return errors;
}
Path Security
function validatePath(filePath, allowedPaths, restrictedPaths) {
const resolved = path.resolve(filePath);
// Check restricted paths first
for (const restricted of restrictedPaths) {
if (resolved.startsWith(path.resolve(restricted))) {
throw new Error(`Access denied: ${filePath} is in restricted area`);
}
}
// Check allowed paths
const isAllowed = allowedPaths.some(allowed =>
resolved.startsWith(path.resolve(allowed))
);
if (!isAllowed) {
throw new Error(`Access denied: ${filePath} is outside allowed paths`);
}
return resolved;
}
Command Injection Prevention
function escapeShellArg(arg) {
return `'${arg.replace(/'/g, "'\\''")}'`;
}
// Never use string concatenation for shell commands
// Bad: exec(`ls ${userInput}`)
// Good: spawn('ls', [userInput])
Performance Optimization
Handler Optimization Tips
- Stream Large Files
const stream = fs.createReadStream(largefile);
stream.on('data', chunk => processChunk(chunk));
- Use Worker Threads for CPU-Intensive Tasks
const { Worker } = require('worker_threads');
const worker = new Worker('./heavy-computation.js');
- Implement Caching
const cache = new Map();
function getCachedOrCompute(key, computeFn) {
if (cache.has(key)) return cache.get(key);
const result = computeFn();
cache.set(key, result);
return result;
}
Deployment Checklist
Before deploying a new tool:
- Schema validates against JSON schema spec
- Handler executes without errors
- All required parameters are documented
- Error cases return proper JSON responses
- Security paths are validated
- Performance meets requirements (<5s for simple operations)
- Documentation includes clear examples
- Tests pass (unit and integration)
- Tool is registered in tool-permissions.json
- Activity logging captures tool usage
Advanced Topics
Multi-Step Tools
For complex operations requiring multiple steps:
async function multiStepTool(params) {
const steps = [
{ name: 'validate', fn: validateInput },
{ name: 'prepare', fn: prepareData },
{ name: 'process', fn: processData },
{ name: 'generate', fn: generateOutput },
{ name: 'cleanup', fn: cleanupTemp }
];
const results = {};
for (const step of steps) {
try {
results[step.name] = await step.fn(params, results);
// Report progress
console.log(JSON.stringify({
type: 'progress',
step: step.name,
completed: steps.indexOf(step) + 1,
total: steps.length
}));
} catch (error) {
// Rollback on failure
await rollback(results);
throw new Error(`Failed at ${step.name}: ${error.message}`);
}
}
return results.generate;
}
Tool Composition
Combining multiple tools:
async function composedTool(params) {
// Use output of one tool as input to another
const data = await executeToolHandler('extract_data', {
source: params.source
});
const analysis = await executeToolHandler('analyze_data', {
data: data.output
});
const report = await executeToolHandler('create_report', {
title: params.title,
analysis: analysis.output
});
return report;
}
Dynamic Tool Loading
Hot-reload tools without restart:
class DynamicToolLoader {
constructor() {
this.watchers = new Map();
}
watchTool(toolPath) {
const watcher = fs.watch(toolPath, (event) => {
if (event === 'change') {
this.reloadTool(toolPath);
}
});
this.watchers.set(toolPath, watcher);
}
async reloadTool(toolPath) {
// Clear require cache
delete require.cache[require.resolve(toolPath)];
// Reload tool
const tool = require(toolPath);
// Re-register
await toolRegistry.registerTool(tool);
console.log(`β»οΈ Reloaded tool: ${toolPath}`);
}
}
Best Practices Summary
- Keep handlers focused - One tool, one purpose
- Return structured JSON - Consistent success/error format
- Validate all inputs - Never trust user data
- Handle errors gracefully - Always return meaningful errors
- Document thoroughly - Users need clear examples
- Test comprehensively - Cover success and failure cases
- Log appropriately - Track usage without exposing sensitive data
- Optimize for common cases - Make frequent operations fast
- Version your tools - Track changes over time
- Monitor performance - Know how your tools perform in production
This guide provides everything needed to develop robust, secure, and user-friendly tools for the Sasha system.