@herbertgao/pi-extensions 2026.10.2 → 2026.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -12
- package/THIRD_PARTY_NOTICES.md +5 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +1 -1
- package/node_modules/@herbertgao/pi-subagents/package.json +4 -5
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +48 -72
- package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/workflow/collisions.ts +4 -1
- package/node_modules/@herbertgao/pi-subagents/src/workflow/json-schema.ts +9 -8
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +6 -1
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +1 -1
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +13 -1
- package/node_modules/pi-lens/CHANGELOG.md +95 -0
- package/node_modules/pi-lens/README.md +1 -0
- package/node_modules/pi-lens/dist/chunk-2FQ62SPM.js +83 -0
- package/node_modules/pi-lens/dist/{chunk-6P62QVEA.js → chunk-2UMYMRI3.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-O6TQT6RI.js → chunk-34OCDWOS.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-XK4CIILI.js → chunk-3FERWMCH.js} +26 -26
- package/node_modules/pi-lens/dist/{chunk-ZAWW3AF6.js → chunk-3JL6IFD7.js} +8 -8
- package/node_modules/pi-lens/dist/{chunk-OAXNDZJ7.js → chunk-3JSEWUWI.js} +8 -4
- package/node_modules/pi-lens/dist/{chunk-RPD5K5NC.js → chunk-3MRXIJ6I.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-IVP5KAB7.js → chunk-3P4PF2BX.js} +14 -14
- package/node_modules/pi-lens/dist/{chunk-5THXD6X5.js → chunk-3PNFWVHT.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-VD4JQCQC.js → chunk-3TTPKMEF.js} +37 -37
- package/node_modules/pi-lens/dist/{chunk-7F72PXY2.js → chunk-5JHXUETW.js} +3 -3
- package/node_modules/pi-lens/dist/{chunk-5ZEJHV35.js → chunk-64LU2OVH.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-ODEEOXPH.js → chunk-6B5I6ICN.js} +11 -11
- package/node_modules/pi-lens/dist/{chunk-5RXBNSW2.js → chunk-6P54TMWL.js} +16 -8
- package/node_modules/pi-lens/dist/{chunk-K6VYLB2U.js → chunk-6UYA642J.js} +12 -12
- package/node_modules/pi-lens/dist/{chunk-V7R425XG.js → chunk-6XX5HCLK.js} +4 -4
- package/node_modules/pi-lens/dist/{chunk-D6QS3U6M.js → chunk-7AXO3ZDF.js} +15 -15
- package/node_modules/pi-lens/dist/{chunk-YGVYKNSN.js → chunk-7BX5DWSA.js} +59 -12
- package/node_modules/pi-lens/dist/{chunk-N3YQJI6O.js → chunk-7ELY2CSM.js} +7 -4
- package/node_modules/pi-lens/dist/{chunk-AWE5TMPO.js → chunk-7M4T5N3V.js} +13 -13
- package/node_modules/pi-lens/dist/{chunk-AJ3QXTRQ.js → chunk-AFZYMAIV.js} +33 -33
- package/node_modules/pi-lens/dist/{chunk-AJXODZY6.js → chunk-ALQRYH6O.js} +12 -12
- package/node_modules/pi-lens/dist/{chunk-AJVLWNFN.js → chunk-AXKPQ6ZI.js} +5 -5
- package/node_modules/pi-lens/dist/{chunk-HWBZIGMJ.js → chunk-BHWHXMQ2.js} +8 -8
- package/node_modules/pi-lens/dist/{chunk-IXEBJSUE.js → chunk-BJF25SUG.js} +10 -10
- package/node_modules/pi-lens/dist/{chunk-GVXA6CCE.js → chunk-BNKLYOOO.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-T4ERT73Y.js → chunk-CBD2B5CP.js} +20 -20
- package/node_modules/pi-lens/dist/{chunk-O5WILGMI.js → chunk-CFYISHUW.js} +5 -5
- package/node_modules/pi-lens/dist/{chunk-EOKRQ3SA.js → chunk-CMU4RMJB.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-YSNFMJKN.js → chunk-CULQYFLU.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-ORTMY4Q4.js → chunk-D65GOCJB.js} +21 -14
- package/node_modules/pi-lens/dist/{chunk-NTUJEGDZ.js → chunk-D6YF5HKX.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-BRJKOBZP.js → chunk-DMS52BEP.js} +17 -6
- package/node_modules/pi-lens/dist/{chunk-NL5GLMFI.js → chunk-ELVWJYRV.js} +11 -11
- package/node_modules/pi-lens/dist/{chunk-4W2PLC4J.js → chunk-EUT3B22Y.js} +12 -12
- package/node_modules/pi-lens/dist/{chunk-GUOGVTCQ.js → chunk-F5VLZHUW.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-I7SPGOWI.js → chunk-FGALODG3.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-Q5U6FMDI.js → chunk-G3RLJYA2.js} +12 -6
- package/node_modules/pi-lens/dist/{chunk-FJNKANOS.js → chunk-GAYGGICG.js} +9 -9
- package/node_modules/pi-lens/dist/{chunk-HVUZXSWK.js → chunk-H2W2AXD3.js} +38 -40
- package/node_modules/pi-lens/dist/{chunk-VHQLPIRJ.js → chunk-H5EV3VMF.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-27BBHSZC.js → chunk-HREZQCT3.js} +3 -3
- package/node_modules/pi-lens/dist/{chunk-TT22N74J.js → chunk-HSIRMK2D.js} +12 -12
- package/node_modules/pi-lens/dist/{chunk-3I5QFT3N.js → chunk-IMSQR4WO.js} +6 -3
- package/node_modules/pi-lens/dist/{chunk-B3I2UUT4.js → chunk-IQZXOZ7S.js} +156 -32
- package/node_modules/pi-lens/dist/{chunk-RIISMHKN.js → chunk-IR6XFA5P.js} +14 -13
- package/node_modules/pi-lens/dist/{chunk-LNAKEO63.js → chunk-IUKPRZER.js} +4 -4
- package/node_modules/pi-lens/dist/{chunk-RUOG5D2C.js → chunk-IUR76K6A.js} +5 -5
- package/node_modules/pi-lens/dist/{chunk-TO6WL3GM.js → chunk-J3FZR3YY.js} +12 -2
- package/node_modules/pi-lens/dist/{chunk-Y4T27COV.js → chunk-J6DFZDFA.js} +4 -4
- package/node_modules/pi-lens/dist/{chunk-E4MAEDPM.js → chunk-J7WJZ6W7.js} +11 -11
- package/node_modules/pi-lens/dist/{chunk-52H5B5TC.js → chunk-JONGANYH.js} +30 -30
- package/node_modules/pi-lens/dist/{chunk-TGPXLY2J.js → chunk-KJCWXUUS.js} +35 -1
- package/node_modules/pi-lens/dist/{chunk-I3R7GKDR.js → chunk-KJN4IOEJ.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-RPD4ZSB7.js → chunk-KMGPUKFD.js} +12 -12
- package/node_modules/pi-lens/dist/{chunk-OQGFCZJ4.js → chunk-LD7WFTRX.js} +13 -13
- package/node_modules/pi-lens/dist/{chunk-DIULIWKU.js → chunk-LNOMA3MP.js} +3 -3
- package/node_modules/pi-lens/dist/{chunk-HHYAELLJ.js → chunk-MGIWSROG.js} +5 -5
- package/node_modules/pi-lens/dist/{chunk-YLF3PPT5.js → chunk-MQA2YBAH.js} +3 -3
- package/node_modules/pi-lens/dist/{chunk-TVN5N6GY.js → chunk-MTDRLSI7.js} +690 -186
- package/node_modules/pi-lens/dist/{chunk-DQ2NZ7C4.js → chunk-N2IRDBZI.js} +44 -6
- package/node_modules/pi-lens/dist/{chunk-AHDW3GPE.js → chunk-N77WFUL7.js} +22 -22
- package/node_modules/pi-lens/dist/{chunk-NXPS2L6U.js → chunk-N7YNJE5R.js} +7 -7
- package/node_modules/pi-lens/dist/{chunk-7FBF7NPR.js → chunk-NFXP47RV.js} +13 -12
- package/node_modules/pi-lens/dist/{chunk-UY752QSN.js → chunk-NJF2X5XF.js} +17 -17
- package/node_modules/pi-lens/dist/{chunk-WONERJTP.js → chunk-O7HZX7CA.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-AS44CFQ4.js → chunk-OC7LYAU5.js} +7 -7
- package/node_modules/pi-lens/dist/{chunk-4RERDEXR.js → chunk-OEX24ECI.js} +30 -30
- package/node_modules/pi-lens/dist/{chunk-6VCD4VKZ.js → chunk-OYOB3ZFG.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-IIXOAGHR.js → chunk-OYTMZXRF.js} +18 -15
- package/node_modules/pi-lens/dist/{chunk-ARFCXG7S.js → chunk-OZJINN4O.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-34U7PLGE.js → chunk-PCHDGUKT.js} +11 -11
- package/node_modules/pi-lens/dist/{chunk-VCSRSYLF.js → chunk-RJFED73B.js} +34 -36
- package/node_modules/pi-lens/dist/chunk-RTQNRM5X.js +27 -0
- package/node_modules/pi-lens/dist/{chunk-PAK6IDME.js → chunk-S6AMWKPC.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-U3VGR4VU.js → chunk-T4QZ6FEH.js} +4 -4
- package/node_modules/pi-lens/dist/{chunk-VN2AXLEX.js → chunk-T54VJRAD.js} +54 -12
- package/node_modules/pi-lens/dist/{chunk-ZMOL6RQC.js → chunk-TA5L5KWQ.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-JOLITGCC.js → chunk-TQFBZVCG.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-VDBPVIKD.js → chunk-U52SAGRM.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-LW6XKDZQ.js → chunk-UEAHAMEI.js} +284 -35
- package/node_modules/pi-lens/dist/{chunk-NMABWBZN.js → chunk-UGSMCQ4D.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-6QIUX4EP.js → chunk-USHG4DLH.js} +7 -7
- package/node_modules/pi-lens/dist/{chunk-LSMJ6KBE.js → chunk-V5MEBST3.js} +16 -16
- package/node_modules/pi-lens/dist/chunk-VGEVL5EQ.js +29 -0
- package/node_modules/pi-lens/dist/{chunk-47D2BO4S.js → chunk-VPWKF3GN.js} +10 -10
- package/node_modules/pi-lens/dist/{chunk-UK3CMEAL.js → chunk-VSMJK3ZJ.js} +1 -1
- package/node_modules/pi-lens/dist/{chunk-22AVIGXS.js → chunk-VYKKMYVT.js} +7 -7
- package/node_modules/pi-lens/dist/{chunk-OS32RNR2.js → chunk-VZHJS6DR.js} +15 -15
- package/node_modules/pi-lens/dist/{chunk-E2WFBRHM.js → chunk-WATC3IQR.js} +3 -3
- package/node_modules/pi-lens/dist/chunk-WPE5FPGR.js +54 -0
- package/node_modules/pi-lens/dist/{chunk-V5PXFSZK.js → chunk-WSEWKCXU.js} +4 -4
- package/node_modules/pi-lens/dist/{chunk-NKOUFPQF.js → chunk-WW5BLB74.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-PWFHBTSF.js → chunk-XGUL4SLQ.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-F3P7VQ7H.js → chunk-XHSCVSOL.js} +3 -3
- package/node_modules/pi-lens/dist/{chunk-DFVMIKMQ.js → chunk-YLN4K6WD.js} +45 -9
- package/node_modules/pi-lens/dist/{chunk-VNMT7C64.js → chunk-Z6BB4JLQ.js} +2 -2
- package/node_modules/pi-lens/dist/{chunk-NTHH2J74.js → chunk-Z7QQA2KT.js} +12 -12
- package/node_modules/pi-lens/dist/{chunk-F3AU2TMM.js → chunk-ZETYB3XG.js} +15 -15
- package/node_modules/pi-lens/dist/{chunk-XECLWQZN.js → chunk-ZMOMPX2Z.js} +12 -10
- package/node_modules/pi-lens/dist/{chunk-C2AUYXQH.js → chunk-ZOEYVXSV.js} +4 -4
- package/node_modules/pi-lens/dist/{chunk-TLGFBM3F.js → chunk-ZZUFZUQZ.js} +68 -64
- package/node_modules/pi-lens/dist/index.js +30113 -28398
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +8 -8
- package/node_modules/pi-lens/dist/mcp/cli.js +37 -37
- package/node_modules/pi-lens/dist/mcp/server.js +257 -158
- package/node_modules/pi-lens/dist/mcp/worker.js +55 -55
- package/node_modules/pi-lens/dist/probes/installer.js +19 -19
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +2 -2
- package/node_modules/pi-lens/docs/configuration.md +9 -2
- package/node_modules/pi-lens/docs/custom-rules.md +25 -2
- package/node_modules/pi-lens/docs/environment-variables.md +34 -6
- package/node_modules/pi-lens/docs/features.md +37 -1
- package/node_modules/pi-lens/docs/globalconfig.md +1 -1
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -1
- package/node_modules/pi-lens/docs/lsp-idle-eviction.md +1 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +9 -5
- package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +43 -1
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +3 -1
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +4 -3
- package/node_modules/pi-lens/docs/schema/pi-lens-config-v1.json +530 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
- package/node_modules/pi-lens/docs/word-index.md +6 -0
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-identity-operator-on-literals-test.yml +12 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-js-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/throw-new-error-test.yml +2 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-identity-operator-on-literals.yml +20 -12
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error-js.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/throw-new-error.yml +2 -2
- package/node_modules/pi-provider-qoder/README.md +140 -0
- package/node_modules/pi-provider-qoder/dist/index.js +10249 -0
- package/node_modules/pi-provider-qoder/package.json +57 -0
- package/node_modules/pi-web-access/CHANGELOG.md +28 -0
- package/node_modules/pi-web-access/README.md +10 -6
- package/node_modules/pi-web-access/ceramic.ts +219 -0
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +846 -588
- package/node_modules/pi-web-access/dist/mcp-cli.js +725 -516
- package/node_modules/pi-web-access/gemini-search.ts +8 -4
- package/node_modules/pi-web-access/github-extract.ts +34 -2
- package/node_modules/pi-web-access/index.ts +72 -30
- package/node_modules/pi-web-access/openai-search.ts +3 -3
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +6 -9
- package/node_modules/pi-web-access/parallel-mcp.ts +2 -0
- package/node_modules/pi-web-access/query-rewrite.ts +2 -2
- package/node_modules/pi-web-access/storage.ts +49 -0
- package/node_modules/pi-web-access/summary-review.ts +7 -6
- package/node_modules/pi-web-access/utils.ts +21 -6
- package/node_modules/pi-web-access/web-tool-core.ts +1 -0
- package/package.json +10 -12
- package/node_modules/pi-lens/dist/chunk-54B42YRX.js +0 -27
- package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +0 -29
- package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +0 -83
- package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +0 -54
- package/node_modules/pi-web-access/opencode-session-headers.ts +0 -24
- package/node_modules/remote-pi/LICENSE +0 -21
- package/node_modules/remote-pi/README.md +0 -675
- package/node_modules/remote-pi/dist/actions/handlers.d.ts +0 -122
- package/node_modules/remote-pi/dist/actions/handlers.js +0 -160
- package/node_modules/remote-pi/dist/actions/handlers.js.map +0 -1
- package/node_modules/remote-pi/dist/actions/registry.d.ts +0 -39
- package/node_modules/remote-pi/dist/actions/registry.js +0 -58
- package/node_modules/remote-pi/dist/actions/registry.js.map +0 -1
- package/node_modules/remote-pi/dist/bin/supervisord.d.ts +0 -2
- package/node_modules/remote-pi/dist/bin/supervisord.js +0 -86
- package/node_modules/remote-pi/dist/bin/supervisord.js.map +0 -1
- package/node_modules/remote-pi/dist/commands/builtin_mirror.d.ts +0 -58
- package/node_modules/remote-pi/dist/commands/builtin_mirror.js +0 -71
- package/node_modules/remote-pi/dist/commands/builtin_mirror.js.map +0 -1
- package/node_modules/remote-pi/dist/commands/list_commands.d.ts +0 -60
- package/node_modules/remote-pi/dist/commands/list_commands.js +0 -73
- package/node_modules/remote-pi/dist/commands/list_commands.js.map +0 -1
- package/node_modules/remote-pi/dist/config.d.ts +0 -62
- package/node_modules/remote-pi/dist/config.js +0 -115
- package/node_modules/remote-pi/dist/config.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/client.d.ts +0 -20
- package/node_modules/remote-pi/dist/daemon/client.js +0 -131
- package/node_modules/remote-pi/dist/daemon/client.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/control_protocol.d.ts +0 -176
- package/node_modules/remote-pi/dist/daemon/control_protocol.js +0 -63
- package/node_modules/remote-pi/dist/daemon/control_protocol.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/cron_log.d.ts +0 -45
- package/node_modules/remote-pi/dist/daemon/cron_log.js +0 -72
- package/node_modules/remote-pi/dist/daemon/cron_log.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/cron_registry.d.ts +0 -80
- package/node_modules/remote-pi/dist/daemon/cron_registry.js +0 -194
- package/node_modules/remote-pi/dist/daemon/cron_registry.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/id.d.ts +0 -24
- package/node_modules/remote-pi/dist/daemon/id.js +0 -36
- package/node_modules/remote-pi/dist/daemon/id.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/install.d.ts +0 -170
- package/node_modules/remote-pi/dist/daemon/install.js +0 -547
- package/node_modules/remote-pi/dist/daemon/install.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/registry.d.ts +0 -63
- package/node_modules/remote-pi/dist/daemon/registry.js +0 -153
- package/node_modules/remote-pi/dist/daemon/registry.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/rpc_child.d.ts +0 -194
- package/node_modules/remote-pi/dist/daemon/rpc_child.js +0 -389
- package/node_modules/remote-pi/dist/daemon/rpc_child.js.map +0 -1
- package/node_modules/remote-pi/dist/daemon/supervisor.d.ts +0 -93
- package/node_modules/remote-pi/dist/daemon/supervisor.js +0 -596
- package/node_modules/remote-pi/dist/daemon/supervisor.js.map +0 -1
- package/node_modules/remote-pi/dist/extension_ui_bridge.d.ts +0 -26
- package/node_modules/remote-pi/dist/extension_ui_bridge.js +0 -341
- package/node_modules/remote-pi/dist/extension_ui_bridge.js.map +0 -1
- package/node_modules/remote-pi/dist/index.d.ts +0 -213
- package/node_modules/remote-pi/dist/index.js +0 -4996
- package/node_modules/remote-pi/dist/index.js.map +0 -1
- package/node_modules/remote-pi/dist/mcp/mesh_result.d.ts +0 -10
- package/node_modules/remote-pi/dist/mcp/mesh_result.js +0 -42
- package/node_modules/remote-pi/dist/mcp/mesh_result.js.map +0 -1
- package/node_modules/remote-pi/dist/mcp/mesh_server.d.ts +0 -16
- package/node_modules/remote-pi/dist/mcp/mesh_server.js +0 -327
- package/node_modules/remote-pi/dist/mcp/mesh_server.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/canonical.d.ts +0 -30
- package/node_modules/remote-pi/dist/mesh/canonical.js +0 -61
- package/node_modules/remote-pi/dist/mesh/canonical.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/client.d.ts +0 -18
- package/node_modules/remote-pi/dist/mesh/client.js +0 -125
- package/node_modules/remote-pi/dist/mesh/client.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/encoding.d.ts +0 -38
- package/node_modules/remote-pi/dist/mesh/encoding.js +0 -216
- package/node_modules/remote-pi/dist/mesh/encoding.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/self_revoke.d.ts +0 -73
- package/node_modules/remote-pi/dist/mesh/self_revoke.js +0 -401
- package/node_modules/remote-pi/dist/mesh/self_revoke.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/siblings.d.ts +0 -49
- package/node_modules/remote-pi/dist/mesh/siblings.js +0 -159
- package/node_modules/remote-pi/dist/mesh/siblings.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/types.d.ts +0 -34
- package/node_modules/remote-pi/dist/mesh/types.js +0 -11
- package/node_modules/remote-pi/dist/mesh/types.js.map +0 -1
- package/node_modules/remote-pi/dist/mesh/verify.d.ts +0 -17
- package/node_modules/remote-pi/dist/mesh/verify.js +0 -86
- package/node_modules/remote-pi/dist/mesh/verify.js.map +0 -1
- package/node_modules/remote-pi/dist/pairing/crypto.d.ts +0 -8
- package/node_modules/remote-pi/dist/pairing/crypto.js +0 -22
- package/node_modules/remote-pi/dist/pairing/crypto.js.map +0 -1
- package/node_modules/remote-pi/dist/pairing/handshake.d.ts +0 -28
- package/node_modules/remote-pi/dist/pairing/handshake.js +0 -113
- package/node_modules/remote-pi/dist/pairing/handshake.js.map +0 -1
- package/node_modules/remote-pi/dist/pairing/noise-sha256.d.ts +0 -16
- package/node_modules/remote-pi/dist/pairing/noise-sha256.js +0 -103
- package/node_modules/remote-pi/dist/pairing/noise-sha256.js.map +0 -1
- package/node_modules/remote-pi/dist/pairing/qr.d.ts +0 -59
- package/node_modules/remote-pi/dist/pairing/qr.js +0 -125
- package/node_modules/remote-pi/dist/pairing/qr.js.map +0 -1
- package/node_modules/remote-pi/dist/pairing/storage.d.ts +0 -120
- package/node_modules/remote-pi/dist/pairing/storage.js +0 -604
- package/node_modules/remote-pi/dist/pairing/storage.js.map +0 -1
- package/node_modules/remote-pi/dist/protocol/codec.d.ts +0 -7
- package/node_modules/remote-pi/dist/protocol/codec.js +0 -49
- package/node_modules/remote-pi/dist/protocol/codec.js.map +0 -1
- package/node_modules/remote-pi/dist/protocol/types.d.ts +0 -393
- package/node_modules/remote-pi/dist/protocol/types.js +0 -2
- package/node_modules/remote-pi/dist/protocol/types.js.map +0 -1
- package/node_modules/remote-pi/dist/rooms.d.ts +0 -29
- package/node_modules/remote-pi/dist/rooms.js +0 -57
- package/node_modules/remote-pi/dist/rooms.js.map +0 -1
- package/node_modules/remote-pi/dist/session/address.d.ts +0 -49
- package/node_modules/remote-pi/dist/session/address.js +0 -58
- package/node_modules/remote-pi/dist/session/address.js.map +0 -1
- package/node_modules/remote-pi/dist/session/agent_bridge.d.ts +0 -55
- package/node_modules/remote-pi/dist/session/agent_bridge.js +0 -146
- package/node_modules/remote-pi/dist/session/agent_bridge.js.map +0 -1
- package/node_modules/remote-pi/dist/session/bridge.d.ts +0 -37
- package/node_modules/remote-pi/dist/session/bridge.js +0 -130
- package/node_modules/remote-pi/dist/session/bridge.js.map +0 -1
- package/node_modules/remote-pi/dist/session/broker.d.ts +0 -189
- package/node_modules/remote-pi/dist/session/broker.js +0 -507
- package/node_modules/remote-pi/dist/session/broker.js.map +0 -1
- package/node_modules/remote-pi/dist/session/broker_remote.d.ts +0 -74
- package/node_modules/remote-pi/dist/session/broker_remote.js +0 -583
- package/node_modules/remote-pi/dist/session/broker_remote.js.map +0 -1
- package/node_modules/remote-pi/dist/session/cwd_lock.d.ts +0 -33
- package/node_modules/remote-pi/dist/session/cwd_lock.js +0 -126
- package/node_modules/remote-pi/dist/session/cwd_lock.js.map +0 -1
- package/node_modules/remote-pi/dist/session/envelope.d.ts +0 -33
- package/node_modules/remote-pi/dist/session/envelope.js +0 -117
- package/node_modules/remote-pi/dist/session/envelope.js.map +0 -1
- package/node_modules/remote-pi/dist/session/global_config.d.ts +0 -33
- package/node_modules/remote-pi/dist/session/global_config.js +0 -73
- package/node_modules/remote-pi/dist/session/global_config.js.map +0 -1
- package/node_modules/remote-pi/dist/session/ipc.d.ts +0 -27
- package/node_modules/remote-pi/dist/session/ipc.js +0 -22
- package/node_modules/remote-pi/dist/session/ipc.js.map +0 -1
- package/node_modules/remote-pi/dist/session/leader_election.d.ts +0 -32
- package/node_modules/remote-pi/dist/session/leader_election.js +0 -106
- package/node_modules/remote-pi/dist/session/leader_election.js.map +0 -1
- package/node_modules/remote-pi/dist/session/local_config.d.ts +0 -55
- package/node_modules/remote-pi/dist/session/local_config.js +0 -157
- package/node_modules/remote-pi/dist/session/local_config.js.map +0 -1
- package/node_modules/remote-pi/dist/session/mesh_message.d.ts +0 -20
- package/node_modules/remote-pi/dist/session/mesh_message.js +0 -28
- package/node_modules/remote-pi/dist/session/mesh_message.js.map +0 -1
- package/node_modules/remote-pi/dist/session/mesh_node.d.ts +0 -116
- package/node_modules/remote-pi/dist/session/mesh_node.js +0 -573
- package/node_modules/remote-pi/dist/session/mesh_node.js.map +0 -1
- package/node_modules/remote-pi/dist/session/peer.d.ts +0 -139
- package/node_modules/remote-pi/dist/session/peer.js +0 -451
- package/node_modules/remote-pi/dist/session/peer.js.map +0 -1
- package/node_modules/remote-pi/dist/session/peer_inventory.d.ts +0 -13
- package/node_modules/remote-pi/dist/session/peer_inventory.js +0 -48
- package/node_modules/remote-pi/dist/session/peer_inventory.js.map +0 -1
- package/node_modules/remote-pi/dist/session/peer_limits.d.ts +0 -9
- package/node_modules/remote-pi/dist/session/peer_limits.js +0 -21
- package/node_modules/remote-pi/dist/session/peer_limits.js.map +0 -1
- package/node_modules/remote-pi/dist/session/setup_wizard.d.ts +0 -39
- package/node_modules/remote-pi/dist/session/setup_wizard.js +0 -63
- package/node_modules/remote-pi/dist/session/setup_wizard.js.map +0 -1
- package/node_modules/remote-pi/dist/session/tool_gate.d.ts +0 -5
- package/node_modules/remote-pi/dist/session/tool_gate.js +0 -11
- package/node_modules/remote-pi/dist/session/tool_gate.js.map +0 -1
- package/node_modules/remote-pi/dist/session/tools.d.ts +0 -24
- package/node_modules/remote-pi/dist/session/tools.js +0 -252
- package/node_modules/remote-pi/dist/session/tools.js.map +0 -1
- package/node_modules/remote-pi/dist/session/wizard.d.ts +0 -13
- package/node_modules/remote-pi/dist/session/wizard.js +0 -20
- package/node_modules/remote-pi/dist/session/wizard.js.map +0 -1
- package/node_modules/remote-pi/dist/settings.d.ts +0 -15
- package/node_modules/remote-pi/dist/settings.js +0 -52
- package/node_modules/remote-pi/dist/settings.js.map +0 -1
- package/node_modules/remote-pi/dist/transport/peer_channel.d.ts +0 -37
- package/node_modules/remote-pi/dist/transport/peer_channel.js +0 -98
- package/node_modules/remote-pi/dist/transport/peer_channel.js.map +0 -1
- package/node_modules/remote-pi/dist/transport/pi_forward_client.d.ts +0 -29
- package/node_modules/remote-pi/dist/transport/pi_forward_client.js +0 -62
- package/node_modules/remote-pi/dist/transport/pi_forward_client.js.map +0 -1
- package/node_modules/remote-pi/dist/transport/relay_client.d.ts +0 -91
- package/node_modules/remote-pi/dist/transport/relay_client.js +0 -206
- package/node_modules/remote-pi/dist/transport/relay_client.js.map +0 -1
- package/node_modules/remote-pi/dist/ui/footer.d.ts +0 -32
- package/node_modules/remote-pi/dist/ui/footer.js +0 -34
- package/node_modules/remote-pi/dist/ui/footer.js.map +0 -1
- package/node_modules/remote-pi/docs/daemon.md +0 -289
- package/node_modules/remote-pi/package.json +0 -92
- package/node_modules/remote-pi/service-templates/launchd.plist.template +0 -35
- package/node_modules/remote-pi/service-templates/systemd.service.template +0 -19
- package/node_modules/remote-pi/service-templates/task-launcher.vbs.template +0 -10
- package/node_modules/remote-pi/service-templates/task-scheduler.xml.template +0 -38
- package/node_modules/remote-pi/skills/agent-network/SKILL.md +0 -310
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-provider-qoder",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Pi extension for Qoder AI — with OAuth authentication, COSY signatures and WAF bypass",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"pi-package",
|
|
12
|
+
"pi",
|
|
13
|
+
"qoder",
|
|
14
|
+
"ai",
|
|
15
|
+
"provider"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@earendil-works/pi-ai --external:@earendil-works/pi-coding-agent --external:@earendil-works/pi-ai/compat",
|
|
23
|
+
"check": "node node_modules/typescript/bin/tsc --noEmit",
|
|
24
|
+
"lint": "node node_modules/@biomejs/biome/bin/biome check .",
|
|
25
|
+
"lint:fix": "node node_modules/@biomejs/biome/bin/biome check --write .",
|
|
26
|
+
"format": "node node_modules/@biomejs/biome/bin/biome format --write .",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:live": "tsx scripts/record-live-fixtures.ts",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"prepare": "npm run build",
|
|
31
|
+
"prepublishOnly": "npm run check && npm run build",
|
|
32
|
+
"demo:credits": "tsx scripts/demo-credit-footer.ts"
|
|
33
|
+
},
|
|
34
|
+
"pi": {
|
|
35
|
+
"extensions": [
|
|
36
|
+
"./dist/index.js"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@biomejs/biome": "2.5.15",
|
|
41
|
+
"@earendil-works/pi-ai": "^1.0.4",
|
|
42
|
+
"@earendil-works/pi-coding-agent": "^1.0.4",
|
|
43
|
+
"@earendil-works/pi-tui": "^1.0.4",
|
|
44
|
+
"esbuild": "^0.28.2",
|
|
45
|
+
"tsx": "^4.23.15",
|
|
46
|
+
"typescript": "^7.0.2",
|
|
47
|
+
"vitest": "^5.0.3"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/simonsmh/pi-provider-qoder.git"
|
|
52
|
+
},
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/simonsmh/pi-provider-qoder/issues"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/simonsmh/pi-provider-qoder#readme"
|
|
57
|
+
}
|
|
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.38.0] - 2026-10-09
|
|
8
|
+
|
|
9
|
+
### Highlights
|
|
10
|
+
|
|
11
|
+
- Search with Ceramic, a new paid keyword search provider you can select.
|
|
12
|
+
- OpenAI search now defaults to the newest Luna model, which costs about 20 times less per token than Terra.
|
|
13
|
+
- Answer mode and query rewriting work with models whose provider signs in without an API key, such as pi-multiprovider models.
|
|
14
|
+
- Requests through a proxy keep their method, headers, body and `Content-Length`, and HEAD requests stay HEAD requests.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- New Ceramic search provider for [Ceramic](https://www.ceramic.ai)'s keyword web search API, used only when you select it. Set `ceramicApiKey` or `CERAMIC_API_KEY` to your Ceramic key. Ceramic is a paid API with free starter credits, so it is never picked by `auto` or `all`. Allowed domains are added to the query as `site:` filters, and excluded domains are removed from the results. Ceramic has no date filter, so `recencyFilter` is ignored. Thanks to [@sweepies](https://github.com/sweepies) for [issue #523](https://github.com/nicobailon/pi-web-access/issues/523).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- The `web_search` tool description is now four sentences on how to use the tool, instead of a paragraph listing every provider, the `all` policy and each workflow mode. The model still sees the providers and the `all` policy in the `provider` parameter and the modes in the `workflow` parameter. Thanks to [@mcwalrus](https://github.com/mcwalrus) for [issue #528](https://github.com/nicobailon/pi-web-access/issues/528).
|
|
23
|
+
- OpenAI `web_search` now runs on the newest Luna model by default, such as `gpt-6-luna` on a ChatGPT subscription, instead of `gpt-5.6-terra`. Terra costs about 20 times as much per token as `gpt-6-luna` and is a generation older, so searches were using a large share of subscription usage. With only an API key, the default is `gpt-6-luna`. Set `openaiSearchModel` to pick a different model. Thanks to [@sslotin](https://github.com/sslotin) for [issue #520](https://github.com/nicobailon/pi-web-access/issues/520).
|
|
24
|
+
- Search summaries and `fetch_content` answer mode now give Pi the session ID and let its `opencode` and `opencode-go` providers add OpenCode's `x-opencode-session` header, instead of building OpenCode's headers themselves. They no longer send `x-opencode-client`, and custom providers pointed at `opencode.ai` no longer get the session header from pi-web-access. See [issue #537](https://github.com/nicobailon/pi-web-access/issues/537).
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- With a proxy set, `fetch(new Request(...))` now sends the Request's own method, headers, body, redirect setting and abort signal. It used to arrive as a bare GET without them. HEAD requests through the proxy are now real HEAD requests with an empty response body instead of GETs. Thanks to [@xiaozhu1337](https://github.com/xiaozhu1337) for [issue #535](https://github.com/nicobailon/pi-web-access/issues/535) and [PR #536](https://github.com/nicobailon/pi-web-access/pull/536).
|
|
29
|
+
- With a proxy set, HEAD responses keep the server's `Content-Length`, and a POST or PUT with no body sends `Content-Length: 0` as native fetch does, instead of omitting it (which some servers answer with 411). See [issue #539](https://github.com/nicobailon/pi-web-access/issues/539).
|
|
30
|
+
- Keyless Parallel MCP searches no longer fail with Parallel's "free-tier rate limit" error. Requests went out with Node's default `undici` User-Agent, which Parallel rate-limits as one shared bucket, so they now send `User-Agent: pi-web-access`. Thanks to [@kestermcullough](https://github.com/kestermcullough) for [PR #531](https://github.com/nicobailon/pi-web-access/pull/531).
|
|
31
|
+
- `fetch_content` answer mode and search query rewriting now work with models whose provider signs in without giving Pi an API key, such as models added by [pi-multiprovider](https://github.com/monotykamary/pi-multiprovider). Answer mode used to fail with "No API key available for answer model" even though Pi could call the model. Thanks to [@eikopf](https://github.com/eikopf) for [issue #530](https://github.com/nicobailon/pi-web-access/issues/530).
|
|
32
|
+
- A host that serves several conversations at once from one process, with one extension instance per session (an app or chat server built on the Pi SDK), no longer loses results across conversations. Each instance now keeps its own session's results, background fetches and active flag: one conversation starting or ending used to clear every stored result, so a `get_search_content` in another conversation answered "No stored results for responseId …", and its background `includeContent` fetches were aborted. Sessions forked from one history hold the results they share until the last of them ends, and deleting a shared result from `/search` removes it only from that session. Pi itself, which runs one instance and switches its session, behaves as before. GitHub clones are removed when the last live session ends, and while sessions keep overlapping, a session's end removes the clones no fetch has returned since the oldest live session started. A session that restores results from history marks the clones they point at as used, so a session forked from another keeps its clone paths after the original ends. Thanks to [@kid7st](https://github.com/kid7st) for [PR #521](https://github.com/nicobailon/pi-web-access/pull/521).
|
|
33
|
+
- `@modelcontextprotocol/sdk` is updated from 1.27.1 to 1.32.1, which fixes [GHSA-6qxp-vccf-f47h](https://github.com/advisories/GHSA-6qxp-vccf-f47h), so `npm audit` no longer reports it. That advisory covers the SDK's OAuth client, which pi-web-access doesn't use: its HTTP providers send API keys in headers and its MCP server runs over stdio. Thanks to [@M1racleShih](https://github.com/M1racleShih) for [PR #522](https://github.com/nicobailon/pi-web-access/pull/522) and [@SidShaytay](https://github.com/SidShaytay) for [issue #519](https://github.com/nicobailon/pi-web-access/issues/519).
|
|
34
|
+
|
|
7
35
|
## [0.37.0] - 2026-10-05
|
|
8
36
|
|
|
9
37
|
### Highlights
|
|
@@ -165,7 +165,7 @@ fetch_content({ url: "/path/to/recording.mp4", prompt: "What error appears on sc
|
|
|
165
165
|
|
|
166
166
|
### web_search
|
|
167
167
|
|
|
168
|
-
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, Serply, You.com, keyless Keenable, self-hosted SearXNG, explicit-only degoog, keyless DuckDuckGo, Exa, Perplexity AI, Gemini, or Kimi. The default `none` workflow makes no summary-generation model call and opens no curator/browser: it returns bounded raw results, identifies the provider used for each query, and stores the full results for retrieval by responseId. The selected search provider may itself be model-backed.
|
|
168
|
+
Search the web via OpenAI, Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data SERP, SerpBase, SerpApi, Serper, Serply, You.com, keyless Keenable, Ceramic, self-hosted SearXNG, explicit-only degoog, keyless DuckDuckGo, Exa, Perplexity AI, Gemini, or Kimi. The default `none` workflow makes no summary-generation model call and opens no curator/browser: it returns bounded raw results, identifies the provider used for each query, and stores the full results for retrieval by responseId. The selected search provider may itself be model-backed.
|
|
169
169
|
|
|
170
170
|
```typescript
|
|
171
171
|
web_search({ query: "rust async programming" })
|
|
@@ -189,7 +189,7 @@ web_search({ queries: ["query 1", "query 2"], workflow: "auto-summary" })
|
|
|
189
189
|
| `recencyFilter` | `day`, `week`, `month`, or `year` |
|
|
190
190
|
| `domainFilter` | Limit to domains (prefix with `-` to exclude) |
|
|
191
191
|
| `category` | Exa only: restrict results to a category such as `news` or `research paper`; other providers ignore it. Without an API key, if Exa's filtered search is unavailable, the category is added to the query instead |
|
|
192
|
-
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except Parallel MCP, degoog, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, and
|
|
192
|
+
| `provider` | Configured provider when omitted or set to `auto`; `all` searches every eligible provider except Parallel MCP, degoog, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, Keenable, and Ceramic simultaneously; otherwise `openai`, `brave`, `parallel`, `parallel-mcp`, `tinyfish`, `search1api`, `searchinfinity`, `querit`, `tavily`, `you`, `firecrawl`, `jina`, `serpdive`, `kagi`, `bocha`, `ollama`, `anysearch`, `xcrawl`, `valyu`, `xai`, `mistral`, `brightdata`, `serpbase`, `serpapi`, `serper`, `serply`, `baizhi`, `zai`, `keenable`, `ceramic`, `searxng`, `degoog`, `duckduckgo`, `exa`, `perplexity`, `gemini`, or `kimi` (auto-selects when no provider or routing is configured; Parallel MCP, degoog, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, Keenable, and Ceramic are explicit-only) |
|
|
193
193
|
| `includeContent` | Fetch full page content from sources in background |
|
|
194
194
|
| `workflow` | `none` (skip curator; fresh-install default), `summary-review` (open curator and auto-generate a summary draft), or `auto-summary` (generate a summary without opening the curator) |
|
|
195
195
|
|
|
@@ -476,6 +476,7 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
476
476
|
"ollamaApiKey": "$OLLAMA_API_KEY",
|
|
477
477
|
"valyuApiKey": "$VALYU_API_KEY",
|
|
478
478
|
"keenableApiKey": "$KEENABLE_API_KEY",
|
|
479
|
+
"ceramicApiKey": "$CERAMIC_API_KEY",
|
|
479
480
|
"serpbaseApiKey": "$SERPBASE_API_KEY",
|
|
480
481
|
"serpapiApiKey": "$SERPAPI_KEY",
|
|
481
482
|
"serperApiKey": "$SERPER_API_KEY",
|
|
@@ -614,7 +615,7 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
|
|
|
614
615
|
|
|
615
616
|
`summaryInstructions` appends custom text to the Requirements section of the summary prompt used by the curator UI and `auto-summary` mode (e.g. `"- Preserve concrete figures verbatim: prices, limits, versions, dates.\n- Name the product version each claim applies to."`). The text is passed verbatim to the summary model, so use one `- ` bullet per requirement. It is additive: the built-in guardrails (no invented sources, explicit weak/conflicting-evidence notes, the Sources section) always stay in place, and curator regeneration feedback is still appended afterwards. Missing, non-string, or blank values leave the default prompt unchanged.
|
|
616
617
|
|
|
617
|
-
All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `youApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `keenableApiKey`, `degoogApiKey`, `anysearchApiKey`, `xcrawlApiKey`, `xaiApiKey`, `mistralApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `crawl4aiApiToken`, `exaApiKey`, `perplexityApiKey`, `geminiApiKey`, `datalabApiKey`, and `cloudflareApiKey`) accept explicit credential sources. Use `$NAME` or `${NAME}` to read one named environment variable, or prefix a trusted local shell command with `!` to resolve one value at provider request time. Escape `$$` as a literal leading `$` and `$!` as a literal leading `!`:
|
|
618
|
+
All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `youApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `keenableApiKey`, `ceramicApiKey`, `degoogApiKey`, `anysearchApiKey`, `xcrawlApiKey`, `xaiApiKey`, `mistralApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `crawl4aiApiToken`, `exaApiKey`, `perplexityApiKey`, `geminiApiKey`, `datalabApiKey`, and `cloudflareApiKey`) accept explicit credential sources. Use `$NAME` or `${NAME}` to read one named environment variable, or prefix a trusted local shell command with `!` to resolve one value at provider request time. Escape `$$` as a literal leading `$` and `$!` as a literal leading `!`:
|
|
618
619
|
|
|
619
620
|
```json
|
|
620
621
|
{
|
|
@@ -625,7 +626,7 @@ All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `t
|
|
|
625
626
|
}
|
|
626
627
|
```
|
|
627
628
|
|
|
628
|
-
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `crawl4aiApiToken`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `keenableApiKey`, `degoogApiKey`, `mistralApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `firecrawlPdfMaxPages`, `crawl4aiBaseUrl`, `degoogBaseUrl`, `degoogEngines`, `brightdataSerpZone`, `brightdataUnlockerZone`, and `zaiEndpoint` are literal config values.
|
|
629
|
+
This syntax applies to provider credentials only; other configuration fields are not interpolated. `firecrawlApiKey`, `crawl4aiApiToken`, `kagiApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `keenableApiKey`, `ceramicApiKey`, `degoogApiKey`, `mistralApiKey`, and `brightdataApiKey` use the same credential-source rules, while `braveBaseUrl`, `exaBaseUrl`, `tavilyBaseUrl`, `firecrawlBaseUrl`, `firecrawlApiVersion`, `firecrawlFreshScrape`, `firecrawlPdfMaxPages`, `crawl4aiBaseUrl`, `degoogBaseUrl`, `degoogEngines`, `brightdataSerpZone`, `brightdataUnlockerZone`, and `zaiEndpoint` are literal config values.
|
|
629
630
|
|
|
630
631
|
A command source is not run while the extension loads or registers tools. Each selected provider request runs it again with a five-second timeout, a 16 KiB output limit, a minimized environment, and a one-line non-empty stdout requirement. Command text and stderr are omitted from errors. These commands are trusted local configuration, not a same-user process isolation boundary; use absolute executable paths and protect the config file. `OP_SESSION_*` and `OP_SERVICE_ACCOUNT_TOKEN`, when present in Pi's environment, are forwarded to trusted resolver commands so 1Password CLI sessions and service accounts can be reused without storing their credentials in config. For example, `"braveApiKey": "!/absolute/path/to/op read 'op://Automation/Brave/credential'"` resolves that item after you replace the executable placeholder with your trusted installation's absolute path, but the `op://` argument is not an authorization boundary: every configured resolver command that receives the token can exercise all vault and item permissions granted to its service account. Prefer a narrowly scoped service account. An explicit source overrides legacy provider environment variables and fails that provider locally rather than falling back with a stale credential. Direct Google Gemini API requests send the resolved key only in the `x-goog-api-key` header, never in the URL.
|
|
631
632
|
|
|
@@ -673,6 +674,8 @@ Bright Data Web Unlocker is a paid `fetch_content` fallback after Parallel and b
|
|
|
673
674
|
|
|
674
675
|
**Keenable.** Select `provider: "keenable"` to query Keenable's web search API. No API key is needed: without one, requests go to Keenable's public endpoint, which is rate-limited per IP. Set `keenableApiKey` or `KEENABLE_API_KEY` to use the authenticated endpoint and its higher limits. Keenable is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`. A single allowed domain is sent as Keenable's `site` filter; several domains and exclusions become `site:` / `-site:` clauses in the query, and all domain filters are reapplied locally. Recency maps to a `published_after` date. Result snippets are capped at 500 characters.
|
|
675
676
|
|
|
677
|
+
**Ceramic.** Set `ceramicApiKey` or `CERAMIC_API_KEY` ([get a key](https://platform.ceramic.ai/keys)) and select `provider: "ceramic"` to query [Ceramic](https://www.ceramic.ai)'s web search API, a paid keyword search index with free starter credits. Ceramic is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`. Ceramic matches keywords rather than questions, so short keyword queries work best. Allowed domains become `site:` clauses in the query; excluded domains are only removed from the returned results, because Ceramic documents no exclusion syntax, so a search with exclusions can return fewer results than requested. `recencyFilter` is unsupported and is not sent, because Ceramic has no date filter. Result snippets are capped at 500 characters.
|
|
678
|
+
|
|
676
679
|
**Serper.** Set `serperApiKey` or `SERPER_API_KEY` and select `provider: "serper"` to query Serper's Google Search API. Serper is explicit-only: it is never chosen by `auto` or `provider: "all"`, but it can be configured as the named provider or added to `searchRouting`.
|
|
677
680
|
|
|
678
681
|
**Tavily key pool.** Set `TAVILY_API_KEY_1` through `TAVILY_API_KEY_20` to try another key when Tavily returns HTTP 401, 402, 403, 429, or 432. `TAVILY_API_KEY_INDEX` picks the first slot to try (default `1`). Search tries each distinct numbered key once, wrapping around, then tries `tavilyApiKey` / `TAVILY_API_KEY`. Explicit `$VAR` and `!command` sources still fail closed. All attempts share the 60-second search deadline.
|
|
@@ -689,7 +692,7 @@ The Baizhi input contract is based on the [published September 16 discovery snap
|
|
|
689
692
|
|
|
690
693
|
**Parallel MCP.** Select `provider: "parallel-mcp"` to use Parallel Search MCP without an API key, or add it to `searchRouting`. It is explicit-only and is never chosen by `auto` or `provider: "all"`; the existing `parallel` provider remains the key-required REST API. A configured `parallelApiKey` or `PARALLEL_API_KEY` is sent as an optional Bearer token for higher MCP limits. To use MCP `web_fetch`, add `parallel-mcp` to `fetchRouting.providers` and set `fetchRouting.allowRemoteHostedProviders` to `true`; it is not part of the default fetch route.
|
|
691
694
|
|
|
692
|
-
Without an explicit `$` or `!` source, `OPENAI_API_KEY`, `BRAVE_API_KEY`, `PARALLEL_API_KEY`, `TINYFISH_API_KEY`, `SEARCH1API_KEY`, `SEARCHINFINITY_API_KEY`, `QUERIT_API_KEY`, `TAVILY_API_KEY`, `YDC_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `SERPAPI_KEY`, `SERPER_API_KEY`, `SERPLY_API_KEY`, `BAIZHI_API_KEY`, `ZAI_API_KEY`, `KEENABLE_API_KEY`, `ANYSEARCH_API_KEY`, `XCRAWL_API_KEY`, `VALYU_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `CRAWL4AI_API_TOKEN`, `DEGOOG_API_KEY`, `EXA_API_KEY`, `GEMINI_API_KEY`, `DATALAB_API_KEY`, `DATALAB_PROCESSING_LOCATION`, `DATALAB_MODE`, `DATALAB_API_BASE`, `PERPLEXITY_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, and `CLOUDFLARE_API_KEY` env vars retain their existing precedence over literal config file values. `openaiResponsesUrl` can point OpenAI `web_search` and `source_check` at a third-party gateway that supports the OpenAI Responses API and web search tool; it is an explicit endpoint override, not derived from Pi model provider settings, and defaults to `https://api.openai.com/v1/responses`. `openaiSearchModel` pins the model id used for OpenAI `web_search`, bypassing automatic selection (newest
|
|
695
|
+
Without an explicit `$` or `!` source, `OPENAI_API_KEY`, `BRAVE_API_KEY`, `PARALLEL_API_KEY`, `TINYFISH_API_KEY`, `SEARCH1API_KEY`, `SEARCHINFINITY_API_KEY`, `QUERIT_API_KEY`, `TAVILY_API_KEY`, `YDC_API_KEY`, `JINA_API_KEY`, `SERPDIVE_API_KEY`, `KAGI_API_KEY`, `BOCHA_API_KEY`, `OLLAMA_API_KEY`, `SERPBASE_API_KEY`, `SERPAPI_KEY`, `SERPER_API_KEY`, `SERPLY_API_KEY`, `BAIZHI_API_KEY`, `ZAI_API_KEY`, `KEENABLE_API_KEY`, `CERAMIC_API_KEY`, `ANYSEARCH_API_KEY`, `XCRAWL_API_KEY`, `VALYU_API_KEY`, `XAI_API_KEY`, `MISTRAL_API_KEY`, `BRIGHTDATA_API_KEY`, `FIRECRAWL_API_KEY`, `CRAWL4AI_API_TOKEN`, `DEGOOG_API_KEY`, `EXA_API_KEY`, `GEMINI_API_KEY`, `DATALAB_API_KEY`, `DATALAB_PROCESSING_LOCATION`, `DATALAB_MODE`, `DATALAB_API_BASE`, `PERPLEXITY_API_KEY`, `GOOGLE_GEMINI_BASE_URL`, and `CLOUDFLARE_API_KEY` env vars retain their existing precedence over literal config file values. `openaiResponsesUrl` can point OpenAI `web_search` and `source_check` at a third-party gateway that supports the OpenAI Responses API and web search tool; it is an explicit endpoint override, not derived from Pi model provider settings, and defaults to `https://api.openai.com/v1/responses`. `openaiSearchModel` pins the model id used for OpenAI `web_search`, bypassing automatic selection (newest Luna model, then newest bare `gpt-N` or `gpt-N.N` id, then newest other `gpt-<version>` id; `gpt-6-luna` when only an API key is configured); the id is sent verbatim with whichever OpenAI auth resolves, so gateway-only model ids work too. `xaiSearchModel` similarly pins the xAI search model. `openaiSearchProviders` sets which Pi model providers OpenAI `web_search` resolves login credentials from, in priority order; it defaults to `["openai-codex", "openai"]`, entries that are not registered or not signed in are skipped, and an empty array skips Pi credentials entirely so the `openaiApiKey` / `OPENAI_API_KEY` fallback applies. Useful for choosing between multiple Codex accounts (for example a second account registered by an extension) or forcing API-key billing while signed into Codex. Configured Exa API keys use Exa's own account limits directly; any legacy local `exa-usage.json` file is ignored. `GOOGLE_GEMINI_BASE_URL` overrides the Gemini API host for Gemini generate-content calls such as search, URL context, YouTube, and local video analysis. Set it to a bare host with no trailing slash and no version segment, for example `https://my-gateway.example.com/gemini`; `geminiBaseUrl` is the config-file equivalent. When the configured host contains `gateway.ai.cloudflare.com`, authentication uses `cf-aig-authorization: Bearer <token>` from `CLOUDFLARE_API_KEY` or `cloudflareApiKey`, and `GEMINI_API_KEY` is not required for generate-content calls. Alternatively, set `geminiAuth` to `"adc"` to authenticate Gemini generate-content calls with Google Application Default Credentials (ADC) instead of an API key; calls go to the Vertex AI endpoint (`aiplatform.googleapis.com`) with an OAuth bearer token minted from the ADC file (`GOOGLE_APPLICATION_CREDENTIALS` or `~/.config/gcloud/application_default_credentials.json`, i.e. `gcloud auth application-default login`). `geminiProject`/`geminiLocation` set the Vertex project and location and fall back to the `GOOGLE_CLOUD_PROJECT`/`GOOGLE_CLOUD_LOCATION` (or `GCLOUD_PROJECT`) env vars; project and location are required. ADC supports `authorized_user` (OAuth refresh token) and `service_account` (JWT assertion) credential files, and tokens are cached and refreshed from expiry. ADC mode covers search, URL context, and PDF/inline-data extraction; YouTube and local video analysis still go through the Gemini Files API, so they fall back to Gemini Web unless a `GEMINI_API_KEY` is also configured. The access token is treated as a credential and is redacted from errors. Local video file upload still uses Google's Files API directly, so gateway-only video extraction falls back to Gemini Web unless a `GEMINI_API_KEY` is also configured. `provider` or `searchProvider` sets the default search provider and is used when a tool call omits `provider` or sends `"auto"`: `"all"`, `"openai"`, `"brave"`, `"parallel"`, `"parallel-mcp"`, `"tinyfish"`, `"search1api"`, `"searchinfinity"`, `"querit"`, `"tavily"`, `"firecrawl"`, `"jina"`, `"serpdive"`, `"kagi"`, `"bocha"`, `"ollama"`, `"anysearch"`, `"xcrawl"`, `"valyu"`, `"xai"`, `"mistral"`, `"brightdata"`, `"serpbase"`, `"serpapi"`, `"serper"`, `"serply"`, `"you"`, `"baizhi"`, `"zai"`, `"keenable"`, `"ceramic"`, `"searxng"`, `"degoog"`, `"exa"`, `"perplexity"`, or `"gemini"`. Parallel MCP, degoog, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, Keenable, and Ceramic are never selected by `auto`; choose them explicitly or place them in `searchRouting`. If either single-provider field is configured, it takes precedence over `searchRouting`. Otherwise, `searchRouting` can opt into an ordered `providers` list and an explicit `fallbackOn` list containing `"transient"`, `"quota"`, `"network"`, and/or `"invalid-response"`; only those typed failures continue to the next available candidate. `"all"` is not valid inside `searchRouting.providers`, because that list defines sequential fallback rather than multi-provider aggregation. Named providers remain strict, and exhausted routes return per-provider diagnostics. `provider` can also be a non-empty array of named providers such as `["brave", "exa"]`; those providers run concurrently using the same aggregation path as `"all"`, while `"auto"` and `"all"` are invalid inside arrays. Random, weighted, sticky, and cooldown routing are not enabled. This is also updated automatically when you change the provider in the curator UI. Set `webSearch.enabled` to `false` to unregister the configured search and source-check tools while leaving fetch/content tools available. `toolNames` can opt into alternate public tool names for environments where another extension or model reserves the defaults, without changing behavior: `webSearch`, `sourceCheck`, `fetchContent`, and `getSearchContent` default to `web_search`, `source_check`, `fetch_content`, and `get_search_content`. `workflow` sets the default search workflow: `"none"` (default; raw results, no curator), `"summary-review"` (opens curator with an auto-generated summary draft), or `"auto-summary"` (returns a model-generated summary without opening the browser curator). Overridden per-call via the `workflow` parameter on the configured search tool, or toggled at runtime with `/curator`. `browserCookies.profile` pins Gemini Web cookie lookup to a specific Chromium profile. When omitted, detected Chromium profiles are scanned in stable order and the first profile containing the required Gemini cookies is used. macOS discovery supports Helium, Chrome, Brave, and Arc; Linux discovery supports Chromium and Chrome. `allowBrowserCookies` enables Chromium cookie extraction for Gemini Web; it defaults to `false` to avoid browser data access and surprise macOS Keychain prompts. You can also set `PI_ALLOW_BROWSER_COOKIES=1`. Cookie databases are copied to a temporary read-only working copy; the reader uses `node:sqlite` when available and otherwise tries the `sqlite3` CLI or Python's standard-library SQLite module. `searchModel` overrides the Gemini API model used by the configured search tool without changing URL, YouTube, or video extraction defaults. Gemini API grounded search uses `gemini-3.6-flash` by default; set `searchModel` to choose another model. Gemini Web browser-cookie fallback uses its separate `gemini-3.1-pro` default because Gemini Web relies on private header values; explicitly configured unsupported Web models fail instead of silently falling back to 2.5 Flash. `summaryModel` sets the default model used for generating summary drafts in the curator UI and `auto-summary` mode (e.g. `"anthropic/claude-haiku-4-5"`, `"openai-codex/gpt-5.3-codex-spark"`, or `"openrouter/nvidia/nemotron-3-super-120b-a12b:free"`). Preferred summary and query-rewrite models also resolve through routed provider registrations such as OpenRouter when the native provider is unavailable. When Pi `enabledModels` is configured, summaries are limited to that allowlist; if no enabled summary model is available, the tool returns a deterministic summary instead of calling an unrelated model. `summaryGenerationDeadlineMs` sets the maximum time for one summary model attempt in the curator UI and `auto-summary` mode. It defaults to `30000`, must be a positive integer, and is capped at `600000`. `maxInlineContentChars` sets the direct `fetch_content` content slice and the default and maximum `get_search_content` slice. It defaults to `30000`, must be a positive integer, and is capped at `200000`; full fetched content remains stored for later retrieval. `curatorTimeoutSeconds` controls the initial curator idle timeout (default `20`, max `600`); users can still adjust the timer in the curator UI. `ssrf.allowRanges` lists CIDR ranges (e.g. `"198.18.0.0/15"`, `"fd00::/8"`) exempted from the SSRF guard that otherwise blocks private/reserved IP ranges. This unblocks `fetch_content`/`web_search` on hosts whose network proxy runs in TUN + fake-IP mode (Surge, Clash, Mihomo, Stash, ...), where public domains resolve into a synthetic reserved range. It is **off by default** — the guard stays fully enabled unless you list ranges here. Use the narrowest range that covers your proxy's fake-IP pool. All-address CIDRs such as `0.0.0.0/0` and `::/0` are rejected. `ssrf.trustEnvProxy` is a separate opt-in for networks where only the proxy can resolve hostnames; it skips local DNS preflight only for hostnames sent through the configured `proxy` or, when none is configured, hostnames covered by valid HTTP(S) proxy env vars in sandboxes that route through them, and still blocks localhost, literal private IP targets, and `NO_PROXY` matches. It does not configure proxy transport.
|
|
693
696
|
|
|
694
697
|
### Kimi Code Plan
|
|
695
698
|
Run `/login kimi-coding` in Pi and complete sign-in for an active Kimi Code Plan. Then select `provider: "kimi"`, include `"kimi"` in an explicit provider array, or add it to `searchRouting.providers`. The extension resolves a model with provider `kimi-coding` from Pi's model registry and reuses Pi's refreshed OAuth credential; no Moonshot Open Platform key is configured here.
|
|
@@ -700,7 +703,7 @@ Kimi is explicit-only: it is never chosen by `auto` and never participates in `p
|
|
|
700
703
|
|
|
701
704
|
### All providers
|
|
702
705
|
|
|
703
|
-
Set `provider: "all"` on `web_search` or `source_check`, or configure `"provider": "all"` as the default, to run the same query against every eligible search provider simultaneously. Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, and
|
|
706
|
+
Set `provider: "all"` on `web_search` or `source_check`, or configure `"provider": "all"` as the default, to run the same query against every eligible search provider simultaneously. Parallel MCP, DuckDuckGo, Kimi, AnySearch, XCrawl, Valyu, xAI, Mistral, Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, Baizhi, Z.ai, Keenable, and Ceramic are always excluded because they are explicit-only; Bright Data, SerpBase, SerpApi, Serper, Serply, You.com, and Ceramic are paid search APIs, while Baizhi, Z.ai, Kimi, and Mistral may consume account quota or paid search-tool usage, so `all` never spends either resource without an explicit request. Exa remains eligible through its zero-config MCP path, OpenAI can use Pi auth, and other API-backed search providers participate when their API key, local endpoint, or gateway makes them available. Browser-cookie access alone does not opt Gemini into `all`; select Gemini explicitly or configure its API/gateway.
|
|
704
707
|
|
|
705
708
|
Successful provider answers are preserved separately while source URLs and inline content are deduplicated, and one provider failure does not discard the other results. If every participating provider fails, the tool returns per-provider diagnostics. Configured Firecrawl participates in `all` like other eligible providers. In the Curator, **All** can also be selected like the other provider buttons. Each participating provider gets its own result card, including a provider badge and independent selection checkbox; failed providers get their own disabled error card. The final summary is generated from the selected provider cards and is what Pi receives. Outside the Curator, the same provider answers remain available as labeled sections in one tool response.
|
|
706
709
|
|
|
@@ -1118,6 +1121,7 @@ Rate limits: Perplexity is capped at 10 requests/minute (client-side). Jina Sear
|
|
|
1118
1121
|
| `xcrawl.ts` | Explicit-only XCrawl search provider |
|
|
1119
1122
|
| `valyu.ts` | Explicit-only Valyu research search provider |
|
|
1120
1123
|
| `keenable.ts` | Explicit-only Keenable search provider, keyless with an optional API key |
|
|
1124
|
+
| `ceramic.ts` | Explicit-only Ceramic keyword search provider |
|
|
1121
1125
|
| `xai-search.ts` | Explicit-only xAI (Grok) hosted web/X search provider |
|
|
1122
1126
|
| `mistral-search.ts` | Explicit-only Mistral Conversations web search provider |
|
|
1123
1127
|
| `kimi-search.ts` | Explicit-only Kimi Code Plan search provider |
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { activityMonitor } from "./activity.ts";
|
|
3
|
+
import { normalizeDomain } from "./domain-filter-normalization.ts";
|
|
4
|
+
import type { SearchOptions, SearchResponse } from "./perplexity.ts";
|
|
5
|
+
import { formatSearchResultsAsAnswer } from "./search-answer-formatting.ts";
|
|
6
|
+
import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
|
|
7
|
+
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
|
|
8
|
+
import { fetchWithCredentialRedirects, getWebSearchConfigPath } from "./utils.ts";
|
|
9
|
+
|
|
10
|
+
const CERAMIC_SEARCH_URL = "https://api.ceramic.ai/search";
|
|
11
|
+
const CONFIG_PATH = getWebSearchConfigPath();
|
|
12
|
+
const SEARCH_TIMEOUT_MS = 30_000;
|
|
13
|
+
const MAX_REQUEST_RESULTS = 20;
|
|
14
|
+
// Descriptions default to 3,000 characters; 1,000 is the smallest the API accepts. Results
|
|
15
|
+
// are trimmed further so ten of them, shown twice (answer and list), fit the inline budget.
|
|
16
|
+
const REQUEST_DESCRIPTION_CHARS = 1_000;
|
|
17
|
+
const MAX_SNIPPET_CHARS = 500;
|
|
18
|
+
|
|
19
|
+
interface WebSearchConfig {
|
|
20
|
+
ceramicApiKey?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CeramicResult {
|
|
24
|
+
title?: unknown;
|
|
25
|
+
url?: unknown;
|
|
26
|
+
description?: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let cachedConfig: WebSearchConfig | null = null;
|
|
30
|
+
|
|
31
|
+
function loadConfig(): WebSearchConfig {
|
|
32
|
+
if (cachedConfig) return cachedConfig;
|
|
33
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
34
|
+
cachedConfig = {};
|
|
35
|
+
return cachedConfig;
|
|
36
|
+
}
|
|
37
|
+
const raw = readFileSync(CONFIG_PATH, "utf-8");
|
|
38
|
+
let parsed: unknown;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(raw);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
+
throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
|
|
44
|
+
}
|
|
45
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
46
|
+
throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
|
|
47
|
+
}
|
|
48
|
+
cachedConfig = parsed as WebSearchConfig;
|
|
49
|
+
return cachedConfig;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function requireApiKey(signal?: AbortSignal): Promise<string> {
|
|
53
|
+
const apiKey = await resolveCredential({
|
|
54
|
+
provider: "Ceramic",
|
|
55
|
+
configuredValue: loadConfig().ceramicApiKey,
|
|
56
|
+
environmentValue: process.env.CERAMIC_API_KEY,
|
|
57
|
+
signal,
|
|
58
|
+
});
|
|
59
|
+
if (!apiKey) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Ceramic API key not found. Either:\n" +
|
|
62
|
+
` 1. Create ${CONFIG_PATH} with { "ceramicApiKey": "your-key" }\n` +
|
|
63
|
+
" 2. Set CERAMIC_API_KEY environment variable\n" +
|
|
64
|
+
"Get a key at https://platform.ceramic.ai/keys",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return apiKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface DomainFilters {
|
|
71
|
+
include: string[];
|
|
72
|
+
exclude: string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
|
|
76
|
+
const filters: DomainFilters = { include: [], exclude: [] };
|
|
77
|
+
for (const raw of domainFilter ?? []) {
|
|
78
|
+
const domain = normalizeDomain(raw);
|
|
79
|
+
if (!domain) continue;
|
|
80
|
+
const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
|
|
81
|
+
if (!target.includes(domain)) target.push(domain);
|
|
82
|
+
}
|
|
83
|
+
return filters;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function passesDomainFilters(url: URL, filters: DomainFilters): boolean {
|
|
87
|
+
if (filters.include.length === 0 && filters.exclude.length === 0) return true;
|
|
88
|
+
const hostname = url.hostname.toLowerCase();
|
|
89
|
+
const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
|
|
90
|
+
if (filters.exclude.some(matches)) return false;
|
|
91
|
+
return filters.include.length === 0 || filters.include.some(matches);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ceramic documents `site:`, uppercase `OR`, and parentheses, but not `-site:`, so
|
|
95
|
+
// excluded domains are only filtered out of the returned results.
|
|
96
|
+
function buildQuery(query: string, filters: DomainFilters): string {
|
|
97
|
+
if (filters.include.length === 0) return query;
|
|
98
|
+
const sites = filters.include.map(domain => `site:${domain}`);
|
|
99
|
+
return `${query} ${sites.length === 1 ? sites[0] : `(${sites.join(" OR ")})`}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function text(value: unknown): string {
|
|
103
|
+
return typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function errorMessage(err: unknown): string {
|
|
107
|
+
return err instanceof Error ? err.message : String(err);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function invalidResponse(message: string): Error {
|
|
111
|
+
return new Error(`Ceramic returned invalid response: ${message}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseResponse(value: unknown): CeramicResult[] {
|
|
115
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("expected an object envelope");
|
|
116
|
+
const result = (value as Record<string, unknown>).result;
|
|
117
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) throw invalidResponse("expected result object");
|
|
118
|
+
const results = (result as Record<string, unknown>).results;
|
|
119
|
+
if (!Array.isArray(results)) throw invalidResponse("expected result.results array");
|
|
120
|
+
return results as CeramicResult[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Ceramic errors are `{ title, status, detail, requestId, code }`; show the readable part.
|
|
124
|
+
function describeError(body: string): string {
|
|
125
|
+
try {
|
|
126
|
+
const parsed = JSON.parse(body) as Record<string, unknown>;
|
|
127
|
+
const parts = [text(parsed.title), text(parsed.detail)].filter(Boolean);
|
|
128
|
+
if (parts.length > 0) return parts.join(": ");
|
|
129
|
+
} catch {}
|
|
130
|
+
return body;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function isCeramicAvailable(): boolean {
|
|
134
|
+
return hasCredentialSource({ provider: "Ceramic", configuredValue: loadConfig().ceramicApiKey, environmentValue: process.env.CERAMIC_API_KEY });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Ceramic has no date filter and returns no dates, so `recencyFilter` is not applied.
|
|
138
|
+
export async function searchWithCeramic(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
|
|
139
|
+
const apiKey = await requireApiKey(options.signal);
|
|
140
|
+
const numResults = normalizeSearchResultCount(options.numResults);
|
|
141
|
+
const filters = parseDomainFilter(options.domainFilter);
|
|
142
|
+
// Results are reapplied to the domain filters locally, so leave a little headroom.
|
|
143
|
+
const hasDomainFilters = filters.include.length > 0 || filters.exclude.length > 0;
|
|
144
|
+
const body = {
|
|
145
|
+
query: buildQuery(query, filters),
|
|
146
|
+
maxResults: hasDomainFilters ? Math.min(MAX_REQUEST_RESULTS, numResults + 5) : numResults,
|
|
147
|
+
maxDescriptionLength: REQUEST_DESCRIPTION_CHARS,
|
|
148
|
+
};
|
|
149
|
+
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
150
|
+
const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
|
|
151
|
+
let response: Response;
|
|
152
|
+
let entries: CeramicResult[];
|
|
153
|
+
try {
|
|
154
|
+
response = await fetchWithCredentialRedirects(CERAMIC_SEARCH_URL, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
Accept: "application/json",
|
|
158
|
+
"Content-Type": "application/json",
|
|
159
|
+
Authorization: `Bearer ${apiKey}`,
|
|
160
|
+
},
|
|
161
|
+
body: JSON.stringify(body),
|
|
162
|
+
signal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,
|
|
163
|
+
}, ["Authorization"]);
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
const errorText = redactCredential(describeError(await response.text()), apiKey).slice(0, 300);
|
|
166
|
+
throw new Error(`Ceramic error ${response.status}: ${errorText}`);
|
|
167
|
+
}
|
|
168
|
+
let rawData: unknown;
|
|
169
|
+
try {
|
|
170
|
+
rawData = await response.json();
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (err instanceof Error && err.name === "TimeoutError") throw err;
|
|
173
|
+
throw new Error(`Ceramic returned invalid JSON: ${errorMessage(err)}`);
|
|
174
|
+
}
|
|
175
|
+
entries = parseResponse(rawData);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (options.signal?.aborted) {
|
|
178
|
+
activityMonitor.logComplete(activityId, 0);
|
|
179
|
+
throw new Error("Aborted");
|
|
180
|
+
}
|
|
181
|
+
const message = errorMessage(err);
|
|
182
|
+
const providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === "TimeoutError");
|
|
183
|
+
const outgoing = providerTimeout
|
|
184
|
+
? new Error(`Ceramic request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)
|
|
185
|
+
: (() => {
|
|
186
|
+
const redactedMessage = redactCredential(message, apiKey);
|
|
187
|
+
if (redactedMessage === message && err instanceof Error) return err;
|
|
188
|
+
const redactedError = new Error(redactedMessage);
|
|
189
|
+
if (err instanceof Error) redactedError.name = err.name;
|
|
190
|
+
return redactedError;
|
|
191
|
+
})();
|
|
192
|
+
activityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));
|
|
193
|
+
throw outgoing;
|
|
194
|
+
}
|
|
195
|
+
activityMonitor.logComplete(activityId, response.status);
|
|
196
|
+
const results: SearchResponse["results"] = [];
|
|
197
|
+
const seen = new Set<string>();
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
if (!entry || typeof entry !== "object") continue;
|
|
200
|
+
if (typeof entry.url !== "string" || !entry.url) continue;
|
|
201
|
+
let resultUrl: URL;
|
|
202
|
+
try {
|
|
203
|
+
resultUrl = new URL(entry.url);
|
|
204
|
+
} catch {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (resultUrl.protocol !== "http:" && resultUrl.protocol !== "https:") continue;
|
|
208
|
+
if (!passesDomainFilters(resultUrl, filters)) continue;
|
|
209
|
+
if (seen.has(resultUrl.href)) continue;
|
|
210
|
+
seen.add(resultUrl.href);
|
|
211
|
+
results.push({
|
|
212
|
+
title: text(entry.title) || `Source ${results.length + 1}`,
|
|
213
|
+
url: resultUrl.href,
|
|
214
|
+
snippet: text(entry.description).slice(0, MAX_SNIPPET_CHARS),
|
|
215
|
+
});
|
|
216
|
+
if (results.length >= numResults) break;
|
|
217
|
+
}
|
|
218
|
+
return { answer: formatSearchResultsAsAnswer(results), results };
|
|
219
|
+
}
|
|
@@ -52,6 +52,7 @@ function buildProviderButtons(
|
|
|
52
52
|
{ value: "zai", label: "Z.ai", available: available.zai },
|
|
53
53
|
{ value: "valyu", label: "Valyu", available: available.valyu },
|
|
54
54
|
{ value: "keenable", label: "Keenable", available: available.keenable },
|
|
55
|
+
{ value: "ceramic", label: "Ceramic", available: available.ceramic },
|
|
55
56
|
];
|
|
56
57
|
|
|
57
58
|
return providers
|
|
@@ -1478,7 +1479,7 @@ const SCRIPT = `(function() {
|
|
|
1478
1479
|
var token = DATA.sessionToken;
|
|
1479
1480
|
var timeoutSec = DATA.timeout;
|
|
1480
1481
|
var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
|
|
1481
|
-
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "degoog", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "baizhi", "you", "zai", "keenable"];
|
|
1482
|
+
var providers = ["auto", "all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "degoog", "duckduckgo", "perplexity", "gemini", "kimi", "anysearch", "xcrawl", "xai", "mistral", "brightdata", "serpbase", "serpapi", "serper", "serply", "valyu", "baizhi", "you", "zai", "keenable", "ceramic"];
|
|
1482
1483
|
var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
|
|
1483
1484
|
var workflow = "summary-review";
|
|
1484
1485
|
var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
|
|
@@ -1719,6 +1720,7 @@ const SCRIPT = `(function() {
|
|
|
1719
1720
|
if (provider === "zai") return "Z.ai";
|
|
1720
1721
|
if (provider === "valyu") return "Valyu";
|
|
1721
1722
|
if (provider === "keenable") return "Keenable";
|
|
1723
|
+
if (provider === "ceramic") return "Ceramic";
|
|
1722
1724
|
return "Unknown";
|
|
1723
1725
|
}
|
|
1724
1726
|
|
|
@@ -311,6 +311,7 @@ export function startCuratorServer(
|
|
|
311
311
|
if (provider === "zai") return availableProviders.zai;
|
|
312
312
|
if (provider === "valyu") return availableProviders.valyu;
|
|
313
313
|
if (provider === "keenable") return availableProviders.keenable;
|
|
314
|
+
if (provider === "ceramic") return availableProviders.ceramic;
|
|
314
315
|
return false;
|
|
315
316
|
}
|
|
316
317
|
|