@travisennis/acai 0.0.1
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/.acai/acai.json +9 -0
- package/.acai/prompts/add-openrouter-model.md +13 -0
- package/.acai/prompts/project-status.md +4 -0
- package/.acai/prompts/update-architecture-document.md +9 -0
- package/.acai/rules/learned-rules.md +9 -0
- package/.ai/docs/available-tools.txt +3 -0
- package/.ai/docs/cognitive_complexity_refactoring_progress.md +65 -0
- package/.ai/docs/deleted_tools.md +168 -0
- package/.ai/docs/deleted_tools_88ced9ef.md +56 -0
- package/.ai/docs/image-pasting.md +46 -0
- package/.ai/docs/initialize-app.md +117 -0
- package/.ai/docs/issue-4-plan.md +44 -0
- package/.ai/docs/marked-renderer-debug.md +15 -0
- package/.ai/docs/marked-renderer-refactor-plan.md +64 -0
- package/.ai/docs/memory-use-cases.md +55 -0
- package/.ai/docs/prompt-consistency.md +31 -0
- package/.ai/docs/refactoring-tools.md +98 -0
- package/.ai/docs/system-prompt-update.md +174 -0
- package/.ai/docs/system_prompt.txt +210 -0
- package/.ai/docs/tasks.md +49 -0
- package/.ai/plan.md +131 -0
- package/.ai/prompt.md +1 -0
- package/.ai/scripts/fetch_models.js +27 -0
- package/.ai/scripts/generateSystemPrompt.ts +15 -0
- package/.ai/scripts/list-tools.mjs +4 -0
- package/.ai/scripts/p5_geometric_shapes.js +149 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +3 -0
- package/.husky/pre-push +1 -0
- package/.ignore +4 -0
- package/AGENTS.md +25 -0
- package/ARCHITECTURE.md +304 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/TODO.md +2 -0
- package/biome.json +61 -0
- package/commitlint.config.js +3 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +116 -0
- package/dist/commands/application-log-command.d.ts +2 -0
- package/dist/commands/application-log-command.js +43 -0
- package/dist/commands/clear-command.d.ts +2 -0
- package/dist/commands/clear-command.js +12 -0
- package/dist/commands/compact-command.d.ts +2 -0
- package/dist/commands/compact-command.js +51 -0
- package/dist/commands/copy-command.d.ts +2 -0
- package/dist/commands/copy-command.js +51 -0
- package/dist/commands/edit-command.d.ts +2 -0
- package/dist/commands/edit-command.js +53 -0
- package/dist/commands/edit-prompt-command.d.ts +2 -0
- package/dist/commands/edit-prompt-command.js +25 -0
- package/dist/commands/exit-command.d.ts +2 -0
- package/dist/commands/exit-command.js +14 -0
- package/dist/commands/files-command.d.ts +2 -0
- package/dist/commands/files-command.js +63 -0
- package/dist/commands/generate-rules-command.d.ts +2 -0
- package/dist/commands/generate-rules-command.js +61 -0
- package/dist/commands/help-command.d.ts +2 -0
- package/dist/commands/help-command.js +19 -0
- package/dist/commands/init-command.d.ts +2 -0
- package/dist/commands/init-command.js +40 -0
- package/dist/commands/last-log-command.d.ts +2 -0
- package/dist/commands/last-log-command.js +76 -0
- package/dist/commands/manager.d.ts +22 -0
- package/dist/commands/manager.js +123 -0
- package/dist/commands/model-command.d.ts +2 -0
- package/dist/commands/model-command.js +84 -0
- package/dist/commands/paste-command.d.ts +2 -0
- package/dist/commands/paste-command.js +40 -0
- package/dist/commands/prompt-command.d.ts +2 -0
- package/dist/commands/prompt-command.js +111 -0
- package/dist/commands/reset-command.d.ts +2 -0
- package/dist/commands/reset-command.js +16 -0
- package/dist/commands/rules-command.d.ts +2 -0
- package/dist/commands/rules-command.js +68 -0
- package/dist/commands/save-command.d.ts +2 -0
- package/dist/commands/save-command.js +14 -0
- package/dist/commands/types.d.ts +26 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/usage-command.d.ts +2 -0
- package/dist/commands/usage-command.js +21 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.js +193 -0
- package/dist/conversation-analyzer.d.ts +10 -0
- package/dist/conversation-analyzer.js +88 -0
- package/dist/dedent.d.ts +3 -0
- package/dist/dedent.js +38 -0
- package/dist/formatting.d.ts +17 -0
- package/dist/formatting.js +103 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +213 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +24 -0
- package/dist/mentions.d.ts +9 -0
- package/dist/mentions.js +182 -0
- package/dist/messages.d.ts +69 -0
- package/dist/messages.js +261 -0
- package/dist/middleware/audit-message.d.ts +5 -0
- package/dist/middleware/audit-message.js +95 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/rate-limit.d.ts +4 -0
- package/dist/middleware/rate-limit.js +17 -0
- package/dist/models/ai-config.d.ts +12 -0
- package/dist/models/ai-config.js +87 -0
- package/dist/models/anthropic-provider.d.ts +25 -0
- package/dist/models/anthropic-provider.js +184 -0
- package/dist/models/deepseek-provider.d.ts +20 -0
- package/dist/models/deepseek-provider.js +42 -0
- package/dist/models/google-provider.d.ts +19 -0
- package/dist/models/google-provider.js +56 -0
- package/dist/models/manager.d.ts +15 -0
- package/dist/models/manager.js +48 -0
- package/dist/models/openai-provider.d.ts +22 -0
- package/dist/models/openai-provider.js +70 -0
- package/dist/models/openrouter-provider.d.ts +36 -0
- package/dist/models/openrouter-provider.js +276 -0
- package/dist/models/providers.d.ts +33 -0
- package/dist/models/providers.js +116 -0
- package/dist/models/xai-provider.d.ts +20 -0
- package/dist/models/xai-provider.js +47 -0
- package/dist/parsing.d.ts +2 -0
- package/dist/parsing.js +18 -0
- package/dist/prompts/manager.d.ts +19 -0
- package/dist/prompts/manager.js +71 -0
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.js +158 -0
- package/dist/repl-prompt.d.ts +14 -0
- package/dist/repl-prompt.js +147 -0
- package/dist/repl.d.ts +27 -0
- package/dist/repl.js +431 -0
- package/dist/source/cli.d.ts +19 -0
- package/dist/source/cli.js +116 -0
- package/dist/source/commands/application-log-command.d.ts +2 -0
- package/dist/source/commands/application-log-command.js +43 -0
- package/dist/source/commands/clear-command.d.ts +2 -0
- package/dist/source/commands/clear-command.js +12 -0
- package/dist/source/commands/compact-command.d.ts +2 -0
- package/dist/source/commands/compact-command.js +51 -0
- package/dist/source/commands/copy-command.d.ts +2 -0
- package/dist/source/commands/copy-command.js +51 -0
- package/dist/source/commands/edit-command.d.ts +2 -0
- package/dist/source/commands/edit-command.js +53 -0
- package/dist/source/commands/edit-prompt-command.d.ts +2 -0
- package/dist/source/commands/edit-prompt-command.js +25 -0
- package/dist/source/commands/exit-command.d.ts +2 -0
- package/dist/source/commands/exit-command.js +14 -0
- package/dist/source/commands/files-command.d.ts +2 -0
- package/dist/source/commands/files-command.js +63 -0
- package/dist/source/commands/generate-rules-command.d.ts +2 -0
- package/dist/source/commands/generate-rules-command.js +61 -0
- package/dist/source/commands/help-command.d.ts +2 -0
- package/dist/source/commands/help-command.js +19 -0
- package/dist/source/commands/init-command.d.ts +2 -0
- package/dist/source/commands/init-command.js +40 -0
- package/dist/source/commands/last-log-command.d.ts +2 -0
- package/dist/source/commands/last-log-command.js +76 -0
- package/dist/source/commands/manager.d.ts +22 -0
- package/dist/source/commands/manager.js +123 -0
- package/dist/source/commands/model-command.d.ts +2 -0
- package/dist/source/commands/model-command.js +84 -0
- package/dist/source/commands/paste-command.d.ts +2 -0
- package/dist/source/commands/paste-command.js +40 -0
- package/dist/source/commands/prompt-command.d.ts +2 -0
- package/dist/source/commands/prompt-command.js +111 -0
- package/dist/source/commands/reset-command.d.ts +2 -0
- package/dist/source/commands/reset-command.js +16 -0
- package/dist/source/commands/rules-command.d.ts +2 -0
- package/dist/source/commands/rules-command.js +68 -0
- package/dist/source/commands/save-command.d.ts +2 -0
- package/dist/source/commands/save-command.js +14 -0
- package/dist/source/commands/types.d.ts +26 -0
- package/dist/source/commands/types.js +1 -0
- package/dist/source/commands/usage-command.d.ts +2 -0
- package/dist/source/commands/usage-command.js +21 -0
- package/dist/source/config.d.ts +60 -0
- package/dist/source/config.js +193 -0
- package/dist/source/conversation-analyzer.d.ts +10 -0
- package/dist/source/conversation-analyzer.js +88 -0
- package/dist/source/dedent.d.ts +3 -0
- package/dist/source/dedent.js +38 -0
- package/dist/source/formatting.d.ts +17 -0
- package/dist/source/formatting.js +103 -0
- package/dist/source/index.d.ts +18 -0
- package/dist/source/index.js +213 -0
- package/dist/source/logger.d.ts +2 -0
- package/dist/source/logger.js +24 -0
- package/dist/source/mentions.d.ts +9 -0
- package/dist/source/mentions.js +182 -0
- package/dist/source/messages.d.ts +69 -0
- package/dist/source/messages.js +261 -0
- package/dist/source/middleware/audit-message.d.ts +5 -0
- package/dist/source/middleware/audit-message.js +95 -0
- package/dist/source/middleware/index.d.ts +2 -0
- package/dist/source/middleware/index.js +2 -0
- package/dist/source/middleware/rate-limit.d.ts +4 -0
- package/dist/source/middleware/rate-limit.js +17 -0
- package/dist/source/models/ai-config.d.ts +12 -0
- package/dist/source/models/ai-config.js +87 -0
- package/dist/source/models/anthropic-provider.d.ts +25 -0
- package/dist/source/models/anthropic-provider.js +184 -0
- package/dist/source/models/deepseek-provider.d.ts +20 -0
- package/dist/source/models/deepseek-provider.js +42 -0
- package/dist/source/models/google-provider.d.ts +19 -0
- package/dist/source/models/google-provider.js +56 -0
- package/dist/source/models/manager.d.ts +15 -0
- package/dist/source/models/manager.js +48 -0
- package/dist/source/models/openai-provider.d.ts +22 -0
- package/dist/source/models/openai-provider.js +70 -0
- package/dist/source/models/openrouter-provider.d.ts +36 -0
- package/dist/source/models/openrouter-provider.js +276 -0
- package/dist/source/models/providers.d.ts +33 -0
- package/dist/source/models/providers.js +116 -0
- package/dist/source/models/xai-provider.d.ts +20 -0
- package/dist/source/models/xai-provider.js +47 -0
- package/dist/source/parsing.d.ts +2 -0
- package/dist/source/parsing.js +18 -0
- package/dist/source/prompts/manager.d.ts +19 -0
- package/dist/source/prompts/manager.js +71 -0
- package/dist/source/prompts.d.ts +4 -0
- package/dist/source/prompts.js +158 -0
- package/dist/source/repl-prompt.d.ts +14 -0
- package/dist/source/repl-prompt.js +147 -0
- package/dist/source/repl.d.ts +27 -0
- package/dist/source/repl.js +431 -0
- package/dist/source/terminal/formatting.d.ts +37 -0
- package/dist/source/terminal/formatting.js +106 -0
- package/dist/source/terminal/index.d.ts +94 -0
- package/dist/source/terminal/index.js +420 -0
- package/dist/source/terminal/markdown-utils.d.ts +2 -0
- package/dist/source/terminal/markdown-utils.js +81 -0
- package/dist/source/terminal/markdown.d.ts +1 -0
- package/dist/source/terminal/markdown.js +111 -0
- package/dist/source/terminal/types.d.ts +71 -0
- package/dist/source/terminal/types.js +1 -0
- package/dist/source/terminal-output.d.ts +8 -0
- package/dist/source/terminal-output.js +213 -0
- package/dist/source/terminal-output.test.d.ts +8 -0
- package/dist/source/terminal-output.test.js +213 -0
- package/dist/source/token-tracker.d.ts +14 -0
- package/dist/source/token-tracker.js +53 -0
- package/dist/source/token-utils.d.ts +7 -0
- package/dist/source/token-utils.js +13 -0
- package/dist/source/tools/agent.d.ts +17 -0
- package/dist/source/tools/agent.js +87 -0
- package/dist/source/tools/bash.d.ts +19 -0
- package/dist/source/tools/bash.js +294 -0
- package/dist/source/tools/code-interpreter.d.ts +12 -0
- package/dist/source/tools/code-interpreter.js +131 -0
- package/dist/source/tools/command-validation.d.ts +8 -0
- package/dist/source/tools/command-validation.js +69 -0
- package/dist/source/tools/delete-file.d.ts +12 -0
- package/dist/source/tools/delete-file.js +56 -0
- package/dist/source/tools/directory-tree.d.ts +12 -0
- package/dist/source/tools/directory-tree.js +38 -0
- package/dist/source/tools/edit-file.d.ts +19 -0
- package/dist/source/tools/edit-file.js +107 -0
- package/dist/source/tools/filesystem-utils.d.ts +22 -0
- package/dist/source/tools/filesystem-utils.js +191 -0
- package/dist/source/tools/git-utils.d.ts +14 -0
- package/dist/source/tools/git-utils.js +64 -0
- package/dist/source/tools/grep.d.ts +17 -0
- package/dist/source/tools/grep.js +138 -0
- package/dist/source/tools/index.d.ts +161 -0
- package/dist/source/tools/index.js +209 -0
- package/dist/source/tools/memory-read.d.ts +13 -0
- package/dist/source/tools/memory-read.js +135 -0
- package/dist/source/tools/memory-write.d.ts +12 -0
- package/dist/source/tools/memory-write.js +83 -0
- package/dist/source/tools/move-file.d.ts +13 -0
- package/dist/source/tools/move-file.js +44 -0
- package/dist/source/tools/read-file.d.ts +17 -0
- package/dist/source/tools/read-file.js +86 -0
- package/dist/source/tools/read-multiple-files.d.ts +14 -0
- package/dist/source/tools/read-multiple-files.js +55 -0
- package/dist/source/tools/save-file.d.ts +17 -0
- package/dist/source/tools/save-file.js +98 -0
- package/dist/source/tools/think.d.ts +11 -0
- package/dist/source/tools/think.js +45 -0
- package/dist/source/tools/types.d.ts +29 -0
- package/dist/source/tools/types.js +14 -0
- package/dist/source/tools/web-fetch.d.ts +47 -0
- package/dist/source/tools/web-fetch.js +246 -0
- package/dist/source/tools/web-search.d.ts +13 -0
- package/dist/source/tools/web-search.js +80 -0
- package/dist/source/utils/process.d.ts +36 -0
- package/dist/source/utils/process.js +75 -0
- package/dist/source/version.d.ts +1 -0
- package/dist/source/version.js +21 -0
- package/dist/terminal/formatting.d.ts +37 -0
- package/dist/terminal/formatting.js +106 -0
- package/dist/terminal/index.d.ts +94 -0
- package/dist/terminal/index.js +420 -0
- package/dist/terminal/markdown-utils.d.ts +2 -0
- package/dist/terminal/markdown-utils.js +81 -0
- package/dist/terminal/markdown.d.ts +1 -0
- package/dist/terminal/markdown.js +111 -0
- package/dist/terminal/types.d.ts +71 -0
- package/dist/terminal/types.js +1 -0
- package/dist/terminal-output.d.ts +8 -0
- package/dist/terminal-output.js +213 -0
- package/dist/token-tracker.d.ts +14 -0
- package/dist/token-tracker.js +53 -0
- package/dist/token-utils.d.ts +7 -0
- package/dist/token-utils.js +13 -0
- package/dist/tools/agent.d.ts +17 -0
- package/dist/tools/agent.js +87 -0
- package/dist/tools/bash.d.ts +19 -0
- package/dist/tools/bash.js +294 -0
- package/dist/tools/code-interpreter.d.ts +12 -0
- package/dist/tools/code-interpreter.js +131 -0
- package/dist/tools/command-validation.d.ts +8 -0
- package/dist/tools/command-validation.js +69 -0
- package/dist/tools/delete-file.d.ts +12 -0
- package/dist/tools/delete-file.js +56 -0
- package/dist/tools/directory-tree.d.ts +12 -0
- package/dist/tools/directory-tree.js +38 -0
- package/dist/tools/edit-file.d.ts +19 -0
- package/dist/tools/edit-file.js +107 -0
- package/dist/tools/filesystem-utils.d.ts +22 -0
- package/dist/tools/filesystem-utils.js +191 -0
- package/dist/tools/git-utils.d.ts +14 -0
- package/dist/tools/git-utils.js +64 -0
- package/dist/tools/grep.d.ts +17 -0
- package/dist/tools/grep.js +138 -0
- package/dist/tools/index.d.ts +161 -0
- package/dist/tools/index.js +209 -0
- package/dist/tools/memory-read.d.ts +13 -0
- package/dist/tools/memory-read.js +135 -0
- package/dist/tools/memory-write.d.ts +12 -0
- package/dist/tools/memory-write.js +83 -0
- package/dist/tools/move-file.d.ts +13 -0
- package/dist/tools/move-file.js +44 -0
- package/dist/tools/read-file.d.ts +17 -0
- package/dist/tools/read-file.js +86 -0
- package/dist/tools/read-multiple-files.d.ts +14 -0
- package/dist/tools/read-multiple-files.js +55 -0
- package/dist/tools/save-file.d.ts +17 -0
- package/dist/tools/save-file.js +98 -0
- package/dist/tools/think.d.ts +11 -0
- package/dist/tools/think.js +45 -0
- package/dist/tools/types.d.ts +29 -0
- package/dist/tools/types.js +14 -0
- package/dist/tools/web-fetch.d.ts +47 -0
- package/dist/tools/web-fetch.js +246 -0
- package/dist/tools/web-search.d.ts +13 -0
- package/dist/tools/web-search.js +80 -0
- package/dist/utils/process.d.ts +36 -0
- package/dist/utils/process.js +75 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +21 -0
- package/knip.json +5 -0
- package/package.json +83 -0
- package/source/cli.ts +172 -0
- package/source/commands/application-log-command.ts +53 -0
- package/source/commands/clear-command.ts +14 -0
- package/source/commands/compact-command.ts +64 -0
- package/source/commands/copy-command.ts +55 -0
- package/source/commands/edit-command.ts +63 -0
- package/source/commands/edit-prompt-command.ts +31 -0
- package/source/commands/exit-command.ts +18 -0
- package/source/commands/files-command.ts +85 -0
- package/source/commands/generate-rules-command.ts +82 -0
- package/source/commands/help-command.ts +27 -0
- package/source/commands/init-command.ts +48 -0
- package/source/commands/last-log-command.ts +88 -0
- package/source/commands/manager.ts +151 -0
- package/source/commands/model-command.ts +123 -0
- package/source/commands/paste-command.ts +62 -0
- package/source/commands/prompt-command.ts +150 -0
- package/source/commands/reset-command.ts +22 -0
- package/source/commands/rules-command.ts +76 -0
- package/source/commands/save-command.ts +20 -0
- package/source/commands/types.ts +28 -0
- package/source/commands/usage-command.ts +26 -0
- package/source/config.ts +223 -0
- package/source/conversation-analyzer.ts +115 -0
- package/source/dedent.ts +53 -0
- package/source/formatting.ts +132 -0
- package/source/index.ts +240 -0
- package/source/logger.ts +29 -0
- package/source/mentions.ts +227 -0
- package/source/messages.ts +360 -0
- package/source/middleware/audit-message.ts +133 -0
- package/source/middleware/index.ts +2 -0
- package/source/middleware/rate-limit.ts +24 -0
- package/source/models/ai-config.ts +109 -0
- package/source/models/anthropic-provider.ts +199 -0
- package/source/models/deepseek-provider.ts +53 -0
- package/source/models/google-provider.ts +68 -0
- package/source/models/manager.ts +84 -0
- package/source/models/openai-provider.ts +81 -0
- package/source/models/openrouter-provider.ts +288 -0
- package/source/models/providers.ts +197 -0
- package/source/models/xai-provider.ts +59 -0
- package/source/parsing.ts +20 -0
- package/source/prompts/manager.ts +90 -0
- package/source/prompts.ts +172 -0
- package/source/repl-prompt.ts +196 -0
- package/source/repl.ts +572 -0
- package/source/terminal/formatting.ts +121 -0
- package/source/terminal/index.ts +518 -0
- package/source/terminal/markdown-utils.ts +89 -0
- package/source/terminal/markdown.ts +155 -0
- package/source/terminal/types.ts +84 -0
- package/source/terminal-output.test.ts +266 -0
- package/source/token-tracker.ts +78 -0
- package/source/token-utils.ts +17 -0
- package/source/tools/agent.ts +107 -0
- package/source/tools/bash.ts +367 -0
- package/source/tools/code-interpreter.ts +172 -0
- package/source/tools/command-validation.ts +81 -0
- package/source/tools/delete-file.ts +71 -0
- package/source/tools/directory-tree.ts +54 -0
- package/source/tools/edit-file.ts +155 -0
- package/source/tools/filesystem-utils.ts +265 -0
- package/source/tools/git-utils.ts +70 -0
- package/source/tools/grep.ts +184 -0
- package/source/tools/index.ts +278 -0
- package/source/tools/memory-read.ts +174 -0
- package/source/tools/memory-write.ts +105 -0
- package/source/tools/move-file.ts +59 -0
- package/source/tools/read-file.ts +129 -0
- package/source/tools/read-multiple-files.ts +80 -0
- package/source/tools/save-file.ts +147 -0
- package/source/tools/think.ts +51 -0
- package/source/tools/types.ts +58 -0
- package/source/tools/web-fetch.ts +327 -0
- package/source/tools/web-search.ts +101 -0
- package/source/utils/process.ts +121 -0
- package/source/version.ts +21 -0
- package/test/commands/copy-command.test.ts +69 -0
- package/test/config.test.ts +200 -0
- package/test/terminal/markdown-utils.test.ts +124 -0
- package/test/tools/bash-tool.test.ts +58 -0
- package/test/tools/code-interpreter.test.ts +91 -0
- package/test/tools/command-validation.test.ts +48 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { isNumber } from "@travisennis/stdlib/typeguards";
|
|
3
|
+
import { tool } from "ai";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { config } from "../config.js";
|
|
7
|
+
import { joinWorkingDir, validatePath } from "./filesystem-utils.js";
|
|
8
|
+
import { fileEncodingSchema } from "./types.js";
|
|
9
|
+
export const ReadFileTool = {
|
|
10
|
+
name: "readFile",
|
|
11
|
+
};
|
|
12
|
+
export const createReadFileTool = async ({ workingDir, sendData, tokenCounter, }) => {
|
|
13
|
+
const allowedDirectory = workingDir;
|
|
14
|
+
return {
|
|
15
|
+
[ReadFileTool.name]: tool({
|
|
16
|
+
description: "Read the complete contents of a file from the file system unless startLine and lineCount are given to read a file selection. " +
|
|
17
|
+
"Handles various text encodings and provides detailed error messages " +
|
|
18
|
+
"if the file cannot be read. Use this tool when you need to examine " +
|
|
19
|
+
"the contents of a single file. Only works within allowed directories.",
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
path: z.string().describe("Absolute path to file to read"),
|
|
22
|
+
encoding: fileEncodingSchema.describe('Encoding format for reading the file. Use "utf-8" as default for text files'),
|
|
23
|
+
startLine: z
|
|
24
|
+
.number()
|
|
25
|
+
.nullable()
|
|
26
|
+
.describe("1-based line number to start reading from. Pass null to start at beginning of file"),
|
|
27
|
+
lineCount: z
|
|
28
|
+
.number()
|
|
29
|
+
.nullable()
|
|
30
|
+
.describe("Maximum number of lines to read. Pass null to get all lines."),
|
|
31
|
+
}),
|
|
32
|
+
execute: async ({ path: providedPath, encoding, startLine, lineCount, }, { toolCallId }) => {
|
|
33
|
+
sendData?.({
|
|
34
|
+
id: toolCallId,
|
|
35
|
+
event: "tool-init",
|
|
36
|
+
data: `Reading file: ${chalk.cyan(providedPath)}${startLine ? chalk.cyan(`:${startLine}`) : ""}${lineCount ? chalk.cyan(`:${lineCount}`) : ""}`,
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
const filePath = await validatePath(joinWorkingDir(providedPath, workingDir), allowedDirectory);
|
|
40
|
+
let file = await fs.readFile(filePath, { encoding });
|
|
41
|
+
// Apply line-based selection if requested
|
|
42
|
+
if (isNumber(startLine) || isNumber(lineCount)) {
|
|
43
|
+
const lines = file.split("\n");
|
|
44
|
+
const totalLines = lines.length;
|
|
45
|
+
const startIndex = (startLine ?? 1) - 1; // Default to start of file if only lineCount is given
|
|
46
|
+
const count = lineCount ?? totalLines - startIndex; // Default to read all lines from start if only startLine is given
|
|
47
|
+
if (startIndex < 0 || startIndex >= totalLines) {
|
|
48
|
+
return `startLine ${startLine} is out of bounds for file with ${totalLines} lines.`;
|
|
49
|
+
}
|
|
50
|
+
const endIndex = Math.min(startIndex + count, totalLines);
|
|
51
|
+
file = lines.slice(startIndex, endIndex).join("\n");
|
|
52
|
+
}
|
|
53
|
+
let tokenCount = 0;
|
|
54
|
+
try {
|
|
55
|
+
// Only calculate tokens for non-image files and if encoding is text-based
|
|
56
|
+
if (encoding.startsWith("utf")) {
|
|
57
|
+
tokenCount = tokenCounter.count(file);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (tokenError) {
|
|
61
|
+
console.error("Error calculating token count:", tokenError);
|
|
62
|
+
// Log or handle error, but don't block file return
|
|
63
|
+
}
|
|
64
|
+
const maxTokens = (await config.readProjectConfig()).tools.maxTokens;
|
|
65
|
+
// Adjust max token check message if line selection was used
|
|
66
|
+
const maxTokenMessage = isNumber(startLine) || isNumber(lineCount)
|
|
67
|
+
? `Selected file content (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}). Consider adjusting startLine/lineCount or using grepFiles.`
|
|
68
|
+
: `File content (${tokenCount} tokens) exceeds maximum allowed tokens (${maxTokens}). Please use startLine and lineCount parameters to read specific portions of the file, or using grepFiles to search for specific content.`;
|
|
69
|
+
const result = tokenCount <= maxTokens ? file : maxTokenMessage;
|
|
70
|
+
sendData?.({
|
|
71
|
+
id: toolCallId,
|
|
72
|
+
event: "tool-completion",
|
|
73
|
+
// Include token count only if calculated (i.e., for text files)
|
|
74
|
+
data: tokenCount <= maxTokens
|
|
75
|
+
? `File read successfully ${tokenCount > 0 ? ` (${tokenCount} tokens)` : ""}`
|
|
76
|
+
: result,
|
|
77
|
+
});
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return `Failed to read file: ${error.message}`;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
}),
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TokenCounter } from "../token-utils.ts";
|
|
2
|
+
import type { SendData } from "./types.ts";
|
|
3
|
+
export declare const ReadMultipleFilesTool: {
|
|
4
|
+
name: "readMultipleFiles";
|
|
5
|
+
};
|
|
6
|
+
export declare const createReadMultipleFilesTool: ({ workingDir, sendData, tokenCounter, }: {
|
|
7
|
+
workingDir: string;
|
|
8
|
+
sendData?: SendData;
|
|
9
|
+
tokenCounter: TokenCounter;
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
readMultipleFiles: import("ai").Tool<{
|
|
12
|
+
paths: string[];
|
|
13
|
+
}, string>;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { config } from "../config.js";
|
|
5
|
+
import { readFileAndCountTokens } from "./filesystem-utils.js";
|
|
6
|
+
export const ReadMultipleFilesTool = {
|
|
7
|
+
name: "readMultipleFiles",
|
|
8
|
+
};
|
|
9
|
+
export const createReadMultipleFilesTool = async ({ workingDir, sendData, tokenCounter, }) => {
|
|
10
|
+
const allowedDirectory = workingDir;
|
|
11
|
+
return {
|
|
12
|
+
[ReadMultipleFilesTool.name]: tool({
|
|
13
|
+
description: "Read the contents of multiple files simultaneously. This is more " +
|
|
14
|
+
"efficient than reading files one by one when you need to analyze " +
|
|
15
|
+
"or compare multiple files. Each file's content is returned with its " +
|
|
16
|
+
"path as a reference. Failed reads for individual files won't stop " +
|
|
17
|
+
"the entire operation. Only works within allowed directories.",
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
paths: z.array(z.string()),
|
|
20
|
+
}),
|
|
21
|
+
execute: async ({ paths }, { toolCallId }) => {
|
|
22
|
+
sendData?.({
|
|
23
|
+
id: toolCallId,
|
|
24
|
+
event: "tool-init",
|
|
25
|
+
data: `Reading files: ${paths.map((p) => chalk.cyan(p)).join(", ")}`,
|
|
26
|
+
});
|
|
27
|
+
const maxTokens = (await config.readProjectConfig()).tools.maxTokens;
|
|
28
|
+
const results = await Promise.all(paths.map((filePath) => readFileAndCountTokens(filePath, workingDir, allowedDirectory, tokenCounter, maxTokens)));
|
|
29
|
+
let totalTokens = 0;
|
|
30
|
+
let filesReadCount = 0;
|
|
31
|
+
const formattedResults = results.map((result) => {
|
|
32
|
+
if (result.error) {
|
|
33
|
+
return `${result.path}: Error - ${result.error}`;
|
|
34
|
+
}
|
|
35
|
+
// Check if tokenCount is > 0, meaning it wasn't skipped
|
|
36
|
+
if (result.tokenCount > 0) {
|
|
37
|
+
filesReadCount++;
|
|
38
|
+
}
|
|
39
|
+
totalTokens += result.tokenCount; // Add the token count (will be 0 for skipped files)
|
|
40
|
+
// Return content (or max token message)
|
|
41
|
+
return `${result.path}:\n${result.content}\n`;
|
|
42
|
+
});
|
|
43
|
+
const completionMessage = filesReadCount === paths.length
|
|
44
|
+
? `Read ${paths.length} files successfully (${totalTokens} total tokens).`
|
|
45
|
+
: `Read ${filesReadCount} of ${paths.length} files successfully (${totalTokens} total tokens). Files exceeding token limit were skipped.`;
|
|
46
|
+
sendData?.({
|
|
47
|
+
id: toolCallId,
|
|
48
|
+
event: "tool-completion",
|
|
49
|
+
data: completionMessage,
|
|
50
|
+
});
|
|
51
|
+
return formattedResults.join("\n---\n");
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Terminal } from "../terminal/index.ts";
|
|
2
|
+
import { type SendData } from "./types.ts";
|
|
3
|
+
export declare const SaveFileTool: {
|
|
4
|
+
name: "saveFile";
|
|
5
|
+
};
|
|
6
|
+
export declare const createSaveFileTool: ({ workingDir, sendData, terminal, autoAcceptAll, }: {
|
|
7
|
+
workingDir: string;
|
|
8
|
+
sendData?: SendData;
|
|
9
|
+
terminal?: Terminal;
|
|
10
|
+
autoAcceptAll?: boolean;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
saveFile: import("ai").Tool<{
|
|
13
|
+
path: string;
|
|
14
|
+
encoding: "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "base64url" | "latin1" | "binary" | "hex";
|
|
15
|
+
content: string;
|
|
16
|
+
}, string>;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { input, select } from "@inquirer/prompts";
|
|
4
|
+
import { tool } from "ai";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { joinWorkingDir, validatePath } from "./filesystem-utils.js";
|
|
8
|
+
import { fileEncodingSchema } from "./types.js";
|
|
9
|
+
export const SaveFileTool = {
|
|
10
|
+
name: "saveFile",
|
|
11
|
+
};
|
|
12
|
+
export const createSaveFileTool = async ({ workingDir, sendData, terminal, autoAcceptAll, }) => {
|
|
13
|
+
const allowedDirectory = workingDir;
|
|
14
|
+
let autoAcceptSaves = autoAcceptAll ?? false;
|
|
15
|
+
return {
|
|
16
|
+
[SaveFileTool.name]: tool({
|
|
17
|
+
description: "Create a new file or completely overwrite an existing file with new content. " +
|
|
18
|
+
"Use with caution as it will overwrite existing files without warning. " +
|
|
19
|
+
"Handles text content with proper encoding. Only works within allowed directories.",
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
path: z.string().describe("Absolute path to file to save to"),
|
|
22
|
+
content: z.string().describe("Content to save in the file"),
|
|
23
|
+
encoding: fileEncodingSchema.describe('Encoding format for saving the file. Use "utf-8" as default for text files'),
|
|
24
|
+
}),
|
|
25
|
+
execute: async ({ path: userPath, content, encoding, }, { toolCallId }) => {
|
|
26
|
+
sendData?.({
|
|
27
|
+
id: toolCallId,
|
|
28
|
+
event: "tool-init",
|
|
29
|
+
data: `Saving file: ${chalk.cyan(userPath)}`,
|
|
30
|
+
});
|
|
31
|
+
try {
|
|
32
|
+
const filePath = await validatePath(joinWorkingDir(userPath, workingDir), allowedDirectory);
|
|
33
|
+
if (terminal) {
|
|
34
|
+
terminal.writeln(`\n${chalk.blue.bold("●")} Proposing file save: ${chalk.cyan(userPath)}`);
|
|
35
|
+
terminal.lineBreak();
|
|
36
|
+
terminal.writeln("Proposed file content:");
|
|
37
|
+
terminal.lineBreak();
|
|
38
|
+
terminal.display(content);
|
|
39
|
+
terminal.lineBreak();
|
|
40
|
+
let userChoice;
|
|
41
|
+
if (autoAcceptSaves) {
|
|
42
|
+
terminal.writeln(chalk.green("✓ Auto-accepting saves (all future saves will be accepted)"));
|
|
43
|
+
userChoice = "accept";
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
userChoice = await select({
|
|
47
|
+
message: "What would you like to do with this file?",
|
|
48
|
+
choices: [
|
|
49
|
+
{ name: "Accept and save this file", value: "accept" },
|
|
50
|
+
{
|
|
51
|
+
name: "Accept all future saves (including this)",
|
|
52
|
+
value: "accept-all",
|
|
53
|
+
},
|
|
54
|
+
{ name: "Reject this save", value: "reject" },
|
|
55
|
+
],
|
|
56
|
+
default: "accept",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
terminal.lineBreak();
|
|
60
|
+
if (userChoice === "accept-all") {
|
|
61
|
+
autoAcceptSaves = true;
|
|
62
|
+
terminal.writeln(chalk.yellow("✓ Auto-accept mode enabled for all future saves"));
|
|
63
|
+
terminal.lineBreak();
|
|
64
|
+
}
|
|
65
|
+
if (userChoice === "reject") {
|
|
66
|
+
const reason = await input({ message: "Feedback: " });
|
|
67
|
+
terminal.lineBreak();
|
|
68
|
+
sendData?.({
|
|
69
|
+
id: toolCallId,
|
|
70
|
+
event: "tool-completion",
|
|
71
|
+
data: `Save rejected by user. Reason: ${reason}`,
|
|
72
|
+
});
|
|
73
|
+
return `The user rejected this save. Reason: ${reason}`;
|
|
74
|
+
}
|
|
75
|
+
// If accepted, proceed to write file
|
|
76
|
+
}
|
|
77
|
+
// Ensure parent directory exists
|
|
78
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
79
|
+
await fs.writeFile(filePath, content, { encoding });
|
|
80
|
+
sendData?.({
|
|
81
|
+
id: toolCallId,
|
|
82
|
+
event: "tool-completion",
|
|
83
|
+
data: `File saved successfully: ${userPath}`,
|
|
84
|
+
});
|
|
85
|
+
return `File saved successfully: ${filePath}`;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
sendData?.({
|
|
89
|
+
id: toolCallId,
|
|
90
|
+
event: "tool-error",
|
|
91
|
+
data: `Failed to save file: ${error.message}`,
|
|
92
|
+
});
|
|
93
|
+
return `Failed to save file: ${error.message}`;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SendData } from "./types.ts";
|
|
2
|
+
export declare const ThinkTool: {
|
|
3
|
+
name: "think";
|
|
4
|
+
};
|
|
5
|
+
export declare const createThinkTool: (options?: {
|
|
6
|
+
sendData?: SendData | undefined;
|
|
7
|
+
}) => {
|
|
8
|
+
think: import("ai").Tool<{
|
|
9
|
+
thought: string;
|
|
10
|
+
}, string>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const ThinkTool = {
|
|
4
|
+
name: "think",
|
|
5
|
+
};
|
|
6
|
+
const toolDescription = `Use the tool to think about something. It will not obtain new information or make any changes to the repository, but just log the thought. Use it when complex reasoning or brainstorming is needed.
|
|
7
|
+
Common use cases:
|
|
8
|
+
1. When exploring a repository and discovering the source of a bug, call this tool to brainstorm several unique ways of fixing the bug, and assess which change(s) are likely to be simplest and most effective
|
|
9
|
+
2. After receiving test results, use this tool to brainstorm ways to fix failing tests
|
|
10
|
+
3. When planning a complex refactoring, use this tool to outline different approaches and their tradeoffs
|
|
11
|
+
4. When designing a new feature, use this tool to think through architecture decisions and implementation details
|
|
12
|
+
5. When debugging a complex issue, use this tool to organize your thoughts and hypotheses
|
|
13
|
+
The tool simply logs your thought process for better transparency and does not execute any code or make changes.`;
|
|
14
|
+
// This is a no-op tool that logs a thought. It is inspired by the tau-bench think tool.
|
|
15
|
+
export const createThinkTool = (options = {}) => {
|
|
16
|
+
const { sendData } = options;
|
|
17
|
+
return {
|
|
18
|
+
[ThinkTool.name]: tool({
|
|
19
|
+
description: toolDescription,
|
|
20
|
+
inputSchema: z.object({
|
|
21
|
+
thought: z.string().describe("Your thought"),
|
|
22
|
+
}),
|
|
23
|
+
execute: ({ thought }, { toolCallId }) => {
|
|
24
|
+
// Replace literal '\\n' with actual newline characters
|
|
25
|
+
const formattedThought = thought.replace(/\\n/g, "\n");
|
|
26
|
+
sendData?.({
|
|
27
|
+
event: "tool-init",
|
|
28
|
+
id: toolCallId,
|
|
29
|
+
data: "Logging Thought",
|
|
30
|
+
});
|
|
31
|
+
sendData?.({
|
|
32
|
+
event: "tool-update",
|
|
33
|
+
id: toolCallId,
|
|
34
|
+
data: { primary: "Thought:", secondary: [formattedThought] },
|
|
35
|
+
});
|
|
36
|
+
sendData?.({
|
|
37
|
+
event: "tool-completion",
|
|
38
|
+
id: toolCallId,
|
|
39
|
+
data: "Done",
|
|
40
|
+
});
|
|
41
|
+
return Promise.resolve("Your thought has been logged.");
|
|
42
|
+
},
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const fileEncodingSchema: z.ZodEnum<["ascii", "utf8", "utf-8", "utf16le", "ucs2", "ucs-2", "base64", "base64url", "latin1", "binary", "hex"]>;
|
|
3
|
+
interface MessageData {
|
|
4
|
+
primary: string;
|
|
5
|
+
secondary?: string[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
interface BaseMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
retry?: number;
|
|
10
|
+
}
|
|
11
|
+
interface ToolInitMessage extends BaseMessage {
|
|
12
|
+
event: "tool-init";
|
|
13
|
+
data: string;
|
|
14
|
+
}
|
|
15
|
+
interface ToolErrorMessage extends BaseMessage {
|
|
16
|
+
event: "tool-error";
|
|
17
|
+
data: string;
|
|
18
|
+
}
|
|
19
|
+
interface ToolCompletionMessage extends BaseMessage {
|
|
20
|
+
event: "tool-completion";
|
|
21
|
+
data: string;
|
|
22
|
+
}
|
|
23
|
+
interface ToolUpdateMessage extends BaseMessage {
|
|
24
|
+
event: "tool-update";
|
|
25
|
+
data: MessageData;
|
|
26
|
+
}
|
|
27
|
+
export type Message = ToolInitMessage | ToolErrorMessage | ToolCompletionMessage | ToolUpdateMessage;
|
|
28
|
+
export type SendData = ({ data, event, id, retry, }: Message) => void | Promise<void>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { TokenCounter } from "../token-utils.ts";
|
|
2
|
+
import type { SendData } from "./types.ts";
|
|
3
|
+
export declare const WebFetchTool: {
|
|
4
|
+
name: "webFetch";
|
|
5
|
+
};
|
|
6
|
+
export declare const createWebFetchTool: (options: {
|
|
7
|
+
sendData?: SendData | undefined;
|
|
8
|
+
tokenCounter: TokenCounter;
|
|
9
|
+
}) => {
|
|
10
|
+
webFetch: import("ai").Tool<{
|
|
11
|
+
url: string;
|
|
12
|
+
}, string>;
|
|
13
|
+
};
|
|
14
|
+
export type ContentType = "text/plain" | "text/html" | "text/markdown" | "application/json" | "application/xml" | "application/pdf" | "image/png" | "image/jpeg" | "image/gif" | "image/webp" | "image/svg+xml" | "audio/mpeg" | "audio/wav" | "video/mp4" | "video/webm" | "application/zip" | "application/octet-stream";
|
|
15
|
+
export type ReadUrlResult = {
|
|
16
|
+
contentType: ContentType;
|
|
17
|
+
data: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function readUrl(url: string, abortSignal?: AbortSignal | undefined): Promise<ReadUrlResult>;
|
|
20
|
+
export declare class HtmlCleaner {
|
|
21
|
+
static new(html: string): HtmlCleaner;
|
|
22
|
+
private html;
|
|
23
|
+
private constructor();
|
|
24
|
+
/**
|
|
25
|
+
* Cleans HTML content by removing unnecessary elements and simplifying structure
|
|
26
|
+
* @param {Object} [options] - Configuration options for cleaning
|
|
27
|
+
* @param {boolean} [options.simplify=true] - Whether to simplify HTML structure by removing redundant elements
|
|
28
|
+
* @param {boolean} [options.empty=true] - Whether to remove empty elements from the HTML
|
|
29
|
+
* @returns {string} Cleaned HTML content with removed whitespace and line breaks
|
|
30
|
+
*/
|
|
31
|
+
clean(options?: {
|
|
32
|
+
simplify?: boolean;
|
|
33
|
+
empty?: boolean;
|
|
34
|
+
}): string;
|
|
35
|
+
/**
|
|
36
|
+
* Removes scripts, styles, and comments from HTML
|
|
37
|
+
*/
|
|
38
|
+
private removeUnnecessaryElements;
|
|
39
|
+
/**
|
|
40
|
+
* Simplifies HTML structure by merging redundant tags
|
|
41
|
+
*/
|
|
42
|
+
private simplifyStructure;
|
|
43
|
+
/**
|
|
44
|
+
* Removes empty elements from HTML
|
|
45
|
+
*/
|
|
46
|
+
private removeEmptyElements;
|
|
47
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { load } from "cheerio";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
6
|
+
export const WebFetchTool = {
|
|
7
|
+
name: "webFetch",
|
|
8
|
+
};
|
|
9
|
+
export const createWebFetchTool = (options) => {
|
|
10
|
+
const { sendData } = options;
|
|
11
|
+
return {
|
|
12
|
+
[WebFetchTool.name]: tool({
|
|
13
|
+
description: "Fetches the content of a given URL. It intelligently handles HTML content by attempting to use a specialized service for cleaner extraction, falling back to local cleaning if needed. For non-HTML content (like plain text or markdown), it fetches the raw content directly. IMPORTANT: Does not retrieve binary files.",
|
|
14
|
+
inputSchema: z.object({
|
|
15
|
+
url: z.string().describe("The URL to fetch content from."),
|
|
16
|
+
}),
|
|
17
|
+
execute: async ({ url }, { toolCallId, abortSignal }) => {
|
|
18
|
+
try {
|
|
19
|
+
sendData?.({
|
|
20
|
+
event: "tool-init",
|
|
21
|
+
id: toolCallId,
|
|
22
|
+
data: `Reading URL: ${chalk.cyan(url)}`,
|
|
23
|
+
});
|
|
24
|
+
logger.info(`Initiating fetch for URL: ${url}`);
|
|
25
|
+
const result = await readUrl(url, abortSignal);
|
|
26
|
+
const urlContent = result.data;
|
|
27
|
+
const tokenCount = options.tokenCounter.count(urlContent);
|
|
28
|
+
sendData?.({
|
|
29
|
+
event: "tool-completion",
|
|
30
|
+
id: toolCallId,
|
|
31
|
+
data: `Read URL successfully (${tokenCount} tokens)`,
|
|
32
|
+
});
|
|
33
|
+
logger.info(`Successfully read URL: ${url} (${tokenCount} tokens)`);
|
|
34
|
+
return urlContent;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const errorMessage = error.message;
|
|
38
|
+
sendData?.({
|
|
39
|
+
event: "tool-error",
|
|
40
|
+
id: toolCallId,
|
|
41
|
+
data: `Error reading URL ${url}: ${errorMessage}`,
|
|
42
|
+
});
|
|
43
|
+
logger.error(`Error reading URL ${url}: ${errorMessage}`);
|
|
44
|
+
// Return the error message so the LLM knows the tool failed.
|
|
45
|
+
return `Failed to read URL: ${errorMessage}`;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export async function readUrl(url, abortSignal) {
|
|
52
|
+
let initialResponse;
|
|
53
|
+
try {
|
|
54
|
+
// Initial fetch to check content type and potentially use directly
|
|
55
|
+
logger.debug(`Performing initial fetch for: ${url}`);
|
|
56
|
+
initialResponse = await fetch(url, { signal: abortSignal });
|
|
57
|
+
if (!initialResponse.ok) {
|
|
58
|
+
throw new Error(`HTTP error! status: ${initialResponse.status} ${initialResponse.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
logger.debug(`Initial fetch successful for: ${url}, Status: ${initialResponse.status}`);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
// If the initial fetch fails entirely, rethrow
|
|
64
|
+
logger.error(`Initial fetch failed for ${url}: ${error}`);
|
|
65
|
+
throw new Error(`Error fetching initial data for ${url}: ${error}`);
|
|
66
|
+
}
|
|
67
|
+
const contentType = initialResponse.headers.get("content-type") ??
|
|
68
|
+
"text/plain";
|
|
69
|
+
logger.debug(`Content-Type for ${url}: ${contentType}`);
|
|
70
|
+
// If content type is HTML, try Jina first
|
|
71
|
+
if (contentType.includes("text/html")) {
|
|
72
|
+
logger.info(`Detected HTML content for ${url}. Attempting Jina AI fetch.`);
|
|
73
|
+
try {
|
|
74
|
+
const apiKey = process.env["JINA_READER_API_KEY"];
|
|
75
|
+
if (!apiKey) {
|
|
76
|
+
logger.warn("JINA_READER_API_KEY not set. Skipping Jina fetch.");
|
|
77
|
+
throw new Error("Jina API key not available"); // Skip to fallback
|
|
78
|
+
}
|
|
79
|
+
const jinaReadUrl = `https://r.jina.ai/${url}`;
|
|
80
|
+
logger.debug(`Fetching with Jina: ${jinaReadUrl}`);
|
|
81
|
+
const jinaResponse = await fetch(jinaReadUrl, {
|
|
82
|
+
method: "GET",
|
|
83
|
+
headers: {
|
|
84
|
+
// biome-ignore lint/style/useNamingConvention: API requirement
|
|
85
|
+
Authorization: `Bearer ${apiKey}`,
|
|
86
|
+
"X-With-Generated-Alt": "true", // Optional: Ask Jina to include image descriptions
|
|
87
|
+
"X-With-Links-Summary": "true", // Optional: Ask Jina for a summary of links
|
|
88
|
+
},
|
|
89
|
+
signal: abortSignal,
|
|
90
|
+
});
|
|
91
|
+
if (jinaResponse.ok) {
|
|
92
|
+
const data = await jinaResponse.text();
|
|
93
|
+
logger.info(`Successfully fetched and processed HTML URL with Jina: ${url}`);
|
|
94
|
+
return {
|
|
95
|
+
contentType,
|
|
96
|
+
data,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
logger.warn(`Jina fetch failed for ${url} with status ${jinaResponse.status}: ${jinaResponse.statusText}. Falling back to direct fetch and clean.`);
|
|
100
|
+
// Fall through to use the initialResponse if Jina fails
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger.warn(`Error fetching from Jina for ${url}: ${error.message}. Falling back to direct fetch and clean.`);
|
|
104
|
+
// Fall through to use the initialResponse if Jina fails
|
|
105
|
+
}
|
|
106
|
+
// Fallback for HTML: Use the initial response and clean it
|
|
107
|
+
try {
|
|
108
|
+
logger.warn(`Falling back to direct fetch and cleaning for HTML URL: ${url}`);
|
|
109
|
+
const htmlText = await initialResponse.text();
|
|
110
|
+
logger.debug(`Cleaning HTML content for ${url} (length: ${htmlText.length})`);
|
|
111
|
+
const cleaner = HtmlCleaner.new(htmlText);
|
|
112
|
+
const processedText = cleaner.clean();
|
|
113
|
+
logger.info(`Successfully cleaned HTML content for ${url} (length: ${processedText.length})`);
|
|
114
|
+
return {
|
|
115
|
+
contentType,
|
|
116
|
+
data: processedText,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (cleanError) {
|
|
120
|
+
logger.error(`Error cleaning HTML from fallback fetch for ${url}: ${cleanError}`);
|
|
121
|
+
throw new Error(`Error cleaning HTML from fallback fetch for ${url}: ${cleanError}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// If not HTML, return the text directly from the initial response
|
|
126
|
+
logger.info(`Fetched non-HTML content directly: ${url} (Content-Type: ${contentType})`);
|
|
127
|
+
try {
|
|
128
|
+
if (contentType.startsWith("image/")) {
|
|
129
|
+
const arrayBuffer = await initialResponse.arrayBuffer();
|
|
130
|
+
const base64 = Buffer.from(arrayBuffer).toString("base64");
|
|
131
|
+
const base64Url = `data:${contentType};base64,${base64}`;
|
|
132
|
+
logger.debug(`Returning base64 image data for ${url} (length: ${base64.length})`);
|
|
133
|
+
return {
|
|
134
|
+
contentType,
|
|
135
|
+
data: base64Url,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const textContent = await initialResponse.text();
|
|
139
|
+
logger.debug(`Returning raw text content for ${url} (length: ${textContent.length})`);
|
|
140
|
+
return {
|
|
141
|
+
contentType,
|
|
142
|
+
data: textContent,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (textError) {
|
|
146
|
+
logger.error(`Error reading response for ${url}: ${textError}`);
|
|
147
|
+
throw new Error(`Error reading response for ${url}: ${textError}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export class HtmlCleaner {
|
|
152
|
+
static new(html) {
|
|
153
|
+
return new HtmlCleaner(html);
|
|
154
|
+
}
|
|
155
|
+
html;
|
|
156
|
+
constructor(html) {
|
|
157
|
+
this.html = html;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Cleans HTML content by removing unnecessary elements and simplifying structure
|
|
161
|
+
* @param {Object} [options] - Configuration options for cleaning
|
|
162
|
+
* @param {boolean} [options.simplify=true] - Whether to simplify HTML structure by removing redundant elements
|
|
163
|
+
* @param {boolean} [options.empty=true] - Whether to remove empty elements from the HTML
|
|
164
|
+
* @returns {string} Cleaned HTML content with removed whitespace and line breaks
|
|
165
|
+
*/
|
|
166
|
+
clean(options) {
|
|
167
|
+
const { simplify = true, empty = true } = options ?? {};
|
|
168
|
+
const $ = load(this.html);
|
|
169
|
+
// Remove scripts, styles, and comments
|
|
170
|
+
this.removeUnnecessaryElements($);
|
|
171
|
+
// Simplify HTML structure
|
|
172
|
+
if (simplify) {
|
|
173
|
+
this.simplifyStructure($);
|
|
174
|
+
}
|
|
175
|
+
// Remove empty elements
|
|
176
|
+
if (empty) {
|
|
177
|
+
this.removeEmptyElements($);
|
|
178
|
+
}
|
|
179
|
+
// Get cleaned HTML
|
|
180
|
+
return $.html()
|
|
181
|
+
.trim()
|
|
182
|
+
.replace(/^\s*[\r\n]/gm, "");
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Removes scripts, styles, and comments from HTML
|
|
186
|
+
*/
|
|
187
|
+
removeUnnecessaryElements($) {
|
|
188
|
+
// Remove all script tags
|
|
189
|
+
$("script").remove();
|
|
190
|
+
// Remove all noscript tags
|
|
191
|
+
$("noscript").remove();
|
|
192
|
+
// Remove all style tags
|
|
193
|
+
$("style").remove();
|
|
194
|
+
// Remove all link tags (external stylesheets)
|
|
195
|
+
$('link[rel="stylesheet"]').remove();
|
|
196
|
+
// Remove all preload link tags
|
|
197
|
+
$('link[rel="preload"]').remove();
|
|
198
|
+
// Remove all link tags
|
|
199
|
+
$("link").remove();
|
|
200
|
+
// Remove all forms
|
|
201
|
+
$("form").remove();
|
|
202
|
+
// Remove comments
|
|
203
|
+
$("*")
|
|
204
|
+
.contents()
|
|
205
|
+
.each((_, element) => {
|
|
206
|
+
if (element.type === "comment") {
|
|
207
|
+
$(element).remove();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Remove all inline styles
|
|
211
|
+
$("[style]").removeAttr("style");
|
|
212
|
+
// Remove all class attributes
|
|
213
|
+
$("[class]").removeAttr("class");
|
|
214
|
+
// Remove all id attributes
|
|
215
|
+
$("[id]").removeAttr("id");
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Simplifies HTML structure by merging redundant tags
|
|
219
|
+
*/
|
|
220
|
+
simplifyStructure($) {
|
|
221
|
+
// Merge nested divs
|
|
222
|
+
$("div div").each((_, element) => {
|
|
223
|
+
const $element = $(element);
|
|
224
|
+
const parent = $element.parent();
|
|
225
|
+
if (parent.children().length === 1 && parent.get(0)?.tagName === "div") {
|
|
226
|
+
$element.unwrap();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
// Remove redundant spans
|
|
230
|
+
$("span").each((_, element) => {
|
|
231
|
+
const $element = $(element);
|
|
232
|
+
if (!$element.attr() || Object.keys($element.attr() ?? {}).length === 0) {
|
|
233
|
+
const h = $element.html();
|
|
234
|
+
if (h) {
|
|
235
|
+
$element.replaceWith(h);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Removes empty elements from HTML
|
|
242
|
+
*/
|
|
243
|
+
removeEmptyElements($) {
|
|
244
|
+
$(":empty").remove();
|
|
245
|
+
}
|
|
246
|
+
}
|