@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,1591 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type {
|
|
5
|
+
AgentTool,
|
|
6
|
+
AgentToolContext,
|
|
7
|
+
AgentToolResult,
|
|
8
|
+
AgentToolUpdateCallback,
|
|
9
|
+
} from "@f5xc-salesdemos/pi-agent-core";
|
|
10
|
+
import type { ImageContent, TextContent } from "@f5xc-salesdemos/pi-ai";
|
|
11
|
+
import { glob } from "@f5xc-salesdemos/pi-natives";
|
|
12
|
+
import type { Component } from "@f5xc-salesdemos/pi-tui";
|
|
13
|
+
import { Text } from "@f5xc-salesdemos/pi-tui";
|
|
14
|
+
import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@f5xc-salesdemos/pi-utils";
|
|
15
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
16
|
+
import { computeLineHash } from "../edit/line-hash";
|
|
17
|
+
import {
|
|
18
|
+
type ChunkReadTarget,
|
|
19
|
+
formatChunkedRead,
|
|
20
|
+
parseChunkReadPath,
|
|
21
|
+
parseChunkSelector,
|
|
22
|
+
resolveAnchorStyle,
|
|
23
|
+
resolveChunkAutoIndent,
|
|
24
|
+
} from "../edit/modes/chunk";
|
|
25
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
26
|
+
import { parseInternalUrl } from "../internal-urls/parse";
|
|
27
|
+
import type { InternalUrl } from "../internal-urls/types";
|
|
28
|
+
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
29
|
+
import readDescription from "../prompts/tools/read.md" with { type: "text" };
|
|
30
|
+
import readChunkDescription from "../prompts/tools/read-chunk.md" with { type: "text" };
|
|
31
|
+
import type { ToolSession } from "../sdk";
|
|
32
|
+
import {
|
|
33
|
+
DEFAULT_MAX_BYTES,
|
|
34
|
+
DEFAULT_MAX_LINES,
|
|
35
|
+
noTruncResult,
|
|
36
|
+
type TruncationResult,
|
|
37
|
+
truncateHead,
|
|
38
|
+
truncateHeadBytes,
|
|
39
|
+
} from "../session/streaming-output";
|
|
40
|
+
import { renderCodeCell, renderStatusLine } from "../tui";
|
|
41
|
+
import { CachedOutputBlock } from "../tui/output-block";
|
|
42
|
+
import { resolveEditMode } from "../utils/edit-mode";
|
|
43
|
+
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
44
|
+
import { ImageInputTooLargeError, loadImageInput, MAX_IMAGE_INPUT_BYTES } from "../utils/image-loading";
|
|
45
|
+
import { convertFileWithMarkit } from "../utils/markit";
|
|
46
|
+
import { type ArchiveReader, openArchive, parseArchivePathCandidates } from "./archive-reader";
|
|
47
|
+
import {
|
|
48
|
+
executeReadUrl,
|
|
49
|
+
isReadableUrlPath,
|
|
50
|
+
loadReadUrlCacheEntry,
|
|
51
|
+
parseReadUrlTarget,
|
|
52
|
+
type ReadUrlToolDetails,
|
|
53
|
+
renderReadUrlCall,
|
|
54
|
+
renderReadUrlResult,
|
|
55
|
+
} from "./fetch";
|
|
56
|
+
import { applyListLimit } from "./list-limit";
|
|
57
|
+
import { formatFullOutputReference, formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
58
|
+
import { expandPath, resolveReadPath } from "./path-utils";
|
|
59
|
+
import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-utils";
|
|
60
|
+
import {
|
|
61
|
+
executeReadQuery,
|
|
62
|
+
getRowByKey,
|
|
63
|
+
getRowByRowId,
|
|
64
|
+
getTableSchema,
|
|
65
|
+
isSqliteFile,
|
|
66
|
+
listTables,
|
|
67
|
+
parseSqlitePathCandidates,
|
|
68
|
+
parseSqliteSelector,
|
|
69
|
+
queryRows,
|
|
70
|
+
renderRow,
|
|
71
|
+
renderSchema,
|
|
72
|
+
renderTable,
|
|
73
|
+
renderTableList,
|
|
74
|
+
resolveTableRowLookup,
|
|
75
|
+
} from "./sqlite-reader";
|
|
76
|
+
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
77
|
+
import { toolResult } from "./tool-result";
|
|
78
|
+
|
|
79
|
+
const PROSE_LANGUAGES = new Set(["markdown", "text", "log", "asciidoc", "restructuredtext"]);
|
|
80
|
+
|
|
81
|
+
function isProseLanguage(language: string | undefined): boolean {
|
|
82
|
+
return language !== undefined && PROSE_LANGUAGES.has(language);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Document types converted to markdown via markit.
|
|
86
|
+
const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
|
|
87
|
+
|
|
88
|
+
// Remote mount path prefix (sshfs mounts) - skip fuzzy matching to avoid hangs
|
|
89
|
+
const REMOTE_MOUNT_PREFIX = getRemoteDir() + path.sep;
|
|
90
|
+
|
|
91
|
+
function isRemoteMountPath(absolutePath: string): boolean {
|
|
92
|
+
return absolutePath.startsWith(REMOTE_MOUNT_PREFIX);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function prependLineNumbers(text: string, startNum: number): string {
|
|
96
|
+
const textLines = text.split("\n");
|
|
97
|
+
const lastLineNum = startNum + textLines.length - 1;
|
|
98
|
+
const padWidth = String(lastLineNum).length;
|
|
99
|
+
return textLines
|
|
100
|
+
.map((line, i) => {
|
|
101
|
+
const lineNum = String(startNum + i).padStart(padWidth, " ");
|
|
102
|
+
return `${lineNum}|${line}`;
|
|
103
|
+
})
|
|
104
|
+
.join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function prependHashLines(text: string, startNum: number): string {
|
|
108
|
+
const textLines = text.split("\n");
|
|
109
|
+
return textLines.map((line, i) => `${startNum + i}#${computeLineHash(startNum + i, line)}:${line}`).join("\n");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function formatTextWithMode(
|
|
113
|
+
text: string,
|
|
114
|
+
startNum: number,
|
|
115
|
+
shouldAddHashLines: boolean,
|
|
116
|
+
shouldAddLineNumbers: boolean,
|
|
117
|
+
): string {
|
|
118
|
+
if (shouldAddHashLines) return prependHashLines(text, startNum);
|
|
119
|
+
if (shouldAddLineNumbers) return prependLineNumbers(text, startNum);
|
|
120
|
+
return text;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const READ_CHUNK_SIZE = 8 * 1024;
|
|
124
|
+
|
|
125
|
+
async function streamLinesFromFile(
|
|
126
|
+
filePath: string,
|
|
127
|
+
startLine: number,
|
|
128
|
+
maxLinesToCollect: number,
|
|
129
|
+
maxBytes: number,
|
|
130
|
+
selectedLineLimit: number | null,
|
|
131
|
+
signal?: AbortSignal,
|
|
132
|
+
): Promise<{
|
|
133
|
+
lines: string[];
|
|
134
|
+
totalFileLines: number;
|
|
135
|
+
collectedBytes: number;
|
|
136
|
+
stoppedByByteLimit: boolean;
|
|
137
|
+
firstLinePreview?: { text: string; bytes: number };
|
|
138
|
+
firstLineByteLength?: number;
|
|
139
|
+
selectedBytesTotal: number;
|
|
140
|
+
}> {
|
|
141
|
+
const bufferChunk = Buffer.allocUnsafe(READ_CHUNK_SIZE);
|
|
142
|
+
const collectedLines: string[] = [];
|
|
143
|
+
let lineIndex = 0;
|
|
144
|
+
let collectedBytes = 0;
|
|
145
|
+
let stoppedByByteLimit = false;
|
|
146
|
+
let doneCollecting = false;
|
|
147
|
+
let fileHandle: fs.FileHandle | null = null;
|
|
148
|
+
let currentLineLength = 0;
|
|
149
|
+
let currentLineChunks: Buffer[] = [];
|
|
150
|
+
let sawAnyByte = false;
|
|
151
|
+
let endedWithNewline = false;
|
|
152
|
+
let firstLinePreviewBytes = 0;
|
|
153
|
+
const firstLinePreviewChunks: Buffer[] = [];
|
|
154
|
+
let firstLineByteLength: number | undefined;
|
|
155
|
+
let selectedBytesTotal = 0;
|
|
156
|
+
let selectedLinesSeen = 0;
|
|
157
|
+
let captureLine = false;
|
|
158
|
+
let discardLineChunks = false;
|
|
159
|
+
let lineCaptureLimit = 0;
|
|
160
|
+
|
|
161
|
+
const setupLineState = () => {
|
|
162
|
+
captureLine = !doneCollecting && lineIndex >= startLine;
|
|
163
|
+
discardLineChunks = !captureLine;
|
|
164
|
+
if (captureLine) {
|
|
165
|
+
const separatorBytes = collectedLines.length > 0 ? 1 : 0;
|
|
166
|
+
lineCaptureLimit = maxBytes - collectedBytes - separatorBytes;
|
|
167
|
+
if (lineCaptureLimit <= 0) {
|
|
168
|
+
discardLineChunks = true;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
lineCaptureLimit = 0;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const decodeLine = (): string => {
|
|
176
|
+
if (currentLineLength === 0) return "";
|
|
177
|
+
if (currentLineChunks.length === 1 && currentLineChunks[0]?.length === currentLineLength) {
|
|
178
|
+
return currentLineChunks[0].toString("utf-8");
|
|
179
|
+
}
|
|
180
|
+
return Buffer.concat(currentLineChunks, currentLineLength).toString("utf-8");
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const maybeCapturePreview = (segment: Uint8Array) => {
|
|
184
|
+
if (doneCollecting || lineIndex < startLine || collectedLines.length !== 0) return;
|
|
185
|
+
if (firstLinePreviewBytes >= maxBytes || segment.length === 0) return;
|
|
186
|
+
const remaining = maxBytes - firstLinePreviewBytes;
|
|
187
|
+
const slice = segment.length > remaining ? segment.subarray(0, remaining) : segment;
|
|
188
|
+
if (slice.length === 0) return;
|
|
189
|
+
firstLinePreviewChunks.push(Buffer.from(slice));
|
|
190
|
+
firstLinePreviewBytes += slice.length;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const appendSegment = (segment: Uint8Array) => {
|
|
194
|
+
currentLineLength += segment.length;
|
|
195
|
+
maybeCapturePreview(segment);
|
|
196
|
+
if (!captureLine || discardLineChunks || segment.length === 0) return;
|
|
197
|
+
if (currentLineLength <= lineCaptureLimit) {
|
|
198
|
+
currentLineChunks.push(Buffer.from(segment));
|
|
199
|
+
} else {
|
|
200
|
+
discardLineChunks = true;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const finalizeLine = () => {
|
|
205
|
+
if (lineIndex >= startLine && (selectedLineLimit === null || selectedLinesSeen < selectedLineLimit)) {
|
|
206
|
+
selectedBytesTotal += currentLineLength + (selectedLinesSeen > 0 ? 1 : 0);
|
|
207
|
+
selectedLinesSeen++;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!doneCollecting && lineIndex >= startLine) {
|
|
211
|
+
const separatorBytes = collectedLines.length > 0 ? 1 : 0;
|
|
212
|
+
if (collectedLines.length >= maxLinesToCollect) {
|
|
213
|
+
doneCollecting = true;
|
|
214
|
+
} else if (collectedLines.length === 0 && currentLineLength > maxBytes) {
|
|
215
|
+
stoppedByByteLimit = true;
|
|
216
|
+
doneCollecting = true;
|
|
217
|
+
if (firstLineByteLength === undefined) {
|
|
218
|
+
firstLineByteLength = currentLineLength;
|
|
219
|
+
}
|
|
220
|
+
} else if (collectedLines.length > 0 && collectedBytes + separatorBytes + currentLineLength > maxBytes) {
|
|
221
|
+
stoppedByByteLimit = true;
|
|
222
|
+
doneCollecting = true;
|
|
223
|
+
} else {
|
|
224
|
+
const lineText = decodeLine();
|
|
225
|
+
collectedLines.push(lineText);
|
|
226
|
+
collectedBytes += separatorBytes + currentLineLength;
|
|
227
|
+
if (firstLineByteLength === undefined) {
|
|
228
|
+
firstLineByteLength = currentLineLength;
|
|
229
|
+
}
|
|
230
|
+
if (collectedBytes > maxBytes) {
|
|
231
|
+
stoppedByByteLimit = true;
|
|
232
|
+
doneCollecting = true;
|
|
233
|
+
} else if (collectedLines.length >= maxLinesToCollect) {
|
|
234
|
+
doneCollecting = true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} else if (lineIndex >= startLine && firstLineByteLength === undefined) {
|
|
238
|
+
firstLineByteLength = currentLineLength;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
lineIndex++;
|
|
242
|
+
currentLineLength = 0;
|
|
243
|
+
currentLineChunks = [];
|
|
244
|
+
setupLineState();
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
setupLineState();
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
fileHandle = await fs.open(filePath, "r");
|
|
251
|
+
|
|
252
|
+
while (true) {
|
|
253
|
+
throwIfAborted(signal);
|
|
254
|
+
const { bytesRead } = await fileHandle.read(bufferChunk, 0, bufferChunk.length, null);
|
|
255
|
+
if (bytesRead === 0) break;
|
|
256
|
+
|
|
257
|
+
sawAnyByte = true;
|
|
258
|
+
const chunk = bufferChunk.subarray(0, bytesRead);
|
|
259
|
+
endedWithNewline = chunk[bytesRead - 1] === 0x0a;
|
|
260
|
+
|
|
261
|
+
let start = 0;
|
|
262
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
263
|
+
if (chunk[i] === 0x0a) {
|
|
264
|
+
const segment = chunk.subarray(start, i);
|
|
265
|
+
if (segment.length > 0) {
|
|
266
|
+
appendSegment(segment);
|
|
267
|
+
}
|
|
268
|
+
finalizeLine();
|
|
269
|
+
start = i + 1;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (start < chunk.length) {
|
|
274
|
+
appendSegment(chunk.subarray(start));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} finally {
|
|
278
|
+
if (fileHandle) {
|
|
279
|
+
await fileHandle.close();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (endedWithNewline || currentLineLength > 0 || !sawAnyByte) {
|
|
284
|
+
finalizeLine();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
let firstLinePreview: { text: string; bytes: number } | undefined;
|
|
288
|
+
if (firstLinePreviewBytes > 0) {
|
|
289
|
+
const { text, bytes } = truncateHeadBytes(Buffer.concat(firstLinePreviewChunks, firstLinePreviewBytes), maxBytes);
|
|
290
|
+
firstLinePreview = { text, bytes };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
lines: collectedLines,
|
|
295
|
+
totalFileLines: lineIndex,
|
|
296
|
+
collectedBytes,
|
|
297
|
+
stoppedByByteLimit,
|
|
298
|
+
firstLinePreview,
|
|
299
|
+
firstLineByteLength,
|
|
300
|
+
selectedBytesTotal,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Maximum image file size (20MB) - larger images will be rejected to prevent OOM during serialization
|
|
305
|
+
const MAX_IMAGE_SIZE = MAX_IMAGE_INPUT_BYTES;
|
|
306
|
+
const GLOB_TIMEOUT_MS = 5000;
|
|
307
|
+
|
|
308
|
+
function isNotFoundError(error: unknown): boolean {
|
|
309
|
+
if (!error || typeof error !== "object") return false;
|
|
310
|
+
const code = (error as { code?: string }).code;
|
|
311
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Attempt to resolve a non-existent path by finding a unique suffix match within the workspace.
|
|
316
|
+
* Uses a glob suffix pattern so the native engine handles matching directly.
|
|
317
|
+
* Returns null when 0 or >1 candidates match (ambiguous = no auto-resolution).
|
|
318
|
+
*/
|
|
319
|
+
async function findUniqueSuffixMatch(
|
|
320
|
+
rawPath: string,
|
|
321
|
+
cwd: string,
|
|
322
|
+
signal?: AbortSignal,
|
|
323
|
+
): Promise<{ absolutePath: string; displayPath: string } | null> {
|
|
324
|
+
const normalized = rawPath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
325
|
+
if (!normalized) return null;
|
|
326
|
+
|
|
327
|
+
const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
|
|
328
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
329
|
+
|
|
330
|
+
let matches: string[];
|
|
331
|
+
try {
|
|
332
|
+
const result = await untilAborted(combinedSignal, () =>
|
|
333
|
+
glob({
|
|
334
|
+
pattern: `**/${normalized}`,
|
|
335
|
+
path: cwd,
|
|
336
|
+
// No fileType filter: matches both files and directories
|
|
337
|
+
hidden: true,
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
340
|
+
matches = result.matches.map(m => m.path);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
343
|
+
if (!signal?.aborted) return null; // timeout — give up silently
|
|
344
|
+
throw new ToolAbortError();
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (matches.length !== 1) return null;
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
absolutePath: path.resolve(cwd, matches[0]),
|
|
353
|
+
displayPath: matches[0],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function decodeUtf8Text(bytes: Uint8Array): string | null {
|
|
358
|
+
for (const byte of bytes) {
|
|
359
|
+
if (byte === 0) return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
364
|
+
} catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function prependSuffixResolutionNotice(text: string, suffixResolution?: { from: string; to: string }): string {
|
|
370
|
+
if (!suffixResolution) return text;
|
|
371
|
+
|
|
372
|
+
const notice = `[Path '${suffixResolution.from}' not found; resolved to '${suffixResolution.to}' via suffix match]`;
|
|
373
|
+
return text ? `${notice}\n${text}` : notice;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const readSchema = Type.Object({
|
|
377
|
+
path: Type.String({ description: "Path or URL to read" }),
|
|
378
|
+
sel: Type.Optional(Type.String({ description: "Selector: chunk path, L10-L50, or raw" })),
|
|
379
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })),
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
export type ReadToolInput = Static<typeof readSchema>;
|
|
383
|
+
|
|
384
|
+
export interface ReadToolDetails {
|
|
385
|
+
kind?: "file" | "url";
|
|
386
|
+
truncation?: TruncationResult;
|
|
387
|
+
isDirectory?: boolean;
|
|
388
|
+
resolvedPath?: string;
|
|
389
|
+
suffixResolution?: { from: string; to: string };
|
|
390
|
+
chunk?: ChunkReadTarget;
|
|
391
|
+
url?: string;
|
|
392
|
+
finalUrl?: string;
|
|
393
|
+
contentType?: string;
|
|
394
|
+
method?: string;
|
|
395
|
+
notes?: string[];
|
|
396
|
+
meta?: OutputMeta;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
type ReadParams = ReadToolInput;
|
|
400
|
+
|
|
401
|
+
/** Parsed representation of the `sel` parameter. */
|
|
402
|
+
type ParsedSelector =
|
|
403
|
+
| { kind: "none" }
|
|
404
|
+
| { kind: "raw" }
|
|
405
|
+
| { kind: "lines"; startLine: number; endLine: number | undefined }
|
|
406
|
+
| { kind: "chunk"; selector: string };
|
|
407
|
+
|
|
408
|
+
const LINE_RANGE_RE = /^L(\d+)(?:-L?(\d+))?$/i;
|
|
409
|
+
|
|
410
|
+
function parseSel(sel: string | undefined): ParsedSelector {
|
|
411
|
+
if (!sel || sel.length === 0) return { kind: "none" };
|
|
412
|
+
const normalizedSelector = parseChunkSelector(sel).selector ?? sel;
|
|
413
|
+
if (normalizedSelector === "raw") return { kind: "raw" };
|
|
414
|
+
const lineMatch = LINE_RANGE_RE.exec(normalizedSelector);
|
|
415
|
+
if (lineMatch) {
|
|
416
|
+
const rawStart = Number.parseInt(lineMatch[1]!, 10);
|
|
417
|
+
if (rawStart < 1) {
|
|
418
|
+
throw new ToolError("L0 is invalid; lines are 1-indexed. Use sel=L1.");
|
|
419
|
+
}
|
|
420
|
+
const rawEnd = lineMatch[2] ? Number.parseInt(lineMatch[2], 10) : undefined;
|
|
421
|
+
if (rawEnd !== undefined && rawEnd < rawStart) {
|
|
422
|
+
throw new ToolError(`Invalid range L${rawStart}-L${rawEnd}: end must be >= start.`);
|
|
423
|
+
}
|
|
424
|
+
return { kind: "lines", startLine: rawStart, endLine: rawEnd };
|
|
425
|
+
}
|
|
426
|
+
return { kind: "chunk", selector: normalizedSelector };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Convert a line-range selector to the offset/limit pair used by internal pagination. */
|
|
430
|
+
function selToOffsetLimit(parsed: ParsedSelector): { offset?: number; limit?: number } {
|
|
431
|
+
if (parsed.kind === "lines") {
|
|
432
|
+
const limit = parsed.endLine !== undefined ? parsed.endLine - parsed.startLine + 1 : undefined;
|
|
433
|
+
return { offset: parsed.startLine, limit };
|
|
434
|
+
}
|
|
435
|
+
return {};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
interface ResolvedArchiveReadPath {
|
|
439
|
+
absolutePath: string;
|
|
440
|
+
archiveSubPath: string;
|
|
441
|
+
suffixResolution?: { from: string; to: string };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
interface ResolvedSqliteReadPath {
|
|
445
|
+
absolutePath: string;
|
|
446
|
+
sqliteSubPath: string;
|
|
447
|
+
queryString: string;
|
|
448
|
+
suffixResolution?: { from: string; to: string };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function parseSqliteSelectorInput(selector: string | undefined): { subPath: string; queryString: string } {
|
|
452
|
+
if (!selector) {
|
|
453
|
+
return { subPath: "", queryString: "" };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const queryIndex = selector.indexOf("?");
|
|
457
|
+
if (queryIndex === -1) {
|
|
458
|
+
return { subPath: selector.replace(/^:+/, ""), queryString: "" };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
subPath: selector.slice(0, queryIndex).replace(/^:+/, ""),
|
|
463
|
+
queryString: selector.slice(queryIndex + 1),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Read tool implementation.
|
|
469
|
+
*
|
|
470
|
+
* Reads files with support for images, converted documents (via markit), and text.
|
|
471
|
+
* Directories return a formatted listing with modification times.
|
|
472
|
+
*/
|
|
473
|
+
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
474
|
+
readonly name = "read";
|
|
475
|
+
readonly label = "Read";
|
|
476
|
+
readonly description: string;
|
|
477
|
+
readonly parameters = readSchema;
|
|
478
|
+
readonly nonAbortable = true;
|
|
479
|
+
readonly strict = true;
|
|
480
|
+
|
|
481
|
+
readonly #autoResizeImages: boolean;
|
|
482
|
+
readonly #defaultLimit: number;
|
|
483
|
+
readonly #inspectImageEnabled: boolean;
|
|
484
|
+
|
|
485
|
+
constructor(private readonly session: ToolSession) {
|
|
486
|
+
const displayMode = resolveFileDisplayMode(session);
|
|
487
|
+
this.#autoResizeImages = session.settings.get("images.autoResize");
|
|
488
|
+
this.#defaultLimit = Math.max(
|
|
489
|
+
1,
|
|
490
|
+
Math.min(session.settings.get("read.defaultLimit") ?? DEFAULT_MAX_LINES, DEFAULT_MAX_LINES),
|
|
491
|
+
);
|
|
492
|
+
this.#inspectImageEnabled = session.settings.get("inspect_image.enabled");
|
|
493
|
+
this.description =
|
|
494
|
+
resolveEditMode(session) === "chunk"
|
|
495
|
+
? prompt.render(readChunkDescription, {
|
|
496
|
+
anchorStyle: resolveAnchorStyle(session.settings),
|
|
497
|
+
chunkAutoIndent: resolveChunkAutoIndent(),
|
|
498
|
+
})
|
|
499
|
+
: prompt.render(readDescription, {
|
|
500
|
+
DEFAULT_LIMIT: String(this.#defaultLimit),
|
|
501
|
+
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
|
|
502
|
+
IS_HASHLINE_MODE: displayMode.hashLines,
|
|
503
|
+
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async #resolveArchiveReadPath(readPath: string, signal?: AbortSignal): Promise<ResolvedArchiveReadPath | null> {
|
|
508
|
+
const candidates = parseArchivePathCandidates(readPath);
|
|
509
|
+
for (const candidate of candidates) {
|
|
510
|
+
let absolutePath = resolveReadPath(candidate.archivePath, this.session.cwd);
|
|
511
|
+
let suffixResolution: { from: string; to: string } | undefined;
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
const stat = await Bun.file(absolutePath).stat();
|
|
515
|
+
if (stat.isDirectory()) continue;
|
|
516
|
+
return {
|
|
517
|
+
absolutePath,
|
|
518
|
+
archiveSubPath: candidate.archivePath === readPath ? "" : candidate.subPath,
|
|
519
|
+
suffixResolution,
|
|
520
|
+
};
|
|
521
|
+
} catch (error) {
|
|
522
|
+
if (!isNotFoundError(error) || isRemoteMountPath(absolutePath)) continue;
|
|
523
|
+
|
|
524
|
+
const suffixMatch = await findUniqueSuffixMatch(candidate.archivePath, this.session.cwd, signal);
|
|
525
|
+
if (!suffixMatch) continue;
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
|
|
529
|
+
if (retryStat.isDirectory()) continue;
|
|
530
|
+
|
|
531
|
+
absolutePath = suffixMatch.absolutePath;
|
|
532
|
+
suffixResolution = { from: candidate.archivePath, to: suffixMatch.displayPath };
|
|
533
|
+
return {
|
|
534
|
+
absolutePath,
|
|
535
|
+
archiveSubPath: candidate.archivePath === readPath ? "" : candidate.subPath,
|
|
536
|
+
suffixResolution,
|
|
537
|
+
};
|
|
538
|
+
} catch (retryError) {
|
|
539
|
+
if (!isNotFoundError(retryError)) {
|
|
540
|
+
throw retryError;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async #resolveSqliteReadPath(readPath: string, signal?: AbortSignal): Promise<ResolvedSqliteReadPath | null> {
|
|
550
|
+
const candidates = parseSqlitePathCandidates(readPath);
|
|
551
|
+
for (const candidate of candidates) {
|
|
552
|
+
let absolutePath = resolveReadPath(candidate.sqlitePath, this.session.cwd);
|
|
553
|
+
let suffixResolution: { from: string; to: string } | undefined;
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const stat = await Bun.file(absolutePath).stat();
|
|
557
|
+
if (stat.isDirectory()) continue;
|
|
558
|
+
if (!(await isSqliteFile(absolutePath))) continue;
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
absolutePath,
|
|
562
|
+
sqliteSubPath: candidate.subPath,
|
|
563
|
+
queryString: candidate.queryString,
|
|
564
|
+
suffixResolution,
|
|
565
|
+
};
|
|
566
|
+
} catch (error) {
|
|
567
|
+
if (!isNotFoundError(error) || isRemoteMountPath(absolutePath)) continue;
|
|
568
|
+
|
|
569
|
+
const suffixMatch = await findUniqueSuffixMatch(candidate.sqlitePath, this.session.cwd, signal);
|
|
570
|
+
if (!suffixMatch) continue;
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
|
|
574
|
+
if (retryStat.isDirectory()) continue;
|
|
575
|
+
if (!(await isSqliteFile(suffixMatch.absolutePath))) continue;
|
|
576
|
+
|
|
577
|
+
absolutePath = suffixMatch.absolutePath;
|
|
578
|
+
suffixResolution = { from: candidate.sqlitePath, to: suffixMatch.displayPath };
|
|
579
|
+
return {
|
|
580
|
+
absolutePath,
|
|
581
|
+
sqliteSubPath: candidate.subPath,
|
|
582
|
+
queryString: candidate.queryString,
|
|
583
|
+
suffixResolution,
|
|
584
|
+
};
|
|
585
|
+
} catch (retryError) {
|
|
586
|
+
if (!isNotFoundError(retryError)) {
|
|
587
|
+
throw retryError;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
#buildInMemoryTextResult(
|
|
597
|
+
text: string,
|
|
598
|
+
offset: number | undefined,
|
|
599
|
+
limit: number | undefined,
|
|
600
|
+
options: {
|
|
601
|
+
details?: ReadToolDetails;
|
|
602
|
+
sourcePath?: string;
|
|
603
|
+
sourceUrl?: string;
|
|
604
|
+
sourceInternal?: string;
|
|
605
|
+
entityLabel: string;
|
|
606
|
+
ignoreResultLimits?: boolean;
|
|
607
|
+
},
|
|
608
|
+
): AgentToolResult<ReadToolDetails> {
|
|
609
|
+
const displayMode = resolveFileDisplayMode(this.session);
|
|
610
|
+
const details = options.details ?? {};
|
|
611
|
+
const allLines = text.split("\n");
|
|
612
|
+
const totalLines = allLines.length;
|
|
613
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
614
|
+
const startLineDisplay = startLine + 1;
|
|
615
|
+
const ignoreResultLimits = options.ignoreResultLimits ?? false;
|
|
616
|
+
|
|
617
|
+
const resultBuilder = toolResult(details);
|
|
618
|
+
if (options.sourcePath) {
|
|
619
|
+
resultBuilder.sourcePath(options.sourcePath);
|
|
620
|
+
}
|
|
621
|
+
if (options.sourceUrl) {
|
|
622
|
+
resultBuilder.sourceUrl(options.sourceUrl);
|
|
623
|
+
}
|
|
624
|
+
if (options.sourceInternal) {
|
|
625
|
+
resultBuilder.sourceInternal(options.sourceInternal);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (startLine >= allLines.length) {
|
|
629
|
+
const suggestion =
|
|
630
|
+
allLines.length === 0
|
|
631
|
+
? `The ${options.entityLabel} is empty.`
|
|
632
|
+
: `Use sel=L1 to read from the start, or sel=L${allLines.length} to read the last line.`;
|
|
633
|
+
return resultBuilder
|
|
634
|
+
.text(
|
|
635
|
+
`Line ${startLineDisplay} is beyond end of ${options.entityLabel} (${allLines.length} lines total). ${suggestion}`,
|
|
636
|
+
)
|
|
637
|
+
.done();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const endLine =
|
|
641
|
+
limit !== undefined && !ignoreResultLimits ? Math.min(startLine + limit, allLines.length) : allLines.length;
|
|
642
|
+
const selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
643
|
+
const userLimitedLines = limit !== undefined && !ignoreResultLimits ? endLine - startLine : undefined;
|
|
644
|
+
const truncation = ignoreResultLimits ? noTruncResult(selectedContent) : truncateHead(selectedContent);
|
|
645
|
+
|
|
646
|
+
const shouldAddHashLines = displayMode.hashLines;
|
|
647
|
+
const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
|
|
648
|
+
const formatText = (content: string, startNum: number): string =>
|
|
649
|
+
formatTextWithMode(content, startNum, shouldAddHashLines, shouldAddLineNumbers);
|
|
650
|
+
|
|
651
|
+
let outputText: string;
|
|
652
|
+
let truncationInfo:
|
|
653
|
+
| { result: TruncationResult; options: { direction: "head"; startLine?: number; totalFileLines?: number } }
|
|
654
|
+
| undefined;
|
|
655
|
+
|
|
656
|
+
if (truncation.firstLineExceedsLimit) {
|
|
657
|
+
const firstLine = allLines[startLine] ?? "";
|
|
658
|
+
const firstLineBytes = Buffer.byteLength(firstLine, "utf-8");
|
|
659
|
+
const snippet = truncateHeadBytes(firstLine, DEFAULT_MAX_BYTES);
|
|
660
|
+
|
|
661
|
+
if (shouldAddHashLines) {
|
|
662
|
+
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
663
|
+
firstLineBytes,
|
|
664
|
+
)}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Hashline output requires full lines; cannot compute hashes for a truncated preview.]`;
|
|
665
|
+
} else {
|
|
666
|
+
outputText = formatText(snippet.text, startLineDisplay);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (snippet.text.length === 0) {
|
|
670
|
+
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
671
|
+
firstLineBytes,
|
|
672
|
+
)}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
details.truncation = truncation;
|
|
676
|
+
truncationInfo = {
|
|
677
|
+
result: truncation,
|
|
678
|
+
options: { direction: "head", startLine: startLineDisplay, totalFileLines: totalLines },
|
|
679
|
+
};
|
|
680
|
+
} else if (truncation.truncated) {
|
|
681
|
+
outputText = formatText(truncation.content, startLineDisplay);
|
|
682
|
+
details.truncation = truncation;
|
|
683
|
+
truncationInfo = {
|
|
684
|
+
result: truncation,
|
|
685
|
+
options: { direction: "head", startLine: startLineDisplay, totalFileLines: totalLines },
|
|
686
|
+
};
|
|
687
|
+
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
688
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
689
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
690
|
+
|
|
691
|
+
outputText = formatText(selectedContent, startLineDisplay);
|
|
692
|
+
outputText += `\n\n[${remaining} more lines in ${options.entityLabel}. Use sel=L${nextOffset} to continue]`;
|
|
693
|
+
} else {
|
|
694
|
+
outputText = formatText(truncation.content, startLineDisplay);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
resultBuilder.text(outputText);
|
|
698
|
+
if (truncationInfo) {
|
|
699
|
+
resultBuilder.truncation(truncationInfo.result, truncationInfo.options);
|
|
700
|
+
}
|
|
701
|
+
return resultBuilder.done();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async #readArchiveDirectory(
|
|
705
|
+
archive: ArchiveReader,
|
|
706
|
+
archivePath: string,
|
|
707
|
+
subPath: string,
|
|
708
|
+
limit: number | undefined,
|
|
709
|
+
details: ReadToolDetails,
|
|
710
|
+
signal?: AbortSignal,
|
|
711
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
712
|
+
const DEFAULT_LIMIT = 500;
|
|
713
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
714
|
+
const entries = archive.listDirectory(subPath);
|
|
715
|
+
|
|
716
|
+
const listLimit = applyListLimit(entries, { limit: effectiveLimit });
|
|
717
|
+
const limitedEntries = listLimit.items;
|
|
718
|
+
const limitMeta = listLimit.meta;
|
|
719
|
+
|
|
720
|
+
const results: string[] = [];
|
|
721
|
+
for (const entry of limitedEntries) {
|
|
722
|
+
throwIfAborted(signal);
|
|
723
|
+
if (entry.isDirectory) {
|
|
724
|
+
results.push(`${entry.name}/`);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const sizeSuffix = entry.size > 0 ? ` (${formatBytes(entry.size)})` : "";
|
|
729
|
+
results.push(`${entry.name}${sizeSuffix}`);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const output = results.length > 0 ? results.join("\n") : "(empty archive directory)";
|
|
733
|
+
const text = prependSuffixResolutionNotice(output, details.suffixResolution);
|
|
734
|
+
const truncation = truncateHead(text, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
735
|
+
const directoryDetails: ReadToolDetails = { ...details, isDirectory: true };
|
|
736
|
+
const resultBuilder = toolResult<ReadToolDetails>(directoryDetails).text(truncation.content);
|
|
737
|
+
resultBuilder.sourcePath(archivePath).limits({ resultLimit: limitMeta.resultLimit?.reached });
|
|
738
|
+
if (truncation.truncated) {
|
|
739
|
+
directoryDetails.truncation = truncation;
|
|
740
|
+
resultBuilder.truncation(truncation, { direction: "head" });
|
|
741
|
+
}
|
|
742
|
+
return resultBuilder.done();
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async #readArchive(
|
|
746
|
+
readPath: string,
|
|
747
|
+
offset: number | undefined,
|
|
748
|
+
limit: number | undefined,
|
|
749
|
+
resolvedArchivePath: ResolvedArchiveReadPath,
|
|
750
|
+
signal?: AbortSignal,
|
|
751
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
752
|
+
throwIfAborted(signal);
|
|
753
|
+
const archive = await openArchive(resolvedArchivePath.absolutePath);
|
|
754
|
+
throwIfAborted(signal);
|
|
755
|
+
|
|
756
|
+
const details: ReadToolDetails = {
|
|
757
|
+
resolvedPath: resolvedArchivePath.absolutePath,
|
|
758
|
+
suffixResolution: resolvedArchivePath.suffixResolution,
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const node = archive.getNode(resolvedArchivePath.archiveSubPath);
|
|
762
|
+
if (!node) {
|
|
763
|
+
throw new ToolError(`Path '${readPath}' not found inside archive`);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (node.isDirectory) {
|
|
767
|
+
return this.#readArchiveDirectory(
|
|
768
|
+
archive,
|
|
769
|
+
resolvedArchivePath.absolutePath,
|
|
770
|
+
resolvedArchivePath.archiveSubPath,
|
|
771
|
+
limit,
|
|
772
|
+
details,
|
|
773
|
+
signal,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const entry = await archive.readFile(resolvedArchivePath.archiveSubPath);
|
|
778
|
+
const text = decodeUtf8Text(entry.bytes);
|
|
779
|
+
if (text === null) {
|
|
780
|
+
return toolResult<ReadToolDetails>(details)
|
|
781
|
+
.text(
|
|
782
|
+
prependSuffixResolutionNotice(
|
|
783
|
+
`[Cannot read binary archive entry '${entry.path}' (${formatBytes(entry.size)})]`,
|
|
784
|
+
resolvedArchivePath.suffixResolution,
|
|
785
|
+
),
|
|
786
|
+
)
|
|
787
|
+
.sourcePath(resolvedArchivePath.absolutePath)
|
|
788
|
+
.done();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const result = this.#buildInMemoryTextResult(text, offset, limit, {
|
|
792
|
+
details,
|
|
793
|
+
sourcePath: resolvedArchivePath.absolutePath,
|
|
794
|
+
entityLabel: "archive entry",
|
|
795
|
+
});
|
|
796
|
+
const firstText = result.content.find((content): content is TextContent => content.type === "text");
|
|
797
|
+
if (firstText) {
|
|
798
|
+
firstText.text = prependSuffixResolutionNotice(firstText.text, resolvedArchivePath.suffixResolution);
|
|
799
|
+
}
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
async #readSqlite(
|
|
804
|
+
sel: string | undefined,
|
|
805
|
+
resolvedSqlitePath: ResolvedSqliteReadPath,
|
|
806
|
+
signal?: AbortSignal,
|
|
807
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
808
|
+
throwIfAborted(signal);
|
|
809
|
+
|
|
810
|
+
const selectorInput = sel
|
|
811
|
+
? parseSqliteSelectorInput(sel)
|
|
812
|
+
: { subPath: resolvedSqlitePath.sqliteSubPath, queryString: resolvedSqlitePath.queryString };
|
|
813
|
+
const selector = parseSqliteSelector(selectorInput.subPath, selectorInput.queryString);
|
|
814
|
+
const details: ReadToolDetails = {
|
|
815
|
+
resolvedPath: resolvedSqlitePath.absolutePath,
|
|
816
|
+
suffixResolution: resolvedSqlitePath.suffixResolution,
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
let db: Database | null = null;
|
|
820
|
+
try {
|
|
821
|
+
db = new Database(resolvedSqlitePath.absolutePath, { readonly: true, strict: true });
|
|
822
|
+
db.run("PRAGMA busy_timeout = 3000");
|
|
823
|
+
throwIfAborted(signal);
|
|
824
|
+
|
|
825
|
+
switch (selector.kind) {
|
|
826
|
+
case "list": {
|
|
827
|
+
const listLimit = applyListLimit(listTables(db), { limit: 500 });
|
|
828
|
+
const output = prependSuffixResolutionNotice(
|
|
829
|
+
renderTableList(listLimit.items),
|
|
830
|
+
resolvedSqlitePath.suffixResolution,
|
|
831
|
+
);
|
|
832
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
833
|
+
details.truncation = truncation.truncated ? truncation : undefined;
|
|
834
|
+
const resultBuilder = toolResult<ReadToolDetails>(details)
|
|
835
|
+
.text(truncation.content)
|
|
836
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
837
|
+
.limits({ resultLimit: listLimit.meta.resultLimit?.reached });
|
|
838
|
+
if (truncation.truncated) {
|
|
839
|
+
resultBuilder.truncation(truncation, { direction: "head" });
|
|
840
|
+
}
|
|
841
|
+
return resultBuilder.done();
|
|
842
|
+
}
|
|
843
|
+
case "schema": {
|
|
844
|
+
const sampleRows = queryRows(db, selector.table, { limit: selector.sampleLimit, offset: 0 });
|
|
845
|
+
let output = renderSchema(getTableSchema(db, selector.table), {
|
|
846
|
+
columns: sampleRows.columns,
|
|
847
|
+
rows: sampleRows.rows,
|
|
848
|
+
});
|
|
849
|
+
if (sampleRows.rows.length < sampleRows.totalCount) {
|
|
850
|
+
const remaining = sampleRows.totalCount - sampleRows.rows.length;
|
|
851
|
+
output += `\n[${remaining} more rows; use sel="${selector.table}?limit=20&offset=${sampleRows.rows.length}" to continue]`;
|
|
852
|
+
}
|
|
853
|
+
return toolResult<ReadToolDetails>(details)
|
|
854
|
+
.text(prependSuffixResolutionNotice(output, resolvedSqlitePath.suffixResolution))
|
|
855
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
856
|
+
.done();
|
|
857
|
+
}
|
|
858
|
+
case "row": {
|
|
859
|
+
const lookup = resolveTableRowLookup(db, selector.table);
|
|
860
|
+
const row =
|
|
861
|
+
lookup.kind === "pk"
|
|
862
|
+
? getRowByKey(db, selector.table, lookup, selector.key)
|
|
863
|
+
: getRowByRowId(db, selector.table, selector.key);
|
|
864
|
+
if (!row) {
|
|
865
|
+
return toolResult<ReadToolDetails>(details)
|
|
866
|
+
.text(
|
|
867
|
+
prependSuffixResolutionNotice(
|
|
868
|
+
`No row found in table '${selector.table}' for key '${selector.key}'.`,
|
|
869
|
+
resolvedSqlitePath.suffixResolution,
|
|
870
|
+
),
|
|
871
|
+
)
|
|
872
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
873
|
+
.done();
|
|
874
|
+
}
|
|
875
|
+
return toolResult<ReadToolDetails>(details)
|
|
876
|
+
.text(prependSuffixResolutionNotice(renderRow(row), resolvedSqlitePath.suffixResolution))
|
|
877
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
878
|
+
.done();
|
|
879
|
+
}
|
|
880
|
+
case "query": {
|
|
881
|
+
const page = queryRows(db, selector.table, selector);
|
|
882
|
+
return toolResult<ReadToolDetails>(details)
|
|
883
|
+
.text(
|
|
884
|
+
prependSuffixResolutionNotice(
|
|
885
|
+
renderTable(page.columns, page.rows, {
|
|
886
|
+
totalCount: page.totalCount,
|
|
887
|
+
offset: selector.offset,
|
|
888
|
+
limit: selector.limit,
|
|
889
|
+
table: selector.table,
|
|
890
|
+
dbPath: resolvedSqlitePath.absolutePath,
|
|
891
|
+
}),
|
|
892
|
+
resolvedSqlitePath.suffixResolution,
|
|
893
|
+
),
|
|
894
|
+
)
|
|
895
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
896
|
+
.done();
|
|
897
|
+
}
|
|
898
|
+
case "raw": {
|
|
899
|
+
const result = executeReadQuery(db, selector.sql);
|
|
900
|
+
return toolResult<ReadToolDetails>(details)
|
|
901
|
+
.text(
|
|
902
|
+
prependSuffixResolutionNotice(
|
|
903
|
+
renderTable(result.columns, result.rows, {
|
|
904
|
+
totalCount: result.rows.length,
|
|
905
|
+
offset: 0,
|
|
906
|
+
limit: result.rows.length || DEFAULT_MAX_LINES,
|
|
907
|
+
table: "query",
|
|
908
|
+
dbPath: resolvedSqlitePath.absolutePath,
|
|
909
|
+
}),
|
|
910
|
+
resolvedSqlitePath.suffixResolution,
|
|
911
|
+
),
|
|
912
|
+
)
|
|
913
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
914
|
+
.done();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
throw new ToolError("Unsupported SQLite selector");
|
|
919
|
+
} catch (error) {
|
|
920
|
+
if (error instanceof ToolError) {
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
924
|
+
} finally {
|
|
925
|
+
db?.close();
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async execute(
|
|
930
|
+
_toolCallId: string,
|
|
931
|
+
params: ReadParams,
|
|
932
|
+
signal?: AbortSignal,
|
|
933
|
+
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
934
|
+
_toolContext?: AgentToolContext,
|
|
935
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
936
|
+
let { path: readPath, sel, timeout } = params;
|
|
937
|
+
if (readPath.startsWith("file://")) {
|
|
938
|
+
readPath = expandPath(readPath);
|
|
939
|
+
}
|
|
940
|
+
const displayMode = resolveFileDisplayMode(this.session);
|
|
941
|
+
const chunkMode = resolveEditMode(this.session) === "chunk";
|
|
942
|
+
|
|
943
|
+
// Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://)
|
|
944
|
+
const internalRouter = this.session.internalRouter;
|
|
945
|
+
if (internalRouter?.canHandle(readPath)) {
|
|
946
|
+
const parsed = parseSel(sel);
|
|
947
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
948
|
+
return this.#handleInternalUrl(readPath, offset, limit);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const parsedUrlTarget = parseReadUrlTarget(readPath, sel);
|
|
952
|
+
if (parsedUrlTarget) {
|
|
953
|
+
if (!this.session.settings.get("fetch.enabled")) {
|
|
954
|
+
throw new ToolError("URL reads are disabled by settings.");
|
|
955
|
+
}
|
|
956
|
+
if (parsedUrlTarget.offset !== undefined || parsedUrlTarget.limit !== undefined) {
|
|
957
|
+
const cached = await loadReadUrlCacheEntry(
|
|
958
|
+
this.session,
|
|
959
|
+
{ path: parsedUrlTarget.path, timeout, raw: parsedUrlTarget.raw },
|
|
960
|
+
signal,
|
|
961
|
+
{
|
|
962
|
+
ensureArtifact: true,
|
|
963
|
+
preferCached: true,
|
|
964
|
+
},
|
|
965
|
+
);
|
|
966
|
+
return this.#buildInMemoryTextResult(cached.output, parsedUrlTarget.offset, parsedUrlTarget.limit, {
|
|
967
|
+
details: { ...cached.details },
|
|
968
|
+
sourceUrl: cached.details.finalUrl,
|
|
969
|
+
entityLabel: "URL output",
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
return executeReadUrl(this.session, { path: parsedUrlTarget.path, timeout, raw: parsedUrlTarget.raw }, signal);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const parsedReadPath = chunkMode ? parseChunkReadPath(readPath) : { filePath: readPath };
|
|
976
|
+
const localReadPath = parsedReadPath.filePath;
|
|
977
|
+
const pathSelectorParsed = chunkMode ? parseSel(parsedReadPath.selector) : { kind: "none" as const };
|
|
978
|
+
const pathChunkSelector = pathSelectorParsed.kind === "chunk" ? pathSelectorParsed.selector : undefined;
|
|
979
|
+
const selectorInput = sel ?? parsedReadPath.selector;
|
|
980
|
+
const rawSelectorInput = sel ?? parsedReadPath.selector;
|
|
981
|
+
const parsed = parseSel(selectorInput);
|
|
982
|
+
|
|
983
|
+
const archivePath = await this.#resolveArchiveReadPath(localReadPath, signal);
|
|
984
|
+
if (archivePath) {
|
|
985
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
986
|
+
return this.#readArchive(readPath, offset, limit, archivePath, signal);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const sqlitePath = await this.#resolveSqliteReadPath(readPath, signal);
|
|
990
|
+
if (sqlitePath) {
|
|
991
|
+
return this.#readSqlite(sel, sqlitePath, signal);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
let absolutePath = resolveReadPath(localReadPath, this.session.cwd);
|
|
995
|
+
let suffixResolution: { from: string; to: string } | undefined;
|
|
996
|
+
|
|
997
|
+
let isDirectory = false;
|
|
998
|
+
let fileSize = 0;
|
|
999
|
+
try {
|
|
1000
|
+
const stat = await Bun.file(absolutePath).stat();
|
|
1001
|
+
fileSize = stat.size;
|
|
1002
|
+
isDirectory = stat.isDirectory();
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
if (isNotFoundError(error)) {
|
|
1005
|
+
// Attempt unique suffix resolution before falling back to fuzzy suggestions
|
|
1006
|
+
if (!isRemoteMountPath(absolutePath)) {
|
|
1007
|
+
const suffixMatch = await findUniqueSuffixMatch(localReadPath, this.session.cwd, signal);
|
|
1008
|
+
if (suffixMatch) {
|
|
1009
|
+
try {
|
|
1010
|
+
const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
|
|
1011
|
+
absolutePath = suffixMatch.absolutePath;
|
|
1012
|
+
fileSize = retryStat.size;
|
|
1013
|
+
isDirectory = retryStat.isDirectory();
|
|
1014
|
+
suffixResolution = { from: localReadPath, to: suffixMatch.displayPath };
|
|
1015
|
+
} catch {
|
|
1016
|
+
// Suffix match candidate no longer stats — fall through to error path
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (!suffixResolution) {
|
|
1022
|
+
throw new ToolError(`Path '${localReadPath}' not found`);
|
|
1023
|
+
}
|
|
1024
|
+
} else {
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
if (isDirectory) {
|
|
1030
|
+
const dirResult = await this.#readDirectory(absolutePath, selToOffsetLimit(parsed).limit, signal);
|
|
1031
|
+
if (suffixResolution) {
|
|
1032
|
+
dirResult.details ??= {};
|
|
1033
|
+
dirResult.details.suffixResolution = suffixResolution;
|
|
1034
|
+
}
|
|
1035
|
+
return dirResult;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
const imageMetadata = await readImageMetadata(absolutePath);
|
|
1039
|
+
const mimeType = imageMetadata?.mimeType;
|
|
1040
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
1041
|
+
const hasEditTool = this.session.hasEditTool ?? true;
|
|
1042
|
+
const language = getLanguageFromPath(absolutePath);
|
|
1043
|
+
const skipChunksForExplore = !hasEditTool && !this.session.settings.get("read.explorechunks");
|
|
1044
|
+
const skipChunksForProse = isProseLanguage(language) && !this.session.settings.get("read.prosechunks");
|
|
1045
|
+
const shouldConvertWithMarkit =
|
|
1046
|
+
CONVERTIBLE_EXTENSIONS.has(ext) || (ext === ".ipynb" && (parsed.kind === "raw" || !chunkMode));
|
|
1047
|
+
|
|
1048
|
+
if (chunkMode && parsed.kind !== "raw" && !skipChunksForExplore && !skipChunksForProse) {
|
|
1049
|
+
const absoluteLineRange =
|
|
1050
|
+
pathChunkSelector && parsed.kind === "lines"
|
|
1051
|
+
? { startLine: parsed.startLine, endLine: parsed.endLine }
|
|
1052
|
+
: undefined;
|
|
1053
|
+
// sel= wins over path:chunk when both are provided (explicit param > embedded path).
|
|
1054
|
+
const effectiveSelector = sel ? selectorInput : (pathChunkSelector ?? selectorInput);
|
|
1055
|
+
const rawEffectiveSelector = sel ? selectorInput : (rawSelectorInput ?? effectiveSelector);
|
|
1056
|
+
const chunkReadPath =
|
|
1057
|
+
parsed.kind === "chunk" || (pathChunkSelector && !sel)
|
|
1058
|
+
? rawEffectiveSelector
|
|
1059
|
+
? `${localReadPath}:${rawEffectiveSelector}`
|
|
1060
|
+
: localReadPath
|
|
1061
|
+
: parsed.kind === "lines"
|
|
1062
|
+
? parsed.endLine !== undefined
|
|
1063
|
+
? `${localReadPath}:L${parsed.startLine}-L${parsed.endLine}`
|
|
1064
|
+
: `${localReadPath}:L${parsed.startLine}`
|
|
1065
|
+
: localReadPath;
|
|
1066
|
+
const chunkResult = await formatChunkedRead({
|
|
1067
|
+
filePath: absolutePath,
|
|
1068
|
+
readPath: chunkReadPath,
|
|
1069
|
+
cwd: this.session.cwd,
|
|
1070
|
+
language,
|
|
1071
|
+
omitChecksum: !hasEditTool,
|
|
1072
|
+
anchorStyle: resolveAnchorStyle(this.session.settings),
|
|
1073
|
+
absoluteLineRange,
|
|
1074
|
+
});
|
|
1075
|
+
let text = chunkResult.text;
|
|
1076
|
+
if (suffixResolution) {
|
|
1077
|
+
text = prependSuffixResolutionNotice(text, suffixResolution);
|
|
1078
|
+
}
|
|
1079
|
+
return toolResult<ReadToolDetails>({
|
|
1080
|
+
resolvedPath: absolutePath,
|
|
1081
|
+
suffixResolution,
|
|
1082
|
+
chunk: chunkResult.chunk,
|
|
1083
|
+
})
|
|
1084
|
+
.text(text)
|
|
1085
|
+
.sourcePath(absolutePath)
|
|
1086
|
+
.done();
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Read the file based on type
|
|
1090
|
+
let content: Array<TextContent | ImageContent>;
|
|
1091
|
+
let details: ReadToolDetails = {};
|
|
1092
|
+
let sourcePath: string | undefined;
|
|
1093
|
+
let truncationInfo:
|
|
1094
|
+
| { result: TruncationResult; options: { direction: "head"; startLine?: number; totalFileLines?: number } }
|
|
1095
|
+
| undefined;
|
|
1096
|
+
|
|
1097
|
+
if (mimeType) {
|
|
1098
|
+
if (this.#inspectImageEnabled) {
|
|
1099
|
+
const metadata = imageMetadata;
|
|
1100
|
+
const outputMime = metadata?.mimeType ?? mimeType;
|
|
1101
|
+
const outputBytes = fileSize;
|
|
1102
|
+
const metadataLines = [
|
|
1103
|
+
"Image metadata:",
|
|
1104
|
+
`- MIME: ${outputMime}`,
|
|
1105
|
+
`- Bytes: ${outputBytes} (${formatBytes(outputBytes)})`,
|
|
1106
|
+
metadata?.width !== undefined && metadata.height !== undefined
|
|
1107
|
+
? `- Dimensions: ${metadata.width}x${metadata.height}`
|
|
1108
|
+
: "- Dimensions: unknown",
|
|
1109
|
+
metadata?.channels !== undefined ? `- Channels: ${metadata.channels}` : "- Channels: unknown",
|
|
1110
|
+
metadata?.hasAlpha === true
|
|
1111
|
+
? "- Alpha: yes"
|
|
1112
|
+
: metadata?.hasAlpha === false
|
|
1113
|
+
? "- Alpha: no"
|
|
1114
|
+
: "- Alpha: unknown",
|
|
1115
|
+
"",
|
|
1116
|
+
`If you want to analyze the image, call inspect_image with path="${readPath}" and a question describing what to inspect and the desired output format.`,
|
|
1117
|
+
];
|
|
1118
|
+
content = [{ type: "text", text: metadataLines.join("\n") }];
|
|
1119
|
+
details = {};
|
|
1120
|
+
sourcePath = absolutePath;
|
|
1121
|
+
} else {
|
|
1122
|
+
if (fileSize > MAX_IMAGE_SIZE) {
|
|
1123
|
+
const sizeStr = formatBytes(fileSize);
|
|
1124
|
+
const maxStr = formatBytes(MAX_IMAGE_SIZE);
|
|
1125
|
+
throw new ToolError(`Image file too large: ${sizeStr} exceeds ${maxStr} limit.`);
|
|
1126
|
+
}
|
|
1127
|
+
try {
|
|
1128
|
+
const imageInput = await loadImageInput({
|
|
1129
|
+
path: readPath,
|
|
1130
|
+
cwd: this.session.cwd,
|
|
1131
|
+
autoResize: this.#autoResizeImages,
|
|
1132
|
+
maxBytes: MAX_IMAGE_SIZE,
|
|
1133
|
+
resolvedPath: absolutePath,
|
|
1134
|
+
detectedMimeType: mimeType,
|
|
1135
|
+
});
|
|
1136
|
+
if (!imageInput) {
|
|
1137
|
+
throw new ToolError(`Read image file [${mimeType}] failed: unsupported image format.`);
|
|
1138
|
+
}
|
|
1139
|
+
content = [
|
|
1140
|
+
{ type: "text", text: imageInput.textNote },
|
|
1141
|
+
{ type: "image", data: imageInput.data, mimeType: imageInput.mimeType },
|
|
1142
|
+
];
|
|
1143
|
+
details = {};
|
|
1144
|
+
sourcePath = imageInput.resolvedPath;
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
if (error instanceof ImageInputTooLargeError) {
|
|
1147
|
+
throw new ToolError(error.message);
|
|
1148
|
+
}
|
|
1149
|
+
throw error;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
} else if (shouldConvertWithMarkit) {
|
|
1153
|
+
// Convert document or notebook via markit.
|
|
1154
|
+
const result = await convertFileWithMarkit(absolutePath, signal);
|
|
1155
|
+
if (result.ok) {
|
|
1156
|
+
// Apply truncation to converted content
|
|
1157
|
+
const truncation = truncateHead(result.content);
|
|
1158
|
+
const outputText = truncation.content;
|
|
1159
|
+
|
|
1160
|
+
details = { truncation };
|
|
1161
|
+
sourcePath = absolutePath;
|
|
1162
|
+
truncationInfo = { result: truncation, options: { direction: "head", startLine: 1 } };
|
|
1163
|
+
|
|
1164
|
+
content = [{ type: "text", text: outputText }];
|
|
1165
|
+
} else if (result.error) {
|
|
1166
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: ${result.error || "conversion failed"}]` }];
|
|
1167
|
+
} else {
|
|
1168
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: conversion failed]` }];
|
|
1169
|
+
}
|
|
1170
|
+
} else {
|
|
1171
|
+
// Chunk mode: dispatch to chunk tree unless raw or line range requested
|
|
1172
|
+
if (chunkMode && parsed.kind !== "raw" && parsed.kind !== "lines") {
|
|
1173
|
+
const chunkSel = parsed.kind === "chunk" ? parsed.selector : undefined;
|
|
1174
|
+
const chunkResult = await formatChunkedRead({
|
|
1175
|
+
filePath: absolutePath,
|
|
1176
|
+
readPath: chunkSel ? `${localReadPath}:${chunkSel}` : localReadPath,
|
|
1177
|
+
cwd: this.session.cwd,
|
|
1178
|
+
language: getLanguageFromPath(absolutePath),
|
|
1179
|
+
omitChecksum: !(this.session.hasEditTool ?? true),
|
|
1180
|
+
anchorStyle: resolveAnchorStyle(this.session.settings),
|
|
1181
|
+
});
|
|
1182
|
+
let text = chunkResult.text;
|
|
1183
|
+
if (suffixResolution) {
|
|
1184
|
+
text = prependSuffixResolutionNotice(text, suffixResolution);
|
|
1185
|
+
}
|
|
1186
|
+
return toolResult<ReadToolDetails>({
|
|
1187
|
+
resolvedPath: absolutePath,
|
|
1188
|
+
suffixResolution,
|
|
1189
|
+
chunk: chunkResult.chunk,
|
|
1190
|
+
})
|
|
1191
|
+
.text(text)
|
|
1192
|
+
.sourcePath(absolutePath)
|
|
1193
|
+
.done();
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Raw text or line-range mode
|
|
1197
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
1198
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
1199
|
+
const startLineDisplay = startLine + 1;
|
|
1200
|
+
|
|
1201
|
+
const DEFAULT_LIMIT = this.#defaultLimit;
|
|
1202
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
1203
|
+
const maxLinesToCollect = Math.min(effectiveLimit, DEFAULT_MAX_LINES);
|
|
1204
|
+
const selectedLineLimit = effectiveLimit;
|
|
1205
|
+
|
|
1206
|
+
const streamResult = await streamLinesFromFile(
|
|
1207
|
+
absolutePath,
|
|
1208
|
+
startLine,
|
|
1209
|
+
maxLinesToCollect,
|
|
1210
|
+
DEFAULT_MAX_BYTES,
|
|
1211
|
+
selectedLineLimit,
|
|
1212
|
+
signal,
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
const {
|
|
1216
|
+
lines: collectedLines,
|
|
1217
|
+
totalFileLines,
|
|
1218
|
+
collectedBytes,
|
|
1219
|
+
stoppedByByteLimit,
|
|
1220
|
+
firstLinePreview,
|
|
1221
|
+
firstLineByteLength,
|
|
1222
|
+
} = streamResult;
|
|
1223
|
+
|
|
1224
|
+
// Check if offset is out of bounds - return graceful message instead of throwing
|
|
1225
|
+
if (startLine >= totalFileLines) {
|
|
1226
|
+
const suggestion =
|
|
1227
|
+
totalFileLines === 0
|
|
1228
|
+
? "The file is empty."
|
|
1229
|
+
: `Use sel=L1 to read from the start, or sel=L${totalFileLines} to read the last line.`;
|
|
1230
|
+
return toolResult<ReadToolDetails>({ resolvedPath: absolutePath, suffixResolution })
|
|
1231
|
+
.text(`Line ${startLineDisplay} is beyond end of file (${totalFileLines} lines total). ${suggestion}`)
|
|
1232
|
+
.done();
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const selectedContent = collectedLines.join("\n");
|
|
1236
|
+
const userLimitedLines = collectedLines.length;
|
|
1237
|
+
|
|
1238
|
+
const totalSelectedLines = totalFileLines - startLine;
|
|
1239
|
+
const totalSelectedBytes = collectedBytes;
|
|
1240
|
+
const wasTruncated = collectedLines.length < totalSelectedLines || stoppedByByteLimit;
|
|
1241
|
+
const firstLineExceedsLimit = firstLineByteLength !== undefined && firstLineByteLength > DEFAULT_MAX_BYTES;
|
|
1242
|
+
|
|
1243
|
+
const truncation: TruncationResult = {
|
|
1244
|
+
content: selectedContent,
|
|
1245
|
+
truncated: wasTruncated,
|
|
1246
|
+
truncatedBy: stoppedByByteLimit ? "bytes" : wasTruncated ? "lines" : undefined,
|
|
1247
|
+
totalLines: totalSelectedLines,
|
|
1248
|
+
totalBytes: totalSelectedBytes,
|
|
1249
|
+
outputLines: collectedLines.length,
|
|
1250
|
+
outputBytes: collectedBytes,
|
|
1251
|
+
lastLinePartial: false,
|
|
1252
|
+
firstLineExceedsLimit,
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
const shouldAddHashLines = displayMode.hashLines;
|
|
1256
|
+
const shouldAddLineNumbers = shouldAddHashLines ? false : displayMode.lineNumbers;
|
|
1257
|
+
const formatText = (text: string, startNum: number): string => {
|
|
1258
|
+
return formatTextWithMode(text, startNum, shouldAddHashLines, shouldAddLineNumbers);
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
let outputText: string;
|
|
1262
|
+
|
|
1263
|
+
if (truncation.firstLineExceedsLimit) {
|
|
1264
|
+
const firstLineBytes = firstLineByteLength ?? 0;
|
|
1265
|
+
const snippet = firstLinePreview ?? { text: "", bytes: 0 };
|
|
1266
|
+
|
|
1267
|
+
if (shouldAddHashLines) {
|
|
1268
|
+
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
1269
|
+
firstLineBytes,
|
|
1270
|
+
)}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Hashline output requires full lines; cannot compute hashes for a truncated preview.]`;
|
|
1271
|
+
} else {
|
|
1272
|
+
outputText = formatText(snippet.text, startLineDisplay);
|
|
1273
|
+
}
|
|
1274
|
+
if (snippet.text.length === 0) {
|
|
1275
|
+
outputText = `[Line ${startLineDisplay} is ${formatBytes(
|
|
1276
|
+
firstLineBytes,
|
|
1277
|
+
)}, exceeds ${formatBytes(DEFAULT_MAX_BYTES)} limit. Unable to display a valid UTF-8 snippet.]`;
|
|
1278
|
+
}
|
|
1279
|
+
details = { truncation };
|
|
1280
|
+
sourcePath = absolutePath;
|
|
1281
|
+
truncationInfo = {
|
|
1282
|
+
result: truncation,
|
|
1283
|
+
options: { direction: "head", startLine: startLineDisplay, totalFileLines },
|
|
1284
|
+
};
|
|
1285
|
+
} else if (truncation.truncated) {
|
|
1286
|
+
outputText = formatText(truncation.content, startLineDisplay);
|
|
1287
|
+
details = { truncation };
|
|
1288
|
+
sourcePath = absolutePath;
|
|
1289
|
+
truncationInfo = {
|
|
1290
|
+
result: truncation,
|
|
1291
|
+
options: { direction: "head", startLine: startLineDisplay, totalFileLines },
|
|
1292
|
+
};
|
|
1293
|
+
} else if (startLine + userLimitedLines < totalFileLines) {
|
|
1294
|
+
const remaining = totalFileLines - (startLine + userLimitedLines);
|
|
1295
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
1296
|
+
|
|
1297
|
+
outputText = formatText(truncation.content, startLineDisplay);
|
|
1298
|
+
outputText += `\n\n[${remaining} more lines in file. Use sel=L${nextOffset} to continue]`;
|
|
1299
|
+
details = {};
|
|
1300
|
+
sourcePath = absolutePath;
|
|
1301
|
+
} else {
|
|
1302
|
+
// No truncation, no user limit exceeded
|
|
1303
|
+
outputText = formatText(truncation.content, startLineDisplay);
|
|
1304
|
+
details = {};
|
|
1305
|
+
sourcePath = absolutePath;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
content = [{ type: "text", text: outputText }];
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (suffixResolution) {
|
|
1312
|
+
details.suffixResolution = suffixResolution;
|
|
1313
|
+
// Inline resolution notice into first text block so the model sees the actual path
|
|
1314
|
+
const notice = `[Path '${suffixResolution.from}' not found; resolved to '${suffixResolution.to}' via suffix match]`;
|
|
1315
|
+
const firstText = content.find((c): c is TextContent => c.type === "text");
|
|
1316
|
+
if (firstText) {
|
|
1317
|
+
firstText.text = `${notice}\n${firstText.text}`;
|
|
1318
|
+
} else {
|
|
1319
|
+
content = [{ type: "text", text: notice }, ...content];
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const resultBuilder = toolResult(details).content(content);
|
|
1323
|
+
if (sourcePath) {
|
|
1324
|
+
resultBuilder.sourcePath(sourcePath);
|
|
1325
|
+
}
|
|
1326
|
+
if (truncationInfo) {
|
|
1327
|
+
resultBuilder.truncation(truncationInfo.result, truncationInfo.options);
|
|
1328
|
+
}
|
|
1329
|
+
return resultBuilder.done();
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://).
|
|
1334
|
+
* Supports pagination via offset/limit but rejects them when query extraction is used.
|
|
1335
|
+
*/
|
|
1336
|
+
async #handleInternalUrl(url: string, offset?: number, limit?: number): Promise<AgentToolResult<ReadToolDetails>> {
|
|
1337
|
+
const internalRouter = this.session.internalRouter!;
|
|
1338
|
+
|
|
1339
|
+
// Check if URL has query extraction (agent:// only).
|
|
1340
|
+
// Use parseInternalUrl which handles colons in host (namespaced skills).
|
|
1341
|
+
let parsed: InternalUrl;
|
|
1342
|
+
try {
|
|
1343
|
+
parsed = parseInternalUrl(url);
|
|
1344
|
+
} catch (e) {
|
|
1345
|
+
throw new ToolError(e instanceof Error ? e.message : String(e));
|
|
1346
|
+
}
|
|
1347
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
1348
|
+
let hasExtraction = false;
|
|
1349
|
+
if (scheme === "agent") {
|
|
1350
|
+
const hasPathExtraction = parsed.pathname && parsed.pathname !== "/" && parsed.pathname !== "";
|
|
1351
|
+
const queryParam = parsed.searchParams.get("q");
|
|
1352
|
+
const hasQueryExtraction = queryParam !== null && queryParam !== "";
|
|
1353
|
+
hasExtraction = hasPathExtraction || hasQueryExtraction;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Reject offset/limit with query extraction
|
|
1357
|
+
if (hasExtraction && (offset !== undefined || limit !== undefined)) {
|
|
1358
|
+
throw new ToolError("Cannot combine query extraction with offset/limit");
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// Resolve the internal URL
|
|
1362
|
+
const resource = await internalRouter.resolve(url);
|
|
1363
|
+
const details: ReadToolDetails = { resolvedPath: resource.sourcePath };
|
|
1364
|
+
|
|
1365
|
+
// If extraction was used, return directly (no pagination)
|
|
1366
|
+
if (hasExtraction) {
|
|
1367
|
+
return toolResult(details).text(resource.content).sourceInternal(url).done();
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
return this.#buildInMemoryTextResult(resource.content, offset, limit, {
|
|
1371
|
+
details,
|
|
1372
|
+
sourcePath: resource.sourcePath,
|
|
1373
|
+
sourceInternal: url,
|
|
1374
|
+
entityLabel: "resource",
|
|
1375
|
+
ignoreResultLimits: scheme === "skill",
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/** Read directory contents as a formatted listing */
|
|
1380
|
+
async #readDirectory(
|
|
1381
|
+
absolutePath: string,
|
|
1382
|
+
limit: number | undefined,
|
|
1383
|
+
signal?: AbortSignal,
|
|
1384
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
1385
|
+
const DEFAULT_LIMIT = 500;
|
|
1386
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
1387
|
+
|
|
1388
|
+
let entries: string[];
|
|
1389
|
+
try {
|
|
1390
|
+
entries = await fs.readdir(absolutePath);
|
|
1391
|
+
} catch (error) {
|
|
1392
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1393
|
+
throw new ToolError(`Cannot read directory: ${message}`);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Sort alphabetically (case-insensitive)
|
|
1397
|
+
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
1398
|
+
|
|
1399
|
+
const listLimit = applyListLimit(entries, { limit: effectiveLimit });
|
|
1400
|
+
const limitedEntries = listLimit.items;
|
|
1401
|
+
const limitMeta = listLimit.meta;
|
|
1402
|
+
|
|
1403
|
+
// Format entries with directory indicators and ages
|
|
1404
|
+
const results: string[] = [];
|
|
1405
|
+
|
|
1406
|
+
for (const entry of limitedEntries) {
|
|
1407
|
+
throwIfAborted(signal);
|
|
1408
|
+
const fullPath = path.join(absolutePath, entry);
|
|
1409
|
+
let suffix = "";
|
|
1410
|
+
let age = "";
|
|
1411
|
+
|
|
1412
|
+
try {
|
|
1413
|
+
const entryStat = await fs.stat(fullPath);
|
|
1414
|
+
suffix = entryStat.isDirectory() ? "/" : "";
|
|
1415
|
+
const ageSeconds = Math.floor((Date.now() - entryStat.mtimeMs) / 1000);
|
|
1416
|
+
age = formatAge(ageSeconds);
|
|
1417
|
+
} catch {
|
|
1418
|
+
// Skip entries we can't stat
|
|
1419
|
+
continue;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
const line = age ? `${entry}${suffix} (${age})` : entry + suffix;
|
|
1423
|
+
results.push(line);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (results.length === 0) {
|
|
1427
|
+
return { content: [{ type: "text", text: "(empty directory)" }], details: {} };
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
const output = results.join("\n");
|
|
1431
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
1432
|
+
|
|
1433
|
+
const details: ReadToolDetails = {
|
|
1434
|
+
isDirectory: true,
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
const resultBuilder = toolResult(details)
|
|
1438
|
+
.text(truncation.content)
|
|
1439
|
+
.limits({ resultLimit: limitMeta.resultLimit?.reached });
|
|
1440
|
+
if (truncation.truncated) {
|
|
1441
|
+
resultBuilder.truncation(truncation, { direction: "head" });
|
|
1442
|
+
details.truncation = truncation;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
return resultBuilder.done();
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// =============================================================================
|
|
1450
|
+
// TUI Renderer
|
|
1451
|
+
// =============================================================================
|
|
1452
|
+
|
|
1453
|
+
interface ReadRenderArgs {
|
|
1454
|
+
path?: string;
|
|
1455
|
+
file_path?: string;
|
|
1456
|
+
sel?: string;
|
|
1457
|
+
timeout?: number;
|
|
1458
|
+
// Legacy fields from old schema — tolerated for in-flight tool calls during transition
|
|
1459
|
+
offset?: number;
|
|
1460
|
+
limit?: number;
|
|
1461
|
+
raw?: boolean;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
export const readToolRenderer = {
|
|
1465
|
+
renderCall(args: ReadRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1466
|
+
if (isReadableUrlPath(args.file_path || args.path || "")) {
|
|
1467
|
+
return renderReadUrlCall(args, _options, uiTheme);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const rawPath = args.file_path || args.path || "";
|
|
1471
|
+
const filePath = shortenPath(rawPath);
|
|
1472
|
+
const offset = args.offset;
|
|
1473
|
+
const limit = args.limit;
|
|
1474
|
+
|
|
1475
|
+
let pathDisplay = filePath || "…";
|
|
1476
|
+
if (offset !== undefined || limit !== undefined) {
|
|
1477
|
+
const startLine = offset ?? 1;
|
|
1478
|
+
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
1479
|
+
pathDisplay += `:${startLine}${endLine ? `-${endLine}` : ""}`;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const text = renderStatusLine({ icon: "pending", title: "Read", description: pathDisplay }, uiTheme);
|
|
1483
|
+
return new Text(text, 0, 0);
|
|
1484
|
+
},
|
|
1485
|
+
|
|
1486
|
+
renderResult(
|
|
1487
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ReadToolDetails },
|
|
1488
|
+
_options: RenderResultOptions,
|
|
1489
|
+
uiTheme: Theme,
|
|
1490
|
+
args?: ReadRenderArgs,
|
|
1491
|
+
): Component {
|
|
1492
|
+
const urlDetails = result.details as ReadUrlToolDetails | undefined;
|
|
1493
|
+
if (urlDetails?.kind === "url" || isReadableUrlPath(args?.file_path || args?.path || "")) {
|
|
1494
|
+
return renderReadUrlResult(
|
|
1495
|
+
result as { content: Array<{ type: string; text?: string }>; details?: ReadUrlToolDetails },
|
|
1496
|
+
_options,
|
|
1497
|
+
uiTheme,
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const details = result.details;
|
|
1502
|
+
const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1503
|
+
const imageContent = result.content?.find(c => c.type === "image");
|
|
1504
|
+
const rawPath = args?.file_path || args?.path || "";
|
|
1505
|
+
const filePath = shortenPath(rawPath);
|
|
1506
|
+
const lang = getLanguageFromPath(rawPath);
|
|
1507
|
+
|
|
1508
|
+
const warningLines: string[] = [];
|
|
1509
|
+
const truncation = details?.meta?.truncation;
|
|
1510
|
+
const fallback = details?.truncation;
|
|
1511
|
+
if (details?.resolvedPath) {
|
|
1512
|
+
warningLines.push(uiTheme.fg("dim", wrapBrackets(`Resolved path: ${details.resolvedPath}`, uiTheme)));
|
|
1513
|
+
}
|
|
1514
|
+
if (truncation) {
|
|
1515
|
+
if (fallback?.firstLineExceedsLimit) {
|
|
1516
|
+
let warning = `First line exceeds ${formatBytes(fallback.outputBytes ?? fallback.totalBytes)} limit`;
|
|
1517
|
+
if (truncation.artifactId) {
|
|
1518
|
+
warning += `. ${formatFullOutputReference(truncation.artifactId)}`;
|
|
1519
|
+
}
|
|
1520
|
+
warningLines.push(uiTheme.fg("warning", wrapBrackets(warning, uiTheme)));
|
|
1521
|
+
} else {
|
|
1522
|
+
const warning = formatStyledTruncationWarning(details?.meta, uiTheme);
|
|
1523
|
+
if (warning) warningLines.push(warning);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (imageContent) {
|
|
1528
|
+
const suffix = details?.suffixResolution;
|
|
1529
|
+
const displayPath = suffix ? shortenPath(suffix.to) : filePath || rawPath || "image";
|
|
1530
|
+
const correction = suffix ? ` ${uiTheme.fg("dim", `(corrected from ${shortenPath(suffix.from)})`)}` : "";
|
|
1531
|
+
const header = renderStatusLine({ title: "Read", description: `${displayPath}${correction}` }, uiTheme);
|
|
1532
|
+
const detailLines = contentText ? contentText.split("\n").map(line => uiTheme.fg("toolOutput", line)) : [];
|
|
1533
|
+
const lines = [...detailLines, ...warningLines];
|
|
1534
|
+
const outputBlock = new CachedOutputBlock();
|
|
1535
|
+
return {
|
|
1536
|
+
render: (width: number) =>
|
|
1537
|
+
outputBlock.render(
|
|
1538
|
+
{
|
|
1539
|
+
header,
|
|
1540
|
+
state: "success",
|
|
1541
|
+
sections: [
|
|
1542
|
+
{
|
|
1543
|
+
label: uiTheme.fg("toolTitle", "Details"),
|
|
1544
|
+
lines: lines.length > 0 ? lines : [uiTheme.fg("dim", "(image)")],
|
|
1545
|
+
},
|
|
1546
|
+
],
|
|
1547
|
+
width,
|
|
1548
|
+
},
|
|
1549
|
+
uiTheme,
|
|
1550
|
+
),
|
|
1551
|
+
invalidate: () => outputBlock.invalidate(),
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
const suffix = details?.suffixResolution;
|
|
1556
|
+
const displayPath = suffix ? shortenPath(suffix.to) : filePath;
|
|
1557
|
+
const correction = suffix ? ` ${uiTheme.fg("dim", `(corrected from ${shortenPath(suffix.from)})`)}` : "";
|
|
1558
|
+
let title = displayPath ? `Read ${displayPath}${correction}` : "Read";
|
|
1559
|
+
if (args?.offset !== undefined || args?.limit !== undefined) {
|
|
1560
|
+
const startLine = args.offset ?? 1;
|
|
1561
|
+
const endLine = args.limit !== undefined ? startLine + args.limit - 1 : "";
|
|
1562
|
+
title += `:${startLine}${endLine ? `-${endLine}` : ""}`;
|
|
1563
|
+
}
|
|
1564
|
+
let cachedWidth: number | undefined;
|
|
1565
|
+
let cachedLines: string[] | undefined;
|
|
1566
|
+
return {
|
|
1567
|
+
render: (width: number) => {
|
|
1568
|
+
if (cachedLines && cachedWidth === width) return cachedLines;
|
|
1569
|
+
cachedLines = renderCodeCell(
|
|
1570
|
+
{
|
|
1571
|
+
code: contentText,
|
|
1572
|
+
language: lang,
|
|
1573
|
+
title,
|
|
1574
|
+
status: "complete",
|
|
1575
|
+
output: warningLines.length > 0 ? warningLines.join("\n") : undefined,
|
|
1576
|
+
expanded: true,
|
|
1577
|
+
width,
|
|
1578
|
+
},
|
|
1579
|
+
uiTheme,
|
|
1580
|
+
);
|
|
1581
|
+
cachedWidth = width;
|
|
1582
|
+
return cachedLines;
|
|
1583
|
+
},
|
|
1584
|
+
invalidate: () => {
|
|
1585
|
+
cachedWidth = undefined;
|
|
1586
|
+
cachedLines = undefined;
|
|
1587
|
+
},
|
|
1588
|
+
};
|
|
1589
|
+
},
|
|
1590
|
+
mergeCallAndResult: true,
|
|
1591
|
+
};
|