@dbx-tools/appkit-web-search 0.3.28 → 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 +128 -30
- package/index.ts +5 -2
- package/package.json +6 -6
- package/src/allowlist.ts +7 -12
- package/src/config.ts +226 -56
- package/src/defaults.ts +127 -0
- package/src/fetch.ts +49 -54
- package/src/html-text.ts +72 -0
- package/src/plugin.ts +219 -33
- package/src/provider.ts +28 -5
- package/src/runtime.ts +87 -3
- package/src/schema.ts +22 -0
- package/src/scrape.ts +35 -27
- package/src/search.ts +147 -56
- package/src/tool.ts +44 -45
- package/test/allowlist.test.ts +70 -4
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
|
-
-
|
|
27
|
-
[`@dbx-tools/path`](../path)'s `match` matcher: `web_search` silently
|
|
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
|
|
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
|
-
|
|
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/");
|
|
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).
|
|
169
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
|
197
|
-
default
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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,15 +307,23 @@ 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
|
|
225
318
|
native web-search model.
|
|
226
|
-
- `fetch` - `runWebFetch()` over got-scraping
|
|
227
|
-
- `
|
|
228
|
-
|
|
319
|
+
- `fetch` - `runWebFetch()` over got-scraping.
|
|
320
|
+
- `html-text` - `htmlToText()` / `htmlFragmentToText()` / `decodeHtmlEntities()`, shared by
|
|
321
|
+
`fetch` and the DuckDuckGo scrape fallback.
|
|
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.
|
|
229
327
|
- `allowlist` - URL allow-list parsing/compiling on top of `@dbx-tools/path`.
|
|
230
328
|
- `schema` - zod tool contracts and inferred types.
|
|
231
329
|
|
package/index.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
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";
|
|
9
|
+
export * as htmlText from "./src/html-text";
|
|
8
10
|
export * as plugin from "./src/plugin";
|
|
9
11
|
export * as provider from "./src/provider";
|
|
10
12
|
export * as runtime from "./src/runtime";
|
|
@@ -13,9 +15,10 @@ export * as scrape from "./src/scrape";
|
|
|
13
15
|
export * as search from "./src/search";
|
|
14
16
|
export * as tool from "./src/tool";
|
|
15
17
|
export type { UrlAllowList } from "./src/allowlist";
|
|
16
|
-
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";
|
|
17
20
|
export type { WebSearchProvider, WebSearchProviderSpec } from "./src/provider";
|
|
18
|
-
export type { WebSearchRuntime } from "./src/runtime";
|
|
21
|
+
export type { WebSearchExecutor, WebSearchRuntime } from "./src/runtime";
|
|
19
22
|
export type { WebSearchRequest, WebSearchCitation, WebSearchResult, WebFetchRequest, WebFetchResult } from "./src/schema";
|
|
20
23
|
export type { WebSearchContext } from "./src/search";
|
|
21
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": "
|
|
20
|
-
"@dbx-tools/
|
|
21
|
-
"@dbx-tools/
|
|
22
|
-
"@dbx-tools/shared-core": "0.3.
|
|
23
|
-
"@dbx-tools/shared-model": "0.3.
|
|
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.
|
|
30
|
+
"version": "0.3.30",
|
|
31
31
|
"types": "index.ts",
|
|
32
32
|
"type": "module",
|
|
33
33
|
"exports": {
|
package/src/allowlist.ts
CHANGED
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
* @module
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
import { ValidationError } from "@databricks/appkit";
|
|
32
33
|
import { match, type PathMatcher } from "@dbx-tools/path";
|
|
33
|
-
import {
|
|
34
|
+
import { string } from "@dbx-tools/shared-core";
|
|
34
35
|
|
|
35
36
|
/** A compiled URL allow-list. Build one with {@link toUrlAllowList}. */
|
|
36
37
|
export interface UrlAllowList {
|
|
@@ -64,15 +65,7 @@ export function normalizeUrlPattern(pattern: string): string {
|
|
|
64
65
|
* identically.
|
|
65
66
|
*/
|
|
66
67
|
export function parseAllowedUrls(raw: string | string[] | undefined): string[] {
|
|
67
|
-
|
|
68
|
-
return [
|
|
69
|
-
...object
|
|
70
|
-
.sequence(entries)
|
|
71
|
-
.map((entry) => normalizeUrlPattern(entry))
|
|
72
|
-
.filter((entry) => entry.length > 0)
|
|
73
|
-
.distinct()
|
|
74
|
-
.toArray(),
|
|
75
|
-
];
|
|
68
|
+
return string.parseList(raw, normalizeUrlPattern);
|
|
76
69
|
}
|
|
77
70
|
|
|
78
71
|
/** One compiled entry: which URL slice it tests, and the matcher for it. */
|
|
@@ -136,8 +129,10 @@ export function toUrlAllowList(patterns: readonly string[]): UrlAllowList {
|
|
|
136
129
|
*/
|
|
137
130
|
export function assertUrlAllowed(url: string, allow: UrlAllowList): void {
|
|
138
131
|
if (!allow.allows(url)) {
|
|
139
|
-
throw
|
|
140
|
-
|
|
132
|
+
throw ValidationError.invalidValue(
|
|
133
|
+
"url",
|
|
134
|
+
url,
|
|
135
|
+
`a URL permitted by the configured allow-list (${allow.patterns.join(", ")})`,
|
|
141
136
|
);
|
|
142
137
|
}
|
|
143
138
|
}
|