@dbx-tools/appkit-web-search 0.6.222 → 0.6.224
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 +47 -26
- package/index.ts +2 -2
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/src/fetch.d.ts +2 -2
- package/lib/src/fetch.js +6 -4
- package/lib/src/html-text.d.ts +4 -8
- package/lib/src/html-text.js +27 -36
- package/lib/src/plugin.d.ts +24 -27
- package/lib/src/plugin.js +30 -35
- package/lib/src/runtime.d.ts +29 -28
- package/lib/src/runtime.js +38 -40
- package/lib/src/scrape.d.ts +5 -3
- package/lib/src/scrape.js +17 -22
- package/lib/src/search.d.ts +2 -2
- package/lib/src/search.js +10 -7
- package/lib/src/tool.d.ts +11 -5
- package/lib/src/tool.js +22 -16
- package/package.json +10 -6
- package/src/fetch.ts +5 -3
- package/src/html-text.ts +28 -37
- package/src/plugin.ts +32 -39
- package/src/runtime.ts +61 -39
- package/src/scrape.ts +16 -20
- package/src/search.ts +14 -5
- package/src/tool.ts +28 -16
package/src/plugin.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AppKit plugin (registered name: `web-search`) that owns the resolved
|
|
3
3
|
* web-search runtime - the URL policy, result / length caps, timeout, and
|
|
4
|
-
* default approval gate
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* AppKit's cache / retry / timeout / telemetry chain.
|
|
4
|
+
* default approval gate its tools read. Registering it resolves and logs the
|
|
5
|
+
* effective config (which URL policy is in force, the caps) so a
|
|
6
|
+
* misconfiguration is visible in the boot logs rather than on the first
|
|
7
|
+
* search. The plugin's `execute()` is captured by its own runtime so every
|
|
8
|
+
* outbound call picks up AppKit's cache / retry / timeout / telemetry chain.
|
|
10
9
|
*
|
|
11
10
|
* The plugin also implements AppKit's `ToolProvider`, so an AppKit agent gets
|
|
12
|
-
* `web_search` / `web_fetch` without going through Mastra
|
|
13
|
-
*
|
|
11
|
+
* `web_search` / `web_fetch` without going through Mastra. App-integrated
|
|
12
|
+
* Mastra agents consume this provider through `plugins["web-search"].toolkit()`;
|
|
13
|
+
* standalone factories in `tool.ts` use their own explicit runtime or config.
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
type WebSearchPluginConfig,
|
|
39
39
|
} from "./config.ts";
|
|
40
40
|
import { runWebFetch } from "./fetch.ts";
|
|
41
|
-
import {
|
|
41
|
+
import { createWebSearchRuntime, type WebSearchRuntime } from "./runtime.ts";
|
|
42
42
|
import {
|
|
43
43
|
webFetchRequestSchema,
|
|
44
44
|
webSearchRequestSchema,
|
|
@@ -120,6 +120,11 @@ export class WebSearchPlugin extends Plugin<WebSearchPluginConfig> implements To
|
|
|
120
120
|
config: { schema: WEB_SEARCH_CONFIG_SCHEMA },
|
|
121
121
|
} satisfies PluginManifest<"web-search">;
|
|
122
122
|
|
|
123
|
+
/** Runtime owned exclusively by this plugin instance. */
|
|
124
|
+
private readonly runtime: WebSearchRuntime = createWebSearchRuntime(this.config, (fn, settings) =>
|
|
125
|
+
this.execute(fn, settings),
|
|
126
|
+
);
|
|
127
|
+
|
|
123
128
|
/**
|
|
124
129
|
* Promote the serving endpoint to a required resource once a deployment
|
|
125
130
|
* pins one, through plugin config or either environment name. Left optional
|
|
@@ -166,13 +171,12 @@ export class WebSearchPlugin extends Plugin<WebSearchPluginConfig> implements To
|
|
|
166
171
|
};
|
|
167
172
|
|
|
168
173
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
174
|
+
* Log the effective policy so an active allow-list and caps are obvious at
|
|
175
|
+
* boot. Runtime construction already resolved config and captured this
|
|
176
|
+
* plugin's interceptor chain.
|
|
172
177
|
*/
|
|
173
178
|
override async setup(): Promise<void> {
|
|
174
|
-
const { config } =
|
|
175
|
-
setWebSearchExecutor((fn, settings) => this.execute(fn, settings));
|
|
179
|
+
const { config } = this.runtime;
|
|
176
180
|
logger.info("ready", {
|
|
177
181
|
model: config.model ?? `fallbacks:[${config.modelFallbacks.join(", ")}]`,
|
|
178
182
|
modelSource: config.modelSource,
|
|
@@ -186,24 +190,16 @@ export class WebSearchPlugin extends Plugin<WebSearchPluginConfig> implements To
|
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* idempotent: there is no connection to drain, only the memo to clear.
|
|
193
|
+
* No runtime resources require draining. The instance-owned config and
|
|
194
|
+
* executor become unreachable with this plugin.
|
|
192
195
|
*/
|
|
193
|
-
|
|
194
|
-
|
|
196
|
+
shutdown(): void {
|
|
197
|
+
return;
|
|
195
198
|
}
|
|
196
199
|
|
|
197
|
-
/**
|
|
198
|
-
* Abort in-flight work. AppKit's graceful shutdown only invokes this hook -
|
|
199
|
-
* it never calls {@link shutdown} - so the runtime memo is dropped from here
|
|
200
|
-
* to keep a restarted app from calling through a torn-down `execute()`. The
|
|
201
|
-
* teardown is synchronous and idempotent, so the un-awaited call costs
|
|
202
|
-
* nothing.
|
|
203
|
-
*/
|
|
200
|
+
/** Abort in-flight work owned by this plugin instance. */
|
|
204
201
|
override abortActiveOperations(): void {
|
|
205
202
|
super.abortActiveOperations();
|
|
206
|
-
void this.shutdown();
|
|
207
203
|
}
|
|
208
204
|
|
|
209
205
|
/** AppKit `ToolProvider`: the tool definitions offered to an agent. */
|
|
@@ -224,14 +220,14 @@ export class WebSearchPlugin extends Plugin<WebSearchPluginConfig> implements To
|
|
|
224
220
|
return {
|
|
225
221
|
/**
|
|
226
222
|
* Run a web search directly (bypassing the agent tool). Resolves the
|
|
227
|
-
* OBO client from the active execution context and reads
|
|
228
|
-
* runtime config
|
|
223
|
+
* OBO client from the active execution context and reads this plugin's
|
|
224
|
+
* runtime config.
|
|
229
225
|
*/
|
|
230
226
|
search: (request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult> =>
|
|
231
227
|
this.search(request, signal),
|
|
232
228
|
/**
|
|
233
229
|
* Fetch one URL directly (bypassing the agent tool). Enforces the
|
|
234
|
-
* configured URL policy.
|
|
230
|
+
* configured URL policy.
|
|
235
231
|
*/
|
|
236
232
|
fetch: (request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult> =>
|
|
237
233
|
this.fetch(request, signal),
|
|
@@ -239,16 +235,11 @@ export class WebSearchPlugin extends Plugin<WebSearchPluginConfig> implements To
|
|
|
239
235
|
}
|
|
240
236
|
|
|
241
237
|
private async search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult> {
|
|
242
|
-
return runWebSearch(
|
|
243
|
-
request,
|
|
244
|
-
getWebSearchRuntime().config,
|
|
245
|
-
await resolveWebSearchContext(),
|
|
246
|
-
signal,
|
|
247
|
-
);
|
|
238
|
+
return runWebSearch(request, this.runtime, await resolveWebSearchContext(), signal);
|
|
248
239
|
}
|
|
249
240
|
|
|
250
241
|
private async fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult> {
|
|
251
|
-
return runWebFetch(request,
|
|
242
|
+
return runWebFetch(request, this.runtime, signal);
|
|
252
243
|
}
|
|
253
244
|
}
|
|
254
245
|
|
|
@@ -258,12 +249,14 @@ export class WebSearchPlugin extends Plugin<WebSearchPluginConfig> implements To
|
|
|
258
249
|
* @example
|
|
259
250
|
* ```ts
|
|
260
251
|
* import { createApp, server } from "@databricks/appkit";
|
|
261
|
-
* import { plugin as webSearchPlugin
|
|
252
|
+
* import { plugin as webSearchPlugin } from "@dbx-tools/appkit-web-search";
|
|
262
253
|
* import { agents, plugin as mastraPlugin } from "@dbx-tools/appkit-mastra";
|
|
263
254
|
*
|
|
264
255
|
* const researcher = agents.createAgent({
|
|
265
256
|
* instructions: "Research questions with web_search, then read sources with web_fetch.",
|
|
266
|
-
* tools
|
|
257
|
+
* async tools(plugins) {
|
|
258
|
+
* return { ...(await plugins["web-search"].toolkit()) };
|
|
259
|
+
* },
|
|
267
260
|
* });
|
|
268
261
|
*
|
|
269
262
|
* await createApp({
|
package/src/runtime.ts
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* the plugin and the `web_search` / `web_fetch` tools, so both read one
|
|
4
|
-
* resolved allow-list / cap / timeout set. The first caller (normally the
|
|
5
|
-
* plugin at setup) primes it from the plugin's config; later callers (the
|
|
6
|
-
* tools' `execute`) reuse it.
|
|
2
|
+
* Web-search runtime construction and execution helpers.
|
|
7
3
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* get AppKit's cache / retry / timeout / telemetry chain. Without a
|
|
12
|
-
* registered plugin (a direct call from a script or a test) the calls still
|
|
13
|
-
* run, just without interceptors.
|
|
14
|
-
*
|
|
15
|
-
* Unlike the email runtime there is no connection to pool - the backend is
|
|
16
|
-
* stateless HTTP per call - so the runtime holds only the resolved config and
|
|
17
|
-
* that executor.
|
|
4
|
+
* Each plugin instance owns one runtime. Standalone tool factories create their
|
|
5
|
+
* own runtime, so separate apps and direct consumers cannot overwrite each
|
|
6
|
+
* other's policy or executor.
|
|
18
7
|
*
|
|
19
8
|
* @module
|
|
20
9
|
*/
|
|
@@ -40,51 +29,84 @@ export type WebSearchExecutor = <T>(
|
|
|
40
29
|
settings: WebSearchExecutionSettings,
|
|
41
30
|
) => Promise<ExecutionResult<T>>;
|
|
42
31
|
|
|
43
|
-
/** The
|
|
32
|
+
/** The resolved config plus the executor outbound calls run through. */
|
|
44
33
|
export interface WebSearchRuntime {
|
|
45
34
|
config: ResolvedWebSearchConfig;
|
|
46
35
|
execute: WebSearchExecutor;
|
|
47
36
|
}
|
|
48
37
|
|
|
38
|
+
/** Runtime-aware operation input, retaining resolved-config compatibility. */
|
|
39
|
+
export type WebSearchRuntimeInput = WebSearchRuntime | ResolvedWebSearchConfig;
|
|
40
|
+
|
|
49
41
|
/**
|
|
50
|
-
* Executor used
|
|
51
|
-
*
|
|
52
|
-
*
|
|
42
|
+
* Executor used by standalone runtimes: run the call directly, mapping a throw
|
|
43
|
+
* onto the same {@link ExecutionResult} shape so call sites branch on `ok`
|
|
44
|
+
* either way.
|
|
53
45
|
*/
|
|
54
46
|
const directExecute = execution.directExecutor<WebSearchExecutionSettings>();
|
|
55
47
|
|
|
56
|
-
|
|
48
|
+
/** Build an isolated runtime for one plugin instance or standalone tool set. */
|
|
49
|
+
export function createWebSearchRuntime(
|
|
50
|
+
overrides?: WebSearchPluginConfig,
|
|
51
|
+
execute: WebSearchExecutor = directExecute,
|
|
52
|
+
): WebSearchRuntime {
|
|
53
|
+
return { config: resolveWebSearchConfig(overrides), execute };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Build an isolated runtime from config that has already been resolved. */
|
|
57
|
+
export function createResolvedWebSearchRuntime(
|
|
58
|
+
config: ResolvedWebSearchConfig,
|
|
59
|
+
execute: WebSearchExecutor = directExecute,
|
|
60
|
+
): WebSearchRuntime {
|
|
61
|
+
return { config, execute };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Normalize an explicit runtime or legacy resolved config for one operation. */
|
|
65
|
+
export function toWebSearchRuntime(input: WebSearchRuntimeInput): WebSearchRuntime {
|
|
66
|
+
return "execute" in input ? input : createResolvedWebSearchRuntime(input);
|
|
67
|
+
}
|
|
57
68
|
|
|
58
69
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* instance.
|
|
70
|
+
* Build an isolated standalone runtime.
|
|
71
|
+
*
|
|
72
|
+
* @deprecated Use {@link createWebSearchRuntime}. This compatibility helper
|
|
73
|
+
* returns a new runtime on every call and never reads or updates plugin state.
|
|
64
74
|
*/
|
|
65
75
|
export function getWebSearchRuntime(overrides?: WebSearchPluginConfig): WebSearchRuntime {
|
|
66
|
-
|
|
67
|
-
runtime = { config: resolveWebSearchConfig(overrides), execute: directExecute };
|
|
68
|
-
}
|
|
69
|
-
return runtime;
|
|
76
|
+
return createWebSearchRuntime(overrides);
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
/** Install an executor on an explicit runtime. */
|
|
80
|
+
export function setWebSearchExecutor(runtime: WebSearchRuntime, execute: WebSearchExecutor): void;
|
|
72
81
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
82
|
+
* @deprecated Process-global executor registration is no longer supported.
|
|
83
|
+
* Pass a runtime as the first argument or provide the executor to
|
|
84
|
+
* {@link createWebSearchRuntime}.
|
|
76
85
|
*/
|
|
77
|
-
export function setWebSearchExecutor(execute: WebSearchExecutor):
|
|
78
|
-
|
|
86
|
+
export function setWebSearchExecutor(execute: WebSearchExecutor): never;
|
|
87
|
+
export function setWebSearchExecutor(
|
|
88
|
+
runtimeOrExecute: WebSearchRuntime | WebSearchExecutor,
|
|
89
|
+
execute?: WebSearchExecutor,
|
|
90
|
+
): void {
|
|
91
|
+
if (typeof runtimeOrExecute === "function") {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
"setWebSearchExecutor requires an explicit runtime; use createWebSearchRuntime(config, executor)",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (!execute) throw new TypeError("setWebSearchExecutor requires an executor");
|
|
97
|
+
runtimeOrExecute.execute = execute;
|
|
79
98
|
}
|
|
80
99
|
|
|
81
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* @deprecated Plugin runtimes are instance-owned and need no global reset.
|
|
102
|
+
* This compatibility helper is intentionally a no-op.
|
|
103
|
+
*/
|
|
82
104
|
export function resetWebSearchRuntime(): void {
|
|
83
|
-
|
|
105
|
+
return;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
/**
|
|
87
|
-
* Run one idempotent read through the
|
|
109
|
+
* Run one idempotent read through the runtime executor and unwrap it.
|
|
88
110
|
*
|
|
89
111
|
* `execute()` never throws, so a failed call arrives as `{ ok: false }` with
|
|
90
112
|
* a status the interceptors already sanitized; it is logged here and re-raised
|
|
@@ -94,16 +116,16 @@ export function resetWebSearchRuntime(): void {
|
|
|
94
116
|
* interceptor supplies so either one unwinds the I/O.
|
|
95
117
|
*/
|
|
96
118
|
export async function executeRead<T>(
|
|
119
|
+
runtime: WebSearchRuntime,
|
|
97
120
|
operation: string,
|
|
98
121
|
settings: WebSearchExecutionSettings,
|
|
99
122
|
fn: (signal?: AbortSignal) => Promise<T>,
|
|
100
123
|
signal?: AbortSignal,
|
|
101
124
|
): Promise<T> {
|
|
102
|
-
const { execute } = getWebSearchRuntime();
|
|
103
125
|
return execution.run({
|
|
104
126
|
operation,
|
|
105
127
|
settings,
|
|
106
|
-
execute,
|
|
128
|
+
execute: runtime.execute,
|
|
107
129
|
fn,
|
|
108
130
|
signal,
|
|
109
131
|
canceled: ExecutionError.canceled,
|
package/src/scrape.ts
CHANGED
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { log } from "@dbx-tools/shared-core";
|
|
22
|
+
import { load } from "cheerio";
|
|
22
23
|
import { gotScraping } from "got-scraping";
|
|
23
|
-
import type { ResolvedWebSearchConfig } from "./config.ts";
|
|
24
24
|
import { scrapeSearchExecuteDefaults, toCallSettings } from "./defaults.ts";
|
|
25
25
|
import { htmlFragmentToText } from "./html-text.ts";
|
|
26
|
-
import { executeRead } from "./runtime.ts";
|
|
26
|
+
import { executeRead, toWebSearchRuntime, type WebSearchRuntimeInput } from "./runtime.ts";
|
|
27
27
|
import type { WebSearchCitation, WebSearchRequest, WebSearchResult } from "./schema.ts";
|
|
28
28
|
|
|
29
29
|
const logger = log.logger("web-search/scrape");
|
|
@@ -47,25 +47,18 @@ function unwrapDdgUrl(href: string): string {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/** Parse DDG HTML result blocks into citations (title, url, snippet). */
|
|
50
|
-
function parseDdgHtml(html: string): WebSearchCitation[] {
|
|
50
|
+
export function parseDdgHtml(html: string): WebSearchCitation[] {
|
|
51
|
+
const $ = load(html);
|
|
51
52
|
const citations: WebSearchCitation[] = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
let am: RegExpExecArray | null;
|
|
60
|
-
let i = 0;
|
|
61
|
-
while ((am = anchorRe.exec(html)) !== null) {
|
|
62
|
-
const url = unwrapDdgUrl(am[1] ?? "");
|
|
63
|
-
const title = htmlFragmentToText(am[2] ?? "");
|
|
64
|
-
if (!url || !title) continue;
|
|
65
|
-
const snippet = snippets[i] ?? "";
|
|
53
|
+
$("a.result__a").each((_index, element) => {
|
|
54
|
+
const anchor = $(element);
|
|
55
|
+
const url = unwrapDdgUrl(anchor.attr("href") ?? "");
|
|
56
|
+
const title = htmlFragmentToText(anchor.html() ?? "");
|
|
57
|
+
if (!url || !title) return;
|
|
58
|
+
const snippetElement = anchor.closest(".result").find(".result__snippet").first();
|
|
59
|
+
const snippet = htmlFragmentToText(snippetElement.html() ?? "");
|
|
66
60
|
citations.push({ url, title, ...(snippet ? { snippet } : {}) });
|
|
67
|
-
|
|
68
|
-
}
|
|
61
|
+
});
|
|
69
62
|
return citations;
|
|
70
63
|
}
|
|
71
64
|
|
|
@@ -78,10 +71,13 @@ function parseDdgHtml(html: string): WebSearchCitation[] {
|
|
|
78
71
|
*/
|
|
79
72
|
export async function runScrapeSearch(
|
|
80
73
|
request: WebSearchRequest,
|
|
81
|
-
|
|
74
|
+
runtimeOrConfig: WebSearchRuntimeInput,
|
|
82
75
|
signal?: AbortSignal,
|
|
83
76
|
): Promise<WebSearchResult> {
|
|
77
|
+
const runtime = toWebSearchRuntime(runtimeOrConfig);
|
|
78
|
+
const { config } = runtime;
|
|
84
79
|
const page = await executeRead(
|
|
80
|
+
runtime,
|
|
85
81
|
"scrape-search",
|
|
86
82
|
toCallSettings(scrapeSearchExecuteDefaults, config.timeoutMs, [
|
|
87
83
|
"web-search",
|
package/src/search.ts
CHANGED
|
@@ -34,7 +34,12 @@ import { openaiChat, openaiResponses } from "@dbx-tools/shared-model";
|
|
|
34
34
|
import { MODEL_ENV, SERVING_ENDPOINT_ENV, type ResolvedWebSearchConfig } from "./config.ts";
|
|
35
35
|
import { toCallSettings, webSearchExecuteDefaults } from "./defaults.ts";
|
|
36
36
|
import { detectWebSearchProvider, supportsWebSearch, webSearchToolSpec } from "./provider.ts";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
executeRead,
|
|
39
|
+
toWebSearchRuntime,
|
|
40
|
+
type WebSearchRuntime,
|
|
41
|
+
type WebSearchRuntimeInput,
|
|
42
|
+
} from "./runtime.ts";
|
|
38
43
|
import type { WebSearchCitation, WebSearchRequest, WebSearchResult } from "./schema.ts";
|
|
39
44
|
import { runScrapeSearch } from "./scrape.ts";
|
|
40
45
|
|
|
@@ -168,11 +173,13 @@ async function postServing(
|
|
|
168
173
|
ctx: WebSearchContext,
|
|
169
174
|
url: string,
|
|
170
175
|
body: unknown,
|
|
171
|
-
|
|
176
|
+
runtime: WebSearchRuntime,
|
|
172
177
|
cacheKey: readonly (string | number)[],
|
|
173
178
|
signal?: AbortSignal,
|
|
174
179
|
): Promise<Record<string, unknown>> {
|
|
180
|
+
const { config } = runtime;
|
|
175
181
|
const payload = await executeRead(
|
|
182
|
+
runtime,
|
|
176
183
|
"serving-request",
|
|
177
184
|
toCallSettings(webSearchExecuteDefaults, config.timeoutMs, cacheKey),
|
|
178
185
|
async (executeSignal): Promise<unknown> => {
|
|
@@ -262,17 +269,19 @@ function fromChatPayload(payload: Record<string, unknown>): {
|
|
|
262
269
|
*/
|
|
263
270
|
export async function runWebSearch(
|
|
264
271
|
request: WebSearchRequest,
|
|
265
|
-
|
|
272
|
+
runtimeOrConfig: WebSearchRuntimeInput,
|
|
266
273
|
ctx: WebSearchContext,
|
|
267
274
|
signal?: AbortSignal,
|
|
268
275
|
): Promise<WebSearchResult> {
|
|
276
|
+
const runtime = toWebSearchRuntime(runtimeOrConfig);
|
|
277
|
+
const { config } = runtime;
|
|
269
278
|
const modelId = await resolveWebSearchModel(ctx, config, request.model);
|
|
270
279
|
|
|
271
280
|
if (modelId === null) {
|
|
272
281
|
// No native web-search model deployed in this workspace.
|
|
273
282
|
if (config.scrapeFallback) {
|
|
274
283
|
logger.info("no-native-model:scrape-fallback", { query: request.query });
|
|
275
|
-
return runScrapeSearch(request,
|
|
284
|
+
return runScrapeSearch(request, runtime, signal);
|
|
276
285
|
}
|
|
277
286
|
throw ConfigurationError.resourceNotFound(
|
|
278
287
|
"Web-search-capable serving endpoint",
|
|
@@ -304,7 +313,7 @@ export async function runWebSearch(
|
|
|
304
313
|
ctx,
|
|
305
314
|
url,
|
|
306
315
|
body,
|
|
307
|
-
|
|
316
|
+
runtime,
|
|
308
317
|
["web-search", "serving", spec.api, modelId, request.query],
|
|
309
318
|
signal,
|
|
310
319
|
);
|
package/src/tool.ts
CHANGED
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
* list) gates only calls whose URL matches - for `web_fetch` that is evaluated
|
|
14
14
|
* against the target URL, while `web_search` (whose result URLs aren't known
|
|
15
15
|
* before the call) treats a pattern gate as "always gate". `approval` falls
|
|
16
|
-
* back to the
|
|
16
|
+
* back to the standalone runtime's `approval` config when a tool omits its own.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* App-integrated agents should consume the plugin's native toolkit so calls
|
|
19
|
+
* retain that plugin's policy and executor. These factories are the standalone
|
|
20
|
+
* Mastra surface and capture an explicit runtime or isolated config.
|
|
20
21
|
*
|
|
21
22
|
* @module
|
|
22
23
|
*/
|
|
@@ -29,9 +30,10 @@ import {
|
|
|
29
30
|
toApprovalPolicy,
|
|
30
31
|
type ApprovalGate,
|
|
31
32
|
type ApprovalPolicy,
|
|
33
|
+
type WebSearchPluginConfig,
|
|
32
34
|
} from "./config.ts";
|
|
33
35
|
import { runWebFetch } from "./fetch.ts";
|
|
34
|
-
import {
|
|
36
|
+
import { createWebSearchRuntime, type WebSearchRuntime } from "./runtime.ts";
|
|
35
37
|
import {
|
|
36
38
|
webFetchRequestSchema,
|
|
37
39
|
webFetchResultSchema,
|
|
@@ -60,19 +62,29 @@ function parseToolInput<S extends z.ZodType>(schema: S, input: unknown): z.infer
|
|
|
60
62
|
export interface WebSearchToolOptions {
|
|
61
63
|
/** Override the tool id. */
|
|
62
64
|
id?: string;
|
|
65
|
+
/** Isolated runtime used by this standalone tool. Mutually exclusive with `config`. */
|
|
66
|
+
runtime?: WebSearchRuntime;
|
|
67
|
+
/** Config used to create an isolated direct-execution runtime. */
|
|
68
|
+
config?: WebSearchPluginConfig;
|
|
63
69
|
/**
|
|
64
|
-
* Approval gate for this tool, overriding the
|
|
70
|
+
* Approval gate for this tool, overriding the runtime's `approval`. `true`
|
|
65
71
|
* gates every call; a URL-pattern (or list) gates only matching calls;
|
|
66
72
|
* omit / `false` for no approval. See {@link ApprovalGate}.
|
|
67
73
|
*/
|
|
68
74
|
approval?: ApprovalGate | ApprovalPolicy;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
|
-
/** Resolve the
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
/** Resolve the isolated runtime captured by one standalone tool factory. */
|
|
78
|
+
function toolRuntime(opts: WebSearchToolOptions): WebSearchRuntime {
|
|
79
|
+
if (opts.runtime && opts.config) {
|
|
80
|
+
throw new TypeError("web-search tool options accept either runtime or config, not both");
|
|
81
|
+
}
|
|
82
|
+
return opts.runtime ?? createWebSearchRuntime(opts.config);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Resolve the effective gate: explicit tool option, else the runtime default. */
|
|
86
|
+
function effectiveGate(opts: WebSearchToolOptions, runtime: WebSearchRuntime): ApprovalPolicy {
|
|
87
|
+
return opts.approval === undefined ? runtime.config.approval : toApprovalPolicy(opts.approval);
|
|
76
88
|
}
|
|
77
89
|
|
|
78
90
|
/**
|
|
@@ -91,7 +103,8 @@ function effectiveGate(opts: WebSearchToolOptions): ApprovalPolicy {
|
|
|
91
103
|
* ```
|
|
92
104
|
*/
|
|
93
105
|
export function webSearchTool(opts: WebSearchToolOptions = {}) {
|
|
94
|
-
const
|
|
106
|
+
const runtime = toolRuntime(opts);
|
|
107
|
+
const gate = effectiveGate(opts, runtime);
|
|
95
108
|
return createTool({
|
|
96
109
|
id: opts.id ?? "web_search",
|
|
97
110
|
description: WEB_SEARCH_TOOL_DESCRIPTION,
|
|
@@ -101,9 +114,8 @@ export function webSearchTool(opts: WebSearchToolOptions = {}) {
|
|
|
101
114
|
// is treated as "always gate".
|
|
102
115
|
...(gate.mode === "none" ? {} : { requireApproval: () => true }),
|
|
103
116
|
execute: async (input) => {
|
|
104
|
-
const { config } = getWebSearchRuntime();
|
|
105
117
|
const request = parseToolInput(webSearchRequestSchema, input);
|
|
106
|
-
return runWebSearch(request,
|
|
118
|
+
return runWebSearch(request, runtime, await resolveWebSearchContext());
|
|
107
119
|
},
|
|
108
120
|
});
|
|
109
121
|
}
|
|
@@ -120,7 +132,8 @@ export function webSearchTool(opts: WebSearchToolOptions = {}) {
|
|
|
120
132
|
* ```
|
|
121
133
|
*/
|
|
122
134
|
export function webFetchTool(opts: WebSearchToolOptions = {}) {
|
|
123
|
-
const
|
|
135
|
+
const runtime = toolRuntime(opts);
|
|
136
|
+
const gate = effectiveGate(opts, runtime);
|
|
124
137
|
return createTool({
|
|
125
138
|
id: opts.id ?? "web_fetch",
|
|
126
139
|
description: WEB_FETCH_TOOL_DESCRIPTION,
|
|
@@ -136,8 +149,7 @@ export function webFetchTool(opts: WebSearchToolOptions = {}) {
|
|
|
136
149
|
approvalMatches(gate, [parseToolInput(webFetchRequestSchema, input).url]),
|
|
137
150
|
}),
|
|
138
151
|
execute: async (input) => {
|
|
139
|
-
|
|
140
|
-
return runWebFetch(parseToolInput(webFetchRequestSchema, input), config);
|
|
152
|
+
return runWebFetch(parseToolInput(webFetchRequestSchema, input), runtime);
|
|
141
153
|
},
|
|
142
154
|
});
|
|
143
155
|
}
|