explorbot 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -26
- package/bin/explorbot-cli.ts +679 -0
- package/boat/api-tester/src/ai/chief/styles.ts +15 -0
- package/boat/api-tester/src/ai/chief.ts +335 -0
- package/boat/api-tester/src/ai/curler-tools.ts +278 -0
- package/boat/api-tester/src/ai/curler.ts +306 -0
- package/boat/api-tester/src/api-client.ts +28 -0
- package/boat/api-tester/src/apibot.ts +203 -0
- package/boat/api-tester/src/cli.ts +301 -0
- package/boat/api-tester/src/config.ts +190 -0
- package/dist/bin/explorbot-cli.js +19 -98
- package/dist/boat/api-tester/bin/apibot-cli.js +0 -1
- package/dist/boat/api-tester/src/ai/chief/styles.js +0 -1
- package/dist/boat/api-tester/src/ai/chief.js +0 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js +0 -1
- package/dist/boat/api-tester/src/ai/curler.js +0 -1
- package/dist/boat/api-tester/src/api-client.js +0 -1
- package/dist/boat/api-tester/src/apibot.js +0 -1
- package/dist/boat/api-tester/src/cli.js +0 -1
- package/dist/boat/api-tester/src/config.js +0 -1
- package/dist/src/action-result.js +0 -1
- package/dist/src/action.js +0 -1
- package/dist/src/activity.js +0 -1
- package/dist/src/ai/agent.js +0 -1
- package/dist/src/ai/bosun.js +0 -1
- package/dist/src/ai/captain/idle-mode.js +0 -1
- package/dist/src/ai/captain/mixin.js +0 -1
- package/dist/src/ai/captain/test-mode.js +0 -1
- package/dist/src/ai/captain/web-mode.js +0 -1
- package/dist/src/ai/captain.js +0 -1
- package/dist/src/ai/conversation.js +0 -1
- package/dist/src/ai/experience-compactor.js +0 -1
- package/dist/src/ai/fisherman-tools.js +0 -1
- package/dist/src/ai/fisherman.js +0 -1
- package/dist/src/ai/historian.js +0 -1
- package/dist/src/ai/navigator.js +0 -1
- package/dist/src/ai/pilot.js +0 -1
- package/dist/src/ai/planner/session-dedup.js +0 -1
- package/dist/src/ai/planner/styles.js +0 -1
- package/dist/src/ai/planner/subpages.js +0 -1
- package/dist/src/ai/planner.js +0 -1
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.js +0 -1
- package/dist/src/ai/researcher/cache.js +0 -1
- package/dist/src/ai/researcher/coordinates.js +0 -1
- package/dist/src/ai/researcher/deep-analysis.js +0 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +0 -1
- package/dist/src/ai/researcher/focus.js +0 -1
- package/dist/src/ai/researcher/locators.js +0 -1
- package/dist/src/ai/researcher/mixin.js +0 -1
- package/dist/src/ai/researcher/parser.js +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -1
- package/dist/src/ai/researcher.js +0 -1
- package/dist/src/ai/rules.js +0 -1
- package/dist/src/ai/task-agent.js +0 -1
- package/dist/src/ai/tester.js +0 -1
- package/dist/src/ai/tools.js +0 -1
- package/dist/src/api/api-client.js +0 -1
- package/dist/src/api/request-result.js +0 -1
- package/dist/src/api/request-store.js +0 -1
- package/dist/src/api/spec-reader.js +0 -1
- package/dist/src/api/xhr-capture.js +0 -1
- package/dist/src/browser-server.js +0 -1
- package/dist/src/command-handler.js +0 -1
- package/dist/src/commands/add-rule-command.js +0 -1
- package/dist/src/commands/base-command.js +0 -1
- package/dist/src/commands/clean-command.js +0 -1
- package/dist/src/commands/context-aria-command.js +0 -1
- package/dist/src/commands/context-command.js +0 -1
- package/dist/src/commands/context-data-command.js +0 -1
- package/dist/src/commands/context-experience-command.js +0 -1
- package/dist/src/commands/context-html-command.js +0 -1
- package/dist/src/commands/context-knowledge-command.js +0 -1
- package/dist/src/commands/debug-command.js +0 -1
- package/dist/src/commands/drill-command.js +0 -1
- package/dist/src/commands/exit-command.js +0 -1
- package/dist/src/commands/explore-command.js +2 -2
- package/dist/src/commands/freesail-command.js +0 -1
- package/dist/src/commands/help-command.js +0 -1
- package/dist/src/commands/index.js +0 -1
- package/dist/src/commands/init-command.js +115 -0
- package/dist/src/commands/knows-command.js +0 -1
- package/dist/src/commands/learn-command.js +0 -1
- package/dist/src/commands/navigate-command.js +0 -1
- package/dist/src/commands/path-command.js +0 -1
- package/dist/src/commands/plan-clear-command.js +0 -1
- package/dist/src/commands/plan-command.js +0 -1
- package/dist/src/commands/plan-edit-command.js +0 -1
- package/dist/src/commands/plan-load-command.js +0 -1
- package/dist/src/commands/plan-reload-command.js +0 -1
- package/dist/src/commands/plan-save-command.js +0 -1
- package/dist/src/commands/research-command.js +0 -1
- package/dist/src/commands/start-command.js +0 -1
- package/dist/src/commands/status-command.js +0 -1
- package/dist/src/commands/test-command.js +0 -1
- package/dist/src/components/ActivityPane.js +0 -1
- package/dist/src/components/AddKnowledge.js +0 -1
- package/dist/src/components/AddRule.js +0 -1
- package/dist/src/components/App.js +0 -1
- package/dist/src/components/Autocomplete.js +0 -1
- package/dist/src/components/InputPane.js +0 -1
- package/dist/src/components/InputReadline.js +0 -1
- package/dist/src/components/LogPane.js +0 -1
- package/dist/src/components/PlanEditor.js +0 -1
- package/dist/src/components/PlanPane.js +0 -1
- package/dist/src/components/SessionTimer.js +0 -1
- package/dist/src/components/StateTransitionPane.js +0 -1
- package/dist/src/components/StatusPane.js +0 -1
- package/dist/src/components/TaskPane.js +0 -1
- package/dist/src/components/Welcome.js +0 -1
- package/dist/src/components/WelcomeChecklist.js +0 -1
- package/dist/src/components/WelcomeCommands.js +0 -1
- package/dist/src/components/autocomplete-store.js +0 -1
- package/dist/src/components/parse-keypress.js +0 -1
- package/dist/src/config.js +0 -1
- package/dist/src/execution-controller.js +0 -1
- package/dist/src/experience-tracker.js +0 -1
- package/dist/src/explorbot.js +0 -1
- package/dist/src/explorer.js +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/knowledge-tracker.js +2 -2
- package/dist/src/observability.js +0 -1
- package/dist/src/reporter.js +0 -1
- package/dist/src/state-manager.js +0 -1
- package/dist/src/stats.js +0 -1
- package/dist/src/test-plan.js +0 -1
- package/dist/src/utils/aria.js +0 -1
- package/dist/src/utils/cli-name.js +16 -0
- package/dist/src/utils/code-extractor.js +0 -1
- package/dist/src/utils/context-formatter.js +0 -1
- package/dist/src/utils/error-page.js +0 -1
- package/dist/src/utils/expandable.js +0 -1
- package/dist/src/utils/hooks-runner.js +0 -1
- package/dist/src/utils/html-diff.js +0 -1
- package/dist/src/utils/html.js +0 -1
- package/dist/src/utils/logger.js +0 -1
- package/dist/src/utils/loop.js +0 -1
- package/dist/src/utils/markdown-parser.js +0 -1
- package/dist/src/utils/markdown-query.js +0 -1
- package/dist/src/utils/markdown-terminal.js +0 -1
- package/dist/src/utils/research-parser.js +0 -1
- package/dist/src/utils/retry.js +0 -1
- package/dist/src/utils/rules-loader.js +0 -1
- package/dist/src/utils/strings.js +0 -1
- package/dist/src/utils/test-plan-markdown.js +0 -1
- package/dist/src/utils/throttle.js +0 -1
- package/dist/src/utils/unique-names.js +0 -1
- package/dist/src/utils/url-matcher.js +0 -1
- package/dist/src/utils/web-element.js +0 -1
- package/dist/src/utils/xpath.js +0 -1
- package/package.json +27 -3
- package/src/action-result.ts +694 -0
- package/src/action.ts +445 -0
- package/src/activity.ts +111 -0
- package/src/ai/agent.ts +3 -0
- package/src/ai/bosun.ts +557 -0
- package/src/ai/captain/idle-mode.ts +116 -0
- package/src/ai/captain/mixin.ts +22 -0
- package/src/ai/captain/test-mode.ts +262 -0
- package/src/ai/captain/web-mode.ts +136 -0
- package/src/ai/captain.ts +504 -0
- package/src/ai/conversation.ts +205 -0
- package/src/ai/experience-compactor.ts +284 -0
- package/src/ai/fisherman-tools.ts +181 -0
- package/src/ai/fisherman.ts +223 -0
- package/src/ai/historian.ts +457 -0
- package/src/ai/navigator.ts +572 -0
- package/src/ai/pilot.ts +776 -0
- package/src/ai/planner/session-dedup.ts +35 -0
- package/src/ai/planner/styles.ts +17 -0
- package/src/ai/planner/subpages.ts +141 -0
- package/src/ai/planner.ts +536 -0
- package/src/ai/provider.ts +613 -0
- package/src/ai/quartermaster.ts +286 -0
- package/src/ai/researcher/cache.ts +103 -0
- package/src/ai/researcher/coordinates.ts +238 -0
- package/src/ai/researcher/deep-analysis.ts +415 -0
- package/src/ai/researcher/fingerprint-worker.ts +59 -0
- package/src/ai/researcher/focus.ts +42 -0
- package/src/ai/researcher/locators.ts +282 -0
- package/src/ai/researcher/mixin.ts +4 -0
- package/src/ai/researcher/parser.ts +186 -0
- package/src/ai/researcher/research-result.ts +115 -0
- package/src/ai/researcher.ts +857 -0
- package/src/ai/rules.ts +376 -0
- package/src/ai/task-agent.ts +141 -0
- package/src/ai/tester.ts +939 -0
- package/src/ai/tools.ts +1117 -0
- package/src/api/api-client.ts +109 -0
- package/src/api/request-result.ts +212 -0
- package/src/api/request-store.ts +130 -0
- package/src/api/spec-reader.ts +174 -0
- package/src/api/xhr-capture.ts +100 -0
- package/src/browser-server.ts +74 -0
- package/src/command-handler.ts +454 -0
- package/src/commands/add-rule-command.ts +63 -0
- package/src/commands/base-command.ts +27 -0
- package/src/commands/clean-command.ts +73 -0
- package/src/commands/context-aria-command.ts +22 -0
- package/src/commands/context-command.ts +67 -0
- package/src/commands/context-data-command.ts +30 -0
- package/src/commands/context-experience-command.ts +48 -0
- package/src/commands/context-html-command.ts +33 -0
- package/src/commands/context-knowledge-command.ts +43 -0
- package/src/commands/debug-command.ts +13 -0
- package/src/commands/drill-command.ts +34 -0
- package/src/commands/exit-command.ts +32 -0
- package/src/commands/explore-command.ts +129 -0
- package/src/commands/freesail-command.ts +95 -0
- package/src/commands/help-command.ts +8 -0
- package/src/commands/index.ts +69 -0
- package/src/commands/init-command.ts +128 -0
- package/src/commands/knows-command.ts +68 -0
- package/src/commands/learn-command.ts +44 -0
- package/src/commands/navigate-command.ts +18 -0
- package/src/commands/path-command.ts +83 -0
- package/src/commands/plan-clear-command.ts +14 -0
- package/src/commands/plan-command.ts +41 -0
- package/src/commands/plan-edit-command.ts +9 -0
- package/src/commands/plan-load-command.ts +18 -0
- package/src/commands/plan-reload-command.ts +28 -0
- package/src/commands/plan-save-command.ts +25 -0
- package/src/commands/research-command.ts +45 -0
- package/src/commands/start-command.ts +13 -0
- package/src/commands/status-command.tsx +23 -0
- package/src/commands/test-command.ts +84 -0
- package/src/components/ActivityPane.tsx +80 -0
- package/src/components/AddKnowledge.tsx +169 -0
- package/src/components/AddRule.tsx +174 -0
- package/src/components/App.tsx +377 -0
- package/src/components/Autocomplete.tsx +63 -0
- package/src/components/InputPane.tsx +259 -0
- package/src/components/InputReadline.tsx +704 -0
- package/src/components/LogPane.tsx +187 -0
- package/src/components/PlanEditor.tsx +150 -0
- package/src/components/PlanPane.tsx +71 -0
- package/src/components/SessionTimer.tsx +35 -0
- package/src/components/StateTransitionPane.tsx +149 -0
- package/src/components/StatusPane.tsx +62 -0
- package/src/components/TaskPane.tsx +119 -0
- package/src/components/Welcome.tsx +83 -0
- package/src/components/WelcomeChecklist.tsx +118 -0
- package/src/components/WelcomeCommands.tsx +102 -0
- package/src/components/autocomplete-store.ts +35 -0
- package/src/components/parse-keypress.ts +170 -0
- package/src/config.ts +490 -0
- package/src/execution-controller.ts +109 -0
- package/src/experience-tracker.ts +350 -0
- package/src/explorbot.ts +405 -0
- package/src/explorer.ts +713 -0
- package/src/index.tsx +62 -0
- package/src/knowledge-tracker.ts +230 -0
- package/src/observability.ts +150 -0
- package/src/reporter.ts +224 -0
- package/src/state-manager.ts +556 -0
- package/src/stats.ts +53 -0
- package/src/test-plan.ts +432 -0
- package/src/utils/aria.ts +629 -0
- package/src/utils/cli-name.ts +13 -0
- package/src/utils/code-extractor.ts +22 -0
- package/src/utils/context-formatter.ts +239 -0
- package/src/utils/error-page.ts +23 -0
- package/src/utils/expandable.ts +38 -0
- package/src/utils/hooks-runner.ts +79 -0
- package/src/utils/html-diff.ts +918 -0
- package/src/utils/html.ts +1316 -0
- package/src/utils/logger.ts +534 -0
- package/src/utils/loop.ts +176 -0
- package/src/utils/markdown-parser.ts +127 -0
- package/src/utils/markdown-query.ts +466 -0
- package/src/utils/markdown-terminal.ts +43 -0
- package/src/utils/research-parser.ts +11 -0
- package/src/utils/retry.ts +73 -0
- package/src/utils/rules-loader.ts +118 -0
- package/src/utils/strings.ts +13 -0
- package/src/utils/test-plan-markdown.ts +332 -0
- package/src/utils/throttle.ts +18 -0
- package/src/utils/unique-names.ts +14 -0
- package/src/utils/url-matcher.ts +45 -0
- package/src/utils/web-element.ts +145 -0
- package/src/utils/xpath.ts +129 -0
- package/dist/bin/explorbot-cli.js.map +0 -1
- package/dist/boat/api-tester/bin/apibot-cli.js.map +0 -1
- package/dist/boat/api-tester/example/apibot.config.js +0 -31
- package/dist/boat/api-tester/example/apibot.config.js.map +0 -1
- package/dist/boat/api-tester/src/ai/chief/styles.js.map +0 -1
- package/dist/boat/api-tester/src/ai/chief.js.map +0 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js.map +0 -1
- package/dist/boat/api-tester/src/ai/curler.js.map +0 -1
- package/dist/boat/api-tester/src/api-client.js.map +0 -1
- package/dist/boat/api-tester/src/apibot.js.map +0 -1
- package/dist/boat/api-tester/src/cli.js.map +0 -1
- package/dist/boat/api-tester/src/config.js.map +0 -1
- package/dist/prompts/audit-rules.md +0 -124
- package/dist/src/action-result.js.map +0 -1
- package/dist/src/action.js.map +0 -1
- package/dist/src/activity.js.map +0 -1
- package/dist/src/ai/agent.js.map +0 -1
- package/dist/src/ai/bosun.js.map +0 -1
- package/dist/src/ai/captain/idle-mode.js.map +0 -1
- package/dist/src/ai/captain/mixin.js.map +0 -1
- package/dist/src/ai/captain/test-mode.js.map +0 -1
- package/dist/src/ai/captain/web-mode.js.map +0 -1
- package/dist/src/ai/captain.js.map +0 -1
- package/dist/src/ai/conversation.js.map +0 -1
- package/dist/src/ai/experience-compactor.js.map +0 -1
- package/dist/src/ai/fisherman-tools.js.map +0 -1
- package/dist/src/ai/fisherman.js.map +0 -1
- package/dist/src/ai/historian.js.map +0 -1
- package/dist/src/ai/navigator.js.map +0 -1
- package/dist/src/ai/pilot.js.map +0 -1
- package/dist/src/ai/planner/session-dedup.js.map +0 -1
- package/dist/src/ai/planner/styles.js.map +0 -1
- package/dist/src/ai/planner/subpages.js.map +0 -1
- package/dist/src/ai/planner.js.map +0 -1
- package/dist/src/ai/provider.js.map +0 -1
- package/dist/src/ai/quartermaster.js.map +0 -1
- package/dist/src/ai/researcher/cache.js.map +0 -1
- package/dist/src/ai/researcher/coordinates.js.map +0 -1
- package/dist/src/ai/researcher/deep-analysis.js.map +0 -1
- package/dist/src/ai/researcher/fingerprint-worker.js.map +0 -1
- package/dist/src/ai/researcher/focus.js.map +0 -1
- package/dist/src/ai/researcher/locators.js.map +0 -1
- package/dist/src/ai/researcher/mixin.js.map +0 -1
- package/dist/src/ai/researcher/parser.js.map +0 -1
- package/dist/src/ai/researcher/research-result.js.map +0 -1
- package/dist/src/ai/researcher.js.map +0 -1
- package/dist/src/ai/rules.js.map +0 -1
- package/dist/src/ai/task-agent.js.map +0 -1
- package/dist/src/ai/tester.js.map +0 -1
- package/dist/src/ai/tools.js.map +0 -1
- package/dist/src/api/api-client.js.map +0 -1
- package/dist/src/api/request-result.js.map +0 -1
- package/dist/src/api/request-store.js.map +0 -1
- package/dist/src/api/spec-reader.js.map +0 -1
- package/dist/src/api/xhr-capture.js.map +0 -1
- package/dist/src/browser-server.js.map +0 -1
- package/dist/src/command-handler.js.map +0 -1
- package/dist/src/commands/add-rule-command.js.map +0 -1
- package/dist/src/commands/base-command.js.map +0 -1
- package/dist/src/commands/clean-command.js.map +0 -1
- package/dist/src/commands/context-aria-command.js.map +0 -1
- package/dist/src/commands/context-command.js.map +0 -1
- package/dist/src/commands/context-data-command.js.map +0 -1
- package/dist/src/commands/context-experience-command.js.map +0 -1
- package/dist/src/commands/context-html-command.js.map +0 -1
- package/dist/src/commands/context-knowledge-command.js.map +0 -1
- package/dist/src/commands/debug-command.js.map +0 -1
- package/dist/src/commands/drill-command.js.map +0 -1
- package/dist/src/commands/exit-command.js.map +0 -1
- package/dist/src/commands/explore-command.js.map +0 -1
- package/dist/src/commands/freesail-command.js.map +0 -1
- package/dist/src/commands/help-command.js.map +0 -1
- package/dist/src/commands/index.js.map +0 -1
- package/dist/src/commands/knows-command.js.map +0 -1
- package/dist/src/commands/learn-command.js.map +0 -1
- package/dist/src/commands/navigate-command.js.map +0 -1
- package/dist/src/commands/path-command.js.map +0 -1
- package/dist/src/commands/plan-clear-command.js.map +0 -1
- package/dist/src/commands/plan-command.js.map +0 -1
- package/dist/src/commands/plan-edit-command.js.map +0 -1
- package/dist/src/commands/plan-load-command.js.map +0 -1
- package/dist/src/commands/plan-reload-command.js.map +0 -1
- package/dist/src/commands/plan-save-command.js.map +0 -1
- package/dist/src/commands/research-command.js.map +0 -1
- package/dist/src/commands/start-command.js.map +0 -1
- package/dist/src/commands/status-command.js.map +0 -1
- package/dist/src/commands/test-command.js.map +0 -1
- package/dist/src/components/ActivityPane.js.map +0 -1
- package/dist/src/components/AddKnowledge.js.map +0 -1
- package/dist/src/components/AddRule.js.map +0 -1
- package/dist/src/components/App.js.map +0 -1
- package/dist/src/components/Autocomplete.js.map +0 -1
- package/dist/src/components/InputPane.js.map +0 -1
- package/dist/src/components/InputReadline.js.map +0 -1
- package/dist/src/components/LogPane.js.map +0 -1
- package/dist/src/components/PlanEditor.js.map +0 -1
- package/dist/src/components/PlanPane.js.map +0 -1
- package/dist/src/components/SessionTimer.js.map +0 -1
- package/dist/src/components/StateTransitionPane.js.map +0 -1
- package/dist/src/components/StatusPane.js.map +0 -1
- package/dist/src/components/TaskPane.js.map +0 -1
- package/dist/src/components/Welcome.js.map +0 -1
- package/dist/src/components/WelcomeChecklist.js.map +0 -1
- package/dist/src/components/WelcomeCommands.js.map +0 -1
- package/dist/src/components/autocomplete-store.js.map +0 -1
- package/dist/src/components/parse-keypress.js.map +0 -1
- package/dist/src/config.js.map +0 -1
- package/dist/src/execution-controller.js.map +0 -1
- package/dist/src/experience-tracker.js.map +0 -1
- package/dist/src/explorbot.js.map +0 -1
- package/dist/src/explorer.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/knowledge-tracker.js.map +0 -1
- package/dist/src/observability.js.map +0 -1
- package/dist/src/reporter.js.map +0 -1
- package/dist/src/state-manager.js.map +0 -1
- package/dist/src/stats.js.map +0 -1
- package/dist/src/test-plan.js.map +0 -1
- package/dist/src/utils/aria.js.map +0 -1
- package/dist/src/utils/code-extractor.js.map +0 -1
- package/dist/src/utils/context-formatter.js.map +0 -1
- package/dist/src/utils/error-page.js.map +0 -1
- package/dist/src/utils/expandable.js.map +0 -1
- package/dist/src/utils/hooks-runner.js.map +0 -1
- package/dist/src/utils/html-diff.js.map +0 -1
- package/dist/src/utils/html.js.map +0 -1
- package/dist/src/utils/logger.js.map +0 -1
- package/dist/src/utils/loop.js.map +0 -1
- package/dist/src/utils/markdown-parser.js.map +0 -1
- package/dist/src/utils/markdown-query.js.map +0 -1
- package/dist/src/utils/markdown-terminal.js.map +0 -1
- package/dist/src/utils/research-parser.js.map +0 -1
- package/dist/src/utils/retry.js.map +0 -1
- package/dist/src/utils/rules-loader.js.map +0 -1
- package/dist/src/utils/strings.js.map +0 -1
- package/dist/src/utils/test-plan-markdown.js.map +0 -1
- package/dist/src/utils/throttle.js.map +0 -1
- package/dist/src/utils/unique-names.js.map +0 -1
- package/dist/src/utils/url-matcher.js.map +0 -1
- package/dist/src/utils/web-element.js.map +0 -1
- package/dist/src/utils/xpath.js.map +0 -1
- package/prompts/audit-rules.md +0 -124
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
|
2
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
3
|
+
import { generateObject, generateText } from 'ai';
|
|
4
|
+
import type { ModelMessage } from 'ai';
|
|
5
|
+
import { clearActivity, setActivity } from '../activity.ts';
|
|
6
|
+
import type { AIConfig } from '../config.js';
|
|
7
|
+
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
8
|
+
import { executionController } from '../execution-controller.ts';
|
|
9
|
+
import { Observability } from '../observability.ts';
|
|
10
|
+
import { Stats } from '../stats.ts';
|
|
11
|
+
import { createDebug, tag } from '../utils/logger.js';
|
|
12
|
+
import { type RetryOptions, withRetry } from '../utils/retry.js';
|
|
13
|
+
import { Conversation } from './conversation.js';
|
|
14
|
+
|
|
15
|
+
const debugLog = createDebug('explorbot:provider');
|
|
16
|
+
const promptLog = createDebug('explorbot:provider:out');
|
|
17
|
+
const responseLog = createDebug('explorbot:provider:in');
|
|
18
|
+
|
|
19
|
+
class AiError extends Error {}
|
|
20
|
+
export class ContextLengthError extends Error {}
|
|
21
|
+
|
|
22
|
+
export class Provider {
|
|
23
|
+
private config: AIConfig;
|
|
24
|
+
private telemetryEnabled = false;
|
|
25
|
+
private otelSdk: NodeSDK | null = null;
|
|
26
|
+
private defaultRetryOptions: RetryOptions = {
|
|
27
|
+
maxAttempts: 3,
|
|
28
|
+
baseDelay: 10,
|
|
29
|
+
maxDelay: 10000,
|
|
30
|
+
retryCondition: (error: Error) => {
|
|
31
|
+
return (
|
|
32
|
+
(error.name === 'AI_APICallError' ||
|
|
33
|
+
error.message.includes('timeout') ||
|
|
34
|
+
error.message.includes('network') ||
|
|
35
|
+
error.message.includes('rate limit') ||
|
|
36
|
+
error.message.includes('AI request timeout') ||
|
|
37
|
+
error.message.includes('schema') ||
|
|
38
|
+
error.message.includes('No object generated') ||
|
|
39
|
+
error.message.includes('No response text') ||
|
|
40
|
+
error.message.includes('validate JSON')) &&
|
|
41
|
+
!error.message.includes('output truncated at maxTokens')
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
static readonly CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens'];
|
|
47
|
+
lastConversation: Conversation | null = null;
|
|
48
|
+
|
|
49
|
+
constructor(config: AIConfig) {
|
|
50
|
+
if (!config?.model) {
|
|
51
|
+
throw new AiError('AI model is not configured. Set ai.model in your config file.');
|
|
52
|
+
}
|
|
53
|
+
this.config = config;
|
|
54
|
+
this.initLangfuse();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private getModelName(model: any): string {
|
|
58
|
+
return model?.modelId || model?.model || 'unknown';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async validateConnection(): Promise<void> {
|
|
62
|
+
try {
|
|
63
|
+
await generateText({
|
|
64
|
+
model: this.config.model,
|
|
65
|
+
prompt: 'hi',
|
|
66
|
+
maxTokens: 1,
|
|
67
|
+
});
|
|
68
|
+
} catch (error: any) {
|
|
69
|
+
throw new AiError(`AI connection failed: ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getModelForAgent(agentName?: string): any {
|
|
74
|
+
if (!agentName) {
|
|
75
|
+
return this.config.model;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents];
|
|
79
|
+
return agentConfig?.model || this.config.model;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getAgenticModel(agentName?: string): any {
|
|
83
|
+
if (agentName) {
|
|
84
|
+
const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents];
|
|
85
|
+
if (agentConfig?.model) return agentConfig.model;
|
|
86
|
+
}
|
|
87
|
+
return this.config.agenticModel || this.config.model;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getSystemPromptForAgent(agentName: string, currentUrl?: string): string | undefined {
|
|
91
|
+
const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents];
|
|
92
|
+
const parts: string[] = [];
|
|
93
|
+
|
|
94
|
+
if (agentConfig?.rules && currentUrl) {
|
|
95
|
+
const rulesText = RulesLoader.loadRules(agentName, agentConfig.rules, currentUrl);
|
|
96
|
+
if (rulesText) parts.push(rulesText);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (agentConfig?.systemPrompt) parts.push(agentConfig.systemPrompt);
|
|
100
|
+
|
|
101
|
+
return parts.length > 0 ? parts.join('\n\n') : undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getProviderOptionsForAgent(agentName: string): Record<string, any> | undefined {
|
|
105
|
+
const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents];
|
|
106
|
+
return agentConfig?.providerOptions;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private getRetryOptions(options: any = {}): RetryOptions {
|
|
110
|
+
return {
|
|
111
|
+
...this.defaultRetryOptions,
|
|
112
|
+
maxAttempts: options.maxRetries || this.defaultRetryOptions.maxAttempts,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private mergeProviderOptions(config: Record<string, any>, agentName?: string): Record<string, any> {
|
|
117
|
+
if (!agentName) return config;
|
|
118
|
+
const agentOptions = this.getProviderOptionsForAgent(agentName);
|
|
119
|
+
if (!agentOptions) return config;
|
|
120
|
+
return {
|
|
121
|
+
...config,
|
|
122
|
+
providerOptions: { ...config.providerOptions, ...agentOptions },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private initLangfuse() {
|
|
127
|
+
const langfuseConfig = this.config.langfuse;
|
|
128
|
+
const publicKey = langfuseConfig?.publicKey || process.env.LANGFUSE_PUBLIC_KEY;
|
|
129
|
+
const secretKey = langfuseConfig?.secretKey || process.env.LANGFUSE_SECRET_KEY;
|
|
130
|
+
const baseUrl = langfuseConfig?.baseUrl || process.env.LANGFUSE_BASE_URL || process.env.LANGFUSE_HOST;
|
|
131
|
+
const enabled = langfuseConfig?.enabled ?? Boolean(publicKey && secretKey);
|
|
132
|
+
|
|
133
|
+
if (!enabled || !publicKey || !secretKey) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const processor = new LangfuseSpanProcessor({
|
|
138
|
+
publicKey,
|
|
139
|
+
secretKey,
|
|
140
|
+
baseUrl,
|
|
141
|
+
});
|
|
142
|
+
this.otelSdk = new NodeSDK({
|
|
143
|
+
spanProcessors: [processor],
|
|
144
|
+
instrumentations: [],
|
|
145
|
+
});
|
|
146
|
+
void this.otelSdk.start();
|
|
147
|
+
this.telemetryEnabled = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private getTelemetry(options: any) {
|
|
151
|
+
if (!this.telemetryEnabled) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const runTelemetry = Observability.getTelemetry();
|
|
156
|
+
|
|
157
|
+
if (!options.experimental_telemetry) {
|
|
158
|
+
return runTelemetry || { isEnabled: true };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!runTelemetry) {
|
|
162
|
+
return options.experimental_telemetry;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...runTelemetry,
|
|
167
|
+
...options.experimental_telemetry,
|
|
168
|
+
metadata: {
|
|
169
|
+
...runTelemetry.metadata,
|
|
170
|
+
...options.experimental_telemetry.metadata,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
startConversation(systemMessage: string, agentName?: string, model?: any) {
|
|
176
|
+
const resolvedModel = model || this.getModelForAgent(agentName);
|
|
177
|
+
return new Conversation(
|
|
178
|
+
[
|
|
179
|
+
{
|
|
180
|
+
role: 'system',
|
|
181
|
+
content: systemMessage,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
resolvedModel
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async invokeConversation(conversation: Conversation, tools?: any, options: any = {}): Promise<{ conversation: Conversation; response: any; toolExecutions?: any[] } | null> {
|
|
189
|
+
const response = tools ? await this.generateWithTools(conversation.messages, conversation.model, tools, options) : await this.chat(conversation.messages, conversation.model, options);
|
|
190
|
+
|
|
191
|
+
const responseMessages = response.response?.messages || [];
|
|
192
|
+
if (responseMessages.length > 0) {
|
|
193
|
+
conversation.messages.push(...responseMessages);
|
|
194
|
+
tag('debug').log('Added', responseMessages.length, 'messages from response');
|
|
195
|
+
} else {
|
|
196
|
+
conversation.addAssistantText(response.text || '');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.lastConversation = conversation;
|
|
200
|
+
|
|
201
|
+
const toolCalls = response.toolCalls || [];
|
|
202
|
+
const toolResults = response.toolResults || [];
|
|
203
|
+
|
|
204
|
+
const toolExecutions = toolCalls.map((call: any, index: number) => ({
|
|
205
|
+
toolName: call.toolName || '',
|
|
206
|
+
input: call.input,
|
|
207
|
+
output: toolResults[index]?.output,
|
|
208
|
+
wasSuccessful: toolResults[index]?.output?.success || false,
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
return { conversation, response, toolExecutions };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async chat(messages: ModelMessage[], model: any, options: any = {}): Promise<any> {
|
|
215
|
+
const modelName = this.getModelName(model);
|
|
216
|
+
setActivity(`🤖 Asking ${modelName}`, 'ai');
|
|
217
|
+
promptLog(`Using model: ${modelName}`);
|
|
218
|
+
|
|
219
|
+
const telemetry = this.getTelemetry(options);
|
|
220
|
+
const config = this.mergeProviderOptions(
|
|
221
|
+
{
|
|
222
|
+
maxTokens: 16384,
|
|
223
|
+
...(this.config.config || {}),
|
|
224
|
+
...options,
|
|
225
|
+
...(telemetry ? { experimental_telemetry: telemetry } : {}),
|
|
226
|
+
model,
|
|
227
|
+
abortSignal: executionController.getAbortSignal(),
|
|
228
|
+
},
|
|
229
|
+
options.agentName
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
promptLog(messages[messages.length - 1].content);
|
|
233
|
+
try {
|
|
234
|
+
const response = await withRetry(async () => {
|
|
235
|
+
const result = await generateText({ messages, ...config });
|
|
236
|
+
if (!result.text) {
|
|
237
|
+
debugLog(result);
|
|
238
|
+
if (result.finishReason === 'length') {
|
|
239
|
+
throw new ContextLengthError('AI response empty: output truncated at maxTokens. Increase maxTokens in config or use a model with higher output capacity.');
|
|
240
|
+
}
|
|
241
|
+
throw new Error('No response text from AI');
|
|
242
|
+
}
|
|
243
|
+
if (result.finishReason === 'length') {
|
|
244
|
+
debugLog('finishReason=length, response may be truncated');
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
}, this.getRetryOptions(options));
|
|
248
|
+
|
|
249
|
+
clearActivity();
|
|
250
|
+
responseLog(response.text);
|
|
251
|
+
|
|
252
|
+
if (response.usage) {
|
|
253
|
+
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
254
|
+
input: response.usage.promptTokens || 0,
|
|
255
|
+
output: response.usage.completionTokens || 0,
|
|
256
|
+
total: response.usage.totalTokens || 0,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return response;
|
|
261
|
+
} catch (error: any) {
|
|
262
|
+
clearActivity();
|
|
263
|
+
if (error?.name === 'AbortError') throw error;
|
|
264
|
+
if (error instanceof ContextLengthError) throw error;
|
|
265
|
+
if (Provider.isContextLengthError(error)) {
|
|
266
|
+
const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
|
|
267
|
+
if (reduced) {
|
|
268
|
+
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
269
|
+
return this.chat(reduced.messages, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
270
|
+
}
|
|
271
|
+
throw new ContextLengthError(error.message || error.toString());
|
|
272
|
+
}
|
|
273
|
+
tag('error').log(error.message || error.toString());
|
|
274
|
+
throw new AiError(error.message || error.toString());
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async generateWithTools(messages: ModelMessage[], model: any, tools: any, options: any = {}): Promise<any> {
|
|
279
|
+
const modelName = this.getModelName(model);
|
|
280
|
+
setActivity(`🤖 Asking ${modelName} with dynamic tools`, 'ai');
|
|
281
|
+
promptLog(`Using model: ${modelName}`);
|
|
282
|
+
|
|
283
|
+
const toolNames = Object.keys(tools || {});
|
|
284
|
+
tag('debug').log(`Tools enabled: [${toolNames.join(', ')}]`);
|
|
285
|
+
promptLog('Available tools:', toolNames);
|
|
286
|
+
promptLog(messages[messages.length - 1].content);
|
|
287
|
+
|
|
288
|
+
const telemetry = this.getTelemetry(options);
|
|
289
|
+
const config = this.mergeProviderOptions(
|
|
290
|
+
{
|
|
291
|
+
tools,
|
|
292
|
+
maxTokens: 16384,
|
|
293
|
+
maxToolRoundtrips: options.maxToolRoundtrips ?? 5,
|
|
294
|
+
toolChoice: 'auto',
|
|
295
|
+
...(this.config.config || {}),
|
|
296
|
+
...options,
|
|
297
|
+
...(telemetry ? { experimental_telemetry: telemetry } : {}),
|
|
298
|
+
model,
|
|
299
|
+
abortSignal: executionController.getAbortSignal(),
|
|
300
|
+
},
|
|
301
|
+
options.agentName
|
|
302
|
+
);
|
|
303
|
+
try {
|
|
304
|
+
const response = await withRetry(async () => {
|
|
305
|
+
const timeout = config.timeout || 30000;
|
|
306
|
+
return (await Promise.race([
|
|
307
|
+
generateText({
|
|
308
|
+
messages,
|
|
309
|
+
...config,
|
|
310
|
+
}),
|
|
311
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('AI request timeout')), timeout)),
|
|
312
|
+
])) as any;
|
|
313
|
+
}, this.getRetryOptions(options));
|
|
314
|
+
|
|
315
|
+
clearActivity();
|
|
316
|
+
|
|
317
|
+
// Log tool usage summary
|
|
318
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
319
|
+
responseLog(response.toolCalls);
|
|
320
|
+
response.toolCalls.forEach((call: any, index: number) => {
|
|
321
|
+
tag('debug').log(`${call.toolName} > ${Object.values(call?.input || []).join(', ')}`);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
responseLog(response.text);
|
|
326
|
+
|
|
327
|
+
if (response.usage) {
|
|
328
|
+
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
329
|
+
input: response.usage.promptTokens || 0,
|
|
330
|
+
output: response.usage.completionTokens || 0,
|
|
331
|
+
total: response.usage.totalTokens || 0,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return response;
|
|
336
|
+
} catch (error: any) {
|
|
337
|
+
clearActivity();
|
|
338
|
+
if (error?.message?.includes('Tool choice is required')) {
|
|
339
|
+
tag('warning').log('Model completed without calling a tool, returning empty result');
|
|
340
|
+
return { text: '', toolCalls: [], toolResults: [], response: { messages: [] }, usage: null };
|
|
341
|
+
}
|
|
342
|
+
if (error?.name === 'AbortError') throw error;
|
|
343
|
+
if (error instanceof ContextLengthError) throw error;
|
|
344
|
+
if (Provider.isContextLengthError(error)) {
|
|
345
|
+
const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
|
|
346
|
+
if (reduced) {
|
|
347
|
+
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
348
|
+
return this.generateWithTools(reduced.messages, model, tools, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
349
|
+
}
|
|
350
|
+
throw new ContextLengthError(error.message || error.toString());
|
|
351
|
+
}
|
|
352
|
+
if (error.constructor?.name === 'AI_APICallError') {
|
|
353
|
+
responseLog(error.message);
|
|
354
|
+
throw new AiError(error.message);
|
|
355
|
+
}
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async generateObject(messages: ModelMessage[], schema: any, model?: any, options: any = {}): Promise<any> {
|
|
361
|
+
const modelToUse = model || this.config.model;
|
|
362
|
+
const modelName = this.getModelName(modelToUse);
|
|
363
|
+
setActivity(`🤖 Asking ${modelName} for structured output`, 'ai');
|
|
364
|
+
promptLog(`Using model: ${modelName}`);
|
|
365
|
+
|
|
366
|
+
const telemetry = this.getTelemetry(options);
|
|
367
|
+
const config = this.mergeProviderOptions(
|
|
368
|
+
{
|
|
369
|
+
schema,
|
|
370
|
+
...(this.config.config || {}),
|
|
371
|
+
...options,
|
|
372
|
+
...(telemetry ? { experimental_telemetry: telemetry } : {}),
|
|
373
|
+
model: modelToUse,
|
|
374
|
+
abortSignal: executionController.getAbortSignal(),
|
|
375
|
+
},
|
|
376
|
+
options.agentName
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
promptLog(messages[messages.length - 1].content);
|
|
381
|
+
const response = await withRetry(async () => {
|
|
382
|
+
const timeout = config.timeout || 30000;
|
|
383
|
+
return (await Promise.race([
|
|
384
|
+
generateObject({
|
|
385
|
+
messages,
|
|
386
|
+
...config,
|
|
387
|
+
}),
|
|
388
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('AI request timeout')), timeout)),
|
|
389
|
+
])) as any;
|
|
390
|
+
}, this.getRetryOptions(options));
|
|
391
|
+
|
|
392
|
+
clearActivity();
|
|
393
|
+
responseLog(response.object);
|
|
394
|
+
|
|
395
|
+
if (response.usage) {
|
|
396
|
+
Stats.recordTokens(options.agentName || 'unknown', modelName, {
|
|
397
|
+
input: response.usage.promptTokens || 0,
|
|
398
|
+
output: response.usage.completionTokens || 0,
|
|
399
|
+
total: response.usage.totalTokens || 0,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return response;
|
|
404
|
+
} catch (error: any) {
|
|
405
|
+
clearActivity();
|
|
406
|
+
if (error?.name === 'AbortError') throw error;
|
|
407
|
+
if (error instanceof ContextLengthError) throw error;
|
|
408
|
+
if (Provider.isContextLengthError(error)) {
|
|
409
|
+
const reduced = this.tryReduceMessages(messages, options._contextRetryLevel || 0);
|
|
410
|
+
if (reduced) {
|
|
411
|
+
tag('warning').log('Context length exceeded, retrying with reduced messages...');
|
|
412
|
+
return this.generateObject(reduced.messages, schema, model, { ...options, _contextRetryLevel: reduced.nextLevel });
|
|
413
|
+
}
|
|
414
|
+
throw new ContextLengthError(error.message || error.toString());
|
|
415
|
+
}
|
|
416
|
+
throw new AiError(error.message || error.toString());
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
static isContextLengthError(error: any): boolean {
|
|
421
|
+
const msg = (error?.message || error?.toString() || '').toLowerCase();
|
|
422
|
+
return Provider.CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static trimMessagesForRetry(messages: ModelMessage[]): ModelMessage[] | null {
|
|
426
|
+
const tagRegex = /<(\w[\w-]*)>([\s\S]*?)<\/\1>/g;
|
|
427
|
+
let didTrim = false;
|
|
428
|
+
|
|
429
|
+
const trimmed = messages.map((msg, idx) => {
|
|
430
|
+
if (typeof msg.content === 'string') {
|
|
431
|
+
const newContent = msg.content.replace(tagRegex, (match, tagName, content) => {
|
|
432
|
+
if (content.length > 2000) {
|
|
433
|
+
didTrim = true;
|
|
434
|
+
return `<${tagName}>${content.substring(0, Math.floor(content.length / 2))}\n[...trimmed...]</${tagName}>`;
|
|
435
|
+
}
|
|
436
|
+
return match;
|
|
437
|
+
});
|
|
438
|
+
return { ...msg, content: newContent };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (msg.role === 'tool' && Array.isArray(msg.content) && idx < messages.length - 3) {
|
|
442
|
+
const newContent = (msg.content as any[]).map((part: any) => {
|
|
443
|
+
if (part.type !== 'tool-result' || !part.output) return part;
|
|
444
|
+
const output = part.output?.type === 'json' ? part.output.value : part.output;
|
|
445
|
+
if (!output || typeof output !== 'object') return part;
|
|
446
|
+
const json = JSON.stringify(output);
|
|
447
|
+
if (json.length < 2000) return part;
|
|
448
|
+
didTrim = true;
|
|
449
|
+
const trimmedOutput = { success: output.success, action: output.action, trimmed: true };
|
|
450
|
+
return { ...part, output: part.output?.type === 'json' ? { type: 'json', value: trimmedOutput } : trimmedOutput };
|
|
451
|
+
});
|
|
452
|
+
return { ...msg, content: newContent };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return msg;
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
return didTrim ? trimmed : null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static compactMessagesForRetry(messages: ModelMessage[]): ModelMessage[] | null {
|
|
462
|
+
if (messages.length <= 5) return null;
|
|
463
|
+
|
|
464
|
+
const head = messages[0];
|
|
465
|
+
let tailStart = messages.length - 4;
|
|
466
|
+
|
|
467
|
+
if (messages[tailStart]?.role === 'tool' && tailStart > 1) {
|
|
468
|
+
tailStart--;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const tail = messages.slice(tailStart);
|
|
472
|
+
const middle = messages.slice(1, tailStart);
|
|
473
|
+
|
|
474
|
+
if (middle.length === 0) return null;
|
|
475
|
+
|
|
476
|
+
const executions = new Conversation(middle).getToolExecutions();
|
|
477
|
+
const toolStats = new Map<string, { total: number; success: number; fail: number }>();
|
|
478
|
+
const urls = new Set<string>();
|
|
479
|
+
const failedAttempts: string[] = [];
|
|
480
|
+
|
|
481
|
+
for (const exec of executions) {
|
|
482
|
+
const stats = toolStats.get(exec.toolName) || { total: 0, success: 0, fail: 0 };
|
|
483
|
+
stats.total++;
|
|
484
|
+
if (exec.wasSuccessful) stats.success++;
|
|
485
|
+
else stats.fail++;
|
|
486
|
+
toolStats.set(exec.toolName, stats);
|
|
487
|
+
|
|
488
|
+
const url = exec.output?.url || exec.output?.pageDiff?.currentUrl;
|
|
489
|
+
if (url) urls.add(url);
|
|
490
|
+
|
|
491
|
+
if (!exec.wasSuccessful && failedAttempts.length < 10) {
|
|
492
|
+
const inputLabel = exec.input ? Object.values(exec.input)[0] : '';
|
|
493
|
+
const errorMsg = exec.output?.message || exec.output?.error || 'failed';
|
|
494
|
+
failedAttempts.push(`- ${exec.toolName}("${inputLabel}"): ${errorMsg}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let lastUserText = '';
|
|
499
|
+
for (let i = middle.length - 1; i >= 0; i--) {
|
|
500
|
+
if (middle[i].role === 'user' && typeof middle[i].content === 'string') {
|
|
501
|
+
lastUserText = middle[i].content as string;
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const lines: string[] = [`[Previous conversation compacted - ${middle.length} messages summarized]`];
|
|
507
|
+
|
|
508
|
+
if (toolStats.size > 0) {
|
|
509
|
+
lines.push('', 'Actions performed:');
|
|
510
|
+
for (const [name, stats] of toolStats) {
|
|
511
|
+
lines.push(`- ${name}: ${stats.total} calls (${stats.success} successful${stats.fail > 0 ? `, ${stats.fail} failed` : ''})`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (urls.size > 0) {
|
|
516
|
+
lines.push('', `Pages visited: ${[...urls].join(', ')}`);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (failedAttempts.length > 0) {
|
|
520
|
+
lines.push('', 'Failed attempts:', ...failedAttempts);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (lastUserText) {
|
|
524
|
+
const truncated = lastUserText.length > 1000 ? `${lastUserText.substring(0, 1000)}...` : lastUserText;
|
|
525
|
+
lines.push('', 'Last context before compaction:', truncated);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const summary: ModelMessage = { role: 'user', content: lines.join('\n') };
|
|
529
|
+
return [head, summary, ...tail];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private tryReduceMessages(messages: ModelMessage[], retryLevel: number): { messages: ModelMessage[]; nextLevel: number } | null {
|
|
533
|
+
if (retryLevel >= 2) return null;
|
|
534
|
+
|
|
535
|
+
if (retryLevel === 0) {
|
|
536
|
+
const trimmed = Provider.trimMessagesForRetry(messages);
|
|
537
|
+
if (trimmed) return { messages: trimmed, nextLevel: 1 };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const compacted = Provider.compactMessagesForRetry(messages);
|
|
541
|
+
if (compacted) return { messages: compacted, nextLevel: 2 };
|
|
542
|
+
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async processImage(prompt: string, image: string): Promise<any> {
|
|
547
|
+
if (!this.config.visionModel) {
|
|
548
|
+
throw new Error('Vision model not configured. Please set ai.visionModel in your config.');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
setActivity(`🤖 Processing image with ${this.config.visionModel}`, 'ai');
|
|
552
|
+
|
|
553
|
+
const imageData = `data:image/png;base64,${image.toString()}`;
|
|
554
|
+
|
|
555
|
+
const messages: ModelMessage[] = [
|
|
556
|
+
{
|
|
557
|
+
role: 'user',
|
|
558
|
+
content: [
|
|
559
|
+
{
|
|
560
|
+
type: 'text',
|
|
561
|
+
text: prompt,
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
type: 'image',
|
|
565
|
+
image: imageData,
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
},
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
const telemetry = this.getTelemetry({});
|
|
572
|
+
const config = {
|
|
573
|
+
maxTokens: 16384,
|
|
574
|
+
...(this.config.config || {}),
|
|
575
|
+
...(telemetry ? { experimental_telemetry: telemetry } : {}),
|
|
576
|
+
model: this.config.visionModel,
|
|
577
|
+
abortSignal: executionController.getAbortSignal(),
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
try {
|
|
581
|
+
promptLog(`Processing image with prompt: ${prompt}`);
|
|
582
|
+
const response = await withRetry(async () => {
|
|
583
|
+
return await generateText({
|
|
584
|
+
messages,
|
|
585
|
+
...config,
|
|
586
|
+
});
|
|
587
|
+
}, this.getRetryOptions());
|
|
588
|
+
|
|
589
|
+
clearActivity();
|
|
590
|
+
responseLog(response.text);
|
|
591
|
+
|
|
592
|
+
if (response.usage) {
|
|
593
|
+
Stats.recordTokens('vision', this.getModelName(this.config.visionModel), {
|
|
594
|
+
input: response.usage.promptTokens || 0,
|
|
595
|
+
output: response.usage.completionTokens || 0,
|
|
596
|
+
total: response.usage.totalTokens || 0,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return response;
|
|
601
|
+
} catch (error: any) {
|
|
602
|
+
clearActivity();
|
|
603
|
+
if (error?.name === 'AbortError') throw error;
|
|
604
|
+
throw new AiError(error.message || error.toString());
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
hasVision(): boolean {
|
|
609
|
+
return this.config.visionModel !== undefined;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export { AiError, Provider as AIProvider };
|