@samrahimi/smol-js-alt
v0.7.4
Published
A TypeScript agentic framework with custom tools plugin system - CodeAgent and ToolUseAgent with YAML orchestration, drop-in custom tools, HTTP transport, and Exa.ai integration
Maintainers
Readme
smol-js
A TypeScript agentic framework inspired by smolagents.
Build AI agents that solve tasks autonomously using the ReAct (Reasoning + Acting) pattern. Agents can write and execute JavaScript code, call tools, delegate to other agents, and orchestrate complex workflows via YAML definitions.
Features
- Two Agent Types:
CodeAgent- Writes JavaScript code to solve tasksToolUseAgent- Uses native LLM function calling (OpenAI-style)
- YAML Orchestration: Define complex agent workflows declaratively
- Sandboxed Execution: JavaScript runs in Node's vm module with state persistence
- Extensible Tool System: Built-in tools + easy custom tool creation
- Nested Agents: Manager-worker patterns for hierarchical task delegation
- Exa.ai Integration: Semantic web search, content extraction, and research automation
- Dynamic Imports: Import npm packages on-the-fly via jsdelivr CDN
- OpenAI-Compatible: Works with OpenRouter, OpenAI, Azure, Anthropic, and local servers
- Streaming: Real-time output streaming from the LLM
- Memory Management: Context-aware with truncate/compact strategies
- Color-Coded Logging: Beautiful terminal output with session logging to disk
Installation
npm install @samrahimi/smol-jsQuick Start
Via CLI (YAML Workflows)
The easiest way to get started is using YAML workflows:
# Run directly with npx (no installation needed)
npx @samrahimi/smol-js workflow.yaml --task "Your task here"
# Or install globally and use the CLI
npm install -g @samrahimi/smol-js
smol-js workflow.yaml --task "Research quantum computing"
# Validate a workflow
npx @samrahimi/smol-js validate workflow.yamlExample workflow (research-agent.yaml):
name: "Research Agent"
description: "An agent that can search the web and write reports"
model:
modelId: "anthropic/claude-sonnet-4.5"
baseUrl: "https://openrouter.ai/api/v1"
maxTokens: 4000
tools:
search:
type: exa_search
config:
apiKey: "$EXA_API_KEY"
write:
type: write_file
agents:
researcher:
type: ToolUseAgent
tools:
- search
- write
maxSteps: 10
customInstructions: "You are a thorough researcher. Always cite sources."
entrypoint: researcherProgrammatic Usage
import 'dotenv/config';
import { CodeAgent, OpenAIModel } from '@samrahimi/smol-js';
// Create the model (defaults to Claude via OpenRouter)
const model = new OpenAIModel({
modelId: 'anthropic/claude-sonnet-4.5',
});
// Create the agent
const agent = new CodeAgent({
model,
maxSteps: 10,
});
// Run a task
const result = await agent.run('Calculate the first 10 prime numbers');
console.log(result.output); // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]Agent Types
CodeAgent
Generates and executes JavaScript code to solve tasks. The agent has access to tools as async functions in its execution environment.
import { CodeAgent, OpenAIModel } from '@samrahimi/smol-js';
const agent = new CodeAgent({
model,
tools: [myTool],
maxSteps: 20,
});
await agent.run('Analyze the file data.csv and create a summary');How it works:
- Agent generates thought + JavaScript code
- Code executes in sandboxed VM
- Results become observations
- Repeats until
final_answer()is called
ToolUseAgent
Uses native LLM function calling (OpenAI-style tool calling). Better for LLMs with strong tool-calling capabilities.
import { ToolUseAgent, OpenAIModel } from '@samrahimi/smol-js';
const agent = new ToolUseAgent({
model,
tools: [searchTool, readFileTool],
maxSteps: 15,
enableParallelToolCalls: true, // Execute independent tools in parallel
});
await agent.run('Search for recent AI papers and summarize the top 3');Configuration
Environment Variables
# API key for LLM provider
OPENAI_API_KEY=sk-or-v1-your-openrouter-key
# or
OPENROUTER_API_KEY=sk-or-v1-your-key
# For Exa.ai tools
EXA_API_KEY=your-exa-api-keyModel Configuration
const model = new OpenAIModel({
modelId: 'anthropic/claude-sonnet-4.5', // Model identifier
apiKey: 'sk-...', // API key (or use env var)
baseUrl: 'https://openrouter.ai/api/v1', // API endpoint
maxTokens: 4096, // Max tokens to generate
temperature: 0.7, // Generation temperature
timeout: 120000, // Request timeout in ms
});Agent Configuration
const agent = new CodeAgent({
model,
tools: [myTool], // Custom tools
maxSteps: 20, // Max iterations (default: 20)
codeExecutionDelay: 5000, // Safety delay before execution (default: 5000ms)
customInstructions: '...', // Additional system prompt instructions
verboseLevel: LogLevel.INFO, // Logging level
streamOutputs: true, // Stream LLM output in real-time
persistent: false, // Retain memory between run() calls
maxContextLength: 100000, // Token limit for context
memoryStrategy: 'truncate', // 'truncate' or 'compact'
additionalAuthorizedImports: ['lodash'], // npm packages (CodeAgent only)
workingDirectory: '/path/to/dir', // Working dir for fs operations
});Built-in Tools
- FinalAnswerTool: Return the final result (always available)
- UserInputTool: Prompt for user input
- ReadFileTool: Read file contents
- WriteFileTool: Write to files
- CurlTool: Make HTTP requests
- ExaSearchTool: Semantic web search via Exa.ai
- ExaGetContentsTool: Fetch and extract webpage content
- ExaResearchTool: Multi-step research workflow
- AgentTool: Wrap agents as tools for nested architectures
Creating Custom Tools
Class-Based Tools
import { Tool } from '@samrahimi/smol-js';
import type { ToolInputs } from '@samrahimi/smol-js';
class WeatherTool extends Tool {
readonly name = 'get_weather';
readonly description = 'Get current weather for a city';
readonly inputs: ToolInputs = {
city: {
type: 'string',
description: 'The city name',
required: true,
},
};
readonly outputType = 'object';
async execute(args: Record<string, unknown>): Promise<unknown> {
const city = args.city as string;
const response = await fetch(`https://api.weather.com/${city}`);
return response.json();
}
}
const agent = new CodeAgent({
model,
tools: [new WeatherTool()],
});Registering Tools for YAML Workflows
import { YAMLLoader, Orchestrator } from '@samrahimi/smol-js';
import { MyCustomTool } from './tools.js';
const loader = new YAMLLoader();
// Register custom tools by type name
loader.registerToolType('my_tool', MyCustomTool);
// Now use in YAML:
// tools:
// custom:
// type: my_tool
// config:
// apiKey: "$MY_API_KEY"
const workflow = loader.loadFromFile('./workflow.yaml');
const orchestrator = new Orchestrator();
await orchestrator.runWorkflow(workflow, 'Your task here');YAML Workflow System
Define complex agent architectures declaratively:
name: "Multi-Agent Research System"
description: "Manager-worker pattern with specialized agents"
model:
modelId: "anthropic/claude-sonnet-4.5"
baseUrl: "https://openrouter.ai/api/v1"
apiKey: "$OPENROUTER_API_KEY"
tools:
search:
type: exa_search
config:
apiKey: "$EXA_API_KEY"
read:
type: read_file
write:
type: write_file
agents:
# Worker agent: specialized in research
researcher:
type: ToolUseAgent
tools:
- search
- read
maxSteps: 8
temperature: 0.3
customInstructions: "You are a research specialist. Be thorough and cite sources."
# Worker agent: specialized in writing
writer:
type: CodeAgent
tools:
- write
maxSteps: 5
temperature: 0.7
customInstructions: "You are a skilled technical writer. Create clear, engaging content."
# Manager agent: delegates to workers
manager:
type: ToolUseAgent
agents:
- researcher # Available as a tool
- writer # Available as a tool
maxSteps: 10
customInstructions: "You coordinate research and writing tasks. Delegate appropriately."
entrypoint: managerRun it:
npx @samrahimi/smol-js research-workflow.yaml --task "Write a report on quantum computing"Nested Agents (Manager-Worker Pattern)
Use agents as tools for hierarchical task delegation:
import { CodeAgent, ToolUseAgent, OpenAIModel, AgentTool } from '@samrahimi/smol-js';
// Create specialized worker agents
const mathAgent = new CodeAgent({
model,
maxSteps: 5,
verboseLevel: LogLevel.OFF, // Quiet - manager handles output
});
const researchAgent = new ToolUseAgent({
model,
tools: [searchTool],
maxSteps: 8,
verboseLevel: LogLevel.OFF,
});
// Wrap workers as tools
const mathExpert = new AgentTool({
agent: mathAgent,
name: 'math_expert',
description: 'Delegate math and calculation tasks to this agent',
});
const researcher = new AgentTool({
agent: researchAgent,
name: 'researcher',
description: 'Delegate research and information gathering to this agent',
});
// Create manager that uses the workers
const manager = new ToolUseAgent({
model,
tools: [mathExpert, researcher],
maxSteps: 10,
});
await manager.run('Research Tokyo population and calculate water consumption per capita');Exa.ai Integration
Three tools for web research powered by Exa.ai:
ExaSearchTool
Semantic search with advanced filtering:
import { ExaSearchTool } from '@samrahimi/smol-js';
const searchTool = new ExaSearchTool({
apiKey: process.env.EXA_API_KEY,
numResults: 10,
searchType: 'auto', // 'auto', 'neural', or 'keyword'
});ExaGetContentsTool
Extract clean webpage content:
import { ExaGetContentsTool } from '@samrahimi/smol-js';
const contentTool = new ExaGetContentsTool({
apiKey: process.env.EXA_API_KEY,
textOnly: true,
});ExaResearchTool
Agentic web research that writes comprehensive reports:
import { ExaResearchTool } from '@samrahimi/smol-js';
const researchTool = new ExaResearchTool({
apiKey: process.env.EXA_API_KEY,
model: 'exa-research', // or 'exa-research-fast', 'exa-research-pro'
});
// The Exa Research API is an asynchronous research agent that:
// 1. Plans the research approach
// 2. Executes searches across the web
// 3. Extracts and analyzes facts from sources
// 4. Synthesizes findings into a markdown report with citations
// 5. Returns the complete report (typically 20-90 seconds)
// Usage in agent:
await agent.run('Use exa_research to write a comprehensive report on quantum computing breakthroughs in 2024');Built-in Capabilities (CodeAgent)
The CodeAgent sandbox includes:
| Category | Available |
|----------|-----------|
| Output | console.log(), console.error(), print() |
| HTTP | fetch(), URL, URLSearchParams |
| File System | fs.* (readFileSync, writeFileSync, etc.) |
| Path | path.* (join, resolve, dirname, etc.) |
| Data | JSON, Buffer, TextEncoder, TextDecoder |
| Math | Math.*, parseInt(), parseFloat() |
| Types | Object, Array, Map, Set, Date, RegExp, Promise |
| Timers | setTimeout(), setInterval() |
| Final | final_answer(value) - Return the result |
Dynamic npm Imports
const agent = new CodeAgent({
model,
additionalAuthorizedImports: ['lodash', 'dayjs', 'uuid'],
});
// Agent can now write:
// const _ = await importPackage('lodash');
// const result = _.uniq([1, 2, 2, 3]);Packages are fetched from jsdelivr CDN and cached in ~/.smol-js/packages/.
Examples
See the examples/js/ directory for complete examples:
- main.ts: Main demo with custom tools and YAML workflows
- custom-tools.ts: Custom tool implementations (TimestampTool, TextStatsTool, SlugifyTool)
- agents/: YAML workflow definitions
custom_tools.yaml: Workflow using custom toolsbloomberg.yaml: Bloomberg research workflowpolicy.yaml: Policy analysis workflowsimple-test.yaml: Simple test workflow
Run an example:
cd examples/js
npm install
npx tsx main.tsMemory Management
Agents track all execution steps and manage context automatically:
const agent = new CodeAgent({
model,
maxContextLength: 100000, // Token limit
memoryStrategy: 'truncate', // or 'compact'
persistent: false, // Retain memory between run() calls
});
// Non-persistent (default): Fresh memory each run
await agent.run('Task 1');
await agent.run('Task 2'); // Doesn't remember Task 1
// Persistent: Maintains conversation context
const persistentAgent = new CodeAgent({ model, persistent: true });
await persistentAgent.run('Remember this: X = 42');
await persistentAgent.run('What is X?'); // Remembers X = 42Memory Strategies:
- truncate: Removes oldest steps when over token limit
- compact: Uses LLM to summarize older steps
Session Logging
All sessions are logged to ~/.smol-js/:
session-<timestamp>.log- Full session transcript with color codespackages/- Cached npm packages from dynamic imports
API Reference
Agent Base Class
abstract class Agent {
constructor(config: AgentConfig)
run(task: string, reset?: boolean): Promise<RunResult>
stop(): void
reset(): void
addTool(tool: Tool): void
removeTool(name: string): boolean
getMemory(): AgentMemory
}CodeAgent
class CodeAgent extends Agent {
constructor(config: CodeAgentConfig)
getExecutor(): LocalExecutor
}ToolUseAgent
class ToolUseAgent extends Agent {
constructor(config: ToolUseAgentConfig)
}
interface ToolUseAgentConfig extends AgentConfig {
enableParallelToolCalls?: boolean; // Execute independent tools in parallel
}RunResult
interface RunResult {
output: unknown; // Final answer
steps: MemoryStep[]; // Execution history
tokenUsage: TokenUsage; // Token counts
duration: number; // Total time in ms
}Tool
abstract class Tool {
abstract readonly name: string;
abstract readonly description: string;
abstract readonly inputs: ToolInputs;
abstract readonly outputType: string;
abstract execute(args: Record<string, unknown>): Promise<unknown>;
setup(): Promise<void>;
call(args: Record<string, unknown>): Promise<unknown>;
toCodePrompt(): string; // For CodeAgent
toOpenAITool(): OpenAITool; // For ToolUseAgent
}Orchestrator
class Orchestrator {
constructor(config?: { verbose?: boolean })
loadWorkflow(filePath: string): Workflow
loadWorkflowFromString(yaml: string): Workflow
runWorkflow(workflow: Workflow, task: string): Promise<void>
runAgent(agent: Agent, task: string): Promise<void>
getEventLog(): OrchestrationEvent[]
}YAMLLoader
class YAMLLoader {
registerToolType(typeName: string, toolClass: typeof Tool): void
loadFromFile(filePath: string): Workflow
loadFromString(yaml: string): Workflow
}CLI Reference
# Run a workflow
npx @samrahimi/smol-js <workflow.yaml> [options]
npx @samrahimi/smol-js run <workflow.yaml> [options]
# Validate a workflow
npx @samrahimi/smol-js validate <workflow.yaml>
# Options
--task, -t <task> Task description (prompted if not provided)
--quiet, -q Reduce output verbosity
--help, -h Show help messageSecurity Considerations
- Sandboxed Execution: Code runs in Node's vm module, isolated from the main process
- Authorized Imports: Only explicitly allowed npm packages can be imported
- File System Isolation: fs operations are restricted to configured working directory
- Execution Delay: Configurable delay before code execution allows user interruption (Ctrl+C)
- Timeout Protection: Code execution has a configurable timeout (default: 30s)
Comparison with Python smolagents
| Feature | Python smolagents | smol-js |
|---------|------------------|---------|
| Code execution | Python interpreter | Node.js vm module |
| Imports | import statement | await importPackage() |
| Tool definition | @tool decorator | Class extending Tool |
| Nested agents | ManagedAgent | AgentTool |
| Async support | Optional | All tools are async |
| HTTP requests | Requires tool | Built-in fetch() |
| Remote executors | E2B, Docker, etc. | Local only |
| Agent types | CodeAgent, ToolCallingAgent | CodeAgent, ToolUseAgent |
| YAML workflows | ❌ | ✅ |
| Exa.ai integration | ❌ | ✅ Built-in |
Contributing
Contributions are welcome! Please follow OOP principles and open an issue or PR on GitHub.
# Clone and install
git clone https://github.com/samrahimi/smolagents
cd smolagents/smol-js
npm install
# Build
npm run build
# Run tests
npm test
# Lint
npm run lint
npm run lint:fix
# Type check
npm run typecheckLicense
MIT
Credits
This is a TypeScript framework inspired by smolagents by Hugging Face, with additional features including YAML orchestration, ToolUseAgent, and Exa.ai integration.
