@humain/terminal 0.2.2 → 0.3.0

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.
Files changed (156) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/bundle/chunks/{chunk-NMPIM7MN.js → chunk-CBTAWCW5.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-D6HHRSB5.js → chunk-S43N4JYF.js} +14 -29
  4. package/dist/bundle/chunks/{chunk-ADQYVZ6A.js → chunk-WKLAQTQ4.js} +104 -128
  5. package/dist/bundle/chunks/{compat-CN7WN6DA.js → compat-L5WILLPV.js} +1 -1
  6. package/dist/bundle/chunks/{extract-CTIFQZDJ.js → extract-2YP6VMY4.js} +3 -3
  7. package/dist/bundle/cli.js +1 -1
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/bundle/rpc-entry.js +1 -1
  10. package/dist/core/tools/subagent.d.ts +2 -0
  11. package/dist/core/tools/subagent.d.ts.map +1 -1
  12. package/dist/core/tools/subagent.js +46 -16
  13. package/dist/core/tools/subagent.js.map +1 -1
  14. package/dist/humain/web-access/vendor/anysearch.d.ts.map +1 -1
  15. package/dist/humain/web-access/vendor/anysearch.js +4 -14
  16. package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
  17. package/dist/humain/web-access/vendor/bocha.d.ts.map +1 -1
  18. package/dist/humain/web-access/vendor/bocha.js +3 -24
  19. package/dist/humain/web-access/vendor/bocha.js.map +1 -1
  20. package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
  21. package/dist/humain/web-access/vendor/brave.js +3 -24
  22. package/dist/humain/web-access/vendor/brave.js.map +1 -1
  23. package/dist/humain/web-access/vendor/brightdata.d.ts.map +1 -1
  24. package/dist/humain/web-access/vendor/brightdata.js +5 -34
  25. package/dist/humain/web-access/vendor/brightdata.js.map +1 -1
  26. package/dist/humain/web-access/vendor/content-find.d.ts.map +1 -1
  27. package/dist/humain/web-access/vendor/content-find.js +186 -51
  28. package/dist/humain/web-access/vendor/content-find.js.map +1 -1
  29. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  30. package/dist/humain/web-access/vendor/curator-page.js +4 -1
  31. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  32. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -1
  33. package/dist/humain/web-access/vendor/curator-run.js +3 -1
  34. package/dist/humain/web-access/vendor/curator-run.js.map +1 -1
  35. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  36. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  37. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  38. package/dist/humain/web-access/vendor/domain-filter-normalization.d.ts +2 -0
  39. package/dist/humain/web-access/vendor/domain-filter-normalization.d.ts.map +1 -0
  40. package/dist/humain/web-access/vendor/domain-filter-normalization.js +19 -0
  41. package/dist/humain/web-access/vendor/domain-filter-normalization.js.map +1 -0
  42. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  43. package/dist/humain/web-access/vendor/duckduckgo.js +3 -24
  44. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  45. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  46. package/dist/humain/web-access/vendor/extract.js +3 -1
  47. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  48. package/dist/humain/web-access/vendor/firecrawl.d.ts.map +1 -1
  49. package/dist/humain/web-access/vendor/firecrawl.js +5 -32
  50. package/dist/humain/web-access/vendor/firecrawl.js.map +1 -1
  51. package/dist/humain/web-access/vendor/gemini-search.d.ts +5 -2
  52. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  53. package/dist/humain/web-access/vendor/gemini-search.js +85 -37
  54. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  55. package/dist/humain/web-access/vendor/index.d.ts +3 -3
  56. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  57. package/dist/humain/web-access/vendor/index.js +135 -157
  58. package/dist/humain/web-access/vendor/index.js.map +1 -1
  59. package/dist/humain/web-access/vendor/jina-search.d.ts.map +1 -1
  60. package/dist/humain/web-access/vendor/jina-search.js +4 -14
  61. package/dist/humain/web-access/vendor/jina-search.js.map +1 -1
  62. package/dist/humain/web-access/vendor/kagi.d.ts.map +1 -1
  63. package/dist/humain/web-access/vendor/kagi.js +4 -12
  64. package/dist/humain/web-access/vendor/kagi.js.map +1 -1
  65. package/dist/humain/web-access/vendor/kimi-search.d.ts.map +1 -1
  66. package/dist/humain/web-access/vendor/kimi-search.js +5 -32
  67. package/dist/humain/web-access/vendor/kimi-search.js.map +1 -1
  68. package/dist/humain/web-access/vendor/mistral-search.d.ts.map +1 -1
  69. package/dist/humain/web-access/vendor/mistral-search.js +1 -18
  70. package/dist/humain/web-access/vendor/mistral-search.js.map +1 -1
  71. package/dist/humain/web-access/vendor/ollama.d.ts.map +1 -1
  72. package/dist/humain/web-access/vendor/ollama.js +2 -6
  73. package/dist/humain/web-access/vendor/ollama.js.map +1 -1
  74. package/dist/humain/web-access/vendor/openai-search.d.ts +3 -0
  75. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  76. package/dist/humain/web-access/vendor/openai-search.js +181 -38
  77. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  78. package/dist/humain/web-access/vendor/opencode-session-headers.d.ts +10 -0
  79. package/dist/humain/web-access/vendor/opencode-session-headers.d.ts.map +1 -0
  80. package/dist/humain/web-access/vendor/opencode-session-headers.js +23 -0
  81. package/dist/humain/web-access/vendor/opencode-session-headers.js.map +1 -0
  82. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  83. package/dist/humain/web-access/vendor/page-query.js +10 -2
  84. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  85. package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
  86. package/dist/humain/web-access/vendor/parallel.js +1 -18
  87. package/dist/humain/web-access/vendor/parallel.js.map +1 -1
  88. package/dist/humain/web-access/vendor/pdf-extract.d.ts +2 -0
  89. package/dist/humain/web-access/vendor/pdf-extract.d.ts.map +1 -1
  90. package/dist/humain/web-access/vendor/pdf-extract.js +1 -0
  91. package/dist/humain/web-access/vendor/pdf-extract.js.map +1 -1
  92. package/dist/humain/web-access/vendor/querit.d.ts.map +1 -1
  93. package/dist/humain/web-access/vendor/querit.js +5 -32
  94. package/dist/humain/web-access/vendor/querit.js.map +1 -1
  95. package/dist/humain/web-access/vendor/search-answer-formatting.d.ts +8 -0
  96. package/dist/humain/web-access/vendor/search-answer-formatting.d.ts.map +1 -0
  97. package/dist/humain/web-access/vendor/search-answer-formatting.js +6 -0
  98. package/dist/humain/web-access/vendor/search-answer-formatting.js.map +1 -0
  99. package/dist/humain/web-access/vendor/search-result-count-normalization.d.ts +2 -0
  100. package/dist/humain/web-access/vendor/search-result-count-normalization.d.ts.map +1 -0
  101. package/dist/humain/web-access/vendor/search-result-count-normalization.js +6 -0
  102. package/dist/humain/web-access/vendor/search-result-count-normalization.js.map +1 -0
  103. package/dist/humain/web-access/vendor/search1api.d.ts.map +1 -1
  104. package/dist/humain/web-access/vendor/search1api.js +5 -32
  105. package/dist/humain/web-access/vendor/search1api.js.map +1 -1
  106. package/dist/humain/web-access/vendor/searchinfinity.d.ts.map +1 -1
  107. package/dist/humain/web-access/vendor/searchinfinity.js +5 -32
  108. package/dist/humain/web-access/vendor/searchinfinity.js.map +1 -1
  109. package/dist/humain/web-access/vendor/searxng.d.ts.map +1 -1
  110. package/dist/humain/web-access/vendor/searxng.js +3 -24
  111. package/dist/humain/web-access/vendor/searxng.js.map +1 -1
  112. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -1
  113. package/dist/humain/web-access/vendor/serpapi.js +5 -30
  114. package/dist/humain/web-access/vendor/serpapi.js.map +1 -1
  115. package/dist/humain/web-access/vendor/serpbase.d.ts.map +1 -1
  116. package/dist/humain/web-access/vendor/serpbase.js +3 -24
  117. package/dist/humain/web-access/vendor/serpbase.js.map +1 -1
  118. package/dist/humain/web-access/vendor/serpdive.d.ts.map +1 -1
  119. package/dist/humain/web-access/vendor/serpdive.js +3 -24
  120. package/dist/humain/web-access/vendor/serpdive.js.map +1 -1
  121. package/dist/humain/web-access/vendor/serper.d.ts.map +1 -1
  122. package/dist/humain/web-access/vendor/serper.js +5 -30
  123. package/dist/humain/web-access/vendor/serper.js.map +1 -1
  124. package/dist/humain/web-access/vendor/serply.d.ts +4 -0
  125. package/dist/humain/web-access/vendor/serply.d.ts.map +1 -0
  126. package/dist/humain/web-access/vendor/serply.js +191 -0
  127. package/dist/humain/web-access/vendor/serply.js.map +1 -0
  128. package/dist/humain/web-access/vendor/source-check.d.ts.map +1 -1
  129. package/dist/humain/web-access/vendor/source-check.js +11 -47
  130. package/dist/humain/web-access/vendor/source-check.js.map +1 -1
  131. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  132. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  133. package/dist/humain/web-access/vendor/summary-review.js +6 -2
  134. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  135. package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
  136. package/dist/humain/web-access/vendor/tavily.js +3 -24
  137. package/dist/humain/web-access/vendor/tavily.js.map +1 -1
  138. package/dist/humain/web-access/vendor/tinyfish.d.ts.map +1 -1
  139. package/dist/humain/web-access/vendor/tinyfish.js +5 -32
  140. package/dist/humain/web-access/vendor/tinyfish.js.map +1 -1
  141. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  142. package/dist/humain/web-access/vendor/utils.js +2 -0
  143. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  144. package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -1
  145. package/dist/humain/web-access/vendor/valyu.js +5 -30
  146. package/dist/humain/web-access/vendor/valyu.js.map +1 -1
  147. package/dist/humain/web-access/vendor/xai-search.d.ts.map +1 -1
  148. package/dist/humain/web-access/vendor/xai-search.js +1 -18
  149. package/dist/humain/web-access/vendor/xai-search.js.map +1 -1
  150. package/dist/humain/web-access/vendor/xcrawl.d.ts.map +1 -1
  151. package/dist/humain/web-access/vendor/xcrawl.js +5 -34
  152. package/dist/humain/web-access/vendor/xcrawl.js.map +1 -1
  153. package/npm-shrinkwrap.json +6 -6
  154. package/package.json +2 -2
  155. package/src/humain/web-access/UPSTREAM.md +33 -1
  156. package/src/humain/web-access/upstream.json +6 -5
