@herbertgao/pi-extensions 2026.8.6 → 2026.8.7
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 +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
- package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +1 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -10
- package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +2 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
- package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
- package/node_modules/@narumitw/pi-btw/README.md +120 -111
- package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
- package/node_modules/@narumitw/pi-btw/package.json +10 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +909 -0
- package/node_modules/pi-lens/README.md +11 -3
- package/node_modules/pi-lens/config/ruff/core.toml +42 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
- package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
- package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
- package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
- package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
- package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
- package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
- package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
- package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
- package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
- package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +82 -6
- package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
- package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
- package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
- package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
- package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
- package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
- package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
- package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
- package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
- package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +67738 -55739
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/docs/agent-guide.md +6 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +19 -5
- package/node_modules/pi-lens/docs/globalconfig.md +3 -0
- package/node_modules/pi-lens/docs/language-coverage.md +71 -5
- package/node_modules/pi-lens/docs/mcp.md +2 -2
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/settings.md +19 -1
- package/node_modules/pi-lens/docs/usage.md +1 -0
- package/node_modules/pi-lens/package.json +10 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
- package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
- package/node_modules/pi-mcp-adapter/README.md +47 -5
- package/node_modules/pi-mcp-adapter/cli.js +163 -1
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +25 -7
- package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
- package/node_modules/pi-mcp-adapter/index.ts +219 -32
- package/node_modules/pi-mcp-adapter/init.ts +36 -4
- package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
- package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +3 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
- package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
- package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +2 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
- package/node_modules/pi-mcp-adapter/utils.ts +70 -0
- package/node_modules/pi-web-access/CHANGELOG.md +23 -0
- package/node_modules/pi-web-access/README.md +27 -9
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/curator-page.ts +6 -3
- package/node_modules/pi-web-access/curator-server.ts +4 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +28 -3
- package/node_modules/pi-web-access/gemini-search.ts +16 -4
- package/node_modules/pi-web-access/index.ts +41 -57
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +4 -2
- package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
- package/node_modules/pi-web-access/parallel.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +51 -0
- package/node_modules/pi-web-access/serper.ts +211 -0
- package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
- package/node_modules/pi-web-access/summary-review.ts +44 -22
- package/node_modules/pi-web-access/tavily.ts +16 -4
- package/node_modules/pi-web-access/utils.ts +87 -0
- package/node_modules/pi-web-access/valyu.ts +199 -0
- package/package.json +13 -13
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Pi Web Access
|
|
6
6
|
|
|
7
|
-
**Web search, content extraction, and video understanding for Pi agent. OpenAI/Codex search, zero-config Exa search, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, xAI/Grok, Bright Data SERP, SerpBase, self-hosted SearXNG, keyless DuckDuckGo, optional browser-cookie Gemini Web, or bring your own API keys.**
|
|
7
|
+
**Web search, content extraction, and video understanding for Pi agent. OpenAI/Codex search, zero-config Exa search, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, Valyu, xAI/Grok, Bright Data SERP, SerpBase, Serper, self-hosted SearXNG, keyless DuckDuckGo, optional browser-cookie Gemini Web, or bring your own API keys.**
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/pi-web-access)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
## Why Pi Web Access
|
|
16
16
|
|
|
17
|
-
**Zero Config** — Works out of the box with Exa MCP (no API key needed). If you're signed into Pi with a Codex subscription, OpenAI web search can reuse that auth. Add API keys or endpoints for OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, SerpBase, Exa, Perplexity, or Gemini API for more control; configure a self-hosted SearXNG endpoint for private search; or opt into browser-cookie access for Gemini Web.
|
|
17
|
+
**Zero Config** — Works out of the box with Exa MCP (no API key needed). If you're signed into Pi with a Codex subscription, OpenAI web search can reuse that auth. Add API keys or endpoints for OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Valyu, SerpBase, Serper, Exa, Perplexity, or Gemini API for more control; configure a self-hosted SearXNG endpoint for private search; or opt into browser-cookie access for Gemini Web.
|
|
18
18
|
|
|
19
19
|
**Video Understanding** — Point it at a YouTube video or local screen recording and ask questions about what's on screen. Full transcripts, visual descriptions, and frame extraction at exact timestamps.
|
|
20
20
|
|
|
@@ -46,7 +46,7 @@ Works immediately with no API keys — Exa MCP provides zero-config search. If P
|
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search, then OpenAI when suitable and available, Exa (direct API if keyed, MCP if not), Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Perplexity, Gemini API, and Gemini Web when browser-cookie access is enabled. With no SearXNG configured, the existing zero-config order is unchanged. Exa handles search; curator summary drafts are generated separately by the configured Pi summary model. Slow summary drafts fall back to a deterministic result summary after a bounded deadline.
|
|
49
|
+
In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search, then OpenAI when suitable and available, Exa (direct API if keyed, MCP if not), Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Perplexity, Gemini API, and Gemini Web when browser-cookie access is enabled. With no SearXNG configured, the existing zero-config order is unchanged. Exa handles search; curator summary drafts are generated separately by the configured Pi summary model, defaulting to Claude Haiku, Codex Luna, Codex Terra, Gemini 3.6 Flash, GPT-5 mini, then DeepSeek V4 Flash when available. Slow summary drafts fall back to a deterministic result summary after a bounded deadline.
|
|
50
50
|
|
|
51
51
|
If your OpenAI key belongs to a third-party Responses-compatible gateway, set `openaiResponsesUrl` to that gateway's full Responses endpoint. The default remains `https://api.openai.com/v1/responses`.
|
|
52
52
|
|
|
@@ -96,7 +96,7 @@ fetch_content({ url: "/path/to/recording.mp4", prompt: "What error appears on sc
|
|
|
96
96
|
|
|
97
97
|
### web_search
|
|
98
98
|
|
|
99
|
-
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, xAI, Bright Data SERP, SerpBase, self-hosted SearXNG, keyless DuckDuckGo, Exa, Perplexity AI, or Gemini. Returns a synthesized answer with source citations.
|
|
99
|
+
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, Valyu, xAI, Bright Data SERP, SerpBase, Serper, self-hosted SearXNG, keyless DuckDuckGo, Exa, Perplexity AI, or Gemini. Returns a synthesized answer with source citations.
|
|
100
100
|
|
|
101
101
|
```typescript
|
|
102
102
|
web_search({ query: "rust async programming" })
|
|
@@ -117,7 +117,7 @@ web_search({ queries: ["query 1", "query 2"], workflow: "auto-summary" })
|
|
|
117
117
|
| `numResults` | Results per query (default: 5, max: 20) |
|
|
118
118
|
| `recencyFilter` | `day`, `week`, `month`, or `year` |
|
|
119
119
|
| `domainFilter` | Limit to domains (prefix with `-` to exclude) |
|
|
120
|
-
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except DuckDuckGo, AnySearch, xAI, Bright Data, and
|
|
120
|
+
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except Parallel MCP, DuckDuckGo, AnySearch, Valyu, xAI, Bright Data, SerpBase, and Serper simultaneously; otherwise `openai`, `brave`, `parallel`, `parallel-mcp`, `tinyfish`, `search1api`, `searchinfinity`, `querit`, `tavily`, `firecrawl`, `jina`, `serpdive`, `kagi`, `bocha`, `ollama`, `anysearch`, `valyu`, `xai`, `brightdata`, `serpbase`, `serper`, `searxng`, `duckduckgo`, `exa`, `perplexity`, or `gemini` (auto-selects when no provider or routing is configured; Parallel MCP, DuckDuckGo, AnySearch, Valyu, xAI, Bright Data, SerpBase, and Serper are explicit-only) |
|
|
121
121
|
| `includeContent` | Fetch full page content from sources in background |
|
|
122
122
|
| `workflow` | `none` (skip curator), `summary-review` (open curator and auto-generate a summary draft, default), or `auto-summary` (generate a summary without opening the curator) |
|
|
123
123
|
|
|
@@ -250,7 +250,7 @@ Raw and direct-image HTTP requests use the same SSRF validation, hostname domain
|
|
|
250
250
|
|
|
251
251
|
`fetch_content` can opt into local browser-cookie auth with `auth: "profile"`, or `auth: true` when exactly one `authFetch` profile exists. Configure profiles in `~/.pi/web-search.json`, for example `{ "authFetch": { "social": ["x.com", "instagram.com"], "work": { "hosts": ["docs.company.com"], "chromeProfile": "Profile 2", "cache": "off" } } }`. Auth fetch uses only the local direct HTTP path, requires HTTPS, allows only configured hosts and their subdomains, refuses cross-origin redirects, and never sends cookies or authenticated content to hosted extraction providers. Browser cookie extraction remains opt-in through `allowBrowserCookies: true` or `PI_ALLOW_BROWSER_COOKIES=1`.
|
|
252
252
|
|
|
253
|
-
When Readability fails or returns only a cookie notice, the extension can retry configured Firecrawl extraction, Jina Reader (handles JS rendering server-side, no API key needed), TinyFish, Search1API, Querit, Kagi Extract, Ollama Web Fetch, Parallel, Bright Data Web Unlocker, Gemini URL Context API, and Gemini Web extraction when browser cookies are enabled. Configure `fetchRouting.providers` to change the order or set of `fetch_content` providers. Supported values are `http`, `firecrawl`, `jina`, `tinyfish`, `search1api`, `querit`, `kagi`, `ollama`, `parallel`, `brightdata`, and `gemini`; when absent, the default order is unchanged. For remote HTTP(S) targets, third-party hosted providers are disabled unless `fetchRouting.allowRemoteHostedProviders` is `true`, because hosted services perform their own fetch and can see a different redirect chain than the local safety gate. Firecrawl stays available as a configured extraction service. Firecrawl requests are cache-only by default and require an explicit fresh-scrape opt-in before the Firecrawl server can fetch target URLs. Bright Data Web Unlocker runs last of the remote scraping providers, ahead of only the Gemini fallbacks, because it is billed per request against a paid account; it is skipped unless both a key and an `unblocker` zone are configured. It applies no minimum-length check, so any non-empty body it returns — including a short consent or paywall stub — is the final answer for that URL and the Gemini fallbacks are not tried. Handles SPAs, JS-heavy pages, and anti-bot protections transparently. Also parses Next.js RSC flight data when present. HTML extraction also surfaces registered discovery relations (`service-desc`, `service-doc`, `service-meta`, `api-catalog`, `describedby`) from the HTTP `Link` header and matching `link`/`a[rel]` markup. Readable or rendered content remains primary; on an empty shell, the normal extraction fallbacks run before declared links are returned on their own.
|
|
253
|
+
When Readability fails or returns only a cookie notice, the extension can retry configured Firecrawl extraction, Jina Reader (handles JS rendering server-side, no API key needed), TinyFish, Search1API, Querit, Kagi Extract, Ollama Web Fetch, Parallel, Bright Data Web Unlocker, Gemini URL Context API, and Gemini Web extraction when browser cookies are enabled. Configure `fetchRouting.providers` to change the order or set of `fetch_content` providers. Supported values are `http`, `firecrawl`, `jina`, `tinyfish`, `search1api`, `querit`, `kagi`, `ollama`, `parallel`, `parallel-mcp`, `brightdata`, and `gemini`; when absent, the default order is unchanged. `parallel-mcp` is not in the default fetch order and must be listed explicitly. For remote HTTP(S) targets, third-party hosted providers are disabled unless `fetchRouting.allowRemoteHostedProviders` is `true`, because hosted services perform their own fetch and can see a different redirect chain than the local safety gate. Firecrawl stays available as a configured extraction service. Firecrawl requests are cache-only by default and require an explicit fresh-scrape opt-in before the Firecrawl server can fetch target URLs. Bright Data Web Unlocker runs last of the remote scraping providers, ahead of only the Gemini fallbacks, because it is billed per request against a paid account; it is skipped unless both a key and an `unblocker` zone are configured. It applies no minimum-length check, so any non-empty body it returns — including a short consent or paywall stub — is the final answer for that URL and the Gemini fallbacks are not tried. Handles SPAs, JS-heavy pages, and anti-bot protections transparently. Also parses Next.js RSC flight data when present. HTML extraction also surfaces registered discovery relations (`service-desc`, `service-doc`, `service-meta`, `api-catalog`, `describedby`) from the HTTP `Link` header and matching `link`/`a[rel]` markup. Readable or rendered content remains primary; on an empty shell, the normal extraction fallbacks run before declared links are returned on their own.
|
|
254
254
|
|
|
255
255
|
## How It Works
|
|
256
256
|
|
|
@@ -322,17 +322,22 @@ Config defaults to `~/.pi/web-search.json`, or `web-search.json` under `PI_CODIN
|
|
|
322
322
|
"openaiApiKey": "sk-...",
|
|
323
323
|
"openaiResponsesUrl": "https://gateway.example.com/v1/responses",
|
|
324
324
|
"braveApiKey": "BSA_...",
|
|
325
|
+
"braveBaseUrl": "https://gateway.example.com/brave/res/v1",
|
|
325
326
|
"exaApiKey": "exa-...",
|
|
327
|
+
"exaBaseUrl": "https://gateway.example.com/exa",
|
|
326
328
|
"parallelApiKey": "...",
|
|
327
329
|
"tinyfishApiKey": "sk-tinyfish-...",
|
|
328
330
|
"search1apiApiKey": "...",
|
|
329
331
|
"tavilyApiKey": "tvly-...",
|
|
332
|
+
"tavilyBaseUrl": "https://gateway.example.com/tavily",
|
|
330
333
|
"jinaApiKey": "$JINA_API_KEY",
|
|
331
334
|
"serpdiveApiKey": "sd_live_...",
|
|
332
335
|
"serpdiveModel": "krill",
|
|
333
336
|
"kagiApiKey": "$KAGI_API_KEY",
|
|
334
337
|
"ollamaApiKey": "$OLLAMA_API_KEY",
|
|
338
|
+
"valyuApiKey": "$VALYU_API_KEY",
|
|
335
339
|
"serpbaseApiKey": "$SERPBASE_API_KEY",
|
|
340
|
+
"serperApiKey": "$SERPER_API_KEY",
|
|
336
341
|
"brightdataApiKey": "$BRIGHTDATA_API_KEY",
|
|
337
342
|
"brightdataSerpZone": "pi_serp",
|
|
338
343
|
"searxngBaseUrl": "https://search.example.com",
|
|
@@ -426,6 +431,8 @@ Config defaults to `~/.pi/web-search.json`, or `web-search.json` under `PI_CODIN
|
|
|
426
431
|
}
|
|
427
432
|
```
|
|
428
433
|
|
|
434
|
+
`summaryModel` accepts an optional thinking-level suffix, such as `anthropic/claude-haiku-4-5:low`. Supported suffixes are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
|
|
435
|
+
|
|
429
436
|
All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `serpbaseApiKey`, `anysearchApiKey`, `xaiApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `exaApiKey`, `perplexityApiKey`, `geminiApiKey`, `datalabApiKey`, and `cloudflareApiKey`) accept explicit credential sources. Use `$NAME` or `${NAME}` to read one named environment variable, or prefix a trusted local shell command with `!` to resolve one value at provider request time. Escape `$$` as a literal leading `$` and `$!` as a literal leading `!`:
|
|
430
437
|
|
|
431
438
|
```json
|
|
@@ -437,10 +444,12 @@ All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `t
|
|
|
437
444
|
}
|
|
438
445
|
```
|
|
439
446
|
|
|
440
|
-
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `kagiApiKey`, `ollamaApiKey`, `serpbaseApiKey`, and `brightdataApiKey` use the same credential-source rules, while `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `brightdataSerpZone`, and `brightdataUnlockerZone` are literal config values.
|
|
447
|
+
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serperApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `brightdataSerpZone`, and `brightdataUnlockerZone` are literal config values.
|
|
441
448
|
|
|
442
449
|
A command source is not run while the extension loads or registers tools. Each selected provider request runs it again with a five-second timeout, a 16 KiB output limit, a minimized environment, and a one-line non-empty stdout requirement. Command text and stderr are omitted from errors. These commands are trusted local configuration, not a same-user process isolation boundary; use absolute executable paths and protect the config file. `OP_SESSION_*` variables are forwarded to trusted resolver commands so shell-local 1Password sessions can be reused without storing them in config. An explicit source overrides legacy provider environment variables and fails that provider locally rather than falling back with a stale credential. Direct Google Gemini API requests send the resolved key only in the `x-goog-api-key` header, never in the URL.
|
|
443
450
|
|
|
451
|
+
Set `braveBaseUrl`, `exaBaseUrl`, or `tavilyBaseUrl` to route those providers through a compatible HTTPS API gateway. `BRAVE_BASE_URL`, `EXA_BASE_URL`, and `TAVILY_BASE_URL` are the environment-variable equivalents and take precedence over config. Brave appends `/web/search`, Exa appends `/answer` or `/search`, and Tavily appends `/search`. Defaults remain the providers' official API roots. `exaBaseUrl` applies only to keyed direct API calls; zero-config Exa MCP search continues to use Exa's hosted MCP endpoint. Invalid overrides fail before a request is sent instead of falling back to an official endpoint, and credential headers are removed if a gateway redirects to another origin.
|
|
452
|
+
|
|
444
453
|
`authFetch` configures named local browser-cookie auth profiles for explicit `fetch_content` calls. A profile can be a host array (`"work": ["docs.company.com"]`) or an object with `hosts`, optional `chromeProfile`, `redirects: "same-origin"`, and `cache: "session" | "off"`.
|
|
445
454
|
|
|
446
455
|
`fetchContent.domainPolicy` is an optional hostname allow/deny policy for `fetch_content` target URLs. It is off when omitted. Each bare hostname matches itself and its subdomains; `deny` wins when a hostname matches both lists. The policy is checked before HTTP(S) target handling and before each redirect followed by this extension's own fetch path. Local file paths and non-HTTP sources are not subject to this policy. It is an additional restriction: the existing SSRF guard still blocks private and internal destinations. Remote extraction services can still perform their own DNS, redirects, and egress after this extension preflights the submitted target URL, so third-party hosted HTTP(S) fallbacks stay disabled unless `fetchRouting.allowRemoteHostedProviders` is enabled for separately isolated provider deployments.
|
|
@@ -463,11 +472,18 @@ Bright Data Web Unlocker is a paid `fetch_content` fallback after Parallel and b
|
|
|
463
472
|
|
|
464
473
|
**SerpBase.** Set `serpbaseApiKey` or `SERPBASE_API_KEY` and select `provider: "serpbase"` to query SerpBase's Google Search Results API. SerpBase is explicit-only: it is never chosen by `auto` and never participates in `provider: "all"`, because each request can consume paid Google SERP credits. Domain filters are sent as Google `site:` clauses and reapplied locally; recency maps to Google's `tbs` time filter.
|
|
465
474
|
|
|
466
|
-
|
|
475
|
+
**Valyu.** Set `valyuApiKey` or `VALYU_API_KEY` and select `provider: "valyu"` to query Valyu's research search API. Valyu is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`. Returned page bodies provide result snippets and optional inline content, capped at 2,500 and 4,000 characters per result.
|
|
476
|
+
|
|
477
|
+
**Serper.** Set `serperApiKey` or `SERPER_API_KEY` and select `provider: "serper"` to query Serper's Google Search API. Serper is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`.
|
|
478
|
+
|
|
479
|
+
**Parallel MCP.** Select `provider: "parallel-mcp"` to use Parallel Search MCP without an API key, or add it to `searchRouting`. It is explicit-only and is never chosen by `auto` or `provider: "all"`; the existing `parallel` provider remains the key-required REST API. A configured `parallelApiKey` or `PARALLEL_API_KEY` is sent as an optional Bearer token for higher MCP limits. To use MCP `web_fetch`, add `parallel-mcp` to `fetchRouting.providers` and set `fetchRouting.allowRemoteHostedProviders` to `true`; it is not part of the default fetch route.
|
|
480
|
+
|
|
481
|
+
Without an explicit `$` or `!` source, `OPENAI_API_KEY`, `BRAVE_API_KEY`, `PARALLEL_API_KEY`, `TINYFISH_API_KEY`, `SEARCH1API_KEY`, `SEARCHINFINITY_API_KEY`, `QUERIT_API_KEY`, `TAVILY_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `ANYSEARCH_API_KEY`, `XAI_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `EXA_API_KEY`, `GEMINI_API_KEY`, `DATALAB_API_KEY`, `DATALAB_PROCESSING_LOCATION`, `DATALAB_MODE`, `DATALAB_API_BASE`, `PERPLEXITY_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, and `CLOUDFLARE_API_KEY` env vars retain their existing precedence over literal config file values. `openaiResponsesUrl` can point OpenAI `web_search` and `source_check` at a third-party gateway that supports the OpenAI Responses API and web search tool; it is an explicit endpoint override, not derived from Pi model provider settings, and defaults to `https://api.openai.com/v1/responses`. `openaiSearchModel` pins the model id used for OpenAI `web_search`, bypassing automatic selection (newest terra-tier model); the id is sent verbatim with whichever OpenAI auth resolves, so gateway-only model ids work too. `xaiSearchModel` similarly pins the xAI search model. Configured Exa API keys use Exa's own account limits directly; any legacy local `exa-usage.json` file is ignored. `GOOGLE_GEMINI_BASE_URL` overrides the Gemini API host for Gemini generate-content calls such as search, URL context, YouTube, and local video analysis. Set it to a bare host with no trailing slash and no version segment, for example `https://my-gateway.example.com/gemini`; `geminiBaseUrl` is the config-file equivalent. When the configured host contains `gateway.ai.cloudflare.com`, authentication uses `cf-aig-authorization: Bearer <token>` from `CLOUDFLARE_API_KEY` or `cloudflareApiKey`, and `GEMINI_API_KEY` is not required for generate-content calls. Local video file upload still uses Google's Files API directly, so gateway-only video extraction falls back to Gemini Web unless a `GEMINI_API_KEY` is also configured. `provider` or `searchProvider` sets the default search provider and is used when a tool call omits `provider` or sends `"auto"`: `"all"`, `"openai"`, `"brave"`, `"parallel"`, `"parallel-mcp"`, `"tinyfish"`, `"search1api"`, `"searchinfinity"`, `"querit"`, `"tavily"`, `"firecrawl"`, `"jina"`, `"serpdive"`, `"kagi"`, `"bocha"`, `"ollama"`, `"anysearch"`, `"valyu"`, `"xai"`, `"brightdata"`, `"serpbase"`, `"serper"`, `"searxng"`, `"exa"`, `"perplexity"`, or `"gemini"`. Parallel MCP, AnySearch, Valyu, xAI, Bright Data, SerpBase, and Serper are never selected by `auto`; choose them explicitly or place them in `searchRouting`. If either single-provider field is configured, it takes precedence over `searchRouting`. Otherwise, `searchRouting` can opt into an ordered `providers` list and an explicit `fallbackOn` list containing `"transient"`, `"quota"`, `"network"`, and/or `"invalid-response"`; only those typed failures continue to the next available candidate. `"all"` is not valid inside `searchRouting.providers`, because that list defines sequential fallback rather than multi-provider aggregation. Named providers remain strict, and exhausted routes return per-provider diagnostics. `provider` can also be a non-empty array of named providers such as `["brave", "exa"]`; those providers run concurrently using the same aggregation path as `"all"`, while `"auto"` and `"all"` are invalid inside arrays. Random, weighted, sticky, and cooldown routing are not enabled. This is also updated automatically when you change the provider in the curator UI. Set `webSearch.enabled` to `false` to unregister the configured search and source-check tools while leaving fetch/content tools available. `toolNames` can opt into alternate public tool names for environments where another extension or model reserves the defaults, without changing behavior: `webSearch`, `sourceCheck`, `fetchContent`, and `getSearchContent` default to `web_search`, `source_check`, `fetch_content`, and `get_search_content`. `workflow` sets the default search workflow: `"summary-review"` (default, opens curator with auto-generated summary draft), `"auto-summary"` (returns a model-generated summary without opening the curator), or `"none"` (raw results, no curator). Overridden per-call via the `workflow` parameter on the configured search tool, or toggled at runtime with `/curator`. `chromeProfile` pins Gemini Web cookie lookup to a specific Chromium profile. When omitted, detected Chromium profiles are scanned in stable order and the first profile containing the required Gemini cookies is used. macOS discovery supports Helium, Chrome, Brave, and Arc; Linux discovery supports Chromium and Chrome. `allowBrowserCookies` enables Chromium cookie extraction for Gemini Web; it defaults to `false` to avoid browser data access and surprise macOS Keychain prompts. You can also set `PI_ALLOW_BROWSER_COOKIES=1`. Cookie databases are copied to a temporary read-only working copy; the reader uses `node:sqlite` when available and otherwise tries the `sqlite3` CLI or Python's standard-library SQLite module. `searchModel` overrides the Gemini API model used by the configured search tool without changing URL, YouTube, or video extraction defaults. Gemini API grounded search uses `gemini-3.6-flash` by default; set `searchModel` to choose another model. Gemini Web browser-cookie fallback uses its separate `gemini-3.1-pro` default because Gemini Web relies on private header values; explicitly configured unsupported Web models fail instead of silently falling back to 2.5 Flash. `summaryModel` sets the default model used for generating summary drafts in the curator UI and `auto-summary` mode (e.g. `"anthropic/claude-haiku-4-5"`, `"openai-codex/gpt-5.3-codex-spark"`, or `"openrouter/nvidia/nemotron-3-super-120b-a12b:free"`). Preferred summary and query-rewrite models also resolve through routed provider registrations such as OpenRouter when the native provider is unavailable. When Pi `enabledModels` is configured, summaries are limited to that allowlist; if no enabled summary model is available, the tool returns a deterministic summary instead of calling an unrelated model. `summaryGenerationDeadlineMs` sets the maximum time for one summary model attempt in the curator UI and `auto-summary` mode. It defaults to `30000`, must be a positive integer, and is capped at `600000`. `maxInlineContentChars` sets the direct `fetch_content` content slice and the default and maximum `get_search_content` slice. It defaults to `30000`, must be a positive integer, and is capped at `200000`; full fetched content remains stored for later retrieval. `curatorTimeoutSeconds` controls the initial curator idle timeout (default `20`, max `600`); users can still adjust the timer in the curator UI. `ssrf.allowRanges` lists CIDR ranges (e.g. `"198.18.0.0/15"`, `"fd00::/8"`) exempted from the SSRF guard that otherwise blocks private/reserved IP ranges. This unblocks `fetch_content`/`web_search` on hosts whose network proxy runs in TUN + fake-IP mode (Surge, Clash, Mihomo, Stash, ...), where public domains resolve into a synthetic reserved range. It is **off by default** — the guard stays fully enabled unless you list ranges here. Use the narrowest range that covers your proxy's fake-IP pool. All-address CIDRs such as `0.0.0.0/0` and `::/0` are rejected. `ssrf.trustEnvProxy` is a separate opt-in for sandboxed environments with valid HTTP(S) proxy env vars; it skips local DNS preflight only for proxied hostnames and still blocks localhost, literal private IPs, and `NO_PROXY` matches. It does not configure proxy transport.
|
|
482
|
+
`VALYU_API_KEY` and `SERPER_API_KEY` also retain this precedence. `provider` and `searchProvider` also accept `"parallel-mcp"`, `"valyu"`, and `"serper"`; all three remain explicit-only.
|
|
467
483
|
|
|
468
484
|
### All providers
|
|
469
485
|
|
|
470
|
-
Set `provider: "all"` on `web_search` or `source_check`, or configure `"provider": "all"` as the default, to run the same query against every eligible search provider simultaneously. DuckDuckGo, AnySearch, xAI, Bright Data, and
|
|
486
|
+
Set `provider: "all"` on `web_search` or `source_check`, or configure `"provider": "all"` as the default, to run the same query against every eligible search provider simultaneously. Parallel MCP, DuckDuckGo, AnySearch, Valyu, xAI, Bright Data, SerpBase, and Serper are always excluded because they are explicit-only; Bright Data, SerpBase, and Serper are paid Google SERP providers, so `all` never spends on them. Exa remains eligible through its zero-config MCP path, OpenAI can use Pi auth, and other API-backed search providers participate when their API key, local endpoint, or gateway makes them available. Browser-cookie access alone does not opt Gemini into `all`; select Gemini explicitly or configure its API/gateway.
|
|
471
487
|
|
|
472
488
|
Successful provider answers are preserved separately while source URLs and inline content are deduplicated, and one provider failure does not discard the other results. If every participating provider fails, the tool returns per-provider diagnostics. Configured Firecrawl participates in `all` like other eligible providers. In the Curator, **All** can also be selected like the other provider buttons. Each participating provider gets its own result card, including a provider badge and independent selection checkbox; failed providers get their own disabled error card. The final summary is generated from the selected provider cards and is what Pi receives. Outside the Curator, the same provider answers remain available as labeled sections in one tool response.
|
|
473
489
|
|
|
@@ -831,7 +847,9 @@ Rate limits: Perplexity is capped at 10 requests/minute (client-side). Jina Sear
|
|
|
831
847
|
| `ollama.ts` | Ollama Cloud Web Search provider and Web Fetch fallback |
|
|
832
848
|
| `brightdata.ts` | Explicit-only Bright Data SERP search provider |
|
|
833
849
|
| `serpbase.ts` | Explicit-only SerpBase Google SERP provider |
|
|
850
|
+
| `serper.ts` | Explicit-only Serper Google SERP provider |
|
|
834
851
|
| `anysearch.ts` | Explicit-only AnySearch search provider |
|
|
852
|
+
| `valyu.ts` | Explicit-only Valyu research search provider |
|
|
835
853
|
| `xai-search.ts` | Explicit-only xAI (Grok) hosted web_search provider |
|
|
836
854
|
| `searxng.ts` | Self-hosted SearXNG JSON API search provider |
|
|
837
855
|
| `duckduckgo.ts` | Explicit-only keyless DuckDuckGo HTML search provider |
|
|
@@ -103,9 +103,11 @@ function parseResponse(value: unknown): AnySearchResponse {
|
|
|
103
103
|
if (typeof title !== "string") throw invalidResponse(`expected data.results[${index}].title string`);
|
|
104
104
|
if (typeof url !== "string") throw invalidResponse(`expected data.results[${index}].url string`);
|
|
105
105
|
if (typeof snippet !== "string") throw invalidResponse(`expected data.results[${index}].snippet string`);
|
|
106
|
-
if (typeof content !== "string")
|
|
106
|
+
if (content !== undefined && content !== null && typeof content !== "string") {
|
|
107
|
+
throw invalidResponse(`expected data.results[${index}].content string`);
|
|
108
|
+
}
|
|
107
109
|
if (!url) throw invalidResponse(`expected data.results[${index}].url to be non-empty`);
|
|
108
|
-
results.push({ title, url, snippet, content });
|
|
110
|
+
results.push({ title, url, snippet, content: typeof content === "string" ? content : "" });
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
return { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };
|
|
@@ -2,14 +2,15 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
3
|
import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
|
|
4
4
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
5
|
-
import { getWebSearchConfigPath } from "./utils.ts";
|
|
5
|
+
import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.ts";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const BRAVE_API_BASE_URL = "https://api.search.brave.com/res/v1";
|
|
8
8
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
9
9
|
const SEARCH_TIMEOUT_MS = 30_000;
|
|
10
10
|
|
|
11
11
|
interface WebSearchConfig {
|
|
12
12
|
braveApiKey?: unknown;
|
|
13
|
+
braveBaseUrl?: unknown;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
interface NormalizedDomainFilters {
|
|
@@ -45,6 +46,16 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
|
|
|
45
46
|
});
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function getApiUrl(): string {
|
|
50
|
+
return `${resolveApiBaseUrl({
|
|
51
|
+
configKey: "braveBaseUrl",
|
|
52
|
+
configuredValue: loadConfig().braveBaseUrl,
|
|
53
|
+
defaultValue: BRAVE_API_BASE_URL,
|
|
54
|
+
environmentKey: "BRAVE_BASE_URL",
|
|
55
|
+
environmentValue: process.env.BRAVE_BASE_URL,
|
|
56
|
+
})}/web/search`;
|
|
57
|
+
}
|
|
58
|
+
|
|
48
59
|
function normalizeCount(value: number | undefined): number {
|
|
49
60
|
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
50
61
|
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
@@ -129,6 +140,7 @@ export async function searchWithBrave(
|
|
|
129
140
|
query: string,
|
|
130
141
|
options: SearchOptions = {},
|
|
131
142
|
): Promise<SearchResponse> {
|
|
143
|
+
const apiUrl = getApiUrl();
|
|
132
144
|
const apiKey = await getApiKey(options.signal);
|
|
133
145
|
if (!apiKey) {
|
|
134
146
|
throw new Error(
|
|
@@ -160,7 +172,7 @@ export async function searchWithBrave(
|
|
|
160
172
|
}
|
|
161
173
|
|
|
162
174
|
try {
|
|
163
|
-
const response = await
|
|
175
|
+
const response = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {
|
|
164
176
|
method: "GET",
|
|
165
177
|
headers: {
|
|
166
178
|
"X-Subscription-Token": apiKey,
|
|
@@ -170,7 +182,7 @@ export async function searchWithBrave(
|
|
|
170
182
|
signal: options.signal
|
|
171
183
|
? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])
|
|
172
184
|
: AbortSignal.timeout(SEARCH_TIMEOUT_MS),
|
|
173
|
-
});
|
|
185
|
+
}, ["X-Subscription-Token"]);
|
|
174
186
|
|
|
175
187
|
if (!response.ok) {
|
|
176
188
|
activityMonitor.logError(activityId, `HTTP ${response.status}`);
|
|
@@ -8,7 +8,7 @@ function safeInlineJSON(data: unknown): string {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function buildProviderButtons(
|
|
11
|
-
available: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean },
|
|
11
|
+
available: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; "parallel-mcp": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean },
|
|
12
12
|
selected: string,
|
|
13
13
|
hasInitialQueries: boolean,
|
|
14
14
|
): string {
|
|
@@ -18,6 +18,7 @@ function buildProviderButtons(
|
|
|
18
18
|
{ value: "exa", label: "Exa", available: available.exa },
|
|
19
19
|
{ value: "brave", label: "Brave", available: available.brave },
|
|
20
20
|
{ value: "parallel", label: "Parallel", available: available.parallel },
|
|
21
|
+
{ value: "parallel-mcp", label: "Parallel MCP", available: available["parallel-mcp"] },
|
|
21
22
|
{ value: "tinyfish", label: "TinyFish", available: available.tinyfish },
|
|
22
23
|
{ value: "search1api", label: "Search1API", available: available.search1api },
|
|
23
24
|
{ value: "searchinfinity", label: "Searchinfinity", available: available.searchinfinity },
|
|
@@ -37,6 +38,8 @@ function buildProviderButtons(
|
|
|
37
38
|
{ value: "xai", label: "xAI", available: available.xai },
|
|
38
39
|
{ value: "brightdata", label: "Bright Data", available: available.brightdata },
|
|
39
40
|
{ value: "serpbase", label: "SerpBase", available: available.serpbase },
|
|
41
|
+
{ value: "serper", label: "Serper", available: available.serper },
|
|
42
|
+
{ value: "valyu", label: "Valyu", available: available.valyu },
|
|
40
43
|
];
|
|
41
44
|
|
|
42
45
|
return providers
|
|
@@ -55,7 +58,7 @@ export function generateCuratorPage(
|
|
|
55
58
|
queries: string[],
|
|
56
59
|
sessionToken: string,
|
|
57
60
|
timeout: number,
|
|
58
|
-
availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean },
|
|
61
|
+
availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; "parallel-mcp": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean },
|
|
59
62
|
defaultProvider: string,
|
|
60
63
|
searchProvider: string,
|
|
61
64
|
summaryModels: Array<{ value: string; label: string }>,
|
|
@@ -1456,7 +1459,7 @@ const SCRIPT = `(function() {
|
|
|
1456
1459
|
var token = DATA.sessionToken;
|
|
1457
1460
|
var timeoutSec = DATA.timeout;
|
|
1458
1461
|
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
1459
|
-
var providers = ["all", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "anysearch", "xai", "brightdata", "serpbase"];
|
|
1462
|
+
var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "anysearch", "xai", "brightdata", "serpbase", "serper", "valyu"];
|
|
1460
1463
|
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
1461
1464
|
var workflow = "summary-review";
|
|
1462
1465
|
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
@@ -18,7 +18,7 @@ export interface CuratorServerOptions {
|
|
|
18
18
|
queries: string[];
|
|
19
19
|
sessionToken: string;
|
|
20
20
|
timeout: number;
|
|
21
|
-
availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean };
|
|
21
|
+
availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; "parallel-mcp": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean };
|
|
22
22
|
defaultProvider: string;
|
|
23
23
|
searchProvider: string;
|
|
24
24
|
summaryModels: Array<{ value: string; label: string }>;
|
|
@@ -275,6 +275,7 @@ export function startCuratorServer(
|
|
|
275
275
|
if (provider === "openai") return availableProviders.openai;
|
|
276
276
|
if (provider === "brave") return availableProviders.brave;
|
|
277
277
|
if (provider === "parallel") return availableProviders.parallel;
|
|
278
|
+
if (provider === "parallel-mcp") return availableProviders["parallel-mcp"];
|
|
278
279
|
if (provider === "tinyfish") return availableProviders.tinyfish;
|
|
279
280
|
if (provider === "search1api") return availableProviders.search1api;
|
|
280
281
|
if (provider === "searchinfinity") return availableProviders.searchinfinity;
|
|
@@ -295,6 +296,8 @@ export function startCuratorServer(
|
|
|
295
296
|
if (provider === "xai") return availableProviders.xai;
|
|
296
297
|
if (provider === "brightdata") return availableProviders.brightdata;
|
|
297
298
|
if (provider === "serpbase") return availableProviders.serpbase;
|
|
299
|
+
if (provider === "serper") return availableProviders.serper;
|
|
300
|
+
if (provider === "valyu") return availableProviders.valyu;
|
|
298
301
|
return false;
|
|
299
302
|
}
|
|
300
303
|
|
|
@@ -3,10 +3,9 @@ import { activityMonitor } from "./activity.ts";
|
|
|
3
3
|
import type { ExtractedContent } from "./extract.ts";
|
|
4
4
|
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
5
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
6
|
-
import { getWebSearchConfigPath } from "./utils.ts";
|
|
6
|
+
import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.ts";
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
const EXA_SEARCH_URL = "https://api.exa.ai/search";
|
|
8
|
+
const EXA_API_BASE_URL = "https://api.exa.ai";
|
|
10
9
|
const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
|
|
11
10
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
12
11
|
const EXA_MCP_ADVANCED_TOOL = "web_search_advanced_exa";
|
|
@@ -14,6 +13,7 @@ const EXA_MCP_BASIC_TOOL = "web_search_exa";
|
|
|
14
13
|
|
|
15
14
|
interface WebSearchConfig {
|
|
16
15
|
exaApiKey?: unknown;
|
|
16
|
+
exaBaseUrl?: unknown;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
interface ExaAnswerResponse {
|
|
@@ -80,6 +80,16 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function getApiBaseUrl(): string {
|
|
84
|
+
return resolveApiBaseUrl({
|
|
85
|
+
configKey: "exaBaseUrl",
|
|
86
|
+
configuredValue: loadConfig().exaBaseUrl,
|
|
87
|
+
defaultValue: EXA_API_BASE_URL,
|
|
88
|
+
environmentKey: "EXA_BASE_URL",
|
|
89
|
+
environmentValue: process.env.EXA_BASE_URL,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
function exaApiHeaders(apiKey: string): Record<string, string> {
|
|
84
94
|
return {
|
|
85
95
|
"x-api-key": apiKey,
|
|
@@ -447,6 +457,7 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
|
|
|
447
457
|
return searchWithExaMcp(query, options);
|
|
448
458
|
}
|
|
449
459
|
|
|
460
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
450
461
|
const useSearch = options.includeContent
|
|
451
462
|
|| !!options.recencyFilter
|
|
452
463
|
|| !!options.domainFilter?.length
|
|
@@ -456,12 +467,12 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
|
|
|
456
467
|
|
|
457
468
|
try {
|
|
458
469
|
if (!useSearch) {
|
|
459
|
-
const response = await
|
|
470
|
+
const response = await fetchWithCredentialRedirects(`${apiBaseUrl}/answer`, {
|
|
460
471
|
method: "POST",
|
|
461
472
|
headers: exaApiHeaders(apiKey),
|
|
462
473
|
body: JSON.stringify({ query }),
|
|
463
474
|
signal: requestSignal(options.signal),
|
|
464
|
-
});
|
|
475
|
+
}, ["x-api-key"]);
|
|
465
476
|
|
|
466
477
|
if (!response.ok) {
|
|
467
478
|
const errorText = redactCredential(await response.text(), apiKey);
|
|
@@ -476,7 +487,7 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
|
|
|
476
487
|
};
|
|
477
488
|
}
|
|
478
489
|
|
|
479
|
-
const response = await
|
|
490
|
+
const response = await fetchWithCredentialRedirects(`${apiBaseUrl}/search`, {
|
|
480
491
|
method: "POST",
|
|
481
492
|
headers: exaApiHeaders(apiKey),
|
|
482
493
|
body: JSON.stringify({
|
|
@@ -486,7 +497,7 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
|
|
|
486
497
|
: { highlights: true },
|
|
487
498
|
}),
|
|
488
499
|
signal: requestSignal(options.signal),
|
|
489
|
-
});
|
|
500
|
+
}, ["x-api-key"]);
|
|
490
501
|
|
|
491
502
|
if (!response.ok) {
|
|
492
503
|
const errorText = redactCredential(await response.text(), apiKey);
|
|
@@ -12,6 +12,7 @@ import { isYouTubeURL, isYouTubeEnabled, extractYouTube, extractYouTubeFrame, ex
|
|
|
12
12
|
import { CredentialResolutionError } from "./credential-source.ts";
|
|
13
13
|
import { extractWithUrlContext, extractWithGeminiWeb } from "./gemini-url-context.ts";
|
|
14
14
|
import { extractWithParallel, isParallelAvailable } from "./parallel.ts";
|
|
15
|
+
import { extractWithParallelMcp } from "./parallel-mcp.ts";
|
|
15
16
|
import { extractWithTinyFish, isTinyFishAvailable } from "./tinyfish.ts";
|
|
16
17
|
import { extractWithSearch1API, isSearch1APIAvailable } from "./search1api.ts";
|
|
17
18
|
import { extractWithQuerit, isQueritAvailable } from "./querit.ts";
|
|
@@ -34,11 +35,11 @@ const NON_RECOVERABLE_ERRORS = ["Unsupported content type", "Response too large"
|
|
|
34
35
|
const MIN_USEFUL_CONTENT = 500;
|
|
35
36
|
const SUPPORTED_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
36
37
|
const WEB_SEARCH_CONFIG_PATH = getWebSearchConfigPath();
|
|
37
|
-
const FETCH_PROVIDERS = ["http", "firecrawl", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "brightdata", "gemini"] as const;
|
|
38
|
+
const FETCH_PROVIDERS = ["http", "firecrawl", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "parallel-mcp", "brightdata", "gemini"] as const;
|
|
38
39
|
type FetchProvider = typeof FETCH_PROVIDERS[number];
|
|
39
40
|
type FetchRouting = { providers: FetchProvider[]; allowRemoteHostedProviders: boolean };
|
|
40
41
|
const DEFAULT_FETCH_PROVIDER_ORDER: FetchProvider[] = ["http", "firecrawl", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "brightdata", "gemini"];
|
|
41
|
-
const REMOTE_HOSTED_FETCH_PROVIDERS = new Set<FetchProvider>(["jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "brightdata", "gemini"]);
|
|
42
|
+
const REMOTE_HOSTED_FETCH_PROVIDERS = new Set<FetchProvider>(["jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "parallel-mcp", "brightdata", "gemini"]);
|
|
42
43
|
|
|
43
44
|
export { loadSsrfConfig } from "./ssrf-protection.ts";
|
|
44
45
|
|
|
@@ -668,6 +669,7 @@ export async function extractContent(
|
|
|
668
669
|
let kagiError: string | null = null;
|
|
669
670
|
let ollamaError: string | null = null;
|
|
670
671
|
let parallelError: string | null = null;
|
|
672
|
+
let parallelMcpError: string | null = null;
|
|
671
673
|
let brightdataError: string | null = null;
|
|
672
674
|
|
|
673
675
|
if (remoteUrl && providerOrder[0] !== "http") {
|
|
@@ -803,6 +805,18 @@ export async function extractContent(
|
|
|
803
805
|
continue;
|
|
804
806
|
}
|
|
805
807
|
|
|
808
|
+
if (provider === "parallel-mcp") {
|
|
809
|
+
try {
|
|
810
|
+
const parallelMcpResult = await extractWithParallelMcp(url, signal, options);
|
|
811
|
+
if (parallelMcpResult) return withDeclaredLinks(parallelMcpResult);
|
|
812
|
+
} catch (err) {
|
|
813
|
+
if (isAbortError(err)) return abortedResult(url);
|
|
814
|
+
parallelMcpError = errorMessage(err);
|
|
815
|
+
if (isConfigParseError(err)) return parseErrorResult(parallelMcpError);
|
|
816
|
+
}
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
|
|
806
820
|
if (provider === "brightdata") {
|
|
807
821
|
try {
|
|
808
822
|
if (isBrightDataUnlockerAvailable()) {
|
|
@@ -850,6 +864,7 @@ export async function extractContent(
|
|
|
850
864
|
...(kagiError ? [`Kagi fallback failed: ${kagiError}`] : []),
|
|
851
865
|
...(ollamaError ? [`Ollama fallback failed: ${ollamaError}`] : []),
|
|
852
866
|
...(parallelError ? [`Parallel fallback failed: ${parallelError}`] : []),
|
|
867
|
+
...(parallelMcpError ? [`Parallel MCP fallback failed: ${parallelMcpError}`] : []),
|
|
853
868
|
...(brightdataError ? [`Bright Data fallback failed: ${brightdataError}`] : []),
|
|
854
869
|
"",
|
|
855
870
|
"Fallback options:",
|
|
@@ -1144,7 +1159,7 @@ async function extractViaHttp(
|
|
|
1144
1159
|
|
|
1145
1160
|
if (!article) {
|
|
1146
1161
|
const rscResult = extractRSCContent(text);
|
|
1147
|
-
if (rscResult) {
|
|
1162
|
+
if (rscResult && rscResult.content.length >= MIN_USEFUL_CONTENT) {
|
|
1148
1163
|
activityMonitor.logComplete(activityId, response.status);
|
|
1149
1164
|
return {
|
|
1150
1165
|
url,
|
|
@@ -1177,6 +1192,16 @@ async function extractViaHttp(
|
|
|
1177
1192
|
activityMonitor.logComplete(activityId, response.status);
|
|
1178
1193
|
|
|
1179
1194
|
if (markdown.length < MIN_USEFUL_CONTENT) {
|
|
1195
|
+
const rscResult = extractRSCContent(text);
|
|
1196
|
+
if (rscResult && rscResult.content.length >= MIN_USEFUL_CONTENT) {
|
|
1197
|
+
return {
|
|
1198
|
+
url,
|
|
1199
|
+
title: rscResult.title,
|
|
1200
|
+
content: appendDeclaredWebLinks(rscResult.content, declaredLinks),
|
|
1201
|
+
error: null,
|
|
1202
|
+
declaredLinks,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1180
1205
|
return {
|
|
1181
1206
|
url,
|
|
1182
1207
|
title: article.title || documentTitle,
|
|
@@ -9,6 +9,7 @@ import { isExaAvailable, searchWithExa } from "./exa.ts";
|
|
|
9
9
|
import { isBraveAvailable, searchWithBrave } from "./brave.ts";
|
|
10
10
|
import { isOpenAISearchAvailable, searchWithOpenAI } from "./openai-search.ts";
|
|
11
11
|
import { isParallelAvailable, searchWithParallel } from "./parallel.ts";
|
|
12
|
+
import { isParallelMcpAvailable, searchWithParallelMcp } from "./parallel-mcp.ts";
|
|
12
13
|
import { isTinyFishAvailable, searchWithTinyFish } from "./tinyfish.ts";
|
|
13
14
|
import { isSearch1APIAvailable, searchWithSearch1API } from "./search1api.ts";
|
|
14
15
|
import { isSearchinfinityAvailable, searchWithSearchinfinity } from "./searchinfinity.ts";
|
|
@@ -26,9 +27,11 @@ import { isAnySearchAvailable, searchWithAnySearch } from "./anysearch.ts";
|
|
|
26
27
|
import { isXaiSearchAvailable, searchWithXai } from "./xai-search.ts";
|
|
27
28
|
import { isBrightDataAvailable, searchWithBrightData } from "./brightdata.ts";
|
|
28
29
|
import { isSerpBaseAvailable, searchWithSerpBase } from "./serpbase.ts";
|
|
30
|
+
import { isSerperAvailable, searchWithSerper } from "./serper.ts";
|
|
31
|
+
import { isValyuAvailable, searchWithValyu } from "./valyu.ts";
|
|
29
32
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
30
33
|
|
|
31
|
-
export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "bocha"] as const;
|
|
34
|
+
export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "serper", "valyu", "bocha"] as const;
|
|
32
35
|
export const SEARCH_PROVIDERS = ["auto", "all", ...RESOLVED_SEARCH_PROVIDERS] as const;
|
|
33
36
|
|
|
34
37
|
export type ResolvedSearchProvider = typeof RESOLVED_SEARCH_PROVIDERS[number];
|
|
@@ -90,7 +93,7 @@ export interface AttributedSearchResponse extends SearchResponse {
|
|
|
90
93
|
|
|
91
94
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
92
95
|
const DEFAULT_SEARCH_MODEL = "gemini-3.6-flash";
|
|
93
|
-
// Explicit-only providers (DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase) are deliberately absent:
|
|
96
|
+
// Explicit-only providers (Parallel MCP, DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase, Serper, Valyu) are deliberately absent:
|
|
94
97
|
// `all` must never fan out to an opt-in or paid provider without the user asking for it.
|
|
95
98
|
const ALL_SEARCH_PROVIDERS: ResolvedSearchProvider[] = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
|
|
96
99
|
const VALID_ROUTING_KINDS = ["transient", "quota", "network", "invalid-response"] as const;
|
|
@@ -300,6 +303,7 @@ async function searchWithResolvedProvider(
|
|
|
300
303
|
}
|
|
301
304
|
if (provider === "brave") return { ...(await searchWithBrave(query, options)), provider };
|
|
302
305
|
if (provider === "parallel") return { ...(await searchWithParallel(query, options)), provider };
|
|
306
|
+
if (provider === "parallel-mcp") return { ...(await searchWithParallelMcp(query, options)), provider };
|
|
303
307
|
if (provider === "tinyfish") return { ...(await searchWithTinyFish(query, options)), provider };
|
|
304
308
|
if (provider === "search1api") return { ...(await searchWithSearch1API(query, options)), provider };
|
|
305
309
|
if (provider === "searchinfinity") return { ...(await searchWithSearchinfinity(query, options)), provider };
|
|
@@ -315,6 +319,8 @@ async function searchWithResolvedProvider(
|
|
|
315
319
|
if (provider === "xai") return { ...(await searchWithXai(query, options, options.extensionContext)), provider };
|
|
316
320
|
if (provider === "brightdata") return { ...(await searchWithBrightData(query, options)), provider };
|
|
317
321
|
if (provider === "serpbase") return { ...(await searchWithSerpBase(query, options)), provider };
|
|
322
|
+
if (provider === "serper") return { ...(await searchWithSerper(query, options)), provider };
|
|
323
|
+
if (provider === "valyu") return { ...(await searchWithValyu(query, options)), provider };
|
|
318
324
|
if (provider === "perplexity") return { ...(await searchWithPerplexity(query, options)), provider };
|
|
319
325
|
if (provider === "searxng") return { ...(await searchWithSearXNG(query, options)), provider };
|
|
320
326
|
if (provider === "duckduckgo") return { ...(await searchWithDuckDuckGo(query, options)), provider };
|
|
@@ -337,6 +343,7 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
|
|
|
337
343
|
if (provider === "openai") return isOpenAISearchAvailable(options.extensionContext);
|
|
338
344
|
if (provider === "brave") return isBraveAvailable();
|
|
339
345
|
if (provider === "parallel") return isParallelAvailable();
|
|
346
|
+
if (provider === "parallel-mcp") return isParallelMcpAvailable();
|
|
340
347
|
if (provider === "tinyfish") return isTinyFishAvailable();
|
|
341
348
|
if (provider === "search1api") return isSearch1APIAvailable();
|
|
342
349
|
if (provider === "searchinfinity") return isSearchinfinityAvailable();
|
|
@@ -352,6 +359,8 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
|
|
|
352
359
|
if (provider === "xai") return isXaiSearchAvailable(options.extensionContext);
|
|
353
360
|
if (provider === "brightdata") return isBrightDataAvailable();
|
|
354
361
|
if (provider === "serpbase") return isSerpBaseAvailable();
|
|
362
|
+
if (provider === "serper") return isSerperAvailable();
|
|
363
|
+
if (provider === "valyu") return isValyuAvailable();
|
|
355
364
|
if (provider === "perplexity") return isPerplexityAvailable();
|
|
356
365
|
if (provider === "searxng") return isSearXNGAvailable();
|
|
357
366
|
if (provider === "duckduckgo") return isDuckDuckGoAvailable();
|
|
@@ -361,6 +370,7 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
|
|
|
361
370
|
|
|
362
371
|
function providerLabel(provider: ResolvedSearchProvider): string {
|
|
363
372
|
if (provider === "openai") return "OpenAI";
|
|
373
|
+
if (provider === "parallel-mcp") return "Parallel MCP";
|
|
364
374
|
if (provider === "tinyfish") return "TinyFish";
|
|
365
375
|
if (provider === "search1api") return "Search1API";
|
|
366
376
|
if (provider === "searchinfinity") return "Searchinfinity";
|
|
@@ -375,6 +385,8 @@ function providerLabel(provider: ResolvedSearchProvider): string {
|
|
|
375
385
|
if (provider === "xai") return "xAI";
|
|
376
386
|
if (provider === "brightdata") return "Bright Data";
|
|
377
387
|
if (provider === "serpbase") return "SerpBase";
|
|
388
|
+
if (provider === "serper") return "Serper";
|
|
389
|
+
if (provider === "valyu") return "Valyu";
|
|
378
390
|
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
379
391
|
}
|
|
380
392
|
|
|
@@ -401,7 +413,7 @@ async function searchWithProviders(
|
|
|
401
413
|
: await isResolvedProviderAvailable(provider, options),
|
|
402
414
|
})))).filter((entry) => entry.available).map((entry) => entry.provider);
|
|
403
415
|
if (providers.length === 0) {
|
|
404
|
-
throw new Error("No configured search provider available for provider \"all\". AnySearch, xAI, Bright Data, and
|
|
416
|
+
throw new Error("No configured search provider available for provider \"all\". Parallel MCP, AnySearch, xAI, Bright Data, SerpBase, Serper, and Valyu are excluded.");
|
|
405
417
|
}
|
|
406
418
|
|
|
407
419
|
const settled = await Promise.allSettled(
|
|
@@ -693,7 +705,7 @@ export async function search(query: string, options: FullSearchOptions = {}): Pr
|
|
|
693
705
|
" 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, FIRECRAWL_BASE_URL, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\n" +
|
|
694
706
|
" 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\n" +
|
|
695
707
|
" 5. Sign into gemini.google.com in a supported Chromium-based browser\n" +
|
|
696
|
-
" 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xai\" for Grok, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP,
|
|
708
|
+
" 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xai\" for Grok, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP, \"serpbase\" or \"serper\" for Google SERP, or \"valyu\" for research search"
|
|
697
709
|
);
|
|
698
710
|
}
|
|
699
711
|
|