@nghyane/arcane 0.1.0
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 +3 -0
- package/README.md +12 -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 +550 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/todo.ts +296 -0
- package/examples/extensions/tools.ts +144 -0
- package/examples/extensions/with-deps/index.ts +35 -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 +116 -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 +150 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +119 -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 +36 -0
- package/examples/sdk/08-slash-commands.ts +41 -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 +150 -0
- package/package.json +464 -0
- package/scripts/format-prompts.ts +184 -0
- package/scripts/generate-docs-index.ts +40 -0
- package/scripts/generate-template.ts +32 -0
- package/src/bun-imports.d.ts +22 -0
- package/src/capability/context-file.ts +39 -0
- package/src/capability/extension-module.ts +33 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +89 -0
- package/src/capability/hook.ts +39 -0
- package/src/capability/index.ts +432 -0
- package/src/capability/instruction.ts +36 -0
- package/src/capability/mcp.ts +60 -0
- package/src/capability/prompt.ts +34 -0
- package/src/capability/rule.ts +223 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +48 -0
- package/src/capability/slash-command.ts +39 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +37 -0
- package/src/capability/types.ts +156 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/config-cli.ts +357 -0
- package/src/cli/file-processor.ts +124 -0
- package/src/cli/grep-cli.ts +152 -0
- package/src/cli/jupyter-cli.ts +106 -0
- package/src/cli/list-models.ts +103 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +42 -0
- package/src/cli/setup-cli.ts +376 -0
- package/src/cli/shell-cli.ts +174 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/stats-cli.ts +197 -0
- package/src/cli/update-cli.ts +286 -0
- package/src/cli/web-search-cli.ts +143 -0
- package/src/cli.ts +65 -0
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +41 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/launch.ts +139 -0
- package/src/commands/plugin.ts +70 -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 +42 -0
- package/src/commit/agentic/agent.ts +311 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +359 -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 +69 -0
- package/src/commit/agentic/tools/analyze-file.ts +118 -0
- package/src/commit/agentic/tools/git-file-diff.ts +194 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +84 -0
- package/src/commit/agentic/tools/index.ts +56 -0
- package/src/commit/agentic/tools/propose-changelog.ts +128 -0
- package/src/commit/agentic/tools/propose-commit.ts +154 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/split-commit.ts +280 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +200 -0
- package/src/commit/analysis/conventional.ts +165 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +112 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +37 -0
- package/src/commit/changelog/generate.ts +110 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +93 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/git/errors.ts +9 -0
- package/src/commit/git/index.ts +211 -0
- package/src/commit/git/operations.ts +54 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +64 -0
- package/src/commit/map-reduce/map-phase.ts +178 -0
- package/src/commit/map-reduce/reduce-phase.ts +145 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +69 -0
- package/src/commit/pipeline.ts +243 -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 +109 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +280 -0
- package/src/config/model-registry.ts +1140 -0
- package/src/config/model-resolver.ts +812 -0
- package/src/config/prompt-templates.ts +526 -0
- package/src/config/resolve-config-value.ts +92 -0
- package/src/config/settings-schema.ts +1236 -0
- package/src/config/settings.ts +706 -0
- package/src/config.ts +414 -0
- package/src/cursor.ts +239 -0
- package/src/debug/index.ts +431 -0
- package/src/debug/log-formatting.ts +60 -0
- package/src/debug/log-viewer.ts +903 -0
- package/src/debug/profiler.ts +158 -0
- package/src/debug/report-bundle.ts +366 -0
- package/src/debug/system-info.ts +112 -0
- package/src/discovery/agents-md.ts +68 -0
- package/src/discovery/agents.ts +199 -0
- package/src/discovery/builtin.ts +815 -0
- package/src/discovery/claude-plugins.ts +205 -0
- package/src/discovery/claude.ts +506 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +532 -0
- package/src/discovery/cursor.ts +218 -0
- package/src/discovery/gemini.ts +395 -0
- package/src/discovery/github.ts +117 -0
- package/src/discovery/helpers.ts +698 -0
- package/src/discovery/index.ts +89 -0
- package/src/discovery/mcp-json.ts +156 -0
- package/src/discovery/opencode.ts +394 -0
- package/src/discovery/ssh.ts +160 -0
- package/src/discovery/vscode.ts +103 -0
- package/src/discovery/windsurf.ts +145 -0
- package/src/exa/company.ts +57 -0
- package/src/exa/index.ts +62 -0
- package/src/exa/linkedin.ts +57 -0
- package/src/exa/mcp-client.ts +289 -0
- package/src/exa/render.ts +244 -0
- package/src/exa/researcher.ts +89 -0
- package/src/exa/search.ts +330 -0
- package/src/exa/types.ts +166 -0
- package/src/exa/websets.ts +247 -0
- package/src/exec/bash-executor.ts +184 -0
- package/src/exec/exec.ts +53 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +162 -0
- package/src/export/html/template.css +889 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +45 -0
- package/src/export/html/template.js +1329 -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/review/index.ts +433 -0
- package/src/extensibility/custom-commands/index.ts +15 -0
- package/src/extensibility/custom-commands/loader.ts +231 -0
- package/src/extensibility/custom-commands/types.ts +111 -0
- package/src/extensibility/custom-tools/index.ts +22 -0
- package/src/extensibility/custom-tools/loader.ts +235 -0
- package/src/extensibility/custom-tools/types.ts +226 -0
- package/src/extensibility/custom-tools/wrapper.ts +45 -0
- package/src/extensibility/extensions/index.ts +136 -0
- package/src/extensibility/extensions/loader.ts +520 -0
- package/src/extensibility/extensions/runner.ts +774 -0
- package/src/extensibility/extensions/types.ts +1293 -0
- package/src/extensibility/extensions/wrapper.ts +188 -0
- package/src/extensibility/hooks/index.ts +16 -0
- package/src/extensibility/hooks/loader.ts +273 -0
- package/src/extensibility/hooks/runner.ts +441 -0
- package/src/extensibility/hooks/tool-wrapper.ts +106 -0
- package/src/extensibility/hooks/types.ts +817 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +281 -0
- package/src/extensibility/plugins/index.ts +33 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/loader.ts +338 -0
- package/src/extensibility/plugins/manager.ts +716 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +190 -0
- package/src/extensibility/skills.ts +385 -0
- package/src/extensibility/slash-commands.ts +287 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/index.ts +275 -0
- package/src/internal-urls/agent-protocol.ts +136 -0
- package/src/internal-urls/artifact-protocol.ts +97 -0
- package/src/internal-urls/docs-index.generated.ts +54 -0
- package/src/internal-urls/docs-protocol.ts +84 -0
- package/src/internal-urls/index.ts +31 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/memory-protocol.ts +133 -0
- package/src/internal-urls/router.ts +70 -0
- package/src/internal-urls/rule-protocol.ts +55 -0
- package/src/internal-urls/skill-protocol.ts +111 -0
- package/src/internal-urls/types.ts +52 -0
- package/src/ipy/executor.ts +556 -0
- package/src/ipy/gateway-coordinator.ts +426 -0
- package/src/ipy/kernel.ts +892 -0
- package/src/ipy/modules.ts +109 -0
- package/src/ipy/prelude.py +831 -0
- package/src/ipy/prelude.ts +3 -0
- package/src/ipy/runtime.ts +222 -0
- package/src/lsp/client.ts +867 -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 +397 -0
- package/src/lsp/defaults.json +464 -0
- package/src/lsp/edits.ts +109 -0
- package/src/lsp/index.ts +1268 -0
- package/src/lsp/lspmux.ts +250 -0
- package/src/lsp/render.ts +689 -0
- package/src/lsp/types.ts +414 -0
- package/src/lsp/utils.ts +549 -0
- package/src/main.ts +773 -0
- package/src/mcp/client.ts +239 -0
- package/src/mcp/config-writer.ts +215 -0
- package/src/mcp/config.ts +363 -0
- package/src/mcp/index.ts +55 -0
- package/src/mcp/json-rpc.ts +84 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +490 -0
- package/src/mcp/oauth-discovery.ts +274 -0
- package/src/mcp/oauth-flow.ts +229 -0
- package/src/mcp/render.ts +123 -0
- package/src/mcp/tool-bridge.ts +372 -0
- package/src/mcp/tool-cache.ts +121 -0
- package/src/mcp/transports/http.ts +332 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +281 -0
- package/src/mcp/types.ts +248 -0
- package/src/memories/index.ts +1099 -0
- package/src/memories/storage.ts +563 -0
- package/src/modes/components/agent-dashboard.ts +1130 -0
- package/src/modes/components/assistant-message.ts +144 -0
- package/src/modes/components/bash-execution.ts +218 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +45 -0
- package/src/modes/components/codemode-group.ts +369 -0
- package/src/modes/components/compaction-summary-message.ts +51 -0
- package/src/modes/components/countdown-timer.ts +46 -0
- package/src/modes/components/custom-editor.ts +181 -0
- package/src/modes/components/custom-message.ts +91 -0
- package/src/modes/components/diff.ts +186 -0
- package/src/modes/components/dynamic-border.ts +25 -0
- package/src/modes/components/extensions/extension-dashboard.ts +325 -0
- package/src/modes/components/extensions/extension-list.ts +484 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +321 -0
- package/src/modes/components/extensions/state-manager.ts +586 -0
- package/src/modes/components/extensions/types.ts +191 -0
- package/src/modes/components/footer.ts +315 -0
- package/src/modes/components/history-search.ts +157 -0
- package/src/modes/components/hook-editor.ts +101 -0
- package/src/modes/components/hook-input.ts +72 -0
- package/src/modes/components/hook-message.ts +100 -0
- package/src/modes/components/hook-selector.ts +155 -0
- package/src/modes/components/index.ts +41 -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 +1295 -0
- package/src/modes/components/model-selector.ts +625 -0
- package/src/modes/components/oauth-selector.ts +210 -0
- package/src/modes/components/plugin-settings.ts +477 -0
- package/src/modes/components/python-execution.ts +196 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +119 -0
- package/src/modes/components/session-selector.ts +242 -0
- package/src/modes/components/settings-defs.ts +340 -0
- package/src/modes/components/settings-selector.ts +529 -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/index.ts +4 -0
- package/src/modes/components/status-line/presets.ts +94 -0
- package/src/modes/components/status-line/segments.ts +352 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +75 -0
- package/src/modes/components/status-line-segment-editor.ts +354 -0
- package/src/modes/components/status-line.ts +421 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +64 -0
- package/src/modes/components/todo-display.ts +115 -0
- package/src/modes/components/todo-reminder.ts +40 -0
- package/src/modes/components/tool-execution.ts +703 -0
- package/src/modes/components/tree-selector.ts +904 -0
- package/src/modes/components/ttsr-notification.ts +80 -0
- package/src/modes/components/user-message-selector.ts +146 -0
- package/src/modes/components/user-message.ts +22 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +247 -0
- package/src/modes/controllers/command-controller.ts +1120 -0
- package/src/modes/controllers/event-controller.ts +479 -0
- package/src/modes/controllers/extension-ui-controller.ts +778 -0
- package/src/modes/controllers/input-controller.ts +671 -0
- package/src/modes/controllers/mcp-command-controller.ts +1315 -0
- package/src/modes/controllers/selector-controller.ts +712 -0
- package/src/modes/controllers/ssh-command-controller.ts +452 -0
- package/src/modes/index.ts +15 -0
- package/src/modes/interactive-mode.ts +1027 -0
- package/src/modes/print-mode.ts +191 -0
- package/src/modes/rpc/rpc-client.ts +583 -0
- package/src/modes/rpc/rpc-mode.ts +700 -0
- package/src/modes/rpc/rpc-types.ts +236 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +195 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +111 -0
- package/src/modes/theme/theme-schema.json +429 -0
- package/src/modes/theme/theme.ts +2333 -0
- package/src/modes/types.ts +216 -0
- package/src/modes/utils/ui-helpers.ts +529 -0
- package/src/patch/applicator.ts +1482 -0
- package/src/patch/diff.ts +425 -0
- package/src/patch/fuzzy.ts +784 -0
- package/src/patch/hashline.ts +972 -0
- package/src/patch/index.ts +964 -0
- package/src/patch/normalize.ts +397 -0
- package/src/patch/normative.ts +72 -0
- package/src/patch/parser.ts +532 -0
- package/src/patch/shared.ts +400 -0
- package/src/patch/types.ts +292 -0
- package/src/priority.json +35 -0
- package/src/prompts/agents/explore.md +48 -0
- package/src/prompts/agents/frontmatter.md +9 -0
- package/src/prompts/agents/init.md +36 -0
- package/src/prompts/agents/librarian.md +53 -0
- package/src/prompts/agents/oracle.md +51 -0
- package/src/prompts/agents/reviewer.md +70 -0
- package/src/prompts/agents/task.md +14 -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 +64 -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/custom-system-prompt.md +68 -0
- package/src/prompts/system/file-operations.md +10 -0
- package/src/prompts/system/subagent-submit-reminder.md +11 -0
- package/src/prompts/system/subagent-system-prompt.md +31 -0
- package/src/prompts/system/subagent-user-prompt.md +8 -0
- package/src/prompts/system/summarization-system.md +3 -0
- package/src/prompts/system/system-prompt.md +300 -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 +44 -0
- package/src/prompts/tools/bash.md +24 -0
- package/src/prompts/tools/browser.md +33 -0
- package/src/prompts/tools/calculator.md +12 -0
- package/src/prompts/tools/explore.md +29 -0
- package/src/prompts/tools/fetch.md +16 -0
- package/src/prompts/tools/find.md +18 -0
- package/src/prompts/tools/gemini-image.md +23 -0
- package/src/prompts/tools/grep.md +28 -0
- package/src/prompts/tools/hashline.md +232 -0
- package/src/prompts/tools/librarian.md +24 -0
- package/src/prompts/tools/lsp.md +28 -0
- package/src/prompts/tools/oracle.md +26 -0
- package/src/prompts/tools/patch.md +74 -0
- package/src/prompts/tools/python.md +66 -0
- package/src/prompts/tools/read.md +36 -0
- package/src/prompts/tools/replace.md +38 -0
- package/src/prompts/tools/reviewer.md +41 -0
- package/src/prompts/tools/ssh.md +51 -0
- package/src/prompts/tools/task-summary.md +28 -0
- package/src/prompts/tools/task.md +275 -0
- package/src/prompts/tools/todo-write.md +65 -0
- package/src/prompts/tools/undo-edit.md +7 -0
- package/src/prompts/tools/web-search.md +19 -0
- package/src/prompts/tools/write.md +18 -0
- package/src/sdk.ts +1287 -0
- package/src/secrets/index.ts +116 -0
- package/src/secrets/obfuscator.ts +269 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +4669 -0
- package/src/session/agent-storage.ts +621 -0
- package/src/session/artifacts.ts +132 -0
- package/src/session/auth-storage.ts +1433 -0
- package/src/session/blob-store.ts +103 -0
- package/src/session/compaction/branch-summarization.ts +315 -0
- package/src/session/compaction/compaction.ts +864 -0
- package/src/session/compaction/index.ts +7 -0
- package/src/session/compaction/pruning.ts +91 -0
- package/src/session/compaction/utils.ts +171 -0
- package/src/session/history-storage.ts +170 -0
- package/src/session/messages.ts +317 -0
- package/src/session/session-manager.ts +2276 -0
- package/src/session/session-storage.ts +342 -0
- package/src/session/streaming-output.ts +565 -0
- package/src/slash-commands/builtin-registry.ts +439 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +444 -0
- package/src/ssh/ssh-executor.ts +127 -0
- package/src/ssh/sshfs-mount.ts +135 -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 +685 -0
- package/src/task/agents.ts +155 -0
- package/src/task/batch.ts +102 -0
- package/src/task/commands.ts +134 -0
- package/src/task/discovery.ts +126 -0
- package/src/task/executor.ts +908 -0
- package/src/task/index.ts +223 -0
- package/src/task/output-manager.ts +107 -0
- package/src/task/parallel.ts +84 -0
- package/src/task/render.ts +326 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/template.ts +32 -0
- package/src/task/types.ts +144 -0
- package/src/tools/ask.ts +523 -0
- package/src/tools/bash-interactive.ts +419 -0
- package/src/tools/bash-interceptor.ts +105 -0
- package/src/tools/bash-normalize.ts +107 -0
- package/src/tools/bash-skill-urls.ts +177 -0
- package/src/tools/bash.ts +347 -0
- package/src/tools/browser.ts +1374 -0
- package/src/tools/calculator.ts +537 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/explore.ts +23 -0
- package/src/tools/fetch.ts +1091 -0
- package/src/tools/find.ts +540 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gemini-image.ts +907 -0
- package/src/tools/grep.ts +489 -0
- package/src/tools/index.ts +337 -0
- package/src/tools/json-tree.ts +231 -0
- package/src/tools/jtd-to-json-schema.ts +247 -0
- package/src/tools/jtd-to-typescript.ts +198 -0
- package/src/tools/librarian.ts +33 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/notebook.ts +287 -0
- package/src/tools/oracle.ts +40 -0
- package/src/tools/output-meta.ts +459 -0
- package/src/tools/output-utils.ts +63 -0
- package/src/tools/path-utils.ts +116 -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 +1118 -0
- package/src/tools/read.ts +1193 -0
- package/src/tools/render-utils.ts +680 -0
- package/src/tools/renderers.ts +60 -0
- package/src/tools/reviewer-tool.ts +41 -0
- package/src/tools/ssh.ts +326 -0
- package/src/tools/subagent-tool.ts +169 -0
- package/src/tools/submit-result.ts +152 -0
- package/src/tools/todo-write.ts +255 -0
- package/src/tools/tool-errors.ts +92 -0
- package/src/tools/tool-result.ts +86 -0
- package/src/tools/undo-edit.ts +145 -0
- package/src/tools/undo-history.ts +22 -0
- package/src/tools/write.ts +274 -0
- package/src/tui/code-cell.ts +108 -0
- package/src/tui/file-list.ts +47 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +144 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +53 -0
- package/src/tui/types.ts +16 -0
- package/src/tui/utils.ts +116 -0
- package/src/utils/changelog.ts +98 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +59 -0
- package/src/utils/file-display-mode.ts +36 -0
- package/src/utils/file-mentions.ts +384 -0
- package/src/utils/frontmatter.ts +101 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/ignore-files.ts +119 -0
- package/src/utils/image-convert.ts +27 -0
- package/src/utils/image-resize.ts +236 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/open.ts +20 -0
- package/src/utils/shell-snapshot.ts +199 -0
- package/src/utils/timings.ts +26 -0
- package/src/utils/title-generator.ts +167 -0
- package/src/utils/tools-manager.ts +362 -0
- package/src/web/scrapers/artifacthub.ts +215 -0
- package/src/web/scrapers/arxiv.ts +88 -0
- package/src/web/scrapers/aur.ts +175 -0
- package/src/web/scrapers/biorxiv.ts +141 -0
- package/src/web/scrapers/bluesky.ts +284 -0
- package/src/web/scrapers/brew.ts +177 -0
- package/src/web/scrapers/cheatsh.ts +78 -0
- package/src/web/scrapers/chocolatey.ts +158 -0
- package/src/web/scrapers/choosealicense.ts +110 -0
- package/src/web/scrapers/cisa-kev.ts +100 -0
- package/src/web/scrapers/clojars.ts +180 -0
- package/src/web/scrapers/coingecko.ts +184 -0
- package/src/web/scrapers/crates-io.ts +128 -0
- package/src/web/scrapers/crossref.ts +149 -0
- package/src/web/scrapers/devto.ts +177 -0
- package/src/web/scrapers/discogs.ts +307 -0
- package/src/web/scrapers/discourse.ts +221 -0
- package/src/web/scrapers/dockerhub.ts +160 -0
- package/src/web/scrapers/fdroid.ts +158 -0
- package/src/web/scrapers/firefox-addons.ts +214 -0
- package/src/web/scrapers/flathub.ts +239 -0
- package/src/web/scrapers/github-gist.ts +68 -0
- package/src/web/scrapers/github.ts +490 -0
- package/src/web/scrapers/gitlab.ts +456 -0
- package/src/web/scrapers/go-pkg.ts +275 -0
- package/src/web/scrapers/hackage.ts +94 -0
- package/src/web/scrapers/hackernews.ts +208 -0
- package/src/web/scrapers/hex.ts +121 -0
- package/src/web/scrapers/huggingface.ts +385 -0
- package/src/web/scrapers/iacr.ts +86 -0
- package/src/web/scrapers/index.ts +249 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +169 -0
- package/src/web/scrapers/lemmy.ts +220 -0
- package/src/web/scrapers/lobsters.ts +186 -0
- package/src/web/scrapers/mastodon.ts +310 -0
- package/src/web/scrapers/maven.ts +152 -0
- package/src/web/scrapers/mdn.ts +172 -0
- package/src/web/scrapers/metacpan.ts +253 -0
- package/src/web/scrapers/musicbrainz.ts +272 -0
- package/src/web/scrapers/npm.ts +114 -0
- package/src/web/scrapers/nuget.ts +205 -0
- package/src/web/scrapers/nvd.ts +243 -0
- package/src/web/scrapers/ollama.ts +265 -0
- package/src/web/scrapers/open-vsx.ts +119 -0
- package/src/web/scrapers/opencorporates.ts +275 -0
- package/src/web/scrapers/openlibrary.ts +319 -0
- package/src/web/scrapers/orcid.ts +298 -0
- package/src/web/scrapers/osv.ts +192 -0
- package/src/web/scrapers/packagist.ts +174 -0
- package/src/web/scrapers/pub-dev.ts +185 -0
- package/src/web/scrapers/pubmed.ts +177 -0
- package/src/web/scrapers/pypi.ts +129 -0
- package/src/web/scrapers/rawg.ts +124 -0
- package/src/web/scrapers/readthedocs.ts +125 -0
- package/src/web/scrapers/reddit.ts +104 -0
- package/src/web/scrapers/repology.ts +262 -0
- package/src/web/scrapers/rfc.ts +209 -0
- package/src/web/scrapers/rubygems.ts +117 -0
- package/src/web/scrapers/searchcode.ts +217 -0
- package/src/web/scrapers/sec-edgar.ts +274 -0
- package/src/web/scrapers/semantic-scholar.ts +190 -0
- package/src/web/scrapers/snapcraft.ts +200 -0
- package/src/web/scrapers/sourcegraph.ts +373 -0
- package/src/web/scrapers/spdx.ts +121 -0
- package/src/web/scrapers/spotify.ts +217 -0
- package/src/web/scrapers/stackoverflow.ts +124 -0
- package/src/web/scrapers/terraform.ts +304 -0
- package/src/web/scrapers/tldr.ts +51 -0
- package/src/web/scrapers/twitter.ts +97 -0
- package/src/web/scrapers/types.ts +200 -0
- package/src/web/scrapers/utils.ts +142 -0
- package/src/web/scrapers/vimeo.ts +152 -0
- package/src/web/scrapers/vscode-marketplace.ts +195 -0
- package/src/web/scrapers/w3c.ts +163 -0
- package/src/web/scrapers/wikidata.ts +357 -0
- package/src/web/scrapers/wikipedia.ts +95 -0
- package/src/web/scrapers/youtube.ts +312 -0
- package/src/web/search/auth.ts +178 -0
- package/src/web/search/index.ts +598 -0
- package/src/web/search/provider.ts +77 -0
- package/src/web/search/providers/anthropic.ts +284 -0
- package/src/web/search/providers/base.ts +22 -0
- package/src/web/search/providers/brave.ts +165 -0
- package/src/web/search/providers/codex.ts +377 -0
- package/src/web/search/providers/exa.ts +158 -0
- package/src/web/search/providers/gemini.ts +437 -0
- package/src/web/search/providers/jina.ts +99 -0
- package/src/web/search/providers/kimi.ts +196 -0
- package/src/web/search/providers/perplexity.ts +546 -0
- package/src/web/search/providers/synthetic.ts +136 -0
- package/src/web/search/providers/zai.ts +352 -0
- package/src/web/search/render.ts +299 -0
- package/src/web/search/types.ts +437 -0
package/src/lsp/index.ts
ADDED
|
@@ -0,0 +1,1268 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@nghyane/arcane-agent";
|
|
4
|
+
import { logger, once, untilAborted } from "@nghyane/arcane-utils";
|
|
5
|
+
import type { BunFile } from "bun";
|
|
6
|
+
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
7
|
+
import { type Theme, theme } from "../modes/theme/theme";
|
|
8
|
+
import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
|
|
9
|
+
import type { ToolSession } from "../tools";
|
|
10
|
+
import { resolveToCwd } from "../tools/path-utils";
|
|
11
|
+
import { ToolAbortError, throwIfAborted } from "../tools/tool-errors";
|
|
12
|
+
import {
|
|
13
|
+
ensureFileOpen,
|
|
14
|
+
getActiveClients,
|
|
15
|
+
getOrCreateClient,
|
|
16
|
+
type LspServerStatus,
|
|
17
|
+
notifySaved,
|
|
18
|
+
refreshFile,
|
|
19
|
+
sendRequest,
|
|
20
|
+
setIdleTimeout,
|
|
21
|
+
syncContent,
|
|
22
|
+
WARMUP_TIMEOUT_MS,
|
|
23
|
+
} from "./client";
|
|
24
|
+
import { getLinterClient } from "./clients";
|
|
25
|
+
import { getServersForFile, type LspConfig, loadConfig } from "./config";
|
|
26
|
+
import { applyTextEditsToString, applyWorkspaceEdit } from "./edits";
|
|
27
|
+
import { detectLspmux } from "./lspmux";
|
|
28
|
+
import { renderCall, renderResult } from "./render";
|
|
29
|
+
import {
|
|
30
|
+
type Diagnostic,
|
|
31
|
+
type DocumentSymbol,
|
|
32
|
+
type Hover,
|
|
33
|
+
type Location,
|
|
34
|
+
type LocationLink,
|
|
35
|
+
type LspClient,
|
|
36
|
+
type LspParams,
|
|
37
|
+
type LspToolDetails,
|
|
38
|
+
lspSchema,
|
|
39
|
+
type ServerConfig,
|
|
40
|
+
type SymbolInformation,
|
|
41
|
+
type TextEdit,
|
|
42
|
+
type WorkspaceEdit,
|
|
43
|
+
} from "./types";
|
|
44
|
+
import {
|
|
45
|
+
extractHoverText,
|
|
46
|
+
fileToUri,
|
|
47
|
+
formatDiagnostic,
|
|
48
|
+
formatDiagnosticsSummary,
|
|
49
|
+
formatDocumentSymbol,
|
|
50
|
+
formatLocation,
|
|
51
|
+
formatSymbolInformation,
|
|
52
|
+
formatWorkspaceEdit,
|
|
53
|
+
sortDiagnostics,
|
|
54
|
+
symbolKindToIcon,
|
|
55
|
+
} from "./utils";
|
|
56
|
+
|
|
57
|
+
export type { LspServerStatus } from "./client";
|
|
58
|
+
export type { LspToolDetails } from "./types";
|
|
59
|
+
|
|
60
|
+
/** Result from warming up LSP servers */
|
|
61
|
+
export interface LspWarmupResult {
|
|
62
|
+
servers: Array<{
|
|
63
|
+
name: string;
|
|
64
|
+
status: "ready" | "error";
|
|
65
|
+
fileTypes: string[];
|
|
66
|
+
error?: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Options for warming up LSP servers */
|
|
71
|
+
export interface LspWarmupOptions {
|
|
72
|
+
/** Called when starting to connect to servers */
|
|
73
|
+
onConnecting?: (serverNames: string[]) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Warm up LSP servers for a directory by connecting to all detected servers.
|
|
78
|
+
* This should be called at startup to avoid cold-start delays.
|
|
79
|
+
*
|
|
80
|
+
* @param cwd - Working directory to detect and start servers for
|
|
81
|
+
* @param options - Optional callbacks for progress reporting
|
|
82
|
+
* @returns Status of each server that was started
|
|
83
|
+
*/
|
|
84
|
+
export async function warmupLspServers(cwd: string, options?: LspWarmupOptions): Promise<LspWarmupResult> {
|
|
85
|
+
const config = loadConfig(cwd);
|
|
86
|
+
setIdleTimeout(config.idleTimeoutMs);
|
|
87
|
+
const servers: LspWarmupResult["servers"] = [];
|
|
88
|
+
const lspServers = getLspServers(config);
|
|
89
|
+
|
|
90
|
+
// Notify caller which servers we're connecting to
|
|
91
|
+
if (lspServers.length > 0 && options?.onConnecting) {
|
|
92
|
+
options.onConnecting(lspServers.map(([name]) => name));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Start all detected servers in parallel with a short timeout
|
|
96
|
+
// Servers that don't respond quickly will be initialized lazily on first use
|
|
97
|
+
const results = await Promise.allSettled(
|
|
98
|
+
lspServers.map(async ([name, serverConfig]) => {
|
|
99
|
+
const client = await getOrCreateClient(serverConfig, cwd, serverConfig.warmupTimeoutMs ?? WARMUP_TIMEOUT_MS);
|
|
100
|
+
return { name, client, fileTypes: serverConfig.fileTypes };
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
for (let i = 0; i < results.length; i++) {
|
|
105
|
+
const result = results[i];
|
|
106
|
+
const [name, serverConfig] = lspServers[i];
|
|
107
|
+
if (result.status === "fulfilled") {
|
|
108
|
+
servers.push({
|
|
109
|
+
name: result.value.name,
|
|
110
|
+
status: "ready",
|
|
111
|
+
fileTypes: result.value.fileTypes,
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
const errorMsg = result.reason?.message ?? String(result.reason);
|
|
115
|
+
logger.warn("LSP server failed to start", { server: name, error: errorMsg });
|
|
116
|
+
servers.push({
|
|
117
|
+
name,
|
|
118
|
+
status: "error",
|
|
119
|
+
fileTypes: serverConfig.fileTypes,
|
|
120
|
+
error: errorMsg,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { servers };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get status of currently active LSP servers.
|
|
130
|
+
*/
|
|
131
|
+
export function getLspStatus(): LspServerStatus[] {
|
|
132
|
+
return getActiveClients();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Sync in-memory file content to all applicable LSP servers.
|
|
137
|
+
* Sends didOpen (if new) or didChange (if already open).
|
|
138
|
+
*
|
|
139
|
+
* @param absolutePath - Absolute path to the file
|
|
140
|
+
* @param content - The new file content
|
|
141
|
+
* @param cwd - Working directory for LSP config resolution
|
|
142
|
+
* @param servers - Servers to sync to
|
|
143
|
+
*/
|
|
144
|
+
async function syncFileContent(
|
|
145
|
+
absolutePath: string,
|
|
146
|
+
content: string,
|
|
147
|
+
cwd: string,
|
|
148
|
+
servers: Array<[string, ServerConfig]>,
|
|
149
|
+
signal?: AbortSignal,
|
|
150
|
+
): Promise<void> {
|
|
151
|
+
throwIfAborted(signal);
|
|
152
|
+
await Promise.allSettled(
|
|
153
|
+
servers.map(async ([_serverName, serverConfig]) => {
|
|
154
|
+
throwIfAborted(signal);
|
|
155
|
+
if (serverConfig.createClient) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const client = await getOrCreateClient(serverConfig, cwd);
|
|
159
|
+
throwIfAborted(signal);
|
|
160
|
+
await syncContent(client, absolutePath, content, signal);
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Notify all LSP servers that a file was saved.
|
|
167
|
+
* Assumes content was already synced via syncFileContent.
|
|
168
|
+
*
|
|
169
|
+
* @param absolutePath - Absolute path to the file
|
|
170
|
+
* @param cwd - Working directory for LSP config resolution
|
|
171
|
+
* @param servers - Servers to notify
|
|
172
|
+
*/
|
|
173
|
+
async function notifyFileSaved(
|
|
174
|
+
absolutePath: string,
|
|
175
|
+
cwd: string,
|
|
176
|
+
servers: Array<[string, ServerConfig]>,
|
|
177
|
+
signal?: AbortSignal,
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
throwIfAborted(signal);
|
|
180
|
+
await Promise.allSettled(
|
|
181
|
+
servers.map(async ([_serverName, serverConfig]) => {
|
|
182
|
+
throwIfAborted(signal);
|
|
183
|
+
if (serverConfig.createClient) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const client = await getOrCreateClient(serverConfig, cwd);
|
|
187
|
+
await notifySaved(client, absolutePath, signal);
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Cache config per cwd to avoid repeated file I/O
|
|
193
|
+
const configCache = new Map<string, LspConfig>();
|
|
194
|
+
|
|
195
|
+
function getConfig(cwd: string): LspConfig {
|
|
196
|
+
let config = configCache.get(cwd);
|
|
197
|
+
if (!config) {
|
|
198
|
+
config = loadConfig(cwd);
|
|
199
|
+
setIdleTimeout(config.idleTimeoutMs);
|
|
200
|
+
configCache.set(cwd, config);
|
|
201
|
+
}
|
|
202
|
+
return config;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isCustomLinter(serverConfig: ServerConfig): boolean {
|
|
206
|
+
return Boolean(serverConfig.createClient);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function splitServers(servers: Array<[string, ServerConfig]>): {
|
|
210
|
+
lspServers: Array<[string, ServerConfig]>;
|
|
211
|
+
customLinterServers: Array<[string, ServerConfig]>;
|
|
212
|
+
} {
|
|
213
|
+
const lspServers: Array<[string, ServerConfig]> = [];
|
|
214
|
+
const customLinterServers: Array<[string, ServerConfig]> = [];
|
|
215
|
+
for (const entry of servers) {
|
|
216
|
+
if (isCustomLinter(entry[1])) {
|
|
217
|
+
customLinterServers.push(entry);
|
|
218
|
+
} else {
|
|
219
|
+
lspServers.push(entry);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { lspServers, customLinterServers };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getLspServers(config: LspConfig): Array<[string, ServerConfig]> {
|
|
226
|
+
return (Object.entries(config.servers) as Array<[string, ServerConfig]>).filter(
|
|
227
|
+
([, serverConfig]) => !isCustomLinter(serverConfig),
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getLspServersForFile(config: LspConfig, filePath: string): Array<[string, ServerConfig]> {
|
|
232
|
+
return getServersForFile(config, filePath).filter(([, serverConfig]) => !isCustomLinter(serverConfig));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getLspServerForFile(config: LspConfig, filePath: string): [string, ServerConfig] | null {
|
|
236
|
+
const servers = getLspServersForFile(config, filePath);
|
|
237
|
+
return servers.length > 0 ? servers[0] : null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const DIAGNOSTIC_MESSAGE_LIMIT = 50;
|
|
241
|
+
|
|
242
|
+
function limitDiagnosticMessages(messages: string[]): string[] {
|
|
243
|
+
if (messages.length <= DIAGNOSTIC_MESSAGE_LIMIT) {
|
|
244
|
+
return messages;
|
|
245
|
+
}
|
|
246
|
+
return messages.slice(0, DIAGNOSTIC_MESSAGE_LIMIT);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function getServerForWorkspaceAction(config: LspConfig, action: string): [string, ServerConfig] | null {
|
|
250
|
+
const entries = getLspServers(config);
|
|
251
|
+
if (entries.length === 0) return null;
|
|
252
|
+
|
|
253
|
+
if (action === "symbols" || action === "reload") {
|
|
254
|
+
return entries[0];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function waitForDiagnostics(
|
|
261
|
+
client: LspClient,
|
|
262
|
+
uri: string,
|
|
263
|
+
timeoutMs = 3000,
|
|
264
|
+
signal?: AbortSignal,
|
|
265
|
+
minVersion?: number,
|
|
266
|
+
): Promise<Diagnostic[]> {
|
|
267
|
+
const start = Date.now();
|
|
268
|
+
while (Date.now() - start < timeoutMs) {
|
|
269
|
+
throwIfAborted(signal);
|
|
270
|
+
const diagnostics = client.diagnostics.get(uri);
|
|
271
|
+
const versionOk = minVersion === undefined || client.diagnosticsVersion > minVersion;
|
|
272
|
+
if (diagnostics !== undefined && versionOk) return diagnostics;
|
|
273
|
+
await Bun.sleep(100);
|
|
274
|
+
}
|
|
275
|
+
return client.diagnostics.get(uri) ?? [];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Project type detection result */
|
|
279
|
+
interface ProjectType {
|
|
280
|
+
type: "rust" | "typescript" | "go" | "python" | "unknown";
|
|
281
|
+
command?: string[];
|
|
282
|
+
description: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Detect project type from root markers */
|
|
286
|
+
function detectProjectType(cwd: string): ProjectType {
|
|
287
|
+
// Check for Rust (Cargo.toml)
|
|
288
|
+
if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
|
|
289
|
+
return { type: "rust", command: ["cargo", "check", "--message-format=short"], description: "Rust (cargo check)" };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check for TypeScript (tsconfig.json)
|
|
293
|
+
if (fs.existsSync(path.join(cwd, "tsconfig.json"))) {
|
|
294
|
+
return { type: "typescript", command: ["npx", "tsc", "--noEmit"], description: "TypeScript (tsc --noEmit)" };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check for Go (go.mod)
|
|
298
|
+
if (fs.existsSync(path.join(cwd, "go.mod"))) {
|
|
299
|
+
return { type: "go", command: ["go", "build", "./..."], description: "Go (go build)" };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check for Python (pyproject.toml or pyrightconfig.json)
|
|
303
|
+
if (fs.existsSync(path.join(cwd, "pyproject.toml")) || fs.existsSync(path.join(cwd, "pyrightconfig.json"))) {
|
|
304
|
+
return { type: "python", command: ["pyright"], description: "Python (pyright)" };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return { type: "unknown", description: "Unknown project type" };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Run workspace diagnostics command and parse output */
|
|
311
|
+
async function runWorkspaceDiagnostics(
|
|
312
|
+
cwd: string,
|
|
313
|
+
signal?: AbortSignal,
|
|
314
|
+
): Promise<{ output: string; projectType: ProjectType }> {
|
|
315
|
+
throwIfAborted(signal);
|
|
316
|
+
const projectType = detectProjectType(cwd);
|
|
317
|
+
if (!projectType.command) {
|
|
318
|
+
return {
|
|
319
|
+
output: `Cannot detect project type. Supported: Rust (Cargo.toml), TypeScript (tsconfig.json), Go (go.mod), Python (pyproject.toml)`,
|
|
320
|
+
projectType,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const proc = Bun.spawn(projectType.command, {
|
|
324
|
+
cwd,
|
|
325
|
+
stdout: "pipe",
|
|
326
|
+
stderr: "pipe",
|
|
327
|
+
windowsHide: true,
|
|
328
|
+
});
|
|
329
|
+
const abortHandler = () => {
|
|
330
|
+
proc.kill();
|
|
331
|
+
};
|
|
332
|
+
if (signal) {
|
|
333
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
|
|
338
|
+
await proc.exited;
|
|
339
|
+
throwIfAborted(signal);
|
|
340
|
+
const combined = (stdout + stderr).trim();
|
|
341
|
+
if (!combined) {
|
|
342
|
+
return { output: "No issues found", projectType };
|
|
343
|
+
}
|
|
344
|
+
// Limit output length
|
|
345
|
+
const lines = combined.split("\n");
|
|
346
|
+
if (lines.length > 50) {
|
|
347
|
+
return { output: `${lines.slice(0, 50).join("\n")}\n... and ${lines.length - 50} more lines`, projectType };
|
|
348
|
+
}
|
|
349
|
+
return { output: combined, projectType };
|
|
350
|
+
} catch (e) {
|
|
351
|
+
if (signal?.aborted) {
|
|
352
|
+
throw new ToolAbortError();
|
|
353
|
+
}
|
|
354
|
+
return { output: `Failed to run ${projectType.command.join(" ")}: ${e}`, projectType };
|
|
355
|
+
} finally {
|
|
356
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Result from getDiagnosticsForFile */
|
|
361
|
+
export interface FileDiagnosticsResult {
|
|
362
|
+
/** Name of the LSP server used (if available) */
|
|
363
|
+
server?: string;
|
|
364
|
+
/** Formatted diagnostic messages */
|
|
365
|
+
messages: string[];
|
|
366
|
+
/** Summary string (e.g., "2 error(s), 1 warning(s)") */
|
|
367
|
+
summary: string;
|
|
368
|
+
/** Whether there are any errors (severity 1) */
|
|
369
|
+
errored: boolean;
|
|
370
|
+
/** Whether the file was formatted */
|
|
371
|
+
formatter?: FileFormatResult;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Captured diagnostic versions per server (before sync) */
|
|
375
|
+
type DiagnosticVersions = Map<string, number>;
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Capture current diagnostic versions for all LSP servers.
|
|
379
|
+
* Call this BEFORE syncing content to detect stale diagnostics later.
|
|
380
|
+
*/
|
|
381
|
+
async function captureDiagnosticVersions(
|
|
382
|
+
cwd: string,
|
|
383
|
+
servers: Array<[string, ServerConfig]>,
|
|
384
|
+
): Promise<DiagnosticVersions> {
|
|
385
|
+
const versions = new Map<string, number>();
|
|
386
|
+
await Promise.allSettled(
|
|
387
|
+
servers.map(async ([serverName, serverConfig]) => {
|
|
388
|
+
if (serverConfig.createClient) return;
|
|
389
|
+
const client = await getOrCreateClient(serverConfig, cwd);
|
|
390
|
+
versions.set(serverName, client.diagnosticsVersion);
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
return versions;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get diagnostics for a file using LSP or custom linter client.
|
|
398
|
+
*
|
|
399
|
+
* @param absolutePath - Absolute path to the file
|
|
400
|
+
* @param cwd - Working directory for LSP config resolution
|
|
401
|
+
* @param servers - Servers to query diagnostics for
|
|
402
|
+
* @param minVersions - Minimum diagnostic versions per server (to detect stale results)
|
|
403
|
+
* @returns Diagnostic results or undefined if no servers
|
|
404
|
+
*/
|
|
405
|
+
async function getDiagnosticsForFile(
|
|
406
|
+
absolutePath: string,
|
|
407
|
+
cwd: string,
|
|
408
|
+
servers: Array<[string, ServerConfig]>,
|
|
409
|
+
signal?: AbortSignal,
|
|
410
|
+
minVersions?: DiagnosticVersions,
|
|
411
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
412
|
+
if (servers.length === 0) {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const uri = fileToUri(absolutePath);
|
|
417
|
+
const relPath = path.relative(cwd, absolutePath);
|
|
418
|
+
const allDiagnostics: Diagnostic[] = [];
|
|
419
|
+
const serverNames: string[] = [];
|
|
420
|
+
|
|
421
|
+
// Wait for diagnostics from all servers in parallel
|
|
422
|
+
const results = await Promise.allSettled(
|
|
423
|
+
servers.map(async ([serverName, serverConfig]) => {
|
|
424
|
+
throwIfAborted(signal);
|
|
425
|
+
// Use custom linter client if configured
|
|
426
|
+
if (serverConfig.createClient) {
|
|
427
|
+
const linterClient = getLinterClient(serverName, serverConfig, cwd);
|
|
428
|
+
const diagnostics = await linterClient.lint(absolutePath);
|
|
429
|
+
return { serverName, diagnostics };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Default: use LSP
|
|
433
|
+
const client = await getOrCreateClient(serverConfig, cwd);
|
|
434
|
+
throwIfAborted(signal);
|
|
435
|
+
// Content already synced + didSave sent, wait for fresh diagnostics
|
|
436
|
+
const minVersion = minVersions?.get(serverName);
|
|
437
|
+
const diagnostics = await waitForDiagnostics(client, uri, 3000, signal, minVersion);
|
|
438
|
+
return { serverName, diagnostics };
|
|
439
|
+
}),
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
for (const result of results) {
|
|
443
|
+
if (result.status === "fulfilled") {
|
|
444
|
+
serverNames.push(result.value.serverName);
|
|
445
|
+
allDiagnostics.push(...result.value.diagnostics);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (serverNames.length === 0) {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (allDiagnostics.length === 0) {
|
|
454
|
+
return {
|
|
455
|
+
server: serverNames.join(", "),
|
|
456
|
+
messages: [],
|
|
457
|
+
summary: "OK",
|
|
458
|
+
errored: false,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Deduplicate diagnostics by range + message (different servers might report similar issues)
|
|
463
|
+
const seen = new Set<string>();
|
|
464
|
+
const uniqueDiagnostics: Diagnostic[] = [];
|
|
465
|
+
for (const d of allDiagnostics) {
|
|
466
|
+
const key = `${d.range.start.line}:${d.range.start.character}:${d.range.end.line}:${d.range.end.character}:${d.message}`;
|
|
467
|
+
if (!seen.has(key)) {
|
|
468
|
+
seen.add(key);
|
|
469
|
+
uniqueDiagnostics.push(d);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
sortDiagnostics(uniqueDiagnostics);
|
|
474
|
+
const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
|
|
475
|
+
const limited = limitDiagnosticMessages(formatted);
|
|
476
|
+
const summary = formatDiagnosticsSummary(uniqueDiagnostics);
|
|
477
|
+
const hasErrors = uniqueDiagnostics.some(d => d.severity === 1);
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
server: serverNames.join(", "),
|
|
481
|
+
messages: limited,
|
|
482
|
+
summary,
|
|
483
|
+
errored: hasErrors,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export enum FileFormatResult {
|
|
488
|
+
UNCHANGED = "unchanged",
|
|
489
|
+
FORMATTED = "formatted",
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Default formatting options for LSP */
|
|
493
|
+
const DEFAULT_FORMAT_OPTIONS = {
|
|
494
|
+
tabSize: 3,
|
|
495
|
+
insertSpaces: true,
|
|
496
|
+
trimTrailingWhitespace: true,
|
|
497
|
+
insertFinalNewline: true,
|
|
498
|
+
trimFinalNewlines: true,
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Format content using LSP or custom linter client.
|
|
503
|
+
*
|
|
504
|
+
* @param absolutePath - Absolute path (for URI)
|
|
505
|
+
* @param content - Content to format
|
|
506
|
+
* @param cwd - Working directory for LSP config resolution
|
|
507
|
+
* @param servers - Servers to try formatting with
|
|
508
|
+
* @returns Formatted content, or original if no formatter available
|
|
509
|
+
*/
|
|
510
|
+
async function formatContent(
|
|
511
|
+
absolutePath: string,
|
|
512
|
+
content: string,
|
|
513
|
+
cwd: string,
|
|
514
|
+
servers: Array<[string, ServerConfig]>,
|
|
515
|
+
signal?: AbortSignal,
|
|
516
|
+
): Promise<string> {
|
|
517
|
+
if (servers.length === 0) {
|
|
518
|
+
return content;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const uri = fileToUri(absolutePath);
|
|
522
|
+
|
|
523
|
+
for (const [serverName, serverConfig] of servers) {
|
|
524
|
+
try {
|
|
525
|
+
throwIfAborted(signal);
|
|
526
|
+
// Use custom linter client if configured
|
|
527
|
+
if (serverConfig.createClient) {
|
|
528
|
+
const linterClient = getLinterClient(serverName, serverConfig, cwd);
|
|
529
|
+
return await linterClient.format(absolutePath, content);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Default: use LSP
|
|
533
|
+
const client = await getOrCreateClient(serverConfig, cwd);
|
|
534
|
+
throwIfAborted(signal);
|
|
535
|
+
|
|
536
|
+
const caps = client.serverCapabilities;
|
|
537
|
+
if (!caps?.documentFormattingProvider) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Request formatting (content already synced)
|
|
542
|
+
const edits = (await sendRequest(
|
|
543
|
+
client,
|
|
544
|
+
"textDocument/formatting",
|
|
545
|
+
{
|
|
546
|
+
textDocument: { uri },
|
|
547
|
+
options: DEFAULT_FORMAT_OPTIONS,
|
|
548
|
+
},
|
|
549
|
+
signal,
|
|
550
|
+
)) as TextEdit[] | null;
|
|
551
|
+
|
|
552
|
+
if (!edits || edits.length === 0) {
|
|
553
|
+
return content;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Apply edits in-memory and return
|
|
557
|
+
return applyTextEditsToString(content, edits);
|
|
558
|
+
} catch {}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return content;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Options for creating the LSP writethrough callback */
|
|
565
|
+
export interface WritethroughOptions {
|
|
566
|
+
/** Whether to format the file using LSP after writing */
|
|
567
|
+
enableFormat?: boolean;
|
|
568
|
+
/** Whether to get LSP diagnostics after writing */
|
|
569
|
+
enableDiagnostics?: boolean;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Callback type for the LSP writethrough */
|
|
573
|
+
export type WritethroughCallback = (
|
|
574
|
+
dst: string,
|
|
575
|
+
content: string,
|
|
576
|
+
signal?: AbortSignal,
|
|
577
|
+
file?: BunFile,
|
|
578
|
+
batch?: LspWritethroughBatchRequest,
|
|
579
|
+
) => Promise<FileDiagnosticsResult | undefined>;
|
|
580
|
+
|
|
581
|
+
/** No-op writethrough callback */
|
|
582
|
+
export async function writethroughNoop(
|
|
583
|
+
dst: string,
|
|
584
|
+
content: string,
|
|
585
|
+
_signal?: AbortSignal,
|
|
586
|
+
file?: BunFile,
|
|
587
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
588
|
+
if (file) {
|
|
589
|
+
await file.write(content);
|
|
590
|
+
} else {
|
|
591
|
+
await Bun.write(dst, content);
|
|
592
|
+
}
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
interface PendingWritethrough {
|
|
597
|
+
dst: string;
|
|
598
|
+
content: string;
|
|
599
|
+
file?: BunFile;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
interface LspWritethroughBatchRequest {
|
|
603
|
+
id: string;
|
|
604
|
+
flush: boolean;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
interface LspWritethroughBatchState {
|
|
608
|
+
entries: Map<string, PendingWritethrough>;
|
|
609
|
+
options: Required<WritethroughOptions>;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const writethroughBatches = new Map<string, LspWritethroughBatchState>();
|
|
613
|
+
|
|
614
|
+
function getOrCreateWritethroughBatch(id: string, options: Required<WritethroughOptions>): LspWritethroughBatchState {
|
|
615
|
+
const existing = writethroughBatches.get(id);
|
|
616
|
+
if (existing) {
|
|
617
|
+
existing.options.enableFormat ||= options.enableFormat;
|
|
618
|
+
existing.options.enableDiagnostics ||= options.enableDiagnostics;
|
|
619
|
+
return existing;
|
|
620
|
+
}
|
|
621
|
+
const batch: LspWritethroughBatchState = {
|
|
622
|
+
entries: new Map<string, PendingWritethrough>(),
|
|
623
|
+
options: { ...options },
|
|
624
|
+
};
|
|
625
|
+
writethroughBatches.set(id, batch);
|
|
626
|
+
return batch;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export async function flushLspWritethroughBatch(
|
|
630
|
+
id: string,
|
|
631
|
+
cwd: string,
|
|
632
|
+
signal?: AbortSignal,
|
|
633
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
634
|
+
const state = writethroughBatches.get(id);
|
|
635
|
+
if (!state) {
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
writethroughBatches.delete(id);
|
|
639
|
+
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function summarizeDiagnosticMessages(messages: string[]): { summary: string; errored: boolean } {
|
|
643
|
+
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
|
|
644
|
+
for (const message of messages) {
|
|
645
|
+
const match = message.match(/\[(error|warning|info|hint)\]/i);
|
|
646
|
+
if (!match) continue;
|
|
647
|
+
const key = match[1].toLowerCase() as keyof typeof counts;
|
|
648
|
+
counts[key] += 1;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const parts: string[] = [];
|
|
652
|
+
if (counts.error > 0) parts.push(`${counts.error} error(s)`);
|
|
653
|
+
if (counts.warning > 0) parts.push(`${counts.warning} warning(s)`);
|
|
654
|
+
if (counts.info > 0) parts.push(`${counts.info} info(s)`);
|
|
655
|
+
if (counts.hint > 0) parts.push(`${counts.hint} hint(s)`);
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
summary: parts.length > 0 ? parts.join(", ") : "no issues",
|
|
659
|
+
errored: counts.error > 0,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function mergeDiagnostics(
|
|
664
|
+
results: Array<FileDiagnosticsResult | undefined>,
|
|
665
|
+
options: Required<WritethroughOptions>,
|
|
666
|
+
): FileDiagnosticsResult | undefined {
|
|
667
|
+
const messages: string[] = [];
|
|
668
|
+
const servers = new Set<string>();
|
|
669
|
+
let hasResults = false;
|
|
670
|
+
let hasFormatter = false;
|
|
671
|
+
let formatted = false;
|
|
672
|
+
|
|
673
|
+
for (const result of results) {
|
|
674
|
+
if (!result) continue;
|
|
675
|
+
hasResults = true;
|
|
676
|
+
if (result.server) {
|
|
677
|
+
for (const server of result.server.split(",")) {
|
|
678
|
+
const trimmed = server.trim();
|
|
679
|
+
if (trimmed) {
|
|
680
|
+
servers.add(trimmed);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (result.messages.length > 0) {
|
|
685
|
+
messages.push(...result.messages);
|
|
686
|
+
}
|
|
687
|
+
if (result.formatter !== undefined) {
|
|
688
|
+
hasFormatter = true;
|
|
689
|
+
if (result.formatter === FileFormatResult.FORMATTED) {
|
|
690
|
+
formatted = true;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (!hasResults && !hasFormatter) {
|
|
696
|
+
return undefined;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
let summary = options.enableDiagnostics ? "no issues" : "OK";
|
|
700
|
+
let errored = false;
|
|
701
|
+
let limitedMessages = messages;
|
|
702
|
+
if (messages.length > 0) {
|
|
703
|
+
const summaryInfo = summarizeDiagnosticMessages(messages);
|
|
704
|
+
summary = summaryInfo.summary;
|
|
705
|
+
errored = summaryInfo.errored;
|
|
706
|
+
limitedMessages = limitDiagnosticMessages(messages);
|
|
707
|
+
}
|
|
708
|
+
const formatter = hasFormatter ? (formatted ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED) : undefined;
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
server: servers.size > 0 ? Array.from(servers).join(", ") : undefined,
|
|
712
|
+
messages: limitedMessages,
|
|
713
|
+
summary,
|
|
714
|
+
errored,
|
|
715
|
+
formatter,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async function runLspWritethrough(
|
|
720
|
+
dst: string,
|
|
721
|
+
content: string,
|
|
722
|
+
cwd: string,
|
|
723
|
+
options: Required<WritethroughOptions>,
|
|
724
|
+
signal?: AbortSignal,
|
|
725
|
+
file?: BunFile,
|
|
726
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
727
|
+
const { enableFormat, enableDiagnostics } = options;
|
|
728
|
+
const config = getConfig(cwd);
|
|
729
|
+
const servers = getServersForFile(config, dst);
|
|
730
|
+
if (servers.length === 0) {
|
|
731
|
+
return writethroughNoop(dst, content, signal, file);
|
|
732
|
+
}
|
|
733
|
+
const { lspServers, customLinterServers } = splitServers(servers);
|
|
734
|
+
|
|
735
|
+
let finalContent = content;
|
|
736
|
+
const writeContent = async (value: string) => (file ? file.write(value) : Bun.write(dst, value));
|
|
737
|
+
const getWritePromise = once(() => writeContent(finalContent));
|
|
738
|
+
const useCustomFormatter = enableFormat && customLinterServers.length > 0;
|
|
739
|
+
|
|
740
|
+
// Capture diagnostic versions BEFORE syncing to detect stale diagnostics
|
|
741
|
+
const minVersions = enableDiagnostics ? await captureDiagnosticVersions(cwd, servers) : undefined;
|
|
742
|
+
|
|
743
|
+
let formatter: FileFormatResult | undefined;
|
|
744
|
+
let diagnostics: FileDiagnosticsResult | undefined;
|
|
745
|
+
let timedOut = false;
|
|
746
|
+
try {
|
|
747
|
+
const timeoutSignal = AbortSignal.timeout(10_000);
|
|
748
|
+
timeoutSignal.addEventListener(
|
|
749
|
+
"abort",
|
|
750
|
+
() => {
|
|
751
|
+
timedOut = true;
|
|
752
|
+
},
|
|
753
|
+
{ once: true },
|
|
754
|
+
);
|
|
755
|
+
const operationSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
756
|
+
await untilAborted(operationSignal, async () => {
|
|
757
|
+
if (useCustomFormatter) {
|
|
758
|
+
// Custom linters (e.g. Biome CLI) require on-disk input.
|
|
759
|
+
await writeContent(content);
|
|
760
|
+
finalContent = await formatContent(dst, content, cwd, customLinterServers, operationSignal);
|
|
761
|
+
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
762
|
+
await writeContent(finalContent);
|
|
763
|
+
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
|
|
764
|
+
} else {
|
|
765
|
+
// 1. Sync original content to LSP servers
|
|
766
|
+
await syncFileContent(dst, content, cwd, lspServers, operationSignal);
|
|
767
|
+
|
|
768
|
+
// 2. Format in-memory via LSP
|
|
769
|
+
if (enableFormat) {
|
|
770
|
+
finalContent = await formatContent(dst, content, cwd, lspServers, operationSignal);
|
|
771
|
+
formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// 3. If formatted, sync formatted content to LSP servers
|
|
775
|
+
if (finalContent !== content) {
|
|
776
|
+
await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// 4. Write to disk
|
|
780
|
+
await getWritePromise();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// 5. Notify saved to LSP servers
|
|
784
|
+
await notifyFileSaved(dst, cwd, lspServers, operationSignal);
|
|
785
|
+
|
|
786
|
+
// 6. Get diagnostics from all servers (wait for fresh results)
|
|
787
|
+
if (enableDiagnostics) {
|
|
788
|
+
diagnostics = await getDiagnosticsForFile(dst, cwd, servers, operationSignal, minVersions);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
} catch {
|
|
792
|
+
if (timedOut) {
|
|
793
|
+
formatter = undefined;
|
|
794
|
+
diagnostics = undefined;
|
|
795
|
+
}
|
|
796
|
+
await getWritePromise();
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (formatter !== undefined) {
|
|
800
|
+
diagnostics ??= {
|
|
801
|
+
server: servers.map(([name]) => name).join(", "),
|
|
802
|
+
messages: [],
|
|
803
|
+
summary: "OK",
|
|
804
|
+
errored: false,
|
|
805
|
+
};
|
|
806
|
+
diagnostics.formatter = formatter;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return diagnostics;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async function flushWritethroughBatch(
|
|
813
|
+
batch: PendingWritethrough[],
|
|
814
|
+
cwd: string,
|
|
815
|
+
options: Required<WritethroughOptions>,
|
|
816
|
+
signal?: AbortSignal,
|
|
817
|
+
): Promise<FileDiagnosticsResult | undefined> {
|
|
818
|
+
if (batch.length === 0) {
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
821
|
+
const results: Array<FileDiagnosticsResult | undefined> = [];
|
|
822
|
+
for (const entry of batch) {
|
|
823
|
+
results.push(await runLspWritethrough(entry.dst, entry.content, cwd, options, signal, entry.file));
|
|
824
|
+
}
|
|
825
|
+
return mergeDiagnostics(results, options);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/** Create a writethrough callback for LSP aware write operations */
|
|
829
|
+
export function createLspWritethrough(cwd: string, options?: WritethroughOptions): WritethroughCallback {
|
|
830
|
+
const resolvedOptions: Required<WritethroughOptions> = {
|
|
831
|
+
enableFormat: options?.enableFormat ?? false,
|
|
832
|
+
enableDiagnostics: options?.enableDiagnostics ?? false,
|
|
833
|
+
};
|
|
834
|
+
if (!resolvedOptions.enableFormat && !resolvedOptions.enableDiagnostics) {
|
|
835
|
+
return writethroughNoop;
|
|
836
|
+
}
|
|
837
|
+
return async (
|
|
838
|
+
dst: string,
|
|
839
|
+
content: string,
|
|
840
|
+
signal?: AbortSignal,
|
|
841
|
+
file?: BunFile,
|
|
842
|
+
batch?: LspWritethroughBatchRequest,
|
|
843
|
+
) => {
|
|
844
|
+
if (!batch) {
|
|
845
|
+
return runLspWritethrough(dst, content, cwd, resolvedOptions, signal, file);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const state = getOrCreateWritethroughBatch(batch.id, resolvedOptions);
|
|
849
|
+
state.entries.set(dst, { dst, content, file });
|
|
850
|
+
|
|
851
|
+
if (!batch.flush) {
|
|
852
|
+
await writethroughNoop(dst, content, signal, file);
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
writethroughBatches.delete(batch.id);
|
|
857
|
+
return flushWritethroughBatch(Array.from(state.entries.values()), cwd, state.options, signal);
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* LSP tool for language server protocol operations.
|
|
863
|
+
*/
|
|
864
|
+
export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Theme> {
|
|
865
|
+
readonly name = "lsp";
|
|
866
|
+
readonly label = "LSP";
|
|
867
|
+
readonly description: string;
|
|
868
|
+
readonly parameters = lspSchema;
|
|
869
|
+
readonly renderCall = renderCall;
|
|
870
|
+
readonly renderResult = renderResult;
|
|
871
|
+
readonly mergeCallAndResult = true;
|
|
872
|
+
readonly inline = true;
|
|
873
|
+
|
|
874
|
+
constructor(private readonly session: ToolSession) {
|
|
875
|
+
this.description = renderPromptTemplate(lspDescription);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
static createIf(session: ToolSession): LspTool | null {
|
|
879
|
+
return session.enableLsp === false ? null : new LspTool(session);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
async execute(
|
|
883
|
+
_toolCallId: string,
|
|
884
|
+
params: LspParams,
|
|
885
|
+
signal?: AbortSignal,
|
|
886
|
+
_onUpdate?: AgentToolUpdateCallback<LspToolDetails>,
|
|
887
|
+
_context?: AgentToolContext,
|
|
888
|
+
): Promise<AgentToolResult<LspToolDetails>> {
|
|
889
|
+
const { action, file, files, line, column, query, new_name, apply, include_declaration } = params;
|
|
890
|
+
throwIfAborted(signal);
|
|
891
|
+
|
|
892
|
+
const config = getConfig(this.session.cwd);
|
|
893
|
+
|
|
894
|
+
// Status action doesn't need a file
|
|
895
|
+
if (action === "status") {
|
|
896
|
+
const servers = Object.keys(config.servers);
|
|
897
|
+
const lspmuxState = await detectLspmux();
|
|
898
|
+
const lspmuxStatus = lspmuxState.available
|
|
899
|
+
? lspmuxState.running
|
|
900
|
+
? "lspmux: active (multiplexing enabled)"
|
|
901
|
+
: "lspmux: installed but server not running"
|
|
902
|
+
: "";
|
|
903
|
+
|
|
904
|
+
const serverStatus =
|
|
905
|
+
servers.length > 0
|
|
906
|
+
? `Active language servers: ${servers.join(", ")}`
|
|
907
|
+
: "No language servers configured for this project";
|
|
908
|
+
|
|
909
|
+
const output = lspmuxStatus ? `${serverStatus}\n${lspmuxStatus}` : serverStatus;
|
|
910
|
+
return {
|
|
911
|
+
content: [{ type: "text", text: output }],
|
|
912
|
+
details: { action, success: true, request: params },
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Diagnostics can be batch or single-file - queries all applicable servers
|
|
917
|
+
if (action === "diagnostics") {
|
|
918
|
+
const targets = files?.length ? files : file ? [file] : null;
|
|
919
|
+
if (!targets) {
|
|
920
|
+
// No file specified - run workspace diagnostics
|
|
921
|
+
const result = await runWorkspaceDiagnostics(this.session.cwd, signal);
|
|
922
|
+
return {
|
|
923
|
+
content: [
|
|
924
|
+
{
|
|
925
|
+
type: "text",
|
|
926
|
+
text: `Workspace diagnostics (${result.projectType.description}):\n${result.output}`,
|
|
927
|
+
},
|
|
928
|
+
],
|
|
929
|
+
details: { action, success: true, request: params },
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const detailed = Boolean(files?.length);
|
|
934
|
+
const results: string[] = [];
|
|
935
|
+
const allServerNames = new Set<string>();
|
|
936
|
+
|
|
937
|
+
for (const target of targets) {
|
|
938
|
+
throwIfAborted(signal);
|
|
939
|
+
const resolved = resolveToCwd(target, this.session.cwd);
|
|
940
|
+
const servers = getServersForFile(config, resolved);
|
|
941
|
+
if (servers.length === 0) {
|
|
942
|
+
results.push(`${theme.status.error} ${target}: No language server found`);
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const uri = fileToUri(resolved);
|
|
947
|
+
const relPath = path.relative(this.session.cwd, resolved);
|
|
948
|
+
const allDiagnostics: Diagnostic[] = [];
|
|
949
|
+
|
|
950
|
+
// Query all applicable servers for this file
|
|
951
|
+
for (const [serverName, serverConfig] of servers) {
|
|
952
|
+
allServerNames.add(serverName);
|
|
953
|
+
try {
|
|
954
|
+
throwIfAborted(signal);
|
|
955
|
+
if (serverConfig.createClient) {
|
|
956
|
+
const linterClient = getLinterClient(serverName, serverConfig, this.session.cwd);
|
|
957
|
+
const diagnostics = await linterClient.lint(resolved);
|
|
958
|
+
allDiagnostics.push(...diagnostics);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
const client = await getOrCreateClient(serverConfig, this.session.cwd);
|
|
962
|
+
const minVersion = client.diagnosticsVersion;
|
|
963
|
+
await refreshFile(client, resolved, signal);
|
|
964
|
+
const diagnostics = await waitForDiagnostics(client, uri, 3000, signal, minVersion);
|
|
965
|
+
allDiagnostics.push(...diagnostics);
|
|
966
|
+
} catch (err) {
|
|
967
|
+
if (err instanceof ToolAbortError || signal?.aborted) {
|
|
968
|
+
throw err;
|
|
969
|
+
}
|
|
970
|
+
// Server failed, continue with others
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Deduplicate diagnostics
|
|
975
|
+
const seen = new Set<string>();
|
|
976
|
+
const uniqueDiagnostics: Diagnostic[] = [];
|
|
977
|
+
for (const d of allDiagnostics) {
|
|
978
|
+
const key = `${d.range.start.line}:${d.range.start.character}:${d.range.end.line}:${d.range.end.character}:${d.message}`;
|
|
979
|
+
if (!seen.has(key)) {
|
|
980
|
+
seen.add(key);
|
|
981
|
+
uniqueDiagnostics.push(d);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
sortDiagnostics(uniqueDiagnostics);
|
|
986
|
+
|
|
987
|
+
if (!detailed && targets.length === 1) {
|
|
988
|
+
if (uniqueDiagnostics.length === 0) {
|
|
989
|
+
return {
|
|
990
|
+
content: [{ type: "text", text: "No diagnostics" }],
|
|
991
|
+
details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const summary = formatDiagnosticsSummary(uniqueDiagnostics);
|
|
996
|
+
const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
|
|
997
|
+
const output = `${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}`;
|
|
998
|
+
return {
|
|
999
|
+
content: [{ type: "text", text: output }],
|
|
1000
|
+
details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
if (uniqueDiagnostics.length === 0) {
|
|
1005
|
+
results.push(`${theme.status.success} ${relPath}: no issues`);
|
|
1006
|
+
} else {
|
|
1007
|
+
const summary = formatDiagnosticsSummary(uniqueDiagnostics);
|
|
1008
|
+
results.push(`${theme.status.error} ${relPath}: ${summary}`);
|
|
1009
|
+
for (const diag of uniqueDiagnostics) {
|
|
1010
|
+
results.push(` ${formatDiagnostic(diag, relPath)}`);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
content: [{ type: "text", text: results.join("\n") }],
|
|
1017
|
+
details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const requiresFile = !file && action !== "symbols" && action !== "reload";
|
|
1022
|
+
|
|
1023
|
+
if (requiresFile) {
|
|
1024
|
+
return {
|
|
1025
|
+
content: [{ type: "text", text: "Error: file parameter required for this action" }],
|
|
1026
|
+
details: { action, success: false },
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const resolvedFile = file ? resolveToCwd(file, this.session.cwd) : null;
|
|
1031
|
+
const serverInfo = resolvedFile
|
|
1032
|
+
? getLspServerForFile(config, resolvedFile)
|
|
1033
|
+
: getServerForWorkspaceAction(config, action);
|
|
1034
|
+
|
|
1035
|
+
if (!serverInfo) {
|
|
1036
|
+
return {
|
|
1037
|
+
content: [{ type: "text", text: "No language server found for this action" }],
|
|
1038
|
+
details: { action, success: false },
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const [serverName, serverConfig] = serverInfo;
|
|
1043
|
+
|
|
1044
|
+
try {
|
|
1045
|
+
const client = await getOrCreateClient(serverConfig, this.session.cwd);
|
|
1046
|
+
const targetFile = resolvedFile;
|
|
1047
|
+
|
|
1048
|
+
if (targetFile) {
|
|
1049
|
+
await ensureFileOpen(client, targetFile, signal);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const uri = targetFile ? fileToUri(targetFile) : "";
|
|
1053
|
+
const position = { line: (line || 1) - 1, character: (column || 1) - 1 };
|
|
1054
|
+
|
|
1055
|
+
let output: string;
|
|
1056
|
+
|
|
1057
|
+
switch (action) {
|
|
1058
|
+
// =====================================================================
|
|
1059
|
+
// Standard LSP Operations
|
|
1060
|
+
// =====================================================================
|
|
1061
|
+
|
|
1062
|
+
case "definition": {
|
|
1063
|
+
const result = (await sendRequest(
|
|
1064
|
+
client,
|
|
1065
|
+
"textDocument/definition",
|
|
1066
|
+
{
|
|
1067
|
+
textDocument: { uri },
|
|
1068
|
+
position,
|
|
1069
|
+
},
|
|
1070
|
+
signal,
|
|
1071
|
+
)) as Location | Location[] | LocationLink | LocationLink[] | null;
|
|
1072
|
+
|
|
1073
|
+
if (!result) {
|
|
1074
|
+
output = "No definition found";
|
|
1075
|
+
} else {
|
|
1076
|
+
const raw = Array.isArray(result) ? result : [result];
|
|
1077
|
+
const locations = raw.flatMap(loc => {
|
|
1078
|
+
if ("uri" in loc) {
|
|
1079
|
+
return [loc as Location];
|
|
1080
|
+
}
|
|
1081
|
+
if ("targetUri" in loc) {
|
|
1082
|
+
// Use targetSelectionRange (the precise identifier range) with fallback to targetRange
|
|
1083
|
+
const link = loc as LocationLink;
|
|
1084
|
+
return [{ uri: link.targetUri, range: link.targetSelectionRange ?? link.targetRange }];
|
|
1085
|
+
}
|
|
1086
|
+
return [];
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
if (locations.length === 0) {
|
|
1090
|
+
output = "No definition found";
|
|
1091
|
+
} else {
|
|
1092
|
+
output = `Found ${locations.length} definition(s):\n${locations
|
|
1093
|
+
.map(loc => ` ${formatLocation(loc, this.session.cwd)}`)
|
|
1094
|
+
.join("\n")}`;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
break;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
case "references": {
|
|
1101
|
+
const result = (await sendRequest(
|
|
1102
|
+
client,
|
|
1103
|
+
"textDocument/references",
|
|
1104
|
+
{
|
|
1105
|
+
textDocument: { uri },
|
|
1106
|
+
position,
|
|
1107
|
+
context: { includeDeclaration: include_declaration ?? true },
|
|
1108
|
+
},
|
|
1109
|
+
signal,
|
|
1110
|
+
)) as Location[] | null;
|
|
1111
|
+
|
|
1112
|
+
if (!result || result.length === 0) {
|
|
1113
|
+
output = "No references found";
|
|
1114
|
+
} else {
|
|
1115
|
+
const lines = result.map(loc => ` ${formatLocation(loc, this.session.cwd)}`);
|
|
1116
|
+
output = `Found ${result.length} reference(s):\n${lines.join("\n")}`;
|
|
1117
|
+
}
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
case "hover": {
|
|
1122
|
+
const result = (await sendRequest(
|
|
1123
|
+
client,
|
|
1124
|
+
"textDocument/hover",
|
|
1125
|
+
{
|
|
1126
|
+
textDocument: { uri },
|
|
1127
|
+
position,
|
|
1128
|
+
},
|
|
1129
|
+
signal,
|
|
1130
|
+
)) as Hover | null;
|
|
1131
|
+
|
|
1132
|
+
if (!result || !result.contents) {
|
|
1133
|
+
output = "No hover information";
|
|
1134
|
+
} else {
|
|
1135
|
+
output = extractHoverText(result.contents);
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
case "symbols": {
|
|
1141
|
+
// If no file, do workspace symbol search (requires query)
|
|
1142
|
+
if (!targetFile) {
|
|
1143
|
+
if (!query) {
|
|
1144
|
+
return {
|
|
1145
|
+
content: [
|
|
1146
|
+
{ type: "text", text: "Error: query parameter required for workspace symbol search" },
|
|
1147
|
+
],
|
|
1148
|
+
details: { action, serverName, success: false },
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
const result = (await sendRequest(client, "workspace/symbol", { query }, signal)) as
|
|
1152
|
+
| SymbolInformation[]
|
|
1153
|
+
| null;
|
|
1154
|
+
if (!result || result.length === 0) {
|
|
1155
|
+
output = `No symbols matching "${query}"`;
|
|
1156
|
+
} else {
|
|
1157
|
+
const lines = result.map(s => formatSymbolInformation(s, this.session.cwd));
|
|
1158
|
+
output = `Found ${result.length} symbol(s) matching "${query}":\n${lines.map(l => ` ${l}`).join("\n")}`;
|
|
1159
|
+
}
|
|
1160
|
+
} else {
|
|
1161
|
+
// File-based document symbols
|
|
1162
|
+
const result = (await sendRequest(
|
|
1163
|
+
client,
|
|
1164
|
+
"textDocument/documentSymbol",
|
|
1165
|
+
{
|
|
1166
|
+
textDocument: { uri },
|
|
1167
|
+
},
|
|
1168
|
+
signal,
|
|
1169
|
+
)) as (DocumentSymbol | SymbolInformation)[] | null;
|
|
1170
|
+
|
|
1171
|
+
if (!result || result.length === 0) {
|
|
1172
|
+
output = "No symbols found";
|
|
1173
|
+
} else {
|
|
1174
|
+
const relPath = path.relative(this.session.cwd, targetFile);
|
|
1175
|
+
if ("selectionRange" in result[0]) {
|
|
1176
|
+
const lines = (result as DocumentSymbol[]).flatMap(s => formatDocumentSymbol(s));
|
|
1177
|
+
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
|
|
1178
|
+
} else {
|
|
1179
|
+
const lines = (result as SymbolInformation[]).map(s => {
|
|
1180
|
+
const line = s.location.range.start.line + 1;
|
|
1181
|
+
const icon = symbolKindToIcon(s.kind);
|
|
1182
|
+
return `${icon} ${s.name} @ line ${line}`;
|
|
1183
|
+
});
|
|
1184
|
+
output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
case "rename": {
|
|
1192
|
+
if (!new_name) {
|
|
1193
|
+
return {
|
|
1194
|
+
content: [{ type: "text", text: "Error: new_name parameter required for rename" }],
|
|
1195
|
+
details: { action, serverName, success: false },
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const result = (await sendRequest(
|
|
1200
|
+
client,
|
|
1201
|
+
"textDocument/rename",
|
|
1202
|
+
{
|
|
1203
|
+
textDocument: { uri },
|
|
1204
|
+
position,
|
|
1205
|
+
newName: new_name,
|
|
1206
|
+
},
|
|
1207
|
+
signal,
|
|
1208
|
+
)) as WorkspaceEdit | null;
|
|
1209
|
+
|
|
1210
|
+
if (!result) {
|
|
1211
|
+
output = "Rename returned no edits";
|
|
1212
|
+
} else {
|
|
1213
|
+
const shouldApply = apply !== false;
|
|
1214
|
+
if (shouldApply) {
|
|
1215
|
+
const applied = await applyWorkspaceEdit(result, this.session.cwd);
|
|
1216
|
+
output = `Applied rename:\n${applied.map(a => ` ${a}`).join("\n")}`;
|
|
1217
|
+
} else {
|
|
1218
|
+
const preview = formatWorkspaceEdit(result, this.session.cwd);
|
|
1219
|
+
output = `Rename preview:\n${preview.map(p => ` ${p}`).join("\n")}`;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
case "reload": {
|
|
1226
|
+
// Try graceful reload first, fall back to kill
|
|
1227
|
+
output = `Restarted ${serverName}`;
|
|
1228
|
+
const reloadMethods = ["rust-analyzer/reloadWorkspace", "workspace/didChangeConfiguration"];
|
|
1229
|
+
for (const method of reloadMethods) {
|
|
1230
|
+
try {
|
|
1231
|
+
await sendRequest(
|
|
1232
|
+
client,
|
|
1233
|
+
method,
|
|
1234
|
+
method.includes("Configuration") ? { settings: {} } : null,
|
|
1235
|
+
signal,
|
|
1236
|
+
);
|
|
1237
|
+
output = `Reloaded ${serverName}`;
|
|
1238
|
+
break;
|
|
1239
|
+
} catch {
|
|
1240
|
+
// Method not supported, try next
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (output.startsWith("Restarted")) {
|
|
1244
|
+
client.proc.kill();
|
|
1245
|
+
}
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
default:
|
|
1250
|
+
output = `Unknown action: ${action}`;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
return {
|
|
1254
|
+
content: [{ type: "text", text: output }],
|
|
1255
|
+
details: { serverName, action, success: true, request: params },
|
|
1256
|
+
};
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
if (err instanceof ToolAbortError || signal?.aborted) {
|
|
1259
|
+
throw new ToolAbortError();
|
|
1260
|
+
}
|
|
1261
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1262
|
+
return {
|
|
1263
|
+
content: [{ type: "text", text: `LSP error: ${errorMessage}` }],
|
|
1264
|
+
details: { serverName, action, success: false, request: params },
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|