@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.
- package/lib/index.d.ts +32 -0
- package/lib/index.js +28 -0
- package/lib/src/allowlist.d.ts +66 -0
- package/lib/src/allowlist.js +113 -0
- package/lib/src/config.d.ts +233 -0
- package/lib/src/config.js +269 -0
- package/lib/src/defaults.d.ts +98 -0
- package/lib/src/defaults.js +96 -0
- package/lib/src/fetch.d.ts +27 -0
- package/lib/src/fetch.js +86 -0
- package/lib/src/html-text.d.ts +30 -0
- package/lib/src/html-text.js +66 -0
- package/lib/src/plugin.d.ts +162 -0
- package/lib/src/plugin.js +225 -0
- package/lib/src/provider.d.ts +62 -0
- package/lib/src/provider.js +89 -0
- package/lib/src/runtime.d.ts +61 -0
- package/lib/src/runtime.js +95 -0
- package/lib/src/schema.d.ts +76 -0
- package/lib/src/schema.js +102 -0
- package/lib/src/scrape.d.ts +29 -0
- package/lib/src/scrape.js +113 -0
- package/lib/src/search.d.ts +49 -0
- package/lib/src/search.js +252 -0
- package/lib/src/tool.d.ts +85 -0
- package/lib/src/tool.js +113 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +13 -9
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2NyYXBlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NjcmFwZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFM0MsT0FBTyxFQUFFLDJCQUEyQixFQUFFLGNBQWMsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUN6RSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDakQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUd4QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFFL0Msa0VBQWtFO0FBQ2xFLE1BQU0sWUFBWSxHQUFHLG1DQUFtQyxDQUFDO0FBRXpEOzs7R0FHRztBQUNILFNBQVMsWUFBWSxDQUFDLElBQVk7SUFDaEMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsSUFBSSxFQUFFLHdCQUF3QixDQUFDLENBQUM7UUFDbEQsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDMUMsSUFBSSxNQUFNO1lBQUUsT0FBTyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUM5QyxPQUFPLENBQUMsQ0FBQyxRQUFRLEtBQUssT0FBTyxJQUFJLENBQUMsQ0FBQyxRQUFRLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUNqRixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELHlFQUF5RTtBQUN6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cGluZyBmYWxsYmFjayBmb3IgYHdlYl9zZWFyY2hgLCB1c2VkIE9OTFkgd2hlbiB0aGVcbiAqIHdvcmtzcGFjZSBoYXMgbm8gRGF0YWJyaWNrcyB3ZWItc2VhcmNoLWNhcGFibGUgbW9kZWwgZGVwbG95ZWQgKG5vIEdQVCAvXG4gKiBHZW1pbmkgc2VydmluZyBlbmRwb2ludCkuIFRoZSBuYXRpdmUgRGF0YWJyaWNrcyB3ZWItc2VhcmNoIHRvb2wgaXMgYWx3YXlzXG4gKiBwcmVmZXJyZWQgKHNlZSBgc2VhcmNoLnRzYCk7IHRoaXMgZXhpc3RzIHNvIHRoZSB0b29sIHN0aWxsIHJldHVybnMgdXNlZnVsXG4gKiByZXN1bHRzIGluIGFuIGVudmlyb25tZW50IHRoYXQgY2FuJ3QgcnVuIGl0LCByYXRoZXIgdGhhbiBlcnJvcmluZyBvbiBldmVyeVxuICogY2FsbC5cbiAqXG4gKiBJdCBxdWVyaWVzIER1Y2tEdWNrR28ncyBuby1KUyBIVE1MIGVuZHBvaW50IHRocm91Z2ggYGdvdC1zY3JhcGluZ2BcbiAqIChicm93c2VyLWxpa2UgZmluZ2VycHJpbnRzIHNvIHRoZSByZXF1ZXN0IGlzbid0IGJsb2NrZWQpIHZpYSBhIEdFVCB3aXRoIHRoZVxuICogcXVlcnkgaW4gdGhlIHF1ZXJ5IHN0cmluZyAtIGEgUE9TVCB0byB0aGUgc2FtZSBlbmRwb2ludCB0cmlwcyBEREcncyBib3RcbiAqIGNoYWxsZW5nZSAoSFRUUCAyMDIpLCB3aGlsZSB0aGUgR0VUIHJldHVybnMgbm9ybWFsIHJlc3VsdCBtYXJrdXAuIEl0IHRoZW5cbiAqIHBhcnNlcyB0aGUgcmVzdWx0IGFuY2hvcnMgKyBzbmlwcGV0cy4gVW5saWtlIHRoZSBuYXRpdmUgdG9vbCB0aGVyZSBpcyBub1xuICogbW9kZWwgc3ludGhlc2l6aW5nIGFuIGFuc3dlciwgc28gYGFuc3dlcmAgaXMgYSBzaG9ydCBsZWFkLWluIG92ZXIgdGhlIHRvcFxuICogc25pcHBldHMgYW5kIHRoZSBzdWJzdGFuY2UgcmlkZXMgaW4gYGNpdGF0aW9uc2AgLSB0aGUgY2FsbGluZyBhZ2VudCByZWFkc1xuICogdGhvc2UgYW5kIHdyaXRlcyBpdHMgb3duIGFuc3dlci5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IGdvdFNjcmFwaW5nIH0gZnJvbSBcImdvdC1zY3JhcGluZ1wiO1xuaW1wb3J0IHR5cGUgeyBSZXNvbHZlZFdlYlNlYXJjaENvbmZpZyB9IGZyb20gXCIuL2NvbmZpZ1wiO1xuaW1wb3J0IHsgc2NyYXBlU2VhcmNoRXhlY3V0ZURlZmF1bHRzLCB0b0NhbGxTZXR0aW5ncyB9IGZyb20gXCIuL2RlZmF1bHRzXCI7XG5pbXBvcnQgeyBodG1sRnJhZ21lbnRUb1RleHQgfSBmcm9tIFwiLi9odG1sLXRleHRcIjtcbmltcG9ydCB7IGV4ZWN1dGVSZWFkIH0gZnJvbSBcIi4vcnVudGltZVwiO1xuaW1wb3J0IHR5cGUgeyBXZWJTZWFyY2hDaXRhdGlvbiwgV2ViU2VhcmNoUmVxdWVzdCwgV2ViU2VhcmNoUmVzdWx0IH0gZnJvbSBcIi4vc2NoZW1hXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ3ZWItc2VhcmNoL3NjcmFwZVwiKTtcblxuLyoqIER1Y2tEdWNrR28ncyBuby1KUyBIVE1MIHJlc3VsdHMgZW5kcG9pbnQgKHF1ZXJpZWQgdmlhIEdFVCkuICovXG5jb25zdCBEREdfSFRNTF9VUkwgPSBcImh0dHBzOi8vaHRtbC5kdWNrZHVja2dvLmNvbS9odG1sL1wiO1xuXG4vKipcbiAqIERERyB3cmFwcyByZXN1bHQgVVJMcyBpbiBhIHJlZGlyZWN0IChgLy9kdWNrZHVja2dvLmNvbS9sLz91ZGRnPTxlbmNvZGVkPmApLlxuICogVW53cmFwIHRvIHRoZSByZWFsIGRlc3RpbmF0aW9uOyBsZWF2ZSBhbHJlYWR5LWFic29sdXRlIFVSTHMgdW50b3VjaGVkLlxuICovXG5mdW5jdGlvbiB1bndyYXBEZGdVcmwoaHJlZjogc3RyaW5nKTogc3RyaW5nIHtcbiAgdHJ5IHtcbiAgICBjb25zdCB1ID0gbmV3IFVSTChocmVmLCBcImh0dHBzOi8vZHVja2R1Y2tnby5jb21cIik7XG4gICAgY29uc3QgdGFyZ2V0ID0gdS5zZWFyY2hQYXJhbXMuZ2V0KFwidWRkZ1wiKTtcbiAgICBpZiAodGFyZ2V0KSByZXR1cm4gZGVjb2RlVVJJQ29tcG9uZW50KHRhcmdldCk7XG4gICAgcmV0dXJuIHUucHJvdG9jb2wgPT09IFwiaHR0cDpcIiB8fCB1LnByb3RvY29sID09PSBcImh0dHBzOlwiID8gdS50b1N0cmluZygpIDogaHJlZjtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIGhyZWY7XG4gIH1cbn1cblxuLyoqIFBhcnNlIERERyBIVE1MIHJlc3VsdCBibG9ja3MgaW50byBjaXRhdGlvbnMgKHRpdGxlLCB1cmwsIHNuaXBwZXQpLiAqL1xuZnVuY3Rpb24gcGFyc2VEZGdIdG1sKGh0bWw6IHN0cmluZyk6IFdlYlNlYXJjaENpdGF0aW9uW10ge1xuICBjb25zdCBjaXRhdGlvbnM6IFdlYlNlYXJjaENpdGF0aW9uW10gPSBbXTtcbiAgLy8gRWFjaCByZXN1bHQ6IGFuIGFuY2hvciB3aXRoIGNsYXNzIHJlc3VsdF9fYSAodGl0bGUgKyBocmVmKSBhbmQgYSBzbmlwcGV0XG4gIC8vIHdpdGggY2xhc3MgcmVzdWx0X19zbmlwcGV0LiBNYXRjaCBhbmNob3JzLCB0aGVuIHRoZSBmb2xsb3dpbmcgc25pcHBldC5cbiAgY29uc3QgYW5jaG9yUmUgPSAvPGFbXj5dKmNsYXNzPVwiW15cIl0qcmVzdWx0X19hW15cIl0qXCJbXj5dKmhyZWY9XCIoW15cIl0rKVwiW14+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>;
|