@dbx-tools/appkit-web-search 0.3.21 → 0.3.22

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/package.json CHANGED
@@ -17,17 +17,17 @@
17
17
  "@mastra/core": "^1.47.0",
18
18
  "got-scraping": "^4.2.1",
19
19
  "zod": "^4.3.6",
20
- "@dbx-tools/shared-core": "0.3.21",
21
- "@dbx-tools/model": "0.3.21",
22
- "@dbx-tools/path": "0.3.21",
23
- "@dbx-tools/shared-model": "0.3.21"
20
+ "@dbx-tools/model": "0.3.22",
21
+ "@dbx-tools/path": "0.3.22",
22
+ "@dbx-tools/shared-core": "0.3.22",
23
+ "@dbx-tools/shared-model": "0.3.22"
24
24
  },
25
25
  "main": "index.ts",
26
26
  "license": "UNLICENSED",
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "version": "0.3.21",
30
+ "version": "0.3.22",
31
31
  "types": "index.ts",
32
32
  "type": "module",
33
33
  "exports": {
package/src/config.ts CHANGED
@@ -222,7 +222,7 @@ function resolvePositiveInt(value: number | undefined, envKey: string, fallback:
222
222
 
223
223
  /** Parse the `WEB_SEARCH_TOOLS` env var (JSON), else `{}`. Bad JSON is ignored. */
224
224
  function parseToolsEnv(): Record<string, unknown> {
225
- const raw = process.env["WEB_SEARCH_TOOLS"];
225
+ const raw = process.env.WEB_SEARCH_TOOLS;
226
226
  if (!raw) return {};
227
227
  try {
228
228
  const parsed = JSON.parse(raw) as unknown;
@@ -241,18 +241,23 @@ function parseToolsEnv(): Record<string, unknown> {
241
241
  export function resolveWebSearchConfig(
242
242
  config: WebSearchPluginConfig = {},
243
243
  ): ResolvedWebSearchConfig {
244
- const patterns = parseAllowedUrls(config.allowedUrls ?? process.env["WEB_SEARCH_ALLOWED_URLS"]);
245
- const model = config.model ?? process.env["WEB_SEARCH_MODEL"];
246
- const fallbacks = toStringList(config.modelFallbacks ?? process.env["WEB_SEARCH_MODEL_FALLBACKS"]);
247
- const fuzzyThresholdRaw = config.modelFuzzyThreshold ?? Number(process.env["WEB_SEARCH_FUZZY_THRESHOLD"]);
244
+ const patterns = parseAllowedUrls(config.allowedUrls ?? process.env.WEB_SEARCH_ALLOWED_URLS);
245
+ const model = config.model ?? process.env.WEB_SEARCH_MODEL;
246
+ const fallbacks = toStringList(config.modelFallbacks ?? process.env.WEB_SEARCH_MODEL_FALLBACKS);
247
+ const fuzzyThresholdRaw =
248
+ config.modelFuzzyThreshold ?? Number(process.env.WEB_SEARCH_FUZZY_THRESHOLD);
248
249
  return {
249
250
  ...(model ? { model } : {}),
250
251
  modelFallbacks: fallbacks.length > 0 ? fallbacks : DEFAULT_MODEL_FALLBACKS,
251
252
  webSearchTools: { ...parseToolsEnv(), ...(config.webSearchTools ?? {}) },
252
- fuzzy:
253
- config.modelFuzzyMatch ?? object.toBoolean(process.env["WEB_SEARCH_FUZZY"]) ?? true,
254
- fuzzyThreshold: Number.isFinite(fuzzyThresholdRaw) && fuzzyThresholdRaw ? Number(fuzzyThresholdRaw) : 0.4,
255
- maxCitations: resolvePositiveInt(config.maxCitations, "WEB_SEARCH_MAX_CITATIONS", DEFAULT_MAX_CITATIONS),
253
+ fuzzy: config.modelFuzzyMatch ?? object.toBoolean(process.env.WEB_SEARCH_FUZZY) ?? true,
254
+ fuzzyThreshold:
255
+ Number.isFinite(fuzzyThresholdRaw) && fuzzyThresholdRaw ? Number(fuzzyThresholdRaw) : 0.4,
256
+ maxCitations: resolvePositiveInt(
257
+ config.maxCitations,
258
+ "WEB_SEARCH_MAX_CITATIONS",
259
+ DEFAULT_MAX_CITATIONS,
260
+ ),
256
261
  fetchMaxLength: resolvePositiveInt(
257
262
  config.fetchMaxLength,
258
263
  "WEB_SEARCH_FETCH_MAX_LENGTH",
@@ -260,7 +265,7 @@ export function resolveWebSearchConfig(
260
265
  ),
261
266
  timeoutMs: resolvePositiveInt(config.timeoutMs, "WEB_SEARCH_TIMEOUT_MS", DEFAULT_TIMEOUT_MS),
262
267
  scrapeFallback:
263
- config.scrapeFallback ?? object.toBoolean(process.env["WEB_SEARCH_SCRAPE_FALLBACK"]) ?? true,
268
+ config.scrapeFallback ?? object.toBoolean(process.env.WEB_SEARCH_SCRAPE_FALLBACK) ?? true,
264
269
  allowList: toUrlAllowList(patterns),
265
270
  approval: config.approval ?? false,
266
271
  };
package/src/fetch.ts CHANGED
@@ -94,8 +94,7 @@ export async function runWebFetch(
94
94
  const body = typeof response.body === "string" ? response.body : String(response.body ?? "");
95
95
  const contentType = response.headers["content-type"];
96
96
  const isHtml = !contentType || /html|xml/i.test(contentType);
97
- const rawContent =
98
- request.format === "html" || !isHtml ? body : htmlToText(body);
97
+ const rawContent = request.format === "html" || !isHtml ? body : htmlToText(body);
99
98
  const { content, truncated } = truncate(rawContent, cap);
100
99
  const title = isHtml ? extractTitle(body) : undefined;
101
100
 
package/src/runtime.ts CHANGED
@@ -11,7 +11,11 @@
11
11
  * @module
12
12
  */
13
13
 
14
- import { resolveWebSearchConfig, type ResolvedWebSearchConfig, type WebSearchPluginConfig } from "./config";
14
+ import {
15
+ resolveWebSearchConfig,
16
+ type ResolvedWebSearchConfig,
17
+ type WebSearchPluginConfig,
18
+ } from "./config";
15
19
 
16
20
  /** The shared resolved config. */
17
21
  export interface WebSearchRuntime {
package/src/schema.ts CHANGED
@@ -58,9 +58,7 @@ export type WebSearchCitation = z.infer<typeof webSearchCitationSchema>;
58
58
  /** Schema for the `web_search` tool output. */
59
59
  export const webSearchResultSchema = z.object({
60
60
  query: z.string().describe("Echo of the query that was searched."),
61
- answer: z
62
- .string()
63
- .describe("The model's answer, synthesized from live web results."),
61
+ answer: z.string().describe("The model's answer, synthesized from live web results."),
64
62
  citations: z
65
63
  .array(webSearchCitationSchema)
66
64
  .describe(
package/src/scrape.ts CHANGED
@@ -104,7 +104,9 @@ export async function runScrapeSearch(
104
104
  const answer =
105
105
  citations.length > 0
106
106
  ? `Web results for "${request.query}" (no Databricks web-search model is deployed in this workspace, so these are unsynthesized search results - read the citations):\n\n` +
107
- citations.map((c, n) => `${n + 1}. ${c.title}${c.snippet ? ` - ${c.snippet}` : ""} (${c.url})`).join("\n")
107
+ citations
108
+ .map((c, n) => `${n + 1}. ${c.title}${c.snippet ? ` - ${c.snippet}` : ""} (${c.url})`)
109
+ .join("\n")
108
110
  : `No web results found for "${request.query}".`;
109
111
  logger.debug("scraped", {
110
112
  query: request.query,
package/src/search.ts CHANGED
@@ -21,8 +21,9 @@
21
21
  * @module
22
22
  */
23
23
 
24
- import { error, log } from "@dbx-tools/shared-core";
25
24
  import { resolve, serving } from "@dbx-tools/model";
25
+ import { error, log } from "@dbx-tools/shared-core";
26
+ import { openaiResponses } from "@dbx-tools/shared-model";
26
27
  import type { ResolvedWebSearchConfig } from "./config";
27
28
  import { detectWebSearchProvider, supportsWebSearch, webSearchToolSpec } from "./provider";
28
29
  import type { WebSearchCitation, WebSearchRequest, WebSearchResult } from "./schema";
@@ -118,36 +119,16 @@ function str(v: unknown): string {
118
119
  }
119
120
 
120
121
  /**
121
- * Extract answer text + citations from an OpenAI Responses API payload. The
122
- * Responses API returns an `output` array of items; message items carry
123
- * `content` parts with `text` and optional `annotations` (url_citation).
124
- * `output_text` is the convenience aggregate when present.
122
+ * Extract answer text + citations from an OpenAI Responses API payload, via the
123
+ * shared reader in `@dbx-tools/shared-model` (the same module the model-proxy
124
+ * uses to translate the Responses wire format in the other direction).
125
125
  */
126
126
  function fromResponsesPayload(payload: Record<string, unknown>): {
127
127
  answer: string;
128
128
  citations: WebSearchCitation[];
129
129
  } {
130
- const citations: WebSearchCitation[] = [];
131
- const texts: string[] = [];
132
- const output = Array.isArray(payload["output"]) ? (payload["output"] as unknown[]) : [];
133
- for (const item of output) {
134
- const content = (item as { content?: unknown }).content;
135
- if (!Array.isArray(content)) continue;
136
- for (const part of content) {
137
- const p = part as { text?: unknown; annotations?: unknown };
138
- const text = str(p.text);
139
- if (text) texts.push(text);
140
- if (Array.isArray(p.annotations)) {
141
- for (const ann of p.annotations) {
142
- const a = ann as { url?: unknown; title?: unknown };
143
- const url = str(a.url);
144
- if (url) citations.push({ url, ...(str(a.title) ? { title: str(a.title) } : {}) });
145
- }
146
- }
147
- }
148
- }
149
- const answer = str(payload["output_text"]) || texts.join("\n").trim();
150
- return { answer, citations };
130
+ const { text, citations } = openaiResponses.readResponsesOutput(payload);
131
+ return { answer: text, citations };
151
132
  }
152
133
 
153
134
  /**
@@ -160,26 +141,26 @@ function fromChatPayload(payload: Record<string, unknown>): {
160
141
  answer: string;
161
142
  citations: WebSearchCitation[];
162
143
  } {
163
- const choices = Array.isArray(payload["choices"]) ? (payload["choices"] as unknown[]) : [];
144
+ const choices = Array.isArray(payload.choices) ? (payload.choices as unknown[]) : [];
164
145
  const message = (choices[0] as { message?: Record<string, unknown> })?.message ?? {};
165
- const answer = str(message["content"]);
146
+ const answer = str(message.content);
166
147
  const citations: WebSearchCitation[] = [];
167
148
  // Best-effort grounding extraction: walk any nested object for {uri|url,title}.
168
149
  const seen = new Set<string>();
169
150
  const visit = (v: unknown, depth: number): void => {
170
151
  if (depth > 6 || v === null || typeof v !== "object") return;
171
152
  const o = v as Record<string, unknown>;
172
- const url = str(o["url"]) || str(o["uri"]);
153
+ const url = str(o.url) || str(o.uri);
173
154
  if (url && !seen.has(url)) {
174
155
  seen.add(url);
175
- citations.push({ url, ...(str(o["title"]) ? { title: str(o["title"]) } : {}) });
156
+ citations.push({ url, ...(str(o.title) ? { title: str(o.title) } : {}) });
176
157
  }
177
158
  for (const val of Object.values(o)) {
178
159
  if (Array.isArray(val)) val.forEach((x) => visit(x, depth + 1));
179
160
  else if (val && typeof val === "object") visit(val, depth + 1);
180
161
  }
181
162
  };
182
- visit(message["grounding_metadata"] ?? message["groundingMetadata"], 0);
163
+ visit(message.grounding_metadata ?? message.groundingMetadata, 0);
183
164
  return { answer, citations };
184
165
  }
185
166