@dbx-tools/appkit-web-search 0.3.37 → 0.3.40
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 +10 -6
- package/test/allowlist.test.ts +0 -216
- package/test/tsconfig.json +0 -14
- package/tsconfig.json +0 -41
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"repository": {
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "git+https://github.com/reggie-db/dbx-tools.git",
|
|
6
|
-
"directory": "
|
|
6
|
+
"directory": "packages/node/appkit-web-search"
|
|
7
7
|
},
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@types/express": "^5.0.5",
|
|
@@ -17,23 +17,27 @@
|
|
|
17
17
|
"@mastra/core": "^1.47.0",
|
|
18
18
|
"got-scraping": "^4.2.1",
|
|
19
19
|
"zod": "4.3.6",
|
|
20
|
-
"@dbx-tools/model": "0.3.
|
|
21
|
-
"@dbx-tools/
|
|
22
|
-
"@dbx-tools/
|
|
23
|
-
"@dbx-tools/shared-model": "0.3.
|
|
20
|
+
"@dbx-tools/model": "0.3.40",
|
|
21
|
+
"@dbx-tools/shared-core": "0.3.40",
|
|
22
|
+
"@dbx-tools/path": "0.3.40",
|
|
23
|
+
"@dbx-tools/shared-model": "0.3.40"
|
|
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.40",
|
|
31
31
|
"types": "index.ts",
|
|
32
32
|
"type": "module",
|
|
33
33
|
"exports": {
|
|
34
34
|
".": "./index.ts",
|
|
35
35
|
"./package.json": "./package.json"
|
|
36
36
|
},
|
|
37
|
+
"files": [
|
|
38
|
+
"index.ts",
|
|
39
|
+
"src"
|
|
40
|
+
],
|
|
37
41
|
"dbxToolsConfig": {
|
|
38
42
|
"tags": [
|
|
39
43
|
"node"
|
package/test/allowlist.test.ts
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
assertUrlAllowed,
|
|
5
|
-
normalizeUrlPattern,
|
|
6
|
-
parseAllowedUrls,
|
|
7
|
-
toUrlAllowList,
|
|
8
|
-
} from "../src/allowlist";
|
|
9
|
-
import { approvalMatches, resolveWebSearchConfig, toApprovalPolicy } from "../src/config";
|
|
10
|
-
import { htmlToText } from "../src/html-text";
|
|
11
|
-
import {
|
|
12
|
-
detectWebSearchProvider,
|
|
13
|
-
supportsWebSearch,
|
|
14
|
-
webSearchToolSpec,
|
|
15
|
-
WEB_SEARCH_PROVIDERS,
|
|
16
|
-
} from "../src/provider";
|
|
17
|
-
|
|
18
|
-
describe("web-search allow-list", () => {
|
|
19
|
-
it("strips the scheme from entries, leaving host / path verbatim", () => {
|
|
20
|
-
assert.equal(normalizeUrlPattern("https://*.databricks.com"), "*.databricks.com");
|
|
21
|
-
assert.equal(normalizeUrlPattern("docs.example.com/api/**"), "docs.example.com/api/**");
|
|
22
|
-
assert.equal(normalizeUrlPattern("databricks.com"), "databricks.com");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("parses CSV and array forms, trimming + de-duping", () => {
|
|
26
|
-
assert.deepEqual(parseAllowedUrls("https://databricks.com, docs.example.com"), [
|
|
27
|
-
"databricks.com",
|
|
28
|
-
"docs.example.com",
|
|
29
|
-
]);
|
|
30
|
-
assert.deepEqual(parseAllowedUrls(["databricks.com", "databricks.com"]), ["databricks.com"]);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("permits everything when unconfigured", () => {
|
|
34
|
-
const list = toUrlAllowList(parseAllowedUrls(undefined));
|
|
35
|
-
assert.equal(list.restricted, false);
|
|
36
|
-
assert.equal(list.allows("https://anything.example.com/x"), true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("a bare host permits the host and its subdomains, blocks others", () => {
|
|
40
|
-
const list = toUrlAllowList(parseAllowedUrls(["databricks.com", "*.trusted.io"]));
|
|
41
|
-
assert.equal(list.restricted, true);
|
|
42
|
-
assert.equal(list.allows("https://databricks.com/"), true);
|
|
43
|
-
assert.equal(list.allows("https://docs.databricks.com/aws/en/index.html"), true);
|
|
44
|
-
assert.equal(list.allows("https://api.trusted.io/v1"), true);
|
|
45
|
-
assert.equal(list.allows("https://evil.example.com/"), false);
|
|
46
|
-
assert.equal(list.allows("https://notdatabricks.com/"), false);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("a path entry constrains the pathname", () => {
|
|
50
|
-
const list = toUrlAllowList(parseAllowedUrls(["docs.example.com/api/**"]));
|
|
51
|
-
assert.equal(list.allows("https://docs.example.com/api/v1/x"), true);
|
|
52
|
-
assert.equal(list.allows("https://docs.example.com/blog/x"), false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("assertUrlAllowed throws for a disallowed URL, passes an allowed one", () => {
|
|
56
|
-
const list = toUrlAllowList(parseAllowedUrls(["databricks.com"]));
|
|
57
|
-
assert.throws(() => assertUrlAllowed("https://evil.example.com/", list), /allow-list/);
|
|
58
|
-
assert.doesNotThrow(() => assertUrlAllowed("https://databricks.com/", list));
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe("web-search provider detection", () => {
|
|
63
|
-
it("maps GPT ids to openai and Gemini ids to gemini", () => {
|
|
64
|
-
assert.equal(detectWebSearchProvider("databricks-gpt-5"), "openai");
|
|
65
|
-
assert.equal(detectWebSearchProvider("databricks-gpt-5-mini"), "openai");
|
|
66
|
-
assert.equal(detectWebSearchProvider("databricks-gemini-3-pro"), "gemini");
|
|
67
|
-
assert.equal(detectWebSearchProvider("databricks-gemini-2-5-flash"), "gemini");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("treats gpt-oss and non-web families as unsupported", () => {
|
|
71
|
-
assert.equal(detectWebSearchProvider("databricks-gpt-oss-120b"), null);
|
|
72
|
-
assert.equal(detectWebSearchProvider("databricks-claude-sonnet-4-6"), null);
|
|
73
|
-
assert.equal(detectWebSearchProvider("databricks-llama-3-70b"), null);
|
|
74
|
-
assert.equal(supportsWebSearch("databricks-claude-sonnet-4-6"), false);
|
|
75
|
-
assert.equal(supportsWebSearch("databricks-gpt-5"), true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("uses the built-in tool spec per provider", () => {
|
|
79
|
-
assert.deepEqual(webSearchToolSpec("openai").tool, { type: "web_search" });
|
|
80
|
-
assert.equal(webSearchToolSpec("openai").api, "responses");
|
|
81
|
-
assert.deepEqual(webSearchToolSpec("gemini").tool, { google_search: {} });
|
|
82
|
-
assert.equal(webSearchToolSpec("gemini").api, "chat");
|
|
83
|
-
assert.deepEqual(WEB_SEARCH_PROVIDERS.openai.tool, { type: "web_search" });
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("merges an operator override over the built-in map", () => {
|
|
87
|
-
const overrides = { gemini: { tool: { google_search_retrieval: {} } } };
|
|
88
|
-
const spec = webSearchToolSpec("gemini", overrides);
|
|
89
|
-
assert.deepEqual(spec.tool, { google_search_retrieval: {} });
|
|
90
|
-
// api falls through to the built-in when not overridden
|
|
91
|
-
assert.equal(spec.api, "chat");
|
|
92
|
-
// other providers keep their defaults
|
|
93
|
-
assert.deepEqual(webSearchToolSpec("openai", overrides).tool, { type: "web_search" });
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe("web-search config", () => {
|
|
98
|
-
it("defaults model fallbacks to Gemini-then-GPT and approval to none", () => {
|
|
99
|
-
const c = resolveWebSearchConfig();
|
|
100
|
-
assert.deepEqual(c.approval, { mode: "none" });
|
|
101
|
-
assert.equal(c.model, undefined);
|
|
102
|
-
assert.equal(c.modelSource, "none");
|
|
103
|
-
assert.match(c.modelFallbacks[0]!, /gemini/);
|
|
104
|
-
assert.ok(c.modelFallbacks.some((m) => m.includes("gpt")));
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("merges webSearchTools override into resolved config", () => {
|
|
108
|
-
const c = resolveWebSearchConfig({ webSearchTools: { gemini: { tool: { x: {} } } } });
|
|
109
|
-
assert.deepEqual((c.webSearchTools as { gemini: unknown }).gemini, { tool: { x: {} } });
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("enables the scrape fallback by default, honors an explicit off", () => {
|
|
113
|
-
assert.equal(resolveWebSearchConfig().scrapeFallback, true);
|
|
114
|
-
assert.equal(resolveWebSearchConfig({ scrapeFallback: false }).scrapeFallback, false);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("records where a pinned endpoint came from", () => {
|
|
118
|
-
assert.equal(resolveWebSearchConfig({ model: "gemini" }).modelSource, "config");
|
|
119
|
-
process.env.DATABRICKS_SERVING_ENDPOINT_NAME = "databricks-claude-sonnet-4-6";
|
|
120
|
-
try {
|
|
121
|
-
const c = resolveWebSearchConfig();
|
|
122
|
-
assert.equal(c.model, "databricks-claude-sonnet-4-6");
|
|
123
|
-
assert.equal(c.modelSource, "DATABRICKS_SERVING_ENDPOINT_NAME");
|
|
124
|
-
// The dedicated override wins over the shared resource binding.
|
|
125
|
-
assert.equal(resolveWebSearchConfig({ model: "gemini" }).modelSource, "config");
|
|
126
|
-
} finally {
|
|
127
|
-
delete process.env.DATABRICKS_SERVING_ENDPOINT_NAME;
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("names the URL policy, defaulting from whether an allow-list was given", () => {
|
|
132
|
-
assert.equal(resolveWebSearchConfig().urlPolicy, "unrestricted");
|
|
133
|
-
assert.equal(resolveWebSearchConfig().allowList.restricted, false);
|
|
134
|
-
const restricted = resolveWebSearchConfig({ allowedUrls: ["databricks.com"] });
|
|
135
|
-
assert.equal(restricted.urlPolicy, "allowlist");
|
|
136
|
-
assert.equal(restricted.allowList.restricted, true);
|
|
137
|
-
assert.equal(
|
|
138
|
-
resolveWebSearchConfig({ urlPolicy: "allowlist", allowedUrls: ["databricks.com"] }).urlPolicy,
|
|
139
|
-
"allowlist",
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("fails loudly on a URL policy that contradicts its allow-list", () => {
|
|
144
|
-
assert.throws(() => resolveWebSearchConfig({ urlPolicy: "allowlist" }), /allowedUrls/);
|
|
145
|
-
assert.throws(
|
|
146
|
-
() => resolveWebSearchConfig({ urlPolicy: "unrestricted", allowedUrls: ["databricks.com"] }),
|
|
147
|
-
/allowedUrls/,
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("fails loudly on a WEB_SEARCH_TOOLS value that is not a JSON object", () => {
|
|
152
|
-
process.env.WEB_SEARCH_TOOLS = "not-json";
|
|
153
|
-
try {
|
|
154
|
-
assert.throws(() => resolveWebSearchConfig(), /WEB_SEARCH_TOOLS/);
|
|
155
|
-
} finally {
|
|
156
|
-
delete process.env.WEB_SEARCH_TOOLS;
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("rejects a webSearchTools override that is not a tool spec", () => {
|
|
161
|
-
assert.throws(() => webSearchToolSpec("gemini", { gemini: "nope" }), /webSearchTools.gemini/);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe("web-search approval policy", () => {
|
|
166
|
-
it("normalizes every accepted spelling", () => {
|
|
167
|
-
assert.deepEqual(toApprovalPolicy(undefined), { mode: "none" });
|
|
168
|
-
assert.deepEqual(toApprovalPolicy(false), { mode: "none" });
|
|
169
|
-
assert.deepEqual(toApprovalPolicy(true), { mode: "always" });
|
|
170
|
-
assert.deepEqual(toApprovalPolicy("*.internal.example.com"), {
|
|
171
|
-
mode: "urls",
|
|
172
|
-
patterns: ["*.internal.example.com"],
|
|
173
|
-
});
|
|
174
|
-
assert.deepEqual(toApprovalPolicy({ mode: "always" }), { mode: "always" });
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("collapses an empty pattern list to no approval", () => {
|
|
178
|
-
assert.deepEqual(toApprovalPolicy(" "), { mode: "none" });
|
|
179
|
-
assert.deepEqual(toApprovalPolicy({ mode: "urls", patterns: [] }), { mode: "none" });
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe("web-search approval gate", () => {
|
|
184
|
-
it("boolean gates pass through", () => {
|
|
185
|
-
assert.equal(approvalMatches(true, ["https://x.com"]), true);
|
|
186
|
-
assert.equal(approvalMatches(false, ["https://x.com"]), false);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("pattern gate matches only candidate URLs it covers", () => {
|
|
190
|
-
assert.equal(
|
|
191
|
-
approvalMatches("*.internal.example.com", ["https://api.internal.example.com/x"]),
|
|
192
|
-
true,
|
|
193
|
-
);
|
|
194
|
-
assert.equal(
|
|
195
|
-
approvalMatches("*.internal.example.com", ["https://public.example.com/x"]),
|
|
196
|
-
false,
|
|
197
|
-
);
|
|
198
|
-
assert.equal(approvalMatches(["a.com", "b.com"], ["https://b.com/y"]), true);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe("web-search html-to-text", () => {
|
|
203
|
-
it("strips script/style and unwraps tags", () => {
|
|
204
|
-
const html =
|
|
205
|
-
"<html><head><title>Hi</title><style>.x{}</style></head><body><p>One</p><script>bad()</script><p>Two</p></body></html>";
|
|
206
|
-
const text = htmlToText(html);
|
|
207
|
-
assert.match(text, /One/);
|
|
208
|
-
assert.match(text, /Two/);
|
|
209
|
-
assert.doesNotMatch(text, /bad\(\)/);
|
|
210
|
-
assert.doesNotMatch(text, /\.x\{\}/);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("decodes common entities", () => {
|
|
214
|
-
assert.equal(htmlToText("<p>a & b <c></p>"), "a & b <c>");
|
|
215
|
-
});
|
|
216
|
-
});
|
package/test/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
|
|
2
|
-
{
|
|
3
|
-
"extends": "../tsconfig.json",
|
|
4
|
-
"compilerOptions": {
|
|
5
|
-
"noEmit": true,
|
|
6
|
-
"rootDir": ".."
|
|
7
|
-
},
|
|
8
|
-
"include": [
|
|
9
|
-
"**/*.ts"
|
|
10
|
-
],
|
|
11
|
-
"exclude": [
|
|
12
|
-
"node_modules"
|
|
13
|
-
]
|
|
14
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
|
|
2
|
-
{
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
|
-
"outDir": "lib",
|
|
6
|
-
"alwaysStrict": true,
|
|
7
|
-
"declaration": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"inlineSourceMap": true,
|
|
11
|
-
"inlineSources": true,
|
|
12
|
-
"lib": [
|
|
13
|
-
"ES2022"
|
|
14
|
-
],
|
|
15
|
-
"module": "ESNext",
|
|
16
|
-
"noEmitOnError": false,
|
|
17
|
-
"noFallthroughCasesInSwitch": true,
|
|
18
|
-
"noImplicitAny": true,
|
|
19
|
-
"noImplicitReturns": true,
|
|
20
|
-
"noImplicitThis": true,
|
|
21
|
-
"noUnusedLocals": true,
|
|
22
|
-
"noUnusedParameters": true,
|
|
23
|
-
"resolveJsonModule": true,
|
|
24
|
-
"strict": true,
|
|
25
|
-
"strictNullChecks": true,
|
|
26
|
-
"strictPropertyInitialization": true,
|
|
27
|
-
"stripInternal": true,
|
|
28
|
-
"target": "ES2022",
|
|
29
|
-
"types": [
|
|
30
|
-
"node"
|
|
31
|
-
],
|
|
32
|
-
"moduleResolution": "bundler",
|
|
33
|
-
"skipLibCheck": true
|
|
34
|
-
},
|
|
35
|
-
"include": [
|
|
36
|
-
"src/**/*.ts"
|
|
37
|
-
],
|
|
38
|
-
"exclude": [
|
|
39
|
-
"node_modules"
|
|
40
|
-
]
|
|
41
|
-
}
|