@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
@@ -9,6 +9,7 @@ export interface CuratorServerOptions {
9
9
  openai: boolean;
10
10
  brave: boolean;
11
11
  parallel: boolean;
12
+ "parallel-mcp": boolean;
12
13
  tinyfish: boolean;
13
14
  search1api: boolean;
14
15
  searchinfinity: boolean;
@@ -29,6 +30,8 @@ export interface CuratorServerOptions {
29
30
  xai: boolean;
30
31
  brightdata: boolean;
31
32
  serpbase: boolean;
33
+ serper: boolean;
34
+ valyu: boolean;
32
35
  };
33
36
  defaultProvider: string;
34
37
  searchProvider: string;
@@ -1 +1 @@
1
- {"version":3,"file":"curator-server.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/curator-server.ts"],"names":[],"mappings":"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","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { generateCuratorPage } from \"./curator-page.ts\";\nimport type { SummaryMeta } from \"./summary-review.ts\";\nimport { resolveCuratorNetworkConfig } from \"./utils.ts\";\n\nconst STALE_THRESHOLD_MS = 30000;\nconst WATCHDOG_INTERVAL_MS = 1000;\nconst MAX_BODY_SIZE = 64 * 1024;\n\ntype ServerState = \"SEARCHING\" | \"RESULT_SELECTION\" | \"COMPLETED\";\ntype CuratorResultEventData = IndexedCuratorSearchEntry & { slotIndex?: number };\ntype CuratorSearchErrorEventData = { queryIndex: number; query: string; error: string; provider?: string; slotIndex?: number };\ntype CuratorStoredEvent =\n\t| { event: \"result\"; data: CuratorResultEventData }\n\t| { event: \"search-error\"; data: CuratorSearchErrorEventData };\n\nexport interface CuratorServerOptions {\n\tqueries: string[];\n\tsessionToken: string;\n\ttimeout: number;\n\tavailableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean };\n\tdefaultProvider: string;\n\tsearchProvider: string;\n\tsummaryModels: Array<{ value: string; label: string }>;\n\tdefaultSummaryModel: string | null;\n}\n\nexport interface CuratorSearchEntry {\n\tanswer: string;\n\tresults: Array<{ title: string; url: string; domain: string; snippet?: string }>;\n\tprovider: string;\n\terror?: string;\n}\n\nexport interface IndexedCuratorSearchEntry extends CuratorSearchEntry {\n\tqueryIndex: number;\n\tquery: string;\n}\n\nexport interface CuratorServerCallbacks {\n\tonSubmit: (payload: { selectedQueryIndices: number[]; summary?: string; summaryMeta?: SummaryMeta; rawResults?: boolean }) => void;\n\tonCancel: (reason: \"user\" | \"timeout\" | \"stale\") => void;\n\tonProviderChange: (provider: string) => void;\n\tonAddSearch: (query: string, provider?: string) => Promise<CuratorSearchEntry[]>;\n\tonAddSearchResults: (entries: IndexedCuratorSearchEntry[]) => void;\n\tonSummarize: (\n\t\tselectedQueryIndices: number[],\n\t\tsignal: AbortSignal,\n\t\tmodel?: string,\n\t\tfeedback?: string,\n\t) => Promise<{ summary: string; meta: SummaryMeta }>;\n\tonRewriteQuery: (query: string, signal: AbortSignal) => Promise<string>;\n}\n\nexport interface CuratorServerHandle {\n\tserver: http.Server;\n\turl: string;\n\tclose: () => void;\n\tpushResult: (queryIndex: number, data: CuratorSearchEntry & { query?: string; slotIndex?: number }) => void;\n\tpushError: (queryIndex: number, error: string, provider?: string, meta?: { query?: string; slotIndex?: number }) => void;\n\tsearchesDone: () => void;\n\t/** Reports browser connection state so a cancelled search can surface WHY it\n\t * went stale (e.g. the browser never connected). */\n\tgetConnectionState: () => { browserConnected: boolean; lastHeartbeatAgeMs: number };\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n\tres.writeHead(status, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"Cache-Control\": \"no-store\",\n\t});\n\tres.end(JSON.stringify(payload));\n}\n\nfunction parseJSONBody(req: IncomingMessage): Promise<unknown> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet body = \"\";\n\t\tlet size = 0;\n\t\treq.on(\"data\", (chunk: Buffer) => {\n\t\t\tsize += chunk.length;\n\t\t\tif (size > MAX_BODY_SIZE) {\n\t\t\t\treq.destroy();\n\t\t\t\treject(new Error(\"Request body too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbody += chunk.toString();\n\t\t});\n\t\treq.on(\"end\", () => {\n\t\t\ttry {\n\t\t\t\tresolve(JSON.parse(body));\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treject(new Error(`Invalid JSON: ${message}`));\n\t\t\t}\n\t\t});\n\t\treq.on(\"error\", reject);\n\t});\n}\n\nasync function parseBodyOrSend(req: IncomingMessage, res: ServerResponse): Promise<unknown | null> {\n\ttry {\n\t\treturn await parseJSONBody(req);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"Invalid body\";\n\t\tconst status = message === \"Request body too large\" ? 413 : 400;\n\t\tsendJson(res, status, { ok: false, error: message });\n\t\treturn null;\n\t}\n}\n\nfunction normalizeSelectedIndices(\n\tvalue: unknown,\n\toptions: { allowEmpty: boolean; maxExclusive: number },\n): { ok: true; indices: number[] } | { ok: false; error: string } {\n\tif (!Array.isArray(value)) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tif (!options.allowEmpty && value.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tconst normalized: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of value) {\n\t\tif (typeof item !== \"number\" || !Number.isInteger(item) || item < 0) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (item >= options.maxExclusive) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (seen.has(item)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(item);\n\t\tnormalized.push(item);\n\t}\n\n\tif (!options.allowEmpty && normalized.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\treturn { ok: true, indices: normalized };\n}\n\nfunction normalizeSummaryMeta(value: unknown): SummaryMeta | null {\n\tif (!value || typeof value !== \"object\") return null;\n\tconst meta = value as Record<string, unknown>;\n\n\tconst model = meta.model === null\n\t\t? null\n\t\t: typeof meta.model === \"string\"\n\t\t\t? meta.model\n\t\t\t: undefined;\n\tif (model === undefined) return null;\n\n\tconst durationMs = meta.durationMs;\n\tif (typeof durationMs !== \"number\" || !Number.isFinite(durationMs) || durationMs < 0) return null;\n\n\tconst tokenEstimate = meta.tokenEstimate;\n\tif (typeof tokenEstimate !== \"number\" || !Number.isFinite(tokenEstimate) || tokenEstimate < 0) return null;\n\n\tconst fallbackUsed = meta.fallbackUsed;\n\tif (typeof fallbackUsed !== \"boolean\") return null;\n\n\tconst fallbackReason = typeof meta.fallbackReason === \"string\" ? meta.fallbackReason : undefined;\n\tif (meta.fallbackReason !== undefined && fallbackReason === undefined) return null;\n\n\tconst phase = meta.phase === \"summary-model\" || meta.phase === \"deterministic-fallback\"\n\t\t? meta.phase\n\t\t: undefined;\n\tif (meta.phase !== undefined && phase === undefined) return null;\n\tif (phase === \"deterministic-fallback\" && fallbackUsed !== true) return null;\n\tif (phase === \"summary-model\" && fallbackUsed !== false) return null;\n\n\tconst edited = typeof meta.edited === \"boolean\" ? meta.edited : undefined;\n\tif (meta.edited !== undefined && edited === undefined) return null;\n\n\treturn {\n\t\tmodel,\n\t\tdurationMs,\n\t\ttokenEstimate,\n\t\tfallbackUsed,\n\t\t...(fallbackReason !== undefined ? { fallbackReason } : {}),\n\t\t...(phase !== undefined ? { phase } : {}),\n\t\t...(edited !== undefined ? { edited } : {}),\n\t};\n}\n\nexport function startCuratorServer(\n\toptions: CuratorServerOptions,\n\tcallbacks: CuratorServerCallbacks,\n): Promise<CuratorServerHandle> {\n\tconst {\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t} = options;\n\tlet browserConnected = false;\n\tlet lastHeartbeatAt = Date.now();\n\tlet stateChangedAt = Date.now();\n\tlet clientIdleMs: number | null = null;\n\tlet clientTimeoutSeconds = timeout;\n\tlet completed = false;\n\tlet watchdog: NodeJS.Timeout | null = null;\n\tlet state: ServerState = \"SEARCHING\";\n\tlet sseResponse: ServerResponse | null = null;\n\tconst streamedEventsByResultIndex = new Map<number, CuratorStoredEvent>();\n\tlet searchStreamDone = queries.length === 0;\n\tlet nextQueryIndex = queries.length;\n\tlet summarizeAbortController: AbortController | null = null;\n\tlet summarizeRequestSeq = 0;\n\n\tlet sseKeepalive: NodeJS.Timeout | null = null;\n\n\tconst abortInFlightSummarize = (): void => {\n\t\tif (!summarizeAbortController) return;\n\t\tsummarizeAbortController.abort();\n\t\tsummarizeAbortController = null;\n\t};\n\n\tconst markCompleted = (): boolean => {\n\t\tif (completed) return false;\n\t\tcompleted = true;\n\t\tstate = \"COMPLETED\";\n\t\tstateChangedAt = Date.now();\n\t\tif (watchdog) {\n\t\t\tclearInterval(watchdog);\n\t\t\twatchdog = null;\n\t\t}\n\t\tif (sseKeepalive) {\n\t\t\tclearInterval(sseKeepalive);\n\t\t\tsseKeepalive = null;\n\t\t}\n\t\tabortInFlightSummarize();\n\t\tif (sseResponse) {\n\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\tsseResponse = null;\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst touchHeartbeat = (): void => {\n\t\tlastHeartbeatAt = Date.now();\n\t\tbrowserConnected = true;\n\t};\n\n\tconst getEffectiveTimeoutMs = (): number => Math.max(1000, Math.floor(clientTimeoutSeconds) * 1000);\n\n\tconst shouldTimeoutFromClientIdle = (): boolean => (\n\t\tstate === \"RESULT_SELECTION\" &&\n\t\tclientIdleMs !== null &&\n\t\tclientIdleMs >= getEffectiveTimeoutMs()\n\t);\n\n\tfunction validateToken(body: unknown, res: ServerResponse): boolean {\n\t\tif (!body || typeof body !== \"object\") {\n\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid body\" });\n\t\t\treturn false;\n\t\t}\n\t\tif ((body as { token?: string }).token !== sessionToken) {\n\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tfunction isAvailableProvider(provider: string): boolean {\n\t\tif (provider === \"all\") return availableProviders.all;\n\t\tif (provider === \"openai\") return availableProviders.openai;\n\t\tif (provider === \"brave\") return availableProviders.brave;\n\t\tif (provider === \"parallel\") return availableProviders.parallel;\n\t\tif (provider === \"tinyfish\") return availableProviders.tinyfish;\n\t\tif (provider === \"search1api\") return availableProviders.search1api;\n\t\tif (provider === \"searchinfinity\") return availableProviders.searchinfinity;\n\t\tif (provider === \"querit\") return availableProviders.querit;\n\t\tif (provider === \"tavily\") return availableProviders.tavily;\n\t\tif (provider === \"firecrawl\") return availableProviders.firecrawl;\n\t\tif (provider === \"jina\") return availableProviders.jina;\n\t\tif (provider === \"serpdive\") return availableProviders.serpdive;\n\t\tif (provider === \"kagi\") return availableProviders.kagi;\n\t\tif (provider === \"bocha\") return availableProviders.bocha;\n\t\tif (provider === \"ollama\") return availableProviders.ollama;\n\t\tif (provider === \"searxng\") return availableProviders.searxng;\n\t\tif (provider === \"duckduckgo\") return availableProviders.duckduckgo;\n\t\tif (provider === \"perplexity\") return availableProviders.perplexity;\n\t\tif (provider === \"exa\") return availableProviders.exa;\n\t\tif (provider === \"gemini\") return availableProviders.gemini;\n\t\tif (provider === \"anysearch\") return availableProviders.anysearch;\n\t\tif (provider === \"xai\") return availableProviders.xai;\n\t\tif (provider === \"brightdata\") return availableProviders.brightdata;\n\t\tif (provider === \"serpbase\") return availableProviders.serpbase;\n\t\treturn false;\n\t}\n\n\tfunction writeSSE(res: ServerResponse, event: string, data: unknown): boolean {\n\t\tconst payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n\t\ttry {\n\t\t\tres.write(payload);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfunction sendSSE(event: string, data: unknown): void {\n\t\tconst res = sseResponse;\n\t\tif (res && !res.writableEnded && res.socket && !res.socket.destroyed && writeSSE(res, event, data)) return;\n\t\tif (sseResponse === res) sseResponse = null;\n\t}\n\n\tfunction retainStreamedEvent(event: CuratorStoredEvent): void {\n\t\tstreamedEventsByResultIndex.set(event.data.queryIndex, event);\n\t}\n\n\tfunction getStreamedEvents(): CuratorStoredEvent[] {\n\t\treturn [...streamedEventsByResultIndex.values()];\n\t}\n\n\tfunction replaySSE(res: ServerResponse): void {\n\t\tfor (const item of getStreamedEvents()) {\n\t\t\tif (!writeSSE(res, item.event, item.data)) return;\n\t\t}\n\t\tif (searchStreamDone) writeSSE(res, \"done\", {});\n\t}\n\n\tconst pageHtml = generateCuratorPage(\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t);\n\n\tconst server = http.createServer(async (req, res) => {\n\t\ttry {\n\t\t\tconst method = req.method || \"GET\";\n\t\t\tconst url = new URL(req.url || \"/\", `http://${req.headers.host || \"127.0.0.1\"}`);\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/html; charset=utf-8\",\n\t\t\t\t\t\"Cache-Control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(pageHtml);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/events\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"No events available\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sseResponse) {\n\t\t\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\tConnection: \"keep-alive\",\n\t\t\t\t\t\"X-Accel-Buffering\": \"no\",\n\t\t\t\t});\n\t\t\t\tres.flushHeaders();\n\t\t\t\tif (res.socket) res.socket.setNoDelay(true);\n\t\t\t\tsseResponse = res;\n\t\t\t\treplaySSE(res);\n\t\t\t\tif (sseKeepalive) clearInterval(sseKeepalive);\n\t\t\t\tsseKeepalive = setInterval(() => {\n\t\t\t\t\tif (sseResponse) {\n\t\t\t\t\t\ttry { sseResponse.write(\":keepalive\\n\\n\"); } catch {}\n\t\t\t\t\t}\n\t\t\t\t}, 15000);\n\t\t\t\treq.on(\"close\", () => {\n\t\t\t\t\tif (sseResponse === res) sseResponse = null;\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/state\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tsendJson(res, 200, { ok: true, events: getStreamedEvents(), done: searchStreamDone });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/heartbeat\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tconst heartbeat = body as { idleMs?: unknown; timeoutSec?: unknown };\n\t\t\t\tif (typeof heartbeat.timeoutSec === \"number\" && Number.isFinite(heartbeat.timeoutSec) && heartbeat.timeoutSec > 0) {\n\t\t\t\t\tclientTimeoutSeconds = Math.min(600, Math.floor(heartbeat.timeoutSec));\n\t\t\t\t}\n\t\t\t\tif (typeof heartbeat.idleMs === \"number\" && Number.isFinite(heartbeat.idleMs) && heartbeat.idleMs >= 0) {\n\t\t\t\t\tclientIdleMs = Math.floor(heartbeat.idleMs);\n\t\t\t\t}\n\t\t\t\tconst timedOut = shouldTimeoutFromClientIdle();\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tif (timedOut && markCompleted()) {\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/provider\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tconst { provider } = body as { provider?: string };\n\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetImmediate(() => callbacks.onProviderChange(provider));\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/search\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query, provider } = body as { query?: string; provider?: string };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (provider !== undefined) {\n\t\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst qi = nextQueryIndex++;\n\t\t\t\tconst trimmedQuery = query.trim();\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst results = await callbacks.onAddSearch(trimmedQuery, provider);\n\t\t\t\t\tif (results.length === 0) throw new Error(\"Search returned no provider results\");\n\t\t\t\t\tconst entries = results.map((result, index): IndexedCuratorSearchEntry => ({\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tqueryIndex: index === 0 ? qi : nextQueryIndex++,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t}));\n\t\t\t\t\tcallbacks.onAddSearchResults(entries);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entries[0], entries });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Search failed\";\n\t\t\t\t\tconst entry: IndexedCuratorSearchEntry = {\n\t\t\t\t\t\tqueryIndex: qi,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t\tanswer: \"\",\n\t\t\t\t\t\tresults: [],\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tprovider: typeof provider === \"string\" && provider.length > 0 ? provider : defaultProvider,\n\t\t\t\t\t};\n\t\t\t\t\tcallbacks.onAddSearchResults([entry]);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entry, entries: [entry] });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/summarize\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: false,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet model: string | undefined;\n\t\t\t\tconst bodyModel = (body as { model?: unknown }).model;\n\t\t\t\tif (bodyModel !== undefined) {\n\t\t\t\t\tif (typeof bodyModel !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid model\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedModel = bodyModel.trim();\n\t\t\t\t\tmodel = trimmedModel.length > 0 ? trimmedModel : undefined;\n\t\t\t\t}\n\n\t\t\t\tconst bodyFeedback = (body as { feedback?: unknown }).feedback;\n\t\t\t\tconst feedback = typeof bodyFeedback === \"string\" && bodyFeedback.trim().length > 0\n\t\t\t\t\t? bodyFeedback.trim()\n\t\t\t\t\t: undefined;\n\n\t\t\t\tabortInFlightSummarize();\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tsummarizeAbortController = controller;\n\t\t\t\tconst requestId = ++summarizeRequestSeq;\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await callbacks.onSummarize(parsed.indices, controller.signal, model, feedback);\n\t\t\t\t\tif (requestId !== summarizeRequestSeq || completed) {\n\t\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Summarize request superseded\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsendJson(res, 200, {\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tmeta: result.meta,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Summary generation failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t} finally {\n\t\t\t\t\tif (summarizeAbortController === controller) {\n\t\t\t\t\t\tsummarizeAbortController = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/rewrite\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query } = body as { query?: unknown };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\treq.on(\"close\", () => controller.abort());\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst rewritten = await callbacks.onRewriteQuery(query.trim(), controller.signal);\n\t\t\t\t\tsendJson(res, 200, { ok: true, query: rewritten });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Rewrite failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/submit\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet summary: string | undefined;\n\t\t\t\tconst bodySummary = (body as { summary?: unknown }).summary;\n\t\t\t\tif (bodySummary !== undefined) {\n\t\t\t\t\tif (typeof bodySummary !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summary\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedSummary = bodySummary.trim();\n\t\t\t\t\tsummary = trimmedSummary.length > 0 ? trimmedSummary : undefined;\n\t\t\t\t}\n\n\t\t\t\tlet summaryMeta: SummaryMeta | undefined;\n\t\t\t\tconst bodySummaryMeta = (body as { summaryMeta?: unknown }).summaryMeta;\n\t\t\t\tif (bodySummaryMeta !== undefined) {\n\t\t\t\t\tconst parsedSummaryMeta = normalizeSummaryMeta(bodySummaryMeta);\n\t\t\t\t\tif (!parsedSummaryMeta) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summaryMeta\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsummaryMeta = parsedSummaryMeta;\n\t\t\t\t}\n\n\t\t\t\tif (state !== \"SEARCHING\" && state !== \"RESULT_SELECTION\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Cannot submit in current state\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst rawResults = (body as { rawResults?: unknown }).rawResults === true;\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tsetImmediate(() => callbacks.onSubmit({\n\t\t\t\t\tselectedQueryIndices: parsed.indices,\n\t\t\t\t\t...(summary !== undefined ? { summary } : {}),\n\t\t\t\t\t...(summaryMeta !== undefined ? { summaryMeta } : {}),\n\t\t\t\t\trawResults,\n\t\t\t\t}));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/cancel\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { reason } = body as { reason?: string };\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tconst cancelReason = reason === \"timeout\" ? \"timeout\" : \"user\";\n\t\t\t\tsetImmediate(() => callbacks.onCancel(cancelReason));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(404, { \"Content-Type\": \"text/plain\" });\n\t\t\tres.end(\"Not found\");\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : \"Server error\";\n\t\t\tsendJson(res, 500, { ok: false, error: message });\n\t\t}\n\t});\n\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (err: Error) => {\n\t\t\treject(new Error(`Curator server failed to start: ${err.message}`));\n\t\t};\n\n\t\tconst networkConfig = resolveCuratorNetworkConfig();\n\n\t\tserver.once(\"error\", onError);\n\t\tserver.listen(0, networkConfig.bind, () => {\n\t\t\tserver.off(\"error\", onError);\n\t\t\tconst addr = server.address();\n\t\t\tif (!addr || typeof addr === \"string\") {\n\t\t\t\treject(new Error(\"Curator server: invalid address\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst url = `http://${networkConfig.host}:${addr.port}/?session=${sessionToken}`;\n\n\t\t\twatchdog = setInterval(() => {\n\t\t\t\tif (completed) return;\n\t\t\t\tif (!browserConnected) {\n\t\t\t\t\tconst noBrowserTimeoutMs = Math.max(5000, getEffectiveTimeoutMs());\n\t\t\t\t\tif (state !== \"RESULT_SELECTION\") return;\n\t\t\t\t\tif (Date.now() - stateChangedAt <= noBrowserTimeoutMs) return;\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (shouldTimeoutFromClientIdle()) {\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - lastHeartbeatAt <= STALE_THRESHOLD_MS) return;\n\t\t\t\tconst staleReason = state === \"RESULT_SELECTION\" ? \"timeout\" : \"stale\";\n\t\t\t\tif (!markCompleted()) return;\n\t\t\t\tsetImmediate(() => callbacks.onCancel(staleReason));\n\t\t\t}, WATCHDOG_INTERVAL_MS);\n\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\turl,\n\t\t\t\tclose: () => {\n\t\t\t\t\tconst wasOpen = markCompleted();\n\t\t\t\t\ttry { server.close(); } catch {}\n\t\t\t\t\tif (wasOpen) {\n\t\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"stale\"));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpushResult: (queryIndex, data) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorResultEventData = { ...data, queryIndex, query: data.query ?? queries[queryIndex] ?? \"\" };\n\t\t\t\t\tretainStreamedEvent({ event: \"result\", data: eventData });\n\t\t\t\t\tsendSSE(\"result\", eventData);\n\t\t\t\t},\n\t\t\t\tpushError: (queryIndex, error, provider, meta) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorSearchErrorEventData = { queryIndex, query: meta?.query ?? queries[queryIndex] ?? \"\", error, provider, slotIndex: meta?.slotIndex };\n\t\t\t\t\tretainStreamedEvent({ event: \"search-error\", data: eventData });\n\t\t\t\t\tsendSSE(\"search-error\", eventData);\n\t\t\t\t},\n\t\t\t\tsearchesDone: () => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tsearchStreamDone = true;\n\t\t\t\t\tsendSSE(\"done\", {});\n\t\t\t\t\tstate = \"RESULT_SELECTION\";\n\t\t\t\t\tstateChangedAt = Date.now();\n\t\t\t\t},\n\t\t\t\tgetConnectionState: () => ({\n\t\t\t\t\tbrowserConnected,\n\t\t\t\t\tlastHeartbeatAgeMs: Date.now() - lastHeartbeatAt,\n\t\t\t\t}),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
1
+ {"version":3,"file":"curator-server.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/curator-server.ts"],"names":[],"mappings":"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","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { generateCuratorPage } from \"./curator-page.ts\";\nimport type { SummaryMeta } from \"./summary-review.ts\";\nimport { resolveCuratorNetworkConfig } from \"./utils.ts\";\n\nconst STALE_THRESHOLD_MS = 30000;\nconst WATCHDOG_INTERVAL_MS = 1000;\nconst MAX_BODY_SIZE = 64 * 1024;\n\ntype ServerState = \"SEARCHING\" | \"RESULT_SELECTION\" | \"COMPLETED\";\ntype CuratorResultEventData = IndexedCuratorSearchEntry & { slotIndex?: number };\ntype CuratorSearchErrorEventData = { queryIndex: number; query: string; error: string; provider?: string; slotIndex?: number };\ntype CuratorStoredEvent =\n\t| { event: \"result\"; data: CuratorResultEventData }\n\t| { event: \"search-error\"; data: CuratorSearchErrorEventData };\n\nexport interface CuratorServerOptions {\n\tqueries: string[];\n\tsessionToken: string;\n\ttimeout: number;\n\tavailableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; \"parallel-mcp\": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean };\n\tdefaultProvider: string;\n\tsearchProvider: string;\n\tsummaryModels: Array<{ value: string; label: string }>;\n\tdefaultSummaryModel: string | null;\n}\n\nexport interface CuratorSearchEntry {\n\tanswer: string;\n\tresults: Array<{ title: string; url: string; domain: string; snippet?: string }>;\n\tprovider: string;\n\terror?: string;\n}\n\nexport interface IndexedCuratorSearchEntry extends CuratorSearchEntry {\n\tqueryIndex: number;\n\tquery: string;\n}\n\nexport interface CuratorServerCallbacks {\n\tonSubmit: (payload: { selectedQueryIndices: number[]; summary?: string; summaryMeta?: SummaryMeta; rawResults?: boolean }) => void;\n\tonCancel: (reason: \"user\" | \"timeout\" | \"stale\") => void;\n\tonProviderChange: (provider: string) => void;\n\tonAddSearch: (query: string, provider?: string) => Promise<CuratorSearchEntry[]>;\n\tonAddSearchResults: (entries: IndexedCuratorSearchEntry[]) => void;\n\tonSummarize: (\n\t\tselectedQueryIndices: number[],\n\t\tsignal: AbortSignal,\n\t\tmodel?: string,\n\t\tfeedback?: string,\n\t) => Promise<{ summary: string; meta: SummaryMeta }>;\n\tonRewriteQuery: (query: string, signal: AbortSignal) => Promise<string>;\n}\n\nexport interface CuratorServerHandle {\n\tserver: http.Server;\n\turl: string;\n\tclose: () => void;\n\tpushResult: (queryIndex: number, data: CuratorSearchEntry & { query?: string; slotIndex?: number }) => void;\n\tpushError: (queryIndex: number, error: string, provider?: string, meta?: { query?: string; slotIndex?: number }) => void;\n\tsearchesDone: () => void;\n\t/** Reports browser connection state so a cancelled search can surface WHY it\n\t * went stale (e.g. the browser never connected). */\n\tgetConnectionState: () => { browserConnected: boolean; lastHeartbeatAgeMs: number };\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n\tres.writeHead(status, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"Cache-Control\": \"no-store\",\n\t});\n\tres.end(JSON.stringify(payload));\n}\n\nfunction parseJSONBody(req: IncomingMessage): Promise<unknown> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet body = \"\";\n\t\tlet size = 0;\n\t\treq.on(\"data\", (chunk: Buffer) => {\n\t\t\tsize += chunk.length;\n\t\t\tif (size > MAX_BODY_SIZE) {\n\t\t\t\treq.destroy();\n\t\t\t\treject(new Error(\"Request body too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbody += chunk.toString();\n\t\t});\n\t\treq.on(\"end\", () => {\n\t\t\ttry {\n\t\t\t\tresolve(JSON.parse(body));\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treject(new Error(`Invalid JSON: ${message}`));\n\t\t\t}\n\t\t});\n\t\treq.on(\"error\", reject);\n\t});\n}\n\nasync function parseBodyOrSend(req: IncomingMessage, res: ServerResponse): Promise<unknown | null> {\n\ttry {\n\t\treturn await parseJSONBody(req);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"Invalid body\";\n\t\tconst status = message === \"Request body too large\" ? 413 : 400;\n\t\tsendJson(res, status, { ok: false, error: message });\n\t\treturn null;\n\t}\n}\n\nfunction normalizeSelectedIndices(\n\tvalue: unknown,\n\toptions: { allowEmpty: boolean; maxExclusive: number },\n): { ok: true; indices: number[] } | { ok: false; error: string } {\n\tif (!Array.isArray(value)) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tif (!options.allowEmpty && value.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tconst normalized: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of value) {\n\t\tif (typeof item !== \"number\" || !Number.isInteger(item) || item < 0) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (item >= options.maxExclusive) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (seen.has(item)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(item);\n\t\tnormalized.push(item);\n\t}\n\n\tif (!options.allowEmpty && normalized.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\treturn { ok: true, indices: normalized };\n}\n\nfunction normalizeSummaryMeta(value: unknown): SummaryMeta | null {\n\tif (!value || typeof value !== \"object\") return null;\n\tconst meta = value as Record<string, unknown>;\n\n\tconst model = meta.model === null\n\t\t? null\n\t\t: typeof meta.model === \"string\"\n\t\t\t? meta.model\n\t\t\t: undefined;\n\tif (model === undefined) return null;\n\n\tconst durationMs = meta.durationMs;\n\tif (typeof durationMs !== \"number\" || !Number.isFinite(durationMs) || durationMs < 0) return null;\n\n\tconst tokenEstimate = meta.tokenEstimate;\n\tif (typeof tokenEstimate !== \"number\" || !Number.isFinite(tokenEstimate) || tokenEstimate < 0) return null;\n\n\tconst fallbackUsed = meta.fallbackUsed;\n\tif (typeof fallbackUsed !== \"boolean\") return null;\n\n\tconst fallbackReason = typeof meta.fallbackReason === \"string\" ? meta.fallbackReason : undefined;\n\tif (meta.fallbackReason !== undefined && fallbackReason === undefined) return null;\n\n\tconst phase = meta.phase === \"summary-model\" || meta.phase === \"deterministic-fallback\"\n\t\t? meta.phase\n\t\t: undefined;\n\tif (meta.phase !== undefined && phase === undefined) return null;\n\tif (phase === \"deterministic-fallback\" && fallbackUsed !== true) return null;\n\tif (phase === \"summary-model\" && fallbackUsed !== false) return null;\n\n\tconst edited = typeof meta.edited === \"boolean\" ? meta.edited : undefined;\n\tif (meta.edited !== undefined && edited === undefined) return null;\n\n\treturn {\n\t\tmodel,\n\t\tdurationMs,\n\t\ttokenEstimate,\n\t\tfallbackUsed,\n\t\t...(fallbackReason !== undefined ? { fallbackReason } : {}),\n\t\t...(phase !== undefined ? { phase } : {}),\n\t\t...(edited !== undefined ? { edited } : {}),\n\t};\n}\n\nexport function startCuratorServer(\n\toptions: CuratorServerOptions,\n\tcallbacks: CuratorServerCallbacks,\n): Promise<CuratorServerHandle> {\n\tconst {\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t} = options;\n\tlet browserConnected = false;\n\tlet lastHeartbeatAt = Date.now();\n\tlet stateChangedAt = Date.now();\n\tlet clientIdleMs: number | null = null;\n\tlet clientTimeoutSeconds = timeout;\n\tlet completed = false;\n\tlet watchdog: NodeJS.Timeout | null = null;\n\tlet state: ServerState = \"SEARCHING\";\n\tlet sseResponse: ServerResponse | null = null;\n\tconst streamedEventsByResultIndex = new Map<number, CuratorStoredEvent>();\n\tlet searchStreamDone = queries.length === 0;\n\tlet nextQueryIndex = queries.length;\n\tlet summarizeAbortController: AbortController | null = null;\n\tlet summarizeRequestSeq = 0;\n\n\tlet sseKeepalive: NodeJS.Timeout | null = null;\n\n\tconst abortInFlightSummarize = (): void => {\n\t\tif (!summarizeAbortController) return;\n\t\tsummarizeAbortController.abort();\n\t\tsummarizeAbortController = null;\n\t};\n\n\tconst markCompleted = (): boolean => {\n\t\tif (completed) return false;\n\t\tcompleted = true;\n\t\tstate = \"COMPLETED\";\n\t\tstateChangedAt = Date.now();\n\t\tif (watchdog) {\n\t\t\tclearInterval(watchdog);\n\t\t\twatchdog = null;\n\t\t}\n\t\tif (sseKeepalive) {\n\t\t\tclearInterval(sseKeepalive);\n\t\t\tsseKeepalive = null;\n\t\t}\n\t\tabortInFlightSummarize();\n\t\tif (sseResponse) {\n\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\tsseResponse = null;\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst touchHeartbeat = (): void => {\n\t\tlastHeartbeatAt = Date.now();\n\t\tbrowserConnected = true;\n\t};\n\n\tconst getEffectiveTimeoutMs = (): number => Math.max(1000, Math.floor(clientTimeoutSeconds) * 1000);\n\n\tconst shouldTimeoutFromClientIdle = (): boolean => (\n\t\tstate === \"RESULT_SELECTION\" &&\n\t\tclientIdleMs !== null &&\n\t\tclientIdleMs >= getEffectiveTimeoutMs()\n\t);\n\n\tfunction validateToken(body: unknown, res: ServerResponse): boolean {\n\t\tif (!body || typeof body !== \"object\") {\n\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid body\" });\n\t\t\treturn false;\n\t\t}\n\t\tif ((body as { token?: string }).token !== sessionToken) {\n\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tfunction isAvailableProvider(provider: string): boolean {\n\t\tif (provider === \"all\") return availableProviders.all;\n\t\tif (provider === \"openai\") return availableProviders.openai;\n\t\tif (provider === \"brave\") return availableProviders.brave;\n\t\tif (provider === \"parallel\") return availableProviders.parallel;\n\t\tif (provider === \"parallel-mcp\") return availableProviders[\"parallel-mcp\"];\n\t\tif (provider === \"tinyfish\") return availableProviders.tinyfish;\n\t\tif (provider === \"search1api\") return availableProviders.search1api;\n\t\tif (provider === \"searchinfinity\") return availableProviders.searchinfinity;\n\t\tif (provider === \"querit\") return availableProviders.querit;\n\t\tif (provider === \"tavily\") return availableProviders.tavily;\n\t\tif (provider === \"firecrawl\") return availableProviders.firecrawl;\n\t\tif (provider === \"jina\") return availableProviders.jina;\n\t\tif (provider === \"serpdive\") return availableProviders.serpdive;\n\t\tif (provider === \"kagi\") return availableProviders.kagi;\n\t\tif (provider === \"bocha\") return availableProviders.bocha;\n\t\tif (provider === \"ollama\") return availableProviders.ollama;\n\t\tif (provider === \"searxng\") return availableProviders.searxng;\n\t\tif (provider === \"duckduckgo\") return availableProviders.duckduckgo;\n\t\tif (provider === \"perplexity\") return availableProviders.perplexity;\n\t\tif (provider === \"exa\") return availableProviders.exa;\n\t\tif (provider === \"gemini\") return availableProviders.gemini;\n\t\tif (provider === \"anysearch\") return availableProviders.anysearch;\n\t\tif (provider === \"xai\") return availableProviders.xai;\n\t\tif (provider === \"brightdata\") return availableProviders.brightdata;\n\t\tif (provider === \"serpbase\") return availableProviders.serpbase;\n\t\tif (provider === \"serper\") return availableProviders.serper;\n\t\tif (provider === \"valyu\") return availableProviders.valyu;\n\t\treturn false;\n\t}\n\n\tfunction writeSSE(res: ServerResponse, event: string, data: unknown): boolean {\n\t\tconst payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n\t\ttry {\n\t\t\tres.write(payload);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfunction sendSSE(event: string, data: unknown): void {\n\t\tconst res = sseResponse;\n\t\tif (res && !res.writableEnded && res.socket && !res.socket.destroyed && writeSSE(res, event, data)) return;\n\t\tif (sseResponse === res) sseResponse = null;\n\t}\n\n\tfunction retainStreamedEvent(event: CuratorStoredEvent): void {\n\t\tstreamedEventsByResultIndex.set(event.data.queryIndex, event);\n\t}\n\n\tfunction getStreamedEvents(): CuratorStoredEvent[] {\n\t\treturn [...streamedEventsByResultIndex.values()];\n\t}\n\n\tfunction replaySSE(res: ServerResponse): void {\n\t\tfor (const item of getStreamedEvents()) {\n\t\t\tif (!writeSSE(res, item.event, item.data)) return;\n\t\t}\n\t\tif (searchStreamDone) writeSSE(res, \"done\", {});\n\t}\n\n\tconst pageHtml = generateCuratorPage(\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t);\n\n\tconst server = http.createServer(async (req, res) => {\n\t\ttry {\n\t\t\tconst method = req.method || \"GET\";\n\t\t\tconst url = new URL(req.url || \"/\", `http://${req.headers.host || \"127.0.0.1\"}`);\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/html; charset=utf-8\",\n\t\t\t\t\t\"Cache-Control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(pageHtml);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/events\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"No events available\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sseResponse) {\n\t\t\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\tConnection: \"keep-alive\",\n\t\t\t\t\t\"X-Accel-Buffering\": \"no\",\n\t\t\t\t});\n\t\t\t\tres.flushHeaders();\n\t\t\t\tif (res.socket) res.socket.setNoDelay(true);\n\t\t\t\tsseResponse = res;\n\t\t\t\treplaySSE(res);\n\t\t\t\tif (sseKeepalive) clearInterval(sseKeepalive);\n\t\t\t\tsseKeepalive = setInterval(() => {\n\t\t\t\t\tif (sseResponse) {\n\t\t\t\t\t\ttry { sseResponse.write(\":keepalive\\n\\n\"); } catch {}\n\t\t\t\t\t}\n\t\t\t\t}, 15000);\n\t\t\t\treq.on(\"close\", () => {\n\t\t\t\t\tif (sseResponse === res) sseResponse = null;\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/state\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tsendJson(res, 200, { ok: true, events: getStreamedEvents(), done: searchStreamDone });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/heartbeat\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tconst heartbeat = body as { idleMs?: unknown; timeoutSec?: unknown };\n\t\t\t\tif (typeof heartbeat.timeoutSec === \"number\" && Number.isFinite(heartbeat.timeoutSec) && heartbeat.timeoutSec > 0) {\n\t\t\t\t\tclientTimeoutSeconds = Math.min(600, Math.floor(heartbeat.timeoutSec));\n\t\t\t\t}\n\t\t\t\tif (typeof heartbeat.idleMs === \"number\" && Number.isFinite(heartbeat.idleMs) && heartbeat.idleMs >= 0) {\n\t\t\t\t\tclientIdleMs = Math.floor(heartbeat.idleMs);\n\t\t\t\t}\n\t\t\t\tconst timedOut = shouldTimeoutFromClientIdle();\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tif (timedOut && markCompleted()) {\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/provider\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tconst { provider } = body as { provider?: string };\n\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetImmediate(() => callbacks.onProviderChange(provider));\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/search\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query, provider } = body as { query?: string; provider?: string };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (provider !== undefined) {\n\t\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst qi = nextQueryIndex++;\n\t\t\t\tconst trimmedQuery = query.trim();\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst results = await callbacks.onAddSearch(trimmedQuery, provider);\n\t\t\t\t\tif (results.length === 0) throw new Error(\"Search returned no provider results\");\n\t\t\t\t\tconst entries = results.map((result, index): IndexedCuratorSearchEntry => ({\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tqueryIndex: index === 0 ? qi : nextQueryIndex++,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t}));\n\t\t\t\t\tcallbacks.onAddSearchResults(entries);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entries[0], entries });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Search failed\";\n\t\t\t\t\tconst entry: IndexedCuratorSearchEntry = {\n\t\t\t\t\t\tqueryIndex: qi,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t\tanswer: \"\",\n\t\t\t\t\t\tresults: [],\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tprovider: typeof provider === \"string\" && provider.length > 0 ? provider : defaultProvider,\n\t\t\t\t\t};\n\t\t\t\t\tcallbacks.onAddSearchResults([entry]);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entry, entries: [entry] });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/summarize\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: false,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet model: string | undefined;\n\t\t\t\tconst bodyModel = (body as { model?: unknown }).model;\n\t\t\t\tif (bodyModel !== undefined) {\n\t\t\t\t\tif (typeof bodyModel !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid model\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedModel = bodyModel.trim();\n\t\t\t\t\tmodel = trimmedModel.length > 0 ? trimmedModel : undefined;\n\t\t\t\t}\n\n\t\t\t\tconst bodyFeedback = (body as { feedback?: unknown }).feedback;\n\t\t\t\tconst feedback = typeof bodyFeedback === \"string\" && bodyFeedback.trim().length > 0\n\t\t\t\t\t? bodyFeedback.trim()\n\t\t\t\t\t: undefined;\n\n\t\t\t\tabortInFlightSummarize();\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tsummarizeAbortController = controller;\n\t\t\t\tconst requestId = ++summarizeRequestSeq;\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await callbacks.onSummarize(parsed.indices, controller.signal, model, feedback);\n\t\t\t\t\tif (requestId !== summarizeRequestSeq || completed) {\n\t\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Summarize request superseded\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsendJson(res, 200, {\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tmeta: result.meta,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Summary generation failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t} finally {\n\t\t\t\t\tif (summarizeAbortController === controller) {\n\t\t\t\t\t\tsummarizeAbortController = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/rewrite\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query } = body as { query?: unknown };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\treq.on(\"close\", () => controller.abort());\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst rewritten = await callbacks.onRewriteQuery(query.trim(), controller.signal);\n\t\t\t\t\tsendJson(res, 200, { ok: true, query: rewritten });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Rewrite failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/submit\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet summary: string | undefined;\n\t\t\t\tconst bodySummary = (body as { summary?: unknown }).summary;\n\t\t\t\tif (bodySummary !== undefined) {\n\t\t\t\t\tif (typeof bodySummary !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summary\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedSummary = bodySummary.trim();\n\t\t\t\t\tsummary = trimmedSummary.length > 0 ? trimmedSummary : undefined;\n\t\t\t\t}\n\n\t\t\t\tlet summaryMeta: SummaryMeta | undefined;\n\t\t\t\tconst bodySummaryMeta = (body as { summaryMeta?: unknown }).summaryMeta;\n\t\t\t\tif (bodySummaryMeta !== undefined) {\n\t\t\t\t\tconst parsedSummaryMeta = normalizeSummaryMeta(bodySummaryMeta);\n\t\t\t\t\tif (!parsedSummaryMeta) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summaryMeta\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsummaryMeta = parsedSummaryMeta;\n\t\t\t\t}\n\n\t\t\t\tif (state !== \"SEARCHING\" && state !== \"RESULT_SELECTION\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Cannot submit in current state\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst rawResults = (body as { rawResults?: unknown }).rawResults === true;\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tsetImmediate(() => callbacks.onSubmit({\n\t\t\t\t\tselectedQueryIndices: parsed.indices,\n\t\t\t\t\t...(summary !== undefined ? { summary } : {}),\n\t\t\t\t\t...(summaryMeta !== undefined ? { summaryMeta } : {}),\n\t\t\t\t\trawResults,\n\t\t\t\t}));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/cancel\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { reason } = body as { reason?: string };\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tconst cancelReason = reason === \"timeout\" ? \"timeout\" : \"user\";\n\t\t\t\tsetImmediate(() => callbacks.onCancel(cancelReason));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(404, { \"Content-Type\": \"text/plain\" });\n\t\t\tres.end(\"Not found\");\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : \"Server error\";\n\t\t\tsendJson(res, 500, { ok: false, error: message });\n\t\t}\n\t});\n\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (err: Error) => {\n\t\t\treject(new Error(`Curator server failed to start: ${err.message}`));\n\t\t};\n\n\t\tconst networkConfig = resolveCuratorNetworkConfig();\n\n\t\tserver.once(\"error\", onError);\n\t\tserver.listen(0, networkConfig.bind, () => {\n\t\t\tserver.off(\"error\", onError);\n\t\t\tconst addr = server.address();\n\t\t\tif (!addr || typeof addr === \"string\") {\n\t\t\t\treject(new Error(\"Curator server: invalid address\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst url = `http://${networkConfig.host}:${addr.port}/?session=${sessionToken}`;\n\n\t\t\twatchdog = setInterval(() => {\n\t\t\t\tif (completed) return;\n\t\t\t\tif (!browserConnected) {\n\t\t\t\t\tconst noBrowserTimeoutMs = Math.max(5000, getEffectiveTimeoutMs());\n\t\t\t\t\tif (state !== \"RESULT_SELECTION\") return;\n\t\t\t\t\tif (Date.now() - stateChangedAt <= noBrowserTimeoutMs) return;\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (shouldTimeoutFromClientIdle()) {\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - lastHeartbeatAt <= STALE_THRESHOLD_MS) return;\n\t\t\t\tconst staleReason = state === \"RESULT_SELECTION\" ? \"timeout\" : \"stale\";\n\t\t\t\tif (!markCompleted()) return;\n\t\t\t\tsetImmediate(() => callbacks.onCancel(staleReason));\n\t\t\t}, WATCHDOG_INTERVAL_MS);\n\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\turl,\n\t\t\t\tclose: () => {\n\t\t\t\t\tconst wasOpen = markCompleted();\n\t\t\t\t\ttry { server.close(); } catch {}\n\t\t\t\t\tif (wasOpen) {\n\t\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"stale\"));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpushResult: (queryIndex, data) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorResultEventData = { ...data, queryIndex, query: data.query ?? queries[queryIndex] ?? \"\" };\n\t\t\t\t\tretainStreamedEvent({ event: \"result\", data: eventData });\n\t\t\t\t\tsendSSE(\"result\", eventData);\n\t\t\t\t},\n\t\t\t\tpushError: (queryIndex, error, provider, meta) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorSearchErrorEventData = { queryIndex, query: meta?.query ?? queries[queryIndex] ?? \"\", error, provider, slotIndex: meta?.slotIndex };\n\t\t\t\t\tretainStreamedEvent({ event: \"search-error\", data: eventData });\n\t\t\t\t\tsendSSE(\"search-error\", eventData);\n\t\t\t\t},\n\t\t\t\tsearchesDone: () => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tsearchStreamDone = true;\n\t\t\t\t\tsendSSE(\"done\", {});\n\t\t\t\t\tstate = \"RESULT_SELECTION\";\n\t\t\t\t\tstateChangedAt = Date.now();\n\t\t\t\t},\n\t\t\t\tgetConnectionState: () => ({\n\t\t\t\t\tbrowserConnected,\n\t\t\t\t\tlastHeartbeatAgeMs: Date.now() - lastHeartbeatAt,\n\t\t\t\t}),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
@@ -194,6 +194,8 @@ export function startCuratorServer(options, callbacks) {
194
194
  return availableProviders.brave;
195
195
  if (provider === "parallel")
196
196
  return availableProviders.parallel;
197
+ if (provider === "parallel-mcp")
198
+ return availableProviders["parallel-mcp"];
197
199
  if (provider === "tinyfish")
198
200
  return availableProviders.tinyfish;
199
201
  if (provider === "search1api")
@@ -234,6 +236,10 @@ export function startCuratorServer(options, callbacks) {
234
236
  return availableProviders.brightdata;
235
237
  if (provider === "serpbase")
236
238
  return availableProviders.serpbase;
239
+ if (provider === "serper")
240
+ return availableProviders.serper;
241
+ if (provider === "valyu")
242
+ return availableProviders.valyu;
237
243
  return false;
238
244
  }
239
245
  function writeSSE(res, event, data) {
@@ -1 +1 @@
1
- {"version":3,"file":"curator-server.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/curator-server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAmD,MAAM,WAAW,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACjC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAClC,MAAM,aAAa,GAAG,EAAE,GAAG,IAAI,CAAC;AA2DhC,SAAS,QAAQ,CAAC,GAAmB,EAAE,MAAc,EAAE,OAAgB,EAAQ;IAC9E,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE;QACrB,cAAc,EAAE,kBAAkB;QAClC,eAAe,EAAE,UAAU;KAC3B,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CACjC;AAED,SAAS,aAAa,CAAC,GAAoB,EAAoB;IAC9D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QACvC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC;YACjC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,aAAa,EAAE,CAAC;gBAC1B,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;gBAC5C,OAAO;YACR,CAAC;YACD,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAAA,CACzB,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC;gBACJ,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM,CAAC,IAAI,KAAK,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC,CAAC;YAC/C,CAAC;QAAA,CACD,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAAA,CACxB,CAAC,CAAC;AAAA,CACH;AAED,KAAK,UAAU,eAAe,CAAC,GAAoB,EAAE,GAAmB,EAA2B;IAClG,IAAI,CAAC;QACJ,OAAO,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;QACpE,MAAM,MAAM,GAAG,OAAO,KAAK,wBAAwB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC;IACb,CAAC;AAAA,CACD;AAED,SAAS,wBAAwB,CAChC,KAAc,EACd,OAAsD,EACW;IACjE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAClD,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;YACrE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,IAAI,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YAClC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;QAClD,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,SAAS;QACV,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAClD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AAAA,CACzC;AAED,SAAS,oBAAoB,CAAC,KAAc,EAAsB;IACjE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAE9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI;QAChC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;YAC/B,CAAC,CAAC,IAAI,CAAC,KAAK;YACZ,CAAC,CAAC,SAAS,CAAC;IACd,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACnC,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAElG,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;IACzC,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3G,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;IACvC,IAAI,OAAO,YAAY,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnD,MAAM,cAAc,GAAG,OAAO,IAAI,CAAC,cAAc,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;IACjG,IAAI,IAAI,CAAC,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,eAAe,IAAI,IAAI,CAAC,KAAK,KAAK,wBAAwB;QACtF,CAAC,CAAC,IAAI,CAAC,KAAK;QACZ,CAAC,CAAC,SAAS,CAAC;IACb,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACjE,IAAI,KAAK,KAAK,wBAAwB,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,KAAK,KAAK,eAAe,IAAI,YAAY,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAErE,MAAM,MAAM,GAAG,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAEnE,OAAO;QACN,KAAK;QACL,UAAU;QACV,aAAa;QACb,YAAY;QACZ,GAAG,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3C,CAAC;AAAA,CACF;AAED,MAAM,UAAU,kBAAkB,CACjC,OAA6B,EAC7B,SAAiC,EACF;IAC/B,MAAM,EACL,OAAO,EACP,YAAY,EACZ,OAAO,EACP,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,aAAa,EACb,mBAAmB,GACnB,GAAG,OAAO,CAAC;IACZ,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,IAAI,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACjC,IAAI,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,IAAI,YAAY,GAAkB,IAAI,CAAC;IACvC,IAAI,oBAAoB,GAAG,OAAO,CAAC;IACnC,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,QAAQ,GAA0B,IAAI,CAAC;IAC3C,IAAI,KAAK,GAAgB,WAAW,CAAC;IACrC,IAAI,WAAW,GAA0B,IAAI,CAAC;IAC9C,MAAM,2BAA2B,GAAG,IAAI,GAAG,EAA8B,CAAC;IAC1E,IAAI,gBAAgB,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IAC5C,IAAI,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IACpC,IAAI,wBAAwB,GAA2B,IAAI,CAAC;IAC5D,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAE5B,IAAI,YAAY,GAA0B,IAAI,CAAC;IAE/C,MAAM,sBAAsB,GAAG,GAAS,EAAE,CAAC;QAC1C,IAAI,CAAC,wBAAwB;YAAE,OAAO;QACtC,wBAAwB,CAAC,KAAK,EAAE,CAAC;QACjC,wBAAwB,GAAG,IAAI,CAAC;IAAA,CAChC,CAAC;IAEF,MAAM,aAAa,GAAG,GAAY,EAAE,CAAC;QACpC,IAAI,SAAS;YAAE,OAAO,KAAK,CAAC;QAC5B,SAAS,GAAG,IAAI,CAAC;QACjB,KAAK,GAAG,WAAW,CAAC;QACpB,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,QAAQ,EAAE,CAAC;YACd,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxB,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,YAAY,EAAE,CAAC;YAClB,aAAa,CAAC,YAAY,CAAC,CAAC;YAC5B,YAAY,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,sBAAsB,EAAE,CAAC;QACzB,IAAI,WAAW,EAAE,CAAC;YACjB,IAAI,CAAC;gBAAC,WAAW,CAAC,GAAG,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;YACnC,WAAW,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ,CAAC;IAEF,MAAM,cAAc,GAAG,GAAS,EAAE,CAAC;QAClC,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,gBAAgB,GAAG,IAAI,CAAC;IAAA,CACxB,CAAC;IAEF,MAAM,qBAAqB,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC,CAAC;IAEpG,MAAM,2BAA2B,GAAG,GAAY,EAAE,CAAC,CAClD,KAAK,KAAK,kBAAkB;QAC5B,YAAY,KAAK,IAAI;QACrB,YAAY,IAAI,qBAAqB,EAAE,CACvC,CAAC;IAEF,SAAS,aAAa,CAAC,IAAa,EAAE,GAAmB,EAAW;QACnE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;YACzD,OAAO,KAAK,CAAC;QACd,CAAC;QACD,IAAK,IAA2B,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;YACzD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YAC5D,OAAO,KAAK,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,SAAS,mBAAmB,CAAC,QAAgB,EAAW;QACvD,IAAI,QAAQ,KAAK,KAAK;YAAE,OAAO,kBAAkB,CAAC,GAAG,CAAC;QACtD,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,kBAAkB,CAAC,MAAM,CAAC;QAC5D,IAAI,QAAQ,KAAK,OAAO;YAAE,OAAO,kBAAkB,CAAC,KAAK,CAAC;QAC1D,IAAI,QAAQ,KAAK,UAAU;YAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAC;QAChE,IAAI,QAAQ,KAAK,UAAU;YAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAC;QAChE,IAAI,QAAQ,KAAK,YAAY;YAAE,OAAO,kBAAkB,CAAC,UAAU,CAAC;QACpE,IAAI,QAAQ,KAAK,gBAAgB;YAAE,OAAO,kBAAkB,CAAC,cAAc,CAAC;QAC5E,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,kBAAkB,CAAC,MAAM,CAAC;QAC5D,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,kBAAkB,CAAC,MAAM,CAAC;QAC5D,IAAI,QAAQ,KAAK,WAAW;YAAE,OAAO,kBAAkB,CAAC,SAAS,CAAC;QAClE,IAAI,QAAQ,KAAK,MAAM;YAAE,OAAO,kBAAkB,CAAC,IAAI,CAAC;QACxD,IAAI,QAAQ,KAAK,UAAU;YAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAC;QAChE,IAAI,QAAQ,KAAK,MAAM;YAAE,OAAO,kBAAkB,CAAC,IAAI,CAAC;QACxD,IAAI,QAAQ,KAAK,OAAO;YAAE,OAAO,kBAAkB,CAAC,KAAK,CAAC;QAC1D,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,kBAAkB,CAAC,MAAM,CAAC;QAC5D,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,kBAAkB,CAAC,OAAO,CAAC;QAC9D,IAAI,QAAQ,KAAK,YAAY;YAAE,OAAO,kBAAkB,CAAC,UAAU,CAAC;QACpE,IAAI,QAAQ,KAAK,YAAY;YAAE,OAAO,kBAAkB,CAAC,UAAU,CAAC;QACpE,IAAI,QAAQ,KAAK,KAAK;YAAE,OAAO,kBAAkB,CAAC,GAAG,CAAC;QACtD,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,kBAAkB,CAAC,MAAM,CAAC;QAC5D,IAAI,QAAQ,KAAK,WAAW;YAAE,OAAO,kBAAkB,CAAC,SAAS,CAAC;QAClE,IAAI,QAAQ,KAAK,KAAK;YAAE,OAAO,kBAAkB,CAAC,GAAG,CAAC;QACtD,IAAI,QAAQ,KAAK,YAAY;YAAE,OAAO,kBAAkB,CAAC,UAAU,CAAC;QACpE,IAAI,QAAQ,KAAK,UAAU;YAAE,OAAO,kBAAkB,CAAC,QAAQ,CAAC;QAChE,OAAO,KAAK,CAAC;IAAA,CACb;IAED,SAAS,QAAQ,CAAC,GAAmB,EAAE,KAAa,EAAE,IAAa,EAAW;QAC7E,MAAM,OAAO,GAAG,UAAU,KAAK,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QACrE,IAAI,CAAC;YACJ,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,KAAK,CAAC;QACd,CAAC;IAAA,CACD;IAED,SAAS,OAAO,CAAC,KAAa,EAAE,IAAa,EAAQ;QACpD,MAAM,GAAG,GAAG,WAAW,CAAC;QACxB,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC;YAAE,OAAO;QAC3G,IAAI,WAAW,KAAK,GAAG;YAAE,WAAW,GAAG,IAAI,CAAC;IAAA,CAC5C;IAED,SAAS,mBAAmB,CAAC,KAAyB,EAAQ;QAC7D,2BAA2B,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAAA,CAC9D;IAED,SAAS,iBAAiB,GAAyB;QAClD,OAAO,CAAC,GAAG,2BAA2B,CAAC,MAAM,EAAE,CAAC,CAAC;IAAA,CACjD;IAED,SAAS,SAAS,CAAC,GAAmB,EAAQ;QAC7C,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC;gBAAE,OAAO;QACnD,CAAC;QACD,IAAI,gBAAgB;YAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IAAA,CAChD;IAED,MAAM,QAAQ,GAAG,mBAAmB,CACnC,OAAO,EACP,YAAY,EACZ,OAAO,EACP,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,aAAa,EACb,mBAAmB,CACnB,CAAC;IAEF,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;QACpD,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;YACnC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC,CAAC;YAEjF,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;gBAC9C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC9C,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;oBAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;oBACrD,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;oBAC3B,OAAO;gBACR,CAAC;gBACD,cAAc,EAAE,CAAC;gBACjB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;oBAClB,cAAc,EAAE,0BAA0B;oBAC1C,eAAe,EAAE,UAAU;iBAC3B,CAAC,CAAC;gBACH,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAClB,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACpD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC9C,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;oBAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;oBACrD,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;oBAC3B,OAAO;gBACR,CAAC;gBACD,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;oBAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;oBAChE,OAAO;gBACR,CAAC;gBACD,IAAI,WAAW,EAAE,CAAC;oBACjB,IAAI,CAAC;wBAAC,WAAW,CAAC,GAAG,EAAE,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAA,CAAC;gBACpC,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;oBAClB,cAAc,EAAE,mBAAmB;oBACnC,eAAe,EAAE,UAAU;oBAC3B,UAAU,EAAE,YAAY;oBACxB,mBAAmB,EAAE,IAAI;iBACzB,CAAC,CAAC;gBACH,GAAG,CAAC,YAAY,EAAE,CAAC;gBACnB,IAAI,GAAG,CAAC,MAAM;oBAAE,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAC5C,WAAW,GAAG,GAAG,CAAC;gBAClB,SAAS,CAAC,GAAG,CAAC,CAAC;gBACf,IAAI,YAAY;oBAAE,aAAa,CAAC,YAAY,CAAC,CAAC;gBAC9C,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;oBAChC,IAAI,WAAW,EAAE,CAAC;wBACjB,IAAI,CAAC;4BAAC,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;wBAAC,CAAC;wBAAC,MAAM,CAAC,CAAA,CAAC;oBACtD,CAAC;gBAAA,CACD,EAAE,KAAK,CAAC,CAAC;gBACV,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC;oBACrB,IAAI,WAAW,KAAK,GAAG;wBAAE,WAAW,GAAG,IAAI,CAAC;gBAAA,CAC5C,CAAC,CAAC;gBACH,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC9C,IAAI,KAAK,KAAK,YAAY,EAAE,CAAC;oBAC5B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;oBAC5D,OAAO;gBACR,CAAC;gBACD,cAAc,EAAE,CAAC;gBACjB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACtF,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBACxD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBACtC,cAAc,EAAE,CAAC;gBACjB,MAAM,SAAS,GAAG,IAAkD,CAAC;gBACrE,IAAI,OAAO,SAAS,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,SAAS,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;oBACnH,oBAAoB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;gBACxE,CAAC;gBACD,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;oBACxG,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;gBAC7C,CAAC;gBACD,MAAM,QAAQ,GAAG,2BAA2B,EAAE,CAAC;gBAC/C,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjC,IAAI,QAAQ,IAAI,aAAa,EAAE,EAAE,CAAC;oBACjC,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;gBACnD,CAAC;gBACD,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;gBACvD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBACtC,MAAM,EAAE,QAAQ,EAAE,GAAG,IAA6B,CAAC;gBACnD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC3D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;oBAC7D,OAAO;gBACR,CAAC;gBACD,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACpC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,QAAQ,EAAE,EAAE,CAAC,CAAC;oBAC9E,OAAO;gBACR,CAAC;gBACD,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACzD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjC,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACrD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBACtC,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;oBAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;oBAC3D,OAAO;gBACR,CAAC;gBACD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAA6C,CAAC;gBAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;oBAC1D,OAAO;gBACR,CAAC;gBACD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;oBAC5B,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAC3D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBAC7D,OAAO;oBACR,CAAC;oBACD,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;wBACpC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,QAAQ,EAAE,EAAE,CAAC,CAAC;wBAC9E,OAAO;oBACR,CAAC;gBACF,CAAC;gBACD,MAAM,EAAE,GAAG,cAAc,EAAE,CAAC;gBAC5B,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;gBAClC,cAAc,EAAE,CAAC;gBACjB,IAAI,CAAC;oBACJ,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;oBACpE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;wBAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;oBACjF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAA6B,EAAE,CAAC,CAAC;wBAC1E,GAAG,MAAM;wBACT,UAAU,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE;wBAC/C,KAAK,EAAE,YAAY;qBACnB,CAAC,CAAC,CAAC;oBACJ,SAAS,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;oBACtC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC1D,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;oBACrE,MAAM,KAAK,GAA8B;wBACxC,UAAU,EAAE,EAAE;wBACd,KAAK,EAAE,YAAY;wBACnB,MAAM,EAAE,EAAE;wBACV,OAAO,EAAE,EAAE;wBACX,KAAK,EAAE,OAAO;wBACd,QAAQ,EAAE,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe;qBAC1F,CAAC;oBACF,SAAS,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;oBACtC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAC9D,CAAC;gBACD,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;gBACxD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBACtC,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;oBAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;oBAC3D,OAAO;gBACR,CAAC;gBAED,MAAM,MAAM,GAAG,wBAAwB,CAAE,IAA+B,CAAC,QAAQ,EAAE;oBAClF,UAAU,EAAE,KAAK;oBACjB,YAAY,EAAE,cAAc;iBAC5B,CAAC,CAAC;gBACH,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;oBACvB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;oBACvD,OAAO;gBACR,CAAC;gBAED,IAAI,KAAyB,CAAC;gBAC9B,MAAM,SAAS,GAAI,IAA4B,CAAC,KAAK,CAAC;gBACtD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC7B,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;wBACnC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;wBAC1D,OAAO;oBACR,CAAC;oBACD,MAAM,YAAY,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;oBACtC,KAAK,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;gBAC5D,CAAC;gBAED,MAAM,YAAY,GAAI,IAA+B,CAAC,QAAQ,CAAC;gBAC/D,MAAM,QAAQ,GAAG,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;oBAClF,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE;oBACrB,CAAC,CAAC,SAAS,CAAC;gBAEb,sBAAsB,EAAE,CAAC;gBACzB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;gBACzC,wBAAwB,GAAG,UAAU,CAAC;gBACtC,MAAM,SAAS,GAAG,EAAE,mBAAmB,CAAC;gBAExC,IAAI,CAAC;oBACJ,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;oBAC/F,IAAI,SAAS,KAAK,mBAAmB,IAAI,SAAS,EAAE,CAAC;wBACpD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;wBACzE,OAAO;oBACR,CAAC;oBACD,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE;wBAClB,EAAE,EAAE,IAAI;wBACR,OAAO,EAAE,MAAM,CAAC,OAAO;wBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;qBACjB,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,2BAA2B,CAAC;oBACjF,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;oBACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;gBACtD,CAAC;wBAAS,CAAC;oBACV,IAAI,wBAAwB,KAAK,UAAU,EAAE,CAAC;wBAC7C,wBAAwB,GAAG,IAAI,CAAC;oBACjC,CAAC;gBACF,CAAC;gBACD,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;gBACtD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBACtC,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;oBAC3B,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;oBAC3D,OAAO;gBACR,CAAC;gBACD,MAAM,EAAE,KAAK,EAAE,GAAG,IAA2B,CAAC;gBAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;oBAC1D,OAAO;gBACR,CAAC;gBACD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;gBACzC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC1C,cAAc,EAAE,CAAC;gBACjB,IAAI,CAAC;oBACJ,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;oBAClF,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;gBACpD,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC;oBACtE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;oBACrD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;gBACtD,CAAC;gBACD,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACrD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBAEtC,MAAM,MAAM,GAAG,wBAAwB,CAAE,IAA+B,CAAC,QAAQ,EAAE;oBAClF,UAAU,EAAE,IAAI;oBAChB,YAAY,EAAE,cAAc;iBAC5B,CAAC,CAAC;gBACH,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;oBACvB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;oBACvD,OAAO;gBACR,CAAC;gBAED,IAAI,OAA2B,CAAC;gBAChC,MAAM,WAAW,GAAI,IAA8B,CAAC,OAAO,CAAC;gBAC5D,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;oBAC/B,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;wBACrC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;wBAC5D,OAAO;oBACR,CAAC;oBACD,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC;oBAC1C,OAAO,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;gBAClE,CAAC;gBAED,IAAI,WAAoC,CAAC;gBACzC,MAAM,eAAe,GAAI,IAAkC,CAAC,WAAW,CAAC;gBACxE,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;oBACnC,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,eAAe,CAAC,CAAC;oBAChE,IAAI,CAAC,iBAAiB,EAAE,CAAC;wBACxB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;wBAChE,OAAO;oBACR,CAAC;oBACD,WAAW,GAAG,iBAAiB,CAAC;gBACjC,CAAC;gBAED,IAAI,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,kBAAkB,EAAE,CAAC;oBAC3D,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;oBAC3E,OAAO;gBACR,CAAC;gBACD,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;oBACtB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;oBAC3D,OAAO;gBACR,CAAC;gBACD,MAAM,UAAU,GAAI,IAAiC,CAAC,UAAU,KAAK,IAAI,CAAC;gBAC1E,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjC,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC;oBACrC,oBAAoB,EAAE,MAAM,CAAC,OAAO;oBACpC,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC7C,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrD,UAAU;iBACV,CAAC,CAAC,CAAC;gBACJ,OAAO;YACR,CAAC;YAED,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACrD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI;oBAAE,OAAO;gBAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC;oBAAE,OAAO;gBACtC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;oBACtB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;oBACjC,OAAO;gBACR,CAAC;gBACD,MAAM,EAAE,MAAM,EAAE,GAAG,IAA2B,CAAC;gBAC/C,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjC,MAAM,YAAY,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;gBAC/D,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;gBACrD,OAAO;YACR,CAAC;YAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;YACrD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;YACpE,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACnD,CAAC;IAAA,CACD,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,CAAC,GAAU,EAAE,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAAA,CACpE,CAAC;QAEF,MAAM,aAAa,GAAG,2BAA2B,EAAE,CAAC;QAEpD,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvC,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;gBACrD,OAAO;YACR,CAAC;YACD,MAAM,GAAG,GAAG,UAAU,aAAa,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,aAAa,YAAY,EAAE,CAAC;YAEjF,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;gBAC5B,IAAI,SAAS;oBAAE,OAAO;gBACtB,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACvB,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAqB,EAAE,CAAC,CAAC;oBACnE,IAAI,KAAK,KAAK,kBAAkB;wBAAE,OAAO;oBACzC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,IAAI,kBAAkB;wBAAE,OAAO;oBAC9D,IAAI,CAAC,aAAa,EAAE;wBAAE,OAAO;oBAC7B,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;oBAClD,OAAO;gBACR,CAAC;gBACD,IAAI,2BAA2B,EAAE,EAAE,CAAC;oBACnC,IAAI,CAAC,aAAa,EAAE;wBAAE,OAAO;oBAC7B,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;oBAClD,OAAO;gBACR,CAAC;gBACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe,IAAI,kBAAkB;oBAAE,OAAO;gBAC/D,MAAM,WAAW,GAAG,KAAK,KAAK,kBAAkB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;gBACvE,IAAI,CAAC,aAAa,EAAE;oBAAE,OAAO;gBAC7B,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;YAAA,CACpD,EAAE,oBAAoB,CAAC,CAAC;YAEzB,OAAO,CAAC;gBACP,MAAM;gBACN,GAAG;gBACH,KAAK,EAAE,GAAG,EAAE,CAAC;oBACZ,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;oBAChC,IAAI,CAAC;wBAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAA,CAAC;oBAChC,IAAI,OAAO,EAAE,CAAC;wBACb,YAAY,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;oBACjD,CAAC;gBAAA,CACD;gBACD,UAAU,EAAE,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;oBACjC,IAAI,SAAS;wBAAE,OAAO;oBACtB,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;oBAC1D,MAAM,SAAS,GAA2B,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;oBAClH,mBAAmB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;oBAC1D,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;gBAAA,CAC7B;gBACD,SAAS,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;oBACjD,IAAI,SAAS;wBAAE,OAAO;oBACtB,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC;oBAC1D,MAAM,SAAS,GAAgC,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;oBAC5J,mBAAmB,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;oBAChE,OAAO,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;gBAAA,CACnC;gBACD,YAAY,EAAE,GAAG,EAAE,CAAC;oBACnB,IAAI,SAAS;wBAAE,OAAO;oBACtB,gBAAgB,GAAG,IAAI,CAAC;oBACxB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;oBACpB,KAAK,GAAG,kBAAkB,CAAC;oBAC3B,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAAA,CAC5B;gBACD,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC;oBAC1B,gBAAgB;oBAChB,kBAAkB,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,eAAe;iBAChD,CAAC;aACF,CAAC,CAAC;QAAA,CACH,CAAC,CAAC;IAAA,CACH,CAAC,CAAC;AAAA,CACH","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { generateCuratorPage } from \"./curator-page.ts\";\nimport type { SummaryMeta } from \"./summary-review.ts\";\nimport { resolveCuratorNetworkConfig } from \"./utils.ts\";\n\nconst STALE_THRESHOLD_MS = 30000;\nconst WATCHDOG_INTERVAL_MS = 1000;\nconst MAX_BODY_SIZE = 64 * 1024;\n\ntype ServerState = \"SEARCHING\" | \"RESULT_SELECTION\" | \"COMPLETED\";\ntype CuratorResultEventData = IndexedCuratorSearchEntry & { slotIndex?: number };\ntype CuratorSearchErrorEventData = { queryIndex: number; query: string; error: string; provider?: string; slotIndex?: number };\ntype CuratorStoredEvent =\n\t| { event: \"result\"; data: CuratorResultEventData }\n\t| { event: \"search-error\"; data: CuratorSearchErrorEventData };\n\nexport interface CuratorServerOptions {\n\tqueries: string[];\n\tsessionToken: string;\n\ttimeout: number;\n\tavailableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean };\n\tdefaultProvider: string;\n\tsearchProvider: string;\n\tsummaryModels: Array<{ value: string; label: string }>;\n\tdefaultSummaryModel: string | null;\n}\n\nexport interface CuratorSearchEntry {\n\tanswer: string;\n\tresults: Array<{ title: string; url: string; domain: string; snippet?: string }>;\n\tprovider: string;\n\terror?: string;\n}\n\nexport interface IndexedCuratorSearchEntry extends CuratorSearchEntry {\n\tqueryIndex: number;\n\tquery: string;\n}\n\nexport interface CuratorServerCallbacks {\n\tonSubmit: (payload: { selectedQueryIndices: number[]; summary?: string; summaryMeta?: SummaryMeta; rawResults?: boolean }) => void;\n\tonCancel: (reason: \"user\" | \"timeout\" | \"stale\") => void;\n\tonProviderChange: (provider: string) => void;\n\tonAddSearch: (query: string, provider?: string) => Promise<CuratorSearchEntry[]>;\n\tonAddSearchResults: (entries: IndexedCuratorSearchEntry[]) => void;\n\tonSummarize: (\n\t\tselectedQueryIndices: number[],\n\t\tsignal: AbortSignal,\n\t\tmodel?: string,\n\t\tfeedback?: string,\n\t) => Promise<{ summary: string; meta: SummaryMeta }>;\n\tonRewriteQuery: (query: string, signal: AbortSignal) => Promise<string>;\n}\n\nexport interface CuratorServerHandle {\n\tserver: http.Server;\n\turl: string;\n\tclose: () => void;\n\tpushResult: (queryIndex: number, data: CuratorSearchEntry & { query?: string; slotIndex?: number }) => void;\n\tpushError: (queryIndex: number, error: string, provider?: string, meta?: { query?: string; slotIndex?: number }) => void;\n\tsearchesDone: () => void;\n\t/** Reports browser connection state so a cancelled search can surface WHY it\n\t * went stale (e.g. the browser never connected). */\n\tgetConnectionState: () => { browserConnected: boolean; lastHeartbeatAgeMs: number };\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n\tres.writeHead(status, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"Cache-Control\": \"no-store\",\n\t});\n\tres.end(JSON.stringify(payload));\n}\n\nfunction parseJSONBody(req: IncomingMessage): Promise<unknown> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet body = \"\";\n\t\tlet size = 0;\n\t\treq.on(\"data\", (chunk: Buffer) => {\n\t\t\tsize += chunk.length;\n\t\t\tif (size > MAX_BODY_SIZE) {\n\t\t\t\treq.destroy();\n\t\t\t\treject(new Error(\"Request body too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbody += chunk.toString();\n\t\t});\n\t\treq.on(\"end\", () => {\n\t\t\ttry {\n\t\t\t\tresolve(JSON.parse(body));\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treject(new Error(`Invalid JSON: ${message}`));\n\t\t\t}\n\t\t});\n\t\treq.on(\"error\", reject);\n\t});\n}\n\nasync function parseBodyOrSend(req: IncomingMessage, res: ServerResponse): Promise<unknown | null> {\n\ttry {\n\t\treturn await parseJSONBody(req);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"Invalid body\";\n\t\tconst status = message === \"Request body too large\" ? 413 : 400;\n\t\tsendJson(res, status, { ok: false, error: message });\n\t\treturn null;\n\t}\n}\n\nfunction normalizeSelectedIndices(\n\tvalue: unknown,\n\toptions: { allowEmpty: boolean; maxExclusive: number },\n): { ok: true; indices: number[] } | { ok: false; error: string } {\n\tif (!Array.isArray(value)) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tif (!options.allowEmpty && value.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tconst normalized: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of value) {\n\t\tif (typeof item !== \"number\" || !Number.isInteger(item) || item < 0) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (item >= options.maxExclusive) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (seen.has(item)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(item);\n\t\tnormalized.push(item);\n\t}\n\n\tif (!options.allowEmpty && normalized.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\treturn { ok: true, indices: normalized };\n}\n\nfunction normalizeSummaryMeta(value: unknown): SummaryMeta | null {\n\tif (!value || typeof value !== \"object\") return null;\n\tconst meta = value as Record<string, unknown>;\n\n\tconst model = meta.model === null\n\t\t? null\n\t\t: typeof meta.model === \"string\"\n\t\t\t? meta.model\n\t\t\t: undefined;\n\tif (model === undefined) return null;\n\n\tconst durationMs = meta.durationMs;\n\tif (typeof durationMs !== \"number\" || !Number.isFinite(durationMs) || durationMs < 0) return null;\n\n\tconst tokenEstimate = meta.tokenEstimate;\n\tif (typeof tokenEstimate !== \"number\" || !Number.isFinite(tokenEstimate) || tokenEstimate < 0) return null;\n\n\tconst fallbackUsed = meta.fallbackUsed;\n\tif (typeof fallbackUsed !== \"boolean\") return null;\n\n\tconst fallbackReason = typeof meta.fallbackReason === \"string\" ? meta.fallbackReason : undefined;\n\tif (meta.fallbackReason !== undefined && fallbackReason === undefined) return null;\n\n\tconst phase = meta.phase === \"summary-model\" || meta.phase === \"deterministic-fallback\"\n\t\t? meta.phase\n\t\t: undefined;\n\tif (meta.phase !== undefined && phase === undefined) return null;\n\tif (phase === \"deterministic-fallback\" && fallbackUsed !== true) return null;\n\tif (phase === \"summary-model\" && fallbackUsed !== false) return null;\n\n\tconst edited = typeof meta.edited === \"boolean\" ? meta.edited : undefined;\n\tif (meta.edited !== undefined && edited === undefined) return null;\n\n\treturn {\n\t\tmodel,\n\t\tdurationMs,\n\t\ttokenEstimate,\n\t\tfallbackUsed,\n\t\t...(fallbackReason !== undefined ? { fallbackReason } : {}),\n\t\t...(phase !== undefined ? { phase } : {}),\n\t\t...(edited !== undefined ? { edited } : {}),\n\t};\n}\n\nexport function startCuratorServer(\n\toptions: CuratorServerOptions,\n\tcallbacks: CuratorServerCallbacks,\n): Promise<CuratorServerHandle> {\n\tconst {\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t} = options;\n\tlet browserConnected = false;\n\tlet lastHeartbeatAt = Date.now();\n\tlet stateChangedAt = Date.now();\n\tlet clientIdleMs: number | null = null;\n\tlet clientTimeoutSeconds = timeout;\n\tlet completed = false;\n\tlet watchdog: NodeJS.Timeout | null = null;\n\tlet state: ServerState = \"SEARCHING\";\n\tlet sseResponse: ServerResponse | null = null;\n\tconst streamedEventsByResultIndex = new Map<number, CuratorStoredEvent>();\n\tlet searchStreamDone = queries.length === 0;\n\tlet nextQueryIndex = queries.length;\n\tlet summarizeAbortController: AbortController | null = null;\n\tlet summarizeRequestSeq = 0;\n\n\tlet sseKeepalive: NodeJS.Timeout | null = null;\n\n\tconst abortInFlightSummarize = (): void => {\n\t\tif (!summarizeAbortController) return;\n\t\tsummarizeAbortController.abort();\n\t\tsummarizeAbortController = null;\n\t};\n\n\tconst markCompleted = (): boolean => {\n\t\tif (completed) return false;\n\t\tcompleted = true;\n\t\tstate = \"COMPLETED\";\n\t\tstateChangedAt = Date.now();\n\t\tif (watchdog) {\n\t\t\tclearInterval(watchdog);\n\t\t\twatchdog = null;\n\t\t}\n\t\tif (sseKeepalive) {\n\t\t\tclearInterval(sseKeepalive);\n\t\t\tsseKeepalive = null;\n\t\t}\n\t\tabortInFlightSummarize();\n\t\tif (sseResponse) {\n\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\tsseResponse = null;\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst touchHeartbeat = (): void => {\n\t\tlastHeartbeatAt = Date.now();\n\t\tbrowserConnected = true;\n\t};\n\n\tconst getEffectiveTimeoutMs = (): number => Math.max(1000, Math.floor(clientTimeoutSeconds) * 1000);\n\n\tconst shouldTimeoutFromClientIdle = (): boolean => (\n\t\tstate === \"RESULT_SELECTION\" &&\n\t\tclientIdleMs !== null &&\n\t\tclientIdleMs >= getEffectiveTimeoutMs()\n\t);\n\n\tfunction validateToken(body: unknown, res: ServerResponse): boolean {\n\t\tif (!body || typeof body !== \"object\") {\n\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid body\" });\n\t\t\treturn false;\n\t\t}\n\t\tif ((body as { token?: string }).token !== sessionToken) {\n\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tfunction isAvailableProvider(provider: string): boolean {\n\t\tif (provider === \"all\") return availableProviders.all;\n\t\tif (provider === \"openai\") return availableProviders.openai;\n\t\tif (provider === \"brave\") return availableProviders.brave;\n\t\tif (provider === \"parallel\") return availableProviders.parallel;\n\t\tif (provider === \"tinyfish\") return availableProviders.tinyfish;\n\t\tif (provider === \"search1api\") return availableProviders.search1api;\n\t\tif (provider === \"searchinfinity\") return availableProviders.searchinfinity;\n\t\tif (provider === \"querit\") return availableProviders.querit;\n\t\tif (provider === \"tavily\") return availableProviders.tavily;\n\t\tif (provider === \"firecrawl\") return availableProviders.firecrawl;\n\t\tif (provider === \"jina\") return availableProviders.jina;\n\t\tif (provider === \"serpdive\") return availableProviders.serpdive;\n\t\tif (provider === \"kagi\") return availableProviders.kagi;\n\t\tif (provider === \"bocha\") return availableProviders.bocha;\n\t\tif (provider === \"ollama\") return availableProviders.ollama;\n\t\tif (provider === \"searxng\") return availableProviders.searxng;\n\t\tif (provider === \"duckduckgo\") return availableProviders.duckduckgo;\n\t\tif (provider === \"perplexity\") return availableProviders.perplexity;\n\t\tif (provider === \"exa\") return availableProviders.exa;\n\t\tif (provider === \"gemini\") return availableProviders.gemini;\n\t\tif (provider === \"anysearch\") return availableProviders.anysearch;\n\t\tif (provider === \"xai\") return availableProviders.xai;\n\t\tif (provider === \"brightdata\") return availableProviders.brightdata;\n\t\tif (provider === \"serpbase\") return availableProviders.serpbase;\n\t\treturn false;\n\t}\n\n\tfunction writeSSE(res: ServerResponse, event: string, data: unknown): boolean {\n\t\tconst payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n\t\ttry {\n\t\t\tres.write(payload);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfunction sendSSE(event: string, data: unknown): void {\n\t\tconst res = sseResponse;\n\t\tif (res && !res.writableEnded && res.socket && !res.socket.destroyed && writeSSE(res, event, data)) return;\n\t\tif (sseResponse === res) sseResponse = null;\n\t}\n\n\tfunction retainStreamedEvent(event: CuratorStoredEvent): void {\n\t\tstreamedEventsByResultIndex.set(event.data.queryIndex, event);\n\t}\n\n\tfunction getStreamedEvents(): CuratorStoredEvent[] {\n\t\treturn [...streamedEventsByResultIndex.values()];\n\t}\n\n\tfunction replaySSE(res: ServerResponse): void {\n\t\tfor (const item of getStreamedEvents()) {\n\t\t\tif (!writeSSE(res, item.event, item.data)) return;\n\t\t}\n\t\tif (searchStreamDone) writeSSE(res, \"done\", {});\n\t}\n\n\tconst pageHtml = generateCuratorPage(\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t);\n\n\tconst server = http.createServer(async (req, res) => {\n\t\ttry {\n\t\t\tconst method = req.method || \"GET\";\n\t\t\tconst url = new URL(req.url || \"/\", `http://${req.headers.host || \"127.0.0.1\"}`);\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/html; charset=utf-8\",\n\t\t\t\t\t\"Cache-Control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(pageHtml);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/events\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"No events available\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sseResponse) {\n\t\t\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\tConnection: \"keep-alive\",\n\t\t\t\t\t\"X-Accel-Buffering\": \"no\",\n\t\t\t\t});\n\t\t\t\tres.flushHeaders();\n\t\t\t\tif (res.socket) res.socket.setNoDelay(true);\n\t\t\t\tsseResponse = res;\n\t\t\t\treplaySSE(res);\n\t\t\t\tif (sseKeepalive) clearInterval(sseKeepalive);\n\t\t\t\tsseKeepalive = setInterval(() => {\n\t\t\t\t\tif (sseResponse) {\n\t\t\t\t\t\ttry { sseResponse.write(\":keepalive\\n\\n\"); } catch {}\n\t\t\t\t\t}\n\t\t\t\t}, 15000);\n\t\t\t\treq.on(\"close\", () => {\n\t\t\t\t\tif (sseResponse === res) sseResponse = null;\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/state\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tsendJson(res, 200, { ok: true, events: getStreamedEvents(), done: searchStreamDone });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/heartbeat\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tconst heartbeat = body as { idleMs?: unknown; timeoutSec?: unknown };\n\t\t\t\tif (typeof heartbeat.timeoutSec === \"number\" && Number.isFinite(heartbeat.timeoutSec) && heartbeat.timeoutSec > 0) {\n\t\t\t\t\tclientTimeoutSeconds = Math.min(600, Math.floor(heartbeat.timeoutSec));\n\t\t\t\t}\n\t\t\t\tif (typeof heartbeat.idleMs === \"number\" && Number.isFinite(heartbeat.idleMs) && heartbeat.idleMs >= 0) {\n\t\t\t\t\tclientIdleMs = Math.floor(heartbeat.idleMs);\n\t\t\t\t}\n\t\t\t\tconst timedOut = shouldTimeoutFromClientIdle();\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tif (timedOut && markCompleted()) {\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/provider\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tconst { provider } = body as { provider?: string };\n\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetImmediate(() => callbacks.onProviderChange(provider));\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/search\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query, provider } = body as { query?: string; provider?: string };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (provider !== undefined) {\n\t\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst qi = nextQueryIndex++;\n\t\t\t\tconst trimmedQuery = query.trim();\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst results = await callbacks.onAddSearch(trimmedQuery, provider);\n\t\t\t\t\tif (results.length === 0) throw new Error(\"Search returned no provider results\");\n\t\t\t\t\tconst entries = results.map((result, index): IndexedCuratorSearchEntry => ({\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tqueryIndex: index === 0 ? qi : nextQueryIndex++,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t}));\n\t\t\t\t\tcallbacks.onAddSearchResults(entries);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entries[0], entries });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Search failed\";\n\t\t\t\t\tconst entry: IndexedCuratorSearchEntry = {\n\t\t\t\t\t\tqueryIndex: qi,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t\tanswer: \"\",\n\t\t\t\t\t\tresults: [],\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tprovider: typeof provider === \"string\" && provider.length > 0 ? provider : defaultProvider,\n\t\t\t\t\t};\n\t\t\t\t\tcallbacks.onAddSearchResults([entry]);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entry, entries: [entry] });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/summarize\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: false,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet model: string | undefined;\n\t\t\t\tconst bodyModel = (body as { model?: unknown }).model;\n\t\t\t\tif (bodyModel !== undefined) {\n\t\t\t\t\tif (typeof bodyModel !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid model\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedModel = bodyModel.trim();\n\t\t\t\t\tmodel = trimmedModel.length > 0 ? trimmedModel : undefined;\n\t\t\t\t}\n\n\t\t\t\tconst bodyFeedback = (body as { feedback?: unknown }).feedback;\n\t\t\t\tconst feedback = typeof bodyFeedback === \"string\" && bodyFeedback.trim().length > 0\n\t\t\t\t\t? bodyFeedback.trim()\n\t\t\t\t\t: undefined;\n\n\t\t\t\tabortInFlightSummarize();\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tsummarizeAbortController = controller;\n\t\t\t\tconst requestId = ++summarizeRequestSeq;\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await callbacks.onSummarize(parsed.indices, controller.signal, model, feedback);\n\t\t\t\t\tif (requestId !== summarizeRequestSeq || completed) {\n\t\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Summarize request superseded\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsendJson(res, 200, {\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tmeta: result.meta,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Summary generation failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t} finally {\n\t\t\t\t\tif (summarizeAbortController === controller) {\n\t\t\t\t\t\tsummarizeAbortController = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/rewrite\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query } = body as { query?: unknown };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\treq.on(\"close\", () => controller.abort());\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst rewritten = await callbacks.onRewriteQuery(query.trim(), controller.signal);\n\t\t\t\t\tsendJson(res, 200, { ok: true, query: rewritten });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Rewrite failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/submit\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet summary: string | undefined;\n\t\t\t\tconst bodySummary = (body as { summary?: unknown }).summary;\n\t\t\t\tif (bodySummary !== undefined) {\n\t\t\t\t\tif (typeof bodySummary !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summary\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedSummary = bodySummary.trim();\n\t\t\t\t\tsummary = trimmedSummary.length > 0 ? trimmedSummary : undefined;\n\t\t\t\t}\n\n\t\t\t\tlet summaryMeta: SummaryMeta | undefined;\n\t\t\t\tconst bodySummaryMeta = (body as { summaryMeta?: unknown }).summaryMeta;\n\t\t\t\tif (bodySummaryMeta !== undefined) {\n\t\t\t\t\tconst parsedSummaryMeta = normalizeSummaryMeta(bodySummaryMeta);\n\t\t\t\t\tif (!parsedSummaryMeta) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summaryMeta\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsummaryMeta = parsedSummaryMeta;\n\t\t\t\t}\n\n\t\t\t\tif (state !== \"SEARCHING\" && state !== \"RESULT_SELECTION\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Cannot submit in current state\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst rawResults = (body as { rawResults?: unknown }).rawResults === true;\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tsetImmediate(() => callbacks.onSubmit({\n\t\t\t\t\tselectedQueryIndices: parsed.indices,\n\t\t\t\t\t...(summary !== undefined ? { summary } : {}),\n\t\t\t\t\t...(summaryMeta !== undefined ? { summaryMeta } : {}),\n\t\t\t\t\trawResults,\n\t\t\t\t}));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/cancel\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { reason } = body as { reason?: string };\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tconst cancelReason = reason === \"timeout\" ? \"timeout\" : \"user\";\n\t\t\t\tsetImmediate(() => callbacks.onCancel(cancelReason));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(404, { \"Content-Type\": \"text/plain\" });\n\t\t\tres.end(\"Not found\");\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : \"Server error\";\n\t\t\tsendJson(res, 500, { ok: false, error: message });\n\t\t}\n\t});\n\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (err: Error) => {\n\t\t\treject(new Error(`Curator server failed to start: ${err.message}`));\n\t\t};\n\n\t\tconst networkConfig = resolveCuratorNetworkConfig();\n\n\t\tserver.once(\"error\", onError);\n\t\tserver.listen(0, networkConfig.bind, () => {\n\t\t\tserver.off(\"error\", onError);\n\t\t\tconst addr = server.address();\n\t\t\tif (!addr || typeof addr === \"string\") {\n\t\t\t\treject(new Error(\"Curator server: invalid address\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst url = `http://${networkConfig.host}:${addr.port}/?session=${sessionToken}`;\n\n\t\t\twatchdog = setInterval(() => {\n\t\t\t\tif (completed) return;\n\t\t\t\tif (!browserConnected) {\n\t\t\t\t\tconst noBrowserTimeoutMs = Math.max(5000, getEffectiveTimeoutMs());\n\t\t\t\t\tif (state !== \"RESULT_SELECTION\") return;\n\t\t\t\t\tif (Date.now() - stateChangedAt <= noBrowserTimeoutMs) return;\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (shouldTimeoutFromClientIdle()) {\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - lastHeartbeatAt <= STALE_THRESHOLD_MS) return;\n\t\t\t\tconst staleReason = state === \"RESULT_SELECTION\" ? \"timeout\" : \"stale\";\n\t\t\t\tif (!markCompleted()) return;\n\t\t\t\tsetImmediate(() => callbacks.onCancel(staleReason));\n\t\t\t}, WATCHDOG_INTERVAL_MS);\n\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\turl,\n\t\t\t\tclose: () => {\n\t\t\t\t\tconst wasOpen = markCompleted();\n\t\t\t\t\ttry { server.close(); } catch {}\n\t\t\t\t\tif (wasOpen) {\n\t\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"stale\"));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpushResult: (queryIndex, data) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorResultEventData = { ...data, queryIndex, query: data.query ?? queries[queryIndex] ?? \"\" };\n\t\t\t\t\tretainStreamedEvent({ event: \"result\", data: eventData });\n\t\t\t\t\tsendSSE(\"result\", eventData);\n\t\t\t\t},\n\t\t\t\tpushError: (queryIndex, error, provider, meta) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorSearchErrorEventData = { queryIndex, query: meta?.query ?? queries[queryIndex] ?? \"\", error, provider, slotIndex: meta?.slotIndex };\n\t\t\t\t\tretainStreamedEvent({ event: \"search-error\", data: eventData });\n\t\t\t\t\tsendSSE(\"search-error\", eventData);\n\t\t\t\t},\n\t\t\t\tsearchesDone: () => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tsearchStreamDone = true;\n\t\t\t\t\tsendSSE(\"done\", {});\n\t\t\t\t\tstate = \"RESULT_SELECTION\";\n\t\t\t\t\tstateChangedAt = Date.now();\n\t\t\t\t},\n\t\t\t\tgetConnectionState: () => ({\n\t\t\t\t\tbrowserConnected,\n\t\t\t\t\tlastHeartbeatAgeMs: Date.now() - lastHeartbeatAt,\n\t\t\t\t}),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
1
+ {"version":3,"file":"curator-server.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/curator-server.ts"],"names":[],"mappings":"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","sourcesContent":["import http, { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { generateCuratorPage } from \"./curator-page.ts\";\nimport type { SummaryMeta } from \"./summary-review.ts\";\nimport { resolveCuratorNetworkConfig } from \"./utils.ts\";\n\nconst STALE_THRESHOLD_MS = 30000;\nconst WATCHDOG_INTERVAL_MS = 1000;\nconst MAX_BODY_SIZE = 64 * 1024;\n\ntype ServerState = \"SEARCHING\" | \"RESULT_SELECTION\" | \"COMPLETED\";\ntype CuratorResultEventData = IndexedCuratorSearchEntry & { slotIndex?: number };\ntype CuratorSearchErrorEventData = { queryIndex: number; query: string; error: string; provider?: string; slotIndex?: number };\ntype CuratorStoredEvent =\n\t| { event: \"result\"; data: CuratorResultEventData }\n\t| { event: \"search-error\"; data: CuratorSearchErrorEventData };\n\nexport interface CuratorServerOptions {\n\tqueries: string[];\n\tsessionToken: string;\n\ttimeout: number;\n\tavailableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; \"parallel-mcp\": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean };\n\tdefaultProvider: string;\n\tsearchProvider: string;\n\tsummaryModels: Array<{ value: string; label: string }>;\n\tdefaultSummaryModel: string | null;\n}\n\nexport interface CuratorSearchEntry {\n\tanswer: string;\n\tresults: Array<{ title: string; url: string; domain: string; snippet?: string }>;\n\tprovider: string;\n\terror?: string;\n}\n\nexport interface IndexedCuratorSearchEntry extends CuratorSearchEntry {\n\tqueryIndex: number;\n\tquery: string;\n}\n\nexport interface CuratorServerCallbacks {\n\tonSubmit: (payload: { selectedQueryIndices: number[]; summary?: string; summaryMeta?: SummaryMeta; rawResults?: boolean }) => void;\n\tonCancel: (reason: \"user\" | \"timeout\" | \"stale\") => void;\n\tonProviderChange: (provider: string) => void;\n\tonAddSearch: (query: string, provider?: string) => Promise<CuratorSearchEntry[]>;\n\tonAddSearchResults: (entries: IndexedCuratorSearchEntry[]) => void;\n\tonSummarize: (\n\t\tselectedQueryIndices: number[],\n\t\tsignal: AbortSignal,\n\t\tmodel?: string,\n\t\tfeedback?: string,\n\t) => Promise<{ summary: string; meta: SummaryMeta }>;\n\tonRewriteQuery: (query: string, signal: AbortSignal) => Promise<string>;\n}\n\nexport interface CuratorServerHandle {\n\tserver: http.Server;\n\turl: string;\n\tclose: () => void;\n\tpushResult: (queryIndex: number, data: CuratorSearchEntry & { query?: string; slotIndex?: number }) => void;\n\tpushError: (queryIndex: number, error: string, provider?: string, meta?: { query?: string; slotIndex?: number }) => void;\n\tsearchesDone: () => void;\n\t/** Reports browser connection state so a cancelled search can surface WHY it\n\t * went stale (e.g. the browser never connected). */\n\tgetConnectionState: () => { browserConnected: boolean; lastHeartbeatAgeMs: number };\n}\n\nfunction sendJson(res: ServerResponse, status: number, payload: unknown): void {\n\tres.writeHead(status, {\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"Cache-Control\": \"no-store\",\n\t});\n\tres.end(JSON.stringify(payload));\n}\n\nfunction parseJSONBody(req: IncomingMessage): Promise<unknown> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet body = \"\";\n\t\tlet size = 0;\n\t\treq.on(\"data\", (chunk: Buffer) => {\n\t\t\tsize += chunk.length;\n\t\t\tif (size > MAX_BODY_SIZE) {\n\t\t\t\treq.destroy();\n\t\t\t\treject(new Error(\"Request body too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbody += chunk.toString();\n\t\t});\n\t\treq.on(\"end\", () => {\n\t\t\ttry {\n\t\t\t\tresolve(JSON.parse(body));\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\treject(new Error(`Invalid JSON: ${message}`));\n\t\t\t}\n\t\t});\n\t\treq.on(\"error\", reject);\n\t});\n}\n\nasync function parseBodyOrSend(req: IncomingMessage, res: ServerResponse): Promise<unknown | null> {\n\ttry {\n\t\treturn await parseJSONBody(req);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : \"Invalid body\";\n\t\tconst status = message === \"Request body too large\" ? 413 : 400;\n\t\tsendJson(res, status, { ok: false, error: message });\n\t\treturn null;\n\t}\n}\n\nfunction normalizeSelectedIndices(\n\tvalue: unknown,\n\toptions: { allowEmpty: boolean; maxExclusive: number },\n): { ok: true; indices: number[] } | { ok: false; error: string } {\n\tif (!Array.isArray(value)) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tif (!options.allowEmpty && value.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\tconst normalized: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of value) {\n\t\tif (typeof item !== \"number\" || !Number.isInteger(item) || item < 0) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (item >= options.maxExclusive) {\n\t\t\treturn { ok: false, error: \"Invalid selection\" };\n\t\t}\n\t\tif (seen.has(item)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseen.add(item);\n\t\tnormalized.push(item);\n\t}\n\n\tif (!options.allowEmpty && normalized.length === 0) {\n\t\treturn { ok: false, error: \"Invalid selection\" };\n\t}\n\n\treturn { ok: true, indices: normalized };\n}\n\nfunction normalizeSummaryMeta(value: unknown): SummaryMeta | null {\n\tif (!value || typeof value !== \"object\") return null;\n\tconst meta = value as Record<string, unknown>;\n\n\tconst model = meta.model === null\n\t\t? null\n\t\t: typeof meta.model === \"string\"\n\t\t\t? meta.model\n\t\t\t: undefined;\n\tif (model === undefined) return null;\n\n\tconst durationMs = meta.durationMs;\n\tif (typeof durationMs !== \"number\" || !Number.isFinite(durationMs) || durationMs < 0) return null;\n\n\tconst tokenEstimate = meta.tokenEstimate;\n\tif (typeof tokenEstimate !== \"number\" || !Number.isFinite(tokenEstimate) || tokenEstimate < 0) return null;\n\n\tconst fallbackUsed = meta.fallbackUsed;\n\tif (typeof fallbackUsed !== \"boolean\") return null;\n\n\tconst fallbackReason = typeof meta.fallbackReason === \"string\" ? meta.fallbackReason : undefined;\n\tif (meta.fallbackReason !== undefined && fallbackReason === undefined) return null;\n\n\tconst phase = meta.phase === \"summary-model\" || meta.phase === \"deterministic-fallback\"\n\t\t? meta.phase\n\t\t: undefined;\n\tif (meta.phase !== undefined && phase === undefined) return null;\n\tif (phase === \"deterministic-fallback\" && fallbackUsed !== true) return null;\n\tif (phase === \"summary-model\" && fallbackUsed !== false) return null;\n\n\tconst edited = typeof meta.edited === \"boolean\" ? meta.edited : undefined;\n\tif (meta.edited !== undefined && edited === undefined) return null;\n\n\treturn {\n\t\tmodel,\n\t\tdurationMs,\n\t\ttokenEstimate,\n\t\tfallbackUsed,\n\t\t...(fallbackReason !== undefined ? { fallbackReason } : {}),\n\t\t...(phase !== undefined ? { phase } : {}),\n\t\t...(edited !== undefined ? { edited } : {}),\n\t};\n}\n\nexport function startCuratorServer(\n\toptions: CuratorServerOptions,\n\tcallbacks: CuratorServerCallbacks,\n): Promise<CuratorServerHandle> {\n\tconst {\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t} = options;\n\tlet browserConnected = false;\n\tlet lastHeartbeatAt = Date.now();\n\tlet stateChangedAt = Date.now();\n\tlet clientIdleMs: number | null = null;\n\tlet clientTimeoutSeconds = timeout;\n\tlet completed = false;\n\tlet watchdog: NodeJS.Timeout | null = null;\n\tlet state: ServerState = \"SEARCHING\";\n\tlet sseResponse: ServerResponse | null = null;\n\tconst streamedEventsByResultIndex = new Map<number, CuratorStoredEvent>();\n\tlet searchStreamDone = queries.length === 0;\n\tlet nextQueryIndex = queries.length;\n\tlet summarizeAbortController: AbortController | null = null;\n\tlet summarizeRequestSeq = 0;\n\n\tlet sseKeepalive: NodeJS.Timeout | null = null;\n\n\tconst abortInFlightSummarize = (): void => {\n\t\tif (!summarizeAbortController) return;\n\t\tsummarizeAbortController.abort();\n\t\tsummarizeAbortController = null;\n\t};\n\n\tconst markCompleted = (): boolean => {\n\t\tif (completed) return false;\n\t\tcompleted = true;\n\t\tstate = \"COMPLETED\";\n\t\tstateChangedAt = Date.now();\n\t\tif (watchdog) {\n\t\t\tclearInterval(watchdog);\n\t\t\twatchdog = null;\n\t\t}\n\t\tif (sseKeepalive) {\n\t\t\tclearInterval(sseKeepalive);\n\t\t\tsseKeepalive = null;\n\t\t}\n\t\tabortInFlightSummarize();\n\t\tif (sseResponse) {\n\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\tsseResponse = null;\n\t\t}\n\t\treturn true;\n\t};\n\n\tconst touchHeartbeat = (): void => {\n\t\tlastHeartbeatAt = Date.now();\n\t\tbrowserConnected = true;\n\t};\n\n\tconst getEffectiveTimeoutMs = (): number => Math.max(1000, Math.floor(clientTimeoutSeconds) * 1000);\n\n\tconst shouldTimeoutFromClientIdle = (): boolean => (\n\t\tstate === \"RESULT_SELECTION\" &&\n\t\tclientIdleMs !== null &&\n\t\tclientIdleMs >= getEffectiveTimeoutMs()\n\t);\n\n\tfunction validateToken(body: unknown, res: ServerResponse): boolean {\n\t\tif (!body || typeof body !== \"object\") {\n\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid body\" });\n\t\t\treturn false;\n\t\t}\n\t\tif ((body as { token?: string }).token !== sessionToken) {\n\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tfunction isAvailableProvider(provider: string): boolean {\n\t\tif (provider === \"all\") return availableProviders.all;\n\t\tif (provider === \"openai\") return availableProviders.openai;\n\t\tif (provider === \"brave\") return availableProviders.brave;\n\t\tif (provider === \"parallel\") return availableProviders.parallel;\n\t\tif (provider === \"parallel-mcp\") return availableProviders[\"parallel-mcp\"];\n\t\tif (provider === \"tinyfish\") return availableProviders.tinyfish;\n\t\tif (provider === \"search1api\") return availableProviders.search1api;\n\t\tif (provider === \"searchinfinity\") return availableProviders.searchinfinity;\n\t\tif (provider === \"querit\") return availableProviders.querit;\n\t\tif (provider === \"tavily\") return availableProviders.tavily;\n\t\tif (provider === \"firecrawl\") return availableProviders.firecrawl;\n\t\tif (provider === \"jina\") return availableProviders.jina;\n\t\tif (provider === \"serpdive\") return availableProviders.serpdive;\n\t\tif (provider === \"kagi\") return availableProviders.kagi;\n\t\tif (provider === \"bocha\") return availableProviders.bocha;\n\t\tif (provider === \"ollama\") return availableProviders.ollama;\n\t\tif (provider === \"searxng\") return availableProviders.searxng;\n\t\tif (provider === \"duckduckgo\") return availableProviders.duckduckgo;\n\t\tif (provider === \"perplexity\") return availableProviders.perplexity;\n\t\tif (provider === \"exa\") return availableProviders.exa;\n\t\tif (provider === \"gemini\") return availableProviders.gemini;\n\t\tif (provider === \"anysearch\") return availableProviders.anysearch;\n\t\tif (provider === \"xai\") return availableProviders.xai;\n\t\tif (provider === \"brightdata\") return availableProviders.brightdata;\n\t\tif (provider === \"serpbase\") return availableProviders.serpbase;\n\t\tif (provider === \"serper\") return availableProviders.serper;\n\t\tif (provider === \"valyu\") return availableProviders.valyu;\n\t\treturn false;\n\t}\n\n\tfunction writeSSE(res: ServerResponse, event: string, data: unknown): boolean {\n\t\tconst payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\n\t\ttry {\n\t\t\tres.write(payload);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tfunction sendSSE(event: string, data: unknown): void {\n\t\tconst res = sseResponse;\n\t\tif (res && !res.writableEnded && res.socket && !res.socket.destroyed && writeSSE(res, event, data)) return;\n\t\tif (sseResponse === res) sseResponse = null;\n\t}\n\n\tfunction retainStreamedEvent(event: CuratorStoredEvent): void {\n\t\tstreamedEventsByResultIndex.set(event.data.queryIndex, event);\n\t}\n\n\tfunction getStreamedEvents(): CuratorStoredEvent[] {\n\t\treturn [...streamedEventsByResultIndex.values()];\n\t}\n\n\tfunction replaySSE(res: ServerResponse): void {\n\t\tfor (const item of getStreamedEvents()) {\n\t\t\tif (!writeSSE(res, item.event, item.data)) return;\n\t\t}\n\t\tif (searchStreamDone) writeSSE(res, \"done\", {});\n\t}\n\n\tconst pageHtml = generateCuratorPage(\n\t\tqueries,\n\t\tsessionToken,\n\t\ttimeout,\n\t\tavailableProviders,\n\t\tdefaultProvider,\n\t\tsearchProvider,\n\t\tsummaryModels,\n\t\tdefaultSummaryModel,\n\t);\n\n\tconst server = http.createServer(async (req, res) => {\n\t\ttry {\n\t\t\tconst method = req.method || \"GET\";\n\t\t\tconst url = new URL(req.url || \"/\", `http://${req.headers.host || \"127.0.0.1\"}`);\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/html; charset=utf-8\",\n\t\t\t\t\t\"Cache-Control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(pageHtml);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/events\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tres.writeHead(403, { \"Content-Type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Invalid session\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"No events available\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sseResponse) {\n\t\t\t\t\ttry { sseResponse.end(); } catch {}\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"Content-Type\": \"text/event-stream\",\n\t\t\t\t\t\"Cache-Control\": \"no-cache\",\n\t\t\t\t\tConnection: \"keep-alive\",\n\t\t\t\t\t\"X-Accel-Buffering\": \"no\",\n\t\t\t\t});\n\t\t\t\tres.flushHeaders();\n\t\t\t\tif (res.socket) res.socket.setNoDelay(true);\n\t\t\t\tsseResponse = res;\n\t\t\t\treplaySSE(res);\n\t\t\t\tif (sseKeepalive) clearInterval(sseKeepalive);\n\t\t\t\tsseKeepalive = setInterval(() => {\n\t\t\t\t\tif (sseResponse) {\n\t\t\t\t\t\ttry { sseResponse.write(\":keepalive\\n\\n\"); } catch {}\n\t\t\t\t\t}\n\t\t\t\t}, 15000);\n\t\t\t\treq.on(\"close\", () => {\n\t\t\t\t\tif (sseResponse === res) sseResponse = null;\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"GET\" && url.pathname === \"/state\") {\n\t\t\t\tconst token = url.searchParams.get(\"session\");\n\t\t\t\tif (token !== sessionToken) {\n\t\t\t\t\tsendJson(res, 403, { ok: false, error: \"Invalid session\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tsendJson(res, 200, { ok: true, events: getStreamedEvents(), done: searchStreamDone });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/heartbeat\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\ttouchHeartbeat();\n\t\t\t\tconst heartbeat = body as { idleMs?: unknown; timeoutSec?: unknown };\n\t\t\t\tif (typeof heartbeat.timeoutSec === \"number\" && Number.isFinite(heartbeat.timeoutSec) && heartbeat.timeoutSec > 0) {\n\t\t\t\t\tclientTimeoutSeconds = Math.min(600, Math.floor(heartbeat.timeoutSec));\n\t\t\t\t}\n\t\t\t\tif (typeof heartbeat.idleMs === \"number\" && Number.isFinite(heartbeat.idleMs) && heartbeat.idleMs >= 0) {\n\t\t\t\t\tclientIdleMs = Math.floor(heartbeat.idleMs);\n\t\t\t\t}\n\t\t\t\tconst timedOut = shouldTimeoutFromClientIdle();\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tif (timedOut && markCompleted()) {\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/provider\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tconst { provider } = body as { provider?: string };\n\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetImmediate(() => callbacks.onProviderChange(provider));\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/search\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query, provider } = body as { query?: string; provider?: string };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (provider !== undefined) {\n\t\t\t\t\tif (typeof provider !== \"string\" || provider.length === 0) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid provider\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (!isAvailableProvider(provider)) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst qi = nextQueryIndex++;\n\t\t\t\tconst trimmedQuery = query.trim();\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst results = await callbacks.onAddSearch(trimmedQuery, provider);\n\t\t\t\t\tif (results.length === 0) throw new Error(\"Search returned no provider results\");\n\t\t\t\t\tconst entries = results.map((result, index): IndexedCuratorSearchEntry => ({\n\t\t\t\t\t\t...result,\n\t\t\t\t\t\tqueryIndex: index === 0 ? qi : nextQueryIndex++,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t}));\n\t\t\t\t\tcallbacks.onAddSearchResults(entries);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entries[0], entries });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Search failed\";\n\t\t\t\t\tconst entry: IndexedCuratorSearchEntry = {\n\t\t\t\t\t\tqueryIndex: qi,\n\t\t\t\t\t\tquery: trimmedQuery,\n\t\t\t\t\t\tanswer: \"\",\n\t\t\t\t\t\tresults: [],\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t\tprovider: typeof provider === \"string\" && provider.length > 0 ? provider : defaultProvider,\n\t\t\t\t\t};\n\t\t\t\t\tcallbacks.onAddSearchResults([entry]);\n\t\t\t\t\tsendJson(res, 200, { ok: true, ...entry, entries: [entry] });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/summarize\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: false,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet model: string | undefined;\n\t\t\t\tconst bodyModel = (body as { model?: unknown }).model;\n\t\t\t\tif (bodyModel !== undefined) {\n\t\t\t\t\tif (typeof bodyModel !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid model\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedModel = bodyModel.trim();\n\t\t\t\t\tmodel = trimmedModel.length > 0 ? trimmedModel : undefined;\n\t\t\t\t}\n\n\t\t\t\tconst bodyFeedback = (body as { feedback?: unknown }).feedback;\n\t\t\t\tconst feedback = typeof bodyFeedback === \"string\" && bodyFeedback.trim().length > 0\n\t\t\t\t\t? bodyFeedback.trim()\n\t\t\t\t\t: undefined;\n\n\t\t\t\tabortInFlightSummarize();\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tsummarizeAbortController = controller;\n\t\t\t\tconst requestId = ++summarizeRequestSeq;\n\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await callbacks.onSummarize(parsed.indices, controller.signal, model, feedback);\n\t\t\t\t\tif (requestId !== summarizeRequestSeq || completed) {\n\t\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Summarize request superseded\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsendJson(res, 200, {\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tsummary: result.summary,\n\t\t\t\t\t\tmeta: result.meta,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Summary generation failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t} finally {\n\t\t\t\t\tif (summarizeAbortController === controller) {\n\t\t\t\t\t\tsummarizeAbortController = null;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/rewrite\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (state === \"COMPLETED\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { query } = body as { query?: unknown };\n\t\t\t\tif (typeof query !== \"string\" || query.trim().length === 0) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid query\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\treq.on(\"close\", () => controller.abort());\n\t\t\t\ttouchHeartbeat();\n\t\t\t\ttry {\n\t\t\t\t\tconst rewritten = await callbacks.onRewriteQuery(query.trim(), controller.signal);\n\t\t\t\t\tsendJson(res, 200, { ok: true, query: rewritten });\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : \"Rewrite failed\";\n\t\t\t\t\tconst status = controller.signal.aborted ? 409 : 500;\n\t\t\t\t\tsendJson(res, status, { ok: false, error: message });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/submit\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\n\t\t\t\tconst parsed = normalizeSelectedIndices((body as { selected?: unknown }).selected, {\n\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\tmaxExclusive: nextQueryIndex,\n\t\t\t\t});\n\t\t\t\tif (\"error\" in parsed) {\n\t\t\t\t\tsendJson(res, 400, { ok: false, error: parsed.error });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet summary: string | undefined;\n\t\t\t\tconst bodySummary = (body as { summary?: unknown }).summary;\n\t\t\t\tif (bodySummary !== undefined) {\n\t\t\t\t\tif (typeof bodySummary !== \"string\") {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summary\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst trimmedSummary = bodySummary.trim();\n\t\t\t\t\tsummary = trimmedSummary.length > 0 ? trimmedSummary : undefined;\n\t\t\t\t}\n\n\t\t\t\tlet summaryMeta: SummaryMeta | undefined;\n\t\t\t\tconst bodySummaryMeta = (body as { summaryMeta?: unknown }).summaryMeta;\n\t\t\t\tif (bodySummaryMeta !== undefined) {\n\t\t\t\t\tconst parsedSummaryMeta = normalizeSummaryMeta(bodySummaryMeta);\n\t\t\t\t\tif (!parsedSummaryMeta) {\n\t\t\t\t\t\tsendJson(res, 400, { ok: false, error: \"Invalid summaryMeta\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsummaryMeta = parsedSummaryMeta;\n\t\t\t\t}\n\n\t\t\t\tif (state !== \"SEARCHING\" && state !== \"RESULT_SELECTION\") {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Cannot submit in current state\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 409, { ok: false, error: \"Session closed\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst rawResults = (body as { rawResults?: unknown }).rawResults === true;\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tsetImmediate(() => callbacks.onSubmit({\n\t\t\t\t\tselectedQueryIndices: parsed.indices,\n\t\t\t\t\t...(summary !== undefined ? { summary } : {}),\n\t\t\t\t\t...(summaryMeta !== undefined ? { summaryMeta } : {}),\n\t\t\t\t\trawResults,\n\t\t\t\t}));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (method === \"POST\" && url.pathname === \"/cancel\") {\n\t\t\t\tconst body = await parseBodyOrSend(req, res);\n\t\t\t\tif (!body) return;\n\t\t\t\tif (!validateToken(body, res)) return;\n\t\t\t\tif (!markCompleted()) {\n\t\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst { reason } = body as { reason?: string };\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tconst cancelReason = reason === \"timeout\" ? \"timeout\" : \"user\";\n\t\t\t\tsetImmediate(() => callbacks.onCancel(cancelReason));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(404, { \"Content-Type\": \"text/plain\" });\n\t\t\tres.end(\"Not found\");\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : \"Server error\";\n\t\t\tsendJson(res, 500, { ok: false, error: message });\n\t\t}\n\t});\n\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (err: Error) => {\n\t\t\treject(new Error(`Curator server failed to start: ${err.message}`));\n\t\t};\n\n\t\tconst networkConfig = resolveCuratorNetworkConfig();\n\n\t\tserver.once(\"error\", onError);\n\t\tserver.listen(0, networkConfig.bind, () => {\n\t\t\tserver.off(\"error\", onError);\n\t\t\tconst addr = server.address();\n\t\t\tif (!addr || typeof addr === \"string\") {\n\t\t\t\treject(new Error(\"Curator server: invalid address\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst url = `http://${networkConfig.host}:${addr.port}/?session=${sessionToken}`;\n\n\t\t\twatchdog = setInterval(() => {\n\t\t\t\tif (completed) return;\n\t\t\t\tif (!browserConnected) {\n\t\t\t\t\tconst noBrowserTimeoutMs = Math.max(5000, getEffectiveTimeoutMs());\n\t\t\t\t\tif (state !== \"RESULT_SELECTION\") return;\n\t\t\t\t\tif (Date.now() - stateChangedAt <= noBrowserTimeoutMs) return;\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (shouldTimeoutFromClientIdle()) {\n\t\t\t\t\tif (!markCompleted()) return;\n\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"timeout\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (Date.now() - lastHeartbeatAt <= STALE_THRESHOLD_MS) return;\n\t\t\t\tconst staleReason = state === \"RESULT_SELECTION\" ? \"timeout\" : \"stale\";\n\t\t\t\tif (!markCompleted()) return;\n\t\t\t\tsetImmediate(() => callbacks.onCancel(staleReason));\n\t\t\t}, WATCHDOG_INTERVAL_MS);\n\n\t\t\tresolve({\n\t\t\t\tserver,\n\t\t\t\turl,\n\t\t\t\tclose: () => {\n\t\t\t\t\tconst wasOpen = markCompleted();\n\t\t\t\t\ttry { server.close(); } catch {}\n\t\t\t\t\tif (wasOpen) {\n\t\t\t\t\t\tsetImmediate(() => callbacks.onCancel(\"stale\"));\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tpushResult: (queryIndex, data) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorResultEventData = { ...data, queryIndex, query: data.query ?? queries[queryIndex] ?? \"\" };\n\t\t\t\t\tretainStreamedEvent({ event: \"result\", data: eventData });\n\t\t\t\t\tsendSSE(\"result\", eventData);\n\t\t\t\t},\n\t\t\t\tpushError: (queryIndex, error, provider, meta) => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tnextQueryIndex = Math.max(nextQueryIndex, queryIndex + 1);\n\t\t\t\t\tconst eventData: CuratorSearchErrorEventData = { queryIndex, query: meta?.query ?? queries[queryIndex] ?? \"\", error, provider, slotIndex: meta?.slotIndex };\n\t\t\t\t\tretainStreamedEvent({ event: \"search-error\", data: eventData });\n\t\t\t\t\tsendSSE(\"search-error\", eventData);\n\t\t\t\t},\n\t\t\t\tsearchesDone: () => {\n\t\t\t\t\tif (completed) return;\n\t\t\t\t\tsearchStreamDone = true;\n\t\t\t\t\tsendSSE(\"done\", {});\n\t\t\t\t\tstate = \"RESULT_SELECTION\";\n\t\t\t\t\tstateChangedAt = Date.now();\n\t\t\t\t},\n\t\t\t\tgetConnectionState: () => ({\n\t\t\t\t\tbrowserConnected,\n\t\t\t\t\tlastHeartbeatAgeMs: Date.now() - lastHeartbeatAt,\n\t\t\t\t}),\n\t\t\t});\n\t\t});\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"exa.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/exa.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA2CrE,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,IAAI,CAAC;AAEpD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AA+ID,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,CAAC,CAmFjB;AAuJD,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,wBAAgB,YAAY,IAAI,OAAO,CAMtC;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuE3G","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst EXA_ANSWER_URL = \"https://api.exa.ai/answer\";\nconst EXA_SEARCH_URL = \"https://api.exa.ai/search\";\nconst EXA_MCP_URL = \"https://mcp.exa.ai/mcp\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst EXA_MCP_ADVANCED_TOOL = \"web_search_advanced_exa\";\nconst EXA_MCP_BASIC_TOOL = \"web_search_exa\";\n\ninterface WebSearchConfig {\n\texaApiKey?: unknown;\n}\n\ninterface ExaAnswerResponse {\n\tanswer?: string;\n\tcitations?: Array<{ url?: string; title?: string; text?: string; publishedDate?: string }>;\n}\n\ninterface ExaSearchResponse {\n\tresults?: Array<{\n\t\ttitle?: string;\n\t\turl?: string;\n\t\tpublishedDate?: string;\n\t\tauthor?: string;\n\t\ttext?: string;\n\t\thighlights?: unknown;\n\t\thighlightScores?: number[];\n\t}>;\n}\n\ninterface ExaMcpRpcResponse {\n\tresult?: {\n\t\tcontent?: Array<{ type?: string; text?: string }>;\n\t\tisError?: boolean;\n\t};\n\terror?: {\n\t\tcode?: number;\n\t\tmessage?: string;\n\t};\n}\n\nexport type ExaSearchResult = SearchResponse | null;\n\nexport interface ExaSearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\ntype McpParsedResult = { title: string; url: string; content: string };\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: \"Exa\",\n\t\tconfiguredValue: loadConfig().exaApiKey,\n\t\tenvironmentValue: process.env.EXA_API_KEY,\n\t\tsignal,\n\t});\n}\n\nfunction exaApiHeaders(apiKey: string): Record<string, string> {\n\treturn {\n\t\t\"x-api-key\": apiKey,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"x-exa-integration\": \"pi-web-access\",\n\t};\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(60000);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction recencyToStartDate(filter: string): string {\n\tconst now = new Date();\n\tconst offsets: Record<string, number> = {\n\t\tday: 1,\n\t\tweek: 7,\n\t\tmonth: 30,\n\t\tyear: 365,\n\t};\n\tconst days = offsets[filter] ?? 0;\n\treturn new Date(now.getTime() - days * 86400000).toISOString();\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { includeDomains?: string[]; excludeDomains?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst includeDomains = domainFilter\n\t\t.filter(d => !d.startsWith(\"-\") && d.trim().length > 0)\n\t\t.map(d => d.trim());\n\tconst excludeDomains = domainFilter\n\t\t.filter(d => d.startsWith(\"-\"))\n\t\t.map(d => d.slice(1).trim())\n\t\t.filter(Boolean);\n\treturn {\n\t\t...(includeDomains.length ? { includeDomains } : {}),\n\t\t...(excludeDomains.length ? { excludeDomains } : {}),\n\t};\n}\n\nfunction exaSearchArgs(query: string, options: ExaSearchOptions): Record<string, unknown> {\n\tconst startDate = options.recencyFilter ? recencyToStartDate(options.recencyFilter) : null;\n\treturn {\n\t\tquery,\n\t\ttype: \"auto\",\n\t\tnumResults: options.numResults ?? 5,\n\t\t...mapDomainFilter(options.domainFilter),\n\t\t...(startDate ? { startPublishedDate: startDate } : {}),\n\t};\n}\n\nfunction normalizeHighlights(value: unknown): string[] {\n\tif (!Array.isArray(value)) return [];\n\treturn value.filter((item): item is string => typeof item === \"string\" && item.trim().length > 0);\n}\n\nfunction buildAnswerFromSearchResults(results: ExaSearchResponse[\"results\"]): string {\n\tif (!results?.length) return \"\";\n\tconst parts: string[] = [];\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst item = results[i];\n\t\tif (!item?.url) continue;\n\t\tconst highlights = normalizeHighlights(item.highlights);\n\t\tconst content = highlights.length > 0\n\t\t\t? highlights.join(\" \")\n\t\t\t: typeof item.text === \"string\" ? item.text.trim().slice(0, 1000) : \"\";\n\t\tif (!content) continue;\n\t\tconst sourceTitle = item.title || `Source ${i + 1}`;\n\t\tparts.push(`${content}\\nSource: ${sourceTitle} (${item.url})`);\n\t}\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction mapResults(results: ExaSearchResponse[\"results\"] | ExaAnswerResponse[\"citations\"]): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst item = results[i];\n\t\tif (!item?.url) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${i + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: \"\",\n\t\t});\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: ExaSearchResponse[\"results\"]): ExtractedContent[] {\n\tif (!results?.length) return [];\n\treturn results\n\t\t.filter((r): r is NonNullable<ExaSearchResponse[\"results\"]>[number] & { url: string; text: string } =>\n\t\t\t!!r?.url && typeof r.text === \"string\" && r.text.length > 0)\n\t\t.map(r => ({\n\t\t\turl: r.url,\n\t\t\ttitle: r.title || \"\",\n\t\t\tcontent: r.text,\n\t\t\terror: null,\n\t\t}));\n}\n\nfunction toSearchResponse(\n\tanswer: string,\n\tresults: SearchResponse[\"results\"],\n\tinlineContent: ExtractedContent[] | null,\n): SearchResponse {\n\tconst response: SearchResponse = { answer, results };\n\tif (inlineContent?.length) response.inlineContent = inlineContent;\n\treturn response;\n}\n\nexport async function callExaMcp(\n\ttoolName: string,\n\targs: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\tconst response = await fetch(`${EXA_MCP_URL}?tools=${toolName}`, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"Accept\": \"application/json, text/event-stream\",\n\t\t\t\"x-exa-source\": \"pi-web-access\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tid: 1,\n\t\t\tmethod: \"tools/call\",\n\t\t\tparams: {\n\t\t\t\tname: toolName,\n\t\t\t\targuments: args,\n\t\t\t},\n\t\t}),\n\t\tsignal: requestSignal(signal),\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tif (response.status === 429) {\n\t\t\tthrow new Error(\n\t\t\t\t`Exa MCP rate limit reached (429). Add \"exaApiKey\" to ${CONFIG_PATH} for unthrottled Exa search: ${errorText.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(`Exa MCP error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tconst body = await response.text();\n\tconst dataLines = body.split(\"\\n\").filter(line => line.startsWith(\"data:\"));\n\n\tlet parsed: ExaMcpRpcResponse | null = null;\n\tfor (const line of dataLines) {\n\t\tconst payload = line.slice(5).trim();\n\t\tif (!payload) continue;\n\t\ttry {\n\t\t\tconst candidate = JSON.parse(payload) as ExaMcpRpcResponse;\n\t\t\tif (candidate?.result || candidate?.error) {\n\t\t\t\tparsed = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} catch {\n\t\t}\n\t}\n\n\tif (!parsed) {\n\t\ttry {\n\t\t\tconst candidate = JSON.parse(body) as ExaMcpRpcResponse;\n\t\t\tif (candidate?.result || candidate?.error) {\n\t\t\t\tparsed = candidate;\n\t\t\t}\n\t\t} catch {\n\t\t}\n\t}\n\n\tif (!parsed) {\n\t\tthrow new Error(\"Exa MCP returned an empty response\");\n\t}\n\n\tif (parsed.error) {\n\t\tconst code = typeof parsed.error.code === \"number\" ? ` ${parsed.error.code}` : \"\";\n\t\tconst message = parsed.error.message || \"Unknown error\";\n\t\tthrow new Error(`Exa MCP error${code}: ${message}`);\n\t}\n\n\tif (parsed.result?.isError) {\n\t\tconst message = parsed.result.content\n\t\t\t?.find(item => item.type === \"text\" && typeof item.text === \"string\")\n\t\t\t?.text?.trim();\n\t\tthrow new Error(message || \"Exa MCP returned an error\");\n\t}\n\n\tconst text = parsed.result?.content\n\t\t?.find(item => item.type === \"text\" && typeof item.text === \"string\" && item.text.trim().length > 0)\n\t\t?.text;\n\n\tif (!text) {\n\t\tthrow new Error(\"Exa MCP returned empty content\");\n\t}\n\n\treturn text;\n}\n\nfunction parseMcpResults(text: string): McpParsedResult[] | null {\n\tconst blocks = text.split(/(?=^Title: )/m).filter(block => block.trim().length > 0);\n\tconst parsed = blocks.map(block => {\n\t\tconst title = block.match(/^Title: (.+)/m)?.[1]?.trim() ?? \"\";\n\t\tconst url = block.match(/^URL: (.+)/m)?.[1]?.trim() ?? \"\";\n\t\tlet content = \"\";\n\t\tconst textStart = block.indexOf(\"\\nText: \");\n\t\tif (textStart >= 0) {\n\t\t\tcontent = block.slice(textStart + 7).trim();\n\t\t} else {\n\t\t\tconst hlMatch = block.match(/\\nHighlights:\\s*\\n/);\n\t\t\tif (hlMatch?.index != null) {\n\t\t\t\tcontent = block.slice(hlMatch.index + hlMatch[0].length).trim();\n\t\t\t}\n\t\t}\n\t\tcontent = content.replace(/\\n---\\s*$/, \"\").trim();\n\t\treturn { title, url, content };\n\t}).filter(result => result.url.length > 0);\n\treturn parsed.length > 0 ? parsed : null;\n}\n\nfunction buildAnswerFromMcpResults(results: McpParsedResult[]): string {\n\tif (results.length === 0) return \"\";\n\tconst parts: string[] = [];\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst result = results[i];\n\t\tconst snippet = result.content.replace(/\\s+/g, \" \").trim().slice(0, 500);\n\t\tif (!snippet) continue;\n\t\tconst sourceTitle = result.title || `Source ${i + 1}`;\n\t\tparts.push(`${snippet}\\nSource: ${sourceTitle} (${result.url})`);\n\t}\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction mapMcpInlineContent(results: McpParsedResult[]): ExtractedContent[] {\n\treturn results\n\t\t.filter(result => result.content.length > 0)\n\t\t.map(result => ({\n\t\t\turl: result.url,\n\t\t\ttitle: result.title,\n\t\t\tcontent: result.content,\n\t\t\terror: null,\n\t\t}));\n}\n\nfunction buildMcpQuery(query: string, options: ExaSearchOptions): string {\n\tconst parts = [query];\n\tif (options.domainFilter?.length) {\n\t\tfor (const d of options.domainFilter) {\n\t\t\tparts.push(d.startsWith(\"-\") ? `-site:${d.slice(1)}` : `site:${d}`);\n\t\t}\n\t}\n\tif (options.recencyFilter) {\n\t\tconst now = new Date();\n\t\tswitch (options.recencyFilter) {\n\t\t\tcase \"day\": parts.push(\"past 24 hours\"); break;\n\t\t\tcase \"week\": parts.push(\"past week\"); break;\n\t\t\tcase \"month\": parts.push(`${now.toLocaleString(\"en\", { month: \"long\" })} ${now.getFullYear()}`); break;\n\t\t\tcase \"year\": parts.push(String(now.getFullYear())); break;\n\t\t}\n\t}\n\treturn parts.join(\" \");\n}\n\nfunction isAbortMessage(message: string): boolean {\n\treturn message.toLowerCase().includes(\"abort\");\n}\n\nfunction parseJsonMcpResults(text: string): ExaSearchResponse[\"results\"] | null {\n\ttry {\n\t\tconst results = (JSON.parse(text) as ExaSearchResponse).results;\n\t\treturn Array.isArray(results) && results.length > 0 ? results : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Calls one Exa MCP search tool and normalizes its payload. `web_search_advanced_exa`\n * returns the raw Exa search JSON; `web_search_exa` returns a formatted text block.\n */\nasync function searchWithExaMcpTool(\n\ttool: string,\n\targs: Record<string, unknown>,\n\toptions: ExaSearchOptions,\n): Promise<SearchResponse | null> {\n\tconst text = await callExaMcp(tool, args, options.signal);\n\n\tconst jsonResults = parseJsonMcpResults(text);\n\tif (jsonResults) {\n\t\treturn toSearchResponse(\n\t\t\tbuildAnswerFromSearchResults(jsonResults),\n\t\t\tmapResults(jsonResults),\n\t\t\toptions.includeContent ? mapInlineContent(jsonResults) : null,\n\t\t);\n\t}\n\n\tconst textResults = parseMcpResults(text);\n\tif (!textResults) return null;\n\n\treturn toSearchResponse(\n\t\tbuildAnswerFromMcpResults(textResults),\n\t\tmapResults(textResults),\n\t\toptions.includeContent ? mapMcpInlineContent(textResults) : null,\n\t);\n}\n\n/** Filtered searches need the advanced tool, which not every deployment exposes. */\nasync function searchWithFilteredExaMcp(\n\tquery: string,\n\toptions: ExaSearchOptions,\n\tbasicArgs: Record<string, unknown>,\n): Promise<SearchResponse | null> {\n\ttry {\n\t\treturn await searchWithExaMcpTool(EXA_MCP_ADVANCED_TOOL, {\n\t\t\t...exaSearchArgs(query, options),\n\t\t\tenableHighlights: true,\n\t\t\ttextMaxCharacters: options.includeContent ? 50000 : 3000,\n\t\t}, options);\n\t} catch (err) {\n\t\tif (isAbortMessage(err instanceof Error ? err.message : String(err))) throw err;\n\t\t// The basic tool ignores every argument except query/numResults, so the\n\t\t// filters degrade into the query text.\n\t\treturn searchWithExaMcpTool(EXA_MCP_BASIC_TOOL, basicArgs, options);\n\t}\n}\n\nasync function searchWithExaMcp(query: string, options: ExaSearchOptions = {}): Promise<SearchResponse | null> {\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tconst basicArgs = { query: buildMcpQuery(query, options), numResults: options.numResults ?? 5 };\n\tconst filtered = !!options.includeContent || !!options.recencyFilter || !!options.domainFilter?.length;\n\n\ttry {\n\t\tconst response = filtered\n\t\t\t? await searchWithFilteredExaMcp(query, options, basicArgs)\n\t\t\t: await searchWithExaMcpTool(EXA_MCP_BASIC_TOOL, basicArgs, options);\n\t\tactivityMonitor.logComplete(activityId, 200);\n\t\treturn response;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (isAbortMessage(message)) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nexport function isExaAvailable(): boolean {\n\treturn true;\n}\n\nexport function hasExaApiKey(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Exa\",\n\t\tconfiguredValue: loadConfig().exaApiKey,\n\t\tenvironmentValue: process.env.EXA_API_KEY,\n\t});\n}\n\nexport async function searchWithExa(query: string, options: ExaSearchOptions = {}): Promise<ExaSearchResult> {\n\tconst apiKey = await getApiKey(options.signal);\n\tif (!apiKey) {\n\t\treturn searchWithExaMcp(query, options);\n\t}\n\n\tconst useSearch = options.includeContent\n\t\t|| !!options.recencyFilter\n\t\t|| !!options.domainFilter?.length\n\t\t|| !!(options.numResults && options.numResults !== 5);\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tif (!useSearch) {\n\t\t\tconst response = await fetch(EXA_ANSWER_URL, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: exaApiHeaders(apiKey),\n\t\t\t\tbody: JSON.stringify({ query }),\n\t\t\t\tsignal: requestSignal(options.signal),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\t\tthrow new Error(`Exa API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t\t}\n\n\t\t\tconst data = await response.json() as ExaAnswerResponse;\n\t\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\t\treturn {\n\t\t\t\tanswer: data.answer || \"\",\n\t\t\t\tresults: mapResults(data.citations),\n\t\t\t};\n\t\t}\n\n\t\tconst response = await fetch(EXA_SEARCH_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: exaApiHeaders(apiKey),\n\t\t\tbody: JSON.stringify({\n\t\t\t\t...exaSearchArgs(query, options),\n\t\t\t\tcontents: options.includeContent\n\t\t\t\t\t? { text: true, highlights: true }\n\t\t\t\t\t: { highlights: true },\n\t\t\t}),\n\t\t\tsignal: requestSignal(options.signal),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\tthrow new Error(`Exa API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await response.json() as ExaSearchResponse;\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\n\t\treturn toSearchResponse(\n\t\t\tbuildAnswerFromSearchResults(data.results),\n\t\t\tmapResults(data.results),\n\t\t\toptions.includeContent ? mapInlineContent(data.results) : null,\n\t\t);\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 (isAbortMessage(redactedMessage)) {\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\tthrow new Error(redactedMessage);\n\t}\n}\n"]}
1
+ {"version":3,"file":"exa.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/exa.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA2CrE,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,IAAI,CAAC;AAEpD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAyJD,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,CAAC,CAmFjB;AAuJD,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,wBAAgB,YAAY,IAAI,OAAO,CAMtC;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwE3G","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from \"./utils.ts\";\n\nconst EXA_API_BASE_URL = \"https://api.exa.ai\";\nconst EXA_MCP_URL = \"https://mcp.exa.ai/mcp\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst EXA_MCP_ADVANCED_TOOL = \"web_search_advanced_exa\";\nconst EXA_MCP_BASIC_TOOL = \"web_search_exa\";\n\ninterface WebSearchConfig {\n\texaApiKey?: unknown;\n\texaBaseUrl?: unknown;\n}\n\ninterface ExaAnswerResponse {\n\tanswer?: string;\n\tcitations?: Array<{ url?: string; title?: string; text?: string; publishedDate?: string }>;\n}\n\ninterface ExaSearchResponse {\n\tresults?: Array<{\n\t\ttitle?: string;\n\t\turl?: string;\n\t\tpublishedDate?: string;\n\t\tauthor?: string;\n\t\ttext?: string;\n\t\thighlights?: unknown;\n\t\thighlightScores?: number[];\n\t}>;\n}\n\ninterface ExaMcpRpcResponse {\n\tresult?: {\n\t\tcontent?: Array<{ type?: string; text?: string }>;\n\t\tisError?: boolean;\n\t};\n\terror?: {\n\t\tcode?: number;\n\t\tmessage?: string;\n\t};\n}\n\nexport type ExaSearchResult = SearchResponse | null;\n\nexport interface ExaSearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\ntype McpParsedResult = { title: string; url: string; content: string };\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: \"Exa\",\n\t\tconfiguredValue: loadConfig().exaApiKey,\n\t\tenvironmentValue: process.env.EXA_API_KEY,\n\t\tsignal,\n\t});\n}\n\nfunction getApiBaseUrl(): string {\n\treturn resolveApiBaseUrl({\n\t\tconfigKey: \"exaBaseUrl\",\n\t\tconfiguredValue: loadConfig().exaBaseUrl,\n\t\tdefaultValue: EXA_API_BASE_URL,\n\t\tenvironmentKey: \"EXA_BASE_URL\",\n\t\tenvironmentValue: process.env.EXA_BASE_URL,\n\t});\n}\n\nfunction exaApiHeaders(apiKey: string): Record<string, string> {\n\treturn {\n\t\t\"x-api-key\": apiKey,\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"x-exa-integration\": \"pi-web-access\",\n\t};\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(60000);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction recencyToStartDate(filter: string): string {\n\tconst now = new Date();\n\tconst offsets: Record<string, number> = {\n\t\tday: 1,\n\t\tweek: 7,\n\t\tmonth: 30,\n\t\tyear: 365,\n\t};\n\tconst days = offsets[filter] ?? 0;\n\treturn new Date(now.getTime() - days * 86400000).toISOString();\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { includeDomains?: string[]; excludeDomains?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst includeDomains = domainFilter\n\t\t.filter(d => !d.startsWith(\"-\") && d.trim().length > 0)\n\t\t.map(d => d.trim());\n\tconst excludeDomains = domainFilter\n\t\t.filter(d => d.startsWith(\"-\"))\n\t\t.map(d => d.slice(1).trim())\n\t\t.filter(Boolean);\n\treturn {\n\t\t...(includeDomains.length ? { includeDomains } : {}),\n\t\t...(excludeDomains.length ? { excludeDomains } : {}),\n\t};\n}\n\nfunction exaSearchArgs(query: string, options: ExaSearchOptions): Record<string, unknown> {\n\tconst startDate = options.recencyFilter ? recencyToStartDate(options.recencyFilter) : null;\n\treturn {\n\t\tquery,\n\t\ttype: \"auto\",\n\t\tnumResults: options.numResults ?? 5,\n\t\t...mapDomainFilter(options.domainFilter),\n\t\t...(startDate ? { startPublishedDate: startDate } : {}),\n\t};\n}\n\nfunction normalizeHighlights(value: unknown): string[] {\n\tif (!Array.isArray(value)) return [];\n\treturn value.filter((item): item is string => typeof item === \"string\" && item.trim().length > 0);\n}\n\nfunction buildAnswerFromSearchResults(results: ExaSearchResponse[\"results\"]): string {\n\tif (!results?.length) return \"\";\n\tconst parts: string[] = [];\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst item = results[i];\n\t\tif (!item?.url) continue;\n\t\tconst highlights = normalizeHighlights(item.highlights);\n\t\tconst content = highlights.length > 0\n\t\t\t? highlights.join(\" \")\n\t\t\t: typeof item.text === \"string\" ? item.text.trim().slice(0, 1000) : \"\";\n\t\tif (!content) continue;\n\t\tconst sourceTitle = item.title || `Source ${i + 1}`;\n\t\tparts.push(`${content}\\nSource: ${sourceTitle} (${item.url})`);\n\t}\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction mapResults(results: ExaSearchResponse[\"results\"] | ExaAnswerResponse[\"citations\"]): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst item = results[i];\n\t\tif (!item?.url) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${i + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: \"\",\n\t\t});\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: ExaSearchResponse[\"results\"]): ExtractedContent[] {\n\tif (!results?.length) return [];\n\treturn results\n\t\t.filter((r): r is NonNullable<ExaSearchResponse[\"results\"]>[number] & { url: string; text: string } =>\n\t\t\t!!r?.url && typeof r.text === \"string\" && r.text.length > 0)\n\t\t.map(r => ({\n\t\t\turl: r.url,\n\t\t\ttitle: r.title || \"\",\n\t\t\tcontent: r.text,\n\t\t\terror: null,\n\t\t}));\n}\n\nfunction toSearchResponse(\n\tanswer: string,\n\tresults: SearchResponse[\"results\"],\n\tinlineContent: ExtractedContent[] | null,\n): SearchResponse {\n\tconst response: SearchResponse = { answer, results };\n\tif (inlineContent?.length) response.inlineContent = inlineContent;\n\treturn response;\n}\n\nexport async function callExaMcp(\n\ttoolName: string,\n\targs: Record<string, unknown>,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\tconst response = await fetch(`${EXA_MCP_URL}?tools=${toolName}`, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"Accept\": \"application/json, text/event-stream\",\n\t\t\t\"x-exa-source\": \"pi-web-access\",\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tid: 1,\n\t\t\tmethod: \"tools/call\",\n\t\t\tparams: {\n\t\t\t\tname: toolName,\n\t\t\t\targuments: args,\n\t\t\t},\n\t\t}),\n\t\tsignal: requestSignal(signal),\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tif (response.status === 429) {\n\t\t\tthrow new Error(\n\t\t\t\t`Exa MCP rate limit reached (429). Add \"exaApiKey\" to ${CONFIG_PATH} for unthrottled Exa search: ${errorText.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tthrow new Error(`Exa MCP error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tconst body = await response.text();\n\tconst dataLines = body.split(\"\\n\").filter(line => line.startsWith(\"data:\"));\n\n\tlet parsed: ExaMcpRpcResponse | null = null;\n\tfor (const line of dataLines) {\n\t\tconst payload = line.slice(5).trim();\n\t\tif (!payload) continue;\n\t\ttry {\n\t\t\tconst candidate = JSON.parse(payload) as ExaMcpRpcResponse;\n\t\t\tif (candidate?.result || candidate?.error) {\n\t\t\t\tparsed = candidate;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} catch {\n\t\t}\n\t}\n\n\tif (!parsed) {\n\t\ttry {\n\t\t\tconst candidate = JSON.parse(body) as ExaMcpRpcResponse;\n\t\t\tif (candidate?.result || candidate?.error) {\n\t\t\t\tparsed = candidate;\n\t\t\t}\n\t\t} catch {\n\t\t}\n\t}\n\n\tif (!parsed) {\n\t\tthrow new Error(\"Exa MCP returned an empty response\");\n\t}\n\n\tif (parsed.error) {\n\t\tconst code = typeof parsed.error.code === \"number\" ? ` ${parsed.error.code}` : \"\";\n\t\tconst message = parsed.error.message || \"Unknown error\";\n\t\tthrow new Error(`Exa MCP error${code}: ${message}`);\n\t}\n\n\tif (parsed.result?.isError) {\n\t\tconst message = parsed.result.content\n\t\t\t?.find(item => item.type === \"text\" && typeof item.text === \"string\")\n\t\t\t?.text?.trim();\n\t\tthrow new Error(message || \"Exa MCP returned an error\");\n\t}\n\n\tconst text = parsed.result?.content\n\t\t?.find(item => item.type === \"text\" && typeof item.text === \"string\" && item.text.trim().length > 0)\n\t\t?.text;\n\n\tif (!text) {\n\t\tthrow new Error(\"Exa MCP returned empty content\");\n\t}\n\n\treturn text;\n}\n\nfunction parseMcpResults(text: string): McpParsedResult[] | null {\n\tconst blocks = text.split(/(?=^Title: )/m).filter(block => block.trim().length > 0);\n\tconst parsed = blocks.map(block => {\n\t\tconst title = block.match(/^Title: (.+)/m)?.[1]?.trim() ?? \"\";\n\t\tconst url = block.match(/^URL: (.+)/m)?.[1]?.trim() ?? \"\";\n\t\tlet content = \"\";\n\t\tconst textStart = block.indexOf(\"\\nText: \");\n\t\tif (textStart >= 0) {\n\t\t\tcontent = block.slice(textStart + 7).trim();\n\t\t} else {\n\t\t\tconst hlMatch = block.match(/\\nHighlights:\\s*\\n/);\n\t\t\tif (hlMatch?.index != null) {\n\t\t\t\tcontent = block.slice(hlMatch.index + hlMatch[0].length).trim();\n\t\t\t}\n\t\t}\n\t\tcontent = content.replace(/\\n---\\s*$/, \"\").trim();\n\t\treturn { title, url, content };\n\t}).filter(result => result.url.length > 0);\n\treturn parsed.length > 0 ? parsed : null;\n}\n\nfunction buildAnswerFromMcpResults(results: McpParsedResult[]): string {\n\tif (results.length === 0) return \"\";\n\tconst parts: string[] = [];\n\tfor (let i = 0; i < results.length; i++) {\n\t\tconst result = results[i];\n\t\tconst snippet = result.content.replace(/\\s+/g, \" \").trim().slice(0, 500);\n\t\tif (!snippet) continue;\n\t\tconst sourceTitle = result.title || `Source ${i + 1}`;\n\t\tparts.push(`${snippet}\\nSource: ${sourceTitle} (${result.url})`);\n\t}\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction mapMcpInlineContent(results: McpParsedResult[]): ExtractedContent[] {\n\treturn results\n\t\t.filter(result => result.content.length > 0)\n\t\t.map(result => ({\n\t\t\turl: result.url,\n\t\t\ttitle: result.title,\n\t\t\tcontent: result.content,\n\t\t\terror: null,\n\t\t}));\n}\n\nfunction buildMcpQuery(query: string, options: ExaSearchOptions): string {\n\tconst parts = [query];\n\tif (options.domainFilter?.length) {\n\t\tfor (const d of options.domainFilter) {\n\t\t\tparts.push(d.startsWith(\"-\") ? `-site:${d.slice(1)}` : `site:${d}`);\n\t\t}\n\t}\n\tif (options.recencyFilter) {\n\t\tconst now = new Date();\n\t\tswitch (options.recencyFilter) {\n\t\t\tcase \"day\": parts.push(\"past 24 hours\"); break;\n\t\t\tcase \"week\": parts.push(\"past week\"); break;\n\t\t\tcase \"month\": parts.push(`${now.toLocaleString(\"en\", { month: \"long\" })} ${now.getFullYear()}`); break;\n\t\t\tcase \"year\": parts.push(String(now.getFullYear())); break;\n\t\t}\n\t}\n\treturn parts.join(\" \");\n}\n\nfunction isAbortMessage(message: string): boolean {\n\treturn message.toLowerCase().includes(\"abort\");\n}\n\nfunction parseJsonMcpResults(text: string): ExaSearchResponse[\"results\"] | null {\n\ttry {\n\t\tconst results = (JSON.parse(text) as ExaSearchResponse).results;\n\t\treturn Array.isArray(results) && results.length > 0 ? results : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Calls one Exa MCP search tool and normalizes its payload. `web_search_advanced_exa`\n * returns the raw Exa search JSON; `web_search_exa` returns a formatted text block.\n */\nasync function searchWithExaMcpTool(\n\ttool: string,\n\targs: Record<string, unknown>,\n\toptions: ExaSearchOptions,\n): Promise<SearchResponse | null> {\n\tconst text = await callExaMcp(tool, args, options.signal);\n\n\tconst jsonResults = parseJsonMcpResults(text);\n\tif (jsonResults) {\n\t\treturn toSearchResponse(\n\t\t\tbuildAnswerFromSearchResults(jsonResults),\n\t\t\tmapResults(jsonResults),\n\t\t\toptions.includeContent ? mapInlineContent(jsonResults) : null,\n\t\t);\n\t}\n\n\tconst textResults = parseMcpResults(text);\n\tif (!textResults) return null;\n\n\treturn toSearchResponse(\n\t\tbuildAnswerFromMcpResults(textResults),\n\t\tmapResults(textResults),\n\t\toptions.includeContent ? mapMcpInlineContent(textResults) : null,\n\t);\n}\n\n/** Filtered searches need the advanced tool, which not every deployment exposes. */\nasync function searchWithFilteredExaMcp(\n\tquery: string,\n\toptions: ExaSearchOptions,\n\tbasicArgs: Record<string, unknown>,\n): Promise<SearchResponse | null> {\n\ttry {\n\t\treturn await searchWithExaMcpTool(EXA_MCP_ADVANCED_TOOL, {\n\t\t\t...exaSearchArgs(query, options),\n\t\t\tenableHighlights: true,\n\t\t\ttextMaxCharacters: options.includeContent ? 50000 : 3000,\n\t\t}, options);\n\t} catch (err) {\n\t\tif (isAbortMessage(err instanceof Error ? err.message : String(err))) throw err;\n\t\t// The basic tool ignores every argument except query/numResults, so the\n\t\t// filters degrade into the query text.\n\t\treturn searchWithExaMcpTool(EXA_MCP_BASIC_TOOL, basicArgs, options);\n\t}\n}\n\nasync function searchWithExaMcp(query: string, options: ExaSearchOptions = {}): Promise<SearchResponse | null> {\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tconst basicArgs = { query: buildMcpQuery(query, options), numResults: options.numResults ?? 5 };\n\tconst filtered = !!options.includeContent || !!options.recencyFilter || !!options.domainFilter?.length;\n\n\ttry {\n\t\tconst response = filtered\n\t\t\t? await searchWithFilteredExaMcp(query, options, basicArgs)\n\t\t\t: await searchWithExaMcpTool(EXA_MCP_BASIC_TOOL, basicArgs, options);\n\t\tactivityMonitor.logComplete(activityId, 200);\n\t\treturn response;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tif (isAbortMessage(message)) {\n\t\t\tactivityMonitor.logComplete(activityId, 0);\n\t\t} else {\n\t\t\tactivityMonitor.logError(activityId, message);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nexport function isExaAvailable(): boolean {\n\treturn true;\n}\n\nexport function hasExaApiKey(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Exa\",\n\t\tconfiguredValue: loadConfig().exaApiKey,\n\t\tenvironmentValue: process.env.EXA_API_KEY,\n\t});\n}\n\nexport async function searchWithExa(query: string, options: ExaSearchOptions = {}): Promise<ExaSearchResult> {\n\tconst apiKey = await getApiKey(options.signal);\n\tif (!apiKey) {\n\t\treturn searchWithExaMcp(query, options);\n\t}\n\n\tconst apiBaseUrl = getApiBaseUrl();\n\tconst useSearch = options.includeContent\n\t\t|| !!options.recencyFilter\n\t\t|| !!options.domainFilter?.length\n\t\t|| !!(options.numResults && options.numResults !== 5);\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\n\ttry {\n\t\tif (!useSearch) {\n\t\t\tconst response = await fetchWithCredentialRedirects(`${apiBaseUrl}/answer`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: exaApiHeaders(apiKey),\n\t\t\t\tbody: JSON.stringify({ query }),\n\t\t\t\tsignal: requestSignal(options.signal),\n\t\t\t}, [\"x-api-key\"]);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\t\tthrow new Error(`Exa API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t\t}\n\n\t\t\tconst data = await response.json() as ExaAnswerResponse;\n\t\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\t\treturn {\n\t\t\t\tanswer: data.answer || \"\",\n\t\t\t\tresults: mapResults(data.citations),\n\t\t\t};\n\t\t}\n\n\t\tconst response = await fetchWithCredentialRedirects(`${apiBaseUrl}/search`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: exaApiHeaders(apiKey),\n\t\t\tbody: JSON.stringify({\n\t\t\t\t...exaSearchArgs(query, options),\n\t\t\t\tcontents: options.includeContent\n\t\t\t\t\t? { text: true, highlights: true }\n\t\t\t\t\t: { highlights: true },\n\t\t\t}),\n\t\t\tsignal: requestSignal(options.signal),\n\t\t}, [\"x-api-key\"]);\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\t\tthrow new Error(`Exa API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t\t}\n\n\t\tconst data = await response.json() as ExaSearchResponse;\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\n\t\treturn toSearchResponse(\n\t\t\tbuildAnswerFromSearchResults(data.results),\n\t\t\tmapResults(data.results),\n\t\t\toptions.includeContent ? mapInlineContent(data.results) : null,\n\t\t);\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 (isAbortMessage(redactedMessage)) {\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\tthrow new Error(redactedMessage);\n\t}\n}\n"]}