@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
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
4
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
6
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
7
|
+
import { redactCredential, resolveCredential } from "./credential-source.ts";
|
|
8
|
+
import { fetchWithCredentialRedirects, getWebSearchConfigPath } from "./utils.ts";
|
|
9
|
+
|
|
10
|
+
const KEENABLE_SEARCH_URL = "https://api.keenable.ai/v1/search";
|
|
11
|
+
// Without a key the request goes to the public endpoint, which identifies the caller
|
|
12
|
+
// by X-Keenable-Title instead of a credential and answers 400 when the header is missing.
|
|
13
|
+
const KEENABLE_PUBLIC_SEARCH_URL = "https://api.keenable.ai/v1/search/public";
|
|
14
|
+
const KEENABLE_CLIENT_TITLE = "pi-web-access";
|
|
15
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
16
|
+
const SEARCH_TIMEOUT_MS = 30_000;
|
|
17
|
+
const MAX_REQUEST_RESULTS = 50;
|
|
18
|
+
const MAX_SNIPPET_CHARS = 500;
|
|
19
|
+
const RECENCY_DAYS: Record<NonNullable<SearchOptions["recencyFilter"]>, number> = {
|
|
20
|
+
day: 1,
|
|
21
|
+
week: 7,
|
|
22
|
+
month: 30,
|
|
23
|
+
year: 365,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
interface WebSearchConfig {
|
|
27
|
+
keenableApiKey?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface KeenableResult {
|
|
31
|
+
title?: unknown;
|
|
32
|
+
url?: unknown;
|
|
33
|
+
snippet?: unknown;
|
|
34
|
+
description?: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let cachedConfig: WebSearchConfig | null = null;
|
|
38
|
+
|
|
39
|
+
function loadConfig(): WebSearchConfig {
|
|
40
|
+
if (cachedConfig) return cachedConfig;
|
|
41
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
42
|
+
cachedConfig = {};
|
|
43
|
+
return cachedConfig;
|
|
44
|
+
}
|
|
45
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
46
|
+
let parsed: unknown;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(raw);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
54
|
+
throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
|
|
55
|
+
}
|
|
56
|
+
cachedConfig = parsed as WebSearchConfig;
|
|
57
|
+
return cachedConfig;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getApiKey(signal?: AbortSignal): Promise<string | null> {
|
|
61
|
+
return resolveCredential({
|
|
62
|
+
provider: "Keenable",
|
|
63
|
+
configuredValue: loadConfig().keenableApiKey,
|
|
64
|
+
environmentValue: process.env.KEENABLE_API_KEY,
|
|
65
|
+
signal,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface DomainFilters {
|
|
70
|
+
include: string[];
|
|
71
|
+
exclude: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
75
|
+
const filters: DomainFilters = { include: [], exclude: [] };
|
|
76
|
+
for (const raw of domainFilter ?? []) {
|
|
77
|
+
const domain = normalizeDomain(raw);
|
|
78
|
+
if (!domain) continue;
|
|
79
|
+
const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
|
|
80
|
+
if (!target.includes(domain)) target.push(domain);
|
|
81
|
+
}
|
|
82
|
+
return filters;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function passesDomainFilters(url: URL, filters: DomainFilters): boolean {
|
|
86
|
+
if (filters.include.length === 0 && filters.exclude.length === 0) return true;
|
|
87
|
+
const hostname = url.hostname.toLowerCase();
|
|
88
|
+
const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
|
|
89
|
+
if (filters.exclude.some(matches)) return false;
|
|
90
|
+
return filters.include.length === 0 || filters.include.some(matches);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// One domain uses the API's `site` field; the query syntax covers the rest, so no ranked
|
|
94
|
+
// window has to be filtered down locally to find in-domain pages.
|
|
95
|
+
function buildQuery(query: string, filters: DomainFilters, site: string | undefined): string {
|
|
96
|
+
const parts = [query];
|
|
97
|
+
if (!site && filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(" OR ")})`);
|
|
98
|
+
for (const domain of filters.exclude) parts.push(`-site:${domain}`);
|
|
99
|
+
return parts.join(" ");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function publishedAfter(recency: NonNullable<SearchOptions["recencyFilter"]>): string {
|
|
103
|
+
return new Date(Date.now() - RECENCY_DAYS[recency] * 86_400_000).toISOString().slice(0, 10);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function text(value: unknown): string {
|
|
107
|
+
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function errorMessage(err: unknown): string {
|
|
111
|
+
return err instanceof Error ? err.message : String(err);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function invalidResponse(message: string): Error {
|
|
115
|
+
return new Error(`Keenable returned invalid response: ${message}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseResponse(value: unknown): KeenableResult[] {
|
|
119
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("expected an object envelope");
|
|
120
|
+
const envelope = value as Record<string, unknown>;
|
|
121
|
+
if (!Array.isArray(envelope.results)) throw invalidResponse("expected results array");
|
|
122
|
+
return envelope.results as KeenableResult[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The public endpoint needs no key, so the provider is always usable once selected.
|
|
126
|
+
export function isKeenableAvailable(): boolean {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function searchWithKeenable(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
131
|
+
const apiKey = await getApiKey(options.signal);
|
|
132
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
133
|
+
const filters = parseDomainFilter(options.domainFilter);
|
|
134
|
+
const site = filters.include.length === 1 ? filters.include[0] : undefined;
|
|
135
|
+
// Results are reapplied to the domain filters locally, so leave a little headroom.
|
|
136
|
+
const hasDomainClauses = filters.include.length > 1 || filters.exclude.length > 0;
|
|
137
|
+
const body = {
|
|
138
|
+
query: buildQuery(query, filters, site),
|
|
139
|
+
max_results: hasDomainClauses ? Math.min(MAX_REQUEST_RESULTS, numResults + 5) : numResults,
|
|
140
|
+
snippet_max_length: MAX_SNIPPET_CHARS,
|
|
141
|
+
...(site ? { site } : {}),
|
|
142
|
+
...(options.recencyFilter ? { published_after: publishedAfter(options.recencyFilter) } : {}),
|
|
143
|
+
};
|
|
144
|
+
const headers: Record<string, string> = {
|
|
145
|
+
Accept: "application/json",
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
"X-Keenable-Title": KEENABLE_CLIENT_TITLE,
|
|
148
|
+
...(apiKey ? { "X-API-Key": apiKey } : {}),
|
|
149
|
+
};
|
|
150
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
151
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
152
|
+
let response: Response;
|
|
153
|
+
let entries: KeenableResult[];
|
|
154
|
+
try {
|
|
155
|
+
response = await fetchWithCredentialRedirects(apiKey ? KEENABLE_SEARCH_URL : KEENABLE_PUBLIC_SEARCH_URL, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers,
|
|
158
|
+
body: JSON.stringify(body),
|
|
159
|
+
signal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,
|
|
160
|
+
}, ["X-API-Key"]);
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
const errorText = redactCredential(await response.text(), apiKey).slice(0, 300);
|
|
163
|
+
const hint = response.status === 429 && !apiKey
|
|
164
|
+
? ` (keyless rate limit; set keenableApiKey or KEENABLE_API_KEY to raise it)`
|
|
165
|
+
: "";
|
|
166
|
+
throw new Error(`Keenable error ${response.status}: ${errorText}${hint}`);
|
|
167
|
+
}
|
|
168
|
+
let rawData: unknown;
|
|
169
|
+
try {
|
|
170
|
+
rawData = await response.json();
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (err instanceof Error && err.name === "TimeoutError") throw err;
|
|
173
|
+
throw new Error(`Keenable returned invalid JSON: ${errorMessage(err)}`);
|
|
174
|
+
}
|
|
175
|
+
entries = parseResponse(rawData);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (options.signal?.aborted) {
|
|
178
|
+
activityMonitor.logComplete(activityId, 0);
|
|
179
|
+
throw new Error("Aborted");
|
|
180
|
+
}
|
|
181
|
+
const message = errorMessage(err);
|
|
182
|
+
const providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === "TimeoutError");
|
|
183
|
+
const outgoing = providerTimeout
|
|
184
|
+
? new Error(`Keenable request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)
|
|
185
|
+
: (() => {
|
|
186
|
+
const redactedMessage = redactCredential(message, apiKey);
|
|
187
|
+
if (redactedMessage === message && err instanceof Error) return err;
|
|
188
|
+
const redactedError = new Error(redactedMessage);
|
|
189
|
+
if (err instanceof Error) redactedError.name = err.name;
|
|
190
|
+
return redactedError;
|
|
191
|
+
})();
|
|
192
|
+
activityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));
|
|
193
|
+
throw outgoing;
|
|
194
|
+
}
|
|
195
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
196
|
+
const results: SearchResponse["results"] = [];
|
|
197
|
+
const seen = new Set<string>();
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (!entry || typeof entry !== "object") continue;
|
|
200
|
+
if (typeof entry.url !== "string" || !entry.url) continue;
|
|
201
|
+
let resultUrl: URL;
|
|
202
|
+
try {
|
|
203
|
+
resultUrl = new URL(entry.url);
|
|
204
|
+
} catch {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (resultUrl.protocol !== "http:" && resultUrl.protocol !== "https:") continue;
|
|
208
|
+
if (!passesDomainFilters(resultUrl, filters)) continue;
|
|
209
|
+
if (seen.has(resultUrl.href)) continue;
|
|
210
|
+
seen.add(resultUrl.href);
|
|
211
|
+
// The page text lives in `snippet`; `description` is usually empty and only a fallback.
|
|
212
|
+
const snippet = text(entry.snippet) || text(entry.description);
|
|
213
|
+
results.push({
|
|
214
|
+
title: text(entry.title) || `Source ${results.length + 1}`,
|
|
215
|
+
url: resultUrl.href,
|
|
216
|
+
snippet: snippet.slice(0, MAX_SNIPPET_CHARS),
|
|
217
|
+
});
|
|
218
|
+
if (results.length >= numResults) break;
|
|
219
|
+
}
|
|
220
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
221
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pi-web-access-mcp: serves the web tools over MCP stdio. stdout carries only
|
|
3
|
+
// JSON-RPC, so every diagnostic goes to stderr.
|
|
4
|
+
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import pkg from "./package.json" with { type: "json" };
|
|
7
|
+
import { createMcpServer } from "./mcp-server.ts";
|
|
8
|
+
import { createStandaloneWebToolCore } from "./web-tool-core.ts";
|
|
9
|
+
|
|
10
|
+
// Keep stray console output from dependencies off the JSON-RPC stream.
|
|
11
|
+
console.log = console.info = console.debug = console.error;
|
|
12
|
+
|
|
13
|
+
const core = createStandaloneWebToolCore();
|
|
14
|
+
const server = createMcpServer(core, { name: "pi-web-access", version: pkg.version }, core.enabledToolNames);
|
|
15
|
+
const transport = new StdioServerTransport();
|
|
16
|
+
|
|
17
|
+
let closing = false;
|
|
18
|
+
async function shutdown(code: number): Promise<void> {
|
|
19
|
+
if (closing) return;
|
|
20
|
+
closing = true;
|
|
21
|
+
try {
|
|
22
|
+
// Closing aborts in-flight tool calls through their request signals.
|
|
23
|
+
await server.close();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("pi-web-access-mcp: error during shutdown:", error);
|
|
26
|
+
}
|
|
27
|
+
process.exit(code);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process.stdin.once("end", () => void shutdown(0));
|
|
31
|
+
process.stdin.once("close", () => void shutdown(0));
|
|
32
|
+
process.once("SIGINT", () => void shutdown(0));
|
|
33
|
+
process.once("SIGTERM", () => void shutdown(0));
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await server.connect(transport);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("pi-web-access-mcp: failed to start:", error);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// MCP server exposing the web tools over any transport. Tool behavior lives in
|
|
2
|
+
// the WebToolCore; this module only lists tools, validates arguments at the
|
|
3
|
+
// boundary, forwards cancellation, and maps results to MCP content.
|
|
4
|
+
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
type CallToolResult,
|
|
9
|
+
ErrorCode,
|
|
10
|
+
ListToolsRequestSchema,
|
|
11
|
+
McpError,
|
|
12
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { MCP_TOOL_DEFINITIONS, validateMcpToolArguments } from "./mcp-tool-definitions.ts";
|
|
14
|
+
import type {
|
|
15
|
+
FetchContentCallParams,
|
|
16
|
+
GetSearchContentCallParams,
|
|
17
|
+
SourceCheckCallParams,
|
|
18
|
+
WebSearchCallParams,
|
|
19
|
+
WebToolCore,
|
|
20
|
+
WebToolResult,
|
|
21
|
+
} from "./web-tool-contract.ts";
|
|
22
|
+
|
|
23
|
+
export interface McpServerInfo {
|
|
24
|
+
name: string;
|
|
25
|
+
version: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ToolRunner = (core: WebToolCore, args: Record<string, unknown>, signal: AbortSignal) => Promise<WebToolResult>;
|
|
29
|
+
type ToolInputSchema = { type: "object"; [key: string]: unknown };
|
|
30
|
+
|
|
31
|
+
// Arguments are validated against the matching MCP schema before these casts run.
|
|
32
|
+
const TOOL_RUNNERS: Record<string, ToolRunner> = {
|
|
33
|
+
web_search: (core, args, signal) => core.webSearch(args as WebSearchCallParams, signal),
|
|
34
|
+
fetch_content: (core, args, signal) => core.fetchContent(args as FetchContentCallParams, signal),
|
|
35
|
+
get_search_content: (core, args, signal) => core.getSearchContent(args as unknown as GetSearchContentCallParams, signal),
|
|
36
|
+
source_check: (core, args, signal) => core.sourceCheck(args as unknown as SourceCheckCallParams, signal),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function errorResult(text: string): CallToolResult {
|
|
40
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// details stays off the wire: clients such as Codex show the model structuredContent
|
|
44
|
+
// instead of content, and details holds host metadata rather than the results.
|
|
45
|
+
function toCallToolResult(result: WebToolResult): CallToolResult {
|
|
46
|
+
return { content: result.content, ...(result.isError && { isError: true }) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Serves the tools named in enabledTools (default: all four MCP tools). */
|
|
50
|
+
export function createMcpServer(core: WebToolCore, info: McpServerInfo, enabledTools?: readonly string[]): Server {
|
|
51
|
+
const server = new Server(info, { capabilities: { tools: {} } });
|
|
52
|
+
const tools = enabledTools ? MCP_TOOL_DEFINITIONS.filter((tool) => enabledTools.includes(tool.name)) : MCP_TOOL_DEFINITIONS;
|
|
53
|
+
|
|
54
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
55
|
+
tools: tools.map((tool) => ({
|
|
56
|
+
name: tool.name,
|
|
57
|
+
description: tool.description,
|
|
58
|
+
inputSchema: tool.inputSchema as ToolInputSchema,
|
|
59
|
+
annotations: tool.annotations,
|
|
60
|
+
})),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
64
|
+
const { name } = request.params;
|
|
65
|
+
const tool = tools.find((candidate) => candidate.name === name);
|
|
66
|
+
const run = TOOL_RUNNERS[name];
|
|
67
|
+
if (!tool || !run) throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${name}`);
|
|
68
|
+
|
|
69
|
+
const args = request.params.arguments ?? {};
|
|
70
|
+
const problems = validateMcpToolArguments(tool, args);
|
|
71
|
+
if (problems.length > 0) return errorResult(`Invalid arguments for ${name}:\n- ${problems.join("\n- ")}`);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
return toCallToolResult(await run(core, args, extra.signal));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return errorResult(error instanceof Error ? error.message : String(error));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return server;
|
|
81
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// MCP-only tool definitions: names, descriptions, and input schemas for the
|
|
2
|
+
// standalone MCP server. Deliberately independent of the Pi tool schemas in
|
|
3
|
+
// index.ts so MCP changes never touch Pi's provider-facing definitions.
|
|
4
|
+
// Pi-only parameters (workflow/curator, prompt, answerModel, timestamp, frames,
|
|
5
|
+
// model, auth) are not exposed; unknown properties are rejected.
|
|
6
|
+
|
|
7
|
+
import { Type, type TSchema } from "typebox";
|
|
8
|
+
import { Value } from "typebox/value";
|
|
9
|
+
import { RESOLVED_SEARCH_PROVIDERS } from "./gemini-search.ts";
|
|
10
|
+
|
|
11
|
+
function stringEnum(values: readonly string[], description: string) {
|
|
12
|
+
return Type.Unsafe<string>({ type: "string", enum: [...values], description });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Kimi authenticates only through Pi's login, so it is never offered here.
|
|
16
|
+
const MCP_SEARCH_PROVIDERS = RESOLVED_SEARCH_PROVIDERS.filter((provider) => provider !== "kimi");
|
|
17
|
+
|
|
18
|
+
const providerSchema = Type.Union([
|
|
19
|
+
stringEnum(["auto", "all", ...MCP_SEARCH_PROVIDERS], "Single provider."),
|
|
20
|
+
Type.Array(stringEnum(MCP_SEARCH_PROVIDERS, "Provider."), { minItems: 1 }),
|
|
21
|
+
], { description: "Search provider, or a non-empty list of providers to search simultaneously. Omit or use auto for the configured provider; all searches every eligible provider. Providers without credentials return an error." });
|
|
22
|
+
|
|
23
|
+
const recencySchema = stringEnum(["day", "week", "month", "year"], "Filter results by recency.");
|
|
24
|
+
const domainFilterSchema = Type.Array(Type.String(), { description: "Limit to domains; prefix with - to exclude." });
|
|
25
|
+
const numResultsSchema = Type.Integer({ minimum: 1, maximum: 20, description: "Results per query (default: 5, max: 20)." });
|
|
26
|
+
const proxySchema = Type.String({ description: "http(s) or socks proxy URL for this call's outbound requests; empty string forces direct access." });
|
|
27
|
+
// Page fetches write a page cache and clone GitHub URLs into a temp directory,
|
|
28
|
+
// including the fetches behind includeContent and fetchContent.
|
|
29
|
+
const fetchingAnnotations = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
|
|
30
|
+
|
|
31
|
+
export interface McpToolDefinition {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
inputSchema: TSchema;
|
|
35
|
+
annotations: { readOnlyHint: boolean; destructiveHint?: boolean; openWorldHint: boolean };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const MCP_TOOL_DEFINITIONS: readonly McpToolDefinition[] = [
|
|
39
|
+
{
|
|
40
|
+
name: "web_search",
|
|
41
|
+
description: "Search the web and return bounded, source-linked results or provider answers. Full results are stored for retrieval with get_search_content using the returned responseId.",
|
|
42
|
+
inputSchema: Type.Object({
|
|
43
|
+
query: Type.Optional(Type.String({ description: "Single search query." })),
|
|
44
|
+
queries: Type.Optional(Type.Array(Type.String(), { description: "Multiple queries searched concurrently; vary angles for broader coverage." })),
|
|
45
|
+
numResults: Type.Optional(numResultsSchema),
|
|
46
|
+
includeContent: Type.Optional(Type.Boolean({ description: "Also fetch full page content for the results, retrievable with get_search_content." })),
|
|
47
|
+
recencyFilter: Type.Optional(recencySchema),
|
|
48
|
+
domainFilter: Type.Optional(domainFilterSchema),
|
|
49
|
+
category: Type.Optional(Type.String({ description: "Exa only: result category, e.g. news, research paper." })),
|
|
50
|
+
provider: Type.Optional(providerSchema),
|
|
51
|
+
proxy: Type.Optional(proxySchema),
|
|
52
|
+
}, { additionalProperties: false }),
|
|
53
|
+
annotations: fetchingAnnotations,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "fetch_content",
|
|
57
|
+
description: "Fetch URL(s) and return bounded content. Supports web pages, GitHub repositories, and PDFs. Full content is stored for retrieval with get_search_content using the returned responseId.",
|
|
58
|
+
inputSchema: Type.Object({
|
|
59
|
+
url: Type.Optional(Type.String({ description: "Single URL to fetch." })),
|
|
60
|
+
urls: Type.Optional(Type.Array(Type.String(), { description: "Multiple URLs fetched in parallel." })),
|
|
61
|
+
mode: Type.Optional(stringEnum(["readable", "raw"], "readable = extract readable content as markdown (default); raw = return the exact textual body using direct HTTP only.")),
|
|
62
|
+
forceClone: Type.Optional(Type.Boolean({ description: "Force cloning GitHub repositories that exceed the size threshold." })),
|
|
63
|
+
proxy: Type.Optional(proxySchema),
|
|
64
|
+
}, { additionalProperties: false }),
|
|
65
|
+
annotations: fetchingAnnotations,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "get_search_content",
|
|
69
|
+
description: "Retrieve bounded pages of stored search results or fetched content from a previous web_search, fetch_content, or source_check call, or find matching passages with findText.",
|
|
70
|
+
inputSchema: Type.Object({
|
|
71
|
+
responseId: Type.String({ description: "The responseId from web_search, fetch_content, or source_check." }),
|
|
72
|
+
query: Type.Optional(Type.String({ description: "Select stored results for this search query." })),
|
|
73
|
+
queryIndex: Type.Optional(Type.Integer({ minimum: 0, description: "Select stored results for the query at this index." })),
|
|
74
|
+
url: Type.Optional(Type.String({ description: "Select stored content for this URL." })),
|
|
75
|
+
urlIndex: Type.Optional(Type.Integer({ minimum: 0, description: "Select stored content for the URL at this index." })),
|
|
76
|
+
offset: Type.Optional(Type.Integer({ minimum: 0, description: "Character offset into the stored content (default 0). Ignored when findText is supplied." })),
|
|
77
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, description: "Maximum characters to return. Ignored when findText is supplied." })),
|
|
78
|
+
findText: Type.Optional(Type.Union([
|
|
79
|
+
Type.String({ minLength: 1, maxLength: 500 }),
|
|
80
|
+
Type.Array(Type.String({ minLength: 1, maxLength: 500 }), { minItems: 1, maxItems: 10 }),
|
|
81
|
+
], { description: "Text or texts to find in the selected stored content." })),
|
|
82
|
+
findMode: Type.Optional(stringEnum(["exact", "case-insensitive", "fuzzy"], "Matching mode for findText (default: case-insensitive).")),
|
|
83
|
+
}, { additionalProperties: false }),
|
|
84
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "source_check",
|
|
88
|
+
description: "Gather web sources for a claim and return a bounded machine-readable research artifact with exact passage citations for manual review.",
|
|
89
|
+
inputSchema: Type.Object({
|
|
90
|
+
claim: Type.String({ description: "The assertion to gather web sources for." }),
|
|
91
|
+
queries: Type.Optional(Type.Array(Type.String(), { description: "Search queries (default: the claim)." })),
|
|
92
|
+
numResults: Type.Optional(numResultsSchema),
|
|
93
|
+
fetchContent: Type.Optional(Type.Boolean({ description: "Fetch up to 5 result pages for exact passage extraction." })),
|
|
94
|
+
recencyFilter: Type.Optional(recencySchema),
|
|
95
|
+
domainFilter: Type.Optional(domainFilterSchema),
|
|
96
|
+
provider: Type.Optional(providerSchema),
|
|
97
|
+
proxy: Type.Optional(proxySchema),
|
|
98
|
+
}, { additionalProperties: false }),
|
|
99
|
+
annotations: fetchingAnnotations,
|
|
100
|
+
},
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
/** Returns human-readable validation problems; an empty array means the arguments are valid. */
|
|
104
|
+
export function validateMcpToolArguments(tool: McpToolDefinition, args: unknown): string[] {
|
|
105
|
+
if (Value.Check(tool.inputSchema, args)) return [];
|
|
106
|
+
const problems = new Set<string>();
|
|
107
|
+
for (const error of Value.Errors(tool.inputSchema, args)) {
|
|
108
|
+
// additionalProperties also reports a per-property "schema is false"; the summary below names the property.
|
|
109
|
+
if (error.keyword === "boolean") continue;
|
|
110
|
+
const path = error.instancePath || "arguments";
|
|
111
|
+
const unknown = error.keyword === "additionalProperties"
|
|
112
|
+
? (error.params as { additionalProperties?: string[] }).additionalProperties
|
|
113
|
+
: undefined;
|
|
114
|
+
problems.add(unknown?.length ? `${path}: unknown properties ${unknown.join(", ")}` : `${path}: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
return problems.size > 0 ? [...problems] : ["arguments do not match the input schema"];
|
|
117
|
+
}
|
|
@@ -91,6 +91,14 @@ function resolveCurrentModelSearchTarget(model: CurrentModel): CurrentModelSearc
|
|
|
91
91
|
throw new Error("Current model is not backed by an official OpenAI Responses endpoint");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// The active model draws on a ChatGPT subscription: openai-codex, or openai signed in
|
|
95
|
+
// through Pi's "Sign in with ChatGPT" OAuth rather than an API key.
|
|
96
|
+
export function isOpenAISubscriptionModelSelected(ctx?: Pick<ExtensionContext, "model"> & { modelRegistry?: Pick<ExtensionContext["modelRegistry"], "isUsingOAuth"> }): boolean {
|
|
97
|
+
const model = ctx?.model;
|
|
98
|
+
if (model?.provider === "openai-codex") return true;
|
|
99
|
+
return model?.provider === "openai" && ctx?.modelRegistry?.isUsingOAuth?.(model) === true;
|
|
100
|
+
}
|
|
101
|
+
|
|
94
102
|
export function isCurrentModelHostedSearchEligible(ctx?: Pick<ExtensionContext, "model">): boolean {
|
|
95
103
|
const model = ctx?.model;
|
|
96
104
|
if (!model || !/^gpt-/iu.test(model.id)) return false;
|
|
@@ -163,6 +171,15 @@ function isCodexJwt(token: string): boolean {
|
|
|
163
171
|
return !!payload?.["https://api.openai.com/auth"];
|
|
164
172
|
}
|
|
165
173
|
|
|
174
|
+
function isOfficialOpenAIBaseUrl(baseUrl: string | undefined): boolean {
|
|
175
|
+
if (baseUrl === undefined) return false;
|
|
176
|
+
try {
|
|
177
|
+
return new URL(baseUrl).toString().replace(/\/+$/u, "") === "https://api.openai.com/v1";
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
166
183
|
function extractAccountId(token: string): string | undefined {
|
|
167
184
|
const payload = decodeJwtPayload(token);
|
|
168
185
|
const auth = payload?.["https://api.openai.com/auth"];
|
|
@@ -282,16 +299,12 @@ async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, provid
|
|
|
282
299
|
// Auth can override the model's base URL. Do not guess Responses/web_search
|
|
283
300
|
// support from a gateway URL, or pair its credential with the official API.
|
|
284
301
|
const baseUrl = resolved.baseUrl ?? preferred.baseUrl;
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (!isOfficial) {
|
|
293
|
-
throw new CustomOpenAIBaseUrlError(`OpenAI web search cannot reuse Pi credentials with a custom baseUrl by default. Set openaiResponsesUrl in ${CONFIG_PATH} to the full Responses endpoint for this credential.`);
|
|
294
|
-
}
|
|
302
|
+
const isOfficialBaseUrl = isOfficialOpenAIBaseUrl(baseUrl);
|
|
303
|
+
// Pi sends credentials for the official API base directly to api.openai.com, including
|
|
304
|
+
// "Sign in with ChatGPT" access tokens, which are ChatGPT JWTs but not Codex credentials.
|
|
305
|
+
const useCodexEndpoint = provider === "openai-codex" || (!isOfficialBaseUrl && isCodexJwt(resolved.apiKey));
|
|
306
|
+
if (!hasExplicitResponsesUrl && !useProviderBaseUrl && !useCodexEndpoint && baseUrl !== undefined && !isOfficialBaseUrl) {
|
|
307
|
+
throw new CustomOpenAIBaseUrlError(`OpenAI web search cannot reuse Pi credentials with a custom baseUrl by default. Set openaiResponsesUrl in ${CONFIG_PATH} to the full Responses endpoint for this credential.`);
|
|
295
308
|
}
|
|
296
309
|
let providerResponsesUrl = responsesUrl;
|
|
297
310
|
if (useProviderBaseUrl) {
|
|
@@ -309,6 +322,7 @@ async function resolvePiAuth(ctx: ExtensionContext, responsesUrl: string, provid
|
|
|
309
322
|
model: modelOverride ?? preferred.id,
|
|
310
323
|
headers: resolved.headers ?? {},
|
|
311
324
|
responsesUrl: providerResponsesUrl,
|
|
325
|
+
useCodexEndpoint,
|
|
312
326
|
...(useProviderBaseUrl ? { useProviderBaseUrl: true } : {}),
|
|
313
327
|
};
|
|
314
328
|
}
|
|
@@ -454,24 +468,57 @@ async function parseOpenAIResponse(response: Response): Promise<ParsedOpenAIResp
|
|
|
454
468
|
const outputItems: unknown[] = [];
|
|
455
469
|
let completedResponse: Record<string, unknown> | null = null;
|
|
456
470
|
let webSearchCallSeen = false;
|
|
471
|
+
let streamError: { code?: unknown; type?: unknown; message?: unknown } | null = null;
|
|
457
472
|
for (const line of text.split("\n")) {
|
|
458
473
|
if (!line.startsWith("data: ")) continue;
|
|
459
474
|
const data = line.slice(6).trim();
|
|
460
475
|
if (!data || data === "[DONE]") continue;
|
|
461
476
|
try {
|
|
462
477
|
const parsed = JSON.parse(data) as Record<string, unknown>;
|
|
478
|
+
// The API can fail mid-stream with HTTP 200: a terminal `error` event and
|
|
479
|
+
// `response.failed` carry the real error (e.g. quota exhaustion), while no
|
|
480
|
+
// output items or completed response ever arrive. Surface it faithfully.
|
|
481
|
+
// `error` events are terminal in the Responses API, so a captured stream
|
|
482
|
+
// error deliberately takes precedence over any output parsed afterwards.
|
|
483
|
+
if (parsed.type === "error") {
|
|
484
|
+
const err = parsed.error;
|
|
485
|
+
streamError ??= err && typeof err === "object"
|
|
486
|
+
? err as { code?: unknown; type?: unknown; message?: unknown }
|
|
487
|
+
: { code: parsed.code, message: typeof err === "string" ? err : (typeof parsed.message === "string" ? parsed.message : undefined) };
|
|
488
|
+
}
|
|
463
489
|
if (typeof parsed.type === "string" && parsed.type.startsWith("response.web_search_call")) webSearchCallSeen = true;
|
|
464
490
|
if (parsed.type === "response.output_item.done" && parsed.item) {
|
|
465
491
|
outputItems.push(parsed.item);
|
|
466
492
|
webSearchCallSeen ||= isWebSearchCall(parsed.item);
|
|
467
493
|
}
|
|
468
|
-
if ((parsed.type === "response.done" || parsed.type === "response.completed") && parsed.response && typeof parsed.response === "object") {
|
|
494
|
+
if ((parsed.type === "response.failed" || parsed.type === "response.incomplete" || parsed.type === "response.done" || parsed.type === "response.completed") && parsed.response && typeof parsed.response === "object") {
|
|
469
495
|
completedResponse = parsed.response as Record<string, unknown>;
|
|
496
|
+
const responseError = (completedResponse as { error?: unknown }).error;
|
|
497
|
+
if (responseError && typeof responseError === "object") {
|
|
498
|
+
streamError ??= responseError as { code?: unknown; type?: unknown; message?: unknown };
|
|
499
|
+
}
|
|
500
|
+
// A failed response can carry `error: null`; never let its partial
|
|
501
|
+
// output pass for a successful search.
|
|
502
|
+
if (parsed.type === "response.failed") {
|
|
503
|
+
// Wording keeps "invalid response" so provider routing classifies
|
|
504
|
+
// this the same as the old "no web_search_call" outcome.
|
|
505
|
+
streamError ??= { message: "response stream failed with no error payload (invalid response)" };
|
|
506
|
+
}
|
|
470
507
|
}
|
|
471
508
|
} catch {
|
|
472
509
|
}
|
|
473
510
|
}
|
|
474
511
|
|
|
512
|
+
if (streamError) {
|
|
513
|
+
const code = typeof streamError.code === "string" ? streamError.code : undefined;
|
|
514
|
+
const type = typeof streamError.type === "string" ? streamError.type : undefined;
|
|
515
|
+
const message = typeof streamError.message === "string" && streamError.message.trim().length > 0
|
|
516
|
+
? streamError.message
|
|
517
|
+
: "OpenAI API stream failed";
|
|
518
|
+
const detail = [type, code].filter(Boolean).join("/");
|
|
519
|
+
throw new Error(`OpenAI API stream error${detail ? ` (${detail})` : ""}: ${message}`);
|
|
520
|
+
}
|
|
521
|
+
|
|
475
522
|
if (completedResponse) {
|
|
476
523
|
const output = Array.isArray(completedResponse.output) ? completedResponse.output : [];
|
|
477
524
|
const payload = output.length > 0 ? completedResponse : { ...completedResponse, output: outputItems };
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-access",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agent. Supports OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Crawl4AI, Jina, SERPdive, Kagi, Ollama, AnySearch, Bright Data SERP, SerpBase, SerpApi, Serply, You.com, SearXNG, Exa, Perplexity, Gemini, Kimi, and Mistral.",
|
|
3
|
+
"version": "0.37.0",
|
|
4
|
+
"description": "Web search, URL fetching, GitHub repo cloning, PDF extraction, YouTube video understanding, and local video analysis for Pi coding agent. Supports OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Crawl4AI, Jina, SERPdive, Kagi, Ollama, AnySearch, Bright Data SERP, SerpBase, SerpApi, Serply, You.com, SearXNG, degoog, Exa, Perplexity, Gemini, Kimi, and Mistral.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pi-web-access-mcp": "dist/mcp-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=22.19.0"
|
|
11
|
+
},
|
|
6
12
|
"scripts": {
|
|
7
13
|
"test": "node --import ./test/isolate-env.mjs --test",
|
|
8
14
|
"typecheck": "tsc",
|
|
@@ -22,6 +28,7 @@
|
|
|
22
28
|
"search1api",
|
|
23
29
|
"searchinfinity",
|
|
24
30
|
"querit",
|
|
31
|
+
"degoog",
|
|
25
32
|
"perplexity",
|
|
26
33
|
"fetch",
|
|
27
34
|
"scraping"
|
|
@@ -45,9 +52,9 @@
|
|
|
45
52
|
"pi-web-fetch-demo.mp4"
|
|
46
53
|
],
|
|
47
54
|
"devDependencies": {
|
|
48
|
-
"@earendil-works/pi-ai": "0.
|
|
49
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
50
|
-
"@earendil-works/pi-tui": "0.
|
|
55
|
+
"@earendil-works/pi-ai": "1.0.4",
|
|
56
|
+
"@earendil-works/pi-coding-agent": "1.0.4",
|
|
57
|
+
"@earendil-works/pi-tui": "1.0.4",
|
|
51
58
|
"@types/turndown": "^5.0.6",
|
|
52
59
|
"esbuild": "^0.28.1",
|
|
53
60
|
"typebox": "1.3.27",
|
|
@@ -70,6 +77,17 @@
|
|
|
70
77
|
"@earendil-works/pi-tui": "*",
|
|
71
78
|
"typebox": "*"
|
|
72
79
|
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"@earendil-works/pi-ai": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"@earendil-works/pi-coding-agent": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"@earendil-works/pi-tui": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
73
91
|
"pi": {
|
|
74
92
|
"extensions": [
|
|
75
93
|
"./dist"
|