@dbx-tools/appkit-web-search 0.3.29 → 0.3.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,11 +11,12 @@ through [`got-scraping`](https://www.npmjs.com/package/got-scraping) (browser-li
11
11
  TLS + header fingerprints so a fetch survives common bot walls), which Databricks
12
12
  has no equivalent for.
13
13
 
14
- Key features:
14
+ **Key features:**
15
15
 
16
16
  - AppKit plugin registration that resolves and logs the effective policy at boot.
17
17
  - Two Mastra tools: `web_search` (answer + citations) and `web_fetch` (page
18
- contents as readable text or raw HTML).
18
+ contents as readable text or raw HTML), plus the same pair as AppKit agent
19
+ tools through the plugin's `ToolProvider`.
19
20
  - `web_search` resolves its OWN web-search-capable model - defaulting to Gemini,
20
21
  then GPT - independently of the calling agent's chat model (which may not
21
22
  support web search). Loose names (`"gemini"`, `"gpt"`) fuzzy-match the live
@@ -23,11 +24,15 @@ Key features:
23
24
  - A built-in provider -> tool-spec map (OpenAI Responses API
24
25
  `{"type":"web_search"}`, Gemini Chat Completions `{"google_search":{}}`),
25
26
  overridable per provider via the `WEB_SEARCH_TOOLS` setting.
26
- - An optional URL allow-list (globs or bare hosts) built on
27
- [`@dbx-tools/path`](../path)'s `match` matcher: `web_search` silently filters
28
- disallowed citations, `web_fetch` refuses a disallowed URL.
27
+ - A named URL policy (`allowlist` or `unrestricted`) over a glob allow-list
28
+ built on [`@dbx-tools/path`](../path)'s `match` matcher: `web_search` silently
29
+ filters disallowed citations, `web_fetch` refuses a disallowed URL, and the
30
+ mode in force is logged at boot.
29
31
  - Per-tool approval gating: gate every call, or (for `web_fetch`) only calls
30
32
  whose URL matches a pattern, mapped onto Mastra's `requireApproval`.
33
+ - Every outbound call runs through AppKit's `execute()` chain - per-user cache,
34
+ retry with jittered backoff, timeout, telemetry - and unwinds on an
35
+ `AbortSignal`.
31
36
 
32
37
  ## Why Use This Over Native AppKit
33
38
 
@@ -68,12 +73,28 @@ await createApp({
68
73
  });
