diff --git a/docs/guides/code-execution.md b/docs/guides/code-execution.md index 30ee6a7..26b8b82 100644 --- a/docs/guides/code-execution.md +++ b/docs/guides/code-execution.md @@ -215,6 +215,10 @@ See [E2B templates](https://e2b.dev/docs/templates) for details. - Costs money (has free tier) - Slightly higher latency than local +### Full Example + +See [examples/e2b-example.ts](https://github.com/artificial-analysis/StirrupJS/blob/main/examples/e2b-example.ts) for a complete working example using E2B with the code interpreter template. + ## Choosing a Backend | Backend | Best For | Pros | Cons | diff --git a/examples/_helpers.ts b/examples/_helpers.ts index cad8fea..ac7e347 100644 --- a/examples/_helpers.ts +++ b/examples/_helpers.ts @@ -22,12 +22,8 @@ export function loadEnv() { */ export function getApiConfig() { const apiKey = process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY; - const baseURL = process.env.OPENROUTER_API_KEY - ? 'https://openrouter.ai/api/v1' - : undefined; - const model = process.env.OPENROUTER_API_KEY - ? 'anthropic/claude-sonnet-4.5' - : 'gpt-5.2'; + const baseURL = process.env.OPENROUTER_API_KEY ? 'https://openrouter.ai/api/v1' : undefined; + const model = process.env.OPENROUTER_API_KEY ? 'anthropic/claude-sonnet-4.5' : 'gpt-5.2'; if (!apiKey) { console.error('Error: Set OPENROUTER_API_KEY or OPENAI_API_KEY in .env file'); diff --git a/examples/custom-tool.ts b/examples/custom-tool.ts index 6169a39..48407b4 100644 --- a/examples/custom-tool.ts +++ b/examples/custom-tool.ts @@ -10,7 +10,15 @@ import { z } from 'zod'; import { ChatCompletionsClient } from '../src/clients/openai-client.js'; -import { Agent, SIMPLE_FINISH_TOOL, ToolUseCountMetadata, type AgentRunResult, type FinishParams, type Tool, type ToolResult } from '../src/index.js'; +import { + Agent, + SIMPLE_FINISH_TOOL, + ToolUseCountMetadata, + type AgentRunResult, + type FinishParams, + type Tool, + type ToolResult, +} from '../src/index.js'; import { getApiConfig, loadEnv } from './_helpers.js'; // Load environment variables @@ -51,9 +59,6 @@ const databaseTool: Tool = { description: 'Execute a SQL query against the database', parameters: DbQueryParamsSchema, executor: async (params) => { - // Simulate database query - console.log(`Executing query: ${params.query}`); - // Mock results const results = [ { id: 1, name: 'Alice', age: 30 }, diff --git a/examples/e2b-example.ts b/examples/e2b-example.ts new file mode 100644 index 0000000..49e0917 --- /dev/null +++ b/examples/e2b-example.ts @@ -0,0 +1,39 @@ +/** + * E2B Example + * Demonstrates how to use the E2BCodeExecToolProvider to execute code in a sandboxed environment + * + * To run this example: + * 1. Create a .env file with: E2B_API_KEY=your-key-here + * 2. Run: npx tsx examples/e2b-example.ts + */ +import 'dotenv/config'; +import { Agent, E2BCodeExecToolProvider, SIMPLE_FINISH_TOOL } from '../src/index.js'; +import { ChatCompletionsClient } from '../src/clients/openai-client.js'; + +async function main() { + const client = new ChatCompletionsClient({ + apiKey: process.env.OPENROUTER_API_KEY, + baseURL: 'https://openrouter.ai/api/v1', + model: 'anthropic/claude-opus-4.5', + maxTokens: 100_000, + }); + + const codeExec = new E2BCodeExecToolProvider({ apiKey: process.env.E2B_API_KEY!, template: 'code-interpreter-v1' }); + + const agent = new Agent({ + client, + name: 'agent', + maxTurns: 15, + tools: [codeExec], + finishTool: SIMPLE_FINISH_TOOL, + }); + + await using session = agent.session({ outputDir: './output' }); + + const result = await session.run( + 'Generate the first 50 numbers of the Fibonacci sequence and create ' + + 'a line chart showing the growth. Save the chart as fibonacci.png' + ); +} + +main().catch(console.error); diff --git a/examples/events-example.ts b/examples/events-example.ts index 60822bc..3811c96 100644 --- a/examples/events-example.ts +++ b/examples/events-example.ts @@ -91,8 +91,9 @@ async function example1_BasicEvents() { // Run the agent with session // Note: We disable the default logger here to avoid duplicate logs with our custom event listeners await using session = agent.session({ noLogger: true }); - const result: AgentRunResult = await session.run('What is 2+2? When you know the answer, call the finish tool.'); - + const result: AgentRunResult = await session.run( + 'What is 2+2? When you know the answer, call the finish tool.' + ); } // ============================================================================ @@ -135,9 +136,7 @@ async function example2_Streaming() { case 'message': if (event.message.role === 'assistant') { const content = - typeof event.message.content === 'string' - ? event.message.content - : JSON.stringify(event.message.content); + typeof event.message.content === 'string' ? event.message.content : JSON.stringify(event.message.content); console.log('šŸ’¬ Assistant:', content.substring(0, 80)); } else if (event.message.role === 'tool') { console.log('šŸ”§ Tool result received'); @@ -233,6 +232,76 @@ async function example3_Cancellation() { } } +// ============================================================================ +// Example 4: Structured Logging +// ============================================================================ + +async function example4_StructuredLogging() { + console.log('\n========================================'); + console.log('Example 4: Structured Logging'); + console.log('========================================\n'); + + const { apiKey, baseURL, model } = getApiConfig(); + const client = new ChatCompletionsClient({ + apiKey, + baseURL, + model, + }); + + const agent = new Agent({ + client, + name: 'logging-agent', + maxTurns: 5, + finishTool: SIMPLE_FINISH_TOOL, + }); + + // The session automatically attaches the structured logger by default + // This provides formatted console output with boxes for messages, tool results, and summaries + await using session = agent.session(); + await session.run('What is 2+2? Call finish when done.'); +} + +// ============================================================================ +// Example 5: Combined Events and Structured Logging +// ============================================================================ + +async function example5_Combined() { + console.log('\n========================================'); + console.log('Example 5: Combined Events + Logging'); + console.log('========================================\n'); + + const { apiKey, baseURL, model } = getApiConfig(); + const client = new ChatCompletionsClient({ + apiKey, + baseURL, + model, + }); + + const agent = new Agent({ + client, + name: 'combined-agent', + maxTurns: 5, + tools: [new WebToolProvider()], + finishTool: SIMPLE_FINISH_TOOL, + }); + + // Add custom event handlers alongside the structured logger + // These can be used for custom metrics, alerts, or side effects + agent.on('tool:complete', ({ name, success }) => { + // Example: Send metrics to monitoring system + console.log(`[METRICS] Tool ${name} completed: ${success ? 'success' : 'failure'}`); + }); + + agent.on('run:complete', ({ duration }) => { + // Example: Log to external system + console.log(`[METRICS] Total run duration: ${duration}ms`); + }); + + // The structured logger runs alongside custom event handlers + await using session = agent.session(); + await session.run('What is 2+2? Call finish when done.'); +} + // ============================================================================ // Main // ============================================================================ @@ -254,11 +323,11 @@ if (import.meta.url === `file://${process.argv[1]}`) { } // Run examples - uncomment the ones you want to try: - await example1_BasicEvents(); + // await example1_BasicEvents(); // await example2_Streaming(); // await example3_Cancellation(); // await example4_StructuredLogging(); - // await example5_Combined(); + await example5_Combined(); console.log('\n✨ Demo complete!'); } diff --git a/examples/getting-started.ts b/examples/getting-started.ts index 6daee77..6d08852 100644 --- a/examples/getting-started.ts +++ b/examples/getting-started.ts @@ -10,7 +10,14 @@ */ import { ChatCompletionsClient } from '../src/clients/openai-client.js'; -import { Agent, CALCULATOR_TOOL, DEFAULT_TOOLS, SIMPLE_FINISH_TOOL, type AgentRunResult, type FinishParams } from '../src/index.js'; +import { + Agent, + CALCULATOR_TOOL, + DEFAULT_TOOLS, + SIMPLE_FINISH_TOOL, + type AgentRunResult, + type FinishParams, +} from '../src/index.js'; import { getApiConfig, loadEnv } from './_helpers.js'; // Load environment variables from .env file @@ -45,7 +52,9 @@ async function main() { }); // 4. Run the agent with a task - const result: AgentRunResult = await session.run('What is 2 + 2? Calculate it using the calculator tool and then finish.'); + const result: AgentRunResult = await session.run( + 'What is 2 + 2? Calculate it using the calculator tool and then finish.' + ); console.log(result); } diff --git a/examples/image.ts b/examples/image.ts index 09c7ee2..88e1296 100644 --- a/examples/image.ts +++ b/examples/image.ts @@ -45,8 +45,8 @@ The paths parameter must always be an array of strings, even if there's only one // 3. Use session to configure output directory (files will be auto-saved on cleanup) // Structured logging is enabled by default with debug level - await using session = agent.session({ - outputDir: './output' + await using session = agent.session({ + outputDir: './output', }); // 4. Run the agent with a task diff --git a/examples/skills/skills-example.ts b/examples/skills/skills-example.ts index d9d19df..32a5218 100644 --- a/examples/skills/skills-example.ts +++ b/examples/skills/skills-example.ts @@ -53,5 +53,3 @@ async function main() { } main().catch(console.error); - - diff --git a/examples/sub-agent.ts b/examples/sub-agent.ts index 57264b6..13fa0f5 100644 --- a/examples/sub-agent.ts +++ b/examples/sub-agent.ts @@ -10,7 +10,14 @@ */ import { ChatCompletionsClient } from '../src/clients/openai-client.js'; -import { Agent, CALCULATOR_TOOL, SIMPLE_FINISH_TOOL, type AgentRunResult, type FinishParams } from '../src/index.js'; +import { + Agent, + CALCULATOR_TOOL, + E2BCodeExecToolProvider, + SIMPLE_FINISH_TOOL, + type AgentRunResult, + type FinishParams, +} from '../src/index.js'; import { WebToolProvider } from '../src/tools/web/provider.js'; import { getApiConfig, loadEnv } from './_helpers.js'; @@ -27,6 +34,8 @@ async function main() { maxTokens: 100_000, }); + const codeExec = new E2BCodeExecToolProvider({ apiKey: process.env.E2B_API_KEY!, template: 'code-interpreter-v1' }); + // Create specialized sub-agents // 1. Research Agent - specialized in web research @@ -36,7 +45,8 @@ async function main() { maxTurns: 5, tools: [new WebToolProvider(180_000, process.env.BRAVE_API_KEY)], finishTool: SIMPLE_FINISH_TOOL, - systemPrompt: 'You are a research specialist. Use web search to find accurate information.', + systemPrompt: + 'You are a research specialist. Use web search to find accurate information. Return all relevant information in the finish tool.', }); // 2. Calculator Agent - specialized in math @@ -46,7 +56,8 @@ async function main() { maxTurns: 3, tools: [CALCULATOR_TOOL], finishTool: SIMPLE_FINISH_TOOL, - systemPrompt: 'You are a math specialist. Calculate precisely using the calculator tool.', + systemPrompt: + 'You are a math specialist. Calculate precisely using the calculator tool. Return all relevant information in the finish tool.', }); // 3. Main Coordinator Agent - delegates to sub-agents @@ -58,6 +69,7 @@ async function main() { // Convert sub-agents to tools researchAgent.toTool('Delegate research tasks to the research specialist'), mathAgent.toTool('Delegate math calculations to the math specialist'), + codeExec, ], finishTool: SIMPLE_FINISH_TOOL, systemPrompt: `You are a task coordinator. Delegate tasks to specialized sub-agents: diff --git a/examples/user-input.ts b/examples/user-input.ts index 4abe081..51cd5f3 100644 --- a/examples/user-input.ts +++ b/examples/user-input.ts @@ -58,5 +58,3 @@ What is the population of the user's home country over the last 3 years? } main().catch(console.error); - - diff --git a/package-lock.json b/package-lock.json index 5da19fd..4616ab0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,26 @@ { "name": "@stirrup/stirrup", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@stirrup/stirrup", - "version": "0.1.0", + "version": "1.0.0", "license": "MIT", "dependencies": { - "@ai-sdk/anthropic": "3.0.2", - "@mozilla/readability": "^0.6.0", + "@types/cheerio": "^0.22.35", "async-retry": "^1.3.3", "axios": "^1.7.7", "boxen": "^8.0.1", "chalk": "^5.3.0", + "cheerio": "^1.1.2", "cli-table3": "^0.6.5", "dotenv": "^16.4.5", "execa": "^9.4.0", "ffmpeg-static": "^5.2.0", "file-type": "^19.5.0", "fluent-ffmpeg": "^2.1.3", - "jsdom": "^25.0.1", "openai": "^4.68.4", "ora": "^8.1.0", "pino": "^9.6.0", @@ -33,7 +32,6 @@ "devDependencies": { "@types/async-retry": "^1.4.9", "@types/fluent-ffmpeg": "^2.1.27", - "@types/jsdom": "^27.0.0", "@types/node": "^22.7.7", "@types/turndown": "^5.0.5", "@typescript-eslint/eslint-plugin": "^7.18.0", @@ -238,19 +236,6 @@ "license": "MIT", "optional": true }, - "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -366,116 +351,6 @@ "@connectrpc/connect": "2.0.0-rc.3" } }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/@derhuerst/http-basic": { "version": "8.2.4", "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", @@ -1625,15 +1500,6 @@ } } }, - "node_modules/@mozilla/readability": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.6.0.tgz", - "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2129,6 +1995,15 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/cheerio": { + "version": "0.22.35", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", + "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -2176,18 +2051,6 @@ "@types/node": "*" } }, - "node_modules/@types/jsdom": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-27.0.0.tgz", - "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, "node_modules/@types/node": { "version": "22.19.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", @@ -2241,13 +2104,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/turndown": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.6.tgz", @@ -3002,6 +2858,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/boxen": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", @@ -3177,6 +3039,48 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -3503,36 +3407,32 @@ "node": ">= 8" } }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "license": "MIT", + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssstyle/node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" - }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=18" + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, "node_modules/dateformat": { @@ -3561,12 +3461,6 @@ } } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3663,6 +3557,73 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -3730,6 +3691,31 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -4946,18 +4932,6 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, - "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^3.1.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4965,6 +4939,25 @@ "dev": true, "license": "MIT" }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -4986,28 +4979,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/http-response-object": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", @@ -5238,12 +5209,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "license": "MIT" - }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -5374,68 +5339,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsdom": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", - "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", - "license": "MIT", - "dependencies": { - "cssstyle": "^4.1.0", - "data-urls": "^5.0.0", - "decimal.js": "^10.4.3", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.5", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.12", - "parse5": "^7.1.2", - "rrweb-cssom": "^0.7.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^5.0.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0", - "ws": "^8.18.0", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "canvas": "^2.11.2" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -5553,12 +5456,6 @@ "license": "Apache-2.0", "optional": true }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -5886,11 +5783,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "license": "MIT" + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -6176,6 +6079,31 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -6545,6 +6473,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -6794,12 +6723,6 @@ "node": ">= 18" } }, - "node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "license": "MIT" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6859,18 +6782,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, "node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -7334,12 +7245,6 @@ "node": ">=8" } }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "license": "MIT" - }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -7430,24 +7335,6 @@ "node": ">=14.0.0" } }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7499,30 +7386,6 @@ "node": ">=6" } }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -7651,6 +7514,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -7872,18 +7744,6 @@ } } }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -7893,15 +7753,6 @@ "node": ">= 14" } }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -7935,19 +7786,6 @@ "node": ">=18" } }, - "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8055,42 +7893,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "license": "MIT" - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 66db308..6847e0f 100644 --- a/package.json +++ b/package.json @@ -59,18 +59,18 @@ "node": ">=20.0.0" }, "dependencies": { - "@mozilla/readability": "^0.6.0", + "@types/cheerio": "^0.22.35", "async-retry": "^1.3.3", "axios": "^1.7.7", "boxen": "^8.0.1", "chalk": "^5.3.0", + "cheerio": "^1.1.2", "cli-table3": "^0.6.5", "dotenv": "^16.4.5", "execa": "^9.4.0", "ffmpeg-static": "^5.2.0", "file-type": "^19.5.0", "fluent-ffmpeg": "^2.1.3", - "jsdom": "^25.0.1", "openai": "^4.68.4", "ora": "^8.1.0", "pino": "^9.6.0", @@ -92,7 +92,6 @@ "devDependencies": { "@types/async-retry": "^1.4.9", "@types/fluent-ffmpeg": "^2.1.27", - "@types/jsdom": "^27.0.0", "@types/node": "^22.7.7", "@types/turndown": "^5.0.5", "@typescript-eslint/eslint-plugin": "^7.18.0", @@ -105,4 +104,4 @@ "typescript": "^5.6.3", "vitest": "^4.0.16" } -} \ No newline at end of file +} diff --git a/src/core/agent.ts b/src/core/agent.ts index 24b6159..68a76ae 100644 --- a/src/core/agent.ts +++ b/src/core/agent.ts @@ -33,7 +33,7 @@ import { SubAgentMetadata, SubAgentParamsSchema, type SubAgentParams } from './s */ export interface AgentEvents { 'run:start': (data: { task: string | ChatMessage[]; depth: number }) => void; - 'run:complete': (data: { result: AgentRunResult; duration: number }) => void; + 'run:complete': (data: { result: AgentRunResult; duration: number; outputDir?: string }) => void; 'run:error': (data: { error: Error; duration: number }) => void; 'turn:start': (data: { turn: number; maxTurns: number }) => void; @@ -299,16 +299,6 @@ export class Agent extends Ev const { assistantMessage, toolMessages } = await this.step(currentMessages, runMetadata); - if (assistantMessage.content) { - this.emit('message:assistant', { - content: - typeof assistantMessage.content === 'string' - ? assistantMessage.content - : JSON.stringify(assistantMessage.content), - toolCalls: assistantMessage.toolCalls, - }); - } - for (const toolMsg of toolMessages) { this.emit('message:tool', { name: toolMsg.name || 'unknown', @@ -386,7 +376,7 @@ export class Agent extends Ev this.lastFinishParams = finishParams; const duration = Date.now() - startTime; - this.emit('run:complete', { result, duration }); + this.emit('run:complete', { result, duration, outputDir: this.sessionState?.outputDir }); return result; } catch (error) { @@ -577,6 +567,17 @@ export class Agent extends Ev runMetadata['token_usage']?.push(TokenUsageMetadata.fromTokenUsage(assistantMessage.tokenUsage)); } + // Emit assistant message BEFORE tool execution so logs appear in correct order + if (assistantMessage.content || assistantMessage.toolCalls) { + this.emit('message:assistant', { + content: + typeof assistantMessage.content === 'string' + ? assistantMessage.content + : JSON.stringify(assistantMessage.content), + toolCalls: assistantMessage.toolCalls, + }); + } + const toolMessages: ToolMessage[] = []; if (assistantMessage.toolCalls) { for (const toolCall of assistantMessage.toolCalls) { diff --git a/src/tools/code-exec/base.ts b/src/tools/code-exec/base.ts index c358dc6..41f56fb 100644 --- a/src/tools/code-exec/base.ts +++ b/src/tools/code-exec/base.ts @@ -296,11 +296,11 @@ export abstract class CodeExecToolProvider implements ToolProvider { output += ` ${result.exitCode}\n`; if (result.stdout) { - output += ` \n`; + output += ` ${this.truncate(result.stdout, 10000)}\n`; } if (result.stderr) { - output += ` \n`; + output += ` ${this.truncate(result.stderr, 10000)}\n`; } if (result.errorKind) { diff --git a/src/tools/code-exec/e2b.ts b/src/tools/code-exec/e2b.ts index adac275..1cdb376 100644 --- a/src/tools/code-exec/e2b.ts +++ b/src/tools/code-exec/e2b.ts @@ -77,27 +77,14 @@ export class E2BCodeExecToolProvider extends CodeExecToolProvider { } try { - // Execute command - const execution = await this.sandbox.runCode(cmd, { - onStdout: () => {}, // We collect output from result - onStderr: () => {}, - }); - - // Check for errors - if (execution.error) { - const errorMessage = (execution.error as any).message || String(execution.error); - return { - exitCode: 1, - stdout: '', - stderr: errorMessage, - errorKind: 'execution_error', - }; - } + // Execute shell command + const result = await this.sandbox.commands.run(cmd); return { - exitCode: 0, - stdout: execution.logs.stdout.join('\n'), - stderr: execution.logs.stderr.join('\n'), + exitCode: result.exitCode, + stdout: result.stdout, + stderr: result.stderr, + errorKind: result.exitCode !== 0 ? 'execution_error' : undefined, }; } catch (error: any) { // Handle timeout @@ -111,17 +98,6 @@ export class E2BCodeExecToolProvider extends CodeExecToolProvider { }; } - // Handle invalid arguments (NUL bytes, etc.) - if (error.message?.includes('invalid')) { - return { - exitCode: -1, - stdout: '', - stderr: error.message, - errorKind: 'invalid_argument', - advice: 'Check command for invalid characters (NUL bytes, control characters)', - }; - } - return { exitCode: -1, stdout: '', @@ -137,7 +113,8 @@ export class E2BCodeExecToolProvider extends CodeExecToolProvider { } try { - const content = await this.sandbox.files.read(path); + // Read as bytes for binary file support + const content = await this.sandbox.files.read(path, { format: 'bytes' }); return Buffer.from(content); } catch (error) { throw new Error(`Failed to read file ${path}: ${error}`); diff --git a/src/tools/finish.ts b/src/tools/finish.ts index 0434bfc..9392db3 100644 --- a/src/tools/finish.ts +++ b/src/tools/finish.ts @@ -48,7 +48,8 @@ export type FinishParams = z.infer; */ export const SIMPLE_FINISH_TOOL: Tool = { name: 'finish', - description: 'Signal that the task is complete and provide a summary', + description: + 'Signal that the task is complete. You MUST include any files you created or modified in the paths parameter.', parameters: FinishParamsSchema, executor: async (params): Promise> => { return { diff --git a/src/tools/web/provider.ts b/src/tools/web/provider.ts index b1b1ff0..16b5757 100644 --- a/src/tools/web/provider.ts +++ b/src/tools/web/provider.ts @@ -4,8 +4,7 @@ import axios, { type AxiosInstance } from 'axios'; import retry from 'async-retry'; -import { JSDOM } from 'jsdom'; -import { Readability } from '@mozilla/readability'; +import * as cheerio from 'cheerio'; import TurndownService from 'turndown'; import { z } from 'zod'; import type { Tool, BaseTool, ToolProvider, ToolResult } from '../../core/models.js'; @@ -182,21 +181,32 @@ export class WebToolProvider implements ToolProvider { const html = response.data as string; - // Parse with JSDOM - const dom = new JSDOM(html, { url }); - const document = dom.window.document; + // Parse with cheerio + const $ = cheerio.load(html); - // Extract readable content with Readability - const reader = new Readability(document, { - charThreshold: 500, - }); + // Remove non-content elements + $('script, style, nav, footer, header, aside, noscript, iframe, svg, form').remove(); + $('[role="navigation"], [role="banner"], [role="contentinfo"], [aria-hidden="true"]').remove(); + $('.nav, .navbar, .menu, .sidebar, .footer, .header, .ads, .advertisement, .social-share').remove(); + + // Try to find the main content area + let contentHtml: string; + const mainContent = $('article, main, [role="main"], .content, .post, .article, #content, #main').first(); - const article = reader.parse(); + if (mainContent.length > 0) { + contentHtml = mainContent.html() || ''; + } else { + // Fall back to body content + contentHtml = $('body').html() || ''; + } - if (!article || !article.content) { + if (!contentHtml.trim()) { throw new Error('Failed to extract article content'); } + // Extract title + const title = $('title').text().trim() || $('h1').first().text().trim(); + // Convert to Markdown with Turndown const turndown = new TurndownService({ headingStyle: 'atx', @@ -204,11 +214,11 @@ export class WebToolProvider implements ToolProvider { bulletListMarker: '-', }); - let markdown = turndown.turndown(article.content); + let markdown = turndown.turndown(contentHtml); // Add title if available - if (article.title) { - markdown = `# ${article.title}\n\n${markdown}`; + if (title) { + markdown = `# ${title}\n\n${markdown}`; } // Truncate if too long diff --git a/src/utils/logging/logger.ts b/src/utils/logging/logger.ts index fb4c72d..d036971 100644 --- a/src/utils/logging/logger.ts +++ b/src/utils/logging/logger.ts @@ -1,5 +1,5 @@ /** - * Default agent logger with rich terminal output + * Default agent logger with terminal output */ import chalk from 'chalk'; @@ -11,7 +11,7 @@ import type { AssistantMessage, UserMessage, ToolMessage } from '../../core/mode import { SUBAGENT_INDENT_SPACES } from '../../constants.js'; /** - * Agent logger with rich terminal output + * Agent logger with terminal output * Uses chalk for colors, cli-table3 for tables, ora for spinners, boxen for panels */ export class AgentLogger implements AgentLoggerBase { diff --git a/src/utils/logging/structured-logger.ts b/src/utils/logging/structured-logger.ts index 09d88ed..af56c60 100644 --- a/src/utils/logging/structured-logger.ts +++ b/src/utils/logging/structured-logger.ts @@ -3,6 +3,9 @@ * Provides beautiful, readable logging for agent runs */ const MAX_MESSAGE_LENGTH = 10_000; +const getTerminalWidth = () => process.stdout.columns || 80; +import boxen from 'boxen'; +import chalk from 'chalk'; import pino from 'pino'; import type { Agent, AgentEvents } from '../../core/agent.js'; @@ -29,6 +32,8 @@ function createConsoleLogger(agent: Agent, level: string startTime?: number; agentName?: string; depth?: number; + currentTurn?: number; + maxTurns?: number; } = {}; // Start handler @@ -56,37 +61,111 @@ function createConsoleLogger(agent: Agent, level: string // Turn start handler const onTurnStart: AgentEvents['turn:start'] = (data) => { - const prefix = runData.depth && runData.depth > 0 ? ` ${' '.repeat(runData.depth - 1)} ` : ''; - console.log(`${prefix}šŸ“ Turn ${data.turn + 1}/${data.maxTurns}`); + runData.currentTurn = data.turn + 1; + runData.maxTurns = data.maxTurns; }; // Assistant message handler const onMessageAssistant: AgentEvents['message:assistant'] = (data) => { - const prefix = runData.depth && runData.depth > 0 ? ` ${' '.repeat(runData.depth - 1)} ` : ''; + const indent = runData.depth && runData.depth > 0 ? ' '.repeat(runData.depth) : ''; - // Show assistant message if verbose - if (level === 'trace' || level === 'debug') { - if (data.content && data.content.length > 0) { - const truncated = - data.content.length > MAX_MESSAGE_LENGTH - ? data.content.substring(0, MAX_MESSAGE_LENGTH) + '...' - : data.content; - console.log(`${prefix} šŸ’¬ ${truncated}`); - } + // Build content for the box + const contentParts: string[] = []; + + // Add message content + if (data.content && data.content.length > 0) { + const truncated = + data.content.length > MAX_MESSAGE_LENGTH ? data.content.substring(0, MAX_MESSAGE_LENGTH) + '...' : data.content; + contentParts.push(truncated); } - // Show tool calls (more important) + // Add tool calls section if (data.toolCalls && data.toolCalls.length > 0) { - const toolNames = data.toolCalls.map((tc) => tc.name).join(', '); - console.log(`${prefix} šŸ”§ Calling: ${toolNames}`); + if (contentParts.length > 0) contentParts.push(''); + contentParts.push(chalk.gray('Tool Calls:')); + for (const tc of data.toolCalls) { + contentParts.push(` šŸ”§ ${chalk.yellow(tc.name)}`); + // Format arguments nicely + try { + const args = JSON.parse(tc.arguments) as Record; + for (const [key, value] of Object.entries(args)) { + const strValue = typeof value === 'string' ? value : JSON.stringify(value); + const truncated = strValue.length > 1000 ? strValue.substring(0, 1000) + '...' : strValue; + const displayValue = truncated.replace(/\n/g, '\\n'); + contentParts.push(chalk.gray(` ${key}=${displayValue}`)); + } + } catch { + contentParts.push(chalk.gray(` ${tc.arguments}`)); + } + } + } + + // Only show box if there's content + if (contentParts.length > 0) { + const title = `AssistantMessage │ ${runData.agentName} │ Turn ${runData.currentTurn || '?'}/${runData.maxTurns || '?'}`; + const termWidth = getTerminalWidth(); + const boxWidth = indent ? termWidth - indent.length : termWidth; + const box = boxen(contentParts.join('\n'), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderStyle: 'round', + borderColor: 'cyan', + title, + titleAlignment: 'left', + width: boxWidth, + }); + // Add indent for sub-agents + if (indent) { + console.log( + box + .split('\n') + .map((line) => indent + line) + .join('\n') + ); + } else { + console.log(box); + } } }; - // Tool start handler - const onToolStart: AgentEvents['tool:start'] = (data) => { - const prefix = runData.depth && runData.depth > 0 ? ` ${' '.repeat(runData.depth - 1)} ` : ''; - const argsStr = level === 'debug' || level === 'trace' ? `: ${JSON.stringify(data.arguments)}` : ''; - console.log(`${prefix} └─ ${data.name}${argsStr}`); + // Tool complete handler + const onToolComplete: AgentEvents['tool:complete'] = (data) => { + const indent = runData.depth && runData.depth > 0 ? ' '.repeat(runData.depth) : ''; + + // Truncate result if too long + let truncatedResult: string; + if (data.result.length > 1000) { + truncatedResult = data.result.substring(0, 500) + '\n...\n' + data.result.substring(data.result.length - 500); + } else { + truncatedResult = data.result; + } + + const statusIcon = data.success ? chalk.green('āœ“') : chalk.red('āœ—'); + const turnInfo = runData.currentTurn ? ` │ Turn ${runData.currentTurn}/${runData.maxTurns}` : ''; + const title = `${statusIcon} ToolResult │ ${data.name}${turnInfo}`; + const borderColor = data.success ? 'green' : 'red'; + + const termWidth = getTerminalWidth(); + const boxWidth = indent ? termWidth - indent.length : termWidth; + const box = boxen(truncatedResult, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderStyle: 'round', + borderColor, + title, + titleAlignment: 'left', + width: boxWidth, + }); + + // Add indent for sub-agents + if (indent) { + console.log( + box + .split('\n') + .map((line) => indent + line) + .join('\n') + ); + } else { + console.log(box); + } }; // Turn complete handler @@ -131,16 +210,18 @@ function createConsoleLogger(agent: Agent, level: string return; } + const termWidth = getTerminalWidth(); + // For main agent, show full summary - console.log('═'.repeat(80)); + console.log('═'.repeat(termWidth)); console.log('āœ… Agent Complete'); - console.log('═'.repeat(80)); + console.log('═'.repeat(termWidth)); if (data.result.finishParams) { console.log('\nšŸ“ Result:', (data.result.finishParams as any).reason || JSON.stringify(data.result.finishParams)); } - console.log('\n' + '─'.repeat(80)); + console.log('\n' + '─'.repeat(termWidth)); // Tool Usage section const tools = Object.entries(data.result.runMetadata) @@ -148,45 +229,66 @@ function createConsoleLogger(agent: Agent, level: string .map(([name, toolData]: [string, any]) => ({ name, uses: toolData.numUses || 0 })) .filter((t) => t.uses > 0); - console.log('╭─ Tool Usage ' + '─'.repeat(65) + 'ā•®'); - if (tools.length > 0) { - tools.forEach(({ name, uses }) => { - const callsText = `${uses} call${uses === 1 ? '' : 's'}`; - const padding = 80 - 3 - name.length - 1 - callsText.length - 1; - console.log(`│ ${name} ${callsText}${' '.repeat(Math.max(0, padding))}│`); - }); - } else { - console.log('│ No tools used' + ' '.repeat(63) + '│'); - } - console.log('ā•°' + '─'.repeat(78) + '╯'); - - // Paths section - const paths = (data.result.finishParams as any)?.paths || []; - console.log('╭─ Paths ' + '─'.repeat(70) + 'ā•®'); - if (paths.length > 0) { - paths.forEach((path: string) => { - const displayPath = path.length > 75 ? '...' + path.slice(-72) : path; - console.log(`│ ${displayPath.padEnd(75)}│`); - }); - } else { - console.log('│ No output paths' + ' '.repeat(60) + '│'); - } - console.log('ā•°' + '─'.repeat(78) + '╯'); + const toolContent = + tools.length > 0 + ? tools.map(({ name, uses }) => `${name} ${uses} call${uses === 1 ? '' : 's'}`).join('\n') + : 'No tools used'; + + console.log( + boxen(toolContent, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderStyle: 'round', + borderColor: 'gray', + title: 'Tool Usage', + titleAlignment: 'left', + width: termWidth, + }) + ); + + // Paths section - normalize with outputDir if available + const rawPaths = (data.result.finishParams as any)?.paths || []; + const outputDir = data.outputDir; + const paths = rawPaths.map((p: string) => { + // If outputDir is set and path doesn't already start with it, prepend it + if (outputDir && !p.startsWith(outputDir) && !p.startsWith('/')) { + return `${outputDir}/${p}`; + } + return p; + }); + const pathContent = + paths.length > 0 + ? paths + .map((path: string) => (path.length > termWidth - 6 ? '...' + path.slice(-(termWidth - 9)) : path)) + .join('\n') + : 'No output paths'; + + console.log( + boxen(pathContent, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderStyle: 'round', + borderColor: 'gray', + title: 'Paths', + titleAlignment: 'left', + width: termWidth, + }) + ); // Token Usage section const tokenUsage = data.result.runMetadata.token_usage as any; - console.log('╭─ Token Usage ' + '─'.repeat(64) + 'ā•®'); - if (tokenUsage) { - const inputStr = tokenUsage.input.toLocaleString().padStart(10); - const outputStr = tokenUsage.output.toLocaleString().padStart(10); - const totalStr = tokenUsage.total.toLocaleString().padStart(10); - console.log(`│ Input ${inputStr}${' '.repeat(56)}│`); - console.log(`│ Output ${outputStr}${' '.repeat(56)}│`); - console.log(`│ Total ${totalStr}${' '.repeat(56)}│`); - } else { - console.log('│ No token usage data' + ' '.repeat(56) + '│'); - } - console.log('ā•°' + '─'.repeat(78) + '╯'); + const tokenContent = tokenUsage + ? `Input ${tokenUsage.input.toLocaleString().padStart(10)}\nOutput ${tokenUsage.output.toLocaleString().padStart(10)}\nTotal ${tokenUsage.total.toLocaleString().padStart(10)}` + : 'No token usage data'; + + console.log( + boxen(tokenContent, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderStyle: 'round', + borderColor: 'gray', + title: 'Token Usage', + titleAlignment: 'left', + width: termWidth, + }) + ); console.log(); }; @@ -204,7 +306,7 @@ function createConsoleLogger(agent: Agent, level: string agent.on('run:start', onRunStart); agent.on('turn:start', onTurnStart); agent.on('message:assistant', onMessageAssistant); - agent.on('tool:start', onToolStart); + agent.on('tool:complete', onToolComplete); agent.on('turn:complete', onTurnComplete); agent.on('tool:error', onToolError); agent.on('summarization:start', onSummarizationStart); @@ -217,7 +319,7 @@ function createConsoleLogger(agent: Agent, level: string agent.off('run:start', onRunStart); agent.off('turn:start', onTurnStart); agent.off('message:assistant', onMessageAssistant); - agent.off('tool:start', onToolStart); + agent.off('tool:complete', onToolComplete); agent.off('turn:complete', onTurnComplete); agent.off('tool:error', onToolError); agent.off('summarization:start', onSummarizationStart);