@dbx-tools/appkit-web-search 0.3.44 → 0.4.0

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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Last-resort scraping fallback for `web_search`, used ONLY when the
3
+ * workspace has no Databricks web-search-capable model deployed (no GPT /
4
+ * Gemini serving endpoint). The native Databricks web-search tool is always
5
+ * preferred (see `search.ts`); this exists so the tool still returns useful
6
+ * results in an environment that can't run it, rather than erroring on every
7
+ * call.
8
+ *
9
+ * It queries DuckDuckGo's no-JS HTML endpoint through `got-scraping`
10
+ * (browser-like fingerprints so the request isn't blocked) via a GET with the
11
+ * query in the query string - a POST to the same endpoint trips DDG's bot
12
+ * challenge (HTTP 202), while the GET returns normal result markup. It then
13
+ * parses the result anchors + snippets. Unlike the native tool there is no
14
+ * model synthesizing an answer, so `answer` is a short lead-in over the top
15
+ * snippets and the substance rides in `citations` - the calling agent reads
16
+ * those and writes its own answer.
17
+ *
18
+ * @module
19
+ */
20
+ import { log } from "@dbx-tools/shared-core";
21
+ import { gotScraping } from "got-scraping";
22
+ import { scrapeSearchExecuteDefaults, toCallSettings } from "./defaults.js";
23
+ import { htmlFragmentToText } from "./html-text.js";
24
+ import { executeRead } from "./runtime.js";
25
+ const logger = log.logger("web-search/scrape");
26
+ /** DuckDuckGo's no-JS HTML results endpoint (queried via GET). */
27
+ const DDG_HTML_URL = "https://html.duckduckgo.com/html/";
28
+ /**
29
+ * DDG wraps result URLs in a redirect (`//duckduckgo.com/l/?uddg=<encoded>`).
30
+ * Unwrap to the real destination; leave already-absolute URLs untouched.
31
+ */
32
+ function unwrapDdgUrl(href) {
33
+ try {
34
+ const u = new URL(href, "https://duckduckgo.com");
35
+ const target = u.searchParams.get("uddg");
36
+ if (target)
37
+ return decodeURIComponent(target);
38
+ return u.protocol === "http:" || u.protocol === "https:" ? u.toString() : href;
39
+ }
40
+ catch {
41
+ return href;
42
+ }
43
+ }
44
+ /** Parse DDG HTML result blocks into citations (title, url, snippet). */
45
+ function parseDdgHtml(html) {
46
+ const citations = [];
47
+ // Each result: an anchor with class result__a (title + href) and a snippet
48
+ // with class result__snippet. Match anchors, then the following snippet.
49
+ const anchorRe = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
50
+ const snippetRe = /<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/g;
51
+ const snippets = [];
52
+ let sm;
53
+ while ((sm = snippetRe.exec(html)) !== null)
54
+ snippets.push(htmlFragmentToText(sm[1] ?? ""));
55
+ let am;
56
+ let i = 0;
57
+ while ((am = anchorRe.exec(html)) !== null) {
58
+ const url = unwrapDdgUrl(am[1] ?? "");
59
+ const title = htmlFragmentToText(am[2] ?? "");
60
+ if (!url || !title)
61
+ continue;
62
+ const snippet = snippets[i] ?? "";
63
+ citations.push({ url, title, ...(snippet ? { snippet } : {}) });
64
+ i += 1;
65
+ }
66
+ return citations;
67
+ }
68
+ /**
69
+ * Run a scraping search over DuckDuckGo. Returns the same
70
+ * {@link WebSearchResult} shape as the native path, with `model` set to
71
+ * `"scrape:duckduckgo"` so callers can tell how the result was produced.
72
+ * Citations are filtered through the configured URL allow-list. `signal`
73
+ * cancels the in-flight request.
74
+ */
75
+ export async function runScrapeSearch(request, config, signal) {
76
+ const page = await executeRead("scrape-search", toCallSettings(scrapeSearchExecuteDefaults, config.timeoutMs, [
77
+ "web-search",
78
+ "scrape",
79
+ request.query,
80
+ ]), async (executeSignal) => {
81
+ const response = await gotScraping({
82
+ url: `${DDG_HTML_URL}?q=${encodeURIComponent(request.query)}`,
83
+ method: "GET",
84
+ // got's own timeout aborts the socket and reports which phase timed
85
+ // out; the interceptor timeout bounds the whole attempt around it.
86
+ timeout: { request: config.timeoutMs },
87
+ throwHttpErrors: false,
88
+ followRedirect: true,
89
+ ...(executeSignal ? { signal: executeSignal } : {}),
90
+ });
91
+ return {
92
+ body: typeof response.body === "string" ? response.body : String(response.body ?? ""),
93
+ statusCode: response.statusCode,
94
+ };
95
+ }, signal);
96
+ const all = parseDdgHtml(page.body);
97
+ const permitted = all.filter((c) => config.allowList.allows(c.url));
98
+ const citations = permitted.slice(0, config.maxCitations);
99
+ const answer = citations.length > 0
100
+ ? `Web results for "${request.query}" (no Databricks web-search model is deployed in this workspace, so these are unsynthesized search results - read the citations):\n\n` +
101
+ citations
102
+ .map((c, n) => `${n + 1}. ${c.title}${c.snippet ? ` - ${c.snippet}` : ""} (${c.url})`)
103
+ .join("\n")
104
+ : `No web results found for "${request.query}".`;
105
+ logger.debug("scraped", {
106
+ query: request.query,
107
+ found: all.length,
108
+ returned: citations.length,
109
+ status: page.statusCode,
110
+ });
111
+ return { query: request.query, answer, citations, model: "scrape:duckduckgo" };
112
+ }
113
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NyYXBlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NjcmFwZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFM0MsT0FBTyxFQUFFLDJCQUEyQixFQUFFLGNBQWMsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN6RSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDakQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUd4QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFFL0Msa0VBQWtFO0FBQ2xFLE1BQU0sWUFBWSxHQUFHLG1DQUFtQyxDQUFDO0FBRXpEOzs7R0FHRztBQUNILFNBQVMsWUFBWSxDQUFDLElBQVk7SUFDaEMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLHdCQUF3QixDQUFDLENBQUM7UUFDbEQsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDMUMsSUFBSSxNQUFNO1lBQUUsT0FBTyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM5QyxPQUFPLENBQUMsQ0FBQyxRQUFRLEtBQUssT0FBTyxJQUFJLENBQUMsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUNqRixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELHlFQUF5RTtBQUN6RSxTQUFTLFlBQVksQ0FBQyxJQUFZO0lBQ2hDLE1BQU0sU0FBUyxHQUF3QixFQUFFLENBQUM7SUFDMUMsMkVBQTJFO0lBQzNFLHlFQUF5RTtJQUN6RSxNQUFNLFFBQVEsR0FBRyw2RUFBNkUsQ0FBQztJQUMvRixNQUFNLFNBQVMsR0FBRyxnRUFBZ0UsQ0FBQztJQUNuRixNQUFNLFFBQVEsR0FBYSxFQUFFLENBQUM7SUFDOUIsSUFBSSxFQUEwQixDQUFDO0lBQy9CLE9BQU8sQ0FBQyxFQUFFLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLElBQUk7UUFBRSxRQUFRLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzVGLElBQUksRUFBMEIsQ0FBQztJQUMvQixJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDVixPQUFPLENBQUMsRUFBRSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUMzQyxNQUFNLEdBQUcsR0FBRyxZQUFZLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3RDLE1BQU0sS0FBSyxHQUFHLGtCQUFrQixDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUM5QyxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsS0FBSztZQUFFLFNBQVM7UUFDN0IsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNsQyxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDaEUsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNULENBQUM7SUFDRCxPQUFPLFNBQVMsQ0FBQztBQUNuQixDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLE9BQXlCLEVBQ3pCLE1BQStCLEVBQy9CLE1BQW9CO0lBRXBCLE1BQU0sSUFBSSxHQUFHLE1BQU0sV0FBVyxDQUM1QixlQUFlLEVBQ2YsY0FBYyxDQUFDLDJCQUEyQixFQUFFLE1BQU0sQ0FBQyxTQUFTLEVBQUU7UUFDNUQsWUFBWTtRQUNaLFFBQVE7UUFDUixPQUFPLENBQUMsS0FBSztLQUNkLENBQUMsRUFDRixLQUFLLEVBQUUsYUFBYSxFQUFpRCxFQUFFO1FBQ3JFLE1BQU0sUUFBUSxHQUFHLE1BQU0sV0FBVyxDQUFDO1lBQ2pDLEdBQUcsRUFBRSxHQUFHLFlBQVksTUFBTSxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUU7WUFDN0QsTUFBTSxFQUFFLEtBQUs7WUFDYixvRUFBb0U7WUFDcEUsbUVBQW1FO1lBQ25FLE9BQU8sRUFBRSxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUMsU0FBUyxFQUFFO1lBQ3RDLGVBQWUsRUFBRSxLQUFLO1lBQ3RCLGNBQWMsRUFBRSxJQUFJO1lBQ3BCLEdBQUcsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLGFBQWEsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7U0FDcEQsQ0FBQyxDQUFDO1FBQ0gsT0FBTztZQUNMLElBQUksRUFBRSxPQUFPLFFBQVEsQ0FBQyxJQUFJLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7WUFDckYsVUFBVSxFQUFFLFFBQVEsQ0FBQyxVQUFVO1NBQ2hDLENBQUM7SUFDSixDQUFDLEVBQ0QsTUFBTSxDQUNQLENBQUM7SUFDRixNQUFNLEdBQUcsR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3BDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3BFLE1BQU0sU0FBUyxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUMxRCxNQUFNLE1BQU0sR0FDVixTQUFTLENBQUMsTUFBTSxHQUFHLENBQUM7UUFDbEIsQ0FBQyxDQUFDLG9CQUFvQixPQUFPLENBQUMsS0FBSyx1SUFBdUk7WUFDeEssU0FBUztpQkFDTixHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQztpQkFDckYsSUFBSSxDQUFDLElBQUksQ0FBQztRQUNmLENBQUMsQ0FBQyw2QkFBNkIsT0FBTyxDQUFDLEtBQUssSUFBSSxDQUFDO0lBQ3JELE1BQU0sQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUFFO1FBQ3RCLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSztRQUNwQixLQUFLLEVBQUUsR0FBRyxDQUFDLE1BQU07UUFDakIsUUFBUSxFQUFFLFNBQVMsQ0FBQyxNQUFNO1FBQzFCLE1BQU0sRUFBRSxJQUFJLENBQUMsVUFBVTtLQUN4QixDQUFDLENBQUM7SUFDSCxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQztBQUNqRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBMYXN0LXJlc29ydCBzY3JhcGluZyBmYWxsYmFjayBmb3IgYHdlYl9zZWFyY2hgLCB1c2VkIE9OTFkgd2hlbiB0aGVcbiAqIHdvcmtzcGFjZSBoYXMgbm8gRGF0YWJyaWNrcyB3ZWItc2VhcmNoLWNhcGFibGUgbW9kZWwgZGVwbG95ZWQgKG5vIEdQVCAvXG4gKiBHZW1pbmkgc2VydmluZyBlbmRwb2ludCkuIFRoZSBuYXRpdmUgRGF0YWJyaWNrcyB3ZWItc2VhcmNoIHRvb2wgaXMgYWx3YXlzXG4gKiBwcmVmZXJyZWQgKHNlZSBgc2VhcmNoLnRzYCk7IHRoaXMgZXhpc3RzIHNvIHRoZSB0b29sIHN0aWxsIHJldHVybnMgdXNlZnVsXG4gKiByZXN1bHRzIGluIGFuIGVudmlyb25tZW50IHRoYXQgY2FuJ3QgcnVuIGl0LCByYXRoZXIgdGhhbiBlcnJvcmluZyBvbiBldmVyeVxuICogY2FsbC5cbiAqXG4gKiBJdCBxdWVyaWVzIER1Y2tEdWNrR28ncyBuby1KUyBIVE1MIGVuZHBvaW50IHRocm91Z2ggYGdvdC1zY3JhcGluZ2BcbiAqIChicm93c2VyLWxpa2UgZmluZ2VycHJpbnRzIHNvIHRoZSByZXF1ZXN0IGlzbid0IGJsb2NrZWQpIHZpYSBhIEdFVCB3aXRoIHRoZVxuICogcXVlcnkgaW4gdGhlIHF1ZXJ5IHN0cmluZyAtIGEgUE9TVCB0byB0aGUgc2FtZSBlbmRwb2ludCB0cmlwcyBEREcncyBib3RcbiAqIGNoYWxsZW5nZSAoSFRUUCAyMDIpLCB3aGlsZSB0aGUgR0VUIHJldHVybnMgbm9ybWFsIHJlc3VsdCBtYXJrdXAuIEl0IHRoZW5cbiAqIHBhcnNlcyB0aGUgcmVzdWx0IGFuY2hvcnMgKyBzbmlwcGV0cy4gVW5saWtlIHRoZSBuYXRpdmUgdG9vbCB0aGVyZSBpcyBub1xuICogbW9kZWwgc3ludGhlc2l6aW5nIGFuIGFuc3dlciwgc28gYGFuc3dlcmAgaXMgYSBzaG9ydCBsZWFkLWluIG92ZXIgdGhlIHRvcFxuICogc25pcHBldHMgYW5kIHRoZSBzdWJzdGFuY2UgcmlkZXMgaW4gYGNpdGF0aW9uc2AgLSB0aGUgY2FsbGluZyBhZ2VudCByZWFkc1xuICogdGhvc2UgYW5kIHdyaXRlcyBpdHMgb3duIGFuc3dlci5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IGdvdFNjcmFwaW5nIH0gZnJvbSBcImdvdC1zY3JhcGluZ1wiO1xuaW1wb3J0IHR5cGUgeyBSZXNvbHZlZFdlYlNlYXJjaENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZ1wiO1xuaW1wb3J0IHsgc2NyYXBlU2VhcmNoRXhlY3V0ZURlZmF1bHRzLCB0b0NhbGxTZXR0aW5ncyB9IGZyb20gXCIuL2RlZmF1bHRzXCI7XG5pbXBvcnQgeyBodG1sRnJhZ21lbnRUb1RleHQgfSBmcm9tIFwiLi9odG1sLXRleHRcIjtcbmltcG9ydCB7IGV4ZWN1dGVSZWFkIH0gZnJvbSBcIi4vcnVudGltZVwiO1xuaW1wb3J0IHR5cGUgeyBXZWJTZWFyY2hDaXRhdGlvbiwgV2ViU2VhcmNoUmVxdWVzdCwgV2ViU2VhcmNoUmVzdWx0IH0gZnJvbSBcIi4vc2NoZW1hXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ3ZWItc2VhcmNoL3NjcmFwZVwiKTtcblxuLyoqIER1Y2tEdWNrR28ncyBuby1KUyBIVE1MIHJlc3VsdHMgZW5kcG9pbnQgKHF1ZXJpZWQgdmlhIEdFVCkuICovXG5jb25zdCBEREdfSFRNTF9VUkwgPSBcImh0dHBzOi8vaHRtbC5kdWNrZHVja2dvLmNvbS9odG1sL1wiO1xuXG4vKipcbiAqIERERyB3cmFwcyByZXN1bHQgVVJMcyBpbiBhIHJlZGlyZWN0IChgLy9kdWNrZHVja2dvLmNvbS9sLz91ZGRnPTxlbmNvZGVkPmApLlxuICogVW53cmFwIHRvIHRoZSByZWFsIGRlc3RpbmF0aW9uOyBsZWF2ZSBhbHJlYWR5LWFic29sdXRlIFVSTHMgdW50b3VjaGVkLlxuICovXG5mdW5jdGlvbiB1bndyYXBEZGdVcmwoaHJlZjogc3RyaW5nKTogc3RyaW5nIHtcbiAgdHJ5IHtcbiAgICBjb25zdCB1ID0gbmV3IFVSTChocmVmLCBcImh0dHBzOi8vZHVja2R1Y2tnby5jb21cIik7XG4gICAgY29uc3QgdGFyZ2V0ID0gdS5zZWFyY2hQYXJhbXMuZ2V0KFwidWRkZ1wiKTtcbiAgICBpZiAodGFyZ2V0KSByZXR1cm4gZGVjb2RlVVJJQ29tcG9uZW50KHRhcmdldCk7XG4gICAgcmV0dXJuIHUucHJvdG9jb2wgPT09IFwiaHR0cDpcIiB8fCB1LnByb3RvY29sID09PSBcImh0dHBzOlwiID8gdS50b1N0cmluZygpIDogaHJlZjtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIGhyZWY7XG4gIH1cbn1cblxuLyoqIFBhcnNlIERERyBIVE1MIHJlc3VsdCBibG9ja3MgaW50byBjaXRhdGlvbnMgKHRpdGxlLCB1cmwsIHNuaXBwZXQpLiAqL1xuZnVuY3Rpb24gcGFyc2VEZGdIdG1sKGh0bWw6IHN0cmluZyk6IFdlYlNlYXJjaENpdGF0aW9uW10ge1xuICBjb25zdCBjaXRhdGlvbnM6IFdlYlNlYXJjaENpdGF0aW9uW10gPSBbXTtcbiAgLy8gRWFjaCByZXN1bHQ6IGFuIGFuY2hvciB3aXRoIGNsYXNzIHJlc3VsdF9fYSAodGl0bGUgKyBocmVmKSBhbmQgYSBzbmlwcGV0XG4gIC8vIHdpdGggY2xhc3MgcmVzdWx0X19zbmlwcGV0LiBNYXRjaCBhbmNob3JzLCB0aGVuIHRoZSBmb2xsb3dpbmcgc25pcHBldC5cbiAgY29uc3QgYW5jaG9yUmUgPSAvPGFbXj5dKmNsYXNzPVwiW15cIl0qcmVzdWx0X19hW15cIl0qXCJbXj5dKmhyZWY9XCIoW15cIl0rKVwiW14+XSo+KFtcXHNcXFNdKj8pPFxcL2E+L2c7XG4gIGNvbnN0IHNuaXBwZXRSZSA9IC88YVtePl0qY2xhc3M9XCJbXlwiXSpyZXN1bHRfX3NuaXBwZXRbXlwiXSpcIltePl0qPihbXFxzXFxTXSo/KTxcXC9hPi9nO1xuICBjb25zdCBzbmlwcGV0czogc3RyaW5nW10gPSBbXTtcbiAgbGV0IHNtOiBSZWdFeHBFeGVjQXJyYXkgfCBudWxsO1xuICB3aGlsZSAoKHNtID0gc25pcHBldFJlLmV4ZWMoaHRtbCkpICE9PSBudWxsKSBzbmlwcGV0cy5wdXNoKGh0bWxGcmFnbWVudFRvVGV4dChzbVsxXSA/PyBcIlwiKSk7XG4gIGxldCBhbTogUmVnRXhwRXhlY0FycmF5IHwgbnVsbDtcbiAgbGV0IGkgPSAwO1xuICB3aGlsZSAoKGFtID0gYW5jaG9yUmUuZXhlYyhodG1sKSkgIT09IG51bGwpIHtcbiAgICBjb25zdCB1cmwgPSB1bndyYXBEZGdVcmwoYW1bMV0gPz8gXCJcIik7XG4gICAgY29uc3QgdGl0bGUgPSBodG1sRnJhZ21lbnRUb1RleHQoYW1bMl0gPz8gXCJcIik7XG4gICAgaWYgKCF1cmwgfHwgIXRpdGxlKSBjb250aW51ZTtcbiAgICBjb25zdCBzbmlwcGV0ID0gc25pcHBldHNbaV0gPz8gXCJcIjtcbiAgICBjaXRhdGlvbnMucHVzaCh7IHVybCwgdGl0bGUsIC4uLihzbmlwcGV0ID8geyBzbmlwcGV0IH0gOiB7fSkgfSk7XG4gICAgaSArPSAxO1xuICB9XG4gIHJldHVybiBjaXRhdGlvbnM7XG59XG5cbi8qKlxuICogUnVuIGEgc2NyYXBpbmcgc2VhcmNoIG92ZXIgRHVja0R1Y2tHby4gUmV0dXJucyB0aGUgc2FtZVxuICoge0BsaW5rIFdlYlNlYXJjaFJlc3VsdH0gc2hhcGUgYXMgdGhlIG5hdGl2ZSBwYXRoLCB3aXRoIGBtb2RlbGAgc2V0IHRvXG4gKiBgXCJzY3JhcGU6ZHVja2R1Y2tnb1wiYCBzbyBjYWxsZXJzIGNhbiB0ZWxsIGhvdyB0aGUgcmVzdWx0IHdhcyBwcm9kdWNlZC5cbiAqIENpdGF0aW9ucyBhcmUgZmlsdGVyZWQgdGhyb3VnaCB0aGUgY29uZmlndXJlZCBVUkwgYWxsb3ctbGlzdC4gYHNpZ25hbGBcbiAqIGNhbmNlbHMgdGhlIGluLWZsaWdodCByZXF1ZXN0LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcnVuU2NyYXBlU2VhcmNoKFxuICByZXF1ZXN0OiBXZWJTZWFyY2hSZXF1ZXN0LFxuICBjb25maWc6IFJlc29sdmVkV2ViU2VhcmNoQ29uZmlnLFxuICBzaWduYWw/OiBBYm9ydFNpZ25hbCxcbik6IFByb21pc2U8V2ViU2VhcmNoUmVzdWx0PiB7XG4gIGNvbnN0IHBhZ2UgPSBhd2FpdCBleGVjdXRlUmVhZChcbiAgICBcInNjcmFwZS1zZWFyY2hcIixcbiAgICB0b0NhbGxTZXR0aW5ncyhzY3JhcGVTZWFyY2hFeGVjdXRlRGVmYXVsdHMsIGNvbmZpZy50aW1lb3V0TXMsIFtcbiAgICAgIFwid2ViLXNlYXJjaFwiLFxuICAgICAgXCJzY3JhcGVcIixcbiAgICAgIHJlcXVlc3QucXVlcnksXG4gICAgXSksXG4gICAgYXN5bmMgKGV4ZWN1dGVTaWduYWwpOiBQcm9taXNlPHsgYm9keTogc3RyaW5nOyBzdGF0dXNDb2RlOiBudW1iZXIgfT4gPT4ge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBnb3RTY3JhcGluZyh7XG4gICAgICAgIHVybDogYCR7RERHX0hUTUxfVVJMfT9xPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHJlcXVlc3QucXVlcnkpfWAsXG4gICAgICAgIG1ldGhvZDogXCJHRVRcIixcbiAgICAgICAgLy8gZ290J3Mgb3duIHRpbWVvdXQgYWJvcnRzIHRoZSBzb2NrZXQgYW5kIHJlcG9ydHMgd2hpY2ggcGhhc2UgdGltZWRcbiAgICAgICAgLy8gb3V0OyB0aGUgaW50ZXJjZXB0b3IgdGltZW91dCBib3VuZHMgdGhlIHdob2xlIGF0dGVtcHQgYXJvdW5kIGl0LlxuICAgICAgICB0aW1lb3V0OiB7IHJlcXVlc3Q6IGNvbmZpZy50aW1lb3V0TXMgfSxcbiAgICAgICAgdGhyb3dIdHRwRXJyb3JzOiBmYWxzZSxcbiAgICAgICAgZm9sbG93UmVkaXJlY3Q6IHRydWUsXG4gICAgICAgIC4uLihleGVjdXRlU2lnbmFsID8geyBzaWduYWw6IGV4ZWN1dGVTaWduYWwgfSA6IHt9KSxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgYm9keTogdHlwZW9mIHJlc3BvbnNlLmJvZHkgPT09IFwic3RyaW5nXCIgPyByZXNwb25zZS5ib2R5IDogU3RyaW5nKHJlc3BvbnNlLmJvZHkgPz8gXCJcIiksXG4gICAgICAgIHN0YXR1c0NvZGU6IHJlc3BvbnNlLnN0YXR1c0NvZGUsXG4gICAgICB9O1xuICAgIH0sXG4gICAgc2lnbmFsLFxuICApO1xuICBjb25zdCBhbGwgPSBwYXJzZURkZ0h0bWwocGFnZS5ib2R5KTtcbiAgY29uc3QgcGVybWl0dGVkID0gYWxsLmZpbHRlcigoYykgPT4gY29uZmlnLmFsbG93TGlzdC5hbGxvd3MoYy51cmwpKTtcbiAgY29uc3QgY2l0YXRpb25zID0gcGVybWl0dGVkLnNsaWNlKDAsIGNvbmZpZy5tYXhDaXRhdGlvbnMpO1xuICBjb25zdCBhbnN3ZXIgPVxuICAgIGNpdGF0aW9ucy5sZW5ndGggPiAwXG4gICAgICA/IGBXZWIgcmVzdWx0cyBmb3IgXCIke3JlcXVlc3QucXVlcnl9XCIgKG5vIERhdGFicmlja3Mgd2ViLXNlYXJjaCBtb2RlbCBpcyBkZXBsb3llZCBpbiB0aGlzIHdvcmtzcGFjZSwgc28gdGhlc2UgYXJlIHVuc3ludGhlc2l6ZWQgc2VhcmNoIHJlc3VsdHMgLSByZWFkIHRoZSBjaXRhdGlvbnMpOlxcblxcbmAgK1xuICAgICAgICBjaXRhdGlvbnNcbiAgICAgICAgICAubWFwKChjLCBuKSA9PiBgJHtuICsgMX0uICR7Yy50aXRsZX0ke2Muc25pcHBldCA/IGAgLSAke2Muc25pcHBldH1gIDogXCJcIn0gKCR7Yy51cmx9KWApXG4gICAgICAgICAgLmpvaW4oXCJcXG5cIilcbiAgICAgIDogYE5vIHdlYiByZXN1bHRzIGZvdW5kIGZvciBcIiR7cmVxdWVzdC5xdWVyeX1cIi5gO1xuICBsb2dnZXIuZGVidWcoXCJzY3JhcGVkXCIsIHtcbiAgICBxdWVyeTogcmVxdWVzdC5xdWVyeSxcbiAgICBmb3VuZDogYWxsLmxlbmd0aCxcbiAgICByZXR1cm5lZDogY2l0YXRpb25zLmxlbmd0aCxcbiAgICBzdGF0dXM6IHBhZ2Uuc3RhdHVzQ29kZSxcbiAgfSk7XG4gIHJldHVybiB7IHF1ZXJ5OiByZXF1ZXN0LnF1ZXJ5LCBhbnN3ZXIsIGNpdGF0aW9ucywgbW9kZWw6IFwic2NyYXBlOmR1Y2tkdWNrZ29cIiB9O1xufVxuIl19
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Web search backed by the Databricks Model Serving native web-search tool.
3
+ *
4
+ * Unlike a scraping client, the search runs *inside* a model call: we POST the
5
+ * query to the workspace's serving endpoint with the provider's web-search
6
+ * tool spec attached, and the model searches the web and writes the answer.
7
+ * {@link runWebSearch}:
8
+ *
9
+ * 1. Resolves a web-search-capable model INDEPENDENTLY of the calling
10
+ * agent's chat model (the agent may run on a model without web search).
11
+ * A pinned `model` (request or config) is fuzzy-matched; otherwise the
12
+ * configured fallback order (Gemini, then GPT) is walked to the first
13
+ * web-search-capable endpoint that exists. An explicit but unsupported
14
+ * model is a hard error, not a silent fallback.
15
+ * 2. POSTs to the provider's REST surface (`/serving-endpoints/responses`
16
+ * for OpenAI, `/serving-endpoints/chat/completions` for Gemini) with the
17
+ * mapped tool spec, authenticated as the OBO caller.
18
+ * 3. Returns the synthesized answer plus the cited sources, with citations
19
+ * silently filtered through the configured URL allow-list.
20
+ *
21
+ * @module
22
+ */
23
+ import { serving } from "@dbx-tools/model";
24
+ import { type ResolvedWebSearchConfig } from "./config.js";
25
+ import type { WebSearchRequest, WebSearchResult } from "./schema.js";
26
+ type WorkspaceClientLike = serving.WorkspaceClientLike;
27
+ /** Context a search needs from the caller: the OBO client + workspace host. */
28
+ export interface WebSearchContext {
29
+ client: WorkspaceClientLike;
30
+ host: string;
31
+ }
32
+ /**
33
+ * Resolve the OBO workspace client + host from the active AppKit execution
34
+ * context. Inside `agent.stream`'s `asUser(req)` scope this hits the serving
35
+ * endpoint as the requesting user; outside a user context AppKit falls back to
36
+ * the service principal.
37
+ */
38
+ export declare function resolveWebSearchContext(): Promise<WebSearchContext>;
39
+ /**
40
+ * Run a web search. Prefers the Databricks native web-search tool on a
41
+ * deployed GPT/Gemini endpoint (synthesized answer + citations); when the
42
+ * workspace has no such endpoint AND the scrape fallback is enabled, falls
43
+ * back to a DuckDuckGo scrape so the tool still returns results instead of
44
+ * erroring. Citations are filtered through the configured URL allow-list.
45
+ *
46
+ * `signal` cancels the whole call, including the in-flight serving request.
47
+ */
48
+ export declare function runWebSearch(request: WebSearchRequest, config: ResolvedWebSearchConfig, ctx: WebSearchContext, signal?: AbortSignal): Promise<WebSearchResult>;
49
+ export {};
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Web search backed by the Databricks Model Serving native web-search tool.
3
+ *
4
+ * Unlike a scraping client, the search runs *inside* a model call: we POST the
5
+ * query to the workspace's serving endpoint with the provider's web-search
6
+ * tool spec attached, and the model searches the web and writes the answer.
7
+ * {@link runWebSearch}:
8
+ *
9
+ * 1. Resolves a web-search-capable model INDEPENDENTLY of the calling
10
+ * agent's chat model (the agent may run on a model without web search).
11
+ * A pinned `model` (request or config) is fuzzy-matched; otherwise the
12
+ * configured fallback order (Gemini, then GPT) is walked to the first
13
+ * web-search-capable endpoint that exists. An explicit but unsupported
14
+ * model is a hard error, not a silent fallback.
15
+ * 2. POSTs to the provider's REST surface (`/serving-endpoints/responses`
16
+ * for OpenAI, `/serving-endpoints/chat/completions` for Gemini) with the
17
+ * mapped tool spec, authenticated as the OBO caller.
18
+ * 3. Returns the synthesized answer plus the cited sources, with citations
19
+ * silently filtered through the configured URL allow-list.
20
+ *
21
+ * @module
22
+ */
23
+ import { ConfigurationError, ConnectionError, ExecutionError, getExecutionContext, ValidationError, } from "@databricks/appkit";
24
+ import { invoke, resolve, serving } from "@dbx-tools/model";
25
+ import { log, object, string } from "@dbx-tools/shared-core";
26
+ import { openaiChat, openaiResponses } from "@dbx-tools/shared-model";
27
+ import { MODEL_ENV, SERVING_ENDPOINT_ENV } from "./config.js";
28
+ import { toCallSettings, webSearchExecuteDefaults } from "./defaults.js";
29
+ import { detectWebSearchProvider, supportsWebSearch, webSearchToolSpec } from "./provider.js";
30
+ import { executeRead } from "./runtime.js";
31
+ import { runScrapeSearch } from "./scrape.js";
32
+ const logger = log.logger("web-search/search");
33
+ const { resolveModel } = resolve;
34
+ const { listServingEndpoints } = serving;
35
+ /**
36
+ * How deep the grounding-metadata walk descends. Gemini nests its sources a
37
+ * handful of levels down and the shape varies by model version, so the walk
38
+ * is generic; the bound is what keeps a pathological payload from turning it
39
+ * into a full traversal of the response.
40
+ */
41
+ const MAX_GROUNDING_WALK_DEPTH = 6;
42
+ /** Lowest HTTP status treated as a server-side (retryable) serving failure. */
43
+ const SERVER_ERROR_STATUS = 500;
44
+ /** Status Model Serving uses to shed load; retryable like a 5xx. */
45
+ const RATE_LIMITED_STATUS = 429;
46
+ /**
47
+ * Resolve the OBO workspace client + host from the active AppKit execution
48
+ * context. Inside `agent.stream`'s `asUser(req)` scope this hits the serving
49
+ * endpoint as the requesting user; outside a user context AppKit falls back to
50
+ * the service principal.
51
+ */
52
+ export async function resolveWebSearchContext() {
53
+ const ctx = getExecutionContext();
54
+ const host = (await ctx.client.config.getHost()).toString();
55
+ return { client: ctx.client, host };
56
+ }
57
+ /**
58
+ * Resolve a web-search-capable model against the LIVE workspace catalogue - so
59
+ * we never return an endpoint id that isn't actually deployed (the "endpoint
60
+ * does not exist" failure a hardcoded fallback id would cause). Reuses
61
+ * `@dbx-tools/model`'s existing catalogue + resolver rather than a custom
62
+ * lookup: {@link listServingEndpoints} lists the endpoints (cached), and we
63
+ * restrict the candidate set to the {@link supportsWebSearch} ones before
64
+ * {@link resolveModel} fuzzy-picks within it.
65
+ *
66
+ * Returns the chosen endpoint id, or `null` when the workspace has no
67
+ * web-search-capable model deployed (the caller then uses the scrape
68
+ * fallback). A pin the caller chose deliberately - the request's `model`, the
69
+ * plugin's `model`, or `WEB_SEARCH_MODEL` - throws when it resolves to an
70
+ * unsupported / absent endpoint, so a bad pin surfaces rather than silently
71
+ * degrading. A pin inherited from the shared `DATABRICKS_SERVING_ENDPOINT_NAME`
72
+ * binding is only a preference: that endpoint is the app's serving endpoint,
73
+ * not necessarily a web-search-capable one, so an unusable value falls through
74
+ * to the fallback order.
75
+ */
76
+ async function resolveWebSearchModel(ctx, config, requested) {
77
+ const endpoints = await listServingEndpoints(ctx.client, ctx.host);
78
+ // Only deployed, web-search-capable endpoints are candidates.
79
+ const capable = endpoints.filter((e) => supportsWebSearch(e.name));
80
+ const pinned = requested ?? config.model;
81
+ const pinIsDeliberate = requested !== undefined || config.modelSource === "config" || config.modelSource === MODEL_ENV;
82
+ if (pinned) {
83
+ // Resolve the explicit ask within the capable set only.
84
+ const { modelId } = resolveModel(capable, {
85
+ explicit: pinned,
86
+ fuzzy: config.fuzzy,
87
+ threshold: config.fuzzyThreshold,
88
+ });
89
+ // resolveModel returns the input verbatim on no match; require it to be a
90
+ // real capable endpoint so a bad pin is a clear error, not a phantom call.
91
+ const usable = capable.some((e) => e.name === modelId) && supportsWebSearch(modelId);
92
+ if (usable)
93
+ return modelId;
94
+ const deployed = capable.map((e) => e.name).join(", ") || "none";
95
+ if (!pinIsDeliberate) {
96
+ logger.info("shared-endpoint-not-web-capable", {
97
+ envVar: SERVING_ENDPOINT_ENV,
98
+ deployed,
99
+ });
100
+ }
101
+ else if (requested !== undefined) {
102
+ throw ValidationError.invalidValue("model", pinned, `a deployed web-search-capable endpoint (deployed: ${deployed})`);
103
+ }
104
+ else {
105
+ throw ConfigurationError.resourceNotFound("Web-search-capable serving endpoint", `Deployed web-search-capable endpoints: ${deployed}. Set model or ${MODEL_ENV} to one of them.`);
106
+ }
107
+ }
108
+ if (capable.length === 0)
109
+ return null;
110
+ // Nothing usable pinned: prefer the configured fallback order (Gemini, then
111
+ // GPT) when those ids are actually deployed; else take the best capable
112
+ // endpoint.
113
+ const { modelId } = resolveModel(capable, {
114
+ fallbacks: config.modelFallbacks,
115
+ fuzzy: config.fuzzy,
116
+ threshold: config.fuzzyThreshold,
117
+ });
118
+ return capable.some((e) => e.name === modelId) ? modelId : (capable[0]?.name ?? null);
119
+ }
120
+ /**
121
+ * POST a serving request as the OBO caller and return the parsed JSON body.
122
+ *
123
+ * Auth headers are minted per call from the OBO client's SDK config, which
124
+ * refreshes the token when it is close to expiry, so the request carries the
125
+ * requesting user's identity.
126
+ *
127
+ * This is the one Databricks call in the repo that does not go through
128
+ * `apiClient.request` + `databricks.toContext` (which does forward
129
+ * cancellation). Retry classification here has to distinguish a load-shed or
130
+ * server fault from this request's own 4xx, and `fetch` exposes the HTTP status
131
+ * directly; the SDK raises an `ApiError` that carries the status under
132
+ * inconsistent keys, which is guesswork by comparison.
133
+ */
134
+ async function postServing(ctx, url, body, config, cacheKey, signal) {
135
+ const payload = await executeRead("serving-request", toCallSettings(webSearchExecuteDefaults, config.timeoutMs, cacheKey), async (executeSignal) => {
136
+ const response = await fetch(url, {
137
+ method: "POST",
138
+ headers: {
139
+ ...(await invoke.authHeaders(ctx.client)),
140
+ Accept: "application/json",
141
+ "Content-Type": "application/json",
142
+ },
143
+ body: JSON.stringify(body),
144
+ ...(executeSignal ? { signal: executeSignal } : {}),
145
+ });
146
+ if (!response.ok) {
147
+ // Load-shedding and server faults are worth another attempt; a 4xx is
148
+ // this request's own problem, so it must not be retried.
149
+ const retryable = response.status >= SERVER_ERROR_STATUS || response.status === RATE_LIMITED_STATUS;
150
+ const message = `web-search: Model Serving rejected the search request (HTTP ${response.status})`;
151
+ throw retryable
152
+ ? new ConnectionError(message, { context: { status: response.status } })
153
+ : new ExecutionError(message, { context: { status: response.status } });
154
+ }
155
+ return response.json();
156
+ }, signal);
157
+ return object.isRecord(payload) ? payload : {};
158
+ }
159
+ /* --------------------------- response extraction --------------------------- */
160
+ /**
161
+ * Extract answer text + citations from an OpenAI Responses API payload, via the
162
+ * shared reader in `@dbx-tools/shared-model` (the same module the model-proxy
163
+ * uses to translate the Responses wire format in the other direction).
164
+ */
165
+ function fromResponsesPayload(payload) {
166
+ const { text, citations } = openaiResponses.readResponsesOutput(payload);
167
+ return { answer: text, citations };
168
+ }
169
+ /**
170
+ * Extract answer text + citations from a Chat Completions payload (Gemini via
171
+ * `google_search`). The answer is `choices[0].message.content`; grounding
172
+ * sources, when present, surface under `choices[0].message` grounding
173
+ * metadata (best-effort - shapes vary, so we scan for url-bearing entries).
174
+ */
175
+ function fromChatPayload(payload) {
176
+ const choices = Array.isArray(payload.choices) ? payload.choices : [];
177
+ const first = choices[0];
178
+ const message = object.isRecord(first) && object.isRecord(first.message) ? first.message : {};
179
+ const answer = openaiChat.chatContentToText(message.content);
180
+ const citations = [];
181
+ // Best-effort grounding extraction: walk any nested object for {uri|url,title}.
182
+ const seen = new Set();
183
+ const visit = (v, depth) => {
184
+ if (depth > MAX_GROUNDING_WALK_DEPTH || !object.isRecord(v))
185
+ return;
186
+ const url = string.trimToEmpty(v.url) || string.trimToEmpty(v.uri);
187
+ if (url && !seen.has(url)) {
188
+ seen.add(url);
189
+ const title = string.trimToEmpty(v.title);
190
+ citations.push({ url, ...(title ? { title } : {}) });
191
+ }
192
+ for (const val of Object.values(v)) {
193
+ if (Array.isArray(val))
194
+ val.forEach((x) => visit(x, depth + 1));
195
+ else if (val && typeof val === "object")
196
+ visit(val, depth + 1);
197
+ }
198
+ };
199
+ visit(message.grounding_metadata ?? message.groundingMetadata, 0);
200
+ return { answer, citations };
201
+ }
202
+ /**
203
+ * Run a web search. Prefers the Databricks native web-search tool on a
204
+ * deployed GPT/Gemini endpoint (synthesized answer + citations); when the
205
+ * workspace has no such endpoint AND the scrape fallback is enabled, falls
206
+ * back to a DuckDuckGo scrape so the tool still returns results instead of
207
+ * erroring. Citations are filtered through the configured URL allow-list.
208
+ *
209
+ * `signal` cancels the whole call, including the in-flight serving request.
210
+ */
211
+ export async function runWebSearch(request, config, ctx, signal) {
212
+ const modelId = await resolveWebSearchModel(ctx, config, request.model);
213
+ if (modelId === null) {
214
+ // No native web-search model deployed in this workspace.
215
+ if (config.scrapeFallback) {
216
+ logger.info("no-native-model:scrape-fallback", { query: request.query });
217
+ return runScrapeSearch(request, config, signal);
218
+ }
219
+ throw ConfigurationError.resourceNotFound("Web-search-capable serving endpoint", "No GPT/Gemini endpoint is deployed in this workspace and the scrape fallback is " +
220
+ `disabled. Deploy a supported endpoint, set model or ${MODEL_ENV}, or enable the ` +
221
+ "fallback (WEB_SEARCH_SCRAPE_FALLBACK=1).");
222
+ }
223
+ const provider = detectWebSearchProvider(modelId); // guaranteed by resolve step
224
+ const spec = webSearchToolSpec(provider, config.webSearchTools);
225
+ const url = spec.api === "responses" ? invoke.responsesUrl(ctx.host) : invoke.chatCompletionsUrl(ctx.host);
226
+ const body = spec.api === "responses"
227
+ ? {
228
+ model: modelId,
229
+ input: [{ role: "user", content: request.query }],
230
+ tools: [spec.tool],
231
+ }
232
+ : {
233
+ model: modelId,
234
+ messages: [{ role: "user", content: request.query }],
235
+ tools: [spec.tool],
236
+ };
237
+ const payload = await postServing(ctx, url, body, config, ["web-search", "serving", spec.api, modelId, request.query], signal);
238
+ const { answer, citations } = spec.api === "responses" ? fromResponsesPayload(payload) : fromChatPayload(payload);
239
+ const permitted = citations.filter((c) => config.allowList.allows(c.url));
240
+ const dropped = citations.length - permitted.length;
241
+ const trimmed = permitted.slice(0, config.maxCitations);
242
+ logger.debug("searched", {
243
+ query: request.query,
244
+ model: modelId,
245
+ provider,
246
+ citations: citations.length,
247
+ ...(dropped > 0 ? { filtered: dropped } : {}),
248
+ returned: trimmed.length,
249
+ });
250
+ return { query: request.query, answer, citations: trimmed, model: modelId };
251
+ }
252
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VhcmNoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlYXJjaC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBcUJHO0FBRUgsT0FBTyxFQUNMLGtCQUFrQixFQUNsQixlQUFlLEVBQ2YsY0FBYyxFQUNkLG1CQUFtQixFQUNuQixlQUFlLEdBQ2hCLE1BQU0sb0JBQW9CLENBQUM7QUFDNUIsT0FBTyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDNUQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDN0QsT0FBTyxFQUFFLFVBQVUsRUFBRSxlQUFlLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN0RSxPQUFPLEVBQUUsU0FBUyxFQUFFLG9CQUFvQixFQUFnQyxNQUFNLFVBQVUsQ0FBQztBQUN6RixPQUFPLEVBQUUsY0FBYyxFQUFFLHdCQUF3QixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3RFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxpQkFBaUIsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUMzRixPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFHM0MsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO0FBQy9DLE1BQU0sRUFBRSxZQUFZLEVBQUUsR0FBRyxPQUFPLENBQUM7QUFDakMsTUFBTSxFQUFFLG9CQUFvQixFQUFFLEdBQUcsT0FBTyxDQUFDO0FBRXpDOzs7OztHQUtHO0FBQ0gsTUFBTSx3QkFBd0IsR0FBRyxDQUFDLENBQUM7QUFFbkMsK0VBQStFO0FBQy9FLE1BQU0sbUJBQW1CLEdBQUcsR0FBRyxDQUFDO0FBRWhD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QU0sQ0FDUCxDQUFDO0lBRUYsTUFBTSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsR0FDekIsSUFBSSxDQUFDLEdBQUcsS0FBSyxXQUFXLENBQUMsQ0FBQyxDQUFDLG9CQUFvQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFdEYsTUFBTSxTQUFTLEdBQUcsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDMUUsTUFBTSxPQUFPLEdBQUcsU0FBUyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsTUFBTSxDQUFDO0lBQ3BELE1BQU0sT0FBTyxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUN4RCxNQUFNLENBQUMsS0FBSyxDQUFDLFVBQVUsRUFBRTtRQUN2QixLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7UUFDcEIsS0FBSyxFQUFFLE9BQU87UUFDZCxRQUFRO1FBQ1IsU0FBUyxFQUFFLFNBQVMsQ0FBQyxNQUFNO1FBQzNCLEdBQUcsQ0FBQyxPQUFPLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzdDLFFBQVEsRUFBRSxPQUFPLENBQUMsTUFBTTtLQUN6QixDQUFDLENBQUM7SUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDO0FBQzlFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFdlYiBzZWFyY2ggYmFja2VkIGJ5IHRoZSBEYXRhYnJpY2tzIE1vZGVsIFNlcnZpbmcgbmF0aXZlIHdlYi1zZWFyY2ggdG9vbC5cbiAqXG4gKiBVbmxpa2UgYSBzY3JhcGluZyBjbGllbnQsIHRoZSBzZWFyY2ggcnVucyAqaW5zaWRlKiBhIG1vZGVsIGNhbGw6IHdlIFBPU1QgdGhlXG4gKiBxdWVyeSB0byB0aGUgd29ya3NwYWNlJ3Mgc2VydmluZyBlbmRwb2ludCB3aXRoIHRoZSBwcm92aWRlcidzIHdlYi1zZWFyY2hcbiAqIHRvb2wgc3BlYyBhdHRhY2hlZCwgYW5kIHRoZSBtb2RlbCBzZWFyY2hlcyB0aGUgd2ViIGFuZCB3cml0ZXMgdGhlIGFuc3dlci5cbiAqIHtAbGluayBydW5XZWJTZWFyY2h9OlxuICpcbiAqICAgMS4gUmVzb2x2ZXMgYSB3ZWItc2VhcmNoLWNhcGFibGUgbW9kZWwgSU5ERVBFTkRFTlRMWSBvZiB0aGUgY2FsbGluZ1xuICogICAgICBhZ2VudCdzIGNoYXQgbW9kZWwgKHRoZSBhZ2VudCBtYXkgcnVuIG9uIGEgbW9kZWwgd2l0aG91dCB3ZWIgc2VhcmNoKS5cbiAqICAgICAgQSBwaW5uZWQgYG1vZGVsYCAocmVxdWVzdCBvciBjb25maWcpIGlzIGZ1enp5LW1hdGNoZWQ7IG90aGVyd2lzZSB0aGVcbiAqICAgICAgY29uZmlndXJlZCBmYWxsYmFjayBvcmRlciAoR2VtaW5pLCB0aGVuIEdQVCkgaXMgd2Fsa2VkIHRvIHRoZSBmaXJzdFxuICogICAgICB3ZWItc2VhcmNoLWNhcGFibGUgZW5kcG9pbnQgdGhhdCBleGlzdHMuIEFuIGV4cGxpY2l0IGJ1dCB1bnN1cHBvcnRlZFxuICogICAgICBtb2RlbCBpcyBhIGhhcmQgZXJyb3IsIG5vdCBhIHNpbGVudCBmYWxsYmFjay5cbiAqICAgMi4gUE9TVHMgdG8gdGhlIHByb3ZpZGVyJ3MgUkVTVCBzdXJmYWNlIChgL3NlcnZpbmctZW5kcG9pbnRzL3Jlc3BvbnNlc2BcbiAqICAgICAgZm9yIE9wZW5BSSwgYC9zZXJ2aW5nLWVuZHBvaW50cy9jaGF0L2NvbXBsZXRpb25zYCBmb3IgR2VtaW5pKSB3aXRoIHRoZVxuICogICAgICBtYXBwZWQgdG9vbCBzcGVjLCBhdXRoZW50aWNhdGVkIGFzIHRoZSBPQk8gY2FsbGVyLlxuICogICAzLiBSZXR1cm5zIHRoZSBzeW50aGVzaXplZCBhbnN3ZXIgcGx1cyB0aGUgY2l0ZWQgc291cmNlcywgd2l0aCBjaXRhdGlvbnNcbiAqICAgICAgc2lsZW50bHkgZmlsdGVyZWQgdGhyb3VnaCB0aGUgY29uZmlndXJlZCBVUkwgYWxsb3ctbGlzdC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHtcbiAgQ29uZmlndXJhdGlvbkVycm9yLFxuICBDb25uZWN0aW9uRXJyb3IsXG4gIEV4ZWN1dGlvbkVycm9yLFxuICBnZXRFeGVjdXRpb25Db250ZXh0LFxuICBWYWxpZGF0aW9uRXJyb3IsXG59IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbmltcG9ydCB7IGludm9rZSwgcmVzb2x2ZSwgc2VydmluZyB9IGZyb20gXCJAZGJ4LXRvb2xzL21vZGVsXCI7XG5pbXBvcnQgeyBsb2csIG9iamVjdCwgc3RyaW5nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IG9wZW5haUNoYXQsIG9wZW5haVJlc3BvbnNlcyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tb2RlbFwiO1xuaW1wb3J0IHsgTU9ERUxfRU5WLCBTRVJWSU5HX0VORFBPSU5UX0VOViwgdHlwZSBSZXNvbHZlZFdlYlNlYXJjaENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZ1wiO1xuaW1wb3J0IHsgdG9DYWxsU2V0dGluZ3MsIHdlYlNlYXJjaEV4ZWN1dGVEZWZhdWx0cyB9IGZyb20gXCIuL2RlZmF1bHRzXCI7XG5pbXBvcnQgeyBkZXRlY3RXZWJTZWFyY2hQcm92aWRlciwgc3VwcG9ydHNXZWJTZWFyY2gsIHdlYlNlYXJjaFRvb2xTcGVjIH0gZnJvbSBcIi4vcHJvdmlkZXJcIjtcbmltcG9ydCB7IGV4ZWN1dGVSZWFkIH0gZnJvbSBcIi4vcnVudGltZVwiO1xuaW1wb3J0IHR5cGUgeyBXZWJTZWFyY2hDaXRhdGlvbiwgV2ViU2VhcmNoUmVxdWVzdCwgV2ViU2VhcmNoUmVzdWx0IH0gZnJvbSBcIi4vc2NoZW1hXCI7XG5pbXBvcnQgeyBydW5TY3JhcGVTZWFyY2ggfSBmcm9tIFwiLi9zY3JhcGVcIjtcblxudHlwZSBXb3Jrc3BhY2VDbGllbnRMaWtlID0gc2VydmluZy5Xb3Jrc3BhY2VDbGllbnRMaWtlO1xuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcIndlYi1zZWFyY2gvc2VhcmNoXCIpO1xuY29uc3QgeyByZXNvbHZlTW9kZWwgfSA9IHJlc29sdmU7XG5jb25zdCB7IGxpc3RTZXJ2aW5nRW5kcG9pbnRzIH0gPSBzZXJ2aW5nO1xuXG4vKipcbiAqIEhvdyBkZWVwIHRoZSBncm91bmRpbmctbWV0YWRhdGEgd2FsayBkZXNjZW5kcy4gR2VtaW5pIG5lc3RzIGl0cyBzb3VyY2VzIGFcbiAqIGhhbmRmdWwgb2YgbGV2ZWxzIGRvd24gYW5kIHRoZSBzaGFwZSB2YXJpZXMgYnkgbW9kZWwgdmVyc2lvbiwgc28gdGhlIHdhbGtcbiAqIGlzIGdlbmVyaWM7IHRoZSBib3VuZCBpcyB3aGF0IGtlZXBzIGEgcGF0aG9sb2dpY2FsIHBheWxvYWQgZnJvbSB0dXJuaW5nIGl0XG4gKiBpbnRvIGEgZnVsbCB0cmF2ZXJzYWwgb2YgdGhlIHJlc3BvbnNlLlxuICovXG5jb25zdCBNQVhfR1JPVU5ESU5HX1dBTEtfREVQVEggPSA2O1xuXG4vKiogTG93ZXN0IEhUVFAgc3RhdHVzIHRyZWF0ZWQgYXMgYSBzZXJ2ZXItc2lkZSAocmV0cnlhYmxlKSBzZXJ2aW5nIGZhaWx1cmUuICovXG5jb25zdCBTRVJWRVJfRVJST1JfU1RBVFVTID0gNTAwO1xuXG4vKiogU3RhdHVzIE1vZGVsIFNlcnZpbmcgdXNlcyB0byBzaGVkIGxvYWQ7IHJldHJ5YWJsZSBsaWtlIGEgNXh4LiAqL1xuY29uc3QgUkFURV9MSU1JVEVEX1NUQVRVUyA9IDQyOTtcblxuLyoqIENvbnRleHQgYSBzZWFyY2ggbmVlZHMgZnJvbSB0aGUgY2FsbGVyOiB0aGUgT0JPIGNsaWVudCArIHdvcmtzcGFjZSBob3N0LiAqL1xuZXhwb3J0IGludGVyZmFjZSBXZWJTZWFyY2hDb250ZXh0IHtcbiAgY2xpZW50OiBXb3Jrc3BhY2VDbGllbnRMaWtlO1xuICBob3N0OiBzdHJpbmc7XG59XG5cbi8qKlxuICogUmVzb2x2ZSB0aGUgT0JPIHdvcmtzcGFjZSBjbGllbnQgKyBob3N0IGZyb20gdGhlIGFjdGl2ZSBBcHBLaXQgZXhlY3V0aW9uXG4gKiBjb250ZXh0LiBJbnNpZGUgYGFnZW50LnN0cmVhbWAncyBgYXNVc2VyKHJlcSlgIHNjb3BlIHRoaXMgaGl0cyB0aGUgc2VydmluZ1xuICogZW5kcG9pbnQgYXMgdGhlIHJlcXVlc3RpbmcgdXNlcjsgb3V0c2lkZSBhIHVzZXIgY29udGV4dCBBcHBLaXQgZmFsbHMgYmFjayB0b1xuICogdGhlIHNlcnZpY2UgcHJpbmNpcGFsLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZVdlYlNlYXJjaENvbnRleHQoKTogUHJvbWlzZTxXZWJTZWFyY2hDb250ZXh0PiB7XG4gIGNvbnN0IGN0eCA9IGdldEV4ZWN1dGlvbkNvbnRleHQoKTtcbiAgY29uc3QgaG9zdCA9IChhd2FpdCBjdHguY2xpZW50LmNvbmZpZy5nZXRIb3N0KCkpLnRvU3RyaW5nKCk7XG4gIHJldHVybiB7IGNsaWVudDogY3R4LmNsaWVudCwgaG9zdCB9O1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSB3ZWItc2VhcmNoLWNhcGFibGUgbW9kZWwgYWdhaW5zdCB0aGUgTElWRSB3b3Jrc3BhY2UgY2F0YWxvZ3VlIC0gc29cbiAqIHdlIG5ldmVyIHJldHVybiBhbiBlbmRwb2ludCBpZCB0aGF0IGlzbid0IGFjdHVhbGx5IGRlcGxveWVkICh0aGUgXCJlbmRwb2ludFxuICogZG9lcyBub3QgZXhpc3RcIiBmYWlsdXJlIGEgaGFyZGNvZGVkIGZhbGxiYWNrIGlkIHdvdWxkIGNhdXNlKS4gUmV1c2VzXG4gKiBgQGRieC10b29scy9tb2RlbGAncyBleGlzdGluZyBjYXRhbG9ndWUgKyByZXNvbHZlciByYXRoZXIgdGhhbiBhIGN1c3RvbVxuICogbG9va3VwOiB7QGxpbmsgbGlzdFNlcnZpbmdFbmRwb2ludHN9IGxpc3RzIHRoZSBlbmRwb2ludHMgKGNhY2hlZCksIGFuZCB3ZVxuICogcmVzdHJpY3QgdGhlIGNhbmRpZGF0ZSBzZXQgdG8gdGhlIHtAbGluayBzdXBwb3J0c1dlYlNlYXJjaH0gb25lcyBiZWZvcmVcbiAqIHtAbGluayByZXNvbHZlTW9kZWx9IGZ1enp5LXBpY2tzIHdpdGhpbiBpdC5cbiAqXG4gKiBSZXR1cm5zIHRoZSBjaG9zZW4gZW5kcG9pbnQgaWQsIG9yIGBudWxsYCB3aGVuIHRoZSB3b3Jrc3BhY2UgaGFzIG5vXG4gKiB3ZWItc2VhcmNoLWNhcGFibGUgbW9kZWwgZGVwbG95ZWQgKHRoZSBjYWxsZXIgdGhlbiB1c2VzIHRoZSBzY3JhcGVcbiAqIGZhbGxiYWNrKS4gQSBwaW4gdGhlIGNhbGxlciBjaG9zZSBkZWxpYmVyYXRlbHkgLSB0aGUgcmVxdWVzdCdzIGBtb2RlbGAsIHRoZVxuICogcGx1Z2luJ3MgYG1vZGVsYCwgb3IgYFdFQl9TRUFSQ0hfTU9ERUxgIC0gdGhyb3dzIHdoZW4gaXQgcmVzb2x2ZXMgdG8gYW5cbiAqIHVuc3VwcG9ydGVkIC8gYWJzZW50IGVuZHBvaW50LCBzbyBhIGJhZCBwaW4gc3VyZmFjZXMgcmF0aGVyIHRoYW4gc2lsZW50bHlcbiAqIGRlZ3JhZGluZy4gQSBwaW4gaW5oZXJpdGVkIGZyb20gdGhlIHNoYXJlZCBgREFUQUJSSUNLU19TRVJWSU5HX0VORFBPSU5UX05BTUVgXG4gKiBiaW5kaW5nIGlzIG9ubHkgYSBwcmVmZXJlbmNlOiB0aGF0IGVuZHBvaW50IGlzIHRoZSBhcHAncyBzZXJ2aW5nIGVuZHBvaW50LFxuICogbm90IG5lY2Vzc2FyaWx5IGEgd2ViLXNlYXJjaC1jYXBhYmxlIG9uZSwgc28gYW4gdW51c2FibGUgdmFsdWUgZmFsbHMgdGhyb3VnaFxuICogdG8gdGhlIGZhbGxiYWNrIG9yZGVyLlxuICovXG5hc3luYyBmdW5jdGlvbiByZXNvbHZlV2ViU2VhcmNoTW9kZWwoXG4gIGN0eDogV2ViU2VhcmNoQ29udGV4dCxcbiAgY29uZmlnOiBSZXNvbHZlZFdlYlNlYXJjaENvbmZpZyxcbiAgcmVxdWVzdGVkOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4pOiBQcm9taXNlPHN0cmluZyB8IG51bGw+IHtcbiAgY29uc3QgZW5kcG9pbnRzID0gYXdhaXQgbGlzdFNlcnZpbmdFbmRwb2ludHMoY3R4LmNsaWVudCwgY3R4Lmhvc3QpO1xuICAvLyBPbmx5IGRlcGxveWVkLCB3ZWItc2VhcmNoLWNhcGFibGUgZW5kcG9pbnRzIGFyZSBjYW5kaWRhdGVzLlxuICBjb25zdCBjYXBhYmxlID0gZW5kcG9pbnRzLmZpbHRlcigoZSkgPT4gc3VwcG9ydHNXZWJTZWFyY2goZS5uYW1lKSk7XG4gIGNvbnN0IHBpbm5lZCA9IHJlcXVlc3RlZCA/PyBjb25maWcubW9kZWw7XG4gIGNvbnN0IHBpbklzRGVsaWJlcmF0ZSA9XG4gICAgcmVxdWVzdGVkICE9PSB1bmRlZmluZWQgfHwgY29uZmlnLm1vZGVsU291cmNlID09PSBcImNvbmZpZ1wiIHx8IGNvbmZpZy5tb2RlbFNvdXJjZSA9PT0gTU9ERUxfRU5WO1xuXG4gIGlmIChwaW5uZWQpIHtcbiAgICAvLyBSZXNvbHZlIHRoZSBleHBsaWNpdCBhc2sgd2l0aGluIHRoZSBjYXBhYmxlIHNldCBvbmx5LlxuICAgIGNvbnN0IHsgbW9kZWxJZCB9ID0gcmVzb2x2ZU1vZGVsKGNhcGFibGUsIHtcbiAgICAgIGV4cGxpY2l0OiBwaW5uZWQsXG4gICAgICBmdXp6eTogY29uZmlnLmZ1enp5LFxuICAgICAgdGhyZXNob2xkOiBjb25maWcuZnV6enlUaHJlc2hvbGQsXG4gICAgfSk7XG4gICAgLy8gcmVzb2x2ZU1vZGVsIHJldHVybnMgdGhlIGlucHV0IHZlcmJhdGltIG9uIG5vIG1hdGNoOyByZXF1aXJlIGl0IHRvIGJlIGFcbiAgICAvLyByZWFsIGNhcGFibGUgZW5kcG9pbnQgc28gYSBiYWQgcGluIGlzIGEgY2xlYXIgZXJyb3IsIG5vdCBhIHBoYW50b20gY2FsbC5cbiAgICBjb25zdCB1c2FibGUgPSBjYXBhYmxlLnNvbWUoKGUpID0+IGUubmFtZSA9PT0gbW9kZWxJZCkgJiYgc3VwcG9ydHNXZWJTZWFyY2gobW9kZWxJZCk7XG4gICAgaWYgKHVzYWJsZSkgcmV0dXJuIG1vZGVsSWQ7XG4gICAgY29uc3QgZGVwbG95ZWQgPSBjYXBhYmxlLm1hcCgoZSkgPT4gZS5uYW1lKS5qb2luKFwiLCBcIikgfHwgXCJub25lXCI7XG4gICAgaWYgKCFwaW5Jc0RlbGliZXJhdGUpIHtcbiAgICAgIGxvZ2dlci5pbmZvKFwic2hhcmVkLWVuZHBvaW50LW5vdC13ZWItY2FwYWJsZVwiLCB7XG4gICAgICAgIGVudlZhcjogU0VSVklOR19FTkRQT0lOVF9FTlYsXG4gICAgICAgIGRlcGxveWVkLFxuICAgICAgfSk7XG4gICAgfSBlbHNlIGlmIChyZXF1ZXN0ZWQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhyb3cgVmFsaWRhdGlvbkVycm9yLmludmFsaWRWYWx1ZShcbiAgICAgICAgXCJtb2RlbFwiLFxuICAgICAgICBwaW5uZWQsXG4gICAgICAgIGBhIGRlcGxveWVkIHdlYi1zZWFyY2gtY2FwYWJsZSBlbmRwb2ludCAoZGVwbG95ZWQ6ICR7ZGVwbG95ZWR9KWAsXG4gICAgICApO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBDb25maWd1cmF0aW9uRXJyb3IucmVzb3VyY2VOb3RGb3VuZChcbiAgICAgICAgXCJXZWItc2VhcmNoLWNhcGFibGUgc2VydmluZyBlbmRwb2ludFwiLFxuICAgICAgICBgRGVwbG95ZWQgd2ViLXNlYXJjaC1jYXBhYmxlIGVuZHBvaW50czogJHtkZXBsb3llZH0uIFNldCBtb2RlbCBvciAke01PREVMX0VOVn0gdG8gb25lIG9mIHRoZW0uYCxcbiAgICAgICk7XG4gICAgfVxuICB9XG5cbiAgaWYgKGNhcGFibGUubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbDtcblxuICAvLyBOb3RoaW5nIHVzYWJsZSBwaW5uZWQ6IHByZWZlciB0aGUgY29uZmlndXJlZCBmYWxsYmFjayBvcmRlciAoR2VtaW5pLCB0aGVuXG4gIC8vIEdQVCkgd2hlbiB0aG9zZSBpZHMgYXJlIGFjdHVhbGx5IGRlcGxveWVkOyBlbHNlIHRha2UgdGhlIGJlc3QgY2FwYWJsZVxuICAvLyBlbmRwb2ludC5cbiAgY29uc3QgeyBtb2RlbElkIH0gPSByZXNvbHZlTW9kZWwoY2FwYWJsZSwge1xuICAgIGZhbGxiYWNrczogY29uZmlnLm1vZGVsRmFsbGJhY2tzLFxuICAgIGZ1enp5OiBjb25maWcuZnV6enksXG4gICAgdGhyZXNob2xkOiBjb25maWcuZnV6enlUaHJlc2hvbGQsXG4gIH0pO1xuICByZXR1cm4gY2FwYWJsZS5zb21lKChlKSA9PiBlLm5hbWUgPT09IG1vZGVsSWQpID8gbW9kZWxJZCA6IChjYXBhYmxlWzBdPy5uYW1lID8/IG51bGwpO1xufVxuXG4vKipcbiAqIFBPU1QgYSBzZXJ2aW5nIHJlcXVlc3QgYXMgdGhlIE9CTyBjYWxsZXIgYW5kIHJldHVybiB0aGUgcGFyc2VkIEpTT04gYm9keS5cbiAqXG4gKiBBdXRoIGhlYWRlcnMgYXJlIG1pbnRlZCBwZXIgY2FsbCBmcm9tIHRoZSBPQk8gY2xpZW50J3MgU0RLIGNvbmZpZywgd2hpY2hcbiAqIHJlZnJlc2hlcyB0aGUgdG9rZW4gd2hlbiBpdCBpcyBjbG9zZSB0byBleHBpcnksIHNvIHRoZSByZXF1ZXN0IGNhcnJpZXMgdGhlXG4gKiByZXF1ZXN0aW5nIHVzZXIncyBpZGVudGl0eS5cbiAqXG4gKiBUaGlzIGlzIHRoZSBvbmUgRGF0YWJyaWNrcyBjYWxsIGluIHRoZSByZXBvIHRoYXQgZG9lcyBub3QgZ28gdGhyb3VnaFxuICogYGFwaUNsaWVudC5yZXF1ZXN0YCArIGBkYXRhYnJpY2tzLnRvQ29udGV4dGAgKHdoaWNoIGRvZXMgZm9yd2FyZFxuICogY2FuY2VsbGF0aW9uKS4gUmV0cnkgY2xhc3NpZmljYXRpb24gaGVyZSBoYXMgdG8gZGlzdGluZ3Vpc2ggYSBsb2FkLXNoZWQgb3JcbiAqIHNlcnZlciBmYXVsdCBmcm9tIHRoaXMgcmVxdWVzdCdzIG93biA0eHgsIGFuZCBgZmV0Y2hgIGV4cG9zZXMgdGhlIEhUVFAgc3RhdHVzXG4gKiBkaXJlY3RseTsgdGhlIFNESyByYWlzZXMgYW4gYEFwaUVycm9yYCB0aGF0IGNhcnJpZXMgdGhlIHN0YXR1cyB1bmRlclxuICogaW5jb25zaXN0ZW50IGtleXMsIHdoaWNoIGlzIGd1ZXNzd29yayBieSBjb21wYXJpc29uLlxuICovXG5hc3luYyBmdW5jdGlvbiBwb3N0U2VydmluZyhcbiAgY3R4OiBXZWJTZWFyY2hDb250ZXh0LFxuICB1cmw6IHN0cmluZyxcbiAgYm9keTogdW5rbm93bixcbiAgY29uZmlnOiBSZXNvbHZlZFdlYlNlYXJjaENvbmZpZyxcbiAgY2FjaGVLZXk6IHJlYWRvbmx5IChzdHJpbmcgfCBudW1iZXIpW10sXG4gIHNpZ25hbD86IEFib3J0U2lnbmFsLFxuKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4ge1xuICBjb25zdCBwYXlsb2FkID0gYXdhaXQgZXhlY3V0ZVJlYWQoXG4gICAgXCJzZXJ2aW5nLXJlcXVlc3RcIixcbiAgICB0b0NhbGxTZXR0aW5ncyh3ZWJTZWFyY2hFeGVjdXRlRGVmYXVsdHMsIGNvbmZpZy50aW1lb3V0TXMsIGNhY2hlS2V5KSxcbiAgICBhc3luYyAoZXhlY3V0ZVNpZ25hbCk6IFByb21pc2U8dW5rbm93bj4gPT4ge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh1cmwsIHtcbiAgICAgICAgbWV0aG9kOiBcIlBPU1RcIixcbiAgICAgICAgaGVhZGVyczoge1xuICAgICAgICAgIC4uLihhd2FpdCBpbnZva2UuYXV0aEhlYWRlcnMoY3R4LmNsaWVudCkpLFxuICAgICAgICAgIEFjY2VwdDogXCJhcHBsaWNhdGlvbi9qc29uXCIsXG4gICAgICAgICAgXCJDb250ZW50LVR5cGVcIjogXCJhcHBsaWNhdGlvbi9qc29uXCIsXG4gICAgICAgIH0sXG4gICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KGJvZHkpLFxuICAgICAgICAuLi4oZXhlY3V0ZVNpZ25hbCA/IHsgc2lnbmFsOiBleGVjdXRlU2lnbmFsIH0gOiB7fSksXG4gICAgICB9KTtcbiAgICAgIGlmICghcmVzcG9uc2Uub2spIHtcbiAgICAgICAgLy8gTG9hZC1zaGVkZGluZyBhbmQgc2VydmVyIGZhdWx0cyBhcmUgd29ydGggYW5vdGhlciBhdHRlbXB0OyBhIDR4eCBpc1xuICAgICAgICAvLyB0aGlzIHJlcXVlc3QncyBvd24gcHJvYmxlbSwgc28gaXQgbXVzdCBub3QgYmUgcmV0cmllZC5cbiAgICAgICAgY29uc3QgcmV0cnlhYmxlID1cbiAgICAgICAgICByZXNwb25zZS5zdGF0dXMgPj0gU0VSVkVSX0VSUk9SX1NUQVRVUyB8fCByZXNwb25zZS5zdGF0dXMgPT09IFJBVEVfTElNSVRFRF9TVEFUVVM7XG4gICAgICAgIGNvbnN0IG1lc3NhZ2UgPSBgd2ViLXNlYXJjaDogTW9kZWwgU2VydmluZyByZWplY3RlZCB0aGUgc2VhcmNoIHJlcXVlc3QgKEhUVFAgJHtyZXNwb25zZS5zdGF0dXN9KWA7XG4gICAgICAgIHRocm93IHJldHJ5YWJsZVxuICAgICAgICAgID8gbmV3IENvbm5lY3Rpb25FcnJvcihtZXNzYWdlLCB7IGNvbnRleHQ6IHsgc3RhdHVzOiByZXNwb25zZS5zdGF0dXMgfSB9KVxuICAgICAgICAgIDogbmV3IEV4ZWN1dGlvbkVycm9yKG1lc3NhZ2UsIHsgY29udGV4dDogeyBzdGF0dXM6IHJlc3BvbnNlLnN0YXR1cyB9IH0pO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHJlc3BvbnNlLmpzb24oKTtcbiAgICB9LFxuICAgIHNpZ25hbCxcbiAgKTtcbiAgcmV0dXJuIG9iamVjdC5pc1JlY29yZChwYXlsb2FkKSA/IHBheWxvYWQgOiB7fTtcbn1cblxuLyogLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHJlc3BvbnNlIGV4dHJhY3Rpb24gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICovXG5cbi8qKlxuICogRXh0cmFjdCBhbnN3ZXIgdGV4dCArIGNpdGF0aW9ucyBmcm9tIGFuIE9wZW5BSSBSZXNwb25zZXMgQVBJIHBheWxvYWQsIHZpYSB0aGVcbiAqIHNoYXJlZCByZWFkZXIgaW4gYEBkYngtdG9vbHMvc2hhcmVkLW1vZGVsYCAodGhlIHNhbWUgbW9kdWxlIHRoZSBtb2RlbC1wcm94eVxuICogdXNlcyB0byB0cmFuc2xhdGUgdGhlIFJlc3BvbnNlcyB3aXJlIGZvcm1hdCBpbiB0aGUgb3RoZXIgZGlyZWN0aW9uKS5cbiAqL1xuZnVuY3Rpb24gZnJvbVJlc3BvbnNlc1BheWxvYWQocGF5bG9hZDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiB7XG4gIGFuc3dlcjogc3RyaW5nO1xuICBjaXRhdGlvbnM6IFdlYlNlYXJjaENpdGF0aW9uW107XG59IHtcbiAgY29uc3QgeyB0ZXh0LCBjaXRhdGlvbnMgfSA9IG9wZW5haVJlc3BvbnNlcy5yZWFkUmVzcG9uc2VzT3V0cHV0KHBheWxvYWQpO1xuICByZXR1cm4geyBhbnN3ZXI6IHRleHQsIGNpdGF0aW9ucyB9O1xufVxuXG4vKipcbiAqIEV4dHJhY3QgYW5zd2VyIHRleHQgKyBjaXRhdGlvbnMgZnJvbSBhIENoYXQgQ29tcGxldGlvbnMgcGF5bG9hZCAoR2VtaW5pIHZpYVxuICogYGdvb2dsZV9zZWFyY2hgKS4gVGhlIGFuc3dlciBpcyBgY2hvaWNlc1swXS5tZXNzYWdlLmNvbnRlbnRgOyBncm91bmRpbmdcbiAqIHNvdXJjZXMsIHdoZW4gcHJlc2VudCwgc3VyZmFjZSB1bmRlciBgY2hvaWNlc1swXS5tZXNzYWdlYCBncm91bmRpbmdcbiAqIG1ldGFkYXRhIChiZXN0LWVmZm9ydCAtIHNoYXBlcyB2YXJ5LCBzbyB3ZSBzY2FuIGZvciB1cmwtYmVhcmluZyBlbnRyaWVzKS5cbiAqL1xuZnVuY3Rpb24gZnJvbUNoYXRQYXlsb2FkKHBheWxvYWQ6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KToge1xuICBhbnN3ZXI6IHN0cmluZztcbiAgY2l0YXRpb25zOiBXZWJTZWFyY2hDaXRhdGlvbltdO1xufSB7XG4gIGNvbnN0IGNob2ljZXMgPSBBcnJheS5pc0FycmF5KHBheWxvYWQuY2hvaWNlcykgPyBwYXlsb2FkLmNob2ljZXMgOiBbXTtcbiAgY29uc3QgZmlyc3QgPSBjaG9pY2VzWzBdO1xuICBjb25zdCBtZXNzYWdlID0gb2JqZWN0LmlzUmVjb3JkKGZpcnN0KSAmJiBvYmplY3QuaXNSZWNvcmQoZmlyc3QubWVzc2FnZSkgPyBmaXJzdC5tZXNzYWdlIDoge307XG4gIGNvbnN0IGFuc3dlciA9IG9wZW5haUNoYXQuY2hhdENvbnRlbnRUb1RleHQobWVzc2FnZS5jb250ZW50KTtcbiAgY29uc3QgY2l0YXRpb25zOiBXZWJTZWFyY2hDaXRhdGlvbltdID0gW107XG4gIC8vIEJlc3QtZWZmb3J0IGdyb3VuZGluZyBleHRyYWN0aW9uOiB3YWxrIGFueSBuZXN0ZWQgb2JqZWN0IGZvciB7dXJpfHVybCx0aXRsZX0uXG4gIGNvbnN0IHNlZW4gPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgY29uc3QgdmlzaXQgPSAodjogdW5rbm93biwgZGVwdGg6IG51bWJlcik6IHZvaWQgPT4ge1xuICAgIGlmIChkZXB0aCA+IE1BWF9HUk9VTkRJTkdfV0FMS19ERVBUSCB8fCAhb2JqZWN0LmlzUmVjb3JkKHYpKSByZXR1cm47XG4gICAgY29uc3QgdXJsID0gc3RyaW5nLnRyaW1Ub0VtcHR5KHYudXJsKSB8fCBzdHJpbmcudHJpbVRvRW1wdHkodi51cmkpO1xuICAgIGlmICh1cmwgJiYgIXNlZW4uaGFzKHVybCkpIHtcbiAgICAgIHNlZW4uYWRkKHVybCk7XG4gICAgICBjb25zdCB0aXRsZSA9IHN0cmluZy50cmltVG9FbXB0eSh2LnRpdGxlKTtcbiAgICAgIGNpdGF0aW9ucy5wdXNoKHsgdXJsLCAuLi4odGl0bGUgPyB7IHRpdGxlIH0gOiB7fSkgfSk7XG4gICAgfVxuICAgIGZvciAoY29uc3QgdmFsIG9mIE9iamVjdC52YWx1ZXModikpIHtcbiAgICAgIGlmIChBcnJheS5pc0FycmF5KHZhbCkpIHZhbC5mb3JFYWNoKCh4KSA9PiB2aXNpdCh4LCBkZXB0aCArIDEpKTtcbiAgICAgIGVsc2UgaWYgKHZhbCAmJiB0eXBlb2YgdmFsID09PSBcIm9iamVjdFwiKSB2aXNpdCh2YWwsIGRlcHRoICsgMSk7XG4gICAgfVxuICB9O1xuICB2aXNpdChtZXNzYWdlLmdyb3VuZGluZ19tZXRhZGF0YSA/PyBtZXNzYWdlLmdyb3VuZGluZ01ldGFkYXRhLCAwKTtcbiAgcmV0dXJuIHsgYW5zd2VyLCBjaXRhdGlvbnMgfTtcbn1cblxuLyoqXG4gKiBSdW4gYSB3ZWIgc2VhcmNoLiBQcmVmZXJzIHRoZSBEYXRhYnJpY2tzIG5hdGl2ZSB3ZWItc2VhcmNoIHRvb2wgb24gYVxuICogZGVwbG95ZWQgR1BUL0dlbWluaSBlbmRwb2ludCAoc3ludGhlc2l6ZWQgYW5zd2VyICsgY2l0YXRpb25zKTsgd2hlbiB0aGVcbiAqIHdvcmtzcGFjZSBoYXMgbm8gc3VjaCBlbmRwb2ludCBBTkQgdGhlIHNjcmFwZSBmYWxsYmFjayBpcyBlbmFibGVkLCBmYWxsc1xuICogYmFjayB0byBhIER1Y2tEdWNrR28gc2NyYXBlIHNvIHRoZSB0b29sIHN0aWxsIHJldHVybnMgcmVzdWx0cyBpbnN0ZWFkIG9mXG4gKiBlcnJvcmluZy4gQ2l0YXRpb25zIGFyZSBmaWx0ZXJlZCB0aHJvdWdoIHRoZSBjb25maWd1cmVkIFVSTCBhbGxvdy1saXN0LlxuICpcbiAqIGBzaWduYWxgIGNhbmNlbHMgdGhlIHdob2xlIGNhbGwsIGluY2x1ZGluZyB0aGUgaW4tZmxpZ2h0IHNlcnZpbmcgcmVxdWVzdC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bldlYlNlYXJjaChcbiAgcmVxdWVzdDogV2ViU2VhcmNoUmVxdWVzdCxcbiAgY29uZmlnOiBSZXNvbHZlZFdlYlNlYXJjaENvbmZpZyxcbiAgY3R4OiBXZWJTZWFyY2hDb250ZXh0LFxuICBzaWduYWw/OiBBYm9ydFNpZ25hbCxcbik6IFByb21pc2U8V2ViU2VhcmNoUmVzdWx0PiB7XG4gIGNvbnN0IG1vZGVsSWQgPSBhd2FpdCByZXNvbHZlV2ViU2VhcmNoTW9kZWwoY3R4LCBjb25maWcsIHJlcXVlc3QubW9kZWwpO1xuXG4gIGlmIChtb2RlbElkID09PSBudWxsKSB7XG4gICAgLy8gTm8gbmF0aXZlIHdlYi1zZWFyY2ggbW9kZWwgZGVwbG95ZWQgaW4gdGhpcyB3b3Jrc3BhY2UuXG4gICAgaWYgKGNvbmZpZy5zY3JhcGVGYWxsYmFjaykge1xuICAgICAgbG9nZ2VyLmluZm8oXCJuby1uYXRpdmUtbW9kZWw6c2NyYXBlLWZhbGxiYWNrXCIsIHsgcXVlcnk6IHJlcXVlc3QucXVlcnkgfSk7XG4gICAgICByZXR1cm4gcnVuU2NyYXBlU2VhcmNoKHJlcXVlc3QsIGNvbmZpZywgc2lnbmFsKTtcbiAgICB9XG4gICAgdGhyb3cgQ29uZmlndXJhdGlvbkVycm9yLnJlc291cmNlTm90Rm91bmQoXG4gICAgICBcIldlYi1zZWFyY2gtY2FwYWJsZSBzZXJ2aW5nIGVuZHBvaW50XCIsXG4gICAgICBcIk5vIEdQVC9HZW1pbmkgZW5kcG9pbnQgaXMgZGVwbG95ZWQgaW4gdGhpcyB3b3Jrc3BhY2UgYW5kIHRoZSBzY3JhcGUgZmFsbGJhY2sgaXMgXCIgK1xuICAgICAgICBgZGlzYWJsZWQuIERlcGxveSBhIHN1cHBvcnRlZCBlbmRwb2ludCwgc2V0IG1vZGVsIG9yICR7TU9ERUxfRU5WfSwgb3IgZW5hYmxlIHRoZSBgICtcbiAgICAgICAgXCJmYWxsYmFjayAoV0VCX1NFQVJDSF9TQ1JBUEVfRkFMTEJBQ0s9MSkuXCIsXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IHByb3ZpZGVyID0gZGV0ZWN0V2ViU2VhcmNoUHJvdmlkZXIobW9kZWxJZCkhOyAvLyBndWFyYW50ZWVkIGJ5IHJlc29sdmUgc3RlcFxuICBjb25zdCBzcGVjID0gd2ViU2VhcmNoVG9vbFNwZWMocHJvdmlkZXIsIGNvbmZpZy53ZWJTZWFyY2hUb29scyk7XG5cbiAgY29uc3QgdXJsID1cbiAgICBzcGVjLmFwaSA9PT0gXCJyZXNwb25zZXNcIiA/IGludm9rZS5yZXNwb25zZXNVcmwoY3R4Lmhvc3QpIDogaW52b2tlLmNoYXRDb21wbGV0aW9uc1VybChjdHguaG9zdCk7XG4gIGNvbnN0IGJvZHkgPVxuICAgIHNwZWMuYXBpID09PSBcInJlc3BvbnNlc1wiXG4gICAgICA/IHtcbiAgICAgICAgICBtb2RlbDogbW9kZWxJZCxcbiAgICAgICAgICBpbnB1dDogW3sgcm9sZTogXCJ1c2VyXCIsIGNvbnRlbnQ6IHJlcXVlc3QucXVlcnkgfV0sXG4gICAgICAgICAgdG9vbHM6IFtzcGVjLnRvb2xdLFxuICAgICAgICB9XG4gICAgICA6IHtcbiAgICAgICAgICBtb2RlbDogbW9kZWxJZCxcbiAgICAgICAgICBtZXNzYWdlczogW3sgcm9sZTogXCJ1c2VyXCIsIGNvbnRlbnQ6IHJlcXVlc3QucXVlcnkgfV0sXG4gICAgICAgICAgdG9vbHM6IFtzcGVjLnRvb2xdLFxuICAgICAgICB9O1xuXG4gIGNvbnN0IHBheWxvYWQgPSBhd2FpdCBwb3N0U2VydmluZyhcbiAgICBjdHgsXG4gICAgdXJsLFxuICAgIGJvZHksXG4gICAgY29uZmlnLFxuICAgIFtcIndlYi1zZWFyY2hcIiwgXCJzZXJ2aW5nXCIsIHNwZWMuYXBpLCBtb2RlbElkLCByZXF1ZXN0LnF1ZXJ5XSxcbiAgICBzaWduYWwsXG4gICk7XG5cbiAgY29uc3QgeyBhbnN3ZXIsIGNpdGF0aW9ucyB9ID1cbiAgICBzcGVjLmFwaSA9PT0gXCJyZXNwb25zZXNcIiA/IGZyb21SZXNwb25zZXNQYXlsb2FkKHBheWxvYWQpIDogZnJvbUNoYXRQYXlsb2FkKHBheWxvYWQpO1xuXG4gIGNvbnN0IHBlcm1pdHRlZCA9IGNpdGF0aW9ucy5maWx0ZXIoKGMpID0+IGNvbmZpZy5hbGxvd0xpc3QuYWxsb3dzKGMudXJsKSk7XG4gIGNvbnN0IGRyb3BwZWQgPSBjaXRhdGlvbnMubGVuZ3RoIC0gcGVybWl0dGVkLmxlbmd0aDtcbiAgY29uc3QgdHJpbW1lZCA9IHBlcm1pdHRlZC5zbGljZSgwLCBjb25maWcubWF4Q2l0YXRpb25zKTtcbiAgbG9nZ2VyLmRlYnVnKFwic2VhcmNoZWRcIiwge1xuICAgIHF1ZXJ5OiByZXF1ZXN0LnF1ZXJ5LFxuICAgIG1vZGVsOiBtb2RlbElkLFxuICAgIHByb3ZpZGVyLFxuICAgIGNpdGF0aW9uczogY2l0YXRpb25zLmxlbmd0aCxcbiAgICAuLi4oZHJvcHBlZCA+IDAgPyB7IGZpbHRlcmVkOiBkcm9wcGVkIH0gOiB7fSksXG4gICAgcmV0dXJuZWQ6IHRyaW1tZWQubGVuZ3RoLFxuICB9KTtcblxuICByZXR1cm4geyBxdWVyeTogcmVxdWVzdC5xdWVyeSwgYW5zd2VyLCBjaXRhdGlvbnM6IHRyaW1tZWQsIG1vZGVsOiBtb2RlbElkIH07XG59XG4iXX0=
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The `web_search` and `web_fetch` Mastra tools.
3
+ *
4
+ * `web_search` is backed by the Databricks Model Serving native web-search
5
+ * tool: it resolves its own web-search-capable model (see `search.ts`) and
6
+ * calls the workspace serving endpoint under the caller's OBO scope, so the
7
+ * search runs as the requesting user and independently of the agent's chat
8
+ * model. `web_fetch` reads a page via got-scraping.
9
+ *
10
+ * Both are read-only and run without approval by default; each accepts an
11
+ * optional {@link ApprovalGate} (`approval`) that maps onto Mastra's
12
+ * `requireApproval`. `true` gates every call; a URL-pattern (or {@link OneOrMany}
13
+ * list) gates only calls whose URL matches - for `web_fetch` that is evaluated
14
+ * against the target URL, while `web_search` (whose result URLs aren't known
15
+ * before the call) treats a pattern gate as "always gate". `approval` falls
16
+ * back to the plugin's `approval` config when a tool omits its own.
17
+ *
18
+ * The same two tools are exposed to AppKit's own agents through the plugin's
19
+ * `ToolProvider` (see `plugin.ts`); this module is the Mastra half.
20
+ *
21
+ * @module
22
+ */
23
+ import { type ApprovalGate, type ApprovalPolicy } from "./config.js";
24
+ /** Options shared by both web tools. */
25
+ export interface WebSearchToolOptions {
26
+ /** Override the tool id. */
27
+ id?: string;
28
+ /**
29
+ * Approval gate for this tool, overriding the plugin's `approval`. `true`
30
+ * gates every call; a URL-pattern (or list) gates only matching calls;
31
+ * omit / `false` for no approval. See {@link ApprovalGate}.
32
+ */
33
+ approval?: ApprovalGate | ApprovalPolicy;
34
+ }
35
+ /**
36
+ * Build the `web_search` tool. Spread it into the agents that should be able
37
+ * to search the web.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * import { webSearchTool } from "@dbx-tools/appkit-web-search";
42
+ * import { createAgent } from "@dbx-tools/appkit-mastra";
43
+ *
44
+ * const researcher = createAgent({
45
+ * instructions: "...",
46
+ * tools: () => ({ web_search: webSearchTool() }),
47
+ * });
48
+ * ```
49
+ */
50
+ export declare function webSearchTool(opts?: WebSearchToolOptions): import("@mastra/core/tools").Tool<{
51
+ query: string;
52
+ model?: string | undefined;
53
+ }, {
54
+ query: string;
55
+ answer: string;
56
+ citations: {
57
+ url: string;
58
+ title?: string | undefined;
59
+ snippet?: string | undefined;
60
+ }[];
61
+ model: string;
62
+ }, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, string, unknown>;
63
+ /**
64
+ * Build the `web_fetch` tool. Spread it into the agents that should be able
65
+ * to read a page's contents.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * import { webFetchTool } from "@dbx-tools/appkit-web-search";
70
+ *
71
+ * tools: () => ({ web_fetch: webFetchTool({ approval: "*.internal.example.com" }) })
72
+ * ```
73
+ */
74
+ export declare function webFetchTool(opts?: WebSearchToolOptions): import("@mastra/core/tools").Tool<{
75
+ url: string;
76
+ format?: "text" | "html" | undefined;
77
+ maxLength?: number | undefined;
78
+ }, {
79
+ url: string;
80
+ status: number;
81
+ content: string;
82
+ truncated: boolean;
83
+ contentType?: string | undefined;
84
+ title?: string | undefined;
85
+ }, unknown, unknown, import("@mastra/core/tools").ToolExecutionContext<unknown, unknown, unknown>, string, unknown>;