@herbertgao/pi-extensions 2026.9.17 → 2026.9.19
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 +31 -33
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +21 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +15 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +43 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +7 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +75 -75
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
- package/node_modules/@herbertgao/resume-from/package.json +4 -2
- package/node_modules/@herbertgao/sol-pi/package.json +6 -6
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +99 -99
- package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
- package/node_modules/@tifan/pi-handoff/package.json +1 -1
- package/node_modules/@tifan/pi-handoff/src/index.ts +56 -26
- package/node_modules/@tifan/pi-mermaid-open/package.json +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
- package/node_modules/@tifan/pi-recap/package.json +1 -1
- package/node_modules/@tifan/pi-rename/package.json +1 -1
- package/node_modules/pi-antigravity/README.md +29 -6
- package/node_modules/pi-antigravity/package.json +14 -9
- package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
- package/node_modules/pi-antigravity/src/client/client.ts +1 -1
- package/node_modules/pi-antigravity/src/image/image.ts +4 -4
- package/node_modules/pi-antigravity/src/index.ts +203 -13
- package/node_modules/pi-antigravity/src/search/index.ts +1 -0
- package/node_modules/pi-antigravity/src/search/search.ts +325 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
- package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
- package/node_modules/pi-antigravity/src/types/types.ts +5 -12
- package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
- package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
- package/node_modules/pi-lens/package.json +158 -158
- package/node_modules/pi-multi-account/package.json +2 -2
- package/node_modules/pi-typesafe/README.md +3 -1
- package/node_modules/pi-typesafe/dist/auth.d.ts +9 -5
- package/node_modules/pi-typesafe/dist/auth.js +7 -3
- package/node_modules/pi-typesafe/dist/backends.d.ts +33 -3
- package/node_modules/pi-typesafe/dist/backends.js +95 -1
- package/node_modules/pi-typesafe/dist/client.d.ts +5 -5
- package/node_modules/pi-typesafe/dist/client.js +12 -9
- package/node_modules/pi-typesafe/dist/credentials.d.ts +14 -8
- package/node_modules/pi-typesafe/dist/credentials.js +10 -7
- package/node_modules/pi-typesafe/dist/errors.d.ts +2 -2
- 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/login.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/login.js +2 -2
- package/node_modules/pi-typesafe/package.json +6 -5
- package/node_modules/pi-web-access/CHANGELOG.md +72 -0
- package/node_modules/pi-web-access/README.md +34 -13
- package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
- 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/dist/index.js +1337 -1009
- package/node_modules/pi-web-access/exa.ts +20 -52
- package/node_modules/pi-web-access/extract.ts +41 -4
- package/node_modules/pi-web-access/gemini-search.ts +12 -4
- package/node_modules/pi-web-access/index.ts +29 -37
- package/node_modules/pi-web-access/openai-search.ts +35 -7
- package/node_modules/pi-web-access/package.json +8 -7
- package/node_modules/pi-web-access/page-query.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +2 -3
- package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
- package/node_modules/pi-web-access/storage.ts +48 -77
- package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
- package/node_modules/pi-web-access/summary-review.ts +3 -4
- package/node_modules/pi-web-access/tavily.ts +67 -20
- package/node_modules/pi-web-access/tool-activation.ts +53 -33
- package/node_modules/pi-web-access/utils.ts +1 -1
- package/node_modules/pi-web-access/you.ts +164 -0
- package/node_modules/pi-web-access/zai.ts +161 -0
- package/node_modules/remote-pi/package.json +5 -1
- package/package.json +19 -31
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +0 -1013
- package/node_modules/pi-mcp-adapter/LICENSE +0 -21
- package/node_modules/pi-mcp-adapter/OAUTH.md +0 -369
- package/node_modules/pi-mcp-adapter/README.md +0 -1021
- package/node_modules/pi-mcp-adapter/abort.ts +0 -34
- package/node_modules/pi-mcp-adapter/agent-dir.ts +0 -68
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +0 -416
- package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +0 -36
- package/node_modules/pi-mcp-adapter/app-bridge.bundle.js +0 -67
- package/node_modules/pi-mcp-adapter/banner.png +0 -0
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +0 -193
- package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +0 -365
- package/node_modules/pi-mcp-adapter/cli.js +0 -454
- package/node_modules/pi-mcp-adapter/commands.ts +0 -861
- package/node_modules/pi-mcp-adapter/config.ts +0 -1565
- package/node_modules/pi-mcp-adapter/consent-manager.ts +0 -95
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +0 -244
- package/node_modules/pi-mcp-adapter/direct-tools.ts +0 -428
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +0 -2
- package/node_modules/pi-mcp-adapter/dist/abort.js +0 -36
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +0 -10
- package/node_modules/pi-mcp-adapter/dist/agent-dir.js +0 -63
- package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +0 -8
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +0 -398
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +0 -1
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +0 -31
- package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +0 -6
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +0 -194
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +0 -9
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +0 -362
- package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +0 -134
- package/node_modules/pi-mcp-adapter/dist/config.js +0 -1361
- package/node_modules/pi-mcp-adapter/dist/config.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +0 -18
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +0 -88
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +0 -17
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +0 -316
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +0 -131
- package/node_modules/pi-mcp-adapter/dist/errors.js +0 -278
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +0 -8
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +0 -88
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +0 -14
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +0 -367
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +0 -77
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +0 -2
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +0 -51
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +0 -205
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +0 -2
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +0 -68
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +0 -56
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +0 -410
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +0 -51
- package/node_modules/pi-mcp-adapter/dist/logger.js +0 -131
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +0 -26
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +0 -103
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +0 -118
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +0 -1097
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +0 -170
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +0 -1142
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +0 -24
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +0 -203
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +0 -53
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +0 -441
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +0 -148
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +0 -689
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +0 -6
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +0 -166
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +0 -102
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +0 -369
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +0 -95
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +0 -242
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +0 -19
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +0 -301
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +0 -6
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +0 -505
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +0 -2
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +0 -144
- package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +0 -10
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +0 -312
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +0 -1
- package/node_modules/pi-mcp-adapter/dist/resource-tools.js +0 -15
- package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +0 -13
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +0 -97
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +0 -17
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +0 -203
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +0 -11
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +0 -172
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +0 -157
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +0 -1745
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +0 -34
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +0 -140
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +0 -26
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +0 -142
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +0 -73
- package/node_modules/pi-mcp-adapter/dist/state.js +0 -2
- package/node_modules/pi-mcp-adapter/dist/state.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +0 -642
- package/node_modules/pi-mcp-adapter/dist/types.js +0 -331
- package/node_modules/pi-mcp-adapter/dist/types.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +0 -6
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -37
- package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +0 -17
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +0 -219
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +0 -104
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +0 -59
- package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +0 -4
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +0 -27
- package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +0 -15
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +0 -85
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +0 -1
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +0 -41
- package/node_modules/pi-mcp-adapter/dist/utils.js +0 -417
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +0 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +0 -358
- package/node_modules/pi-mcp-adapter/error-signal.ts +0 -32
- package/node_modules/pi-mcp-adapter/errors.ts +0 -364
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +0 -85
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +0 -34
- package/node_modules/pi-mcp-adapter/failure-backoff.ts +0 -23
- package/node_modules/pi-mcp-adapter/glimpse-ui.ts +0 -80
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -515
- package/node_modules/pi-mcp-adapter/http-ca.ts +0 -84
- package/node_modules/pi-mcp-adapter/index.ts +0 -2106
- package/node_modules/pi-mcp-adapter/init.ts +0 -721
- package/node_modules/pi-mcp-adapter/jev-client.ts +0 -300
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +0 -53
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +0 -192
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +0 -83
- package/node_modules/pi-mcp-adapter/lazy-loader.ts +0 -7
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -457
- package/node_modules/pi-mcp-adapter/logger.ts +0 -169
- package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +0 -113
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +0 -1296
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +0 -1287
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -250
- package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +0 -534
- package/node_modules/pi-mcp-adapter/mcp-code.ts +0 -495
- package/node_modules/pi-mcp-adapter/mcp-install.ts +0 -60
- package/node_modules/pi-mcp-adapter/mcp-keyring-helper.cjs +0 -89
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +0 -819
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +0 -511
- package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +0 -104
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +0 -1099
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +0 -201
- package/node_modules/pi-mcp-adapter/mcp-references.ts +0 -373
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +0 -137
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +0 -747
- package/node_modules/pi-mcp-adapter/mcp-status.ts +0 -105
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +0 -467
- package/node_modules/pi-mcp-adapter/mcp-trace.ts +0 -306
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +0 -362
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +0 -334
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +0 -543
- package/node_modules/pi-mcp-adapter/oauth-handler.ts +0 -30
- package/node_modules/pi-mcp-adapter/oauth.ts +0 -52
- package/node_modules/pi-mcp-adapter/onboarding-state.ts +0 -76
- package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +0 -156
- package/node_modules/pi-mcp-adapter/package.json +0 -219
- package/node_modules/pi-mcp-adapter/panel-keys.ts +0 -53
- package/node_modules/pi-mcp-adapter/prompts.ts +0 -385
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +0 -1735
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +0 -333
- package/node_modules/pi-mcp-adapter/resource-tools.ts +0 -17
- package/node_modules/pi-mcp-adapter/runtime-owner.ts +0 -106
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +0 -261
- package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +0 -144
- package/node_modules/pi-mcp-adapter/search-ranking.ts +0 -309
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +0 -170
- package/node_modules/pi-mcp-adapter/semantic-search.ts +0 -186
- package/node_modules/pi-mcp-adapter/server-manager.ts +0 -2088
- package/node_modules/pi-mcp-adapter/session-approvals.ts +0 -201
- package/node_modules/pi-mcp-adapter/session-recovery.ts +0 -158
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +0 -49
- package/node_modules/pi-mcp-adapter/state.ts +0 -71
- package/node_modules/pi-mcp-adapter/tool-approval.ts +0 -205
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +0 -302
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +0 -255
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +0 -573
- package/node_modules/pi-mcp-adapter/ts-shape.ts +0 -149
- package/node_modules/pi-mcp-adapter/types.ts +0 -1120
- package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +0 -40
- package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +0 -266
- package/node_modules/pi-mcp-adapter/ui-server.ts +0 -993
- package/node_modules/pi-mcp-adapter/ui-session.ts +0 -624
- package/node_modules/pi-mcp-adapter/ui-stream-types.ts +0 -89
- package/node_modules/pi-mcp-adapter/ui-tool-visibility.ts +0 -25
- package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +0 -85
- package/node_modules/pi-mcp-adapter/utils.ts +0 -464
|
@@ -60,6 +60,19 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
const TAVILY_KEY_POOL_RETRIES = new Set([401, 402, 403, 429, 432]);
|
|
64
|
+
|
|
65
|
+
function tavilyKeyPool(): string[] {
|
|
66
|
+
const slots: Array<[number, string]> = [];
|
|
67
|
+
for (let slot = 1; slot <= 20; slot++) {
|
|
68
|
+
const key = process.env[`TAVILY_API_KEY_${slot}`]?.trim();
|
|
69
|
+
if (key) slots.push([slot, key]);
|
|
70
|
+
}
|
|
71
|
+
const requestedSlot = Number.parseInt(process.env.TAVILY_API_KEY_INDEX ?? "", 10) || 1;
|
|
72
|
+
const start = Math.max(0, slots.findIndex(([slot]) => slot >= requestedSlot));
|
|
73
|
+
return [...new Set([...slots.slice(start), ...slots.slice(0, start)].map(([, key]) => key))];
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
function getApiUrl(): string {
|
|
64
77
|
return `${resolveApiBaseUrl({
|
|
65
78
|
configKey: "tavilyBaseUrl",
|
|
@@ -141,12 +154,11 @@ export function isTavilyAvailable(): boolean {
|
|
|
141
154
|
provider: "Tavily",
|
|
142
155
|
configuredValue: loadConfig().tavilyApiKey,
|
|
143
156
|
environmentValue: process.env.TAVILY_API_KEY,
|
|
144
|
-
});
|
|
157
|
+
}) || tavilyKeyPool().length > 0;
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
export async function searchWithTavily(query: string, options: TavilySearchOptions = {}): Promise<SearchResponse> {
|
|
148
161
|
const apiUrl = getApiUrl();
|
|
149
|
-
const apiKey = await requireApiKey(options.signal);
|
|
150
162
|
const numResults = normalizeSearchResultCount(options.numResults);
|
|
151
163
|
const body: Record<string, unknown> = {
|
|
152
164
|
query,
|
|
@@ -158,7 +170,58 @@ export async function searchWithTavily(query: string, options: TavilySearchOptio
|
|
|
158
170
|
...mapDomainFilter(options.domainFilter),
|
|
159
171
|
};
|
|
160
172
|
|
|
173
|
+
const signal = requestSignal(options.signal);
|
|
161
174
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
175
|
+
let data: TavilyResponse;
|
|
176
|
+
try {
|
|
177
|
+
data = await requestWithKeyFailover(apiUrl, body, signal);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (options.signal?.aborted || (err instanceof Error && err.name === "AbortError")) activityMonitor.logComplete(activityId, 0);
|
|
180
|
+
else {
|
|
181
|
+
const status = tavilyErrorStatus(err);
|
|
182
|
+
if (status > 0) activityMonitor.logComplete(activityId, status);
|
|
183
|
+
else activityMonitor.logError(activityId, errorMessage(err));
|
|
184
|
+
}
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
activityMonitor.logComplete(activityId, 200);
|
|
189
|
+
const result: SearchResponse = {
|
|
190
|
+
answer: typeof data.answer === "string" ? data.answer : "",
|
|
191
|
+
results: mapResults(data.results, numResults),
|
|
192
|
+
};
|
|
193
|
+
if (options.includeContent) {
|
|
194
|
+
const inlineContent = mapInlineContent(data.results);
|
|
195
|
+
if (inlineContent.length > 0) result.inlineContent = inlineContent;
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function tavilyErrorStatus(err: unknown): number {
|
|
201
|
+
return Number(errorMessage(err).match(/^Tavily API error (\d{3}):/)?.[1] ?? 0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function requestWithKeyFailover(apiUrl: string, body: Record<string, unknown>, signal: AbortSignal): Promise<TavilyResponse> {
|
|
205
|
+
const pool = tavilyKeyPool();
|
|
206
|
+
const keys = pool.length > 0 ? pool : [await requireApiKey(signal)];
|
|
207
|
+
let fallbackChecked = pool.length === 0;
|
|
208
|
+
for (let i = 0; ; i++) {
|
|
209
|
+
try {
|
|
210
|
+
return await tavilyRequest(apiUrl, keys[i], body, signal);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (!TAVILY_KEY_POOL_RETRIES.has(tavilyErrorStatus(err))) throw err;
|
|
213
|
+
if (i + 1 < keys.length) continue;
|
|
214
|
+
if (fallbackChecked) throw err;
|
|
215
|
+
// Resolve the standalone credential only after every numbered pool key failed.
|
|
216
|
+
fallbackChecked = true;
|
|
217
|
+
const fallback = await getApiKey(signal);
|
|
218
|
+
if (!fallback || keys.includes(fallback)) throw err;
|
|
219
|
+
keys.push(fallback);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function tavilyRequest(apiUrl: string, apiKey: string, body: Record<string, unknown>, signal: AbortSignal): Promise<TavilyResponse> {
|
|
162
225
|
let response: Response;
|
|
163
226
|
try {
|
|
164
227
|
response = await fetchWithCredentialRedirects(apiUrl, {
|
|
@@ -168,13 +231,11 @@ export async function searchWithTavily(query: string, options: TavilySearchOptio
|
|
|
168
231
|
"Content-Type": "application/json",
|
|
169
232
|
},
|
|
170
233
|
body: JSON.stringify(body),
|
|
171
|
-
signal
|
|
234
|
+
signal,
|
|
172
235
|
}, ["Authorization"]);
|
|
173
236
|
} catch (err) {
|
|
174
237
|
const message = errorMessage(err);
|
|
175
238
|
const redactedMessage = redactCredential(message, apiKey);
|
|
176
|
-
if (redactedMessage.toLowerCase().includes("abort")) activityMonitor.logComplete(activityId, 0);
|
|
177
|
-
else activityMonitor.logError(activityId, redactedMessage);
|
|
178
239
|
if (redactedMessage === message) throw err;
|
|
179
240
|
const redactedError = new Error(redactedMessage);
|
|
180
241
|
if (err instanceof Error) redactedError.name = err.name;
|
|
@@ -182,27 +243,13 @@ export async function searchWithTavily(query: string, options: TavilySearchOptio
|
|
|
182
243
|
}
|
|
183
244
|
|
|
184
245
|
if (!response.ok) {
|
|
185
|
-
activityMonitor.logComplete(activityId, response.status);
|
|
186
246
|
const errorText = redactCredential(await response.text(), apiKey);
|
|
187
247
|
throw new Error(`Tavily API error ${response.status}: ${errorText.slice(0, 300)}`);
|
|
188
248
|
}
|
|
189
249
|
|
|
190
|
-
let data: TavilyResponse;
|
|
191
250
|
try {
|
|
192
|
-
|
|
251
|
+
return await response.json() as TavilyResponse;
|
|
193
252
|
} catch (err) {
|
|
194
|
-
activityMonitor.logComplete(activityId, response.status);
|
|
195
253
|
throw new Error(`Tavily API returned invalid JSON: ${errorMessage(err)}`);
|
|
196
254
|
}
|
|
197
|
-
|
|
198
|
-
activityMonitor.logComplete(activityId, response.status);
|
|
199
|
-
const result: SearchResponse = {
|
|
200
|
-
answer: typeof data.answer === "string" ? data.answer : "",
|
|
201
|
-
results: mapResults(data.results, numResults),
|
|
202
|
-
};
|
|
203
|
-
if (options.includeContent) {
|
|
204
|
-
const inlineContent = mapInlineContent(data.results);
|
|
205
|
-
if (inlineContent.length > 0) result.inlineContent = inlineContent;
|
|
206
|
-
}
|
|
207
|
-
return result;
|
|
208
255
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { buildSessionContext, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import { buildSessionContext, type ExtensionAPI, type ExtensionContext, type SessionContext } from "@earendil-works/pi-coding-agent";
|
|
5
2
|
import { Type } from "typebox";
|
|
6
3
|
|
|
7
4
|
export type WebCapability = "search" | "source-check" | "fetch" | "stored-content";
|
|
@@ -21,31 +18,52 @@ const CAPABILITY_LABELS: Record<WebCapability, string> = {
|
|
|
21
18
|
|
|
22
19
|
function supportsDynamicTools(pi: ExtensionAPI): boolean {
|
|
23
20
|
if (typeof pi.getAllTools !== "function" || typeof pi.getActiveTools !== "function" || typeof pi.setActiveTools !== "function") return false;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
// Pi 0.86.0 shipped transcript-backed tool changes in the same release that made pi.on() return
|
|
22
|
+
// an unsubscribe function. Probe the host API object: an imported VERSION can come from a stale
|
|
23
|
+
// Pi package installed beside this extension instead of the running host.
|
|
24
|
+
const unsubscribe: unknown = pi.on("session_start", () => {});
|
|
25
|
+
if (typeof unsubscribe !== "function") return false;
|
|
26
|
+
unsubscribe();
|
|
27
|
+
return true;
|
|
31
28
|
}
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
// Mirrors pi-ai's per-API transcript handling: without these flags, Pi resends the whole transcript
|
|
31
|
+
// as a checkpoint when web_enable changes the tools and prompt, which can miss the prompt cache.
|
|
32
|
+
function addsToolsWithoutCheckpoint(model: ExtensionContext["model"]): boolean {
|
|
33
|
+
const compat = model?.compat as {
|
|
34
|
+
supportsMidConvoSystemMessages?: boolean;
|
|
35
|
+
supportsMidConvoToolChanges?: boolean;
|
|
36
|
+
supportsMidConvoToolAdditions?: boolean;
|
|
37
|
+
supportsAdditionalTools?: boolean;
|
|
38
|
+
supportsToolSearch?: boolean;
|
|
39
|
+
} | undefined;
|
|
40
|
+
if (!model || compat?.supportsMidConvoSystemMessages !== true) return false;
|
|
41
|
+
switch (model.api) {
|
|
42
|
+
case "anthropic-messages": return compat.supportsMidConvoToolChanges === true;
|
|
43
|
+
case "openai-completions": return compat.supportsMidConvoToolAdditions === true;
|
|
44
|
+
case "openai-responses":
|
|
45
|
+
case "openai-codex-responses":
|
|
46
|
+
case "azure-openai-responses": return compat.supportsAdditionalTools === true || compat.supportsToolSearch === true;
|
|
47
|
+
default: return false;
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
// Undefined when the transcript never declared tool changes.
|
|
52
|
+
function transcriptToolNames(messages: SessionContext["messages"]): Set<string> | undefined {
|
|
53
|
+
let tools: Set<string> | undefined;
|
|
54
|
+
for (const message of messages) {
|
|
55
|
+
if (message.role !== "system" || !("toolsAdded" in message || "toolsRemoved" in message)) continue;
|
|
56
|
+
tools ??= new Set();
|
|
57
|
+
for (const tool of message.toolsRemoved ?? []) tools.delete(tool.name);
|
|
58
|
+
for (const tool of message.toolsAdded ?? []) tools.add(tool.name);
|
|
59
|
+
}
|
|
60
|
+
return tools;
|
|
43
61
|
}
|
|
44
62
|
|
|
45
|
-
export function registerWebToolActivation(pi: ExtensionAPI, tools: ReadonlyArray<WebActivationTool
|
|
63
|
+
export function registerWebToolActivation(pi: ExtensionAPI, tools: ReadonlyArray<WebActivationTool>, mode: "auto" | "dynamic"): void {
|
|
46
64
|
if (tools.length === 0) return;
|
|
47
65
|
if (!supportsDynamicTools(pi)) {
|
|
48
|
-
console.warn("[pi-web-access] Dynamic tool activation requires Pi 0.86.
|
|
66
|
+
console.warn("[pi-web-access] Dynamic tool activation requires Pi 0.86.0 or newer; web tools remain eagerly available.");
|
|
49
67
|
return;
|
|
50
68
|
}
|
|
51
69
|
const names = tools.map(tool => tool.name);
|
|
@@ -56,7 +74,7 @@ export function registerWebToolActivation(pi: ExtensionAPI, tools: ReadonlyArray
|
|
|
56
74
|
name: LOADER_NAME,
|
|
57
75
|
label: "Enable Web Access",
|
|
58
76
|
description: "Enable configured pi-web-access tools for web research and content retrieval. Does not search or fetch. Enabled tools are available on the next model request; disabled capabilities remain unavailable.",
|
|
59
|
-
promptSnippet: `
|
|
77
|
+
promptSnippet: `If tools for ${capabilities} are not already available, call web_enable first whenever current, external, or linked information could help; the tools appear on the next model request.`,
|
|
60
78
|
parameters,
|
|
61
79
|
async execute() {
|
|
62
80
|
const registered = new Set(pi.getAllTools().map(tool => tool.name));
|
|
@@ -100,19 +118,21 @@ export function registerWebToolActivation(pi: ExtensionAPI, tools: ReadonlyArray
|
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
let warned = false;
|
|
103
|
-
|
|
121
|
+
// A resumed transcript that declared its tools without the loader keeps that set; adding the
|
|
122
|
+
// loader there would record a mid-conversation tool change the user never asked for (#462).
|
|
123
|
+
let loaderSelected = true;
|
|
124
|
+
function selectFromSession(ctx: ExtensionContext): void {
|
|
104
125
|
if (!loaderAvailable()) return;
|
|
105
126
|
try {
|
|
106
127
|
const messages = buildSessionContext(ctx.sessionManager.getBranch()).messages;
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
pi.setActiveTools([...new Set([...active, LOADER_NAME])]);
|
|
128
|
+
const declared = transcriptToolNames(messages);
|
|
129
|
+
// "auto" decides once, for a fresh session; recorded sessions keep their tools.
|
|
130
|
+
const eager = mode === "auto" && !declared && messages.length === 0 && !addsToolsWithoutCheckpoint(ctx.model);
|
|
131
|
+
// Sessions from before transcript tool declarations keep every web tool; fresh ones start with none.
|
|
132
|
+
const recorded = declared ?? new Set(messages.length > 0 || eager ? names : []);
|
|
133
|
+
loaderSelected = !eager && (!declared || declared.has(LOADER_NAME));
|
|
134
|
+
const others = pi.getActiveTools().filter(name => name !== LOADER_NAME && !names.includes(name));
|
|
135
|
+
pi.setActiveTools([...new Set([...others, ...names.filter(name => recorded.has(name)), ...(loaderSelected ? [LOADER_NAME] : [])])]);
|
|
116
136
|
} catch (error) {
|
|
117
137
|
if (!warned) {
|
|
118
138
|
warned = true;
|
|
@@ -124,7 +144,7 @@ export function registerWebToolActivation(pi: ExtensionAPI, tools: ReadonlyArray
|
|
|
124
144
|
pi.on("session_start", (_event, ctx) => selectFromSession(ctx));
|
|
125
145
|
pi.on("session_tree", (_event, ctx) => selectFromSession(ctx));
|
|
126
146
|
pi.on("before_agent_start", () => {
|
|
127
|
-
if (!loaderAvailable() || pi.getActiveTools().includes(LOADER_NAME)) return;
|
|
147
|
+
if (!loaderSelected || !loaderAvailable() || pi.getActiveTools().includes(LOADER_NAME)) return;
|
|
128
148
|
try {
|
|
129
149
|
pi.setActiveTools([...pi.getActiveTools(), LOADER_NAME]);
|
|
130
150
|
} catch {
|
|
@@ -249,7 +249,7 @@ function redactProxyUrl(value: string): string {
|
|
|
249
249
|
return parsed.toString();
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
function loadConfiguredProxy(): string | null {
|
|
252
|
+
export function loadConfiguredProxy(): string | null {
|
|
253
253
|
let configured: unknown;
|
|
254
254
|
const path = getWebSearchConfigPath();
|
|
255
255
|
if (existsSync(path)) {
|
|
@@ -0,0 +1,164 @@
|
|
|
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 { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
8
|
+
import { fetchWithCredentialRedirects, getWebSearchConfigPath } from "./utils.ts";
|
|
9
|
+
|
|
10
|
+
const YOU_SEARCH_URL = "https://ydc-index.io/v1/search";
|
|
11
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
12
|
+
const SEARCH_TIMEOUT_MS = 60_000;
|
|
13
|
+
|
|
14
|
+
interface WebSearchConfig {
|
|
15
|
+
youApiKey?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let cachedConfig: WebSearchConfig | null = null;
|
|
19
|
+
|
|
20
|
+
function loadConfig(): WebSearchConfig {
|
|
21
|
+
if (cachedConfig) return cachedConfig;
|
|
22
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
23
|
+
cachedConfig = {};
|
|
24
|
+
return cachedConfig;
|
|
25
|
+
}
|
|
26
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
27
|
+
let parsed: unknown;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(raw);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32
|
+
throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
|
|
33
|
+
}
|
|
34
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
35
|
+
throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
|
|
36
|
+
}
|
|
37
|
+
cachedConfig = parsed as WebSearchConfig;
|
|
38
|
+
return cachedConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function requireApiKey(signal?: AbortSignal): Promise<string> {
|
|
42
|
+
const apiKey = await resolveCredential({
|
|
43
|
+
provider: "You.com",
|
|
44
|
+
configuredValue: loadConfig().youApiKey,
|
|
45
|
+
environmentValue: process.env.YDC_API_KEY,
|
|
46
|
+
signal,
|
|
47
|
+
});
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"You.com API key not found. Either:\n" +
|
|
51
|
+
` 1. Create ${CONFIG_PATH} with { "youApiKey": "your-key" }\n` +
|
|
52
|
+
" 2. Set YDC_API_KEY environment variable\n" +
|
|
53
|
+
"Get a key at https://you.com/platform/api-keys",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return apiKey;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mapDomainFilter(domainFilter: string[] | undefined): { include_domains?: string[]; exclude_domains?: string[] } {
|
|
60
|
+
const include_domains: string[] = [];
|
|
61
|
+
const exclude_domains: string[] = [];
|
|
62
|
+
for (const raw of domainFilter ?? []) {
|
|
63
|
+
const domain = normalizeDomain(raw);
|
|
64
|
+
if (!domain) continue;
|
|
65
|
+
const target = raw.trim().startsWith("-") ? exclude_domains : include_domains;
|
|
66
|
+
if (!target.includes(domain)) target.push(domain);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...(include_domains.length > 0 ? { include_domains } : {}),
|
|
70
|
+
...(exclude_domains.length > 0 ? { exclude_domains } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function errorMessage(err: unknown): string {
|
|
75
|
+
return err instanceof Error ? err.message : String(err);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseWebResults(value: unknown): unknown[] {
|
|
79
|
+
const results = value && typeof value === "object" ? (value as { results?: unknown }).results : undefined;
|
|
80
|
+
const web = results && typeof results === "object" ? (results as { web?: unknown }).web : undefined;
|
|
81
|
+
if (!Array.isArray(web)) throw new Error("You.com returned invalid response: expected results.web array");
|
|
82
|
+
return web;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isYouAvailable(): boolean {
|
|
86
|
+
return hasCredentialSource({ provider: "You.com", configuredValue: loadConfig().youApiKey, environmentValue: process.env.YDC_API_KEY });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function searchWithYou(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
90
|
+
const apiKey = await requireApiKey(options.signal);
|
|
91
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
92
|
+
const body = {
|
|
93
|
+
query,
|
|
94
|
+
count: numResults,
|
|
95
|
+
...(options.recencyFilter ? { freshness: options.recencyFilter } : {}),
|
|
96
|
+
...mapDomainFilter(options.domainFilter),
|
|
97
|
+
};
|
|
98
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
99
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
100
|
+
let response: Response;
|
|
101
|
+
let entries: unknown[];
|
|
102
|
+
try {
|
|
103
|
+
response = await fetchWithCredentialRedirects(YOU_SEARCH_URL, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: { Accept: "application/json", "Content-Type": "application/json", "X-API-Key": apiKey },
|
|
106
|
+
body: JSON.stringify(body),
|
|
107
|
+
signal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,
|
|
108
|
+
}, ["X-API-Key"]);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const errorText = await response.text();
|
|
111
|
+
throw new Error(`You.com error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);
|
|
112
|
+
}
|
|
113
|
+
let rawData: unknown;
|
|
114
|
+
try {
|
|
115
|
+
rawData = await response.json();
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof Error && err.name === "TimeoutError") throw err;
|
|
118
|
+
throw new Error(`You.com returned invalid JSON: ${errorMessage(err)}`);
|
|
119
|
+
}
|
|
120
|
+
entries = parseWebResults(rawData);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (options.signal?.aborted) {
|
|
123
|
+
activityMonitor.logComplete(activityId, 0);
|
|
124
|
+
throw new Error("Aborted");
|
|
125
|
+
}
|
|
126
|
+
const message = errorMessage(err);
|
|
127
|
+
const providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === "TimeoutError");
|
|
128
|
+
const outgoing = providerTimeout
|
|
129
|
+
? new Error(`You.com request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)
|
|
130
|
+
: (() => {
|
|
131
|
+
const redactedMessage = redactCredential(message, apiKey);
|
|
132
|
+
if (redactedMessage === message && err instanceof Error) return err;
|
|
133
|
+
const redactedError = new Error(redactedMessage);
|
|
134
|
+
if (err instanceof Error) redactedError.name = err.name;
|
|
135
|
+
return redactedError;
|
|
136
|
+
})();
|
|
137
|
+
activityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));
|
|
138
|
+
throw outgoing;
|
|
139
|
+
}
|
|
140
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
141
|
+
const results: SearchResponse["results"] = [];
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry || typeof entry !== "object") continue;
|
|
144
|
+
const { url, title, description, snippets } = entry as Record<string, unknown>;
|
|
145
|
+
if (typeof url !== "string" || !url) continue;
|
|
146
|
+
let resultUrl: URL;
|
|
147
|
+
try {
|
|
148
|
+
resultUrl = new URL(url);
|
|
149
|
+
} catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (resultUrl.protocol !== "http:" && resultUrl.protocol !== "https:") continue;
|
|
153
|
+
const snippet = typeof description === "string" && description.trim()
|
|
154
|
+
? description
|
|
155
|
+
: Array.isArray(snippets) && typeof snippets[0] === "string" ? snippets[0] : "";
|
|
156
|
+
results.push({
|
|
157
|
+
title: typeof title === "string" && title.trim() ? title.trim() : `Source ${results.length + 1}`,
|
|
158
|
+
url: resultUrl.href,
|
|
159
|
+
snippet,
|
|
160
|
+
});
|
|
161
|
+
if (results.length >= numResults) break;
|
|
162
|
+
}
|
|
163
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
164
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
4
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
5
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
6
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
7
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
8
|
+
import { getWebSearchConfigPath } from "./utils.ts";
|
|
9
|
+
|
|
10
|
+
// GLM Coding Plan keys are issued per site, so the user picks the matching endpoint.
|
|
11
|
+
const MCP_URLS = {
|
|
12
|
+
global: "https://api.z.ai/api/mcp/web_search_prime/mcp",
|
|
13
|
+
china: "https://open.bigmodel.cn/api/mcp/web_search_prime/mcp",
|
|
14
|
+
} as const;
|
|
15
|
+
const RECENCY = { day: "oneDay", week: "oneWeek", month: "oneMonth", year: "oneYear" } as const;
|
|
16
|
+
const SEARCH_TIMEOUT_MS = 60_000;
|
|
17
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
18
|
+
|
|
19
|
+
function loadConfig(): Record<string, unknown> {
|
|
20
|
+
if (!existsSync(CONFIG_PATH)) return {};
|
|
21
|
+
try {
|
|
22
|
+
const parsed: unknown = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
23
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
|
|
24
|
+
return parsed as Record<string, unknown>;
|
|
25
|
+
} catch {
|
|
26
|
+
// JSON parser errors may include a fragment containing a literal key.
|
|
27
|
+
throw new Error(`Invalid configuration in ${CONFIG_PATH}: expected a JSON object`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function credentialOptions(config: Record<string, unknown>) {
|
|
32
|
+
return { provider: "Z.ai", configuredValue: config.zaiApiKey, environmentValue: process.env.ZAI_API_KEY };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function endpointUrl(config: Record<string, unknown>): string {
|
|
36
|
+
const endpoint = config.zaiEndpoint ?? "global";
|
|
37
|
+
if (endpoint !== "global" && endpoint !== "china") {
|
|
38
|
+
throw new Error(`zaiEndpoint in ${CONFIG_PATH} must be "global" or "china"`);
|
|
39
|
+
}
|
|
40
|
+
return MCP_URLS[endpoint];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isZaiAvailable(): boolean {
|
|
44
|
+
return hasCredentialSource(credentialOptions(loadConfig()));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function searchArguments(query: string, options: SearchOptions): Record<string, unknown> {
|
|
48
|
+
if (!query.trim()) throw new Error("Z.ai search query must not be empty");
|
|
49
|
+
const domains = (options.domainFilter ?? []).map((raw) => raw.trim()).filter(Boolean);
|
|
50
|
+
if (domains.length > 1 || domains.some((raw) => raw.startsWith("-"))) {
|
|
51
|
+
throw new Error("Z.ai domainFilter supports a single included domain");
|
|
52
|
+
}
|
|
53
|
+
const domain = domains[0] === undefined ? undefined : normalizeDomain(domains[0]);
|
|
54
|
+
if (domain === null) throw new Error("Z.ai domain filter must contain a valid domain");
|
|
55
|
+
return {
|
|
56
|
+
search_query: query.trim(),
|
|
57
|
+
...(options.recencyFilter ? { search_recency_filter: RECENCY[options.recencyFilter] } : {}),
|
|
58
|
+
...(domain ? { search_domain_filter: domain } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The tool returns its result list as JSON text, sometimes encoded twice. */
|
|
63
|
+
function parseResults(text: string, limit: number, domain: string | undefined): SearchResponse["results"] {
|
|
64
|
+
let value: unknown = JSON.parse(text);
|
|
65
|
+
if (typeof value === "string") value = JSON.parse(value);
|
|
66
|
+
if (!Array.isArray(value)) throw new Error("Z.ai returned invalid response: expected a result list");
|
|
67
|
+
const results: SearchResponse["results"] = [];
|
|
68
|
+
for (const entry of value) {
|
|
69
|
+
if (!entry || typeof entry !== "object") continue;
|
|
70
|
+
const { link, title, content } = entry as Record<string, unknown>;
|
|
71
|
+
if (typeof link !== "string") continue;
|
|
72
|
+
let url: URL;
|
|
73
|
+
try {
|
|
74
|
+
url = new URL(link);
|
|
75
|
+
} catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") continue;
|
|
79
|
+
// The remote filter is undocumented, so enforce it locally too.
|
|
80
|
+
const host = url.hostname.toLowerCase().replace(/\.+$/, "");
|
|
81
|
+
if (domain && host !== domain && !host.endsWith(`.${domain}`)) continue;
|
|
82
|
+
results.push({
|
|
83
|
+
title: typeof title === "string" && title.trim() ? title.trim() : `Source ${results.length + 1}`,
|
|
84
|
+
url: url.href,
|
|
85
|
+
snippet: typeof content === "string" ? content : "",
|
|
86
|
+
});
|
|
87
|
+
if (results.length >= limit) break;
|
|
88
|
+
}
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function searchWithZai(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
93
|
+
const config = loadConfig();
|
|
94
|
+
const mcpUrl = endpointUrl(config);
|
|
95
|
+
const args = searchArguments(query, options);
|
|
96
|
+
const limit = normalizeSearchResultCount(options.numResults);
|
|
97
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
98
|
+
const signal = options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal;
|
|
99
|
+
if (signal.aborted) throw new Error("Aborted");
|
|
100
|
+
const apiKey = await resolveCredential({ ...credentialOptions(config), signal });
|
|
101
|
+
if (!apiKey) throw new Error("Z.ai API key not found. Set ZAI_API_KEY or zaiApiKey in web-search.json with a GLM Coding Plan key.");
|
|
102
|
+
let headers: Headers;
|
|
103
|
+
try { headers = new Headers({ Authorization: `Bearer ${apiKey}` }); }
|
|
104
|
+
catch { throw new Error("Z.ai credential resolution failed: invalid-header-value"); }
|
|
105
|
+
// Load the protocol client only when this explicit provider is selected.
|
|
106
|
+
const [{ Client }, { StreamableHTTPClientTransport }] = await Promise.all([
|
|
107
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
108
|
+
import("@modelcontextprotocol/sdk/client/streamableHttp.js"),
|
|
109
|
+
]);
|
|
110
|
+
let cleaningUp = false;
|
|
111
|
+
const transport = new StreamableHTTPClientTransport(new URL(mcpUrl), {
|
|
112
|
+
requestInit: { headers },
|
|
113
|
+
fetch: async (url, init) => {
|
|
114
|
+
// Fixed endpoint, no redirects: neither credentials nor session IDs can
|
|
115
|
+
// be forwarded to another origin (including MCP discovery redirects).
|
|
116
|
+
if (String(url) !== mcpUrl) throw new Error("Z.ai endpoint changed unexpectedly");
|
|
117
|
+
const requestSignal = cleaningUp ? AbortSignal.timeout(1_500) : signal;
|
|
118
|
+
return fetch(url, { ...init, redirect: "error", signal: !cleaningUp && init?.signal ? AbortSignal.any([init.signal, requestSignal]) : requestSignal });
|
|
119
|
+
},
|
|
120
|
+
reconnectionOptions: { maxRetries: 0, initialReconnectionDelay: 1_000, maxReconnectionDelay: 1_000, reconnectionDelayGrowFactor: 1 },
|
|
121
|
+
});
|
|
122
|
+
const client = new Client({ name: "pi-web-access-zai", version: "1.0.0" });
|
|
123
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
124
|
+
try {
|
|
125
|
+
await client.connect(transport, { signal, timeout: SEARCH_TIMEOUT_MS });
|
|
126
|
+
// The live China server lists `web_search_prime` while both sites document
|
|
127
|
+
// `webSearchPrime`, so call whichever name this server exposes.
|
|
128
|
+
const { tools } = await client.listTools(undefined, { signal, timeout: SEARCH_TIMEOUT_MS });
|
|
129
|
+
const toolName = tools.find((tool) => tool.name === "web_search_prime" || tool.name === "webSearchPrime")?.name;
|
|
130
|
+
if (!toolName) throw new Error("Z.ai returned invalid response: web search tool not listed");
|
|
131
|
+
const result = await client.callTool({ name: toolName, arguments: args }, undefined, { signal, timeout: SEARCH_TIMEOUT_MS });
|
|
132
|
+
if (result.isError) throw new Error("Z.ai search tool returned an error");
|
|
133
|
+
const text = (Array.isArray(result.content) ? result.content : []).find((item) => item.type === "text" && typeof item.text === "string");
|
|
134
|
+
if (!text) throw new Error("Z.ai returned invalid response: no search text");
|
|
135
|
+
const results = parseResults(text.text as string, limit, args.search_domain_filter as string | undefined);
|
|
136
|
+
activityMonitor.logComplete(activityId, 200);
|
|
137
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
138
|
+
} catch (err) {
|
|
139
|
+
let message: string;
|
|
140
|
+
if (options.signal?.aborted) message = "Aborted";
|
|
141
|
+
else if (timeoutSignal.aborted) message = "Z.ai request timed out after 60s";
|
|
142
|
+
else {
|
|
143
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : undefined;
|
|
144
|
+
// Never expose remote HTTP/JSON-RPC bodies or nested SDK errors: they
|
|
145
|
+
// can echo credentials in plain, encoded or transformed form.
|
|
146
|
+
message = typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599
|
|
147
|
+
? `Z.ai HTTP ${code} request failed; check your GLM Coding Plan key, zaiEndpoint and plan quota`
|
|
148
|
+
: err instanceof TypeError ? "Z.ai network request failed"
|
|
149
|
+
: err instanceof SyntaxError || code === -32700 || code === -32602 ? "Z.ai returned invalid response"
|
|
150
|
+
: err instanceof Error && /^Z\.ai (search tool returned an error|returned invalid response:)/.test(err.message) ? err.message
|
|
151
|
+
: "Z.ai MCP request failed; check your connection and GLM Coding Plan key";
|
|
152
|
+
}
|
|
153
|
+
activityMonitor.logError(activityId, redactCredential(message, apiKey));
|
|
154
|
+
throw new Error(message);
|
|
155
|
+
} finally {
|
|
156
|
+
cleaningUp = true;
|
|
157
|
+
// Best effort, separately bounded DELETE; always close the local streams.
|
|
158
|
+
try { await transport.terminateSession(); } catch { /* Server may not support DELETE. */ }
|
|
159
|
+
await client.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -81,8 +81,12 @@
|
|
|
81
81
|
"@noble/ed25519": "^3.1.0",
|
|
82
82
|
"croner": "^10.0.1",
|
|
83
83
|
"qrcode-terminal": "^0.12.0",
|
|
84
|
-
"typebox": "^1.1.38",
|
|
85
84
|
"ws": "^8.21.0",
|
|
86
85
|
"zod": "^4.4.3"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
89
|
+
"@earendil-works/pi-tui": "*",
|
|
90
|
+
"typebox": "*"
|
|
87
91
|
}
|
|
88
92
|
}
|