diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fbbeaf..66cd4bdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.12.1 - UNRELEASED +### Fixes + +- YouTube: keep Gemini-only no-caption runs on the transcription path by forwarding the Google API key from the top-level URL flow into link-preview transcription config (#148, thanks @bytrangle). + ## 0.12.0 - 2026-03-11 ### Features diff --git a/src/run/flows/url/flow.ts b/src/run/flows/url/flow.ts index 5c431fa8..5695896d 100644 --- a/src/run/flows/url/flow.ts +++ b/src/run/flows/url/flow.ts @@ -146,6 +146,7 @@ export async function runUrlFlow({ groqApiKey: model.apiStatus.groqApiKey, assemblyaiApiKey: model.apiStatus.assemblyaiApiKey, openaiApiKey: model.apiStatus.openaiTranscriptionKey, + geminiApiKey: model.apiStatus.googleApiKey, }, scrapeWithFirecrawl, convertHtmlToMarkdown: markdown.convertHtmlToMarkdown, diff --git a/tests/run.url-flow.gemini-transcription-forwarding.test.ts b/tests/run.url-flow.gemini-transcription-forwarding.test.ts new file mode 100644 index 00000000..85928745 --- /dev/null +++ b/tests/run.url-flow.gemini-transcription-forwarding.test.ts @@ -0,0 +1,111 @@ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import type { CacheState } from "../src/cache.js"; +import type { ExtractedLinkContent } from "../src/content/index.js"; +import type { LinkPreviewClientOptions } from "../src/content/index.js"; +import { createDaemonUrlFlowContext } from "../src/daemon/flow-context.js"; + +const mocks = vi.hoisted(() => { + const fetchLinkContent = vi.fn<(url: string) => Promise>(); + const createLinkPreviewClient = vi.fn((options?: LinkPreviewClientOptions) => ({ + fetchLinkContent: async (url: string) => fetchLinkContent(url), + options, + })); + return { fetchLinkContent, createLinkPreviewClient }; +}); + +vi.mock("../src/content/index.js", () => ({ + createLinkPreviewClient: mocks.createLinkPreviewClient, +})); + +import { runUrlFlow } from "../src/run/flows/url/flow.js"; + +afterEach(() => { + vi.clearAllMocks(); +}); + +describe("runUrlFlow transcription wiring", () => { + it("forwards googleApiKey into link preview transcription config", async () => { + const root = mkdtempSync(join(tmpdir(), "summarize-gemini-url-flow-")); + const url = "https://www.youtube.com/watch?v=hhAbp3iQA44"; + const cache: CacheState = { + mode: "bypass", + store: null, + ttlMs: 0, + maxBytes: 0, + path: null, + }; + + mocks.fetchLinkContent.mockResolvedValueOnce({ + url, + title: "Video", + description: null, + siteName: "YouTube", + content: "Transcript text", + truncated: false, + totalCharacters: 15, + wordCount: 2, + transcriptCharacters: 15, + transcriptLines: 1, + transcriptWordCount: 2, + transcriptSource: "yt-dlp", + transcriptionProvider: "gemini-2.5-flash", + transcriptMetadata: null, + transcriptSegments: null, + transcriptTimedText: null, + mediaDurationSeconds: 120, + video: { kind: "youtube", url }, + isVideoOnly: false, + diagnostics: { + strategy: "html", + firecrawl: { + attempted: false, + used: false, + cacheMode: "bypass", + cacheStatus: "bypassed", + notes: null, + }, + markdown: { + requested: false, + used: false, + provider: null, + notes: null, + }, + transcript: { + cacheMode: "bypass", + cacheStatus: "unknown", + textProvided: true, + provider: "yt-dlp", + attemptedProviders: ["yt-dlp"], + notes: null, + }, + }, + }); + + const ctx = createDaemonUrlFlowContext({ + env: { HOME: root, OPENAI_API_KEY: "test" }, + fetchImpl: vi.fn() as unknown as typeof fetch, + cache, + modelOverride: "google/gemini-3-flash", + promptOverride: null, + lengthRaw: "short", + languageRaw: "auto", + maxExtractCharacters: null, + extractOnly: true, + runStartedAtMs: Date.now(), + stdoutSink: { writeChunk: () => {} }, + }); + + ctx.model.apiStatus.googleApiKey = "gemini-key"; + ctx.model.apiStatus.googleConfigured = true; + + await runUrlFlow({ ctx, url, isYoutubeUrl: true }); + + expect(mocks.createLinkPreviewClient).toHaveBeenCalledTimes(1); + const options = mocks.createLinkPreviewClient.mock.calls[0]?.[0]; + expect(options?.transcription?.geminiApiKey).toBe("gemini-key"); + expect(options?.transcription?.openaiApiKey).toBe("test"); + }); +});