chainlesschain 0.162.34 → 0.162.36
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BYfi9NYS.js → AIOps-vAVAFNJ4.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BiS_tAN7.js → ActionButton-BnRHFCKM.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-jiWl_p-B.js → Analytics-BOjwqWqG.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-m4sIzDot.js → AppLayout-Dc0D1Txn.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-CPla3Erm.js → Audit-dd_2efaZ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BGeQzTaB.js → Backup-HF1jgm8G.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DTf7Z1iU.js → BaseInput-CCtzmoKe.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DPTlQlD-.js → Chat-BNfH1c3p.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BgRXce4e.js → ChatBubbleRenderer-DCWFqmI4.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DY-XuQMu.js → Checkbox-BOr-NscK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B6oxPiZI.js → Codegen-DE058N7-.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Dqxb4wSE.js → Col-SOREo1XE.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DCIX514p.js → Community-sOvNZo9f.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGtCzDoJ.js → Compact-DnBe558D.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-zcOYd55o.js → Compliance-o-r6CUbg.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DVTtdIdM.js → Cowork-D6_k9mHP.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-CPUaR69k.js → Cron-CEV3Xkrm.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DnjUS6QH.js → Crosschain-eJ1lQWKU.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Dnz8VDmx.js → DID-B-WqM9Hp.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CtWf27j7.js → Dashboard-ZnKPcsHN.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B4GC1ZV4.js → Dropdown-B8uLWDIP.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-wjij3kzr.js → EmailListRenderer-Jmj2Y7aH.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-rS-2W4u5.js → FamilyGuardDashboard-Cb2xetG-.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-90p5Tnoz.js → Federation-C_07GXoq.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cnrw7gzq.js → FormItemContext-D3kbYrMU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C85NsWa3.js → GenericCardRenderer-9xgqvGPg.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BFAVM9F8.js → Git-BlwWlMMB.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DBoRonpq.js → Governance-DxN3wQZ_.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DHRyD66j.js → Inference-ls7pSw_D.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CTvUKecD.js → KnowledgeGraph-_n9hYuPI.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CB0dv_Ts.js → Logs-CvEVY5TK.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CN7Hm5Uw.js → Marketplace-C3qvQJT7.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-q5H25_8L.js → McpTools-DiwKpnKx.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BCV3pZ1d.js → Memory-CIBPi_da.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C04Mngt4.js → MobileBridge-D-v0Se8y.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-cP1apTQD.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-ByAMz2DN.js → Mtc-BMFWrI65.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-B7V7byJq.js → MtcAudit-2s8LaHtR.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DtKmcVQV.js → Multisig-dL_nvj7d.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CaMbT5SC.js → NLProgramming-BbrJp06R.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DRjbSTCU.js → Notes-jR9irwy3.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-B9YbJID5.js → NotificationSettings-Dk-STCIX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BcI_-vGS.js → OrderTableRenderer-CqqfY6zq.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-oTask4BE.js → Organization-BCK5jylo.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Bab06ey7.js → Overflow-BRAY7Smt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P--wlBeU0N.js → P2P-BltVRGjb.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-D4t77Pwc.js → PdhVaultBrowser-CV8UbXHe.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B3sf6CJ3.js → Permissions-_tNl47Qh.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BXOojk63.js → PersonalDataHub-Cgc4HjpX.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DReqtBFN.js → Pipeline-Bn_QU4mu.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-cT1GwKLx.js → Privacy-jzJowp5P.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BhTAzVhH.js → ProjectInit-B_1pJ8qd.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CK-D8Fyj.js → ProjectSettings-CPVZpXzs.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CbHiwen6.js → Projects-CQsHOWnT.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B-ftiXa8.js → Providers-CzzMiLC0.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CT5XPwTF.js → QuickAsk-MxBKIn9o.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CohhlBZ_.js → Recommend-D8lN6Lis.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-CrgbixFz.js → Reputation-CfYK-IrV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ClExmBn3.js → Row-Bg7NZDP9.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-VV0qizCJ.js → RssFeed-BOVNJhj0.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CqJapSiL.js → Search-B38qzmhY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DY66Zie6.js → Security-CjqleZpe.js} +4 -4
- package/src/assets/web-panel/assets/{Services-RQwxat7-.js → Services-Bu9JSJap.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-0v37UTU_.js → Skeleton-B2RvRkaX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B4Vm4DxN.js → Skills-_h42mxMN.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CggphTlo.js → Sla-BssLs56D.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BAOU08C7.js → SpeechSettings-DCxFYHsd.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DmtC4J1w.js → SyncSettings-D2xQuNLE.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DhpOGOlo.js +1 -0
- package/src/assets/web-panel/assets/{Templates-C1QK0YoU.js → Templates-CYG-R-aS.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CieOfmqp.js → Tenant-BQRYLsvP.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DWdhrxRq.js → Terminal-imKU7N5j.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CjFVUUDU.js → TimelineRenderer-BIZzBftk.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bwbk3id9.js → Tokens-uMLH5p_a.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-uJle_yj4.js → Trigger-BzS6XPqx.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BcOuxAA5.js → Trust-R4zhHufZ.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DUu7Ufg6.js → UkeySign-DATQCoGe.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Ck8JtQ2n.js → VideoEditing-ClUmKOtS.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B3jw43on.js → Wallet-DzJTbQzD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Baf9K0y7.js → WebAuthn-CrXrLmzQ.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CTEDl_83.js → WorkflowEditor-CpvZ0Tma.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CKV51quV.js → chat-a6wpYmVL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BO_RP_yz.js → colors-CXJADb1t.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BZsxw_ZG.js → compact-item-CL2pohS_.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CAbvtzVL.js → createContext-xFi_1G5_.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BtmELbtB.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-QmHT8zDz.js → hasIn-Bchh1rAi.js} +1 -1
- package/src/assets/web-panel/assets/{index-fnDgExTu.js → index-B3Tpv7-d.js} +1 -1
- package/src/assets/web-panel/assets/index-B4l4vLTB.js +1 -0
- package/src/assets/web-panel/assets/{index-BEJa1FiF.js → index-B4zNisy9.js} +1 -1
- package/src/assets/web-panel/assets/{index-jd2r-T4p.js → index-B6NehWty.js} +1 -1
- package/src/assets/web-panel/assets/index-B7Ek5iiY.js +1 -0
- package/src/assets/web-panel/assets/{index-BPZHeug4.js → index-B7knYOpm.js} +1 -1
- package/src/assets/web-panel/assets/{index-GPY0LjCu.js → index-B7wT5VRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKnngF_f.js → index-BF4xx1_b.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRNYA0BV.js → index-BH9t10pe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKquNxL2.js → index-BPH5ESqs.js} +3 -3
- package/src/assets/web-panel/assets/{index-CEh2Ry_A.js → index-BmsIKzyu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dob6B6qS.js → index-BoaRB-4a.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ha2_56mf.js → index-BrbJBnT-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dln_vjSY.js → index-C2eMYASq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqiKnXtL.js → index-C4yBRKT4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3fwyCjJ.js → index-CGq4HQno.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2aiE8jk.js → index-CMybtJY6.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5zhcul9.js → index-CR3kFPuC.js} +1 -1
- package/src/assets/web-panel/assets/{index-8BMLlHCv.js → index-CTRd7vkq.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6i3reUS.js → index-CdU8BwRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNvTNZ1V.js → index-Cua_P8St.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjrDGJP2.js → index-CuehgDOp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCsZiq4i.js → index-D-TT9Swq.js} +1 -1
- package/src/assets/web-panel/assets/{index-qPafbZmr.js → index-DEYcLAl7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeC7lehI.js → index-DQ_hw_5P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnPBG3Tr.js → index-DTEu7TSF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8CHQnPl.js → index-DVo1GJoj.js} +1 -1
- package/src/assets/web-panel/assets/{index-9IqJODII.js → index-DjdOL159.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBCYOypV.js → index-DsbMVBj1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BL7gQAuB.js → index-DxahxRP7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC1CFfQU.js → index-EPERz4Pu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVoYeZ5Q.js → index-IkvkNxbc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsBx0u5G.js → index-KCib1PTw.js} +1 -1
- package/src/assets/web-panel/assets/{index-5hlO2-JQ.js → index-M8SZI11a.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSaI8R_7.js → index-TxbHusq2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6AA-xB2.js → index-dsLc7t6W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRK0oAV5.js → index-jMcv1u5o.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9Z83FTS.js → index-majCS3s2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3K1eHDd.js → index-u8K1y_lh.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Bc2GWeWe.js → initDefaultProps-DYn3Gc09.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BI-Rxw6o.js → motion-ZS3eolb9.js} +1 -1
- package/src/assets/web-panel/assets/{move-DRPdwDQB.js → move-CEw4uqr3.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B4XTl3jW.js → omit-DlHFZnPp.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Do5d86Wr.js → pickAttrs-eZQvV5fA.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B8VGZ0ZF.js → placementArrow-B31jQwa-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cf0kI_vN.js → responsiveObserve-DAsNmVto.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cb0psjSL.js → slide-gPQPrYZC.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bjuo5Oal.js → statusUtils-DwWKX5co.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BLMhoHJ5.js → styleChecker-B3VOtXuH.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BdCwAfNU.js → useFlexGapSupport-6ADctM2r.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-9Jhaz5gG.js → useFs-6Zx1SSKs.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-xYFyXKwD.js → usePersonalDataHub-BzReowln.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVhepE6Z.js → vnode-C8IpEQbD.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-IbbtJ4Zr.js → zoom-ruc9vHr0.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +161 -6
- package/src/commands/agents.js +199 -0
- package/src/commands/command.js +7 -2
- package/src/commands/hook.js +136 -28
- package/src/commands/ide.js +168 -0
- package/src/commands/mcp.js +92 -0
- package/src/commands/output-style.js +127 -0
- package/src/commands/permissions.js +211 -0
- package/src/commands/statusline.js +93 -0
- package/src/index.js +8 -0
- package/src/lib/agent-core.js +7 -0
- package/src/lib/agents.js +147 -0
- package/src/lib/hook-manager.js +1 -0
- package/src/lib/hook-runner.cjs +183 -0
- package/src/lib/ide-bridge.js +310 -0
- package/src/lib/image-input.js +156 -0
- package/src/lib/mcp-oauth.js +415 -0
- package/src/lib/output-styles.js +179 -0
- package/src/lib/permission-rules.cjs +325 -0
- package/src/lib/provider-options.js +11 -7
- package/src/lib/settings-hook-events.cjs +102 -0
- package/src/lib/settings-hooks.cjs +163 -0
- package/src/lib/settings-loader.cjs +244 -0
- package/src/lib/slash-commands.js +21 -13
- package/src/lib/status-line.cjs +204 -0
- package/src/lib/sub-agent-profiles.js +3 -0
- package/src/lib/web-search.js +487 -0
- package/src/repl/agent-repl.js +445 -35
- package/src/repl/slash-macro.js +45 -0
- package/src/runtime/agent-core.js +799 -21
- package/src/runtime/coding-agent-contract-shared.cjs +94 -4
- package/src/runtime/coding-agent-policy.cjs +24 -0
- package/src/runtime/headless-runner.js +162 -6
- package/src/runtime/headless-stream.js +133 -7
- package/src/runtime/mcp-config.js +161 -15
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/runtime/system-prompt.js +6 -1
- package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CExqxzL6.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +0 -1
- package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +0 -1
- package/src/assets/web-panel/assets/index-CZZnSJEX.js +0 -1
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Search — agent-safe web search with pluggable backends.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of web-fetch.js: a small, dependency-free HTTP client that fans a
|
|
5
|
+
* query out to a search provider and returns normalized results
|
|
6
|
+
* ({ title, url, snippet }) plus an optional synthesized `answer`.
|
|
7
|
+
*
|
|
8
|
+
* The provider is pluggable via .chainlesschain/config.json:webSearch (or env
|
|
9
|
+
* keys). Default `provider: "auto"` picks whichever API key is configured —
|
|
10
|
+
* tavily > brave > bocha — and falls back to keyless DuckDuckGo when none is.
|
|
11
|
+
* This keeps the search source a config knob, not a one-way code decision.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import http from "http";
|
|
15
|
+
import https from "https";
|
|
16
|
+
import { URL } from "url";
|
|
17
|
+
|
|
18
|
+
const DEFAULT_MAX_BYTES = 2_000_000;
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
20
|
+
const DEFAULT_MAX_RESULTS = 8;
|
|
21
|
+
|
|
22
|
+
const KEYED_PROVIDERS = ["tavily", "brave", "bocha", "qianfan"];
|
|
23
|
+
const KEYLESS_PROVIDERS = ["duckduckgo", "searxng", "baidu"];
|
|
24
|
+
|
|
25
|
+
// Baidu Qianfan (千帆) AI Search — retrieval endpoint, overridable via config.
|
|
26
|
+
const DEFAULT_QIANFAN_URL = "https://qianfan.baidubce.com/v2/ai_search";
|
|
27
|
+
export const SUPPORTED_PROVIDERS = [...KEYED_PROVIDERS, ...KEYLESS_PROVIDERS];
|
|
28
|
+
|
|
29
|
+
// A realistic desktop-Chrome UA — Baidu (and some others) serve a stripped /
|
|
30
|
+
// verification page to non-browser agents.
|
|
31
|
+
const BROWSER_UA =
|
|
32
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
|
33
|
+
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
34
|
+
|
|
35
|
+
/** Resolve the API key for a keyed provider from options → config → env. */
|
|
36
|
+
export function resolveApiKey(provider, options = {}, config = {}) {
|
|
37
|
+
const env = process.env;
|
|
38
|
+
switch (provider) {
|
|
39
|
+
case "tavily":
|
|
40
|
+
return (
|
|
41
|
+
options.apiKey || config.tavilyApiKey || config.apiKey || env.TAVILY_API_KEY || ""
|
|
42
|
+
);
|
|
43
|
+
case "brave":
|
|
44
|
+
return (
|
|
45
|
+
options.apiKey ||
|
|
46
|
+
config.braveApiKey ||
|
|
47
|
+
config.apiKey ||
|
|
48
|
+
env.BRAVE_API_KEY ||
|
|
49
|
+
env.BRAVE_SEARCH_API_KEY ||
|
|
50
|
+
""
|
|
51
|
+
);
|
|
52
|
+
case "bocha":
|
|
53
|
+
return (
|
|
54
|
+
options.apiKey || config.bochaApiKey || config.apiKey || env.BOCHA_API_KEY || ""
|
|
55
|
+
);
|
|
56
|
+
case "qianfan":
|
|
57
|
+
return (
|
|
58
|
+
options.apiKey ||
|
|
59
|
+
config.qianfanApiKey ||
|
|
60
|
+
config.apiKey ||
|
|
61
|
+
env.QIANFAN_API_KEY ||
|
|
62
|
+
""
|
|
63
|
+
);
|
|
64
|
+
default:
|
|
65
|
+
return "";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Decide which provider to use. An explicit (non-auto) provider is honored
|
|
71
|
+
* as-is; "auto"/empty picks the first keyed provider with a usable key, else
|
|
72
|
+
* the keyless DuckDuckGo fallback.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveProvider(options = {}, config = {}) {
|
|
75
|
+
const requested = String(
|
|
76
|
+
options.provider || config.provider || "auto",
|
|
77
|
+
).toLowerCase();
|
|
78
|
+
if (requested && requested !== "auto") return requested;
|
|
79
|
+
for (const p of KEYED_PROVIDERS) {
|
|
80
|
+
if (resolveApiKey(p, options, config)) return p;
|
|
81
|
+
}
|
|
82
|
+
return "duckduckgo";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function _request(urlStr, { method = "GET", headers = {}, body = null, timeout, maxBytes }) {
|
|
86
|
+
const parsed = new URL(urlStr);
|
|
87
|
+
const lib = parsed.protocol === "https:" ? _deps.https : _deps.http;
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const req = lib.request(
|
|
90
|
+
{
|
|
91
|
+
method,
|
|
92
|
+
protocol: parsed.protocol,
|
|
93
|
+
hostname: parsed.hostname,
|
|
94
|
+
port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
|
|
95
|
+
path: parsed.pathname + parsed.search,
|
|
96
|
+
headers: {
|
|
97
|
+
"User-Agent": "ChainlessChain-Agent/1.0",
|
|
98
|
+
Accept: "*/*",
|
|
99
|
+
...headers,
|
|
100
|
+
},
|
|
101
|
+
timeout,
|
|
102
|
+
},
|
|
103
|
+
(res) => {
|
|
104
|
+
const chunks = [];
|
|
105
|
+
let size = 0;
|
|
106
|
+
res.on("data", (chunk) => {
|
|
107
|
+
size += chunk.length;
|
|
108
|
+
if (size > maxBytes) {
|
|
109
|
+
req.destroy(new Error(`response exceeds maxBytes (${maxBytes})`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
chunks.push(chunk);
|
|
113
|
+
});
|
|
114
|
+
res.on("end", () =>
|
|
115
|
+
resolve({
|
|
116
|
+
statusCode: res.statusCode,
|
|
117
|
+
headers: res.headers,
|
|
118
|
+
body: Buffer.concat(chunks).toString("utf8"),
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
res.on("error", reject);
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
req.on("error", reject);
|
|
125
|
+
req.on("timeout", () => req.destroy(new Error("request timeout")));
|
|
126
|
+
if (body != null) req.write(body);
|
|
127
|
+
req.end();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _stripTags(html) {
|
|
132
|
+
return String(html).replace(/<[^>]+>/g, "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function _decodeEntities(s) {
|
|
136
|
+
return String(s)
|
|
137
|
+
.replace(/ /g, " ")
|
|
138
|
+
.replace(/'/g, "'")
|
|
139
|
+
.replace(/'/g, "'")
|
|
140
|
+
.replace(/"/g, '"')
|
|
141
|
+
.replace(/</g, "<")
|
|
142
|
+
.replace(/>/g, ">")
|
|
143
|
+
.replace(/&/g, "&");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function _clean(s) {
|
|
147
|
+
return _decodeEntities(_stripTags(s)).replace(/\s+/g, " ").trim();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---- Provider adapters: each returns { results: [{title,url,snippet}], answer } ----
|
|
151
|
+
|
|
152
|
+
async function _searchTavily(query, { apiKey, maxResults, timeout, maxBytes }) {
|
|
153
|
+
if (!apiKey) return { error: "tavily: missing API key" };
|
|
154
|
+
const res = await _request("https://api.tavily.com/search", {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "application/json",
|
|
158
|
+
Authorization: `Bearer ${apiKey}`,
|
|
159
|
+
},
|
|
160
|
+
body: JSON.stringify({
|
|
161
|
+
query,
|
|
162
|
+
max_results: maxResults,
|
|
163
|
+
include_answer: true,
|
|
164
|
+
search_depth: "basic",
|
|
165
|
+
}),
|
|
166
|
+
timeout,
|
|
167
|
+
maxBytes,
|
|
168
|
+
});
|
|
169
|
+
if (res.statusCode >= 400) {
|
|
170
|
+
return { error: `tavily HTTP ${res.statusCode}: ${res.body.slice(0, 200)}` };
|
|
171
|
+
}
|
|
172
|
+
let json;
|
|
173
|
+
try {
|
|
174
|
+
json = JSON.parse(res.body);
|
|
175
|
+
} catch {
|
|
176
|
+
return { error: "tavily: invalid JSON response" };
|
|
177
|
+
}
|
|
178
|
+
const results = (json.results || []).slice(0, maxResults).map((r) => ({
|
|
179
|
+
title: r.title || "",
|
|
180
|
+
url: r.url || "",
|
|
181
|
+
snippet: _clean(r.content || ""),
|
|
182
|
+
}));
|
|
183
|
+
return { results, answer: json.answer || "" };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function _searchBrave(query, { apiKey, maxResults, timeout, maxBytes }) {
|
|
187
|
+
if (!apiKey) return { error: "brave: missing API key" };
|
|
188
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${maxResults}`;
|
|
189
|
+
const res = await _request(url, {
|
|
190
|
+
method: "GET",
|
|
191
|
+
headers: { Accept: "application/json", "X-Subscription-Token": apiKey },
|
|
192
|
+
timeout,
|
|
193
|
+
maxBytes,
|
|
194
|
+
});
|
|
195
|
+
if (res.statusCode >= 400) {
|
|
196
|
+
return { error: `brave HTTP ${res.statusCode}: ${res.body.slice(0, 200)}` };
|
|
197
|
+
}
|
|
198
|
+
let json;
|
|
199
|
+
try {
|
|
200
|
+
json = JSON.parse(res.body);
|
|
201
|
+
} catch {
|
|
202
|
+
return { error: "brave: invalid JSON response" };
|
|
203
|
+
}
|
|
204
|
+
const results = ((json.web && json.web.results) || [])
|
|
205
|
+
.slice(0, maxResults)
|
|
206
|
+
.map((r) => ({
|
|
207
|
+
title: _clean(r.title || ""),
|
|
208
|
+
url: r.url || "",
|
|
209
|
+
snippet: _clean(r.description || ""),
|
|
210
|
+
}));
|
|
211
|
+
return { results, answer: "" };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function _searchBocha(query, { apiKey, maxResults, timeout, maxBytes }) {
|
|
215
|
+
if (!apiKey) return { error: "bocha: missing API key" };
|
|
216
|
+
const res = await _request("https://api.bochaai.com/v1/web-search", {
|
|
217
|
+
method: "POST",
|
|
218
|
+
headers: {
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
Authorization: `Bearer ${apiKey}`,
|
|
221
|
+
},
|
|
222
|
+
body: JSON.stringify({ query, count: maxResults, summary: true }),
|
|
223
|
+
timeout,
|
|
224
|
+
maxBytes,
|
|
225
|
+
});
|
|
226
|
+
if (res.statusCode >= 400) {
|
|
227
|
+
return { error: `bocha HTTP ${res.statusCode}: ${res.body.slice(0, 200)}` };
|
|
228
|
+
}
|
|
229
|
+
let json;
|
|
230
|
+
try {
|
|
231
|
+
json = JSON.parse(res.body);
|
|
232
|
+
} catch {
|
|
233
|
+
return { error: "bocha: invalid JSON response" };
|
|
234
|
+
}
|
|
235
|
+
// Bing-style schema: data.webPages.value[]
|
|
236
|
+
const pages =
|
|
237
|
+
(json.data && json.data.webPages && json.data.webPages.value) || [];
|
|
238
|
+
const results = pages.slice(0, maxResults).map((r) => ({
|
|
239
|
+
title: _clean(r.name || ""),
|
|
240
|
+
url: r.url || "",
|
|
241
|
+
snippet: _clean(r.summary || r.snippet || ""),
|
|
242
|
+
}));
|
|
243
|
+
return { results, answer: "" };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function _searchDuckDuckGo(query, { maxResults, timeout, maxBytes }) {
|
|
247
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
|
|
248
|
+
const res = await _request(url, {
|
|
249
|
+
method: "GET",
|
|
250
|
+
headers: { Accept: "text/html" },
|
|
251
|
+
timeout,
|
|
252
|
+
maxBytes,
|
|
253
|
+
});
|
|
254
|
+
if (res.statusCode >= 400) {
|
|
255
|
+
return { error: `duckduckgo HTTP ${res.statusCode}` };
|
|
256
|
+
}
|
|
257
|
+
const html = res.body;
|
|
258
|
+
const results = [];
|
|
259
|
+
const anchorRe =
|
|
260
|
+
/<a[^>]+class="[^"]*result__a[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
261
|
+
const snippetRe =
|
|
262
|
+
/<a[^>]+class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
263
|
+
const snippets = [];
|
|
264
|
+
let sm;
|
|
265
|
+
while ((sm = snippetRe.exec(html)) !== null) snippets.push(_clean(sm[1]));
|
|
266
|
+
let m;
|
|
267
|
+
let i = 0;
|
|
268
|
+
while ((m = anchorRe.exec(html)) !== null && results.length < maxResults) {
|
|
269
|
+
let href = m[1];
|
|
270
|
+
// DDG wraps targets as //duckduckgo.com/l/?uddg=<encoded-url>
|
|
271
|
+
const uddg = href.match(/[?&]uddg=([^&]+)/);
|
|
272
|
+
if (uddg) {
|
|
273
|
+
try {
|
|
274
|
+
href = decodeURIComponent(uddg[1]);
|
|
275
|
+
} catch {
|
|
276
|
+
/* keep raw href */
|
|
277
|
+
}
|
|
278
|
+
} else if (href.startsWith("//")) {
|
|
279
|
+
href = "https:" + href;
|
|
280
|
+
}
|
|
281
|
+
results.push({
|
|
282
|
+
title: _clean(m[2]),
|
|
283
|
+
url: href,
|
|
284
|
+
snippet: snippets[i] || "",
|
|
285
|
+
});
|
|
286
|
+
i++;
|
|
287
|
+
}
|
|
288
|
+
return { results, answer: "" };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function _searchQianfan(
|
|
292
|
+
query,
|
|
293
|
+
{ apiKey, endpoint, maxResults, timeout, maxBytes },
|
|
294
|
+
) {
|
|
295
|
+
if (!apiKey) return { error: "qianfan: missing API key" };
|
|
296
|
+
const res = await _request(endpoint || DEFAULT_QIANFAN_URL, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: {
|
|
299
|
+
"Content-Type": "application/json",
|
|
300
|
+
Authorization: `Bearer ${apiKey}`,
|
|
301
|
+
},
|
|
302
|
+
body: JSON.stringify({
|
|
303
|
+
messages: [{ role: "user", content: query }],
|
|
304
|
+
search_source: "baidu_search_v2",
|
|
305
|
+
resource_type_filter: [{ type: "web", top_k: maxResults }],
|
|
306
|
+
}),
|
|
307
|
+
timeout,
|
|
308
|
+
maxBytes,
|
|
309
|
+
});
|
|
310
|
+
if (res.statusCode >= 400) {
|
|
311
|
+
return {
|
|
312
|
+
error: `qianfan HTTP ${res.statusCode}: ${res.body.slice(0, 200)}`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
let json;
|
|
316
|
+
try {
|
|
317
|
+
json = JSON.parse(res.body);
|
|
318
|
+
} catch {
|
|
319
|
+
return { error: "qianfan: invalid JSON response" };
|
|
320
|
+
}
|
|
321
|
+
// AI Search retrieval returns `references`; the chat form may also carry an
|
|
322
|
+
// answer. Tolerate either shape.
|
|
323
|
+
const refs = json.references || json.data?.references || [];
|
|
324
|
+
const results = refs.slice(0, maxResults).map((r) => ({
|
|
325
|
+
title: _clean(r.title || r.web_anchor || ""),
|
|
326
|
+
url: r.url || "",
|
|
327
|
+
snippet: _clean(r.content || r.web_anchor || ""),
|
|
328
|
+
}));
|
|
329
|
+
const answer =
|
|
330
|
+
json.answer ||
|
|
331
|
+
json.choices?.[0]?.message?.content ||
|
|
332
|
+
"";
|
|
333
|
+
return { results, answer: typeof answer === "string" ? answer : "" };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function _searchBaidu(query, { maxResults, timeout, maxBytes }) {
|
|
337
|
+
const url = `https://www.baidu.com/s?wd=${encodeURIComponent(query)}&rn=${Math.max(maxResults, 10)}`;
|
|
338
|
+
const res = await _request(url, {
|
|
339
|
+
method: "GET",
|
|
340
|
+
headers: { "User-Agent": BROWSER_UA, "Accept-Language": "zh-CN,zh;q=0.9" },
|
|
341
|
+
timeout,
|
|
342
|
+
maxBytes,
|
|
343
|
+
});
|
|
344
|
+
if (res.statusCode >= 400) return { error: `baidu HTTP ${res.statusCode}` };
|
|
345
|
+
const html = res.body;
|
|
346
|
+
// Baidu aggressively rate-limits non-browser traffic: it 302s to a captcha
|
|
347
|
+
// (wappass) or serves a verification page. Surface that distinctly so the
|
|
348
|
+
// caller can back off / switch provider rather than seeing "no results".
|
|
349
|
+
if (
|
|
350
|
+
(res.statusCode >= 300 && res.statusCode < 400) ||
|
|
351
|
+
/wappass|captcha|安全验证|请输入验证码|antirobot/.test(html)
|
|
352
|
+
) {
|
|
353
|
+
return {
|
|
354
|
+
error:
|
|
355
|
+
"baidu: rate-limited / captcha challenge — retry later or use a keyed provider",
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// Baidu shows a verification/security page (no result containers) to bots.
|
|
359
|
+
if (!/c-container|class="t"/.test(html)) {
|
|
360
|
+
return { error: "baidu: no results (blocked or markup changed)" };
|
|
361
|
+
}
|
|
362
|
+
// Markup-agnostic: anchor each result on its <h3 class="t|c-title"> heading,
|
|
363
|
+
// pull the URL from the anchor inside it (a baidu.com/link?url= redirect that
|
|
364
|
+
// web_fetch resolves), and take the text up to the next heading as snippet.
|
|
365
|
+
const h3re = /<h3[^>]*class="[^"]*\b(?:t|c-title)\b[^"]*"[^>]*>([\s\S]*?)<\/h3>/gi;
|
|
366
|
+
const marks = [];
|
|
367
|
+
let m;
|
|
368
|
+
while ((m = h3re.exec(html)) !== null) {
|
|
369
|
+
marks.push({ inner: m[1], start: m.index, end: h3re.lastIndex });
|
|
370
|
+
}
|
|
371
|
+
const results = [];
|
|
372
|
+
for (let i = 0; i < marks.length && results.length < maxResults; i++) {
|
|
373
|
+
const title = _clean(marks[i].inner);
|
|
374
|
+
if (!title) continue;
|
|
375
|
+
const href = (marks[i].inner.match(/href="([^"]+)"/) || [])[1] || "";
|
|
376
|
+
const segEnd =
|
|
377
|
+
i + 1 < marks.length ? marks[i + 1].start : marks[i].end + 2000;
|
|
378
|
+
const snippet = _clean(html.slice(marks[i].end, segEnd)).slice(0, 200);
|
|
379
|
+
results.push({ title, url: href, snippet });
|
|
380
|
+
}
|
|
381
|
+
return { results, answer: "" };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function _searchSearxng(query, { instanceUrl, maxResults, timeout, maxBytes }) {
|
|
385
|
+
if (!instanceUrl) return { error: "searxng: missing instanceUrl" };
|
|
386
|
+
const base = instanceUrl.replace(/\/+$/, "");
|
|
387
|
+
const url = `${base}/search?q=${encodeURIComponent(query)}&format=json`;
|
|
388
|
+
const res = await _request(url, {
|
|
389
|
+
method: "GET",
|
|
390
|
+
headers: { Accept: "application/json" },
|
|
391
|
+
timeout,
|
|
392
|
+
maxBytes,
|
|
393
|
+
});
|
|
394
|
+
if (res.statusCode >= 400) {
|
|
395
|
+
return { error: `searxng HTTP ${res.statusCode}` };
|
|
396
|
+
}
|
|
397
|
+
let json;
|
|
398
|
+
try {
|
|
399
|
+
json = JSON.parse(res.body);
|
|
400
|
+
} catch {
|
|
401
|
+
return { error: "searxng: invalid JSON response (is json format enabled?)" };
|
|
402
|
+
}
|
|
403
|
+
const results = (json.results || []).slice(0, maxResults).map((r) => ({
|
|
404
|
+
title: _clean(r.title || ""),
|
|
405
|
+
url: r.url || "",
|
|
406
|
+
snippet: _clean(r.content || ""),
|
|
407
|
+
}));
|
|
408
|
+
return { results, answer: json.answer || "" };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Run a web search. Returns { query, provider, count, results, answer } or
|
|
413
|
+
* { error }. Never throws for provider/HTTP failures — surfaces them as errors
|
|
414
|
+
* so the agent can adapt.
|
|
415
|
+
*/
|
|
416
|
+
export async function webSearch(query, options = {}) {
|
|
417
|
+
const q = String(query || "").trim();
|
|
418
|
+
if (!q) return { error: "web_search: empty query" };
|
|
419
|
+
|
|
420
|
+
const config = options.config || {};
|
|
421
|
+
const provider = resolveProvider(options, config);
|
|
422
|
+
const maxResults =
|
|
423
|
+
Number(options.maxResults) ||
|
|
424
|
+
Number(config.maxResults) ||
|
|
425
|
+
DEFAULT_MAX_RESULTS;
|
|
426
|
+
const timeout = Number(options.timeout) || DEFAULT_TIMEOUT_MS;
|
|
427
|
+
const maxBytes = Number(options.maxBytes) || DEFAULT_MAX_BYTES;
|
|
428
|
+
|
|
429
|
+
if (!SUPPORTED_PROVIDERS.includes(provider)) {
|
|
430
|
+
return {
|
|
431
|
+
error: `web_search: unsupported provider "${provider}" (supported: ${SUPPORTED_PROVIDERS.join(", ")})`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const apiKey = resolveApiKey(provider, options, config);
|
|
436
|
+
const common = { apiKey, maxResults, timeout, maxBytes };
|
|
437
|
+
|
|
438
|
+
let out;
|
|
439
|
+
try {
|
|
440
|
+
switch (provider) {
|
|
441
|
+
case "tavily":
|
|
442
|
+
out = await _searchTavily(q, common);
|
|
443
|
+
break;
|
|
444
|
+
case "brave":
|
|
445
|
+
out = await _searchBrave(q, common);
|
|
446
|
+
break;
|
|
447
|
+
case "bocha":
|
|
448
|
+
out = await _searchBocha(q, common);
|
|
449
|
+
break;
|
|
450
|
+
case "qianfan":
|
|
451
|
+
out = await _searchQianfan(q, {
|
|
452
|
+
...common,
|
|
453
|
+
endpoint: options.qianfanUrl || config.qianfanUrl,
|
|
454
|
+
});
|
|
455
|
+
break;
|
|
456
|
+
case "searxng":
|
|
457
|
+
out = await _searchSearxng(q, {
|
|
458
|
+
instanceUrl: options.instanceUrl || config.instanceUrl,
|
|
459
|
+
maxResults,
|
|
460
|
+
timeout,
|
|
461
|
+
maxBytes,
|
|
462
|
+
});
|
|
463
|
+
break;
|
|
464
|
+
case "baidu":
|
|
465
|
+
out = await _searchBaidu(q, { maxResults, timeout, maxBytes });
|
|
466
|
+
break;
|
|
467
|
+
case "duckduckgo":
|
|
468
|
+
default:
|
|
469
|
+
out = await _searchDuckDuckGo(q, { maxResults, timeout, maxBytes });
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
} catch (err) {
|
|
473
|
+
return { error: `web_search (${provider}) failed: ${err.message}`, provider };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (out && out.error) return { ...out, provider };
|
|
477
|
+
const results = (out && out.results) || [];
|
|
478
|
+
return {
|
|
479
|
+
query: q,
|
|
480
|
+
provider,
|
|
481
|
+
count: results.length,
|
|
482
|
+
results,
|
|
483
|
+
answer: (out && out.answer) || "",
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export const _deps = { http, https };
|