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/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
- const request = wire.request(input, target.endpoint, { kind: "keyless" });
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
- const request = wire.request(input, target.endpoint, {
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 send(request, signal, upstream) {
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 (!response.ok) {
325
- const failure = statusError(response.status, upstream);
326
- const body = await readFailureText(response, signal, upstream, matcher);
327
- const detail = failureExplanation(body.text, matcher, body.truncated);
328
- if (detail === void 0) throw failure;
329
- throw new WebError(failure.message + upstreamSuffix(upstream.label, detail), failure.code);
330
- }
331
- return response;
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 requestJson(request, signal, upstream) {
479
+ async function exchangeJson(request, signal, upstream) {
452
480
  const matcher = upstream.matcher === void 0 ? secretMatcher([]) : upstream.matcher;
453
- return redactJson(await readJson(await send(request, signal, {
454
- ...upstream,
455
- matcher
456
- }), signal, upstream.onDiagnostic, matcher), matcher);
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
- const text = upstreamText(truncated ? stripPrefix(value, matcher) : value);
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 (detail.kind === "withheld") return "\n\n" + label + "'s response was withheld because it contained this request's credential.";
494
- return "\n\n" + label + " responded (untrusted): " + detail.text;
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 failureExplanation(body, matcher, truncated) {
501
- if (containsSecret(body, matcher)) return { kind: "withheld" };
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 = upstreamText(candidate);
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.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-web-search-enhanced",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "web_search provider for DeepSeek Harness with switchable search backends; installed through dsh-tool-web-enhanced-profile",
5
5
  "keywords": [
6
6
  "deepseek-harness",