dsh-web-search-enhanced 0.2.2 → 0.2.3
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 +9 -2
- package/lib/client.cjs +94 -57
- package/lib/client.cjs.map +1 -1
- package/lib/index.js +174 -37
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -150,10 +150,23 @@ function decodeJsonEscapes(value) {
|
|
|
150
150
|
}
|
|
151
151
|
//#endregion
|
|
152
152
|
//#region ../core/src/host/adapter.ts
|
|
153
|
+
/** The default: generic wording with the generic explanation, as today. */
|
|
154
|
+
const statusVerdict = (response) => ({
|
|
155
|
+
kind: "status",
|
|
156
|
+
detail: response.explanation
|
|
157
|
+
});
|
|
153
158
|
function mapWire(wire, map) {
|
|
159
|
+
const parseFailure = wire.parseFailure === void 0 ? void 0 : wire.parseFailure.bind(wire);
|
|
154
160
|
const mapped = {
|
|
155
161
|
request: (input, endpoint, auth) => wire.request(input, endpoint, auth),
|
|
156
|
-
parse: (payload, input) => map(wire.parse(payload, input))
|
|
162
|
+
parse: (payload, input) => map(wire.parse(payload, input)),
|
|
163
|
+
...parseFailure === void 0 ? {} : { parseFailure(response, input) {
|
|
164
|
+
const verdict = parseFailure(response, input);
|
|
165
|
+
return verdict.kind === "status" ? verdict : {
|
|
166
|
+
kind: "outcome",
|
|
167
|
+
outcome: map(verdict.outcome)
|
|
168
|
+
};
|
|
169
|
+
} }
|
|
157
170
|
};
|
|
158
171
|
const route = wire.keyless;
|
|
159
172
|
if (route === void 0) return mapped;
|
|
@@ -216,8 +229,7 @@ function runAdapter(io, wire, target, input, signal) {
|
|
|
216
229
|
}, deadline, upstream);
|
|
217
230
|
return route.parse(result, input);
|
|
218
231
|
}
|
|
219
|
-
|
|
220
|
-
return wire.parse(await io.transport.requestJson(request, deadline, upstream), input);
|
|
232
|
+
return await runRest(io, wire, wire.request(input, target.endpoint, { kind: "keyless" }), input, deadline, upstream);
|
|
221
233
|
}
|
|
222
234
|
const stored = storedApiKey(await adapterCredential(io, target.credentialRef, deadline));
|
|
223
235
|
if (stored.kind === "missing") throw io.errors.error("WEB_PROVIDER_CREDENTIAL_MISSING", io.credentialMissing(target.credentialRef));
|
|
@@ -229,17 +241,23 @@ function runAdapter(io, wire, target, input, signal) {
|
|
|
229
241
|
onDiagnostic: io.onDiagnostic,
|
|
230
242
|
matcher
|
|
231
243
|
};
|
|
232
|
-
|
|
244
|
+
return await runRest(io, wire, wire.request(input, target.endpoint, {
|
|
233
245
|
kind: "api-key",
|
|
234
246
|
key: stored.key
|
|
235
|
-
});
|
|
236
|
-
return redactJson(wire.parse(await io.transport.requestJson(request, deadline, upstream), input), matcher);
|
|
247
|
+
}), input, deadline, upstream);
|
|
237
248
|
} catch (error) {
|
|
238
249
|
if (error instanceof WebError) throw error;
|
|
239
250
|
throw redactDiagnostic(error, matcher === void 0 ? secretMatcher([]) : matcher);
|
|
240
251
|
}
|
|
241
252
|
});
|
|
242
253
|
}
|
|
254
|
+
async function runRest(io, wire, request, input, signal, upstream) {
|
|
255
|
+
const exchange = await io.transport.exchangeJson(request, signal, upstream);
|
|
256
|
+
if (exchange.kind === "ok") return redactJson(wire.parse(exchange.payload, input), upstream.matcher);
|
|
257
|
+
const verdict = wire.parseFailure === void 0 ? statusVerdict(exchange.response) : wire.parseFailure(exchange.response, input);
|
|
258
|
+
if (verdict.kind === "status") throw io.transport.statusFailure(exchange.response.status, upstream, verdict.detail);
|
|
259
|
+
return verdict.outcome;
|
|
260
|
+
}
|
|
243
261
|
async function adapterCredential(io, ref, signal) {
|
|
244
262
|
try {
|
|
245
263
|
return await io.errors.abortable(io.credential(ref), signal);
|
|
@@ -304,9 +322,8 @@ function createTransport(errors, { timeoutMs, maxResponseBytes, messages }) {
|
|
|
304
322
|
clearTimeout(timer);
|
|
305
323
|
}
|
|
306
324
|
}
|
|
307
|
-
async function
|
|
325
|
+
async function exchangeResponse(request, signal, upstream, matcher) {
|
|
308
326
|
throwIfAborted(signal);
|
|
309
|
-
const matcher = upstream.matcher === void 0 ? secretMatcher([]) : upstream.matcher;
|
|
310
327
|
let response;
|
|
311
328
|
try {
|
|
312
329
|
response = await fetch(request.url, {
|
|
@@ -321,14 +338,25 @@ function createTransport(errors, { timeoutMs, maxResponseBytes, messages }) {
|
|
|
321
338
|
throwIfAborted(signal);
|
|
322
339
|
throw capabilityError("WEB_PROVIDER_ERROR", messages.unreachable(upstream.label));
|
|
323
340
|
}
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
341
|
+
if (response.ok) return {
|
|
342
|
+
kind: "ok",
|
|
343
|
+
response
|
|
344
|
+
};
|
|
345
|
+
const body = await readFailureText(response, signal, upstream, matcher);
|
|
346
|
+
return {
|
|
347
|
+
kind: "failure",
|
|
348
|
+
response: failureResponse(response.status, body.text, matcher, body.truncated)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
async function send(request, signal, upstream) {
|
|
352
|
+
const exchange = await exchangeResponse(request, signal, upstream, upstream.matcher === void 0 ? secretMatcher([]) : upstream.matcher);
|
|
353
|
+
if (exchange.kind === "failure") throw statusFailure(exchange.response.status, upstream, exchange.response.explanation);
|
|
354
|
+
return exchange.response;
|
|
355
|
+
}
|
|
356
|
+
function statusFailure(status, upstream, detail) {
|
|
357
|
+
const failure = statusError(status, upstream);
|
|
358
|
+
if (detail === void 0) return failure;
|
|
359
|
+
return new WebError(failure.message + upstreamSuffix(upstream.label, detail), failure.code);
|
|
332
360
|
}
|
|
333
361
|
function statusError(status, { label, access }) {
|
|
334
362
|
if (status === 401 || status === 403) {
|
|
@@ -448,12 +476,19 @@ function createTransport(errors, { timeoutMs, maxResponseBytes, messages }) {
|
|
|
448
476
|
}
|
|
449
477
|
return payload;
|
|
450
478
|
}
|
|
451
|
-
async function
|
|
479
|
+
async function exchangeJson(request, signal, upstream) {
|
|
452
480
|
const matcher = upstream.matcher === void 0 ? secretMatcher([]) : upstream.matcher;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
481
|
+
const exchange = await exchangeResponse(request, signal, upstream, matcher);
|
|
482
|
+
if (exchange.kind === "failure") return exchange;
|
|
483
|
+
return {
|
|
484
|
+
kind: "ok",
|
|
485
|
+
payload: redactJson(await readJson(exchange.response, signal, upstream.onDiagnostic, matcher), matcher)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
async function requestJson(request, signal, upstream) {
|
|
489
|
+
const exchange = await exchangeJson(request, signal, upstream);
|
|
490
|
+
if (exchange.kind === "failure") throw statusFailure(exchange.response.status, upstream, exchange.response.explanation);
|
|
491
|
+
return exchange.payload;
|
|
457
492
|
}
|
|
458
493
|
function invalidResponse(detail) {
|
|
459
494
|
return capabilityError("WEB_PROVIDER_ERROR", messages.invalidResponse(detail));
|
|
@@ -467,6 +502,8 @@ function createTransport(errors, { timeoutMs, maxResponseBytes, messages }) {
|
|
|
467
502
|
readText,
|
|
468
503
|
readJson,
|
|
469
504
|
requestJson,
|
|
505
|
+
exchangeJson,
|
|
506
|
+
statusFailure,
|
|
470
507
|
invalidResponse
|
|
471
508
|
};
|
|
472
509
|
}
|
|
@@ -479,26 +516,55 @@ function upstreamText(value) {
|
|
|
479
516
|
const boundary = last >= 55296 && last <= 56319 ? 1998 : 1999;
|
|
480
517
|
return text.slice(0, boundary) + "…";
|
|
481
518
|
}
|
|
519
|
+
function screenedText(value) {
|
|
520
|
+
return value.trim() === "" ? void 0 : {
|
|
521
|
+
kind: "text",
|
|
522
|
+
text: value
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/** Text from a successful body, already redacted by transport: uncut, never withheld. */
|
|
526
|
+
function bodyDetail(value) {
|
|
527
|
+
return typeof value === "string" ? screenedText(value) : void 0;
|
|
528
|
+
}
|
|
482
529
|
/** Sensitive raw explanations are withheld, not precisely edited. */
|
|
483
530
|
function upstreamDetail(value, matcher, truncated = false) {
|
|
484
531
|
if (containsSecret(value, matcher)) return { kind: "withheld" };
|
|
485
|
-
|
|
486
|
-
return text === void 0 ? void 0 : {
|
|
487
|
-
kind: "text",
|
|
488
|
-
text
|
|
489
|
-
};
|
|
532
|
+
return screenedText(truncated ? stripPrefix(value, matcher) : value);
|
|
490
533
|
}
|
|
491
534
|
function upstreamSuffix(label, detail) {
|
|
492
535
|
if (detail === void 0) return "";
|
|
493
|
-
if (
|
|
494
|
-
|
|
536
|
+
if ("text" in detail) {
|
|
537
|
+
const text = upstreamText(detail.text);
|
|
538
|
+
return text === void 0 ? "" : "\n\n" + label + " responded (untrusted): " + text;
|
|
539
|
+
}
|
|
540
|
+
return "\n\n" + label + "'s response was withheld because it contained this request's credential.";
|
|
495
541
|
}
|
|
496
542
|
function stripPrefix(text, matcher) {
|
|
497
543
|
for (let length = Math.min(7, text.length); length > 0; length--) if (matcher.prefixes.has(text.slice(-length))) return text.slice(0, -length);
|
|
498
544
|
return text;
|
|
499
545
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
546
|
+
function failureResponse(status, body, matcher, truncated) {
|
|
547
|
+
const sensitive = containsSecret(body, matcher);
|
|
548
|
+
return {
|
|
549
|
+
status,
|
|
550
|
+
json: truncated ? void 0 : redactJson(failureJson(body), matcher),
|
|
551
|
+
explanation: failureExplanation(body, matcher, truncated, sensitive),
|
|
552
|
+
explain(value) {
|
|
553
|
+
if (sensitive) return { kind: "withheld" };
|
|
554
|
+
return typeof value === "string" ? upstreamDetail(value, matcher) : void 0;
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function failureJson(body) {
|
|
559
|
+
try {
|
|
560
|
+
return JSON.parse(body);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function failureExplanation(body, matcher, truncated, sensitive) {
|
|
567
|
+
if (sensitive) return { kind: "withheld" };
|
|
502
568
|
const text = truncated ? stripPrefix(body, matcher) : body;
|
|
503
569
|
let payload;
|
|
504
570
|
try {
|
|
@@ -16617,7 +16683,7 @@ function envelope(payload) {
|
|
|
16617
16683
|
payload.message
|
|
16618
16684
|
]) {
|
|
16619
16685
|
if (typeof candidate !== "string") continue;
|
|
16620
|
-
detail =
|
|
16686
|
+
detail = bodyDetail(candidate);
|
|
16621
16687
|
if (detail !== void 0) break;
|
|
16622
16688
|
}
|
|
16623
16689
|
return {
|
|
@@ -16935,6 +17001,54 @@ const tinyfishSearch = {
|
|
|
16935
17001
|
}
|
|
16936
17002
|
};
|
|
16937
17003
|
//#endregion
|
|
17004
|
+
//#region ../core/src/host/providers/anysearch.ts
|
|
17005
|
+
/** AnySearch REST search and extract. The catalog offers personal keys only; there is no anonymous route here. */
|
|
17006
|
+
/** 402 means the quota is spent; its body is ignored (for anonymous callers it carries a generated account). */
|
|
17007
|
+
function quotaVerdict(response) {
|
|
17008
|
+
return response.status === 402 ? {
|
|
17009
|
+
kind: "status",
|
|
17010
|
+
detail: void 0
|
|
17011
|
+
} : void 0;
|
|
17012
|
+
}
|
|
17013
|
+
const anysearchSearch = {
|
|
17014
|
+
request(input, endpoint, auth) {
|
|
17015
|
+
return postJson(endpoint, bearer(auth), {
|
|
17016
|
+
...input.options,
|
|
17017
|
+
query: input.query,
|
|
17018
|
+
...count("max_results", input.maxResults, 10)
|
|
17019
|
+
});
|
|
17020
|
+
},
|
|
17021
|
+
parse(payload) {
|
|
17022
|
+
if (!isRecord(payload)) return {
|
|
17023
|
+
kind: "failure",
|
|
17024
|
+
failure: "invalid",
|
|
17025
|
+
reason: "not-object"
|
|
17026
|
+
};
|
|
17027
|
+
if (typeof payload.code !== "number") return {
|
|
17028
|
+
kind: "failure",
|
|
17029
|
+
failure: "invalid",
|
|
17030
|
+
reason: "code-number"
|
|
17031
|
+
};
|
|
17032
|
+
if (payload.code !== 0) return {
|
|
17033
|
+
kind: "failure",
|
|
17034
|
+
failure: "api",
|
|
17035
|
+
detail: bodyDetail(payload.message)
|
|
17036
|
+
};
|
|
17037
|
+
const rows = isRecord(payload.data) ? payload.data.results : void 0;
|
|
17038
|
+
if (!Array.isArray(rows)) return {
|
|
17039
|
+
kind: "failure",
|
|
17040
|
+
failure: "invalid",
|
|
17041
|
+
reason: "data-results-array"
|
|
17042
|
+
};
|
|
17043
|
+
return mapRows(rows, (row) => ({
|
|
17044
|
+
url: row.url,
|
|
17045
|
+
title: row.title,
|
|
17046
|
+
snippet: text$1(row.snippet) ?? row.content
|
|
17047
|
+
}));
|
|
17048
|
+
},
|
|
17049
|
+
parseFailure: (response) => quotaVerdict(response) ?? statusVerdict(response)
|
|
17050
|
+
};
|
|
17051
|
+
//#endregion
|
|
16938
17052
|
//#region ../core/src/host/providers/openalex.ts
|
|
16939
17053
|
/** OpenAlex works search, scholarly field mapping and bounded inverted abstracts. */
|
|
16940
17054
|
/** OpenAlex asks anonymous clients to identify themselves for the polite pool. */
|
|
@@ -17431,7 +17545,7 @@ const { withDeadline, readJson, requestJson, invalidResponse } = transport;
|
|
|
17431
17545
|
const mcp = createMcpClient(transport, searchErrors, {
|
|
17432
17546
|
clientInfo: {
|
|
17433
17547
|
name: SEARCH.packageName,
|
|
17434
|
-
version: "0.2.
|
|
17548
|
+
version: "0.2.3"
|
|
17435
17549
|
},
|
|
17436
17550
|
messages: {
|
|
17437
17551
|
toolFailed: (label, detail) => label + " could not complete the search; retry later or choose another search connection" + upstreamSuffix(label, detail),
|
|
@@ -17780,6 +17894,30 @@ const CATALOG = {
|
|
|
17780
17894
|
}
|
|
17781
17895
|
}
|
|
17782
17896
|
},
|
|
17897
|
+
anysearch: {
|
|
17898
|
+
label: "AnySearch",
|
|
17899
|
+
endpoint: "https://api.anysearch.com/v1/search",
|
|
17900
|
+
access: {
|
|
17901
|
+
keyless: false,
|
|
17902
|
+
apiKey: { credentialRef: "ANYSEARCH_API_KEY" },
|
|
17903
|
+
account: false
|
|
17904
|
+
},
|
|
17905
|
+
kind: "adapter",
|
|
17906
|
+
remote: true,
|
|
17907
|
+
optIn: true,
|
|
17908
|
+
freshness: [],
|
|
17909
|
+
homepage: "https://www.anysearch.com",
|
|
17910
|
+
options: {
|
|
17911
|
+
zone: {
|
|
17912
|
+
kind: "enum",
|
|
17913
|
+
values: ["cn", "intl"]
|
|
17914
|
+
},
|
|
17915
|
+
language: {
|
|
17916
|
+
kind: "text",
|
|
17917
|
+
pattern: /^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,2}$/u
|
|
17918
|
+
}
|
|
17919
|
+
}
|
|
17920
|
+
},
|
|
17783
17921
|
openalex: {
|
|
17784
17922
|
label: "OpenAlex",
|
|
17785
17923
|
endpoint: "https://api.openalex.org/works",
|
|
@@ -18157,6 +18295,8 @@ contract.options;
|
|
|
18157
18295
|
/** What the search service returned, in the model-facing invalid-response message. */
|
|
18158
18296
|
const INVALID_DETAIL = {
|
|
18159
18297
|
"not-object": "a body that is not a JSON object",
|
|
18298
|
+
"code-number": "no numeric \"code\" field",
|
|
18299
|
+
"data-results-array": "no \"data.results\" array",
|
|
18160
18300
|
"results-array": "no \"results\" array",
|
|
18161
18301
|
"data-array": "no \"data\" array",
|
|
18162
18302
|
"data-web-array": "no \"data.web\" array",
|
|
@@ -18172,11 +18312,7 @@ function searchSources(outcome) {
|
|
|
18172
18312
|
for (const row of outcome.rows) list.add(row);
|
|
18173
18313
|
return list.sources;
|
|
18174
18314
|
}
|
|
18175
|
-
if (outcome.failure === "api")
|
|
18176
|
-
const detail = outcome.detail === void 0 ? void 0 : upstreamText(outcome.detail);
|
|
18177
|
-
const message = "the search service reported an API error; retry later or choose another search connection";
|
|
18178
|
-
throw searchError("WEB_PROVIDER_ERROR", detail === void 0 ? message : message + "\n\nThe search service responded (untrusted): " + detail);
|
|
18179
|
-
}
|
|
18315
|
+
if (outcome.failure === "api") throw searchError("WEB_PROVIDER_ERROR", "the search service reported an API error; retry later or choose another search connection" + upstreamSuffix("The search service", outcome.detail));
|
|
18180
18316
|
throw invalidResponse(INVALID_DETAIL[outcome.reason]);
|
|
18181
18317
|
}
|
|
18182
18318
|
//#endregion
|
|
@@ -18186,6 +18322,7 @@ const ADAPTER_WIRES = {
|
|
|
18186
18322
|
firecrawl: mapWire(firecrawlSearch, searchSources),
|
|
18187
18323
|
tavily: mapWire(tavilySearch, searchSources),
|
|
18188
18324
|
tinyfish: mapWire(tinyfishSearch, searchSources),
|
|
18325
|
+
anysearch: mapWire(anysearchSearch, searchSources),
|
|
18189
18326
|
openalex: mapWire(openalexSearch, searchSources),
|
|
18190
18327
|
semanticscholar: mapWire(semanticscholarSearch, searchSources)
|
|
18191
18328
|
};
|
package/package.json
CHANGED