@humain/terminal 0.0.3-beta.2 → 0.0.4-beta.1

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 (136) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bun/cli.d.ts.map +1 -1
  3. package/dist/bun/cli.js +0 -1
  4. package/dist/bun/cli.js.map +1 -1
  5. package/dist/core/agent-session.d.ts.map +1 -1
  6. package/dist/core/agent-session.js +7 -1
  7. package/dist/core/agent-session.js.map +1 -1
  8. package/dist/core/sdk.d.ts.map +1 -1
  9. package/dist/core/sdk.js +1 -1
  10. package/dist/core/sdk.js.map +1 -1
  11. package/dist/core/session-manager.d.ts +4 -0
  12. package/dist/core/session-manager.d.ts.map +1 -1
  13. package/dist/core/session-manager.js +46 -22
  14. package/dist/core/session-manager.js.map +1 -1
  15. package/dist/core/tools/index.d.ts +2 -1
  16. package/dist/core/tools/index.d.ts.map +1 -1
  17. package/dist/core/tools/index.js +9 -1
  18. package/dist/core/tools/index.js.map +1 -1
  19. package/dist/core/tools/subagent.d.ts +82 -0
  20. package/dist/core/tools/subagent.d.ts.map +1 -0
  21. package/dist/core/tools/subagent.js +980 -0
  22. package/dist/core/tools/subagent.js.map +1 -0
  23. package/dist/humain/bedrock-provider/vendor/index.bundle.d.mts +3 -0
  24. package/dist/humain/bedrock-provider/vendor/index.bundle.mjs +1509 -0
  25. package/dist/humain/index.d.ts.map +1 -1
  26. package/dist/humain/index.js +3 -0
  27. package/dist/humain/index.js.map +1 -1
  28. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
  29. package/dist/humain/mcp-adapter/vendor/README.md +47 -5
  30. package/dist/humain/mcp-adapter/vendor/cli.js +163 -1
  31. package/dist/humain/mcp-adapter/vendor/commands.ts +128 -22
  32. package/dist/humain/mcp-adapter/vendor/config.ts +25 -7
  33. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +5 -4
  34. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1663 -458
  35. package/dist/humain/mcp-adapter/vendor/index.ts +219 -32
  36. package/dist/humain/mcp-adapter/vendor/init.ts +36 -4
  37. package/dist/humain/mcp-adapter/vendor/lifecycle.ts +315 -24
  38. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +39 -6
  39. package/dist/humain/mcp-adapter/vendor/mcp-bearer-store.ts +387 -0
  40. package/dist/humain/mcp-adapter/vendor/mcp-callback-server.ts +27 -3
  41. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +4 -0
  42. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +42 -13
  43. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +32 -21
  44. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +168 -0
  45. package/dist/humain/mcp-adapter/vendor/package.json +179 -177
  46. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +5 -4
  47. package/dist/humain/mcp-adapter/vendor/request-headers-command.ts +31 -37
  48. package/dist/humain/mcp-adapter/vendor/search-ranking.ts +92 -22
  49. package/dist/humain/mcp-adapter/vendor/server-manager.ts +140 -2
  50. package/dist/humain/mcp-adapter/vendor/session-recovery.ts +7 -0
  51. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +15 -2
  52. package/dist/humain/mcp-adapter/vendor/tool-result-renderer.ts +44 -4
  53. package/dist/humain/mcp-adapter/vendor/types.ts +2 -0
  54. package/dist/humain/mcp-adapter/vendor/ui-server.ts +18 -16
  55. package/dist/humain/mcp-adapter/vendor/utils.ts +70 -0
  56. package/dist/humain/web-access/vendor/anysearch.d.ts.map +1 -1
  57. package/dist/humain/web-access/vendor/anysearch.js +3 -2
  58. package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
  59. package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
  60. package/dist/humain/web-access/vendor/brave.js +14 -4
  61. package/dist/humain/web-access/vendor/brave.js.map +1 -1
  62. package/dist/humain/web-access/vendor/curator-page.d.ts +3 -0
  63. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  64. package/dist/humain/web-access/vendor/curator-page.js +4 -1
  65. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  66. package/dist/humain/web-access/vendor/curator-server.d.ts +3 -0
  67. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  68. package/dist/humain/web-access/vendor/curator-server.js +6 -0
  69. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  70. package/dist/humain/web-access/vendor/exa.d.ts.map +1 -1
  71. package/dist/humain/web-access/vendor/exa.js +16 -7
  72. package/dist/humain/web-access/vendor/exa.js.map +1 -1
  73. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  74. package/dist/humain/web-access/vendor/extract.js +31 -3
  75. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  76. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  77. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  78. package/dist/humain/web-access/vendor/gemini-search.js +25 -4
  79. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  80. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  81. package/dist/humain/web-access/vendor/index.js +36 -49
  82. package/dist/humain/web-access/vendor/index.js.map +1 -1
  83. package/dist/humain/web-access/vendor/page-query.d.ts +1 -2
  84. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  85. package/dist/humain/web-access/vendor/page-query.js +5 -2
  86. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  87. package/dist/humain/web-access/vendor/parallel-mcp.d.ts +10 -0
  88. package/dist/humain/web-access/vendor/parallel-mcp.d.ts.map +1 -0
  89. package/dist/humain/web-access/vendor/parallel-mcp.js +224 -0
  90. package/dist/humain/web-access/vendor/parallel-mcp.js.map +1 -0
  91. package/dist/humain/web-access/vendor/parallel.d.ts +1 -0
  92. package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
  93. package/dist/humain/web-access/vendor/parallel.js +2 -2
  94. package/dist/humain/web-access/vendor/parallel.js.map +1 -1
  95. package/dist/humain/web-access/vendor/query-rewrite.d.ts +3 -0
  96. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -0
  97. package/dist/humain/web-access/vendor/query-rewrite.js +42 -0
  98. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -0
  99. package/dist/humain/web-access/vendor/serper.d.ts +4 -0
  100. package/dist/humain/web-access/vendor/serper.d.ts.map +1 -0
  101. package/dist/humain/web-access/vendor/serper.js +206 -0
  102. package/dist/humain/web-access/vendor/serper.js.map +1 -0
  103. package/dist/humain/web-access/vendor/summary-model-scope.d.ts +5 -0
  104. package/dist/humain/web-access/vendor/summary-model-scope.d.ts.map +1 -1
  105. package/dist/humain/web-access/vendor/summary-model-scope.js +10 -5
  106. package/dist/humain/web-access/vendor/summary-model-scope.js.map +1 -1
  107. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  108. package/dist/humain/web-access/vendor/summary-review.js +40 -14
  109. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  110. package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
  111. package/dist/humain/web-access/vendor/tavily.js +14 -4
  112. package/dist/humain/web-access/vendor/tavily.js.map +1 -1
  113. package/dist/humain/web-access/vendor/utils.d.ts +10 -0
  114. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  115. package/dist/humain/web-access/vendor/utils.js +70 -0
  116. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  117. package/dist/humain/web-access/vendor/valyu.d.ts +6 -0
  118. package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/valyu.js +194 -0
  120. package/dist/humain/web-access/vendor/valyu.js.map +1 -0
  121. package/docs/extensions.md +1 -1
  122. package/examples/extensions/README.md +1 -1
  123. package/examples/extensions/subagent/README.md +24 -147
  124. package/npm-shrinkwrap.json +7 -2
  125. package/package.json +7 -2
  126. package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
  127. package/src/humain/mcp-adapter/upstream.json +3 -3
  128. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
  129. package/src/humain/web-access/UPSTREAM.md +1 -1
  130. package/src/humain/web-access/upstream.json +4 -4
  131. package/dist/bun/register-bedrock.d.ts +0 -2
  132. package/dist/bun/register-bedrock.d.ts.map +0 -1
  133. package/dist/bun/register-bedrock.js +0 -4
  134. package/dist/bun/register-bedrock.js.map +0 -1
  135. package/examples/extensions/subagent/agents.ts +0 -126
  136. package/examples/extensions/subagent/index.ts +0 -1015