@@ -1,6 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.js";
3
+ import { normalizeDomain } from "./domain-filter-normalization.js";
3
4
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
5
+ import { formatSearchResultsAsAnswer } from "./search-answer-formatting.js";
6
+ import { normalizeSearchResultCount } from "./search-result-count-normalization.js";
4
7
  import { getWebSearchConfigPath } from "./utils.js";
5
8
  const SEARCHINFINITY_SEARCH_URL = "https://torchlight.byteintlapi.com/search_api/web_search";
6
9
  const CONFIG_PATH = getWebSearchConfigPath();
@@ -58,29 +61,6 @@ function requestSignal(signal, timeoutMs) {
58
61
  const timeout = AbortSignal.timeout(timeoutMs);
59
62
  return signal ? AbortSignal.any([signal, timeout]) : timeout;
60
63
  }
61
- function normalizeNumResults(value) {
62
- if (typeof value !== "number" || !Number.isFinite(value))
63
- return 5;
64
- return Math.max(1, Math.min(Math.floor(value), 20));
65
- }
66
- function normalizeDomain(value) {
67
- let input = value.trim().toLowerCase();
68
- if (!input)
69
- return null;
70
- if (input.startsWith("-"))
71
- input = input.slice(1).trim();
72
- if (!input)
73
- return null;
74
- try {
75
- const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
76
- input = parsed.hostname;
77
- }
78
- catch {
79
- input = input.split("/")[0]?.split(":")[0] ?? "";
80
- }
81
- input = input.replace(/^\.+|\.+$/g, "");
82
- return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
83
- }
84
64
  function mapRecencyFilter(recency) {
85
65
  if (recency === "day")
86
66
  return "OneDay";
@@ -111,7 +91,7 @@ function buildSearchBody(query, options) {
111
91
  const timeRange = mapRecencyFilter(options.recencyFilter);
112
92
  return {
113
93
  Query: query,
114
- Count: normalizeNumResults(options.numResults),
94
+ Count: normalizeSearchResultCount(options.numResults),
115
95
  ...(Object.keys(filter).length > 0 ? { Filter: filter } : {}),
116
96
  ...(timeRange ? { TimeRange: timeRange } : {}),
117
97
  };
@@ -194,20 +174,13 @@ function mapSearchResults(results) {
194
174
  }];
195
175
  });
196
176
  }
