@yourgpt/copilot-sdk 2.0.1 → 2.0.2-beta.2
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 +42 -0
- package/dist/{ThreadManager-JT0sqSSD.d.ts → ThreadManager-Dkp_eLty.d.ts} +1 -1
- package/dist/{ThreadManager-CUq5Ocu2.d.cts → ThreadManager-LfFRhr4e.d.cts} +1 -1
- package/dist/anthropic-6F5GRE3B.js +4 -0
- package/dist/anthropic-6F5GRE3B.js.map +1 -0
- package/dist/anthropic-DGalr_Fw.d.cts +17 -0
- package/dist/anthropic-DkCEDYOt.d.ts +17 -0
- package/dist/anthropic-NMTRABEH.cjs +21 -0
- package/dist/anthropic-NMTRABEH.cjs.map +1 -0
- package/dist/brave-DdnWb7Gb.d.cts +17 -0
- package/dist/brave-DsI9n7Wr.d.ts +17 -0
- package/dist/brave-OYKCOZEM.cjs +21 -0
- package/dist/brave-OYKCOZEM.cjs.map +1 -0
- package/dist/brave-XSASGGH2.js +4 -0
- package/dist/brave-XSASGGH2.js.map +1 -0
- package/dist/chunk-2FAWEBZS.cjs +88 -0
- package/dist/chunk-2FAWEBZS.cjs.map +1 -0
- package/dist/chunk-53UGJNHN.js +92 -0
- package/dist/chunk-53UGJNHN.js.map +1 -0
- package/dist/chunk-6T5XXJEP.cjs +80 -0
- package/dist/chunk-6T5XXJEP.cjs.map +1 -0
- package/dist/chunk-7K7HZMP4.cjs +1170 -0
- package/dist/chunk-7K7HZMP4.cjs.map +1 -0
- package/dist/chunk-7W7QLZNC.js +72 -0
- package/dist/chunk-7W7QLZNC.js.map +1 -0
- package/dist/{chunk-JM7PB2LP.js → chunk-7XFFRV7D.js} +10 -66
- package/dist/chunk-7XFFRV7D.js.map +1 -0
- package/dist/chunk-ASV6JLYG.cjs +99 -0
- package/dist/chunk-ASV6JLYG.cjs.map +1 -0
- package/dist/chunk-BH7MNDWW.js +1152 -0
- package/dist/chunk-BH7MNDWW.js.map +1 -0
- package/dist/chunk-BKO7DSPU.js +67 -0
- package/dist/chunk-BKO7DSPU.js.map +1 -0
- package/dist/chunk-CBAHCI4R.cjs +76 -0
- package/dist/chunk-CBAHCI4R.cjs.map +1 -0
- package/dist/chunk-CEKAYA2Q.cjs +74 -0
- package/dist/chunk-CEKAYA2Q.cjs.map +1 -0
- package/dist/chunk-CEOMTQTP.js +85 -0
- package/dist/chunk-CEOMTQTP.js.map +1 -0
- package/dist/chunk-DABZYCVX.js +84 -0
- package/dist/chunk-DABZYCVX.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +10 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-G4SF2PNQ.js +33 -0
- package/dist/chunk-G4SF2PNQ.js.map +1 -0
- package/dist/chunk-GANCV72Z.cjs +110 -0
- package/dist/chunk-GANCV72Z.cjs.map +1 -0
- package/dist/{chunk-BLSI67J6.cjs → chunk-H5XMKBBA.cjs} +425 -30
- package/dist/chunk-H5XMKBBA.cjs.map +1 -0
- package/dist/{chunk-CJ7UWN2Y.js → chunk-IXFV6AW6.js} +397 -7
- package/dist/chunk-IXFV6AW6.js.map +1 -0
- package/dist/chunk-JEQ2X3Z6.cjs +12 -0
- package/dist/chunk-JEQ2X3Z6.cjs.map +1 -0
- package/dist/chunk-JO4BHPAD.cjs +40 -0
- package/dist/chunk-JO4BHPAD.cjs.map +1 -0
- package/dist/chunk-MEBXW75C.cjs +89 -0
- package/dist/chunk-MEBXW75C.cjs.map +1 -0
- package/dist/chunk-MNDGIW47.js +76 -0
- package/dist/chunk-MNDGIW47.js.map +1 -0
- package/dist/chunk-PPFHA6IL.js +83 -0
- package/dist/chunk-PPFHA6IL.js.map +1 -0
- package/dist/chunk-RQ74USYU.js +128 -0
- package/dist/chunk-RQ74USYU.js.map +1 -0
- package/dist/chunk-TXLIY7GF.cjs +132 -0
- package/dist/chunk-TXLIY7GF.cjs.map +1 -0
- package/dist/chunk-UIWFYMAO.cjs +82 -0
- package/dist/chunk-UIWFYMAO.cjs.map +1 -0
- package/dist/{chunk-4PRWNAXQ.cjs → chunk-UOWLKFXK.cjs} +27 -89
- package/dist/chunk-UOWLKFXK.cjs.map +1 -0
- package/dist/chunk-VD74IPKB.js +106 -0
- package/dist/chunk-VD74IPKB.js.map +1 -0
- package/dist/chunk-W73FBYIH.cjs +87 -0
- package/dist/chunk-W73FBYIH.cjs.map +1 -0
- package/dist/chunk-XGITAEXU.js +93 -0
- package/dist/chunk-XGITAEXU.js.map +1 -0
- package/dist/chunk-XWOHNY3F.cjs +96 -0
- package/dist/chunk-XWOHNY3F.cjs.map +1 -0
- package/dist/chunk-ZPYQDMUX.js +79 -0
- package/dist/chunk-ZPYQDMUX.js.map +1 -0
- package/dist/core/index.cjs +156 -84
- package/dist/core/index.d.cts +16 -4
- package/dist/core/index.d.ts +16 -4
- package/dist/core/index.js +13 -1
- package/dist/exa-72KFY5A7.cjs +21 -0
- package/dist/exa-72KFY5A7.cjs.map +1 -0
- package/dist/exa-Dp9U-WTc.d.ts +17 -0
- package/dist/exa-NNVPBC2M.js +4 -0
- package/dist/exa-NNVPBC2M.js.map +1 -0
- package/dist/exa-jJSPhyUW.d.cts +17 -0
- package/dist/google-CHU2yycE.d.cts +17 -0
- package/dist/google-CTEK6SV2.js +4 -0
- package/dist/google-CTEK6SV2.js.map +1 -0
- package/dist/google-Da8IQxaI.d.ts +17 -0
- package/dist/google-IIUXFFVF.cjs +21 -0
- package/dist/google-IIUXFFVF.cjs.map +1 -0
- package/dist/index-2VtgKM8S.d.cts +206 -0
- package/dist/index-pWEH7pUE.d.ts +206 -0
- package/dist/mcp/index.cjs +670 -0
- package/dist/mcp/index.cjs.map +1 -0
- package/dist/mcp/index.d.cts +779 -0
- package/dist/mcp/index.d.ts +779 -0
- package/dist/mcp/index.js +574 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/openai-6KTCQ7PZ.cjs +21 -0
- package/dist/openai-6KTCQ7PZ.cjs.map +1 -0
- package/dist/openai-7W2PCNW5.js +4 -0
- package/dist/openai-7W2PCNW5.js.map +1 -0
- package/dist/openai-Cam8hF4f.d.ts +17 -0
- package/dist/openai-HVSCuXgO.d.cts +17 -0
- package/dist/react/index.cjs +75 -42
- package/dist/react/index.d.cts +270 -45
- package/dist/react/index.d.ts +270 -45
- package/dist/react/index.js +15 -2
- package/dist/searxng-AXLVGY7Z.js +4 -0
- package/dist/searxng-AXLVGY7Z.js.map +1 -0
- package/dist/searxng-EJKNY236.cjs +21 -0
- package/dist/searxng-EJKNY236.cjs.map +1 -0
- package/dist/searxng-K0qtY9vp.d.ts +17 -0
- package/dist/searxng-QGOte_Gq.d.cts +17 -0
- package/dist/serper-3JYJHJX6.js +4 -0
- package/dist/serper-3JYJHJX6.js.map +1 -0
- package/dist/serper-63FT4AOL.cjs +21 -0
- package/dist/serper-63FT4AOL.cjs.map +1 -0
- package/dist/serper-7Czya3PW.d.ts +17 -0
- package/dist/serper-JzdaSnS9.d.cts +17 -0
- package/dist/styles.css +38 -0
- package/dist/tavily-AWFP4RM7.cjs +21 -0
- package/dist/tavily-AWFP4RM7.cjs.map +1 -0
- package/dist/tavily-C8cXXojE.d.cts +17 -0
- package/dist/tavily-CIWAAZPH.js +4 -0
- package/dist/tavily-CIWAAZPH.js.map +1 -0
- package/dist/tavily-DdSGVgkE.d.ts +17 -0
- package/dist/themes/catppuccin.css +2 -0
- package/dist/themes/claude.css +2 -0
- package/dist/themes/linear.css +2 -0
- package/dist/themes/modern-minimal.css +2 -0
- package/dist/themes/posthog.css +2 -0
- package/dist/themes/supabase.css +2 -0
- package/dist/themes/twitter.css +2 -0
- package/dist/themes/vercel.css +2 -0
- package/dist/tools/anthropic/index.cjs +61 -0
- package/dist/tools/anthropic/index.cjs.map +1 -0
- package/dist/tools/anthropic/index.d.cts +67 -0
- package/dist/tools/anthropic/index.d.ts +67 -0
- package/dist/tools/anthropic/index.js +56 -0
- package/dist/tools/anthropic/index.js.map +1 -0
- package/dist/tools/brave/index.cjs +85 -0
- package/dist/tools/brave/index.cjs.map +1 -0
- package/dist/tools/brave/index.d.cts +91 -0
- package/dist/tools/brave/index.d.ts +91 -0
- package/dist/tools/brave/index.js +80 -0
- package/dist/tools/brave/index.js.map +1 -0
- package/dist/tools/exa/index.cjs +90 -0
- package/dist/tools/exa/index.cjs.map +1 -0
- package/dist/tools/exa/index.d.cts +92 -0
- package/dist/tools/exa/index.d.ts +92 -0
- package/dist/tools/exa/index.js +85 -0
- package/dist/tools/exa/index.js.map +1 -0
- package/dist/tools/google/index.cjs +81 -0
- package/dist/tools/google/index.cjs.map +1 -0
- package/dist/tools/google/index.d.cts +81 -0
- package/dist/tools/google/index.d.ts +81 -0
- package/dist/tools/google/index.js +76 -0
- package/dist/tools/google/index.js.map +1 -0
- package/dist/tools/openai/index.cjs +83 -0
- package/dist/tools/openai/index.cjs.map +1 -0
- package/dist/tools/openai/index.d.cts +84 -0
- package/dist/tools/openai/index.d.ts +84 -0
- package/dist/tools/openai/index.js +78 -0
- package/dist/tools/openai/index.js.map +1 -0
- package/dist/tools/searxng/index.cjs +85 -0
- package/dist/tools/searxng/index.cjs.map +1 -0
- package/dist/tools/searxng/index.d.cts +91 -0
- package/dist/tools/searxng/index.d.ts +91 -0
- package/dist/tools/searxng/index.js +80 -0
- package/dist/tools/searxng/index.js.map +1 -0
- package/dist/tools/serper/index.cjs +85 -0
- package/dist/tools/serper/index.cjs.map +1 -0
- package/dist/tools/serper/index.d.cts +91 -0
- package/dist/tools/serper/index.d.ts +91 -0
- package/dist/tools/serper/index.js +80 -0
- package/dist/tools/serper/index.js.map +1 -0
- package/dist/tools/tavily/index.cjs +91 -0
- package/dist/tools/tavily/index.cjs.map +1 -0
- package/dist/tools/tavily/index.d.cts +95 -0
- package/dist/tools/tavily/index.d.ts +95 -0
- package/dist/tools/tavily/index.js +86 -0
- package/dist/tools/tavily/index.js.map +1 -0
- package/dist/tools/web-search/index.cjs +31 -0
- package/dist/tools/web-search/index.cjs.map +1 -0
- package/dist/tools/web-search/index.d.cts +3 -0
- package/dist/tools/web-search/index.d.ts +3 -0
- package/dist/tools/web-search/index.js +14 -0
- package/dist/tools/web-search/index.js.map +1 -0
- package/dist/{types-BtAaOV07.d.cts → tools-DDWrco4h.d.cts} +43 -367
- package/dist/{types-BtAaOV07.d.ts → tools-DDWrco4h.d.ts} +43 -367
- package/dist/types-B20VCJXL.d.cts +347 -0
- package/dist/types-B20VCJXL.d.ts +347 -0
- package/dist/types-Cizh9K_f.d.ts +441 -0
- package/dist/types-DG2ya08y.d.cts +367 -0
- package/dist/types-DG2ya08y.d.ts +367 -0
- package/dist/types-DjSfYNKj.d.cts +441 -0
- package/dist/types-ZguuKEs_.d.cts +127 -0
- package/dist/types-ZguuKEs_.d.ts +127 -0
- package/dist/ui/index.cjs +1075 -148
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +410 -4
- package/dist/ui/index.d.ts +410 -4
- package/dist/ui/index.js +1007 -96
- package/dist/ui/index.js.map +1 -1
- package/package.json +52 -2
- package/dist/chunk-4PRWNAXQ.cjs.map +0 -1
- package/dist/chunk-BLSI67J6.cjs.map +0 -1
- package/dist/chunk-CJ7UWN2Y.js.map +0 -1
- package/dist/chunk-JM7PB2LP.js.map +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/core/tools/webSearch/providers/serper.ts
|
|
2
|
+
var SERPER_API_URL = "https://google.serper.dev/search";
|
|
3
|
+
function validateSerperConfig(config) {
|
|
4
|
+
if (!config.apiKey) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"Serper API key is required. Get one at https://serper.dev/"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
async function searchSerper(params, config) {
|
|
11
|
+
validateSerperConfig(config);
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
const requestBody = {
|
|
14
|
+
q: params.query,
|
|
15
|
+
num: params.maxResults ?? config.maxResults ?? 5
|
|
16
|
+
};
|
|
17
|
+
if (config.country) {
|
|
18
|
+
requestBody.gl = config.country;
|
|
19
|
+
}
|
|
20
|
+
if (config.language) {
|
|
21
|
+
requestBody.hl = config.language;
|
|
22
|
+
}
|
|
23
|
+
const response = await fetch(SERPER_API_URL, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
"X-API-KEY": config.apiKey
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify(requestBody),
|
|
30
|
+
signal: config.timeout ? AbortSignal.timeout(config.timeout) : void 0
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
34
|
+
throw new Error(`Serper API error (${response.status}): ${errorText}`);
|
|
35
|
+
}
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
const searchTime = Date.now() - startTime;
|
|
38
|
+
let results = data.organic || [];
|
|
39
|
+
if (config.includeDomains?.length) {
|
|
40
|
+
results = results.filter(
|
|
41
|
+
(r) => config.includeDomains.some((domain) => r.link.includes(domain))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (config.excludeDomains?.length) {
|
|
45
|
+
results = results.filter(
|
|
46
|
+
(r) => !config.excludeDomains.some((domain) => r.link.includes(domain))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const answer = data.answerBox?.answer || data.answerBox?.snippet;
|
|
50
|
+
return {
|
|
51
|
+
query: params.query,
|
|
52
|
+
answer,
|
|
53
|
+
results: results.map((result) => ({
|
|
54
|
+
title: result.title,
|
|
55
|
+
url: result.link,
|
|
56
|
+
content: result.snippet,
|
|
57
|
+
publishedDate: result.date,
|
|
58
|
+
image: result.imageUrl,
|
|
59
|
+
domain: extractDomain(result.link)
|
|
60
|
+
})),
|
|
61
|
+
images: data.images?.map((img) => ({
|
|
62
|
+
url: img.imageUrl,
|
|
63
|
+
description: img.title,
|
|
64
|
+
sourceUrl: img.link
|
|
65
|
+
})),
|
|
66
|
+
provider: "serper",
|
|
67
|
+
totalResults: results.length,
|
|
68
|
+
searchTime
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function extractDomain(url) {
|
|
72
|
+
try {
|
|
73
|
+
return new URL(url).hostname;
|
|
74
|
+
} catch {
|
|
75
|
+
return url;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
var serperProvider = {
|
|
79
|
+
search: searchSerper,
|
|
80
|
+
validateConfig: validateSerperConfig
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export { searchSerper, serperProvider, validateSerperConfig };
|
|
84
|
+
//# sourceMappingURL=chunk-CEOMTQTP.js.map
|
|
85
|
+
//# sourceMappingURL=chunk-CEOMTQTP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/tools/webSearch/providers/serper.ts"],"names":[],"mappings":"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","file":"chunk-CEOMTQTP.js","sourcesContent":["/**\n * Serper Search Provider\n *\n * Serper is a Google Search API that provides SERP data.\n * Fast and cost-effective for real-time Google results.\n *\n * @see https://serper.dev/\n */\n\nimport type {\n WebSearchConfig,\n WebSearchParams,\n WebSearchResponse,\n SerperApiResponse,\n WebSearchProviderInterface,\n} from \"../types\";\n\nconst SERPER_API_URL = \"https://google.serper.dev/search\";\n\n/**\n * Validate Serper configuration\n */\nexport function validateSerperConfig(config: WebSearchConfig): void {\n if (!config.apiKey) {\n throw new Error(\n \"Serper API key is required. Get one at https://serper.dev/\",\n );\n }\n}\n\n/**\n * Search using Serper API\n */\nexport async function searchSerper(\n params: WebSearchParams,\n config: WebSearchConfig,\n): Promise<WebSearchResponse> {\n validateSerperConfig(config);\n\n const startTime = Date.now();\n\n const requestBody: Record<string, unknown> = {\n q: params.query,\n num: params.maxResults ?? config.maxResults ?? 5,\n };\n\n // Add locale settings\n if (config.country) {\n requestBody.gl = config.country;\n }\n if (config.language) {\n requestBody.hl = config.language;\n }\n\n const response = await fetch(SERPER_API_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-KEY\": config.apiKey!,\n },\n body: JSON.stringify(requestBody),\n signal: config.timeout ? AbortSignal.timeout(config.timeout) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n throw new Error(`Serper API error (${response.status}): ${errorText}`);\n }\n\n const data: SerperApiResponse = await response.json();\n const searchTime = Date.now() - startTime;\n\n // Filter by domains if specified\n let results = data.organic || [];\n if (config.includeDomains?.length) {\n results = results.filter((r) =>\n config.includeDomains!.some((domain) => r.link.includes(domain)),\n );\n }\n if (config.excludeDomains?.length) {\n results = results.filter(\n (r) => !config.excludeDomains!.some((domain) => r.link.includes(domain)),\n );\n }\n\n // Extract answer from answer box if available\n const answer = data.answerBox?.answer || data.answerBox?.snippet;\n\n return {\n query: params.query,\n answer,\n results: results.map((result) => ({\n title: result.title,\n url: result.link,\n content: result.snippet,\n publishedDate: result.date,\n image: result.imageUrl,\n domain: extractDomain(result.link),\n })),\n images: data.images?.map((img) => ({\n url: img.imageUrl,\n description: img.title,\n sourceUrl: img.link,\n })),\n provider: \"serper\",\n totalResults: results.length,\n searchTime,\n };\n}\n\n/**\n * Extract domain from URL\n */\nfunction extractDomain(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Serper provider implementation\n */\nexport const serperProvider: WebSearchProviderInterface = {\n search: searchSerper,\n validateConfig: validateSerperConfig,\n};\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/core/tools/webSearch/providers/searxng.ts
|
|
2
|
+
var DEFAULT_SEARXNG_URL = "https://searxng.instance.local";
|
|
3
|
+
function validateSearxngConfig(config) {
|
|
4
|
+
if (!config.baseUrl) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"SearXNG base URL is required. Self-host SearXNG or use a public instance. See https://docs.searxng.org/ for setup instructions."
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
async function searchSearxng(params, config) {
|
|
11
|
+
validateSearxngConfig(config);
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
const baseUrl = config.baseUrl || DEFAULT_SEARXNG_URL;
|
|
14
|
+
const searchParams = new URLSearchParams({
|
|
15
|
+
q: params.query,
|
|
16
|
+
format: "json"
|
|
17
|
+
});
|
|
18
|
+
if (config.language) {
|
|
19
|
+
searchParams.set("language", config.language);
|
|
20
|
+
}
|
|
21
|
+
const url = `${baseUrl.replace(/\/$/, "")}/search?${searchParams.toString()}`;
|
|
22
|
+
const headers = {
|
|
23
|
+
Accept: "application/json"
|
|
24
|
+
};
|
|
25
|
+
if (config.apiKey) {
|
|
26
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
27
|
+
}
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method: "GET",
|
|
30
|
+
headers,
|
|
31
|
+
signal: config.timeout ? AbortSignal.timeout(config.timeout) : void 0
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
35
|
+
throw new Error(`SearXNG API error (${response.status}): ${errorText}`);
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
const searchTime = Date.now() - startTime;
|
|
39
|
+
let results = data.results || [];
|
|
40
|
+
if (config.includeDomains?.length) {
|
|
41
|
+
results = results.filter(
|
|
42
|
+
(r) => config.includeDomains.some((domain) => r.url.includes(domain))
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (config.excludeDomains?.length) {
|
|
46
|
+
results = results.filter(
|
|
47
|
+
(r) => !config.excludeDomains.some((domain) => r.url.includes(domain))
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const maxResults = params.maxResults ?? config.maxResults ?? 5;
|
|
51
|
+
results = results.slice(0, maxResults);
|
|
52
|
+
const answer = data.answers?.[0] || data.infoboxes?.[0]?.content;
|
|
53
|
+
return {
|
|
54
|
+
query: params.query,
|
|
55
|
+
answer,
|
|
56
|
+
results: results.map((result) => ({
|
|
57
|
+
title: result.title,
|
|
58
|
+
url: result.url,
|
|
59
|
+
content: result.content,
|
|
60
|
+
score: result.score,
|
|
61
|
+
publishedDate: result.publishedDate,
|
|
62
|
+
image: result.img_src,
|
|
63
|
+
domain: extractDomain(result.url)
|
|
64
|
+
})),
|
|
65
|
+
provider: "searxng",
|
|
66
|
+
totalResults: data.number_of_results,
|
|
67
|
+
searchTime
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function extractDomain(url) {
|
|
71
|
+
try {
|
|
72
|
+
return new URL(url).hostname;
|
|
73
|
+
} catch {
|
|
74
|
+
return url;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
var searxngProvider = {
|
|
78
|
+
search: searchSearxng,
|
|
79
|
+
validateConfig: validateSearxngConfig
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export { searchSearxng, searxngProvider, validateSearxngConfig };
|
|
83
|
+
//# sourceMappingURL=chunk-DABZYCVX.js.map
|
|
84
|
+
//# sourceMappingURL=chunk-DABZYCVX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/tools/webSearch/providers/searxng.ts"],"names":[],"mappings":"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","file":"chunk-DABZYCVX.js","sourcesContent":["/**\n * SearXNG Search Provider\n *\n * SearXNG is a privacy-respecting, self-hostable metasearch engine.\n * It aggregates results from multiple search engines without tracking.\n *\n * @see https://docs.searxng.org/\n */\n\nimport type {\n WebSearchConfig,\n WebSearchParams,\n WebSearchResponse,\n SearxngApiResponse,\n WebSearchProviderInterface,\n} from \"../types\";\n\n// Default public instance (users should self-host for production)\nconst DEFAULT_SEARXNG_URL = \"https://searxng.instance.local\";\n\n/**\n * Validate SearXNG configuration\n */\nexport function validateSearxngConfig(config: WebSearchConfig): void {\n if (!config.baseUrl) {\n throw new Error(\n \"SearXNG base URL is required. Self-host SearXNG or use a public instance. \" +\n \"See https://docs.searxng.org/ for setup instructions.\",\n );\n }\n}\n\n/**\n * Search using SearXNG API\n */\nexport async function searchSearxng(\n params: WebSearchParams,\n config: WebSearchConfig,\n): Promise<WebSearchResponse> {\n validateSearxngConfig(config);\n\n const startTime = Date.now();\n\n const baseUrl = config.baseUrl || DEFAULT_SEARXNG_URL;\n const searchParams = new URLSearchParams({\n q: params.query,\n format: \"json\",\n });\n\n // SearXNG doesn't have a direct max_results param, but we can limit after\n if (config.language) {\n searchParams.set(\"language\", config.language);\n }\n\n // Add specific engines if needed\n // searchParams.set('engines', 'google,duckduckgo,bing');\n\n const url = `${baseUrl.replace(/\\/$/, \"\")}/search?${searchParams.toString()}`;\n\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n };\n\n // Add API key if provided (some instances require it)\n if (config.apiKey) {\n headers[\"Authorization\"] = `Bearer ${config.apiKey}`;\n }\n\n const response = await fetch(url, {\n method: \"GET\",\n headers,\n signal: config.timeout ? AbortSignal.timeout(config.timeout) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n throw new Error(`SearXNG API error (${response.status}): ${errorText}`);\n }\n\n const data: SearxngApiResponse = await response.json();\n const searchTime = Date.now() - startTime;\n\n // Filter by domains if specified\n let results = data.results || [];\n if (config.includeDomains?.length) {\n results = results.filter((r) =>\n config.includeDomains!.some((domain) => r.url.includes(domain)),\n );\n }\n if (config.excludeDomains?.length) {\n results = results.filter(\n (r) => !config.excludeDomains!.some((domain) => r.url.includes(domain)),\n );\n }\n\n // Limit results\n const maxResults = params.maxResults ?? config.maxResults ?? 5;\n results = results.slice(0, maxResults);\n\n // Extract answer from infoboxes or answers if available\n const answer = data.answers?.[0] || data.infoboxes?.[0]?.content;\n\n return {\n query: params.query,\n answer,\n results: results.map((result) => ({\n title: result.title,\n url: result.url,\n content: result.content,\n score: result.score,\n publishedDate: result.publishedDate,\n image: result.img_src,\n domain: extractDomain(result.url),\n })),\n provider: \"searxng\",\n totalResults: data.number_of_results,\n searchTime,\n };\n}\n\n/**\n * Extract domain from URL\n */\nfunction extractDomain(url: string): string {\n try {\n return new URL(url).hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * SearXNG provider implementation\n */\nexport const searxngProvider: WebSearchProviderInterface = {\n search: searchSearxng,\n validateConfig: validateSearxngConfig,\n};\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export { __require };
|
|
9
|
+
//# sourceMappingURL=chunk-DGUM43GV.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-DGUM43GV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-DGUM43GV.js"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// src/mcp/ui/types.ts
|
|
2
|
+
function isMCPUIResourceContent(content) {
|
|
3
|
+
return typeof content === "object" && content !== null && "type" in content && content.type === "ui" && "resource" in content;
|
|
4
|
+
}
|
|
5
|
+
function isMCPUIIntent(data) {
|
|
6
|
+
if (typeof data !== "object" || data === null) return false;
|
|
7
|
+
const intent = data;
|
|
8
|
+
return intent.type === "tool" || intent.type === "intent" || intent.type === "prompt" || intent.type === "notify" || intent.type === "link";
|
|
9
|
+
}
|
|
10
|
+
function isMCPUIMessage(data) {
|
|
11
|
+
if (typeof data !== "object" || data === null) return false;
|
|
12
|
+
const message = data;
|
|
13
|
+
if (message.source === "mcp-ui" && message.intent) {
|
|
14
|
+
return isMCPUIIntent(message.intent);
|
|
15
|
+
}
|
|
16
|
+
return isMCPUIIntent(data);
|
|
17
|
+
}
|
|
18
|
+
function parseMCPUIMessage(data) {
|
|
19
|
+
if (typeof data === "object" && data !== null && "source" in data && data.source === "mcp-ui") {
|
|
20
|
+
const message = data;
|
|
21
|
+
return isMCPUIIntent(message.intent) ? message.intent : null;
|
|
22
|
+
}
|
|
23
|
+
if (isMCPUIIntent(data)) {
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
var DEFAULT_MCP_UI_SANDBOX = "allow-scripts allow-forms";
|
|
29
|
+
var RESTRICTED_MCP_UI_SANDBOX = "allow-scripts";
|
|
30
|
+
|
|
31
|
+
export { DEFAULT_MCP_UI_SANDBOX, RESTRICTED_MCP_UI_SANDBOX, isMCPUIIntent, isMCPUIMessage, isMCPUIResourceContent, parseMCPUIMessage };
|
|
32
|
+
//# sourceMappingURL=chunk-G4SF2PNQ.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-G4SF2PNQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/ui/types.ts"],"names":[],"mappings":";AA6YO,SAAS,uBACd,OAAA,EACiC;AACjC,EAAA,OACE,OAAO,OAAA,KAAY,QAAA,IACnB,OAAA,KAAY,IAAA,IACZ,UAAU,OAAA,IACT,OAAA,CAAiC,IAAA,KAAS,IAAA,IAC3C,UAAA,IAAc,OAAA;AAElB;AAKO,SAAS,cAAc,IAAA,EAAoC;AAChE,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,MAAM,OAAO,KAAA;AAEtD,EAAA,MAAM,MAAA,GAAS,IAAA;AACf,EAAA,OACE,MAAA,CAAO,IAAA,KAAS,MAAA,IAChB,MAAA,CAAO,IAAA,KAAS,QAAA,IAChB,MAAA,CAAO,IAAA,KAAS,QAAA,IAChB,MAAA,CAAO,IAAA,KAAS,QAAA,IAChB,OAAO,IAAA,KAAS,MAAA;AAEpB;AAKO,SAAS,eAAe,IAAA,EAAqC;AAClE,EAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,MAAM,OAAO,KAAA;AAEtD,EAAA,MAAM,OAAA,GAAU,IAAA;AAEhB,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,QAAA,IAAY,OAAA,CAAQ,MAAA,EAAQ;AACjD,IAAA,OAAO,aAAA,CAAc,QAAQ,MAAM,CAAA;AAAA,EACrC;AAEA,EAAA,OAAO,cAAc,IAAI,CAAA;AAC3B;AAMO,SAAS,kBAAkB,IAAA,EAAmC;AAEnE,EAAA,IACE,OAAO,SAAS,QAAA,IAChB,IAAA,KAAS,QACT,QAAA,IAAY,IAAA,IACX,IAAA,CAAsB,MAAA,KAAW,QAAA,EAClC;AACA,IAAA,MAAM,OAAA,GAAU,IAAA;AAChB,IAAA,OAAO,aAAA,CAAc,OAAA,CAAQ,MAAM,CAAA,GAAI,QAAQ,MAAA,GAAS,IAAA;AAAA,EAC1D;AAGA,EAAA,IAAI,aAAA,CAAc,IAAI,CAAA,EAAG;AACvB,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAA;AACT;AAMO,IAAM,sBAAA,GAAyB;AAM/B,IAAM,yBAAA,GAA4B","file":"chunk-G4SF2PNQ.js","sourcesContent":["/**\n * MCP-UI Type Definitions\n *\n * This module defines types for MCP-UI support, enabling MCP tools\n * to return interactive UI components (forms, charts, product selectors, etc.)\n * that render directly in the chat interface.\n *\n * @see https://github.com/idosal/mcp-ui\n * @see https://shopify.engineering/mcp-ui-breaking-the-text-wall\n */\n\n// ============================================\n// UI Resource Types\n// ============================================\n\n/**\n * MCP UI Resource MIME types\n *\n * - `text/html`: Inline HTML content rendered in iframe\n * - `text/uri-list`: External URL to load in iframe\n * - `application/vnd.mcp-ui.remote-dom`: Remote DOM rendering (advanced)\n */\nexport type MCPUIResourceMimeType =\n | \"text/html\"\n | \"text/uri-list\"\n | \"application/vnd.mcp-ui.remote-dom\";\n\n/**\n * MCP UI Resource metadata\n *\n * Optional configuration for how the UI should be displayed.\n */\nexport interface MCPUIResourceMetadata {\n /** Title shown above the UI component */\n title?: string;\n /** Width of the iframe (e.g., \"100%\", \"400px\") */\n width?: string;\n /** Height of the iframe (e.g., \"200px\", \"auto\") */\n height?: string;\n /**\n * Custom iframe sandbox permissions\n * @default [\"allow-scripts\", \"allow-forms\"]\n */\n sandbox?: string[];\n /** Additional CSS classes for styling */\n className?: string;\n}\n\n/**\n * MCP UI Resource\n *\n * Represents a UI component returned by an MCP tool.\n * Can be inline HTML, an external URL, or Remote DOM content.\n *\n * @example\n * ```typescript\n * // Inline HTML\n * const resource: MCPUIResource = {\n * uri: \"ui://shop/product/123\",\n * mimeType: \"text/html\",\n * content: \"<div class='product-card'>...</div>\",\n * metadata: { height: \"300px\" }\n * };\n *\n * // External URL\n * const urlResource: MCPUIResource = {\n * uri: \"ui://dashboard/chart\",\n * mimeType: \"text/uri-list\",\n * content: \"https://charts.example.com/embed/abc123\",\n * metadata: { height: \"400px\", title: \"Sales Chart\" }\n * };\n * ```\n */\nexport interface MCPUIResource {\n /** Unique URI identifying this UI resource (e.g., ui://server/resource-id) */\n uri: string;\n /** MIME type determining how to render the content */\n mimeType: MCPUIResourceMimeType;\n /** Content: inline HTML for text/html, URL for text/uri-list */\n content?: string;\n /** Base64-encoded content (alternative to content) */\n blob?: string;\n /** Display configuration */\n metadata?: MCPUIResourceMetadata;\n}\n\n/**\n * MCP UI Resource content type for tool results\n *\n * This is the content array item format returned by MCP tools.\n *\n * @example\n * ```typescript\n * // MCP tool returning UI\n * return {\n * content: [\n * { type: \"text\", text: \"Here's the product:\" },\n * {\n * type: \"ui\",\n * resource: {\n * uri: \"ui://shop/product/123\",\n * mimeType: \"text/html\",\n * content: \"<div>...</div>\"\n * }\n * }\n * ]\n * };\n * ```\n */\nexport interface MCPUIResourceContent {\n type: \"ui\";\n resource: MCPUIResource;\n}\n\n// ============================================\n// Intent Types (UI → Host Communication)\n// ============================================\n\n/**\n * Tool intent - Request to call another MCP tool\n *\n * @example\n * ```typescript\n * // Button click triggers tool call\n * window.parent.postMessage({\n * type: \"tool\",\n * name: \"add_to_cart\",\n * arguments: { productId: \"123\", quantity: 1 }\n * }, \"*\");\n * ```\n */\nexport interface MCPUIToolIntent {\n type: \"tool\";\n /** Name of the tool to call */\n name: string;\n /** Arguments to pass to the tool */\n arguments?: Record<string, unknown>;\n}\n\n/**\n * Action intent - Semantic action for the agent to interpret\n *\n * Unlike tool intents, these don't directly call tools but let the\n * AI agent decide how to handle the action.\n *\n * @example\n * ```typescript\n * // User selects a product variant\n * window.parent.postMessage({\n * type: \"intent\",\n * action: \"select_variant\",\n * data: { variantId: \"blue-xl\", productId: \"123\" }\n * }, \"*\");\n * ```\n */\nexport interface MCPUIActionIntent {\n type: \"intent\";\n /** Semantic action name */\n action: string;\n /** Action data */\n data?: Record<string, unknown>;\n}\n\n/**\n * Prompt intent - Add text to the chat input\n *\n * @example\n * ```typescript\n * // \"Ask about this\" button\n * window.parent.postMessage({\n * type: \"prompt\",\n * text: \"Tell me more about product X\"\n * }, \"*\");\n * ```\n */\nexport interface MCPUIPromptIntent {\n type: \"prompt\";\n /** Text to add to chat input */\n text: string;\n}\n\n/**\n * Notify intent - Show a notification/toast\n *\n * @example\n * ```typescript\n * // Show success message\n * window.parent.postMessage({\n * type: \"notify\",\n * message: \"Added to cart!\",\n * level: \"success\"\n * }, \"*\");\n * ```\n */\nexport interface MCPUINotifyIntent {\n type: \"notify\";\n /** Notification message */\n message: string;\n /** Notification level */\n level?: \"info\" | \"success\" | \"warning\" | \"error\";\n}\n\n/**\n * Link intent - Open a URL (with user consent)\n *\n * @example\n * ```typescript\n * // \"View full details\" button\n * window.parent.postMessage({\n * type: \"link\",\n * url: \"https://shop.example.com/product/123\"\n * }, \"*\");\n * ```\n */\nexport interface MCPUILinkIntent {\n type: \"link\";\n /** URL to open */\n url: string;\n /** Open in new tab (default: true) */\n newTab?: boolean;\n}\n\n/**\n * Union of all MCP-UI intent types\n *\n * These are messages sent from the UI iframe to the host application\n * via postMessage.\n */\nexport type MCPUIIntent =\n | MCPUIToolIntent\n | MCPUIActionIntent\n | MCPUIPromptIntent\n | MCPUINotifyIntent\n | MCPUILinkIntent;\n\n// ============================================\n// Message Types (for postMessage)\n// ============================================\n\n/**\n * Message envelope for postMessage communication\n *\n * All messages from UI iframes should include a source identifier\n * for validation.\n */\nexport interface MCPUIMessage {\n /** Source identifier for validation */\n source?: \"mcp-ui\";\n /** The intent payload */\n intent: MCPUIIntent;\n}\n\n// ============================================\n// Intent Handler Types\n// ============================================\n\n/**\n * Intent handler callback signature\n */\nexport type MCPUIIntentHandler<T extends MCPUIIntent = MCPUIIntent> = (\n intent: T,\n context?: MCPUIIntentContext,\n) => void | Promise<void>;\n\n/**\n * Context passed to intent handlers\n */\nexport interface MCPUIIntentContext {\n /** Source tool name that rendered the UI */\n toolName?: string;\n /** Tool call ID */\n toolCallId?: string;\n /** Original UI resource */\n resource?: MCPUIResource;\n}\n\n/**\n * Configuration for useMCPUIIntents hook\n */\nexport interface UseMCPUIIntentsConfig {\n /**\n * Handler for tool intents - call another MCP tool\n */\n onToolCall?: (\n name: string,\n args?: Record<string, unknown>,\n context?: MCPUIIntentContext,\n ) => void | Promise<void>;\n\n /**\n * Handler for action intents - semantic actions for agent interpretation\n */\n onIntent?: (\n action: string,\n data?: Record<string, unknown>,\n context?: MCPUIIntentContext,\n ) => void | Promise<void>;\n\n /**\n * Handler for prompt intents - add text to chat input\n */\n onPrompt?: (text: string, context?: MCPUIIntentContext) => void;\n\n /**\n * Handler for notify intents - show notifications\n */\n onNotify?: (\n message: string,\n level?: \"info\" | \"success\" | \"warning\" | \"error\",\n context?: MCPUIIntentContext,\n ) => void;\n\n /**\n * Handler for link intents - open URLs\n * Return false to prevent default behavior\n */\n onLink?: (\n url: string,\n newTab?: boolean,\n context?: MCPUIIntentContext,\n ) => boolean | void;\n\n /**\n * Whether to require user consent for sensitive intents\n * @default true for \"tool\" and \"link\" intents\n */\n requireConsent?: {\n tool?: boolean;\n link?: boolean;\n };\n}\n\n/**\n * Return type for useMCPUIIntents hook\n */\nexport interface UseMCPUIIntentsReturn {\n /**\n * Handle an intent from a UI component\n * This should be passed to MCPUIFrame's onIntent prop\n */\n handleIntent: (\n intent: MCPUIIntent,\n context?: MCPUIIntentContext,\n ) => void | Promise<void>;\n}\n\n// ============================================\n// Component Props Types\n// ============================================\n\n/**\n * Props for MCPUIFrame component\n */\nexport interface MCPUIFrameProps {\n /** UI resource to render */\n resource: MCPUIResource;\n /**\n * Callback when UI emits an intent\n * @param intent The intent from the UI\n */\n onIntent?: (intent: MCPUIIntent) => void;\n /**\n * Callback when an error occurs\n * @param error The error\n */\n onError?: (error: Error) => void;\n /**\n * Callback when iframe loads\n */\n onLoad?: () => void;\n /** Additional CSS class names */\n className?: string;\n /** Inline styles */\n style?: React.CSSProperties;\n /**\n * Override default sandbox permissions\n * @default \"allow-scripts allow-forms\"\n */\n sandbox?: string;\n /**\n * Whether to show a loading state while iframe loads\n * @default true\n */\n showLoading?: boolean;\n /**\n * Test ID for testing\n */\n testId?: string;\n}\n\n// ============================================\n// Utility Types\n// ============================================\n\n/**\n * Type guard for MCPUIResourceContent\n */\nexport function isMCPUIResourceContent(\n content: unknown,\n): content is MCPUIResourceContent {\n return (\n typeof content === \"object\" &&\n content !== null &&\n \"type\" in content &&\n (content as MCPUIResourceContent).type === \"ui\" &&\n \"resource\" in content\n );\n}\n\n/**\n * Type guard for MCPUIIntent\n */\nexport function isMCPUIIntent(data: unknown): data is MCPUIIntent {\n if (typeof data !== \"object\" || data === null) return false;\n\n const intent = data as { type?: string };\n return (\n intent.type === \"tool\" ||\n intent.type === \"intent\" ||\n intent.type === \"prompt\" ||\n intent.type === \"notify\" ||\n intent.type === \"link\"\n );\n}\n\n/**\n * Type guard for MCPUIMessage (postMessage envelope)\n */\nexport function isMCPUIMessage(data: unknown): data is MCPUIMessage {\n if (typeof data !== \"object\" || data === null) return false;\n\n const message = data as MCPUIMessage;\n // Accept messages with source: \"mcp-ui\" OR direct intents\n if (message.source === \"mcp-ui\" && message.intent) {\n return isMCPUIIntent(message.intent);\n }\n // Also accept direct intents without envelope\n return isMCPUIIntent(data);\n}\n\n/**\n * Parse a postMessage event data to MCPUIIntent\n * Returns null if the data is not a valid MCP-UI message\n */\nexport function parseMCPUIMessage(data: unknown): MCPUIIntent | null {\n // Check for envelope format\n if (\n typeof data === \"object\" &&\n data !== null &&\n \"source\" in data &&\n (data as MCPUIMessage).source === \"mcp-ui\"\n ) {\n const message = data as MCPUIMessage;\n return isMCPUIIntent(message.intent) ? message.intent : null;\n }\n\n // Check for direct intent format\n if (isMCPUIIntent(data)) {\n return data;\n }\n\n return null;\n}\n\n/**\n * Default iframe sandbox permissions\n * Provides a secure default that allows interactivity\n */\nexport const DEFAULT_MCP_UI_SANDBOX = \"allow-scripts allow-forms\";\n\n/**\n * Restricted sandbox for untrusted content\n * More secure but limits functionality\n */\nexport const RESTRICTED_MCP_UI_SANDBOX = \"allow-scripts\";\n"]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/tools/webSearch/providers/openai.ts
|
|
4
|
+
function validateOpenAIConfig(config) {
|
|
5
|
+
if (!config.apiKey) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
"OpenAI API key is required for native web search. Pass apiKey or set OPENAI_API_KEY environment variable."
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
async function searchOpenAI(params, config) {
|
|
12
|
+
validateOpenAIConfig(config);
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
|
|
15
|
+
const tools = [
|
|
16
|
+
{
|
|
17
|
+
type: "web_search",
|
|
18
|
+
// Domain filtering if provided
|
|
19
|
+
...config.includeDomains?.length && {
|
|
20
|
+
filters: {
|
|
21
|
+
domains: config.includeDomains
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
const response = await fetch("https://api.openai.com/v1/responses", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
Authorization: `Bearer ${apiKey}`
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
model: "gpt-4o",
|
|
34
|
+
// Use GPT-4o for web search
|
|
35
|
+
tools,
|
|
36
|
+
input: params.query
|
|
37
|
+
}),
|
|
38
|
+
signal: config.timeout ? AbortSignal.timeout(config.timeout) : void 0
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const errorText = await response.text().catch(() => "Unknown error");
|
|
42
|
+
console.error(
|
|
43
|
+
"[OpenAI Native Search] API error:",
|
|
44
|
+
response.status,
|
|
45
|
+
errorText
|
|
46
|
+
);
|
|
47
|
+
throw new Error(
|
|
48
|
+
`OpenAI Responses API error (${response.status}): ${errorText}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
const searchTime = Date.now() - startTime;
|
|
53
|
+
let outputText = "";
|
|
54
|
+
const sources = [];
|
|
55
|
+
if (data.output && Array.isArray(data.output)) {
|
|
56
|
+
for (const item of data.output) {
|
|
57
|
+
if (item.type === "message" && item.content) {
|
|
58
|
+
for (const contentPart of item.content) {
|
|
59
|
+
if (contentPart.type === "output_text" && contentPart.text) {
|
|
60
|
+
outputText = contentPart.text;
|
|
61
|
+
}
|
|
62
|
+
if (contentPart.annotations && Array.isArray(contentPart.annotations)) {
|
|
63
|
+
for (const annotation of contentPart.annotations) {
|
|
64
|
+
if (annotation.type === "url_citation" && annotation.url) {
|
|
65
|
+
if (!sources.find((s) => s.url === annotation.url)) {
|
|
66
|
+
sources.push({
|
|
67
|
+
url: annotation.url,
|
|
68
|
+
title: annotation.title || extractDomain(annotation.url)
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
query: params.query,
|
|
80
|
+
answer: outputText,
|
|
81
|
+
results: sources.slice(0, params.maxResults ?? config.maxResults ?? 5).map((source, i) => ({
|
|
82
|
+
title: source.title,
|
|
83
|
+
url: source.url,
|
|
84
|
+
content: "",
|
|
85
|
+
// OpenAI returns answer with inline citations, not separate snippets
|
|
86
|
+
score: 1 - i * 0.1,
|
|
87
|
+
domain: extractDomain(source.url)
|
|
88
|
+
})),
|
|
89
|
+
provider: "openai",
|
|
90
|
+
totalResults: sources.length,
|
|
91
|
+
searchTime
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function extractDomain(url) {
|
|
95
|
+
try {
|
|
96
|
+
return new URL(url).hostname.replace("www.", "");
|
|
97
|
+
} catch {
|
|
98
|
+
return url;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
var openaiProvider = {
|
|
102
|
+
search: searchOpenAI,
|
|
103
|
+
validateConfig: validateOpenAIConfig
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
exports.openaiProvider = openaiProvider;
|
|
107
|
+
exports.searchOpenAI = searchOpenAI;
|
|
108
|
+
exports.validateOpenAIConfig = validateOpenAIConfig;
|
|
109
|
+
//# sourceMappingURL=chunk-GANCV72Z.cjs.map
|
|
110
|
+
//# sourceMappingURL=chunk-GANCV72Z.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/tools/webSearch/providers/openai.ts"],"names":[],"mappings":"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","file":"chunk-GANCV72Z.cjs","sourcesContent":["/**\n * OpenAI Web Search Provider\n *\n * Uses OpenAI's built-in web_search tool via the Responses API.\n * No third-party API key required - uses your OpenAI API key.\n *\n * @see https://platform.openai.com/docs/guides/tools-web-search\n */\n\nimport type {\n WebSearchConfig,\n WebSearchParams,\n WebSearchResponse,\n WebSearchProviderInterface,\n} from \"../types\";\n\n/**\n * Validate OpenAI native search configuration\n */\nexport function validateOpenAIConfig(config: WebSearchConfig): void {\n if (!config.apiKey) {\n throw new Error(\n \"OpenAI API key is required for native web search. \" +\n \"Pass apiKey or set OPENAI_API_KEY environment variable.\",\n );\n }\n}\n\n/**\n * Search using OpenAI's native web_search tool (Responses API)\n */\nexport async function searchOpenAI(\n params: WebSearchParams,\n config: WebSearchConfig,\n): Promise<WebSearchResponse> {\n validateOpenAIConfig(config);\n\n const startTime = Date.now();\n const apiKey = config.apiKey || process.env.OPENAI_API_KEY;\n\n // Build tools array with web_search\n const tools: Array<Record<string, unknown>> = [\n {\n type: \"web_search\",\n // Domain filtering if provided\n ...(config.includeDomains?.length && {\n filters: {\n domains: config.includeDomains,\n },\n }),\n },\n ];\n\n // Call OpenAI Responses API\n const response = await fetch(\"https://api.openai.com/v1/responses\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({\n model: \"gpt-4o\", // Use GPT-4o for web search\n tools,\n input: params.query,\n }),\n signal: config.timeout ? AbortSignal.timeout(config.timeout) : undefined,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n console.error(\n \"[OpenAI Native Search] API error:\",\n response.status,\n errorText,\n );\n throw new Error(\n `OpenAI Responses API error (${response.status}): ${errorText}`,\n );\n }\n\n const data = await response.json();\n const searchTime = Date.now() - startTime;\n\n // Extract answer text and annotations from the message output\n let outputText = \"\";\n const sources: Array<{ url: string; title: string }> = [];\n\n if (data.output && Array.isArray(data.output)) {\n for (const item of data.output) {\n // Find the message with content\n if (item.type === \"message\" && item.content) {\n for (const contentPart of item.content) {\n // Extract text\n if (contentPart.type === \"output_text\" && contentPart.text) {\n outputText = contentPart.text;\n }\n\n // Extract sources from annotations (url_citation)\n if (\n contentPart.annotations &&\n Array.isArray(contentPart.annotations)\n ) {\n for (const annotation of contentPart.annotations) {\n if (annotation.type === \"url_citation\" && annotation.url) {\n // Avoid duplicates\n if (!sources.find((s) => s.url === annotation.url)) {\n sources.push({\n url: annotation.url,\n title: annotation.title || extractDomain(annotation.url),\n });\n }\n }\n }\n }\n }\n }\n }\n }\n\n return {\n query: params.query,\n answer: outputText,\n results: sources\n .slice(0, params.maxResults ?? config.maxResults ?? 5)\n .map((source, i) => ({\n title: source.title,\n url: source.url,\n content: \"\", // OpenAI returns answer with inline citations, not separate snippets\n score: 1 - i * 0.1,\n domain: extractDomain(source.url),\n })),\n provider: \"openai\",\n totalResults: sources.length,\n searchTime,\n };\n}\n\n/**\n * Extract domain from URL\n */\nfunction extractDomain(url: string): string {\n try {\n return new URL(url).hostname.replace(\"www.\", \"\");\n } catch {\n return url;\n }\n}\n\n/**\n * OpenAI native search provider implementation\n */\nexport const openaiProvider: WebSearchProviderInterface = {\n search: searchOpenAI,\n validateConfig: validateOpenAIConfig,\n};\n"]}
|