69
74
  ```
70
75
 
71
- `plugin.webSearch()` resolves config (over env), compiles the allow-list, and
76
+ `plugin.webSearch()` resolves config (over env), compiles the URL policy, and
72
77
  primes the shared runtime the tools reuse. `tool.webSearchTool()` /
73
78
  `tool.webFetchTool()` build the two Mastra tools. Approval, when enabled,
74
79
  requires Mastra storage, so register `lakebase()` or configure storage in the
75
80
  Mastra plugin.
76
81
 
82
+ The plugin is also an AppKit `ToolProvider`, so an AppKit agent can take the
83
+ same two tools without Mastra in the picture:
84
+
85
+ ```ts
86
+ import { createAgent } from "@databricks/appkit/beta";
87
+
88
+ const researcher = createAgent({
89
+ instructions: "Research questions with web_search, then read sources with web_fetch.",
90
+ tools: (plugins) => ({ ...plugins["web-search"].toolkit() }),
91
+ });
92
+ ```
93
+
94
+ Both tools are annotated `effect: "read"` and require user context, and neither
95
+ is auto-inheritable: an agent has to ask for them, because `web_fetch` reaches
96
+ whatever URL the model produces.
97
+
77
98
  ## Choose The Web-Search Model
78
99
 
79
100
  The native web-search tool only runs on certain models, and it is provider-
@@ -89,6 +110,24 @@ agent's chat model, so an agent on any model can still search:
89
110
  Llama endpoint), the tool errors rather than silently searching with the wrong
90
111
  thing. When nothing is pinned, unsupported fallbacks are skipped.
91
112
 
113
+ The endpoint id is read in this order, and where it came from changes what
114
+ happens when it cannot run web search:
115
+
116
+ | Order | Source | Unsupported endpoint |
117
+ | ----- | ---------------------------------- | -------------------------- |
118
+ | 1 | `model` in plugin config | Error |
119
+ | 2 | `WEB_SEARCH_MODEL` | Error |
120
+ | 3 | `DATABRICKS_SERVING_ENDPOINT_NAME` | Skipped, fallbacks apply |
121
+ | 4 | `modelFallbacks` | Skipped, next one is tried |
122
+
123
+ `DATABRICKS_SERVING_ENDPOINT_NAME` is AppKit's standard name for a Model
124
+ Serving binding, and it is the field the plugin declares in its manifest, so
125
+ `app.yaml` / bundle wiring reaches this plugin the way it reaches any other.
126
+ Because that binding is usually the app's chat endpoint - which need not
127
+ support web search - it is treated as a preference rather than a pin.
128
+ `WEB_SEARCH_MODEL` stays as the dedicated override for pointing web search at
129
+ a different endpoint than the rest of the app.
130
+
92
131
  Model resolution runs against the LIVE workspace catalogue (via
93
132
  [`@dbx-tools/model`](../model)), so it only ever picks an endpoint that is
94
133
  actually deployed - a configured id that doesn't exist is never called.
@@ -135,7 +174,7 @@ const result = await search.runWebSearch(
135
174
  runtime.getWebSearchRuntime().config,
136
175
  { client: ctx.client, host },
137
176
  );
138
- console.log(result.answer, result.citations, result.model);
177
+ // result.answer, result.citations, result.model
139
178
 
140
179
  const page = await fetch.runWebFetch(
141
180
  { url: result.citations[0]!.url, format: "text" },
@@ -156,7 +195,7 @@ const list = allowlist.toUrlAllowList(
156
195
  );
157
196
 
158
197
  list.allows("https://docs.databricks.com/aws/en/index.html"); // true
159
- list.allows("https://evil.example.com/"); // false
198
+ list.allows("https://evil.example.com/"); // false
160
199
  ```
161
200
 
162
201
  Each entry is a glob compiled by [`@dbx-tools/path`](../path)'s `match` matcher.
@@ -165,15 +204,36 @@ URL's hostname, and a bare host also matches its subdomains; a path entry
165
204
  (`docs.example.com/api/**`) matches host + pathname. Enforcement is asymmetric by
