@humain/terminal 0.2.3 → 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 (152) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/bundle/chunks/{chunk-U75GS4NL.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-LYOOYM65.js → chunk-WKLAQTQ4.js} +100 -124
  5. package/dist/bundle/chunks/{compat-22OQ5DBK.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/humain/web-access/vendor/anysearch.d.ts.map +1 -1
  11. package/dist/humain/web-access/vendor/anysearch.js +4 -14
  12. package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
  13. package/dist/humain/web-access/vendor/bocha.d.ts.map +1 -1
  14. package/dist/humain/web-access/vendor/bocha.js +3 -24
  15. package/dist/humain/web-access/vendor/bocha.js.map +1 -1
  16. package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
  17. package/dist/humain/web-access/vendor/brave.js +3 -24
  18. package/dist/humain/web-access/vendor/brave.js.map +1 -1
  19. package/dist/humain/web-access/vendor/brightdata.d.ts.map +1 -1
  20. package/dist/humain/web-access/vendor/brightdata.js +5 -34
  21. package/dist/humain/web-access/vendor/brightdata.js.map +1 -1
  22. package/dist/humain/web-access/vendor/content-find.d.ts.map +1 -1
  23. package/dist/humain/web-access/vendor/content-find.js +186 -51
  24. package/dist/humain/web-access/vendor/content-find.js.map +1 -1
  25. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  26. package/dist/humain/web-access/vendor/curator-page.js +4 -1
  27. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  28. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -1
  29. package/dist/humain/web-access/vendor/curator-run.js +3 -1
  30. package/dist/humain/web-access/vendor/curator-run.js.map +1 -1
  31. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  32. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  33. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  34. package/dist/humain/web-access/vendor/domain-filter-normalization.d.ts +2 -0
  35. package/dist/humain/web-access/vendor/domain-filter-normalization.d.ts.map +1 -0
  36. package/dist/humain/web-access/vendor/domain-filter-normalization.js +19 -0
  37. package/dist/humain/web-access/vendor/domain-filter-normalization.js.map +1 -0
  38. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  39. package/dist/humain/web-access/vendor/duckduckgo.js +3 -24
  40. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  41. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  42. package/dist/humain/web-access/vendor/extract.js +3 -1
  43. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  44. package/dist/humain/web-access/vendor/firecrawl.d.ts.map +1 -1
  45. package/dist/humain/web-access/vendor/firecrawl.js +5 -32
  46. package/dist/humain/web-access/vendor/firecrawl.js.map +1 -1
  47. package/dist/humain/web-access/vendor/gemini-search.d.ts +5 -2
  48. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  49. package/dist/humain/web-access/vendor/gemini-search.js +85 -37
  50. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  51. package/dist/humain/web-access/vendor/index.d.ts +3 -3
  52. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  53. package/dist/humain/web-access/vendor/index.js +135 -157
  54. package/dist/humain/web-access/vendor/index.js.map +1 -1
  55. package/dist/humain/web-access/vendor/jina-search.d.ts.map +1 -1
  56. package/dist/humain/web-access/vendor/jina-search.js +4 -14
  57. package/dist/humain/web-access/vendor/jina-search.js.map +1 -1
  58. package/dist/humain/web-access/vendor/kagi.d.ts.map +1 -1
  59. package/dist/humain/web-access/vendor/kagi.js +4 -12
  60. package/dist/humain/web-access/vendor/kagi.js.map +1 -1
  61. package/dist/humain/web-access/vendor/kimi-search.d.ts.map +1 -1
  62. package/dist/humain/web-access/vendor/kimi-search.js +5 -32
  63. package/dist/humain/web-access/vendor/kimi-search.js.map +1 -1
  64. package/dist/humain/web-access/vendor/mistral-search.d.ts.map +1 -1
  65. package/dist/humain/web-access/vendor/mistral-search.js +1 -18
  66. package/dist/humain/web-access/vendor/mistral-search.js.map +1 -1
  67. package/dist/humain/web-access/vendor/ollama.d.ts.map +1 -1
  68. package/dist/humain/web-access/vendor/ollama.js +2 -6
  69. package/dist/humain/web-access/vendor/ollama.js.map +1 -1
  70. package/dist/humain/web-access/vendor/openai-search.d.ts +3 -0
  71. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  72. package/dist/humain/web-access/vendor/openai-search.js +181 -38
  73. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  74. package/dist/humain/web-access/vendor/opencode-session-headers.d.ts +10 -0
  75. package/dist/humain/web-access/vendor/opencode-session-headers.d.ts.map +1 -0
  76. package/dist/humain/web-access/vendor/opencode-session-headers.js +23 -0
  77. package/dist/humain/web-access/vendor/opencode-session-headers.js.map +1 -0
  78. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  79. package/dist/humain/web-access/vendor/page-query.js +10 -2
  80. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  81. package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
  82. package/dist/humain/web-access/vendor/parallel.js +1 -18
  83. package/dist/humain/web-access/vendor/parallel.js.map +1 -1
  84. package/dist/humain/web-access/vendor/pdf-extract.d.ts +2 -0
  85. package/dist/humain/web-access/vendor/pdf-extract.d.ts.map +1 -1
  86. package/dist/humain/web-access/vendor/pdf-extract.js +1 -0
  87. package/dist/humain/web-access/vendor/pdf-extract.js.map +1 -1
  88. package/dist/humain/web-access/vendor/querit.d.ts.map +1 -1
  89. package/dist/humain/web-access/vendor/querit.js +5 -32
  90. package/dist/humain/web-access/vendor/querit.js.map +1 -1
  91. package/dist/humain/web-access/vendor/search-answer-formatting.d.ts +8 -0
  92. package/dist/humain/web-access/vendor/search-answer-formatting.d.ts.map +1 -0
  93. package/dist/humain/web-access/vendor/search-answer-formatting.js +6 -0
  94. package/dist/humain/web-access/vendor/search-answer-formatting.js.map +1 -0
  95. package/dist/humain/web-access/vendor/search-result-count-normalization.d.ts +2 -0
  96. package/dist/humain/web-access/vendor/search-result-count-normalization.d.ts.map +1 -0
  97. package/dist/humain/web-access/vendor/search-result-count-normalization.js +6 -0
  98. package/dist/humain/web-access/vendor/search-result-count-normalization.js.map +1 -0
  99. package/dist/humain/web-access/vendor/search1api.d.ts.map +1 -1
  100. package/dist/humain/web-access/vendor/search1api.js +5 -32
  101. package/dist/humain/web-access/vendor/search1api.js.map +1 -1
  102. package/dist/humain/web-access/vendor/searchinfinity.d.ts.map +1 -1
  103. package/dist/humain/web-access/vendor/searchinfinity.js +5 -32
  104. package/dist/humain/web-access/vendor/searchinfinity.js.map +1 -1
  105. package/dist/humain/web-access/vendor/searxng.d.ts.map +1 -1
  106. package/dist/humain/web-access/vendor/searxng.js +3 -24
  107. package/dist/humain/web-access/vendor/searxng.js.map +1 -1
  108. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -1
  109. package/dist/humain/web-access/vendor/serpapi.js +5 -30
  110. package/dist/humain/web-access/vendor/serpapi.js.map +1 -1
  111. package/dist/humain/web-access/vendor/serpbase.d.ts.map +1 -1
  112. package/dist/humain/web-access/vendor/serpbase.js +3 -24
  113. package/dist/humain/web-access/vendor/serpbase.js.map +1 -1
  114. package/dist/humain/web-access/vendor/serpdive.d.ts.map +1 -1
  115. package/dist/humain/web-access/vendor/serpdive.js +3 -24
  116. package/dist/humain/web-access/vendor/serpdive.js.map +1 -1
  117. package/dist/humain/web-access/vendor/serper.d.ts.map +1 -1
  118. package/dist/humain/web-access/vendor/serper.js +5 -30
  119. package/dist/humain/web-access/vendor/serper.js.map +1 -1
  120. package/dist/humain/web-access/vendor/serply.d.ts +4 -0
  121. package/dist/humain/web-access/vendor/serply.d.ts.map +1 -0
  122. package/dist/humain/web-access/vendor/serply.js +191 -0
  123. package/dist/humain/web-access/vendor/serply.js.map +1 -0
  124. package/dist/humain/web-access/vendor/source-check.d.ts.map +1 -1
  125. package/dist/humain/web-access/vendor/source-check.js +11 -47
  126. package/dist/humain/web-access/vendor/source-check.js.map +1 -1
  127. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  128. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  129. package/dist/humain/web-access/vendor/summary-review.js +6 -2
  130. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  131. package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
  132. package/dist/humain/web-access/vendor/tavily.js +3 -24
  133. package/dist/humain/web-access/vendor/tavily.js.map +1 -1
  134. package/dist/humain/web-access/vendor/tinyfish.d.ts.map +1 -1
  135. package/dist/humain/web-access/vendor/tinyfish.js +5 -32
  136. package/dist/humain/web-access/vendor/tinyfish.js.map +1 -1
  137. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  138. package/dist/humain/web-access/vendor/utils.js +2 -0
  139. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  140. package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -1
  141. package/dist/humain/web-access/vendor/valyu.js +5 -30
  142. package/dist/humain/web-access/vendor/valyu.js.map +1 -1
  143. package/dist/humain/web-access/vendor/xai-search.d.ts.map +1 -1
  144. package/dist/humain/web-access/vendor/xai-search.js +1 -18
  145. package/dist/humain/web-access/vendor/xai-search.js.map +1 -1
  146. package/dist/humain/web-access/vendor/xcrawl.d.ts.map +1 -1
  147. package/dist/humain/web-access/vendor/xcrawl.js +5 -34
  148. package/dist/humain/web-access/vendor/xcrawl.js.map +1 -1
  149. package/npm-shrinkwrap.json +6 -6
  150. package/package.json +2 -2
  151. package/src/humain/web-access/UPSTREAM.md +33 -1
  152. package/src/humain/web-access/upstream.json +6 -5
@@ -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 { normalizeSearchResultCount } from "./search-result-count-normalization.js";
3
5
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
6
  import { getWebSearchConfigPath } from "./utils.js";
5
7
  const SERPDIVE_API_URL = "https://api.serpdive.com/v1/search";
@@ -59,29 +61,6 @@ function resolveModel() {
59
61
  const value = raw.trim().toLowerCase();
60
62
  return MODELS.includes(value) ? value : DEFAULT_MODEL;
61
63
  }
62
- function normalizeCount(value) {
63
- if (typeof value !== "number" || !Number.isFinite(value))
64
- return 5;
65
- return Math.max(1, Math.min(Math.floor(value), 20));
66
- }
67
- function normalizeDomain(value) {
68
- let input = value.trim().toLowerCase();
69
- if (!input)
70
- return null;
71
- if (input.startsWith("-"))
72
- input = input.slice(1).trim();
73
- if (!input)
74
- return null;
75
- try {
76
- const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
77
- input = parsed.hostname;
78
- }
79
- catch {
80
- input = input.split("/")[0]?.split(":")[0] ?? "";
81
- }
82
- input = input.replace(/^\.+|\.+$/g, "");
83
- return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
84
- }
85
64
  function parseDomainFilter(domainFilter) {
86
65
  const filters = { include: [], exclude: [] };
87
66
  if (!domainFilter?.length)
@@ -198,7 +177,7 @@ export function isSerpdiveAvailable() {
198
177
  }
199
178
  export async function searchWithSerpdive(query, options = {}) {
200
179
  const apiKey = await requireApiKey(options.signal);
201
- const numResults = normalizeCount(options.numResults);
180
+ const numResults = normalizeSearchResultCount(options.numResults);
202
181
  const filters = parseDomainFilter(options.domainFilter);
203
182
  const model = resolveModel();
204
183
  const body = {
@@ -1 +1 @@
1
- {"version":3,"file":"serpdive.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpdive.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPDIVE_API_URL = \"https://api.serpdive.com/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\n\n// Retrieval depth. The default is deliberately the free tier: this provider\n// ships inside someone else's framework, and installing it must never start\n// spending on a user's behalf without them choosing it.\n// krill — free, fair use, no synthesized answer (see buildAnswer below)\n// mako — the fact-carrying sentences of each page, 1 credit\n// moby — the full readable content of every page, 1.5 credits\n// Pricing: https://serpdive.com/pricing\nconst MODELS = [\"krill\", \"mako\", \"moby\"] as const;\ntype SerpdiveModel = (typeof MODELS)[number];\nconst DEFAULT_MODEL: SerpdiveModel = \"krill\";\n\ninterface WebSearchConfig {\n\tserpdiveApiKey?: unknown;\n\tserpdiveModel?: unknown;\n}\n\ninterface SerpdiveResult {\n\turl?: string;\n\ttitle?: string | null;\n\tdate?: string;\n\tcontent?: string;\n}\n\ninterface SerpdiveResponse {\n\tquery?: string;\n\tmodel?: string;\n\tanswer?: string | null;\n\tresults?: SerpdiveResult[];\n}\n\ninterface SerpdiveSearchOptions 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\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\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"SERPdive\",\n\t\tconfiguredValue: loadConfig().serpdiveApiKey,\n\t\tenvironmentValue: process.env.SERPDIVE_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\"SERPdive API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serpdiveApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPDIVE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serpdive.com/dashboard/keys\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\n// An unknown value falls back to the free default rather than failing: a typo\n// in a config file must not cost the user money, and must not break search.\nfunction resolveModel(): SerpdiveModel {\n\tconst raw = process.env.SERPDIVE_MODEL ?? loadConfig().serpdiveModel;\n\tif (typeof raw !== \"string\") return DEFAULT_MODEL;\n\tconst value = raw.trim().toLowerCase();\n\treturn (MODELS as readonly string[]).includes(value) ? value as SerpdiveModel : DEFAULT_MODEL;\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\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\n// SERPdive exposes no include/exclude domain parameter, so the filter is applied\n// here, on what came back. It can therefore only ever narrow a page of results —\n// it cannot ask the engine for more pages from a given domain.\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\n// SERPdive has NO time-range parameter. Recency is expressed inside the question\n// and read by the engine, which biases ranking toward recent pages — it is a\n// hint, never a guaranteed freshness filter, and results outside the window can\n// still come back.\nfunction applyRecencyHint(query: string, recencyFilter: SearchOptions[\"recencyFilter\"]): string {\n\tif (!recencyFilter) return query;\n\tconst hints: Record<string, string> = {\n\t\tday: \"past 24 hours\",\n\t\tweek: \"past week\",\n\t\tmonth: \"past month\",\n\t\tyear: \"past year\",\n\t};\n\tconst hint = hints[recencyFilter];\n\treturn hint ? `${query} ${hint}` : query;\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction mapResults(\n\tresults: SerpdiveResult[] | undefined,\n\tnumResults: number,\n\tfilters: DomainFilters,\n): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (const item of results) {\n\t\tif (!item?.url || !passesDomainFilters(item.url, filters)) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${mapped.length + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: typeof item.content === \"string\" ? item.content.replace(/\\s+/g, \" \").trim() : \"\",\n\t\t});\n\t\tif (mapped.length >= numResults) break;\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: SerpdiveResult[] | undefined, filters: DomainFilters): ExtractedContent[] {\n\tif (!Array.isArray(results)) return [];\n\treturn results.flatMap((item) => {\n\t\tif (!item?.url || !passesDomainFilters(item.url, filters)) return [];\n\t\tif (typeof item.content !== \"string\" || item.content.trim().length === 0) return [];\n\t\treturn [{\n\t\t\turl: item.url,\n\t\t\ttitle: item.title || \"\",\n\t\t\tcontent: item.content,\n\t\t\terror: null,\n\t\t}];\n\t});\n}\n\n// The free krill tier returns extracted content but no synthesized answer, so\n// one is assembled from the sources — the same shape brave.ts and searxng.ts\n// produce for providers that do not synthesize. mako and moby ask the API for a\n// real answer and use it when it comes back.\nfunction buildAnswer(apiAnswer: string | null | undefined, results: SearchResponse[\"results\"]): string {\n\tif (typeof apiAnswer === \"string\" && apiAnswer.trim().length > 0) return apiAnswer;\n\treturn results\n\t\t.map((result) => {\n\t\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\t\treturn `Source: ${result.title} (${result.url})`;\n\t\t})\n\t\t.join(\"\\n\\n\");\n}\n\nexport function isSerpdiveAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"SERPdive\",\n\t\tconfiguredValue: loadConfig().serpdiveApiKey,\n\t\tenvironmentValue: process.env.SERPDIVE_API_KEY,\n\t});\n}\n\nexport async function searchWithSerpdive(query: string, options: SerpdiveSearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst filters = parseDomainFilter(options.domainFilter);\n\tconst model = resolveModel();\n\tconst body: Record<string, unknown> = {\n\t\tquery: applyRecencyHint(query, options.recencyFilter),\n\t\tmodel,\n\t\t// max_results is a CAP, never a minimum: the engine returns what it\n\t\t// judges relevant, up to this many. Asking for more does not produce more.\n\t\tmax_results: Math.min(numResults, 10),\n\t\t// krill has no answer synthesis — asking for one there is silently ignored\n\t\t// by the API, so it is not asked for at all.\n\t\t...(model === \"krill\" ? {} : { answer: true }),\n\t};\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(SERPDIVE_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Authorization\": `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(options.signal),\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\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(`SERPdive API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tlet data: SerpdiveResponse;\n\ttry {\n\t\tdata = await response.json() as SerpdiveResponse;\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`SERPdive API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results = mapResults(data.results, numResults, filters);\n\tconst result: SearchResponse = {\n\t\tanswer: buildAnswer(data.answer, results),\n\t\tresults,\n\t};\n\tif (options.includeContent) {\n\t\tconst inlineContent = mapInlineContent(data.results, filters);\n\t\tif (inlineContent.length > 0) result.inlineContent = inlineContent;\n\t}\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"serpdive.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serpdive.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 { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { normalizeSearchResultCount } from \"./search-result-count-normalization.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPDIVE_API_URL = \"https://api.serpdive.com/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\n\n// Retrieval depth. The default is deliberately the free tier: this provider\n// ships inside someone else's framework, and installing it must never start\n// spending on a user's behalf without them choosing it.\n// krill — free, fair use, no synthesized answer (see buildAnswer below)\n// mako — the fact-carrying sentences of each page, 1 credit\n// moby — the full readable content of every page, 1.5 credits\n// Pricing: https://serpdive.com/pricing\nconst MODELS = [\"krill\", \"mako\", \"moby\"] as const;\ntype SerpdiveModel = (typeof MODELS)[number];\nconst DEFAULT_MODEL: SerpdiveModel = \"krill\";\n\ninterface WebSearchConfig {\n\tserpdiveApiKey?: unknown;\n\tserpdiveModel?: unknown;\n}\n\ninterface SerpdiveResult {\n\turl?: string;\n\ttitle?: string | null;\n\tdate?: string;\n\tcontent?: string;\n}\n\ninterface SerpdiveResponse {\n\tquery?: string;\n\tmodel?: string;\n\tanswer?: string | null;\n\tresults?: SerpdiveResult[];\n}\n\ninterface SerpdiveSearchOptions 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\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\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"SERPdive\",\n\t\tconfiguredValue: loadConfig().serpdiveApiKey,\n\t\tenvironmentValue: process.env.SERPDIVE_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\"SERPdive API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serpdiveApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPDIVE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serpdive.com/dashboard/keys\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\n// An unknown value falls back to the free default rather than failing: a typo\n// in a config file must not cost the user money, and must not break search.\nfunction resolveModel(): SerpdiveModel {\n\tconst raw = process.env.SERPDIVE_MODEL ?? loadConfig().serpdiveModel;\n\tif (typeof raw !== \"string\") return DEFAULT_MODEL;\n\tconst value = raw.trim().toLowerCase();\n\treturn (MODELS as readonly string[]).includes(value) ? value as SerpdiveModel : DEFAULT_MODEL;\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\n// SERPdive exposes no include/exclude domain parameter, so the filter is applied\n// here, on what came back. It can therefore only ever narrow a page of results —\n// it cannot ask the engine for more pages from a given domain.\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\n// SERPdive has NO time-range parameter. Recency is expressed inside the question\n// and read by the engine, which biases ranking toward recent pages — it is a\n// hint, never a guaranteed freshness filter, and results outside the window can\n// still come back.\nfunction applyRecencyHint(query: string, recencyFilter: SearchOptions[\"recencyFilter\"]): string {\n\tif (!recencyFilter) return query;\n\tconst hints: Record<string, string> = {\n\t\tday: \"past 24 hours\",\n\t\tweek: \"past week\",\n\t\tmonth: \"past month\",\n\t\tyear: \"past year\",\n\t};\n\tconst hint = hints[recencyFilter];\n\treturn hint ? `${query} ${hint}` : query;\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction mapResults(\n\tresults: SerpdiveResult[] | undefined,\n\tnumResults: number,\n\tfilters: DomainFilters,\n): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (const item of results) {\n\t\tif (!item?.url || !passesDomainFilters(item.url, filters)) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${mapped.length + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: typeof item.content === \"string\" ? item.content.replace(/\\s+/g, \" \").trim() : \"\",\n\t\t});\n\t\tif (mapped.length >= numResults) break;\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: SerpdiveResult[] | undefined, filters: DomainFilters): ExtractedContent[] {\n\tif (!Array.isArray(results)) return [];\n\treturn results.flatMap((item) => {\n\t\tif (!item?.url || !passesDomainFilters(item.url, filters)) return [];\n\t\tif (typeof item.content !== \"string\" || item.content.trim().length === 0) return [];\n\t\treturn [{\n\t\t\turl: item.url,\n\t\t\ttitle: item.title || \"\",\n\t\t\tcontent: item.content,\n\t\t\terror: null,\n\t\t}];\n\t});\n}\n\n// The free krill tier returns extracted content but no synthesized answer, so\n// one is assembled from the sources — the same shape brave.ts and searxng.ts\n// produce for providers that do not synthesize. mako and moby ask the API for a\n// real answer and use it when it comes back.\nfunction buildAnswer(apiAnswer: string | null | undefined, results: SearchResponse[\"results\"]): string {\n\tif (typeof apiAnswer === \"string\" && apiAnswer.trim().length > 0) return apiAnswer;\n\treturn results\n\t\t.map((result) => {\n\t\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\t\treturn `Source: ${result.title} (${result.url})`;\n\t\t})\n\t\t.join(\"\\n\\n\");\n}\n\nexport function isSerpdiveAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"SERPdive\",\n\t\tconfiguredValue: loadConfig().serpdiveApiKey,\n\t\tenvironmentValue: process.env.SERPDIVE_API_KEY,\n\t});\n}\n\nexport async function searchWithSerpdive(query: string, options: SerpdiveSearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeSearchResultCount(options.numResults);\n\tconst filters = parseDomainFilter(options.domainFilter);\n\tconst model = resolveModel();\n\tconst body: Record<string, unknown> = {\n\t\tquery: applyRecencyHint(query, options.recencyFilter),\n\t\tmodel,\n\t\t// max_results is a CAP, never a minimum: the engine returns what it\n\t\t// judges relevant, up to this many. Asking for more does not produce more.\n\t\tmax_results: Math.min(numResults, 10),\n\t\t// krill has no answer synthesis — asking for one there is silently ignored\n\t\t// by the API, so it is not asked for at all.\n\t\t...(model === \"krill\" ? {} : { answer: true }),\n\t};\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(SERPDIVE_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Authorization\": `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(options.signal),\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\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(`SERPdive API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tlet data: SerpdiveResponse;\n\ttry {\n\t\tdata = await response.json() as SerpdiveResponse;\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`SERPdive API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results = mapResults(data.results, numResults, filters);\n\tconst result: SearchResponse = {\n\t\tanswer: buildAnswer(data.answer, results),\n\t\tresults,\n\t};\n\tif (options.includeContent) {\n\t\tconst inlineContent = mapInlineContent(data.results, filters);\n\t\tif (inlineContent.length > 0) result.inlineContent = inlineContent;\n\t}\n\treturn result;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"serper.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAmJrE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyD1G"}
1
+ {"version":3,"file":"serper.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serper.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA2HrE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAyD1G"}
@@ -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 SERPER_SEARCH_URL = "https://google.serper.dev/search";
@@ -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 ?? []) {
@@ -126,17 +106,12 @@ function parseResponse(value) {
126
106
  throw invalidResponse("expected organic array");
127
107
  return envelope.organic;
128
108
  }
129
- function buildAnswer(results) {
130
- return results.map((result) => result.snippet
131
- ? `${result.snippet}\nSource: ${result.title} (${result.url})`
132
- : `Source: ${result.title} (${result.url})`).join("\n\n");
133
- }
134
109
  export function isSerperAvailable() {
135
110
  return hasCredentialSource({ provider: "Serper", configuredValue: loadConfig().serperApiKey, environmentValue: process.env.SERPER_API_KEY });
136
111
  }
137
112
  export async function searchWithSerper(query, options = {}) {
138
113
  const apiKey = await requireApiKey(options.signal);
139
- const numResults = normalizeCount(options.numResults);
114
+ const numResults = normalizeSearchResultCount(options.numResults);
140
115
  const filters = parseDomainFilter(options.domainFilter);
141
116
  const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
142
117
  const activityId = activityMonitor.logStart({ type: "api", query });
@@ -201,6 +176,6 @@ export async function searchWithSerper(query, options = {}) {
201
176
  if (results.length >= numResults)
202
177
  break;
203
178
  }
204
- return { answer: buildAnswer(results), results };
179
+ return { answer: formatSearchResultsAsAnswer(results), results };
205
180
  }
206
181
  //# sourceMappingURL=serper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serper.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serper.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 SERPER_SEARCH_URL = \"https://google.serper.dev/search\";\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\tserperApiKey?: unknown;\n}\n\ninterface SerperResult {\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: \"Serper\",\n\t\tconfiguredValue: loadConfig().serperApiKey,\n\t\tenvironmentValue: process.env.SERPER_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\"Serper API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serperApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPER_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serper.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 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(`Serper API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerperResult[] {\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 (!Array.isArray(envelope.organic)) throw invalidResponse(\"expected organic array\");\n\treturn envelope.organic as SerperResult[];\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 isSerperAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Serper\", configuredValue: loadConfig().serperApiKey, environmentValue: process.env.SERPER_API_KEY });\n}\n\nexport async function searchWithSerper(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 activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(SERPER_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"X-API-KEY\": apiKey, \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ q: buildQuery(query, filters), num: requestCount, ...(options.recencyFilter ? { tbs: RECENCY_TBS[options.recencyFilter] } : {}) }),\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(`Serper 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(`Serper API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tlet entries: SerperResult[];\n\ttry {\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\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":"serper.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serper.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 SERPER_SEARCH_URL = \"https://google.serper.dev/search\";\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\tserperApiKey?: unknown;\n}\n\ninterface SerperResult {\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: \"Serper\",\n\t\tconfiguredValue: loadConfig().serperApiKey,\n\t\tenvironmentValue: process.env.SERPER_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\"Serper API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serperApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPER_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serper.dev\",\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(`Serper API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerperResult[] {\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 (!Array.isArray(envelope.organic)) throw invalidResponse(\"expected organic array\");\n\treturn envelope.organic as SerperResult[];\n}\n\nexport function isSerperAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Serper\", configuredValue: loadConfig().serperApiKey, environmentValue: process.env.SERPER_API_KEY });\n}\n\nexport async function searchWithSerper(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 activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(SERPER_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"X-API-KEY\": apiKey, \"Content-Type\": \"application/json\", Accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ q: buildQuery(query, filters), num: requestCount, ...(options.recencyFilter ? { tbs: RECENCY_TBS[options.recencyFilter] } : {}) }),\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(`Serper 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(`Serper API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\tlet entries: SerperResult[];\n\ttry {\n\t\tentries = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\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"]}
@@ -0,0 +1,4 @@
1
+ import type { SearchOptions, SearchResponse } from "./perplexity.ts";
2
+ export declare function isSerplyAvailable(): boolean;
3
+ export declare function searchWithSerply(query: string, options?: SearchOptions): Promise<SearchResponse>;
4
+ //# sourceMappingURL=serply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serply.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serply.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAuHrE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAsE1G"}
@@ -0,0 +1,191 @@
1
+ import { existsSync, readFileSync } from "node:fs";
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";
6
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
7
+ import { fetchWithCredentialRedirects, getWebSearchConfigPath } from "./utils.js";
8
+ const SERPLY_SEARCH_URL = "https://api.serply.io/v1/search";
9
+ const CONFIG_PATH = getWebSearchConfigPath();
10
+ const SEARCH_TIMEOUT_MS = 60_000;
11
+ const RECENCY_TBS = {
12
+ day: "qdr:d",
13
+ week: "qdr:w",
14
+ month: "qdr:m",
15
+ year: "qdr:y",
16
+ };
17
+ let cachedConfig = null;
18
+ function loadConfig() {
19
+ if (cachedConfig)
20
+ return cachedConfig;
21
+ if (!existsSync(CONFIG_PATH)) {
22
+ cachedConfig = {};
23
+ return cachedConfig;
24
+ }
25
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(raw);
29
+ }
30
+ catch (err) {
31
+ const message = err instanceof Error ? err.message : String(err);
32
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
33
+ }
34
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
35
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
36
+ }
37
+ cachedConfig = parsed;
38
+ return cachedConfig;
39
+ }
40
+ async function getApiKey(signal) {
41
+ return resolveCredential({
42
+ provider: "Serply",
43
+ configuredValue: loadConfig().serplyApiKey,
44
+ environmentValue: process.env.SERPLY_API_KEY,
45
+ signal,
46
+ });
47
+ }
48
+ async function requireApiKey(signal) {
49
+ const apiKey = await getApiKey(signal);
50
+ if (!apiKey) {
51
+ throw new Error("Serply API key not found. Either:\n" +
52
+ ` 1. Create ${CONFIG_PATH} with { "serplyApiKey": "your-key" }\n` +
53
+ " 2. Set SERPLY_API_KEY environment variable\n" +
54
+ "Get a key at https://serply.io");
55
+ }
56
+ return apiKey;
57
+ }
58
+ function parseDomainFilter(domainFilter) {
59
+ const filters = { include: [], exclude: [] };
60
+ for (const raw of domainFilter ?? []) {
61
+ const domain = normalizeDomain(raw);
62
+ if (!domain)
63
+ continue;
64
+ const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
65
+ if (!target.includes(domain))
66
+ target.push(domain);
67
+ }
68
+ return filters;
69
+ }
70
+ function passesDomainFilters(url, filters) {
71
+ if (filters.include.length === 0 && filters.exclude.length === 0)
72
+ return true;
73
+ const hostname = url.hostname.toLowerCase();
74
+ const matches = (domain) => hostname === domain || hostname.endsWith(`.${domain}`);
75
+ if (filters.exclude.some(matches))
76
+ return false;
77
+ return filters.include.length === 0 || filters.include.some(matches);
78
+ }
79
+ function buildQuery(query, filters) {
80
+ const parts = [query];
81
+ if (filters.include.length === 1)
82
+ parts.push(`site:${filters.include[0]}`);
83
+ if (filters.include.length > 1)
84
+ parts.push(`(${filters.include.map(domain => `site:${domain}`).join(" OR ")})`);
85
+ for (const domain of filters.exclude)
86
+ parts.push(`-site:${domain}`);
87
+ return parts.join(" ");
88
+ }
89
+ function errorMessage(err) {
90
+ return err instanceof Error ? err.message : String(err);
91
+ }
92
+ function invalidResponse(message) {
93
+ return new Error(`Serply returned invalid response: ${message}`);
94
+ }
95
+ function parseResponse(value) {
96
+ if (!value || typeof value !== "object" || Array.isArray(value))
97
+ throw invalidResponse("expected an object envelope");
98
+ const envelope = value;
99
+ if (typeof envelope.detail === "string" && envelope.detail.trim())
100
+ throw invalidResponse(envelope.detail.trim());
101
+ if (!Array.isArray(envelope.results))
102
+ throw invalidResponse("expected results array");
103
+ return envelope.results;
104
+ }
105
+ export function isSerplyAvailable() {
106
+ return hasCredentialSource({ provider: "Serply", configuredValue: loadConfig().serplyApiKey, environmentValue: process.env.SERPLY_API_KEY });
107
+ }
108
+ export async function searchWithSerply(query, options = {}) {
109
+ const apiKey = await requireApiKey(options.signal);
110
+ const numResults = normalizeSearchResultCount(options.numResults);
111
+ const filters = parseDomainFilter(options.domainFilter);
112
+ const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
113
+ const url = new URL(SERPLY_SEARCH_URL);
114
+ url.searchParams.set("q", buildQuery(query, filters));
115
+ url.searchParams.set("num", String(requestCount));
116
+ if (options.recencyFilter)
117
+ url.searchParams.set("tbs", RECENCY_TBS[options.recencyFilter]);
118
+ const activityId = activityMonitor.logStart({ type: "api", query });
119
+ const timeoutSignal = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
120
+ let response;
121
+ let entries;
122
+ try {
123
+ response = await fetchWithCredentialRedirects(String(url), {
124
+ headers: { Accept: "application/json", "X-Api-Key": apiKey },
125
+ signal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,
126
+ }, ["X-Api-Key"]);
127
+ if (!response.ok) {
128
+ const errorText = await response.text();
129
+ throw new Error(`Serply error ${response.status}: ${redactCredential(errorText, apiKey).slice(0, 300)}`);
130
+ }
131
+ let rawData;
132
+ try {
133
+ rawData = await response.json();
134
+ }
135
+ catch (err) {
136
+ if (err instanceof Error && err.name === "TimeoutError")
137
+ throw err;
138
+ throw new Error(`Serply returned invalid JSON: ${errorMessage(err)}`);
139
+ }
140
+ entries = parseResponse(rawData);
141
+ }
142
+ catch (err) {
143
+ if (options.signal?.aborted) {
144
+ activityMonitor.logComplete(activityId, 0);
145
+ throw new Error("Aborted");
146
+ }
147
+ const message = errorMessage(err);
148
+ const providerTimeout = timeoutSignal.aborted || (err instanceof Error && err.name === "TimeoutError");
149
+ const outgoing = providerTimeout
150
+ ? new Error(`Serply request timed out after ${Math.round(SEARCH_TIMEOUT_MS / 1000)}s`)
151
+ : (() => {
152
+ const redactedMessage = redactCredential(message, apiKey);
153
+ if (redactedMessage === message && err instanceof Error)
154
+ return err;
155
+ const redactedError = new Error(redactedMessage);
156
+ if (err instanceof Error)
157
+ redactedError.name = err.name;
158
+ return redactedError;
159
+ })();
160
+ activityMonitor.logError(activityId, redactCredential(errorMessage(outgoing), apiKey));
161
+ throw outgoing;
162
+ }
163
+ activityMonitor.logComplete(activityId, response.status);
164
+ const results = [];
165
+ for (const entry of entries) {
166
+ if (!entry || typeof entry !== "object")
167
+ continue;
168
+ if (typeof entry.link !== "string" || !entry.link)
169
+ continue;
170
+ let resultUrl;
171
+ try {
172
+ resultUrl = new URL(entry.link);
173
+ }
174
+ catch {
175
+ continue;
176
+ }
177
+ if (resultUrl.protocol !== "http:" && resultUrl.protocol !== "https:")
178
+ continue;
179
+ if (!passesDomainFilters(resultUrl, filters))
180
+ continue;
181
+ results.push({
182
+ title: typeof entry.title === "string" && entry.title.trim() ? entry.title.trim() : `Source ${results.length + 1}`,
183
+ url: resultUrl.href,
184
+ snippet: typeof entry.description === "string" ? entry.description : "",
185
+ });
186
+ if (results.length >= numResults)
187
+ break;
188
+ }
189
+ return { answer: formatSearchResultsAsAnswer(results), results };
190
+ }
191
+ //# sourceMappingURL=serply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serply.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/serply.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 { fetchWithCredentialRedirects, getWebSearchConfigPath } from \"./utils.ts\";\n\nconst SERPLY_SEARCH_URL = \"https://api.serply.io/v1/search\";\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\tserplyApiKey?: unknown;\n}\n\ninterface SerplyOrganicResult {\n\ttitle?: unknown;\n\tlink?: unknown;\n\tdescription?: 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: \"Serply\",\n\t\tconfiguredValue: loadConfig().serplyApiKey,\n\t\tenvironmentValue: process.env.SERPLY_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\"Serply API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"serplyApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set SERPLY_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://serply.io\",\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: URL, filters: DomainFilters): boolean {\n\tif (filters.include.length === 0 && filters.exclude.length === 0) return true;\n\tconst hostname = url.hostname.toLowerCase();\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(`Serply returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): SerplyOrganicResult[] {\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.detail === \"string\" && envelope.detail.trim()) throw invalidResponse(envelope.detail.trim());\n\tif (!Array.isArray(envelope.results)) throw invalidResponse(\"expected results array\");\n\treturn envelope.results as SerplyOrganicResult[];\n}\n\nexport function isSerplyAvailable(): boolean {\n\treturn hasCredentialSource({ provider: \"Serply\", configuredValue: loadConfig().serplyApiKey, environmentValue: process.env.SERPLY_API_KEY });\n}\n\nexport async function searchWithSerply(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(SERPLY_SEARCH_URL);\n\turl.searchParams.set(\"q\", buildQuery(query, filters));\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: SerplyOrganicResult[];\n\ttry {\n\t\tresponse = await fetchWithCredentialRedirects(String(url), {\n\t\t\theaders: { Accept: \"application/json\", \"X-Api-Key\": apiKey },\n\t\t\tsignal: options.signal ? AbortSignal.any([timeoutSignal, options.signal]) : timeoutSignal,\n\t\t}, [\"X-Api-Key\"]);\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\t\t\tthrow new Error(`Serply 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(`Serply 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(`Serply 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\tlet resultUrl: URL;\n\t\ttry {\n\t\t\tresultUrl = new URL(entry.link);\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (resultUrl.protocol !== \"http:\" && resultUrl.protocol !== \"https:\") continue;\n\t\tif (!passesDomainFilters(resultUrl, 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: resultUrl.href,\n\t\t\tsnippet: typeof entry.description === \"string\" ? entry.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":"source-check.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/source-check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"source-check.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/source-check.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- // Structured source checking and machine-readable research artifacts.
1
+ // Structured source evidence and machine-readable research artifacts.
2
2
  import { createHash } from "node:crypto";
3
3
  import { generateId, getResult, storeResult } from "./storage.js";
4
4
  const OFFICIAL_DOCS_HOSTS = /^(developers\.|docs\.|learn\.|reference\.)|\.github\.io$/i;
@@ -96,54 +96,18 @@ export function buildPassages(sources, fetched = [], hint = "") {
96
96
  }
97
97
  return passages;
98
98
  }
99
- const CONTRADICTION_MARKERS = ["not true", "false", "incorrect", "debunked", "retracted", "no longer", "never", "denied", "contrary", "misleading"];
100
- const SUPPORT_MARKERS = ["yes", "true", "correct", "confirmed", "according to", "shows that", "demonstrates", "reported", "verified", "established"];
101
- function containsPhrase(value, phrase) {
102
- const escaped = phrase.trim().toLowerCase().split(/\s+/).map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
103
- return new RegExp(`(?:^|[^a-z0-9])${escaped}(?=$|[^a-z0-9])`, "i").test(value);
104
- }
105
- function markerIsNegated(value, marker) {
106
- const escaped = marker.trim().toLowerCase().split(/\s+/).map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+");
107
- const markerPattern = new RegExp(`(?:^|[^a-z0-9])${escaped}(?=$|[^a-z0-9])`, "i");
108
- const match = markerPattern.exec(value);
109
- if (!match || match.index === undefined)
110
- return false;
111
- const matchedMarker = match[0].replace(/^[^a-z0-9]+/i, "");
112
- const beforeMarker = value.slice(0, match.index + match[0].length - matchedMarker.length);
113
- return /(?:^|[^a-z0-9])(?:not|no|never|without)\s+$/i.test(beforeMarker);
114
- }
115
- function hasPolarityMarker(value, markers, allowNegated = false) {
116
- return markers.some((marker) => containsPhrase(value, marker) && (allowNegated || !markerIsNegated(value, marker)));
117
- }
118
99
  export function assessClaim(claim, passages) {
119
- const terms = tokenize(claim);
120
- if (terms.length === 0 || passages.length === 0) {
121
- return { claim, status: "missing-evidence", supporting_passages: [], contradicting_passages: [], rationale: "No passages available that discuss the claim's terms.", confidence: 0.2 };
122
- }
123
- const supporting = [];
124
- const contradicting = [];
125
- for (const passage of passages) {
126
- const lower = passage.text.toLowerCase();
127
- const overlap = terms.filter((term) => containsPhrase(lower, term)).length;
128
- if (overlap < Math.max(2, Math.ceil(terms.length / 4)))
129
- continue;
130
- const contra = hasPolarityMarker(lower, CONTRADICTION_MARKERS);
131
- const support = hasPolarityMarker(lower, SUPPORT_MARKERS);
132
- if (contra && !support)
133
- contradicting.push(passage.passage_id);
134
- else if (support && !contra)
135
- supporting.push(passage.passage_id);
100
+ if (passages.length === 0) {
101
+ return { claim, status: "missing-evidence", supporting_passages: [], contradicting_passages: [], rationale: "No passages were retrieved for the claim.", confidence: 0.2 };
136
102
  }
137
- if (contradicting.length > 0 && supporting.length === 0) {
138
- return { claim, status: "contradicted", supporting_passages: [], contradicting_passages: contradicting, rationale: `${contradicting.length} passage(s) contradict the claim; none support it.`, confidence: Math.min(0.85, 0.5 + contradicting.length * 0.1) };
139
- }
140
- if (supporting.length > 0 && contradicting.length === 0) {
141
- return { claim, status: "supported", supporting_passages: supporting, contradicting_passages: [], rationale: `${supporting.length} passage(s) support the claim; none contradict it.`, confidence: Math.min(0.85, 0.5 + supporting.length * 0.1) };
142
- }
143
- if (supporting.length > 0 || contradicting.length > 0) {
144
- return { claim, status: "unclear", supporting_passages: supporting, contradicting_passages: contradicting, rationale: `${supporting.length} supporting and ${contradicting.length} contradicting passage(s); evidence is mixed.`, confidence: 0.4 };
145
- }
146
- return { claim, status: "unclear", supporting_passages: [], contradicting_passages: [], rationale: "Passages mention the claim's terms but contain no clear support or contradiction markers.", confidence: 0.3 };
103
+ return {
104
+ claim,
105
+ status: "unclear",
106
+ supporting_passages: [],
107
+ contradicting_passages: [],
108
+ rationale: "Passages were retrieved, but automated semantic support or contradiction assessment is unavailable; review the cited passages manually.",
109
+ confidence: 0.3,
110
+ };
147
111
  }
148
112
  export function buildResearchArtifact(input) {
149
113
  const filters = input.domainFilter ?? [];