@ashx-j/lunr-dev 0.2.13-dev.8.1 → 0.2.16-dev.9.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/CHANGELOG.md +29 -1
- package/dist/builtin-extensions/ashxj-tui.d.ts +14 -0
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +37 -3
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/index.d.ts +1 -15
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +7 -21
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +6 -2
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/index.d.ts +4 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/index.js +163 -169
- package/dist/builtin-extensions/pi-lsp-extension/src/index.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.d.ts +46 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.js +109 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/runtime.js.map +1 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.d.ts +7 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.js +211 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.js.map +1 -0
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.d.ts +0 -5
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js +0 -202
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tools.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js +251 -94
- package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/init.d.ts +4 -1
- package/dist/builtin-extensions/pi-mcp-adapter/init.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/init.js +10 -3
- package/dist/builtin-extensions/pi-mcp-adapter/init.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/metadata-cache.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/metadata-cache.js +6 -1
- package/dist/builtin-extensions/pi-mcp-adapter/metadata-cache.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.js +2 -1
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.d.ts +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.js +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +38 -14
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/fork-context.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/fork-context.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/fork-context.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.js +2 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/extract.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/extract.js +105 -54
- package/dist/builtin-extensions/pi-web-access/extract.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/gemini-search.d.ts +1 -1
- package/dist/builtin-extensions/pi-web-access/gemini-search.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/gemini-search.js +82 -65
- package/dist/builtin-extensions/pi-web-access/gemini-search.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/github-extract.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/github-extract.js +2 -0
- package/dist/builtin-extensions/pi-web-access/github-extract.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +167 -77
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/lazy.d.ts +18 -0
- package/dist/builtin-extensions/pi-web-access/lazy.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-web-access/lazy.js +19 -0
- package/dist/builtin-extensions/pi-web-access/lazy.js.map +1 -0
- package/dist/builtin-extensions/pi-web-access/session-cleanup.d.ts +6 -0
- package/dist/builtin-extensions/pi-web-access/session-cleanup.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-web-access/session-cleanup.js +17 -0
- package/dist/builtin-extensions/pi-web-access/session-cleanup.js.map +1 -0
- package/dist/catalog/publication.json +12 -12
- package/dist/cli-runtime.d.ts +3 -0
- package/dist/cli-runtime.d.ts.map +1 -0
- package/dist/cli-runtime.js +73 -0
- package/dist/cli-runtime.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -53
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +3 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +56 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +10 -5
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/host-static.d.ts.map +1 -1
- package/dist/core/extensions/host-static.js +34 -12
- package/dist/core/extensions/host-static.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +1 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +10 -64
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +2 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/permissions.d.ts +0 -2
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +10 -18
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/timings.d.ts +2 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +17 -2
- package/dist/core/timings.js.map +1 -1
- package/dist/gateway/commands.d.ts +1 -1
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +1 -18
- package/dist/gateway/commands.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +57 -48
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +12 -21
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +120 -144
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +95 -84
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/node-runtime/agent-session-services-IFJMSX7Z.js +36 -0
- package/dist/node-runtime/benchmark-request-UC7PD3V3.js +57 -0
- package/dist/node-runtime/brave-UWDA3I22.js +143 -0
- package/dist/node-runtime/builtin-extensions-LYZ3BFWQ.js +28 -0
- package/dist/node-runtime/chunk-2QKLKMKC.js +173 -0
- package/dist/node-runtime/chunk-3GHXVUAK.js +21 -0
- package/dist/node-runtime/chunk-3RCWXWAY.js +466 -0
- package/dist/node-runtime/chunk-3WFAEVKS.js +115 -0
- package/dist/node-runtime/chunk-3X3KZKMI.js +49 -0
- package/dist/node-runtime/chunk-4HXHDDCF.js +16 -0
- package/dist/node-runtime/chunk-4TLO4PO2.js +3427 -0
- package/dist/node-runtime/chunk-4ZWGEEAS.js +69 -0
- package/dist/node-runtime/chunk-53YJI7JU.js +137 -0
- package/dist/node-runtime/chunk-5P7PZMRU.js +610 -0
- package/dist/node-runtime/chunk-7N3YHTYF.js +26 -0
- package/dist/node-runtime/chunk-7NBZ5P7I.js +24 -0
- package/dist/node-runtime/chunk-A2KQXUJD.js +105 -0
- package/dist/node-runtime/chunk-AD6VXAO7.js +342 -0
- package/dist/node-runtime/chunk-AHSTJBIB.js +64 -0
- package/dist/node-runtime/chunk-AJX357CL.js +20211 -0
- package/dist/node-runtime/chunk-ARC4KOTI.js +11178 -0
- package/dist/node-runtime/chunk-AUXVGBO4.js +19 -0
- package/dist/node-runtime/chunk-AY6CAJSL.js +30 -0
- package/dist/node-runtime/chunk-BHO7O6E5.js +296 -0
- package/dist/node-runtime/chunk-BI3DHYCN.js +8845 -0
- package/dist/node-runtime/chunk-BR7YDUKA.js +109 -0
- package/dist/node-runtime/chunk-BRJX2PRC.js +53 -0
- package/dist/node-runtime/chunk-C4O2RSQQ.js +37 -0
- package/dist/node-runtime/chunk-CCZJ3PYY.js +17 -0
- package/dist/node-runtime/chunk-CDFZEWPU.js +8388 -0
- package/dist/node-runtime/chunk-CGBXPL3E.js +1171 -0
- package/dist/node-runtime/chunk-CJECDQKP.js +19 -0
- package/dist/node-runtime/chunk-DAPBPCBP.js +53 -0
- package/dist/node-runtime/chunk-DDGMHUOW.js +305 -0
- package/dist/node-runtime/chunk-DMMTJQMQ.js +43 -0
- package/dist/node-runtime/chunk-DVGWF6A2.js +1180 -0
- package/dist/node-runtime/chunk-ECD5L7YI.js +1369 -0
- package/dist/node-runtime/chunk-EDBJNRSM.js +46 -0
- package/dist/node-runtime/chunk-F4VQFJI2.js +1269 -0
- package/dist/node-runtime/chunk-FVDOXRUD.js +96 -0
- package/dist/node-runtime/chunk-GZQHMEWL.js +126 -0
- package/dist/node-runtime/chunk-H2EOZYMP.js +57 -0
- package/dist/node-runtime/chunk-HQCKFM2O.js +56 -0
- package/dist/node-runtime/chunk-HYHJIRDW.js +19 -0
- package/dist/node-runtime/chunk-I6R76AG7.js +22 -0
- package/dist/node-runtime/chunk-IPA627GP.js +296 -0
- package/dist/node-runtime/chunk-JHYKXIMM.js +378 -0
- package/dist/node-runtime/chunk-KKSIVROT.js +88 -0
- package/dist/node-runtime/chunk-LZAW4ZX3.js +633 -0
- package/dist/node-runtime/chunk-M3LZLQW2.js +98 -0
- package/dist/node-runtime/chunk-MGWCVY6Y.js +47 -0
- package/dist/node-runtime/chunk-MJQDX3BA.js +72 -0
- package/dist/node-runtime/chunk-MKKH27PH.js +8956 -0
- package/dist/node-runtime/chunk-MX3GJNXJ.js +986 -0
- package/dist/node-runtime/chunk-N77MY65L.js +966 -0
- package/dist/node-runtime/chunk-NUIFPZWX.js +209 -0
- package/dist/node-runtime/chunk-OBMWP3I5.js +68 -0
- package/dist/node-runtime/chunk-OR4L3IB2.js +42 -0
- package/dist/node-runtime/chunk-PBC2Y6GS.js +903 -0
- package/dist/node-runtime/chunk-PCGNAY2O.js +1324 -0
- package/dist/node-runtime/chunk-PCIGF2XK.js +250 -0
- package/dist/node-runtime/chunk-PGAJ5MO5.js +7558 -0
- package/dist/node-runtime/chunk-PL6JKXWI.js +3622 -0
- package/dist/node-runtime/chunk-PVCDKGR6.js +55 -0
- package/dist/node-runtime/chunk-Q4Q6D4IY.js +364 -0
- package/dist/node-runtime/chunk-Q5ZU2VRT.js +83 -0
- package/dist/node-runtime/chunk-QFWG2RVL.js +5477 -0
- package/dist/node-runtime/chunk-QH42CDX3.js +9451 -0
- package/dist/node-runtime/chunk-RQ2Q5IYR.js +72 -0
- package/dist/node-runtime/chunk-RQ5DNUNG.js +429 -0
- package/dist/node-runtime/chunk-RRIZE6QQ.js +17 -0
- package/dist/node-runtime/chunk-SN6G4OU6.js +4753 -0
- package/dist/node-runtime/chunk-SWSN6HAB.js +53 -0
- package/dist/node-runtime/chunk-TQNSPERU.js +949 -0
- package/dist/node-runtime/chunk-VRB2IYGG.js +53 -0
- package/dist/node-runtime/chunk-VSELYKWU.js +772 -0
- package/dist/node-runtime/chunk-W3CVH5RX.js +62 -0
- package/dist/node-runtime/chunk-WJVFOIB5.js +24921 -0
- package/dist/node-runtime/chunk-WOVTOV5P.js +5653 -0
- package/dist/node-runtime/chunk-XBWPBZBN.js +125 -0
- package/dist/node-runtime/chunk-XTHQ7XR7.js +509 -0
- package/dist/node-runtime/chunk-YF63P5ZT.js +864 -0
- package/dist/node-runtime/chunk-YSKSB3BJ.js +295 -0
- package/dist/node-runtime/chunk-ZCHMJ34K.js +130 -0
- package/dist/node-runtime/chunk-ZKMFUD3Q.js +2834 -0
- package/dist/node-runtime/cli-runtime.js +81 -0
- package/dist/node-runtime/clipboard-OZ63BBC6.js +12 -0
- package/dist/node-runtime/clipboard-image-7QR6GXVA.js +13 -0
- package/dist/node-runtime/command-L3LAHDIH.js +13 -0
- package/dist/node-runtime/commands-RL5YBW5I.js +291 -0
- package/dist/node-runtime/config-FMEOZSWE.js +77 -0
- package/dist/node-runtime/curator-server-XCM43VEF.js +3816 -0
- package/dist/node-runtime/customize-XEXL2DGQ.js +12 -0
- package/dist/node-runtime/direct-tool-executor-JVDXTO7A.js +188 -0
- package/dist/node-runtime/exa-LL64QZR2.js +329 -0
- package/dist/node-runtime/export-html-KR26FOUZ.js +150 -0
- package/dist/node-runtime/extension-input-RV5N6APL.js +14 -0
- package/dist/node-runtime/extension-selector-CPD5LWDD.js +14 -0
- package/dist/node-runtime/extract-XLQSJN4V.js +17 -0
- package/dist/node-runtime/file-sync-HCOP2QVF.js +117 -0
- package/dist/node-runtime/gateway-X2TI5X7F.js +2484 -0
- package/dist/node-runtime/gemini-api-QE7ANVDZ.js +29 -0
- package/dist/node-runtime/gemini-search-U7SUMPQA.js +330 -0
- package/dist/node-runtime/gemini-url-context-PXWCBQHK.js +103 -0
- package/dist/node-runtime/gemini-web-MM446SEH.js +14 -0
- package/dist/node-runtime/github-extract-F64RZOEA.js +619 -0
- package/dist/node-runtime/goal-CZQ7FFQJ.js +1831 -0
- package/dist/node-runtime/index.js +859 -0
- package/dist/node-runtime/init-NJV2T55N.js +28 -0
- package/dist/node-runtime/install-cli-TH7IJJQQ.js +308 -0
- package/dist/node-runtime/interactive-mode-KOM35YF2.js +56 -0
- package/dist/node-runtime/interactive-view-U6OILKF3.js +249 -0
- package/dist/node-runtime/lsp-manager-3ASUSO4T.js +1104 -0
- package/dist/node-runtime/lunr-cron-YJ5UHYRD.js +268 -0
- package/dist/node-runtime/main.js +54 -0
- package/dist/node-runtime/mcp-auth-flow-QIXHW6QN.js +32 -0
- package/dist/node-runtime/mcp-panel-IIGMXQGP.js +501 -0
- package/dist/node-runtime/mcp-setup-panel-FEK7663I.js +355 -0
- package/dist/node-runtime/memory-cap-3S4XRLNP.js +20 -0
- package/dist/node-runtime/model-tiers-NJLHUEGK.js +20 -0
- package/dist/node-runtime/openai-search-B3MSNCQA.js +288 -0
- package/dist/node-runtime/package-manager-cli-TZZHJCVI.js +749 -0
- package/dist/node-runtime/parallel-AI2MNSCU.js +265 -0
- package/dist/node-runtime/parser-manager-TQHWYDH7.js +152 -0
- package/dist/node-runtime/pdf-extract-IPE6H4HP.js +57 -0
- package/dist/node-runtime/perplexity-QVJW3I35.js +12 -0
- package/dist/node-runtime/pi-intercom-EEMBGQID.js +2215 -0
- package/dist/node-runtime/pi-mcp-adapter-QFB37AVN.js +422 -0
- package/dist/node-runtime/pi-ollama-cloud-4VZRMULP.js +1440 -0
- package/dist/node-runtime/pi-prompt-template-model-4QT4LLXF.js +3946 -0
- package/dist/node-runtime/pi-subagents-7XJ4H53P.js +6023 -0
- package/dist/node-runtime/pi-web-access-DPGAVKOA.js +1864 -0
- package/dist/node-runtime/print-mode-VWIRFDAC.js +12 -0
- package/dist/node-runtime/proxy-modes-HEEFK4KC.js +685 -0
- package/dist/node-runtime/rpc-entry.js +62 -0
- package/dist/node-runtime/rpc-mode-SJNSOA4G.js +13 -0
- package/dist/node-runtime/runtime-bridges-PPHNBFWL.js +14 -0
- package/dist/node-runtime/session-manager-MQNBYACL.js +35 -0
- package/dist/node-runtime/session-selector-5AZLJS6U.js +14 -0
- package/dist/node-runtime/settings-manager-5LF2VQWC.js +21 -0
- package/dist/node-runtime/src-Y7JSCK64.js +2407 -0
- package/dist/node-runtime/subagent-executor-5RNDEPIJ.js +8483 -0
- package/dist/node-runtime/summary-review-YSMXKITO.js +228 -0
- package/dist/node-runtime/tavily-WEZFVFP7.js +154 -0
- package/dist/node-runtime/timings-UAD5BEAS.js +16 -0
- package/dist/node-runtime/tool-renderer-MV5O452K.js +204 -0
- package/dist/node-runtime/update-cli-A253SZ7P.js +73 -0
- package/dist/node-runtime/v4-5MNC3H7G.js +424 -0
- package/dist/node-runtime/video-extract-OYT5WKS5.js +304 -0
- package/dist/node-runtime/workspace-index-6DBXSTJ6.js +163 -0
- package/dist/node-runtime/youtube-extract-TQYKGFGS.js +264 -0
- package/dist/startup/benchmark-request.d.ts +3 -0
- package/dist/startup/benchmark-request.d.ts.map +1 -0
- package/dist/startup/benchmark-request.js +65 -0
- package/dist/startup/benchmark-request.js.map +1 -0
- package/dist/startup/interactive-view.d.ts +55 -0
- package/dist/startup/interactive-view.d.ts.map +1 -0
- package/dist/startup/interactive-view.js +332 -0
- package/dist/startup/interactive-view.js.map +1 -0
- package/dist/startup/launch-routing.d.ts.map +1 -1
- package/dist/startup/launch-routing.js +9 -10
- package/dist/startup/launch-routing.js.map +1 -1
- package/dist/startup/startup-milestones.d.ts +1 -1
- package/dist/startup/startup-milestones.d.ts.map +1 -1
- package/dist/startup/startup-milestones.js +3 -2
- package/dist/startup/startup-milestones.js.map +1 -1
- package/dist/utils/await-with-abort.d.ts +2 -0
- package/dist/utils/await-with-abort.d.ts.map +1 -0
- package/dist/utils/await-with-abort.js +19 -0
- package/dist/utils/await-with-abort.js.map +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +10 -4
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/docs/compaction.md +5 -3
- package/docs/development.md +5 -1
- package/docs/features.md +0 -1
- package/docs/interactive-startup.md +98 -0
- package/docs/usage.md +0 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +20 -20
- package/dist/core/swarm.d.ts +0 -23
- package/dist/core/swarm.d.ts.map +0 -1
- package/dist/core/swarm.js +0 -45
- package/dist/core/swarm.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-search.d.ts","sourceRoot":"","sources":["../../../src/builtin-extensions/pi-web-access/gemini-search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAIxE,OAAO,EAAkE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQ1I,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC;AACnH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAErE,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC/D,QAAQ,EAAE,sBAAsB,CAAC;CACjC;AAiDD,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACvD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAgDD,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CA4I9G","sourcesContent":["// @ts-nocheck\nimport { existsSync, readFileSync } from \"node:fs\";\nimport type { ExtensionContext } from \"@ashx-j/lunr-dev\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { getApiKey, getVersionedApiBase, buildKeyParam, buildAuthHeaders, isGatewayConfigured, DEFAULT_MODEL } from \"./gemini-api.ts\";\nimport { isGeminiWebAvailable, queryWithCookies } from \"./gemini-web.ts\";\nimport { isPerplexityAvailable, searchWithPerplexity, type SearchResult, type SearchResponse, type SearchOptions } from \"./perplexity.ts\";\nimport { hasExaApiKey, isExaAvailable, searchWithExa } from \"./exa.ts\";\nimport { isBraveAvailable, searchWithBrave } from \"./brave.ts\";\nimport { isOpenAISearchAvailable, searchWithOpenAI } from \"./openai-search.ts\";\nimport { isParallelAvailable, searchWithParallel } from \"./parallel.ts\";\nimport { isTavilyAvailable, searchWithTavily } from \"./tavily.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nexport type SearchProvider = \"auto\" | \"openai\" | \"brave\" | \"parallel\" | \"tavily\" | \"perplexity\" | \"gemini\" | \"exa\";\nexport type ResolvedSearchProvider = Exclude<SearchProvider, \"auto\">;\n\nexport interface AttributedSearchResponse extends SearchResponse {\n\tprovider: ResolvedSearchProvider;\n}\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nlet cachedSearchConfig: { searchProvider: SearchProvider; searchModel?: string } | null = null;\n\nfunction getSearchConfig(): { searchProvider: SearchProvider; searchModel?: string } {\n\tif (cachedSearchConfig) return cachedSearchConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedSearchConfig = { searchProvider: \"auto\", searchModel: undefined };\n\t\treturn cachedSearchConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: {\n\t\tsearchProvider?: SearchProvider;\n\t\tprovider?: SearchProvider;\n\t\tsearchModel?: unknown;\n\t};\n\ttry {\n\t\traw = JSON.parse(rawText) as {\n\t\t\tsearchProvider?: SearchProvider;\n\t\t\tprovider?: SearchProvider;\n\t\t\tsearchModel?: unknown;\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\n\tcachedSearchConfig = {\n\t\tsearchProvider: normalizeSearchProvider(raw.searchProvider ?? raw.provider),\n\t\tsearchModel: normalizeSearchModel(raw.searchModel),\n\t};\n\treturn cachedSearchConfig;\n}\n\nfunction normalizeSearchModel(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : undefined;\n}\n\nfunction normalizeSearchProvider(value: unknown): SearchProvider {\n\tconst normalized = typeof value === \"string\" ? value.trim().toLowerCase() : \"\";\n\tconst valid: SearchProvider[] = [\"auto\", \"openai\", \"brave\", \"parallel\", \"tavily\", \"perplexity\", \"gemini\", \"exa\"];\n\treturn valid.includes(normalized as SearchProvider) ? normalized as SearchProvider : \"auto\";\n}\n\nexport interface FullSearchOptions extends SearchOptions {\n\tprovider?: SearchProvider;\n\tincludeContent?: boolean;\n\textensionContext?: ExtensionContext;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction isAbortError(err: unknown): boolean {\n\treturn errorMessage(err).toLowerCase().includes(\"abort\");\n}\n\nfunction shouldTryOpenAIInAuto(options: SearchOptions): boolean {\n\tif (options.recencyFilter) return false;\n\tif (typeof options.numResults === \"number\" && Number.isFinite(options.numResults) && Math.floor(options.numResults) !== 5) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nasync function searchWithGemini(\n\tquery: string,\n\toptions: SearchOptions,\n\tstrictErrors: boolean,\n): Promise<SearchResponse | null> {\n\tconst errors: string[] = [];\n\n\ttry {\n\t\tconst apiResult = await searchWithGeminiApi(query, options);\n\t\tif (apiResult) return apiResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini API: ${errorMessage(err)}`);\n\t}\n\n\ttry {\n\t\tconst webResult = await searchWithGeminiWeb(query, options);\n\t\tif (webResult) return webResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini Web: ${errorMessage(err)}`);\n\t}\n\n\tif (strictErrors && errors.length > 0) {\n\t\tthrow new Error(`Gemini search failed:\\n - ${errors.join(\"\\n - \")}`);\n\t}\n\n\treturn null;\n}\n\nexport async function search(query: string, options: FullSearchOptions = {}): Promise<AttributedSearchResponse> {\n\tconst config = getSearchConfig();\n\tconst provider = options.provider ?? config.searchProvider;\n\n\tif (provider === \"openai\") {\n\t\tconst result = await searchWithOpenAI(query, options, options.extensionContext);\n\t\treturn { ...result, provider: \"openai\" };\n\t}\n\n\tif (provider === \"brave\") {\n\t\tconst result = await searchWithBrave(query, options);\n\t\treturn { ...result, provider: \"brave\" };\n\t}\n\n\tif (provider === \"parallel\") {\n\t\tconst result = await searchWithParallel(query, options);\n\t\treturn { ...result, provider: \"parallel\" };\n\t}\n\n\tif (provider === \"tavily\") {\n\t\tconst result = await searchWithTavily(query, options);\n\t\treturn { ...result, provider: \"tavily\" };\n\t}\n\n\tif (provider === \"perplexity\") {\n\t\tconst result = await searchWithPerplexity(query, options);\n\t\treturn { ...result, provider: \"perplexity\" };\n\t}\n\n\tif (provider === \"gemini\") {\n\t\tconst result = await searchWithGemini(query, options, true);\n\t\tif (result) return { ...result, provider: \"gemini\" };\n\t\tthrow new Error(\n\t\t\t\"Gemini search unavailable. Either:\\n\" +\n\t\t\t` 1. Set GEMINI_API_KEY in ${CONFIG_PATH}\\n` +\n\t\t\t\" 2. Set GOOGLE_GEMINI_BASE_URL + CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\t\" 3. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t\t);\n\t}\n\n\tif (provider === \"exa\") {\n\t\tconst exaApiKeyConfigured = hasExaApiKey();\n\t\ttry {\n\t\t\tconst result = await searchWithExa(query, options);\n\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t\tif (exaApiKeyConfigured) {\n\t\t\t\tthrow new Error(\"Exa search returned no results.\");\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tif (message.toLowerCase().includes(\"abort\")) throw err;\n\t\t\tif (exaApiKeyConfigured) throw err;\n\t\t\t// No API key: allow provider fallback.\n\t\t}\n\t}\n\n\tconst fallbackErrors: string[] = [];\n\n\tif (shouldTryOpenAIInAuto(options)) {\n\t\ttry {\n\t\t\tif (await isOpenAISearchAvailable(options.extensionContext)) {\n\t\t\t\tconst result = await searchWithOpenAI(query, options, options.extensionContext);\n\t\t\t\treturn { ...result, provider: \"openai\" };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`OpenAI: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (provider !== \"exa\" && isExaAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithExa(query, options);\n\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Exa: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isBraveAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithBrave(query, options);\n\t\t\treturn { ...result, provider: \"brave\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Brave: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isParallelAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithParallel(query, options);\n\t\t\treturn { ...result, provider: \"parallel\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Parallel: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isTavilyAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithTavily(query, options);\n\t\t\treturn { ...result, provider: \"tavily\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Tavily: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isPerplexityAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithPerplexity(query, options);\n\t\t\treturn { ...result, provider: \"perplexity\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Perplexity: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\ttry {\n\t\tconst geminiResult = await searchWithGemini(query, options, false);\n\t\tif (geminiResult) return { ...geminiResult, provider: \"gemini\" };\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\tfallbackErrors.push(`Gemini: ${errorMessage(err)}`);\n\t}\n\n\tif (fallbackErrors.length > 0) {\n\t\tthrow new Error(`Auto provider search failed:\\n - ${fallbackErrors.join(\"\\n - \")}`);\n\t}\n\n\tthrow new Error(\n\t\t\"No search provider available. Either:\\n\" +\n\t\t\" 1. Use /login to sign in with a Codex subscription for OpenAI web search\\n\" +\n\t\t` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tavilyApiKey, perplexityApiKey, exaApiKey, geminiApiKey, or cloudflareApiKey in ${CONFIG_PATH}\\n` +\n\t\t\" 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TAVILY_API_KEY, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\\n\" +\n\t\t\" 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\" 5. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t);\n}\n\nasync function searchWithGeminiApi(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst apiKey = getApiKey();\n\tif (!apiKey && !isGatewayConfigured()) return null;\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst model = getSearchConfig().searchModel ?? DEFAULT_MODEL;\n\t\tconst body = {\n\t\t\tcontents: [{ role: \"user\", parts: [{ text: query }] }],\n\t\t\ttools: [{ google_search: {} }],\n\t\t};\n\n\t\tconst res = await fetch(`${getVersionedApiBase()}/models/${model}:generateContent${buildKeyParam(apiKey)}`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\", ...buildAuthHeaders() },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(60000),\n\t\t\t\t...(options.signal ? [options.signal] : []),\n\t\t\t]),\n\t\t});\n\n\t\tif (!res.ok) {\n\t\t\tconst errorText = await res.text();\n\t\t\tthrow new Error(`Gemini API error ${res.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await res.json() as GeminiSearchResponse;\n\t\tactivityMonitor.logComplete(activityId, res.status);\n\n\t\tconst answer = data.candidates?.[0]?.content?.parts\n\t\t\t?.map(p => p.text).filter(Boolean).join(\"\\n\") ?? \"\";\n\n\t\tconst metadata = data.candidates?.[0]?.groundingMetadata;\n\t\tconst results = await resolveGroundingChunks(metadata?.groundingChunks, options.signal);\n\n\t\tif (!answer && results.length === 0) return null;\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function searchWithGeminiWeb(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst cookies = await isGeminiWebAvailable();\n\tif (!cookies) return null;\n\n\tconst prompt = buildSearchPrompt(query, options);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst text = await queryWithCookies(prompt, cookies, {\n\t\t\tmodel: \"gemini-3-flash-preview\",\n\t\t\tsignal: options.signal,\n\t\t\ttimeoutMs: 60000,\n\t\t});\n\n\t\tactivityMonitor.logComplete(activityId, 200);\n\n\t\tconst results = extractSourceUrls(text);\n\t\treturn { answer: text, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nfunction buildSearchPrompt(query: string, options: SearchOptions): string {\n\tlet prompt = `Search the web and answer the following question. Include source URLs for your claims.\\nFormat your response as:\\n1. A direct answer to the question\\n2. Cited sources as markdown links\\n\\nQuestion: ${query}`;\n\n\tif (options.recencyFilter) {\n\t\tconst labels: Record<string, string> = {\n\t\t\tday: \"past 24 hours\",\n\t\t\tweek: \"past week\",\n\t\t\tmonth: \"past month\",\n\t\t\tyear: \"past year\",\n\t\t};\n\t\tprompt += `\\n\\nOnly include results from the ${labels[options.recencyFilter]}.`;\n\t}\n\n\tif (options.domainFilter?.length) {\n\t\tconst includes = options.domainFilter.filter(d => !d.startsWith(\"-\"));\n\t\tconst excludes = options.domainFilter.filter(d => d.startsWith(\"-\")).map(d => d.slice(1));\n\t\tif (includes.length) prompt += `\\n\\nOnly cite sources from: ${includes.join(\", \")}`;\n\t\tif (excludes.length) prompt += `\\n\\nDo not cite sources from: ${excludes.join(\", \")}`;\n\t}\n\n\treturn prompt;\n}\n\nfunction extractSourceUrls(markdown: string): SearchResult[] {\n\tconst results: SearchResult[] = [];\n\tconst seen = new Set<string>();\n\tconst linkRegex = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n\tfor (const match of markdown.matchAll(linkRegex)) {\n\t\tconst url = match[2];\n\t\tif (seen.has(url)) continue;\n\t\tseen.add(url);\n\t\tresults.push({ title: match[1], url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveGroundingChunks(\n\tchunks: GroundingChunk[] | undefined,\n\tsignal?: AbortSignal,\n): Promise<SearchResult[]> {\n\tif (!chunks?.length) return [];\n\n\tconst results: SearchResult[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (!chunk.web) continue;\n\t\tconst title = chunk.web.title || \"\";\n\t\tlet url = chunk.web.uri || \"\";\n\n\t\tif (url.includes(\"vertexaisearch.cloud.google.com/grounding-api-redirect\")) {\n\t\t\tconst resolved = await resolveRedirect(url, signal);\n\t\t\tif (resolved) url = resolved;\n\t\t}\n\n\t\tif (url) results.push({ title, url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveRedirect(proxyUrl: string, signal?: AbortSignal): Promise<string | null> {\n\ttry {\n\t\tconst res = await fetch(proxyUrl, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tredirect: \"manual\",\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(5000),\n\t\t\t\t...(signal ? [signal] : []),\n\t\t\t]),\n\t\t});\n\t\treturn res.headers.get(\"location\") || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface GeminiSearchResponse {\n\tcandidates?: Array<{\n\t\tcontent?: { parts?: Array<{ text?: string }> };\n\t\tgroundingMetadata?: {\n\t\t\twebSearchQueries?: string[];\n\t\t\tgroundingChunks?: GroundingChunk[];\n\t\t\tgroundingSupports?: Array<{\n\t\t\t\tsegment?: { startIndex?: number; endIndex?: number; text?: string };\n\t\t\t\tgroundingChunkIndices?: number[];\n\t\t\t}>;\n\t\t};\n\t}>;\n}\n\ninterface GroundingChunk {\n\tweb?: { uri?: string; title?: string };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gemini-search.d.ts","sourceRoot":"","sources":["../../../src/builtin-extensions/pi-web-access/gemini-search.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,OAAO,KAAK,EAAgB,cAAc,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAanF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,KAAK,CAAC;AACnH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;AAErE,MAAM,WAAW,wBAAyB,SAAQ,cAAc;IAC/D,QAAQ,EAAE,sBAAsB,CAAC;CACjC;AAiDD,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACvD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACpC;AAgDD,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAkK9G","sourcesContent":["// @ts-nocheck\nimport { existsSync, readFileSync } from \"node:fs\";\nimport type { ExtensionContext } from \"@ashx-j/lunr-dev\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchResult, SearchResponse, SearchOptions } from \"./perplexity.ts\";\nimport {\n\tloadBrave,\n\tloadExa,\n\tloadGeminiApi,\n\tloadGeminiWeb,\n\tloadOpenAISearch,\n\tloadParallel,\n\tloadPerplexity,\n\tloadTavily,\n} from \"./lazy.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nexport type SearchProvider = \"auto\" | \"openai\" | \"brave\" | \"parallel\" | \"tavily\" | \"perplexity\" | \"gemini\" | \"exa\";\nexport type ResolvedSearchProvider = Exclude<SearchProvider, \"auto\">;\n\nexport interface AttributedSearchResponse extends SearchResponse {\n\tprovider: ResolvedSearchProvider;\n}\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nlet cachedSearchConfig: { searchProvider: SearchProvider; searchModel?: string } | null = null;\n\nfunction getSearchConfig(): { searchProvider: SearchProvider; searchModel?: string } {\n\tif (cachedSearchConfig) return cachedSearchConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedSearchConfig = { searchProvider: \"auto\", searchModel: undefined };\n\t\treturn cachedSearchConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: {\n\t\tsearchProvider?: SearchProvider;\n\t\tprovider?: SearchProvider;\n\t\tsearchModel?: unknown;\n\t};\n\ttry {\n\t\traw = JSON.parse(rawText) as {\n\t\t\tsearchProvider?: SearchProvider;\n\t\t\tprovider?: SearchProvider;\n\t\t\tsearchModel?: unknown;\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\n\tcachedSearchConfig = {\n\t\tsearchProvider: normalizeSearchProvider(raw.searchProvider ?? raw.provider),\n\t\tsearchModel: normalizeSearchModel(raw.searchModel),\n\t};\n\treturn cachedSearchConfig;\n}\n\nfunction normalizeSearchModel(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : undefined;\n}\n\nfunction normalizeSearchProvider(value: unknown): SearchProvider {\n\tconst normalized = typeof value === \"string\" ? value.trim().toLowerCase() : \"\";\n\tconst valid: SearchProvider[] = [\"auto\", \"openai\", \"brave\", \"parallel\", \"tavily\", \"perplexity\", \"gemini\", \"exa\"];\n\treturn valid.includes(normalized as SearchProvider) ? normalized as SearchProvider : \"auto\";\n}\n\nexport interface FullSearchOptions extends SearchOptions {\n\tprovider?: SearchProvider;\n\tincludeContent?: boolean;\n\textensionContext?: ExtensionContext;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction isAbortError(err: unknown): boolean {\n\treturn errorMessage(err).toLowerCase().includes(\"abort\");\n}\n\nfunction shouldTryOpenAIInAuto(options: SearchOptions): boolean {\n\tif (options.recencyFilter) return false;\n\tif (typeof options.numResults === \"number\" && Number.isFinite(options.numResults) && Math.floor(options.numResults) !== 5) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nasync function searchWithGemini(\n\tquery: string,\n\toptions: SearchOptions,\n\tstrictErrors: boolean,\n): Promise<SearchResponse | null> {\n\tconst errors: string[] = [];\n\n\ttry {\n\t\tconst apiResult = await searchWithGeminiApi(query, options);\n\t\tif (apiResult) return apiResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini API: ${errorMessage(err)}`);\n\t}\n\n\ttry {\n\t\tconst webResult = await searchWithGeminiWeb(query, options);\n\t\tif (webResult) return webResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini Web: ${errorMessage(err)}`);\n\t}\n\n\tif (strictErrors && errors.length > 0) {\n\t\tthrow new Error(`Gemini search failed:\\n - ${errors.join(\"\\n - \")}`);\n\t}\n\n\treturn null;\n}\n\nexport async function search(query: string, options: FullSearchOptions = {}): Promise<AttributedSearchResponse> {\n\tconst config = getSearchConfig();\n\tconst provider = options.provider ?? config.searchProvider;\n\n\tif (provider === \"openai\") {\n\t\tconst { searchWithOpenAI } = await loadOpenAISearch();\n\t\tconst result = await searchWithOpenAI(query, options, options.extensionContext);\n\t\treturn { ...result, provider: \"openai\" };\n\t}\n\n\tif (provider === \"brave\") {\n\t\tconst { searchWithBrave } = await loadBrave();\n\t\tconst result = await searchWithBrave(query, options);\n\t\treturn { ...result, provider: \"brave\" };\n\t}\n\n\tif (provider === \"parallel\") {\n\t\tconst { searchWithParallel } = await loadParallel();\n\t\tconst result = await searchWithParallel(query, options);\n\t\treturn { ...result, provider: \"parallel\" };\n\t}\n\n\tif (provider === \"tavily\") {\n\t\tconst { searchWithTavily } = await loadTavily();\n\t\tconst result = await searchWithTavily(query, options);\n\t\treturn { ...result, provider: \"tavily\" };\n\t}\n\n\tif (provider === \"perplexity\") {\n\t\tconst { searchWithPerplexity } = await loadPerplexity();\n\t\tconst result = await searchWithPerplexity(query, options);\n\t\treturn { ...result, provider: \"perplexity\" };\n\t}\n\n\tif (provider === \"gemini\") {\n\t\tconst result = await searchWithGemini(query, options, true);\n\t\tif (result) return { ...result, provider: \"gemini\" };\n\t\tthrow new Error(\n\t\t\t\"Gemini search unavailable. Either:\\n\" +\n\t\t\t` 1. Set GEMINI_API_KEY in ${CONFIG_PATH}\\n` +\n\t\t\t\" 2. Set GOOGLE_GEMINI_BASE_URL + CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\t\" 3. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t\t);\n\t}\n\n\tif (provider === \"exa\") {\n\t\tconst exa = await loadExa();\n\t\tconst exaApiKeyConfigured = exa.hasExaApiKey();\n\t\ttry {\n\t\t\tconst result = await exa.searchWithExa(query, options);\n\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t\tif (exaApiKeyConfigured) {\n\t\t\t\tthrow new Error(\"Exa search returned no results.\");\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tif (message.toLowerCase().includes(\"abort\")) throw err;\n\t\t\tif (exaApiKeyConfigured) throw err;\n\t\t\t// No API key: allow provider fallback.\n\t\t}\n\t}\n\n\tconst fallbackErrors: string[] = [];\n\n\tif (shouldTryOpenAIInAuto(options)) {\n\t\ttry {\n\t\t\tconst openai = await loadOpenAISearch();\n\t\t\tif (await openai.isOpenAISearchAvailable(options.extensionContext)) {\n\t\t\t\tconst result = await openai.searchWithOpenAI(query, options, options.extensionContext);\n\t\t\t\treturn { ...result, provider: \"openai\" };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`OpenAI: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (provider !== \"exa\") {\n\t\tconst exa = await loadExa();\n\t\tif (exa.isExaAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await exa.searchWithExa(query, options);\n\t\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Exa: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst brave = await loadBrave();\n\t\tif (brave.isBraveAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await brave.searchWithBrave(query, options);\n\t\t\t\treturn { ...result, provider: \"brave\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Brave: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst parallel = await loadParallel();\n\t\tif (parallel.isParallelAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await parallel.searchWithParallel(query, options);\n\t\t\t\treturn { ...result, provider: \"parallel\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Parallel: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst tavily = await loadTavily();\n\t\tif (tavily.isTavilyAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await tavily.searchWithTavily(query, options);\n\t\t\t\treturn { ...result, provider: \"tavily\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Tavily: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst perplexity = await loadPerplexity();\n\t\tif (perplexity.isPerplexityAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await perplexity.searchWithPerplexity(query, options);\n\t\t\t\treturn { ...result, provider: \"perplexity\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Perplexity: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\tconst geminiResult = await searchWithGemini(query, options, false);\n\t\tif (geminiResult) return { ...geminiResult, provider: \"gemini\" };\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\tfallbackErrors.push(`Gemini: ${errorMessage(err)}`);\n\t}\n\n\tif (fallbackErrors.length > 0) {\n\t\tthrow new Error(`Auto provider search failed:\\n - ${fallbackErrors.join(\"\\n - \")}`);\n\t}\n\n\tthrow new Error(\n\t\t\"No search provider available. Either:\\n\" +\n\t\t\" 1. Use /login to sign in with a Codex subscription for OpenAI web search\\n\" +\n\t\t` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tavilyApiKey, perplexityApiKey, exaApiKey, geminiApiKey, or cloudflareApiKey in ${CONFIG_PATH}\\n` +\n\t\t\" 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TAVILY_API_KEY, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\\n\" +\n\t\t\" 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\" 5. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t);\n}\n\nasync function searchWithGeminiApi(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst geminiApi = await loadGeminiApi();\n\tconst apiKey = geminiApi.getApiKey();\n\tif (!apiKey && !geminiApi.isGatewayConfigured()) return null;\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst model = getSearchConfig().searchModel ?? geminiApi.DEFAULT_MODEL;\n\t\tconst body = {\n\t\t\tcontents: [{ role: \"user\", parts: [{ text: query }] }],\n\t\t\ttools: [{ google_search: {} }],\n\t\t};\n\n\t\tconst res = await fetch(`${geminiApi.getVersionedApiBase()}/models/${model}:generateContent${geminiApi.buildKeyParam(apiKey)}`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\", ...geminiApi.buildAuthHeaders() },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(60000),\n\t\t\t\t...(options.signal ? [options.signal] : []),\n\t\t\t]),\n\t\t});\n\n\t\tif (!res.ok) {\n\t\t\tconst errorText = await res.text();\n\t\t\tthrow new Error(`Gemini API error ${res.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await res.json() as GeminiSearchResponse;\n\t\tactivityMonitor.logComplete(activityId, res.status);\n\n\t\tconst answer = data.candidates?.[0]?.content?.parts\n\t\t\t?.map(p => p.text).filter(Boolean).join(\"\\n\") ?? \"\";\n\n\t\tconst metadata = data.candidates?.[0]?.groundingMetadata;\n\t\tconst results = await resolveGroundingChunks(metadata?.groundingChunks, options.signal);\n\n\t\tif (!answer && results.length === 0) return null;\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function searchWithGeminiWeb(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst geminiWeb = await loadGeminiWeb();\n\tconst cookies = await geminiWeb.isGeminiWebAvailable();\n\tif (!cookies) return null;\n\n\tconst prompt = buildSearchPrompt(query, options);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst text = await geminiWeb.queryWithCookies(prompt, cookies, {\n\t\t\tmodel: \"gemini-3-flash-preview\",\n\t\t\tsignal: options.signal,\n\t\t\ttimeoutMs: 60000,\n\t\t});\n\n\t\tactivityMonitor.logComplete(activityId, 200);\n\n\t\tconst results = extractSourceUrls(text);\n\t\treturn { answer: text, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nfunction buildSearchPrompt(query: string, options: SearchOptions): string {\n\tlet prompt = `Search the web and answer the following question. Include source URLs for your claims.\\nFormat your response as:\\n1. A direct answer to the question\\n2. Cited sources as markdown links\\n\\nQuestion: ${query}`;\n\n\tif (options.recencyFilter) {\n\t\tconst labels: Record<string, string> = {\n\t\t\tday: \"past 24 hours\",\n\t\t\tweek: \"past week\",\n\t\t\tmonth: \"past month\",\n\t\t\tyear: \"past year\",\n\t\t};\n\t\tprompt += `\\n\\nOnly include results from the ${labels[options.recencyFilter]}.`;\n\t}\n\n\tif (options.domainFilter?.length) {\n\t\tconst includes = options.domainFilter.filter(d => !d.startsWith(\"-\"));\n\t\tconst excludes = options.domainFilter.filter(d => d.startsWith(\"-\")).map(d => d.slice(1));\n\t\tif (includes.length) prompt += `\\n\\nOnly cite sources from: ${includes.join(\", \")}`;\n\t\tif (excludes.length) prompt += `\\n\\nDo not cite sources from: ${excludes.join(\", \")}`;\n\t}\n\n\treturn prompt;\n}\n\nfunction extractSourceUrls(markdown: string): SearchResult[] {\n\tconst results: SearchResult[] = [];\n\tconst seen = new Set<string>();\n\tconst linkRegex = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n\tfor (const match of markdown.matchAll(linkRegex)) {\n\t\tconst url = match[2];\n\t\tif (seen.has(url)) continue;\n\t\tseen.add(url);\n\t\tresults.push({ title: match[1], url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveGroundingChunks(\n\tchunks: GroundingChunk[] | undefined,\n\tsignal?: AbortSignal,\n): Promise<SearchResult[]> {\n\tif (!chunks?.length) return [];\n\n\tconst results: SearchResult[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (!chunk.web) continue;\n\t\tconst title = chunk.web.title || \"\";\n\t\tlet url = chunk.web.uri || \"\";\n\n\t\tif (url.includes(\"vertexaisearch.cloud.google.com/grounding-api-redirect\")) {\n\t\t\tconst resolved = await resolveRedirect(url, signal);\n\t\t\tif (resolved) url = resolved;\n\t\t}\n\n\t\tif (url) results.push({ title, url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveRedirect(proxyUrl: string, signal?: AbortSignal): Promise<string | null> {\n\ttry {\n\t\tconst res = await fetch(proxyUrl, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tredirect: \"manual\",\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(5000),\n\t\t\t\t...(signal ? [signal] : []),\n\t\t\t]),\n\t\t});\n\t\treturn res.headers.get(\"location\") || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface GeminiSearchResponse {\n\tcandidates?: Array<{\n\t\tcontent?: { parts?: Array<{ text?: string }> };\n\t\tgroundingMetadata?: {\n\t\t\twebSearchQueries?: string[];\n\t\t\tgroundingChunks?: GroundingChunk[];\n\t\t\tgroundingSupports?: Array<{\n\t\t\t\tsegment?: { startIndex?: number; endIndex?: number; text?: string };\n\t\t\t\tgroundingChunkIndices?: number[];\n\t\t\t}>;\n\t\t};\n\t}>;\n}\n\ninterface GroundingChunk {\n\tweb?: { uri?: string; title?: string };\n}\n"]}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { activityMonitor } from "./activity.js";
|
|
4
|
-
import {
|
|
5
|
-
import { isGeminiWebAvailable, queryWithCookies } from "./gemini-web.js";
|
|
6
|
-
import { isPerplexityAvailable, searchWithPerplexity } from "./perplexity.js";
|
|
7
|
-
import { hasExaApiKey, isExaAvailable, searchWithExa } from "./exa.js";
|
|
8
|
-
import { isBraveAvailable, searchWithBrave } from "./brave.js";
|
|
9
|
-
import { isOpenAISearchAvailable, searchWithOpenAI } from "./openai-search.js";
|
|
10
|
-
import { isParallelAvailable, searchWithParallel } from "./parallel.js";
|
|
11
|
-
import { isTavilyAvailable, searchWithTavily } from "./tavily.js";
|
|
4
|
+
import { loadBrave, loadExa, loadGeminiApi, loadGeminiWeb, loadOpenAISearch, loadParallel, loadPerplexity, loadTavily, } from "./lazy.js";
|
|
12
5
|
import { getWebSearchConfigPath } from "./utils.js";
|
|
13
6
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
14
7
|
let cachedSearchConfig = null;
|
|
@@ -90,22 +83,27 @@ export async function search(query, options = {}) {
|
|
|
90
83
|
const config = getSearchConfig();
|
|
91
84
|
const provider = options.provider ?? config.searchProvider;
|
|
92
85
|
if (provider === "openai") {
|
|
86
|
+
const { searchWithOpenAI } = await loadOpenAISearch();
|
|
93
87
|
const result = await searchWithOpenAI(query, options, options.extensionContext);
|
|
94
88
|
return { ...result, provider: "openai" };
|
|
95
89
|
}
|
|
96
90
|
if (provider === "brave") {
|
|
91
|
+
const { searchWithBrave } = await loadBrave();
|
|
97
92
|
const result = await searchWithBrave(query, options);
|
|
98
93
|
return { ...result, provider: "brave" };
|
|
99
94
|
}
|
|
100
95
|
if (provider === "parallel") {
|
|
96
|
+
const { searchWithParallel } = await loadParallel();
|
|
101
97
|
const result = await searchWithParallel(query, options);
|
|
102
98
|
return { ...result, provider: "parallel" };
|
|
103
99
|
}
|
|
104
100
|
if (provider === "tavily") {
|
|
101
|
+
const { searchWithTavily } = await loadTavily();
|
|
105
102
|
const result = await searchWithTavily(query, options);
|
|
106
103
|
return { ...result, provider: "tavily" };
|
|
107
104
|
}
|
|
108
105
|
if (provider === "perplexity") {
|
|
106
|
+
const { searchWithPerplexity } = await loadPerplexity();
|
|
109
107
|
const result = await searchWithPerplexity(query, options);
|
|
110
108
|
return { ...result, provider: "perplexity" };
|
|
111
109
|
}
|
|
@@ -119,9 +117,10 @@ export async function search(query, options = {}) {
|
|
|
119
117
|
" 3. Sign into gemini.google.com in a supported Chromium-based browser");
|
|
120
118
|
}
|
|
121
119
|
if (provider === "exa") {
|
|
122
|
-
const
|
|
120
|
+
const exa = await loadExa();
|
|
121
|
+
const exaApiKeyConfigured = exa.hasExaApiKey();
|
|
123
122
|
try {
|
|
124
|
-
const result = await searchWithExa(query, options);
|
|
123
|
+
const result = await exa.searchWithExa(query, options);
|
|
125
124
|
if (result)
|
|
126
125
|
return { ...result, provider: "exa" };
|
|
127
126
|
if (exaApiKeyConfigured) {
|
|
@@ -140,8 +139,9 @@ export async function search(query, options = {}) {
|
|
|
140
139
|
const fallbackErrors = [];
|
|
141
140
|
if (shouldTryOpenAIInAuto(options)) {
|
|
142
141
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
const openai = await loadOpenAISearch();
|
|
143
|
+
if (await openai.isOpenAISearchAvailable(options.extensionContext)) {
|
|
144
|
+
const result = await openai.searchWithOpenAI(query, options, options.extensionContext);
|
|
145
145
|
return { ...result, provider: "openai" };
|
|
146
146
|
}
|
|
147
147
|
}
|
|
@@ -151,60 +151,75 @@ export async function search(query, options = {}) {
|
|
|
151
151
|
fallbackErrors.push(`OpenAI: ${errorMessage(err)}`);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
if (provider !== "exa"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
154
|
+
if (provider !== "exa") {
|
|
155
|
+
const exa = await loadExa();
|
|
156
|
+
if (exa.isExaAvailable()) {
|
|
157
|
+
try {
|
|
158
|
+
const result = await exa.searchWithExa(query, options);
|
|
159
|
+
if (result)
|
|
160
|
+
return { ...result, provider: "exa" };
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (isAbortError(err))
|
|
164
|
+
throw err;
|
|
165
|
+
fallbackErrors.push(`Exa: ${errorMessage(err)}`);
|
|
166
|
+
}
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
{
|
|
170
|
+
const brave = await loadBrave();
|
|
171
|
+
if (brave.isBraveAvailable()) {
|
|
172
|
+
try {
|
|
173
|
+
const result = await brave.searchWithBrave(query, options);
|
|
174
|
+
return { ...result, provider: "brave" };
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (isAbortError(err))
|
|
178
|
+
throw err;
|
|
179
|
+
fallbackErrors.push(`Brave: ${errorMessage(err)}`);
|
|
180
|
+
}
|
|
175
181
|
}
|
|
176
182
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
183
|
+
{
|
|
184
|
+
const parallel = await loadParallel();
|
|
185
|
+
if (parallel.isParallelAvailable()) {
|
|
186
|
+
try {
|
|
187
|
+
const result = await parallel.searchWithParallel(query, options);
|
|
188
|
+
return { ...result, provider: "parallel" };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
if (isAbortError(err))
|
|
192
|
+
throw err;
|
|
193
|
+
fallbackErrors.push(`Parallel: ${errorMessage(err)}`);
|
|
194
|
+
}
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
{
|
|
198
|
+
const tavily = await loadTavily();
|
|
199
|
+
if (tavily.isTavilyAvailable()) {
|
|
200
|
+
try {
|
|
201
|
+
const result = await tavily.searchWithTavily(query, options);
|
|
202
|
+
return { ...result, provider: "tavily" };
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (isAbortError(err))
|
|
206
|
+
throw err;
|
|
207
|
+
fallbackErrors.push(`Tavily: ${errorMessage(err)}`);
|
|
208
|
+
}
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
{
|
|
212
|
+
const perplexity = await loadPerplexity();
|
|
213
|
+
if (perplexity.isPerplexityAvailable()) {
|
|
214
|
+
try {
|
|
215
|
+
const result = await perplexity.searchWithPerplexity(query, options);
|
|
216
|
+
return { ...result, provider: "perplexity" };
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (isAbortError(err))
|
|
220
|
+
throw err;
|
|
221
|
+
fallbackErrors.push(`Perplexity: ${errorMessage(err)}`);
|
|
222
|
+
}
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
try {
|
|
@@ -228,19 +243,20 @@ export async function search(query, options = {}) {
|
|
|
228
243
|
" 5. Sign into gemini.google.com in a supported Chromium-based browser");
|
|
229
244
|
}
|
|
230
245
|
async function searchWithGeminiApi(query, options = {}) {
|
|
231
|
-
const
|
|
232
|
-
|
|
246
|
+
const geminiApi = await loadGeminiApi();
|
|
247
|
+
const apiKey = geminiApi.getApiKey();
|
|
248
|
+
if (!apiKey && !geminiApi.isGatewayConfigured())
|
|
233
249
|
return null;
|
|
234
250
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
235
251
|
try {
|
|
236
|
-
const model = getSearchConfig().searchModel ?? DEFAULT_MODEL;
|
|
252
|
+
const model = getSearchConfig().searchModel ?? geminiApi.DEFAULT_MODEL;
|
|
237
253
|
const body = {
|
|
238
254
|
contents: [{ role: "user", parts: [{ text: query }] }],
|
|
239
255
|
tools: [{ google_search: {} }],
|
|
240
256
|
};
|
|
241
|
-
const res = await fetch(`${getVersionedApiBase()}/models/${model}:generateContent${buildKeyParam(apiKey)}`, {
|
|
257
|
+
const res = await fetch(`${geminiApi.getVersionedApiBase()}/models/${model}:generateContent${geminiApi.buildKeyParam(apiKey)}`, {
|
|
242
258
|
method: "POST",
|
|
243
|
-
headers: { "Content-Type": "application/json", ...buildAuthHeaders() },
|
|
259
|
+
headers: { "Content-Type": "application/json", ...geminiApi.buildAuthHeaders() },
|
|
244
260
|
body: JSON.stringify(body),
|
|
245
261
|
signal: AbortSignal.any([
|
|
246
262
|
AbortSignal.timeout(60000),
|
|
@@ -273,13 +289,14 @@ async function searchWithGeminiApi(query, options = {}) {
|
|
|
273
289
|
}
|
|
274
290
|
}
|
|
275
291
|
async function searchWithGeminiWeb(query, options = {}) {
|
|
276
|
-
const
|
|
292
|
+
const geminiWeb = await loadGeminiWeb();
|
|
293
|
+
const cookies = await geminiWeb.isGeminiWebAvailable();
|
|
277
294
|
if (!cookies)
|
|
278
295
|
return null;
|
|
279
296
|
const prompt = buildSearchPrompt(query, options);
|
|
280
297
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
281
298
|
try {
|
|
282
|
-
const text = await queryWithCookies(prompt, cookies, {
|
|
299
|
+
const text = await geminiWeb.queryWithCookies(prompt, cookies, {
|
|
283
300
|
model: "gemini-3-flash-preview",
|
|
284
301
|
signal: options.signal,
|
|
285
302
|
timeoutMs: 60000,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-search.js","sourceRoot":"","sources":["../../../src/builtin-extensions/pi-web-access/gemini-search.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport { existsSync, readFileSync } from \"node:fs\";\nimport type { ExtensionContext } from \"@ashx-j/lunr-dev\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { getApiKey, getVersionedApiBase, buildKeyParam, buildAuthHeaders, isGatewayConfigured, DEFAULT_MODEL } from \"./gemini-api.ts\";\nimport { isGeminiWebAvailable, queryWithCookies } from \"./gemini-web.ts\";\nimport { isPerplexityAvailable, searchWithPerplexity, type SearchResult, type SearchResponse, type SearchOptions } from \"./perplexity.ts\";\nimport { hasExaApiKey, isExaAvailable, searchWithExa } from \"./exa.ts\";\nimport { isBraveAvailable, searchWithBrave } from \"./brave.ts\";\nimport { isOpenAISearchAvailable, searchWithOpenAI } from \"./openai-search.ts\";\nimport { isParallelAvailable, searchWithParallel } from \"./parallel.ts\";\nimport { isTavilyAvailable, searchWithTavily } from \"./tavily.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nexport type SearchProvider = \"auto\" | \"openai\" | \"brave\" | \"parallel\" | \"tavily\" | \"perplexity\" | \"gemini\" | \"exa\";\nexport type ResolvedSearchProvider = Exclude<SearchProvider, \"auto\">;\n\nexport interface AttributedSearchResponse extends SearchResponse {\n\tprovider: ResolvedSearchProvider;\n}\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nlet cachedSearchConfig: { searchProvider: SearchProvider; searchModel?: string } | null = null;\n\nfunction getSearchConfig(): { searchProvider: SearchProvider; searchModel?: string } {\n\tif (cachedSearchConfig) return cachedSearchConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedSearchConfig = { searchProvider: \"auto\", searchModel: undefined };\n\t\treturn cachedSearchConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: {\n\t\tsearchProvider?: SearchProvider;\n\t\tprovider?: SearchProvider;\n\t\tsearchModel?: unknown;\n\t};\n\ttry {\n\t\traw = JSON.parse(rawText) as {\n\t\t\tsearchProvider?: SearchProvider;\n\t\t\tprovider?: SearchProvider;\n\t\t\tsearchModel?: unknown;\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\n\tcachedSearchConfig = {\n\t\tsearchProvider: normalizeSearchProvider(raw.searchProvider ?? raw.provider),\n\t\tsearchModel: normalizeSearchModel(raw.searchModel),\n\t};\n\treturn cachedSearchConfig;\n}\n\nfunction normalizeSearchModel(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : undefined;\n}\n\nfunction normalizeSearchProvider(value: unknown): SearchProvider {\n\tconst normalized = typeof value === \"string\" ? value.trim().toLowerCase() : \"\";\n\tconst valid: SearchProvider[] = [\"auto\", \"openai\", \"brave\", \"parallel\", \"tavily\", \"perplexity\", \"gemini\", \"exa\"];\n\treturn valid.includes(normalized as SearchProvider) ? normalized as SearchProvider : \"auto\";\n}\n\nexport interface FullSearchOptions extends SearchOptions {\n\tprovider?: SearchProvider;\n\tincludeContent?: boolean;\n\textensionContext?: ExtensionContext;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction isAbortError(err: unknown): boolean {\n\treturn errorMessage(err).toLowerCase().includes(\"abort\");\n}\n\nfunction shouldTryOpenAIInAuto(options: SearchOptions): boolean {\n\tif (options.recencyFilter) return false;\n\tif (typeof options.numResults === \"number\" && Number.isFinite(options.numResults) && Math.floor(options.numResults) !== 5) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nasync function searchWithGemini(\n\tquery: string,\n\toptions: SearchOptions,\n\tstrictErrors: boolean,\n): Promise<SearchResponse | null> {\n\tconst errors: string[] = [];\n\n\ttry {\n\t\tconst apiResult = await searchWithGeminiApi(query, options);\n\t\tif (apiResult) return apiResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini API: ${errorMessage(err)}`);\n\t}\n\n\ttry {\n\t\tconst webResult = await searchWithGeminiWeb(query, options);\n\t\tif (webResult) return webResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini Web: ${errorMessage(err)}`);\n\t}\n\n\tif (strictErrors && errors.length > 0) {\n\t\tthrow new Error(`Gemini search failed:\\n - ${errors.join(\"\\n - \")}`);\n\t}\n\n\treturn null;\n}\n\nexport async function search(query: string, options: FullSearchOptions = {}): Promise<AttributedSearchResponse> {\n\tconst config = getSearchConfig();\n\tconst provider = options.provider ?? config.searchProvider;\n\n\tif (provider === \"openai\") {\n\t\tconst result = await searchWithOpenAI(query, options, options.extensionContext);\n\t\treturn { ...result, provider: \"openai\" };\n\t}\n\n\tif (provider === \"brave\") {\n\t\tconst result = await searchWithBrave(query, options);\n\t\treturn { ...result, provider: \"brave\" };\n\t}\n\n\tif (provider === \"parallel\") {\n\t\tconst result = await searchWithParallel(query, options);\n\t\treturn { ...result, provider: \"parallel\" };\n\t}\n\n\tif (provider === \"tavily\") {\n\t\tconst result = await searchWithTavily(query, options);\n\t\treturn { ...result, provider: \"tavily\" };\n\t}\n\n\tif (provider === \"perplexity\") {\n\t\tconst result = await searchWithPerplexity(query, options);\n\t\treturn { ...result, provider: \"perplexity\" };\n\t}\n\n\tif (provider === \"gemini\") {\n\t\tconst result = await searchWithGemini(query, options, true);\n\t\tif (result) return { ...result, provider: \"gemini\" };\n\t\tthrow new Error(\n\t\t\t\"Gemini search unavailable. Either:\\n\" +\n\t\t\t` 1. Set GEMINI_API_KEY in ${CONFIG_PATH}\\n` +\n\t\t\t\" 2. Set GOOGLE_GEMINI_BASE_URL + CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\t\" 3. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t\t);\n\t}\n\n\tif (provider === \"exa\") {\n\t\tconst exaApiKeyConfigured = hasExaApiKey();\n\t\ttry {\n\t\t\tconst result = await searchWithExa(query, options);\n\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t\tif (exaApiKeyConfigured) {\n\t\t\t\tthrow new Error(\"Exa search returned no results.\");\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tif (message.toLowerCase().includes(\"abort\")) throw err;\n\t\t\tif (exaApiKeyConfigured) throw err;\n\t\t\t// No API key: allow provider fallback.\n\t\t}\n\t}\n\n\tconst fallbackErrors: string[] = [];\n\n\tif (shouldTryOpenAIInAuto(options)) {\n\t\ttry {\n\t\t\tif (await isOpenAISearchAvailable(options.extensionContext)) {\n\t\t\t\tconst result = await searchWithOpenAI(query, options, options.extensionContext);\n\t\t\t\treturn { ...result, provider: \"openai\" };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`OpenAI: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (provider !== \"exa\" && isExaAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithExa(query, options);\n\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Exa: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isBraveAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithBrave(query, options);\n\t\t\treturn { ...result, provider: \"brave\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Brave: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isParallelAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithParallel(query, options);\n\t\t\treturn { ...result, provider: \"parallel\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Parallel: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isTavilyAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithTavily(query, options);\n\t\t\treturn { ...result, provider: \"tavily\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Tavily: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (isPerplexityAvailable()) {\n\t\ttry {\n\t\t\tconst result = await searchWithPerplexity(query, options);\n\t\t\treturn { ...result, provider: \"perplexity\" };\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`Perplexity: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\ttry {\n\t\tconst geminiResult = await searchWithGemini(query, options, false);\n\t\tif (geminiResult) return { ...geminiResult, provider: \"gemini\" };\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\tfallbackErrors.push(`Gemini: ${errorMessage(err)}`);\n\t}\n\n\tif (fallbackErrors.length > 0) {\n\t\tthrow new Error(`Auto provider search failed:\\n - ${fallbackErrors.join(\"\\n - \")}`);\n\t}\n\n\tthrow new Error(\n\t\t\"No search provider available. Either:\\n\" +\n\t\t\" 1. Use /login to sign in with a Codex subscription for OpenAI web search\\n\" +\n\t\t` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tavilyApiKey, perplexityApiKey, exaApiKey, geminiApiKey, or cloudflareApiKey in ${CONFIG_PATH}\\n` +\n\t\t\" 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TAVILY_API_KEY, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\\n\" +\n\t\t\" 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\" 5. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t);\n}\n\nasync function searchWithGeminiApi(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst apiKey = getApiKey();\n\tif (!apiKey && !isGatewayConfigured()) return null;\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst model = getSearchConfig().searchModel ?? DEFAULT_MODEL;\n\t\tconst body = {\n\t\t\tcontents: [{ role: \"user\", parts: [{ text: query }] }],\n\t\t\ttools: [{ google_search: {} }],\n\t\t};\n\n\t\tconst res = await fetch(`${getVersionedApiBase()}/models/${model}:generateContent${buildKeyParam(apiKey)}`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\", ...buildAuthHeaders() },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(60000),\n\t\t\t\t...(options.signal ? [options.signal] : []),\n\t\t\t]),\n\t\t});\n\n\t\tif (!res.ok) {\n\t\t\tconst errorText = await res.text();\n\t\t\tthrow new Error(`Gemini API error ${res.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await res.json() as GeminiSearchResponse;\n\t\tactivityMonitor.logComplete(activityId, res.status);\n\n\t\tconst answer = data.candidates?.[0]?.content?.parts\n\t\t\t?.map(p => p.text).filter(Boolean).join(\"\\n\") ?? \"\";\n\n\t\tconst metadata = data.candidates?.[0]?.groundingMetadata;\n\t\tconst results = await resolveGroundingChunks(metadata?.groundingChunks, options.signal);\n\n\t\tif (!answer && results.length === 0) return null;\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function searchWithGeminiWeb(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst cookies = await isGeminiWebAvailable();\n\tif (!cookies) return null;\n\n\tconst prompt = buildSearchPrompt(query, options);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst text = await queryWithCookies(prompt, cookies, {\n\t\t\tmodel: \"gemini-3-flash-preview\",\n\t\t\tsignal: options.signal,\n\t\t\ttimeoutMs: 60000,\n\t\t});\n\n\t\tactivityMonitor.logComplete(activityId, 200);\n\n\t\tconst results = extractSourceUrls(text);\n\t\treturn { answer: text, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nfunction buildSearchPrompt(query: string, options: SearchOptions): string {\n\tlet prompt = `Search the web and answer the following question. Include source URLs for your claims.\\nFormat your response as:\\n1. A direct answer to the question\\n2. Cited sources as markdown links\\n\\nQuestion: ${query}`;\n\n\tif (options.recencyFilter) {\n\t\tconst labels: Record<string, string> = {\n\t\t\tday: \"past 24 hours\",\n\t\t\tweek: \"past week\",\n\t\t\tmonth: \"past month\",\n\t\t\tyear: \"past year\",\n\t\t};\n\t\tprompt += `\\n\\nOnly include results from the ${labels[options.recencyFilter]}.`;\n\t}\n\n\tif (options.domainFilter?.length) {\n\t\tconst includes = options.domainFilter.filter(d => !d.startsWith(\"-\"));\n\t\tconst excludes = options.domainFilter.filter(d => d.startsWith(\"-\")).map(d => d.slice(1));\n\t\tif (includes.length) prompt += `\\n\\nOnly cite sources from: ${includes.join(\", \")}`;\n\t\tif (excludes.length) prompt += `\\n\\nDo not cite sources from: ${excludes.join(\", \")}`;\n\t}\n\n\treturn prompt;\n}\n\nfunction extractSourceUrls(markdown: string): SearchResult[] {\n\tconst results: SearchResult[] = [];\n\tconst seen = new Set<string>();\n\tconst linkRegex = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n\tfor (const match of markdown.matchAll(linkRegex)) {\n\t\tconst url = match[2];\n\t\tif (seen.has(url)) continue;\n\t\tseen.add(url);\n\t\tresults.push({ title: match[1], url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveGroundingChunks(\n\tchunks: GroundingChunk[] | undefined,\n\tsignal?: AbortSignal,\n): Promise<SearchResult[]> {\n\tif (!chunks?.length) return [];\n\n\tconst results: SearchResult[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (!chunk.web) continue;\n\t\tconst title = chunk.web.title || \"\";\n\t\tlet url = chunk.web.uri || \"\";\n\n\t\tif (url.includes(\"vertexaisearch.cloud.google.com/grounding-api-redirect\")) {\n\t\t\tconst resolved = await resolveRedirect(url, signal);\n\t\t\tif (resolved) url = resolved;\n\t\t}\n\n\t\tif (url) results.push({ title, url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveRedirect(proxyUrl: string, signal?: AbortSignal): Promise<string | null> {\n\ttry {\n\t\tconst res = await fetch(proxyUrl, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tredirect: \"manual\",\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(5000),\n\t\t\t\t...(signal ? [signal] : []),\n\t\t\t]),\n\t\t});\n\t\treturn res.headers.get(\"location\") || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface GeminiSearchResponse {\n\tcandidates?: Array<{\n\t\tcontent?: { parts?: Array<{ text?: string }> };\n\t\tgroundingMetadata?: {\n\t\t\twebSearchQueries?: string[];\n\t\t\tgroundingChunks?: GroundingChunk[];\n\t\t\tgroundingSupports?: Array<{\n\t\t\t\tsegment?: { startIndex?: number; endIndex?: number; text?: string };\n\t\t\t\tgroundingChunkIndices?: number[];\n\t\t\t}>;\n\t\t};\n\t}>;\n}\n\ninterface GroundingChunk {\n\tweb?: { uri?: string; title?: string };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"gemini-search.js","sourceRoot":"","sources":["../../../src/builtin-extensions/pi-web-access/gemini-search.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-nocheck\nimport { existsSync, readFileSync } from \"node:fs\";\nimport type { ExtensionContext } from \"@ashx-j/lunr-dev\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchResult, SearchResponse, SearchOptions } from \"./perplexity.ts\";\nimport {\n\tloadBrave,\n\tloadExa,\n\tloadGeminiApi,\n\tloadGeminiWeb,\n\tloadOpenAISearch,\n\tloadParallel,\n\tloadPerplexity,\n\tloadTavily,\n} from \"./lazy.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nexport type SearchProvider = \"auto\" | \"openai\" | \"brave\" | \"parallel\" | \"tavily\" | \"perplexity\" | \"gemini\" | \"exa\";\nexport type ResolvedSearchProvider = Exclude<SearchProvider, \"auto\">;\n\nexport interface AttributedSearchResponse extends SearchResponse {\n\tprovider: ResolvedSearchProvider;\n}\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nlet cachedSearchConfig: { searchProvider: SearchProvider; searchModel?: string } | null = null;\n\nfunction getSearchConfig(): { searchProvider: SearchProvider; searchModel?: string } {\n\tif (cachedSearchConfig) return cachedSearchConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedSearchConfig = { searchProvider: \"auto\", searchModel: undefined };\n\t\treturn cachedSearchConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: {\n\t\tsearchProvider?: SearchProvider;\n\t\tprovider?: SearchProvider;\n\t\tsearchModel?: unknown;\n\t};\n\ttry {\n\t\traw = JSON.parse(rawText) as {\n\t\t\tsearchProvider?: SearchProvider;\n\t\t\tprovider?: SearchProvider;\n\t\t\tsearchModel?: unknown;\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\n\tcachedSearchConfig = {\n\t\tsearchProvider: normalizeSearchProvider(raw.searchProvider ?? raw.provider),\n\t\tsearchModel: normalizeSearchModel(raw.searchModel),\n\t};\n\treturn cachedSearchConfig;\n}\n\nfunction normalizeSearchModel(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : undefined;\n}\n\nfunction normalizeSearchProvider(value: unknown): SearchProvider {\n\tconst normalized = typeof value === \"string\" ? value.trim().toLowerCase() : \"\";\n\tconst valid: SearchProvider[] = [\"auto\", \"openai\", \"brave\", \"parallel\", \"tavily\", \"perplexity\", \"gemini\", \"exa\"];\n\treturn valid.includes(normalized as SearchProvider) ? normalized as SearchProvider : \"auto\";\n}\n\nexport interface FullSearchOptions extends SearchOptions {\n\tprovider?: SearchProvider;\n\tincludeContent?: boolean;\n\textensionContext?: ExtensionContext;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction isAbortError(err: unknown): boolean {\n\treturn errorMessage(err).toLowerCase().includes(\"abort\");\n}\n\nfunction shouldTryOpenAIInAuto(options: SearchOptions): boolean {\n\tif (options.recencyFilter) return false;\n\tif (typeof options.numResults === \"number\" && Number.isFinite(options.numResults) && Math.floor(options.numResults) !== 5) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\nasync function searchWithGemini(\n\tquery: string,\n\toptions: SearchOptions,\n\tstrictErrors: boolean,\n): Promise<SearchResponse | null> {\n\tconst errors: string[] = [];\n\n\ttry {\n\t\tconst apiResult = await searchWithGeminiApi(query, options);\n\t\tif (apiResult) return apiResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini API: ${errorMessage(err)}`);\n\t}\n\n\ttry {\n\t\tconst webResult = await searchWithGeminiWeb(query, options);\n\t\tif (webResult) return webResult;\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\terrors.push(`Gemini Web: ${errorMessage(err)}`);\n\t}\n\n\tif (strictErrors && errors.length > 0) {\n\t\tthrow new Error(`Gemini search failed:\\n - ${errors.join(\"\\n - \")}`);\n\t}\n\n\treturn null;\n}\n\nexport async function search(query: string, options: FullSearchOptions = {}): Promise<AttributedSearchResponse> {\n\tconst config = getSearchConfig();\n\tconst provider = options.provider ?? config.searchProvider;\n\n\tif (provider === \"openai\") {\n\t\tconst { searchWithOpenAI } = await loadOpenAISearch();\n\t\tconst result = await searchWithOpenAI(query, options, options.extensionContext);\n\t\treturn { ...result, provider: \"openai\" };\n\t}\n\n\tif (provider === \"brave\") {\n\t\tconst { searchWithBrave } = await loadBrave();\n\t\tconst result = await searchWithBrave(query, options);\n\t\treturn { ...result, provider: \"brave\" };\n\t}\n\n\tif (provider === \"parallel\") {\n\t\tconst { searchWithParallel } = await loadParallel();\n\t\tconst result = await searchWithParallel(query, options);\n\t\treturn { ...result, provider: \"parallel\" };\n\t}\n\n\tif (provider === \"tavily\") {\n\t\tconst { searchWithTavily } = await loadTavily();\n\t\tconst result = await searchWithTavily(query, options);\n\t\treturn { ...result, provider: \"tavily\" };\n\t}\n\n\tif (provider === \"perplexity\") {\n\t\tconst { searchWithPerplexity } = await loadPerplexity();\n\t\tconst result = await searchWithPerplexity(query, options);\n\t\treturn { ...result, provider: \"perplexity\" };\n\t}\n\n\tif (provider === \"gemini\") {\n\t\tconst result = await searchWithGemini(query, options, true);\n\t\tif (result) return { ...result, provider: \"gemini\" };\n\t\tthrow new Error(\n\t\t\t\"Gemini search unavailable. Either:\\n\" +\n\t\t\t` 1. Set GEMINI_API_KEY in ${CONFIG_PATH}\\n` +\n\t\t\t\" 2. Set GOOGLE_GEMINI_BASE_URL + CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\t\" 3. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t\t);\n\t}\n\n\tif (provider === \"exa\") {\n\t\tconst exa = await loadExa();\n\t\tconst exaApiKeyConfigured = exa.hasExaApiKey();\n\t\ttry {\n\t\t\tconst result = await exa.searchWithExa(query, options);\n\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t\tif (exaApiKeyConfigured) {\n\t\t\t\tthrow new Error(\"Exa search returned no results.\");\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tif (message.toLowerCase().includes(\"abort\")) throw err;\n\t\t\tif (exaApiKeyConfigured) throw err;\n\t\t\t// No API key: allow provider fallback.\n\t\t}\n\t}\n\n\tconst fallbackErrors: string[] = [];\n\n\tif (shouldTryOpenAIInAuto(options)) {\n\t\ttry {\n\t\t\tconst openai = await loadOpenAISearch();\n\t\t\tif (await openai.isOpenAISearchAvailable(options.extensionContext)) {\n\t\t\t\tconst result = await openai.searchWithOpenAI(query, options, options.extensionContext);\n\t\t\t\treturn { ...result, provider: \"openai\" };\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tif (isAbortError(err)) throw err;\n\t\t\tfallbackErrors.push(`OpenAI: ${errorMessage(err)}`);\n\t\t}\n\t}\n\n\tif (provider !== \"exa\") {\n\t\tconst exa = await loadExa();\n\t\tif (exa.isExaAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await exa.searchWithExa(query, options);\n\t\t\t\tif (result) return { ...result, provider: \"exa\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Exa: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst brave = await loadBrave();\n\t\tif (brave.isBraveAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await brave.searchWithBrave(query, options);\n\t\t\t\treturn { ...result, provider: \"brave\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Brave: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst parallel = await loadParallel();\n\t\tif (parallel.isParallelAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await parallel.searchWithParallel(query, options);\n\t\t\t\treturn { ...result, provider: \"parallel\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Parallel: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst tavily = await loadTavily();\n\t\tif (tavily.isTavilyAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await tavily.searchWithTavily(query, options);\n\t\t\t\treturn { ...result, provider: \"tavily\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Tavily: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tconst perplexity = await loadPerplexity();\n\t\tif (perplexity.isPerplexityAvailable()) {\n\t\t\ttry {\n\t\t\t\tconst result = await perplexity.searchWithPerplexity(query, options);\n\t\t\t\treturn { ...result, provider: \"perplexity\" };\n\t\t\t} catch (err) {\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tfallbackErrors.push(`Perplexity: ${errorMessage(err)}`);\n\t\t\t}\n\t\t}\n\t}\n\n\ttry {\n\t\tconst geminiResult = await searchWithGemini(query, options, false);\n\t\tif (geminiResult) return { ...geminiResult, provider: \"gemini\" };\n\t} catch (err) {\n\t\tif (isAbortError(err)) throw err;\n\t\tfallbackErrors.push(`Gemini: ${errorMessage(err)}`);\n\t}\n\n\tif (fallbackErrors.length > 0) {\n\t\tthrow new Error(`Auto provider search failed:\\n - ${fallbackErrors.join(\"\\n - \")}`);\n\t}\n\n\tthrow new Error(\n\t\t\"No search provider available. Either:\\n\" +\n\t\t\" 1. Use /login to sign in with a Codex subscription for OpenAI web search\\n\" +\n\t\t` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tavilyApiKey, perplexityApiKey, exaApiKey, geminiApiKey, or cloudflareApiKey in ${CONFIG_PATH}\\n` +\n\t\t\" 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TAVILY_API_KEY, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\\n\" +\n\t\t\" 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\\n\" +\n\t\t\" 5. Sign into gemini.google.com in a supported Chromium-based browser\"\n\t);\n}\n\nasync function searchWithGeminiApi(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst geminiApi = await loadGeminiApi();\n\tconst apiKey = geminiApi.getApiKey();\n\tif (!apiKey && !geminiApi.isGatewayConfigured()) return null;\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst model = getSearchConfig().searchModel ?? geminiApi.DEFAULT_MODEL;\n\t\tconst body = {\n\t\t\tcontents: [{ role: \"user\", parts: [{ text: query }] }],\n\t\t\ttools: [{ google_search: {} }],\n\t\t};\n\n\t\tconst res = await fetch(`${geminiApi.getVersionedApiBase()}/models/${model}:generateContent${geminiApi.buildKeyParam(apiKey)}`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\", ...geminiApi.buildAuthHeaders() },\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(60000),\n\t\t\t\t...(options.signal ? [options.signal] : []),\n\t\t\t]),\n\t\t});\n\n\t\tif (!res.ok) {\n\t\t\tconst errorText = await res.text();\n\t\t\tthrow new Error(`Gemini API error ${res.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await res.json() as GeminiSearchResponse;\n\t\tactivityMonitor.logComplete(activityId, res.status);\n\n\t\tconst answer = data.candidates?.[0]?.content?.parts\n\t\t\t?.map(p => p.text).filter(Boolean).join(\"\\n\") ?? \"\";\n\n\t\tconst metadata = data.candidates?.[0]?.groundingMetadata;\n\t\tconst results = await resolveGroundingChunks(metadata?.groundingChunks, options.signal);\n\n\t\tif (!answer && results.length === 0) return null;\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nasync function searchWithGeminiWeb(query: string, options: SearchOptions = {}): Promise<SearchResponse | null> {\n\tconst geminiWeb = await loadGeminiWeb();\n\tconst cookies = await geminiWeb.isGeminiWebAvailable();\n\tif (!cookies) return null;\n\n\tconst prompt = buildSearchPrompt(query, options);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tconst text = await geminiWeb.queryWithCookies(prompt, cookies, {\n\t\t\tmodel: \"gemini-3-flash-preview\",\n\t\t\tsignal: options.signal,\n\t\t\ttimeoutMs: 60000,\n\t\t});\n\n\t\tactivityMonitor.logComplete(activityId, 200);\n\n\t\tconst results = extractSourceUrls(text);\n\t\treturn { answer: text, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nfunction buildSearchPrompt(query: string, options: SearchOptions): string {\n\tlet prompt = `Search the web and answer the following question. Include source URLs for your claims.\\nFormat your response as:\\n1. A direct answer to the question\\n2. Cited sources as markdown links\\n\\nQuestion: ${query}`;\n\n\tif (options.recencyFilter) {\n\t\tconst labels: Record<string, string> = {\n\t\t\tday: \"past 24 hours\",\n\t\t\tweek: \"past week\",\n\t\t\tmonth: \"past month\",\n\t\t\tyear: \"past year\",\n\t\t};\n\t\tprompt += `\\n\\nOnly include results from the ${labels[options.recencyFilter]}.`;\n\t}\n\n\tif (options.domainFilter?.length) {\n\t\tconst includes = options.domainFilter.filter(d => !d.startsWith(\"-\"));\n\t\tconst excludes = options.domainFilter.filter(d => d.startsWith(\"-\")).map(d => d.slice(1));\n\t\tif (includes.length) prompt += `\\n\\nOnly cite sources from: ${includes.join(\", \")}`;\n\t\tif (excludes.length) prompt += `\\n\\nDo not cite sources from: ${excludes.join(\", \")}`;\n\t}\n\n\treturn prompt;\n}\n\nfunction extractSourceUrls(markdown: string): SearchResult[] {\n\tconst results: SearchResult[] = [];\n\tconst seen = new Set<string>();\n\tconst linkRegex = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n\tfor (const match of markdown.matchAll(linkRegex)) {\n\t\tconst url = match[2];\n\t\tif (seen.has(url)) continue;\n\t\tseen.add(url);\n\t\tresults.push({ title: match[1], url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveGroundingChunks(\n\tchunks: GroundingChunk[] | undefined,\n\tsignal?: AbortSignal,\n): Promise<SearchResult[]> {\n\tif (!chunks?.length) return [];\n\n\tconst results: SearchResult[] = [];\n\tfor (const chunk of chunks) {\n\t\tif (!chunk.web) continue;\n\t\tconst title = chunk.web.title || \"\";\n\t\tlet url = chunk.web.uri || \"\";\n\n\t\tif (url.includes(\"vertexaisearch.cloud.google.com/grounding-api-redirect\")) {\n\t\t\tconst resolved = await resolveRedirect(url, signal);\n\t\t\tif (resolved) url = resolved;\n\t\t}\n\n\t\tif (url) results.push({ title, url, snippet: \"\" });\n\t}\n\treturn results;\n}\n\nasync function resolveRedirect(proxyUrl: string, signal?: AbortSignal): Promise<string | null> {\n\ttry {\n\t\tconst res = await fetch(proxyUrl, {\n\t\t\tmethod: \"HEAD\",\n\t\t\tredirect: \"manual\",\n\t\t\tsignal: AbortSignal.any([\n\t\t\t\tAbortSignal.timeout(5000),\n\t\t\t\t...(signal ? [signal] : []),\n\t\t\t]),\n\t\t});\n\t\treturn res.headers.get(\"location\") || null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface GeminiSearchResponse {\n\tcandidates?: Array<{\n\t\tcontent?: { parts?: Array<{ text?: string }> };\n\t\tgroundingMetadata?: {\n\t\t\twebSearchQueries?: string[];\n\t\t\tgroundingChunks?: GroundingChunk[];\n\t\t\tgroundingSupports?: Array<{\n\t\t\t\tsegment?: { startIndex?: number; endIndex?: number; text?: string };\n\t\t\t\tgroundingChunkIndices?: number[];\n\t\t\t}>;\n\t\t};\n\t}>;\n}\n\ninterface GroundingChunk {\n\tweb?: { uri?: string; title?: string };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-extract.d.ts","sourceRoot":"","sources":["../../../src/builtin-extensions/pi-web-access/github-extract.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA2BrD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC/B;AA4ED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAiDhE;AAiWD,wBAAsB,aAAa,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAsGlC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC","sourcesContent":["// @ts-nocheck\nimport { existsSync, readFileSync, rmSync, statSync, readdirSync, openSync, readSync, closeSync, realpathSync } from \"node:fs\";\nimport { execFile } from \"node:child_process\";\nimport { extname, join, resolve as resolvePath, sep as pathSep } from \"node:path\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport { checkGhAvailable, checkRepoSize, fetchViaApi, showGhHint } from \"./github-api.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nconst BINARY_EXTENSIONS = new Set([\n\t\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".ico\", \".webp\", \".svg\", \".tiff\", \".tif\",\n\t\".mp3\", \".mp4\", \".avi\", \".mov\", \".mkv\", \".flv\", \".wmv\", \".wav\", \".ogg\", \".webm\", \".flac\", \".aac\",\n\t\".zip\", \".tar\", \".gz\", \".bz2\", \".xz\", \".7z\", \".rar\", \".zst\",\n\t\".exe\", \".dll\", \".so\", \".dylib\", \".bin\", \".o\", \".a\", \".lib\",\n\t\".woff\", \".woff2\", \".ttf\", \".otf\", \".eot\",\n\t\".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\",\n\t\".sqlite\", \".db\", \".sqlite3\",\n\t\".pyc\", \".pyo\", \".class\", \".jar\", \".war\",\n\t\".iso\", \".img\", \".dmg\",\n]);\n\nconst NOISE_DIRS = new Set([\n\t\"node_modules\", \"vendor\", \".next\", \"dist\", \"build\", \"__pycache__\",\n\t\".venv\", \"venv\", \".tox\", \".mypy_cache\", \".pytest_cache\",\n\t\"target\", \".gradle\", \".idea\", \".vscode\",\n]);\n\nconst MAX_INLINE_FILE_CHARS = 100_000;\nconst MAX_TREE_ENTRIES = 200;\n\nexport interface GitHubUrlInfo {\n\towner: string;\n\trepo: string;\n\tref?: string;\n\trefIsFullSha: boolean;\n\tpath?: string;\n\ttype: \"root\" | \"blob\" | \"tree\";\n}\n\ninterface CachedClone {\n\tlocalPath: string;\n\tclonePromise: Promise<string | null>;\n}\n\ninterface GitHubCloneConfig {\n\tenabled: boolean;\n\tmaxRepoSizeMB: number;\n\tcloneTimeoutSeconds: number;\n\tclonePath: string;\n}\n\nconst cloneCache = new Map<string, CachedClone>();\n\nlet cachedConfig: GitHubCloneConfig | null = null;\n\nfunction normalizeEnabled(value: unknown, fallback: boolean): boolean {\n\treturn typeof value === \"boolean\" ? value : fallback;\n}\n\nfunction normalizePositiveNumber(value: unknown, fallback: number): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\n\treturn value > 0 ? value : fallback;\n}\n\nfunction normalizeClonePath(value: unknown, fallback: string): string {\n\tif (typeof value !== \"string\") return fallback;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nfunction loadGitHubConfig(): GitHubCloneConfig {\n\tif (cachedConfig) return cachedConfig;\n\n\tconst defaults: GitHubCloneConfig = {\n\t\tenabled: true,\n\t\tmaxRepoSizeMB: 350,\n\t\tcloneTimeoutSeconds: 30,\n\t\tclonePath: \"/tmp/pi-github-repos\",\n\t};\n\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = defaults;\n\t\treturn cachedConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: { githubClone?: { enabled?: unknown; maxRepoSizeMB?: unknown; cloneTimeoutSeconds?: unknown; clonePath?: unknown } };\n\ttry {\n\t\traw = JSON.parse(rawText) as { githubClone?: { enabled?: unknown; maxRepoSizeMB?: unknown; cloneTimeoutSeconds?: unknown; clonePath?: unknown } };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\n\tconst gc = raw.githubClone ?? {};\n\tcachedConfig = {\n\t\tenabled: normalizeEnabled(gc.enabled, defaults.enabled),\n\t\tmaxRepoSizeMB: normalizePositiveNumber(gc.maxRepoSizeMB, defaults.maxRepoSizeMB),\n\t\tcloneTimeoutSeconds: normalizePositiveNumber(gc.cloneTimeoutSeconds, defaults.cloneTimeoutSeconds),\n\t\tclonePath: normalizeClonePath(gc.clonePath, defaults.clonePath),\n\t};\n\treturn cachedConfig;\n}\n\nconst NON_CODE_SEGMENTS = new Set([\n\t\"issues\", \"pull\", \"pulls\", \"discussions\", \"releases\", \"wiki\",\n\t\"actions\", \"settings\", \"security\", \"projects\", \"graphs\",\n\t\"compare\", \"commits\", \"tags\", \"branches\", \"stargazers\",\n\t\"watchers\", \"network\", \"forks\", \"milestone\", \"labels\",\n\t\"packages\", \"codespaces\", \"contribute\", \"community\",\n\t\"sponsors\", \"invitations\", \"notifications\", \"insights\",\n]);\n\nexport function parseGitHubUrl(url: string): GitHubUrlInfo | null {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\treturn null;\n\t}\n\n\tconst host = parsed.hostname.toLowerCase();\n\tif (host !== \"github.com\" && host !== \"www.github.com\") return null;\n\n\tconst segments = parsed.pathname\n\t\t.split(\"/\")\n\t\t.filter(Boolean)\n\t\t.map((segment) => {\n\t\t\ttry {\n\t\t\t\treturn decodeURIComponent(segment);\n\t\t\t} catch {\n\t\t\t\treturn segment;\n\t\t\t}\n\t\t});\n\tif (segments.length < 2) return null;\n\n\tconst owner = segments[0];\n\tconst repo = segments[1].replace(/\\.git$/, \"\");\n\n\tif (NON_CODE_SEGMENTS.has(segments[2]?.toLowerCase())) return null;\n\n\tif (segments.length === 2) {\n\t\treturn { owner, repo, refIsFullSha: false, type: \"root\" };\n\t}\n\n\tconst action = segments[2];\n\tif (action !== \"blob\" && action !== \"tree\") return null;\n\tif (segments.length < 4) return null;\n\n\tconst ref = segments[3];\n\tconst refIsFullSha = /^[0-9a-f]{40}$/.test(ref);\n\tconst pathParts = segments.slice(4);\n\tconst path = pathParts.length > 0 ? pathParts.join(\"/\") : \"\";\n\n\treturn {\n\t\towner,\n\t\trepo,\n\t\tref,\n\t\trefIsFullSha,\n\t\tpath,\n\t\ttype: action as \"blob\" | \"tree\",\n\t};\n}\n\nfunction cacheKey(owner: string, repo: string, ref?: string): string {\n\treturn ref ? `${owner}/${repo}@${ref}` : `${owner}/${repo}`;\n}\n\nfunction cloneDir(config: GitHubCloneConfig, owner: string, repo: string, ref?: string): string {\n\tconst dirName = ref ? `${repo}@${ref}` : repo;\n\treturn join(config.clonePath, owner, dirName);\n}\n\nfunction execClone(args: string[], localPath: string, timeoutMs: number, signal?: AbortSignal): Promise<string | null> {\n\treturn new Promise((resolve) => {\n\t\tconst child = execFile(args[0], args.slice(1), { timeout: timeoutMs }, (err) => {\n\t\t\tif (err) {\n\t\t\t\ttry {\n\t\t\t\t\trmSync(localPath, { recursive: true, force: true });\n\t\t\t\t} catch {\n\t\t\t\t}\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(localPath);\n\t\t});\n\n\t\tif (signal) {\n\t\t\tconst onAbort = () => child.kill();\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tchild.on(\"exit\", () => signal.removeEventListener(\"abort\", onAbort));\n\t\t}\n\t});\n}\n\nasync function cloneRepo(\n\towner: string,\n\trepo: string,\n\tref: string | undefined,\n\tconfig: GitHubCloneConfig,\n\tsignal?: AbortSignal,\n): Promise<string | null> {\n\tconst localPath = cloneDir(config, owner, repo, ref);\n\n\ttry {\n\t\trmSync(localPath, { recursive: true, force: true });\n\t} catch {\n\t}\n\n\tconst timeoutMs = config.cloneTimeoutSeconds * 1000;\n\tconst hasGh = await checkGhAvailable();\n\n\tif (hasGh) {\n\t\tconst args = [\"gh\", \"repo\", \"clone\", `${owner}/${repo}`, localPath, \"--\", \"--depth\", \"1\", \"--single-branch\"];\n\t\tif (ref) args.push(\"--branch\", ref);\n\t\treturn execClone(args, localPath, timeoutMs, signal);\n\t}\n\n\tshowGhHint();\n\n\tconst gitUrl = `https://github.com/${owner}/${repo}.git`;\n\tconst args = [\"git\", \"clone\", \"--depth\", \"1\", \"--single-branch\"];\n\tif (ref) args.push(\"--branch\", ref);\n\targs.push(gitUrl, localPath);\n\treturn execClone(args, localPath, timeoutMs, signal);\n}\n\nfunction isBinaryFile(filePath: string): boolean {\n\tconst ext = extname(filePath).toLowerCase();\n\tif (BINARY_EXTENSIONS.has(ext)) return true;\n\n\tlet fd: number;\n\ttry {\n\t\tfd = openSync(filePath, \"r\");\n\t} catch {\n\t\treturn false;\n\t}\n\ttry {\n\t\tconst buf = Buffer.alloc(512);\n\t\tconst bytesRead = readSync(fd, buf, 0, 512, 0);\n\t\tfor (let i = 0; i < bytesRead; i++) {\n\t\t\tif (buf[i] === 0) return true;\n\t\t}\n\t} catch {\n\t\treturn false;\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n\n\treturn false;\n}\n\nfunction formatFileSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes} B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction resolveWithinRepo(rootPath: string, relativePath: string): string | null {\n\tconst normalizedRoot = resolvePath(rootPath);\n\tconst candidate = resolvePath(normalizedRoot, relativePath);\n\tif (candidate !== normalizedRoot) {\n\t\tconst rootPrefix = normalizedRoot.endsWith(pathSep) ? normalizedRoot : normalizedRoot + pathSep;\n\t\tif (!candidate.startsWith(rootPrefix)) return null;\n\t}\n\n\tif (!existsSync(candidate)) return candidate;\n\n\ttry {\n\t\tconst realRoot = realpathSync(normalizedRoot);\n\t\tconst realCandidate = realpathSync(candidate);\n\t\tif (realCandidate === realRoot) return candidate;\n\t\tconst realRootPrefix = realRoot.endsWith(pathSep) ? realRoot : realRoot + pathSep;\n\t\treturn realCandidate.startsWith(realRootPrefix) ? candidate : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction readTextFile(path: string): string | null {\n\ttry {\n\t\treturn readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction buildTree(rootPath: string): string {\n\tconst entries: string[] = [];\n\n\tfunction walk(dir: string, relPath: string): void {\n\t\tif (entries.length >= MAX_TREE_ENTRIES) return;\n\n\t\tlet items: string[];\n\t\ttry {\n\t\t\titems = readdirSync(dir).sort();\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const item of items) {\n\t\t\tif (entries.length >= MAX_TREE_ENTRIES) return;\n\t\t\tif (item === \".git\") continue;\n\n\t\t\tconst rel = relPath ? `${relPath}/${item}` : item;\n\t\t\tconst safePath = resolveWithinRepo(rootPath, rel);\n\t\t\tif (!safePath) {\n\t\t\t\tentries.push(`${rel} [outside repo skipped]`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet stat;\n\t\t\ttry {\n\t\t\t\tstat = statSync(safePath);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (stat.isDirectory()) {\n\t\t\t\tif (NOISE_DIRS.has(item)) {\n\t\t\t\t\tentries.push(`${rel}/ [skipped]`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tentries.push(`${rel}/`);\n\t\t\t\twalk(safePath, rel);\n\t\t\t} else {\n\t\t\t\tentries.push(rel);\n\t\t\t}\n\t\t}\n\t}\n\n\twalk(rootPath, \"\");\n\n\tif (entries.length >= MAX_TREE_ENTRIES) {\n\t\tentries.push(`... (truncated at ${MAX_TREE_ENTRIES} entries)`);\n\t}\n\n\treturn entries.join(\"\\n\");\n}\n\nfunction buildDirListing(rootPath: string, subPath: string): string {\n\tconst targetPath = resolveWithinRepo(rootPath, subPath);\n\tif (!targetPath) return \"(path escapes repository root)\";\n\tconst lines: string[] = [];\n\n\tlet items: string[];\n\ttry {\n\t\titems = readdirSync(targetPath).sort();\n\t} catch {\n\t\treturn \"(directory not readable)\";\n\t}\n\n\tfor (const item of items) {\n\t\tif (item === \".git\") continue;\n\t\tconst rel = subPath ? `${subPath}/${item}` : item;\n\t\tconst safePath = resolveWithinRepo(rootPath, rel);\n\t\tif (!safePath) {\n\t\t\tlines.push(` ${item} (outside repo)`);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stat = statSync(safePath);\n\t\t\tif (stat.isDirectory()) {\n\t\t\t\tlines.push(` ${item}/`);\n\t\t\t} else {\n\t\t\t\tlines.push(` ${item} (${formatFileSize(stat.size)})`);\n\t\t\t}\n\t\t} catch {\n\t\t\tlines.push(` ${item} (unreadable)`);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction readReadme(localPath: string): string | null {\n\tconst candidates = [\"README.md\", \"readme.md\", \"README\", \"README.txt\", \"README.rst\"];\n\tfor (const name of candidates) {\n\t\tconst readmePath = join(localPath, name);\n\t\tif (existsSync(readmePath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(readmePath, \"utf-8\");\n\t\t\t\treturn content.length > 8192 ? content.slice(0, 8192) + \"\\n\\n[README truncated at 8K chars]\" : content;\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction generateContent(localPath: string, info: GitHubUrlInfo): string {\n\tconst lines: string[] = [];\n\tlines.push(`Repository cloned to: ${localPath}`);\n\tlines.push(\"\");\n\n\tif (info.type === \"root\") {\n\t\tlines.push(\"## Structure\");\n\t\tlines.push(buildTree(localPath));\n\t\tlines.push(\"\");\n\n\t\tconst readme = readReadme(localPath);\n\t\tif (readme) {\n\t\t\tlines.push(\"## README.md\");\n\t\t\tlines.push(readme);\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tif (info.type === \"tree\") {\n\t\tconst dirPath = info.path || \"\";\n\t\tconst fullDirPath = resolveWithinRepo(localPath, dirPath);\n\n\t\tif (!fullDirPath || !existsSync(fullDirPath)) {\n\t\t\tlines.push(`Path \\`${dirPath}\\` not found in clone. Showing repository root instead.`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"## Structure\");\n\t\t\tlines.push(buildTree(localPath));\n\t\t} else {\n\t\t\tlines.push(`## ${dirPath || \"/\"}`);\n\t\t\tlines.push(buildDirListing(localPath, dirPath));\n\t\t}\n\n\t\tlines.push(\"\");\n\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tif (info.type === \"blob\") {\n\t\tconst filePath = info.path || \"\";\n\t\tconst fullFilePath = resolveWithinRepo(localPath, filePath);\n\n\t\tif (!fullFilePath || !existsSync(fullFilePath)) {\n\t\t\tlines.push(`Path \\`${filePath}\\` not found in clone. Showing repository root instead.`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"## Structure\");\n\t\t\tlines.push(buildTree(localPath));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tlet stat: ReturnType<typeof statSync>;\n\t\ttry {\n\t\t\tstat = statSync(fullFilePath);\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tlines.push(`Could not inspect \\`${filePath}\\`: ${message}`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tif (stat.isDirectory()) {\n\t\t\tlines.push(`## ${filePath || \"/\"}`);\n\t\t\tlines.push(buildDirListing(localPath, filePath));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tif (isBinaryFile(fullFilePath)) {\n\t\t\tconst ext = extname(filePath).replace(\".\", \"\");\n\t\t\tlines.push(`## ${filePath}`);\n\t\t\tlines.push(`Binary file (${ext}, ${formatFileSize(stat.size)}). Use \\`read\\` or \\`bash\\` tools at the path above to inspect.`);\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tconst content = readTextFile(fullFilePath);\n\t\tif (content === null) {\n\t\t\tlines.push(`Could not read \\`${filePath}\\` as UTF-8 text.`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\t\tlines.push(`## ${filePath}`);\n\n\t\tif (content.length > MAX_INLINE_FILE_CHARS) {\n\t\t\tlines.push(content.slice(0, MAX_INLINE_FILE_CHARS));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(`[File truncated at 100K chars. Full file: ${fullFilePath}]`);\n\t\t} else {\n\t\t\tlines.push(content);\n\t\t}\n\n\t\tlines.push(\"\");\n\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nasync function awaitCachedClone(\n\tcached: CachedClone,\n\turl: string,\n\towner: string,\n\trepo: string,\n\tinfo: GitHubUrlInfo,\n\tsignal?: AbortSignal,\n): Promise<ExtractedContent | null> {\n\tif (signal?.aborted) return null;\n\tconst result = await cached.clonePromise;\n\tif (signal?.aborted) return null;\n\tif (result) {\n\t\tconst content = generateContent(result, info);\n\t\tconst title = info.path ? `${owner}/${repo} - ${info.path}` : `${owner}/${repo}`;\n\t\treturn { url, title, content, error: null };\n\t}\n\treturn fetchViaApi(url, owner, repo, info);\n}\n\nexport async function extractGitHub(\n\turl: string,\n\tsignal?: AbortSignal,\n\tforceClone?: boolean,\n): Promise<ExtractedContent | null> {\n\tconst info = parseGitHubUrl(url);\n\tif (!info) return null;\n\n\tif (signal?.aborted) return null;\n\n\tconst config = loadGitHubConfig();\n\tif (!config.enabled) return null;\n\n\tconst { owner, repo } = info;\n\tconst key = cacheKey(owner, repo, info.ref);\n\n\tconst cached = cloneCache.get(key);\n\tif (cached) return awaitCachedClone(cached, url, owner, repo, info, signal);\n\n\tif (info.refIsFullSha) {\n\t\tif (signal?.aborted) return null;\n\t\tconst sizeNote = `Note: Commit SHA URLs use the GitHub API instead of cloning.`;\n\t\treturn fetchViaApi(url, owner, repo, info, sizeNote);\n\t}\n\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: `github.com/${owner}/${repo}` });\n\n\tif (!forceClone) {\n\t\tconst sizeKB = await checkRepoSize(owner, repo);\n\t\tif (signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\treturn null;\n\t\t}\n\t\tif (sizeKB !== null) {\n\t\t\tconst sizeMB = sizeKB / 1024;\n\t\t\tif (sizeMB > config.maxRepoSizeMB) {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst sizeNote =\n\t\t\t\t\t`Note: Repository is ${Math.round(sizeMB)}MB (threshold: ${config.maxRepoSizeMB}MB). ` +\n\t\t\t\t\t`Showing API-fetched content instead of full clone. Ask the user if they'd like to clone the full repo -- ` +\n\t\t\t\t\t`if yes, call fetch_content again with the same URL and add forceClone: true to the params.`;\n\t\t\t\tconst apiView = await fetchViaApi(url, owner, repo, info, sizeNote);\n\t\t\t\tif (apiView) {\n\t\t\t\t\tactivityMonitor.logComplete(activityId, 200);\n\t\t\t\t\treturn apiView;\n\t\t\t\t}\n\t\t\t\tactivityMonitor.logError(activityId, \"api fallback unavailable for oversized repository\");\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (signal?.aborted) {\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\t// Re-check: another concurrent caller may have started a clone while we awaited the size check\n\tconst cachedAfterSizeCheck = cloneCache.get(key);\n\tif (cachedAfterSizeCheck) {\n\t\tconst cachedResult = await awaitCachedClone(cachedAfterSizeCheck, url, owner, repo, info, signal);\n\t\tif (signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else if (cachedResult) {\n\t\t\tactivityMonitor.logComplete(activityId, 200);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, \"clone failed\");\n\t\t}\n\t\treturn cachedResult;\n\t}\n\n\tconst clonePromise = cloneRepo(owner, repo, info.ref, config, signal);\n\tconst localPath = cloneDir(config, owner, repo, info.ref);\n\tcloneCache.set(key, { localPath, clonePromise });\n\n\tconst result = await clonePromise;\n\tif (signal?.aborted) {\n\t\tif (!result) cloneCache.delete(key);\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\tif (!result) {\n\t\tcloneCache.delete(key);\n\t\tif (signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst apiFallback = await fetchViaApi(url, owner, repo, info);\n\t\tif (apiFallback) {\n\t\t\tactivityMonitor.logComplete(activityId, 200);\n\t\t\treturn apiFallback;\n\t\t}\n\n\t\tactivityMonitor.logError(activityId, \"clone and API fallback failed\");\n\t\treturn null;\n\t}\n\n\tactivityMonitor.logComplete(activityId, 200);\n\tconst content = generateContent(result, info);\n\tconst title = info.path ? `${owner}/${repo} - ${info.path}` : `${owner}/${repo}`;\n\treturn { url, title, content, error: null };\n}\n\nexport function clearCloneCache(): void {\n\tfor (const entry of cloneCache.values()) {\n\t\ttry {\n\t\t\trmSync(entry.localPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t}\n\t}\n\tcloneCache.clear();\n\tcachedConfig = null;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"github-extract.d.ts","sourceRoot":"","sources":["../../../src/builtin-extensions/pi-web-access/github-extract.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AA4BrD,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC/B;AA4ED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAiDhE;AAiWD,wBAAsB,aAAa,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,EACpB,UAAU,CAAC,EAAE,OAAO,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAsGlC;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC","sourcesContent":["// @ts-nocheck\nimport { existsSync, readFileSync, rmSync, statSync, readdirSync, openSync, readSync, closeSync, realpathSync } from \"node:fs\";\nimport { execFile } from \"node:child_process\";\nimport { extname, join, resolve as resolvePath, sep as pathSep } from \"node:path\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport { checkGhAvailable, checkRepoSize, fetchViaApi, showGhHint } from \"./github-api.ts\";\nimport { registerSessionCleanup } from \"./session-cleanup.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nconst BINARY_EXTENSIONS = new Set([\n\t\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".ico\", \".webp\", \".svg\", \".tiff\", \".tif\",\n\t\".mp3\", \".mp4\", \".avi\", \".mov\", \".mkv\", \".flv\", \".wmv\", \".wav\", \".ogg\", \".webm\", \".flac\", \".aac\",\n\t\".zip\", \".tar\", \".gz\", \".bz2\", \".xz\", \".7z\", \".rar\", \".zst\",\n\t\".exe\", \".dll\", \".so\", \".dylib\", \".bin\", \".o\", \".a\", \".lib\",\n\t\".woff\", \".woff2\", \".ttf\", \".otf\", \".eot\",\n\t\".pdf\", \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\",\n\t\".sqlite\", \".db\", \".sqlite3\",\n\t\".pyc\", \".pyo\", \".class\", \".jar\", \".war\",\n\t\".iso\", \".img\", \".dmg\",\n]);\n\nconst NOISE_DIRS = new Set([\n\t\"node_modules\", \"vendor\", \".next\", \"dist\", \"build\", \"__pycache__\",\n\t\".venv\", \"venv\", \".tox\", \".mypy_cache\", \".pytest_cache\",\n\t\"target\", \".gradle\", \".idea\", \".vscode\",\n]);\n\nconst MAX_INLINE_FILE_CHARS = 100_000;\nconst MAX_TREE_ENTRIES = 200;\n\nexport interface GitHubUrlInfo {\n\towner: string;\n\trepo: string;\n\tref?: string;\n\trefIsFullSha: boolean;\n\tpath?: string;\n\ttype: \"root\" | \"blob\" | \"tree\";\n}\n\ninterface CachedClone {\n\tlocalPath: string;\n\tclonePromise: Promise<string | null>;\n}\n\ninterface GitHubCloneConfig {\n\tenabled: boolean;\n\tmaxRepoSizeMB: number;\n\tcloneTimeoutSeconds: number;\n\tclonePath: string;\n}\n\nconst cloneCache = new Map<string, CachedClone>();\n\nlet cachedConfig: GitHubCloneConfig | null = null;\n\nfunction normalizeEnabled(value: unknown, fallback: boolean): boolean {\n\treturn typeof value === \"boolean\" ? value : fallback;\n}\n\nfunction normalizePositiveNumber(value: unknown, fallback: number): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\n\treturn value > 0 ? value : fallback;\n}\n\nfunction normalizeClonePath(value: unknown, fallback: string): string {\n\tif (typeof value !== \"string\") return fallback;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nfunction loadGitHubConfig(): GitHubCloneConfig {\n\tif (cachedConfig) return cachedConfig;\n\n\tconst defaults: GitHubCloneConfig = {\n\t\tenabled: true,\n\t\tmaxRepoSizeMB: 350,\n\t\tcloneTimeoutSeconds: 30,\n\t\tclonePath: \"/tmp/pi-github-repos\",\n\t};\n\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = defaults;\n\t\treturn cachedConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: { githubClone?: { enabled?: unknown; maxRepoSizeMB?: unknown; cloneTimeoutSeconds?: unknown; clonePath?: unknown } };\n\ttry {\n\t\traw = JSON.parse(rawText) as { githubClone?: { enabled?: unknown; maxRepoSizeMB?: unknown; cloneTimeoutSeconds?: unknown; clonePath?: unknown } };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\n\tconst gc = raw.githubClone ?? {};\n\tcachedConfig = {\n\t\tenabled: normalizeEnabled(gc.enabled, defaults.enabled),\n\t\tmaxRepoSizeMB: normalizePositiveNumber(gc.maxRepoSizeMB, defaults.maxRepoSizeMB),\n\t\tcloneTimeoutSeconds: normalizePositiveNumber(gc.cloneTimeoutSeconds, defaults.cloneTimeoutSeconds),\n\t\tclonePath: normalizeClonePath(gc.clonePath, defaults.clonePath),\n\t};\n\treturn cachedConfig;\n}\n\nconst NON_CODE_SEGMENTS = new Set([\n\t\"issues\", \"pull\", \"pulls\", \"discussions\", \"releases\", \"wiki\",\n\t\"actions\", \"settings\", \"security\", \"projects\", \"graphs\",\n\t\"compare\", \"commits\", \"tags\", \"branches\", \"stargazers\",\n\t\"watchers\", \"network\", \"forks\", \"milestone\", \"labels\",\n\t\"packages\", \"codespaces\", \"contribute\", \"community\",\n\t\"sponsors\", \"invitations\", \"notifications\", \"insights\",\n]);\n\nexport function parseGitHubUrl(url: string): GitHubUrlInfo | null {\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(url);\n\t} catch {\n\t\treturn null;\n\t}\n\n\tconst host = parsed.hostname.toLowerCase();\n\tif (host !== \"github.com\" && host !== \"www.github.com\") return null;\n\n\tconst segments = parsed.pathname\n\t\t.split(\"/\")\n\t\t.filter(Boolean)\n\t\t.map((segment) => {\n\t\t\ttry {\n\t\t\t\treturn decodeURIComponent(segment);\n\t\t\t} catch {\n\t\t\t\treturn segment;\n\t\t\t}\n\t\t});\n\tif (segments.length < 2) return null;\n\n\tconst owner = segments[0];\n\tconst repo = segments[1].replace(/\\.git$/, \"\");\n\n\tif (NON_CODE_SEGMENTS.has(segments[2]?.toLowerCase())) return null;\n\n\tif (segments.length === 2) {\n\t\treturn { owner, repo, refIsFullSha: false, type: \"root\" };\n\t}\n\n\tconst action = segments[2];\n\tif (action !== \"blob\" && action !== \"tree\") return null;\n\tif (segments.length < 4) return null;\n\n\tconst ref = segments[3];\n\tconst refIsFullSha = /^[0-9a-f]{40}$/.test(ref);\n\tconst pathParts = segments.slice(4);\n\tconst path = pathParts.length > 0 ? pathParts.join(\"/\") : \"\";\n\n\treturn {\n\t\towner,\n\t\trepo,\n\t\tref,\n\t\trefIsFullSha,\n\t\tpath,\n\t\ttype: action as \"blob\" | \"tree\",\n\t};\n}\n\nfunction cacheKey(owner: string, repo: string, ref?: string): string {\n\treturn ref ? `${owner}/${repo}@${ref}` : `${owner}/${repo}`;\n}\n\nfunction cloneDir(config: GitHubCloneConfig, owner: string, repo: string, ref?: string): string {\n\tconst dirName = ref ? `${repo}@${ref}` : repo;\n\treturn join(config.clonePath, owner, dirName);\n}\n\nfunction execClone(args: string[], localPath: string, timeoutMs: number, signal?: AbortSignal): Promise<string | null> {\n\treturn new Promise((resolve) => {\n\t\tconst child = execFile(args[0], args.slice(1), { timeout: timeoutMs }, (err) => {\n\t\t\tif (err) {\n\t\t\t\ttry {\n\t\t\t\t\trmSync(localPath, { recursive: true, force: true });\n\t\t\t\t} catch {\n\t\t\t\t}\n\t\t\t\tresolve(null);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(localPath);\n\t\t});\n\n\t\tif (signal) {\n\t\t\tconst onAbort = () => child.kill();\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tchild.on(\"exit\", () => signal.removeEventListener(\"abort\", onAbort));\n\t\t}\n\t});\n}\n\nasync function cloneRepo(\n\towner: string,\n\trepo: string,\n\tref: string | undefined,\n\tconfig: GitHubCloneConfig,\n\tsignal?: AbortSignal,\n): Promise<string | null> {\n\tconst localPath = cloneDir(config, owner, repo, ref);\n\n\ttry {\n\t\trmSync(localPath, { recursive: true, force: true });\n\t} catch {\n\t}\n\n\tconst timeoutMs = config.cloneTimeoutSeconds * 1000;\n\tconst hasGh = await checkGhAvailable();\n\n\tif (hasGh) {\n\t\tconst args = [\"gh\", \"repo\", \"clone\", `${owner}/${repo}`, localPath, \"--\", \"--depth\", \"1\", \"--single-branch\"];\n\t\tif (ref) args.push(\"--branch\", ref);\n\t\treturn execClone(args, localPath, timeoutMs, signal);\n\t}\n\n\tshowGhHint();\n\n\tconst gitUrl = `https://github.com/${owner}/${repo}.git`;\n\tconst args = [\"git\", \"clone\", \"--depth\", \"1\", \"--single-branch\"];\n\tif (ref) args.push(\"--branch\", ref);\n\targs.push(gitUrl, localPath);\n\treturn execClone(args, localPath, timeoutMs, signal);\n}\n\nfunction isBinaryFile(filePath: string): boolean {\n\tconst ext = extname(filePath).toLowerCase();\n\tif (BINARY_EXTENSIONS.has(ext)) return true;\n\n\tlet fd: number;\n\ttry {\n\t\tfd = openSync(filePath, \"r\");\n\t} catch {\n\t\treturn false;\n\t}\n\ttry {\n\t\tconst buf = Buffer.alloc(512);\n\t\tconst bytesRead = readSync(fd, buf, 0, 512, 0);\n\t\tfor (let i = 0; i < bytesRead; i++) {\n\t\t\tif (buf[i] === 0) return true;\n\t\t}\n\t} catch {\n\t\treturn false;\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n\n\treturn false;\n}\n\nfunction formatFileSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes} B`;\n\tif (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction resolveWithinRepo(rootPath: string, relativePath: string): string | null {\n\tconst normalizedRoot = resolvePath(rootPath);\n\tconst candidate = resolvePath(normalizedRoot, relativePath);\n\tif (candidate !== normalizedRoot) {\n\t\tconst rootPrefix = normalizedRoot.endsWith(pathSep) ? normalizedRoot : normalizedRoot + pathSep;\n\t\tif (!candidate.startsWith(rootPrefix)) return null;\n\t}\n\n\tif (!existsSync(candidate)) return candidate;\n\n\ttry {\n\t\tconst realRoot = realpathSync(normalizedRoot);\n\t\tconst realCandidate = realpathSync(candidate);\n\t\tif (realCandidate === realRoot) return candidate;\n\t\tconst realRootPrefix = realRoot.endsWith(pathSep) ? realRoot : realRoot + pathSep;\n\t\treturn realCandidate.startsWith(realRootPrefix) ? candidate : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction readTextFile(path: string): string | null {\n\ttry {\n\t\treturn readFileSync(path, \"utf-8\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction buildTree(rootPath: string): string {\n\tconst entries: string[] = [];\n\n\tfunction walk(dir: string, relPath: string): void {\n\t\tif (entries.length >= MAX_TREE_ENTRIES) return;\n\n\t\tlet items: string[];\n\t\ttry {\n\t\t\titems = readdirSync(dir).sort();\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (const item of items) {\n\t\t\tif (entries.length >= MAX_TREE_ENTRIES) return;\n\t\t\tif (item === \".git\") continue;\n\n\t\t\tconst rel = relPath ? `${relPath}/${item}` : item;\n\t\t\tconst safePath = resolveWithinRepo(rootPath, rel);\n\t\t\tif (!safePath) {\n\t\t\t\tentries.push(`${rel} [outside repo skipped]`);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet stat;\n\t\t\ttry {\n\t\t\t\tstat = statSync(safePath);\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (stat.isDirectory()) {\n\t\t\t\tif (NOISE_DIRS.has(item)) {\n\t\t\t\t\tentries.push(`${rel}/ [skipped]`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tentries.push(`${rel}/`);\n\t\t\t\twalk(safePath, rel);\n\t\t\t} else {\n\t\t\t\tentries.push(rel);\n\t\t\t}\n\t\t}\n\t}\n\n\twalk(rootPath, \"\");\n\n\tif (entries.length >= MAX_TREE_ENTRIES) {\n\t\tentries.push(`... (truncated at ${MAX_TREE_ENTRIES} entries)`);\n\t}\n\n\treturn entries.join(\"\\n\");\n}\n\nfunction buildDirListing(rootPath: string, subPath: string): string {\n\tconst targetPath = resolveWithinRepo(rootPath, subPath);\n\tif (!targetPath) return \"(path escapes repository root)\";\n\tconst lines: string[] = [];\n\n\tlet items: string[];\n\ttry {\n\t\titems = readdirSync(targetPath).sort();\n\t} catch {\n\t\treturn \"(directory not readable)\";\n\t}\n\n\tfor (const item of items) {\n\t\tif (item === \".git\") continue;\n\t\tconst rel = subPath ? `${subPath}/${item}` : item;\n\t\tconst safePath = resolveWithinRepo(rootPath, rel);\n\t\tif (!safePath) {\n\t\t\tlines.push(` ${item} (outside repo)`);\n\t\t\tcontinue;\n\t\t}\n\t\ttry {\n\t\t\tconst stat = statSync(safePath);\n\t\t\tif (stat.isDirectory()) {\n\t\t\t\tlines.push(` ${item}/`);\n\t\t\t} else {\n\t\t\t\tlines.push(` ${item} (${formatFileSize(stat.size)})`);\n\t\t\t}\n\t\t} catch {\n\t\t\tlines.push(` ${item} (unreadable)`);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction readReadme(localPath: string): string | null {\n\tconst candidates = [\"README.md\", \"readme.md\", \"README\", \"README.txt\", \"README.rst\"];\n\tfor (const name of candidates) {\n\t\tconst readmePath = join(localPath, name);\n\t\tif (existsSync(readmePath)) {\n\t\t\ttry {\n\t\t\t\tconst content = readFileSync(readmePath, \"utf-8\");\n\t\t\t\treturn content.length > 8192 ? content.slice(0, 8192) + \"\\n\\n[README truncated at 8K chars]\" : content;\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction generateContent(localPath: string, info: GitHubUrlInfo): string {\n\tconst lines: string[] = [];\n\tlines.push(`Repository cloned to: ${localPath}`);\n\tlines.push(\"\");\n\n\tif (info.type === \"root\") {\n\t\tlines.push(\"## Structure\");\n\t\tlines.push(buildTree(localPath));\n\t\tlines.push(\"\");\n\n\t\tconst readme = readReadme(localPath);\n\t\tif (readme) {\n\t\t\tlines.push(\"## README.md\");\n\t\t\tlines.push(readme);\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tif (info.type === \"tree\") {\n\t\tconst dirPath = info.path || \"\";\n\t\tconst fullDirPath = resolveWithinRepo(localPath, dirPath);\n\n\t\tif (!fullDirPath || !existsSync(fullDirPath)) {\n\t\t\tlines.push(`Path \\`${dirPath}\\` not found in clone. Showing repository root instead.`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"## Structure\");\n\t\t\tlines.push(buildTree(localPath));\n\t\t} else {\n\t\t\tlines.push(`## ${dirPath || \"/\"}`);\n\t\t\tlines.push(buildDirListing(localPath, dirPath));\n\t\t}\n\n\t\tlines.push(\"\");\n\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tif (info.type === \"blob\") {\n\t\tconst filePath = info.path || \"\";\n\t\tconst fullFilePath = resolveWithinRepo(localPath, filePath);\n\n\t\tif (!fullFilePath || !existsSync(fullFilePath)) {\n\t\t\tlines.push(`Path \\`${filePath}\\` not found in clone. Showing repository root instead.`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"## Structure\");\n\t\t\tlines.push(buildTree(localPath));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tlet stat: ReturnType<typeof statSync>;\n\t\ttry {\n\t\t\tstat = statSync(fullFilePath);\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tlines.push(`Could not inspect \\`${filePath}\\`: ${message}`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tif (stat.isDirectory()) {\n\t\t\tlines.push(`## ${filePath || \"/\"}`);\n\t\t\tlines.push(buildDirListing(localPath, filePath));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tif (isBinaryFile(fullFilePath)) {\n\t\t\tconst ext = extname(filePath).replace(\".\", \"\");\n\t\t\tlines.push(`## ${filePath}`);\n\t\t\tlines.push(`Binary file (${ext}, ${formatFileSize(stat.size)}). Use \\`read\\` or \\`bash\\` tools at the path above to inspect.`);\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\n\t\tconst content = readTextFile(fullFilePath);\n\t\tif (content === null) {\n\t\t\tlines.push(`Could not read \\`${filePath}\\` as UTF-8 text.`);\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\t\tlines.push(`## ${filePath}`);\n\n\t\tif (content.length > MAX_INLINE_FILE_CHARS) {\n\t\t\tlines.push(content.slice(0, MAX_INLINE_FILE_CHARS));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(`[File truncated at 100K chars. Full file: ${fullFilePath}]`);\n\t\t} else {\n\t\t\tlines.push(content);\n\t\t}\n\n\t\tlines.push(\"\");\n\t\tlines.push(\"Use `read` and `bash` tools at the path above to explore further.\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nasync function awaitCachedClone(\n\tcached: CachedClone,\n\turl: string,\n\towner: string,\n\trepo: string,\n\tinfo: GitHubUrlInfo,\n\tsignal?: AbortSignal,\n): Promise<ExtractedContent | null> {\n\tif (signal?.aborted) return null;\n\tconst result = await cached.clonePromise;\n\tif (signal?.aborted) return null;\n\tif (result) {\n\t\tconst content = generateContent(result, info);\n\t\tconst title = info.path ? `${owner}/${repo} - ${info.path}` : `${owner}/${repo}`;\n\t\treturn { url, title, content, error: null };\n\t}\n\treturn fetchViaApi(url, owner, repo, info);\n}\n\nexport async function extractGitHub(\n\turl: string,\n\tsignal?: AbortSignal,\n\tforceClone?: boolean,\n): Promise<ExtractedContent | null> {\n\tconst info = parseGitHubUrl(url);\n\tif (!info) return null;\n\n\tif (signal?.aborted) return null;\n\n\tconst config = loadGitHubConfig();\n\tif (!config.enabled) return null;\n\n\tconst { owner, repo } = info;\n\tconst key = cacheKey(owner, repo, info.ref);\n\n\tconst cached = cloneCache.get(key);\n\tif (cached) return awaitCachedClone(cached, url, owner, repo, info, signal);\n\n\tif (info.refIsFullSha) {\n\t\tif (signal?.aborted) return null;\n\t\tconst sizeNote = `Note: Commit SHA URLs use the GitHub API instead of cloning.`;\n\t\treturn fetchViaApi(url, owner, repo, info, sizeNote);\n\t}\n\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: `github.com/${owner}/${repo}` });\n\n\tif (!forceClone) {\n\t\tconst sizeKB = await checkRepoSize(owner, repo);\n\t\tif (signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\treturn null;\n\t\t}\n\t\tif (sizeKB !== null) {\n\t\t\tconst sizeMB = sizeKB / 1024;\n\t\t\tif (sizeMB > config.maxRepoSizeMB) {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst sizeNote =\n\t\t\t\t\t`Note: Repository is ${Math.round(sizeMB)}MB (threshold: ${config.maxRepoSizeMB}MB). ` +\n\t\t\t\t\t`Showing API-fetched content instead of full clone. Ask the user if they'd like to clone the full repo -- ` +\n\t\t\t\t\t`if yes, call fetch_content again with the same URL and add forceClone: true to the params.`;\n\t\t\t\tconst apiView = await fetchViaApi(url, owner, repo, info, sizeNote);\n\t\t\t\tif (apiView) {\n\t\t\t\t\tactivityMonitor.logComplete(activityId, 200);\n\t\t\t\t\treturn apiView;\n\t\t\t\t}\n\t\t\t\tactivityMonitor.logError(activityId, \"api fallback unavailable for oversized repository\");\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (signal?.aborted) {\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\t// Re-check: another concurrent caller may have started a clone while we awaited the size check\n\tconst cachedAfterSizeCheck = cloneCache.get(key);\n\tif (cachedAfterSizeCheck) {\n\t\tconst cachedResult = await awaitCachedClone(cachedAfterSizeCheck, url, owner, repo, info, signal);\n\t\tif (signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else if (cachedResult) {\n\t\t\tactivityMonitor.logComplete(activityId, 200);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, \"clone failed\");\n\t\t}\n\t\treturn cachedResult;\n\t}\n\n\tconst clonePromise = cloneRepo(owner, repo, info.ref, config, signal);\n\tconst localPath = cloneDir(config, owner, repo, info.ref);\n\tcloneCache.set(key, { localPath, clonePromise });\n\n\tconst result = await clonePromise;\n\tif (signal?.aborted) {\n\t\tif (!result) cloneCache.delete(key);\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\tif (!result) {\n\t\tcloneCache.delete(key);\n\t\tif (signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst apiFallback = await fetchViaApi(url, owner, repo, info);\n\t\tif (apiFallback) {\n\t\t\tactivityMonitor.logComplete(activityId, 200);\n\t\t\treturn apiFallback;\n\t\t}\n\n\t\tactivityMonitor.logError(activityId, \"clone and API fallback failed\");\n\t\treturn null;\n\t}\n\n\tactivityMonitor.logComplete(activityId, 200);\n\tconst content = generateContent(result, info);\n\tconst title = info.path ? `${owner}/${repo} - ${info.path}` : `${owner}/${repo}`;\n\treturn { url, title, content, error: null };\n}\n\nexport function clearCloneCache(): void {\n\tfor (const entry of cloneCache.values()) {\n\t\ttry {\n\t\t\trmSync(entry.localPath, { recursive: true, force: true });\n\t\t} catch {\n\t\t}\n\t}\n\tcloneCache.clear();\n\tcachedConfig = null;\n}\n\nregisterSessionCleanup(clearCloneCache);\n"]}
|
|
@@ -4,6 +4,7 @@ import { execFile } from "node:child_process";
|
|
|
4
4
|
import { extname, join, resolve as resolvePath, sep as pathSep } from "node:path";
|
|
5
5
|
import { activityMonitor } from "./activity.js";
|
|
6
6
|
import { checkGhAvailable, checkRepoSize, fetchViaApi, showGhHint } from "./github-api.js";
|
|
7
|
+
import { registerSessionCleanup } from "./session-cleanup.js";
|
|
7
8
|
import { getWebSearchConfigPath } from "./utils.js";
|
|
8
9
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
9
10
|
const BINARY_EXTENSIONS = new Set([
|
|
@@ -555,4 +556,5 @@ export function clearCloneCache() {
|
|
|
555
556
|
cloneCache.clear();
|
|
556
557
|
cachedConfig = null;
|
|
557
558
|
}
|
|
559
|
+
registerSessionCleanup(clearCloneCache);
|
|
558
560
|
//# sourceMappingURL=github-extract.js.map
|