@@ -25,6 +25,7 @@ export interface McpProxyToolCallInput {
25
25
 
26
26
  interface McpToolRenderState {
27
27
  compactTitle?: string;
28
+ compactInputPreview?: string;
28
29
  }
29
30
 
30
31
  interface McpToolRenderContext {
@@ -52,6 +53,7 @@ export interface McpToolResultDisplay {
52
53
  }
53
54
 
54
55
  const DEFAULT_MAX_CALL_INPUT_CHARS = 1500;
56
+ const DEFAULT_MAX_COMPACT_INPUT_CHARS = 240;
55
57
  const DEFAULT_BOXED_COLLAPSED_LINES = 3;
56
58
  const DEFAULT_COMPACT_COLLAPSED_LINES = 1;
57
59
  const DEFAULT_MAX_COLLAPSED_CHARS = 8000;
@@ -70,6 +72,7 @@ class CompactMcpToolResult implements Component {
70
72
 
71
73
  constructor(
72
74
  private readonly title: string,
75
+ private readonly inputPreview: string,
73
76
  private readonly display: McpToolResultDisplay,
74
77
  private readonly theme: RenderTheme,
75
78
  ) {}
@@ -83,7 +86,7 @@ class CompactMcpToolResult implements Component {
83
86
  });
84
87
  const lines = resultLines.length > 0 ? resultLines : [""];
85
88
  const bodies = lines.map((line, index) => {
86
- const prefix = index === 0 && this.title ? `${this.theme.fg("toolTitle", this.title)} → ` : "";
89
+ const prefix = index === 0 ? this.renderPrefix(safeWidth) : "";
87
90
  return `${prefix}${this.theme.fg("toolOutput", line)}`;
88
91
  });
89
92
  const hiddenText = this.display.truncated || bodies.some((body) => visibleWidth(body) > safeWidth);
@@ -106,6 +109,22 @@ class CompactMcpToolResult implements Component {
106
109
  invalidate(): void {
107
110
  this.rendered = null;
108
111
  }
112
+
113
+ private renderPrefix(width: number): string {
114
+ if (!this.title) return "";
115
+ const arrow = " → ";
116
+ if (!this.inputPreview) return `${this.theme.fg("toolTitle", this.title)}${arrow}`;
117
+
118
+ const maxPrefixWidth = Math.max(12, Math.floor(width * 0.55));
119
+ const titleWidth = visibleWidth(this.title);
120
+ const inputWidth = Math.max(0, maxPrefixWidth - titleWidth - 1);
121
+ if (inputWidth <= 3) {
122
+ return `${this.theme.fg("toolTitle", truncateToWidth(this.title, maxPrefixWidth, "…"))}${arrow}`;
123
+ }
124
+
125
+ const input = truncateToWidth(this.inputPreview, inputWidth, "…");
126
+ return `${this.theme.fg("toolTitle", this.title)} ${this.theme.fg("muted", input)}${arrow}`;
127
+ }
109
128
  }
110
129
 
111
130
  class CollapsibleText implements Component {
@@ -239,6 +258,10 @@ function renderToolCallLines(lines: string[], theme?: RenderTheme) {
239
258
  return new Text([styledTitle, ...styledRest].join("\n"), 0, 0);
240
259
  }
241
260
 
261
+ function formatCompactInputPreview(lines: string[], maxChars = DEFAULT_MAX_COMPACT_INPUT_CHARS): string {
262
+ return truncateText(lines.slice(1).join(" ").replace(/\s+/g, " ").trim(), maxChars);
263
+ }
264
+
242
265
  export function resolveMcpToolRenderOptions(settings?: McpToolRenderSettings): McpToolRenderOptions {
243
266
  const resultRendering = settings?.toolResultRendering === "boxed" ? "boxed" : "compact";
244
267
  const collapsedLines = settings?.collapsedResultLines;
@@ -263,7 +286,10 @@ function renderToolCall(
263
286
  context: McpToolRenderContext | undefined,
264
287
  options: McpToolRenderOptions,
265
288
  ) {
266
- if (context?.state) context.state.compactTitle = lines[0] ?? "mcp";
289
+ if (context?.state) {
290
+ context.state.compactTitle = lines[0] ?? "mcp";
291
+ context.state.compactInputPreview = formatCompactInputPreview(lines);
292
+ }
267
293
  if (shouldUseCompactFinalRender(options, context)) return new EmptyComponent();
268
294
  return renderToolCallLines(lines, theme);
269
295
  }
@@ -307,6 +333,19 @@ function collectCollapsedResultLines(
307
333
  let truncated = false;
308
334
 
309
335
  const appendLine = (line: string) => {
336
+ if (lines.length === 0) {
337
+ const previewWidth = Math.min(line.length, remainingChars);
338
+ if (line.slice(0, previewWidth).trim() === "") {
339
+ if (line.length >= remainingChars) {
340
+ truncated = true;
341
+ remainingChars = 0;
342
+ return false;
343
+ }
344
+ remainingChars -= line.length + 1;
345
+ return true;
346
+ }
347
+ }
348
+
310
349
  if (lines.length >= maxLines || remainingChars <= 0) {
311
350
  truncated = true;
312
351
  return false;
@@ -342,7 +381,7 @@ function collectCollapsedResultLines(
342
381
  if (truncated) break;
343
382
  }
344
383
 
345
- if (lines.length === 0) lines.push("");
384
+ if (lines.length === 0) lines.push(truncated ? "(leading blank output omitted)" : "");
346
385
  if (truncated && lines.length >= maxLines) lines.push("…");
347
386
  return { lines, truncated };
348
387
  }
@@ -393,7 +432,8 @@ export function renderMcpToolResult(
393
432
  if (!expanded && renderOptions.resultRendering === "compact") {
394
433
  const display = formatMcpToolResultLines(result, false, renderOptions.collapsedResultLines);
395
434
  const title = context?.state?.compactTitle ?? formatMcpToolResultIdentity(result.details) ?? "";
396
- return new CompactMcpToolResult(title, display, activeTheme);
435
+ const inputPreview = context?.state?.compactInputPreview ?? "";
436
+ return new CompactMcpToolResult(title, inputPreview, display, activeTheme);
397
437
  }
398
438
 
399
439
  const display = formatMcpToolResultLines(result, expanded, renderOptions.collapsedResultLines);
@@ -392,6 +392,8 @@ export interface ServerEntry {
392
392
  auth?: "oauth" | "bearer" | false;
393
393
  bearerToken?: string;
394
394
  bearerTokenEnv?: string;
395
+ /** Read a static bearer token from the adapter-owned OS credential store. */
396
+ bearerTokenStore?: true;
395
397
  /**
396
398
  * OAuth configuration (optional).
397
399
  * If not provided, the SDK will attempt dynamic client registration.
@@ -10,7 +10,7 @@ import {
10
10
  import { ContentBlockSchema } from "@modelcontextprotocol/core";
11
11
  import type { ConsentManager } from "./consent-manager.ts";
12
12
  import { ServerError, wrapError } from "./errors.ts";
13
- import { formatAuthRequiredMessage } from "./utils.ts";
13
+ import { formatAuthRequiredMessage, normalizeToolArguments } from "./utils.ts";
14
14
  import { buildHostHtmlTemplate, buildCspMetaContent } from "./host-html-template.ts";
15
15
  import { logger } from "./logger.ts";
16
16
  import type { McpServerManager } from "./server-manager.ts";
@@ -113,6 +113,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
113
113
  let currentDisplayMode: UiDisplayMode = options.hostContext?.displayMode ?? "inline";
114
114
  let nextEventId = 1;
115
115
  const eventLog: Array<{ id: number; name: string; payload: unknown }> = [];
116
+ let latestCheckpointEventId: number | undefined;
116
117
  let streamSummary: UiStreamSummary | undefined;
117
118
 
118
119
  // Track messages from UI for retrieval
@@ -185,9 +186,12 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
185
186
  lastHeartbeatAt = Date.now();
186
187
  };
187
188
 
188
- const updateStreamSummary = (payload: unknown) => {
189
+ const updateStreamSummary = (eventId: number, payload: unknown) => {
189
190
  const envelope = getVisualizationStreamEnvelope((payload as { structuredContent?: unknown } | null)?.structuredContent);
190
191
  if (!envelope) return;
192
+ if (envelope.frameType === "checkpoint" || envelope.frameType === "final") {
193
+ latestCheckpointEventId = eventId;
194
+ }
191
195
  if (!streamSummary) {
192
196
  streamSummary = {
193
197
  streamId: envelope.streamId,
@@ -210,15 +214,9 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
210
214
  };
211
215
 
212
216
  const getLatestCheckpointIndex = () => {
213
- for (let index = eventLog.length - 1; index >= 0; index -= 1) {
214
- const entry = eventLog[index];
215
- if (!entry) continue;
216
- const envelope = getVisualizationStreamEnvelope((entry.payload as { structuredContent?: unknown } | null)?.structuredContent);
217
- if (envelope?.frameType === "checkpoint" || envelope?.frameType === "final") {
218
- return index;
219
- }
220
- }
221
- return -1;
217
+ const firstEventId = eventLog[0]?.id;
218
+ if (latestCheckpointEventId === undefined || firstEventId === undefined || latestCheckpointEventId < firstEventId) return -1;
219
+ return latestCheckpointEventId - firstEventId;
222
220
  };
223
221
 
224
222
  const pruneEventLog = () => {
@@ -238,7 +236,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
238
236
  if (completed) return;
239
237
  const eventId = nextEventId++;
240
238
  eventLog.push({ id: eventId, name, payload });
241
- updateStreamSummary(payload);
239
+ updateStreamSummary(eventId, payload);
242
240
  pruneEventLog();
243
241
  const chunk = serializeEvent(eventId, name, payload);
244
242
  for (const client of sseClients) {
@@ -438,12 +436,16 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
438
436
  return;
439
437
  }
440
438
 
439
+ let normalizedArguments: Record<string, unknown>;
440
+ try {
441
+ normalizedArguments = normalizeToolArguments(callParams.arguments, `tool "${callParams.name}" arguments`);
442
+ } catch (error) {
443
+ sendJson(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
444
+ return;
445
+ }
441
446
  const callArgs = {
442
447
  name: callParams.name,
443
- arguments:
444
- callParams.arguments && typeof callParams.arguments === "object" && !Array.isArray(callParams.arguments)
445
- ? callParams.arguments
446
- : {},
448
+ arguments: normalizedArguments,
447
449
  };
448
450
  const toolMeta = {
449
451
  name: callParams.name,
@@ -282,6 +282,76 @@ export function normalizeDirectToolInputSchema(schema: unknown): Record<string,
282
282
  return normalized;
283
283
  }
284
284
 
285
+ export function normalizeToolArguments(
286
+ value: unknown,
287
+ context = "tool arguments",
288
+ ): Record<string, unknown> {
289
+ if (value === undefined || value === null || value === "") return {};
290
+
291
+ if (typeof value === "string") {
292
+ const trimmed = value.trim();
293
+ if (trimmed === "") return {};
294
+ let parsed: unknown;
295
+ try {
296
+ parsed = JSON.parse(trimmed);
297
+ } catch (error) {
298
+ throw new Error(
299
+ `${context}: invalid args JSON (${error instanceof SyntaxError ? error.message : String(error)}); ` +
300
+ `pass args as a JSON object, or as a valid JSON string encoding one`,
301
+ { cause: error },
302
+ );
303
+ }
304
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
305
+ throw new Error(
306
+ `${context}: expected a JSON object, got ${Array.isArray(parsed) ? "array" : typeof parsed}`,
307
+ );
308
+ }
309
+ return parsed as Record<string, unknown>;
310
+ }
311
+
312
+ if (typeof value !== "object" || Array.isArray(value)) {
313
+ throw new Error(
314
+ `${context}: expected a JSON object, got ${Array.isArray(value) ? "array" : typeof value}`,
315
+ );
316
+ }
317
+
318
+ assertJsonSerializable(value, context);
319
+ try {
320
+ return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;
321
+ } catch (error) {
322
+ throw new Error(
323
+ `${context}: value is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,
324
+ { cause: error },
325
+ );
326
+ }
327
+ }
328
+
329
+ function assertJsonSerializable(value: unknown, context: string, path = ""): void {
330
+ if (value === null) return;
331
+ if (typeof value === "string" || typeof value === "boolean") return;
332
+ if (typeof value === "number") {
333
+ if (!Number.isFinite(value)) throw new Error(`${context}: value at ${path || "root"} is not a finite number`);
334
+ return;
335
+ }
336
+ if (Array.isArray(value)) {
337
+ for (const [index, item] of value.entries()) assertJsonSerializable(item, context, `${path}[${index}]`);
338
+ return;
339
+ }
340
+ if (typeof value === "object") {
341
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
342
+ throw new Error(`${context}: value at ${path || "root"} is not a plain JSON object`);
343
+ }
344
+ if (Object.getOwnPropertySymbols(value).length > 0) {
345
+ throw new Error(`${context}: value at ${path || "root"} has symbol keys`);
346
+ }
347
+ for (const [key, item] of Object.entries(value)) {
348
+ assertJsonSerializable(item, context, path ? `${path}.${key}` : key);
349
+ }
350
+ return;
351
+ }
352
+ throw new Error(`${context}: value at ${path || "root"} is not JSON-serializable`);
353
+ }
354
+
285
355
  export function formatAuthRequiredMessage(
286
356
  config: Pick<McpConfig, "settings">,
287
357
  serverName: string,
@@ -1 +1 @@
1
- {"version":3,"file":"anysearch.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/anysearch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAmBrE,UAAU,sBAAuB,SAAQ,aAAa;IACrD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAiGD,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,cAAc,CAAC,CAkEtH","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 { redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst ANYSEARCH_API_URL = \"https://api.anysearch.com/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tanysearchApiKey?: unknown;\n}\n\ninterface AnySearchResult {\n\ttitle: string;\n\turl: string;\n\tsnippet: string;\n\tcontent: string;\n}\n\ninterface AnySearchSearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\ninterface AnySearchResponse {\n\tcode: 0;\n\tdata: {\n\t\tresults: AnySearchResult[];\n\t\tmetadata: Record<string, unknown>;\n\t};\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 | null> {\n\treturn resolveCredential({\n\t\tprovider: \"AnySearch\",\n\t\tconfiguredValue: loadConfig().anysearchApiKey,\n\t\tenvironmentValue: process.env.ANYSEARCH_API_KEY,\n\t\tsignal,\n\t});\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 errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`AnySearch API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): AnySearchResponse {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow invalidResponse(\"expected an object envelope\");\n\t}\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.code !== 0) throw invalidResponse(\"expected code 0\");\n\tif (!envelope.data || typeof envelope.data !== \"object\" || Array.isArray(envelope.data)) {\n\t\tthrow invalidResponse(\"expected data object\");\n\t}\n\tconst data = envelope.data as Record<string, unknown>;\n\tif (!Array.isArray(data.results)) throw invalidResponse(\"expected data.results array\");\n\tif (!data.metadata || typeof data.metadata !== \"object\" || Array.isArray(data.metadata)) {\n\t\tthrow invalidResponse(\"expected data.metadata object\");\n\t}\n\n\tconst results: AnySearchResult[] = [];\n\tfor (const [index, value] of data.results.entries()) {\n\t\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\t\tthrow invalidResponse(`expected data.results[${index}] object`);\n\t\t}\n\t\tconst result = value as Record<string, unknown>;\n\t\tconst { title, url, snippet, content } = result;\n\t\tif (typeof title !== \"string\") throw invalidResponse(`expected data.results[${index}].title string`);\n\t\tif (typeof url !== \"string\") throw invalidResponse(`expected data.results[${index}].url string`);\n\t\tif (typeof snippet !== \"string\") throw invalidResponse(`expected data.results[${index}].snippet string`);\n\t\tif (typeof content !== \"string\") throw invalidResponse(`expected data.results[${index}].content string`);\n\t\tif (!url) throw invalidResponse(`expected data.results[${index}].url to be non-empty`);\n\t\tresults.push({ title, url, snippet, content });\n\t}\n\n\treturn { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results\n\t\t.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\t.join(\"\\n\\n\");\n}\n\nexport function isAnySearchAvailable(): boolean {\n\treturn true;\n}\n\nexport async function searchWithAnySearch(query: string, options: AnySearchSearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body = { query, max_results: numResults };\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(ANYSEARCH_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...(apiKey ? { 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: 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} 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(`AnySearch API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\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(`AnySearch API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tlet data: AnySearchResponse;\n\ttry {\n\t\tdata = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results = data.data.results.slice(0, numResults).map((result) => ({\n\t\ttitle: result.title,\n\t\turl: result.url,\n\t\tsnippet: result.snippet,\n\t}));\n\tconst mapped: SearchResponse = { answer: buildAnswer(results), results };\n\tif (options.includeContent) {\n\t\tconst inlineContent: ExtractedContent[] = data.data.results.slice(0, numResults)\n\t\t\t.filter(result => result.content.length > 0)\n\t\t\t.map(result => ({ url: result.url, title: result.title, content: result.content, error: null }));\n\t\tif (inlineContent.length > 0) mapped.inlineContent = inlineContent;\n\t}\n\treturn mapped;\n}\n"]}
1
+ {"version":3,"file":"anysearch.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/anysearch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAmBrE,UAAU,sBAAuB,SAAQ,aAAa;IACrD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAmGD,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,cAAc,CAAC,CAkEtH","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 { redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst ANYSEARCH_API_URL = \"https://api.anysearch.com/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tanysearchApiKey?: unknown;\n}\n\ninterface AnySearchResult {\n\ttitle: string;\n\turl: string;\n\tsnippet: string;\n\tcontent: string;\n}\n\ninterface AnySearchSearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\ninterface AnySearchResponse {\n\tcode: 0;\n\tdata: {\n\t\tresults: AnySearchResult[];\n\t\tmetadata: Record<string, unknown>;\n\t};\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 | null> {\n\treturn resolveCredential({\n\t\tprovider: \"AnySearch\",\n\t\tconfiguredValue: loadConfig().anysearchApiKey,\n\t\tenvironmentValue: process.env.ANYSEARCH_API_KEY,\n\t\tsignal,\n\t});\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 errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`AnySearch API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): AnySearchResponse {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow invalidResponse(\"expected an object envelope\");\n\t}\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.code !== 0) throw invalidResponse(\"expected code 0\");\n\tif (!envelope.data || typeof envelope.data !== \"object\" || Array.isArray(envelope.data)) {\n\t\tthrow invalidResponse(\"expected data object\");\n\t}\n\tconst data = envelope.data as Record<string, unknown>;\n\tif (!Array.isArray(data.results)) throw invalidResponse(\"expected data.results array\");\n\tif (!data.metadata || typeof data.metadata !== \"object\" || Array.isArray(data.metadata)) {\n\t\tthrow invalidResponse(\"expected data.metadata object\");\n\t}\n\n\tconst results: AnySearchResult[] = [];\n\tfor (const [index, value] of data.results.entries()) {\n\t\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\t\tthrow invalidResponse(`expected data.results[${index}] object`);\n\t\t}\n\t\tconst result = value as Record<string, unknown>;\n\t\tconst { title, url, snippet, content } = result;\n\t\tif (typeof title !== \"string\") throw invalidResponse(`expected data.results[${index}].title string`);\n\t\tif (typeof url !== \"string\") throw invalidResponse(`expected data.results[${index}].url string`);\n\t\tif (typeof snippet !== \"string\") throw invalidResponse(`expected data.results[${index}].snippet string`);\n\t\tif (content !== undefined && content !== null && typeof content !== \"string\") {\n\t\t\tthrow invalidResponse(`expected data.results[${index}].content string`);\n\t\t}\n\t\tif (!url) throw invalidResponse(`expected data.results[${index}].url to be non-empty`);\n\t\tresults.push({ title, url, snippet, content: typeof content === \"string\" ? content : \"\" });\n\t}\n\n\treturn { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results\n\t\t.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\t.join(\"\\n\\n\");\n}\n\nexport function isAnySearchAvailable(): boolean {\n\treturn true;\n}\n\nexport async function searchWithAnySearch(query: string, options: AnySearchSearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body = { query, max_results: numResults };\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(ANYSEARCH_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...(apiKey ? { 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: 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} 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(`AnySearch API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\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(`AnySearch API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tlet data: AnySearchResponse;\n\ttry {\n\t\tdata = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results = data.data.results.slice(0, numResults).map((result) => ({\n\t\ttitle: result.title,\n\t\turl: result.url,\n\t\tsnippet: result.snippet,\n\t}));\n\tconst mapped: SearchResponse = { answer: buildAnswer(results), results };\n\tif (options.includeContent) {\n\t\tconst inlineContent: ExtractedContent[] = data.data.results.slice(0, numResults)\n\t\t\t.filter(result => result.content.length > 0)\n\t\t\t.map(result => ({ url: result.url, title: result.title, content: result.content, error: null }));\n\t\tif (inlineContent.length > 0) mapped.inlineContent = inlineContent;\n\t}\n\treturn mapped;\n}\n"]}
@@ -76,11 +76,12 @@ function parseResponse(value) {
76
76
  throw invalidResponse(`expected data.results[${index}].url string`);
77
77
  if (typeof snippet !== "string")
78
78
  throw invalidResponse(`expected data.results[${index}].snippet string`);
79
- if (typeof content !== "string")
79
+ if (content !== undefined && content !== null && typeof content !== "string") {
80
80
  throw invalidResponse(`expected data.results[${index}].content string`);
81
+ }
81
82
  if (!url)
82
83
  throw invalidResponse(`expected data.results[${index}].url to be non-empty`);
83
- results.push({ title, url, snippet, content });
84
+ results.push({ title, url, snippet, content: typeof content === "string" ? content : "" });
84
85
  }
85
86
  return { code: 0, data: { results, metadata: data.metadata } };
86
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"anysearch.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/anysearch.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 { redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst ANYSEARCH_API_URL = \"https://api.anysearch.com/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tanysearchApiKey?: unknown;\n}\n\ninterface AnySearchResult {\n\ttitle: string;\n\turl: string;\n\tsnippet: string;\n\tcontent: string;\n}\n\ninterface AnySearchSearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\ninterface AnySearchResponse {\n\tcode: 0;\n\tdata: {\n\t\tresults: AnySearchResult[];\n\t\tmetadata: Record<string, unknown>;\n\t};\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 | null> {\n\treturn resolveCredential({\n\t\tprovider: \"AnySearch\",\n\t\tconfiguredValue: loadConfig().anysearchApiKey,\n\t\tenvironmentValue: process.env.ANYSEARCH_API_KEY,\n\t\tsignal,\n\t});\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 errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`AnySearch API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): AnySearchResponse {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow invalidResponse(\"expected an object envelope\");\n\t}\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.code !== 0) throw invalidResponse(\"expected code 0\");\n\tif (!envelope.data || typeof envelope.data !== \"object\" || Array.isArray(envelope.data)) {\n\t\tthrow invalidResponse(\"expected data object\");\n\t}\n\tconst data = envelope.data as Record<string, unknown>;\n\tif (!Array.isArray(data.results)) throw invalidResponse(\"expected data.results array\");\n\tif (!data.metadata || typeof data.metadata !== \"object\" || Array.isArray(data.metadata)) {\n\t\tthrow invalidResponse(\"expected data.metadata object\");\n\t}\n\n\tconst results: AnySearchResult[] = [];\n\tfor (const [index, value] of data.results.entries()) {\n\t\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\t\tthrow invalidResponse(`expected data.results[${index}] object`);\n\t\t}\n\t\tconst result = value as Record<string, unknown>;\n\t\tconst { title, url, snippet, content } = result;\n\t\tif (typeof title !== \"string\") throw invalidResponse(`expected data.results[${index}].title string`);\n\t\tif (typeof url !== \"string\") throw invalidResponse(`expected data.results[${index}].url string`);\n\t\tif (typeof snippet !== \"string\") throw invalidResponse(`expected data.results[${index}].snippet string`);\n\t\tif (typeof content !== \"string\") throw invalidResponse(`expected data.results[${index}].content string`);\n\t\tif (!url) throw invalidResponse(`expected data.results[${index}].url to be non-empty`);\n\t\tresults.push({ title, url, snippet, content });\n\t}\n\n\treturn { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results\n\t\t.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\t.join(\"\\n\\n\");\n}\n\nexport function isAnySearchAvailable(): boolean {\n\treturn true;\n}\n\nexport async function searchWithAnySearch(query: string, options: AnySearchSearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body = { query, max_results: numResults };\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(ANYSEARCH_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...(apiKey ? { 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: 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} 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(`AnySearch API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\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(`AnySearch API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tlet data: AnySearchResponse;\n\ttry {\n\t\tdata = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results = data.data.results.slice(0, numResults).map((result) => ({\n\t\ttitle: result.title,\n\t\turl: result.url,\n\t\tsnippet: result.snippet,\n\t}));\n\tconst mapped: SearchResponse = { answer: buildAnswer(results), results };\n\tif (options.includeContent) {\n\t\tconst inlineContent: ExtractedContent[] = data.data.results.slice(0, numResults)\n\t\t\t.filter(result => result.content.length > 0)\n\t\t\t.map(result => ({ url: result.url, title: result.title, content: result.content, error: null }));\n\t\tif (inlineContent.length > 0) mapped.inlineContent = inlineContent;\n\t}\n\treturn mapped;\n}\n"]}
1
+ {"version":3,"file":"anysearch.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/anysearch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,iBAAiB,GAAG,qCAAqC,CAAC;AAChE,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAC;AAC7C,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAyBjC,IAAI,YAAY,GAA2B,IAAI,CAAC;AAEhD,SAAS,UAAU,GAAoB;IACtC,IAAI,YAAY;QAAE,OAAO,YAAY,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9B,YAAY,GAAG,EAAE,CAAC;QAClB,OAAO,YAAY,CAAC;IACrB,CAAC;IAED,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC/C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,mBAAmB,WAAW,KAAK,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,qBAAqB,WAAW,0BAA0B,CAAC,CAAC;IAC7E,CAAC;IACD,YAAY,GAAG,MAAyB,CAAC;IACzC,OAAO,YAAY,CAAC;AAAA,CACpB;AAED,KAAK,UAAU,SAAS,CAAC,MAAoB,EAA0B;IACtE,OAAO,iBAAiB,CAAC;QACxB,QAAQ,EAAE,WAAW;QACrB,eAAe,EAAE,UAAU,EAAE,CAAC,eAAe;QAC7C,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC/C,MAAM;KACN,CAAC,CAAC;AAAA,CACH;AAED,SAAS,cAAc,CAAC,KAAyB,EAAU;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACnE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,CACpD;AAED,SAAS,YAAY,CAAC,GAAY,EAAU;IAC3C,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAAA,CACxD;AAED,SAAS,eAAe,CAAC,OAAe,EAAS;IAChD,OAAO,IAAI,KAAK,CAAC,4CAA4C,OAAO,EAAE,CAAC,CAAC;AAAA,CACxE;AAED,SAAS,aAAa,CAAC,KAAc,EAAqB;IACzD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,eAAe,CAAC,6BAA6B,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,QAAQ,GAAG,KAAgC,CAAC;IAClD,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;QAAE,MAAM,eAAe,CAAC,iBAAiB,CAAC,CAAC;IAClE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzF,MAAM,eAAe,CAAC,sBAAsB,CAAC,CAAC;IAC/C,CAAC;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAA+B,CAAC;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,MAAM,eAAe,CAAC,6BAA6B,CAAC,CAAC;IACvF,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzF,MAAM,eAAe,CAAC,+BAA+B,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACrD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjE,MAAM,eAAe,CAAC,yBAAyB,KAAK,UAAU,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,MAAM,GAAG,KAAgC,CAAC;QAChD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,MAAM,eAAe,CAAC,yBAAyB,KAAK,gBAAgB,CAAC,CAAC;QACrG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,MAAM,eAAe,CAAC,yBAAyB,KAAK,cAAc,CAAC,CAAC;QACjG,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,MAAM,eAAe,CAAC,yBAAyB,KAAK,kBAAkB,CAAC,CAAC;QACzG,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC9E,MAAM,eAAe,CAAC,yBAAyB,KAAK,kBAAkB,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,CAAC,GAAG;YAAE,MAAM,eAAe,CAAC,yBAAyB,KAAK,uBAAuB,CAAC,CAAC;QACvF,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAmC,EAAE,EAAE,CAAC;AAAA,CAC1F;AAED,SAAS,WAAW,CAAC,OAAkC,EAAU;IAChE,OAAO,OAAO;SACZ,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO;QAC9B,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,aAAa,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG;QAC9D,CAAC,CAAC,WAAW,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,GAAG,GAAG,CAAC;SAC5C,IAAI,CAAC,MAAM,CAAC,CAAC;AAAA,CACf;AAED,MAAM,UAAU,oBAAoB,GAAY;IAC/C,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,KAAa,EAAE,OAAO,GAA2B,EAAE,EAA2B;IACvH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;IAChD,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,IAAI,QAAkB,CAAC;IAEvB,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,KAAK,CAAC,iBAAiB,EAAE;YACzC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACR,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,cAAc,EAAE,kBAAkB;aAClC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;gBACrB,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;gBAC3E,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC;SACzC,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,eAAe,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,eAAe,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;;YAC3F,eAAe,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;QAC3D,IAAI,eAAe,KAAK,OAAO;YAAE,MAAM,GAAG,CAAC;QAC3C,MAAM,aAAa,GAAG,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACjD,IAAI,GAAG,YAAY,KAAK;YAAE,aAAa,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACxD,MAAM,aAAa,CAAC;IACrB,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,wCAAwC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,IAAuB,CAAC;IAC5B,IAAI,CAAC;QACJ,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,MAAM,GAAG,CAAC;IACX,CAAC;IAED,eAAe,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACvE,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,OAAO,EAAE,MAAM,CAAC,OAAO;KACvB,CAAC,CAAC,CAAC;IACJ,MAAM,MAAM,GAAmB,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC;IACzE,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC5B,MAAM,aAAa,GAAuB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC;aAC9E,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;aAC3C,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAClG,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,aAAa,GAAG,aAAa,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd","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 { redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst ANYSEARCH_API_URL = \"https://api.anysearch.com/v1/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tanysearchApiKey?: unknown;\n}\n\ninterface AnySearchResult {\n\ttitle: string;\n\turl: string;\n\tsnippet: string;\n\tcontent: string;\n}\n\ninterface AnySearchSearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\ninterface AnySearchResponse {\n\tcode: 0;\n\tdata: {\n\t\tresults: AnySearchResult[];\n\t\tmetadata: Record<string, unknown>;\n\t};\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 | null> {\n\treturn resolveCredential({\n\t\tprovider: \"AnySearch\",\n\t\tconfiguredValue: loadConfig().anysearchApiKey,\n\t\tenvironmentValue: process.env.ANYSEARCH_API_KEY,\n\t\tsignal,\n\t});\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 errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction invalidResponse(message: string): Error {\n\treturn new Error(`AnySearch API returned invalid response: ${message}`);\n}\n\nfunction parseResponse(value: unknown): AnySearchResponse {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow invalidResponse(\"expected an object envelope\");\n\t}\n\tconst envelope = value as Record<string, unknown>;\n\tif (envelope.code !== 0) throw invalidResponse(\"expected code 0\");\n\tif (!envelope.data || typeof envelope.data !== \"object\" || Array.isArray(envelope.data)) {\n\t\tthrow invalidResponse(\"expected data object\");\n\t}\n\tconst data = envelope.data as Record<string, unknown>;\n\tif (!Array.isArray(data.results)) throw invalidResponse(\"expected data.results array\");\n\tif (!data.metadata || typeof data.metadata !== \"object\" || Array.isArray(data.metadata)) {\n\t\tthrow invalidResponse(\"expected data.metadata object\");\n\t}\n\n\tconst results: AnySearchResult[] = [];\n\tfor (const [index, value] of data.results.entries()) {\n\t\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\t\tthrow invalidResponse(`expected data.results[${index}] object`);\n\t\t}\n\t\tconst result = value as Record<string, unknown>;\n\t\tconst { title, url, snippet, content } = result;\n\t\tif (typeof title !== \"string\") throw invalidResponse(`expected data.results[${index}].title string`);\n\t\tif (typeof url !== \"string\") throw invalidResponse(`expected data.results[${index}].url string`);\n\t\tif (typeof snippet !== \"string\") throw invalidResponse(`expected data.results[${index}].snippet string`);\n\t\tif (content !== undefined && content !== null && typeof content !== \"string\") {\n\t\t\tthrow invalidResponse(`expected data.results[${index}].content string`);\n\t\t}\n\t\tif (!url) throw invalidResponse(`expected data.results[${index}].url to be non-empty`);\n\t\tresults.push({ title, url, snippet, content: typeof content === \"string\" ? content : \"\" });\n\t}\n\n\treturn { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };\n}\n\nfunction buildAnswer(results: SearchResponse[\"results\"]): string {\n\treturn results\n\t\t.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\t.join(\"\\n\\n\");\n}\n\nexport function isAnySearchAvailable(): boolean {\n\treturn true;\n}\n\nexport async function searchWithAnySearch(query: string, options: AnySearchSearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body = { query, max_results: numResults };\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\n\ttry {\n\t\tresponse = await fetch(ANYSEARCH_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...(apiKey ? { 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: 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} 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(`AnySearch API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\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(`AnySearch API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tlet data: AnySearchResponse;\n\ttry {\n\t\tdata = parseResponse(rawData);\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow err;\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst results = data.data.results.slice(0, numResults).map((result) => ({\n\t\ttitle: result.title,\n\t\turl: result.url,\n\t\tsnippet: result.snippet,\n\t}));\n\tconst mapped: SearchResponse = { answer: buildAnswer(results), results };\n\tif (options.includeContent) {\n\t\tconst inlineContent: ExtractedContent[] = data.data.results.slice(0, numResults)\n\t\t\t.filter(result => result.content.length > 0)\n\t\t\t.map(result => ({ url: result.url, title: result.title, content: result.content, error: null }));\n\t\tif (inlineContent.length > 0) mapped.inlineContent = inlineContent;\n\t}\n\treturn mapped;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"brave.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/brave.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAqHnF,wBAAgB,gBAAgB,IAAI,OAAO,CAM1C;AAED,wBAAsB,eAAe,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GACzB,OAAO,CAAC,cAAc,CAAC,CAuFzB","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchOptions, SearchResult, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst BRAVE_API_URL = \"https://api.search.brave.com/res/v1/web/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tbraveApiKey?: unknown;\n}\n\ninterface NormalizedDomainFilters {\n\tallowed: string[];\n\tblocked: 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\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t\tsignal,\n\t});\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\tif (!domainFilter?.length) return filters;\n\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\n\treturn filters;\n}\n\nfunction buildBraveQuery(query: string, domainFilter: string[] | undefined): string {\n\tconst filters = normalizeDomainFilters(domainFilter);\n\tconst parts = [query];\n\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\n\tfor (const domain of filters.blocked) {\n\t\tparts.push(`NOT site:${domain}`);\n\t}\n\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\n\tlet hostname = \"\";\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\n\tif (filters.allowed.length > 0 && !filters.allowed.some(domain => hostMatchesDomain(hostname, domain))) {\n\t\treturn false;\n\t}\n\n\treturn !filters.blocked.some(domain => hostMatchesDomain(hostname, domain));\n}\n\nexport function isBraveAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t});\n}\n\nexport async function searchWithBrave(\n\tquery: string,\n\toptions: SearchOptions = {},\n): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Brave Search API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"braveApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set BRAVE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://brave.com/search/api/\",\n\t\t);\n\t}\n\n\tconst numResults = normalizeCount(options.numResults);\n\tconst domainFilters = normalizeDomainFilters(options.domainFilter);\n\tconst searchQuery = buildBraveQuery(query, options.domainFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query: searchQuery });\n\tconst params = new URLSearchParams({\n\t\tq: searchQuery,\n\t\tcount: String(options.domainFilter?.length ? 20 : numResults),\n\t});\n\n\tif (options.recencyFilter) {\n\t\tconst freshnessMap: Record<string, string> = {\n\t\t\tday: \"pd\",\n\t\t\tweek: \"pw\",\n\t\t\tmonth: \"pm\",\n\t\t\tyear: \"py\",\n\t\t};\n\t\tconst freshness = freshnessMap[options.recencyFilter];\n\t\tif (freshness) params.set(\"freshness\", freshness);\n\t}\n\n\ttry {\n\t\tconst response = await fetch(`${BRAVE_API_URL}?${params.toString()}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: {\n\t\t\t\t\"X-Subscription-Token\": apiKey,\n\t\t\t\t\"Accept\": \"application/json\",\n\t\t\t\t\"Accept-Encoding\": \"gzip\",\n\t\t\t},\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\n\t\tif (!response.ok) {\n\t\t\tactivityMonitor.logError(activityId, `HTTP ${response.status}`);\n\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\tthrow new Error(`Brave Search API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await response.json() as {\n\t\t\tweb?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n\t\t};\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\n\t\tconst results: SearchResult[] = [];\n\t\tfor (const item of data.web?.results ?? []) {\n\t\t\tif (!item.url || !matchesDomainFilters(item.url, domainFilters)) continue;\n\t\t\tresults.push({\n\t\t\t\ttitle: item.title || item.url,\n\t\t\t\turl: item.url,\n\t\t\t\tsnippet: item.description || \"\",\n\t\t\t});\n\t\t\tif (results.length >= numResults) break;\n\t\t}\n\n\t\tconst answer = results\n\t\t\t.map((result) => {\n\t\t\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\t\t\treturn `Source: ${result.title} (${result.url})`;\n\t\t\t})\n\t\t\t.join(\"\\n\\n\");\n\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, redactedMessage);\n\t\t}\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":"brave.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/brave.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAgInF,wBAAgB,gBAAgB,IAAI,OAAO,CAM1C;AAED,wBAAsB,eAAe,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GACzB,OAAO,CAAC,cAAc,CAAC,CAwFzB","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchOptions, SearchResult, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from \"./utils.ts\";\n\nconst BRAVE_API_BASE_URL = \"https://api.search.brave.com/res/v1\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tbraveApiKey?: unknown;\n\tbraveBaseUrl?: unknown;\n}\n\ninterface NormalizedDomainFilters {\n\tallowed: string[];\n\tblocked: 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\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t\tsignal,\n\t});\n}\n\nfunction getApiUrl(): string {\n\treturn `${resolveApiBaseUrl({\n\t\tconfigKey: \"braveBaseUrl\",\n\t\tconfiguredValue: loadConfig().braveBaseUrl,\n\t\tdefaultValue: BRAVE_API_BASE_URL,\n\t\tenvironmentKey: \"BRAVE_BASE_URL\",\n\t\tenvironmentValue: process.env.BRAVE_BASE_URL,\n\t})}/web/search`;\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\tif (!domainFilter?.length) return filters;\n\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\n\treturn filters;\n}\n\nfunction buildBraveQuery(query: string, domainFilter: string[] | undefined): string {\n\tconst filters = normalizeDomainFilters(domainFilter);\n\tconst parts = [query];\n\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\n\tfor (const domain of filters.blocked) {\n\t\tparts.push(`NOT site:${domain}`);\n\t}\n\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\n\tlet hostname = \"\";\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\n\tif (filters.allowed.length > 0 && !filters.allowed.some(domain => hostMatchesDomain(hostname, domain))) {\n\t\treturn false;\n\t}\n\n\treturn !filters.blocked.some(domain => hostMatchesDomain(hostname, domain));\n}\n\nexport function isBraveAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t});\n}\n\nexport async function searchWithBrave(\n\tquery: string,\n\toptions: SearchOptions = {},\n): Promise<SearchResponse> {\n\tconst apiUrl = getApiUrl();\n\tconst apiKey = await getApiKey(options.signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Brave Search API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"braveApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set BRAVE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://brave.com/search/api/\",\n\t\t);\n\t}\n\n\tconst numResults = normalizeCount(options.numResults);\n\tconst domainFilters = normalizeDomainFilters(options.domainFilter);\n\tconst searchQuery = buildBraveQuery(query, options.domainFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query: searchQuery });\n\tconst params = new URLSearchParams({\n\t\tq: searchQuery,\n\t\tcount: String(options.domainFilter?.length ? 20 : numResults),\n\t});\n\n\tif (options.recencyFilter) {\n\t\tconst freshnessMap: Record<string, string> = {\n\t\t\tday: \"pd\",\n\t\t\tweek: \"pw\",\n\t\t\tmonth: \"pm\",\n\t\t\tyear: \"py\",\n\t\t};\n\t\tconst freshness = freshnessMap[options.recencyFilter];\n\t\tif (freshness) params.set(\"freshness\", freshness);\n\t}\n\n\ttry {\n\t\tconst response = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: {\n\t\t\t\t\"X-Subscription-Token\": apiKey,\n\t\t\t\t\"Accept\": \"application/json\",\n\t\t\t\t\"Accept-Encoding\": \"gzip\",\n\t\t\t},\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}, [\"X-Subscription-Token\"]);\n\n\t\tif (!response.ok) {\n\t\t\tactivityMonitor.logError(activityId, `HTTP ${response.status}`);\n\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\tthrow new Error(`Brave Search API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await response.json() as {\n\t\t\tweb?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n\t\t};\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\n\t\tconst results: SearchResult[] = [];\n\t\tfor (const item of data.web?.results ?? []) {\n\t\t\tif (!item.url || !matchesDomainFilters(item.url, domainFilters)) continue;\n\t\t\tresults.push({\n\t\t\t\ttitle: item.title || item.url,\n\t\t\t\turl: item.url,\n\t\t\t\tsnippet: item.description || \"\",\n\t\t\t});\n\t\t\tif (results.length >= numResults) break;\n\t\t}\n\n\t\tconst answer = results\n\t\t\t.map((result) => {\n\t\t\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\t\t\treturn `Source: ${result.title} (${result.url})`;\n\t\t\t})\n\t\t\t.join(\"\\n\\n\");\n\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, redactedMessage);\n\t\t}\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,8 +1,8 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.js";
3
3
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
4
- import { getWebSearchConfigPath } from "./utils.js";
5
- const BRAVE_API_URL = "https://api.search.brave.com/res/v1/web/search";
4
+ import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.js";
5
+ const BRAVE_API_BASE_URL = "https://api.search.brave.com/res/v1";
6
6
  const CONFIG_PATH = getWebSearchConfigPath();
7
7
  const SEARCH_TIMEOUT_MS = 30_000;
8
8
  let cachedConfig = null;
@@ -31,6 +31,15 @@ async function getApiKey(signal) {
31
31
  signal,
32
32
  });
33
33
  }
34
+ function getApiUrl() {
35
+ return `${resolveApiBaseUrl({
36
+ configKey: "braveBaseUrl",
37
+ configuredValue: loadConfig().braveBaseUrl,
38
+ defaultValue: BRAVE_API_BASE_URL,
39
+ environmentKey: "BRAVE_BASE_URL",
40
+ environmentValue: process.env.BRAVE_BASE_URL,
41
+ })}/web/search`;
42
+ }
34
43
  function normalizeCount(value) {
35
44
  if (typeof value !== "number" || !Number.isFinite(value))
36
45
  return 5;
@@ -108,6 +117,7 @@ export function isBraveAvailable() {
108
117
  });
109
118
  }
110
119
  export async function searchWithBrave(query, options = {}) {
120
+ const apiUrl = getApiUrl();
111
121
  const apiKey = await getApiKey(options.signal);
112
122
  if (!apiKey) {
113
123
  throw new Error("Brave Search API key not found. Either:\n" +
@@ -135,7 +145,7 @@ export async function searchWithBrave(query, options = {}) {
135
145
  params.set("freshness", freshness);
136
146
  }
137
147
  try {
138
- const response = await fetch(`${BRAVE_API_URL}?${params.toString()}`, {
148
+ const response = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {
139
149
  method: "GET",
140
150
  headers: {
141
151
  "X-Subscription-Token": apiKey,
@@ -145,7 +155,7 @@ export async function searchWithBrave(query, options = {}) {
145
155
  signal: options.signal
146
156
  ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])
147
157
  : AbortSignal.timeout(SEARCH_TIMEOUT_MS),
148
- });
158
+ }, ["X-Subscription-Token"]);
149
159
  if (!response.ok) {
150
160
  activityMonitor.logError(activityId, `HTTP ${response.status}`);
151
161
  const errorText = redactCredential(await response.text(), apiKey);
@@ -1 +1 @@
1
- {"version":3,"file":"brave.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/brave.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchOptions, SearchResult, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst BRAVE_API_URL = \"https://api.search.brave.com/res/v1/web/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tbraveApiKey?: unknown;\n}\n\ninterface NormalizedDomainFilters {\n\tallowed: string[];\n\tblocked: 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\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t\tsignal,\n\t});\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\tif (!domainFilter?.length) return filters;\n\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\n\treturn filters;\n}\n\nfunction buildBraveQuery(query: string, domainFilter: string[] | undefined): string {\n\tconst filters = normalizeDomainFilters(domainFilter);\n\tconst parts = [query];\n\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\n\tfor (const domain of filters.blocked) {\n\t\tparts.push(`NOT site:${domain}`);\n\t}\n\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\n\tlet hostname = \"\";\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\n\tif (filters.allowed.length > 0 && !filters.allowed.some(domain => hostMatchesDomain(hostname, domain))) {\n\t\treturn false;\n\t}\n\n\treturn !filters.blocked.some(domain => hostMatchesDomain(hostname, domain));\n}\n\nexport function isBraveAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t});\n}\n\nexport async function searchWithBrave(\n\tquery: string,\n\toptions: SearchOptions = {},\n): Promise<SearchResponse> {\n\tconst apiKey = await getApiKey(options.signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Brave Search API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"braveApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set BRAVE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://brave.com/search/api/\",\n\t\t);\n\t}\n\n\tconst numResults = normalizeCount(options.numResults);\n\tconst domainFilters = normalizeDomainFilters(options.domainFilter);\n\tconst searchQuery = buildBraveQuery(query, options.domainFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query: searchQuery });\n\tconst params = new URLSearchParams({\n\t\tq: searchQuery,\n\t\tcount: String(options.domainFilter?.length ? 20 : numResults),\n\t});\n\n\tif (options.recencyFilter) {\n\t\tconst freshnessMap: Record<string, string> = {\n\t\t\tday: \"pd\",\n\t\t\tweek: \"pw\",\n\t\t\tmonth: \"pm\",\n\t\t\tyear: \"py\",\n\t\t};\n\t\tconst freshness = freshnessMap[options.recencyFilter];\n\t\tif (freshness) params.set(\"freshness\", freshness);\n\t}\n\n\ttry {\n\t\tconst response = await fetch(`${BRAVE_API_URL}?${params.toString()}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: {\n\t\t\t\t\"X-Subscription-Token\": apiKey,\n\t\t\t\t\"Accept\": \"application/json\",\n\t\t\t\t\"Accept-Encoding\": \"gzip\",\n\t\t\t},\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\n\t\tif (!response.ok) {\n\t\t\tactivityMonitor.logError(activityId, `HTTP ${response.status}`);\n\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\tthrow new Error(`Brave Search API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await response.json() as {\n\t\t\tweb?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n\t\t};\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\n\t\tconst results: SearchResult[] = [];\n\t\tfor (const item of data.web?.results ?? []) {\n\t\t\tif (!item.url || !matchesDomainFilters(item.url, domainFilters)) continue;\n\t\t\tresults.push({\n\t\t\t\ttitle: item.title || item.url,\n\t\t\t\turl: item.url,\n\t\t\t\tsnippet: item.description || \"\",\n\t\t\t});\n\t\t\tif (results.length >= numResults) break;\n\t\t}\n\n\t\tconst answer = results\n\t\t\t.map((result) => {\n\t\t\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\t\t\treturn `Source: ${result.title} (${result.url})`;\n\t\t\t})\n\t\t\t.join(\"\\n\\n\");\n\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, redactedMessage);\n\t\t}\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":"brave.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/brave.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { SearchOptions, SearchResult, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from \"./utils.ts\";\n\nconst BRAVE_API_BASE_URL = \"https://api.search.brave.com/res/v1\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 30_000;\n\ninterface WebSearchConfig {\n\tbraveApiKey?: unknown;\n\tbraveBaseUrl?: unknown;\n}\n\ninterface NormalizedDomainFilters {\n\tallowed: string[];\n\tblocked: 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\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t\tsignal,\n\t});\n}\n\nfunction getApiUrl(): string {\n\treturn `${resolveApiBaseUrl({\n\t\tconfigKey: \"braveBaseUrl\",\n\t\tconfiguredValue: loadConfig().braveBaseUrl,\n\t\tdefaultValue: BRAVE_API_BASE_URL,\n\t\tenvironmentKey: \"BRAVE_BASE_URL\",\n\t\tenvironmentValue: process.env.BRAVE_BASE_URL,\n\t})}/web/search`;\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\tif (!domainFilter?.length) return filters;\n\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\n\treturn filters;\n}\n\nfunction buildBraveQuery(query: string, domainFilter: string[] | undefined): string {\n\tconst filters = normalizeDomainFilters(domainFilter);\n\tconst parts = [query];\n\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\n\tfor (const domain of filters.blocked) {\n\t\tparts.push(`NOT site:${domain}`);\n\t}\n\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\n\tlet hostname = \"\";\n\ttry {\n\t\thostname = new URL(url).hostname.toLowerCase();\n\t} catch {\n\t\treturn false;\n\t}\n\n\tif (filters.allowed.length > 0 && !filters.allowed.some(domain => hostMatchesDomain(hostname, domain))) {\n\t\treturn false;\n\t}\n\n\treturn !filters.blocked.some(domain => hostMatchesDomain(hostname, domain));\n}\n\nexport function isBraveAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Brave\",\n\t\tconfiguredValue: loadConfig().braveApiKey,\n\t\tenvironmentValue: process.env.BRAVE_API_KEY,\n\t});\n}\n\nexport async function searchWithBrave(\n\tquery: string,\n\toptions: SearchOptions = {},\n): Promise<SearchResponse> {\n\tconst apiUrl = getApiUrl();\n\tconst apiKey = await getApiKey(options.signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Brave Search API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"braveApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set BRAVE_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://brave.com/search/api/\",\n\t\t);\n\t}\n\n\tconst numResults = normalizeCount(options.numResults);\n\tconst domainFilters = normalizeDomainFilters(options.domainFilter);\n\tconst searchQuery = buildBraveQuery(query, options.domainFilter);\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query: searchQuery });\n\tconst params = new URLSearchParams({\n\t\tq: searchQuery,\n\t\tcount: String(options.domainFilter?.length ? 20 : numResults),\n\t});\n\n\tif (options.recencyFilter) {\n\t\tconst freshnessMap: Record<string, string> = {\n\t\t\tday: \"pd\",\n\t\t\tweek: \"pw\",\n\t\t\tmonth: \"pm\",\n\t\t\tyear: \"py\",\n\t\t};\n\t\tconst freshness = freshnessMap[options.recencyFilter];\n\t\tif (freshness) params.set(\"freshness\", freshness);\n\t}\n\n\ttry {\n\t\tconst response = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {\n\t\t\tmethod: \"GET\",\n\t\t\theaders: {\n\t\t\t\t\"X-Subscription-Token\": apiKey,\n\t\t\t\t\"Accept\": \"application/json\",\n\t\t\t\t\"Accept-Encoding\": \"gzip\",\n\t\t\t},\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}, [\"X-Subscription-Token\"]);\n\n\t\tif (!response.ok) {\n\t\t\tactivityMonitor.logError(activityId, `HTTP ${response.status}`);\n\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\tthrow new Error(`Brave Search API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await response.json() as {\n\t\t\tweb?: { results?: Array<{ title?: string; url?: string; description?: string }> };\n\t\t};\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\n\t\tconst results: SearchResult[] = [];\n\t\tfor (const item of data.web?.results ?? []) {\n\t\t\tif (!item.url || !matchesDomainFilters(item.url, domainFilters)) continue;\n\t\t\tresults.push({\n\t\t\t\ttitle: item.title || item.url,\n\t\t\t\turl: item.url,\n\t\t\t\tsnippet: item.description || \"\",\n\t\t\t});\n\t\t\tif (results.length >= numResults) break;\n\t\t}\n\n\t\tconst answer = results\n\t\t\t.map((result) => {\n\t\t\t\tif (result.snippet) return `${result.snippet}\\nSource: ${result.title} (${result.url})`;\n\t\t\t\treturn `Source: ${result.title} (${result.url})`;\n\t\t\t})\n\t\t\t.join(\"\\n\\n\");\n\n\t\treturn { answer, results };\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, redactedMessage);\n\t\t}\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"]}
@@ -3,6 +3,7 @@ export declare function generateCuratorPage(queries: string[], sessionToken: str
3
3
  openai: boolean;
4
4
  brave: boolean;
5
5
  parallel: boolean;
6
+ "parallel-mcp": boolean;
6
7
  tinyfish: boolean;
7
8
  search1api: boolean;
8
9
  searchinfinity: boolean;
@@ -23,6 +24,8 @@ export declare function generateCuratorPage(queries: string[], sessionToken: str
23
24
  xai: boolean;
24
25
  brightdata: boolean;
25
26
  serpbase: boolean;
27
+ serper: boolean;
28
+ valyu: boolean;
26
29
  }, defaultProvider: string, searchProvider: string, summaryModels: Array<{
27
30
  value: string;
28
31
  label: string;