@herbertgao/pi-extensions 2026.9.20 → 2026.10.1
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 +18 -13
- package/THIRD_PARTY_NOTICES.md +31 -5
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/index.js +4 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +43 -3
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +142 -59
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
- package/node_modules/@dietrichgebert/ponytail/README.es.md +87 -204
- package/node_modules/@dietrichgebert/ponytail/README.ja.md +212 -0
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +92 -226
- package/node_modules/@dietrichgebert/ponytail/README.md +58 -244
- package/node_modules/@dietrichgebert/ponytail/README.zh-CN.md +212 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +63 -16
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +8 -7
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +46 -23
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +39 -7
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +20 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/package.json +2 -2
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +47 -17
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -7
- package/node_modules/@dietrichgebert/ponytail/skills/plugin.json +5 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +7 -2
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +16 -14
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +3 -3
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +12 -7
- package/node_modules/@herbertgao/pi-bark/README.md +39 -3
- package/node_modules/@herbertgao/pi-bark/package.json +1 -1
- package/node_modules/@herbertgao/pi-bark/src/index.ts +275 -15
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +50 -50
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +7 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +25 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +24 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +47 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/mcp-chip.ts +136 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +56 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +36 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +11 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +303 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +36 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +88 -34
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +6 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +31 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/message-display.ts +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +3 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +173 -36
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +6 -4
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +3 -2
- package/node_modules/@herbertgao/sol-pi/CONTRIBUTING.md +22 -0
- package/node_modules/@herbertgao/sol-pi/README.md +36 -17
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +10 -6
- package/node_modules/@herbertgao/sol-pi/agents-install.md +15 -12
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +24 -8
- package/node_modules/@herbertgao/sol-pi/docs/configuration.md +2 -2
- package/node_modules/@herbertgao/sol-pi/docs/star-history.md +18 -0
- package/node_modules/@herbertgao/sol-pi/package.json +4 -2
- package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +23 -4
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +32 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +3 -2
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +27 -7
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +137 -41
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +1 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +2 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/projection.ts +58 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +28 -6
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +2 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +15 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +4 -4
- package/node_modules/@pi-plugins/fast-mode/README.md +7 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +7 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +5 -5
- package/node_modules/pi-antigravity/README.md +3 -0
- package/node_modules/pi-antigravity/package.json +8 -8
- package/node_modules/pi-antigravity/src/auth/oauth.ts +6 -3
- package/node_modules/pi-antigravity/src/index.ts +114 -110
- package/node_modules/pi-antigravity/src/stream/stream.ts +1 -1
- package/node_modules/pi-antigravity/src/utils/util.ts +18 -1
- package/node_modules/pi-multi-account/CHANGELOG.md +20 -0
- package/node_modules/pi-multi-account/README.md +46 -8
- package/node_modules/pi-multi-account/config-file-transaction.ts +42 -0
- package/node_modules/pi-multi-account/index.ts +255 -269
- package/node_modules/pi-multi-account/model-pins.ts +36 -0
- package/node_modules/pi-multi-account/package.json +9 -7
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +12 -2
- package/node_modules/pi-multi-account/slot-proxy.ts +27 -5
- package/node_modules/pi-typesafe/README.md +1 -1
- package/node_modules/pi-typesafe/dist/backends.d.ts +15 -5
- package/node_modules/pi-typesafe/dist/backends.js +18 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +4 -3
- package/node_modules/pi-typesafe/dist/client.js +22 -7
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/dist/usage.d.ts +14 -4
- package/node_modules/pi-typesafe/dist/usage.js +147 -42
- package/node_modules/pi-typesafe/package.json +1 -1
- package/node_modules/pi-web-access/CHANGELOG.md +65 -0
- package/node_modules/pi-web-access/README.md +63 -14
- package/node_modules/pi-web-access/brave-rate-limit.ts +176 -0
- package/node_modules/pi-web-access/brave.ts +28 -11
- package/node_modules/pi-web-access/curator-page.ts +5 -1
- package/node_modules/pi-web-access/curator-server.ts +2 -0
- package/node_modules/pi-web-access/declared-web-links.ts +2 -2
- package/node_modules/pi-web-access/degoog.ts +323 -0
- package/node_modules/pi-web-access/dist/index.js +3647 -2740
- package/node_modules/pi-web-access/dist/mcp-cli.js +27443 -0
- package/node_modules/pi-web-access/exa.ts +5 -1
- package/node_modules/pi-web-access/extract.ts +41 -3
- package/node_modules/pi-web-access/firecrawl.ts +35 -4
- package/node_modules/pi-web-access/gemini-search.ts +36 -16
- package/node_modules/pi-web-access/gemini-web.ts +14 -9
- package/node_modules/pi-web-access/index.ts +164 -1207
- package/node_modules/pi-web-access/keenable.ts +221 -0
- package/node_modules/pi-web-access/mcp-cli.ts +40 -0
- package/node_modules/pi-web-access/mcp-server.ts +81 -0
- package/node_modules/pi-web-access/mcp-tool-definitions.ts +117 -0
- package/node_modules/pi-web-access/openai-search.ts +58 -11
- package/node_modules/pi-web-access/package.json +23 -5
- package/node_modules/pi-web-access/perplexity.ts +64 -56
- package/node_modules/pi-web-access/summary-review.ts +8 -2
- package/node_modules/pi-web-access/tool-arguments.ts +20 -0
- package/node_modules/pi-web-access/video-extract.ts +1 -1
- package/node_modules/pi-web-access/web-tool-contract.ts +86 -0
- package/node_modules/pi-web-access/web-tool-core.ts +1502 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -5
- package/node_modules/{@herbertgao/resume-from → resume-from}/README.md +16 -12
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.d.ts +0 -5
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.js +2 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.d.ts +3 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.js +27 -15
- package/node_modules/{@herbertgao/resume-from/dist/adapters/codex → resume-from/dist/adapters/claude-code}/redaction.js +16 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.d.ts +2 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.js +1 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.d.ts +2 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.js +1 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/read.d.ts +16 -0
- package/node_modules/resume-from/dist/adapters/codex/read.js +723 -0
- package/node_modules/{@herbertgao/resume-from/dist/adapters/pi → resume-from/dist/adapters/codex}/redaction.js +16 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.d.ts +44 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.js +88 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.d.ts +5 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.js +10 -6
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from/dist/adapters/claude-code → resume-from/dist/adapters/pi}/redaction.js +16 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.d.ts +2 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.d.ts +1 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.js +4 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.js +31 -8
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.d.ts +1 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.js +1 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.d.ts +4 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.d.ts +8 -6
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.d.ts +10 -5
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/finder.d.ts +2 -1
- package/node_modules/resume-from/dist/import/discovery/finder.js +269 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.d.ts +3 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.js +3 -3
- package/node_modules/resume-from/dist/import/pipeline.d.ts +28 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/pipeline.js +31 -11
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.js +9 -5
- package/node_modules/resume-from/dist/import/preview/warnings.d.ts +14 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/warnings.js +26 -20
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.js +12 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.d.ts +9 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.d.ts +4 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.js +7 -4
- package/node_modules/resume-from/dist/platform/repo/reader.d.ts +3 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/reader.js +60 -20
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.d.ts +8 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/package.json +19 -32
- package/node_modules/{@herbertgao/resume-from → resume-from}/shims/pi/extensions/resume-from.js +2 -2
- package/package.json +21 -21
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +0 -21
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +0 -62
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +0 -115
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +0 -27
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +0 -27
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +0 -4
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +0 -4
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/read.js +0 -270
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/finder.js +0 -129
- package/node_modules/@herbertgao/resume-from/dist/import/pipeline.d.ts +0 -17
- package/node_modules/@herbertgao/resume-from/dist/import/preview/warnings.d.ts +0 -11
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/reader.d.ts +0 -8
- /package/node_modules/{@herbertgao/resume-from → resume-from}/LICENSE +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/redaction.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/redaction.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/redaction.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.js +0 -0
|
@@ -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, XCrawl, Valyu, xAI/Grok, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, explicit-only Serply, self-hosted SearXNG, self-hosted Crawl4AI extraction, keyless DuckDuckGo, optional browser-cookie Gemini Web, Kimi Code Plan search, 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, XCrawl, Valyu, xAI/Grok, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, explicit-only Serply, self-hosted SearXNG, degoog metasearch, self-hosted Crawl4AI extraction, keyless DuckDuckGo, keyless Keenable, optional browser-cookie Gemini Web, Kimi Code Plan search, 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,14 +14,16 @@
|
|
|
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. An active Kimi Code Plan signed in through `/login kimi-coding` enables explicit Kimi search without a separate Open Platform key. Add API keys or endpoints for OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Valyu, SerpBase, SerpApi, Serper, explicit-only Serply, Exa, Perplexity, Gemini API, or Mistral 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. An active Kimi Code Plan signed in through `/login kimi-coding` enables explicit Kimi search without a separate Open Platform key. Add API keys or endpoints for OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Valyu, SerpBase, SerpApi, Serper, explicit-only Serply, Exa, Perplexity, Gemini API, or Mistral for more control; configure a self-hosted SearXNG endpoint or a degoog metasearch instance (public `https://degoog.org` by default) 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
|
|
|
21
|
-
**Smart Fallbacks** — Every capability has a fallback chain. Search tries configured SearXNG first for local/private search. When the active Pi model
|
|
21
|
+
**Smart Fallbacks** — Every capability has a fallback chain. Search tries configured SearXNG first for local/private search. When the active Pi model uses a ChatGPT subscription (`openai-codex`, or `openai` with Sign in with ChatGPT), it then tries subscription-backed OpenAI search. Otherwise it tries Exa before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, and Gemini Web when browser cookies are enabled. YouTube tries Gemini Web when enabled, then API, then Perplexity. Blocked pages try configured self-hosted Firecrawl or Crawl4AI first. Third-party hosted page fetchers require explicit `fetchRouting.allowRemoteHostedProviders` opt-in for remote HTTP(S) targets.
|
|
22
22
|
|
|
23
23
|
**GitHub Cloning** — GitHub URLs are cloned locally instead of scraped. The agent gets real file contents and a local path to explore, not rendered HTML.
|
|
24
24
|
|
|
25
|
+
**Other Agents** — The search and fetch tools also run as a local MCP server for Claude Code, Codex, Cursor, and other MCP clients. See [Use from other agents (MCP)](#use-from-other-agents-mcp).
|
|
26
|
+
|
|
25
27
|
## Install
|
|
26
28
|
|
|
27
29
|
```bash
|
|
@@ -47,7 +49,7 @@ Works immediately with no API keys — Exa MCP provides zero-config search. If P
|
|
|
47
49
|
}
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search. When the active Pi model
|
|
52
|
+
In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search. When the active Pi model uses a ChatGPT subscription (`openai-codex`, or `openai` with Sign in with ChatGPT), it then tries subscription-backed OpenAI search. Otherwise it tries Exa (direct API if keyed, MCP if not) before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Perplexity, Gemini API, and Gemini Web when browser-cookie access is enabled. 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.
|
|
51
53
|
|
|
52
54
|
For a third-party Responses-compatible gateway, set `openaiResponsesUrl` to its full Responses endpoint, or opt into reusing the selected Pi provider's base URL as described below. Without either opt-in, Pi credentials with a custom `baseUrl` are refused before sending a request, and availability checks mark OpenAI unavailable without blocking other providers. The auth-resolved base URL takes precedence over the model's; gateway Responses/`web_search` support is not inferred. An explicit endpoint overrides this guard, including an explicit official endpoint. Existing Codex subscription endpoint selection is preserved when provider URL reuse is not active.
|
|
53
55
|
|
|
@@ -163,7 +165,7 @@ fetch_content({ url: "/path/to/recording.mp4", prompt: "What error appears on sc
|
|
|
163
165
|
|
|
164
166
|
### web_search
|
|
165
167
|
|
|
166
|
-
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, Serply, You.com, self-hosted SearXNG, keyless DuckDuckGo, Exa, Perplexity AI, Gemini, or Kimi. The default `none` workflow makes no summary-generation model call and opens no curator/browser: it returns bounded raw results, identifies the provider used for each query, and stores the full results for retrieval by responseId. The selected search provider may itself be model-backed.
|
|
168
|
+
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, Serply, You.com, keyless Keenable, self-hosted SearXNG, explicit-only degoog, keyless DuckDuckGo, Exa, Perplexity AI, Gemini, or Kimi. The default `none` workflow makes no summary-generation model call and opens no curator/browser: it returns bounded raw results, identifies the provider used for each query, and stores the full results for retrieval by responseId. The selected search provider may itself be model-backed.
|
|
167
169
|
|
|
168
170
|
```typescript
|
|
169
171
|
web_search({ query: "rust async programming" })
|
|
@@ -186,11 +188,12 @@ web_search({ queries: ["query 1", "query 2"], workflow: "auto-summary" })
|
|
|
186
188
|
| `numResults` | Results per query (default: 5, max: 20) |
|
|
187
189
|
| `recencyFilter` | `day`, `week`, `month`, or `year` |
|
|
188
190
|
| `domainFilter` | Limit to domains (prefix with `-` to exclude) |
|
|
189
|
-
| `
|
|
191
|
+
| `category` | Exa only: restrict results to a category such as `news` or `research paper`; other providers ignore it. Without an API key, if Exa's filtered search is unavailable, the category is added to the query instead |
|
|
192
|
+
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except Parallel MCP, degoog, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, and Keenable simultaneously; otherwise `openai`, `brave`, `parallel`, `parallel-mcp`, `tinyfish`, `search1api`, `searchinfinity`, `querit`, `tavily`, `you`, `firecrawl`, `jina`, `serpdive`, `kagi`, `bocha`, `ollama`, `anysearch`, `xcrawl`, `valyu`, `xai`, `mistral`, `brightdata`, `serpbase`, `serpapi`, `serper`, `serply`, `baizhi`, `zai`, `keenable`, `searxng`, `degoog`, `duckduckgo`, `exa`, `perplexity`, `gemini`, or `kimi` (auto-selects when no provider or routing is configured; Parallel MCP, degoog, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, and Keenable are explicit-only) |
|
|
190
193
|
| `includeContent` | Fetch full page content from sources in background |
|
|
191
194
|
| `workflow` | `none` (skip curator; fresh-install default), `summary-review` (open curator and auto-generate a summary draft), or `auto-summary` (generate a summary without opening the curator) |
|
|
192
195
|
|
|
193
|
-
Batch searches run up to three queries concurrently. Provider routing and fallback within each query remain sequential.
|
|
196
|
+
Batch searches run up to three queries concurrently. Provider routing and fallback within each query remain sequential. Brave requests share an in-process queue and honor the rate-limit buckets returned in `X-RateLimit-*`; exhausted short windows delay the next request with a small safety margin, while long quota resets fail fast and keep the advertised cooldown active for later queued calls. HTTP 429 responses are retried once after the advertised `Retry-After` or rate-limit reset. Brave's 30-second search deadline covers queueing, cooldown waits, and both request attempts. The queue coordinates one Pi process; separate Pi processes still rely on Brave's 429 response.
|
|
194
197
|
|
|
195
198
|
### fetch_content
|
|
196
199
|
|
|
@@ -255,6 +258,38 @@ source_check({
|
|
|
255
258
|
|
|
256
259
|
The artifact preserves the `supported`, `contradicted`, `unclear`, or `missing-evidence` claim status schema, source quality hints, SHA-256 content hashes, and passage IDs with exact source offsets. It does not infer semantic support or contradiction automatically: retrieved passages produce `unclear` for manual review, while no passages produce `missing-evidence`. Search and fetch errors remain in the artifact instead of being silently discarded. Artifacts are stored with the session and retrieved through `get_search_content` using the returned `responseId`; paged artifact responses are JSON slices, so request the next `offset` when needed.
|
|
257
260
|
|
|
261
|
+
### In codemode scripts
|
|
262
|
+
|
|
263
|
+
Scripts run by Pi's `codemode` tool (Pi 1.0 or later) get data instead of the text the model sees. `await tools.web_search(...)` resolves to `{ responseId, fetchId, queries }`, where each query has its `answer`, `error`, provider, and `results` (`title`, `url`, `snippet`). `await tools.fetch_content(...)` resolves to `{ responseId, urls }`, where each URL has its full `content`, not limited by `maxInlineContentChars`, and its `error`. Failed searches and fetches still resolve to this data, with the reason in `error`; invalid arguments reject. A script's `web_search` uses `workflow: "none"` unless it passes a workflow, so a configured curator does not open, and with `includeContent` it waits for the pages, readable through `fetchId`, instead of fetching them in the background. A script that passes `workflow: "summary-review"` gets the curator and the background fetch, as the model does.
|
|
264
|
+
|
|
265
|
+
## Use from other agents (MCP)
|
|
266
|
+
|
|
267
|
+
Other agents, such as Claude Code, Codex, Cursor, or Executor, can use `web_search`, `fetch_content`, `get_search_content`, and `source_check` through a local stdio MCP server. It needs Node.js 22.19 or later and no Pi install:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npx -y pi-web-access
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Add it to an `mcpServers` config (Claude Code, Cursor, and similar clients; Codex takes the same command, args, and env in its `config.toml`):
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"mcpServers": {
|
|
278
|
+
"pi-web-access": {
|
|
279
|
+
"command": "npx",
|
|
280
|
+
"args": ["-y", "pi-web-access"],
|
|
281
|
+
"env": { "BRAVE_API_KEY": "BSA_..." }
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- The server reads the same `web-search.json` and provider environment variables as the extension. It lists only the tools enabled there, under their default names; `toolNames` renames do not apply.
|
|
288
|
+
- Pi-only features are not available: the curator and summaries (a configured `workflow` is ignored), Kimi search, OpenAI search through ChatGPT sign-in or the current Pi model, `fetch_content` answer mode and video prompts or frames, and direct image fetches. Explicit requests for them return a tool error rather than a fallback, and automatic provider selection only uses providers that work outside Pi.
|
|
289
|
+
- `includeContent` waits for the page fetch before `web_search` returns.
|
|
290
|
+
- The server keeps the 50 most recent results in memory for `get_search_content`; they are gone when it exits. Restart the server to pick up config changes.
|
|
291
|
+
- Install with npm's default settings or `--legacy-peer-deps`. `--omit=peer` leaves out `zod`, which the MCP SDK needs.
|
|
292
|
+
|
|
258
293
|
## Capabilities
|
|
259
294
|
|
|
260
295
|
### GitHub repos
|
|
@@ -363,7 +398,7 @@ fetch_content(url)
|
|
|
363
398
|
→ Video file? Gemini API (Files API) → Gemini Web (if browser cookies enabled)
|
|
364
399
|
→ GitHub URL? Clone repo, return file contents + local path
|
|
365
400
|
→ YouTube URL? Gemini Web (if browser cookies enabled) → Gemini API → Perplexity
|
|
366
|
-
→ HTTP fetch → PDF? Datalab → Gemini API → local text extraction, save to temp pi-web-pdf
|
|
401
|
+
→ HTTP fetch (asks for markdown first; raw mode does not) → PDF? Datalab → Gemini API → local text extraction, save to temp pi-web-pdf
|
|
367
402
|
→ HTML? Readability (+ declared Link/rel discovery) → RSC parser → Firecrawl → Crawl4AI (each if configured) → third-party hosted fallbacks only when fetchRouting.allowRemoteHostedProviders is enabled
|
|
368
403
|
→ Text/JSON/Markdown? Return directly
|
|
369
404
|
```
|
|
@@ -440,6 +475,7 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
440
475
|
"kagiApiKey": "$KAGI_API_KEY",
|
|
441
476
|
"ollamaApiKey": "$OLLAMA_API_KEY",
|
|
442
477
|
"valyuApiKey": "$VALYU_API_KEY",
|
|
478
|
+
"keenableApiKey": "$KEENABLE_API_KEY",
|
|
443
479
|
"serpbaseApiKey": "$SERPBASE_API_KEY",
|
|
444
480
|
"serpapiApiKey": "$SERPAPI_KEY",
|
|
445
481
|
"serperApiKey": "$SERPER_API_KEY",
|
|
@@ -458,10 +494,14 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
458
494
|
"CF-Access-Client-Id": "xxxxxxxx.access",
|
|
459
495
|
"CF-Access-Client-Secret": "xxxxxxxx"
|
|
460
496
|
},
|
|
497
|
+
"degoogBaseUrl": "https://degoog.example.com",
|
|
498
|
+
"degoogApiKey": "$DEGOOG_API_KEY",
|
|
499
|
+
"degoogEngines": ["degoog-org-official-extensions-brave-engine"],
|
|
461
500
|
"firecrawlBaseUrl": "https://crawl.example.com",
|
|
462
501
|
"firecrawlApiKey": "fc-...",
|
|
463
502
|
"firecrawlApiVersion": "v2",
|
|
464
503
|
"firecrawlFreshScrape": false,
|
|
504
|
+
"firecrawlPdfMaxPages": 50,
|
|
465
505
|
"crawl4aiBaseUrl": "https://crawl4ai.example.com",
|
|
466
506
|
"crawl4aiApiToken": "$CRAWL4AI_API_TOKEN",
|
|
467
507
|
"brightdataUnlockerZone": "pi_unlocker",
|
|
@@ -517,6 +557,7 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
517
557
|
"searchModel": "gemini-3.6-flash",
|
|
518
558
|
"summaryModel": "anthropic/claude-haiku-4-5",
|
|
519
559
|
"summaryGenerationDeadlineMs": 30000,
|
|
560
|
+
"summaryInstructions": "- Preserve concrete figures verbatim: prices, limits, versions, dates.\n- Name the product version each claim applies to.",
|
|
520
561
|
"maxInlineContentChars": 30000,
|
|
521
562
|
"workflow": "summary-review",
|
|
522
563
|
"curatorTimeoutSeconds": 20,
|
|
@@ -571,7 +612,9 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
571
612
|
|
|
572
613
|
`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`.
|
|
573
614
|
|
|
574
|
-
|
|
615
|
+
`summaryInstructions` appends custom text to the Requirements section of the summary prompt used by the curator UI and `auto-summary` mode (e.g. `"- Preserve concrete figures verbatim: prices, limits, versions, dates.\n- Name the product version each claim applies to."`). The text is passed verbatim to the summary model, so use one `- ` bullet per requirement. It is additive: the built-in guardrails (no invented sources, explicit weak/conflicting-evidence notes, the Sources section) always stay in place, and curator regeneration feedback is still appended afterwards. Missing, non-string, or blank values leave the default prompt unchanged.
|
|
616
|
+
|
|
617
|
+
All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `youApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `keenableApiKey`, `degoogApiKey`, `anysearchApiKey`, `xcrawlApiKey`, `xaiApiKey`, `mistralApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `crawl4aiApiToken`, `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 `!`:
|
|
575
618
|
|
|
576
619
|
```json
|
|
577
620
|
{
|
|
@@ -582,7 +625,7 @@ All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `t
|
|
|
582
625
|
}
|
|
583
626
|
```
|
|
584
627
|
|
|
585
|
-
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `crawl4aiApiToken`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `mistralApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `crawl4aiBaseUrl`, `brightdataSerpZone`, `brightdataUnlockerZone`, and `zaiEndpoint` are literal config values.
|
|
628
|
+
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `crawl4aiApiToken`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `keenableApiKey`, `degoogApiKey`, `mistralApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `firecrawlPdfMaxPages`, `crawl4aiBaseUrl`, `degoogBaseUrl`, `degoogEngines`, `brightdataSerpZone`, `brightdataUnlockerZone`, and `zaiEndpoint` are literal config values.
|
|
586
629
|
|
|
587
630
|
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_*` and `OP_SERVICE_ACCOUNT_TOKEN`, when present in Pi's environment, are forwarded to trusted resolver commands so 1Password CLI sessions and service accounts can be reused without storing their credentials in config. For example, `"braveApiKey": "!/absolute/path/to/op read 'op://Automation/Brave/credential'"` resolves that item after you replace the executable placeholder with your trusted installation's absolute path, but the `op://` argument is not an authorization boundary: every configured resolver command that receives the token can exercise all vault and item permissions granted to its service account. Prefer a narrowly scoped service account. 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.
|
|
588
631
|
|
|
@@ -604,9 +647,11 @@ Set `braveBaseUrl`, `exaBaseUrl`, or `tavilyBaseUrl` to route those providers th
|
|
|
604
647
|
|
|
605
648
|
Set `searxngBaseUrl` or `SEARXNG_BASE_URL` to use a self-hosted SearXNG JSON API. A configured endpoint is preferred first in `auto` mode for local/private search. Its base URL and redirects remain subject to the SSRF guard; add only the narrowest self-hosted range to `ssrf.allowRanges` when it resolves to a private or synthetic range. Optional `searxngHeaders` merges extra HTTP headers into each SearXNG request (string values only; invalid header names are ignored), which is useful for reverse-proxy or Zero Trust auth such as Cloudflare Access service tokens (`CF-Access-Client-Id` / `CF-Access-Client-Secret`). Configured headers override the default `Accept: application/json` when the same name is supplied. Thanks to Marcos A. Núñez (@marnunez) for PR #107 and Avinash Kanaujiya (@avinashkanaujiya) for issue #105.
|
|
606
649
|
|
|
650
|
+
**degoog.** Set `degoogBaseUrl` or `DEGOOG_URL` to use a self-hosted [degoog](https://degoog.org) metasearch instance, or omit both to use the public `https://degoog.org` instance. degoog is keyless and explicit-only: select it with `provider: "degoog"` or place it in `searchRouting`; it is never chosen by `auto` and never participates in `provider: "all"`, so queries do not leave for a third-party metasearch instance unless you ask. When the instance protects its search routes, set `degoogApiKey` or `DEGOOG_API_KEY` and the key is sent as `Authorization: Bearer <key>`; the API key and any `degoogHeaders` are stripped from cross-origin redirects. `degoogEngines` restricts the request to specific engine ids from the instance's engine list (an array of strings), which is sent as the `engines` field on `POST /api/search`; omitting it uses the instance defaults. `recencyFilter` maps to degoog's `time` parameter, and domain filters become `site:`/`-site:` query clauses plus a local result filter; multiple allowed domains are grouped as `(site:a OR site:b)` so the query terms apply to every branch. Only absolute HTTP(S) result URLs are returned as sources, and duplicate URLs are collapsed. A 200 response without a results array is reported as an invalid response, so a configured route can continue when `fallbackOn` includes `"invalid-response"`, while a genuine empty `results` array remains a successful empty search. The configured base URL and redirects remain subject to the SSRF guard; add only the narrowest self-hosted range to `ssrf.allowRanges` when it resolves to a private or synthetic range. Optional `degoogHeaders` merges extra HTTP headers (string values only; invalid header names are ignored) for reverse-proxy or Zero Trust auth. Values from `degoogApiKey` and the configured `degoogHeaders` are redacted from reported upstream error text, and a request that exceeds the 30-second deadline fails as a timeout instead of being recorded as a completed request.
|
|
651
|
+
|
|
607
652
|
**DuckDuckGo.** DuckDuckGo HTML search is keyless and explicit-only. Select it with `provider: "duckduckgo"` or place it in `searchRouting`; it is never chosen by `auto` and never participates in `provider: "all"`. Domain filters are enforced locally after DuckDuckGo redirect URLs are decoded. `recencyFilter` is not guaranteed because the HTML endpoint has no documented stable time parameter. A 200 page with no parseable results is reported as an invalid response, so routing can continue when `fallbackOn` includes `"invalid-response"`.
|
|
608
653
|
|
|
609
|
-
Set `firecrawlBaseUrl` or `FIRECRAWL_BASE_URL` to use Firecrawl for `web_search` and as an extraction fallback for `fetch_content`. Search calls `/v2/search` by default, requests `sources: ["web"]`, maps `numResults` to `limit`, maps `recencyFilter` to Firecrawl's `tbs`, and maps domain filters to `includeDomains` or `excludeDomains` when possible. With `includeContent: true`, Firecrawl search adds Markdown scrape options and returns successful result Markdown as inline content. Fetch extraction calls `/v2/scrape` by default. Set `firecrawlApiVersion` or `FIRECRAWL_API_VERSION` to `v1` for older self-hosted images. Firecrawl page scraping is cache-only by default (`lockdown: true`), so the Firecrawl server does not make fresh outbound target requests unless you explicitly set `firecrawlFreshScrape: true` or `FIRECRAWL_FRESH_SCRAPE=1`. Enable fresh scraping only for a Firecrawl deployment whose own egress, redirects, DNS rebinding behavior, and internal-network access are isolated or allowlisted; this extension can preflight submitted fetch URLs but cannot control network requests made by the Firecrawl server. A configured Firecrawl API base URL may use `localhost`, `127.0.0.0/8`, or `::1` without adding those loopback ranges to global `ssrf.allowRanges`; that exception is only for Firecrawl API calls, not submitted fetch/search target URLs. The configured Firecrawl API base URL and redirects are otherwise still validated by the same SSRF guard as other remote requests, and Firecrawl credentials are stripped from cross-origin API redirects.
|
|
654
|
+
Set `firecrawlBaseUrl` or `FIRECRAWL_BASE_URL` to use Firecrawl for `web_search` and as an extraction fallback for `fetch_content`. Search calls `/v2/search` by default, requests `sources: ["web"]`, maps `numResults` to `limit`, maps `recencyFilter` to Firecrawl's `tbs`, and maps domain filters to `includeDomains` or `excludeDomains` when possible. With `includeContent: true`, Firecrawl search adds Markdown scrape options and returns successful result Markdown as inline content. Fetch extraction calls `/v2/scrape` by default. Set `firecrawlApiVersion` or `FIRECRAWL_API_VERSION` to `v1` for older self-hosted images. Firecrawl bills PDFs per parsed page; set `firecrawlPdfMaxPages` to a positive integer to have Firecrawl parse at most that many pages of each PDF in fetch extraction and `includeContent` search. It is a per-document cap, not a credit budget: several PDFs or repeated requests still add up. Unset keeps the current behavior, and API `v1` does not support the cap, so setting it with `v1` is a config error. When Firecrawl reports fewer parsed pages than the PDF has, the content ends with a truncation notice naming both page counts. Firecrawl page scraping is cache-only by default (`lockdown: true`), so the Firecrawl server does not make fresh outbound target requests unless you explicitly set `firecrawlFreshScrape: true` or `FIRECRAWL_FRESH_SCRAPE=1`. Enable fresh scraping only for a Firecrawl deployment whose own egress, redirects, DNS rebinding behavior, and internal-network access are isolated or allowlisted; this extension can preflight submitted fetch URLs but cannot control network requests made by the Firecrawl server. A configured Firecrawl API base URL may use `localhost`, `127.0.0.0/8`, or `::1` without adding those loopback ranges to global `ssrf.allowRanges`; that exception is only for Firecrawl API calls, not submitted fetch/search target URLs. The configured Firecrawl API base URL and redirects are otherwise still validated by the same SSRF guard as other remote requests, and Firecrawl credentials are stripped from cross-origin API redirects.
|
|
610
655
|
|
|
611
656
|
**Crawl4AI.** Set `crawl4aiBaseUrl` or `CRAWL4AI_BASE_URL` to use a self-hosted [Crawl4AI](https://github.com/unclecode/crawl4ai) server as a `fetch_content` extraction fallback. It runs right after Firecrawl and before the hosted providers, and it never participates in `web_search` because Crawl4AI has no search API. Extraction calls `POST /md` with the `fit` markdown filter and returns the response's top-level `markdown`; the first `# ` heading becomes the title. Crawl4AI 0.9 and later require a bearer token by default, so set `crawl4aiApiToken` (credential-source syntax applies) or `CRAWL4AI_API_TOKEN`. The submitted target URL is validated by the local SSRF guard before any request, but the Crawl4AI server then fetches that target from its own network, so leave the base URL unset for URLs that must not be disclosed to that server. A configured Crawl4AI base URL may use `localhost`, `127.0.0.0/8`, or `::1` without adding those ranges to global `ssrf.allowRanges`; a base URL on another private or synthetic range needs a narrow `ssrf.allowRanges` entry. The base URL and its redirects are otherwise validated like other remote requests, and the bearer token is stripped from cross-origin API redirects.
|
|
612
657
|
|
|
@@ -626,6 +671,8 @@ Bright Data Web Unlocker is a paid `fetch_content` fallback after Parallel and b
|
|
|
626
671
|
|
|
627
672
|
**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.
|
|
628
673
|
|
|
674
|
+
**Keenable.** Select `provider: "keenable"` to query Keenable's web search API. No API key is needed: without one, requests go to Keenable's public endpoint, which is rate-limited per IP. Set `keenableApiKey` or `KEENABLE_API_KEY` to use the authenticated endpoint and its higher limits. Keenable 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`. A single allowed domain is sent as Keenable's `site` filter; several domains and exclusions become `site:` / `-site:` clauses in the query, and all domain filters are reapplied locally. Recency maps to a `published_after` date. Result snippets are capped at 500 characters.
|
|
675
|
+
|
|
629
676
|
**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`.
|
|
630
677
|
|
|
631
678
|
**Tavily key pool.** Set `TAVILY_API_KEY_1` through `TAVILY_API_KEY_20` to try another key when Tavily returns HTTP 401, 402, 403, 429, or 432. `TAVILY_API_KEY_INDEX` picks the first slot to try (default `1`). Search tries each distinct numbered key once, wrapping around, then tries `tavilyApiKey` / `TAVILY_API_KEY`. Explicit `$VAR` and `!command` sources still fail closed. All attempts share the 60-second search deadline.
|
|
@@ -642,7 +689,7 @@ The Baizhi input contract is based on the [published September 16 discovery snap
|
|
|
642
689
|
|
|
643
690
|
**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.
|
|
644
691
|
|
|
645
|
-
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`, `YDC_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `SERPAPI_KEY`, `SERPER_API_KEY`, `SERPLY_API_KEY`, `BAIZHI_API_KEY`, `ZAI_API_KEY`, `ANYSEARCH_API_KEY`, `XCRAWL_API_KEY`, `VALYU_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `CRAWL4AI_API_TOKEN`, `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, then newest bare `gpt-N` or `gpt-N.N` id, then newest other `gpt-<version>` id); the id is sent verbatim with whichever OpenAI auth resolves, so gateway-only model ids work too. `xaiSearchModel` similarly pins the xAI search model. `openaiSearchProviders` sets which Pi model providers OpenAI `web_search` resolves login credentials from, in priority order; it defaults to `["openai-codex", "openai"]`, entries that are not registered or not signed in are skipped, and an empty array skips Pi credentials entirely so the `openaiApiKey` / `OPENAI_API_KEY` fallback applies. Useful for choosing between multiple Codex accounts (for example a second account registered by an extension) or forcing API-key billing while signed into Codex. 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. Alternatively, set `geminiAuth` to `"adc"` to authenticate Gemini generate-content calls with Google Application Default Credentials (ADC) instead of an API key; calls go to the Vertex AI endpoint (`aiplatform.googleapis.com`) with an OAuth bearer token minted from the ADC file (`GOOGLE_APPLICATION_CREDENTIALS` or `~/.config/gcloud/application_default_credentials.json`, i.e. `gcloud auth application-default login`). `geminiProject`/`geminiLocation` set the Vertex project and location and fall back to the `GOOGLE_CLOUD_PROJECT`/`GOOGLE_CLOUD_LOCATION` (or `GCLOUD_PROJECT`) env vars; project and location are required. ADC supports `authorized_user` (OAuth refresh token) and `service_account` (JWT assertion) credential files, and tokens are cached and refreshed from expiry. ADC mode covers search, URL context, and PDF/inline-data extraction; YouTube and local video analysis still go through the Gemini Files API, so they fall back to Gemini Web unless a `GEMINI_API_KEY` is also configured. The access token is treated as a credential and is redacted from errors. 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"`, `"xcrawl"`, `"valyu"`, `"xai"`, `"mistral"`, `"brightdata"`, `"serpbase"`, `"serpapi"`, `"serper"`, `"serply"`, `"you"`, `"baizhi"`, `"zai"`, `"searxng"`, `"exa"`, `"perplexity"`, or `"gemini"`. Parallel MCP, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi,
|
|
692
|
+
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`, `YDC_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `SERPAPI_KEY`, `SERPER_API_KEY`, `SERPLY_API_KEY`, `BAIZHI_API_KEY`, `ZAI_API_KEY`, `KEENABLE_API_KEY`, `ANYSEARCH_API_KEY`, `XCRAWL_API_KEY`, `VALYU_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `CRAWL4AI_API_TOKEN`, `DEGOOG_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, then newest bare `gpt-N` or `gpt-N.N` id, then newest other `gpt-<version>` id); the id is sent verbatim with whichever OpenAI auth resolves, so gateway-only model ids work too. `xaiSearchModel` similarly pins the xAI search model. `openaiSearchProviders` sets which Pi model providers OpenAI `web_search` resolves login credentials from, in priority order; it defaults to `["openai-codex", "openai"]`, entries that are not registered or not signed in are skipped, and an empty array skips Pi credentials entirely so the `openaiApiKey` / `OPENAI_API_KEY` fallback applies. Useful for choosing between multiple Codex accounts (for example a second account registered by an extension) or forcing API-key billing while signed into Codex. 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. Alternatively, set `geminiAuth` to `"adc"` to authenticate Gemini generate-content calls with Google Application Default Credentials (ADC) instead of an API key; calls go to the Vertex AI endpoint (`aiplatform.googleapis.com`) with an OAuth bearer token minted from the ADC file (`GOOGLE_APPLICATION_CREDENTIALS` or `~/.config/gcloud/application_default_credentials.json`, i.e. `gcloud auth application-default login`). `geminiProject`/`geminiLocation` set the Vertex project and location and fall back to the `GOOGLE_CLOUD_PROJECT`/`GOOGLE_CLOUD_LOCATION` (or `GCLOUD_PROJECT`) env vars; project and location are required. ADC supports `authorized_user` (OAuth refresh token) and `service_account` (JWT assertion) credential files, and tokens are cached and refreshed from expiry. ADC mode covers search, URL context, and PDF/inline-data extraction; YouTube and local video analysis still go through the Gemini Files API, so they fall back to Gemini Web unless a `GEMINI_API_KEY` is also configured. The access token is treated as a credential and is redacted from errors. 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"`, `"xcrawl"`, `"valyu"`, `"xai"`, `"mistral"`, `"brightdata"`, `"serpbase"`, `"serpapi"`, `"serper"`, `"serply"`, `"you"`, `"baizhi"`, `"zai"`, `"keenable"`, `"searxng"`, `"degoog"`, `"exa"`, `"perplexity"`, or `"gemini"`. Parallel MCP, degoog, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, and Keenable 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: `"none"` (default; raw results, no curator), `"summary-review"` (opens curator with an auto-generated summary draft), or `"auto-summary"` (returns a model-generated summary without opening the browser curator). Overridden per-call via the `workflow` parameter on the configured search tool, or toggled at runtime with `/curator`. `browserCookies.profile` 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 networks where only the proxy can resolve hostnames; it skips local DNS preflight only for hostnames sent through the configured `proxy` or, when none is configured, hostnames covered by valid HTTP(S) proxy env vars in sandboxes that route through them, and still blocks localhost, literal private IP targets, and `NO_PROXY` matches. It does not configure proxy transport.
|
|
646
693
|
|
|
647
694
|
### Kimi Code Plan
|
|
648
695
|
Run `/login kimi-coding` in Pi and complete sign-in for an active Kimi Code Plan. Then select `provider: "kimi"`, include `"kimi"` in an explicit provider array, or add it to `searchRouting.providers`. The extension resolves a model with provider `kimi-coding` from Pi's model registry and reuses Pi's refreshed OAuth credential; no Moonshot Open Platform key is configured here.
|
|
@@ -653,7 +700,7 @@ Kimi is explicit-only: it is never chosen by `auto` and never participates in `p
|
|
|
653
700
|
|
|
654
701
|
### All providers
|
|
655
702
|
|
|
656
|
-
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, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi,
|
|
703
|
+
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, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, and Keenable are always excluded because they are explicit-only; Bright Data, SerpBase, SerpApi, Serper, Serply, and You.com are paid search APIs, while Baizhi, Z.ai, Kimi, and Mistral may consume account quota or paid search-tool usage, so `all` never spends either resource without an explicit request. 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.
|
|
657
704
|
|
|
658
705
|
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.
|
|
659
706
|
|
|
@@ -1070,10 +1117,12 @@ Rate limits: Perplexity is capped at 10 requests/minute (client-side). Jina Sear
|
|
|
1070
1117
|
| `anysearch.ts` | Explicit-only AnySearch search provider |
|
|
1071
1118
|
| `xcrawl.ts` | Explicit-only XCrawl search provider |
|
|
1072
1119
|
| `valyu.ts` | Explicit-only Valyu research search provider |
|
|
1120
|
+
| `keenable.ts` | Explicit-only Keenable search provider, keyless with an optional API key |
|
|
1073
1121
|
| `xai-search.ts` | Explicit-only xAI (Grok) hosted web/X search provider |
|
|
1074
1122
|
| `mistral-search.ts` | Explicit-only Mistral Conversations web search provider |
|
|
1075
1123
|
| `kimi-search.ts` | Explicit-only Kimi Code Plan search provider |
|
|
1076
1124
|
| `searxng.ts` | Self-hosted SearXNG JSON API search provider |
|
|
1125
|
+
| `degoog.ts` | degoog metasearch search provider with optional API key and engine selection |
|
|
1077
1126
|
| `duckduckgo.ts` | Explicit-only keyless DuckDuckGo HTML search provider |
|
|
1078
1127
|
| `exa.ts` | Exa.ai search provider — direct API and MCP proxy |
|
|
1079
1128
|
| `extract.ts` | URL/file path routing, HTTP extraction, fallback orchestration |
|
|
@@ -1090,7 +1139,7 @@ Rate limits: Perplexity is capped at 10 requests/minute (client-side). Jina Sear
|
|
|
1090
1139
|
| `github-extract.ts` | GitHub URL parsing, clone cache, content generation |
|
|
1091
1140
|
| `github-api.ts` | GitHub API fallback for large repos and commit SHAs |
|
|
1092
1141
|
| `github-issue-pr.ts` | GitHub PR and issue URL parsing, gh/REST fetch, markdown rendering |
|
|
1093
|
-
| `perplexity.ts` | Perplexity API client with rate limiting |
|
|
1142
|
+
| `perplexity.ts` | Perplexity Search API client (and Sonar for YouTube summaries) with rate limiting |
|
|
1094
1143
|
| `datalab-pdf-extract.ts` | Datalab hosted PDF-to-Markdown conversion client (upload → convert → poll) |
|
|
1095
1144
|
| `pdf-extract.ts` | PDF text extraction, saves to markdown |
|
|
1096
1145
|
| `rsc-extract.ts` | RSC flight data parser for Next.js pages |
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const DEFAULT_SAFETY_MARGIN_MS = 100;
|
|
2
|
+
const DEFAULT_MAX_WAIT_MS = 30_000;
|
|
3
|
+
|
|
4
|
+
export interface BraveRateLimitBucket {
|
|
5
|
+
limit: number;
|
|
6
|
+
windowSeconds: number | null;
|
|
7
|
+
remaining: number;
|
|
8
|
+
resetSeconds: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BraveRateLimitCoordinatorOptions {
|
|
12
|
+
now?: () => number;
|
|
13
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
14
|
+
safetyMarginMs?: number;
|
|
15
|
+
maxWaitMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseNumberList(value: string | null): number[] {
|
|
19
|
+
if (!value) return [];
|
|
20
|
+
return value.split(",").map(part => Number(part.trim()));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parsePolicyWindows(value: string | null): Array<number | null> {
|
|
24
|
+
if (!value) return [];
|
|
25
|
+
return value.split(",").map(part => {
|
|
26
|
+
const match = /(?:^|;)\s*w=(\d+(?:\.\d+)?)\s*(?:;|$)/i.exec(part.trim());
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
const seconds = Number(match[1]);
|
|
29
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Parse Brave's parallel rate-limit bucket headers by list position. */
|
|
34
|
+
export function parseBraveRateLimitHeaders(headers: Headers): BraveRateLimitBucket[] {
|
|
35
|
+
const limits = parseNumberList(headers.get("x-ratelimit-limit"));
|
|
36
|
+
const remaining = parseNumberList(headers.get("x-ratelimit-remaining"));
|
|
37
|
+
const resets = parseNumberList(headers.get("x-ratelimit-reset"));
|
|
38
|
+
const windows = parsePolicyWindows(headers.get("x-ratelimit-policy"));
|
|
39
|
+
const count = Math.max(limits.length, remaining.length, resets.length, windows.length);
|
|
40
|
+
const buckets: BraveRateLimitBucket[] = [];
|
|
41
|
+
|
|
42
|
+
for (let index = 0; index < count; index++) {
|
|
43
|
+
const limit = limits[index];
|
|
44
|
+
const left = remaining[index];
|
|
45
|
+
const resetSeconds = resets[index];
|
|
46
|
+
if (![limit, left, resetSeconds].every(Number.isFinite) || limit < 0 || left < 0 || resetSeconds < 0) continue;
|
|
47
|
+
buckets.push({
|
|
48
|
+
limit,
|
|
49
|
+
windowSeconds: windows[index] ?? null,
|
|
50
|
+
remaining: left,
|
|
51
|
+
resetSeconds,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return buckets;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parseRetryAfterMs(headers: Headers, now = Date.now()): number | null {
|
|
58
|
+
const value = headers.get("retry-after")?.trim();
|
|
59
|
+
if (!value) return null;
|
|
60
|
+
const seconds = Number(value);
|
|
61
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
|
|
62
|
+
const date = Date.parse(value);
|
|
63
|
+
if (!Number.isFinite(date)) return null;
|
|
64
|
+
return Math.max(0, date - now);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Prepaid plans report a monthly bucket with limit 0: it always reads
|
|
68
|
+
// remaining 0 but never gates requests.
|
|
69
|
+
function exhaustedBuckets(headers: Headers): BraveRateLimitBucket[] {
|
|
70
|
+
return parseBraveRateLimitHeaders(headers).filter(bucket => bucket.limit > 0 && bucket.remaining === 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
74
|
+
if (signal?.aborted) return Promise.reject(new Error("Aborted"));
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
cleanup();
|
|
79
|
+
resolve();
|
|
80
|
+
}, ms);
|
|
81
|
+
const onAbort = () => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
cleanup();
|
|
84
|
+
reject(new Error("Aborted"));
|
|
85
|
+
};
|
|
86
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function formatWait(ms: number): string {
|
|
91
|
+
const seconds = Math.ceil(ms / 1000);
|
|
92
|
+
if (seconds < 60) return `${seconds}s`;
|
|
93
|
+
const minutes = Math.ceil(seconds / 60);
|
|
94
|
+
if (minutes < 60) return `${minutes}m`;
|
|
95
|
+
const hours = Math.ceil(minutes / 60);
|
|
96
|
+
return `${hours}h`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class BraveRateLimitCoordinator {
|
|
100
|
+
private queue: Promise<void> = Promise.resolve();
|
|
101
|
+
private blockedUntil = 0;
|
|
102
|
+
private readonly now: () => number;
|
|
103
|
+
private readonly sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
104
|
+
private readonly safetyMarginMs: number;
|
|
105
|
+
private readonly maxWaitMs: number;
|
|
106
|
+
|
|
107
|
+
constructor(options: BraveRateLimitCoordinatorOptions = {}) {
|
|
108
|
+
this.now = options.now ?? Date.now;
|
|
109
|
+
this.sleep = options.sleep ?? defaultSleep;
|
|
110
|
+
this.safetyMarginMs = options.safetyMarginMs ?? DEFAULT_SAFETY_MARGIN_MS;
|
|
111
|
+
this.maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
run<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
115
|
+
const scheduled = this.queue.then(async () => {
|
|
116
|
+
await this.waitForAvailability(signal);
|
|
117
|
+
if (signal?.aborted) throw new Error("Aborted");
|
|
118
|
+
return operation();
|
|
119
|
+
});
|
|
120
|
+
this.queue = scheduled.then(() => undefined, () => undefined);
|
|
121
|
+
if (!signal) return scheduled;
|
|
122
|
+
if (signal.aborted) return Promise.reject(new Error("Aborted"));
|
|
123
|
+
return new Promise<T>((resolve, reject) => {
|
|
124
|
+
const onAbort = () => reject(new Error("Aborted"));
|
|
125
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
126
|
+
scheduled.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
observe(headers: Headers): void {
|
|
131
|
+
const exhausted = exhaustedBuckets(headers);
|
|
132
|
+
if (exhausted.length === 0) return;
|
|
133
|
+
const resetMs = Math.max(...exhausted.map(bucket => bucket.resetSeconds * 1000));
|
|
134
|
+
this.blockFor(resetMs);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
retryDelay(headers: Headers): number | null {
|
|
138
|
+
const advertised = parseRetryAfterMs(headers, this.now());
|
|
139
|
+
const exhausted = exhaustedBuckets(headers);
|
|
140
|
+
const bucketDelay = exhausted.length > 0
|
|
141
|
+
? Math.max(...exhausted.map(bucket => bucket.resetSeconds * 1000))
|
|
142
|
+
: null;
|
|
143
|
+
if (advertised === null && bucketDelay === null) return null;
|
|
144
|
+
return Math.max(advertised ?? 0, bucketDelay ?? 0) + this.safetyMarginMs;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
recordRetryDelay(delayMs: number): void {
|
|
148
|
+
this.blockFor(delayMs, false);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async waitForRecordedCooldown(signal?: AbortSignal): Promise<void> {
|
|
152
|
+
await this.waitForAvailability(signal);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async waitForRetry(delayMs: number, signal?: AbortSignal): Promise<void> {
|
|
156
|
+
this.recordRetryDelay(delayMs);
|
|
157
|
+
await this.waitForRecordedCooldown(signal);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private blockFor(delayMs: number, addMargin = true): void {
|
|
161
|
+
const boundedDelay = Math.max(0, delayMs) + (addMargin ? this.safetyMarginMs : 0);
|
|
162
|
+
this.blockedUntil = Math.max(this.blockedUntil, this.now() + boundedDelay);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async waitForAvailability(signal?: AbortSignal): Promise<void> {
|
|
166
|
+
const waitMs = Math.max(0, this.blockedUntil - this.now());
|
|
167
|
+
if (waitMs === 0) return;
|
|
168
|
+
this.assertWaitIsBounded(waitMs);
|
|
169
|
+
await this.sleep(waitMs, signal);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private assertWaitIsBounded(waitMs: number): void {
|
|
173
|
+
if (waitMs <= this.maxWaitMs) return;
|
|
174
|
+
throw new Error(`Brave Search quota exhausted; estimated reset in ${formatWait(waitMs)}. Refusing to wait longer than ${formatWait(this.maxWaitMs)} inside one search call.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { BraveRateLimitCoordinator } from "./brave-rate-limit.ts";
|
|
3
4
|
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
4
5
|
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
5
6
|
import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
|
|
@@ -9,6 +10,7 @@ import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl
|
|
|
9
10
|
const BRAVE_API_BASE_URL = "https://api.search.brave.com/res/v1";
|
|
10
11
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
11
12
|
const SEARCH_TIMEOUT_MS = 30_000;
|
|
13
|
+
const braveRateLimit = new BraveRateLimitCoordinator();
|
|
12
14
|
|
|
13
15
|
interface WebSearchConfig {
|
|
14
16
|
braveApiKey?: unknown;
|
|
@@ -154,17 +156,32 @@ export async function searchWithBrave(
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
try {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
159
|
+
const searchDeadline = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
160
|
+
const searchSignal = options.signal
|
|
161
|
+
? AbortSignal.any([searchDeadline, options.signal])
|
|
162
|
+
: searchDeadline;
|
|
163
|
+
const response = await braveRateLimit.run(async () => {
|
|
164
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
165
|
+
const current = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {
|
|
166
|
+
method: "GET",
|
|
167
|
+
headers: {
|
|
168
|
+
"X-Subscription-Token": apiKey,
|
|
169
|
+
"Accept": "application/json",
|
|
170
|
+
"Accept-Encoding": "gzip",
|
|
171
|
+
},
|
|
172
|
+
signal: searchSignal,
|
|
173
|
+
}, ["X-Subscription-Token"]);
|
|
174
|
+
|
|
175
|
+
braveRateLimit.observe(current.headers);
|
|
176
|
+
if (current.status !== 429 || attempt === 1) return current;
|
|
177
|
+
const retryDelay = braveRateLimit.retryDelay(current.headers);
|
|
178
|
+
if (retryDelay === null) return current;
|
|
179
|
+
braveRateLimit.recordRetryDelay(retryDelay);
|
|
180
|
+
await current.body?.cancel().catch(() => undefined);
|
|
181
|
+
await braveRateLimit.waitForRecordedCooldown(searchSignal);
|
|
182
|
+
}
|
|
183
|
+
throw new Error("Brave Search retry loop exited unexpectedly");
|
|
184
|
+
}, searchSignal);
|
|
168
185
|
|
|
169
186
|
if (!response.ok) {
|
|
170
187
|
activityMonitor.logError(activityId, `HTTP ${response.status}`);
|
|
@@ -33,6 +33,7 @@ function buildProviderButtons(
|
|
|
33
33
|
{ value: "bocha", label: "Bocha", available: available.bocha },
|
|
34
34
|
{ value: "ollama", label: "Ollama", available: available.ollama },
|
|
35
35
|
{ value: "searxng", label: "SearXNG", available: available.searxng },
|
|
36
|
+
{ value: "degoog", label: "degoog", available: available.degoog },
|
|
36
37
|
{ value: "duckduckgo", label: "DuckDuckGo", available: available.duckduckgo },
|
|
37
38
|
{ value: "perplexity", label: "Perplexity", available: available.perplexity },
|
|
38
39
|
{ value: "gemini", label: "Gemini", available: available.gemini },
|
|
@@ -50,6 +51,7 @@ function buildProviderButtons(
|
|
|
50
51
|
{ value: "baizhi", label: "Baizhi", available: available.baizhi },
|
|
51
52
|
{ value: "zai", label: "Z.ai", available: available.zai },
|
|
52
53
|
{ value: "valyu", label: "Valyu", available: available.valyu },
|
|
54
|
+
{ value: "keenable", label: "Keenable", available: available.keenable },
|
|
53
55
|
];
|
|
54
56
|
|
|
55
57
|
return providers
|
|
@@ -1476,7 +1478,7 @@ const SCRIPT = `(function() {
|
|
|
1476
1478
|
var token = DATA.sessionToken;
|
|
1477
1479
|
var timeoutSec = DATA.timeout;
|
|
1478
1480
|
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
1479
|
-
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "baizhi", "you", "zai"];
|
|
1481
|
+
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "degoog", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "baizhi", "you", "zai", "keenable"];
|
|
1480
1482
|
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
1481
1483
|
var workflow = "summary-review";
|
|
1482
1484
|
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
@@ -1697,6 +1699,7 @@ const SCRIPT = `(function() {
|
|
|
1697
1699
|
if (provider === "bocha") return "Bocha";
|
|
1698
1700
|
if (provider === "ollama") return "Ollama";
|
|
1699
1701
|
if (provider === "searxng") return "SearXNG";
|
|
1702
|
+
if (provider === "degoog") return "degoog";
|
|
1700
1703
|
if (provider === "duckduckgo") return "DuckDuckGo";
|
|
1701
1704
|
if (provider === "perplexity") return "Perplexity";
|
|
1702
1705
|
if (provider === "exa") return "Exa";
|
|
@@ -1715,6 +1718,7 @@ const SCRIPT = `(function() {
|
|
|
1715
1718
|
if (provider === "baizhi") return "Baizhi";
|
|
1716
1719
|
if (provider === "zai") return "Z.ai";
|
|
1717
1720
|
if (provider === "valyu") return "Valyu";
|
|
1721
|
+
if (provider === "keenable") return "Keenable";
|
|
1718
1722
|
return "Unknown";
|
|
1719
1723
|
}
|
|
1720
1724
|
|
|
@@ -292,6 +292,7 @@ export function startCuratorServer(
|
|
|
292
292
|
if (provider === "bocha") return availableProviders.bocha;
|
|
293
293
|
if (provider === "ollama") return availableProviders.ollama;
|
|
294
294
|
if (provider === "searxng") return availableProviders.searxng;
|
|
295
|
+
if (provider === "degoog") return availableProviders.degoog;
|
|
295
296
|
if (provider === "duckduckgo") return availableProviders.duckduckgo;
|
|
296
297
|
if (provider === "perplexity") return availableProviders.perplexity;
|
|
297
298
|
if (provider === "exa") return availableProviders.exa;
|
|
@@ -309,6 +310,7 @@ export function startCuratorServer(
|
|
|
309
310
|
if (provider === "baizhi") return availableProviders.baizhi;
|
|
310
311
|
if (provider === "zai") return availableProviders.zai;
|
|
311
312
|
if (provider === "valyu") return availableProviders.valyu;
|
|
313
|
+
if (provider === "keenable") return availableProviders.keenable;
|
|
312
314
|
return false;
|
|
313
315
|
}
|
|
314
316
|
|
|
@@ -23,7 +23,7 @@ export interface DeclaredWebLink {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function discoverDeclaredWebLinks(
|
|
26
|
-
document: Document,
|
|
26
|
+
document: Document | null,
|
|
27
27
|
linkHeader: string | null,
|
|
28
28
|
responseUrl: string,
|
|
29
29
|
): DeclaredWebLink[] {
|
|
@@ -41,7 +41,7 @@ export function discoverDeclaredWebLinks(
|
|
|
41
41
|
if (links.size >= MAX_DECLARED_LINKS) break;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if (links.size < MAX_DECLARED_LINKS) {
|
|
44
|
+
if (document && links.size < MAX_DECLARED_LINKS) {
|
|
45
45
|
const declaredBase = document.querySelector("base[href]")?.getAttribute("href");
|
|
46
46
|
const documentBase = resolveHttpUrl(declaredBase, responseUrl) ?? responseUrl;
|
|
47
47
|
for (const element of document.querySelectorAll("link[rel][href], a[rel][href]")) {
|