197
- function buildAnswer(results) {
198
- return results.map((result) => {
199
- if (result.snippet)
200
- return `${result.snippet}\nSource: ${result.title} (${result.url})`;
201
- return `Source: ${result.title} (${result.url})`;
202
- }).join("\n\n");
203
- }
204
177
  export async function searchWithSearchinfinity(query, options = {}) {
205
178
  const apiKey = await getApiKey(options.signal);
206
179
  const activityId = activityMonitor.logStart({ type: "api", query });
207
180
  try {
208
181
  const data = await searchinfinityJsonRequest(apiKey, buildSearchBody(query, options), options.signal);
209
182
  const results = mapSearchResults(data.Result?.WebResults);
210
- const response = { answer: buildAnswer(results), results };
183
+ const response = { answer: formatSearchResultsAsAnswer(results), results };
211
184
  activityMonitor.logComplete(activityId, 200);
212
185
  return response;
213
186
  }
@@ -1 +1 @@
1
- {"version":3,"file":"searchinfinity.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/searchinfinity.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SEARCHINFINITY_SEARCH_URL = \"https://torchlight.byteintlapi.com/search_api/web_search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\n// API Key authenticated requests time out server-side after 30 seconds.\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tsearchinfinityApiKey?: unknown;\n}\n\ninterface SearchinfinityWebResult {\n\tId?: string;\n\tSortId?: number;\n\tTitle?: string | null;\n\tSiteName?: string | null;\n\tUrl?: string | null;\n\tSnippet?: string | null;\n\tSummary?: string | null;\n\tPublishTime?: string | null;\n\tLogoUrl?: string | null;\n}\n\ninterface SearchinfinitySearchResponse {\n\tResponseMetadata?: {\n\t\tRequestId?: string;\n\t\tError?: {\n\t\t\tCodeN?: number;\n\t\t\tCode?: string;\n\t\t\tMessage?: string;\n\t\t} | null;\n\t};\n\tResult?: {\n\t\tResultCount?: number;\n\t\tWebResults?: SearchinfinityWebResult[];\n\t\tSearchContext?: {\n\t\t\tOriginQuery?: string;\n\t\t\tSearchType?: string;\n\t\t};\n\t\tTimeCost?: number;\n\t\tLogId?: string;\n\t} | null;\n}\n\ninterface SearchinfinitySearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string> {\n\tconst key = await resolveCredential({\n\t\tprovider: \"Searchinfinity\",\n\t\tconfiguredValue: loadConfig().searchinfinityApiKey,\n\t\tenvironmentValue: process.env.SEARCHINFINITY_API_KEY,\n\t\tsignal,\n\t});\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Searchinfinity API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"searchinfinityApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SEARCHINFINITY_API_KEY environment variable\\n\" +\n\t\t\t\"Create a key at https://console.byteplus.com/search-infinity/api-key\",\n\t\t);\n\t}\n\treturn key;\n}\n\nexport function isSearchinfinityAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Searchinfinity\",\n\t\tconfiguredValue: loadConfig().searchinfinityApiKey,\n\t\tenvironmentValue: process.env.SEARCHINFINITY_API_KEY,\n\t});\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction requestSignal(signal: AbortSignal | undefined, timeoutMs: number): AbortSignal {\n\tconst timeout = AbortSignal.timeout(timeoutMs);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction normalizeNumResults(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapRecencyFilter(recency: SearchOptions[\"recencyFilter\"]): string | undefined {\n\tif (recency === \"day\") return \"OneDay\";\n\tif (recency === \"week\") return \"OneWeek\";\n\tif (recency === \"month\") return \"OneMonth\";\n\tif (recency === \"year\") return \"OneYear\";\n\treturn undefined;\n}\n\nfunction buildSearchBody(query: string, options: SearchinfinitySearchOptions): Record<string, unknown> {\n\tconst includeSites: string[] = [];\n\tconst blockHosts: string[] = [];\n\tfor (const raw of options.domainFilter ?? []) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? blockHosts : includeSites;\n\t\tif (target.length < 5 && !target.includes(domain)) target.push(domain);\n\t}\n\tconst filter: Record<string, unknown> = {};\n\tif (includeSites.length > 0) filter.Sites = includeSites.join(\"|\");\n\tif (blockHosts.length > 0) filter.BlockHosts = blockHosts.join(\"|\");\n\n\tconst timeRange = mapRecencyFilter(options.recencyFilter);\n\treturn {\n\t\tQuery: query,\n\t\tCount: normalizeNumResults(options.numResults),\n\t\t...(Object.keys(filter).length > 0 ? { Filter: filter } : {}),\n\t\t...(timeRange ? { TimeRange: timeRange } : {}),\n\t};\n}\n\n// Map Searchinfinity business error codes to the closest HTTP semantics so\n// error classification (auth/quota/invalid-request/transient) keeps working.\n// CodeN carries the numeric code (e.g. 700901); Code may be a slug (e.g.\n// \"invalid_api_key\"), so match both.\nfunction businessErrorStatus(codeN: number | undefined, code: string, message: string): number | undefined {\n\tif (codeN === 700901 || code === \"invalid_api_key\") return 401;\n\tif (codeN === 700429 || code === \"700429\") return 429;\n\tif (codeN === 10400 || code === \"10400\") return 400;\n\tif (codeN === 10500 || code === \"10500\") return 500;\n\tif (codeN === 10403 || code === \"10403\") return /quota|exhaust/i.test(message) ? 429 : 403;\n\treturn undefined;\n}\n\nasync function searchinfinityJsonRequest(\n\tapiKey: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<SearchinfinitySearchResponse> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(SEARCHINFINITY_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: requestSignal(signal, SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\n\tconst raw = await response.text();\n\tif (!response.ok) {\n\t\tthrow new Error(`Searchinfinity Search API error ${response.status}: ${redactCredential(raw, apiKey).slice(0, 300)}`);\n\t}\n\tlet data: SearchinfinitySearchResponse;\n\ttry {\n\t\tdata = JSON.parse(raw) as SearchinfinitySearchResponse;\n\t} catch (err) {\n\t\tthrow new Error(`Searchinfinity Search API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tconst businessError = data.ResponseMetadata?.Error;\n\tif (businessError && (businessError.Code || businessError.Message)) {\n\t\tconst code = typeof businessError.Code === \"string\" && businessError.Code ? businessError.Code : \"unknown\";\n\t\tconst message = typeof businessError.Message === \"string\" && businessError.Message ? businessError.Message : \"unknown error\";\n\t\tconst status = businessErrorStatus(businessError.CodeN, code, message);\n\t\tconst codeLabel = typeof businessError.CodeN === \"number\" ? `${businessError.CodeN} ${code}` : code;\n\t\tthrow new Error(\n\t\t\t`Searchinfinity Search API error ${status ?? \"unknown\"}: ${message} (code ${codeLabel})`,\n\t\t);\n\t}\n\treturn data;\n}\n\nfunction mapSearchResults(results: SearchinfinityWebResult[] | undefined): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) {\n\t\tthrow new Error(\"Searchinfinity Search API returned an unexpected response shape\");\n\t}\n\treturn results.flatMap((item) => {\n\t\tif (!item || typeof item.Url !== \"string\" || item.Url.trim().length === 0) return [];\n\t\tconst url = item.Url.trim();\n\t\tconst summary = typeof item.Summary === \"string\" ? item.Summary.replace(/\\s+/g, \" \").trim() : \"\";\n\t\tconst snippet = typeof item.Snippet === \"string\" ? item.Snippet.replace(/\\s+/g, \" \").trim() : \"\";\n\t\treturn [{\n\t\t\ttitle: typeof item.Title === \"string\" && item.Title.trim() ? item.Title.trim() : url,\n\t\t\turl,\n\t\t\tsnippet: summary || snippet,\n\t\t}];\n\t});\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => {\n\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\treturn `Source: ${result.title} (${result.url})`;\n\t}).join(\"\\n\\n\");\n}\n\nexport async function searchWithSearchinfinity(\n\tquery: string,\n\toptions: SearchinfinitySearchOptions = {},\n): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\ttry {\n\t\tconst data = await searchinfinityJsonRequest(apiKey, buildSearchBody(query, options), options.signal);\n\t\tconst results = mapSearchResults(data.Result?.WebResults);\n\t\tconst response: SearchResponse = { answer: buildAnswer(results), results };\n\t\tactivityMonitor.logComplete(activityId, 200);\n\t\treturn response;\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n}\n"]}
1
+ {"version":3,"file":"searchinfinity.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/searchinfinity.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { normalizeDomain } from \"./domain-filter-normalization.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { formatSearchResultsAsAnswer } from \"./search-answer-formatting.ts\";\nimport { normalizeSearchResultCount } from \"./search-result-count-normalization.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SEARCHINFINITY_SEARCH_URL = \"https://torchlight.byteintlapi.com/search_api/web_search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\n// API Key authenticated requests time out server-side after 30 seconds.\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tsearchinfinityApiKey?: unknown;\n}\n\ninterface SearchinfinityWebResult {\n\tId?: string;\n\tSortId?: number;\n\tTitle?: string | null;\n\tSiteName?: string | null;\n\tUrl?: string | null;\n\tSnippet?: string | null;\n\tSummary?: string | null;\n\tPublishTime?: string | null;\n\tLogoUrl?: string | null;\n}\n\ninterface SearchinfinitySearchResponse {\n\tResponseMetadata?: {\n\t\tRequestId?: string;\n\t\tError?: {\n\t\t\tCodeN?: number;\n\t\t\tCode?: string;\n\t\t\tMessage?: string;\n\t\t} | null;\n\t};\n\tResult?: {\n\t\tResultCount?: number;\n\t\tWebResults?: SearchinfinityWebResult[];\n\t\tSearchContext?: {\n\t\t\tOriginQuery?: string;\n\t\t\tSearchType?: string;\n\t\t};\n\t\tTimeCost?: number;\n\t\tLogId?: string;\n\t} | null;\n}\n\ninterface SearchinfinitySearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string> {\n\tconst key = await resolveCredential({\n\t\tprovider: \"Searchinfinity\",\n\t\tconfiguredValue: loadConfig().searchinfinityApiKey,\n\t\tenvironmentValue: process.env.SEARCHINFINITY_API_KEY,\n\t\tsignal,\n\t});\n\tif (!key) {\n\t\tthrow new Error(\n\t\t\t\"Searchinfinity API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"searchinfinityApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SEARCHINFINITY_API_KEY environment variable\\n\" +\n\t\t\t\"Create a key at https://console.byteplus.com/search-infinity/api-key\",\n\t\t);\n\t}\n\treturn key;\n}\n\nexport function isSearchinfinityAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Searchinfinity\",\n\t\tconfiguredValue: loadConfig().searchinfinityApiKey,\n\t\tenvironmentValue: process.env.SEARCHINFINITY_API_KEY,\n\t});\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction requestSignal(signal: AbortSignal | undefined, timeoutMs: number): AbortSignal {\n\tconst timeout = AbortSignal.timeout(timeoutMs);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction mapRecencyFilter(recency: SearchOptions[\"recencyFilter\"]): string | undefined {\n\tif (recency === \"day\") return \"OneDay\";\n\tif (recency === \"week\") return \"OneWeek\";\n\tif (recency === \"month\") return \"OneMonth\";\n\tif (recency === \"year\") return \"OneYear\";\n\treturn undefined;\n}\n\nfunction buildSearchBody(query: string, options: SearchinfinitySearchOptions): Record<string, unknown> {\n\tconst includeSites: string[] = [];\n\tconst blockHosts: string[] = [];\n\tfor (const raw of options.domainFilter ?? []) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? blockHosts : includeSites;\n\t\tif (target.length < 5 && !target.includes(domain)) target.push(domain);\n\t}\n\tconst filter: Record<string, unknown> = {};\n\tif (includeSites.length > 0) filter.Sites = includeSites.join(\"|\");\n\tif (blockHosts.length > 0) filter.BlockHosts = blockHosts.join(\"|\");\n\n\tconst timeRange = mapRecencyFilter(options.recencyFilter);\n\treturn {\n\t\tQuery: query,\n\t\tCount: normalizeSearchResultCount(options.numResults),\n\t\t...(Object.keys(filter).length > 0 ? { Filter: filter } : {}),\n\t\t...(timeRange ? { TimeRange: timeRange } : {}),\n\t};\n}\n\n// Map Searchinfinity business error codes to the closest HTTP semantics so\n// error classification (auth/quota/invalid-request/transient) keeps working.\n// CodeN carries the numeric code (e.g. 700901); Code may be a slug (e.g.\n// \"invalid_api_key\"), so match both.\nfunction businessErrorStatus(codeN: number | undefined, code: string, message: string): number | undefined {\n\tif (codeN === 700901 || code === \"invalid_api_key\") return 401;\n\tif (codeN === 700429 || code === \"700429\") return 429;\n\tif (codeN === 10400 || code === \"10400\") return 400;\n\tif (codeN === 10500 || code === \"10500\") return 500;\n\tif (codeN === 10403 || code === \"10403\") return /quota|exhaust/i.test(message) ? 429 : 403;\n\treturn undefined;\n}\n\nasync function searchinfinityJsonRequest(\n\tapiKey: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<SearchinfinitySearchResponse> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(SEARCHINFINITY_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: requestSignal(signal, SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\n\tconst raw = await response.text();\n\tif (!response.ok) {\n\t\tthrow new Error(`Searchinfinity Search API error ${response.status}: ${redactCredential(raw, apiKey).slice(0, 300)}`);\n\t}\n\tlet data: SearchinfinitySearchResponse;\n\ttry {\n\t\tdata = JSON.parse(raw) as SearchinfinitySearchResponse;\n\t} catch (err) {\n\t\tthrow new Error(`Searchinfinity Search API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tconst businessError = data.ResponseMetadata?.Error;\n\tif (businessError && (businessError.Code || businessError.Message)) {\n\t\tconst code = typeof businessError.Code === \"string\" && businessError.Code ? businessError.Code : \"unknown\";\n\t\tconst message = typeof businessError.Message === \"string\" && businessError.Message ? businessError.Message : \"unknown error\";\n\t\tconst status = businessErrorStatus(businessError.CodeN, code, message);\n\t\tconst codeLabel = typeof businessError.CodeN === \"number\" ? `${businessError.CodeN} ${code}` : code;\n\t\tthrow new Error(\n\t\t\t`Searchinfinity Search API error ${status ?? \"unknown\"}: ${message} (code ${codeLabel})`,\n\t\t);\n\t}\n\treturn data;\n}\n\nfunction mapSearchResults(results: SearchinfinityWebResult[] | undefined): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) {\n\t\tthrow new Error(\"Searchinfinity Search API returned an unexpected response shape\");\n\t}\n\treturn results.flatMap((item) => {\n\t\tif (!item || typeof item.Url !== \"string\" || item.Url.trim().length === 0) return [];\n\t\tconst url = item.Url.trim();\n\t\tconst summary = typeof item.Summary === \"string\" ? item.Summary.replace(/\\s+/g, \" \").trim() : \"\";\n\t\tconst snippet = typeof item.Snippet === \"string\" ? item.Snippet.replace(/\\s+/g, \" \").trim() : \"\";\n\t\treturn [{\n\t\t\ttitle: typeof item.Title === \"string\" && item.Title.trim() ? item.Title.trim() : url,\n\t\t\turl,\n\t\t\tsnippet: summary || snippet,\n\t\t}];\n\t});\n}\n\nexport async function searchWithSearchinfinity(\n\tquery: string,\n\toptions: SearchinfinitySearchOptions = {},\n): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\ttry {\n\t\tconst data = await searchinfinityJsonRequest(apiKey, buildSearchBody(query, options), options.signal);\n\t\tconst results = mapSearchResults(data.Result?.WebResults);\n\t\tconst response: SearchResponse = { answer: formatSearchResultsAsAnswer(results), results };\n\t\tactivityMonitor.logComplete(activityId, 200);\n\t\treturn response;\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"searxng.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/searxng.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAgB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAwLnF,wBAAgB,kBAAkB,IAAI,OAAO,CAK5C;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0D3G"}
1
+ {"version":3,"file":"searxng.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/searxng.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAgB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAqKnF,wBAAgB,kBAAkB,IAAI,OAAO,CAK5C;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0D3G"}
@@ -1,6 +1,8 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.js";
3
+ import { normalizeDomain } from "./domain-filter-normalization.js";
3
4
  import { fetchRemoteUrl, loadSsrfConfig } from "./ssrf-protection.js";
5
+ import { normalizeSearchResultCount } from "./search-result-count-normalization.js";
4
6
  import { getWebSearchConfigPath } from "./utils.js";
5
7
  const CONFIG_PATH = getWebSearchConfigPath();
6
8
  const SEARCH_TIMEOUT_MS = 30_000;
@@ -98,29 +100,6 @@ function requireBaseUrl() {
98
100
  }
99
101
  return baseUrl;
100
102
  }
