@f5-sales-demo/xcsh 19.51.2
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/CHANGELOG.md +7073 -0
- package/README.md +14 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +109 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/todo/index.ts +206 -0
- package/examples/extensions/README.md +143 -0
- package/examples/extensions/api-demo.ts +89 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +32 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/todo.ts +296 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +43 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +80 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/README.md +172 -0
- package/package.json +539 -0
- package/scripts/build-info/resolvers.ts +51 -0
- package/scripts/capture-ax-fixture.ts +84 -0
- package/scripts/capture-login-fixture.ts +30 -0
- package/scripts/check-workflow-field-coverage.ts +101 -0
- package/scripts/extension-uat-harness.ts +340 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-api-spec-index.ts +580 -0
- package/scripts/generate-branding-index.ts +81 -0
- package/scripts/generate-build-info.ts +106 -0
- package/scripts/generate-console-catalog.ts +88 -0
- package/scripts/generate-console-field-metadata.ts +74 -0
- package/scripts/generate-docs-index.ts +40 -0
- package/scripts/generate-template.ts +32 -0
- package/scripts/generate-terraform-index.ts +81 -0
- package/scripts/mermaid-gallery.ts +132 -0
- package/scripts/uat-matrix-modalities.ts +451 -0
- package/scripts/uat-matrix-report.ts +175 -0
- package/scripts/uat-matrix.ts +353 -0
- package/src/async/index.ts +2 -0
- package/src/async/job-manager.ts +416 -0
- package/src/async/support.ts +5 -0
- package/src/autoresearch/apply-contract-to-state.ts +1 -0
- package/src/autoresearch/command-resume.md +17 -0
- package/src/autoresearch/contract.ts +205 -0
- package/src/autoresearch/dashboard.ts +374 -0
- package/src/autoresearch/git.ts +191 -0
- package/src/autoresearch/helpers.ts +328 -0
- package/src/autoresearch/index.ts +402 -0
- package/src/autoresearch/prompt.md +236 -0
- package/src/autoresearch/resume-message.md +16 -0
- package/src/autoresearch/state.ts +303 -0
- package/src/autoresearch/tools/init-experiment.ts +400 -0
- package/src/autoresearch/tools/log-experiment.ts +793 -0
- package/src/autoresearch/tools/run-experiment.ts +679 -0
- package/src/autoresearch/types.ts +114 -0
- package/src/browser/acquire.ts +249 -0
- package/src/browser/actions.ts +126 -0
- package/src/browser/auth.ts +176 -0
- package/src/browser/ax.ts +134 -0
- package/src/browser/cdp-core.ts +45 -0
- package/src/browser/cdp-page-actions.ts +51 -0
- package/src/browser/chrome-locate.ts +66 -0
- package/src/browser/dom-context.ts +89 -0
- package/src/browser/dt-context.ts +100 -0
- package/src/browser/extension-bridge.ts +182 -0
- package/src/browser/extension-page-actions.ts +397 -0
- package/src/browser/extension-provider.ts +373 -0
- package/src/browser/index.ts +17 -0
- package/src/browser/input-commit.ts +74 -0
- package/src/browser/native-messaging.ts +20 -0
- package/src/browser/page-actions.ts +18 -0
- package/src/browser/provider.ts +155 -0
- package/src/browser/resolver.ts +126 -0
- package/src/browser/selector.ts +40 -0
- package/src/bun-imports.d.ts +22 -0
- package/src/capability/context-file.ts +43 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +107 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +438 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +74 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +224 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +50 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +168 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +286 -0
- package/src/cli/chrome-cli.ts +88 -0
- package/src/cli/classify-install-target.ts +50 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/config-cli.ts +418 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/grep-cli.ts +160 -0
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/jupyter-cli.ts +106 -0
- package/src/cli/list-models.ts +128 -0
- package/src/cli/plugin-cli.ts +964 -0
- package/src/cli/read-cli.ts +68 -0
- package/src/cli/session-picker.ts +52 -0
- package/src/cli/setup-cli.ts +479 -0
- package/src/cli/shell-cli.ts +174 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/stats-cli.ts +182 -0
- package/src/cli/update-cli.ts +446 -0
- package/src/cli/web-search-cli.ts +203 -0
- package/src/cli.ts +123 -0
- package/src/commands/agents.ts +57 -0
- package/src/commands/chrome-host.ts +71 -0
- package/src/commands/chrome.ts +28 -0
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +48 -0
- package/src/commands/grievances.ts +20 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/launch.ts +144 -0
- package/src/commands/plugin.ts +78 -0
- package/src/commands/read.ts +33 -0
- package/src/commands/setup.ts +42 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +46 -0
- package/src/commit/agentic/agent.ts +314 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +354 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +127 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +81 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +139 -0
- package/src/commit/agentic/tools/propose-commit.ts +122 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +31 -0
- package/src/commit/agentic/tools/split-commit.ts +251 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +122 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +105 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +101 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +108 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +66 -0
- package/src/commit/pipeline.ts +242 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +44 -0
- package/src/config/auto-config.ts +698 -0
- package/src/config/context-schema.json +115 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +493 -0
- package/src/config/mcp-schema.json +230 -0
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +2265 -0
- package/src/config/model-resolver.ts +1289 -0
- package/src/config/prompt-templates.ts +271 -0
- package/src/config/resolve-config-value.ts +110 -0
- package/src/config/settings-schema.ts +2155 -0
- package/src/config/settings.ts +730 -0
- package/src/config.ts +418 -0
- package/src/cursor.ts +323 -0
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/index.ts +440 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +908 -0
- package/src/debug/profiler.ts +158 -0
- package/src/debug/report-bundle.ts +365 -0
- package/src/debug/system-info.ts +107 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +219 -0
- package/src/discovery/builtin.ts +851 -0
- package/src/discovery/claude-plugins.ts +297 -0
- package/src/discovery/claude.ts +543 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +535 -0
- package/src/discovery/cursor.ts +220 -0
- package/src/discovery/gemini.ts +396 -0
- package/src/discovery/github.ts +118 -0
- package/src/discovery/helpers.ts +934 -0
- package/src/discovery/index.ts +79 -0
- package/src/discovery/language.ts +46 -0
- package/src/discovery/mcp-json.ts +171 -0
- package/src/discovery/opencode.ts +393 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/discovery/vscode.ts +105 -0
- package/src/discovery/windsurf.ts +147 -0
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +481 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +762 -0
- package/src/edit/modes/hashline.ts +1366 -0
- package/src/edit/modes/patch.ts +1791 -0
- package/src/edit/modes/replace.ts +1115 -0
- package/src/edit/normalize.ts +375 -0
- package/src/edit/renderer.ts +627 -0
- package/src/exa/factory.ts +61 -0
- package/src/exa/index.ts +27 -0
- package/src/exa/mcp-client.ts +293 -0
- package/src/exa/render.ts +232 -0
- package/src/exa/researcher.ts +47 -0
- package/src/exa/search.ts +86 -0
- package/src/exa/types.ts +166 -0
- package/src/exa/websets.ts +247 -0
- package/src/exec/bash-executor.ts +332 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +48 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +161 -0
- package/src/export/html/template.css +1001 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +46 -0
- package/src/export/html/template.js +1950 -0
- package/src/export/html/template.macro.ts +24 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/ttsr.ts +434 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +236 -0
- package/src/extensibility/custom-commands/types.ts +111 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +238 -0
- package/src/extensibility/custom-tools/types.ts +251 -0
- package/src/extensibility/custom-tools/wrapper.ts +47 -0
- package/src/extensibility/extensions/index.ts +15 -0
- package/src/extensibility/extensions/loader.ts +572 -0
- package/src/extensibility/extensions/runner.ts +848 -0
- package/src/extensibility/extensions/types.ts +1394 -0
- package/src/extensibility/extensions/wrapper.ts +191 -0
- package/src/extensibility/hooks/index.ts +5 -0
- package/src/extensibility/hooks/loader.ts +255 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +108 -0
- package/src/extensibility/hooks/types.ts +826 -0
- package/src/extensibility/plugins/doctor.ts +66 -0
- package/src/extensibility/plugins/git-url.ts +281 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/loader.ts +267 -0
- package/src/extensibility/plugins/manager.ts +731 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +318 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +781 -0
- package/src/extensibility/plugins/marketplace/prerequisites.ts +255 -0
- package/src/extensibility/plugins/marketplace/registry.ts +196 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +201 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/skills.ts +269 -0
- package/src/extensibility/slash-commands.ts +246 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/extensibility/utils.ts +38 -0
- package/src/index.ts +61 -0
- package/src/internal-urls/agent-protocol.ts +136 -0
- package/src/internal-urls/api-catalog-resolve.ts +495 -0
- package/src/internal-urls/api-catalog-types.ts +94 -0
- package/src/internal-urls/api-spec-resolve.ts +1036 -0
- package/src/internal-urls/api-spec-types.ts +239 -0
- package/src/internal-urls/artifact-protocol.ts +97 -0
- package/src/internal-urls/branding-index.generated.ts +88 -0
- package/src/internal-urls/build-info-runtime.ts +302 -0
- package/src/internal-urls/build-info.generated.ts +33 -0
- package/src/internal-urls/computer-profile.ts +692 -0
- package/src/internal-urls/console-catalog-types.ts +21 -0
- package/src/internal-urls/console-catalog.generated.ts +532 -0
- package/src/internal-urls/console-field-metadata-types.ts +59 -0
- package/src/internal-urls/console-field-metadata.generated.ts +4841 -0
- package/src/internal-urls/console-resolve.ts +178 -0
- package/src/internal-urls/docs-index.generated.ts +698 -0
- package/src/internal-urls/extension-api.md +147 -0
- package/src/internal-urls/index.ts +46 -0
- package/src/internal-urls/jobs-protocol.ts +119 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +206 -0
- package/src/internal-urls/mcp-protocol.ts +156 -0
- package/src/internal-urls/memory-protocol.ts +133 -0
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/profile-collectors.ts +106 -0
- package/src/internal-urls/router.ts +55 -0
- package/src/internal-urls/rule-protocol.ts +55 -0
- package/src/internal-urls/skill-protocol.ts +111 -0
- package/src/internal-urls/terraform-index.generated.ts +1852 -0
- package/src/internal-urls/terraform-resolve.ts +184 -0
- package/src/internal-urls/terraform-types.ts +45 -0
- package/src/internal-urls/types.ts +52 -0
- package/src/internal-urls/user-profile.ts +363 -0
- package/src/internal-urls/xcsh-protocol.ts +644 -0
- package/src/ipy/cancellation.ts +28 -0
- package/src/ipy/executor.ts +1155 -0
- package/src/ipy/gateway-coordinator.ts +424 -0
- package/src/ipy/kernel.ts +1091 -0
- package/src/ipy/modules.ts +144 -0
- package/src/ipy/prelude.py +849 -0
- package/src/ipy/prelude.ts +3 -0
- package/src/ipy/runtime.ts +221 -0
- package/src/locales/ar.json +512 -0
- package/src/locales/de.json +512 -0
- package/src/locales/en.json +549 -0
- package/src/locales/es.json +512 -0
- package/src/locales/fr.json +512 -0
- package/src/locales/hi.json +512 -0
- package/src/locales/index.ts +29 -0
- package/src/locales/it.json +512 -0
- package/src/locales/ja.json +512 -0
- package/src/locales/ko.json +512 -0
- package/src/locales/pt-br.json +512 -0
- package/src/locales/th.json +512 -0
- package/src/locales/zh-cn.json +512 -0
- package/src/locales/zh-tw.json +512 -0
- package/src/lsp/client.ts +941 -0
- package/src/lsp/clients/biome-client.ts +202 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +93 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +418 -0
- package/src/lsp/defaults.json +999 -0
- package/src/lsp/edits.ts +109 -0
- package/src/lsp/index.ts +1746 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/render.ts +692 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +444 -0
- package/src/lsp/utils.ts +691 -0
- package/src/main.ts +944 -0
- package/src/mcp/client.ts +482 -0
- package/src/mcp/config-writer.ts +225 -0
- package/src/mcp/config.ts +365 -0
- package/src/mcp/discoverable-tool-metadata.ts +202 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +84 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +1152 -0
- package/src/mcp/oauth-discovery.ts +349 -0
- package/src/mcp/oauth-flow.ts +387 -0
- package/src/mcp/render.ts +157 -0
- package/src/mcp/smithery-auth.ts +104 -0
- package/src/mcp/smithery-connect.ts +145 -0
- package/src/mcp/smithery-registry.ts +477 -0
- package/src/mcp/tool-bridge.ts +417 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/http.ts +475 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +325 -0
- package/src/mcp/types.ts +423 -0
- package/src/memories/index.ts +1115 -0
- package/src/memories/storage.ts +577 -0
- package/src/modes/acp/acp-agent.ts +1361 -0
- package/src/modes/acp/acp-event-mapper.ts +538 -0
- package/src/modes/acp/acp-mode.ts +15 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/components/agent-dashboard.ts +1124 -0
- package/src/modes/components/assistant-message.ts +213 -0
- package/src/modes/components/bash-execution.ts +345 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +45 -0
- package/src/modes/components/btw-panel.ts +103 -0
- package/src/modes/components/compaction-summary-message.ts +51 -0
- package/src/modes/components/context-add-wizard.ts +534 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +238 -0
- package/src/modes/components/custom-message.ts +91 -0
- package/src/modes/components/diff.ts +210 -0
- package/src/modes/components/dynamic-border.ts +25 -0
- package/src/modes/components/extensions/extension-dashboard.ts +326 -0
- package/src/modes/components/extensions/extension-list.ts +492 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +317 -0
- package/src/modes/components/extensions/state-manager.ts +587 -0
- package/src/modes/components/extensions/types.ts +191 -0
- package/src/modes/components/footer.ts +262 -0
- package/src/modes/components/gutter-block.ts +315 -0
- package/src/modes/components/history-search.ts +158 -0
- package/src/modes/components/hook-editor.ts +151 -0
- package/src/modes/components/hook-input.ts +81 -0
- package/src/modes/components/hook-message.ts +100 -0
- package/src/modes/components/hook-selector.ts +192 -0
- package/src/modes/components/index.ts +36 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/mcp-add-wizard.ts +1314 -0
- package/src/modes/components/model-selector.ts +946 -0
- package/src/modes/components/oauth-selector.ts +211 -0
- package/src/modes/components/plugin-selector.ts +95 -0
- package/src/modes/components/plugin-settings.ts +477 -0
- package/src/modes/components/plugins/index.ts +5 -0
- package/src/modes/components/plugins/plugin-dashboard.ts +523 -0
- package/src/modes/components/plugins/plugin-inspector-pane.ts +84 -0
- package/src/modes/components/plugins/plugin-list-pane.ts +109 -0
- package/src/modes/components/plugins/state-manager.ts +229 -0
- package/src/modes/components/plugins/types.ts +51 -0
- package/src/modes/components/plugins/utils.ts +6 -0
- package/src/modes/components/python-execution.ts +244 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +137 -0
- package/src/modes/components/session-observer-overlay.ts +483 -0
- package/src/modes/components/session-selector.ts +343 -0
- package/src/modes/components/settings-defs.ts +568 -0
- package/src/modes/components/settings-selector.ts +641 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-message.ts +90 -0
- package/src/modes/components/status-line/context-gradient.ts +64 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/hex-ansi.ts +29 -0
- package/src/modes/components/status-line/index.ts +4 -0
- package/src/modes/components/status-line/presets.ts +174 -0
- package/src/modes/components/status-line/segments.ts +494 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/token-rate.ts +66 -0
- package/src/modes/components/status-line/types.ts +96 -0
- package/src/modes/components/status-line-segment-editor.ts +361 -0
- package/src/modes/components/status-line.ts +729 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +52 -0
- package/src/modes/components/todo-reminder.ts +54 -0
- package/src/modes/components/tool-execution.ts +781 -0
- package/src/modes/components/tree-selector.ts +908 -0
- package/src/modes/components/ttsr-notification.ts +80 -0
- package/src/modes/components/user-message-selector.ts +141 -0
- package/src/modes/components/user-message.ts +100 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome-checks.ts +313 -0
- package/src/modes/components/welcome.ts +270 -0
- package/src/modes/controllers/btw-controller.ts +193 -0
- package/src/modes/controllers/chord-routing.ts +37 -0
- package/src/modes/controllers/command-controller.ts +1293 -0
- package/src/modes/controllers/context-command-controller.ts +61 -0
- package/src/modes/controllers/event-controller.ts +743 -0
- package/src/modes/controllers/extension-ui-controller.ts +983 -0
- package/src/modes/controllers/input-controller.ts +877 -0
- package/src/modes/controllers/mcp-command-controller.ts +1940 -0
- package/src/modes/controllers/selector-controller.ts +1292 -0
- package/src/modes/controllers/ssh-command-controller.ts +421 -0
- package/src/modes/index.ts +34 -0
- package/src/modes/interactive-mode.ts +1781 -0
- package/src/modes/oauth-manual-input.ts +42 -0
- package/src/modes/print-mode.ts +196 -0
- package/src/modes/prompt-action-autocomplete.ts +231 -0
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +762 -0
- package/src/modes/rpc/rpc-mode.ts +877 -0
- package/src/modes/rpc/rpc-types.ts +322 -0
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared.ts +30 -0
- package/src/modes/theme/defaults/index.ts +7 -0
- package/src/modes/theme/defaults/xcsh-dark.json +113 -0
- package/src/modes/theme/defaults/xcsh-light.json +115 -0
- package/src/modes/theme/mermaid-cache.ts +151 -0
- package/src/modes/theme/mermaid-palette.ts +39 -0
- package/src/modes/theme/theme-schema.json +553 -0
- package/src/modes/theme/theme.ts +2563 -0
- package/src/modes/types.ts +273 -0
- package/src/modes/utils/hotkeys-markdown.ts +60 -0
- package/src/modes/utils/keybinding-matchers.ts +30 -0
- package/src/modes/utils/read-group-outcome-aggregator.ts +55 -0
- package/src/modes/utils/sanitize-error-message.ts +60 -0
- package/src/modes/utils/tools-markdown.ts +28 -0
- package/src/modes/utils/ui-helpers.ts +734 -0
- package/src/plan-mode/approved-plan.ts +55 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +37 -0
- package/src/prompts/agents/designer.md +67 -0
- package/src/prompts/agents/explore.md +61 -0
- package/src/prompts/agents/frontmatter.md +10 -0
- package/src/prompts/agents/init.md +36 -0
- package/src/prompts/agents/librarian.md +121 -0
- package/src/prompts/agents/plan.md +49 -0
- package/src/prompts/agents/reviewer.md +127 -0
- package/src/prompts/agents/task.md +16 -0
- package/src/prompts/ci-green-request.md +35 -0
- package/src/prompts/compaction/branch-summary-context.md +5 -0
- package/src/prompts/compaction/branch-summary-preamble.md +2 -0
- package/src/prompts/compaction/branch-summary.md +30 -0
- package/src/prompts/compaction/compaction-short-summary.md +9 -0
- package/src/prompts/compaction/compaction-summary-context.md +5 -0
- package/src/prompts/compaction/compaction-summary.md +38 -0
- package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
- package/src/prompts/compaction/compaction-update-summary.md +45 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/read-path.md +11 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-request.md +70 -0
- package/src/prompts/system/agent-creation-architect.md +65 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +2 -0
- package/src/prompts/system/custom-system-prompt.md +82 -0
- package/src/prompts/system/eager-todo.md +13 -0
- package/src/prompts/system/file-operations.md +10 -0
- package/src/prompts/system/handoff-document.md +48 -0
- package/src/prompts/system/plan-mode-active.md +114 -0
- package/src/prompts/system/plan-mode-approved.md +23 -0
- package/src/prompts/system/plan-mode-reference.md +14 -0
- package/src/prompts/system/plan-mode-subagent.md +34 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/subagent-submit-reminder.md +7 -0
- package/src/prompts/system/subagent-system-prompt.md +36 -0
- package/src/prompts/system/subagent-user-prompt.md +12 -0
- package/src/prompts/system/summarization-system.md +3 -0
- package/src/prompts/system/system-prompt.md +698 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/web-search.md +28 -0
- package/src/prompts/tools/ask.md +28 -0
- package/src/prompts/tools/ast-edit.md +48 -0
- package/src/prompts/tools/ast-grep.md +54 -0
- package/src/prompts/tools/async-result.md +5 -0
- package/src/prompts/tools/bash.md +67 -0
- package/src/prompts/tools/browser.md +26 -0
- package/src/prompts/tools/calculator.md +10 -0
- package/src/prompts/tools/cancel-job.md +5 -0
- package/src/prompts/tools/catalog-workflow-runner.md +13 -0
- package/src/prompts/tools/checkpoint.md +16 -0
- package/src/prompts/tools/chunk-edit.md +279 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/display-image.md +23 -0
- package/src/prompts/tools/exit-plan-mode.md +41 -0
- package/src/prompts/tools/find.md +26 -0
- package/src/prompts/tools/gemini-image.md +7 -0
- package/src/prompts/tools/grep.md +29 -0
- package/src/prompts/tools/hashline.md +131 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +32 -0
- package/src/prompts/tools/lsp.md +33 -0
- package/src/prompts/tools/patch.md +77 -0
- package/src/prompts/tools/poll.md +5 -0
- package/src/prompts/tools/python.md +60 -0
- package/src/prompts/tools/read-chunk.md +35 -0
- package/src/prompts/tools/read.md +63 -0
- package/src/prompts/tools/render-mermaid.md +9 -0
- package/src/prompts/tools/replace.md +36 -0
- package/src/prompts/tools/resolve.md +8 -0
- package/src/prompts/tools/rewind.md +13 -0
- package/src/prompts/tools/search-tool-bm25.md +34 -0
- package/src/prompts/tools/ssh.md +44 -0
- package/src/prompts/tools/task-summary.md +28 -0
- package/src/prompts/tools/task.md +131 -0
- package/src/prompts/tools/todo-write.md +89 -0
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/prompts/tools/xcsh-api.md +283 -0
- package/src/resource-management/index.ts +42 -0
- package/src/sdk.ts +2123 -0
- package/src/secrets/index.ts +146 -0
- package/src/secrets/obfuscator.ts +290 -0
- package/src/secrets/regex.ts +21 -0
- package/src/services/context-env.ts +102 -0
- package/src/services/xcsh-api-client.ts +230 -0
- package/src/services/xcsh-context-command.ts +803 -0
- package/src/services/xcsh-context-display.ts +49 -0
- package/src/services/xcsh-context-indicators.ts +17 -0
- package/src/services/xcsh-context-segment.ts +39 -0
- package/src/services/xcsh-context.ts +1410 -0
- package/src/services/xcsh-env.ts +107 -0
- package/src/services/xcsh-knowledge.ts +177 -0
- package/src/services/xcsh-table.ts +164 -0
- package/src/session/agent-session.ts +6690 -0
- package/src/session/agent-storage.ts +451 -0
- package/src/session/artifacts.ts +132 -0
- package/src/session/auth-storage.ts +17 -0
- package/src/session/blob-store.ts +135 -0
- package/src/session/compaction/branch-summarization.ts +322 -0
- package/src/session/compaction/compaction.ts +1388 -0
- package/src/session/compaction/index.ts +7 -0
- package/src/session/compaction/pruning.ts +91 -0
- package/src/session/compaction/utils.ts +184 -0
- package/src/session/history-storage.ts +253 -0
- package/src/session/messages.ts +597 -0
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-manager.ts +2816 -0
- package/src/session/session-storage.ts +370 -0
- package/src/session/streaming-output.ts +770 -0
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +1614 -0
- package/src/slash-commands/export-command.ts +178 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/resource-commands.ts +250 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +445 -0
- package/src/ssh/ssh-executor.ts +131 -0
- package/src/ssh/sshfs-mount.ts +137 -0
- package/src/ssh/utils.ts +8 -0
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/system-prompt.ts +688 -0
- package/src/task/agents.ts +166 -0
- package/src/task/commands.ts +131 -0
- package/src/task/discovery.ts +126 -0
- package/src/task/executor.ts +1324 -0
- package/src/task/index.ts +1197 -0
- package/src/task/isolation-backend.ts +72 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/output-manager.ts +107 -0
- package/src/task/parallel.ts +116 -0
- package/src/task/render.ts +1035 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/template.ts +33 -0
- package/src/task/types.ts +230 -0
- package/src/task/worktree.ts +580 -0
- package/src/task/xcsh-command.ts +26 -0
- package/src/thinking.ts +87 -0
- package/src/tools/action-renderer.ts +198 -0
- package/src/tools/archive-reader.ts +315 -0
- package/src/tools/ask.ts +791 -0
- package/src/tools/ast-edit.ts +492 -0
- package/src/tools/ast-grep.ts +453 -0
- package/src/tools/auto-generated-guard.ts +305 -0
- package/src/tools/bash-interactive.ts +383 -0
- package/src/tools/bash-interceptor.ts +67 -0
- package/src/tools/bash-normalize.ts +107 -0
- package/src/tools/bash-skill-urls.ts +246 -0
- package/src/tools/bash.ts +878 -0
- package/src/tools/browser-renderer.ts +129 -0
- package/src/tools/browser.ts +1366 -0
- package/src/tools/calculator.ts +543 -0
- package/src/tools/cancel-job.ts +100 -0
- package/src/tools/catalog-workflow-runner.ts +718 -0
- package/src/tools/checkpoint.ts +133 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/debug.ts +1004 -0
- package/src/tools/display-image-renderer.ts +96 -0
- package/src/tools/display-image.ts +109 -0
- package/src/tools/exit-plan-mode.ts +101 -0
- package/src/tools/fetch.ts +1462 -0
- package/src/tools/find.ts +437 -0
- package/src/tools/fs-cache-invalidation.ts +33 -0
- package/src/tools/gemini-image.ts +1004 -0
- package/src/tools/grep.ts +705 -0
- package/src/tools/index.ts +442 -0
- package/src/tools/inspect-image-renderer.ts +101 -0
- package/src/tools/inspect-image.ts +176 -0
- package/src/tools/json-tree.ts +266 -0
- package/src/tools/jtd-to-json-schema.ts +199 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/mermaid-renderer.ts +127 -0
- package/src/tools/notebook.ts +288 -0
- package/src/tools/output-meta.ts +588 -0
- package/src/tools/path-utils.ts +560 -0
- package/src/tools/plan-mode-guard.ts +41 -0
- package/src/tools/poll-tool.ts +177 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/python.ts +1184 -0
- package/src/tools/read.ts +1591 -0
- package/src/tools/render-mermaid.ts +93 -0
- package/src/tools/render-utils.ts +677 -0
- package/src/tools/renderers.ts +82 -0
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +196 -0
- package/src/tools/review.ts +238 -0
- package/src/tools/search-tool-bm25.ts +288 -0
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/ssh.ts +322 -0
- package/src/tools/submit-result.ts +229 -0
- package/src/tools/todo-render.ts +33 -0
- package/src/tools/todo-write.ts +440 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +93 -0
- package/src/tools/tool-timeouts.ts +30 -0
- package/src/tools/vim.ts +981 -0
- package/src/tools/write.ts +671 -0
- package/src/tools/xcsh-api-renderer.ts +402 -0
- package/src/tools/xcsh-api.ts +887 -0
- package/src/tui/code-cell.ts +101 -0
- package/src/tui/file-list.ts +47 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +175 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +84 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/utils/changelog.ts +98 -0
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/command-args.ts +76 -0
- package/src/utils/commit-message-generator.ts +137 -0
- package/src/utils/edit-mode.ts +50 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +65 -0
- package/src/utils/file-display-mode.ts +37 -0
- package/src/utils/file-mentions.ts +376 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/git.ts +1495 -0
- package/src/utils/gitstatus.ts +140 -0
- package/src/utils/image-convert.ts +27 -0
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/image-passthrough.ts +118 -0
- package/src/utils/image-resize.ts +272 -0
- package/src/utils/image-viewer.ts +17 -0
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/markit.ts +81 -0
- package/src/utils/open.ts +20 -0
- package/src/utils/session-color.ts +55 -0
- package/src/utils/shell-snapshot.ts +188 -0
- package/src/utils/title-generator.ts +193 -0
- package/src/utils/tool-choice.ts +28 -0
- package/src/utils/tools-manager.ts +356 -0
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
- package/src/web/kagi.ts +174 -0
- package/src/web/parallel.ts +346 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +110 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +653 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +452 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +140 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +169 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +120 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +190 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +93 -0
- package/src/web/scrapers/types.ts +267 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +313 -0
- package/src/web/search/errors.ts +120 -0
- package/src/web/search/index.ts +346 -0
- package/src/web/search/params.ts +129 -0
- package/src/web/search/provider.ts +97 -0
- package/src/web/search/providers/anthropic.ts +339 -0
- package/src/web/search/providers/base.ts +35 -0
- package/src/web/search/providers/brave.ts +150 -0
- package/src/web/search/providers/codex.ts +402 -0
- package/src/web/search/providers/exa.ts +264 -0
- package/src/web/search/providers/firecrawl.ts +115 -0
- package/src/web/search/providers/gemini.ts +566 -0
- package/src/web/search/providers/jina.ts +99 -0
- package/src/web/search/providers/kagi.ts +70 -0
- package/src/web/search/providers/kimi.ts +157 -0
- package/src/web/search/providers/parallel.ts +63 -0
- package/src/web/search/providers/perplexity.ts +546 -0
- package/src/web/search/providers/synthetic.ts +113 -0
- package/src/web/search/providers/tavily.ts +162 -0
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +311 -0
- package/src/web/search/render.ts +348 -0
- package/src/web/search/types.ts +438 -0
- package/src/web/search/utils.ts +17 -0
|
@@ -0,0 +1,1462 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { AgentToolResult } from "@f5xc-salesdemos/pi-agent-core";
|
|
4
|
+
import type { ImageContent, TextContent } from "@f5xc-salesdemos/pi-ai";
|
|
5
|
+
import { htmlToMarkdown } from "@f5xc-salesdemos/pi-natives";
|
|
6
|
+
import { type Component, Text } from "@f5xc-salesdemos/pi-tui";
|
|
7
|
+
import { $which, ptree, truncate } from "@f5xc-salesdemos/pi-utils";
|
|
8
|
+
import { parseHTML } from "linkedom";
|
|
9
|
+
import type { Settings } from "../config/settings";
|
|
10
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
|
+
import { type Theme, theme } from "../modes/theme/theme";
|
|
12
|
+
import type { ToolSession } from "../sdk";
|
|
13
|
+
import { DEFAULT_MAX_BYTES, truncateHead } from "../session/streaming-output";
|
|
14
|
+
import { renderStatusLine } from "../tui";
|
|
15
|
+
import { CachedOutputBlock } from "../tui/output-block";
|
|
16
|
+
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
17
|
+
import { ensureTool } from "../utils/tools-manager";
|
|
18
|
+
import { extractWithParallel, findParallelApiKey, getParallelExtractContent } from "../web/parallel";
|
|
19
|
+
import { specialHandlers } from "../web/scrapers";
|
|
20
|
+
import type { RenderResult } from "../web/scrapers/types";
|
|
21
|
+
import { finalizeOutput, loadPage, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
|
22
|
+
import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
|
|
23
|
+
import { applyListLimit } from "./list-limit";
|
|
24
|
+
import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
|
|
25
|
+
import { formatExpandHint, getDomain } from "./render-utils";
|
|
26
|
+
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
27
|
+
import { toolResult } from "./tool-result";
|
|
28
|
+
import { clampTimeout } from "./tool-timeouts";
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Types and Constants
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
const FETCH_DEFAULT_MAX_LINES = 300;
|
|
35
|
+
// Convertible document types handled by markit.
|
|
36
|
+
const CONVERTIBLE_MIMES = new Set([
|
|
37
|
+
"application/pdf",
|
|
38
|
+
"application/msword",
|
|
39
|
+
"application/vnd.ms-powerpoint",
|
|
40
|
+
"application/vnd.ms-excel",
|
|
41
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
42
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
43
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
44
|
+
"application/rtf",
|
|
45
|
+
"application/epub+zip",
|
|
46
|
+
"application/x-ipynb+json",
|
|
47
|
+
"application/zip",
|
|
48
|
+
"image/png",
|
|
49
|
+
"image/jpeg",
|
|
50
|
+
"image/gif",
|
|
51
|
+
"image/webp",
|
|
52
|
+
"audio/mpeg",
|
|
53
|
+
"audio/wav",
|
|
54
|
+
"audio/ogg",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const CONVERTIBLE_EXTENSIONS = new Set([
|
|
58
|
+
".pdf",
|
|
59
|
+
".doc",
|
|
60
|
+
".docx",
|
|
61
|
+
".ppt",
|
|
62
|
+
".pptx",
|
|
63
|
+
".xls",
|
|
64
|
+
".xlsx",
|
|
65
|
+
".rtf",
|
|
66
|
+
".epub",
|
|
67
|
+
".ipynb",
|
|
68
|
+
".png",
|
|
69
|
+
".jpg",
|
|
70
|
+
".jpeg",
|
|
71
|
+
".gif",
|
|
72
|
+
".webp",
|
|
73
|
+
".mp3",
|
|
74
|
+
".wav",
|
|
75
|
+
".ogg",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const IMAGE_MIME_BY_EXTENSION = new Map<string, string>([
|
|
79
|
+
[".png", "image/png"],
|
|
80
|
+
[".jpg", "image/jpeg"],
|
|
81
|
+
[".jpeg", "image/jpeg"],
|
|
82
|
+
[".gif", "image/gif"],
|
|
83
|
+
[".webp", "image/webp"],
|
|
84
|
+
]);
|
|
85
|
+
const SUPPORTED_INLINE_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
86
|
+
const MAX_INLINE_IMAGE_SOURCE_BYTES = 20 * 1024 * 1024;
|
|
87
|
+
const MAX_INLINE_IMAGE_OUTPUT_BYTES = 300 * 1024;
|
|
88
|
+
|
|
89
|
+
// =============================================================================
|
|
90
|
+
// Utilities
|
|
91
|
+
// =============================================================================
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a command exists (cross-platform)
|
|
95
|
+
*/
|
|
96
|
+
function hasCommand(cmd: string): boolean {
|
|
97
|
+
return Boolean($which(cmd));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build llms.txt candidates scoped to the requested URL
|
|
102
|
+
*/
|
|
103
|
+
function buildLlmEndpointCandidates(url: string): string[] {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = new URL(url);
|
|
106
|
+
if (parsed.pathname === "/") {
|
|
107
|
+
return [`${parsed.origin}/.well-known/llms.txt`, `${parsed.origin}/llms.txt`, `${parsed.origin}/llms.md`];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/, "");
|
|
111
|
+
const segments = trimmedPath.split("/").filter(Boolean);
|
|
112
|
+
const scopeDepth = parsed.pathname.endsWith("/") ? segments.length : Math.max(segments.length - 1, 1);
|
|
113
|
+
const endpoints: string[] = [];
|
|
114
|
+
|
|
115
|
+
for (let depth = scopeDepth; depth >= 1; depth--) {
|
|
116
|
+
const scope = `/${segments.slice(0, depth).join("/")}/`;
|
|
117
|
+
endpoints.push(`${parsed.origin}${scope}llms.txt`, `${parsed.origin}${scope}llms.md`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return endpoints;
|
|
121
|
+
} catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Normalize URL (add scheme if missing)
|
|
128
|
+
*/
|
|
129
|
+
function normalizeUrl(url: string): string {
|
|
130
|
+
if (!url.match(/^https?:\/\//i)) {
|
|
131
|
+
return `https://${url}`;
|
|
132
|
+
}
|
|
133
|
+
return url;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function isReadableUrlPath(value: string): boolean {
|
|
137
|
+
return /^https?:\/\//i.test(value) || /^www\./i.test(value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const URL_LINE_RANGE_RE = /^L(\d+)(?:-L?(\d+))?$/i;
|
|
141
|
+
|
|
142
|
+
export interface ParsedReadUrlTarget {
|
|
143
|
+
path: string;
|
|
144
|
+
raw: boolean;
|
|
145
|
+
offset?: number;
|
|
146
|
+
limit?: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function parseReadUrlTarget(readPath: string, sel?: string): ParsedReadUrlTarget | null {
|
|
150
|
+
const embedded = sel ? undefined : tryExtractEmbeddedUrlSelector(readPath);
|
|
151
|
+
const urlPath = embedded?.path ?? readPath;
|
|
152
|
+
if (!isReadableUrlPath(urlPath)) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const selector = sel ?? embedded?.sel;
|
|
157
|
+
const raw = selector === "raw";
|
|
158
|
+
const lineMatch = selector ? URL_LINE_RANGE_RE.exec(selector) : null;
|
|
159
|
+
if (lineMatch) {
|
|
160
|
+
const startLine = Number.parseInt(lineMatch[1]!, 10);
|
|
161
|
+
if (startLine < 1) {
|
|
162
|
+
throw new ToolError("L0 is invalid; lines are 1-indexed. Use sel=L1.");
|
|
163
|
+
}
|
|
164
|
+
const endLine = lineMatch[2] ? Number.parseInt(lineMatch[2], 10) : undefined;
|
|
165
|
+
if (endLine !== undefined && endLine < startLine) {
|
|
166
|
+
throw new ToolError(`Invalid range L${startLine}-L${endLine}: end must be >= start.`);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
path: urlPath,
|
|
170
|
+
raw: false,
|
|
171
|
+
offset: startLine,
|
|
172
|
+
limit: endLine !== undefined ? endLine - startLine + 1 : undefined,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { path: urlPath, raw };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function tryExtractEmbeddedUrlSelector(readPath: string): { path: string; sel?: string } | null {
|
|
180
|
+
const lastColonIndex = readPath.lastIndexOf(":");
|
|
181
|
+
if (lastColonIndex <= 0) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const candidateSelector = readPath.slice(lastColonIndex + 1);
|
|
186
|
+
const isEmbeddedSelector = candidateSelector === "raw" || URL_LINE_RANGE_RE.test(candidateSelector);
|
|
187
|
+
if (!isEmbeddedSelector) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const basePath = readPath.slice(0, lastColonIndex);
|
|
192
|
+
if (!isReadableUrlPath(basePath)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
new URL(basePath.startsWith("http://") || basePath.startsWith("https://") ? basePath : `https://${basePath}`);
|
|
198
|
+
return { path: basePath, sel: candidateSelector };
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Normalize MIME type (lowercase, strip charset/params)
|
|
206
|
+
*/
|
|
207
|
+
function normalizeMime(contentType: string): string {
|
|
208
|
+
return contentType.split(";")[0].trim().toLowerCase();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get extension from URL or Content-Disposition
|
|
213
|
+
*/
|
|
214
|
+
function getExtensionHint(url: string, contentDisposition?: string): string {
|
|
215
|
+
// Try Content-Disposition filename first
|
|
216
|
+
if (contentDisposition) {
|
|
217
|
+
const match = contentDisposition.match(/filename[*]?=["']?([^"';\n]+)/i);
|
|
218
|
+
if (match) {
|
|
219
|
+
const ext = path.extname(match[1]).toLowerCase();
|
|
220
|
+
if (ext) return ext;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Fall back to URL path
|
|
225
|
+
try {
|
|
226
|
+
const pathname = new URL(url).pathname;
|
|
227
|
+
const ext = path.extname(pathname).toLowerCase();
|
|
228
|
+
if (ext) return ext;
|
|
229
|
+
} catch {}
|
|
230
|
+
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check if content type is convertible via markit.
|
|
236
|
+
*/
|
|
237
|
+
function isConvertible(mime: string, extensionHint: string): boolean {
|
|
238
|
+
if (CONVERTIBLE_MIMES.has(mime)) return true;
|
|
239
|
+
if (mime === "application/octet-stream" && CONVERTIBLE_EXTENSIONS.has(extensionHint)) return true;
|
|
240
|
+
if (CONVERTIBLE_EXTENSIONS.has(extensionHint)) return true;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function resolveImageMimeType(mime: string, extensionHint: string): string | null {
|
|
245
|
+
if (mime.startsWith("image/")) return mime;
|
|
246
|
+
const shouldUseExtensionHint =
|
|
247
|
+
mime.length === 0 || mime === "application/octet-stream" || mime === "binary/octet-stream" || mime === "unknown";
|
|
248
|
+
if (!shouldUseExtensionHint) return null;
|
|
249
|
+
return IMAGE_MIME_BY_EXTENSION.get(extensionHint) ?? null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isInlineImageMimeTypeSupported(mimeType: string): boolean {
|
|
253
|
+
return SUPPORTED_INLINE_IMAGE_MIME_TYPES.has(mimeType);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Check if content looks like HTML
|
|
258
|
+
*/
|
|
259
|
+
function looksLikeHtml(content: string): boolean {
|
|
260
|
+
const trimmed = content.trim().toLowerCase();
|
|
261
|
+
return (
|
|
262
|
+
trimmed.startsWith("<!doctype") ||
|
|
263
|
+
trimmed.startsWith("<html") ||
|
|
264
|
+
trimmed.startsWith("<head") ||
|
|
265
|
+
trimmed.startsWith("<body")
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Try fetching URL with .md appended (llms.txt convention)
|
|
271
|
+
*/
|
|
272
|
+
async function tryMdSuffix(url: string, timeout: number, signal?: AbortSignal): Promise<string | null> {
|
|
273
|
+
const candidates: string[] = [];
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const parsed = new URL(url);
|
|
277
|
+
const pathname = parsed.pathname;
|
|
278
|
+
|
|
279
|
+
if (pathname.endsWith("/")) {
|
|
280
|
+
// /foo/bar/ -> /foo/bar/index.html.md
|
|
281
|
+
candidates.push(`${parsed.origin}${pathname}index.html.md`);
|
|
282
|
+
} else if (pathname.includes(".")) {
|
|
283
|
+
// /foo/bar.html -> /foo/bar.html.md
|
|
284
|
+
candidates.push(`${parsed.origin}${pathname}.md`);
|
|
285
|
+
} else {
|
|
286
|
+
// /foo/bar -> /foo/bar.md
|
|
287
|
+
candidates.push(`${parsed.origin}${pathname}.md`);
|
|
288
|
+
}
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (signal?.aborted) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const candidate of candidates) {
|
|
298
|
+
if (signal?.aborted) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
const result = await loadPage(candidate, { timeout, signal });
|
|
302
|
+
if (result.ok && result.content.trim().length > 100 && !looksLikeHtml(result.content)) {
|
|
303
|
+
return result.content;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Try to fetch LLM-friendly endpoints
|
|
312
|
+
*/
|
|
313
|
+
async function tryLlmEndpoints(
|
|
314
|
+
url: string,
|
|
315
|
+
timeout: number,
|
|
316
|
+
signal?: AbortSignal,
|
|
317
|
+
): Promise<{ content: string; endpoint: string } | null> {
|
|
318
|
+
const endpoints = buildLlmEndpointCandidates(url);
|
|
319
|
+
|
|
320
|
+
if (signal?.aborted || endpoints.length === 0) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
for (const endpoint of endpoints) {
|
|
325
|
+
if (signal?.aborted) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const result = await loadPage(endpoint, { timeout: Math.min(timeout, 5), signal });
|
|
329
|
+
if (result.ok && result.content.trim().length > 100 && !looksLikeHtml(result.content)) {
|
|
330
|
+
return { content: result.content, endpoint };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Try content negotiation for markdown/plain
|
|
338
|
+
*/
|
|
339
|
+
async function tryContentNegotiation(
|
|
340
|
+
url: string,
|
|
341
|
+
timeout: number,
|
|
342
|
+
signal?: AbortSignal,
|
|
343
|
+
): Promise<{ content: string; type: string } | null> {
|
|
344
|
+
if (signal?.aborted) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const result = await loadPage(url, {
|
|
349
|
+
timeout,
|
|
350
|
+
headers: { Accept: "text/markdown, text/plain;q=0.9, text/html;q=0.8" },
|
|
351
|
+
signal,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!result.ok) return null;
|
|
355
|
+
|
|
356
|
+
const mime = normalizeMime(result.contentType);
|
|
357
|
+
if ((mime.includes("markdown") || mime === "text/plain") && !looksLikeHtml(result.content)) {
|
|
358
|
+
return { content: result.content, type: result.contentType };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Read a single HTML attribute from a tag string
|
|
366
|
+
*/
|
|
367
|
+
function getHtmlAttribute(tag: string, attribute: string): string | null {
|
|
368
|
+
const pattern = new RegExp(`\\b${attribute}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`, "i");
|
|
369
|
+
const match = tag.match(pattern);
|
|
370
|
+
if (!match) return null;
|
|
371
|
+
return (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Extract bounded <head> markup to avoid expensive whole-page parsing
|
|
376
|
+
*/
|
|
377
|
+
function extractHeadHtml(html: string): string {
|
|
378
|
+
const lower = html.toLowerCase();
|
|
379
|
+
const headStart = lower.indexOf("<head");
|
|
380
|
+
if (headStart === -1) {
|
|
381
|
+
return html.slice(0, 32 * 1024);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const headTagEnd = html.indexOf(">", headStart);
|
|
385
|
+
if (headTagEnd === -1) {
|
|
386
|
+
return html.slice(headStart, headStart + 32 * 1024);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const headEnd = lower.indexOf("</head>", headTagEnd + 1);
|
|
390
|
+
const fallbackEnd = Math.min(html.length, headTagEnd + 1 + 32 * 1024);
|
|
391
|
+
return html.slice(headStart, headEnd === -1 ? fallbackEnd : headEnd + 7);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Parse alternate links from HTML head
|
|
396
|
+
*/
|
|
397
|
+
function parseAlternateLinks(html: string, pageUrl: string): string[] {
|
|
398
|
+
const links: string[] = [];
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const pagePath = new URL(pageUrl).pathname;
|
|
402
|
+
const headHtml = extractHeadHtml(html);
|
|
403
|
+
const linkTags = headHtml.match(/<link\b[^>]*>/gi) ?? [];
|
|
404
|
+
|
|
405
|
+
for (const tag of linkTags) {
|
|
406
|
+
const rel = getHtmlAttribute(tag, "rel")?.toLowerCase() ?? "";
|
|
407
|
+
const relTokens = rel.split(/\s+/).filter(Boolean);
|
|
408
|
+
if (!relTokens.includes("alternate")) continue;
|
|
409
|
+
|
|
410
|
+
const href = getHtmlAttribute(tag, "href");
|
|
411
|
+
const type = getHtmlAttribute(tag, "type")?.toLowerCase() ?? "";
|
|
412
|
+
if (!href) continue;
|
|
413
|
+
|
|
414
|
+
// Skip site-wide feeds
|
|
415
|
+
if (
|
|
416
|
+
href.includes("RecentChanges") ||
|
|
417
|
+
href.includes("Special:") ||
|
|
418
|
+
href.includes("/feed/") ||
|
|
419
|
+
href.includes("action=feed")
|
|
420
|
+
) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (type.includes("markdown")) {
|
|
425
|
+
links.push(href);
|
|
426
|
+
} else if (
|
|
427
|
+
(type.includes("rss") || type.includes("atom") || type.includes("feed")) &&
|
|
428
|
+
(href.includes(pagePath) || href.includes("comments"))
|
|
429
|
+
) {
|
|
430
|
+
links.push(href);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch {}
|
|
434
|
+
|
|
435
|
+
return links;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Extract document links from HTML (for PDF/DOCX wrapper pages)
|
|
440
|
+
*/
|
|
441
|
+
function extractDocumentLinks(html: string, baseUrl: string): string[] {
|
|
442
|
+
const links: string[] = [];
|
|
443
|
+
const seen = new Set<string>();
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const anchorTags = html.slice(0, 512 * 1024).match(/<a\b[^>]*>/gi) ?? [];
|
|
447
|
+
for (const tag of anchorTags) {
|
|
448
|
+
const href = getHtmlAttribute(tag, "href");
|
|
449
|
+
if (!href) continue;
|
|
450
|
+
|
|
451
|
+
const ext = path.extname(href).toLowerCase();
|
|
452
|
+
if (!CONVERTIBLE_EXTENSIONS.has(ext)) continue;
|
|
453
|
+
|
|
454
|
+
const resolved = href.startsWith("http") ? href : new URL(href, baseUrl).href;
|
|
455
|
+
if (seen.has(resolved)) continue;
|
|
456
|
+
seen.add(resolved);
|
|
457
|
+
links.push(resolved);
|
|
458
|
+
if (links.length >= 20) break;
|
|
459
|
+
}
|
|
460
|
+
} catch {}
|
|
461
|
+
|
|
462
|
+
return links;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Strip CDATA wrapper and clean text
|
|
467
|
+
*/
|
|
468
|
+
function cleanFeedText(text: string): string {
|
|
469
|
+
return text
|
|
470
|
+
.replace(/<!\[CDATA\[/g, "")
|
|
471
|
+
.replace(/\]\]>/g, "")
|
|
472
|
+
.replace(/</g, "<")
|
|
473
|
+
.replace(/>/g, ">")
|
|
474
|
+
.replace(/&/g, "&")
|
|
475
|
+
.replace(/"/g, '"')
|
|
476
|
+
.replace(/<[^>]+>/g, "") // Strip HTML tags
|
|
477
|
+
.trim();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Parse RSS/Atom feed to markdown
|
|
482
|
+
*/
|
|
483
|
+
function parseFeedToMarkdown(content: string, maxItems = 10): string {
|
|
484
|
+
try {
|
|
485
|
+
const doc = parseHTML(content).document;
|
|
486
|
+
|
|
487
|
+
// Try RSS
|
|
488
|
+
const channel = doc.querySelector("channel");
|
|
489
|
+
if (channel) {
|
|
490
|
+
const title = cleanFeedText(channel.querySelector("title")?.text || "RSS Feed");
|
|
491
|
+
const items = channel.querySelectorAll("item").slice(0, maxItems);
|
|
492
|
+
|
|
493
|
+
let md = `# ${title}\n\n`;
|
|
494
|
+
for (const item of items) {
|
|
495
|
+
const itemTitle = cleanFeedText(item.querySelector("title")?.text || "Untitled");
|
|
496
|
+
const link = cleanFeedText(item.querySelector("link")?.text || "");
|
|
497
|
+
const pubDate = cleanFeedText(item.querySelector("pubDate")?.text || "");
|
|
498
|
+
const desc = cleanFeedText(item.querySelector("description")?.text || "");
|
|
499
|
+
|
|
500
|
+
md += `## ${itemTitle}\n`;
|
|
501
|
+
if (pubDate) md += `*${pubDate}*\n\n`;
|
|
502
|
+
if (desc) md += `${desc.slice(0, 500)}${desc.length > 500 ? "..." : ""}\n\n`;
|
|
503
|
+
if (link) md += `[Read more](${link})\n\n`;
|
|
504
|
+
md += "---\n\n";
|
|
505
|
+
}
|
|
506
|
+
return md;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Try Atom
|
|
510
|
+
const feed = doc.querySelector("feed");
|
|
511
|
+
if (feed) {
|
|
512
|
+
const title = cleanFeedText(feed.querySelector("title")?.text || "Atom Feed");
|
|
513
|
+
const entries = feed.querySelectorAll("entry").slice(0, maxItems);
|
|
514
|
+
|
|
515
|
+
let md = `# ${title}\n\n`;
|
|
516
|
+
for (const entry of entries) {
|
|
517
|
+
const entryTitle = cleanFeedText(entry.querySelector("title")?.text || "Untitled");
|
|
518
|
+
const link = entry.querySelector("link")?.getAttribute("href") || "";
|
|
519
|
+
const updated = cleanFeedText(entry.querySelector("updated")?.text || "");
|
|
520
|
+
const summary = cleanFeedText(
|
|
521
|
+
entry.querySelector("summary")?.text || entry.querySelector("content")?.text || "",
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
md += `## ${entryTitle}\n`;
|
|
525
|
+
if (updated) md += `*${updated}*\n\n`;
|
|
526
|
+
if (summary) md += `${summary.slice(0, 500)}${summary.length > 500 ? "..." : ""}\n\n`;
|
|
527
|
+
if (link) md += `[Read more](${link})\n\n`;
|
|
528
|
+
md += "---\n\n";
|
|
529
|
+
}
|
|
530
|
+
return md;
|
|
531
|
+
}
|
|
532
|
+
} catch {}
|
|
533
|
+
|
|
534
|
+
return content; // Fall back to raw content
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Render HTML to markdown using Parallel, jina, trafilatura, lynx (in order of preference)
|
|
539
|
+
*/
|
|
540
|
+
async function renderHtmlToText(
|
|
541
|
+
url: string,
|
|
542
|
+
html: string,
|
|
543
|
+
timeout: number,
|
|
544
|
+
settings: Settings,
|
|
545
|
+
userSignal?: AbortSignal,
|
|
546
|
+
): Promise<{ content: string; ok: boolean; method: string }> {
|
|
547
|
+
const signal = ptree.combineSignals(userSignal, timeout * 1000);
|
|
548
|
+
const execOptions = {
|
|
549
|
+
mode: "group" as const,
|
|
550
|
+
allowNonZero: true,
|
|
551
|
+
allowAbort: true,
|
|
552
|
+
stderr: "full" as const,
|
|
553
|
+
signal,
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// Try Parallel extract first when credentials are configured
|
|
557
|
+
if (settings.get("providers.parallelFetch") && (await findParallelApiKey())) {
|
|
558
|
+
try {
|
|
559
|
+
const parallelResult = await extractWithParallel([url], {
|
|
560
|
+
objective: "Extract the main content",
|
|
561
|
+
excerpts: true,
|
|
562
|
+
fullContent: false,
|
|
563
|
+
signal,
|
|
564
|
+
});
|
|
565
|
+
const firstDocument = parallelResult.results[0];
|
|
566
|
+
if (firstDocument) {
|
|
567
|
+
const content = getParallelExtractContent(firstDocument);
|
|
568
|
+
if (content.trim().length > 100 && !isLowQualityOutput(content)) {
|
|
569
|
+
return { content, ok: true, method: "parallel" };
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch {
|
|
573
|
+
// Parallel extract failed, continue to next method
|
|
574
|
+
signal?.throwIfAborted();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Try jina first (reader API)
|
|
579
|
+
try {
|
|
580
|
+
const jinaUrl = `https://r.jina.ai/${url}`;
|
|
581
|
+
const response = await fetch(jinaUrl, {
|
|
582
|
+
headers: { Accept: "text/markdown" },
|
|
583
|
+
signal,
|
|
584
|
+
});
|
|
585
|
+
if (response.ok) {
|
|
586
|
+
const content = await response.text();
|
|
587
|
+
if (content.trim().length > 100 && !isLowQualityOutput(content)) {
|
|
588
|
+
return { content, ok: true, method: "jina" };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch {
|
|
592
|
+
// Jina failed, continue to next method
|
|
593
|
+
signal?.throwIfAborted();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Try trafilatura (auto-install via uv/pip)
|
|
597
|
+
const trafilatura = await ensureTool("trafilatura", { signal, silent: true });
|
|
598
|
+
if (trafilatura) {
|
|
599
|
+
const result = await ptree.exec([trafilatura, "-u", url, "--output-format", "markdown"], execOptions);
|
|
600
|
+
if (result.ok && result.stdout.trim().length > 100) {
|
|
601
|
+
return { content: result.stdout, ok: true, method: "trafilatura" };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Try lynx (can't auto-install, system package)
|
|
606
|
+
const lynx = hasCommand("lynx");
|
|
607
|
+
if (lynx) {
|
|
608
|
+
const result = await ptree.exec(["lynx", "-dump", "-nolist", "-width", "250", url], execOptions);
|
|
609
|
+
if (result.ok) {
|
|
610
|
+
return { content: result.stdout, ok: true, method: "lynx" };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Fall back to native converter (fastest, no network/subprocess)
|
|
615
|
+
try {
|
|
616
|
+
const content = await htmlToMarkdown(html, { cleanContent: true });
|
|
617
|
+
if (content.trim().length > 100 && !isLowQualityOutput(content)) {
|
|
618
|
+
return { content, ok: true, method: "native" };
|
|
619
|
+
}
|
|
620
|
+
} catch {
|
|
621
|
+
// Native converter failed, continue to next method
|
|
622
|
+
signal?.throwIfAborted();
|
|
623
|
+
}
|
|
624
|
+
return { content: "", ok: false, method: "none" };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Check if lynx output looks JS-gated or mostly navigation
|
|
629
|
+
*/
|
|
630
|
+
function isLowQualityOutput(content: string): boolean {
|
|
631
|
+
const lower = content.toLowerCase();
|
|
632
|
+
|
|
633
|
+
// JS-gated indicators
|
|
634
|
+
const jsGated = [
|
|
635
|
+
"enable javascript",
|
|
636
|
+
"javascript required",
|
|
637
|
+
"turn on javascript",
|
|
638
|
+
"please enable javascript",
|
|
639
|
+
"browser not supported",
|
|
640
|
+
];
|
|
641
|
+
if (content.length < 1024 && jsGated.some(t => lower.includes(t))) {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Mostly navigation (high link/menu density)
|
|
646
|
+
const lines = content.split("\n").filter(l => l.trim());
|
|
647
|
+
const shortLines = lines.filter(l => l.trim().length < 40);
|
|
648
|
+
if (lines.length > 10 && shortLines.length / lines.length > 0.7) {
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Format JSON
|
|
657
|
+
*/
|
|
658
|
+
function formatJson(content: string): string {
|
|
659
|
+
try {
|
|
660
|
+
return JSON.stringify(JSON.parse(content), null, 2);
|
|
661
|
+
} catch {
|
|
662
|
+
return content;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
interface FetchImagePayload {
|
|
667
|
+
data: string;
|
|
668
|
+
mimeType: string;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
type FetchRenderResult = RenderResult & {
|
|
672
|
+
image?: FetchImagePayload;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// =============================================================================
|
|
676
|
+
// Unified Special Handler Dispatch
|
|
677
|
+
// =============================================================================
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Try all special handlers
|
|
681
|
+
*/
|
|
682
|
+
async function handleSpecialUrls(
|
|
683
|
+
url: string,
|
|
684
|
+
timeout: number,
|
|
685
|
+
signal?: AbortSignal,
|
|
686
|
+
): Promise<FetchRenderResult | null> {
|
|
687
|
+
for (const handler of specialHandlers) {
|
|
688
|
+
if (signal?.aborted) {
|
|
689
|
+
throw new ToolAbortError();
|
|
690
|
+
}
|
|
691
|
+
const result = await handler(url, timeout, signal);
|
|
692
|
+
if (result) return result;
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// =============================================================================
|
|
698
|
+
// Main Render Function
|
|
699
|
+
// =============================================================================
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Main render function implementing the full pipeline
|
|
703
|
+
*/
|
|
704
|
+
async function renderUrl(
|
|
705
|
+
url: string,
|
|
706
|
+
timeout: number,
|
|
707
|
+
raw: boolean,
|
|
708
|
+
settings: Settings,
|
|
709
|
+
signal?: AbortSignal,
|
|
710
|
+
): Promise<FetchRenderResult> {
|
|
711
|
+
const notes: string[] = [];
|
|
712
|
+
const fetchedAt = new Date().toISOString();
|
|
713
|
+
if (signal?.aborted) {
|
|
714
|
+
throw new ToolAbortError();
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Handle internal protocol URLs (e.g., pi-internal://) - return empty
|
|
718
|
+
if (url.startsWith("pi-internal://")) {
|
|
719
|
+
return {
|
|
720
|
+
url,
|
|
721
|
+
finalUrl: url,
|
|
722
|
+
contentType: "text/plain",
|
|
723
|
+
method: "internal",
|
|
724
|
+
content: "",
|
|
725
|
+
fetchedAt,
|
|
726
|
+
truncated: false,
|
|
727
|
+
notes: ["Internal protocol URL - no external content"],
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Step 0: Normalize URL (ensure scheme for special handlers)
|
|
732
|
+
url = normalizeUrl(url);
|
|
733
|
+
|
|
734
|
+
// Step 1: Try special handlers for known sites (unless raw mode)
|
|
735
|
+
if (!raw) {
|
|
736
|
+
const specialResult = await handleSpecialUrls(url, timeout, signal);
|
|
737
|
+
if (specialResult) return specialResult;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Step 2: Fetch page
|
|
741
|
+
const response = await loadPage(url, { timeout, signal });
|
|
742
|
+
if (signal?.aborted) {
|
|
743
|
+
throw new ToolAbortError();
|
|
744
|
+
}
|
|
745
|
+
if (!response.ok) {
|
|
746
|
+
return {
|
|
747
|
+
url,
|
|
748
|
+
finalUrl: response.finalUrl || url,
|
|
749
|
+
contentType: response.contentType || "unknown",
|
|
750
|
+
method: "failed",
|
|
751
|
+
content: "",
|
|
752
|
+
fetchedAt,
|
|
753
|
+
truncated: false,
|
|
754
|
+
notes: [response.status ? `Failed to fetch URL (HTTP ${response.status})` : "Failed to fetch URL"],
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const { finalUrl, content: rawContent } = response;
|
|
759
|
+
const mime = normalizeMime(response.contentType);
|
|
760
|
+
const extHint = getExtensionHint(finalUrl);
|
|
761
|
+
|
|
762
|
+
const imageMimeType = resolveImageMimeType(mime, extHint);
|
|
763
|
+
let skipConvertibleBinaryRetry = false;
|
|
764
|
+
if (imageMimeType) {
|
|
765
|
+
if (!isInlineImageMimeTypeSupported(imageMimeType)) {
|
|
766
|
+
notes.push(
|
|
767
|
+
`Image MIME type ${imageMimeType} is unsupported for inline model serialization; returning text metadata only`,
|
|
768
|
+
);
|
|
769
|
+
const shouldTryConvertibleFallback = isConvertible(mime, extHint);
|
|
770
|
+
if (shouldTryConvertibleFallback) {
|
|
771
|
+
notes.push("Attempting binary conversion fallback for unsupported image MIME type");
|
|
772
|
+
} else {
|
|
773
|
+
notes.push("Falling back to textual rendering from initial response");
|
|
774
|
+
}
|
|
775
|
+
skipConvertibleBinaryRetry = !shouldTryConvertibleFallback;
|
|
776
|
+
} else {
|
|
777
|
+
const binary = await fetchBinary(finalUrl, timeout, signal);
|
|
778
|
+
if (binary.ok) {
|
|
779
|
+
notes.push("Fetched image binary");
|
|
780
|
+
const conversionExtension = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
|
|
781
|
+
let convertedText: string | null = null;
|
|
782
|
+
const converted = await convertWithMarkit(binary.buffer, conversionExtension, timeout, signal);
|
|
783
|
+
if (converted.ok) {
|
|
784
|
+
if (converted.content.trim().length > 50) {
|
|
785
|
+
notes.push("Converted with markit");
|
|
786
|
+
convertedText = converted.content;
|
|
787
|
+
} else {
|
|
788
|
+
notes.push("markit conversion produced no usable output");
|
|
789
|
+
}
|
|
790
|
+
} else if (converted.error) {
|
|
791
|
+
notes.push(`markit conversion failed: ${converted.error}`);
|
|
792
|
+
} else {
|
|
793
|
+
notes.push("markit conversion failed");
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (binary.buffer.byteLength > MAX_INLINE_IMAGE_SOURCE_BYTES) {
|
|
797
|
+
notes.push(
|
|
798
|
+
`Image exceeds inline source limit (${binary.buffer.byteLength} bytes > ${MAX_INLINE_IMAGE_SOURCE_BYTES} bytes)`,
|
|
799
|
+
);
|
|
800
|
+
const output = finalizeOutput(
|
|
801
|
+
convertedText ?? `Fetched image content (${imageMimeType}), but it is too large to inline render.`,
|
|
802
|
+
);
|
|
803
|
+
return {
|
|
804
|
+
url,
|
|
805
|
+
finalUrl,
|
|
806
|
+
contentType: imageMimeType,
|
|
807
|
+
method: convertedText ? "markit" : "image-too-large",
|
|
808
|
+
content: output.content,
|
|
809
|
+
fetchedAt,
|
|
810
|
+
truncated: output.truncated,
|
|
811
|
+
notes,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const resized = await resizeImage(
|
|
816
|
+
{ type: "image", data: Buffer.from(binary.buffer).toBase64(), mimeType: imageMimeType },
|
|
817
|
+
{ maxBytes: MAX_INLINE_IMAGE_OUTPUT_BYTES },
|
|
818
|
+
);
|
|
819
|
+
const isDecodedImage =
|
|
820
|
+
resized.originalWidth > 0 && resized.originalHeight > 0 && resized.width > 0 && resized.height > 0;
|
|
821
|
+
if (!isDecodedImage) {
|
|
822
|
+
notes.push(`Fetched payload could not be decoded as ${imageMimeType}; returning text metadata only`);
|
|
823
|
+
const output = finalizeOutput(
|
|
824
|
+
convertedText ??
|
|
825
|
+
rawContent ??
|
|
826
|
+
`Fetched payload was labeled ${imageMimeType}, but bytes were not a valid image.`,
|
|
827
|
+
);
|
|
828
|
+
return {
|
|
829
|
+
url,
|
|
830
|
+
finalUrl,
|
|
831
|
+
contentType: imageMimeType,
|
|
832
|
+
method: convertedText ? "markit" : "image-invalid",
|
|
833
|
+
content: output.content,
|
|
834
|
+
fetchedAt,
|
|
835
|
+
truncated: output.truncated,
|
|
836
|
+
notes,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
if (resized.buffer.length > MAX_INLINE_IMAGE_OUTPUT_BYTES) {
|
|
840
|
+
notes.push(
|
|
841
|
+
`Image exceeds inline output limit after resize (${resized.buffer.length} bytes > ${MAX_INLINE_IMAGE_OUTPUT_BYTES} bytes)`,
|
|
842
|
+
);
|
|
843
|
+
const output = finalizeOutput(
|
|
844
|
+
convertedText ?? `Fetched image content (${imageMimeType}), but it is too large to inline render.`,
|
|
845
|
+
);
|
|
846
|
+
return {
|
|
847
|
+
url,
|
|
848
|
+
finalUrl,
|
|
849
|
+
contentType: imageMimeType,
|
|
850
|
+
method: convertedText ? "markit" : "image-too-large",
|
|
851
|
+
content: output.content,
|
|
852
|
+
fetchedAt,
|
|
853
|
+
truncated: output.truncated,
|
|
854
|
+
notes,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const dimensionNote = formatDimensionNote(resized);
|
|
859
|
+
let imageSummary = convertedText ?? `Fetched image content (${resized.mimeType}).`;
|
|
860
|
+
if (dimensionNote) {
|
|
861
|
+
imageSummary += `\n${dimensionNote}`;
|
|
862
|
+
}
|
|
863
|
+
const output = finalizeOutput(imageSummary);
|
|
864
|
+
return {
|
|
865
|
+
url,
|
|
866
|
+
finalUrl,
|
|
867
|
+
contentType: resized.mimeType,
|
|
868
|
+
method: "image",
|
|
869
|
+
content: output.content,
|
|
870
|
+
fetchedAt,
|
|
871
|
+
truncated: output.truncated,
|
|
872
|
+
notes,
|
|
873
|
+
image: {
|
|
874
|
+
data: resized.data,
|
|
875
|
+
mimeType: resized.mimeType,
|
|
876
|
+
},
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
notes.push(binary.error ? `Binary fetch failed: ${binary.error}` : "Binary fetch failed");
|
|
880
|
+
notes.push("Falling back to textual rendering from initial response");
|
|
881
|
+
skipConvertibleBinaryRetry = true;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Step 3: Handle convertible binary files (PDF, DOCX, etc.)
|
|
886
|
+
if (!skipConvertibleBinaryRetry && isConvertible(mime, extHint)) {
|
|
887
|
+
const binary = await fetchBinary(finalUrl, timeout, signal);
|
|
888
|
+
if (binary.ok) {
|
|
889
|
+
const ext = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
|
|
890
|
+
const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
|
|
891
|
+
if (converted.ok) {
|
|
892
|
+
if (converted.content.trim().length > 50) {
|
|
893
|
+
notes.push("Converted with markit");
|
|
894
|
+
const output = finalizeOutput(converted.content);
|
|
895
|
+
return {
|
|
896
|
+
url,
|
|
897
|
+
finalUrl,
|
|
898
|
+
contentType: mime,
|
|
899
|
+
method: "markit",
|
|
900
|
+
content: output.content,
|
|
901
|
+
fetchedAt,
|
|
902
|
+
truncated: output.truncated,
|
|
903
|
+
notes,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
notes.push("markit conversion produced no usable output");
|
|
907
|
+
} else if (converted.error) {
|
|
908
|
+
notes.push(`markit conversion failed: ${converted.error}`);
|
|
909
|
+
} else {
|
|
910
|
+
notes.push("markit conversion failed");
|
|
911
|
+
}
|
|
912
|
+
} else if (binary.error) {
|
|
913
|
+
notes.push(`Binary fetch failed: ${binary.error}`);
|
|
914
|
+
} else {
|
|
915
|
+
notes.push("Binary fetch failed");
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Step 4: Handle non-HTML text content
|
|
920
|
+
const isHtml = mime.includes("html") || mime.includes("xhtml");
|
|
921
|
+
const isJson = mime.includes("json");
|
|
922
|
+
const isXml = mime.includes("xml") && !isHtml;
|
|
923
|
+
const isText = mime.includes("text/plain") || mime.includes("text/markdown");
|
|
924
|
+
const isFeed = mime.includes("rss") || mime.includes("atom") || mime.includes("feed");
|
|
925
|
+
|
|
926
|
+
if (isJson) {
|
|
927
|
+
const output = finalizeOutput(formatJson(rawContent));
|
|
928
|
+
return {
|
|
929
|
+
url,
|
|
930
|
+
finalUrl,
|
|
931
|
+
contentType: mime,
|
|
932
|
+
method: "json",
|
|
933
|
+
content: output.content,
|
|
934
|
+
fetchedAt,
|
|
935
|
+
truncated: output.truncated,
|
|
936
|
+
notes,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (isFeed || (isXml && (rawContent.includes("<rss") || rawContent.includes("<feed")))) {
|
|
941
|
+
const parsed = parseFeedToMarkdown(rawContent);
|
|
942
|
+
const output = finalizeOutput(parsed);
|
|
943
|
+
return {
|
|
944
|
+
url,
|
|
945
|
+
finalUrl,
|
|
946
|
+
contentType: mime,
|
|
947
|
+
method: "feed",
|
|
948
|
+
content: output.content,
|
|
949
|
+
fetchedAt,
|
|
950
|
+
truncated: output.truncated,
|
|
951
|
+
notes,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
if (isText && !looksLikeHtml(rawContent)) {
|
|
956
|
+
const output = finalizeOutput(rawContent);
|
|
957
|
+
return {
|
|
958
|
+
url,
|
|
959
|
+
finalUrl,
|
|
960
|
+
contentType: mime,
|
|
961
|
+
method: "text",
|
|
962
|
+
content: output.content,
|
|
963
|
+
fetchedAt,
|
|
964
|
+
truncated: output.truncated,
|
|
965
|
+
notes,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Step 5: For HTML, try digestible formats first (unless raw mode)
|
|
970
|
+
if (isHtml && !raw) {
|
|
971
|
+
// 5A: Check for page-specific markdown alternate
|
|
972
|
+
const alternates = parseAlternateLinks(rawContent, finalUrl);
|
|
973
|
+
const markdownAlt = alternates.find(alt => alt.endsWith(".md") || alt.includes("markdown"));
|
|
974
|
+
if (markdownAlt) {
|
|
975
|
+
const resolved = markdownAlt.startsWith("http") ? markdownAlt : new URL(markdownAlt, finalUrl).href;
|
|
976
|
+
const altResult = await loadPage(resolved, { timeout, signal });
|
|
977
|
+
if (altResult.ok && altResult.content.trim().length > 100 && !looksLikeHtml(altResult.content)) {
|
|
978
|
+
notes.push(`Used markdown alternate: ${resolved}`);
|
|
979
|
+
const output = finalizeOutput(altResult.content);
|
|
980
|
+
return {
|
|
981
|
+
url,
|
|
982
|
+
finalUrl,
|
|
983
|
+
contentType: "text/markdown",
|
|
984
|
+
method: "alternate-markdown",
|
|
985
|
+
content: output.content,
|
|
986
|
+
fetchedAt,
|
|
987
|
+
truncated: output.truncated,
|
|
988
|
+
notes,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// 5B: Try URL.md suffix (llms.txt convention)
|
|
994
|
+
const mdSuffix = await tryMdSuffix(finalUrl, timeout, signal);
|
|
995
|
+
if (mdSuffix) {
|
|
996
|
+
notes.push("Found .md suffix version");
|
|
997
|
+
const output = finalizeOutput(mdSuffix);
|
|
998
|
+
return {
|
|
999
|
+
url,
|
|
1000
|
+
finalUrl,
|
|
1001
|
+
contentType: "text/markdown",
|
|
1002
|
+
method: "md-suffix",
|
|
1003
|
+
content: output.content,
|
|
1004
|
+
fetchedAt,
|
|
1005
|
+
truncated: output.truncated,
|
|
1006
|
+
notes,
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// 5C: Content negotiation
|
|
1011
|
+
const negotiated = await tryContentNegotiation(url, timeout, signal);
|
|
1012
|
+
if (negotiated) {
|
|
1013
|
+
notes.push(`Content negotiation returned ${negotiated.type}`);
|
|
1014
|
+
const output = finalizeOutput(negotiated.content);
|
|
1015
|
+
return {
|
|
1016
|
+
url,
|
|
1017
|
+
finalUrl,
|
|
1018
|
+
contentType: normalizeMime(negotiated.type),
|
|
1019
|
+
method: "content-negotiation",
|
|
1020
|
+
content: output.content,
|
|
1021
|
+
fetchedAt,
|
|
1022
|
+
truncated: output.truncated,
|
|
1023
|
+
notes,
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// 5D: Check for feed alternates
|
|
1028
|
+
const feedAlternates = alternates.filter(alt => !alt.endsWith(".md") && !alt.includes("markdown"));
|
|
1029
|
+
for (const altUrl of feedAlternates.slice(0, 2)) {
|
|
1030
|
+
const resolved = altUrl.startsWith("http") ? altUrl : new URL(altUrl, finalUrl).href;
|
|
1031
|
+
const altResult = await loadPage(resolved, { timeout, signal });
|
|
1032
|
+
if (altResult.ok && altResult.content.trim().length > 200) {
|
|
1033
|
+
notes.push(`Used feed alternate: ${resolved}`);
|
|
1034
|
+
const parsed = parseFeedToMarkdown(altResult.content);
|
|
1035
|
+
const output = finalizeOutput(parsed);
|
|
1036
|
+
return {
|
|
1037
|
+
url,
|
|
1038
|
+
finalUrl,
|
|
1039
|
+
contentType: "application/feed",
|
|
1040
|
+
method: "alternate-feed",
|
|
1041
|
+
content: output.content,
|
|
1042
|
+
fetchedAt,
|
|
1043
|
+
truncated: output.truncated,
|
|
1044
|
+
notes,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (signal?.aborted) {
|
|
1050
|
+
throw new ToolAbortError();
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 5E: Render HTML with lynx or html2text
|
|
1054
|
+
const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, settings, signal);
|
|
1055
|
+
if (!htmlResult.ok) {
|
|
1056
|
+
notes.push("html rendering failed (lynx/html2text unavailable)");
|
|
1057
|
+
const output = finalizeOutput(rawContent);
|
|
1058
|
+
return {
|
|
1059
|
+
url,
|
|
1060
|
+
finalUrl,
|
|
1061
|
+
contentType: mime,
|
|
1062
|
+
method: "raw-html",
|
|
1063
|
+
content: output.content,
|
|
1064
|
+
fetchedAt,
|
|
1065
|
+
truncated: output.truncated,
|
|
1066
|
+
notes,
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Step 6: If rendered output is low quality, try more targeted fallbacks
|
|
1071
|
+
if (isLowQualityOutput(htmlResult.content)) {
|
|
1072
|
+
const docLinks = extractDocumentLinks(rawContent, finalUrl);
|
|
1073
|
+
if (docLinks.length > 0) {
|
|
1074
|
+
const docUrl = docLinks[0];
|
|
1075
|
+
const binary = await fetchBinary(docUrl, timeout, signal);
|
|
1076
|
+
if (binary.ok) {
|
|
1077
|
+
const ext = getExtensionHint(docUrl, binary.contentDisposition);
|
|
1078
|
+
const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
|
|
1079
|
+
if (converted.ok && converted.content.trim().length > htmlResult.content.length) {
|
|
1080
|
+
notes.push(`Extracted and converted document: ${docUrl}`);
|
|
1081
|
+
const output = finalizeOutput(converted.content);
|
|
1082
|
+
return {
|
|
1083
|
+
url,
|
|
1084
|
+
finalUrl,
|
|
1085
|
+
contentType: "application/document",
|
|
1086
|
+
method: "extracted-document",
|
|
1087
|
+
content: output.content,
|
|
1088
|
+
fetchedAt,
|
|
1089
|
+
truncated: output.truncated,
|
|
1090
|
+
notes,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
if (!converted.ok && converted.error) {
|
|
1094
|
+
notes.push(`markit conversion failed: ${converted.error}`);
|
|
1095
|
+
}
|
|
1096
|
+
} else if (binary.error) {
|
|
1097
|
+
notes.push(`Binary fetch failed: ${binary.error}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const llmResult = await tryLlmEndpoints(finalUrl, timeout, signal);
|
|
1102
|
+
if (llmResult) {
|
|
1103
|
+
notes.push(`Used llms.txt fallback: ${llmResult.endpoint}`);
|
|
1104
|
+
const output = finalizeOutput(llmResult.content);
|
|
1105
|
+
return {
|
|
1106
|
+
url,
|
|
1107
|
+
finalUrl,
|
|
1108
|
+
contentType: "text/plain",
|
|
1109
|
+
method: "llms.txt",
|
|
1110
|
+
content: output.content,
|
|
1111
|
+
fetchedAt,
|
|
1112
|
+
truncated: output.truncated,
|
|
1113
|
+
notes,
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
notes.push("Page appears to require JavaScript or is mostly navigation");
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const output = finalizeOutput(htmlResult.content);
|
|
1121
|
+
return {
|
|
1122
|
+
url,
|
|
1123
|
+
finalUrl,
|
|
1124
|
+
contentType: mime,
|
|
1125
|
+
method: htmlResult.method,
|
|
1126
|
+
content: output.content,
|
|
1127
|
+
fetchedAt,
|
|
1128
|
+
truncated: output.truncated,
|
|
1129
|
+
notes,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Fallback: return raw content
|
|
1134
|
+
const output = finalizeOutput(rawContent);
|
|
1135
|
+
return {
|
|
1136
|
+
url,
|
|
1137
|
+
finalUrl,
|
|
1138
|
+
contentType: mime,
|
|
1139
|
+
method: "raw",
|
|
1140
|
+
content: output.content,
|
|
1141
|
+
fetchedAt,
|
|
1142
|
+
truncated: output.truncated,
|
|
1143
|
+
notes,
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// =============================================================================
|
|
1148
|
+
// Tool Definition
|
|
1149
|
+
// =============================================================================
|
|
1150
|
+
|
|
1151
|
+
export interface ReadUrlToolDetails {
|
|
1152
|
+
kind: "url";
|
|
1153
|
+
url: string;
|
|
1154
|
+
finalUrl: string;
|
|
1155
|
+
contentType: string;
|
|
1156
|
+
method: string;
|
|
1157
|
+
truncated: boolean;
|
|
1158
|
+
notes: string[];
|
|
1159
|
+
meta?: OutputMeta;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
interface ReadUrlCacheEntry {
|
|
1163
|
+
artifactId?: string;
|
|
1164
|
+
details: ReadUrlToolDetails;
|
|
1165
|
+
image?: FetchImagePayload;
|
|
1166
|
+
output: string;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const readUrlCache = new Map<string, ReadUrlCacheEntry>();
|
|
1170
|
+
|
|
1171
|
+
function getReadUrlCacheKey(session: ToolSession, requestedUrl: string, raw: boolean): string {
|
|
1172
|
+
const scope = session.getSessionFile() ?? session.cwd;
|
|
1173
|
+
return `${scope}::${raw ? "raw" : "rendered"}::${normalizeUrl(requestedUrl)}`;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
async function readArtifactOutput(session: ToolSession, artifactId: string): Promise<string | null> {
|
|
1177
|
+
const artifactsDir = session.getArtifactsDir?.();
|
|
1178
|
+
if (!artifactsDir) return null;
|
|
1179
|
+
|
|
1180
|
+
try {
|
|
1181
|
+
const files = await fs.readdir(artifactsDir);
|
|
1182
|
+
const match = files.find(file => file.startsWith(`${artifactId}.`));
|
|
1183
|
+
if (!match) return null;
|
|
1184
|
+
return await Bun.file(path.join(artifactsDir, match)).text();
|
|
1185
|
+
} catch {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
async function materializeReadUrlCacheEntry(
|
|
1191
|
+
session: ToolSession,
|
|
1192
|
+
entry: ReadUrlCacheEntry,
|
|
1193
|
+
): Promise<ReadUrlCacheEntry | null> {
|
|
1194
|
+
if (entry.artifactId) {
|
|
1195
|
+
const artifactOutput = await readArtifactOutput(session, entry.artifactId);
|
|
1196
|
+
if (artifactOutput !== null) {
|
|
1197
|
+
return { ...entry, output: artifactOutput };
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
return entry.output.length > 0 ? entry : null;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
async function persistReadUrlArtifact(session: ToolSession, output: string): Promise<string | undefined> {
|
|
1205
|
+
const { path: artifactPath, id } = (await session.allocateOutputArtifact?.("read")) ?? {};
|
|
1206
|
+
if (!artifactPath) return undefined;
|
|
1207
|
+
await Bun.write(artifactPath, output);
|
|
1208
|
+
return id;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
async function ensureReadUrlCacheArtifact(session: ToolSession, entry: ReadUrlCacheEntry): Promise<ReadUrlCacheEntry> {
|
|
1212
|
+
if (entry.artifactId) return entry;
|
|
1213
|
+
const artifactId = await persistReadUrlArtifact(session, entry.output);
|
|
1214
|
+
return artifactId ? { ...entry, artifactId } : entry;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: boolean, entry: ReadUrlCacheEntry): void {
|
|
1218
|
+
readUrlCache.set(getReadUrlCacheKey(session, requestedUrl, raw), entry);
|
|
1219
|
+
readUrlCache.set(getReadUrlCacheKey(session, entry.details.finalUrl, raw), entry);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
async function buildReadUrlCacheEntry(
|
|
1223
|
+
session: ToolSession,
|
|
1224
|
+
params: { path: string; timeout?: number; raw?: boolean },
|
|
1225
|
+
signal?: AbortSignal,
|
|
1226
|
+
options?: { ensureArtifact?: boolean },
|
|
1227
|
+
): Promise<ReadUrlCacheEntry> {
|
|
1228
|
+
const { path: url, timeout: rawTimeout = 20, raw = false } = params;
|
|
1229
|
+
|
|
1230
|
+
const effectiveTimeout = clampTimeout("fetch", rawTimeout);
|
|
1231
|
+
|
|
1232
|
+
if (signal?.aborted) {
|
|
1233
|
+
throw new ToolAbortError();
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
const result = await renderUrl(url, effectiveTimeout, raw, session.settings, signal);
|
|
1237
|
+
const output = buildUrlReadOutput(result, result.content);
|
|
1238
|
+
const artifactId = options?.ensureArtifact ? await persistReadUrlArtifact(session, output) : undefined;
|
|
1239
|
+
|
|
1240
|
+
return {
|
|
1241
|
+
artifactId,
|
|
1242
|
+
details: {
|
|
1243
|
+
kind: "url",
|
|
1244
|
+
url: result.url,
|
|
1245
|
+
finalUrl: result.finalUrl,
|
|
1246
|
+
contentType: result.contentType,
|
|
1247
|
+
method: result.method,
|
|
1248
|
+
truncated: Boolean(result.truncated),
|
|
1249
|
+
notes: result.notes,
|
|
1250
|
+
},
|
|
1251
|
+
image: result.image,
|
|
1252
|
+
output,
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export async function loadReadUrlCacheEntry(
|
|
1257
|
+
session: ToolSession,
|
|
1258
|
+
params: { path: string; timeout?: number; raw?: boolean },
|
|
1259
|
+
signal?: AbortSignal,
|
|
1260
|
+
options?: { ensureArtifact?: boolean; preferCached?: boolean },
|
|
1261
|
+
): Promise<ReadUrlCacheEntry> {
|
|
1262
|
+
const raw = params.raw ?? false;
|
|
1263
|
+
const cached = readUrlCache.get(getReadUrlCacheKey(session, params.path, raw));
|
|
1264
|
+
if (options?.preferCached && cached) {
|
|
1265
|
+
const prepared = options.ensureArtifact ? await ensureReadUrlCacheArtifact(session, cached) : cached;
|
|
1266
|
+
const materialized = await materializeReadUrlCacheEntry(session, prepared);
|
|
1267
|
+
if (materialized) {
|
|
1268
|
+
cacheReadUrlEntry(session, params.path, raw, materialized);
|
|
1269
|
+
return materialized;
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const fresh = await buildReadUrlCacheEntry(session, params, signal, {
|
|
1274
|
+
ensureArtifact: options?.ensureArtifact,
|
|
1275
|
+
});
|
|
1276
|
+
cacheReadUrlEntry(session, params.path, raw, fresh);
|
|
1277
|
+
return fresh;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function buildUrlReadOutput(result: FetchRenderResult, content: string): string {
|
|
1281
|
+
let output = "";
|
|
1282
|
+
output += `URL: ${result.finalUrl}\n`;
|
|
1283
|
+
output += `Content-Type: ${result.contentType}\n`;
|
|
1284
|
+
output += `Method: ${result.method}\n`;
|
|
1285
|
+
if (result.notes.length > 0) {
|
|
1286
|
+
output += `Notes: ${result.notes.join("; ")}\n`;
|
|
1287
|
+
}
|
|
1288
|
+
output += `\n---\n\n`;
|
|
1289
|
+
output += content;
|
|
1290
|
+
return output;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
export async function executeReadUrl(
|
|
1294
|
+
session: ToolSession,
|
|
1295
|
+
params: { path: string; timeout?: number; raw?: boolean },
|
|
1296
|
+
signal?: AbortSignal,
|
|
1297
|
+
): Promise<AgentToolResult<ReadUrlToolDetails>> {
|
|
1298
|
+
let cacheEntry = await loadReadUrlCacheEntry(session, params, signal, { preferCached: true });
|
|
1299
|
+
const truncation = truncateHead(cacheEntry.output, {
|
|
1300
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
1301
|
+
maxLines: FETCH_DEFAULT_MAX_LINES,
|
|
1302
|
+
});
|
|
1303
|
+
const needsArtifact = truncation.truncated;
|
|
1304
|
+
if (needsArtifact && !cacheEntry.artifactId) {
|
|
1305
|
+
cacheEntry = await ensureReadUrlCacheArtifact(session, cacheEntry);
|
|
1306
|
+
cacheReadUrlEntry(session, params.path, params.raw ?? false, cacheEntry);
|
|
1307
|
+
}
|
|
1308
|
+
const output = needsArtifact ? truncation.content : cacheEntry.output;
|
|
1309
|
+
const details: ReadUrlToolDetails = {
|
|
1310
|
+
...cacheEntry.details,
|
|
1311
|
+
truncated: Boolean(cacheEntry.details.truncated || needsArtifact),
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
const contentBlocks: Array<TextContent | ImageContent> = [{ type: "text", text: output }];
|
|
1315
|
+
if (cacheEntry.image) {
|
|
1316
|
+
contentBlocks.push({ type: "image", data: cacheEntry.image.data, mimeType: cacheEntry.image.mimeType });
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
const resultBuilder = toolResult(details).content(contentBlocks).sourceUrl(details.finalUrl);
|
|
1320
|
+
if (needsArtifact) {
|
|
1321
|
+
resultBuilder.truncation(truncation, { direction: "head", artifactId: cacheEntry.artifactId });
|
|
1322
|
+
} else if (cacheEntry.details.truncated) {
|
|
1323
|
+
const outputLines = cacheEntry.output.split("\n").length;
|
|
1324
|
+
const outputBytes = Buffer.byteLength(cacheEntry.output, "utf-8");
|
|
1325
|
+
const totalBytes = Math.max(outputBytes + 1, MAX_OUTPUT_CHARS + 1);
|
|
1326
|
+
const totalLines = outputLines + 1;
|
|
1327
|
+
resultBuilder.truncationFromText(cacheEntry.output, {
|
|
1328
|
+
direction: "tail",
|
|
1329
|
+
totalLines,
|
|
1330
|
+
totalBytes,
|
|
1331
|
+
maxBytes: MAX_OUTPUT_CHARS,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return resultBuilder.done();
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// =============================================================================
|
|
1339
|
+
// TUI Rendering
|
|
1340
|
+
// =============================================================================
|
|
1341
|
+
|
|
1342
|
+
/** Count non-empty lines */
|
|
1343
|
+
function countNonEmptyLines(text: string): number {
|
|
1344
|
+
return text.split("\n").filter(l => l.trim()).length;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/** Render URL read call (URL preview) */
|
|
1348
|
+
export function renderReadUrlCall(
|
|
1349
|
+
args: { path?: string; url?: string; timeout?: number; raw?: boolean },
|
|
1350
|
+
_options: RenderResultOptions,
|
|
1351
|
+
uiTheme: Theme = theme,
|
|
1352
|
+
): Component {
|
|
1353
|
+
const url = args.path ?? args.url ?? "";
|
|
1354
|
+
const domain = getDomain(url);
|
|
1355
|
+
const path = truncate(url.replace(/^https?:\/\/[^/]+/, ""), 50, "\u2026");
|
|
1356
|
+
const description = `${domain}${path ? ` ${path}` : ""}`.trim();
|
|
1357
|
+
const meta: string[] = [];
|
|
1358
|
+
if (args.raw) meta.push("raw");
|
|
1359
|
+
if (args.timeout !== undefined) meta.push(`timeout:${args.timeout}s`);
|
|
1360
|
+
const text = renderStatusLine({ icon: "pending", title: "Read", description, meta }, uiTheme);
|
|
1361
|
+
return new Text(text, 0, 0);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/** Render URL read result with tree-based layout */
|
|
1365
|
+
export function renderReadUrlResult(
|
|
1366
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ReadUrlToolDetails },
|
|
1367
|
+
options: RenderResultOptions,
|
|
1368
|
+
uiTheme: Theme = theme,
|
|
1369
|
+
): Component {
|
|
1370
|
+
const details = result.details;
|
|
1371
|
+
|
|
1372
|
+
if (!details) {
|
|
1373
|
+
return new Text(uiTheme.fg("error", "No response data"), 0, 0);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
const domain = getDomain(details.finalUrl);
|
|
1377
|
+
const path = truncate(details.finalUrl.replace(/^https?:\/\/[^/]+/, ""), 50, "…");
|
|
1378
|
+
const hasRedirect = details.url !== details.finalUrl;
|
|
1379
|
+
const hasNotes = details.notes.length > 0;
|
|
1380
|
+
const truncation = details.meta?.truncation;
|
|
1381
|
+
const truncated = Boolean(details.truncated || truncation);
|
|
1382
|
+
|
|
1383
|
+
const header = renderStatusLine(
|
|
1384
|
+
{
|
|
1385
|
+
title: "Read",
|
|
1386
|
+
description: `${domain}${path ? ` ${path}` : ""}`,
|
|
1387
|
+
},
|
|
1388
|
+
uiTheme,
|
|
1389
|
+
);
|
|
1390
|
+
|
|
1391
|
+
const contentText = result.content[0]?.text ?? "";
|
|
1392
|
+
const contentBody = contentText.includes("---\n\n")
|
|
1393
|
+
? contentText.split("---\n\n").slice(1).join("---\n\n")
|
|
1394
|
+
: contentText;
|
|
1395
|
+
const lineCount = countNonEmptyLines(contentBody);
|
|
1396
|
+
const charCount = contentBody.trim().length;
|
|
1397
|
+
const contentLines = contentBody.split("\n").filter(l => l.trim());
|
|
1398
|
+
|
|
1399
|
+
const metadataLines: string[] = [
|
|
1400
|
+
`${uiTheme.fg("muted", "Content-Type:")} ${details.contentType || "unknown"}`,
|
|
1401
|
+
`${uiTheme.fg("muted", "Method:")} ${details.method}`,
|
|
1402
|
+
];
|
|
1403
|
+
if (hasRedirect) {
|
|
1404
|
+
metadataLines.push(`${uiTheme.fg("muted", "Final URL:")} ${uiTheme.fg("mdLinkUrl", details.finalUrl)}`);
|
|
1405
|
+
}
|
|
1406
|
+
const lineLabel = `${lineCount} line${lineCount === 1 ? "" : "s"}`;
|
|
1407
|
+
metadataLines.push(`${uiTheme.fg("muted", "Lines:")} ${lineLabel}`);
|
|
1408
|
+
metadataLines.push(`${uiTheme.fg("muted", "Chars:")} ${charCount}`);
|
|
1409
|
+
if (truncated) {
|
|
1410
|
+
metadataLines.push(uiTheme.fg("warning", `${uiTheme.status.warning} Output truncated`));
|
|
1411
|
+
if (truncation?.artifactId) metadataLines.push(formatStyledArtifactReference(truncation.artifactId, uiTheme));
|
|
1412
|
+
}
|
|
1413
|
+
if (hasNotes) {
|
|
1414
|
+
metadataLines.push(`${uiTheme.fg("muted", "Notes:")} ${details.notes.join("; ")}`);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const outputBlock = new CachedOutputBlock();
|
|
1418
|
+
let lastExpanded: boolean | undefined;
|
|
1419
|
+
let contentPreviewLines: string[] | undefined;
|
|
1420
|
+
|
|
1421
|
+
return {
|
|
1422
|
+
render: (width: number) => {
|
|
1423
|
+
const { expanded } = options;
|
|
1424
|
+
|
|
1425
|
+
if (contentPreviewLines === undefined || lastExpanded !== expanded) {
|
|
1426
|
+
const previewLimit = expanded ? 12 : 3;
|
|
1427
|
+
const previewList = applyListLimit(contentLines, { headLimit: previewLimit });
|
|
1428
|
+
const previewLines = previewList.items.map(line => line.trimEnd());
|
|
1429
|
+
const remaining = Math.max(0, contentLines.length - previewList.items.length);
|
|
1430
|
+
contentPreviewLines =
|
|
1431
|
+
previewLines.length > 0
|
|
1432
|
+
? previewLines.map(line => uiTheme.fg("dim", line))
|
|
1433
|
+
: [uiTheme.fg("dim", "(no content)")];
|
|
1434
|
+
if (remaining > 0) {
|
|
1435
|
+
const hint = formatExpandHint(uiTheme, expanded, true);
|
|
1436
|
+
contentPreviewLines.push(uiTheme.fg("muted", `… ${remaining} more lines${hint ? ` ${hint}` : ""}`));
|
|
1437
|
+
}
|
|
1438
|
+
lastExpanded = expanded;
|
|
1439
|
+
outputBlock.invalidate();
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return outputBlock.render(
|
|
1443
|
+
{
|
|
1444
|
+
header,
|
|
1445
|
+
state: truncated ? "warning" : "success",
|
|
1446
|
+
sections: [
|
|
1447
|
+
{ label: uiTheme.fg("toolTitle", "Metadata"), lines: metadataLines },
|
|
1448
|
+
{ label: uiTheme.fg("toolTitle", "Content Preview"), lines: contentPreviewLines },
|
|
1449
|
+
],
|
|
1450
|
+
width,
|
|
1451
|
+
applyBg: false,
|
|
1452
|
+
},
|
|
1453
|
+
uiTheme,
|
|
1454
|
+
);
|
|
1455
|
+
},
|
|
1456
|
+
invalidate: () => {
|
|
1457
|
+
outputBlock.invalidate();
|
|
1458
|
+
contentPreviewLines = undefined;
|
|
1459
|
+
lastExpanded = undefined;
|
|
1460
|
+
},
|
|
1461
|
+
};
|
|
1462
|
+
}
|