@herbertgao/pi-extensions 2026.9.1 → 2026.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
- package/node_modules/@herbertgao/sol-pi/README.md +159 -0
- package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
- package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
- package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
- package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
- package/node_modules/@herbertgao/sol-pi/package.json +76 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
- package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
- package/node_modules/@narumitw/pi-btw/README.md +57 -141
- package/node_modules/@narumitw/pi-btw/dist/index.ts +478 -43
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
- package/node_modules/@narumitw/pi-btw/package.json +2 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -3
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
- package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
- package/node_modules/pi-lens/CHANGELOG.md +260 -0
- package/node_modules/pi-lens/README.md +13 -8
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
- package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -8
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -1
- package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -6
- package/node_modules/pi-lens/dist/clients/bootstrap.js +4 -4
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -5
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +1 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -6
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +2 -2
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
- package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
- package/node_modules/pi-lens/dist/clients/config-core/index.js +11 -8
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +1 -1
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +2 -9
- package/node_modules/pi-lens/dist/clients/config-core/records.js +1 -1
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +3 -5
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +3 -1
- package/node_modules/pi-lens/dist/clients/config-resolve.js +10 -7
- package/node_modules/pi-lens/dist/clients/config-schema.js +56 -2
- package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -4
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -41
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +7 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +15 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +3 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +5 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +1 -64
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +5 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
- package/node_modules/pi-lens/dist/clients/file-time.js +1 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +5 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
- package/node_modules/pi-lens/dist/clients/format-service.js +0 -1
- package/node_modules/pi-lens/dist/clients/formatters.js +66 -93
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +3 -4
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +5 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
- package/node_modules/pi-lens/dist/clients/installer/index.js +416 -61
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +6 -6
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +12 -11
- package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +7 -7
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +23 -0
- package/node_modules/pi-lens/dist/clients/lens-engine.js +3 -4
- package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +18 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -36
- package/node_modules/pi-lens/dist/clients/lsp/client.js +4 -3
- package/node_modules/pi-lens/dist/clients/lsp/config.js +37 -7
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +79 -1
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +3 -3
- package/node_modules/pi-lens/dist/clients/lsp/index.js +491 -64
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -8
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -2
- package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +5 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
- package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +1 -1
- package/node_modules/pi-lens/dist/clients/module-report.js +1 -1
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +3 -3
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +3 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -4
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +9 -9
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +2 -6
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +23 -2
- package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +9 -4
- package/node_modules/pi-lens/dist/clients/pipeline.js +5 -4
- package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +49 -1
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
- package/node_modules/pi-lens/dist/clients/python-provenance.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +3 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +5 -2
- package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +3 -5
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +10 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +58 -14
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +11 -1
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
- package/node_modules/pi-lens/dist/clients/sanitize.js +0 -11
- package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
- package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
- package/node_modules/pi-lens/dist/clients/sgconfig.js +1 -5
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
- package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +77 -72
- package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +1 -52
- package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
- package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
- package/node_modules/pi-lens/dist/clients/user-notify.js +0 -4
- package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -7
- package/node_modules/pi-lens/dist/clients/word-index-store.js +3 -7
- package/node_modules/pi-lens/dist/clients/word-index.js +9 -16
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +1 -1
- package/node_modules/pi-lens/dist/index.js +19969 -17481
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
- package/node_modules/pi-lens/dist/mcp/server.js +135 -159
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +1 -1
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +1 -1
- package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
- package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +354 -86
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +54 -112
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
- package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
- package/node_modules/pi-lens/dist/tools/render-compact.js +281 -2
- package/node_modules/pi-lens/dist/tools/symbol-search.js +3 -4
- package/node_modules/pi-lens/docs/agent-guide.md +3 -4
- package/node_modules/pi-lens/docs/agent-tools.md +46 -22
- package/node_modules/pi-lens/docs/configuration.md +9 -0
- package/node_modules/pi-lens/docs/environment-variables.md +0 -5
- package/node_modules/pi-lens/docs/features.md +5 -5
- package/node_modules/pi-lens/docs/globalconfig.md +31 -1
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
- package/node_modules/pi-lens/docs/mcp.md +13 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +41 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +21 -2
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/real-harness.md +46 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +17 -5
- package/node_modules/pi-lens/docs/servercapabilities.md +2 -3
- package/node_modules/pi-lens/docs/settings.md +37 -2
- package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +13 -3
- package/node_modules/pi-lens/docs/word-index.md +1 -1
- package/node_modules/pi-lens/package.json +15 -15
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
- package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
- package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +1 -1
- package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
- package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
- package/node_modules/pi-web-access/CHANGELOG.md +29 -0
- package/node_modules/pi-web-access/README.md +33 -22
- package/node_modules/pi-web-access/abortable.ts +17 -0
- package/node_modules/pi-web-access/crawl4ai.ts +204 -0
- package/node_modules/pi-web-access/credential-source.ts +1 -0
- package/node_modules/pi-web-access/curator-page.ts +20 -2
- package/node_modules/pi-web-access/curator-run.ts +44 -0
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/duckduckgo.ts +1 -1
- package/node_modules/pi-web-access/extract.ts +57 -12
- package/node_modules/pi-web-access/fetch-params.ts +1 -1
- package/node_modules/pi-web-access/gemini-search.ts +9 -5
- package/node_modules/pi-web-access/index.ts +25 -18
- package/node_modules/pi-web-access/openai-search.ts +35 -13
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +5 -2
- package/node_modules/pi-web-access/query-rewrite.ts +5 -2
- package/node_modules/pi-web-access/serpapi.ts +220 -0
- package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
- package/node_modules/pi-web-access/storage.ts +10 -0
- package/node_modules/pi-web-access/summary-review.ts +34 -21
- package/node_modules/pi-web-access/utils.ts +6 -4
- package/package.json +7 -4
- package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
|
@@ -41,6 +41,8 @@ interface WebSearchConfig {
|
|
|
41
41
|
|
|
42
42
|
type ProviderHeaders = Record<string, string | null>;
|
|
43
43
|
|
|
44
|
+
class CustomOpenAIBaseUrlError extends Error {}
|
|
45
|
+
|
|
44
46
|
interface OpenAIAuth {
|
|
45
47
|
provider: string;
|
|
46
48
|
apiKey: string;
|
|
@@ -215,7 +217,7 @@ function toRequestHeaders(headers: ProviderHeaders): Record<string, string> {
|
|
|
215
217
|
return requestHeaders;
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, providers: readonly string[], modelOverride?: string): Promise<OpenAIAuth | undefined> {
|
|
220
|
+
async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, providers: readonly string[], modelOverride?: string, hasExplicitResponsesUrl = false): Promise<OpenAIAuth | undefined> {
|
|
219
221
|
let models: ReturnType<typeof ctx.modelRegistry.getAll>;
|
|
220
222
|
try {
|
|
221
223
|
models = ctx.modelRegistry.getAll();
|
|
@@ -225,19 +227,34 @@ async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, provid
|
|
|
225
227
|
for (const provider of providers) {
|
|
226
228
|
const preferred = pickSearchModel(models.filter((model) => model.provider === provider));
|
|
227
229
|
if (!preferred) continue;
|
|
230
|
+
let resolved: Awaited<ReturnType<typeof ctx.modelRegistry.getApiKeyAndHeaders>>;
|
|
228
231
|
try {
|
|
229
|
-
|
|
230
|
-
if (resolved.ok && resolved.apiKey) {
|
|
231
|
-
return {
|
|
232
|
-
provider,
|
|
233
|
-
apiKey: resolved.apiKey,
|
|
234
|
-
model: modelOverride ?? preferred.id,
|
|
235
|
-
headers: resolved.headers ?? {},
|
|
236
|
-
responsesUrl,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
232
|
+
resolved = await ctx.modelRegistry.getApiKeyAndHeaders(preferred);
|
|
239
233
|
} catch {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!resolved.ok || !resolved.apiKey) continue;
|
|
237
|
+
// Auth can override the model's base URL. Do not guess Responses/web_search
|
|
238
|
+
// support from a gateway URL, or pair its credential with the official API.
|
|
239
|
+
const baseUrl = resolved.baseUrl ?? preferred.baseUrl;
|
|
240
|
+
const useCodexEndpoint = provider === "openai-codex" || isCodexJwt(resolved.apiKey);
|
|
241
|
+
if (!hasExplicitResponsesUrl && !useCodexEndpoint && baseUrl !== undefined) {
|
|
242
|
+
let isOfficial = false;
|
|
243
|
+
try {
|
|
244
|
+
isOfficial = new URL(baseUrl).toString().replace(/\/+$/u, "") === "https://api.openai.com/v1";
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
if (!isOfficial) {
|
|
248
|
+
throw new CustomOpenAIBaseUrlError(`OpenAI web search cannot reuse Pi credentials with a custom baseUrl by default. Set openaiResponsesUrl in ${CONFIG_PATH} to the full Responses endpoint for this credential.`);
|
|
249
|
+
}
|
|
240
250
|
}
|
|
251
|
+
return {
|
|
252
|
+
provider,
|
|
253
|
+
apiKey: resolved.apiKey,
|
|
254
|
+
model: modelOverride ?? preferred.id,
|
|
255
|
+
headers: resolved.headers ?? {},
|
|
256
|
+
responsesUrl,
|
|
257
|
+
};
|
|
241
258
|
}
|
|
242
259
|
return undefined;
|
|
243
260
|
}
|
|
@@ -248,7 +265,7 @@ export async function resolveOpenAIAuth(ctx?: ExtensionContext, signal?: AbortSi
|
|
|
248
265
|
const modelOverride = resolveConfiguredSearchModel(config.openaiSearchModel);
|
|
249
266
|
const providers = resolveConfiguredSearchProviders(config.openaiSearchProviders);
|
|
250
267
|
if (ctx) {
|
|
251
|
-
const auth = await resolvePiAuth(ctx, responsesUrl, providers, modelOverride);
|
|
268
|
+
const auth = await resolvePiAuth(ctx, responsesUrl, providers, modelOverride, config.openaiResponsesUrl !== undefined);
|
|
252
269
|
if (auth) return auth;
|
|
253
270
|
}
|
|
254
271
|
|
|
@@ -273,7 +290,12 @@ export async function isOpenAISearchAvailable(ctx?: ExtensionContext): Promise<b
|
|
|
273
290
|
const config = loadConfig();
|
|
274
291
|
const responsesUrl = resolveConfiguredResponsesUrl(config.openaiResponsesUrl);
|
|
275
292
|
const providers = resolveConfiguredSearchProviders(config.openaiSearchProviders);
|
|
276
|
-
|
|
293
|
+
try {
|
|
294
|
+
if (ctx && await resolvePiAuth(ctx, responsesUrl, providers, undefined, config.openaiResponsesUrl !== undefined)) return true;
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (err instanceof CustomOpenAIBaseUrlError) return false;
|
|
297
|
+
throw err;
|
|
298
|
+
}
|
|
277
299
|
return hasCredentialSource({
|
|
278
300
|
provider: "OpenAI",
|
|
279
301
|
configuredValue: config.openaiApiKey,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-access",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agent. Supports OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Ollama, AnySearch, Bright Data SERP, SerpBase, SearXNG, Exa, Perplexity, Gemini, Kimi, and Mistral.",
|
|
3
|
+
"version": "0.29.0",
|
|
4
|
+
"description": "Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agent. Supports OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Ollama, AnySearch, Bright Data SERP, SerpBase, SerpApi, SearXNG, Exa, Perplexity, Gemini, Kimi, and Mistral.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "node --test",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { complete,
|
|
1
|
+
import type { complete, Api, Message, Model } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
|
|
5
5
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
|
+
import { awaitWithAbort } from "./abortable.ts";
|
|
6
7
|
|
|
7
8
|
const OUTPUT_TOKENS = 2_000;
|
|
8
9
|
const INPUT_CONTEXT_FRACTION = 0.6;
|
|
@@ -111,7 +112,9 @@ export async function answerFromPage(
|
|
|
111
112
|
if (!auth.ok || !auth.apiKey) throw new Error(`No API key available for answer model ${model.provider}/${model.id}`);
|
|
112
113
|
const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete };
|
|
113
114
|
const usesRegistryComplete = typeof registry.complete === "function";
|
|
114
|
-
|
|
115
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
116
|
+
const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : (await awaitWithAbort(import("@earendil-works/pi-ai/compat"), signal)).complete;
|
|
117
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
115
118
|
|
|
116
119
|
const contextTokens = model.contextWindow > 0 ? model.contextWindow : FALLBACK_CONTEXT_TOKENS;
|
|
117
120
|
const maximumInputTokens = Math.max(1, Math.min(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { complete,
|
|
1
|
+
import type { complete, Api, Model, ProviderHeaders } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
import type { SummaryGenerationContext } from "./summary-review.ts";
|
|
3
|
+
import { awaitWithAbort } from "./abortable.ts";
|
|
3
4
|
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
|
|
4
5
|
|
|
5
6
|
async function resolveFirstAvailableModel(
|
|
@@ -28,7 +29,9 @@ export async function rewriteSearchQuery(
|
|
|
28
29
|
]);
|
|
29
30
|
const registry = ctx.modelRegistry as typeof ctx.modelRegistry & { complete?: typeof complete };
|
|
30
31
|
const usesRegistryComplete = typeof registry.complete === "function";
|
|
31
|
-
|
|
32
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
33
|
+
const completeFn = usesRegistryComplete ? registry.complete!.bind(registry) : (await awaitWithAbort(import("@earendil-works/pi-ai/compat"), signal)).complete;
|
|
34
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
32
35
|
const response = await completeFn(
|
|
33
36
|
model,
|
|
34
37
|
{
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
4
|
+
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
|
+
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
|
+
|
|
7
|
+
const SERPAPI_SEARCH_URL = "https://serpapi.com/search.json";
|
|
8
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
9
|
+
const SEARCH_TIMEOUT_MS = 60_000;
|
|
10
|
+
const RECENCY_TBS: Record<NonNullable<SearchOptions["recencyFilter"]>, string> = {
|
|
11
|
+
day: "qdr:d",
|
|
12
|
+
week: "qdr:w",
|
|
13
|
+
month: "qdr:m",
|
|
14
|
+
year: "qdr:y",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface WebSearchConfig {
|
|
18
|
+
serpapiApiKey?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SerpApiOrganicResult {
|
|
22
|
+
title?: unknown;
|
|
23
|
+
link?: unknown;
|
|
24
|
+
snippet?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let cachedConfig: WebSearchConfig | null = null;
|
|
28
|
+
|
|
29
|
+
function loadConfig(): WebSearchConfig {
|
|
30
|
+
if (cachedConfig) return cachedConfig;
|
|
31
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
32
|
+
cachedConfig = {};
|
|
33
|
+
return cachedConfig;
|
|
34
|
+
}
|
|
35
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
36
|
+
let parsed: unknown;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(raw);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
41
|
+
throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
|
|
42
|
+
}
|
|
43
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
44
|
+
throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
|
|
45
|
+
}
|
|
46
|
+
cachedConfig = parsed as WebSearchConfig;
|
|
47
|
+
return cachedConfig;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function getApiKey(signal?: AbortSignal): Promise<string | null> {
|
|
51
|
+
return resolveCredential({
|
|
52
|
+
provider: "SerpApi",
|
|
53
|
+
configuredValue: loadConfig().serpapiApiKey,
|
|
54
|
+
environmentValue: process.env.SERPAPI_KEY,
|
|
55
|
+
signal,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function requireApiKey(signal?: AbortSignal): Promise<string> {
|
|
60
|
+
const apiKey = await getApiKey(signal);
|
|
61
|
+
if (!apiKey) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"SerpApi API key not found. Either:\n" +
|
|
64
|
+
` 1. Create ${CONFIG_PATH} with { "serpapiApiKey": "your-key" }\n` +
|
|
65
|
+
" 2. Set SERPAPI_KEY environment variable\n" +
|
|
66
|
+
"Get a key at https://serpapi.com/manage-api-key",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return apiKey;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeCount(value: number | undefined): number {
|
|
73
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
74
|
+
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface DomainFilters {
|
|
78
|
+
include: string[];
|
|
79
|
+
exclude: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeDomain(value: string): string | null {
|
|
83
|
+
let input = value.trim().toLowerCase();
|
|
84
|
+
if (!input) return null;
|
|
85
|
+
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
86
|
+
if (!input) return null;
|
|
87
|
+
try {
|
|
88
|
+
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
89
|
+
input = parsed.hostname;
|
|
90
|
+
} catch {
|
|
91
|
+
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
92
|
+
}
|
|
93
|
+
input = input.replace(/^\.+|\.+$/g, "");
|
|
94
|
+
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
98
|
+
const filters: DomainFilters = { include: [], exclude: [] };
|
|
99
|
+
for (const raw of domainFilter ?? []) {
|
|
100
|
+
const domain = normalizeDomain(raw);
|
|
101
|
+
if (!domain) continue;
|
|
102
|
+
const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
|
|
103
|
+
if (!target.includes(domain)) target.push(domain);
|
|
104
|
+
}
|
|
105
|
+
return filters;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function passesDomainFilters(url: string, filters: DomainFilters): boolean {
|
|
109
|
+
if (filters.include.length === 0 && filters.exclude.length === 0) return true;
|
|
110
|
+
let hostname: string;
|
|
111
|
+
try {
|
|
112
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
|
|
117
|
+
if (filters.exclude.some(matches)) return false;
|
|
118
|
+
return filters.include.length === 0 || filters.include.some(matches);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildQuery(query: string, filters: DomainFilters): string {
|
|
122
|
+
const parts = [query];
|
|
123
|
+
if (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);
|
|
124
|
+
if (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(" OR ")})`);
|
|
125
|
+
for (const domain of filters.exclude) parts.push(`-site:${domain}`);
|
|
126
|
+
return parts.join(" ");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function errorMessage(err: unknown): string {
|
|
130
|
+
return err instanceof Error ? err.message : String(err);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function invalidResponse(message: string): Error {
|
|
134
|
+
return new Error(`SerpApi returned invalid response: ${message}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parseResponse(value: unknown): SerpApiOrganicResult[] {
|
|
138
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("expected an object envelope");
|
|
139
|
+
const envelope = value as Record<string, unknown>;
|
|
140
|
+
if (typeof envelope.error === "string" && envelope.error.trim()) throw invalidResponse(envelope.error.trim());
|
|
141
|
+
if (!Array.isArray(envelope.organic_results)) throw invalidResponse("expected organic_results array");
|
|
142
|
+
return envelope.organic_results as SerpApiOrganicResult[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildAnswer(results: SearchResponse["results"]): string {
|
|
146
|
+
return results.map((result) => result.snippet
|
|
147
|
+
? `${result.snippet}\nSource: ${result.title} (${result.url})`
|
|
148
|
+
: `Source: ${result.title} (${result.url})`).join("\n\n");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function isSerpApiAvailable(): boolean {
|
|
152
|
+
return hasCredentialSource({ provider: "SerpApi", configuredValue: loadConfig().serpapiApiKey, environmentValue: process.env.SERPAPI_KEY });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function searchWithSerpApi(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
156
|
+
const apiKey = await requireApiKey(options.signal);
|
|
157
|
+
const numResults = normalizeCount(options.numResults);
|
|
158
|
+
const filters = parseDomainFilter(options.domainFilter);
|
|
159
|
+
const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
|
|
160
|
+
const url = new URL(SERPAPI_SEARCH_URL);
|
|
161
|
+
url.searchParams.set("engine", "google");
|
|
162
|
+
url.searchParams.set("q", buildQuery(query, filters));
|
|
163
|
+
url.searchParams.set("api_key", apiKey);
|
|
164
|
+
url.searchParams.set("num", String(requestCount));
|
|
165
|
+
if (options.recencyFilter) url.searchParams.set("tbs", RECENCY_TBS[options.recencyFilter]);
|
|
166
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
167
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
168
|
+
let response: Response;
|
|
169
|
+
let entries: SerpApiOrganicResult[];
|
|
170
|
+
try {
|
|
171
|
+
response = await fetch(url, {
|
|
172
|
+
headers: { Accept: "application/json" },
|
|
173
|
+
signal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
const errorText = await response.text();
|
|
177
|
+
throw new Error(`SerpApi error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);
|
|
178
|
+
}
|
|
179
|
+
let rawData: unknown;
|
|
180
|
+
try {
|
|
181
|
+
rawData = await response.json();
|
|
182
|
+
} catch (err) {
|
|
183
|
+
if (err instanceof Error && err.name === "TimeoutError") throw err;
|
|
184
|
+
throw new Error(`SerpApi returned invalid JSON: ${errorMessage(err)}`);
|
|
185
|
+
}
|
|
186
|
+
entries = parseResponse(rawData);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (options.signal?.aborted) {
|
|
189
|
+
activityMonitor.logComplete(activityId, 0);
|
|
190
|
+
throw new Error("Aborted");
|
|
191
|
+
}
|
|
192
|
+
const message = errorMessage(err);
|
|
193
|
+
const providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === "TimeoutError");
|
|
194
|
+
const outgoing = providerTimeout
|
|
195
|
+
? new Error(`SerpApi request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)
|
|
196
|
+
: (() => {
|
|
197
|
+
const redactedMessage = redactCredential(message, apiKey);
|
|
198
|
+
if (redactedMessage === message && err instanceof Error) return err;
|
|
199
|
+
const redactedError = new Error(redactedMessage);
|
|
200
|
+
if (err instanceof Error) redactedError.name = err.name;
|
|
201
|
+
return redactedError;
|
|
202
|
+
})();
|
|
203
|
+
activityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));
|
|
204
|
+
throw outgoing;
|
|
205
|
+
}
|
|
206
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
207
|
+
const results: SearchResponse["results"] = [];
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (!entry || typeof entry !== "object") continue;
|
|
210
|
+
if (typeof entry.link !== "string" || !entry.link) continue;
|
|
211
|
+
if (!passesDomainFilters(entry.link, filters)) continue;
|
|
212
|
+
results.push({
|
|
213
|
+
title: typeof entry.title === "string" && entry.title.trim() ? entry.title.trim() : `Source ${results.length + 1}`,
|
|
214
|
+
url: entry.link,
|
|
215
|
+
snippet: typeof entry.snippet === "string" ? entry.snippet : "",
|
|
216
|
+
});
|
|
217
|
+
if (results.length >= numResults) break;
|
|
218
|
+
}
|
|
219
|
+
return { answer: buildAnswer(results), results };
|
|
220
|
+
}
|
|
@@ -232,6 +232,7 @@ export async function fetchRemoteUrl(
|
|
|
232
232
|
const fetchImpl = options.fetch ?? fetch;
|
|
233
233
|
const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
234
234
|
let current = await validateRemoteUrl(url, options);
|
|
235
|
+
const configuredOrigin = current.origin;
|
|
235
236
|
let requestInit = init;
|
|
236
237
|
|
|
237
238
|
for (let redirects = 0; redirects <= maxRedirects; redirects++) {
|
|
@@ -243,7 +244,10 @@ export async function fetchRemoteUrl(
|
|
|
243
244
|
if (redirects === maxRedirects) throw new Error(`Too many redirects fetching ${current.toString()}`);
|
|
244
245
|
|
|
245
246
|
const from = current;
|
|
246
|
-
|
|
247
|
+
const next = new URL(location, current);
|
|
248
|
+
// allowLoopback exempts an explicitly configured endpoint, never a redirect target: a loopback
|
|
249
|
+
// base must not be able to pivot the request onto a different loopback origin.
|
|
250
|
+
current = await validateRemoteUrl(next, next.origin === configuredOrigin ? options : { ...options, allowLoopback: false });
|
|
247
251
|
if (response.status === 303 || ((response.status === 301 || response.status === 302) && requestInit.method?.toUpperCase() === "POST")) {
|
|
248
252
|
const { body: _body, ...nextInit } = requestInit;
|
|
249
253
|
requestInit = { ...nextInit, method: "GET" };
|
|
@@ -426,8 +426,17 @@ function readCachedFetchData(data: StoredSearchData, now = Date.now()): StoredSe
|
|
|
426
426
|
}
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
+
function pruneExpiredFetchedResults(now: number): void {
|
|
430
|
+
for (const [id, data] of storedResults) {
|
|
431
|
+
if (data.type === "fetch" && now - data.timestamp >= CACHE_TTL_MS) {
|
|
432
|
+
storedResults.set(id, unavailableFetchData(data, "Cached fetched content is missing or expired"));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
429
437
|
export function pruneExpiredFetchCache(now = Date.now(), requestedLimits?: Partial<FetchCacheLimits>): void {
|
|
430
438
|
const limits = cacheLimits(requestedLimits);
|
|
439
|
+
pruneExpiredFetchedResults(now);
|
|
431
440
|
try { pruneFetchCache(now, limits); } catch {}
|
|
432
441
|
}
|
|
433
442
|
|
|
@@ -436,6 +445,7 @@ export function storeResult(id: string, data: StoredSearchData): void {
|
|
|
436
445
|
}
|
|
437
446
|
|
|
438
447
|
export function storeFetchedContentResult(id: string, data: StoredSearchData & { type: "fetch"; urls: ExtractedContent[] }): StoredSearchData {
|
|
448
|
+
pruneExpiredFetchedResults(Date.now());
|
|
439
449
|
let ref: FetchCacheRef | null = null;
|
|
440
450
|
let cacheError: string | undefined;
|
|
441
451
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { complete,
|
|
1
|
+
import type { ModelThinkingLevel, ThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { complete, Api, Message, Model } from "@earendil-works/pi-ai/compat";
|
|
3
3
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix, type SummaryThinkingLevel } from "./summary-model-scope.ts";
|
|
5
5
|
import type { QueryResultData } from "./storage.ts";
|
|
@@ -204,8 +204,9 @@ function parseModelSelector(value: string): { provider: string; id: string; thin
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
function resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): ModelThinkingLevel | undefined {
|
|
207
|
+
async function resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): Promise<ModelThinkingLevel | undefined> {
|
|
208
208
|
if (!requested) return undefined;
|
|
209
|
+
const { clampThinkingLevel } = await import("@earendil-works/pi-ai");
|
|
209
210
|
return clampThinkingLevel(model, requested as ModelThinkingLevel);
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -295,22 +296,21 @@ export async function generateSummaryDraft(
|
|
|
295
296
|
if (!ctx || !ctx.modelRegistry) {
|
|
296
297
|
throw new Error("Summary generation context unavailable");
|
|
297
298
|
}
|
|
299
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
298
300
|
|
|
299
301
|
const registry = ctx.modelRegistry as SummaryModelRegistry;
|
|
300
302
|
const customCompleteFn = completeFn !== undefined;
|
|
301
303
|
const usesRegistryComplete = !customCompleteFn && typeof registry.complete === "function";
|
|
302
|
-
|
|
304
|
+
let piAiCompat: typeof import("@earendil-works/pi-ai/compat") | undefined;
|
|
303
305
|
|
|
304
306
|
const generationStartedAt = Date.now();
|
|
305
307
|
const deadlineController = new AbortController();
|
|
306
308
|
const deadlineMarker = Symbol("summary-generation-deadline");
|
|
307
309
|
let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
|
|
308
|
-
let resolveDeadline!: () => void;
|
|
309
310
|
const deadlinePromise = new Promise<typeof deadlineMarker>(resolve => {
|
|
310
|
-
resolveDeadline = () => resolve(deadlineMarker);
|
|
311
311
|
deadlineTimer = setTimeout(() => {
|
|
312
312
|
deadlineController.abort();
|
|
313
|
-
|
|
313
|
+
resolve(deadlineMarker);
|
|
314
314
|
}, deadlineMs);
|
|
315
315
|
});
|
|
316
316
|
|
|
@@ -326,17 +326,24 @@ export async function generateSummaryDraft(
|
|
|
326
326
|
? AbortSignal.any([signal, deadlineController.signal])
|
|
327
327
|
: deadlineController.signal;
|
|
328
328
|
|
|
329
|
+
function checkSummaryDeadline(): void {
|
|
330
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
331
|
+
// Imports or synchronous providers can finish before an overdue timer runs.
|
|
332
|
+
if (deadlineController.signal.aborted || Date.now() - generationStartedAt >= deadlineMs) {
|
|
333
|
+
deadlineController.abort();
|
|
334
|
+
throw deadlineMarker;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
329
338
|
async function raceSummaryOperation<T>(operation: Promise<T>): Promise<T> {
|
|
330
339
|
// A provider may ignore AbortSignal and never settle; observe it before racing so
|
|
331
340
|
// its eventual rejection cannot become an unhandled promise rejection.
|
|
332
341
|
void operation.then(() => undefined, () => undefined);
|
|
342
|
+
checkSummaryDeadline();
|
|
333
343
|
const contenders: Promise<unknown>[] = [operation, deadlinePromise];
|
|
334
344
|
if (callerAbortPromise) contenders.push(callerAbortPromise);
|
|
335
345
|
const result = await Promise.race(contenders);
|
|
336
|
-
|
|
337
|
-
if (signal?.aborted) throw new Error("Aborted");
|
|
338
|
-
throw deadlineMarker;
|
|
339
|
-
}
|
|
346
|
+
checkSummaryDeadline();
|
|
340
347
|
return result as T;
|
|
341
348
|
}
|
|
342
349
|
|
|
@@ -345,12 +352,14 @@ export async function generateSummaryDraft(
|
|
|
345
352
|
const prompt = buildSummaryPrompt(results, feedback);
|
|
346
353
|
let resolved: Awaited<ReturnType<typeof resolveSummaryModelCandidates>>;
|
|
347
354
|
try {
|
|
355
|
+
checkSummaryDeadline();
|
|
356
|
+
if (!customCompleteFn && !usesRegistryComplete) {
|
|
357
|
+
piAiCompat = await raceSummaryOperation(import("@earendil-works/pi-ai/compat"));
|
|
358
|
+
}
|
|
359
|
+
completeFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : piAiCompat!.complete;
|
|
348
360
|
resolved = await raceSummaryOperation(resolveSummaryModelCandidates(ctx, modelOverride));
|
|
349
361
|
} catch (err) {
|
|
350
|
-
|
|
351
|
-
if (err === deadlineMarker || deadlineController.signal.aborted) {
|
|
352
|
-
return buildFallbackSummary(results, "summary-generation-timeout", Date.now() - generationStartedAt);
|
|
353
|
-
}
|
|
362
|
+
checkSummaryDeadline();
|
|
354
363
|
const message = err instanceof Error ? err.message : String(err);
|
|
355
364
|
return buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);
|
|
356
365
|
}
|
|
@@ -364,7 +373,7 @@ export async function generateSummaryDraft(
|
|
|
364
373
|
content: [{ type: "text", text: prompt }],
|
|
365
374
|
timestamp: Date.now(),
|
|
366
375
|
};
|
|
367
|
-
const requestedThinkingLevel = resolveThinkingLevel(model, thinkingLevel);
|
|
376
|
+
const requestedThinkingLevel = await raceSummaryOperation(resolveThinkingLevel(model, thinkingLevel));
|
|
368
377
|
const enabledThinkingLevel = requestedThinkingLevel && requestedThinkingLevel !== "off"
|
|
369
378
|
? requestedThinkingLevel as ThinkingLevel
|
|
370
379
|
: undefined;
|
|
@@ -374,8 +383,9 @@ export async function generateSummaryDraft(
|
|
|
374
383
|
...(requestedThinkingLevel ? { reasoning: requestedThinkingLevel } : {}),
|
|
375
384
|
...(enabledThinkingLevel ? { reasoningEffort: enabledThinkingLevel } : {}),
|
|
376
385
|
};
|
|
386
|
+
checkSummaryDeadline();
|
|
377
387
|
const completion = thinkingLevel !== undefined && !customCompleteFn && !usesRegistryComplete
|
|
378
|
-
? completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })
|
|
388
|
+
? piAiCompat!.completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })
|
|
379
389
|
: completeFn(model, { messages: [userMessage] }, completionOptions);
|
|
380
390
|
|
|
381
391
|
const response = await raceSummaryOperation(Promise.resolve(completion));
|
|
@@ -408,10 +418,7 @@ export async function generateSummaryDraft(
|
|
|
408
418
|
},
|
|
409
419
|
};
|
|
410
420
|
} catch (err) {
|
|
411
|
-
|
|
412
|
-
if (err === deadlineMarker || deadlineController.signal.aborted) {
|
|
413
|
-
return buildFallbackSummary(results, "summary-generation-timeout", Date.now() - generationStartedAt);
|
|
414
|
-
}
|
|
421
|
+
checkSummaryDeadline();
|
|
415
422
|
if (isAbortError(err)) throw err;
|
|
416
423
|
lastError = err instanceof Error ? err.message : String(err);
|
|
417
424
|
}
|
|
@@ -422,6 +429,12 @@ export async function generateSummaryDraft(
|
|
|
422
429
|
lastError ? `summary-model-unavailable: ${lastError}` : "summary-model-unavailable",
|
|
423
430
|
Date.now() - generationStartedAt,
|
|
424
431
|
);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
434
|
+
if (err === deadlineMarker) {
|
|
435
|
+
return buildFallbackSummary(results, "summary-generation-timeout", Date.now() - generationStartedAt);
|
|
436
|
+
}
|
|
437
|
+
throw err;
|
|
425
438
|
} finally {
|
|
426
439
|
if (deadlineTimer) clearTimeout(deadlineTimer);
|
|
427
440
|
if (signal && callerAbortListener) signal.removeEventListener("abort", callerAbortListener);
|
|
@@ -22,7 +22,7 @@ export function getWebSearchConfigDir(): string {
|
|
|
22
22
|
if (existsSync(join(legacyDir, "web-search.json"))) return cachedWebSearchConfigDir = legacyDir;
|
|
23
23
|
return cachedWebSearchConfigDir = xdgDir;
|
|
24
24
|
}
|
|
25
|
-
return cachedWebSearchConfigDir = join(homedir(), ".pi");
|
|
25
|
+
return cachedWebSearchConfigDir = join(homedir(), ".pi", "agent");
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function getWebSearchConfigPath(): string {
|
|
@@ -208,7 +208,7 @@ const proxyStorage = new AsyncLocalStorage<string | null>();
|
|
|
208
208
|
|
|
209
209
|
export function normalizeProxyUrl(value: unknown, source: string): string | null {
|
|
210
210
|
if (value === undefined || value === null) return null;
|
|
211
|
-
if (typeof value !== "string") throw new Error(`${source} must be an http(s) proxy URL string`);
|
|
211
|
+
if (typeof value !== "string") throw new Error(`${source} must be an http(s) or socks proxy URL string`);
|
|
212
212
|
const trimmed = value.trim();
|
|
213
213
|
if (!trimmed) return null;
|
|
214
214
|
let parsed: URL;
|
|
@@ -217,8 +217,10 @@ export function normalizeProxyUrl(value: unknown, source: string): string | null
|
|
|
217
217
|
} catch {
|
|
218
218
|
throw new Error(`${source} must be a valid proxy URL: ${JSON.stringify(trimmed)}`);
|
|
219
219
|
}
|
|
220
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:"
|
|
221
|
-
|
|
220
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" &&
|
|
221
|
+
parsed.protocol !== "socks4:" && parsed.protocol !== "socks4a:" &&
|
|
222
|
+
parsed.protocol !== "socks5:" && parsed.protocol !== "socks5h:") {
|
|
223
|
+
throw new Error(`${source} must use the http://, https://, or socks scheme: ${trimmed}`);
|
|
222
224
|
}
|
|
223
225
|
if (!parsed.hostname) throw new Error(`${source} must include a proxy host: ${trimmed}`);
|
|
224
226
|
parsed.hash = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herbertgao/pi-extensions",
|
|
3
|
-
"version": "2026.9.
|
|
3
|
+
"version": "2026.9.3",
|
|
4
4
|
"description": "Aggregate installer for HerbertGao-maintained Pi extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"extensions",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@herbertgao/pi-cc-extensions": "0.9.0",
|
|
48
48
|
"@herbertgao/pi-subagents": "0.17.1",
|
|
49
49
|
"@herbertgao/resume-from": "0.2.0",
|
|
50
|
+
"@herbertgao/sol-pi": "0.2.0",
|
|
50
51
|
"@juicesharp/rpiv-ask-user-question": "2.9.0",
|
|
51
52
|
"@juicesharp/rpiv-config": "^2.9.0",
|
|
52
53
|
"@luxusai/pi-hindsight": "0.12.0",
|
|
@@ -56,7 +57,7 @@
|
|
|
56
57
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
57
58
|
"@mozilla/readability": "^0.6.0",
|
|
58
59
|
"@napi-rs/keyring": "^1.3.0",
|
|
59
|
-
"@narumitw/pi-btw": "0.
|
|
60
|
+
"@narumitw/pi-btw": "0.58.1",
|
|
60
61
|
"@narumitw/pi-caffeinate": "0.49.7",
|
|
61
62
|
"@narumitw/pi-tui-kit": "^0.59.0",
|
|
62
63
|
"@noble/ed25519": "^3.1.0",
|
|
@@ -90,9 +91,9 @@
|
|
|
90
91
|
"open": "^10.2.0",
|
|
91
92
|
"p-limit": "^6.1.0",
|
|
92
93
|
"pi-footer": "0.5.1",
|
|
93
|
-
"pi-lens": "4.1.
|
|
94
|
+
"pi-lens": "4.1.6",
|
|
94
95
|
"pi-mcp-adapter": "2.32.1",
|
|
95
|
-
"pi-web-access": "0.
|
|
96
|
+
"pi-web-access": "0.29.0",
|
|
96
97
|
"pidusage": "^4.0.1",
|
|
97
98
|
"promise.try": "^2.0.1",
|
|
98
99
|
"qrcode-terminal": "^0.12.0",
|
|
@@ -119,6 +120,7 @@
|
|
|
119
120
|
"@herbertgao/pi-cc-extensions",
|
|
120
121
|
"@herbertgao/pi-subagents",
|
|
121
122
|
"@herbertgao/resume-from",
|
|
123
|
+
"@herbertgao/sol-pi",
|
|
122
124
|
"@juicesharp/rpiv-ask-user-question",
|
|
123
125
|
"@luxusai/pi-hindsight",
|
|
124
126
|
"@narumitw/pi-btw",
|
|
@@ -144,6 +146,7 @@
|
|
|
144
146
|
"extensions": [
|
|
145
147
|
"./node_modules/@dietrichgebert/ponytail/pi-extension/index.js",
|
|
146
148
|
"./node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts",
|
|
149
|
+
"./node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts",
|
|
147
150
|
"./node_modules/@tifan/pi-copy-response/src/index.ts",
|
|
148
151
|
"./node_modules/@herbertgao/resume-from/shims/pi/extensions/resume-from.js",
|
|
149
152
|
"./node_modules/@tifan/pi-handoff/src/index.ts",
|