@samrahimi/smol-js 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,24 @@
1
1
  # smol-js
2
2
 
3
- A TypeScript port of the [smolagents](https://github.com/huggingface/smolagents) agentic framework. This library provides a CodeAgent that can solve tasks by generating and executing JavaScript code in a sandboxed environment.
3
+ **A TypeScript port of the [smolagents](https://github.com/huggingface/smolagents) agentic framework.**
4
+
5
+ Build AI agents that solve tasks by writing and executing JavaScript code. The agent reasons about problems, generates code, executes it in a sandbox, observes results, and iterates until it finds the answer.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@samrahimi/smol-js.svg)](https://www.npmjs.com/package/@samrahimi/smol-js)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
9
 
5
10
  ## Features
6
11
 
7
- - **CodeAgent**: An LLM-powered agent that generates JavaScript code to solve tasks
8
- - **Multi-step execution**: Variables persist between steps for complex workflows
9
- - **Tool system**: Extensible tools that the agent can use as functions
10
- - **Dynamic imports**: Import npm packages on-the-fly via CDN
11
- - **OpenAI-compatible API**: Works with OpenRouter, OpenAI, Azure, and local servers
12
- - **Streaming support**: Real-time output streaming from the LLM
13
- - **Color-coded logging**: Beautiful terminal output with syntax highlighting
14
- - **Error recovery**: Agent can recover from errors and try different approaches
12
+ - **ReAct Framework**: Reasoning + Acting loop (Thought Code Observation → repeat)
13
+ - **Sandboxed Execution**: JavaScript runs in Node's vm module with state persistence
14
+ - **Tool System**: Extensible tools that agents can call as functions
15
+ - **Nested Agents**: Use agents as tools for hierarchical task delegation
16
+ - **Dynamic Imports**: Import npm packages on-the-fly via jsdelivr CDN
17
+ - **Built-in fetch()**: Agents can make HTTP requests directly in generated code
18
+ - **OpenAI-Compatible**: Works with OpenRouter, OpenAI, Azure, Anthropic, and local servers
19
+ - **Streaming**: Real-time output streaming from the LLM
20
+ - **Color-Coded Logging**: Beautiful terminal output with session logging to disk
21
+ - **Error Recovery**: Agent can recover from errors and try different approaches
15
22
 
16
23
  ## Installation
17
24
 
@@ -19,20 +26,15 @@ A TypeScript port of the [smolagents](https://github.com/huggingface/smolagents)
19
26
  npm install @samrahimi/smol-js
20
27
  ```
21
28
 
22
- Or with yarn:
23
-
24
- ```bash
25
- yarn add @samrahimi/smol-js
26
- ```
27
-
28
29
  ## Quick Start
29
30
 
30
31
  ```typescript
32
+ import 'dotenv/config';
31
33
  import { CodeAgent, OpenAIModel } from '@samrahimi/smol-js';
32
34
 
33
- // Create the model (uses OPENAI_API_KEY env var)
35
+ // Create the model (defaults to Claude via OpenRouter)
34
36
  const model = new OpenAIModel({
35
- modelId: 'anthropic/claude-sonnet-4.5', // default, via OpenRouter
37
+ modelId: 'anthropic/claude-sonnet-4.5',
36
38
  });
37
39
 
38
40
  // Create the agent
@@ -43,7 +45,6 @@ const agent = new CodeAgent({
43
45
 
44
46
  // Run a task
45
47
  const result = await agent.run('Calculate the first 10 prime numbers');
46
-
47
48
  console.log(result.output); // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
48
49
  ```
49
50
 
@@ -52,23 +53,23 @@ console.log(result.output); // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
52
53
  ### Environment Variables
53
54
 
54
55
  ```bash
55
- # Required: API key for LLM provider
56
- OPENAI_API_KEY=your-api-key
56
+ # API key for LLM provider (OpenRouter by default)
57
+ OPENAI_API_KEY=sk-or-v1-your-openrouter-key
57
58
 
58
- # Or use OpenRouter specifically
59
- OPENROUTER_API_KEY=your-openrouter-key
59
+ # Or for OpenAI directly
60
+ OPENAI_API_KEY=sk-your-openai-key
60
61
  ```
61
62
 
62
63
  ### Model Configuration
63
64
 
64
65
  ```typescript
65
66
  const model = new OpenAIModel({
66
- modelId: 'gpt-4', // Model identifier
67
- apiKey: 'sk-...', // API key (or use env var)
68
- baseUrl: 'https://api.openai.com/v1', // API endpoint
69
- maxTokens: 4096, // Max tokens to generate
70
- temperature: 0.7, // Generation temperature
71
- timeout: 120000, // Request timeout in ms
67
+ modelId: 'anthropic/claude-sonnet-4.5', // Model identifier
68
+ apiKey: 'sk-...', // API key (or use env var)
69
+ baseUrl: 'https://openrouter.ai/api/v1', // API endpoint (default: OpenRouter)
70
+ maxTokens: 4096, // Max tokens to generate
71
+ temperature: 0.7, // Generation temperature
72
+ timeout: 120000, // Request timeout in ms
72
73
  });
73
74
  ```
74
75
 
@@ -77,20 +78,22 @@ const model = new OpenAIModel({
77
78
  ```typescript
78
79
  const agent = new CodeAgent({
79
80
  model,
80
- tools: [myTool], // Custom tools
81
- maxSteps: 20, // Max iterations (default: 20)
82
- codeExecutionDelay: 5000, // Delay before execution in ms (default: 5000)
83
- customInstructions: '...', // Additional prompt instructions
84
- verboseLevel: LogLevel.INFO, // Logging level
85
- streamOutputs: true, // Stream LLM output
86
- additionalAuthorizedImports: ['lodash', 'dayjs'], // Allowed npm packages
87
- workingDirectory: '/path/to/dir', // Working dir for fs operations
81
+ tools: [myTool], // Custom tools
82
+ maxSteps: 20, // Max iterations (default: 20)
83
+ codeExecutionDelay: 5000, // Safety delay before execution (default: 5000ms)
84
+ customInstructions: '...', // Additional system prompt instructions
85
+ verboseLevel: LogLevel.INFO, // Logging level (OFF, ERROR, INFO, DEBUG)
86
+ streamOutputs: true, // Stream LLM output in real-time
87
+ additionalAuthorizedImports: ['lodash'], // npm packages the agent can import
88
+ workingDirectory: '/path/to/dir', // Working dir for fs operations
88
89
  });
89
90
  ```
90
91
 
91
92
  ## Creating Tools
92
93
 
93
- Tools extend the agent's capabilities. Create a tool by extending the `Tool` class:
94
+ Tools extend the agent's capabilities. The agent sees tools as async functions it can call.
95
+
96
+ ### Class-Based Tools
94
97
 
95
98
  ```typescript
96
99
  import { Tool } from '@samrahimi/smol-js';
@@ -110,115 +113,164 @@ class WeatherTool extends Tool {
110
113
 
111
114
  async execute(args: Record<string, unknown>): Promise<unknown> {
112
115
  const city = args.city as string;
113
- // Fetch weather data...
114
- return { city, temperature: 22, condition: 'sunny' };
116
+ const response = await fetch(`https://api.weather.com/${city}`);
117
+ return response.json();
115
118
  }
116
119
  }
117
120
 
118
- // Use with agent
119
121
  const agent = new CodeAgent({
120
122
  model,
121
123
  tools: [new WeatherTool()],
122
124
  });
123
125
  ```
124
126
 
125
- Or use the `createTool` helper:
127
+ ### Functional Tools
126
128
 
127
129
  ```typescript
128
130
  import { createTool } from '@samrahimi/smol-js';
129
131
 
130
- const addNumbers = createTool({
131
- name: 'add',
132
- description: 'Adds two numbers',
132
+ const calculator = createTool({
133
+ name: 'calculate',
134
+ description: 'Evaluate a math expression',
133
135
  inputs: {
134
- a: { type: 'number', description: 'First number' },
135
- b: { type: 'number', description: 'Second number' },
136
+ expression: { type: 'string', description: 'Math expression to evaluate', required: true },
136
137
  },
137
138
  outputType: 'number',
138
- execute: async (args) => (args.a as number) + (args.b as number),
139
+ execute: async (args) => {
140
+ return new Function('Math', `return ${args.expression}`)(Math);
141
+ },
139
142
  });
140
143
  ```
141
144
 
142
- ## Dynamic Imports
145
+ ## Nested Agents (Agent as Tool)
143
146
 
144
- The agent can import npm packages dynamically using `importPackage()`:
147
+ Use agents as tools for hierarchical task delegation. A "manager" agent can delegate specialized tasks to "worker" agents.
145
148
 
146
149
  ```typescript
147
- const agent = new CodeAgent({
150
+ import { CodeAgent, OpenAIModel, AgentTool, agentAsTool } from '@samrahimi/smol-js';
151
+
152
+ // Create a specialized worker agent
153
+ const mathAgent = new CodeAgent({
148
154
  model,
149
- additionalAuthorizedImports: ['lodash', 'dayjs', 'uuid'],
155
+ tools: [calculatorTool],
156
+ maxSteps: 5,
157
+ verboseLevel: LogLevel.OFF, // Quiet - manager reports results
150
158
  });
151
159
 
152
- // The agent can now use:
153
- // const _ = await importPackage('lodash');
154
- // const dayjs = await importPackage('dayjs');
160
+ // Wrap it as a tool
161
+ const mathExpert = new AgentTool({
162
+ agent: mathAgent,
163
+ name: 'math_expert',
164
+ description: 'Delegate math problems to a specialized math agent',
165
+ });
166
+
167
+ // Or use the helper function
168
+ const mathExpert = agentAsTool(mathAgent, {
169
+ name: 'math_expert',
170
+ description: 'Delegate math problems to a specialized math agent',
171
+ });
172
+
173
+ // Create manager that uses the worker
174
+ const manager = new CodeAgent({
175
+ model,
176
+ tools: [mathExpert, researchExpert], // Agents as tools!
177
+ maxSteps: 10,
178
+ });
179
+
180
+ await manager.run('Research Tokyo population and calculate water consumption');
155
181
  ```
156
182
 
157
- Packages are fetched from [esm.sh](https://esm.sh) CDN.
183
+ ## Using fetch() Directly
158
184
 
159
- ## Built-in Capabilities
185
+ Agents can make HTTP requests directly in their code without needing a tool:
186
+
187
+ ```typescript
188
+ const agent = new CodeAgent({
189
+ model,
190
+ tools: [], // No tools needed!
191
+ customInstructions: `You can use fetch() directly to make HTTP requests.
192
+ Example: const data = await fetch('https://api.example.com').then(r => r.json());`,
193
+ });
160
194
 
161
- The agent has access to:
195
+ await agent.run('Fetch users from https://jsonplaceholder.typicode.com/users');
196
+ ```
162
197
 
163
- - `console.log()` / `print()` - Output logging
164
- - `fs` - File system operations (read, write, mkdir, etc.)
165
- - `path` - Path utilities
166
- - `fetch()` - HTTP requests
167
- - `JSON`, `Math`, `Date` - Standard JavaScript globals
168
- - `final_answer(value)` - Return the final result
198
+ ## Dynamic npm Imports
169
199
 
170
- ## Log Levels
200
+ The agent can import npm packages dynamically:
171
201
 
172
202
  ```typescript
173
- import { LogLevel } from '@samrahimi/smol-js';
203
+ const agent = new CodeAgent({
204
+ model,
205
+ additionalAuthorizedImports: ['lodash', 'dayjs', 'uuid'],
206
+ });
174
207
 
175
- LogLevel.OFF // No output
176
- LogLevel.ERROR // Errors only
177
- LogLevel.INFO // Normal output (default)
178
- LogLevel.DEBUG // Detailed debugging
208
+ // The agent can now write:
209
+ // const _ = await importPackage('lodash');
210
+ // const dayjs = await importPackage('dayjs');
179
211
  ```
180
212
 
181
- ## Session Logging
213
+ Packages are fetched from [jsdelivr CDN](https://www.jsdelivr.com/) and cached locally in `~/.smol-js/packages/`.
182
214
 
183
- All sessions are logged to `~/.smol-js/session-<timestamp>.log`.
215
+ ## Built-in Capabilities
184
216
 
185
- ## Examples
217
+ The agent's sandbox includes:
186
218
 
187
- See the `examples/` folder for complete examples:
219
+ | Category | Available |
220
+ |----------|-----------|
221
+ | **Output** | `console.log()`, `console.error()`, `print()` |
222
+ | **HTTP** | `fetch()`, `URL`, `URLSearchParams` |
223
+ | **File System** | `fs.readFileSync()`, `fs.writeFileSync()`, `fs.existsSync()`, etc. |
224
+ | **Path** | `path.join()`, `path.resolve()`, `path.dirname()`, etc. |
225
+ | **Data** | `JSON`, `Buffer`, `TextEncoder`, `TextDecoder` |
226
+ | **Math** | `Math.*`, `parseInt()`, `parseFloat()` |
227
+ | **Types** | `Object`, `Array`, `Map`, `Set`, `Date`, `RegExp`, `Promise` |
228
+ | **Timers** | `setTimeout()`, `setInterval()` |
229
+ | **Final** | `final_answer(value)` - Return the result |
188
230
 
189
- 1. **01-simple-math.ts** - Basic calculation task
190
- 2. **02-dynamic-imports.ts** - Using npm packages dynamically
191
- 3. **03-variable-persistence.ts** - Multi-step state management
192
- 4. **04-research-with-tools.ts** - Custom tools for research tasks
193
- 5. **05-error-recovery.ts** - Handling and recovering from errors
231
+ ## Examples
194
232
 
195
- Run all examples:
233
+ The `examples/` folder contains complete, runnable examples:
196
234
 
197
- ```bash
198
- npm run run-examples
199
- ```
235
+ | Example | Description |
236
+ |---------|-------------|
237
+ | **01-simple-math.ts** | Basic calculation task |
238
+ | **02-dynamic-imports.ts** | Using npm packages dynamically |
239
+ | **03-variable-persistence.ts** | Multi-step state management |
240
+ | **04-research-with-tools.ts** | Custom tools for research tasks |
241
+ | **05-error-recovery.ts** | Handling and recovering from errors |
242
+ | **06-deep-research.ts** | Real API calls with DuckDuckGo/Wikipedia |
243
+ | **07-npm-package-import.ts** | Importing from the published npm package |
244
+ | **08-fetch-agent.ts** | Agent using fetch() directly (no tools) |
245
+ | **09-nested-agents.ts** | Manager agent delegating to worker agents |
200
246
 
201
- Or run a specific example:
247
+ Run an example:
202
248
 
203
249
  ```bash
204
- npx tsx examples/01-simple-math.ts
250
+ npx tsx examples/08-fetch-agent.ts
205
251
  ```
206
252
 
207
253
  ## API Reference
208
254
 
209
255
  ### CodeAgent
210
256
 
211
- Main agent class that generates and executes JavaScript code.
212
-
213
257
  ```typescript
214
258
  class CodeAgent {
215
259
  constructor(config: CodeAgentConfig)
260
+
261
+ // Run a task
216
262
  run(task: string, reset?: boolean): Promise<RunResult>
263
+
264
+ // Control
217
265
  stop(): void
218
266
  reset(): void
267
+
268
+ // Tools
219
269
  addTool(tool: Tool): void
220
270
  removeTool(name: string): boolean
221
271
  getTools(): Map<string, Tool>
272
+
273
+ // State
222
274
  getMemory(): AgentMemory
223
275
  getExecutor(): LocalExecutor
224
276
  }
@@ -228,10 +280,10 @@ class CodeAgent {
228
280
 
229
281
  ```typescript
230
282
  interface RunResult {
231
- output: unknown; // Final answer
232
- steps: MemoryStep[]; // All execution steps
233
- tokenUsage: TokenUsage; // Total token usage
234
- duration: number; // Total time in ms
283
+ output: unknown; // Final answer
284
+ steps: MemoryStep[]; // Execution history
285
+ tokenUsage: TokenUsage; // Token counts
286
+ duration: number; // Total time in ms
235
287
  }
236
288
  ```
237
289
 
@@ -245,10 +297,30 @@ abstract class Tool {
245
297
  abstract readonly outputType: string;
246
298
 
247
299
  abstract execute(args: Record<string, unknown>): Promise<unknown>;
248
- setup(): Promise<void>;
300
+
301
+ setup(): Promise<void>; // Optional async initialization
249
302
  call(args: Record<string, unknown>): Promise<unknown>;
250
- toCodePrompt(): string;
303
+ toCodePrompt(): string; // Generate function signature for prompt
304
+ }
305
+ ```
306
+
307
+ ### AgentTool
308
+
309
+ ```typescript
310
+ class AgentTool extends Tool {
311
+ constructor(config: AgentToolConfig)
312
+ }
313
+
314
+ interface AgentToolConfig {
315
+ agent: Agent; // The agent to wrap
316
+ name?: string; // Tool name (default: 'managed_agent')
317
+ description?: string; // Tool description
318
+ additionalContext?: string; // Extra context for the agent
319
+ returnFullResult?: boolean; // Return full result vs just output
251
320
  }
321
+
322
+ // Helper function
323
+ function agentAsTool(agent: Agent, options?: Omit<AgentToolConfig, 'agent'>): AgentTool
252
324
  ```
253
325
 
254
326
  ### LocalExecutor
@@ -256,35 +328,77 @@ abstract class Tool {
256
328
  ```typescript
257
329
  class LocalExecutor {
258
330
  constructor(config?: ExecutorConfig)
331
+
259
332
  execute(code: string): Promise<CodeExecutionOutput>
260
333
  sendTools(tools: Record<string, Tool>): void
261
334
  sendVariables(variables: Record<string, unknown>): void
262
335
  reset(): void
263
336
  getState(): Record<string, unknown>
264
337
  }
338
+
339
+ interface ExecutorConfig {
340
+ timeout?: number; // Execution timeout (default: 30000ms)
341
+ authorizedImports?: string[]; // Allowed npm packages
342
+ allowFs?: boolean; // Enable fs access (default: true)
343
+ workingDirectory?: string; // Working dir for fs operations
344
+ }
345
+ ```
346
+
347
+ ### LogLevel
348
+
349
+ ```typescript
350
+ enum LogLevel {
351
+ OFF = 0, // No output
352
+ ERROR = 1, // Errors only
353
+ INFO = 2, // Normal output (default)
354
+ DEBUG = 3, // Detailed debugging
355
+ }
265
356
  ```
266
357
 
267
- ## Architectural Differences from Python smolagents
358
+ ## Session Logging
359
+
360
+ All sessions are logged to `~/.smol-js/`:
361
+ - `session-<timestamp>.log` - Full session transcript
362
+ - `packages/` - Cached npm packages
363
+
364
+ ## Comparison with Python smolagents
268
365
 
269
366
  | Feature | Python smolagents | smol-js |
270
367
  |---------|------------------|---------|
271
368
  | Code execution | Python interpreter | Node.js vm module |
272
369
  | Imports | `import` statement | `await importPackage()` |
273
370
  | Tool definition | `@tool` decorator | Class extending `Tool` |
371
+ | Nested agents | `ManagedAgent` | `AgentTool` |
274
372
  | Async support | Optional | All tools are async |
373
+ | HTTP requests | Requires tool | Built-in `fetch()` |
275
374
  | Remote executors | E2B, Docker, etc. | Local only (for now) |
276
- | Agent types | CodeAgent, ToolCallingAgent | CodeAgent only (for now) |
375
+ | Agent types | CodeAgent, ToolCallingAgent | CodeAgent only |
376
+ | Multi-agent | Yes | Yes (via AgentTool) |
277
377
 
278
378
  ## Security Considerations
279
379
 
280
- - Code executes in a sandboxed vm context
281
- - Only authorized npm packages can be imported
282
- - File system access is restricted to working directory
283
- - Configurable execution delay allows user interruption
380
+ - **Sandboxed Execution**: Code runs in Node's vm module, isolated from the main process
381
+ - **Authorized Imports**: Only explicitly allowed npm packages can be imported
382
+ - **File System Isolation**: fs operations are restricted to the configured working directory
383
+ - **Execution Delay**: Configurable delay before code execution allows user interruption (Ctrl+C)
384
+ - **Timeout Protection**: Code execution has a configurable timeout (default: 30s)
284
385
 
285
386
  ## Contributing
286
387
 
287
- Contributions are welcome! Please open an issue or PR.
388
+ Contributions are welcome! Please open an issue or PR on GitHub.
389
+
390
+ ```bash
391
+ # Clone and install
392
+ git clone https://github.com/samrahimi/smol-js
393
+ cd smol-js
394
+ npm install
395
+
396
+ # Run tests
397
+ npm test
398
+
399
+ # Run examples
400
+ npx tsx examples/01-simple-math.ts
401
+ ```
288
402
 
289
403
  ## License
290
404
 
package/dist/index.js CHANGED
@@ -228,7 +228,7 @@ var import_chalk = __toESM(require("chalk"));
228
228
  var fs = __toESM(require("fs"));
229
229
  var path = __toESM(require("path"));
230
230
  var os = __toESM(require("os"));
231
- var LOG_DIR = path.join(os.homedir(), ".smol-js");
231
+ var LOG_DIR = path.join(os.homedir(), ".smol-js/logs");
232
232
  var AgentLogger = class {
233
233
  level;
234
234
  logFile;
@@ -1342,7 +1342,7 @@ Thought: [Your reasoning about what to do]
1342
1342
 
1343
1343
  1. **Always use final_answer()**: When you have the complete answer, call \`final_answer(yourResult)\` to return it.
1344
1344
 
1345
- 2. **One action per step**: Execute one logical action per code block. Don't try to do everything at once.
1345
+ 2. **One action per step**: Execute one logical action per code block and one code block per inference step. You will be given additional steps to complete your work if it cannot be done safely in one step. Don't try to do everything at once because you need to make sure that your tools returned valid, useful data before going on to make use of that data. In particular, if you are a Manager agent who is invoking Sub-Agents as tools, do not script the entire workflow in one step, make sure that you get to review the work of your subagents at critical points before going on to the next step.
1346
1346
 
1347
1347
  3. **Handle errors gracefully**: If something fails, explain what went wrong and try a different approach.
1348
1348
 
@@ -1356,6 +1356,8 @@ Thought: [Your reasoning about what to do]
1356
1356
 
1357
1357
  8. **No require()**: Use \`await importPackage('name')\` for npm packages instead of require().
1358
1358
 
1359
+ 9. **Internet Access**: You can use fetch() to get data from the web as needed. However, if you have access to specialized tools for browsing / searching / retrieving information, use those first and fall back to fetch if they don't work
1360
+
1359
1361
  ## Examples
1360
1362
 
1361
1363
  ### Example 1: Simple calculation
@@ -1651,8 +1653,8 @@ var import_openai = __toESM(require("openai"));
1651
1653
  var DEFAULT_CONFIG = {
1652
1654
  modelId: "anthropic/claude-sonnet-4.5",
1653
1655
  baseUrl: "https://openrouter.ai/api/v1",
1654
- maxTokens: 4096,
1655
- temperature: 0.7,
1656
+ maxTokens: 65e3,
1657
+ temperature: 1,
1656
1658
  timeout: 12e4
1657
1659
  };
1658
1660
  var OpenAIModel = class extends Model {