101
- function normalizeCount(value) {
102
- if (typeof value !== "number" || !Number.isFinite(value))
103
- return 5;
104
- return Math.max(1, Math.min(Math.floor(value), 20));
105
- }
106
- function normalizeDomain(value) {
107
- let input = value.trim().toLowerCase();
108
- if (!input)
109
- return null;
110
- if (input.startsWith("-"))
111
- input = input.slice(1).trim();
112
- if (!input)
113
- return null;
114
- try {
115
- const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
116
- input = parsed.hostname;
117
- }
118
- catch {
119
- input = input.split("/")[0]?.split(":")[0] ?? "";
120
- }
121
- input = input.replace(/^\.+|\.+$/g, "");
122
- return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
123
- }
124
103
  function normalizeDomainFilters(domainFilter) {
125
104
  const filters = { allowed: [], blocked: [] };
126
105
  for (const raw of domainFilter ?? []) {
@@ -176,7 +155,7 @@ export function isSearXNGAvailable() {
176
155
  }
177
156
  export async function searchWithSearXNG(query, options = {}) {
178
157
  const baseUrl = requireBaseUrl();
179
- const numResults = normalizeCount(options.numResults);
158
+ const numResults = normalizeSearchResultCount(options.numResults);
180
159
  const filters = normalizeDomainFilters(options.domainFilter);
181
160
  const searchQuery = buildSearXNGQuery(query, filters);
182
161
  const url = new URL(`${baseUrl}/search`);
@@ -1 +1 @@
1
- {"version":3,"file":"searxng.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/searxng.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { fetchRemoteUrl, loadSsrfConfig } from \"./ssrf-protection.ts\";\nimport type { SearchOptions, SearchResult, SearchResponse } from \"./perplexity.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tsearxngBaseUrl?: unknown;\n\tsearxngHeaders?: unknown;\n}\n\ninterface NormalizedDomainFilters {\n\tallowed: string[];\n\tblocked: string[];\n}\n\ninterface SearXNGResult {\n\ttitle?: string;\n\turl?: string;\n\tcontent?: string;\n}\n\ninterface SearXNGResponse {\n\tresults?: SearXNGResult[];\n\tanswers?: string[];\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\ttry {\n\t\tcachedConfig = JSON.parse(raw) as WebSearchConfig;\n\t\treturn cachedConfig;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n}\n\nfunction normalizeBaseUrl(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst trimmed = value.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = new URL(trimmed);\n\t\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return null;\n\t\tif (url.username || url.password) return null;\n\t\turl.pathname = url.pathname.replace(/\\/+$/, \"\");\n\t\turl.search = \"\";\n\t\turl.hash = \"\";\n\t\treturn url.toString().replace(/\\/+$/, \"\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getBaseUrl(): string | null {\n\tconst configured = process.env.SEARXNG_BASE_URL;\n\treturn configured !== undefined\n\t\t? normalizeBaseUrl(configured)\n\t\t: normalizeBaseUrl(loadConfig().searxngBaseUrl);\n}\n\nfunction isValidHeaderValue(value: string): boolean {\n\ttry {\n\t\tnew Headers({ \"x-pi-web-access-validation\": value });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction normalizeHeaders(value: unknown): Record<string, string> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n\tconst headers: Record<string, string> = {};\n\tfor (const [key, headerValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (typeof headerValue !== \"string\") continue;\n\t\tconst name = key.trim();\n\t\t// RFC 7230 token chars only — reject empty or malformed header names.\n\t\tif (!name || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) continue;\n\t\tif (!isValidHeaderValue(headerValue)) continue;\n\t\theaders[name] = headerValue;\n\t}\n\treturn headers;\n}\n\nfunction getConfiguredHeaders(): Record<string, string> {\n\treturn normalizeHeaders(loadConfig().searxngHeaders);\n}\n\nfunction mergeDefaultHeaders(configured: Record<string, string>): Record<string, string> {\n\tconst headers: Record<string, string> = { Accept: \"application/json\" };\n\tfor (const [name, value] of Object.entries(configured)) {\n\t\tfor (const existing of Object.keys(headers)) {\n\t\t\tif (existing.toLowerCase() === name.toLowerCase()) delete headers[existing];\n\t\t}\n\t\theaders[name] = value;\n\t}\n\treturn headers;\n}\n\nfunction requireBaseUrl(): string {\n\tconst baseUrl = getBaseUrl();\n\tif (!baseUrl) {\n\t\tthrow new Error(\n\t\t\t\"SearXNG base URL is invalid or missing. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"searxngBaseUrl\": \"https://search.example.com\" }\\n` +\n\t\t\t\" 2. Set SEARXNG_BASE_URL to an HTTP(S) URL\",\n\t\t);\n\t}\n\treturn baseUrl;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction normalizeDomainFilters(domainFilter: string[] | undefined): NormalizedDomainFilters {\n\tconst filters: NormalizedDomainFilters = { allowed: [], blocked: [] };\n\tfor (const raw of domainFilter ?? []) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? filters.blocked : filters.allowed;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn filters;\n}\n\nfunction buildSearXNGQuery(query: string, filters: NormalizedDomainFilters): string {\n\tconst parts = [query];\n\tif (filters.allowed.length === 1) {\n\t\tparts.push(`site:${filters.allowed[0]}`);\n\t} else if (filters.allowed.length > 1) {\n\t\tparts.push(filters.allowed.map(domain => `site:${domain}`).join(\" OR \"));\n\t}\n\tfor (const domain of filters.blocked) parts.push(`-site:${domain}`);\n\treturn parts.join(\" \");\n}\n\nfunction hostMatchesDomain(hostname: string, domain: string): boolean {\n\treturn hostname === domain || hostname.endsWith(`.${domain}`);\n}\n\nfunction matchesDomainFilters(url: string, filters: NormalizedDomainFilters): boolean {\n\tif (filters.allowed.length === 0 && filters.blocked.length === 0) return true;\n\tlet hostname: string;\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\tif (filters.allowed.length > 0 && !filters.allowed.some(domain => hostMatchesDomain(hostname, domain))) return false;\n\treturn !filters.blocked.some(domain => hostMatchesDomain(hostname, domain));\n}\n\nfunction mapTimeRange(recencyFilter: SearchOptions[\"recencyFilter\"]): string | null {\n\treturn recencyFilter === \"day\" || recencyFilter === \"week\" || recencyFilter === \"month\" || recencyFilter === \"year\"\n\t\t? recencyFilter\n\t\t: null;\n}\n\nexport function isSearXNGAvailable(): boolean {\n\tconst baseUrl = getBaseUrl();\n\tif (baseUrl === null) return false;\n\tloadSsrfConfig();\n\treturn true;\n}\n\nexport async function searchWithSearXNG(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst baseUrl = requireBaseUrl();\n\tconst numResults = normalizeCount(options.numResults);\n\tconst filters = normalizeDomainFilters(options.domainFilter);\n\tconst searchQuery = buildSearXNGQuery(query, filters);\n\tconst url = new URL(`${baseUrl}/search`);\n\turl.searchParams.set(\"q\", searchQuery);\n\turl.searchParams.set(\"format\", \"json\");\n\tconst timeRange = mapTimeRange(options.recencyFilter);\n\tif (timeRange) url.searchParams.set(\"time_range\", timeRange);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query: searchQuery });\n\n\ttry {\n\t\tconst headers = mergeDefaultHeaders(getConfiguredHeaders());\n\t\tconst response = await fetchRemoteUrl(url, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders,\n\t\t\tsignal: options.signal\n\t\t\t\t? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])\n\t\t\t\t: AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t}, {\n\t\t\t...loadSsrfConfig(),\n\t\t\tonRedirect: ({ from, to, init }) => from.origin === to.origin ? init : { ...init, headers: { Accept: \"application/json\" } },\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tactivityMonitor.logError(activityId, `HTTP ${response.status}`);\n\t\t\tconst errorText = await response.text();\n\t\t\tthrow new Error(`SearXNG search error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tlet data: SearXNGResponse;\n\t\ttry {\n\t\t\tdata = await response.json() as SearXNGResponse;\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthrow new Error(`SearXNG returned invalid JSON: ${message}`);\n\t\t}\n\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst results: SearchResult[] = [];\n\t\tfor (const item of data.results ?? []) {\n\t\t\tif (!item.url || !matchesDomainFilters(item.url, filters)) continue;\n\t\t\tresults.push({ title: item.title || item.url, url: item.url, snippet: item.content || \"\" });\n\t\t\tif (results.length >= numResults) break;\n\t\t}\n\n\t\tconst answerParts = (data.answers ?? []).filter((answer): answer is string => typeof answer === \"string\" && answer.trim().length > 0).map(answer => answer.trim());\n\t\tanswerParts.push(...results.map(result => result.snippet\n\t\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t\t: `Source: ${result.title} (${result.url})`));\n\t\treturn { answer: answerParts.join(\"\\n\\n\"), results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, message);\n\t\tthrow err;\n\t}\n}\n"]}
1
+ {"version":3,"file":"searxng.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/searxng.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { normalizeDomain } from \"./domain-filter-normalization.ts\";\nimport { fetchRemoteUrl, loadSsrfConfig } from \"./ssrf-protection.ts\";\nimport type { SearchOptions, SearchResult, SearchResponse } from \"./perplexity.ts\";\nimport { normalizeSearchResultCount } from \"./search-result-count-normalization.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tsearxngBaseUrl?: unknown;\n\tsearxngHeaders?: unknown;\n}\n\ninterface NormalizedDomainFilters {\n\tallowed: string[];\n\tblocked: string[];\n}\n\ninterface SearXNGResult {\n\ttitle?: string;\n\turl?: string;\n\tcontent?: string;\n}\n\ninterface SearXNGResponse {\n\tresults?: SearXNGResult[];\n\tanswers?: string[];\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\ttry {\n\t\tcachedConfig = JSON.parse(raw) as WebSearchConfig;\n\t\treturn cachedConfig;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n}\n\nfunction normalizeBaseUrl(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst trimmed = value.trim();\n\tif (!trimmed) return null;\n\ttry {\n\t\tconst url = new URL(trimmed);\n\t\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return null;\n\t\tif (url.username || url.password) return null;\n\t\turl.pathname = url.pathname.replace(/\\/+$/, \"\");\n\t\turl.search = \"\";\n\t\turl.hash = \"\";\n\t\treturn url.toString().replace(/\\/+$/, \"\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction getBaseUrl(): string | null {\n\tconst configured = process.env.SEARXNG_BASE_URL;\n\treturn configured !== undefined\n\t\t? normalizeBaseUrl(configured)\n\t\t: normalizeBaseUrl(loadConfig().searxngBaseUrl);\n}\n\nfunction isValidHeaderValue(value: string): boolean {\n\ttry {\n\t\tnew Headers({ \"x-pi-web-access-validation\": value });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction normalizeHeaders(value: unknown): Record<string, string> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n\tconst headers: Record<string, string> = {};\n\tfor (const [key, headerValue] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (typeof headerValue !== \"string\") continue;\n\t\tconst name = key.trim();\n\t\t// RFC 7230 token chars only — reject empty or malformed header names.\n\t\tif (!name || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) continue;\n\t\tif (!isValidHeaderValue(headerValue)) continue;\n\t\theaders[name] = headerValue;\n\t}\n\treturn headers;\n}\n\nfunction getConfiguredHeaders(): Record<string, string> {\n\treturn normalizeHeaders(loadConfig().searxngHeaders);\n}\n\nfunction mergeDefaultHeaders(configured: Record<string, string>): Record<string, string> {\n\tconst headers: Record<string, string> = { Accept: \"application/json\" };\n\tfor (const [name, value] of Object.entries(configured)) {\n\t\tfor (const existing of Object.keys(headers)) {\n\t\t\tif (existing.toLowerCase() === name.toLowerCase()) delete headers[existing];\n\t\t}\n\t\theaders[name] = value;\n\t}\n\treturn headers;\n}\n\nfunction requireBaseUrl(): string {\n\tconst baseUrl = getBaseUrl();\n\tif (!baseUrl) {\n\t\tthrow new Error(\n\t\t\t\"SearXNG base URL is invalid or missing. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"searxngBaseUrl\": \"https://search.example.com\" }\\n` +\n\t\t\t\" 2. Set SEARXNG_BASE_URL to an HTTP(S) URL\",\n\t\t);\n\t}\n\treturn baseUrl;\n}\n\nfunction normalizeDomainFilters(domainFilter: string[] | undefined): NormalizedDomainFilters {\n\tconst filters: NormalizedDomainFilters = { allowed: [], blocked: [] };\n\tfor (const raw of domainFilter ?? []) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? filters.blocked : filters.allowed;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn filters;\n}\n\nfunction buildSearXNGQuery(query: string, filters: NormalizedDomainFilters): string {\n\tconst parts = [query];\n\tif (filters.allowed.length === 1) {\n\t\tparts.push(`site:${filters.allowed[0]}`);\n\t} else if (filters.allowed.length > 1) {\n\t\tparts.push(filters.allowed.map(domain => `site:${domain}`).join(\" OR \"));\n\t}\n\tfor (const domain of filters.blocked) parts.push(`-site:${domain}`);\n\treturn parts.join(\" \");\n}\n\nfunction hostMatchesDomain(hostname: string, domain: string): boolean {\n\treturn hostname === domain || hostname.endsWith(`.${domain}`);\n}\n\nfunction matchesDomainFilters(url: string, filters: NormalizedDomainFilters): boolean {\n\tif (filters.allowed.length === 0 && filters.blocked.length === 0) return true;\n\tlet hostname: string;\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\tif (filters.allowed.length > 0 && !filters.allowed.some(domain => hostMatchesDomain(hostname, domain))) return false;\n\treturn !filters.blocked.some(domain => hostMatchesDomain(hostname, domain));\n}\n\nfunction mapTimeRange(recencyFilter: SearchOptions[\"recencyFilter\"]): string | null {\n\treturn recencyFilter === \"day\" || recencyFilter === \"week\" || recencyFilter === \"month\" || recencyFilter === \"year\"\n\t\t? recencyFilter\n\t\t: null;\n}\n\nexport function isSearXNGAvailable(): boolean {\n\tconst baseUrl = getBaseUrl();\n\tif (baseUrl === null) return false;\n\tloadSsrfConfig();\n\treturn true;\n}\n\nexport async function searchWithSearXNG(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst baseUrl = requireBaseUrl();\n\tconst numResults = normalizeSearchResultCount(options.numResults);\n\tconst filters = normalizeDomainFilters(options.domainFilter);\n\tconst searchQuery = buildSearXNGQuery(query, filters);\n\tconst url = new URL(`${baseUrl}/search`);\n\turl.searchParams.set(\"q\", searchQuery);\n\turl.searchParams.set(\"format\", \"json\");\n\tconst timeRange = mapTimeRange(options.recencyFilter);\n\tif (timeRange) url.searchParams.set(\"time_range\", timeRange);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query: searchQuery });\n\n\ttry {\n\t\tconst headers = mergeDefaultHeaders(getConfiguredHeaders());\n\t\tconst response = await fetchRemoteUrl(url, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders,\n\t\t\tsignal: options.signal\n\t\t\t\t? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])\n\t\t\t\t: AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t}, {\n\t\t\t...loadSsrfConfig(),\n\t\t\tonRedirect: ({ from, to, init }) => from.origin === to.origin ? init : { ...init, headers: { Accept: \"application/json\" } },\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tactivityMonitor.logError(activityId, `HTTP ${response.status}`);\n\t\t\tconst errorText = await response.text();\n\t\t\tthrow new Error(`SearXNG search error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tlet data: SearXNGResponse;\n\t\ttry {\n\t\t\tdata = await response.json() as SearXNGResponse;\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\tthrow new Error(`SearXNG returned invalid JSON: ${message}`);\n\t\t}\n\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst results: SearchResult[] = [];\n\t\tfor (const item of data.results ?? []) {\n\t\t\tif (!item.url || !matchesDomainFilters(item.url, filters)) continue;\n\t\t\tresults.push({ title: item.title || item.url, url: item.url, snippet: item.content || \"\" });\n\t\t\tif (results.length >= numResults) break;\n\t\t}\n\n\t\tconst answerParts = (data.answers ?? []).filter((answer): answer is string => typeof answer === \"string\" && answer.trim().length > 0).map(answer => answer.trim());\n\t\tanswerParts.push(...results.map(result => result.snippet\n\t\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t\t: `Source: ${result.title} (${result.url})`));\n\t\treturn { answer: answerParts.join(\"\\n\\n\"), results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (message.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, message);\n\t\tthrow err;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"serpapi.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpapi.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAoJrE,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAiE3G"}
1
+ {"version":3,"file":"serpapi.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpapi.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA4HrE,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAiE3G"}
@@ -1,5 +1,8 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.js";
3
+ import { normalizeDomain } from "./domain-filter-normalization.js";
4
+ import { formatSearchResultsAsAnswer } from "./search-answer-formatting.js";
5
+ import { normalizeSearchResultCount } from "./search-result-count-normalization.js";
3
6
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
7
  import { getWebSearchConfigPath } from "./utils.js";
5
8
  const SERPAPI_SEARCH_URL = "https://serpapi.com/search.json";
@@ -52,29 +55,6 @@ async function requireApiKey(signal) {
52
55
  }
53
56
  return apiKey;
54
57
  }
55
- function normalizeCount(value) {
56
- if (typeof value !== "number" || !Number.isFinite(value))
57
- return 5;
58
- return Math.max(1, Math.min(Math.floor(value), 20));
59
- }
60
- function normalizeDomain(value) {
61
- let input = value.trim().toLowerCase();
62
- if (!input)
63
- return null;
64
- if (input.startsWith("-"))
65
- input = input.slice(1).trim();
66
- if (!input)
67
- return null;
68
- try {
69
- const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
70
- input = parsed.hostname;
71
- }
72
- catch {
73
- input = input.split("/")[0]?.split(":")[0] ?? "";
74
- }
75
- input = input.replace(/^\.+|\.+$/g, "");
76
- return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
77
- }
78
58
  function parseDomainFilter(domainFilter) {
79
59
  const filters = { include: [], exclude: [] };
80
60
  for (const raw of domainFilter ?? []) {
@@ -128,17 +108,12 @@ function parseResponse(value) {
128
108
  throw invalidResponse("expected organic_results array");
129
109
  return envelope.organic_results;
130
110
  }
131
- function buildAnswer(results) {
132
- return results.map((result) => result.snippet
133
- ? `${result.snippet}\nSource: ${result.title} (${result.url})`
134
- : `Source: ${result.title} (${result.url})`).join("\n\n");
135
- }
136
111
  export function isSerpApiAvailable() {
137
112
  return hasCredentialSource({ provider: "SerpApi", configuredValue: loadConfig().serpapiApiKey, environmentValue: process.env.SERPAPI_KEY });
138
113
  }
139
114
  export async function searchWithSerpApi(query, options = {}) {
140
115
  const apiKey = await requireApiKey(options.signal);
141
- const numResults = normalizeCount(options.numResults);
116
+ const numResults = normalizeSearchResultCount(options.numResults);
142
117
  const filters = parseDomainFilter(options.domainFilter);
143
118
  const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
144
119
  const url = new URL(SERPAPI_SEARCH_URL);
@@ -210,6 +185,6 @@ export async function searchWithSerpApi(query, options = {}) {
210
185
  if (results.length >= numResults)
211
186
  break;
212
187
  }
213
- return { answer: buildAnswer(results), results };
188
+ return { answer: formatSearchResultsAsAnswer(results), results };
214
189
  }
215
190
  //# sourceMappingURL=serpapi.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serpapi.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpapi.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPAPI_SEARCH_URL = \"https://serpapi.com/search.json\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst RECENCY_TBS: Record<NonNullable<SearchOptions[\"recencyFilter\"]>, string> = {\n\tday: \"qdr:d\",\n\tweek: \"qdr:w\",\n\tmonth: \"qdr:m\",\n\tyear: \"qdr:y\",\n};\n\ninterface WebSearchConfig {\n\tserpapiApiKey?: unknown;\n}\n\ninterface SerpApiOrganicResult {\n\ttitle?: unknown;\n\tlink?: unknown;\n\tsnippet?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"SerpApi\",\n\t\tconfiguredValue: loadConfig().serpapiApiKey,\n\t\tenvironmentValue: process.env.SERPAPI_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"SerpApi API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serpapiApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPAPI_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serpapi.com/manage-api-key\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\ninterface DomainFilters {\n\tinclude: string[];\n\texclude: string[];\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {\n\tconst filters: DomainFilters = { include: [], exclude: [] };\n\tfor (const raw of domainFilter ?? []) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? filters.exclude : filters.include;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn filters;\n}\n\nfunction passesDomainFilters(url: string, filters: DomainFilters): boolean {\n\tif (filters.include.length === 0 && filters.exclude.length === 0) return true;\n\tlet hostname: string;\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\tconst matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);\n\tif (filters.exclude.some(matches)) return false;\n\treturn filters.include.length === 0 || filters.include.some(matches);\n}\n\nfunction buildQuery(query: string, filters: DomainFilters): string {\n\tconst parts = [query];\n\tif (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);\n\tif (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(\" OR \")})`);\n\tfor (const domain of filters.exclude) parts.push(`-site:${domain}`);\n\treturn parts.join(\" \");\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`SerpApi returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerpApiOrganicResult[] {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as Record<string, unknown>;\n\tif (typeof envelope.error === \"string\" && envelope.error.trim()) throw invalidResponse(envelope.error.trim());\n\tif (!Array.isArray(envelope.organic_results)) throw invalidResponse(\"expected organic_results array\");\n\treturn envelope.organic_results as SerpApiOrganicResult[];\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => result.snippet\n\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t: `Source: ${result.title} (${result.url})`).join(\"\\n\\n\");\n}\n\nexport function isSerpApiAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"SerpApi\", configuredValue: loadConfig().serpapiApiKey, environmentValue: process.env.SERPAPI_KEY });\n}\n\nexport async function searchWithSerpApi(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst filters = parseDomainFilter(options.domainFilter);\n\tconst requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;\n\tconst url = new URL(SERPAPI_SEARCH_URL);\n\turl.searchParams.set(\"engine\", \"google\");\n\turl.searchParams.set(\"q\", buildQuery(query, filters));\n\turl.searchParams.set(\"api_key\", apiKey);\n\turl.searchParams.set(\"num\", String(requestCount));\n\tif (options.recencyFilter) url.searchParams.set(\"tbs\", RECENCY_TBS[options.recencyFilter]);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tconst timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\tlet response: Response;\n\tlet entries: SerpApiOrganicResult[];\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tthrow new Error(`SerpApi error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);\n\t\t}\n\t\tlet rawData: unknown;\n\t\ttry {\n\t\t\trawData = await response.json();\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") throw err;\n\t\t\tthrow new Error(`SerpApi returned invalid JSON: ${errorMessage(err)}`);\n\t\t}\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tif (options.signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\tthrow new Error(\"Aborted\");\n\t\t}\n\t\tconst message = errorMessage(err);\n\t\tconst providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === \"TimeoutError\");\n\t\tconst outgoing = providerTimeout\n\t\t\t? new Error(`SerpApi request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)\n\t\t\t: (() => {\n\t\t\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\t\t\tif (redactedMessage === message && err instanceof Error) return err;\n\t\t\t\tconst redactedError = new Error(redactedMessage);\n\t\t\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\t\t\treturn redactedError;\n\t\t\t})();\n\t\tactivityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));\n\t\tthrow outgoing;\n\t}\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tif (typeof entry.link !== \"string\" || !entry.link) continue;\n\t\tif (!passesDomainFilters(entry.link, filters)) continue;\n\t\tresults.push({\n\t\t\ttitle: typeof entry.title === \"string\" && entry.title.trim() ? entry.title.trim() : `Source ${results.length + 1}`,\n\t\t\turl: entry.link,\n\t\t\tsnippet: typeof entry.snippet === \"string\" ? entry.snippet : \"\",\n\t\t});\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: buildAnswer(results), results };\n}\n"]}
1
+ {"version":3,"file":"serpapi.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpapi.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { normalizeDomain } from \"./domain-filter-normalization.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { formatSearchResultsAsAnswer } from \"./search-answer-formatting.ts\";\nimport { normalizeSearchResultCount } from \"./search-result-count-normalization.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPAPI_SEARCH_URL = \"https://serpapi.com/search.json\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst RECENCY_TBS: Record<NonNullable<SearchOptions[\"recencyFilter\"]>, string> = {\n\tday: \"qdr:d\",\n\tweek: \"qdr:w\",\n\tmonth: \"qdr:m\",\n\tyear: \"qdr:y\",\n};\n\ninterface WebSearchConfig {\n\tserpapiApiKey?: unknown;\n}\n\ninterface SerpApiOrganicResult {\n\ttitle?: unknown;\n\tlink?: unknown;\n\tsnippet?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"SerpApi\",\n\t\tconfiguredValue: loadConfig().serpapiApiKey,\n\t\tenvironmentValue: process.env.SERPAPI_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"SerpApi API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serpapiApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPAPI_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serpapi.com/manage-api-key\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\ninterface DomainFilters {\n\tinclude: string[];\n\texclude: string[];\n}\n\nfunction parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {\n\tconst filters: DomainFilters = { include: [], exclude: [] };\n\tfor (const raw of domainFilter ?? []) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? filters.exclude : filters.include;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn filters;\n}\n\nfunction passesDomainFilters(url: string, filters: DomainFilters): boolean {\n\tif (filters.include.length === 0 && filters.exclude.length === 0) return true;\n\tlet hostname: string;\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\tconst matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);\n\tif (filters.exclude.some(matches)) return false;\n\treturn filters.include.length === 0 || filters.include.some(matches);\n}\n\nfunction buildQuery(query: string, filters: DomainFilters): string {\n\tconst parts = [query];\n\tif (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);\n\tif (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(\" OR \")})`);\n\tfor (const domain of filters.exclude) parts.push(`-site:${domain}`);\n\treturn parts.join(\" \");\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`SerpApi returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerpApiOrganicResult[] {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as Record<string, unknown>;\n\tif (typeof envelope.error === \"string\" && envelope.error.trim()) throw invalidResponse(envelope.error.trim());\n\tif (!Array.isArray(envelope.organic_results)) throw invalidResponse(\"expected organic_results array\");\n\treturn envelope.organic_results as SerpApiOrganicResult[];\n}\n\nexport function isSerpApiAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"SerpApi\", configuredValue: loadConfig().serpapiApiKey, environmentValue: process.env.SERPAPI_KEY });\n}\n\nexport async function searchWithSerpApi(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeSearchResultCount(options.numResults);\n\tconst filters = parseDomainFilter(options.domainFilter);\n\tconst requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;\n\tconst url = new URL(SERPAPI_SEARCH_URL);\n\turl.searchParams.set(\"engine\", \"google\");\n\turl.searchParams.set(\"q\", buildQuery(query, filters));\n\turl.searchParams.set(\"api_key\", apiKey);\n\turl.searchParams.set(\"num\", String(requestCount));\n\tif (options.recencyFilter) url.searchParams.set(\"tbs\", RECENCY_TBS[options.recencyFilter]);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tconst timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\tlet response: Response;\n\tlet entries: SerpApiOrganicResult[];\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tthrow new Error(`SerpApi error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);\n\t\t}\n\t\tlet rawData: unknown;\n\t\ttry {\n\t\t\trawData = await response.json();\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error && err.name === \"TimeoutError\") throw err;\n\t\t\tthrow new Error(`SerpApi returned invalid JSON: ${errorMessage(err)}`);\n\t\t}\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tif (options.signal?.aborted) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t\tthrow new Error(\"Aborted\");\n\t\t}\n\t\tconst message = errorMessage(err);\n\t\tconst providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === \"TimeoutError\");\n\t\tconst outgoing = providerTimeout\n\t\t\t? new Error(`SerpApi request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)\n\t\t\t: (() => {\n\t\t\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\t\t\tif (redactedMessage === message && err instanceof Error) return err;\n\t\t\t\tconst redactedError = new Error(redactedMessage);\n\t\t\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\t\t\treturn redactedError;\n\t\t\t})();\n\t\tactivityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));\n\t\tthrow outgoing;\n\t}\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tfor (const entry of entries) {\n\t\tif (!entry || typeof entry !== \"object\") continue;\n\t\tif (typeof entry.link !== \"string\" || !entry.link) continue;\n\t\tif (!passesDomainFilters(entry.link, filters)) continue;\n\t\tresults.push({\n\t\t\ttitle: typeof entry.title === \"string\" && entry.title.trim() ? entry.title.trim() : `Source ${results.length + 1}`,\n\t\t\turl: entry.link,\n\t\t\tsnippet: typeof entry.snippet === \"string\" ? entry.snippet : \"\",\n\t\t});\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: formatSearchResultsAsAnswer(results), results };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"serpbase.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpbase.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAwKrE,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqD5G"}
1
+ {"version":3,"file":"serpbase.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpbase.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAoJrE,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAqD5G"}
@@ -1,5 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.js";
3
+ import { normalizeDomain } from "./domain-filter-normalization.js";
4
+ import { formatSearchResultsAsAnswer } from "./search-answer-formatting.js";
3
5
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
6
  import { getWebSearchConfigPath } from "./utils.js";
5
7
  const SERPBASE_API_URL = "https://api.serpbase.dev/google/search";
@@ -57,24 +59,6 @@ function normalizeCount(value) {
57
59
  return 10;
58
60
  return Math.max(1, Math.min(Math.floor(value), 20));
59
61
  }
60
- function normalizeDomain(value) {
61
- let input = value.trim().toLowerCase();
62
- if (!input)
63
- return null;
64
- if (input.startsWith("-"))
65
- input = input.slice(1).trim();
66
- if (!input)
67
- return null;
68
- try {
69
- const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
70
- input = parsed.hostname;
71
- }
72
- catch {
73
- input = input.split("/")[0]?.split(":")[0] ?? "";
74
- }
75
- input = input.replace(/^\.+|\.+$/g, "");
76
- return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
77
- }
78
62
  function parseDomainFilter(domainFilter) {
79
63
  const filters = { include: [], exclude: [] };
80
64
  if (!domainFilter?.length)
@@ -137,11 +121,6 @@ function parseResponse(value) {
137
121
  throw invalidResponse("expected organic_results array");
138
122
  return { ...envelope, organic_results: organic };
139
123
  }
140
- function buildAnswer(results) {
141
- return results.map((result) => result.snippet
142
- ? `${result.snippet}\nSource: ${result.title} (${result.url})`
143
- : `Source: ${result.title} (${result.url})`).join("\n\n");
144
- }
145
124
  export function isSerpBaseAvailable() {
146
125
  return hasCredentialSource({ provider: "SerpBase", configuredValue: loadConfig().serpbaseApiKey, environmentValue: process.env.SERPBASE_API_KEY });
147
126
  }
@@ -206,6 +185,6 @@ export async function searchWithSerpBase(query, options = {}) {
206
185
  if (results.length >= numResults)
207
186
  break;
208
187
  }
209
- return { answer: buildAnswer(results), results };
188
+ return { answer: formatSearchResultsAsAnswer(results), results };
210
189
  }
211
190
  //# sourceMappingURL=serpbase.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serpbase.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpbase.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPBASE_API_URL = \"https://api.serpbase.dev/google/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst RECENCY_TBS: Record<string, string> = {\n\tday: \"qdr:d\",\n\tweek: \"qdr:w\",\n\tmonth: \"qdr:m\",\n\tyear: \"qdr:y\",\n};\n\ninterface WebSearchConfig {\n\tserpbaseApiKey?: unknown;\n}\n\ninterface SerpBaseOrganicResult {\n\ttitle?: unknown;\n\tlink?: unknown;\n\turl?: unknown;\n\tsnippet?: unknown;\n\tdescription?: unknown;\n}\n\ninterface SerpBaseResponse {\n\torganic_results?: SerpBaseOrganicResult[];\n\torganic?: SerpBaseOrganicResult[];\n\tresults?: SerpBaseOrganicResult[];\n\tstatus?: unknown;\n\terror?: unknown;\n\tmessage?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"SerpBase\",\n\t\tconfiguredValue: loadConfig().serpbaseApiKey,\n\t\tenvironmentValue: process.env.SERPBASE_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"SerpBase API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serpbaseApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPBASE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serpbase.dev\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 10;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\ninterface DomainFilters {\n\tinclude: string[];\n\texclude: string[];\n}\n\nfunction parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {\n\tconst filters: DomainFilters = { include: [], exclude: [] };\n\tif (!domainFilter?.length) return filters;\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? filters.exclude : filters.include;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn filters;\n}\n\nfunction domainMatches(hostname: string, domain: string): boolean {\n\treturn hostname === domain || hostname.endsWith(`.${domain}`);\n}\n\nfunction passesDomainFilters(url: string, filters: DomainFilters): boolean {\n\tif (filters.include.length === 0 && filters.exclude.length === 0) return true;\n\tlet hostname: string;\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\tif (filters.exclude.some((domain) => domainMatches(hostname, domain))) return false;\n\tif (filters.include.length === 0) return true;\n\treturn filters.include.some((domain) => domainMatches(hostname, domain));\n}\n\nfunction buildQuery(query: string, filters: DomainFilters): string {\n\tconst parts = [query];\n\tif (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);\n\tif (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(\" OR \")})`);\n\tfor (const domain of filters.exclude) parts.push(`-site:${domain}`);\n\treturn parts.join(\" \");\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`SerpBase API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerpBaseResponse {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as SerpBaseResponse;\n\tif (typeof envelope.error === \"string\" && envelope.error.trim()) {\n\t\tconst suffix = typeof envelope.status === \"number\" || typeof envelope.status === \"string\" ? ` (status ${envelope.status})` : \"\";\n\t\tthrow invalidResponse(`${envelope.error}${suffix}`);\n\t}\n\tconst organic = envelope.organic_results ?? envelope.organic ?? envelope.results;\n\tif (!Array.isArray(organic)) throw invalidResponse(\"expected organic_results array\");\n\treturn { ...envelope, organic_results: organic };\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results.map((result) => result.snippet\n\t\t? `${result.snippet}\\nSource: ${result.title} (${result.url})`\n\t\t: `Source: ${result.title} (${result.url})`).join(\"\\n\\n\");\n}\n\nexport function isSerpBaseAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"SerpBase\", configuredValue: loadConfig().serpbaseApiKey, environmentValue: process.env.SERPBASE_API_KEY });\n}\n\nexport async function searchWithSerpBase(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst filters = parseDomainFilter(options.domainFilter);\n\tconst url = new URL(SERPBASE_API_URL);\n\turl.searchParams.set(\"q\", buildQuery(query, filters));\n\t// SerpBase's Google Search endpoint authenticates with an `api_key` query parameter.\n\turl.searchParams.set(\"api_key\", apiKey);\n\turl.searchParams.set(\"num\", String(numResults));\n\tif (options.recencyFilter && RECENCY_TBS[options.recencyFilter]) url.searchParams.set(\"tbs\", RECENCY_TBS[options.recencyFilter]);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`SerpBase API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\tlet rawData: unknown;\n\ttry {\n\t\trawData = await response.json();\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`SerpBase API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tconst data = parseResponse(rawData);\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tfor (const item of data.organic_results ?? []) {\n\t\tconst url = typeof item.link === \"string\" ? item.link : typeof item.url === \"string\" ? item.url : \"\";\n\t\tif (!url || !passesDomainFilters(url, filters)) continue;\n\t\tresults.push({\n\t\t\ttitle: typeof item.title === \"string\" && item.title.trim() ? item.title : `Source ${results.length + 1}`,\n\t\t\turl,\n\t\t\tsnippet: typeof item.snippet === \"string\" ? item.snippet : typeof item.description === \"string\" ? item.description : \"\",\n\t\t});\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: buildAnswer(results), results };\n}\n"]}
1
+ {"version":3,"file":"serpbase.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpbase.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { normalizeDomain } from \"./domain-filter-normalization.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { formatSearchResultsAsAnswer } from \"./search-answer-formatting.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPBASE_API_URL = \"https://api.serpbase.dev/google/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\nconst RECENCY_TBS: Record<string, string> = {\n\tday: \"qdr:d\",\n\tweek: \"qdr:w\",\n\tmonth: \"qdr:m\",\n\tyear: \"qdr:y\",\n};\n\ninterface WebSearchConfig {\n\tserpbaseApiKey?: unknown;\n}\n\ninterface SerpBaseOrganicResult {\n\ttitle?: unknown;\n\tlink?: unknown;\n\turl?: unknown;\n\tsnippet?: unknown;\n\tdescription?: unknown;\n}\n\ninterface SerpBaseResponse {\n\torganic_results?: SerpBaseOrganicResult[];\n\torganic?: SerpBaseOrganicResult[];\n\tresults?: SerpBaseOrganicResult[];\n\tstatus?: unknown;\n\terror?: unknown;\n\tmessage?: unknown;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);\n\t}\n\tcachedConfig = parsed as WebSearchConfig;\n\treturn cachedConfig;\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"SerpBase\",\n\t\tconfiguredValue: loadConfig().serpbaseApiKey,\n\t\tenvironmentValue: process.env.SERPBASE_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"SerpBase API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serpbaseApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPBASE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serpbase.dev\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 10;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\ninterface DomainFilters {\n\tinclude: string[];\n\texclude: string[];\n}\n\nfunction parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {\n\tconst filters: DomainFilters = { include: [], exclude: [] };\n\tif (!domainFilter?.length) return filters;\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? filters.exclude : filters.include;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn filters;\n}\n\nfunction domainMatches(hostname: string, domain: string): boolean {\n\treturn hostname === domain || hostname.endsWith(`.${domain}`);\n}\n\nfunction passesDomainFilters(url: string, filters: DomainFilters): boolean {\n\tif (filters.include.length === 0 && filters.exclude.length === 0) return true;\n\tlet hostname: string;\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\tif (filters.exclude.some((domain) => domainMatches(hostname, domain))) return false;\n\tif (filters.include.length === 0) return true;\n\treturn filters.include.some((domain) => domainMatches(hostname, domain));\n}\n\nfunction buildQuery(query: string, filters: DomainFilters): string {\n\tconst parts = [query];\n\tif (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);\n\tif (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(\" OR \")})`);\n\tfor (const domain of filters.exclude) parts.push(`-site:${domain}`);\n\treturn parts.join(\" \");\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`SerpBase API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerpBaseResponse {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw invalidResponse(\"expected an object envelope\");\n\tconst envelope = value as SerpBaseResponse;\n\tif (typeof envelope.error === \"string\" && envelope.error.trim()) {\n\t\tconst suffix = typeof envelope.status === \"number\" || typeof envelope.status === \"string\" ? ` (status ${envelope.status})` : \"\";\n\t\tthrow invalidResponse(`${envelope.error}${suffix}`);\n\t}\n\tconst organic = envelope.organic_results ?? envelope.organic ?? envelope.results;\n\tif (!Array.isArray(organic)) throw invalidResponse(\"expected organic_results array\");\n\treturn { ...envelope, organic_results: organic };\n}\n\nexport function isSerpBaseAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"SerpBase\", configuredValue: loadConfig().serpbaseApiKey, environmentValue: process.env.SERPBASE_API_KEY });\n}\n\nexport async function searchWithSerpBase(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst filters = parseDomainFilter(options.domainFilter);\n\tconst url = new URL(SERPBASE_API_URL);\n\turl.searchParams.set(\"q\", buildQuery(query, filters));\n\t// SerpBase's Google Search endpoint authenticates with an `api_key` query parameter.\n\turl.searchParams.set(\"api_key\", apiKey);\n\turl.searchParams.set(\"num\", String(numResults));\n\tif (options.recencyFilter && RECENCY_TBS[options.recencyFilter]) url.searchParams.set(\"tbs\", RECENCY_TBS[options.recencyFilter]);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\theaders: { Accept: \"application/json\" },\n\t\t\tsignal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`SerpBase API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\tlet rawData: unknown;\n\ttry {\n\t\trawData = await response.json();\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`SerpBase API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tconst data = parseResponse(rawData);\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results: SearchResponse[\"results\"] = [];\n\tfor (const item of data.organic_results ?? []) {\n\t\tconst url = typeof item.link === \"string\" ? item.link : typeof item.url === \"string\" ? item.url : \"\";\n\t\tif (!url || !passesDomainFilters(url, filters)) continue;\n\t\tresults.push({\n\t\t\ttitle: typeof item.title === \"string\" && item.title.trim() ? item.title : `Source ${results.length + 1}`,\n\t\t\turl,\n\t\t\tsnippet: typeof item.snippet === \"string\" ? item.snippet : typeof item.description === \"string\" ? item.description : \"\",\n\t\t});\n\t\tif (results.length >= numResults) break;\n\t}\n\treturn { answer: formatSearchResultsAsAnswer(results), results };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"serpdive.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpdive.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAsCrE,UAAU,qBAAsB,SAAQ,aAAa;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAqLD,wBAAgB,mBAAmB,IAAI,OAAO,CAM7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAgEpH"}
1
+ {"version":3,"file":"serpdive.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpdive.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAuCrE,UAAU,qBAAsB,SAAQ,aAAa;IACpD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAiKD,wBAAgB,mBAAmB,IAAI,OAAO,CAM7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,cAAc,CAAC,CAgEpH"}