@herbertgao/pi-extensions 2026.8.5 → 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 +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- 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/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- 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 +16 -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 +22 -15
- 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 +34 -10
- package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -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 +8 -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 +119 -103
- 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 +13 -9
- package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
- 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 +78 -15
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
- package/node_modules/pi-lens/CHANGELOG.md +1004 -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 +427 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
- 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 +176 -37
- 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/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
- package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
- 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/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
- 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 +43 -9
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
- 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 +81 -41
- 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 +488 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
- 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 +943 -93
- 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 +106 -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/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
- package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
- 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/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- 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 +49 -21
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
- package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
- package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
- 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 +86 -15
- package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
- 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 +487 -9
- 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-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
- package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
- 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 +517 -69
- 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 +172 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
- 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/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- 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 +178 -30
- 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 +85 -15
- 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/runner-findings.js +23 -2
- 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 +76 -4
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +248 -13
- package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
- package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- 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 +224 -8
- package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
- package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
- package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
- package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- 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/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
- package/node_modules/pi-lens/dist/index.js +64677 -49484
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +44 -14
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
- package/node_modules/pi-lens/docs/dispositions.md +21 -0
- package/node_modules/pi-lens/docs/features.md +33 -6
- package/node_modules/pi-lens/docs/globalconfig.md +14 -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 +25 -1
- package/node_modules/pi-lens/docs/usage.md +24 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -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 +22 -0
- 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-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- 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 +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
- 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-laundering-test.yml +11 -0
- 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 +66 -0
- 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-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- 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 +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
- 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 +32 -0
- 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 +132 -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 +12 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
- package/node_modules/pi-mcp-adapter/README.md +51 -6
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +188 -5
- package/node_modules/pi-mcp-adapter/commands.ts +128 -22
- package/node_modules/pi-mcp-adapter/config.ts +28 -10
- package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
- 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/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
- package/node_modules/pi-mcp-adapter/package.json +4 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
- package/node_modules/pi-mcp-adapter/server-manager.ts +145 -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-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
- package/node_modules/pi-mcp-adapter/types.ts +95 -10
- 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 +37 -0
- package/node_modules/pi-web-access/README.md +38 -14
- package/node_modules/pi-web-access/anysearch.ts +4 -2
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/brave.ts +16 -4
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +8 -3
- package/node_modules/pi-web-access/curator-server.ts +5 -1
- package/node_modules/pi-web-access/exa.ts +18 -7
- package/node_modules/pi-web-access/extract.ts +134 -37
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +33 -7
- package/node_modules/pi-web-access/index.ts +160 -104
- package/node_modules/pi-web-access/package.json +2 -2
- 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 +51 -23
- 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/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +13 -13
|
@@ -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";
|
|
@@ -21,9 +22,11 @@ import { extractWithFirecrawl, isFirecrawlAvailable } from "./firecrawl.ts";
|
|
|
21
22
|
import { extractWithBrightDataUnlocker, isBrightDataUnlockerAvailable } from "./brightdata-unlocker.ts";
|
|
22
23
|
import { isVideoFile, extractVideo, extractVideoFrame, getLocalVideoDuration } from "./video-extract.ts";
|
|
23
24
|
import { appendDeclaredWebLinks, discoverDeclaredWebLinks, type DeclaredWebLink } from "./declared-web-links.ts";
|
|
24
|
-
import { fetchRemoteUrl, loadFetchContentDomainPolicy, loadSsrfConfig, validateRemoteUrl, type Lookup } from "./ssrf-protection.ts";
|
|
25
|
+
import { fetchRemoteUrl, loadFetchContentDomainPolicy, loadSsrfConfig, validateRemoteUrl, type DomainPolicy, type Lookup, type SsrfConfig } from "./ssrf-protection.ts";
|
|
25
26
|
import { formatSeconds, getWebSearchConfigPath } from "./utils.ts";
|
|
26
27
|
import { isImageEnabled } from "./feature-config.ts";
|
|
28
|
+
import { assertAuthFetchUrl, authFetchRedirectGuard, type AuthFetchProfile } from "./auth-fetch.ts";
|
|
29
|
+
import { getBrowserCookiesForHosts, getLastBrowserCookieDiagnostic } from "./chrome-cookies.ts";
|
|
27
30
|
|
|
28
31
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
29
32
|
const CONCURRENT_LIMIT = 3;
|
|
@@ -32,11 +35,11 @@ const NON_RECOVERABLE_ERRORS = ["Unsupported content type", "Response too large"
|
|
|
32
35
|
const MIN_USEFUL_CONTENT = 500;
|
|
33
36
|
const SUPPORTED_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
|
|
34
37
|
const WEB_SEARCH_CONFIG_PATH = getWebSearchConfigPath();
|
|
35
|
-
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;
|
|
36
39
|
type FetchProvider = typeof FETCH_PROVIDERS[number];
|
|
37
40
|
type FetchRouting = { providers: FetchProvider[]; allowRemoteHostedProviders: boolean };
|
|
38
41
|
const DEFAULT_FETCH_PROVIDER_ORDER: FetchProvider[] = ["http", "firecrawl", "jina", "tinyfish", "search1api", "querit", "kagi", "ollama", "parallel", "brightdata", "gemini"];
|
|
39
|
-
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"]);
|
|
40
43
|
|
|
41
44
|
export { loadSsrfConfig } from "./ssrf-protection.ts";
|
|
42
45
|
|
|
@@ -57,7 +60,8 @@ function isAbortError(err: unknown): boolean {
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
function isRedirectPolicyError(message: string): boolean {
|
|
60
|
-
return message.startsWith("
|
|
63
|
+
return message.startsWith("Authenticated fetch refused cross-origin redirect") ||
|
|
64
|
+
message.startsWith("Blocked internal ") ||
|
|
61
65
|
message.startsWith("Blocked hostname by fetch_content domain policy") ||
|
|
62
66
|
message.startsWith("Hostname not allowed by fetch_content domain policy") ||
|
|
63
67
|
message.startsWith("Too many redirects fetching ") ||
|
|
@@ -74,6 +78,56 @@ function imageGateError(): string | null {
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
async function resolveAuthCookieHeader(url: string | URL, profile: AuthFetchProfile): Promise<string> {
|
|
82
|
+
const parsed = assertAuthFetchUrl(profile, url.toString());
|
|
83
|
+
const result = await getBrowserCookiesForHosts({ hosts: [parsed.hostname], profile: profile.chromeProfile, requestUrl: parsed });
|
|
84
|
+
if (result?.cookieHeader) return result.cookieHeader;
|
|
85
|
+
if (!result) {
|
|
86
|
+
const diagnostic = getLastBrowserCookieDiagnostic();
|
|
87
|
+
throw new Error(`Authenticated fetch profile ${profile.name} could not read browser cookies${diagnostic ? `: ${diagnostic}` : ""}`);
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Authenticated fetch profile ${profile.name} could not build a cookie header`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
93
|
+
|
|
94
|
+
async function fetchAuthenticatedRemoteUrl(
|
|
95
|
+
url: string,
|
|
96
|
+
init: RequestInit,
|
|
97
|
+
validationOptions: { ssrf: SsrfConfig; domainPolicy: DomainPolicy; lookup?: Lookup },
|
|
98
|
+
profile: AuthFetchProfile,
|
|
99
|
+
): Promise<Response> {
|
|
100
|
+
let current = await validateRemoteUrl(url, {
|
|
101
|
+
allowRanges: validationOptions.ssrf.allowRanges,
|
|
102
|
+
trustEnvProxy: validationOptions.ssrf.trustEnvProxy,
|
|
103
|
+
domainPolicy: validationOptions.domainPolicy,
|
|
104
|
+
...(validationOptions.lookup ? { lookup: validationOptions.lookup } : {}),
|
|
105
|
+
});
|
|
106
|
+
let requestInit = init;
|
|
107
|
+
for (let redirects = 0; redirects <= 5; redirects++) {
|
|
108
|
+
const cookieHeader = await resolveAuthCookieHeader(current, profile);
|
|
109
|
+
const headers = { ...(requestInit.headers as Record<string, string>), cookie: cookieHeader };
|
|
110
|
+
const response = await fetch(current, { ...requestInit, headers, redirect: "manual" });
|
|
111
|
+
if (!REDIRECT_STATUSES.has(response.status)) return response;
|
|
112
|
+
const location = response.headers.get("location");
|
|
113
|
+
if (!location) return response;
|
|
114
|
+
if (redirects === 5) throw new Error(`Too many redirects fetching ${current.toString()}`);
|
|
115
|
+
const from = current;
|
|
116
|
+
current = await validateRemoteUrl(new URL(location, current), {
|
|
117
|
+
allowRanges: validationOptions.ssrf.allowRanges,
|
|
118
|
+
trustEnvProxy: validationOptions.ssrf.trustEnvProxy,
|
|
119
|
+
domainPolicy: validationOptions.domainPolicy,
|
|
120
|
+
...(validationOptions.lookup ? { lookup: validationOptions.lookup } : {}),
|
|
121
|
+
});
|
|
122
|
+
authFetchRedirectGuard(profile, from, current);
|
|
123
|
+
if (response.status === 303 || ((response.status === 301 || response.status === 302) && requestInit.method?.toUpperCase() === "POST")) {
|
|
124
|
+
const { body: _body, ...nextInit } = requestInit;
|
|
125
|
+
requestInit = { ...nextInit, method: "GET" };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
throw new Error(`Too many redirects fetching ${current.toString()}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
77
131
|
function loadFetchRouting(): FetchRouting {
|
|
78
132
|
if (!existsSync(WEB_SEARCH_CONFIG_PATH)) {
|
|
79
133
|
return { providers: DEFAULT_FETCH_PROVIDER_ORDER, allowRemoteHostedProviders: false };
|
|
@@ -171,6 +225,7 @@ export interface ExtractOptions {
|
|
|
171
225
|
model?: string;
|
|
172
226
|
mode?: "readable" | "raw" | "answer";
|
|
173
227
|
answerModel?: string;
|
|
228
|
+
authFetchProfile?: AuthFetchProfile;
|
|
174
229
|
/** Custom DNS resolver used for SSRF validation. Primarily a test seam. */
|
|
175
230
|
lookup?: Lookup;
|
|
176
231
|
}
|
|
@@ -313,11 +368,17 @@ async function extractLocalFrames(
|
|
|
313
368
|
return { frames, error: frames.length === 0 && firstError ? firstError.error : null };
|
|
314
369
|
}
|
|
315
370
|
|
|
316
|
-
|
|
371
|
+
type LocalVideoInfoResult =
|
|
372
|
+
| { status: "video"; info: NonNullable<ReturnType<typeof isVideoFile>> }
|
|
373
|
+
| { status: "not-video" }
|
|
374
|
+
| { status: "invalid"; error: string };
|
|
375
|
+
|
|
376
|
+
function safeVideoInfo(url: string): LocalVideoInfoResult {
|
|
317
377
|
try {
|
|
318
|
-
|
|
378
|
+
const info = isVideoFile(url);
|
|
379
|
+
return info ? { status: "video", info } : { status: "not-video" };
|
|
319
380
|
} catch (err) {
|
|
320
|
-
return {
|
|
381
|
+
return { status: "invalid", error: errorMessage(err) };
|
|
321
382
|
}
|
|
322
383
|
}
|
|
323
384
|
|
|
@@ -351,6 +412,14 @@ export async function extractContent(
|
|
|
351
412
|
}
|
|
352
413
|
}
|
|
353
414
|
|
|
415
|
+
if (options?.authFetchProfile) {
|
|
416
|
+
try {
|
|
417
|
+
return await extractViaHttp(url, signal, options);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
return { url, title: "", content: "", error: errorMessage(err) };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
354
423
|
if (options?.mode === "raw") {
|
|
355
424
|
return extractViaHttp(url, signal, options);
|
|
356
425
|
}
|
|
@@ -380,10 +449,10 @@ export async function extractContent(
|
|
|
380
449
|
}
|
|
381
450
|
|
|
382
451
|
const localVideo = safeVideoInfo(url);
|
|
383
|
-
if (localVideo.
|
|
452
|
+
if (localVideo.status === "invalid") {
|
|
384
453
|
return { url, title: "", content: "", error: localVideo.error };
|
|
385
454
|
}
|
|
386
|
-
if (localVideo.
|
|
455
|
+
if (localVideo.status === "video") {
|
|
387
456
|
const durationResult = await getLocalVideoDuration(localVideo.info.absolutePath);
|
|
388
457
|
if (typeof durationResult !== "number") {
|
|
389
458
|
return { url, title: "Frames", content: durationResult.error, error: durationResult.error };
|
|
@@ -463,10 +532,10 @@ export async function extractContent(
|
|
|
463
532
|
}
|
|
464
533
|
|
|
465
534
|
const localVideo = safeVideoInfo(url);
|
|
466
|
-
if (localVideo.
|
|
535
|
+
if (localVideo.status === "invalid") {
|
|
467
536
|
return { url, title: "", content: "", error: localVideo.error };
|
|
468
537
|
}
|
|
469
|
-
if (localVideo.
|
|
538
|
+
if (localVideo.status === "video") {
|
|
470
539
|
if (spec.type === "range") {
|
|
471
540
|
const timestamps = frameCount
|
|
472
541
|
? computeRangeTimestamps(spec.start, spec.end, frameCount)
|
|
@@ -495,10 +564,10 @@ export async function extractContent(
|
|
|
495
564
|
}
|
|
496
565
|
|
|
497
566
|
const localVideo = safeVideoInfo(url);
|
|
498
|
-
if (localVideo.
|
|
567
|
+
if (localVideo.status === "invalid") {
|
|
499
568
|
return { url, title: "", content: "", error: localVideo.error };
|
|
500
569
|
}
|
|
501
|
-
if (localVideo.
|
|
570
|
+
if (localVideo.status === "video") {
|
|
502
571
|
try {
|
|
503
572
|
const result = await extractVideo(localVideo.info, signal, options);
|
|
504
573
|
if (signal?.aborted) return abortedResult(url);
|
|
@@ -548,7 +617,7 @@ export async function extractContent(
|
|
|
548
617
|
url,
|
|
549
618
|
title: "",
|
|
550
619
|
content: "",
|
|
551
|
-
error: "Could not extract YouTube video content. Sign into Google in
|
|
620
|
+
error: "Could not extract YouTube video content. Sign into Google in a supported Chromium browser for automatic access, or set GEMINI_API_KEY.",
|
|
552
621
|
};
|
|
553
622
|
}
|
|
554
623
|
|
|
@@ -600,6 +669,7 @@ export async function extractContent(
|
|
|
600
669
|
let kagiError: string | null = null;
|
|
601
670
|
let ollamaError: string | null = null;
|
|
602
671
|
let parallelError: string | null = null;
|
|
672
|
+
let parallelMcpError: string | null = null;
|
|
603
673
|
let brightdataError: string | null = null;
|
|
604
674
|
|
|
605
675
|
if (remoteUrl && providerOrder[0] !== "http") {
|
|
@@ -735,6 +805,18 @@ export async function extractContent(
|
|
|
735
805
|
continue;
|
|
736
806
|
}
|
|
737
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
|
+
|
|
738
820
|
if (provider === "brightdata") {
|
|
739
821
|
try {
|
|
740
822
|
if (isBrightDataUnlockerAvailable()) {
|
|
@@ -782,6 +864,7 @@ export async function extractContent(
|
|
|
782
864
|
...(kagiError ? [`Kagi fallback failed: ${kagiError}`] : []),
|
|
783
865
|
...(ollamaError ? [`Ollama fallback failed: ${ollamaError}`] : []),
|
|
784
866
|
...(parallelError ? [`Parallel fallback failed: ${parallelError}`] : []),
|
|
867
|
+
...(parallelMcpError ? [`Parallel MCP fallback failed: ${parallelMcpError}`] : []),
|
|
785
868
|
...(brightdataError ? [`Bright Data fallback failed: ${brightdataError}`] : []),
|
|
786
869
|
"",
|
|
787
870
|
"Fallback options:",
|
|
@@ -914,29 +997,33 @@ async function extractViaHttp(
|
|
|
914
997
|
try {
|
|
915
998
|
const ssrf = loadSsrfConfig();
|
|
916
999
|
const domainPolicy = loadFetchContentDomainPolicy();
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
"Upgrade-Insecure-Requests": "1",
|
|
931
|
-
},
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
allowRanges: ssrf.allowRanges,
|
|
935
|
-
trustEnvProxy: ssrf.trustEnvProxy,
|
|
936
|
-
domainPolicy,
|
|
937
|
-
...(options?.lookup ? { lookup: options.lookup } : {}),
|
|
1000
|
+
const authProfile = options?.authFetchProfile;
|
|
1001
|
+
const requestInit = {
|
|
1002
|
+
signal: controller.signal,
|
|
1003
|
+
headers: {
|
|
1004
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
|
1005
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
1006
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
1007
|
+
"Cache-Control": "no-cache",
|
|
1008
|
+
"Sec-Fetch-Dest": "document",
|
|
1009
|
+
"Sec-Fetch-Mode": "navigate",
|
|
1010
|
+
"Sec-Fetch-Site": "none",
|
|
1011
|
+
"Sec-Fetch-User": "?1",
|
|
1012
|
+
"Upgrade-Insecure-Requests": "1",
|
|
938
1013
|
},
|
|
939
|
-
|
|
1014
|
+
};
|
|
1015
|
+
const response = authProfile
|
|
1016
|
+
? await fetchAuthenticatedRemoteUrl(url, requestInit, { ssrf, domainPolicy, ...(options?.lookup ? { lookup: options.lookup } : {}) }, authProfile)
|
|
1017
|
+
: await fetchRemoteUrl(
|
|
1018
|
+
url,
|
|
1019
|
+
requestInit,
|
|
1020
|
+
{
|
|
1021
|
+
allowRanges: ssrf.allowRanges,
|
|
1022
|
+
trustEnvProxy: ssrf.trustEnvProxy,
|
|
1023
|
+
domainPolicy,
|
|
1024
|
+
...(options?.lookup ? { lookup: options.lookup } : {}),
|
|
1025
|
+
},
|
|
1026
|
+
);
|
|
940
1027
|
|
|
941
1028
|
if (!response.ok && options?.mode !== "raw") {
|
|
942
1029
|
activityMonitor.logComplete(activityId, response.status);
|
|
@@ -1072,7 +1159,7 @@ async function extractViaHttp(
|
|
|
1072
1159
|
|
|
1073
1160
|
if (!article) {
|
|
1074
1161
|
const rscResult = extractRSCContent(text);
|
|
1075
|
-
if (rscResult) {
|
|
1162
|
+
if (rscResult && rscResult.content.length >= MIN_USEFUL_CONTENT) {
|
|
1076
1163
|
activityMonitor.logComplete(activityId, response.status);
|
|
1077
1164
|
return {
|
|
1078
1165
|
url,
|
|
@@ -1105,6 +1192,16 @@ async function extractViaHttp(
|
|
|
1105
1192
|
activityMonitor.logComplete(activityId, response.status);
|
|
1106
1193
|
|
|
1107
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
|
+
}
|
|
1108
1205
|
return {
|
|
1109
1206
|
url,
|
|
1110
1207
|
title: article.title || documentTitle,
|
|
@@ -8,6 +8,7 @@ export interface FetchContentParams {
|
|
|
8
8
|
model?: unknown;
|
|
9
9
|
mode?: unknown;
|
|
10
10
|
answerModel?: unknown;
|
|
11
|
+
auth?: unknown;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface NormalizedFetchContentParams {
|
|
@@ -20,6 +21,7 @@ export interface NormalizedFetchContentParams {
|
|
|
20
21
|
model?: string;
|
|
21
22
|
mode?: "readable" | "raw" | "answer";
|
|
22
23
|
answerModel?: string;
|
|
24
|
+
auth?: true | string;
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -28,7 +30,7 @@ export function normalizeFetchContentParams(params: FetchContentParams): Normali
|
|
|
28
30
|
const urlList = normalizedUrls.length > 0 ? normalizedUrls : normalizeSingleUrl(params.url);
|
|
29
31
|
const prompt = normalizeOptionalString(params.prompt);
|
|
30
32
|
const timestamp = normalizeOptionalString(params.timestamp);
|
|
31
|
-
const frames =
|
|
33
|
+
const frames = normalizeOptionalFrameCount(params.frames);
|
|
32
34
|
|
|
33
35
|
const shouldIncludeFrames = frames !== undefined && (timestamp !== undefined || frames > 1);
|
|
34
36
|
|
|
@@ -36,6 +38,7 @@ export function normalizeFetchContentParams(params: FetchContentParams): Normali
|
|
|
36
38
|
const model = normalizeOptionalString(params.model);
|
|
37
39
|
const mode = normalizeMode(params.mode);
|
|
38
40
|
const answerModel = normalizeOptionalString(params.answerModel);
|
|
41
|
+
const auth = normalizeAuth(params.auth);
|
|
39
42
|
|
|
40
43
|
return {
|
|
41
44
|
urlList,
|
|
@@ -47,6 +50,7 @@ export function normalizeFetchContentParams(params: FetchContentParams): Normali
|
|
|
47
50
|
...(model !== undefined ? { model } : {}),
|
|
48
51
|
...(mode !== undefined ? { mode } : {}),
|
|
49
52
|
...(answerModel !== undefined ? { answerModel } : {}),
|
|
53
|
+
...(auth !== undefined ? { auth } : {}),
|
|
50
54
|
},
|
|
51
55
|
};
|
|
52
56
|
}
|
|
@@ -74,8 +78,18 @@ function normalizeMode(value: unknown): "readable" | "raw" | "answer" | undefine
|
|
|
74
78
|
throw new Error('mode must be "readable", "raw", or "answer"');
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
function
|
|
78
|
-
if (
|
|
81
|
+
function normalizeAuth(value: unknown): true | string | undefined {
|
|
82
|
+
if (value === undefined || value === false) return undefined;
|
|
83
|
+
if (value === true) return true;
|
|
84
|
+
if (typeof value === "string") {
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
if (trimmed) return trimmed;
|
|
87
|
+
}
|
|
88
|
+
throw new Error("auth must be a profile name, true, or false");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeOptionalFrameCount(value: unknown): number | undefined {
|
|
92
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 12) return undefined;
|
|
79
93
|
return value;
|
|
80
94
|
}
|
|
81
95
|
|
|
@@ -2,12 +2,14 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
3
|
import { redactCredential, resolveCredential } from "./credential-source.ts";
|
|
4
4
|
import type { ExtractedContent, ExtractOptions } from "./extract.ts";
|
|
5
|
-
import {
|
|
5
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
6
|
+
import { loadSsrfConfig, validateRemoteUrl, type Lookup } from "./ssrf-protection.ts";
|
|
6
7
|
import { getWebSearchConfigPath } from "./utils.ts";
|
|
7
8
|
|
|
8
9
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
9
10
|
const DEFAULT_API_VERSION = "v2";
|
|
10
11
|
const EXTRACT_TIMEOUT_MS = 60_000;
|
|
12
|
+
const SEARCH_TIMEOUT_MS = 60_000;
|
|
11
13
|
const DEFAULT_MAX_REDIRECTS = 5;
|
|
12
14
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
13
15
|
const SUPPORTED_API_VERSIONS = ["v1", "v2"] as const;
|
|
@@ -22,6 +24,11 @@ export interface FirecrawlExtractOptions extends Pick<ExtractOptions, "timeoutMs
|
|
|
22
24
|
ssrf?: FirecrawlSsrfOptions;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
interface FirecrawlSearchOptions extends SearchOptions, Pick<ExtractOptions, "timeoutMs" | "lookup"> {
|
|
28
|
+
includeContent?: boolean;
|
|
29
|
+
ssrf?: FirecrawlSsrfOptions;
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
interface FirecrawlConfig {
|
|
26
33
|
firecrawlBaseUrl?: unknown;
|
|
27
34
|
firecrawlApiKey?: unknown;
|
|
@@ -35,6 +42,25 @@ interface FirecrawlScrapeData {
|
|
|
35
42
|
metadata?: { title?: unknown };
|
|
36
43
|
}
|
|
37
44
|
|
|
45
|
+
interface FirecrawlSearchItem {
|
|
46
|
+
title?: unknown;
|
|
47
|
+
description?: unknown;
|
|
48
|
+
snippet?: unknown;
|
|
49
|
+
url?: unknown;
|
|
50
|
+
markdown?: unknown;
|
|
51
|
+
metadata?: {
|
|
52
|
+
title?: unknown;
|
|
53
|
+
description?: unknown;
|
|
54
|
+
sourceURL?: unknown;
|
|
55
|
+
url?: unknown;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface DomainFilters {
|
|
60
|
+
include: string[];
|
|
61
|
+
exclude: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
38
64
|
let cachedConfig: FirecrawlConfig | null = null;
|
|
39
65
|
|
|
40
66
|
function loadConfig(): FirecrawlConfig {
|
|
@@ -149,10 +175,11 @@ function isAbortError(err: unknown): boolean {
|
|
|
149
175
|
return errorMessage(err).toLowerCase().includes("abort");
|
|
150
176
|
}
|
|
151
177
|
|
|
152
|
-
function ssrfOptions(options?: FirecrawlExtractOptions): { lookup?: Lookup; allowRanges: string[]; trustEnvProxy: boolean } {
|
|
178
|
+
function ssrfOptions(options?: FirecrawlExtractOptions | FirecrawlSearchOptions): { lookup?: Lookup; allowRanges: string[]; trustEnvProxy: boolean } {
|
|
179
|
+
const config = loadSsrfConfig();
|
|
153
180
|
return {
|
|
154
|
-
allowRanges: options?.ssrf?.allowRanges ??
|
|
155
|
-
trustEnvProxy: options?.ssrf?.trustEnvProxy ??
|
|
181
|
+
allowRanges: options?.ssrf?.allowRanges ?? config.allowRanges,
|
|
182
|
+
trustEnvProxy: options?.ssrf?.trustEnvProxy ?? config.trustEnvProxy,
|
|
156
183
|
...(options?.lookup ? { lookup: options.lookup } : {}),
|
|
157
184
|
};
|
|
158
185
|
}
|
|
@@ -171,7 +198,7 @@ function withoutSensitiveHeaders(headers: Record<string, string>): Record<string
|
|
|
171
198
|
async function fetchFirecrawlApi(
|
|
172
199
|
url: string,
|
|
173
200
|
init: { method: string; headers: Record<string, string>; body: string; signal: AbortSignal },
|
|
174
|
-
options: FirecrawlExtractOptions | undefined,
|
|
201
|
+
options: FirecrawlExtractOptions | FirecrawlSearchOptions | undefined,
|
|
175
202
|
): Promise<Response> {
|
|
176
203
|
let current = await validateRemoteUrl(url, ssrfOptions(options));
|
|
177
204
|
let headers = init.headers;
|
|
@@ -199,11 +226,126 @@ function scrapeBody(url: string): Record<string, unknown> {
|
|
|
199
226
|
};
|
|
200
227
|
}
|
|
201
228
|
|
|
229
|
+
function normalizeCount(value: number | undefined): number {
|
|
230
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return 5;
|
|
231
|
+
return Math.max(1, Math.min(Math.floor(value), 20));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function normalizeDomain(value: string): string | null {
|
|
235
|
+
let input = value.trim().toLowerCase();
|
|
236
|
+
if (!input) return null;
|
|
237
|
+
if (input.startsWith("-")) input = input.slice(1).trim();
|
|
238
|
+
if (!input) return null;
|
|
239
|
+
try {
|
|
240
|
+
const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
|
|
241
|
+
input = parsed.hostname;
|
|
242
|
+
} catch {
|
|
243
|
+
input = input.split("/")[0]?.split(":")[0] ?? "";
|
|
244
|
+
}
|
|
245
|
+
input = input.replace(/^\.+|\.+$/g, "");
|
|
246
|
+
return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
250
|
+
const filters: DomainFilters = { include: [], exclude: [] };
|
|
251
|
+
for (const raw of domainFilter ?? []) {
|
|
252
|
+
const domain = normalizeDomain(raw);
|
|
253
|
+
if (!domain) continue;
|
|
254
|
+
const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
|
|
255
|
+
if (!target.includes(domain)) target.push(domain);
|
|
256
|
+
}
|
|
257
|
+
return filters;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function passesDomainFilters(url: string, filters: DomainFilters): boolean {
|
|
261
|
+
if (filters.include.length === 0 && filters.exclude.length === 0) return true;
|
|
262
|
+
let hostname: string;
|
|
263
|
+
try {
|
|
264
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
|
|
269
|
+
if (filters.exclude.some(matches)) return false;
|
|
270
|
+
return filters.include.length === 0 || filters.include.some(matches);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function mapRecencyFilter(value: SearchOptions["recencyFilter"]): string | undefined {
|
|
274
|
+
switch (value) {
|
|
275
|
+
case "day": return "qdr:d";
|
|
276
|
+
case "week": return "qdr:w";
|
|
277
|
+
case "month": return "qdr:m";
|
|
278
|
+
case "year": return "qdr:y";
|
|
279
|
+
default: return undefined;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function searchBody(query: string, options: FirecrawlSearchOptions, numResults: number, filters: DomainFilters): Record<string, unknown> {
|
|
284
|
+
return {
|
|
285
|
+
query,
|
|
286
|
+
limit: numResults,
|
|
287
|
+
sources: ["web"],
|
|
288
|
+
...(filters.include.length > 0 ? { includeDomains: filters.include } : {}),
|
|
289
|
+
...(filters.include.length === 0 && filters.exclude.length > 0 ? { excludeDomains: filters.exclude } : {}),
|
|
290
|
+
...(mapRecencyFilter(options.recencyFilter) ? { tbs: mapRecencyFilter(options.recencyFilter) } : {}),
|
|
291
|
+
...(options.includeContent ? {
|
|
292
|
+
scrapeOptions: {
|
|
293
|
+
formats: ["markdown"],
|
|
294
|
+
onlyMainContent: true,
|
|
295
|
+
...(allowFreshScrape() ? {} : { lockdown: true }),
|
|
296
|
+
},
|
|
297
|
+
} : {}),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function firstString(...values: unknown[]): string | null {
|
|
302
|
+
for (const value of values) {
|
|
303
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function buildAnswer(results: SearchResponse["results"]): string {
|
|
309
|
+
return results.map((result) => {
|
|
310
|
+
if (result.snippet) return `${result.snippet}\nSource: ${result.title} (${result.url})`;
|
|
311
|
+
return `Source: ${result.title} (${result.url})`;
|
|
312
|
+
}).join("\n\n");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function mapSearchResults(data: unknown, numResults: number, filters: DomainFilters): {
|
|
316
|
+
results: SearchResponse["results"];
|
|
317
|
+
inlineContent: ExtractedContent[];
|
|
318
|
+
} {
|
|
319
|
+
const web = Array.isArray(data)
|
|
320
|
+
? data
|
|
321
|
+
: data && typeof data === "object" ? (data as Record<string, unknown>).web : undefined;
|
|
322
|
+
if (!Array.isArray(web)) throw new Error("Firecrawl search returned an unexpected web result shape");
|
|
323
|
+
const results: SearchResponse["results"] = [];
|
|
324
|
+
const inlineContent: ExtractedContent[] = [];
|
|
325
|
+
const seen = new Set<string>();
|
|
326
|
+
for (const rawItem of web) {
|
|
327
|
+
if (!rawItem || typeof rawItem !== "object" || Array.isArray(rawItem)) continue;
|
|
328
|
+
const item = rawItem as FirecrawlSearchItem;
|
|
329
|
+
const url = firstString(item.url, item.metadata?.sourceURL, item.metadata?.url);
|
|
330
|
+
if (!url || seen.has(url) || !passesDomainFilters(url, filters)) continue;
|
|
331
|
+
seen.add(url);
|
|
332
|
+
const title = firstString(item.title, item.metadata?.title) ?? url;
|
|
333
|
+
const snippet = firstString(item.description, item.snippet, item.metadata?.description) ?? "";
|
|
334
|
+
results.push({ title, url, snippet });
|
|
335
|
+
const markdown = firstString(item.markdown);
|
|
336
|
+
if (markdown) inlineContent.push({ url, title, content: markdown, error: null });
|
|
337
|
+
if (results.length >= numResults) break;
|
|
338
|
+
}
|
|
339
|
+
return { results, inlineContent };
|
|
340
|
+
}
|
|
341
|
+
|
|
202
342
|
async function firecrawlFetch(
|
|
203
343
|
endpoint: string,
|
|
204
344
|
body: Record<string, unknown>,
|
|
205
345
|
signal: AbortSignal | undefined,
|
|
206
|
-
options: FirecrawlExtractOptions | undefined,
|
|
346
|
+
options: FirecrawlExtractOptions | FirecrawlSearchOptions | undefined,
|
|
347
|
+
label = endpoint,
|
|
348
|
+
activity: { type: "api"; query: string } | { type: "fetch"; url: string } | undefined = undefined,
|
|
207
349
|
): Promise<Record<string, unknown>> {
|
|
208
350
|
const baseUrl = requireBaseUrl();
|
|
209
351
|
const version = getApiVersion();
|
|
@@ -211,7 +353,7 @@ async function firecrawlFetch(
|
|
|
211
353
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
212
354
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
213
355
|
const requestUrl = `${baseUrl}/${version}/${endpoint}`;
|
|
214
|
-
const activityId = activityMonitor.logStart({ type: "fetch", url: requestUrl });
|
|
356
|
+
const activityId = activityMonitor.logStart(activity ?? { type: "fetch", url: requestUrl });
|
|
215
357
|
try {
|
|
216
358
|
const response = await fetchFirecrawlApi(requestUrl, {
|
|
217
359
|
method: "POST",
|
|
@@ -221,24 +363,24 @@ async function firecrawlFetch(
|
|
|
221
363
|
}, options);
|
|
222
364
|
if (!response.ok) {
|
|
223
365
|
const text = await response.text().catch(() => "");
|
|
224
|
-
throw new Error(`Firecrawl
|
|
366
|
+
throw new Error(`Firecrawl ${label} error ${response.status}: ${redactCredential(text.slice(0, 300), apiKey)}`);
|
|
225
367
|
}
|
|
226
368
|
let data: unknown;
|
|
227
369
|
try {
|
|
228
370
|
data = await response.json();
|
|
229
371
|
} catch (err) {
|
|
230
|
-
throw new Error(`Firecrawl
|
|
372
|
+
throw new Error(`Firecrawl ${label} returned invalid JSON: ${errorMessage(err)}`);
|
|
231
373
|
}
|
|
232
374
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
233
|
-
throw new Error(
|
|
375
|
+
throw new Error(`Firecrawl ${label} returned an unexpected response shape`);
|
|
234
376
|
}
|
|
235
377
|
const envelope = data as Record<string, unknown>;
|
|
236
378
|
if (envelope.success === false) {
|
|
237
379
|
const reason = typeof envelope.error === "string" && envelope.error.trim() ? envelope.error : "unknown error";
|
|
238
|
-
throw new Error(`Firecrawl
|
|
380
|
+
throw new Error(`Firecrawl ${label} unsuccessful: ${redactCredential(reason, apiKey)}`);
|
|
239
381
|
}
|
|
240
382
|
if (envelope.success !== true) {
|
|
241
|
-
throw new Error(
|
|
383
|
+
throw new Error(`Firecrawl ${label} returned an unexpected response shape`);
|
|
242
384
|
}
|
|
243
385
|
activityMonitor.logComplete(activityId, response.status);
|
|
244
386
|
return envelope;
|
|
@@ -253,6 +395,24 @@ export function isFirecrawlAvailable(): boolean {
|
|
|
253
395
|
return getBaseUrl() !== null;
|
|
254
396
|
}
|
|
255
397
|
|
|
398
|
+
export async function searchWithFirecrawl(query: string, options: FirecrawlSearchOptions = {}): Promise<SearchResponse> {
|
|
399
|
+
requireBaseUrl();
|
|
400
|
+
const numResults = normalizeCount(options.numResults);
|
|
401
|
+
const filters = parseDomainFilter(options.domainFilter);
|
|
402
|
+
const envelope = await firecrawlFetch(
|
|
403
|
+
"search",
|
|
404
|
+
searchBody(query, options, numResults, filters),
|
|
405
|
+
options.signal,
|
|
406
|
+
{ ...options, timeoutMs: options.timeoutMs ?? SEARCH_TIMEOUT_MS },
|
|
407
|
+
"search",
|
|
408
|
+
{ type: "api", query },
|
|
409
|
+
);
|
|
410
|
+
const mapped = mapSearchResults(envelope.data, numResults, filters);
|
|
411
|
+
const response: SearchResponse = { answer: buildAnswer(mapped.results), results: mapped.results };
|
|
412
|
+
if (options.includeContent && mapped.inlineContent.length > 0) response.inlineContent = mapped.inlineContent;
|
|
413
|
+
return response;
|
|
414
|
+
}
|
|
415
|
+
|
|
256
416
|
export async function extractWithFirecrawl(
|
|
257
417
|
url: string,
|
|
258
418
|
signal?: AbortSignal,
|