@humain/terminal 0.0.3-beta.0 → 0.0.3-beta.2
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 +13 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -3
- package/dist/config.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +13 -0
- package/dist/humain/mcp-adapter/vendor/README.md +4 -1
- package/dist/humain/mcp-adapter/vendor/agent-dir.ts +12 -4
- package/dist/humain/mcp-adapter/vendor/cli.js +25 -4
- package/dist/humain/mcp-adapter/vendor/config.ts +4 -4
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +18 -15
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +416 -60
- package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +2 -1
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +18 -8
- package/dist/humain/mcp-adapter/vendor/package.json +177 -176
- package/dist/humain/mcp-adapter/vendor/request-headers-command.ts +336 -0
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +5 -0
- package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +38 -18
- package/dist/humain/mcp-adapter/vendor/types.ts +93 -10
- package/dist/humain/web-access/vendor/auth-fetch.d.ts +13 -0
- package/dist/humain/web-access/vendor/auth-fetch.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/auth-fetch.js +134 -0
- package/dist/humain/web-access/vendor/auth-fetch.js.map +1 -0
- package/dist/humain/web-access/vendor/chrome-cookies.d.ts +13 -0
- package/dist/humain/web-access/vendor/chrome-cookies.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/chrome-cookies.js +106 -22
- package/dist/humain/web-access/vendor/chrome-cookies.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts +1 -0
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +3 -1
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +2 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts +2 -0
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +79 -17
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/fetch-params.d.ts +2 -0
- package/dist/humain/web-access/vendor/fetch-params.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js +17 -3
- package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
- package/dist/humain/web-access/vendor/firecrawl.d.ts +7 -0
- package/dist/humain/web-access/vendor/firecrawl.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/firecrawl.js +141 -10
- package/dist/humain/web-access/vendor/firecrawl.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 +22 -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 +122 -48
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts +3 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +6 -3
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/youtube-extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/youtube-extract.js +2 -2
- package/dist/humain/web-access/vendor/youtube-extract.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +2 -0
- package/dist/utils/clipboard-image.js.map +1 -1
- package/npm-shrinkwrap.json +7 -7
- package/package.json +6 -6
- 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 +13 -0
- package/src/humain/web-access/UPSTREAM.md +1 -1
- package/src/humain/web-access/upstream.json +4 -4
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { complete } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
import type { ExtensionContext } from "../host.ts";
|
|
3
3
|
import type { QueryResultData } from "./storage.ts";
|
|
4
|
+
type CompleteFunction = typeof complete;
|
|
4
5
|
export declare const SUMMARY_GENERATION_DEADLINE_MS = 30000;
|
|
5
6
|
export interface SummaryMeta {
|
|
6
7
|
model: string | null;
|
|
@@ -17,8 +18,9 @@ export declare function buildDeterministicSummary(results: QueryResultData[]): {
|
|
|
17
18
|
summary: string;
|
|
18
19
|
meta: SummaryMeta;
|
|
19
20
|
};
|
|
20
|
-
export declare function generateSummaryDraft(results: QueryResultData[], ctx: SummaryGenerationContext, signal?: AbortSignal, modelOverride?: string, feedback?: string, completeFn?:
|
|
21
|
+
export declare function generateSummaryDraft(results: QueryResultData[], ctx: SummaryGenerationContext, signal?: AbortSignal, modelOverride?: string, feedback?: string, completeFn?: CompleteFunction, deadlineMs?: number): Promise<{
|
|
21
22
|
summary: string;
|
|
22
23
|
meta: SummaryMeta;
|
|
23
24
|
}>;
|
|
25
|
+
export {};
|
|
24
26
|
//# sourceMappingURL=summary-review.d.ts.map
|
|
@@ -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;AASpD,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,GAAE,OAAO,QAAmB,EACtC,UAAU,SAAiC,GACzC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAyHjD","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>;\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 || !auth.apiKey) {\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: typeof complete = complete,\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 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\t{ 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":"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"]}
|
|
@@ -179,7 +179,7 @@ async function resolveSummaryModelCandidates(ctx, modelOverride) {
|
|
|
179
179
|
continue;
|
|
180
180
|
}
|
|
181
181
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
182
|
-
if (!auth.ok
|
|
182
|
+
if (!auth.ok) {
|
|
183
183
|
errors.push(`No API key available for summary model ${value}`);
|
|
184
184
|
continue;
|
|
185
185
|
}
|
|
@@ -221,10 +221,13 @@ function getContentPartType(part) {
|
|
|
221
221
|
const value = part;
|
|
222
222
|
return typeof value.type === "string" ? value.type : "unknown";
|
|
223
223
|
}
|
|
224
|
-
export async function generateSummaryDraft(results, ctx, signal, modelOverride, feedback, completeFn
|
|
224
|
+
export async function generateSummaryDraft(results, ctx, signal, modelOverride, feedback, completeFn, deadlineMs = SUMMARY_GENERATION_DEADLINE_MS) {
|
|
225
225
|
if (!ctx || !ctx.modelRegistry) {
|
|
226
226
|
throw new Error("Summary generation context unavailable");
|
|
227
227
|
}
|
|
228
|
+
const registry = ctx.modelRegistry;
|
|
229
|
+
const usesRegistryComplete = !completeFn && typeof registry.complete === "function";
|
|
230
|
+
completeFn ??= usesRegistryComplete ? registry.complete.bind(registry) : complete;
|
|
228
231
|
const generationStartedAt = Date.now();
|
|
229
232
|
const deadlineController = new AbortController();
|
|
230
233
|
const deadlineMarker = Symbol("summary-generation-deadline");
|
|
@@ -291,7 +294,7 @@ export async function generateSummaryDraft(results, ctx, signal, modelOverride,
|
|
|
291
294
|
content: [{ type: "text", text: prompt }],
|
|
292
295
|
timestamp: Date.now(),
|
|
293
296
|
};
|
|
294
|
-
const response = await raceSummaryOperation(Promise.resolve(completeFn(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal })));
|
|
297
|
+
const response = await raceSummaryOperation(Promise.resolve(completeFn(model, { messages: [userMessage] }, usesRegistryComplete ? { signal: completionSignal } : { apiKey, headers, signal: completionSignal })));
|
|
295
298
|
if (response.stopReason === "aborted") {
|
|
296
299
|
throw new Error("Aborted");
|
|
297
300
|
}
|
|
@@ -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>;\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 || !auth.apiKey) {\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: typeof complete = complete,\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 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\t{ 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 { 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"youtube-extract.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/youtube-extract.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAuE7G,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAYxF;AAED,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAsB,cAAc,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoClC;AAED,KAAK,UAAU,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AACjE,KAAK,YAAY,GAAG,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAenD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgBjF;AAeD,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,UAAU,GACrB,OAAO,CAAC,WAAW,CAAC,CAItB;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,CAAC,EAAE,UAAU,GACrB,OAAO,CAAC;IAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAWlF;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAY/G","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { canAttachImages } from \"./feature-config.ts\";\nimport { isGeminiWebAvailable, queryWithCookies } from \"./gemini-web.ts\";\nimport { isGeminiApiAvailable, queryGeminiApiWithVideo } from \"./gemini-api.ts\";\nimport { isPerplexityAvailable, searchWithPerplexity } from \"./perplexity.ts\";\nimport { extractHeadingTitle, type ExtractedContent, type FrameResult, type VideoFrame } from \"./extract.ts\";\nimport { formatSeconds, readExecError, isTimeoutError, trimErrorText, mapFfmpegError, getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nconst YOUTUBE_PROMPT = `Extract the complete content of this YouTube video. Include:\n1. Video title, channel name, and duration\n2. A brief summary (2-3 sentences)\n3. Full transcript with timestamps\n4. Descriptions of any code, terminal commands, diagrams, slides, or UI shown on screen\n\nFormat as markdown.`;\n\nconst YOUTUBE_REGEX =\n\t/(?:(?:www\\.|m\\.)?youtube\\.com\\/(?:watch\\?.*v=|shorts\\/|live\\/|embed\\/|v\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/;\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction shouldRethrow(err: unknown): boolean {\n\treturn errorMessage(err).startsWith(\"Failed to parse \");\n}\n\nfunction addAttemptError(errors: string[], label: string, err: unknown): void {\n\tconst message = errorMessage(err).replace(/\\s+/g, \" \").trim();\n\tif (message) errors.push(`${label}: ${message}`);\n}\n\ninterface YouTubeConfig {\n\tenabled: boolean;\n\tpreferredModel: string;\n}\n\nfunction normalizePreferredModel(value: unknown, fallback: string): string {\n\tif (typeof value !== \"string\") return fallback;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nfunction normalizeEnabled(value: unknown, fallback: boolean): boolean {\n\treturn typeof value === \"boolean\" ? value : fallback;\n}\n\nconst defaults: YouTubeConfig = { enabled: true, preferredModel: \"gemini-3.6-flash\" };\nlet cachedConfig: YouTubeConfig | null = null;\n\nfunction loadYouTubeConfig(): YouTubeConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = { ...defaults };\n\t\treturn cachedConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: { youtube?: { enabled?: boolean; preferredModel?: string } };\n\ttry {\n\t\traw = JSON.parse(rawText) as { youtube?: { enabled?: boolean; preferredModel?: string } };\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\tconst yt = raw.youtube ?? {};\n\tcachedConfig = {\n\t\tenabled: normalizeEnabled(yt.enabled, defaults.enabled),\n\t\tpreferredModel: normalizePreferredModel(yt.preferredModel, defaults.preferredModel),\n\t};\n\treturn cachedConfig;\n}\n\nexport function isYouTubeURL(url: string): { isYouTube: boolean; videoId: string | null } {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (parsed.pathname === \"/playlist\") {\n\t\t\treturn { isYouTube: false, videoId: null };\n\t\t}\n\t} catch {\n\t}\n\n\tconst match = url.match(YOUTUBE_REGEX);\n\tif (!match) return { isYouTube: false, videoId: null };\n\treturn { isYouTube: true, videoId: match[1] };\n}\n\nexport function isYouTubeEnabled(): boolean {\n\treturn loadYouTubeConfig().enabled;\n}\n\nexport async function extractYouTube(\n\turl: string,\n\tsignal?: AbortSignal,\n\tprompt?: string,\n\tmodel?: string,\n): Promise<ExtractedContent | null> {\n\tconst config = loadYouTubeConfig();\n\tconst { videoId } = isYouTubeURL(url);\n\tconst canonicalUrl = videoId\n\t\t? `https://www.youtube.com/watch?v=${videoId}`\n\t\t: url;\n\tconst effectivePrompt = prompt ?? YOUTUBE_PROMPT;\n\tconst effectiveModel = model ?? config.preferredModel;\n\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: `youtube.com/${videoId ?? \"video\"}` });\n\tconst attemptErrors: string[] = [];\n\n\tconst result = await tryGeminiWeb(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryGeminiApi(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryPerplexity(url, effectivePrompt, signal, attemptErrors);\n\n\tif (result) {\n\t\tresult.url = url;\n\t\tif (!result.error && videoId && canAttachImages()) {\n\t\t\tconst thumb = await fetchYouTubeThumbnail(videoId);\n\t\t\tif (thumb) result.thumbnail = thumb;\n\t\t}\n\t\tactivityMonitor.logComplete(activityId, result.error ? 0 : 200);\n\t\treturn result;\n\t}\n\n\tif (signal?.aborted) {\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\tconst error = attemptErrors.length > 0\n\t\t? [\"Could not extract YouTube video content.\", \"\", ...attemptErrors.map(message => `- ${message}`)].join(\"\\n\")\n\t\t: \"Could not extract YouTube video content. Sign into Google in Chrome for automatic access, or set GEMINI_API_KEY.\";\n\tactivityMonitor.logError(activityId, error);\n\treturn { url, title: \"\", content: \"\", error };\n}\n\ntype StreamInfo = { streamUrl: string; duration: number | null };\ntype StreamResult = StreamInfo | { error: string };\n\nfunction mapYtDlpError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"yt-dlp is not installed. Install with: brew install yt-dlp\";\n\tif (isTimeoutError(err)) return \"yt-dlp timed out fetching video info\";\n\tconst lower = stderr.toLowerCase();\n\tif (lower.includes(\"private\")) return \"Video is private or unavailable\";\n\tif (lower.includes(\"sign in\")) return \"Video is age-restricted and requires authentication\";\n\tif (lower.includes(\"not available\")) return \"Video is unavailable in your region or has been removed\";\n\tif (lower.includes(\"live\")) return \"Cannot extract frames from a live stream\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `yt-dlp failed: ${snippet}` : \"yt-dlp failed\";\n}\n\nexport async function getYouTubeStreamInfo(videoId: string): Promise<StreamResult> {\n\ttry {\n\t\tconst output = execFileSync(\"yt-dlp\", [\n\t\t\t\"--print\", \"duration\",\n\t\t\t\"-g\", `https://www.youtube.com/watch?v=${videoId}`,\n\t\t], { timeout: 15000, encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n\t\tconst lines = output.split(/\\r?\\n/);\n\t\tconst rawDuration = lines[0]?.trim();\n\t\tconst streamUrl = lines[1]?.trim();\n\t\tif (!streamUrl) return { error: \"yt-dlp failed: missing stream URL\" };\n\t\tconst parsedDuration = rawDuration && rawDuration !== \"NA\" ? Number.parseFloat(rawDuration) : NaN;\n\t\tconst duration = Number.isFinite(parsedDuration) ? parsedDuration : null;\n\t\treturn { streamUrl, duration };\n\t} catch (err) {\n\t\treturn { error: mapYtDlpError(err) };\n\t}\n}\n\nasync function extractFrameFromStream(streamUrl: string, seconds: number): Promise<FrameResult> {\n\ttry {\n\t\tconst buffer = execFileSync(\"ffmpeg\", [\n\t\t\t\"-ss\", String(seconds), \"-i\", streamUrl,\n\t\t\t\"-frames:v\", \"1\", \"-f\", \"image2pipe\", \"-vcodec\", \"mjpeg\", \"pipe:1\",\n\t\t], { maxBuffer: 5 * 1024 * 1024, timeout: 30000, stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tif (buffer.length === 0) return { error: \"ffmpeg failed: empty output\" };\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch (err) {\n\t\treturn { error: mapFfmpegError(err) };\n\t}\n}\n\nexport async function extractYouTubeFrame(\n\tvideoId: string,\n\tseconds: number,\n\tstreamInfo?: StreamInfo,\n): Promise<FrameResult> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return info;\n\treturn extractFrameFromStream(info.streamUrl, seconds);\n}\n\nexport async function extractYouTubeFrames(\n\tvideoId: string,\n\ttimestamps: number[],\n\tstreamInfo?: StreamInfo,\n): Promise<{ frames: VideoFrame[]; duration: number | null; error: string | null }> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return { frames: [], duration: null, error: info.error };\n\tconst results = await Promise.all(timestamps.map(async (t) => {\n\t\tconst frame = await extractFrameFromStream(info.streamUrl, t);\n\t\tif (\"error\" in frame) return { error: frame.error };\n\t\treturn { ...frame, timestamp: formatSeconds(t) };\n\t}));\n\tconst frames = results.filter((f): f is VideoFrame => \"data\" in f);\n\tconst errorResult = results.find((f): f is { error: string } => \"error\" in f);\n\treturn { frames, duration: info.duration, error: frames.length === 0 && errorResult ? errorResult.error : null };\n}\n\nexport async function fetchYouTubeThumbnail(videoId: string): Promise<{ data: string; mimeType: string } | null> {\n\ttry {\n\t\tconst res = await fetch(`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`, {\n\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t});\n\t\tif (!res.ok) return null;\n\t\tconst buffer = Buffer.from(await res.arrayBuffer());\n\t\tif (buffer.length === 0) return null;\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiWeb(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tconst cookies = await isGeminiWebAvailable();\n\t\tif (!cookies) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryWithCookies(prompt, cookies, {\n\t\t\tyoutubeUrl: url,\n\t\t\t...(model !== \"gemini-3.6-flash\" ? { model } : {}),\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini Web\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiApi(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (!isGeminiApiAvailable()) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryGeminiApiWithVideo(prompt, url, {\n\t\t\tmodel,\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini API\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryPerplexity(\n\turl: string,\n\tprompt: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (signal?.aborted || !isPerplexityAvailable()) return null;\n\n\t\tconst perplexityQuery = prompt === YOUTUBE_PROMPT\n\t\t\t? `Summarize this YouTube video in detail: ${url}`\n\t\t\t: `${prompt} YouTube video: ${url}`;\n\n\t\tconst { answer } = await searchWithPerplexity(\n\t\t\tperplexityQuery,\n\t\t\t{ signal },\n\t\t);\n\n\t\tif (!answer) return null;\n\n\t\tconst content =\n\t\t\t`# Video Summary (via Perplexity)\\n\\n${answer}\\n\\n` +\n\t\t\t`*Full video understanding requires Gemini access. Set GEMINI_API_KEY or sign into Google in Chrome.*`;\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: \"Video Summary (via Perplexity)\",\n\t\t\tcontent,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Perplexity\", err);\n\t\treturn null;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"youtube-extract.d.ts","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/youtube-extract.ts"],"names":[],"mappings":"AAOA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAuE7G,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAYxF;AAED,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED,wBAAsB,cAAc,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoClC;AAED,KAAK,UAAU,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AACjE,KAAK,YAAY,GAAG,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAenD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgBjF;AAeD,wBAAsB,mBAAmB,CACxC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,UAAU,GACrB,OAAO,CAAC,WAAW,CAAC,CAItB;AAED,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAAE,EACpB,UAAU,CAAC,EAAE,UAAU,GACrB,OAAO,CAAC;IAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAWlF;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAY/G","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { canAttachImages } from \"./feature-config.ts\";\nimport { isGeminiWebAvailable, queryWithCookies } from \"./gemini-web.ts\";\nimport { isGeminiApiAvailable, queryGeminiApiWithVideo } from \"./gemini-api.ts\";\nimport { isPerplexityAvailable, searchWithPerplexity } from \"./perplexity.ts\";\nimport { extractHeadingTitle, type ExtractedContent, type FrameResult, type VideoFrame } from \"./extract.ts\";\nimport { formatSeconds, readExecError, isTimeoutError, trimErrorText, mapFfmpegError, getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nconst YOUTUBE_PROMPT = `Extract the complete content of this YouTube video. Include:\n1. Video title, channel name, and duration\n2. A brief summary (2-3 sentences)\n3. Full transcript with timestamps\n4. Descriptions of any code, terminal commands, diagrams, slides, or UI shown on screen\n\nFormat as markdown.`;\n\nconst YOUTUBE_REGEX =\n\t/(?:(?:www\\.|m\\.)?youtube\\.com\\/(?:watch\\?.*v=|shorts\\/|live\\/|embed\\/|v\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/;\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction shouldRethrow(err: unknown): boolean {\n\treturn errorMessage(err).startsWith(\"Failed to parse \");\n}\n\nfunction addAttemptError(errors: string[], label: string, err: unknown): void {\n\tconst message = errorMessage(err).replace(/\\s+/g, \" \").trim();\n\tif (message) errors.push(`${label}: ${message}`);\n}\n\ninterface YouTubeConfig {\n\tenabled: boolean;\n\tpreferredModel: string;\n}\n\nfunction normalizePreferredModel(value: unknown, fallback: string): string {\n\tif (typeof value !== \"string\") return fallback;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nfunction normalizeEnabled(value: unknown, fallback: boolean): boolean {\n\treturn typeof value === \"boolean\" ? value : fallback;\n}\n\nconst defaults: YouTubeConfig = { enabled: true, preferredModel: \"gemini-3.6-flash\" };\nlet cachedConfig: YouTubeConfig | null = null;\n\nfunction loadYouTubeConfig(): YouTubeConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = { ...defaults };\n\t\treturn cachedConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: { youtube?: { enabled?: boolean; preferredModel?: string } };\n\ttry {\n\t\traw = JSON.parse(rawText) as { youtube?: { enabled?: boolean; preferredModel?: string } };\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\tconst yt = raw.youtube ?? {};\n\tcachedConfig = {\n\t\tenabled: normalizeEnabled(yt.enabled, defaults.enabled),\n\t\tpreferredModel: normalizePreferredModel(yt.preferredModel, defaults.preferredModel),\n\t};\n\treturn cachedConfig;\n}\n\nexport function isYouTubeURL(url: string): { isYouTube: boolean; videoId: string | null } {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (parsed.pathname === \"/playlist\") {\n\t\t\treturn { isYouTube: false, videoId: null };\n\t\t}\n\t} catch {\n\t}\n\n\tconst match = url.match(YOUTUBE_REGEX);\n\tif (!match) return { isYouTube: false, videoId: null };\n\treturn { isYouTube: true, videoId: match[1] };\n}\n\nexport function isYouTubeEnabled(): boolean {\n\treturn loadYouTubeConfig().enabled;\n}\n\nexport async function extractYouTube(\n\turl: string,\n\tsignal?: AbortSignal,\n\tprompt?: string,\n\tmodel?: string,\n): Promise<ExtractedContent | null> {\n\tconst config = loadYouTubeConfig();\n\tconst { videoId } = isYouTubeURL(url);\n\tconst canonicalUrl = videoId\n\t\t? `https://www.youtube.com/watch?v=${videoId}`\n\t\t: url;\n\tconst effectivePrompt = prompt ?? YOUTUBE_PROMPT;\n\tconst effectiveModel = model ?? config.preferredModel;\n\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: `youtube.com/${videoId ?? \"video\"}` });\n\tconst attemptErrors: string[] = [];\n\n\tconst result = await tryGeminiWeb(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryGeminiApi(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryPerplexity(url, effectivePrompt, signal, attemptErrors);\n\n\tif (result) {\n\t\tresult.url = url;\n\t\tif (!result.error && videoId && canAttachImages()) {\n\t\t\tconst thumb = await fetchYouTubeThumbnail(videoId);\n\t\t\tif (thumb) result.thumbnail = thumb;\n\t\t}\n\t\tactivityMonitor.logComplete(activityId, result.error ? 0 : 200);\n\t\treturn result;\n\t}\n\n\tif (signal?.aborted) {\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\tconst error = attemptErrors.length > 0\n\t\t? [\"Could not extract YouTube video content.\", \"\", ...attemptErrors.map(message => `- ${message}`)].join(\"\\n\")\n\t\t: \"Could not extract YouTube video content. Sign into Google in a supported Chromium browser for automatic access, or set GEMINI_API_KEY.\";\n\tactivityMonitor.logError(activityId, error);\n\treturn { url, title: \"\", content: \"\", error };\n}\n\ntype StreamInfo = { streamUrl: string; duration: number | null };\ntype StreamResult = StreamInfo | { error: string };\n\nfunction mapYtDlpError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"yt-dlp is not installed. Install with: brew install yt-dlp\";\n\tif (isTimeoutError(err)) return \"yt-dlp timed out fetching video info\";\n\tconst lower = stderr.toLowerCase();\n\tif (lower.includes(\"private\")) return \"Video is private or unavailable\";\n\tif (lower.includes(\"sign in\")) return \"Video is age-restricted and requires authentication\";\n\tif (lower.includes(\"not available\")) return \"Video is unavailable in your region or has been removed\";\n\tif (lower.includes(\"live\")) return \"Cannot extract frames from a live stream\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `yt-dlp failed: ${snippet}` : \"yt-dlp failed\";\n}\n\nexport async function getYouTubeStreamInfo(videoId: string): Promise<StreamResult> {\n\ttry {\n\t\tconst output = execFileSync(\"yt-dlp\", [\n\t\t\t\"--print\", \"duration\",\n\t\t\t\"-g\", `https://www.youtube.com/watch?v=${videoId}`,\n\t\t], { timeout: 15000, encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n\t\tconst lines = output.split(/\\r?\\n/);\n\t\tconst rawDuration = lines[0]?.trim();\n\t\tconst streamUrl = lines[1]?.trim();\n\t\tif (!streamUrl) return { error: \"yt-dlp failed: missing stream URL\" };\n\t\tconst parsedDuration = rawDuration && rawDuration !== \"NA\" ? Number.parseFloat(rawDuration) : NaN;\n\t\tconst duration = Number.isFinite(parsedDuration) ? parsedDuration : null;\n\t\treturn { streamUrl, duration };\n\t} catch (err) {\n\t\treturn { error: mapYtDlpError(err) };\n\t}\n}\n\nasync function extractFrameFromStream(streamUrl: string, seconds: number): Promise<FrameResult> {\n\ttry {\n\t\tconst buffer = execFileSync(\"ffmpeg\", [\n\t\t\t\"-ss\", String(seconds), \"-i\", streamUrl,\n\t\t\t\"-frames:v\", \"1\", \"-f\", \"image2pipe\", \"-vcodec\", \"mjpeg\", \"pipe:1\",\n\t\t], { maxBuffer: 5 * 1024 * 1024, timeout: 30000, stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tif (buffer.length === 0) return { error: \"ffmpeg failed: empty output\" };\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch (err) {\n\t\treturn { error: mapFfmpegError(err) };\n\t}\n}\n\nexport async function extractYouTubeFrame(\n\tvideoId: string,\n\tseconds: number,\n\tstreamInfo?: StreamInfo,\n): Promise<FrameResult> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return info;\n\treturn extractFrameFromStream(info.streamUrl, seconds);\n}\n\nexport async function extractYouTubeFrames(\n\tvideoId: string,\n\ttimestamps: number[],\n\tstreamInfo?: StreamInfo,\n): Promise<{ frames: VideoFrame[]; duration: number | null; error: string | null }> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return { frames: [], duration: null, error: info.error };\n\tconst results = await Promise.all(timestamps.map(async (t) => {\n\t\tconst frame = await extractFrameFromStream(info.streamUrl, t);\n\t\tif (\"error\" in frame) return { error: frame.error };\n\t\treturn { ...frame, timestamp: formatSeconds(t) };\n\t}));\n\tconst frames = results.filter((f): f is VideoFrame => \"data\" in f);\n\tconst errorResult = results.find((f): f is { error: string } => \"error\" in f);\n\treturn { frames, duration: info.duration, error: frames.length === 0 && errorResult ? errorResult.error : null };\n}\n\nexport async function fetchYouTubeThumbnail(videoId: string): Promise<{ data: string; mimeType: string } | null> {\n\ttry {\n\t\tconst res = await fetch(`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`, {\n\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t});\n\t\tif (!res.ok) return null;\n\t\tconst buffer = Buffer.from(await res.arrayBuffer());\n\t\tif (buffer.length === 0) return null;\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiWeb(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tconst cookies = await isGeminiWebAvailable();\n\t\tif (!cookies) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryWithCookies(prompt, cookies, {\n\t\t\tyoutubeUrl: url,\n\t\t\t...(model !== \"gemini-3.6-flash\" ? { model } : {}),\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini Web\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiApi(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (!isGeminiApiAvailable()) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryGeminiApiWithVideo(prompt, url, {\n\t\t\tmodel,\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini API\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryPerplexity(\n\turl: string,\n\tprompt: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (signal?.aborted || !isPerplexityAvailable()) return null;\n\n\t\tconst perplexityQuery = prompt === YOUTUBE_PROMPT\n\t\t\t? `Summarize this YouTube video in detail: ${url}`\n\t\t\t: `${prompt} YouTube video: ${url}`;\n\n\t\tconst { answer } = await searchWithPerplexity(\n\t\t\tperplexityQuery,\n\t\t\t{ signal },\n\t\t);\n\n\t\tif (!answer) return null;\n\n\t\tconst content =\n\t\t\t`# Video Summary (via Perplexity)\\n\\n${answer}\\n\\n` +\n\t\t\t`*Full video understanding requires Gemini access. Set GEMINI_API_KEY or sign into Google in a supported Chromium browser.*`;\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: \"Video Summary (via Perplexity)\",\n\t\t\tcontent,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Perplexity\", err);\n\t\treturn null;\n\t}\n}\n"]}
|
|
@@ -107,7 +107,7 @@ export async function extractYouTube(url, signal, prompt, model) {
|
|
|
107
107
|
}
|
|
108
108
|
const error = attemptErrors.length > 0
|
|
109
109
|
? ["Could not extract YouTube video content.", "", ...attemptErrors.map(message => `- ${message}`)].join("\n")
|
|
110
|
-
: "Could not extract YouTube video content. Sign into Google in
|
|
110
|
+
: "Could not extract YouTube video content. Sign into Google in a supported Chromium browser for automatic access, or set GEMINI_API_KEY.";
|
|
111
111
|
activityMonitor.logError(activityId, error);
|
|
112
112
|
return { url, title: "", content: "", error };
|
|
113
113
|
}
|
|
@@ -263,7 +263,7 @@ async function tryPerplexity(url, prompt, signal, attemptErrors) {
|
|
|
263
263
|
if (!answer)
|
|
264
264
|
return null;
|
|
265
265
|
const content = `# Video Summary (via Perplexity)\n\n${answer}\n\n` +
|
|
266
|
-
`*Full video understanding requires Gemini access. Set GEMINI_API_KEY or sign into Google in
|
|
266
|
+
`*Full video understanding requires Gemini access. Set GEMINI_API_KEY or sign into Google in a supported Chromium browser.*`;
|
|
267
267
|
return {
|
|
268
268
|
url,
|
|
269
269
|
title: "Video Summary (via Perplexity)",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"youtube-extract.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/youtube-extract.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { canAttachImages } from \"./feature-config.ts\";\nimport { isGeminiWebAvailable, queryWithCookies } from \"./gemini-web.ts\";\nimport { isGeminiApiAvailable, queryGeminiApiWithVideo } from \"./gemini-api.ts\";\nimport { isPerplexityAvailable, searchWithPerplexity } from \"./perplexity.ts\";\nimport { extractHeadingTitle, type ExtractedContent, type FrameResult, type VideoFrame } from \"./extract.ts\";\nimport { formatSeconds, readExecError, isTimeoutError, trimErrorText, mapFfmpegError, getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nconst YOUTUBE_PROMPT = `Extract the complete content of this YouTube video. Include:\n1. Video title, channel name, and duration\n2. A brief summary (2-3 sentences)\n3. Full transcript with timestamps\n4. Descriptions of any code, terminal commands, diagrams, slides, or UI shown on screen\n\nFormat as markdown.`;\n\nconst YOUTUBE_REGEX =\n\t/(?:(?:www\\.|m\\.)?youtube\\.com\\/(?:watch\\?.*v=|shorts\\/|live\\/|embed\\/|v\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/;\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction shouldRethrow(err: unknown): boolean {\n\treturn errorMessage(err).startsWith(\"Failed to parse \");\n}\n\nfunction addAttemptError(errors: string[], label: string, err: unknown): void {\n\tconst message = errorMessage(err).replace(/\\s+/g, \" \").trim();\n\tif (message) errors.push(`${label}: ${message}`);\n}\n\ninterface YouTubeConfig {\n\tenabled: boolean;\n\tpreferredModel: string;\n}\n\nfunction normalizePreferredModel(value: unknown, fallback: string): string {\n\tif (typeof value !== \"string\") return fallback;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nfunction normalizeEnabled(value: unknown, fallback: boolean): boolean {\n\treturn typeof value === \"boolean\" ? value : fallback;\n}\n\nconst defaults: YouTubeConfig = { enabled: true, preferredModel: \"gemini-3.6-flash\" };\nlet cachedConfig: YouTubeConfig | null = null;\n\nfunction loadYouTubeConfig(): YouTubeConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = { ...defaults };\n\t\treturn cachedConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: { youtube?: { enabled?: boolean; preferredModel?: string } };\n\ttry {\n\t\traw = JSON.parse(rawText) as { youtube?: { enabled?: boolean; preferredModel?: string } };\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\tconst yt = raw.youtube ?? {};\n\tcachedConfig = {\n\t\tenabled: normalizeEnabled(yt.enabled, defaults.enabled),\n\t\tpreferredModel: normalizePreferredModel(yt.preferredModel, defaults.preferredModel),\n\t};\n\treturn cachedConfig;\n}\n\nexport function isYouTubeURL(url: string): { isYouTube: boolean; videoId: string | null } {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (parsed.pathname === \"/playlist\") {\n\t\t\treturn { isYouTube: false, videoId: null };\n\t\t}\n\t} catch {\n\t}\n\n\tconst match = url.match(YOUTUBE_REGEX);\n\tif (!match) return { isYouTube: false, videoId: null };\n\treturn { isYouTube: true, videoId: match[1] };\n}\n\nexport function isYouTubeEnabled(): boolean {\n\treturn loadYouTubeConfig().enabled;\n}\n\nexport async function extractYouTube(\n\turl: string,\n\tsignal?: AbortSignal,\n\tprompt?: string,\n\tmodel?: string,\n): Promise<ExtractedContent | null> {\n\tconst config = loadYouTubeConfig();\n\tconst { videoId } = isYouTubeURL(url);\n\tconst canonicalUrl = videoId\n\t\t? `https://www.youtube.com/watch?v=${videoId}`\n\t\t: url;\n\tconst effectivePrompt = prompt ?? YOUTUBE_PROMPT;\n\tconst effectiveModel = model ?? config.preferredModel;\n\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: `youtube.com/${videoId ?? \"video\"}` });\n\tconst attemptErrors: string[] = [];\n\n\tconst result = await tryGeminiWeb(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryGeminiApi(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryPerplexity(url, effectivePrompt, signal, attemptErrors);\n\n\tif (result) {\n\t\tresult.url = url;\n\t\tif (!result.error && videoId && canAttachImages()) {\n\t\t\tconst thumb = await fetchYouTubeThumbnail(videoId);\n\t\t\tif (thumb) result.thumbnail = thumb;\n\t\t}\n\t\tactivityMonitor.logComplete(activityId, result.error ? 0 : 200);\n\t\treturn result;\n\t}\n\n\tif (signal?.aborted) {\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\tconst error = attemptErrors.length > 0\n\t\t? [\"Could not extract YouTube video content.\", \"\", ...attemptErrors.map(message => `- ${message}`)].join(\"\\n\")\n\t\t: \"Could not extract YouTube video content. Sign into Google in Chrome for automatic access, or set GEMINI_API_KEY.\";\n\tactivityMonitor.logError(activityId, error);\n\treturn { url, title: \"\", content: \"\", error };\n}\n\ntype StreamInfo = { streamUrl: string; duration: number | null };\ntype StreamResult = StreamInfo | { error: string };\n\nfunction mapYtDlpError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"yt-dlp is not installed. Install with: brew install yt-dlp\";\n\tif (isTimeoutError(err)) return \"yt-dlp timed out fetching video info\";\n\tconst lower = stderr.toLowerCase();\n\tif (lower.includes(\"private\")) return \"Video is private or unavailable\";\n\tif (lower.includes(\"sign in\")) return \"Video is age-restricted and requires authentication\";\n\tif (lower.includes(\"not available\")) return \"Video is unavailable in your region or has been removed\";\n\tif (lower.includes(\"live\")) return \"Cannot extract frames from a live stream\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `yt-dlp failed: ${snippet}` : \"yt-dlp failed\";\n}\n\nexport async function getYouTubeStreamInfo(videoId: string): Promise<StreamResult> {\n\ttry {\n\t\tconst output = execFileSync(\"yt-dlp\", [\n\t\t\t\"--print\", \"duration\",\n\t\t\t\"-g\", `https://www.youtube.com/watch?v=${videoId}`,\n\t\t], { timeout: 15000, encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n\t\tconst lines = output.split(/\\r?\\n/);\n\t\tconst rawDuration = lines[0]?.trim();\n\t\tconst streamUrl = lines[1]?.trim();\n\t\tif (!streamUrl) return { error: \"yt-dlp failed: missing stream URL\" };\n\t\tconst parsedDuration = rawDuration && rawDuration !== \"NA\" ? Number.parseFloat(rawDuration) : NaN;\n\t\tconst duration = Number.isFinite(parsedDuration) ? parsedDuration : null;\n\t\treturn { streamUrl, duration };\n\t} catch (err) {\n\t\treturn { error: mapYtDlpError(err) };\n\t}\n}\n\nasync function extractFrameFromStream(streamUrl: string, seconds: number): Promise<FrameResult> {\n\ttry {\n\t\tconst buffer = execFileSync(\"ffmpeg\", [\n\t\t\t\"-ss\", String(seconds), \"-i\", streamUrl,\n\t\t\t\"-frames:v\", \"1\", \"-f\", \"image2pipe\", \"-vcodec\", \"mjpeg\", \"pipe:1\",\n\t\t], { maxBuffer: 5 * 1024 * 1024, timeout: 30000, stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tif (buffer.length === 0) return { error: \"ffmpeg failed: empty output\" };\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch (err) {\n\t\treturn { error: mapFfmpegError(err) };\n\t}\n}\n\nexport async function extractYouTubeFrame(\n\tvideoId: string,\n\tseconds: number,\n\tstreamInfo?: StreamInfo,\n): Promise<FrameResult> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return info;\n\treturn extractFrameFromStream(info.streamUrl, seconds);\n}\n\nexport async function extractYouTubeFrames(\n\tvideoId: string,\n\ttimestamps: number[],\n\tstreamInfo?: StreamInfo,\n): Promise<{ frames: VideoFrame[]; duration: number | null; error: string | null }> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return { frames: [], duration: null, error: info.error };\n\tconst results = await Promise.all(timestamps.map(async (t) => {\n\t\tconst frame = await extractFrameFromStream(info.streamUrl, t);\n\t\tif (\"error\" in frame) return { error: frame.error };\n\t\treturn { ...frame, timestamp: formatSeconds(t) };\n\t}));\n\tconst frames = results.filter((f): f is VideoFrame => \"data\" in f);\n\tconst errorResult = results.find((f): f is { error: string } => \"error\" in f);\n\treturn { frames, duration: info.duration, error: frames.length === 0 && errorResult ? errorResult.error : null };\n}\n\nexport async function fetchYouTubeThumbnail(videoId: string): Promise<{ data: string; mimeType: string } | null> {\n\ttry {\n\t\tconst res = await fetch(`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`, {\n\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t});\n\t\tif (!res.ok) return null;\n\t\tconst buffer = Buffer.from(await res.arrayBuffer());\n\t\tif (buffer.length === 0) return null;\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiWeb(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tconst cookies = await isGeminiWebAvailable();\n\t\tif (!cookies) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryWithCookies(prompt, cookies, {\n\t\t\tyoutubeUrl: url,\n\t\t\t...(model !== \"gemini-3.6-flash\" ? { model } : {}),\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini Web\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiApi(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (!isGeminiApiAvailable()) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryGeminiApiWithVideo(prompt, url, {\n\t\t\tmodel,\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini API\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryPerplexity(\n\turl: string,\n\tprompt: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (signal?.aborted || !isPerplexityAvailable()) return null;\n\n\t\tconst perplexityQuery = prompt === YOUTUBE_PROMPT\n\t\t\t? `Summarize this YouTube video in detail: ${url}`\n\t\t\t: `${prompt} YouTube video: ${url}`;\n\n\t\tconst { answer } = await searchWithPerplexity(\n\t\t\tperplexityQuery,\n\t\t\t{ signal },\n\t\t);\n\n\t\tif (!answer) return null;\n\n\t\tconst content =\n\t\t\t`# Video Summary (via Perplexity)\\n\\n${answer}\\n\\n` +\n\t\t\t`*Full video understanding requires Gemini access. Set GEMINI_API_KEY or sign into Google in Chrome.*`;\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: \"Video Summary (via Perplexity)\",\n\t\t\tcontent,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Perplexity\", err);\n\t\treturn null;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"youtube-extract.js","sourceRoot":"","sources":["../../../../src/humain/web-access/vendor/youtube-extract.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { activityMonitor } from \"./activity.ts\";\nimport { canAttachImages } from \"./feature-config.ts\";\nimport { isGeminiWebAvailable, queryWithCookies } from \"./gemini-web.ts\";\nimport { isGeminiApiAvailable, queryGeminiApiWithVideo } from \"./gemini-api.ts\";\nimport { isPerplexityAvailable, searchWithPerplexity } from \"./perplexity.ts\";\nimport { extractHeadingTitle, type ExtractedContent, type FrameResult, type VideoFrame } from \"./extract.ts\";\nimport { formatSeconds, readExecError, isTimeoutError, trimErrorText, mapFfmpegError, getWebSearchConfigPath } from \"./utils.ts\";\n\nconst CONFIG_PATH = getWebSearchConfigPath();\n\nconst YOUTUBE_PROMPT = `Extract the complete content of this YouTube video. Include:\n1. Video title, channel name, and duration\n2. A brief summary (2-3 sentences)\n3. Full transcript with timestamps\n4. Descriptions of any code, terminal commands, diagrams, slides, or UI shown on screen\n\nFormat as markdown.`;\n\nconst YOUTUBE_REGEX =\n\t/(?:(?:www\\.|m\\.)?youtube\\.com\\/(?:watch\\?.*v=|shorts\\/|live\\/|embed\\/|v\\/)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})/;\n\nfunction errorMessage(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction shouldRethrow(err: unknown): boolean {\n\treturn errorMessage(err).startsWith(\"Failed to parse \");\n}\n\nfunction addAttemptError(errors: string[], label: string, err: unknown): void {\n\tconst message = errorMessage(err).replace(/\\s+/g, \" \").trim();\n\tif (message) errors.push(`${label}: ${message}`);\n}\n\ninterface YouTubeConfig {\n\tenabled: boolean;\n\tpreferredModel: string;\n}\n\nfunction normalizePreferredModel(value: unknown, fallback: string): string {\n\tif (typeof value !== \"string\") return fallback;\n\tconst normalized = value.trim();\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nfunction normalizeEnabled(value: unknown, fallback: boolean): boolean {\n\treturn typeof value === \"boolean\" ? value : fallback;\n}\n\nconst defaults: YouTubeConfig = { enabled: true, preferredModel: \"gemini-3.6-flash\" };\nlet cachedConfig: YouTubeConfig | null = null;\n\nfunction loadYouTubeConfig(): YouTubeConfig {\n\tif (cachedConfig) return cachedConfig;\n\tif (!existsSync(CONFIG_PATH)) {\n\t\tcachedConfig = { ...defaults };\n\t\treturn cachedConfig;\n\t}\n\n\tconst rawText = readFileSync(CONFIG_PATH, \"utf-8\");\n\tlet raw: { youtube?: { enabled?: boolean; preferredModel?: string } };\n\ttry {\n\t\traw = JSON.parse(rawText) as { youtube?: { enabled?: boolean; preferredModel?: string } };\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\tconst yt = raw.youtube ?? {};\n\tcachedConfig = {\n\t\tenabled: normalizeEnabled(yt.enabled, defaults.enabled),\n\t\tpreferredModel: normalizePreferredModel(yt.preferredModel, defaults.preferredModel),\n\t};\n\treturn cachedConfig;\n}\n\nexport function isYouTubeURL(url: string): { isYouTube: boolean; videoId: string | null } {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (parsed.pathname === \"/playlist\") {\n\t\t\treturn { isYouTube: false, videoId: null };\n\t\t}\n\t} catch {\n\t}\n\n\tconst match = url.match(YOUTUBE_REGEX);\n\tif (!match) return { isYouTube: false, videoId: null };\n\treturn { isYouTube: true, videoId: match[1] };\n}\n\nexport function isYouTubeEnabled(): boolean {\n\treturn loadYouTubeConfig().enabled;\n}\n\nexport async function extractYouTube(\n\turl: string,\n\tsignal?: AbortSignal,\n\tprompt?: string,\n\tmodel?: string,\n): Promise<ExtractedContent | null> {\n\tconst config = loadYouTubeConfig();\n\tconst { videoId } = isYouTubeURL(url);\n\tconst canonicalUrl = videoId\n\t\t? `https://www.youtube.com/watch?v=${videoId}`\n\t\t: url;\n\tconst effectivePrompt = prompt ?? YOUTUBE_PROMPT;\n\tconst effectiveModel = model ?? config.preferredModel;\n\n\tconst activityId = activityMonitor.logStart({ type: \"fetch\", url: `youtube.com/${videoId ?? \"video\"}` });\n\tconst attemptErrors: string[] = [];\n\n\tconst result = await tryGeminiWeb(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryGeminiApi(canonicalUrl, effectivePrompt, effectiveModel, signal, attemptErrors)\n\t\t?? await tryPerplexity(url, effectivePrompt, signal, attemptErrors);\n\n\tif (result) {\n\t\tresult.url = url;\n\t\tif (!result.error && videoId && canAttachImages()) {\n\t\t\tconst thumb = await fetchYouTubeThumbnail(videoId);\n\t\t\tif (thumb) result.thumbnail = thumb;\n\t\t}\n\t\tactivityMonitor.logComplete(activityId, result.error ? 0 : 200);\n\t\treturn result;\n\t}\n\n\tif (signal?.aborted) {\n\t\tactivityMonitor.logComplete(activityId, 0);\n\t\treturn null;\n\t}\n\n\tconst error = attemptErrors.length > 0\n\t\t? [\"Could not extract YouTube video content.\", \"\", ...attemptErrors.map(message => `- ${message}`)].join(\"\\n\")\n\t\t: \"Could not extract YouTube video content. Sign into Google in a supported Chromium browser for automatic access, or set GEMINI_API_KEY.\";\n\tactivityMonitor.logError(activityId, error);\n\treturn { url, title: \"\", content: \"\", error };\n}\n\ntype StreamInfo = { streamUrl: string; duration: number | null };\ntype StreamResult = StreamInfo | { error: string };\n\nfunction mapYtDlpError(err: unknown): string {\n\tconst { code, stderr, message } = readExecError(err);\n\tif (code === \"ENOENT\") return \"yt-dlp is not installed. Install with: brew install yt-dlp\";\n\tif (isTimeoutError(err)) return \"yt-dlp timed out fetching video info\";\n\tconst lower = stderr.toLowerCase();\n\tif (lower.includes(\"private\")) return \"Video is private or unavailable\";\n\tif (lower.includes(\"sign in\")) return \"Video is age-restricted and requires authentication\";\n\tif (lower.includes(\"not available\")) return \"Video is unavailable in your region or has been removed\";\n\tif (lower.includes(\"live\")) return \"Cannot extract frames from a live stream\";\n\tconst snippet = trimErrorText(stderr || message);\n\treturn snippet ? `yt-dlp failed: ${snippet}` : \"yt-dlp failed\";\n}\n\nexport async function getYouTubeStreamInfo(videoId: string): Promise<StreamResult> {\n\ttry {\n\t\tconst output = execFileSync(\"yt-dlp\", [\n\t\t\t\"--print\", \"duration\",\n\t\t\t\"-g\", `https://www.youtube.com/watch?v=${videoId}`,\n\t\t], { timeout: 15000, encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim();\n\t\tconst lines = output.split(/\\r?\\n/);\n\t\tconst rawDuration = lines[0]?.trim();\n\t\tconst streamUrl = lines[1]?.trim();\n\t\tif (!streamUrl) return { error: \"yt-dlp failed: missing stream URL\" };\n\t\tconst parsedDuration = rawDuration && rawDuration !== \"NA\" ? Number.parseFloat(rawDuration) : NaN;\n\t\tconst duration = Number.isFinite(parsedDuration) ? parsedDuration : null;\n\t\treturn { streamUrl, duration };\n\t} catch (err) {\n\t\treturn { error: mapYtDlpError(err) };\n\t}\n}\n\nasync function extractFrameFromStream(streamUrl: string, seconds: number): Promise<FrameResult> {\n\ttry {\n\t\tconst buffer = execFileSync(\"ffmpeg\", [\n\t\t\t\"-ss\", String(seconds), \"-i\", streamUrl,\n\t\t\t\"-frames:v\", \"1\", \"-f\", \"image2pipe\", \"-vcodec\", \"mjpeg\", \"pipe:1\",\n\t\t], { maxBuffer: 5 * 1024 * 1024, timeout: 30000, stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tif (buffer.length === 0) return { error: \"ffmpeg failed: empty output\" };\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch (err) {\n\t\treturn { error: mapFfmpegError(err) };\n\t}\n}\n\nexport async function extractYouTubeFrame(\n\tvideoId: string,\n\tseconds: number,\n\tstreamInfo?: StreamInfo,\n): Promise<FrameResult> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return info;\n\treturn extractFrameFromStream(info.streamUrl, seconds);\n}\n\nexport async function extractYouTubeFrames(\n\tvideoId: string,\n\ttimestamps: number[],\n\tstreamInfo?: StreamInfo,\n): Promise<{ frames: VideoFrame[]; duration: number | null; error: string | null }> {\n\tconst info = streamInfo ?? await getYouTubeStreamInfo(videoId);\n\tif (\"error\" in info) return { frames: [], duration: null, error: info.error };\n\tconst results = await Promise.all(timestamps.map(async (t) => {\n\t\tconst frame = await extractFrameFromStream(info.streamUrl, t);\n\t\tif (\"error\" in frame) return { error: frame.error };\n\t\treturn { ...frame, timestamp: formatSeconds(t) };\n\t}));\n\tconst frames = results.filter((f): f is VideoFrame => \"data\" in f);\n\tconst errorResult = results.find((f): f is { error: string } => \"error\" in f);\n\treturn { frames, duration: info.duration, error: frames.length === 0 && errorResult ? errorResult.error : null };\n}\n\nexport async function fetchYouTubeThumbnail(videoId: string): Promise<{ data: string; mimeType: string } | null> {\n\ttry {\n\t\tconst res = await fetch(`https://img.youtube.com/vi/${videoId}/hqdefault.jpg`, {\n\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t});\n\t\tif (!res.ok) return null;\n\t\tconst buffer = Buffer.from(await res.arrayBuffer());\n\t\tif (buffer.length === 0) return null;\n\t\treturn { data: buffer.toString(\"base64\"), mimeType: \"image/jpeg\" };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiWeb(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tconst cookies = await isGeminiWebAvailable();\n\t\tif (!cookies) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryWithCookies(prompt, cookies, {\n\t\t\tyoutubeUrl: url,\n\t\t\t...(model !== \"gemini-3.6-flash\" ? { model } : {}),\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini Web\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryGeminiApi(\n\turl: string,\n\tprompt: string,\n\tmodel: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (!isGeminiApiAvailable()) return null;\n\n\t\tif (signal?.aborted) return null;\n\n\t\tconst text = await queryGeminiApiWithVideo(prompt, url, {\n\t\t\tmodel,\n\t\t\tsignal,\n\t\t\ttimeoutMs: 120000,\n\t\t});\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: extractHeadingTitle(text) ?? \"YouTube Video\",\n\t\t\tcontent: text,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Gemini API\", err);\n\t\treturn null;\n\t}\n}\n\nasync function tryPerplexity(\n\turl: string,\n\tprompt: string,\n\tsignal: AbortSignal | undefined,\n\tattemptErrors: string[],\n): Promise<ExtractedContent | null> {\n\ttry {\n\t\tif (signal?.aborted || !isPerplexityAvailable()) return null;\n\n\t\tconst perplexityQuery = prompt === YOUTUBE_PROMPT\n\t\t\t? `Summarize this YouTube video in detail: ${url}`\n\t\t\t: `${prompt} YouTube video: ${url}`;\n\n\t\tconst { answer } = await searchWithPerplexity(\n\t\t\tperplexityQuery,\n\t\t\t{ signal },\n\t\t);\n\n\t\tif (!answer) return null;\n\n\t\tconst content =\n\t\t\t`# Video Summary (via Perplexity)\\n\\n${answer}\\n\\n` +\n\t\t\t`*Full video understanding requires Gemini access. Set GEMINI_API_KEY or sign into Google in a supported Chromium browser.*`;\n\n\t\treturn {\n\t\t\turl,\n\t\t\ttitle: \"Video Summary (via Perplexity)\",\n\t\t\tcontent,\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tif (shouldRethrow(err)) throw err;\n\t\tif (!signal?.aborted) addAttemptError(attemptErrors, \"Perplexity\", err);\n\t\treturn null;\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AASF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;
|
|
1
|
+
{"version":3,"file":"clipboard-image.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard-image.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AASF,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAE9E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAazE;AAoND,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA2CjC","sourcesContent":["import { spawnSync } from \"child_process\";\nimport { randomUUID } from \"crypto\";\nimport { readFileSync, unlinkSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\n\nimport { clipboard } from \"./clipboard-native.ts\";\nimport { loadPhoton } from \"./photon.ts\";\n\nexport type ClipboardImage = {\n\tbytes: Uint8Array;\n\tmimeType: string;\n};\n\nconst SUPPORTED_IMAGE_MIME_TYPES = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"] as const;\n\nconst DEFAULT_LIST_TIMEOUT_MS = 1000;\nconst DEFAULT_READ_TIMEOUT_MS = 3000;\nconst DEFAULT_POWERSHELL_TIMEOUT_MS = 5000;\nconst DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;\n\nexport function isWaylandSession(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn Boolean(env.WAYLAND_DISPLAY) || env.XDG_SESSION_TYPE === \"wayland\";\n}\n\nfunction baseMimeType(mimeType: string): string {\n\treturn mimeType.split(\";\")[0]?.trim().toLowerCase() ?? mimeType.toLowerCase();\n}\n\nexport function extensionForImageMimeType(mimeType: string): string | null {\n\tswitch (baseMimeType(mimeType)) {\n\t\tcase \"image/png\":\n\t\t\treturn \"png\";\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nfunction selectPreferredImageMimeType(mimeTypes: string[]): string | null {\n\tconst normalized = mimeTypes\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean)\n\t\t.map((t) => ({ raw: t, base: baseMimeType(t) }));\n\n\tfor (const preferred of SUPPORTED_IMAGE_MIME_TYPES) {\n\t\tconst match = normalized.find((t) => t.base === preferred);\n\t\tif (match) {\n\t\t\treturn match.raw;\n\t\t}\n\t}\n\n\tconst anyImage = normalized.find((t) => t.base.startsWith(\"image/\"));\n\treturn anyImage?.raw ?? null;\n}\n\nfunction isSupportedImageMimeType(mimeType: string): boolean {\n\tconst base = baseMimeType(mimeType);\n\treturn SUPPORTED_IMAGE_MIME_TYPES.some((t) => t === base);\n}\n\n/**\n * Convert unsupported image formats to PNG using Photon.\n * Returns null if conversion is unavailable or fails.\n */\nasync function convertToPng(bytes: Uint8Array): Promise<Uint8Array | null> {\n\tconst photon = await loadPhoton();\n\tif (!photon) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst image = photon.PhotonImage.new_from_byteslice(bytes);\n\t\ttry {\n\t\t\treturn image.get_bytes();\n\t\t} finally {\n\t\t\timage.free();\n\t\t}\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction runCommand(\n\tcommand: string,\n\targs: string[],\n\toptions?: { timeoutMs?: number; maxBufferBytes?: number; env?: NodeJS.ProcessEnv },\n): { stdout: Buffer; ok: boolean } {\n\tconst timeoutMs = options?.timeoutMs ?? DEFAULT_READ_TIMEOUT_MS;\n\tconst maxBufferBytes = options?.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;\n\n\tconst result = spawnSync(command, args, {\n\t\ttimeout: timeoutMs,\n\t\tmaxBuffer: maxBufferBytes,\n\t\tenv: options?.env,\n\t});\n\n\tif (result.error) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tif (result.status !== 0) {\n\t\treturn { ok: false, stdout: Buffer.alloc(0) };\n\t}\n\n\tconst stdout = Buffer.isBuffer(result.stdout)\n\t\t? result.stdout\n\t\t: Buffer.from(result.stdout ?? \"\", typeof result.stdout === \"string\" ? \"utf-8\" : undefined);\n\n\treturn { ok: true, stdout };\n}\n\nfunction readClipboardImageViaWlPaste(): ClipboardImage | null {\n\tconst list = runCommand(\"wl-paste\", [\"--list-types\"], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\tif (!list.ok) {\n\t\treturn null;\n\t}\n\n\tconst types = list.stdout\n\t\t.toString(\"utf-8\")\n\t\t.split(/\\r?\\n/)\n\t\t.map((t) => t.trim())\n\t\t.filter(Boolean);\n\n\tconst selectedType = selectPreferredImageMimeType(types);\n\tif (!selectedType) {\n\t\treturn null;\n\t}\n\n\tconst data = runCommand(\"wl-paste\", [\"--type\", selectedType, \"--no-newline\"]);\n\tif (!data.ok || data.stdout.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn { bytes: data.stdout, mimeType: baseMimeType(selectedType) };\n}\n\nfunction isWSL(env: NodeJS.ProcessEnv = process.env): boolean {\n\tif (env.WSL_DISTRO_NAME || env.WSLENV) {\n\t\treturn true;\n\t}\n\tif (env !== process.env) return false;\n\n\ttry {\n\t\tconst release = readFileSync(\"/proc/version\", \"utf-8\");\n\t\treturn /microsoft|wsl/i.test(release);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * On WSL, the Linux clipboard (Wayland/X11) does not receive image data from\n * Windows screenshots (Win+Shift+S). PowerShell can access the Windows clipboard\n * directly, so we use it as a fallback.\n */\nfunction readClipboardImageViaPowerShell(): ClipboardImage | null {\n\tconst tmpFile = join(tmpdir(), `pi-wsl-clip-${randomUUID()}.png`);\n\n\ttry {\n\t\tconst winPathResult = runCommand(\"wslpath\", [\"-w\", tmpFile], { timeoutMs: DEFAULT_LIST_TIMEOUT_MS });\n\t\tif (!winPathResult.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst winPath = winPathResult.stdout.toString(\"utf-8\").trim();\n\t\tif (!winPath) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst psQuotedWinPath = winPath.replaceAll(\"'\", \"''\");\n\t\tconst psScript = [\n\t\t\t\"Add-Type -AssemblyName System.Windows.Forms\",\n\t\t\t\"Add-Type -AssemblyName System.Drawing\",\n\t\t\t`$path = '${psQuotedWinPath}'`,\n\t\t\t\"$img = [System.Windows.Forms.Clipboard]::GetImage()\",\n\t\t\t\"if ($img) { $img.Save($path, [System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'ok' } else { Write-Output 'empty' }\",\n\t\t].join(\"; \");\n\n\t\tconst result = runCommand(\"powershell.exe\", [\"-NoProfile\", \"-Command\", psScript], {\n\t\t\ttimeoutMs: DEFAULT_POWERSHELL_TIMEOUT_MS,\n\t\t});\n\t\tif (!result.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst output = result.stdout.toString(\"utf-8\").trim();\n\t\tif (output !== \"ok\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst bytes = readFileSync(tmpFile);\n\t\tif (bytes.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn { bytes: new Uint8Array(bytes), mimeType: \"image/png\" };\n\t} catch {\n\t\treturn null;\n\t} finally {\n\t\ttry {\n\t\t\tunlinkSync(tmpFile);\n\t\t} catch {\n\t\t\t// Ignore cleanup errors.\n\t\t}\n\t}\n}\n\nfunction readClipboardImageViaXclip(): ClipboardImage | null {\n\tconst targets = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", \"TARGETS\", \"-o\"], {\n\t\ttimeoutMs: DEFAULT_LIST_TIMEOUT_MS,\n\t});\n\n\tlet candidateTypes: string[] = [];\n\tif (targets.ok) {\n\t\tcandidateTypes = targets.stdout\n\t\t\t.toString(\"utf-8\")\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((t) => t.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\tconst preferred = candidateTypes.length > 0 ? selectPreferredImageMimeType(candidateTypes) : null;\n\tconst tryTypes = preferred ? [preferred, ...SUPPORTED_IMAGE_MIME_TYPES] : [...SUPPORTED_IMAGE_MIME_TYPES];\n\n\tfor (const mimeType of tryTypes) {\n\t\tconst data = runCommand(\"xclip\", [\"-selection\", \"clipboard\", \"-t\", mimeType, \"-o\"]);\n\t\tif (data.ok && data.stdout.length > 0) {\n\t\t\treturn { bytes: data.stdout, mimeType: baseMimeType(mimeType) };\n\t\t}\n\t}\n\n\treturn null;\n}\n\nasync function readClipboardImageViaNativeClipboard(): Promise<ClipboardImage | null> {\n\tif (!clipboard || !clipboard.hasImage()) {\n\t\treturn null;\n\t}\n\n\tconst imageData = await clipboard.getImageBinary();\n\tif (!imageData || imageData.length === 0) {\n\t\treturn null;\n\t}\n\n\tconst bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);\n\treturn { bytes, mimeType: \"image/png\" };\n}\n\nexport async function readClipboardImage(options?: {\n\tenv?: NodeJS.ProcessEnv;\n\tplatform?: NodeJS.Platform;\n}): Promise<ClipboardImage | null> {\n\tconst env = options?.env ?? process.env;\n\tconst platform = options?.platform ?? process.platform;\n\n\tif (env.TERMUX_VERSION) {\n\t\treturn null;\n\t}\n\n\tlet image: ClipboardImage | null = null;\n\n\tif (platform === \"linux\") {\n\t\tconst wsl = isWSL(env);\n\t\tconst wayland = isWaylandSession(env);\n\n\t\tif (wayland || wsl) {\n\t\t\timage = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();\n\t\t}\n\n\t\tif (!image && wsl) {\n\t\t\timage = readClipboardImageViaPowerShell();\n\t\t}\n\n\t\tif (!image && !wayland) {\n\t\t\timage = (await readClipboardImageViaNativeClipboard()) ?? readClipboardImageViaXclip();\n\t\t}\n\t} else {\n\t\timage = await readClipboardImageViaNativeClipboard();\n\t}\n\n\tif (!image) {\n\t\treturn null;\n\t}\n\n\t// Convert unsupported formats (e.g., BMP from WSLg) to PNG\n\tif (!isSupportedImageMimeType(image.mimeType)) {\n\t\tconst pngBytes = await convertToPng(image.bytes);\n\t\tif (!pngBytes) {\n\t\t\treturn null;\n\t\t}\n\t\treturn { bytes: pngBytes, mimeType: \"image/png\" };\n\t}\n\n\treturn image;\n}\n"]}
|
|
@@ -113,6 +113,8 @@ function isWSL(env = process.env) {
|
|
|
113
113
|
if (env.WSL_DISTRO_NAME || env.WSLENV) {
|
|
114
114
|
return true;
|
|
115
115
|
}
|
|
116
|
+
if (env !== process.env)
|
|
117
|
+
return false;
|
|
116
118
|
try {
|
|
117
119
|
const release = readFileSync("/proc/version", "utf-8");
|
|
118
120
|
return /microsoft|wsl/i.test(release);
|