@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,887 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import type { AgentTool, AgentToolResult } from "@f5xc-salesdemos/pi-agent-core";
|
|
3
|
+
import { prompt } from "@f5xc-salesdemos/pi-utils";
|
|
4
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
5
|
+
import xcshApiDescription from "../prompts/tools/xcsh-api.md" with { type: "text" };
|
|
6
|
+
import { type ContextEnv, createContextEnv } from "../services/context-env";
|
|
7
|
+
import type { ToolSession } from ".";
|
|
8
|
+
import { humanizeResourceType } from "./render-utils";
|
|
9
|
+
|
|
10
|
+
// Namespace filtering driven by x-f5xc-namespace-profile from enriched API specs.
|
|
11
|
+
|
|
12
|
+
type NamespaceType = "system" | "shared" | "default" | "custom";
|
|
13
|
+
|
|
14
|
+
function namespaceTypeOf(namespaceName: string): NamespaceType {
|
|
15
|
+
if (namespaceName === "system") return "system";
|
|
16
|
+
if (namespaceName === "shared") return "shared";
|
|
17
|
+
if (namespaceName === "default") return "default";
|
|
18
|
+
if (namespaceName.startsWith("ves-io-")) return "system";
|
|
19
|
+
return "custom";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Default allowed namespace types for multi-namespace batch queries.
|
|
24
|
+
* Matches the default_profile.constraint.allowed from namespace_profile.yaml.
|
|
25
|
+
* When a batch spans multiple resource types we cannot use a single resource's
|
|
26
|
+
* namespace profile, so this safe default excludes system namespaces.
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_ALLOWED_NS_TYPES: ReadonlySet<NamespaceType> = new Set<NamespaceType>(["custom", "default", "shared"]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Look up allowed namespace types from the embedded API spec data.
|
|
32
|
+
* Returns the namespace profile constraint if the spec has x-f5xc-namespace-profile,
|
|
33
|
+
* otherwise returns the default allowed types.
|
|
34
|
+
*/
|
|
35
|
+
function loadAllowedNamespaceTypes(domain?: string): ReadonlySet<NamespaceType> {
|
|
36
|
+
if (!domain) return DEFAULT_ALLOWED_NS_TYPES;
|
|
37
|
+
try {
|
|
38
|
+
const mod = require("../internal-urls/api-spec-index.generated") as {
|
|
39
|
+
API_SPEC_DATA?: Readonly<
|
|
40
|
+
Record<
|
|
41
|
+
string,
|
|
42
|
+
{ info?: { "x-f5xc-namespace-profile"?: { constraint?: { allowed?: string[] } } }; [k: string]: unknown }
|
|
43
|
+
>
|
|
44
|
+
>;
|
|
45
|
+
};
|
|
46
|
+
const spec = mod.API_SPEC_DATA?.[domain];
|
|
47
|
+
const allowed = spec?.info?.["x-f5xc-namespace-profile"]?.constraint?.allowed;
|
|
48
|
+
if (Array.isArray(allowed) && allowed.length > 0) {
|
|
49
|
+
return new Set(allowed as NamespaceType[]);
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Spec data unavailable — use default
|
|
53
|
+
}
|
|
54
|
+
return DEFAULT_ALLOWED_NS_TYPES;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const xcshApiSchema = Type.Object({
|
|
58
|
+
method: Type.Union(
|
|
59
|
+
[Type.Literal("GET"), Type.Literal("POST"), Type.Literal("PUT"), Type.Literal("PATCH"), Type.Literal("DELETE")],
|
|
60
|
+
{ description: "HTTP method" },
|
|
61
|
+
),
|
|
62
|
+
path: Type.Optional(
|
|
63
|
+
Type.String({ description: "API path, e.g. /api/config/namespaces/{namespace}/http_loadbalancers" }),
|
|
64
|
+
),
|
|
65
|
+
paths: Type.Optional(
|
|
66
|
+
Type.Array(Type.String(), {
|
|
67
|
+
description:
|
|
68
|
+
"Batch concurrent GETs: provide multiple API paths to execute in parallel. " +
|
|
69
|
+
"Returns combined results keyed by resource type. Ideal for namespace inventory queries. " +
|
|
70
|
+
"Overrides single `path` when non-empty. Only supported for GET method.",
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
params: Type.Optional(
|
|
74
|
+
Type.Record(Type.String(), Type.String(), {
|
|
75
|
+
description:
|
|
76
|
+
"Path parameter substitutions, e.g. { namespace: 'example-ns', vh_name: 'example-vh' }. " +
|
|
77
|
+
"Unspecified params are auto-resolved from context env (e.g. {namespace} from XCSH_NAMESPACE).",
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
payload: Type.Optional(Type.Unknown({ description: "JSON body for POST/PUT/PATCH/DELETE requests" })),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
type XcshApiParams = Static<typeof xcshApiSchema>;
|
|
84
|
+
|
|
85
|
+
export interface XcshApiToolDetails {
|
|
86
|
+
status: number;
|
|
87
|
+
url: string;
|
|
88
|
+
method: string;
|
|
89
|
+
requestId: string;
|
|
90
|
+
/** Round-trip duration in milliseconds. */
|
|
91
|
+
durationMs?: number;
|
|
92
|
+
/** Active context profile name, if available. */
|
|
93
|
+
contextName?: string;
|
|
94
|
+
/** Response body size in bytes. */
|
|
95
|
+
bodySize?: number;
|
|
96
|
+
/** Number of items in the response `items` array (list operations). */
|
|
97
|
+
itemCount?: number;
|
|
98
|
+
/** Response content-type header. */
|
|
99
|
+
contentType?: string;
|
|
100
|
+
/** F5 XC gRPC error code label (e.g. NOT_FOUND, ALREADY_EXISTS) when present in response body. */
|
|
101
|
+
errorCodeLabel?: string;
|
|
102
|
+
/** Whether the request was automatically retried after a transient error (429/503). */
|
|
103
|
+
retried?: boolean;
|
|
104
|
+
/** Batch mode: total paths executed concurrently. */
|
|
105
|
+
batchSize?: number;
|
|
106
|
+
/** Batch mode: paths that returned 2xx. */
|
|
107
|
+
batchSuccessCount?: number;
|
|
108
|
+
/** Batch mode: total items across all list responses. */
|
|
109
|
+
batchTotalItems?: number;
|
|
110
|
+
/** The resolved JSON body string sent to the API (after $XCSH_* expansion). */
|
|
111
|
+
resolvedPayload?: string;
|
|
112
|
+
/** Human-readable verb for mutation results. */
|
|
113
|
+
mutationVerb?: "Created" | "Updated" | "Deleted";
|
|
114
|
+
/** Human-readable resource label, e.g. "load balancer 'rm-headers'". */
|
|
115
|
+
resourceLabel?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type XcshApiResult = AgentToolResult<XcshApiToolDetails> & { isError?: boolean };
|
|
119
|
+
|
|
120
|
+
/** F5 XC gRPC error code labels for human-readable error display. */
|
|
121
|
+
const XCSH_ERROR_CODES: Record<number, string> = {
|
|
122
|
+
3: "INVALID_ARGUMENT",
|
|
123
|
+
5: "NOT_FOUND",
|
|
124
|
+
6: "ALREADY_EXISTS",
|
|
125
|
+
7: "PERMISSION_DENIED",
|
|
126
|
+
8: "RESOURCE_EXHAUSTED",
|
|
127
|
+
9: "FAILED_PRECONDITION",
|
|
128
|
+
13: "INTERNAL",
|
|
129
|
+
14: "UNAVAILABLE",
|
|
130
|
+
16: "UNAUTHENTICATED",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export class XcshApiTool implements AgentTool<typeof xcshApiSchema, XcshApiToolDetails> {
|
|
134
|
+
readonly name = "xcsh_api";
|
|
135
|
+
readonly label = "API";
|
|
136
|
+
readonly description: string;
|
|
137
|
+
readonly parameters = xcshApiSchema;
|
|
138
|
+
#contextEnv: ContextEnv;
|
|
139
|
+
#lastApiBase = "";
|
|
140
|
+
#listablePathsCache: string[] | null = null;
|
|
141
|
+
#autoExpandPathsCache: string[] | null = null;
|
|
142
|
+
#expandedNamespaces = new Set<string>();
|
|
143
|
+
|
|
144
|
+
constructor(session: ToolSession) {
|
|
145
|
+
this.description = prompt.render(xcshApiDescription);
|
|
146
|
+
this.#contextEnv = createContextEnv(session.settings);
|
|
147
|
+
this.#warmTls();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#resolveCredentials(): [string, string] {
|
|
151
|
+
return [
|
|
152
|
+
(process.env.XCSH_API_URL ?? this.#contextEnv.get("XCSH_API_URL") ?? "").replace(/\/+$/, ""),
|
|
153
|
+
process.env.XCSH_API_TOKEN ?? this.#contextEnv.get("XCSH_API_TOKEN") ?? "",
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#warmTls(): void {
|
|
158
|
+
const [apiBase, apiToken] = this.#resolveCredentials();
|
|
159
|
+
if (apiBase && apiToken) {
|
|
160
|
+
this.#lastApiBase = apiBase;
|
|
161
|
+
fetch(`${apiBase}/api/web/namespaces`, {
|
|
162
|
+
method: "HEAD",
|
|
163
|
+
headers: { Authorization: `APIToken ${apiToken}` },
|
|
164
|
+
}).catch(() => {});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#errorResult(text: string, details?: XcshApiToolDetails): XcshApiResult {
|
|
169
|
+
return { content: [{ type: "text", text }], details, isError: true };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Lazily load namespace-scoped list operation paths from the embedded API catalog. */
|
|
173
|
+
#loadListablePaths(): string[] {
|
|
174
|
+
if (this.#listablePathsCache) return this.#listablePathsCache;
|
|
175
|
+
try {
|
|
176
|
+
const mod = require("../internal-urls/api-catalog-index.generated") as {
|
|
177
|
+
API_CATALOG_CATEGORY_SUMMARIES?: ReadonlyArray<{ name: string }>;
|
|
178
|
+
API_CATALOG_DATA?: Readonly<
|
|
179
|
+
Record<string, { operations: ReadonlyArray<{ method: string; path: string }> }>
|
|
180
|
+
>;
|
|
181
|
+
};
|
|
182
|
+
const summaries = mod.API_CATALOG_CATEGORY_SUMMARIES ?? [];
|
|
183
|
+
const data = mod.API_CATALOG_DATA ?? {};
|
|
184
|
+
const seen = new Set<string>();
|
|
185
|
+
const paths: string[] = [];
|
|
186
|
+
const CONFIG_PREFIX = "/api/config/namespaces/{namespace}/";
|
|
187
|
+
// Only include app/security types (keyword filter). Reduces batch from ~136 to ~42 paths,
|
|
188
|
+
// cutting expansion time by ~3× and eliminating infrastructure noise from the response.
|
|
189
|
+
// Healthcheck is included in batch content (for HC labels) but NOT in the auto-expand
|
|
190
|
+
// trigger list — direct GET to /healthchecks should not trigger a full namespace expansion.
|
|
191
|
+
const APP_KW =
|
|
192
|
+
/loadbalancer|pool|firewall|healthcheck|_policys|setting|type|mitigation|identification|network|route|host|definition|rate_limiter|prefix_set|cdn|waf|api_/i;
|
|
193
|
+
const AUTO_EXPAND_KW =
|
|
194
|
+
/loadbalancer|pool|firewall|_policys|setting|type|mitigation|identification|network|route|host|definition|rate_limiter|prefix_set|cdn|waf|api_/i;
|
|
195
|
+
const META_EXCL = /policy_set|policy_rule|data_polic/i;
|
|
196
|
+
for (const summary of summaries) {
|
|
197
|
+
const cat = data[summary.name];
|
|
198
|
+
if (!cat) continue;
|
|
199
|
+
for (const op of cat.operations) {
|
|
200
|
+
if (op.method.toUpperCase() !== "GET") continue;
|
|
201
|
+
if (!op.path.startsWith(CONFIG_PREFIX)) continue;
|
|
202
|
+
const segments = op.path.split("/").filter(Boolean);
|
|
203
|
+
if (segments.length !== 5) continue;
|
|
204
|
+
const last = segments.at(-1) ?? "";
|
|
205
|
+
if (last.startsWith("{")) continue;
|
|
206
|
+
if (!APP_KW.test(last) || META_EXCL.test(last)) continue;
|
|
207
|
+
if (!seen.has(op.path)) {
|
|
208
|
+
seen.add(op.path);
|
|
209
|
+
paths.push(op.path);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Build separate auto-expand trigger list (excludes healthcheck)
|
|
214
|
+
this.#autoExpandPathsCache = paths.filter(p => {
|
|
215
|
+
const last = p.split("/").filter(Boolean).at(-1) ?? "";
|
|
216
|
+
return AUTO_EXPAND_KW.test(last);
|
|
217
|
+
});
|
|
218
|
+
this.#listablePathsCache = paths;
|
|
219
|
+
return paths;
|
|
220
|
+
} catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Batch ALL non-system namespaces in one tool call for tenant-wide queries. */
|
|
226
|
+
async #executeMultiNamespaceBatch(
|
|
227
|
+
paths: string[],
|
|
228
|
+
apiBase: string,
|
|
229
|
+
apiToken: string,
|
|
230
|
+
signal?: AbortSignal,
|
|
231
|
+
): Promise<XcshApiResult> {
|
|
232
|
+
const headers = { Authorization: `APIToken ${apiToken}`, Accept: "application/json" };
|
|
233
|
+
const timeoutSignal = AbortSignal.timeout(90_000);
|
|
234
|
+
const fetchSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
235
|
+
const startMs = performance.now();
|
|
236
|
+
|
|
237
|
+
// Discover all accessible namespaces, filtered by spec-driven namespace profile.
|
|
238
|
+
// Multi-resource batch queries use DEFAULT_ALLOWED_NS_TYPES (custom, default, shared)
|
|
239
|
+
// which excludes system namespaces. When x-f5xc-namespace-profile is present in
|
|
240
|
+
// enriched specs, loadAllowedNamespaceTypes() will use the spec's constraint.
|
|
241
|
+
const allowedTypes = loadAllowedNamespaceTypes();
|
|
242
|
+
let allNs: string[] = [];
|
|
243
|
+
try {
|
|
244
|
+
const nsResp = await fetch(`${apiBase}/api/web/namespaces`, {
|
|
245
|
+
method: "GET",
|
|
246
|
+
headers,
|
|
247
|
+
signal: fetchSignal,
|
|
248
|
+
});
|
|
249
|
+
if (nsResp.ok) {
|
|
250
|
+
const nsData = (await nsResp.json()) as { items?: Array<Record<string, unknown>> };
|
|
251
|
+
allNs = (nsData.items ?? [])
|
|
252
|
+
.map(item => (typeof item.name === "string" ? item.name : null))
|
|
253
|
+
.filter((name): name is string => name !== null && allowedTypes.has(namespaceTypeOf(name)));
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
// Fall back to default namespace only
|
|
257
|
+
const def = this.#contextEnv.get("XCSH_NAMESPACE") ?? "default";
|
|
258
|
+
allNs = [def];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Batch each namespace and combine results
|
|
262
|
+
const nsSections: string[] = [];
|
|
263
|
+
for (const ns of allNs) {
|
|
264
|
+
const nsParams = { namespace: ns };
|
|
265
|
+
this.#expandedNamespaces.add(ns);
|
|
266
|
+
const result = await this.#executeBatch(paths, nsParams, apiBase, apiToken, signal);
|
|
267
|
+
const text = result.content[0]?.type === "text" ? (result.content[0] as { text: string }).text : "";
|
|
268
|
+
if (text.includes("resource type")) {
|
|
269
|
+
nsSections.push(`\n=== Namespace: ${ns} ===\n${text}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const durationMs = Math.round(performance.now() - startMs);
|
|
274
|
+
const combined =
|
|
275
|
+
nsSections.length > 0
|
|
276
|
+
? nsSections.join("\n") +
|
|
277
|
+
"\n\nTenant-wide inventory complete. All namespaces, specs, and relationships shown above. No further API calls needed."
|
|
278
|
+
: "No resources found across accessible namespaces.";
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: "text", text: combined }],
|
|
282
|
+
details: {
|
|
283
|
+
status: 200,
|
|
284
|
+
url: apiBase,
|
|
285
|
+
method: "GET",
|
|
286
|
+
requestId: crypto.randomUUID(),
|
|
287
|
+
durationMs,
|
|
288
|
+
contextName: this.#contextEnv.getContextName(),
|
|
289
|
+
batchSize: paths.length * allNs.length,
|
|
290
|
+
batchSuccessCount: nsSections.length,
|
|
291
|
+
batchTotalItems: 0,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async #executeBatch(
|
|
297
|
+
paths: string[],
|
|
298
|
+
params: Record<string, string> | undefined,
|
|
299
|
+
apiBase: string,
|
|
300
|
+
apiToken: string,
|
|
301
|
+
signal?: AbortSignal,
|
|
302
|
+
): Promise<XcshApiResult> {
|
|
303
|
+
const requestId = crypto.randomUUID();
|
|
304
|
+
const contextName = this.#contextEnv.getContextName();
|
|
305
|
+
|
|
306
|
+
// File-based cache: reuse batch results across xcsh invocations (5-minute TTL).
|
|
307
|
+
// Prevents cumulative rate limiting when the benchmark runs multiple queries.
|
|
308
|
+
const ns = params?.namespace ?? this.#contextEnv.get("XCSH_NAMESPACE") ?? "_default";
|
|
309
|
+
const cachePath = `${os.tmpdir()}/xcsh-batch-${ns}.json`;
|
|
310
|
+
try {
|
|
311
|
+
const cached = (await Bun.file(cachePath).json()) as {
|
|
312
|
+
ts: number;
|
|
313
|
+
text: string;
|
|
314
|
+
batchSize: number;
|
|
315
|
+
batchSuccessCount: number;
|
|
316
|
+
batchTotalItems: number;
|
|
317
|
+
};
|
|
318
|
+
if (cached.ts > Date.now() - 600_000) {
|
|
319
|
+
return {
|
|
320
|
+
content: [{ type: "text", text: cached.text }],
|
|
321
|
+
details: {
|
|
322
|
+
status: 200,
|
|
323
|
+
url: apiBase,
|
|
324
|
+
method: "GET",
|
|
325
|
+
requestId,
|
|
326
|
+
durationMs: 0,
|
|
327
|
+
contextName,
|
|
328
|
+
batchSize: cached.batchSize,
|
|
329
|
+
batchSuccessCount: cached.batchSuccessCount,
|
|
330
|
+
batchTotalItems: cached.batchTotalItems,
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
// Cache miss or invalid — proceed with fresh batch
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const headers: Record<string, string> = {
|
|
339
|
+
Authorization: `APIToken ${apiToken}`,
|
|
340
|
+
Accept: "application/json",
|
|
341
|
+
};
|
|
342
|
+
const timeoutSignal = AbortSignal.timeout(90_000);
|
|
343
|
+
const fetchSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
344
|
+
const startMs = performance.now();
|
|
345
|
+
|
|
346
|
+
type BatchEntry = {
|
|
347
|
+
path: string;
|
|
348
|
+
status: number;
|
|
349
|
+
statusText: string;
|
|
350
|
+
rawBody: string;
|
|
351
|
+
parsed: Record<string, unknown> | null;
|
|
352
|
+
itemCount: number | undefined;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const fetchOne = async (rawPath: string): Promise<BatchEntry> => {
|
|
356
|
+
const resolvedPath = this.#contextEnv.resolvePath(rawPath, params);
|
|
357
|
+
const url = `${apiBase}${resolvedPath}`;
|
|
358
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
359
|
+
try {
|
|
360
|
+
const response = await fetch(url, { method: "GET", headers, signal: fetchSignal });
|
|
361
|
+
// Retry on 429/503 (transient rate limit / server error)
|
|
362
|
+
if ((response.status === 429 || response.status === 503) && attempt < 2 && !fetchSignal.aborted) {
|
|
363
|
+
await Bun.sleep(1000 * (attempt + 1));
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
const raw = await response.text();
|
|
367
|
+
let parsed: Record<string, unknown> | null = null;
|
|
368
|
+
try {
|
|
369
|
+
parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
370
|
+
} catch {
|
|
371
|
+
// Non-JSON body
|
|
372
|
+
}
|
|
373
|
+
const items = parsed?.items;
|
|
374
|
+
const itemCount = Array.isArray(items) ? (items as unknown[]).length : undefined;
|
|
375
|
+
return {
|
|
376
|
+
path: rawPath,
|
|
377
|
+
status: response.status,
|
|
378
|
+
statusText: response.statusText,
|
|
379
|
+
rawBody: raw,
|
|
380
|
+
parsed,
|
|
381
|
+
itemCount,
|
|
382
|
+
};
|
|
383
|
+
} catch (err) {
|
|
384
|
+
if (attempt < 2 && !fetchSignal.aborted) {
|
|
385
|
+
await Bun.sleep(1000 * (attempt + 1));
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
389
|
+
return {
|
|
390
|
+
path: rawPath,
|
|
391
|
+
status: 0,
|
|
392
|
+
statusText: "Error",
|
|
393
|
+
rawBody: message,
|
|
394
|
+
parsed: null,
|
|
395
|
+
itemCount: undefined,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Should not reach here but TypeScript needs a return
|
|
400
|
+
return {
|
|
401
|
+
path: rawPath,
|
|
402
|
+
status: 0,
|
|
403
|
+
statusText: "Error",
|
|
404
|
+
rawBody: "Max retries",
|
|
405
|
+
parsed: null,
|
|
406
|
+
itemCount: undefined,
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
// With file-based cache, only the first invocation hits the API; concurrency can be higher
|
|
411
|
+
const CONCURRENCY = 10;
|
|
412
|
+
const results: BatchEntry[] = [];
|
|
413
|
+
for (let i = 0; i < paths.length; i += CONCURRENCY) {
|
|
414
|
+
const chunk = paths.slice(i, i + CONCURRENCY);
|
|
415
|
+
const chunkResults = await Promise.all(chunk.map(fetchOne));
|
|
416
|
+
results.push(...chunkResults);
|
|
417
|
+
if (i + CONCURRENCY < paths.length && !fetchSignal.aborted) {
|
|
418
|
+
await Bun.sleep(200);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const durationMs = Math.round(performance.now() - startMs);
|
|
423
|
+
|
|
424
|
+
const withData = results.filter(r => (r.itemCount ?? 0) > 0);
|
|
425
|
+
// Batch already filtered to app/security types by #loadListablePaths().
|
|
426
|
+
// Still filter bulk types (>25 items) as a safety net.
|
|
427
|
+
const relevantData = withData.filter(r => (r.itemCount ?? 0) <= 25);
|
|
428
|
+
|
|
429
|
+
// Phase 2: fetch individual resource specs for items in non-empty types.
|
|
430
|
+
// With 42-path batch, this is ~12 items total — adds ~3s, not 100s like the 136-path era.
|
|
431
|
+
// Store raw spec objects for semantic summary generation
|
|
432
|
+
const rawSpecs = new Map<string, Record<string, unknown>>();
|
|
433
|
+
const specItems: Array<{ typePath: string; name: string }> = [];
|
|
434
|
+
// Only fetch specs for types that carry relationship data (LBs, pools, firewalls,
|
|
435
|
+
// healthchecks). Excludes system-generated objects (routes, virtual_hosts, etc.)
|
|
436
|
+
// that inflate the item count and don't add relationship info.
|
|
437
|
+
const SPEC_TYPES = /loadbalancer|origin_pool|app_firewall|healthcheck|rate_limiter/i;
|
|
438
|
+
for (const r of relevantData) {
|
|
439
|
+
const typeName = r.path.split("/").pop() ?? "";
|
|
440
|
+
if (!SPEC_TYPES.test(typeName)) continue;
|
|
441
|
+
const items = (r.parsed?.items as Array<Record<string, unknown>> | undefined) ?? [];
|
|
442
|
+
for (const item of items) {
|
|
443
|
+
const name = typeof item.name === "string" ? item.name : null;
|
|
444
|
+
if (name && items.length <= 15) specItems.push({ typePath: r.path, name });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (specItems.length > 0 && specItems.length <= 40 && !fetchSignal.aborted) {
|
|
448
|
+
for (let i = 0; i < specItems.length; i += CONCURRENCY) {
|
|
449
|
+
const chunk = specItems.slice(i, i + CONCURRENCY);
|
|
450
|
+
await Promise.all(
|
|
451
|
+
chunk.map(async ({ typePath, name }) => {
|
|
452
|
+
const specPath = this.#contextEnv.resolvePath(`${typePath}/${name}`, params);
|
|
453
|
+
try {
|
|
454
|
+
const resp = await fetch(`${apiBase}${specPath}`, { method: "GET", headers, signal: fetchSignal });
|
|
455
|
+
if (resp.ok) {
|
|
456
|
+
const data = (await resp.json()) as Record<string, unknown>;
|
|
457
|
+
const spec = data.spec as Record<string, unknown> | undefined;
|
|
458
|
+
if (spec) {
|
|
459
|
+
rawSpecs.set(`${typePath}/${name}`, spec);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
// Non-fatal
|
|
464
|
+
}
|
|
465
|
+
}),
|
|
466
|
+
);
|
|
467
|
+
if (i + CONCURRENCY < specItems.length && !fetchSignal.aborted) await Bun.sleep(200);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Compact response: names only for discovery, no full JSON blobs
|
|
472
|
+
const sections: string[] = [];
|
|
473
|
+
// Split types into core (relationship-bearing, with specs) and secondary.
|
|
474
|
+
// Core types get detailed per-resource output with spec summaries.
|
|
475
|
+
// Secondary types get a compact count to reduce batch response noise.
|
|
476
|
+
// Shorter, focused output helps the model find relationship data directly.
|
|
477
|
+
const getRawTypeName = (r: BatchEntry) => r.path.split("/").pop() ?? r.path;
|
|
478
|
+
// Human-readable type name for display: http_loadbalancers → load balancers, origin_pools → origin pools
|
|
479
|
+
const humanizeType = (raw: string): string =>
|
|
480
|
+
raw
|
|
481
|
+
.replace(/^http_/, "")
|
|
482
|
+
.replace(/^tcp_/, "TCP ")
|
|
483
|
+
.replace(/_/g, " ")
|
|
484
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
485
|
+
.replace(/([a-z])(balancer|checker)/gi, "$1 $2");
|
|
486
|
+
const coreTypes = relevantData.filter(r => SPEC_TYPES.test(getRawTypeName(r)));
|
|
487
|
+
const secondaryTypes = relevantData.filter(r => !SPEC_TYPES.test(getRawTypeName(r)));
|
|
488
|
+
|
|
489
|
+
if (coreTypes.length > 0) {
|
|
490
|
+
sections.push(`Namespace resource inventory (${coreTypes.length} core types):\n`);
|
|
491
|
+
let idx = 1;
|
|
492
|
+
for (const r of coreTypes) {
|
|
493
|
+
const typeName = humanizeType(getRawTypeName(r));
|
|
494
|
+
const items = (r.parsed?.items as Array<Record<string, unknown>> | undefined) ?? [];
|
|
495
|
+
if (items.length === 0) {
|
|
496
|
+
sections.push(`${idx}. ${typeName}: ${r.itemCount} item(s)`);
|
|
497
|
+
} else {
|
|
498
|
+
sections.push(`${idx}. ${typeName} (${items.length}):`);
|
|
499
|
+
for (const item of items) {
|
|
500
|
+
const name = typeof item.name === "string" ? item.name : "?";
|
|
501
|
+
const disabled = item.disabled === true ? " [DISABLED]" : "";
|
|
502
|
+
sections.push(` - ${name}${disabled}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
idx++;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (secondaryTypes.length > 0) {
|
|
510
|
+
const secondaryCount = secondaryTypes.reduce((sum, r) => sum + (r.itemCount ?? 0), 0);
|
|
511
|
+
sections.push(
|
|
512
|
+
`\n(+${secondaryTypes.length} other types with ${secondaryCount} items: ${secondaryTypes.map(r => humanizeType(getRawTypeName(r))).join(", ")})`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Semantic summary: answer-ready labels from raw specs.
|
|
517
|
+
// Human-readable labels (WAF=name, no-WAF, pools=[...]) instead of raw fields.
|
|
518
|
+
// Directly answers relationship queries without follow-up GETs.
|
|
519
|
+
const summaryLines: string[] = [];
|
|
520
|
+
const extractRef = (v: unknown): string | null => {
|
|
521
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
522
|
+
const o = v as Record<string, unknown>;
|
|
523
|
+
return typeof o.name === "string" ? o.name : null;
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
};
|
|
527
|
+
const extractPoolRefs = (pools: unknown): string[] => {
|
|
528
|
+
if (!Array.isArray(pools)) return [];
|
|
529
|
+
return (pools as Array<Record<string, unknown>>)
|
|
530
|
+
.map(item => {
|
|
531
|
+
if (typeof item?.pool === "object" && item.pool !== null)
|
|
532
|
+
return (item.pool as Record<string, unknown>).name as string;
|
|
533
|
+
return extractRef(item);
|
|
534
|
+
})
|
|
535
|
+
.filter((n): n is string => n !== null);
|
|
536
|
+
};
|
|
537
|
+
for (const [specKey, spec] of rawSpecs) {
|
|
538
|
+
const parts = specKey.split("/");
|
|
539
|
+
const rName = parts.at(-1) ?? "";
|
|
540
|
+
const rType = parts.at(-2) ?? "";
|
|
541
|
+
const labels: string[] = [];
|
|
542
|
+
if (/tcp_loadbalancer/i.test(rType)) {
|
|
543
|
+
labels.push("TCP");
|
|
544
|
+
const pools = extractPoolRefs(spec.origin_pools_weights);
|
|
545
|
+
if (pools.length > 0) labels.push(`pools=[${pools.join(",")}]`);
|
|
546
|
+
if (typeof spec.listen_port === "number") labels.push(`port=${spec.listen_port}`);
|
|
547
|
+
const domains = Array.isArray(spec.domains) ? (spec.domains as string[]) : [];
|
|
548
|
+
if (domains.length > 0) labels.push(`domains=[${domains.join(",")}]`);
|
|
549
|
+
} else if (/loadbalancer/i.test(rType)) {
|
|
550
|
+
const waf = extractRef(spec.app_firewall);
|
|
551
|
+
labels.push(waf ? `WAF=${waf}` : "no-WAF");
|
|
552
|
+
const pools = extractPoolRefs(spec.default_route_pools);
|
|
553
|
+
if (pools.length > 0) labels.push(`pools=[${pools.join(",")}]`);
|
|
554
|
+
if (spec.disable_rate_limit != null) labels.push("no-rate-limit");
|
|
555
|
+
else if (spec.rate_limit != null) labels.push("has-rate-limit");
|
|
556
|
+
const domains = Array.isArray(spec.domains) ? (spec.domains as string[]) : [];
|
|
557
|
+
if (domains.length > 0) labels.push(`domains=[${domains.join(",")}]`);
|
|
558
|
+
}
|
|
559
|
+
if (/origin_pool/i.test(rType)) {
|
|
560
|
+
const hc = Array.isArray(spec.healthcheck)
|
|
561
|
+
? (spec.healthcheck as Array<Record<string, unknown>>).map(h => extractRef(h)).filter(Boolean)
|
|
562
|
+
: [];
|
|
563
|
+
labels.push(hc.length > 0 ? `healthcheck=[${hc.join(",")}]` : "no-healthcheck");
|
|
564
|
+
const servers = Array.isArray(spec.origin_servers) ? (spec.origin_servers as unknown[]).length : 0;
|
|
565
|
+
if (servers > 0) labels.push(`${servers} origin(s)`);
|
|
566
|
+
if (typeof spec.port === "number") labels.push(`port=${spec.port}`);
|
|
567
|
+
}
|
|
568
|
+
if (/app_firewall/i.test(rType)) {
|
|
569
|
+
if (spec.detection_settings != null) labels.push("has-detection-settings");
|
|
570
|
+
if (spec.monitoring != null) labels.push("monitoring-mode");
|
|
571
|
+
else if (spec.blocking != null) labels.push("blocking-mode");
|
|
572
|
+
}
|
|
573
|
+
if (/healthcheck/i.test(rType)) {
|
|
574
|
+
if (spec.http_health_check != null) {
|
|
575
|
+
labels.push("http");
|
|
576
|
+
const httpHC = spec.http_health_check as Record<string, unknown>;
|
|
577
|
+
if (typeof httpHC.path === "string") labels.push(`path=${httpHC.path}`);
|
|
578
|
+
} else if (spec.tcp_health_check != null) {
|
|
579
|
+
labels.push("tcp");
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (/rate_limiter/i.test(rType)) {
|
|
583
|
+
if (typeof spec.total_number === "number") labels.push(`threshold=${spec.total_number}`);
|
|
584
|
+
if (typeof spec.burst_size === "number") labels.push(`burst=${spec.burst_size}`);
|
|
585
|
+
if (typeof spec.committed_information_rate === "number")
|
|
586
|
+
labels.push(`rate=${spec.committed_information_rate}`);
|
|
587
|
+
if (typeof spec.unit === "string") labels.push(`unit=${spec.unit}`);
|
|
588
|
+
}
|
|
589
|
+
if (labels.length > 0) {
|
|
590
|
+
summaryLines.push(`${rName}: ${labels.join(", ")}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (summaryLines.length > 0) {
|
|
594
|
+
sections.push(`\nResource summary:\n${summaryLines.join("\n")}`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
sections.push(
|
|
598
|
+
"\nInventory complete. All listed resources exist and are valid references for mutations. No further API calls needed.",
|
|
599
|
+
);
|
|
600
|
+
const batchTotalItems = relevantData.reduce((sum, r) => sum + (r.itemCount ?? 0), 0);
|
|
601
|
+
const text = sections.join("\n");
|
|
602
|
+
const batchSize = paths.length;
|
|
603
|
+
const errorCount = results.filter(r => r.status >= 400 || r.status === 0).length;
|
|
604
|
+
const batchSuccessCount = results.length - errorCount;
|
|
605
|
+
|
|
606
|
+
// Cache for subsequent invocations
|
|
607
|
+
try {
|
|
608
|
+
await Bun.write(
|
|
609
|
+
cachePath,
|
|
610
|
+
JSON.stringify({ ts: Date.now(), text, batchSize, batchSuccessCount, batchTotalItems }),
|
|
611
|
+
);
|
|
612
|
+
} catch {
|
|
613
|
+
// Cache write failure is non-fatal
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
content: [{ type: "text", text }],
|
|
618
|
+
details: {
|
|
619
|
+
status: 200,
|
|
620
|
+
url: apiBase,
|
|
621
|
+
method: "GET",
|
|
622
|
+
requestId,
|
|
623
|
+
durationMs,
|
|
624
|
+
contextName,
|
|
625
|
+
batchSize,
|
|
626
|
+
batchSuccessCount,
|
|
627
|
+
batchTotalItems,
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#statusGuidance(status: number): string | null {
|
|
633
|
+
const ctx = this.#contextEnv.getContextName();
|
|
634
|
+
const ctxHint = ctx ? ` (context: \`${ctx}\`)` : "";
|
|
635
|
+
switch (status) {
|
|
636
|
+
case 401:
|
|
637
|
+
return `Token may be expired or invalid${ctxHint}. Run \`/context validate\` to check credentials, or \`/context create\` to set up a new context with fresh credentials.`;
|
|
638
|
+
case 403:
|
|
639
|
+
return `Access denied${ctxHint}. The API token may lack the required role or permission for this operation. Check the token's role assignments in the F5 XC console.`;
|
|
640
|
+
case 404: {
|
|
641
|
+
const ns = process.env.XCSH_NAMESPACE ?? this.#contextEnv.get("XCSH_NAMESPACE") ?? "default";
|
|
642
|
+
return `Resource not found in namespace \`${ns}\`${ctxHint}. Verify the resource name, or use POST to create it.`;
|
|
643
|
+
}
|
|
644
|
+
case 409:
|
|
645
|
+
return `Resource already exists${ctxHint}. Use PUT to replace the existing resource, or DELETE it first before creating a new one.`;
|
|
646
|
+
case 400:
|
|
647
|
+
return `Bad request${ctxHint}. Check the payload for missing required fields or invalid values.`;
|
|
648
|
+
case 422:
|
|
649
|
+
return `Validation failed${ctxHint}. The request payload has field-level errors — check the message above.`;
|
|
650
|
+
case 429:
|
|
651
|
+
return `API rate limit exceeded${ctxHint}. Wait briefly and retry the request.`;
|
|
652
|
+
default:
|
|
653
|
+
if (status >= 500) return `Server error (${status})${ctxHint}. This may be transient — retry the request.`;
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async execute(_toolCallId: string, params: XcshApiParams, signal?: AbortSignal): Promise<XcshApiResult> {
|
|
659
|
+
const [apiBase, apiToken] = this.#resolveCredentials();
|
|
660
|
+
if (apiBase && apiBase !== this.#lastApiBase) this.#warmTls();
|
|
661
|
+
if (!apiBase) {
|
|
662
|
+
const ctx = this.#contextEnv.getContextName();
|
|
663
|
+
const ctxNote = ctx ? ` Active context \`${ctx}\` has no API URL.` : "";
|
|
664
|
+
return this.#errorResult(
|
|
665
|
+
`Error: No API URL configured.${ctxNote} Activate a context with \`/context activate <name>\` or \`/context create\`, or set the XCSH_API_URL environment variable.`,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
if (!apiToken) {
|
|
669
|
+
const ctx = this.#contextEnv.getContextName();
|
|
670
|
+
const ctxNote = ctx ? ` Active context \`${ctx}\` has no API token.` : "";
|
|
671
|
+
return this.#errorResult(
|
|
672
|
+
`Error: No API token configured.${ctxNote} Activate a context with \`/context activate <name>\` or \`/context create\`, or set the XCSH_API_TOKEN environment variable.`,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
const batchPaths = params.paths?.filter(p => p.trim().length > 0);
|
|
676
|
+
if (batchPaths && batchPaths.length > 0) {
|
|
677
|
+
// Wildcard "*" auto-discovers all namespace-scoped list paths from the catalog
|
|
678
|
+
const isWildcard = batchPaths.length === 1 && batchPaths[0] === "*";
|
|
679
|
+
const resolved = isWildcard ? this.#loadListablePaths() : batchPaths;
|
|
680
|
+
if (resolved.length > 0) {
|
|
681
|
+
const batchNs = params.params?.namespace ?? this.#contextEnv.get("XCSH_NAMESPACE") ?? "";
|
|
682
|
+
// Wildcard namespace: batch ALL non-system namespaces in one tool call.
|
|
683
|
+
// Reduces multi-namespace queries from N+1 batch calls to 1.
|
|
684
|
+
if (batchNs === "*") {
|
|
685
|
+
return this.#executeMultiNamespaceBatch(resolved, apiBase, apiToken, signal);
|
|
686
|
+
}
|
|
687
|
+
if (batchNs) this.#expandedNamespaces.add(batchNs);
|
|
688
|
+
return this.#executeBatch(resolved, params.params, apiBase, apiToken, signal);
|
|
689
|
+
}
|
|
690
|
+
if (isWildcard) {
|
|
691
|
+
return this.#errorResult(
|
|
692
|
+
"Error: Wildcard namespace discovery found no listable API paths. The API catalog may not be loaded.",
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (!params.path) {
|
|
697
|
+
return this.#errorResult(
|
|
698
|
+
'Error: `path` is required for single-resource operations. Use `paths: ["*"]` for namespace discovery.',
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
// Per-namespace auto-expand: when the model GETs a namespace list endpoint,
|
|
702
|
+
// batch ALL types for that namespace on first access. Each namespace expands once.
|
|
703
|
+
// File-based cache in #executeBatch prevents redundant API calls across sessions.
|
|
704
|
+
if (params.method === "GET" && !params.payload) {
|
|
705
|
+
const listablePaths = this.#loadListablePaths();
|
|
706
|
+
// Use the auto-expand trigger list (excludes healthcheck) to decide WHETHER to expand.
|
|
707
|
+
// The batch itself uses the full listablePaths (includes healthcheck) for content.
|
|
708
|
+
const triggerPaths = this.#autoExpandPathsCache ?? listablePaths;
|
|
709
|
+
if (triggerPaths.length > 0) {
|
|
710
|
+
const normalized = params.path.replace(
|
|
711
|
+
/\/api\/config\/namespaces\/[^/]+\//,
|
|
712
|
+
"/api/config/namespaces/{namespace}/",
|
|
713
|
+
);
|
|
714
|
+
if (triggerPaths.includes(params.path) || triggerPaths.includes(normalized)) {
|
|
715
|
+
const nsMatch = params.path.match(/\/api\/config\/namespaces\/([^/]+)\//);
|
|
716
|
+
const ns = params.params?.namespace ?? (nsMatch?.[1] && nsMatch[1] !== "{namespace}" ? nsMatch[1] : "");
|
|
717
|
+
if (ns && !this.#expandedNamespaces.has(ns)) {
|
|
718
|
+
const batchParams = params.params ?? { namespace: ns };
|
|
719
|
+
this.#expandedNamespaces.add(ns);
|
|
720
|
+
return this.#executeBatch(listablePaths, batchParams, apiBase, apiToken, signal);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const resolvedPath = this.#contextEnv.resolvePath(params.path, params.params);
|
|
726
|
+
const unresolvedPlaceholders = resolvedPath.match(/\{\w+\}/g);
|
|
727
|
+
if (unresolvedPlaceholders) {
|
|
728
|
+
return this.#errorResult(
|
|
729
|
+
`Error: Unresolved path parameter(s): ${unresolvedPlaceholders.join(", ")}. Provide them via \`params\` or ensure they are configured in the active context.`,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
const url = `${apiBase}${resolvedPath}`;
|
|
733
|
+
const requestId = crypto.randomUUID();
|
|
734
|
+
const headers: Record<string, string> = {
|
|
735
|
+
Authorization: `APIToken ${apiToken}`,
|
|
736
|
+
Accept: "application/json",
|
|
737
|
+
"X-Request-ID": requestId,
|
|
738
|
+
};
|
|
739
|
+
const timeoutSignal = AbortSignal.timeout(30_000);
|
|
740
|
+
const fetchSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
741
|
+
const init: RequestInit = { method: params.method, headers, signal: fetchSignal };
|
|
742
|
+
|
|
743
|
+
let resolvedPayload: string | undefined;
|
|
744
|
+
if (params.payload && params.method !== "GET") {
|
|
745
|
+
headers["Content-Type"] = "application/json";
|
|
746
|
+
const payloadJson = JSON.stringify(params.payload);
|
|
747
|
+
const resolved = this.#contextEnv.resolvePayloadVars(payloadJson);
|
|
748
|
+
init.body = resolved;
|
|
749
|
+
resolvedPayload = resolved;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const startMs = performance.now();
|
|
753
|
+
try {
|
|
754
|
+
let response = await fetch(url, init);
|
|
755
|
+
|
|
756
|
+
// Auto-retry idempotent GET on transient 429/503
|
|
757
|
+
let retried = false;
|
|
758
|
+
if (params.method === "GET" && (response.status === 429 || response.status === 503) && !fetchSignal.aborted) {
|
|
759
|
+
const retryAfter = response.headers.get("retry-after");
|
|
760
|
+
const seconds = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;
|
|
761
|
+
const delayMs = Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds * 1000, 10_000) : 1000;
|
|
762
|
+
await Bun.sleep(delayMs);
|
|
763
|
+
if (!fetchSignal.aborted) {
|
|
764
|
+
response = await fetch(url, init);
|
|
765
|
+
retried = true;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const raw = await response.text();
|
|
770
|
+
const durationMs = Math.round(performance.now() - startMs);
|
|
771
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
772
|
+
let bodyText = raw;
|
|
773
|
+
if (contentType.includes("application/json")) {
|
|
774
|
+
try {
|
|
775
|
+
bodyText = JSON.stringify(JSON.parse(raw));
|
|
776
|
+
} catch {
|
|
777
|
+
// Unparseable JSON body — fall back to raw text
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
const statusLine = `${response.status} ${response.statusText}`;
|
|
781
|
+
const contextName = this.#contextEnv.getContextName();
|
|
782
|
+
const bodySize = raw.length;
|
|
783
|
+
let parsedBody: Record<string, unknown> | null = null;
|
|
784
|
+
let itemCount: number | undefined;
|
|
785
|
+
try {
|
|
786
|
+
parsedBody = JSON.parse(raw) as Record<string, unknown>;
|
|
787
|
+
if (Array.isArray(parsedBody?.items)) itemCount = (parsedBody.items as unknown[]).length;
|
|
788
|
+
} catch {
|
|
789
|
+
// Not JSON
|
|
790
|
+
}
|
|
791
|
+
const detail: XcshApiToolDetails = {
|
|
792
|
+
status: response.status,
|
|
793
|
+
url,
|
|
794
|
+
method: params.method,
|
|
795
|
+
requestId,
|
|
796
|
+
durationMs,
|
|
797
|
+
contextName,
|
|
798
|
+
bodySize,
|
|
799
|
+
itemCount,
|
|
800
|
+
contentType: contentType || undefined,
|
|
801
|
+
retried: retried || undefined,
|
|
802
|
+
resolvedPayload,
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const guidance = this.#statusGuidance(response.status);
|
|
806
|
+
if (guidance) {
|
|
807
|
+
let errorCodePrefix = "";
|
|
808
|
+
const codeLabel = parsedBody ? XCSH_ERROR_CODES[parsedBody.code as number] : undefined;
|
|
809
|
+
if (codeLabel) {
|
|
810
|
+
errorCodePrefix = `[${codeLabel}] `;
|
|
811
|
+
detail.errorCodeLabel = codeLabel;
|
|
812
|
+
}
|
|
813
|
+
return this.#errorResult(`${statusLine}\n\n${bodyText}\n\n${errorCodePrefix}${guidance}`, detail);
|
|
814
|
+
}
|
|
815
|
+
// Mutation stop signals + cache invalidation: reduce post-mutation verification GETs
|
|
816
|
+
if (response.status >= 200 && response.status < 300 && params.method !== "GET") {
|
|
817
|
+
// Invalidate batch cache so subsequent namespace queries reflect the mutation
|
|
818
|
+
const nsMatch = resolvedPath.match(/\/api\/config\/namespaces\/([^/]+)\//);
|
|
819
|
+
if (nsMatch?.[1]) {
|
|
820
|
+
const nsCachePath = `${os.tmpdir()}/xcsh-batch-${nsMatch[1]}.json`;
|
|
821
|
+
await Bun.write(nsCachePath, JSON.stringify({ ts: 0 })).catch(() => {});
|
|
822
|
+
}
|
|
823
|
+
// Append stop signal to prevent unnecessary verification GETs
|
|
824
|
+
const verb = params.method === "DELETE" ? "Deleted" : params.method === "POST" ? "Created" : "Updated";
|
|
825
|
+
// Human-readable resource label: "load balancer ar-lb-01" instead of raw API path.
|
|
826
|
+
// Label changes help the model echo type names in its response (Finding 13).
|
|
827
|
+
// INTERACTION EFFECT: this change + TCP protocol label are synergistic (Finding 31).
|
|
828
|
+
const pathSegs = resolvedPath.split("/").filter(Boolean);
|
|
829
|
+
let resourceLabel: string;
|
|
830
|
+
if (params.method === "POST") {
|
|
831
|
+
const rawType = pathSegs.at(-1) ?? "";
|
|
832
|
+
const meta = parsedBody?.metadata as Record<string, unknown> | undefined;
|
|
833
|
+
const name = typeof meta?.name === "string" ? meta.name : null;
|
|
834
|
+
resourceLabel = name ? `${humanizeResourceType(rawType)} ${name}` : resolvedPath;
|
|
835
|
+
} else {
|
|
836
|
+
const name = pathSegs.at(-1) ?? "";
|
|
837
|
+
const rawType = pathSegs.at(-2) ?? "";
|
|
838
|
+
resourceLabel = name && rawType ? `${humanizeResourceType(rawType)} ${name}` : resolvedPath;
|
|
839
|
+
}
|
|
840
|
+
detail.mutationVerb = verb as "Created" | "Updated" | "Deleted";
|
|
841
|
+
detail.resourceLabel = resourceLabel;
|
|
842
|
+
|
|
843
|
+
// POST returns the full resource; PUT/DELETE return {}.
|
|
844
|
+
// Only claim response contains the resource for POST to avoid misleading the model.
|
|
845
|
+
const resourceHint =
|
|
846
|
+
params.method === "POST"
|
|
847
|
+
? "The response above contains the complete resource. No verification GET is needed. Reference this resource by name in subsequent mutations immediately."
|
|
848
|
+
: "No verification GET is needed.";
|
|
849
|
+
|
|
850
|
+
return {
|
|
851
|
+
content: [
|
|
852
|
+
{
|
|
853
|
+
type: "text",
|
|
854
|
+
text: `${statusLine}\n\n${bodyText}\n\n${verb} ${resourceLabel} successfully. ${resourceHint}`,
|
|
855
|
+
},
|
|
856
|
+
],
|
|
857
|
+
details: detail,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
content: [{ type: "text", text: `${statusLine}\n\n${bodyText}` }],
|
|
863
|
+
details: detail,
|
|
864
|
+
isError: response.status >= 400 || undefined,
|
|
865
|
+
};
|
|
866
|
+
} catch (err) {
|
|
867
|
+
const durationMs = Math.round(performance.now() - startMs);
|
|
868
|
+
const contextName = this.#contextEnv.getContextName();
|
|
869
|
+
const detail = { status: 0, url, method: params.method, requestId, durationMs, contextName };
|
|
870
|
+
const ctxLabel = contextName ? ` (context: \`${contextName}\`)` : "";
|
|
871
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
872
|
+
// User abort vs 30s timeout produce different AbortErrors
|
|
873
|
+
const message = signal?.aborted
|
|
874
|
+
? "Request cancelled."
|
|
875
|
+
: `Request timed out after 30s${ctxLabel}. The API endpoint may be unreachable. Verify the API URL with \`/context show\`.`;
|
|
876
|
+
return this.#errorResult(message, detail);
|
|
877
|
+
}
|
|
878
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
879
|
+
if (/ENOTFOUND|ECONNREFUSED|EAI_AGAIN|dns/i.test(message))
|
|
880
|
+
return this.#errorResult(
|
|
881
|
+
`Network error${ctxLabel}: ${message}. The API URL may be incorrect. Check with \`/context show\`.`,
|
|
882
|
+
detail,
|
|
883
|
+
);
|
|
884
|
+
return this.#errorResult(`Request failed${ctxLabel}: ${message}`, detail);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|