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

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 ADDED
@@ -0,0 +1,32 @@
1
+ export * as allowlist from "./src/allowlist.js";
2
+ export * as config from "./src/config.js";
3
+ export * as defaults from "./src/defaults.js";
4
+ export * as fetch from "./src/fetch.js";
5
+ export * as htmlText from "./src/html-text.js";
6
+ export * as plugin from "./src/plugin.js";
7
+ export * as provider from "./src/provider.js";
8
+ export * as runtime from "./src/runtime.js";
9
+ export * as schema from "./src/schema.js";
10
+ export * as scrape from "./src/scrape.js";
11
+ export * as search from "./src/search.js";
12
+ export * as tool from "./src/tool.js";
13
+ export { normalizeUrlPattern, parseAllowedUrls, toUrlAllowList, assertUrlAllowed } from "./src/allowlist.js";
14
+ export type { UrlAllowList } from "./src/allowlist.js";
15
+ export { MODEL_ENV, SERVING_ENDPOINT_ENV, DEFAULT_MODEL_FALLBACKS, DEFAULT_MAX_CITATIONS, DEFAULT_FETCH_MAX_LENGTH, DEFAULT_TIMEOUT_MS, WEB_SEARCH_CONFIG_SCHEMA, toApprovalPolicy, resolveWebSearchConfig, approvalMatches } from "./src/config.js";
16
+ export type { ApprovalGate, ApprovalPolicy, UrlPolicyMode, ModelSource, WebSearchPluginConfig, ResolvedWebSearchConfig } from "./src/config.js";
17
+ export { SEARCH_CACHE_TTL_SECONDS, FETCH_CACHE_TTL_SECONDS, SERVING_RETRY_ATTEMPTS, WEB_RETRY_ATTEMPTS, webSearchExecuteDefaults, webFetchExecuteDefaults, scrapeSearchExecuteDefaults, toCallSettings } from "./src/defaults.js";
18
+ export type { WebSearchExecuteConfig, WebSearchExecutionSettings } from "./src/defaults.js";
19
+ export { runWebFetch } from "./src/fetch.js";
20
+ export { decodeHtmlEntities, htmlFragmentToText, htmlToText } from "./src/html-text.js";
21
+ export { WebSearchPlugin, webSearch } from "./src/plugin.js";
22
+ export { WEB_SEARCH_PROVIDERS, detectWebSearchProvider, supportsWebSearch, webSearchToolSpec } from "./src/provider.js";
23
+ export type { WebSearchProvider, WebSearchProviderSpec } from "./src/provider.js";
24
+ export { getWebSearchRuntime, setWebSearchExecutor, resetWebSearchRuntime, executeRead } from "./src/runtime.js";
25
+ export type { WebSearchExecutor, WebSearchRuntime } from "./src/runtime.js";
26
+ export { WEB_SEARCH_TOOL_DESCRIPTION, WEB_FETCH_TOOL_DESCRIPTION, webSearchRequestSchema, webSearchCitationSchema, webSearchResultSchema, webFetchRequestSchema, webFetchResultSchema } from "./src/schema.js";
27
+ export type { WebSearchRequest, WebSearchCitation, WebSearchResult, WebFetchRequest, WebFetchResult } from "./src/schema.js";
28
+ export { runScrapeSearch } from "./src/scrape.js";
29
+ export { resolveWebSearchContext, runWebSearch } from "./src/search.js";
30
+ export type { WebSearchContext } from "./src/search.js";
31
+ export { webSearchTool, webFetchTool } from "./src/tool.js";
32
+ export type { WebSearchToolOptions } from "./src/tool.js";
package/lib/index.js ADDED
@@ -0,0 +1,28 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export * as allowlist from "./src/allowlist.js";
5
+ export * as config from "./src/config.js";
6
+ export * as defaults from "./src/defaults.js";
7
+ export * as fetch from "./src/fetch.js";
8
+ export * as htmlText from "./src/html-text.js";
9
+ export * as plugin from "./src/plugin.js";
10
+ export * as provider from "./src/provider.js";
11
+ export * as runtime from "./src/runtime.js";
12
+ export * as schema from "./src/schema.js";
13
+ export * as scrape from "./src/scrape.js";
14
+ export * as search from "./src/search.js";
15
+ export * as tool from "./src/tool.js";
16
+ export { normalizeUrlPattern, parseAllowedUrls, toUrlAllowList, assertUrlAllowed } from "./src/allowlist.js";
17
+ export { MODEL_ENV, SERVING_ENDPOINT_ENV, DEFAULT_MODEL_FALLBACKS, DEFAULT_MAX_CITATIONS, DEFAULT_FETCH_MAX_LENGTH, DEFAULT_TIMEOUT_MS, WEB_SEARCH_CONFIG_SCHEMA, toApprovalPolicy, resolveWebSearchConfig, approvalMatches } from "./src/config.js";
18
+ export { SEARCH_CACHE_TTL_SECONDS, FETCH_CACHE_TTL_SECONDS, SERVING_RETRY_ATTEMPTS, WEB_RETRY_ATTEMPTS, webSearchExecuteDefaults, webFetchExecuteDefaults, scrapeSearchExecuteDefaults, toCallSettings } from "./src/defaults.js";
19
+ export { runWebFetch } from "./src/fetch.js";
20
+ export { decodeHtmlEntities, htmlFragmentToText, htmlToText } from "./src/html-text.js";
21
+ export { WebSearchPlugin, webSearch } from "./src/plugin.js";
22
+ export { WEB_SEARCH_PROVIDERS, detectWebSearchProvider, supportsWebSearch, webSearchToolSpec } from "./src/provider.js";
23
+ export { getWebSearchRuntime, setWebSearchExecutor, resetWebSearchRuntime, executeRead } from "./src/runtime.js";
24
+ export { WEB_SEARCH_TOOL_DESCRIPTION, WEB_FETCH_TOOL_DESCRIPTION, webSearchRequestSchema, webSearchCitationSchema, webSearchResultSchema, webFetchRequestSchema, webFetchResultSchema } from "./src/schema.js";
25
+ export { runScrapeSearch } from "./src/scrape.js";
26
+ export { resolveWebSearchContext, runWebSearch } from "./src/search.js";
27
+ export { webSearchTool, webFetchTool } from "./src/tool.js";
28
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssU0FBUyxNQUFNLGlCQUFpQixDQUFDO0FBQzdDLE9BQU8sS0FBSyxNQUFNLE1BQU0sY0FBYyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxRQUFRLE1BQU0sZ0JBQWdCLENBQUM7QUFDM0MsT0FBTyxLQUFLLEtBQUssTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxpQkFBaUIsQ0FBQztBQUM1QyxPQUFPLEtBQUssTUFBTSxNQUFNLGNBQWMsQ0FBQztBQUN2QyxPQUFPLEtBQUssUUFBUSxNQUFNLGdCQUFnQixDQUFDO0FBQzNDLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxNQUFNLE1BQU0sY0FBYyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxNQUFNLE1BQU0sY0FBYyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxNQUFNLE1BQU0sY0FBYyxDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxJQUFJLE1BQU0sWUFBWSxDQUFDO0FBQ25DLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxnQkFBZ0IsRUFBRSxjQUFjLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUUxRyxPQUFPLEVBQUUsU0FBUyxFQUFFLG9CQUFvQixFQUFFLHVCQUF1QixFQUFFLHFCQUFxQixFQUFFLHdCQUF3QixFQUFFLGtCQUFrQixFQUFFLHdCQUF3QixFQUFFLGdCQUFnQixFQUFFLHNCQUFzQixFQUFFLGVBQWUsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUVsUCxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsdUJBQXVCLEVBQUUsc0JBQXNCLEVBQUUsa0JBQWtCLEVBQUUsd0JBQXdCLEVBQUUsdUJBQXVCLEVBQUUsMkJBQTJCLEVBQUUsY0FBYyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFL04sT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUMxQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsa0JBQWtCLEVBQUUsVUFBVSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDckYsT0FBTyxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDMUQsT0FBTyxFQUFFLG9CQUFvQixFQUFFLHVCQUF1QixFQUFFLGlCQUFpQixFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFckgsT0FBTyxFQUFFLG1CQUFtQixFQUFFLG9CQUFvQixFQUFFLHFCQUFxQixFQUFFLFdBQVcsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUU5RyxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsMEJBQTBCLEVBQUUsc0JBQXNCLEVBQUUsdUJBQXVCLEVBQUUscUJBQXFCLEVBQUUscUJBQXFCLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFFNU0sT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUMvQyxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsWUFBWSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRXJFLE9BQU8sRUFBRSxhQUFhLEVBQUUsWUFBWSxFQUFFLE1BQU0sWUFBWSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gR0VORVJBVEVEIGJ5IHByb2plbiB3YXRjaCAtIERPIE5PVCBFRElULlxuLy8gUmVnZW5lcmF0ZWQgZnJvbSB0aGUgZXhwb3J0aW5nIG1vZHVsZXMgaW4gLi9zcmMuXG4vLyBIYW5kIGVkaXRzIGFyZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV4dCB3YXRjaDsgdGhpcyBmaWxlIGlzIHJlYWQtb25seS5cblxuZXhwb3J0ICogYXMgYWxsb3dsaXN0IGZyb20gXCIuL3NyYy9hbGxvd2xpc3RcIjtcbmV4cG9ydCAqIGFzIGNvbmZpZyBmcm9tIFwiLi9zcmMvY29uZmlnXCI7XG5leHBvcnQgKiBhcyBkZWZhdWx0cyBmcm9tIFwiLi9zcmMvZGVmYXVsdHNcIjtcbmV4cG9ydCAqIGFzIGZldGNoIGZyb20gXCIuL3NyYy9mZXRjaFwiO1xuZXhwb3J0ICogYXMgaHRtbFRleHQgZnJvbSBcIi4vc3JjL2h0bWwtdGV4dFwiO1xuZXhwb3J0ICogYXMgcGx1Z2luIGZyb20gXCIuL3NyYy9wbHVnaW5cIjtcbmV4cG9ydCAqIGFzIHByb3ZpZGVyIGZyb20gXCIuL3NyYy9wcm92aWRlclwiO1xuZXhwb3J0ICogYXMgcnVudGltZSBmcm9tIFwiLi9zcmMvcnVudGltZVwiO1xuZXhwb3J0ICogYXMgc2NoZW1hIGZyb20gXCIuL3NyYy9zY2hlbWFcIjtcbmV4cG9ydCAqIGFzIHNjcmFwZSBmcm9tIFwiLi9zcmMvc2NyYXBlXCI7XG5leHBvcnQgKiBhcyBzZWFyY2ggZnJvbSBcIi4vc3JjL3NlYXJjaFwiO1xuZXhwb3J0ICogYXMgdG9vbCBmcm9tIFwiLi9zcmMvdG9vbFwiO1xuZXhwb3J0IHsgbm9ybWFsaXplVXJsUGF0dGVybiwgcGFyc2VBbGxvd2VkVXJscywgdG9VcmxBbGxvd0xpc3QsIGFzc2VydFVybEFsbG93ZWQgfSBmcm9tIFwiLi9zcmMvYWxsb3dsaXN0XCI7XG5leHBvcnQgdHlwZSB7IFVybEFsbG93TGlzdCB9IGZyb20gXCIuL3NyYy9hbGxvd2xpc3RcIjtcbmV4cG9ydCB7IE1PREVMX0VOViwgU0VSVklOR19FTkRQT0lOVF9FTlYsIERFRkFVTFRfTU9ERUxfRkFMTEJBQ0tTLCBERUZBVUxUX01BWF9DSVRBVElPTlMsIERFRkFVTFRfRkVUQ0hfTUFYX0xFTkdUSCwgREVGQVVMVF9USU1FT1VUX01TLCBXRUJfU0VBUkNIX0NPTkZJR19TQ0hFTUEsIHRvQXBwcm92YWxQb2xpY3ksIHJlc29sdmVXZWJTZWFyY2hDb25maWcsIGFwcHJvdmFsTWF0Y2hlcyB9IGZyb20gXCIuL3NyYy9jb25maWdcIjtcbmV4cG9ydCB0eXBlIHsgQXBwcm92YWxHYXRlLCBBcHByb3ZhbFBvbGljeSwgVXJsUG9saWN5TW9kZSwgTW9kZWxTb3VyY2UsIFdlYlNlYXJjaFBsdWdpbkNvbmZpZywgUmVzb2x2ZWRXZWJTZWFyY2hDb25maWcgfSBmcm9tIFwiLi9zcmMvY29uZmlnXCI7XG5leHBvcnQgeyBTRUFSQ0hfQ0FDSEVfVFRMX1NFQ09ORFMsIEZFVENIX0NBQ0hFX1RUTF9TRUNPTkRTLCBTRVJWSU5HX1JFVFJZX0FUVEVNUFRTLCBXRUJfUkVUUllfQVRURU1QVFMsIHdlYlNlYXJjaEV4ZWN1dGVEZWZhdWx0cywgd2ViRmV0Y2hFeGVjdXRlRGVmYXVsdHMsIHNjcmFwZVNlYXJjaEV4ZWN1dGVEZWZhdWx0cywgdG9DYWxsU2V0dGluZ3MgfSBmcm9tIFwiLi9zcmMvZGVmYXVsdHNcIjtcbmV4cG9ydCB0eXBlIHsgV2ViU2VhcmNoRXhlY3V0ZUNvbmZpZywgV2ViU2VhcmNoRXhlY3V0aW9uU2V0dGluZ3MgfSBmcm9tIFwiLi9zcmMvZGVmYXVsdHNcIjtcbmV4cG9ydCB7IHJ1bldlYkZldGNoIH0gZnJvbSBcIi4vc3JjL2ZldGNoXCI7XG5leHBvcnQgeyBkZWNvZGVIdG1sRW50aXRpZXMsIGh0bWxGcmFnbWVudFRvVGV4dCwgaHRtbFRvVGV4dCB9IGZyb20gXCIuL3NyYy9odG1sLXRleHRcIjtcbmV4cG9ydCB7IFdlYlNlYXJjaFBsdWdpbiwgd2ViU2VhcmNoIH0gZnJvbSBcIi4vc3JjL3BsdWdpblwiO1xuZXhwb3J0IHsgV0VCX1NFQVJDSF9QUk9WSURFUlMsIGRldGVjdFdlYlNlYXJjaFByb3ZpZGVyLCBzdXBwb3J0c1dlYlNlYXJjaCwgd2ViU2VhcmNoVG9vbFNwZWMgfSBmcm9tIFwiLi9zcmMvcHJvdmlkZXJcIjtcbmV4cG9ydCB0eXBlIHsgV2ViU2VhcmNoUHJvdmlkZXIsIFdlYlNlYXJjaFByb3ZpZGVyU3BlYyB9IGZyb20gXCIuL3NyYy9wcm92aWRlclwiO1xuZXhwb3J0IHsgZ2V0V2ViU2VhcmNoUnVudGltZSwgc2V0V2ViU2VhcmNoRXhlY3V0b3IsIHJlc2V0V2ViU2VhcmNoUnVudGltZSwgZXhlY3V0ZVJlYWQgfSBmcm9tIFwiLi9zcmMvcnVudGltZVwiO1xuZXhwb3J0IHR5cGUgeyBXZWJTZWFyY2hFeGVjdXRvciwgV2ViU2VhcmNoUnVudGltZSB9IGZyb20gXCIuL3NyYy9ydW50aW1lXCI7XG5leHBvcnQgeyBXRUJfU0VBUkNIX1RPT0xfREVTQ1JJUFRJT04sIFdFQl9GRVRDSF9UT09MX0RFU0NSSVBUSU9OLCB3ZWJTZWFyY2hSZXF1ZXN0U2NoZW1hLCB3ZWJTZWFyY2hDaXRhdGlvblNjaGVtYSwgd2ViU2VhcmNoUmVzdWx0U2NoZW1hLCB3ZWJGZXRjaFJlcXVlc3RTY2hlbWEsIHdlYkZldGNoUmVzdWx0U2NoZW1hIH0gZnJvbSBcIi4vc3JjL3NjaGVtYVwiO1xuZXhwb3J0IHR5cGUgeyBXZWJTZWFyY2hSZXF1ZXN0LCBXZWJTZWFyY2hDaXRhdGlvbiwgV2ViU2VhcmNoUmVzdWx0LCBXZWJGZXRjaFJlcXVlc3QsIFdlYkZldGNoUmVzdWx0IH0gZnJvbSBcIi4vc3JjL3NjaGVtYVwiO1xuZXhwb3J0IHsgcnVuU2NyYXBlU2VhcmNoIH0gZnJvbSBcIi4vc3JjL3NjcmFwZVwiO1xuZXhwb3J0IHsgcmVzb2x2ZVdlYlNlYXJjaENvbnRleHQsIHJ1bldlYlNlYXJjaCB9IGZyb20gXCIuL3NyYy9zZWFyY2hcIjtcbmV4cG9ydCB0eXBlIHsgV2ViU2VhcmNoQ29udGV4dCB9IGZyb20gXCIuL3NyYy9zZWFyY2hcIjtcbmV4cG9ydCB7IHdlYlNlYXJjaFRvb2wsIHdlYkZldGNoVG9vbCB9IGZyb20gXCIuL3NyYy90b29sXCI7XG5leHBvcnQgdHlwZSB7IFdlYlNlYXJjaFRvb2xPcHRpb25zIH0gZnJvbSBcIi4vc3JjL3Rvb2xcIjtcbiJdfQ==
@@ -0,0 +1,66 @@
1
+ /**
2
+ * URL allow-list policy for the web-search add-on.
3
+ *
4
+ * A configured allow-list restricts which URLs the tools will surface or
5
+ * fetch. Each entry is a glob compiled by `@dbx-tools/path`'s
6
+ * {@link match.toPathMatcher} (the same `Minimatch`-backed matcher the
7
+ * package's file scanning uses). Because that matcher treats `/` as a
8
+ * path-segment boundary, entries are matched against the right slice of the
9
+ * URL rather than the raw `href`:
10
+ *
11
+ * - A **host** entry (no `/` after the optional scheme, e.g. `databricks.com`
12
+ * or `*.databricks.com`) is tested against the URL's `hostname`. A bare
13
+ * host with no wildcard also matches its subdomains, so `databricks.com`
14
+ * permits `docs.databricks.com` - the intuitive reading of a domain
15
+ * allow-list.
16
+ * - A **path** entry (contains a `/`, e.g. `docs.example.com/api/**`) is
17
+ * tested against `host + pathname` (scheme and query stripped), so path
18
+ * globs work without fighting the `https://` prefix.
19
+ *
20
+ * Enforcement is asymmetric by design (see the module's two consumers):
21
+ * `web_search` results are SILENTLY filtered to the permitted set, while an
22
+ * explicit `web_fetch` of a disallowed URL is refused with an error - the
23
+ * search never leaks a URL the caller then can't fetch, but a direct fetch
24
+ * of a blocked URL is a visible, correctable mistake rather than a silent
25
+ * empty.
26
+ *
27
+ * An empty / absent allow-list permits everything.
28
+ *
29
+ * @module
30
+ */
31
+ /** A compiled URL allow-list. Build one with {@link toUrlAllowList}. */
32
+ export interface UrlAllowList {
33
+ /** The normalized entries backing this list (for diagnostics). */
34
+ readonly patterns: readonly string[];
35
+ /** Whether the list actually restricts anything (`false` == permit all). */
36
+ readonly restricted: boolean;
37
+ /** Whether `url` is permitted. An unrestricted list permits everything. */
38
+ allows(url: string): boolean;
39
+ }
40
+ /**
41
+ * Normalize one raw allow-list entry: trim it and drop any scheme. The
42
+ * scheme is irrelevant to matching (we compare against hostname / host+path),
43
+ * so `https://docs.example.com/x` and `docs.example.com/x` are equivalent.
44
+ */
45
+ export declare function normalizeUrlPattern(pattern: string): string;
46
+ /**
47
+ * Parse a raw allow-list from config (`string[]`) or an env var (a CSV /
48
+ * whitespace-separated string) into a normalized, de-duplicated entry list.
49
+ * Entries are trimmed and scheme-stripped; empties are dropped. Mirrors the
50
+ * email add-on's `parseAllowedSenders` so the two policies read their config
51
+ * identically.
52
+ */
53
+ export declare function parseAllowedUrls(raw: string | string[] | undefined): string[];
54
+ /**
55
+ * Compile a normalized entry list into a {@link UrlAllowList}. An empty list
56
+ * yields a permit-all allow-list whose {@link UrlAllowList.allows} always
57
+ * returns `true`. A URL that can't be parsed is never permitted by a
58
+ * restricted list.
59
+ */
60
+ export declare function toUrlAllowList(patterns: readonly string[]): UrlAllowList;
61
+ /**
62
+ * Throw when `url` is not permitted by the allow-list. No-op when the
63
+ * allow-list is unrestricted. The single enforcement point for the
64
+ * `web_fetch` path.
65
+ */
66
+ export declare function assertUrlAllowed(url: string, allow: UrlAllowList): void;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * URL allow-list policy for the web-search add-on.
3
+ *
4
+ * A configured allow-list restricts which URLs the tools will surface or
5
+ * fetch. Each entry is a glob compiled by `@dbx-tools/path`'s
6
+ * {@link match.toPathMatcher} (the same `Minimatch`-backed matcher the
7
+ * package's file scanning uses). Because that matcher treats `/` as a
8
+ * path-segment boundary, entries are matched against the right slice of the
9
+ * URL rather than the raw `href`:
10
+ *
11
+ * - A **host** entry (no `/` after the optional scheme, e.g. `databricks.com`
12
+ * or `*.databricks.com`) is tested against the URL's `hostname`. A bare
13
+ * host with no wildcard also matches its subdomains, so `databricks.com`
14
+ * permits `docs.databricks.com` - the intuitive reading of a domain
15
+ * allow-list.
16
+ * - A **path** entry (contains a `/`, e.g. `docs.example.com/api/**`) is
17
+ * tested against `host + pathname` (scheme and query stripped), so path
18
+ * globs work without fighting the `https://` prefix.
19
+ *
20
+ * Enforcement is asymmetric by design (see the module's two consumers):
21
+ * `web_search` results are SILENTLY filtered to the permitted set, while an
22
+ * explicit `web_fetch` of a disallowed URL is refused with an error - the
23
+ * search never leaks a URL the caller then can't fetch, but a direct fetch
24
+ * of a blocked URL is a visible, correctable mistake rather than a silent
25
+ * empty.
26
+ *
27
+ * An empty / absent allow-list permits everything.
28
+ *
29
+ * @module
30
+ */
31
+ import { ValidationError } from "@databricks/appkit";
32
+ import { match } from "@dbx-tools/path";
33
+ import { string } from "@dbx-tools/shared-core";
34
+ /** Strip a leading `scheme://` (or bare `scheme:`) from a pattern. */
35
+ function stripScheme(pattern) {
36
+ return pattern.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "").replace(/^[a-z][a-z0-9+.-]*:/i, "");
37
+ }
38
+ /**
39
+ * Normalize one raw allow-list entry: trim it and drop any scheme. The
40
+ * scheme is irrelevant to matching (we compare against hostname / host+path),
41
+ * so `https://docs.example.com/x` and `docs.example.com/x` are equivalent.
42
+ */
43
+ export function normalizeUrlPattern(pattern) {
44
+ return stripScheme(pattern.trim());
45
+ }
46
+ /**
47
+ * Parse a raw allow-list from config (`string[]`) or an env var (a CSV /
48
+ * whitespace-separated string) into a normalized, de-duplicated entry list.
49
+ * Entries are trimmed and scheme-stripped; empties are dropped. Mirrors the
50
+ * email add-on's `parseAllowedSenders` so the two policies read their config
51
+ * identically.
52
+ */
53
+ export function parseAllowedUrls(raw) {
54
+ return string.parseList(raw, normalizeUrlPattern);
55
+ }
56
+ /** Compile a single normalized entry into a {@link CompiledPattern}. */
57
+ function compilePattern(pattern) {
58
+ if (pattern.includes("/")) {
59
+ // A path entry matches against `host + pathname` (no scheme, no query).
60
+ return { target: "hostPath", matcher: match.toPathMatcher(pattern) };
61
+ }
62
+ // A host entry matches the hostname. A bare host (no wildcard) also
63
+ // permits its subdomains via an added `*.<host>` alternative.
64
+ const globs = pattern.includes("*") ? [pattern] : [pattern, `*.${pattern}`];
65
+ return { target: "hostname", matcher: match.toPathMatcher(...globs) };
66
+ }
67
+ /** The URL slices a compiled pattern is tested against. */
68
+ function urlTargets(url) {
69
+ try {
70
+ const u = new URL(url);
71
+ return {
72
+ hostname: u.hostname,
73
+ // Drop a trailing slash so `example.com/api` matches `example.com/api/`.
74
+ hostPath: `${u.hostname}${u.pathname}`.replace(/\/$/, ""),
75
+ };
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ /**
82
+ * Compile a normalized entry list into a {@link UrlAllowList}. An empty list
83
+ * yields a permit-all allow-list whose {@link UrlAllowList.allows} always
84
+ * returns `true`. A URL that can't be parsed is never permitted by a
85
+ * restricted list.
86
+ */
87
+ export function toUrlAllowList(patterns) {
88
+ if (patterns.length === 0) {
89
+ return { patterns: [], restricted: false, allows: () => true };
90
+ }
91
+ const compiled = patterns.map(compilePattern);
92
+ return {
93
+ patterns,
94
+ restricted: true,
95
+ allows: (url) => {
96
+ const targets = urlTargets(url);
97
+ if (!targets)
98
+ return false;
99
+ return compiled.some((c) => c.matcher(targets[c.target]));
100
+ },
101
+ };
102
+ }
103
+ /**
104
+ * Throw when `url` is not permitted by the allow-list. No-op when the
105
+ * allow-list is unrestricted. The single enforcement point for the
106
+ * `web_fetch` path.
107
+ */
108
+ export function assertUrlAllowed(url, allow) {
109
+ if (!allow.allows(url)) {
110
+ throw ValidationError.invalidValue("url", url, `a URL permitted by the configured allow-list (${allow.patterns.join(", ")})`);
111
+ }
112
+ }
113
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxsb3dsaXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FsbG93bGlzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Qkc7QUFFSCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLEtBQUssRUFBb0IsTUFBTSxpQkFBaUIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFZaEQsc0VBQXNFO0FBQ3RFLFNBQVMsV0FBVyxDQUFDLE9BQWU7SUFDbEMsT0FBTyxPQUFPLENBQUMsT0FBTyxDQUFDLDBCQUEwQixFQUFFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxzQkFBc0IsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUM3RixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxPQUFlO0lBQ2pELE9BQU8sV0FBVyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0FBQ3JDLENBQUM7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsZ0JBQWdCLENBQUMsR0FBa0M7SUFDakUsT0FBTyxNQUFNLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0FBQ3BELENBQUM7QUFRRCx3RUFBd0U7QUFDeEUsU0FBUyxjQUFjLENBQUMsT0FBZTtJQUNyQyxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUMxQix3RUFBd0U7UUFDeEUsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztJQUN2RSxDQUFDO0lBQ0Qsb0VBQW9FO0lBQ3BFLDhEQUE4RDtJQUM5RCxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxLQUFLLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDNUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxhQUFhLENBQUMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDO0FBQ3hFLENBQUM7QUFFRCwyREFBMkQ7QUFDM0QsU0FBUyxVQUFVLENBQUMsR0FBVztJQUM3QixJQUFJLENBQUM7UUFDSCxNQUFNLENBQUMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QixPQUFPO1lBQ0wsUUFBUSxFQUFFLENBQUMsQ0FBQyxRQUFRO1lBQ3BCLHlFQUF5RTtZQUN6RSxRQUFRLEVBQUUsR0FBRyxDQUFDLENBQUMsUUFBUSxHQUFHLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQztTQUMxRCxDQUFDO0lBQ0osQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLFFBQTJCO0lBQ3hELElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUMxQixPQUFPLEVBQUUsUUFBUSxFQUFFLEVBQUUsRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUNqRSxDQUFDO0lBQ0QsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsQ0FBQztJQUM5QyxPQUFPO1FBQ0wsUUFBUTtRQUNSLFVBQVUsRUFBRSxJQUFJO1FBQ2hCLE1BQU0sRUFBRSxDQUFDLEdBQVcsRUFBRSxFQUFFO1lBQ3RCLE1BQU0sT0FBTyxHQUFHLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNoQyxJQUFJLENBQUMsT0FBTztnQkFBRSxPQUFPLEtBQUssQ0FBQztZQUMzQixPQUFPLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDNUQsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxHQUFXLEVBQUUsS0FBbUI7SUFDL0QsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN2QixNQUFNLGVBQWUsQ0FBQyxZQUFZLENBQ2hDLEtBQUssRUFDTCxHQUFHLEVBQ0gsaURBQWlELEtBQUssQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQzlFLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVVJMIGFsbG93LWxpc3QgcG9saWN5IGZvciB0aGUgd2ViLXNlYXJjaCBhZGQtb24uXG4gKlxuICogQSBjb25maWd1cmVkIGFsbG93LWxpc3QgcmVzdHJpY3RzIHdoaWNoIFVSTHMgdGhlIHRvb2xzIHdpbGwgc3VyZmFjZSBvclxuICogZmV0Y2guIEVhY2ggZW50cnkgaXMgYSBnbG9iIGNvbXBpbGVkIGJ5IGBAZGJ4LXRvb2xzL3BhdGhgJ3NcbiAqIHtAbGluayBtYXRjaC50b1BhdGhNYXRjaGVyfSAodGhlIHNhbWUgYE1pbmltYXRjaGAtYmFja2VkIG1hdGNoZXIgdGhlXG4gKiBwYWNrYWdlJ3MgZmlsZSBzY2FubmluZyB1c2VzKS4gQmVjYXVzZSB0aGF0IG1hdGNoZXIgdHJlYXRzIGAvYCBhcyBhXG4gKiBwYXRoLXNlZ21lbnQgYm91bmRhcnksIGVudHJpZXMgYXJlIG1hdGNoZWQgYWdhaW5zdCB0aGUgcmlnaHQgc2xpY2Ugb2YgdGhlXG4gKiBVUkwgcmF0aGVyIHRoYW4gdGhlIHJhdyBgaHJlZmA6XG4gKlxuICogLSBBICoqaG9zdCoqIGVudHJ5IChubyBgL2AgYWZ0ZXIgdGhlIG9wdGlvbmFsIHNjaGVtZSwgZS5nLiBgZGF0YWJyaWNrcy5jb21gXG4gKiAgIG9yIGAqLmRhdGFicmlja3MuY29tYCkgaXMgdGVzdGVkIGFnYWluc3QgdGhlIFVSTCdzIGBob3N0bmFtZWAuIEEgYmFyZVxuICogICBob3N0IHdpdGggbm8gd2lsZGNhcmQgYWxzbyBtYXRjaGVzIGl0cyBzdWJkb21haW5zLCBzbyBgZGF0YWJyaWNrcy5jb21gXG4gKiAgIHBlcm1pdHMgYGRvY3MuZGF0YWJyaWNrcy5jb21gIC0gdGhlIGludHVpdGl2ZSByZWFkaW5nIG9mIGEgZG9tYWluXG4gKiAgIGFsbG93LWxpc3QuXG4gKiAtIEEgKipwYXRoKiogZW50cnkgKGNvbnRhaW5zIGEgYC9gLCBlLmcuIGBkb2NzLmV4YW1wbGUuY29tL2FwaS8qKmApIGlzXG4gKiAgIHRlc3RlZCBhZ2FpbnN0IGBob3N0ICsgcGF0aG5hbWVgIChzY2hlbWUgYW5kIHF1ZXJ5IHN0cmlwcGVkKSwgc28gcGF0aFxuICogICBnbG9icyB3b3JrIHdpdGhvdXQgZmlnaHRpbmcgdGhlIGBodHRwczovL2AgcHJlZml4LlxuICpcbiAqIEVuZm9yY2VtZW50IGlzIGFzeW1tZXRyaWMgYnkgZGVzaWduIChzZWUgdGhlIG1vZHVsZSdzIHR3byBjb25zdW1lcnMpOlxuICogYHdlYl9zZWFyY2hgIHJlc3VsdHMgYXJlIFNJTEVOVExZIGZpbHRlcmVkIHRvIHRoZSBwZXJtaXR0ZWQgc2V0LCB3aGlsZSBhblxuICogZXhwbGljaXQgYHdlYl9mZXRjaGAgb2YgYSBkaXNhbGxvd2VkIFVSTCBpcyByZWZ1c2VkIHdpdGggYW4gZXJyb3IgLSB0aGVcbiAqIHNlYXJjaCBuZXZlciBsZWFrcyBhIFVSTCB0aGUgY2FsbGVyIHRoZW4gY2FuJ3QgZmV0Y2gsIGJ1dCBhIGRpcmVjdCBmZXRjaFxuICogb2YgYSBibG9ja2VkIFVSTCBpcyBhIHZpc2libGUsIGNvcnJlY3RhYmxlIG1pc3Rha2UgcmF0aGVyIHRoYW4gYSBzaWxlbnRcbiAqIGVtcHR5LlxuICpcbiAqIEFuIGVtcHR5IC8gYWJzZW50IGFsbG93LWxpc3QgcGVybWl0cyBldmVyeXRoaW5nLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBWYWxpZGF0aW9uRXJyb3IgfSBmcm9tIFwiQGRhdGFicmlja3MvYXBwa2l0XCI7XG5pbXBvcnQgeyBtYXRjaCwgdHlwZSBQYXRoTWF0Y2hlciB9IGZyb20gXCJAZGJ4LXRvb2xzL3BhdGhcIjtcbmltcG9ydCB7IHN0cmluZyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5cbi8qKiBBIGNvbXBpbGVkIFVSTCBhbGxvdy1saXN0LiBCdWlsZCBvbmUgd2l0aCB7QGxpbmsgdG9VcmxBbGxvd0xpc3R9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBVcmxBbGxvd0xpc3Qge1xuICAvKiogVGhlIG5vcm1hbGl6ZWQgZW50cmllcyBiYWNraW5nIHRoaXMgbGlzdCAoZm9yIGRpYWdub3N0aWNzKS4gKi9cbiAgcmVhZG9ubHkgcGF0dGVybnM6IHJlYWRvbmx5IHN0cmluZ1tdO1xuICAvKiogV2hldGhlciB0aGUgbGlzdCBhY3R1YWxseSByZXN0cmljdHMgYW55dGhpbmcgKGBmYWxzZWAgPT0gcGVybWl0IGFsbCkuICovXG4gIHJlYWRvbmx5IHJlc3RyaWN0ZWQ6IGJvb2xlYW47XG4gIC8qKiBXaGV0aGVyIGB1cmxgIGlzIHBlcm1pdHRlZC4gQW4gdW5yZXN0cmljdGVkIGxpc3QgcGVybWl0cyBldmVyeXRoaW5nLiAqL1xuICBhbGxvd3ModXJsOiBzdHJpbmcpOiBib29sZWFuO1xufVxuXG4vKiogU3RyaXAgYSBsZWFkaW5nIGBzY2hlbWU6Ly9gIChvciBiYXJlIGBzY2hlbWU6YCkgZnJvbSBhIHBhdHRlcm4uICovXG5mdW5jdGlvbiBzdHJpcFNjaGVtZShwYXR0ZXJuOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gcGF0dGVybi5yZXBsYWNlKC9eW2Etel1bYS16MC05Ky4tXSo6XFwvXFwvL2ksIFwiXCIpLnJlcGxhY2UoL15bYS16XVthLXowLTkrLi1dKjovaSwgXCJcIik7XG59XG5cbi8qKlxuICogTm9ybWFsaXplIG9uZSByYXcgYWxsb3ctbGlzdCBlbnRyeTogdHJpbSBpdCBhbmQgZHJvcCBhbnkgc2NoZW1lLiBUaGVcbiAqIHNjaGVtZSBpcyBpcnJlbGV2YW50IHRvIG1hdGNoaW5nICh3ZSBjb21wYXJlIGFnYWluc3QgaG9zdG5hbWUgLyBob3N0K3BhdGgpLFxuICogc28gYGh0dHBzOi8vZG9jcy5leGFtcGxlLmNvbS94YCBhbmQgYGRvY3MuZXhhbXBsZS5jb20veGAgYXJlIGVxdWl2YWxlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVVcmxQYXR0ZXJuKHBhdHRlcm46IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBzdHJpcFNjaGVtZShwYXR0ZXJuLnRyaW0oKSk7XG59XG5cbi8qKlxuICogUGFyc2UgYSByYXcgYWxsb3ctbGlzdCBmcm9tIGNvbmZpZyAoYHN0cmluZ1tdYCkgb3IgYW4gZW52IHZhciAoYSBDU1YgL1xuICogd2hpdGVzcGFjZS1zZXBhcmF0ZWQgc3RyaW5nKSBpbnRvIGEgbm9ybWFsaXplZCwgZGUtZHVwbGljYXRlZCBlbnRyeSBsaXN0LlxuICogRW50cmllcyBhcmUgdHJpbW1lZCBhbmQgc2NoZW1lLXN0cmlwcGVkOyBlbXB0aWVzIGFyZSBkcm9wcGVkLiBNaXJyb3JzIHRoZVxuICogZW1haWwgYWRkLW9uJ3MgYHBhcnNlQWxsb3dlZFNlbmRlcnNgIHNvIHRoZSB0d28gcG9saWNpZXMgcmVhZCB0aGVpciBjb25maWdcbiAqIGlkZW50aWNhbGx5LlxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VBbGxvd2VkVXJscyhyYXc6IHN0cmluZyB8IHN0cmluZ1tdIHwgdW5kZWZpbmVkKTogc3RyaW5nW10ge1xuICByZXR1cm4gc3RyaW5nLnBhcnNlTGlzdChyYXcsIG5vcm1hbGl6ZVVybFBhdHRlcm4pO1xufVxuXG4vKiogT25lIGNvbXBpbGVkIGVudHJ5OiB3aGljaCBVUkwgc2xpY2UgaXQgdGVzdHMsIGFuZCB0aGUgbWF0Y2hlciBmb3IgaXQuICovXG5pbnRlcmZhY2UgQ29tcGlsZWRQYXR0ZXJuIHtcbiAgdGFyZ2V0OiBcImhvc3RuYW1lXCIgfCBcImhvc3RQYXRoXCI7XG4gIG1hdGNoZXI6IFBhdGhNYXRjaGVyO1xufVxuXG4vKiogQ29tcGlsZSBhIHNpbmdsZSBub3JtYWxpemVkIGVudHJ5IGludG8gYSB7QGxpbmsgQ29tcGlsZWRQYXR0ZXJufS4gKi9cbmZ1bmN0aW9uIGNvbXBpbGVQYXR0ZXJuKHBhdHRlcm46IHN0cmluZyk6IENvbXBpbGVkUGF0dGVybiB7XG4gIGlmIChwYXR0ZXJuLmluY2x1ZGVzKFwiL1wiKSkge1xuICAgIC8vIEEgcGF0aCBlbnRyeSBtYXRjaGVzIGFnYWluc3QgYGhvc3QgKyBwYXRobmFtZWAgKG5vIHNjaGVtZSwgbm8gcXVlcnkpLlxuICAgIHJldHVybiB7IHRhcmdldDogXCJob3N0UGF0aFwiLCBtYXRjaGVyOiBtYXRjaC50b1BhdGhNYXRjaGVyKHBhdHRlcm4pIH07XG4gIH1cbiAgLy8gQSBob3N0IGVudHJ5IG1hdGNoZXMgdGhlIGhvc3RuYW1lLiBBIGJhcmUgaG9zdCAobm8gd2lsZGNhcmQpIGFsc29cbiAgLy8gcGVybWl0cyBpdHMgc3ViZG9tYWlucyB2aWEgYW4gYWRkZWQgYCouPGhvc3Q+YCBhbHRlcm5hdGl2ZS5cbiAgY29uc3QgZ2xvYnMgPSBwYXR0ZXJuLmluY2x1ZGVzKFwiKlwiKSA/IFtwYXR0ZXJuXSA6IFtwYXR0ZXJuLCBgKi4ke3BhdHRlcm59YF07XG4gIHJldHVybiB7IHRhcmdldDogXCJob3N0bmFtZVwiLCBtYXRjaGVyOiBtYXRjaC50b1BhdGhNYXRjaGVyKC4uLmdsb2JzKSB9O1xufVxuXG4vKiogVGhlIFVSTCBzbGljZXMgYSBjb21waWxlZCBwYXR0ZXJuIGlzIHRlc3RlZCBhZ2FpbnN0LiAqL1xuZnVuY3Rpb24gdXJsVGFyZ2V0cyh1cmw6IHN0cmluZyk6IHsgaG9zdG5hbWU6IHN0cmluZzsgaG9zdFBhdGg6IHN0cmluZyB9IHwgdW5kZWZpbmVkIHtcbiAgdHJ5IHtcbiAgICBjb25zdCB1ID0gbmV3IFVSTCh1cmwpO1xuICAgIHJldHVybiB7XG4gICAgICBob3N0bmFtZTogdS5ob3N0bmFtZSxcbiAgICAgIC8vIERyb3AgYSB0cmFpbGluZyBzbGFzaCBzbyBgZXhhbXBsZS5jb20vYXBpYCBtYXRjaGVzIGBleGFtcGxlLmNvbS9hcGkvYC5cbiAgICAgIGhvc3RQYXRoOiBgJHt1Lmhvc3RuYW1lfSR7dS5wYXRobmFtZX1gLnJlcGxhY2UoL1xcLyQvLCBcIlwiKSxcbiAgICB9O1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG5cbi8qKlxuICogQ29tcGlsZSBhIG5vcm1hbGl6ZWQgZW50cnkgbGlzdCBpbnRvIGEge0BsaW5rIFVybEFsbG93TGlzdH0uIEFuIGVtcHR5IGxpc3RcbiAqIHlpZWxkcyBhIHBlcm1pdC1hbGwgYWxsb3ctbGlzdCB3aG9zZSB7QGxpbmsgVXJsQWxsb3dMaXN0LmFsbG93c30gYWx3YXlzXG4gKiByZXR1cm5zIGB0cnVlYC4gQSBVUkwgdGhhdCBjYW4ndCBiZSBwYXJzZWQgaXMgbmV2ZXIgcGVybWl0dGVkIGJ5IGFcbiAqIHJlc3RyaWN0ZWQgbGlzdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvVXJsQWxsb3dMaXN0KHBhdHRlcm5zOiByZWFkb25seSBzdHJpbmdbXSk6IFVybEFsbG93TGlzdCB7XG4gIGlmIChwYXR0ZXJucy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4geyBwYXR0ZXJuczogW10sIHJlc3RyaWN0ZWQ6IGZhbHNlLCBhbGxvd3M6ICgpID0+IHRydWUgfTtcbiAgfVxuICBjb25zdCBjb21waWxlZCA9IHBhdHRlcm5zLm1hcChjb21waWxlUGF0dGVybik7XG4gIHJldHVybiB7XG4gICAgcGF0dGVybnMsXG4gICAgcmVzdHJpY3RlZDogdHJ1ZSxcbiAgICBhbGxvd3M6ICh1cmw6IHN0cmluZykgPT4ge1xuICAgICAgY29uc3QgdGFyZ2V0cyA9IHVybFRhcmdldHModXJsKTtcbiAgICAgIGlmICghdGFyZ2V0cykgcmV0dXJuIGZhbHNlO1xuICAgICAgcmV0dXJuIGNvbXBpbGVkLnNvbWUoKGMpID0+IGMubWF0Y2hlcih0YXJnZXRzW2MudGFyZ2V0XSkpO1xuICAgIH0sXG4gIH07XG59XG5cbi8qKlxuICogVGhyb3cgd2hlbiBgdXJsYCBpcyBub3QgcGVybWl0dGVkIGJ5IHRoZSBhbGxvdy1saXN0LiBOby1vcCB3aGVuIHRoZVxuICogYWxsb3ctbGlzdCBpcyB1bnJlc3RyaWN0ZWQuIFRoZSBzaW5nbGUgZW5mb3JjZW1lbnQgcG9pbnQgZm9yIHRoZVxuICogYHdlYl9mZXRjaGAgcGF0aC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2VydFVybEFsbG93ZWQodXJsOiBzdHJpbmcsIGFsbG93OiBVcmxBbGxvd0xpc3QpOiB2b2lkIHtcbiAgaWYgKCFhbGxvdy5hbGxvd3ModXJsKSkge1xuICAgIHRocm93IFZhbGlkYXRpb25FcnJvci5pbnZhbGlkVmFsdWUoXG4gICAgICBcInVybFwiLFxuICAgICAgdXJsLFxuICAgICAgYGEgVVJMIHBlcm1pdHRlZCBieSB0aGUgY29uZmlndXJlZCBhbGxvdy1saXN0ICgke2FsbG93LnBhdHRlcm5zLmpvaW4oXCIsIFwiKX0pYCxcbiAgICApO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Configuration for the web-search plugin: the typed
3
+ * {@link WebSearchPluginConfig} (the plugin's slice of AppKit config), the
4
+ * JSON Schema the manifest publishes for it, and {@link resolveWebSearchConfig}
5
+ * which layers that config over environment defaults into the concrete
6
+ * {@link ResolvedWebSearchConfig} the runtime + tools read.
7
+ *
8
+ * `web_search` runs on the Databricks Model Serving native web-search tool
9
+ * (see `provider.ts` / `search.ts`), so the key knob is which web-search-
10
+ * capable model to use. It resolves independently of the calling agent's chat
11
+ * model: `model` (a name, loose name, or capability class) is fuzzy-matched
12
+ * against the workspace catalogue, and when nothing is pinned the
13
+ * {@link WebSearchPluginConfig.modelFallbacks} order (Gemini, then GPT, then a
14
+ * repo floor) picks the first web-search-capable endpoint that exists.
15
+ *
16
+ * Which endpoint id is used follows the standard precedence - explicit plugin
17
+ * config, then environment, then a default - with two environment sources:
18
+ *
19
+ * 1. `model` in plugin config;
20
+ * 2. `WEB_SEARCH_MODEL`, the dedicated override, so a deployment can point
21
+ * web search at a different endpoint than the agent's chat model;
22
+ * 3. `DATABRICKS_SERVING_ENDPOINT_NAME`, AppKit's standard name for a
23
+ * Model Serving binding, honored so the resource declared in the manifest
24
+ * wires this plugin up like any other. Because that binding is shared
25
+ * with whatever else the app serves, it is treated as a preference: an
26
+ * endpoint that cannot run the native web-search tool is skipped in
27
+ * favor of {@link WebSearchPluginConfig.modelFallbacks} rather than
28
+ * failing the call. A pin from (1) or (2) is explicit and DOES fail;
29
+ * 4. otherwise the fallback order, resolved against the live catalogue.
30
+ *
31
+ * Which model wins is decided lazily, at call time, against the live
32
+ * catalogue. Resolution here is eager about everything else and fails loudly
33
+ * on a contradiction: an unparseable `WEB_SEARCH_TOOLS`, an unknown
34
+ * {@link UrlPolicyMode}, or a URL policy that disagrees with the allow-list it
35
+ * was given.
36
+ *
37
+ * Env fallbacks: `WEB_SEARCH_MODEL`, `DATABRICKS_SERVING_ENDPOINT_NAME`,
38
+ * `WEB_SEARCH_MODEL_FALLBACKS`, `WEB_SEARCH_TOOLS` (JSON),
39
+ * `WEB_SEARCH_URL_POLICY`, `WEB_SEARCH_ALLOWED_URLS`,
40
+ * `WEB_SEARCH_MAX_CITATIONS`, `WEB_SEARCH_FETCH_MAX_LENGTH`,
41
+ * `WEB_SEARCH_TIMEOUT_MS`, `WEB_SEARCH_SCRAPE_FALLBACK`, `WEB_SEARCH_FUZZY`,
42
+ * `WEB_SEARCH_FUZZY_THRESHOLD`.
43
+ *
44
+ * @module
45
+ */
46
+ import { type BasePluginConfig } from "@databricks/appkit";
47
+ import { type OneOrMany } from "@dbx-tools/shared-core";
48
+ import type { JSONSchema7 } from "json-schema";
49
+ import { type UrlAllowList } from "./allowlist.js";
50
+ /**
51
+ * A URL-pattern gate for per-tool approval. `true` gates every call; a
52
+ * pattern (or list of patterns, in the {@link OneOrMany} shape used across
53
+ * the repo) gates only calls whose URL matches. Patterns use the same glob
54
+ * syntax as the allow-list (see `allowlist.ts`). Omit / `false` for no
55
+ * approval. Normalized to an {@link ApprovalPolicy} before use.
56
+ */
57
+ export type ApprovalGate = boolean | OneOrMany<string> | string;
58
+ /**
59
+ * The normalized form of an {@link ApprovalGate}: which calls pause for a
60
+ * human. `"none"` runs every call straight through, `"always"` gates all of
61
+ * them, and `"urls"` gates only calls whose URL matches one of `patterns`.
62
+ */
63
+ export type ApprovalPolicy = {
64
+ readonly mode: "none";
65
+ } | {
66
+ readonly mode: "always";
67
+ } | {
68
+ readonly mode: "urls";
69
+ readonly patterns: readonly string[];
70
+ };
71
+ /**
72
+ * Which URLs the tools may reach. `"allowlist"` permits only the configured
73
+ * entries; `"unrestricted"` names the permissive mode explicitly, so an
74
+ * unrestricted deployment is a stated choice visible in the boot log rather
75
+ * than an empty list nobody noticed.
76
+ */
77
+ export type UrlPolicyMode = "unrestricted" | "allowlist";
78
+ /** Dedicated override for the web-search endpoint id. */
79
+ export declare const MODEL_ENV = "WEB_SEARCH_MODEL";
80
+ /** AppKit's standard environment name for a Model Serving endpoint binding. */
81
+ export declare const SERVING_ENDPOINT_ENV = "DATABRICKS_SERVING_ENDPOINT_NAME";
82
+ /** Where the pinned web-search endpoint id came from, or `"none"` when unpinned. */
83
+ export type ModelSource = "config" | typeof MODEL_ENV | typeof SERVING_ENDPOINT_ENV | "none";
84
+ /**
85
+ * Default web-search model preference, tried in order when no model is
86
+ * pinned. Gemini first, then GPT - both support the native web-search tool;
87
+ * a workspace typically has at least one. Each is fuzzy-matched against the
88
+ * live catalogue, so a close variant (e.g. `databricks-gemini-3-1-pro`) is
89
+ * picked when the exact id isn't present.
90
+ */
91
+ export declare const DEFAULT_MODEL_FALLBACKS: readonly string[];
92
+ /** Default cap on the number of citations returned from a single search. */
93
+ export declare const DEFAULT_MAX_CITATIONS = 10;
94
+ /** Default cap on characters returned from a single `web_fetch`. */
95
+ export declare const DEFAULT_FETCH_MAX_LENGTH = 50000;
96
+ /** Default per-request network timeout (ms) for search + fetch. */
97
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
98
+ /** AppKit config accepted by the web-search plugin. */
99
+ export interface WebSearchPluginConfig extends BasePluginConfig {
100
+ /**
101
+ * The web-search model to use by default: a Databricks serving endpoint
102
+ * name (`"databricks-gemini-3-pro"`), a loose name (`"gemini"`, `"gpt"`),
103
+ * or a capability class. Fuzzy-matched against the live catalogue. Falls
104
+ * back to `WEB_SEARCH_MODEL`, then `DATABRICKS_SERVING_ENDPOINT_NAME`, then
105
+ * the {@link modelFallbacks} order. Chosen independently of the calling
106
+ * agent's chat model.
107
+ */
108
+ model?: string;
109
+ /**
110
+ * Priority-ordered web-search model candidates tried when {@link model} is
111
+ * unset, each fuzzy-matched and checked for web-search support. Falls back
112
+ * to `WEB_SEARCH_MODEL_FALLBACKS` (comma/space-separated), then
113
+ * {@link DEFAULT_MODEL_FALLBACKS} (Gemini, then GPT).
114
+ */
115
+ modelFallbacks?: string | string[];
116
+ /**
117
+ * Provider -> tool-spec override map, merged over the built-in
118
+ * {@link WEB_SEARCH_PROVIDERS} defaults. Keyed by provider family
119
+ * (`"openai"`, `"gemini"`); each value may override the `tool` entry
120
+ * and/or the `api` surface. Use to change the tool shape as the platform
121
+ * evolves without a code change. Falls back to `WEB_SEARCH_TOOLS` parsed as
122
+ * JSON. This is the `WEB_SEARCH_TOOLS` setting.
123
+ */
124
+ webSearchTools?: Record<string, unknown>;
125
+ /**
126
+ * Enable fuzzy matching of loose model names against the catalogue.
127
+ * Defaults to `true`; falls back to `WEB_SEARCH_FUZZY`.
128
+ */
129
+ modelFuzzyMatch?: boolean;
130
+ /** Fuse.js fuzzy threshold. Falls back to `WEB_SEARCH_FUZZY_THRESHOLD`, then the shared default. */
131
+ modelFuzzyThreshold?: number;
132
+ /**
133
+ * Hard cap on the number of citations a single search returns. Falls back
134
+ * to `WEB_SEARCH_MAX_CITATIONS`, then {@link DEFAULT_MAX_CITATIONS}.
135
+ */
136
+ maxCitations?: number;
137
+ /**
138
+ * Hard cap on the character length of a single `web_fetch` result. Falls
139
+ * back to `WEB_SEARCH_FETCH_MAX_LENGTH`, then {@link DEFAULT_FETCH_MAX_LENGTH}.
140
+ */
141
+ fetchMaxLength?: number;
142
+ /**
143
+ * Per-request network timeout in ms for search + fetch. Falls back to
144
+ * `WEB_SEARCH_TIMEOUT_MS`, then {@link DEFAULT_TIMEOUT_MS}.
145
+ */
146
+ timeoutMs?: number;
147
+ /**
148
+ * Fall back to a DuckDuckGo scrape when the workspace has NO deployed
149
+ * web-search-capable model (no GPT / Gemini serving endpoint). The native
150
+ * Databricks tool is always preferred; this only kicks in when there is no
151
+ * native option, so the tool still returns results instead of erroring.
152
+ * Defaults to `true`; set `false` (or `WEB_SEARCH_SCRAPE_FALLBACK=0`) to
153
+ * require a native model and error otherwise.
154
+ */
155
+ scrapeFallback?: boolean;
156
+ /**
157
+ * Which URLs the tools may reach ({@link UrlPolicyMode}). Falls back to
158
+ * `WEB_SEARCH_URL_POLICY`, then to `"allowlist"` when {@link allowedUrls}
159
+ * has entries and `"unrestricted"` when it does not. Naming the mode
160
+ * explicitly is the way to state that an open deployment is intended;
161
+ * `"allowlist"` with no entries, or `"unrestricted"` alongside entries, is
162
+ * a contradiction and fails at resolution.
163
+ */
164
+ urlPolicy?: UrlPolicyMode;
165
+ /**
166
+ * Optional URL allow-list. Each entry is a glob (or bare host) tested
167
+ * against a URL's full `href`. When set, `web_search` silently filters
168
+ * citations to the permitted set and `web_fetch` refuses a disallowed URL.
169
+ * Accepts a `string[]` or a comma-/whitespace-separated string; falls back
170
+ * to `WEB_SEARCH_ALLOWED_URLS`. Omit (or leave empty) for no restriction.
171
+ * See `allowlist.ts`.
172
+ */
173
+ allowedUrls?: string | string[];
174
+ /**
175
+ * Approval gate applied to BOTH tools (per-tool overrides via
176
+ * {@link WebSearchToolOptions.approval} win). `true` gates every call;
177
+ * a URL-pattern (or list) gates only matching calls; an
178
+ * {@link ApprovalPolicy} states the mode directly. Omit for no approval.
179
+ */
180
+ approval?: ApprovalGate | ApprovalPolicy;
181
+ }
182
+ /** Concrete, validated config the runtime + tools read. */
183
+ export interface ResolvedWebSearchConfig {
184
+ /** Pinned web-search model, when configured (else undefined - use fallbacks). */
185
+ model?: string;
186
+ /** Where {@link model} came from, which decides whether an unsupported pin is fatal. */
187
+ modelSource: ModelSource;
188
+ /** Ordered fallback model candidates (Gemini, then GPT, then a floor). */
189
+ modelFallbacks: readonly string[];
190
+ /** Provider -> tool-spec override map, merged over the built-in defaults. */
191
+ webSearchTools: Record<string, unknown>;
192
+ /** Whether to fuzzy-match loose model names. */
193
+ fuzzy: boolean;
194
+ /** Fuse.js fuzzy threshold. */
195
+ fuzzyThreshold: number;
196
+ maxCitations: number;
197
+ fetchMaxLength: number;
198
+ timeoutMs: number;
199
+ /** Whether to scrape-fallback when no native web-search model is deployed. */
200
+ scrapeFallback: boolean;
201
+ /** The named URL policy in force. */
202
+ urlPolicy: UrlPolicyMode;
203
+ /** Compiled allow-list (permit-all under the `unrestricted` policy). */
204
+ allowList: UrlAllowList;
205
+ /** Default per-tool approval policy. */
206
+ approval: ApprovalPolicy;
207
+ }
208
+ /** JSON Schema published on the manifest's `config.schema`. */
209
+ export declare const WEB_SEARCH_CONFIG_SCHEMA: JSONSchema7;
210
+ /**
211
+ * Normalize any accepted approval spelling into an {@link ApprovalPolicy}.
212
+ * A pattern gate with no usable patterns collapses to `"none"` rather than
213
+ * gating everything, so a blank env var can never wedge the tools behind an
214
+ * approval nobody configured.
215
+ */
216
+ export declare function toApprovalPolicy(gate: ApprovalGate | ApprovalPolicy | undefined): ApprovalPolicy;
217
+ /**
218
+ * Resolve plugin config over environment defaults into the concrete
219
+ * {@link ResolvedWebSearchConfig}. Which model is used stays lazy - it is
220
+ * picked at call time against the live catalogue - but everything else is
221
+ * settled here, and a contradiction (unparseable `WEB_SEARCH_TOOLS`, an
222
+ * unknown URL policy, a policy that disagrees with its allow-list) throws a
223
+ * {@link ConfigurationError} naming the field and the environment variable.
224
+ */
225
+ export declare function resolveWebSearchConfig(config?: WebSearchPluginConfig): ResolvedWebSearchConfig;
226
+ /**
227
+ * Resolve an approval gate against a set of candidate URLs into a concrete
228
+ * boolean: `"none"` / `"always"` pass straight through; a `"urls"` policy
229
+ * gates when ANY candidate matches. Empty candidates with a pattern gate
230
+ * never match (nothing to approve). Reuses the allow-list matcher so approval
231
+ * globs read exactly like allow-list globs.
232
+ */
233
+ export declare function approvalMatches(gate: ApprovalGate | ApprovalPolicy, urls: readonly string[]): boolean;