@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.
- package/CHANGELOG.md +16 -0
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +0 -1
- package/dist/bun/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +46 -22
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tools/index.d.ts +2 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +9 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +82 -0
- package/dist/core/tools/subagent.d.ts.map +1 -0
- package/dist/core/tools/subagent.js +980 -0
- package/dist/core/tools/subagent.js.map +1 -0
- package/dist/humain/bedrock-provider/vendor/index.bundle.d.mts +3 -0
- package/dist/humain/bedrock-provider/vendor/index.bundle.mjs +1509 -0
- package/dist/humain/index.d.ts.map +1 -1
- package/dist/humain/index.js +3 -0
- package/dist/humain/index.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
- package/dist/humain/mcp-adapter/vendor/README.md +47 -5
- package/dist/humain/mcp-adapter/vendor/cli.js +163 -1
- package/dist/humain/mcp-adapter/vendor/commands.ts +128 -22
- package/dist/humain/mcp-adapter/vendor/config.ts +25 -7
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +5 -4
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1663 -458
- package/dist/humain/mcp-adapter/vendor/index.ts +219 -32
- package/dist/humain/mcp-adapter/vendor/init.ts +36 -4
- package/dist/humain/mcp-adapter/vendor/lifecycle.ts +315 -24
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +39 -6
- package/dist/humain/mcp-adapter/vendor/mcp-bearer-store.ts +387 -0
- package/dist/humain/mcp-adapter/vendor/mcp-callback-server.ts +27 -3
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +4 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +42 -13
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +32 -21
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +168 -0
- package/dist/humain/mcp-adapter/vendor/package.json +179 -177
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +5 -4
- package/dist/humain/mcp-adapter/vendor/request-headers-command.ts +31 -37
- package/dist/humain/mcp-adapter/vendor/search-ranking.ts +92 -22
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +140 -2
- package/dist/humain/mcp-adapter/vendor/session-recovery.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +15 -2
- package/dist/humain/mcp-adapter/vendor/tool-result-renderer.ts +44 -4
- package/dist/humain/mcp-adapter/vendor/types.ts +2 -0
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +18 -16
- package/dist/humain/mcp-adapter/vendor/utils.ts +70 -0
- package/dist/humain/web-access/vendor/anysearch.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/anysearch.js +3 -2
- package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
- package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/brave.js +14 -4
- package/dist/humain/web-access/vendor/brave.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts +3 -0
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +4 -1
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.d.ts +3 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +6 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/exa.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/exa.js +16 -7
- package/dist/humain/web-access/vendor/exa.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +31 -3
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
- package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.js +25 -4
- package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
- package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/index.js +36 -49
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts +1 -2
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +5 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/parallel-mcp.d.ts +10 -0
- package/dist/humain/web-access/vendor/parallel-mcp.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/parallel-mcp.js +224 -0
- package/dist/humain/web-access/vendor/parallel-mcp.js.map +1 -0
- package/dist/humain/web-access/vendor/parallel.d.ts +1 -0
- package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/parallel.js +2 -2
- package/dist/humain/web-access/vendor/parallel.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts +3 -0
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/query-rewrite.js +42 -0
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -0
- package/dist/humain/web-access/vendor/serper.d.ts +4 -0
- package/dist/humain/web-access/vendor/serper.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serper.js +206 -0
- package/dist/humain/web-access/vendor/serper.js.map +1 -0
- package/dist/humain/web-access/vendor/summary-model-scope.d.ts +5 -0
- package/dist/humain/web-access/vendor/summary-model-scope.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-model-scope.js +10 -5
- package/dist/humain/web-access/vendor/summary-model-scope.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +40 -14
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/tavily.js +14 -4
- package/dist/humain/web-access/vendor/tavily.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts +10 -0
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +70 -0
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/humain/web-access/vendor/valyu.d.ts +6 -0
- package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/valyu.js +194 -0
- package/dist/humain/web-access/vendor/valyu.js.map +1 -0
- package/docs/extensions.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/subagent/README.md +24 -147
- package/npm-shrinkwrap.json +7 -2
- package/package.json +7 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
- package/src/humain/mcp-adapter/upstream.json +3 -3
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
- package/src/humain/web-access/UPSTREAM.md +1 -1
- package/src/humain/web-access/upstream.json +4 -4
- package/dist/bun/register-bedrock.d.ts +0 -2
- package/dist/bun/register-bedrock.d.ts.map +0 -1
- package/dist/bun/register-bedrock.js +0 -4
- package/dist/bun/register-bedrock.js.map +0 -1
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -1015
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary-review.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/summary-review.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAsC,MAAM,8BAA8B,CAAC;AAC5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,KAAK,gBAAgB,GAAG,OAAO,QAAQ,CAAC;AAQxC,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,eAAe,GAAG,wBAAwB,CAAC;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,eAAe,GAAG,KAAK,GAAG,kBAAkB,CAAC,CAAC;AAiCtH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAkCxF;AA2ED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAkB5G;AAuFD,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,eAAe,EAAE,EAC1B,GAAG,EAAE,wBAAwB,EAC7B,MAAM,CAAC,EAAE,WAAW,EACpB,aAAa,CAAC,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,gBAAgB,EAC7B,UAAU,SAAiC,GACzC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CA6HjD","sourcesContent":["import { complete, type Api, type Message, type Model } from \"@earendil-works/pi-ai/compat\";\nimport type { ExtensionContext } from \"../host.ts\";\nimport { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from \"./summary-model-scope.ts\";\nimport type { QueryResultData } from \"./storage.ts\";\n\ntype ProviderHeaders = Record<string, string | null>;\ntype CompleteFunction = typeof complete;\ntype SummaryModelRegistry = SummaryGenerationContext[\"modelRegistry\"] & { complete?: CompleteFunction };\n\nconst PREFERRED_SUMMARY_MODELS = [\n\t{ provider: \"anthropic\", id: \"claude-haiku-4-5\" },\n\t{ provider: \"openai-codex\", id: \"gpt-5.3-codex-spark\" },\n] as const;\n\nexport const SUMMARY_GENERATION_DEADLINE_MS = 30_000;\n\nexport interface SummaryMeta {\n\tmodel: string | null;\n\tdurationMs: number;\n\ttokenEstimate: number;\n\tfallbackUsed: boolean;\n\tfallbackReason?: string;\n\tphase?: \"summary-model\" | \"deterministic-fallback\";\n\tedited?: boolean;\n}\n\nexport type SummaryGenerationContext = Pick<ExtensionContext, \"model\" | \"modelRegistry\" | \"cwd\" | \"isProjectTrusted\">;\n\nfunction estimateTokens(text: string): number {\n\tconst trimmed = text.trim();\n\tif (trimmed.length === 0) return 0;\n\treturn Math.max(1, Math.ceil(trimmed.length / 4));\n}\n\nfunction summarizeQueryResult(result: QueryResultData): string {\n\tif (result.error) {\n\t\treturn `Query: ${result.query}\\nStatus: Error\\nError: ${result.error}`;\n\t}\n\n\tconst lines = [\n\t\t`Query: ${result.query}`,\n\t\t`Provider: ${result.provider ?? \"unknown\"}`,\n\t\t`Answer: ${result.answer || \"(no answer text returned)\"}`,\n\t];\n\n\tif (result.results.length === 0) {\n\t\tlines.push(\"Sources: none\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tlines.push(\"Sources:\");\n\tfor (let i = 0; i < result.results.length; i++) {\n\t\tconst source = result.results[i];\n\t\tlines.push(`${i + 1}. ${source.title} — ${source.url}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSummaryPrompt(results: QueryResultData[], feedback?: string): string {\n\tconst sections = [\n\t\t\"You are writing the final web search summary for a coding assistant.\",\n\t\t\"Write a concise, factual summary using only the provided search results.\",\n\t\t\"Requirements:\",\n\t\t\"- Keep it readable and skimmable.\",\n\t\t\"- Include key findings and caveats.\",\n\t\t\"- Do not invent sources or claims.\",\n\t\t\"- If evidence is weak or conflicting, say so explicitly.\",\n\t\t\"- End with a short \\\"Sources\\\" section listing the most relevant URLs.\",\n\t];\n\n\tif (feedback) {\n\t\tsections.push(\"- Incorporate the user feedback provided below into the summary.\");\n\t}\n\n\tsections.push(\"\");\n\tsections.push(\"<search_results>\");\n\n\tfor (let i = 0; i < results.length; i++) {\n\t\tsections.push(`\\n[Result ${i + 1}]`);\n\t\tsections.push(summarizeQueryResult(results[i]));\n\t}\n\n\tsections.push(\"\\n</search_results>\");\n\n\tif (feedback) {\n\t\tsections.push(\"\");\n\t\tsections.push(\"<user_feedback>\");\n\t\tsections.push(feedback);\n\t\tsections.push(\"</user_feedback>\");\n\t}\n\n\treturn sections.join(\"\\n\");\n}\n\nfunction buildDeterministicAnswerPreview(answer: string): string {\n\tlet text = answer.replace(/\\s+/g, \" \").trim();\n\tif (text.length === 0) return \"\";\n\n\tconst sourceMarker = text.search(/\\bSources?\\s*:/i);\n\tif (sourceMarker >= 0) text = text.slice(0, sourceMarker).trim();\n\tif (text.length === 0) return \"\";\n\n\treturn text.length > 240 ? `${text.slice(0, 237)}...` : text;\n}\n\nfunction buildDeterministicSummaryLines(results: QueryResultData[]): string[] {\n\tif (results.length === 0) {\n\t\treturn [\n\t\t\t\"No completed search results were available when the curator session finished.\",\n\t\t\t\"\",\n\t\t\t\"Sources\",\n\t\t\t\"- None\",\n\t\t];\n\t}\n\n\tconst lines: string[] = [\n\t\t\"Summary based on the currently selected search results.\",\n\t\t\"\",\n\t];\n\n\tconst sourceUrls: string[] = [];\n\tlet successful = 0;\n\tlet failed = 0;\n\n\tfor (const result of results) {\n\t\tif (result.error) {\n\t\t\tfailed += 1;\n\t\t\tlines.push(`- ${result.query}: failed (${result.error})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tsuccessful += 1;\n\t\tconst preview = buildDeterministicAnswerPreview(result.answer);\n\t\tif (preview.length > 0) {\n\t\t\tlines.push(`- ${result.query}: ${preview}`);\n\t\t} else {\n\t\t\tlines.push(`- ${result.query}: returned ${result.results.length} source${result.results.length === 1 ? \"\" : \"s\"} without answer text.`);\n\t\t}\n\n\t\tfor (const source of result.results) {\n\t\t\tif (!sourceUrls.includes(source.url)) {\n\t\t\t\tsourceUrls.push(source.url);\n\t\t\t}\n\t\t}\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Completed queries: ${results.length}`);\n\tlines.push(`Successful: ${successful}`);\n\tlines.push(`Failed: ${failed}`);\n\tlines.push(\"\");\n\tlines.push(\"Sources\");\n\n\tif (sourceUrls.length === 0) {\n\t\tlines.push(\"- None\");\n\t} else {\n\t\tfor (const url of sourceUrls.slice(0, 12)) {\n\t\t\tlines.push(`- ${url}`);\n\t\t}\n\t\tif (sourceUrls.length > 12) {\n\t\t\tlines.push(`- ... and ${sourceUrls.length - 12} more`);\n\t\t}\n\t}\n\n\treturn lines;\n}\n\nexport function buildDeterministicSummary(results: QueryResultData[]): { summary: string; meta: SummaryMeta } {\n\tconst summary = buildDeterministicSummaryLines(results).join(\"\\n\").trim();\n\tconst nonEmptySummary = summary.length > 0\n\t\t? summary\n\t\t: \"No completed search results were available when the curator session finished.\\n\\nSources\\n- None\";\n\n\treturn {\n\t\tsummary: nonEmptySummary,\n\t\tmeta: {\n\t\t\tmodel: null,\n\t\t\tdurationMs: 0,\n\t\t\ttokenEstimate: estimateTokens(nonEmptySummary),\n\t\t\tfallbackUsed: true,\n\t\t\tfallbackReason: \"deterministic-submit-fallback\",\n\t\t\tphase: \"deterministic-fallback\",\n\t\t\tedited: false,\n\t\t},\n\t};\n}\n\nfunction parseModelSelector(value: string): { provider: string; id: string } {\n\tconst slashIndex = value.indexOf(\"/\");\n\tif (slashIndex <= 0 || slashIndex >= value.length - 1) {\n\t\tthrow new Error(`Invalid summary model: ${value}. Use provider/model-id.`);\n\t}\n\treturn {\n\t\tprovider: value.slice(0, slashIndex),\n\t\tid: value.slice(slashIndex + 1),\n\t};\n}\n\nasync function resolveSummaryModelCandidates(\n\tctx: SummaryGenerationContext,\n\tmodelOverride?: string,\n): Promise<{ candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders }>; errors: string[] }> {\n\tconst enabledModelPatterns = loadEnabledModelPatterns(ctx);\n\tconst specs: Array<{ provider: string; id: string }> = [];\n\tconst normalizedOverride = typeof modelOverride === \"string\" ? modelOverride.trim() : \"\";\n\tif (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));\n\tspecs.push(...PREFERRED_SUMMARY_MODELS);\n\n\tconst candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders }> = [];\n\tconst errors: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const spec of specs) {\n\t\tconst value = `${spec.provider}/${spec.id}`;\n\t\tif (seen.has(value)) continue;\n\t\tseen.add(value);\n\n\t\tconst model = findModelWithProviderRouting(ctx.modelRegistry, spec.provider, spec.id);\n\t\tif (!model) {\n\t\t\terrors.push(`Summary model not found: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!modelMatchesEnabledPatterns(model, enabledModelPatterns)) {\n\t\t\terrors.push(`Summary model is not enabled: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);\n\t\tif (!auth.ok) {\n\t\t\terrors.push(`No API key available for summary model ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tcandidates.push({ model, apiKey: auth.apiKey, headers: auth.headers });\n\t}\n\treturn { candidates, errors };\n}\n\nfunction buildFallbackSummary(\n\tresults: QueryResultData[],\n\tfallbackReason: string,\n\tdurationMs = 0,\n): { summary: string; meta: SummaryMeta } {\n\tconst deterministic = buildDeterministicSummary(results);\n\treturn {\n\t\tsummary: deterministic.summary,\n\t\tmeta: {\n\t\t\t...deterministic.meta,\n\t\t\tdurationMs,\n\t\t\tfallbackReason,\n\t\t},\n\t};\n}\n\nfunction isAbortError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tconst name = (err as { name?: unknown }).name;\n\tconst message = (err as { message?: unknown }).message;\n\treturn name === \"AbortError\" || (typeof message === \"string\" && message.toLowerCase().includes(\"abort\"));\n}\n\nfunction getTextFromContentPart(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"\";\n\tconst value = part as Record<string, unknown>;\n\tif (typeof value.text === \"string\") return value.text;\n\tif (typeof value.refusal === \"string\") return value.refusal;\n\treturn \"\";\n}\n\nfunction getContentPartType(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"unknown\";\n\tconst value = part as Record<string, unknown>;\n\treturn typeof value.type === \"string\" ? value.type : \"unknown\";\n}\n\nexport async function generateSummaryDraft(\n\tresults: QueryResultData[],\n\tctx: SummaryGenerationContext,\n\tsignal?: AbortSignal,\n\tmodelOverride?: string,\n\tfeedback?: string,\n\tcompleteFn?: CompleteFunction,\n\tdeadlineMs = SUMMARY_GENERATION_DEADLINE_MS,\n): Promise<{ summary: string; meta: SummaryMeta }> {\n\tif (!ctx || !ctx.modelRegistry) {\n\t\tthrow new Error(\"Summary generation context unavailable\");\n\t}\n\n\tconst registry = ctx.modelRegistry as SummaryModelRegistry;\n\tconst usesRegistryComplete = !completeFn && typeof registry.complete === \"function\";\n\tcompleteFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : complete;\n\n\tconst generationStartedAt = Date.now();\n\tconst deadlineController = new AbortController();\n\tconst deadlineMarker = Symbol(\"summary-generation-deadline\");\n\tlet deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet resolveDeadline!: () => void;\n\tconst deadlinePromise = new Promise<typeof deadlineMarker>(resolve => {\n\t\tresolveDeadline = () => resolve(deadlineMarker);\n\t\tdeadlineTimer = setTimeout(() => {\n\t\t\tdeadlineController.abort();\n\t\t\tresolveDeadline();\n\t\t}, deadlineMs);\n\t});\n\n\tlet callerAbortListener: (() => void) | undefined;\n\tconst callerAbortPromise = signal\n\t\t? new Promise<never>((_, reject) => {\n\t\t\tcallerAbortListener = () => reject(new Error(\"Aborted\"));\n\t\t\tif (signal.aborted) callerAbortListener();\n\t\t\telse signal.addEventListener(\"abort\", callerAbortListener, { once: true });\n\t\t})\n\t\t: undefined;\n\tconst completionSignal = signal\n\t\t? AbortSignal.any([signal, deadlineController.signal])\n\t\t: deadlineController.signal;\n\n\tasync function raceSummaryOperation<T>(operation: Promise<T>): Promise<T> {\n\t\t// A provider may ignore AbortSignal and never settle; observe it before racing so\n\t\t// its eventual rejection cannot become an unhandled promise rejection.\n\t\tvoid operation.then(() => undefined, () => undefined);\n\t\tconst contenders: Promise<unknown>[] = [operation, deadlinePromise];\n\t\tif (callerAbortPromise) contenders.push(callerAbortPromise);\n\t\tconst result = await Promise.race(contenders);\n\t\tif (result === deadlineMarker) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tthrow deadlineMarker;\n\t\t}\n\t\treturn result as T;\n\t}\n\n\ttry {\n\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\tconst prompt = buildSummaryPrompt(results, feedback);\n\t\tlet resolved: Awaited<ReturnType<typeof resolveSummaryModelCandidates>>;\n\t\ttry {\n\t\t\tresolved = await raceSummaryOperation(resolveSummaryModelCandidates(ctx, modelOverride));\n\t\t} catch (err) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t}\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\treturn buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);\n\t\t}\n\n\t\tlet lastError = resolved.errors.at(-1);\n\t\tfor (const { model, apiKey, headers } of resolved.candidates) {\n\t\t\tconst startedAt = Date.now();\n\t\t\ttry {\n\t\t\t\tconst userMessage: Message = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: prompt }],\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t};\n\n\t\t\t\tconst response = await raceSummaryOperation(Promise.resolve(completeFn(\n\t\t\t\t\tmodel,\n\t\t\t\t\t{ messages: [userMessage] },\n\t\t\t\t\tusesRegistryComplete ? { signal: completionSignal } : { apiKey, headers, signal: completionSignal },\n\t\t\t\t)));\n\t\t\t\tif (response.stopReason === \"aborted\") {\n\t\t\t\t\tthrow new Error(\"Aborted\");\n\t\t\t\t}\n\n\t\t\t\tconst contentParts = Array.isArray(response.content) ? response.content : [];\n\t\t\t\tconst summary = contentParts\n\t\t\t\t\t.map(part => getTextFromContentPart(part))\n\t\t\t\t\t.filter(text => text.trim().length > 0)\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t.trim();\n\n\t\t\t\tif (summary.length === 0) {\n\t\t\t\t\tconst partTypes = contentParts.map(part => getContentPartType(part));\n\t\t\t\t\tconst typesLabel = partTypes.length > 0 ? partTypes.join(\", \") : \"none\";\n\t\t\t\t\tthrow new Error(`Summary model returned empty response (content parts: ${typesLabel})`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsummary,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tmodel: `${model.provider}/${model.id}`,\n\t\t\t\t\t\tdurationMs: Math.max(0, Date.now() - startedAt),\n\t\t\t\t\t\ttokenEstimate: estimateTokens(summary),\n\t\t\t\t\t\tfallbackUsed: false,\n\t\t\t\t\t\tphase: \"summary-model\",\n\t\t\t\t\t\tedited: false,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t\t}\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tlastError = err instanceof Error ? err.message : String(err);\n\t\t\t}\n\t\t}\n\n\t\treturn buildFallbackSummary(\n\t\t\tresults,\n\t\t\tlastError ? `summary-model-unavailable: ${lastError}` : \"summary-model-unavailable\",\n\t\t\tDate.now() - generationStartedAt,\n\t\t);\n\t} finally {\n\t\tif (deadlineTimer) clearTimeout(deadlineTimer);\n\t\tif (signal && callerAbortListener) signal.removeEventListener(\"abort\", callerAbortListener);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"summary-review.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/summary-review.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAsD,MAAM,8BAA8B,CAAC;AAC5G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,KAAK,gBAAgB,GAAG,OAAO,QAAQ,CAAC;AAYxC,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAErD,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,eAAe,GAAG,wBAAwB,CAAC;IACnD,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,eAAe,GAAG,KAAK,GAAG,kBAAkB,CAAC,CAAC;AAiCtH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAkCxF;AA2ED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAkB5G;AA8FD,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,eAAe,EAAE,EAC1B,GAAG,EAAE,wBAAwB,EAC7B,MAAM,CAAC,EAAE,WAAW,EACpB,aAAa,CAAC,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,MAAM,EACjB,UAAU,CAAC,EAAE,gBAAgB,EAC7B,UAAU,SAAiC,GACzC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAuIjD","sourcesContent":["import { clampThinkingLevel, type ModelThinkingLevel, type ThinkingLevel } from \"@earendil-works/pi-ai\";\nimport { complete, completeSimple, type Api, type Message, type Model } from \"@earendil-works/pi-ai/compat\";\nimport type { ExtensionContext } from \"../host.ts\";\nimport { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix, type SummaryThinkingLevel } from \"./summary-model-scope.ts\";\nimport type { QueryResultData } from \"./storage.ts\";\n\ntype ProviderHeaders = Record<string, string | null>;\ntype CompleteFunction = typeof complete;\ntype SummaryModelRegistry = SummaryGenerationContext[\"modelRegistry\"] & { complete?: CompleteFunction };\n\nconst PREFERRED_SUMMARY_MODELS = [\n\t{ provider: \"anthropic\", id: \"claude-haiku-4-5\" },\n\t{ provider: \"openai-codex\", id: \"gpt-5.6-luna\" },\n\t{ provider: \"openai-codex\", id: \"gpt-5.6-terra\" },\n\t{ provider: \"google\", id: \"gemini-3.6-flash\" },\n\t{ provider: \"openai\", id: \"gpt-5-mini\" },\n\t{ provider: \"deepseek\", id: \"deepseek-v4-flash\" },\n] as const;\n\nexport const SUMMARY_GENERATION_DEADLINE_MS = 30_000;\n\nexport interface SummaryMeta {\n\tmodel: string | null;\n\tdurationMs: number;\n\ttokenEstimate: number;\n\tfallbackUsed: boolean;\n\tfallbackReason?: string;\n\tphase?: \"summary-model\" | \"deterministic-fallback\";\n\tedited?: boolean;\n}\n\nexport type SummaryGenerationContext = Pick<ExtensionContext, \"model\" | \"modelRegistry\" | \"cwd\" | \"isProjectTrusted\">;\n\nfunction estimateTokens(text: string): number {\n\tconst trimmed = text.trim();\n\tif (trimmed.length === 0) return 0;\n\treturn Math.max(1, Math.ceil(trimmed.length / 4));\n}\n\nfunction summarizeQueryResult(result: QueryResultData): string {\n\tif (result.error) {\n\t\treturn `Query: ${result.query}\\nStatus: Error\\nError: ${result.error}`;\n\t}\n\n\tconst lines = [\n\t\t`Query: ${result.query}`,\n\t\t`Provider: ${result.provider ?? \"unknown\"}`,\n\t\t`Answer: ${result.answer || \"(no answer text returned)\"}`,\n\t];\n\n\tif (result.results.length === 0) {\n\t\tlines.push(\"Sources: none\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tlines.push(\"Sources:\");\n\tfor (let i = 0; i < result.results.length; i++) {\n\t\tconst source = result.results[i];\n\t\tlines.push(`${i + 1}. ${source.title} — ${source.url}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSummaryPrompt(results: QueryResultData[], feedback?: string): string {\n\tconst sections = [\n\t\t\"You are writing the final web search summary for a coding assistant.\",\n\t\t\"Write a concise, factual summary using only the provided search results.\",\n\t\t\"Requirements:\",\n\t\t\"- Keep it readable and skimmable.\",\n\t\t\"- Include key findings and caveats.\",\n\t\t\"- Do not invent sources or claims.\",\n\t\t\"- If evidence is weak or conflicting, say so explicitly.\",\n\t\t\"- End with a short \\\"Sources\\\" section listing the most relevant URLs.\",\n\t];\n\n\tif (feedback) {\n\t\tsections.push(\"- Incorporate the user feedback provided below into the summary.\");\n\t}\n\n\tsections.push(\"\");\n\tsections.push(\"<search_results>\");\n\n\tfor (let i = 0; i < results.length; i++) {\n\t\tsections.push(`\\n[Result ${i + 1}]`);\n\t\tsections.push(summarizeQueryResult(results[i]));\n\t}\n\n\tsections.push(\"\\n</search_results>\");\n\n\tif (feedback) {\n\t\tsections.push(\"\");\n\t\tsections.push(\"<user_feedback>\");\n\t\tsections.push(feedback);\n\t\tsections.push(\"</user_feedback>\");\n\t}\n\n\treturn sections.join(\"\\n\");\n}\n\nfunction buildDeterministicAnswerPreview(answer: string): string {\n\tlet text = answer.replace(/\\s+/g, \" \").trim();\n\tif (text.length === 0) return \"\";\n\n\tconst sourceMarker = text.search(/\\bSources?\\s*:/i);\n\tif (sourceMarker >= 0) text = text.slice(0, sourceMarker).trim();\n\tif (text.length === 0) return \"\";\n\n\treturn text.length > 240 ? `${text.slice(0, 237)}...` : text;\n}\n\nfunction buildDeterministicSummaryLines(results: QueryResultData[]): string[] {\n\tif (results.length === 0) {\n\t\treturn [\n\t\t\t\"No completed search results were available when the curator session finished.\",\n\t\t\t\"\",\n\t\t\t\"Sources\",\n\t\t\t\"- None\",\n\t\t];\n\t}\n\n\tconst lines: string[] = [\n\t\t\"Summary based on the currently selected search results.\",\n\t\t\"\",\n\t];\n\n\tconst sourceUrls: string[] = [];\n\tlet successful = 0;\n\tlet failed = 0;\n\n\tfor (const result of results) {\n\t\tif (result.error) {\n\t\t\tfailed += 1;\n\t\t\tlines.push(`- ${result.query}: failed (${result.error})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tsuccessful += 1;\n\t\tconst preview = buildDeterministicAnswerPreview(result.answer);\n\t\tif (preview.length > 0) {\n\t\t\tlines.push(`- ${result.query}: ${preview}`);\n\t\t} else {\n\t\t\tlines.push(`- ${result.query}: returned ${result.results.length} source${result.results.length === 1 ? \"\" : \"s\"} without answer text.`);\n\t\t}\n\n\t\tfor (const source of result.results) {\n\t\t\tif (!sourceUrls.includes(source.url)) {\n\t\t\t\tsourceUrls.push(source.url);\n\t\t\t}\n\t\t}\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Completed queries: ${results.length}`);\n\tlines.push(`Successful: ${successful}`);\n\tlines.push(`Failed: ${failed}`);\n\tlines.push(\"\");\n\tlines.push(\"Sources\");\n\n\tif (sourceUrls.length === 0) {\n\t\tlines.push(\"- None\");\n\t} else {\n\t\tfor (const url of sourceUrls.slice(0, 12)) {\n\t\t\tlines.push(`- ${url}`);\n\t\t}\n\t\tif (sourceUrls.length > 12) {\n\t\t\tlines.push(`- ... and ${sourceUrls.length - 12} more`);\n\t\t}\n\t}\n\n\treturn lines;\n}\n\nexport function buildDeterministicSummary(results: QueryResultData[]): { summary: string; meta: SummaryMeta } {\n\tconst summary = buildDeterministicSummaryLines(results).join(\"\\n\").trim();\n\tconst nonEmptySummary = summary.length > 0\n\t\t? summary\n\t\t: \"No completed search results were available when the curator session finished.\\n\\nSources\\n- None\";\n\n\treturn {\n\t\tsummary: nonEmptySummary,\n\t\tmeta: {\n\t\t\tmodel: null,\n\t\t\tdurationMs: 0,\n\t\t\ttokenEstimate: estimateTokens(nonEmptySummary),\n\t\t\tfallbackUsed: true,\n\t\t\tfallbackReason: \"deterministic-submit-fallback\",\n\t\t\tphase: \"deterministic-fallback\",\n\t\t\tedited: false,\n\t\t},\n\t};\n}\n\nfunction parseModelSelector(value: string): { provider: string; id: string; thinkingLevel?: SummaryThinkingLevel } {\n\tconst selector = splitThinkingSuffix(value);\n\tconst slashIndex = selector.value.indexOf(\"/\");\n\tif (slashIndex <= 0 || slashIndex >= selector.value.length - 1) {\n\t\tthrow new Error(`Invalid summary model: ${value}. Use provider/model-id.`);\n\t}\n\treturn {\n\t\tprovider: selector.value.slice(0, slashIndex),\n\t\tid: selector.value.slice(slashIndex + 1),\n\t\tthinkingLevel: selector.thinkingLevel,\n\t};\n}\n\nfunction resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): ModelThinkingLevel | undefined {\n\tif (!requested) return undefined;\n\treturn clampThinkingLevel(model, requested as ModelThinkingLevel);\n}\n\nasync function resolveSummaryModelCandidates(\n\tctx: SummaryGenerationContext,\n\tmodelOverride?: string,\n): Promise<{ candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders; thinkingLevel?: SummaryThinkingLevel }>; errors: string[] }> {\n\tconst enabledModelPatterns = loadEnabledModelPatterns(ctx);\n\tconst specs: Array<{ provider: string; id: string; thinkingLevel?: SummaryThinkingLevel }> = [];\n\tconst normalizedOverride = typeof modelOverride === \"string\" ? modelOverride.trim() : \"\";\n\tif (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));\n\tspecs.push(...PREFERRED_SUMMARY_MODELS);\n\n\tconst candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders; thinkingLevel?: SummaryThinkingLevel }> = [];\n\tconst errors: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const spec of specs) {\n\t\tconst value = `${spec.provider}/${spec.id}`;\n\t\tif (seen.has(value)) continue;\n\t\tseen.add(value);\n\n\t\tconst model = findModelWithProviderRouting(ctx.modelRegistry, spec.provider, spec.id);\n\t\tif (!model) {\n\t\t\terrors.push(`Summary model not found: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!modelMatchesEnabledPatterns(model, enabledModelPatterns)) {\n\t\t\terrors.push(`Summary model is not enabled: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);\n\t\tif (!auth.ok) {\n\t\t\terrors.push(`No API key available for summary model ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tcandidates.push({ model, apiKey: auth.apiKey, headers: auth.headers, thinkingLevel: spec.thinkingLevel });\n\t}\n\treturn { candidates, errors };\n}\n\nfunction buildFallbackSummary(\n\tresults: QueryResultData[],\n\tfallbackReason: string,\n\tdurationMs = 0,\n): { summary: string; meta: SummaryMeta } {\n\tconst deterministic = buildDeterministicSummary(results);\n\treturn {\n\t\tsummary: deterministic.summary,\n\t\tmeta: {\n\t\t\t...deterministic.meta,\n\t\t\tdurationMs,\n\t\t\tfallbackReason,\n\t\t},\n\t};\n}\n\nfunction isAbortError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tconst name = (err as { name?: unknown }).name;\n\tconst message = (err as { message?: unknown }).message;\n\treturn name === \"AbortError\" || (typeof message === \"string\" && message.toLowerCase().includes(\"abort\"));\n}\n\nfunction getTextFromContentPart(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"\";\n\tconst value = part as Record<string, unknown>;\n\tif (typeof value.text === \"string\") return value.text;\n\tif (typeof value.refusal === \"string\") return value.refusal;\n\treturn \"\";\n}\n\nfunction getContentPartType(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"unknown\";\n\tconst value = part as Record<string, unknown>;\n\treturn typeof value.type === \"string\" ? value.type : \"unknown\";\n}\n\nexport async function generateSummaryDraft(\n\tresults: QueryResultData[],\n\tctx: SummaryGenerationContext,\n\tsignal?: AbortSignal,\n\tmodelOverride?: string,\n\tfeedback?: string,\n\tcompleteFn?: CompleteFunction,\n\tdeadlineMs = SUMMARY_GENERATION_DEADLINE_MS,\n): Promise<{ summary: string; meta: SummaryMeta }> {\n\tif (!ctx || !ctx.modelRegistry) {\n\t\tthrow new Error(\"Summary generation context unavailable\");\n\t}\n\n\tconst registry = ctx.modelRegistry as SummaryModelRegistry;\n\tconst customCompleteFn = completeFn !== undefined;\n\tconst usesRegistryComplete = !customCompleteFn && typeof registry.complete === \"function\";\n\tcompleteFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : complete;\n\n\tconst generationStartedAt = Date.now();\n\tconst deadlineController = new AbortController();\n\tconst deadlineMarker = Symbol(\"summary-generation-deadline\");\n\tlet deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet resolveDeadline!: () => void;\n\tconst deadlinePromise = new Promise<typeof deadlineMarker>(resolve => {\n\t\tresolveDeadline = () => resolve(deadlineMarker);\n\t\tdeadlineTimer = setTimeout(() => {\n\t\t\tdeadlineController.abort();\n\t\t\tresolveDeadline();\n\t\t}, deadlineMs);\n\t});\n\n\tlet callerAbortListener: (() => void) | undefined;\n\tconst callerAbortPromise = signal\n\t\t? new Promise<never>((_, reject) => {\n\t\t\tcallerAbortListener = () => reject(new Error(\"Aborted\"));\n\t\t\tif (signal.aborted) callerAbortListener();\n\t\t\telse signal.addEventListener(\"abort\", callerAbortListener, { once: true });\n\t\t})\n\t\t: undefined;\n\tconst completionSignal = signal\n\t\t? AbortSignal.any([signal, deadlineController.signal])\n\t\t: deadlineController.signal;\n\n\tasync function raceSummaryOperation<T>(operation: Promise<T>): Promise<T> {\n\t\t// A provider may ignore AbortSignal and never settle; observe it before racing so\n\t\t// its eventual rejection cannot become an unhandled promise rejection.\n\t\tvoid operation.then(() => undefined, () => undefined);\n\t\tconst contenders: Promise<unknown>[] = [operation, deadlinePromise];\n\t\tif (callerAbortPromise) contenders.push(callerAbortPromise);\n\t\tconst result = await Promise.race(contenders);\n\t\tif (result === deadlineMarker) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tthrow deadlineMarker;\n\t\t}\n\t\treturn result as T;\n\t}\n\n\ttry {\n\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\tconst prompt = buildSummaryPrompt(results, feedback);\n\t\tlet resolved: Awaited<ReturnType<typeof resolveSummaryModelCandidates>>;\n\t\ttry {\n\t\t\tresolved = await raceSummaryOperation(resolveSummaryModelCandidates(ctx, modelOverride));\n\t\t} catch (err) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t}\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\treturn buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);\n\t\t}\n\n\t\tlet lastError = resolved.errors.at(-1);\n\t\tfor (const { model, apiKey, headers, thinkingLevel } of resolved.candidates) {\n\t\t\tconst startedAt = Date.now();\n\t\t\ttry {\n\t\t\t\tconst userMessage: Message = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: prompt }],\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t};\n\t\t\t\tconst requestedThinkingLevel = resolveThinkingLevel(model, thinkingLevel);\n\t\t\t\tconst enabledThinkingLevel = requestedThinkingLevel && requestedThinkingLevel !== \"off\"\n\t\t\t\t\t? requestedThinkingLevel as ThinkingLevel\n\t\t\t\t\t: undefined;\n\t\t\t\tconst completionOptions = {\n\t\t\t\t\t...(usesRegistryComplete ? {} : { apiKey, headers }),\n\t\t\t\t\tsignal: completionSignal,\n\t\t\t\t\t...(requestedThinkingLevel ? { reasoning: requestedThinkingLevel } : {}),\n\t\t\t\t\t...(enabledThinkingLevel ? { reasoningEffort: enabledThinkingLevel } : {}),\n\t\t\t\t};\n\t\t\t\tconst completion = thinkingLevel !== undefined && !customCompleteFn && !usesRegistryComplete\n\t\t\t\t\t? completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })\n\t\t\t\t\t: completeFn(model, { messages: [userMessage] }, completionOptions);\n\n\t\t\t\tconst response = await raceSummaryOperation(Promise.resolve(completion));\n\t\t\t\tif (response.stopReason === \"aborted\") {\n\t\t\t\t\tthrow new Error(\"Aborted\");\n\t\t\t\t}\n\n\t\t\t\tconst contentParts = Array.isArray(response.content) ? response.content : [];\n\t\t\t\tconst summary = contentParts\n\t\t\t\t\t.map((part: unknown) => getTextFromContentPart(part))\n\t\t\t\t\t.filter((text: string) => text.trim().length > 0)\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t.trim();\n\n\t\t\t\tif (summary.length === 0) {\n\t\t\t\t\tconst partTypes = contentParts.map((part: unknown) => getContentPartType(part));\n\t\t\t\t\tconst typesLabel = partTypes.length > 0 ? partTypes.join(\", \") : \"none\";\n\t\t\t\t\tthrow new Error(`Summary model returned empty response (content parts: ${typesLabel})`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsummary,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tmodel: `${model.provider}/${model.id}`,\n\t\t\t\t\t\tdurationMs: Math.max(0, Date.now() - startedAt),\n\t\t\t\t\t\ttokenEstimate: estimateTokens(summary),\n\t\t\t\t\t\tfallbackUsed: false,\n\t\t\t\t\t\tphase: \"summary-model\",\n\t\t\t\t\t\tedited: false,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t\t}\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tlastError = err instanceof Error ? err.message : String(err);\n\t\t\t}\n\t\t}\n\n\t\treturn buildFallbackSummary(\n\t\t\tresults,\n\t\t\tlastError ? `summary-model-unavailable: ${lastError}` : \"summary-model-unavailable\",\n\t\t\tDate.now() - generationStartedAt,\n\t\t);\n\t} finally {\n\t\tif (deadlineTimer) clearTimeout(deadlineTimer);\n\t\tif (signal && callerAbortListener) signal.removeEventListener(\"abort\", callerAbortListener);\n\t}\n}\n"]}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { clampThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
+
import { complete, completeSimple } from "@earendil-works/pi-ai/compat";
|
|
3
|
+
import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix } from "./summary-model-scope.js";
|
|
3
4
|
const PREFERRED_SUMMARY_MODELS = [
|
|
4
5
|
{ provider: "anthropic", id: "claude-haiku-4-5" },
|
|
5
|
-
{ provider: "openai-codex", id: "gpt-5.
|
|
6
|
+
{ provider: "openai-codex", id: "gpt-5.6-luna" },
|
|
7
|
+
{ provider: "openai-codex", id: "gpt-5.6-terra" },
|
|
8
|
+
{ provider: "google", id: "gemini-3.6-flash" },
|
|
9
|
+
{ provider: "openai", id: "gpt-5-mini" },
|
|
10
|
+
{ provider: "deepseek", id: "deepseek-v4-flash" },
|
|
6
11
|
];
|
|
7
12
|
export const SUMMARY_GENERATION_DEADLINE_MS = 30_000;
|
|
8
13
|
function estimateTokens(text) {
|
|
@@ -145,15 +150,22 @@ export function buildDeterministicSummary(results) {
|
|
|
145
150
|
};
|
|
146
151
|
}
|
|
147
152
|
function parseModelSelector(value) {
|
|
148
|
-
const
|
|
149
|
-
|
|
153
|
+
const selector = splitThinkingSuffix(value);
|
|
154
|
+
const slashIndex = selector.value.indexOf("/");
|
|
155
|
+
if (slashIndex <= 0 || slashIndex >= selector.value.length - 1) {
|
|
150
156
|
throw new Error(`Invalid summary model: ${value}. Use provider/model-id.`);
|
|
151
157
|
}
|
|
152
158
|
return {
|
|
153
|
-
provider: value.slice(0, slashIndex),
|
|
154
|
-
id: value.slice(slashIndex + 1),
|
|
159
|
+
provider: selector.value.slice(0, slashIndex),
|
|
160
|
+
id: selector.value.slice(slashIndex + 1),
|
|
161
|
+
thinkingLevel: selector.thinkingLevel,
|
|
155
162
|
};
|
|
156
163
|
}
|
|
164
|
+
function resolveThinkingLevel(model, requested) {
|
|
165
|
+
if (!requested)
|
|
166
|
+
return undefined;
|
|
167
|
+
return clampThinkingLevel(model, requested);
|
|
168
|
+
}
|
|
157
169
|
async function resolveSummaryModelCandidates(ctx, modelOverride) {
|
|
158
170
|
const enabledModelPatterns = loadEnabledModelPatterns(ctx);
|
|
159
171
|
const specs = [];
|
|
@@ -183,7 +195,7 @@ async function resolveSummaryModelCandidates(ctx, modelOverride) {
|
|
|
183
195
|
errors.push(`No API key available for summary model ${value}`);
|
|
184
196
|
continue;
|
|
185
197
|
}
|
|
186
|
-
candidates.push({ model, apiKey: auth.apiKey, headers: auth.headers });
|
|
198
|
+
candidates.push({ model, apiKey: auth.apiKey, headers: auth.headers, thinkingLevel: spec.thinkingLevel });
|
|
187
199
|
}
|
|
188
200
|
return { candidates, errors };
|
|
189
201
|
}
|
|
@@ -226,7 +238,8 @@ export async function generateSummaryDraft(results, ctx, signal, modelOverride,
|
|
|
226
238
|
throw new Error("Summary generation context unavailable");
|
|
227
239
|
}
|
|
228
240
|
const registry = ctx.modelRegistry;
|
|
229
|
-
const
|
|
241
|
+
const customCompleteFn = completeFn !== undefined;
|
|
242
|
+
const usesRegistryComplete = !customCompleteFn && typeof registry.complete === "function";
|
|
230
243
|
completeFn ??= usesRegistryComplete ? registry.complete.bind(registry) : complete;
|
|
231
244
|
const generationStartedAt = Date.now();
|
|
232
245
|
const deadlineController = new AbortController();
|
|
@@ -286,7 +299,7 @@ export async function generateSummaryDraft(results, ctx, signal, modelOverride,
|
|
|
286
299
|
return buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);
|
|
287
300
|
}
|
|
288
301
|
let lastError = resolved.errors.at(-1);
|
|
289
|
-
for (const { model, apiKey, headers } of resolved.candidates) {
|
|
302
|
+
for (const { model, apiKey, headers, thinkingLevel } of resolved.candidates) {
|
|
290
303
|
const startedAt = Date.now();
|
|
291
304
|
try {
|
|
292
305
|
const userMessage = {
|
|
@@ -294,18 +307,31 @@ export async function generateSummaryDraft(results, ctx, signal, modelOverride,
|
|
|
294
307
|
content: [{ type: "text", text: prompt }],
|
|
295
308
|
timestamp: Date.now(),
|
|
296
309
|
};
|
|
297
|
-
const
|
|
310
|
+
const requestedThinkingLevel = resolveThinkingLevel(model, thinkingLevel);
|
|
311
|
+
const enabledThinkingLevel = requestedThinkingLevel && requestedThinkingLevel !== "off"
|
|
312
|
+
? requestedThinkingLevel
|
|
313
|
+
: undefined;
|
|
314
|
+
const completionOptions = {
|
|
315
|
+
...(usesRegistryComplete ? {} : { apiKey, headers }),
|
|
316
|
+
signal: completionSignal,
|
|
317
|
+
...(requestedThinkingLevel ? { reasoning: requestedThinkingLevel } : {}),
|
|
318
|
+
...(enabledThinkingLevel ? { reasoningEffort: enabledThinkingLevel } : {}),
|
|
319
|
+
};
|
|
320
|
+
const completion = thinkingLevel !== undefined && !customCompleteFn && !usesRegistryComplete
|
|
321
|
+
? completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })
|
|
322
|
+
: completeFn(model, { messages: [userMessage] }, completionOptions);
|
|
323
|
+
const response = await raceSummaryOperation(Promise.resolve(completion));
|
|
298
324
|
if (response.stopReason === "aborted") {
|
|
299
325
|
throw new Error("Aborted");
|
|
300
326
|
}
|
|
301
327
|
const contentParts = Array.isArray(response.content) ? response.content : [];
|
|
302
328
|
const summary = contentParts
|
|
303
|
-
.map(part => getTextFromContentPart(part))
|
|
304
|
-
.filter(text => text.trim().length > 0)
|
|
329
|
+
.map((part) => getTextFromContentPart(part))
|
|
330
|
+
.filter((text) => text.trim().length > 0)
|
|
305
331
|
.join("\n")
|
|
306
332
|
.trim();
|
|
307
333
|
if (summary.length === 0) {
|
|
308
|
-
const partTypes = contentParts.map(part => getContentPartType(part));
|
|
334
|
+
const partTypes = contentParts.map((part) => getContentPartType(part));
|
|
309
335
|
const typesLabel = partTypes.length > 0 ? partTypes.join(", ") : "none";
|
|
310
336
|
throw new Error(`Summary model returned empty response (content parts: ${typesLabel})`);
|
|
311
337
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary-review.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/summary-review.ts"],"names":[],"mappings":"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","sourcesContent":["import { complete, type Api, type Message, type Model } from \"@earendil-works/pi-ai/compat\";\nimport type { ExtensionContext } from \"../host.ts\";\nimport { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from \"./summary-model-scope.ts\";\nimport type { QueryResultData } from \"./storage.ts\";\n\ntype ProviderHeaders = Record<string, string | null>;\ntype CompleteFunction = typeof complete;\ntype SummaryModelRegistry = SummaryGenerationContext[\"modelRegistry\"] & { complete?: CompleteFunction };\n\nconst PREFERRED_SUMMARY_MODELS = [\n\t{ provider: \"anthropic\", id: \"claude-haiku-4-5\" },\n\t{ provider: \"openai-codex\", id: \"gpt-5.3-codex-spark\" },\n] as const;\n\nexport const SUMMARY_GENERATION_DEADLINE_MS = 30_000;\n\nexport interface SummaryMeta {\n\tmodel: string | null;\n\tdurationMs: number;\n\ttokenEstimate: number;\n\tfallbackUsed: boolean;\n\tfallbackReason?: string;\n\tphase?: \"summary-model\" | \"deterministic-fallback\";\n\tedited?: boolean;\n}\n\nexport type SummaryGenerationContext = Pick<ExtensionContext, \"model\" | \"modelRegistry\" | \"cwd\" | \"isProjectTrusted\">;\n\nfunction estimateTokens(text: string): number {\n\tconst trimmed = text.trim();\n\tif (trimmed.length === 0) return 0;\n\treturn Math.max(1, Math.ceil(trimmed.length / 4));\n}\n\nfunction summarizeQueryResult(result: QueryResultData): string {\n\tif (result.error) {\n\t\treturn `Query: ${result.query}\\nStatus: Error\\nError: ${result.error}`;\n\t}\n\n\tconst lines = [\n\t\t`Query: ${result.query}`,\n\t\t`Provider: ${result.provider ?? \"unknown\"}`,\n\t\t`Answer: ${result.answer || \"(no answer text returned)\"}`,\n\t];\n\n\tif (result.results.length === 0) {\n\t\tlines.push(\"Sources: none\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tlines.push(\"Sources:\");\n\tfor (let i = 0; i < result.results.length; i++) {\n\t\tconst source = result.results[i];\n\t\tlines.push(`${i + 1}. ${source.title} — ${source.url}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSummaryPrompt(results: QueryResultData[], feedback?: string): string {\n\tconst sections = [\n\t\t\"You are writing the final web search summary for a coding assistant.\",\n\t\t\"Write a concise, factual summary using only the provided search results.\",\n\t\t\"Requirements:\",\n\t\t\"- Keep it readable and skimmable.\",\n\t\t\"- Include key findings and caveats.\",\n\t\t\"- Do not invent sources or claims.\",\n\t\t\"- If evidence is weak or conflicting, say so explicitly.\",\n\t\t\"- End with a short \\\"Sources\\\" section listing the most relevant URLs.\",\n\t];\n\n\tif (feedback) {\n\t\tsections.push(\"- Incorporate the user feedback provided below into the summary.\");\n\t}\n\n\tsections.push(\"\");\n\tsections.push(\"<search_results>\");\n\n\tfor (let i = 0; i < results.length; i++) {\n\t\tsections.push(`\\n[Result ${i + 1}]`);\n\t\tsections.push(summarizeQueryResult(results[i]));\n\t}\n\n\tsections.push(\"\\n</search_results>\");\n\n\tif (feedback) {\n\t\tsections.push(\"\");\n\t\tsections.push(\"<user_feedback>\");\n\t\tsections.push(feedback);\n\t\tsections.push(\"</user_feedback>\");\n\t}\n\n\treturn sections.join(\"\\n\");\n}\n\nfunction buildDeterministicAnswerPreview(answer: string): string {\n\tlet text = answer.replace(/\\s+/g, \" \").trim();\n\tif (text.length === 0) return \"\";\n\n\tconst sourceMarker = text.search(/\\bSources?\\s*:/i);\n\tif (sourceMarker >= 0) text = text.slice(0, sourceMarker).trim();\n\tif (text.length === 0) return \"\";\n\n\treturn text.length > 240 ? `${text.slice(0, 237)}...` : text;\n}\n\nfunction buildDeterministicSummaryLines(results: QueryResultData[]): string[] {\n\tif (results.length === 0) {\n\t\treturn [\n\t\t\t\"No completed search results were available when the curator session finished.\",\n\t\t\t\"\",\n\t\t\t\"Sources\",\n\t\t\t\"- None\",\n\t\t];\n\t}\n\n\tconst lines: string[] = [\n\t\t\"Summary based on the currently selected search results.\",\n\t\t\"\",\n\t];\n\n\tconst sourceUrls: string[] = [];\n\tlet successful = 0;\n\tlet failed = 0;\n\n\tfor (const result of results) {\n\t\tif (result.error) {\n\t\t\tfailed += 1;\n\t\t\tlines.push(`- ${result.query}: failed (${result.error})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tsuccessful += 1;\n\t\tconst preview = buildDeterministicAnswerPreview(result.answer);\n\t\tif (preview.length > 0) {\n\t\t\tlines.push(`- ${result.query}: ${preview}`);\n\t\t} else {\n\t\t\tlines.push(`- ${result.query}: returned ${result.results.length} source${result.results.length === 1 ? \"\" : \"s\"} without answer text.`);\n\t\t}\n\n\t\tfor (const source of result.results) {\n\t\t\tif (!sourceUrls.includes(source.url)) {\n\t\t\t\tsourceUrls.push(source.url);\n\t\t\t}\n\t\t}\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Completed queries: ${results.length}`);\n\tlines.push(`Successful: ${successful}`);\n\tlines.push(`Failed: ${failed}`);\n\tlines.push(\"\");\n\tlines.push(\"Sources\");\n\n\tif (sourceUrls.length === 0) {\n\t\tlines.push(\"- None\");\n\t} else {\n\t\tfor (const url of sourceUrls.slice(0, 12)) {\n\t\t\tlines.push(`- ${url}`);\n\t\t}\n\t\tif (sourceUrls.length > 12) {\n\t\t\tlines.push(`- ... and ${sourceUrls.length - 12} more`);\n\t\t}\n\t}\n\n\treturn lines;\n}\n\nexport function buildDeterministicSummary(results: QueryResultData[]): { summary: string; meta: SummaryMeta } {\n\tconst summary = buildDeterministicSummaryLines(results).join(\"\\n\").trim();\n\tconst nonEmptySummary = summary.length > 0\n\t\t? summary\n\t\t: \"No completed search results were available when the curator session finished.\\n\\nSources\\n- None\";\n\n\treturn {\n\t\tsummary: nonEmptySummary,\n\t\tmeta: {\n\t\t\tmodel: null,\n\t\t\tdurationMs: 0,\n\t\t\ttokenEstimate: estimateTokens(nonEmptySummary),\n\t\t\tfallbackUsed: true,\n\t\t\tfallbackReason: \"deterministic-submit-fallback\",\n\t\t\tphase: \"deterministic-fallback\",\n\t\t\tedited: false,\n\t\t},\n\t};\n}\n\nfunction parseModelSelector(value: string): { provider: string; id: string } {\n\tconst slashIndex = value.indexOf(\"/\");\n\tif (slashIndex <= 0 || slashIndex >= value.length - 1) {\n\t\tthrow new Error(`Invalid summary model: ${value}. Use provider/model-id.`);\n\t}\n\treturn {\n\t\tprovider: value.slice(0, slashIndex),\n\t\tid: value.slice(slashIndex + 1),\n\t};\n}\n\nasync function resolveSummaryModelCandidates(\n\tctx: SummaryGenerationContext,\n\tmodelOverride?: string,\n): Promise<{ candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders }>; errors: string[] }> {\n\tconst enabledModelPatterns = loadEnabledModelPatterns(ctx);\n\tconst specs: Array<{ provider: string; id: string }> = [];\n\tconst normalizedOverride = typeof modelOverride === \"string\" ? modelOverride.trim() : \"\";\n\tif (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));\n\tspecs.push(...PREFERRED_SUMMARY_MODELS);\n\n\tconst candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders }> = [];\n\tconst errors: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const spec of specs) {\n\t\tconst value = `${spec.provider}/${spec.id}`;\n\t\tif (seen.has(value)) continue;\n\t\tseen.add(value);\n\n\t\tconst model = findModelWithProviderRouting(ctx.modelRegistry, spec.provider, spec.id);\n\t\tif (!model) {\n\t\t\terrors.push(`Summary model not found: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!modelMatchesEnabledPatterns(model, enabledModelPatterns)) {\n\t\t\terrors.push(`Summary model is not enabled: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);\n\t\tif (!auth.ok) {\n\t\t\terrors.push(`No API key available for summary model ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tcandidates.push({ model, apiKey: auth.apiKey, headers: auth.headers });\n\t}\n\treturn { candidates, errors };\n}\n\nfunction buildFallbackSummary(\n\tresults: QueryResultData[],\n\tfallbackReason: string,\n\tdurationMs = 0,\n): { summary: string; meta: SummaryMeta } {\n\tconst deterministic = buildDeterministicSummary(results);\n\treturn {\n\t\tsummary: deterministic.summary,\n\t\tmeta: {\n\t\t\t...deterministic.meta,\n\t\t\tdurationMs,\n\t\t\tfallbackReason,\n\t\t},\n\t};\n}\n\nfunction isAbortError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tconst name = (err as { name?: unknown }).name;\n\tconst message = (err as { message?: unknown }).message;\n\treturn name === \"AbortError\" || (typeof message === \"string\" && message.toLowerCase().includes(\"abort\"));\n}\n\nfunction getTextFromContentPart(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"\";\n\tconst value = part as Record<string, unknown>;\n\tif (typeof value.text === \"string\") return value.text;\n\tif (typeof value.refusal === \"string\") return value.refusal;\n\treturn \"\";\n}\n\nfunction getContentPartType(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"unknown\";\n\tconst value = part as Record<string, unknown>;\n\treturn typeof value.type === \"string\" ? value.type : \"unknown\";\n}\n\nexport async function generateSummaryDraft(\n\tresults: QueryResultData[],\n\tctx: SummaryGenerationContext,\n\tsignal?: AbortSignal,\n\tmodelOverride?: string,\n\tfeedback?: string,\n\tcompleteFn?: CompleteFunction,\n\tdeadlineMs = SUMMARY_GENERATION_DEADLINE_MS,\n): Promise<{ summary: string; meta: SummaryMeta }> {\n\tif (!ctx || !ctx.modelRegistry) {\n\t\tthrow new Error(\"Summary generation context unavailable\");\n\t}\n\n\tconst registry = ctx.modelRegistry as SummaryModelRegistry;\n\tconst usesRegistryComplete = !completeFn && typeof registry.complete === \"function\";\n\tcompleteFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : complete;\n\n\tconst generationStartedAt = Date.now();\n\tconst deadlineController = new AbortController();\n\tconst deadlineMarker = Symbol(\"summary-generation-deadline\");\n\tlet deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet resolveDeadline!: () => void;\n\tconst deadlinePromise = new Promise<typeof deadlineMarker>(resolve => {\n\t\tresolveDeadline = () => resolve(deadlineMarker);\n\t\tdeadlineTimer = setTimeout(() => {\n\t\t\tdeadlineController.abort();\n\t\t\tresolveDeadline();\n\t\t}, deadlineMs);\n\t});\n\n\tlet callerAbortListener: (() => void) | undefined;\n\tconst callerAbortPromise = signal\n\t\t? new Promise<never>((_, reject) => {\n\t\t\tcallerAbortListener = () => reject(new Error(\"Aborted\"));\n\t\t\tif (signal.aborted) callerAbortListener();\n\t\t\telse signal.addEventListener(\"abort\", callerAbortListener, { once: true });\n\t\t})\n\t\t: undefined;\n\tconst completionSignal = signal\n\t\t? AbortSignal.any([signal, deadlineController.signal])\n\t\t: deadlineController.signal;\n\n\tasync function raceSummaryOperation<T>(operation: Promise<T>): Promise<T> {\n\t\t// A provider may ignore AbortSignal and never settle; observe it before racing so\n\t\t// its eventual rejection cannot become an unhandled promise rejection.\n\t\tvoid operation.then(() => undefined, () => undefined);\n\t\tconst contenders: Promise<unknown>[] = [operation, deadlinePromise];\n\t\tif (callerAbortPromise) contenders.push(callerAbortPromise);\n\t\tconst result = await Promise.race(contenders);\n\t\tif (result === deadlineMarker) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tthrow deadlineMarker;\n\t\t}\n\t\treturn result as T;\n\t}\n\n\ttry {\n\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\tconst prompt = buildSummaryPrompt(results, feedback);\n\t\tlet resolved: Awaited<ReturnType<typeof resolveSummaryModelCandidates>>;\n\t\ttry {\n\t\t\tresolved = await raceSummaryOperation(resolveSummaryModelCandidates(ctx, modelOverride));\n\t\t} catch (err) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t}\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\treturn buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);\n\t\t}\n\n\t\tlet lastError = resolved.errors.at(-1);\n\t\tfor (const { model, apiKey, headers } of resolved.candidates) {\n\t\t\tconst startedAt = Date.now();\n\t\t\ttry {\n\t\t\t\tconst userMessage: Message = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: prompt }],\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t};\n\n\t\t\t\tconst response = await raceSummaryOperation(Promise.resolve(completeFn(\n\t\t\t\t\tmodel,\n\t\t\t\t\t{ messages: [userMessage] },\n\t\t\t\t\tusesRegistryComplete ? { signal: completionSignal } : { apiKey, headers, signal: completionSignal },\n\t\t\t\t)));\n\t\t\t\tif (response.stopReason === \"aborted\") {\n\t\t\t\t\tthrow new Error(\"Aborted\");\n\t\t\t\t}\n\n\t\t\t\tconst contentParts = Array.isArray(response.content) ? response.content : [];\n\t\t\t\tconst summary = contentParts\n\t\t\t\t\t.map(part => getTextFromContentPart(part))\n\t\t\t\t\t.filter(text => text.trim().length > 0)\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t.trim();\n\n\t\t\t\tif (summary.length === 0) {\n\t\t\t\t\tconst partTypes = contentParts.map(part => getContentPartType(part));\n\t\t\t\t\tconst typesLabel = partTypes.length > 0 ? partTypes.join(\", \") : \"none\";\n\t\t\t\t\tthrow new Error(`Summary model returned empty response (content parts: ${typesLabel})`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsummary,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tmodel: `${model.provider}/${model.id}`,\n\t\t\t\t\t\tdurationMs: Math.max(0, Date.now() - startedAt),\n\t\t\t\t\t\ttokenEstimate: estimateTokens(summary),\n\t\t\t\t\t\tfallbackUsed: false,\n\t\t\t\t\t\tphase: \"summary-model\",\n\t\t\t\t\t\tedited: false,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t\t}\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tlastError = err instanceof Error ? err.message : String(err);\n\t\t\t}\n\t\t}\n\n\t\treturn buildFallbackSummary(\n\t\t\tresults,\n\t\t\tlastError ? `summary-model-unavailable: ${lastError}` : \"summary-model-unavailable\",\n\t\t\tDate.now() - generationStartedAt,\n\t\t);\n\t} finally {\n\t\tif (deadlineTimer) clearTimeout(deadlineTimer);\n\t\tif (signal && callerAbortListener) signal.removeEventListener(\"abort\", callerAbortListener);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"summary-review.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/summary-review.ts"],"names":[],"mappings":"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","sourcesContent":["import { clampThinkingLevel, type ModelThinkingLevel, type ThinkingLevel } from \"@earendil-works/pi-ai\";\nimport { complete, completeSimple, type Api, type Message, type Model } from \"@earendil-works/pi-ai/compat\";\nimport type { ExtensionContext } from \"../host.ts\";\nimport { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix, type SummaryThinkingLevel } from \"./summary-model-scope.ts\";\nimport type { QueryResultData } from \"./storage.ts\";\n\ntype ProviderHeaders = Record<string, string | null>;\ntype CompleteFunction = typeof complete;\ntype SummaryModelRegistry = SummaryGenerationContext[\"modelRegistry\"] & { complete?: CompleteFunction };\n\nconst PREFERRED_SUMMARY_MODELS = [\n\t{ provider: \"anthropic\", id: \"claude-haiku-4-5\" },\n\t{ provider: \"openai-codex\", id: \"gpt-5.6-luna\" },\n\t{ provider: \"openai-codex\", id: \"gpt-5.6-terra\" },\n\t{ provider: \"google\", id: \"gemini-3.6-flash\" },\n\t{ provider: \"openai\", id: \"gpt-5-mini\" },\n\t{ provider: \"deepseek\", id: \"deepseek-v4-flash\" },\n] as const;\n\nexport const SUMMARY_GENERATION_DEADLINE_MS = 30_000;\n\nexport interface SummaryMeta {\n\tmodel: string | null;\n\tdurationMs: number;\n\ttokenEstimate: number;\n\tfallbackUsed: boolean;\n\tfallbackReason?: string;\n\tphase?: \"summary-model\" | \"deterministic-fallback\";\n\tedited?: boolean;\n}\n\nexport type SummaryGenerationContext = Pick<ExtensionContext, \"model\" | \"modelRegistry\" | \"cwd\" | \"isProjectTrusted\">;\n\nfunction estimateTokens(text: string): number {\n\tconst trimmed = text.trim();\n\tif (trimmed.length === 0) return 0;\n\treturn Math.max(1, Math.ceil(trimmed.length / 4));\n}\n\nfunction summarizeQueryResult(result: QueryResultData): string {\n\tif (result.error) {\n\t\treturn `Query: ${result.query}\\nStatus: Error\\nError: ${result.error}`;\n\t}\n\n\tconst lines = [\n\t\t`Query: ${result.query}`,\n\t\t`Provider: ${result.provider ?? \"unknown\"}`,\n\t\t`Answer: ${result.answer || \"(no answer text returned)\"}`,\n\t];\n\n\tif (result.results.length === 0) {\n\t\tlines.push(\"Sources: none\");\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\tlines.push(\"Sources:\");\n\tfor (let i = 0; i < result.results.length; i++) {\n\t\tconst source = result.results[i];\n\t\tlines.push(`${i + 1}. ${source.title} — ${source.url}`);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSummaryPrompt(results: QueryResultData[], feedback?: string): string {\n\tconst sections = [\n\t\t\"You are writing the final web search summary for a coding assistant.\",\n\t\t\"Write a concise, factual summary using only the provided search results.\",\n\t\t\"Requirements:\",\n\t\t\"- Keep it readable and skimmable.\",\n\t\t\"- Include key findings and caveats.\",\n\t\t\"- Do not invent sources or claims.\",\n\t\t\"- If evidence is weak or conflicting, say so explicitly.\",\n\t\t\"- End with a short \\\"Sources\\\" section listing the most relevant URLs.\",\n\t];\n\n\tif (feedback) {\n\t\tsections.push(\"- Incorporate the user feedback provided below into the summary.\");\n\t}\n\n\tsections.push(\"\");\n\tsections.push(\"<search_results>\");\n\n\tfor (let i = 0; i < results.length; i++) {\n\t\tsections.push(`\\n[Result ${i + 1}]`);\n\t\tsections.push(summarizeQueryResult(results[i]));\n\t}\n\n\tsections.push(\"\\n</search_results>\");\n\n\tif (feedback) {\n\t\tsections.push(\"\");\n\t\tsections.push(\"<user_feedback>\");\n\t\tsections.push(feedback);\n\t\tsections.push(\"</user_feedback>\");\n\t}\n\n\treturn sections.join(\"\\n\");\n}\n\nfunction buildDeterministicAnswerPreview(answer: string): string {\n\tlet text = answer.replace(/\\s+/g, \" \").trim();\n\tif (text.length === 0) return \"\";\n\n\tconst sourceMarker = text.search(/\\bSources?\\s*:/i);\n\tif (sourceMarker >= 0) text = text.slice(0, sourceMarker).trim();\n\tif (text.length === 0) return \"\";\n\n\treturn text.length > 240 ? `${text.slice(0, 237)}...` : text;\n}\n\nfunction buildDeterministicSummaryLines(results: QueryResultData[]): string[] {\n\tif (results.length === 0) {\n\t\treturn [\n\t\t\t\"No completed search results were available when the curator session finished.\",\n\t\t\t\"\",\n\t\t\t\"Sources\",\n\t\t\t\"- None\",\n\t\t];\n\t}\n\n\tconst lines: string[] = [\n\t\t\"Summary based on the currently selected search results.\",\n\t\t\"\",\n\t];\n\n\tconst sourceUrls: string[] = [];\n\tlet successful = 0;\n\tlet failed = 0;\n\n\tfor (const result of results) {\n\t\tif (result.error) {\n\t\t\tfailed += 1;\n\t\t\tlines.push(`- ${result.query}: failed (${result.error})`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tsuccessful += 1;\n\t\tconst preview = buildDeterministicAnswerPreview(result.answer);\n\t\tif (preview.length > 0) {\n\t\t\tlines.push(`- ${result.query}: ${preview}`);\n\t\t} else {\n\t\t\tlines.push(`- ${result.query}: returned ${result.results.length} source${result.results.length === 1 ? \"\" : \"s\"} without answer text.`);\n\t\t}\n\n\t\tfor (const source of result.results) {\n\t\t\tif (!sourceUrls.includes(source.url)) {\n\t\t\t\tsourceUrls.push(source.url);\n\t\t\t}\n\t\t}\n\t}\n\n\tlines.push(\"\");\n\tlines.push(`Completed queries: ${results.length}`);\n\tlines.push(`Successful: ${successful}`);\n\tlines.push(`Failed: ${failed}`);\n\tlines.push(\"\");\n\tlines.push(\"Sources\");\n\n\tif (sourceUrls.length === 0) {\n\t\tlines.push(\"- None\");\n\t} else {\n\t\tfor (const url of sourceUrls.slice(0, 12)) {\n\t\t\tlines.push(`- ${url}`);\n\t\t}\n\t\tif (sourceUrls.length > 12) {\n\t\t\tlines.push(`- ... and ${sourceUrls.length - 12} more`);\n\t\t}\n\t}\n\n\treturn lines;\n}\n\nexport function buildDeterministicSummary(results: QueryResultData[]): { summary: string; meta: SummaryMeta } {\n\tconst summary = buildDeterministicSummaryLines(results).join(\"\\n\").trim();\n\tconst nonEmptySummary = summary.length > 0\n\t\t? summary\n\t\t: \"No completed search results were available when the curator session finished.\\n\\nSources\\n- None\";\n\n\treturn {\n\t\tsummary: nonEmptySummary,\n\t\tmeta: {\n\t\t\tmodel: null,\n\t\t\tdurationMs: 0,\n\t\t\ttokenEstimate: estimateTokens(nonEmptySummary),\n\t\t\tfallbackUsed: true,\n\t\t\tfallbackReason: \"deterministic-submit-fallback\",\n\t\t\tphase: \"deterministic-fallback\",\n\t\t\tedited: false,\n\t\t},\n\t};\n}\n\nfunction parseModelSelector(value: string): { provider: string; id: string; thinkingLevel?: SummaryThinkingLevel } {\n\tconst selector = splitThinkingSuffix(value);\n\tconst slashIndex = selector.value.indexOf(\"/\");\n\tif (slashIndex <= 0 || slashIndex >= selector.value.length - 1) {\n\t\tthrow new Error(`Invalid summary model: ${value}. Use provider/model-id.`);\n\t}\n\treturn {\n\t\tprovider: selector.value.slice(0, slashIndex),\n\t\tid: selector.value.slice(slashIndex + 1),\n\t\tthinkingLevel: selector.thinkingLevel,\n\t};\n}\n\nfunction resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): ModelThinkingLevel | undefined {\n\tif (!requested) return undefined;\n\treturn clampThinkingLevel(model, requested as ModelThinkingLevel);\n}\n\nasync function resolveSummaryModelCandidates(\n\tctx: SummaryGenerationContext,\n\tmodelOverride?: string,\n): Promise<{ candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders; thinkingLevel?: SummaryThinkingLevel }>; errors: string[] }> {\n\tconst enabledModelPatterns = loadEnabledModelPatterns(ctx);\n\tconst specs: Array<{ provider: string; id: string; thinkingLevel?: SummaryThinkingLevel }> = [];\n\tconst normalizedOverride = typeof modelOverride === \"string\" ? modelOverride.trim() : \"\";\n\tif (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));\n\tspecs.push(...PREFERRED_SUMMARY_MODELS);\n\n\tconst candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders; thinkingLevel?: SummaryThinkingLevel }> = [];\n\tconst errors: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const spec of specs) {\n\t\tconst value = `${spec.provider}/${spec.id}`;\n\t\tif (seen.has(value)) continue;\n\t\tseen.add(value);\n\n\t\tconst model = findModelWithProviderRouting(ctx.modelRegistry, spec.provider, spec.id);\n\t\tif (!model) {\n\t\t\terrors.push(`Summary model not found: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!modelMatchesEnabledPatterns(model, enabledModelPatterns)) {\n\t\t\terrors.push(`Summary model is not enabled: ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tconst auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);\n\t\tif (!auth.ok) {\n\t\t\terrors.push(`No API key available for summary model ${value}`);\n\t\t\tcontinue;\n\t\t}\n\t\tcandidates.push({ model, apiKey: auth.apiKey, headers: auth.headers, thinkingLevel: spec.thinkingLevel });\n\t}\n\treturn { candidates, errors };\n}\n\nfunction buildFallbackSummary(\n\tresults: QueryResultData[],\n\tfallbackReason: string,\n\tdurationMs = 0,\n): { summary: string; meta: SummaryMeta } {\n\tconst deterministic = buildDeterministicSummary(results);\n\treturn {\n\t\tsummary: deterministic.summary,\n\t\tmeta: {\n\t\t\t...deterministic.meta,\n\t\t\tdurationMs,\n\t\t\tfallbackReason,\n\t\t},\n\t};\n}\n\nfunction isAbortError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tconst name = (err as { name?: unknown }).name;\n\tconst message = (err as { message?: unknown }).message;\n\treturn name === \"AbortError\" || (typeof message === \"string\" && message.toLowerCase().includes(\"abort\"));\n}\n\nfunction getTextFromContentPart(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"\";\n\tconst value = part as Record<string, unknown>;\n\tif (typeof value.text === \"string\") return value.text;\n\tif (typeof value.refusal === \"string\") return value.refusal;\n\treturn \"\";\n}\n\nfunction getContentPartType(part: unknown): string {\n\tif (!part || typeof part !== \"object\") return \"unknown\";\n\tconst value = part as Record<string, unknown>;\n\treturn typeof value.type === \"string\" ? value.type : \"unknown\";\n}\n\nexport async function generateSummaryDraft(\n\tresults: QueryResultData[],\n\tctx: SummaryGenerationContext,\n\tsignal?: AbortSignal,\n\tmodelOverride?: string,\n\tfeedback?: string,\n\tcompleteFn?: CompleteFunction,\n\tdeadlineMs = SUMMARY_GENERATION_DEADLINE_MS,\n): Promise<{ summary: string; meta: SummaryMeta }> {\n\tif (!ctx || !ctx.modelRegistry) {\n\t\tthrow new Error(\"Summary generation context unavailable\");\n\t}\n\n\tconst registry = ctx.modelRegistry as SummaryModelRegistry;\n\tconst customCompleteFn = completeFn !== undefined;\n\tconst usesRegistryComplete = !customCompleteFn && typeof registry.complete === \"function\";\n\tcompleteFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : complete;\n\n\tconst generationStartedAt = Date.now();\n\tconst deadlineController = new AbortController();\n\tconst deadlineMarker = Symbol(\"summary-generation-deadline\");\n\tlet deadlineTimer: ReturnType<typeof setTimeout> | undefined;\n\tlet resolveDeadline!: () => void;\n\tconst deadlinePromise = new Promise<typeof deadlineMarker>(resolve => {\n\t\tresolveDeadline = () => resolve(deadlineMarker);\n\t\tdeadlineTimer = setTimeout(() => {\n\t\t\tdeadlineController.abort();\n\t\t\tresolveDeadline();\n\t\t}, deadlineMs);\n\t});\n\n\tlet callerAbortListener: (() => void) | undefined;\n\tconst callerAbortPromise = signal\n\t\t? new Promise<never>((_, reject) => {\n\t\t\tcallerAbortListener = () => reject(new Error(\"Aborted\"));\n\t\t\tif (signal.aborted) callerAbortListener();\n\t\t\telse signal.addEventListener(\"abort\", callerAbortListener, { once: true });\n\t\t})\n\t\t: undefined;\n\tconst completionSignal = signal\n\t\t? AbortSignal.any([signal, deadlineController.signal])\n\t\t: deadlineController.signal;\n\n\tasync function raceSummaryOperation<T>(operation: Promise<T>): Promise<T> {\n\t\t// A provider may ignore AbortSignal and never settle; observe it before racing so\n\t\t// its eventual rejection cannot become an unhandled promise rejection.\n\t\tvoid operation.then(() => undefined, () => undefined);\n\t\tconst contenders: Promise<unknown>[] = [operation, deadlinePromise];\n\t\tif (callerAbortPromise) contenders.push(callerAbortPromise);\n\t\tconst result = await Promise.race(contenders);\n\t\tif (result === deadlineMarker) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tthrow deadlineMarker;\n\t\t}\n\t\treturn result as T;\n\t}\n\n\ttry {\n\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\tconst prompt = buildSummaryPrompt(results, feedback);\n\t\tlet resolved: Awaited<ReturnType<typeof resolveSummaryModelCandidates>>;\n\t\ttry {\n\t\t\tresolved = await raceSummaryOperation(resolveSummaryModelCandidates(ctx, modelOverride));\n\t\t} catch (err) {\n\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t}\n\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\treturn buildFallbackSummary(results, `summary-model-settings-error: ${message}`, Date.now() - generationStartedAt);\n\t\t}\n\n\t\tlet lastError = resolved.errors.at(-1);\n\t\tfor (const { model, apiKey, headers, thinkingLevel } of resolved.candidates) {\n\t\t\tconst startedAt = Date.now();\n\t\t\ttry {\n\t\t\t\tconst userMessage: Message = {\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: [{ type: \"text\", text: prompt }],\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t};\n\t\t\t\tconst requestedThinkingLevel = resolveThinkingLevel(model, thinkingLevel);\n\t\t\t\tconst enabledThinkingLevel = requestedThinkingLevel && requestedThinkingLevel !== \"off\"\n\t\t\t\t\t? requestedThinkingLevel as ThinkingLevel\n\t\t\t\t\t: undefined;\n\t\t\t\tconst completionOptions = {\n\t\t\t\t\t...(usesRegistryComplete ? {} : { apiKey, headers }),\n\t\t\t\t\tsignal: completionSignal,\n\t\t\t\t\t...(requestedThinkingLevel ? { reasoning: requestedThinkingLevel } : {}),\n\t\t\t\t\t...(enabledThinkingLevel ? { reasoningEffort: enabledThinkingLevel } : {}),\n\t\t\t\t};\n\t\t\t\tconst completion = thinkingLevel !== undefined && !customCompleteFn && !usesRegistryComplete\n\t\t\t\t\t? completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })\n\t\t\t\t\t: completeFn(model, { messages: [userMessage] }, completionOptions);\n\n\t\t\t\tconst response = await raceSummaryOperation(Promise.resolve(completion));\n\t\t\t\tif (response.stopReason === \"aborted\") {\n\t\t\t\t\tthrow new Error(\"Aborted\");\n\t\t\t\t}\n\n\t\t\t\tconst contentParts = Array.isArray(response.content) ? response.content : [];\n\t\t\t\tconst summary = contentParts\n\t\t\t\t\t.map((part: unknown) => getTextFromContentPart(part))\n\t\t\t\t\t.filter((text: string) => text.trim().length > 0)\n\t\t\t\t\t.join(\"\\n\")\n\t\t\t\t\t.trim();\n\n\t\t\t\tif (summary.length === 0) {\n\t\t\t\t\tconst partTypes = contentParts.map((part: unknown) => getContentPartType(part));\n\t\t\t\t\tconst typesLabel = partTypes.length > 0 ? partTypes.join(\", \") : \"none\";\n\t\t\t\t\tthrow new Error(`Summary model returned empty response (content parts: ${typesLabel})`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tsummary,\n\t\t\t\t\tmeta: {\n\t\t\t\t\t\tmodel: `${model.provider}/${model.id}`,\n\t\t\t\t\t\tdurationMs: Math.max(0, Date.now() - startedAt),\n\t\t\t\t\t\ttokenEstimate: estimateTokens(summary),\n\t\t\t\t\t\tfallbackUsed: false,\n\t\t\t\t\t\tphase: \"summary-model\",\n\t\t\t\t\t\tedited: false,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (err) {\n\t\t\t\tif (signal?.aborted) throw new Error(\"Aborted\");\n\t\t\t\tif (err === deadlineMarker || deadlineController.signal.aborted) {\n\t\t\t\t\treturn buildFallbackSummary(results, \"summary-generation-timeout\", Date.now() - generationStartedAt);\n\t\t\t\t}\n\t\t\t\tif (isAbortError(err)) throw err;\n\t\t\t\tlastError = err instanceof Error ? err.message : String(err);\n\t\t\t}\n\t\t}\n\n\t\treturn buildFallbackSummary(\n\t\t\tresults,\n\t\t\tlastError ? `summary-model-unavailable: ${lastError}` : \"summary-model-unavailable\",\n\t\t\tDate.now() - generationStartedAt,\n\t\t);\n\t} finally {\n\t\tif (deadlineTimer) clearTimeout(deadlineTimer);\n\t\tif (signal && callerAbortListener) signal.removeEventListener(\"abort\", callerAbortListener);\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/tavily.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/tavily.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAyBrE,UAAU,mBAAoB,SAAQ,aAAa;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AA8HD,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CA6DhH","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 TAVILY_API_BASE_URL = \"https://api.tavily.com\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\n\ninterface WebSearchConfig {\n\ttavilyApiKey?: unknown;\n\ttavilyBaseUrl?: unknown;\n}\n\ninterface TavilyResult {\n\ttitle?: string;\n\turl?: string;\n\tcontent?: string;\n\traw_content?: string | null;\n}\n\ninterface TavilyResponse {\n\tanswer?: string;\n\tresults?: TavilyResult[];\n}\n\ninterface TavilySearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\ttry {\n\t\tcachedConfig = JSON.parse(raw) as WebSearchConfig;\n\t\treturn cachedConfig;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Tavily\",\n\t\tconfiguredValue: loadConfig().tavilyApiKey,\n\t\tenvironmentValue: process.env.TAVILY_API_KEY,\n\t\tsignal,\n\t});\n}\n\nfunction getApiUrl(): string {\n\treturn `${resolveApiBaseUrl({\n\t\tconfigKey: \"tavilyBaseUrl\",\n\t\tconfiguredValue: loadConfig().tavilyBaseUrl,\n\t\tdefaultValue: TAVILY_API_BASE_URL,\n\t\tenvironmentKey: \"TAVILY_BASE_URL\",\n\t\tenvironmentValue: process.env.TAVILY_BASE_URL,\n\t})}/search`;\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Tavily API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"tavilyApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set TAVILY_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://app.tavily.com/\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { include_domains?: string[]; exclude_domains?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst include_domains: string[] = [];\n\tconst exclude_domains: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? exclude_domains : include_domains;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(include_domains.length > 0 ? { include_domains } : {}),\n\t\t...(exclude_domains.length > 0 ? { exclude_domains } : {}),\n\t};\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction mapResults(results: TavilyResult[] | undefined, numResults: number): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (const item of results) {\n\t\tif (!item?.url) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${mapped.length + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: typeof item.content === \"string\" ? item.content.replace(/\\s+/g, \" \").trim() : \"\",\n\t\t});\n\t\tif (mapped.length >= numResults) break;\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: TavilyResult[] | undefined): ExtractedContent[] {\n\tif (!Array.isArray(results)) return [];\n\treturn results.flatMap((item) => {\n\t\tif (!item?.url || typeof item.raw_content !== \"string\" || item.raw_content.trim().length === 0) return [];\n\t\treturn [{\n\t\t\turl: item.url,\n\t\t\ttitle: item.title || \"\",\n\t\t\tcontent: item.raw_content,\n\t\t\terror: null,\n\t\t}];\n\t});\n}\n\nexport function isTavilyAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Tavily\",\n\t\tconfiguredValue: loadConfig().tavilyApiKey,\n\t\tenvironmentValue: process.env.TAVILY_API_KEY,\n\t});\n}\n\nexport async function searchWithTavily(query: string, options: TavilySearchOptions = {}): Promise<SearchResponse> {\n\tconst apiUrl = getApiUrl();\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body: Record<string, unknown> = {\n\t\tquery,\n\t\tsearch_depth: \"basic\",\n\t\tmax_results: numResults,\n\t\tinclude_answer: \"basic\",\n\t\tinclude_raw_content: options.includeContent ? \"markdown\" : false,\n\t\t...(options.recencyFilter ? { time_range: options.recencyFilter } : {}),\n\t\t...mapDomainFilter(options.domainFilter),\n\t};\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetchWithCredentialRedirects(apiUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Authorization\": `Bearer ${apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: requestSignal(options.signal),\n\t\t}, [\"Authorization\"]);\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Tavily API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tlet data: TavilyResponse;\n\ttry {\n\t\tdata = await response.json() as TavilyResponse;\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Tavily API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst result: SearchResponse = {\n\t\tanswer: typeof data.answer === \"string\" ? data.answer : \"\",\n\t\tresults: mapResults(data.results, numResults),\n\t};\n\tif (options.includeContent) {\n\t\tconst inlineContent = mapInlineContent(data.results);\n\t\tif (inlineContent.length > 0) result.inlineContent = inlineContent;\n\t}\n\treturn result;\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { activityMonitor } from "./activity.js";
|
|
3
3
|
import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.js";
|
|
4
|
-
import { getWebSearchConfigPath } from "./utils.js";
|
|
5
|
-
const
|
|
4
|
+
import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.js";
|
|
5
|
+
const TAVILY_API_BASE_URL = "https://api.tavily.com";
|
|
6
6
|
const CONFIG_PATH = getWebSearchConfigPath();
|
|
7
7
|
const SEARCH_TIMEOUT_MS = 60_000;
|
|
8
8
|
let cachedConfig = null;
|
|
@@ -31,6 +31,15 @@ async function getApiKey(signal) {
|
|
|
31
31
|
signal,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
function getApiUrl() {
|
|
35
|
+
return `${resolveApiBaseUrl({
|
|
36
|
+
configKey: "tavilyBaseUrl",
|
|
37
|
+
configuredValue: loadConfig().tavilyBaseUrl,
|
|
38
|
+
defaultValue: TAVILY_API_BASE_URL,
|
|
39
|
+
environmentKey: "TAVILY_BASE_URL",
|
|
40
|
+
environmentValue: process.env.TAVILY_BASE_URL,
|
|
41
|
+
})}/search`;
|
|
42
|
+
}
|
|
34
43
|
async function requireApiKey(signal) {
|
|
35
44
|
const apiKey = await getApiKey(signal);
|
|
36
45
|
if (!apiKey) {
|
|
@@ -128,6 +137,7 @@ export function isTavilyAvailable() {
|
|
|
128
137
|
});
|
|
129
138
|
}
|
|
130
139
|
export async function searchWithTavily(query, options = {}) {
|
|
140
|
+
const apiUrl = getApiUrl();
|
|
131
141
|
const apiKey = await requireApiKey(options.signal);
|
|
132
142
|
const numResults = normalizeCount(options.numResults);
|
|
133
143
|
const body = {
|
|
@@ -142,7 +152,7 @@ export async function searchWithTavily(query, options = {}) {
|
|
|
142
152
|
const activityId = activityMonitor.logStart({ type: "api", query });
|
|
143
153
|
let response;
|
|
144
154
|
try {
|
|
145
|
-
response = await
|
|
155
|
+
response = await fetchWithCredentialRedirects(apiUrl, {
|
|
146
156
|
method: "POST",
|
|
147
157
|
headers: {
|
|
148
158
|
"Authorization": `Bearer ${apiKey}`,
|
|
@@ -150,7 +160,7 @@ export async function searchWithTavily(query, options = {}) {
|
|
|
150
160
|
},
|
|
151
161
|
body: JSON.stringify(body),
|
|
152
162
|
signal: requestSignal(options.signal),
|
|
153
|
-
});
|
|
163
|
+
}, ["Authorization"]);
|
|
154
164
|
}
|
|
155
165
|
catch (err) {
|
|
156
166
|
const message = errorMessage(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tavily.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/tavily.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { getWebSearchConfigPath } from \"./utils.ts\";\n\nconst TAVILY_API_URL = \"https://api.tavily.com/search\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\n\ninterface WebSearchConfig {\n\ttavilyApiKey?: unknown;\n}\n\ninterface TavilyResult {\n\ttitle?: string;\n\turl?: string;\n\tcontent?: string;\n\traw_content?: string | null;\n}\n\ninterface TavilyResponse {\n\tanswer?: string;\n\tresults?: TavilyResult[];\n}\n\ninterface TavilySearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\ttry {\n\t\tcachedConfig = JSON.parse(raw) as WebSearchConfig;\n\t\treturn cachedConfig;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Tavily\",\n\t\tconfiguredValue: loadConfig().tavilyApiKey,\n\t\tenvironmentValue: process.env.TAVILY_API_KEY,\n\t\tsignal,\n\t});\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Tavily API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"tavilyApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set TAVILY_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://app.tavily.com/\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { include_domains?: string[]; exclude_domains?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst include_domains: string[] = [];\n\tconst exclude_domains: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? exclude_domains : include_domains;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(include_domains.length > 0 ? { include_domains } : {}),\n\t\t...(exclude_domains.length > 0 ? { exclude_domains } : {}),\n\t};\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction mapResults(results: TavilyResult[] | undefined, numResults: number): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (const item of results) {\n\t\tif (!item?.url) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${mapped.length + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: typeof item.content === \"string\" ? item.content.replace(/\\s+/g, \" \").trim() : \"\",\n\t\t});\n\t\tif (mapped.length >= numResults) break;\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: TavilyResult[] | undefined): ExtractedContent[] {\n\tif (!Array.isArray(results)) return [];\n\treturn results.flatMap((item) => {\n\t\tif (!item?.url || typeof item.raw_content !== \"string\" || item.raw_content.trim().length === 0) return [];\n\t\treturn [{\n\t\t\turl: item.url,\n\t\t\ttitle: item.title || \"\",\n\t\t\tcontent: item.raw_content,\n\t\t\terror: null,\n\t\t}];\n\t});\n}\n\nexport function isTavilyAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Tavily\",\n\t\tconfiguredValue: loadConfig().tavilyApiKey,\n\t\tenvironmentValue: process.env.TAVILY_API_KEY,\n\t});\n}\n\nexport async function searchWithTavily(query: string, options: TavilySearchOptions = {}): Promise<SearchResponse> {\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body: Record<string, unknown> = {\n\t\tquery,\n\t\tsearch_depth: \"basic\",\n\t\tmax_results: numResults,\n\t\tinclude_answer: \"basic\",\n\t\tinclude_raw_content: options.includeContent ? \"markdown\" : false,\n\t\t...(options.recencyFilter ? { time_range: options.recencyFilter } : {}),\n\t\t...mapDomainFilter(options.domainFilter),\n\t};\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(TAVILY_API_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Authorization\": `Bearer ${apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: requestSignal(options.signal),\n\t\t});\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Tavily API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tlet data: TavilyResponse;\n\ttry {\n\t\tdata = await response.json() as TavilyResponse;\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Tavily API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst result: SearchResponse = {\n\t\tanswer: typeof data.answer === \"string\" ? data.answer : \"\",\n\t\tresults: mapResults(data.results, numResults),\n\t};\n\tif (options.includeContent) {\n\t\tconst inlineContent = mapInlineContent(data.results);\n\t\tif (inlineContent.length > 0) result.inlineContent = inlineContent;\n\t}\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tavily.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/tavily.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport type { ExtractedContent } from \"./extract.ts\";\nimport type { SearchOptions, SearchResponse } from \"./perplexity.ts\";\nimport { hasCredentialSource, redactCredential, resolveCredential } from \"./credential-source.ts\";\nimport { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from \"./utils.ts\";\n\nconst TAVILY_API_BASE_URL = \"https://api.tavily.com\";\nconst CONFIG_PATH = getWebSearchConfigPath();\nconst SEARCH_TIMEOUT_MS = 60_000;\n\ninterface WebSearchConfig {\n\ttavilyApiKey?: unknown;\n\ttavilyBaseUrl?: unknown;\n}\n\ninterface TavilyResult {\n\ttitle?: string;\n\turl?: string;\n\tcontent?: string;\n\traw_content?: string | null;\n}\n\ninterface TavilyResponse {\n\tanswer?: string;\n\tresults?: TavilyResult[];\n}\n\ninterface TavilySearchOptions extends SearchOptions {\n\tincludeContent?: boolean;\n}\n\nlet cachedConfig: WebSearchConfig | null = null;\n\nfunction loadConfig(): WebSearchConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = {};\n\t\treturn cachedConfig;\n\t}\n\n\tconst raw = readFileSync(CONFIG_PATH, \"utf-8\");\n\ttry {\n\t\tcachedConfig = JSON.parse(raw) as WebSearchConfig;\n\t\treturn cachedConfig;\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);\n\t}\n}\n\nasync function getApiKey(signal?: AbortSignal): Promise<string | null> {\n\treturn resolveCredential({\n\t\tprovider: \"Tavily\",\n\t\tconfiguredValue: loadConfig().tavilyApiKey,\n\t\tenvironmentValue: process.env.TAVILY_API_KEY,\n\t\tsignal,\n\t});\n}\n\nfunction getApiUrl(): string {\n\treturn `${resolveApiBaseUrl({\n\t\tconfigKey: \"tavilyBaseUrl\",\n\t\tconfiguredValue: loadConfig().tavilyBaseUrl,\n\t\tdefaultValue: TAVILY_API_BASE_URL,\n\t\tenvironmentKey: \"TAVILY_BASE_URL\",\n\t\tenvironmentValue: process.env.TAVILY_BASE_URL,\n\t})}/search`;\n}\n\nasync function requireApiKey(signal?: AbortSignal): Promise<string> {\n\tconst apiKey = await getApiKey(signal);\n\tif (!apiKey) {\n\t\tthrow new Error(\n\t\t\t\"Tavily API key not found. Either:\\n\" +\n\t\t\t` 1. Create ${CONFIG_PATH} with { \"tavilyApiKey\": \"your-key\" }\\n` +\n\t\t\t\" 2. Set TAVILY_API_KEY environment variable\\n\" +\n\t\t\t\"Get a key at https://app.tavily.com/\",\n\t\t);\n\t}\n\treturn apiKey;\n}\n\nfunction normalizeCount(value: number | undefined): number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value)) return 5;\n\treturn Math.max(1, Math.min(Math.floor(value), 20));\n}\n\nfunction normalizeDomain(value: string): string | null {\n\tlet input = value.trim().toLowerCase();\n\tif (!input) return null;\n\tif (input.startsWith(\"-\")) input = input.slice(1).trim();\n\tif (!input) return null;\n\ttry {\n\t\tconst parsed = input.includes(\"://\") ? new URL(input) : new URL(`https://${input}`);\n\t\tinput = parsed.hostname;\n\t} catch {\n\t\tinput = input.split(\"/\")[0]?.split(\":\")[0] ?? \"\";\n\t}\n\tinput = input.replace(/^\\.+|\\.+$/g, \"\");\n\treturn /^[a-z0-9][a-z0-9.-]*\\.[a-z]{2,}$/i.test(input) ? input : null;\n}\n\nfunction mapDomainFilter(domainFilter: string[] | undefined): { include_domains?: string[]; exclude_domains?: string[] } {\n\tif (!domainFilter?.length) return {};\n\tconst include_domains: string[] = [];\n\tconst exclude_domains: string[] = [];\n\tfor (const raw of domainFilter) {\n\t\tconst domain = normalizeDomain(raw);\n\t\tif (!domain) continue;\n\t\tconst target = raw.trim().startsWith(\"-\") ? exclude_domains : include_domains;\n\t\tif (!target.includes(domain)) target.push(domain);\n\t}\n\treturn {\n\t\t...(include_domains.length > 0 ? { include_domains } : {}),\n\t\t...(exclude_domains.length > 0 ? { exclude_domains } : {}),\n\t};\n}\n\nfunction requestSignal(signal?: AbortSignal): AbortSignal {\n\tconst timeout = AbortSignal.timeout(SEARCH_TIMEOUT_MS);\n\treturn signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction mapResults(results: TavilyResult[] | undefined, numResults: number): SearchResponse[\"results\"] {\n\tif (!Array.isArray(results)) return [];\n\tconst mapped: SearchResponse[\"results\"] = [];\n\tfor (const item of results) {\n\t\tif (!item?.url) continue;\n\t\tmapped.push({\n\t\t\ttitle: item.title || `Source ${mapped.length + 1}`,\n\t\t\turl: item.url,\n\t\t\tsnippet: typeof item.content === \"string\" ? item.content.replace(/\\s+/g, \" \").trim() : \"\",\n\t\t});\n\t\tif (mapped.length >= numResults) break;\n\t}\n\treturn mapped;\n}\n\nfunction mapInlineContent(results: TavilyResult[] | undefined): ExtractedContent[] {\n\tif (!Array.isArray(results)) return [];\n\treturn results.flatMap((item) => {\n\t\tif (!item?.url || typeof item.raw_content !== \"string\" || item.raw_content.trim().length === 0) return [];\n\t\treturn [{\n\t\t\turl: item.url,\n\t\t\ttitle: item.title || \"\",\n\t\t\tcontent: item.raw_content,\n\t\t\terror: null,\n\t\t}];\n\t});\n}\n\nexport function isTavilyAvailable(): boolean {\n\treturn hasCredentialSource({\n\t\tprovider: \"Tavily\",\n\t\tconfiguredValue: loadConfig().tavilyApiKey,\n\t\tenvironmentValue: process.env.TAVILY_API_KEY,\n\t});\n}\n\nexport async function searchWithTavily(query: string, options: TavilySearchOptions = {}): Promise<SearchResponse> {\n\tconst apiUrl = getApiUrl();\n\tconst apiKey = await requireApiKey(options.signal);\n\tconst numResults = normalizeCount(options.numResults);\n\tconst body: Record<string, unknown> = {\n\t\tquery,\n\t\tsearch_depth: \"basic\",\n\t\tmax_results: numResults,\n\t\tinclude_answer: \"basic\",\n\t\tinclude_raw_content: options.includeContent ? \"markdown\" : false,\n\t\t...(options.recencyFilter ? { time_range: options.recencyFilter } : {}),\n\t\t...mapDomainFilter(options.domainFilter),\n\t};\n\n\tconst activityId = activityMonitor.logStart({ type: \"api\", query });\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetchWithCredentialRedirects(apiUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t\"Authorization\": `Bearer ${apiKey}`,\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t},\n\t\t\tbody: JSON.stringify(body),\n\t\t\tsignal: requestSignal(options.signal),\n\t\t}, [\"Authorization\"]);\n\t} catch (err) {\n\t\tconst message = errorMessage(err);\n\t\tconst redactedMessage = redactCredential(message, apiKey);\n\t\tif (redactedMessage.toLowerCase().includes(\"abort\")) activityMonitor.logComplete(activityId, 0);\n\t\telse activityMonitor.logError(activityId, redactedMessage);\n\t\tif (redactedMessage === message) throw err;\n\t\tconst redactedError = new Error(redactedMessage);\n\t\tif (err instanceof Error) redactedError.name = err.name;\n\t\tthrow redactedError;\n\t}\n\n\tif (!response.ok) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tconst errorText = redactCredential(await response.text(), apiKey);\n\t\tthrow new Error(`Tavily API error ${response.status}: ${errorText.slice(0, 300)}`);\n\t}\n\n\tlet data: TavilyResponse;\n\ttry {\n\t\tdata = await response.json() as TavilyResponse;\n\t} catch (err) {\n\t\tactivityMonitor.logComplete(activityId, response.status);\n\t\tthrow new Error(`Tavily API returned invalid JSON: ${errorMessage(err)}`);\n\t}\n\n\tactivityMonitor.logComplete(activityId, response.status);\n\tconst result: SearchResponse = {\n\t\tanswer: typeof data.answer === \"string\" ? data.answer : \"\",\n\t\tresults: mapResults(data.results, numResults),\n\t};\n\tif (options.includeContent) {\n\t\tconst inlineContent = mapInlineContent(data.results);\n\t\tif (inlineContent.length > 0) result.inlineContent = inlineContent;\n\t}\n\treturn result;\n}\n"]}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export declare function getWebSearchConfigDir(): string;
|
|
2
2
|
export declare function getWebSearchConfigPath(): string;
|
|
3
|
+
interface ApiBaseUrlOptions {
|
|
4
|
+
configKey: string;
|
|
5
|
+
configuredValue: unknown;
|
|
6
|
+
defaultValue: string;
|
|
7
|
+
environmentKey: string;
|
|
8
|
+
environmentValue: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveApiBaseUrl(options: ApiBaseUrlOptions): string;
|
|
11
|
+
export declare function fetchWithCredentialRedirects(url: string, init: RequestInit, credentialHeaders: readonly string[]): Promise<Response>;
|
|
3
12
|
export interface CuratorNetworkConfig {
|
|
4
13
|
/** Whether remote access was opted into via curatorRemote. */
|
|
5
14
|
enabled: boolean;
|
|
@@ -17,4 +26,5 @@ export declare function readExecError(err: unknown): {
|
|
|
17
26
|
export declare function isTimeoutError(err: unknown): boolean;
|
|
18
27
|
export declare function trimErrorText(text: string): string;
|
|
19
28
|
export declare function mapFfmpegError(err: unknown): string;
|
|
29
|
+
export {};
|
|
20
30
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/utils.ts"],"names":[],"mappings":"AAKA,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED,MAAM,WAAW,oBAAoB;IACpC,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAUD,kFAAkF;AAClF,wBAAgB,2BAA2B,IAAI,oBAAoB,CAyBlE;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAa9F;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAOpD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOnD","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../host.ts\";\n\nexport function getWebSearchConfigDir(): string {\n\treturn getAgentDir();\n}\n\nexport function getWebSearchConfigPath(): string {\n\treturn join(getWebSearchConfigDir(), \"web-search.json\");\n}\n\nexport interface CuratorNetworkConfig {\n\t/** Whether remote access was opted into via curatorRemote. */\n\tenabled: boolean;\n\thost: string;\n\tbind: string;\n}\n\nconst LOCAL_CURATOR_NETWORK_DEFAULTS: CuratorNetworkConfig = { enabled: false, host: \"localhost\", bind: \"127.0.0.1\" };\n\nfunction trimmedString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Resolves the curator server bind address and URL host from `curatorRemote`. */\nexport function resolveCuratorNetworkConfig(): CuratorNetworkConfig {\n\tconst configPath = getWebSearchConfigPath();\n\tif (!existsSync(configPath)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n\t} catch {\n\t\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tconst curatorRemote = (raw as Record<string, unknown>).curatorRemote;\n\tif (curatorRemote === true) return { enabled: true, host: hostname(), bind: \"0.0.0.0\" };\n\n\tif (curatorRemote && typeof curatorRemote === \"object\" && !Array.isArray(curatorRemote)) {\n\t\tconst obj = curatorRemote as Record<string, unknown>;\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\thost: trimmedString(obj.host) ?? hostname(),\n\t\t\tbind: trimmedString(obj.bind) ?? \"0.0.0.0\",\n\t\t};\n\t}\n\n\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n}\n\nexport function formatSeconds(s: number): string {\n\tconst h = Math.floor(s / 3600);\n\tconst m = Math.floor((s % 3600) / 60);\n\tconst sec = s % 60;\n\tif (h > 0) return `${h}:${String(m).padStart(2, \"0\")}:${String(sec).padStart(2, \"0\")}`;\n\treturn `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nexport function readExecError(err: unknown): { code?: string; stderr: string; message: string } {\n\tif (!err || typeof err !== \"object\") {\n\t\treturn { stderr: \"\", message: String(err) };\n\t}\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\tconst stderrRaw = (err as { stderr?: Buffer | string }).stderr;\n\tconst stderr = Buffer.isBuffer(stderrRaw)\n\t\t? stderrRaw.toString(\"utf-8\")\n\t\t: typeof stderrRaw === \"string\"\n\t\t\t? stderrRaw\n\t\t\t: \"\";\n\treturn { code, stderr, message };\n}\n\nexport function isTimeoutError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tif ((err as { killed?: boolean }).killed) return true;\n\tconst name = (err as { name?: string }).name;\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\treturn name === \"AbortError\" || code === \"ETIMEDOUT\" || message.toLowerCase().includes(\"timed out\");\n}\n\nexport function trimErrorText(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nexport function mapFfmpegError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"ffmpeg is not installed. Install with: brew install ffmpeg\";\n\tif (isTimeoutError(err)) return \"ffmpeg timed out extracting frame\";\n\tif (stderr.includes(\"403\")) return \"Stream URL returned 403 — may have expired, try again\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `ffmpeg failed: ${snippet}` : \"ffmpeg failed\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/utils.ts"],"names":[],"mappings":"AAKA,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAED,UAAU,iBAAiB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAgCpE;AAMD,wBAAsB,4BAA4B,CACjD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,iBAAiB,EAAE,SAAS,MAAM,EAAE,GAClC,OAAO,CAAC,QAAQ,CAAC,CAmCnB;AAED,MAAM,WAAW,oBAAoB;IACpC,8DAA8D;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAUD,kFAAkF;AAClF,wBAAgB,2BAA2B,IAAI,oBAAoB,CAyBlE;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAa9F;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAOpD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAOnD","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { hostname } from \"node:os\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../host.ts\";\n\nexport function getWebSearchConfigDir(): string {\n\treturn getAgentDir();\n}\n\nexport function getWebSearchConfigPath(): string {\n\treturn join(getWebSearchConfigDir(), \"web-search.json\");\n}\n\ninterface ApiBaseUrlOptions {\n\tconfigKey: string;\n\tconfiguredValue: unknown;\n\tdefaultValue: string;\n\tenvironmentKey: string;\n\tenvironmentValue: string | undefined;\n}\n\nexport function resolveApiBaseUrl(options: ApiBaseUrlOptions): string {\n\tconst fromEnvironment = options.environmentValue !== undefined;\n\tconst value = fromEnvironment ? options.environmentValue : options.configuredValue;\n\tif (value === undefined) return options.defaultValue;\n\n\tconst source = fromEnvironment\n\t\t? options.environmentKey\n\t\t: `${options.configKey} in ${getWebSearchConfigPath()}`;\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(`${source} must be an absolute HTTP(S) URL`);\n\t}\n\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value.trim());\n\t} catch {\n\t\tthrow new Error(`${source} must be an absolute HTTP(S) URL`);\n\t}\n\tif (url.protocol !== \"https:\") {\n\t\tthrow new Error(`${source} must be an absolute HTTPS URL`);\n\t}\n\tif (url.username || url.password) {\n\t\tthrow new Error(`${source} must not include credentials`);\n\t}\n\tif (url.search || url.hash) {\n\t\tthrow new Error(`${source} must not include query parameters or fragments`);\n\t}\n\n\turl.search = \"\";\n\turl.hash = \"\";\n\turl.pathname = url.pathname.replace(/\\/+$/, \"\");\n\treturn url.toString().replace(/\\/+$/, \"\");\n}\n\nconst API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);\nconst API_REQUEST_BODY_HEADERS = [\"Content-Encoding\", \"Content-Language\", \"Content-Location\", \"Content-Type\"];\nconst MAX_API_REDIRECTS = 5;\n\nexport async function fetchWithCredentialRedirects(\n\turl: string,\n\tinit: RequestInit,\n\tcredentialHeaders: readonly string[],\n): Promise<Response> {\n\tlet current = new URL(url);\n\tlet requestInit = init;\n\n\tfor (let redirects = 0; ; redirects++) {\n\t\tconst response = await fetch(current, { ...requestInit, redirect: \"manual\" });\n\t\tif (!API_REDIRECT_STATUSES.has(response.status)) return response;\n\n\t\tconst location = response.headers.get(\"location\");\n\t\tif (!location) return response;\n\t\tif (redirects === MAX_API_REDIRECTS) {\n\t\t\tthrow new Error(`Too many API redirects from ${url}`);\n\t\t}\n\n\t\tconst next = new URL(location, current);\n\t\tif (next.protocol !== \"http:\" && next.protocol !== \"https:\") {\n\t\t\tthrow new Error(`API redirect from ${current.origin} must use HTTP(S)`);\n\t\t}\n\t\tconst method = requestInit.method?.toUpperCase() ?? \"GET\";\n\t\tif (\n\t\t\t((response.status === 301 || response.status === 302) && method === \"POST\")\n\t\t\t|| (response.status === 303 && method !== \"GET\" && method !== \"HEAD\")\n\t\t) {\n\t\t\tconst headers = new Headers(requestInit.headers);\n\t\t\tfor (const name of API_REQUEST_BODY_HEADERS) headers.delete(name);\n\t\t\tconst { body: _body, ...withoutBody } = requestInit;\n\t\t\trequestInit = { ...withoutBody, method: \"GET\", headers };\n\t\t}\n\t\tif (next.origin !== current.origin) {\n\t\t\tconst headers = new Headers(requestInit.headers);\n\t\t\tfor (const name of credentialHeaders) headers.delete(name);\n\t\t\trequestInit = { ...requestInit, headers };\n\t\t}\n\t\tcurrent = next;\n\t}\n}\n\nexport interface CuratorNetworkConfig {\n\t/** Whether remote access was opted into via curatorRemote. */\n\tenabled: boolean;\n\thost: string;\n\tbind: string;\n}\n\nconst LOCAL_CURATOR_NETWORK_DEFAULTS: CuratorNetworkConfig = { enabled: false, host: \"localhost\", bind: \"127.0.0.1\" };\n\nfunction trimmedString(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\treturn trimmed.length > 0 ? trimmed : undefined;\n}\n\n/** Resolves the curator server bind address and URL host from `curatorRemote`. */\nexport function resolveCuratorNetworkConfig(): CuratorNetworkConfig {\n\tconst configPath = getWebSearchConfigPath();\n\tif (!existsSync(configPath)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(readFileSync(configPath, \"utf-8\"));\n\t} catch {\n\t\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n\t}\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return LOCAL_CURATOR_NETWORK_DEFAULTS;\n\n\tconst curatorRemote = (raw as Record<string, unknown>).curatorRemote;\n\tif (curatorRemote === true) return { enabled: true, host: hostname(), bind: \"0.0.0.0\" };\n\n\tif (curatorRemote && typeof curatorRemote === \"object\" && !Array.isArray(curatorRemote)) {\n\t\tconst obj = curatorRemote as Record<string, unknown>;\n\t\treturn {\n\t\t\tenabled: true,\n\t\t\thost: trimmedString(obj.host) ?? hostname(),\n\t\t\tbind: trimmedString(obj.bind) ?? \"0.0.0.0\",\n\t\t};\n\t}\n\n\treturn LOCAL_CURATOR_NETWORK_DEFAULTS;\n}\n\nexport function formatSeconds(s: number): string {\n\tconst h = Math.floor(s / 3600);\n\tconst m = Math.floor((s % 3600) / 60);\n\tconst sec = s % 60;\n\tif (h > 0) return `${h}:${String(m).padStart(2, \"0\")}:${String(sec).padStart(2, \"0\")}`;\n\treturn `${m}:${String(sec).padStart(2, \"0\")}`;\n}\n\nexport function readExecError(err: unknown): { code?: string; stderr: string; message: string } {\n\tif (!err || typeof err !== \"object\") {\n\t\treturn { stderr: \"\", message: String(err) };\n\t}\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\tconst stderrRaw = (err as { stderr?: Buffer | string }).stderr;\n\tconst stderr = Buffer.isBuffer(stderrRaw)\n\t\t? stderrRaw.toString(\"utf-8\")\n\t\t: typeof stderrRaw === \"string\"\n\t\t\t? stderrRaw\n\t\t\t: \"\";\n\treturn { code, stderr, message };\n}\n\nexport function isTimeoutError(err: unknown): boolean {\n\tif (!err || typeof err !== \"object\") return false;\n\tif ((err as { killed?: boolean }).killed) return true;\n\tconst name = (err as { name?: string }).name;\n\tconst code = (err as { code?: string }).code;\n\tconst message = (err as { message?: string }).message ?? \"\";\n\treturn name === \"AbortError\" || code === \"ETIMEDOUT\" || message.toLowerCase().includes(\"timed out\");\n}\n\nexport function trimErrorText(text: string): string {\n\treturn text.replace(/\\s+/g, \" \").trim().slice(0, 200);\n}\n\nexport function mapFfmpegError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"ffmpeg is not installed. Install with: brew install ffmpeg\";\n\tif (isTimeoutError(err)) return \"ffmpeg timed out extracting frame\";\n\tif (stderr.includes(\"403\")) return \"Stream URL returned 403 — may have expired, try again\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `ffmpeg failed: ${snippet}` : \"ffmpeg failed\";\n}\n"]}
|