166
205
  design: `web_search` citations are silently filtered to the permitted set (the
167
206
  model never surfaces a source it then can't fetch), while an explicit `web_fetch`
168
- of a disallowed URL is refused with an error (a visible, correctable mistake). An
169
- empty / absent allow-list permits everything.
207
+ of a disallowed URL is refused with an error (a visible, correctable mistake).
208
+
209
+ Which of the two modes is in force is named by `urlPolicy`, and the effective
210
+ mode is in the boot log:
211
+
212
+ | `urlPolicy` | Effect |
213
+ | ---------------- | ------------------------------------------------- |
214
+ | `"allowlist"` | Only `allowedUrls` entries are reachable |
215
+ | `"unrestricted"` | Any URL is reachable; the open mode, stated aloud |
216
+
217
+ Leave `urlPolicy` unset and it follows `allowedUrls`: `"allowlist"` when there
218
+ are entries, `"unrestricted"` when there are none. Set it to say which one you
219
+ meant. The two contradictions fail at startup rather than quietly picking a
220
+ side: `"allowlist"` with no entries, and `"unrestricted"` with entries.
221
+
222
+ ```ts
223
+ // An open deployment, said out loud:
224
+ plugin.webSearch({ urlPolicy: "unrestricted" });
225
+
226
+ // A closed one:
227
+ plugin.webSearch({ urlPolicy: "allowlist", allowedUrls: ["*.databricks.com"] });
228
+ ```
170
229
 
171
230
  ## Gate Calls For Approval
172
231
 
173
232
  Both tools run without approval by default. Pass `approval` to a tool (or set it
174
233
  plugin-wide) to require a human click. `true` gates every call; a URL pattern (or
175
234
  list) gates only calls whose URL matches - a `web_fetch` is evaluated precisely
176
- against its target URL.
235
+ against its target URL, while a `web_search` (whose result URLs are unknown
236
+ before the call) treats a pattern gate as "gate every call".
177
237
 
178
238
  ```ts
179
239
  import { tool } from "@dbx-tools/appkit-web-search";
@@ -184,32 +244,62 @@ tool.webFetchTool({ approval: true });
184
244
  // Only fetches of an internal domain require approval:
185
245
  tool.webFetchTool({ approval: "*.internal.example.com" });
186
246
 
247
+ // The same thing, with the mode named:
248
+ tool.webFetchTool({ approval: { mode: "urls", patterns: ["*.internal.example.com"] } });
249
+
187
250
  // Plugin-wide default (tools inherit unless they set their own):
188
251
  plugin.webSearch({ approval: ["*.internal.example.com", "*.corp.example.com"] });
189
252
  ```
190
253
 
191
- Approval uses the same glob syntax as the allow-list, so the two policies read
192
- identically.
254
+ Every spelling normalizes to one of three modes - `{ mode: "none" }`,
255
+ `{ mode: "always" }`, `{ mode: "urls", patterns }` - which is what the resolved
256
+ config carries and the boot log reports. Approval uses the same glob syntax as
257
+ the allow-list, so the two policies read identically.
193
258
 
194
259
  ## Configuration
195
260
 
196
- Resolution order is explicit config first, then env vars, then a built-in
197
- default:
198
-
199
- - `WEB_SEARCH_MODEL` (default web-search model; else the Gemini->GPT fallbacks);
200
- - `WEB_SEARCH_MODEL_FALLBACKS` (comma/space-separated candidate ids);
201
- - `WEB_SEARCH_TOOLS` (JSON provider -> tool-spec override map);
202
- - `WEB_SEARCH_FUZZY` / `WEB_SEARCH_FUZZY_THRESHOLD` (loose-name matching; default on / 0.4);
203
- - `WEB_SEARCH_MAX_CITATIONS` (default 10);
204
- - `WEB_SEARCH_FETCH_MAX_LENGTH` (default 50000 characters);
205
- - `WEB_SEARCH_TIMEOUT_MS` (default 30000);
206
- - `WEB_SEARCH_SCRAPE_FALLBACK` (fall back to a DuckDuckGo scrape when no native
207
- web-search model is deployed; default on);
208
- - `WEB_SEARCH_ALLOWED_URLS` (comma/space-separated globs; empty = unrestricted).
261
+ Resolution order is explicit config first, then the environment variable, then a
262
+ built-in default.
263
+
264
+ | Option | Type | Default | Description |
265
+ | --------------------- | ------------------------------------------------- | ----------------------- | --------------------------------------------------------------------------- |
266
+ | `model` | `string` | resolved from fallbacks | Web-search endpoint: an endpoint name, a loose name, or a capability class. |
267
+ | `modelFallbacks` | `string \| string[]` | Gemini, then GPT | Ordered candidates tried when `model` is unset. |
268
+ | `webSearchTools` | `Record<string, unknown>` | built-in provider map | Provider -> tool-spec overrides, merged over the defaults. |
269
+ | `modelFuzzyMatch` | `boolean` | `true` | Fuzzy-match loose model names against the live catalogue. |
270
+ | `modelFuzzyThreshold` | `number` | `0.4` | Fuse.js score below which a fuzzy match is accepted. |
271
+ | `maxCitations` | `number` | `10` | Hard cap on citations returned from one search. |
272
+ | `fetchMaxLength` | `number` | `50000` | Hard cap on `web_fetch` content length, in characters. |
273
+ | `timeoutMs` | `number` | `30000` | Per-request network timeout. |
274
+ | `scrapeFallback` | `boolean` | `true` | Scrape DuckDuckGo when no web-search-capable model is deployed. |
275
+ | `urlPolicy` | `"unrestricted" \| "allowlist"` | follows `allowedUrls` | Which URLs the tools may reach. |
276
+ | `allowedUrls` | `string \| string[]` | none | Allow-list globs or bare hosts. |
277
+ | `approval` | `boolean \| string \| string[] \| ApprovalPolicy` | `{ mode: "none" }` | Default approval gate for both tools. |
278
+
279
+ | Environment variable | Sets |
280
+ | ---------------------------------- | ----------------------------------------------------------------- |
281
+ | `WEB_SEARCH_MODEL` | `model` (dedicated override, wins over the binding below) |
282
+ | `DATABRICKS_SERVING_ENDPOINT_NAME` | `model` (AppKit's Model Serving binding, treated as a preference) |
283
+ | `WEB_SEARCH_MODEL_FALLBACKS` | `modelFallbacks` (comma/space-separated) |
284
+ | `WEB_SEARCH_TOOLS` | `webSearchTools` (JSON object; anything else fails at boot) |
285
+ | `WEB_SEARCH_FUZZY` | `modelFuzzyMatch` |
286
+ | `WEB_SEARCH_FUZZY_THRESHOLD` | `modelFuzzyThreshold` |
287
+ | `WEB_SEARCH_MAX_CITATIONS` | `maxCitations` |
288
+ | `WEB_SEARCH_FETCH_MAX_LENGTH` | `fetchMaxLength` |
289
+ | `WEB_SEARCH_TIMEOUT_MS` | `timeoutMs` |
290
+ | `WEB_SEARCH_SCRAPE_FALLBACK` | `scrapeFallback` |
291
+ | `WEB_SEARCH_URL_POLICY` | `urlPolicy` |
292
+ | `WEB_SEARCH_ALLOWED_URLS` | `allowedUrls` (comma/space-separated) |
209
293
 
210
294
  The plugin's `maxCitations` / `fetchMaxLength` are hard caps: a per-call request
211
295
  may narrow them but never exceed them.
212
296
 
297
+ Every outbound call - the serving POST, a page fetch, the scrape fallback -
298
+ runs through the plugin's `execute()` chain, so all three are cached, retried
299
+ with jittered backoff, timed out, and traced. The cache is namespaced per user,
300
+ so an on-behalf-of result never crosses identities. See `defaults.ts` for the
301
+ TTLs and retry budgets and why each was chosen.
302
+
213
303
  Requirements: the native web-search tool runs on pay-per-token GPT / Gemini
214
304
  serving endpoints with cross-region processing enabled; it is unavailable on
215
305
  provisioned throughput, for external models, or under HIPAA/BAA compliance. See
@@ -217,8 +307,11 @@ the [Databricks docs](https://docs.databricks.com/aws/en/machine-learning/model-
217
307
 
218
308
  ## Modules
219
309
 
220
- - `plugin` - `WebSearchPlugin`, `webSearch()` AppKit plugin factory.
310
+ - `plugin` - `WebSearchPlugin`, `webSearch()` AppKit plugin factory, and the
311
+ AppKit `ToolProvider` implementation.
221
312
  - `tool` - `webSearchTool()` / `webFetchTool()` Mastra tools.
313
+ - `defaults` - the cache / retry / timeout settings every outbound call runs
314
+ with, and the reasoning behind each.
222
315
  - `search` - `runWebSearch()` over the Databricks native web-search tool.
223
316
  - `provider` - provider detection + the provider -> tool-spec map.
224
317
  - `scrape` - `runScrapeSearch()` DuckDuckGo fallback for workspaces with no
@@ -226,8 +319,11 @@ the [Databricks docs](https://docs.databricks.com/aws/en/machine-learning/model-
226
319
  - `fetch` - `runWebFetch()` over got-scraping.
227
320
  - `html-text` - `htmlToText()` / `htmlFragmentToText()` / `decodeHtmlEntities()`, shared by
228
321
  `fetch` and the DuckDuckGo scrape fallback.
229
- - `runtime` - shared runtime, `getWebSearchRuntime()`, `resetWebSearchRuntime()`.
230
- - `config` - config types, JSON schema, `resolveWebSearchConfig()`, approval helpers.
322
+ - `runtime` - shared runtime and the executor outbound calls run through:
323
+ `getWebSearchRuntime()`, `setWebSearchExecutor()`, `executeRead()`,
324
+ `resetWebSearchRuntime()`.
325
+ - `config` - config types, JSON schema, `resolveWebSearchConfig()`, URL policy
326
+ and approval helpers.
231
327
  - `allowlist` - URL allow-list parsing/compiling on top of `@dbx-tools/path`.
232
328
  - `schema` - zod tool contracts and inferred types.
233
329
 
package/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  export * as allowlist from "./src/allowlist";
6
6
  export * as config from "./src/config";
7
+ export * as defaults from "./src/defaults";
7
8
  export * as fetch from "./src/fetch";
8
9
  export * as htmlText from "./src/html-text";
9
10
  export * as plugin from "./src/plugin";
@@ -14,9 +15,10 @@ export * as scrape from "./src/scrape";
14
15
  export * as search from "./src/search";
15
16
  export * as tool from "./src/tool";
16
17
  export type { UrlAllowList } from "./src/allowlist";
17
- export type { ApprovalGate, WebSearchPluginConfig, ResolvedWebSearchConfig } from "./src/config";
18
+ export type { ApprovalGate, ApprovalPolicy, UrlPolicyMode, ModelSource, WebSearchPluginConfig, ResolvedWebSearchConfig } from "./src/config";
19
+ export type { WebSearchExecuteConfig, WebSearchExecutionSettings } from "./src/defaults";
18
20
  export type { WebSearchProvider, WebSearchProviderSpec } from "./src/provider";
19
- export type { WebSearchRuntime } from "./src/runtime";
21
+ export type { WebSearchExecutor, WebSearchRuntime } from "./src/runtime";
20
22
  export type { WebSearchRequest, WebSearchCitation, WebSearchResult, WebFetchRequest, WebFetchResult } from "./src/schema";
21
23
  export type { WebSearchContext } from "./src/search";
22
24
  export type { WebSearchToolOptions } from "./src/tool";
package/package.json CHANGED
@@ -16,18 +16,18 @@
16
16
  "@databricks/appkit": "^0.43.0",
17
17
  "@mastra/core": "^1.47.0",
18
18
  "got-scraping": "^4.2.1",
19
- "zod": "^4.3.6",
20
- "@dbx-tools/path": "0.3.29",
21
- "@dbx-tools/model": "0.3.29",
22
- "@dbx-tools/shared-core": "0.3.29",
23
- "@dbx-tools/shared-model": "0.3.29"
19
+ "zod": "4.3.6",
20
+ "@dbx-tools/model": "0.3.30",
21
+ "@dbx-tools/path": "0.3.30",
22
+ "@dbx-tools/shared-core": "0.3.30",
23
+ "@dbx-tools/shared-model": "0.3.30"
24
24
  },
25
25
  "main": "index.ts",
26
26
  "license": "UNLICENSED",
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "version": "0.3.29",
30
+ "version": "0.3.30",
31
31
  "types": "index.ts",
32
32
  "type": "module",
33
33
  "exports": {
package/src/allowlist.ts CHANGED
@@ -29,6 +29,7 @@
29
29
  * @module
30
30
  */
31
31
 
32
+ import { ValidationError } from "@databricks/appkit";
32
33
  import { match, type PathMatcher } from "@dbx-tools/path";
33
34
  import { string } from "@dbx-tools/shared-core";
34
35
 
@@ -128,8 +129,10 @@ export function toUrlAllowList(patterns: readonly string[]): UrlAllowList {
128
129
  */
129
130
  export function assertUrlAllowed(url: string, allow: UrlAllowList): void {
130
131
  if (!allow.allows(url)) {
131
- throw new Error(
132
- `web-search: URL "${url}" is not permitted by the configured allow-list (${allow.patterns.join(", ")})`,
132
+ throw ValidationError.invalidValue(
133
+ "url",
134
+ url,
135
+ `a URL permitted by the configured allow-list (${allow.patterns.join(", ")})`,
133
136
  );
134
137
  }
135
138
  }