npm package: @samrahimi/smol-js-alt
Description: 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
Author: Sam Rahimi
Latest version: 0.7.4

# smol-js

**A TypeScript agentic framework inspired by [smolagents](https://github.com/huggingface/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.

[![npm version](https://img.shields.io/npm/v/@samrahimi/smol-js.svg)](https://www.npmjs.com/package/@samrahimi/smol-js)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Two Agent Types**:
  - `CodeAgent` - Writes JavaScript code to solve tasks
  - `ToolUseAgent` - 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

```bash
npm install @samrahimi/smol-js
```

## Quick Start

### Via CLI (YAML Workflows)

The easiest way to get started is using YAML workflows:

```bash
# 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.yaml
```

Example workflow (`research-agent.yaml`):

```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: researcher
```

### Programmatic Usage

```typescript
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.

```typescript
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:**
1. Agent generates thought + JavaScript code
2. Code executes in sandboxed VM
3. Results become observations
4. Repeats until `final_answer()` is called

### ToolUseAgent

Uses native LLM function calling (OpenAI-style tool calling). Better for LLMs with strong tool-calling capabilities.

```typescript
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

```bash
# 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-key
```

### Model Configuration

```typescript
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

```typescript
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

```typescript
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

```typescript
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:

```yaml
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: manager
```

Run it:

```bash
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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

```typescript
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](https://www.jsdelivr.com/) 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 tools
  - `bloomberg.yaml`: Bloomberg research workflow
  - `policy.yaml`: Policy analysis workflow
  - `simple-test.yaml`: Simple test workflow

Run an example:

```bash
cd examples/js
npm install
npx tsx main.ts
```

## Memory Management

Agents track all execution steps and manage context automatically:

```typescript
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 = 42
```

**Memory 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 codes
- `packages/` - Cached npm packages from dynamic imports

## API Reference

### Agent Base Class

```typescript
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

```typescript
class CodeAgent extends Agent {
  constructor(config: CodeAgentConfig)
  getExecutor(): LocalExecutor
}
```

### ToolUseAgent

```typescript
class ToolUseAgent extends Agent {
  constructor(config: ToolUseAgentConfig)
}

interface ToolUseAgentConfig extends AgentConfig {
  enableParallelToolCalls?: boolean; // Execute independent tools in parallel
}
```

### RunResult

```typescript
interface RunResult {
  output: unknown;        // Final answer
  steps: MemoryStep[];    // Execution history
  tokenUsage: TokenUsage; // Token counts
  duration: number;       // Total time in ms
}
```

### Tool

```typescript
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

```typescript
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

```typescript
class YAMLLoader {
  registerToolType(typeName: string, toolClass: typeof Tool): void
  loadFromFile(filePath: string): Workflow
  loadFromString(yaml: string): Workflow
}
```

## CLI Reference

```bash
# 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 message
```

## Security 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.

```bash
# 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 typecheck
```

## License

MIT

## Credits

This is a TypeScript framework inspired by [smolagents](https://github.com/huggingface/smolagents) by Hugging Face, with additional features including YAML orchestration, ToolUseAgent, and Exa.ai integration.