diff options
Diffstat (limited to 'packages/tool-youtube-transcript/src/tool.ts')
| -rw-r--r-- | packages/tool-youtube-transcript/src/tool.ts | 168 |
1 files changed, 84 insertions, 84 deletions
diff --git a/packages/tool-youtube-transcript/src/tool.ts b/packages/tool-youtube-transcript/src/tool.ts index a11f739..b24a6c5 100644 --- a/packages/tool-youtube-transcript/src/tool.ts +++ b/packages/tool-youtube-transcript/src/tool.ts @@ -11,11 +11,11 @@ import { mkdirSync, writeFileSync } from "node:fs"; import type { ToolContract, ToolExecuteContext, ToolResult } from "@dispatch/kernel"; import type { TranscriptClient } from "./client.js"; import { - formatCompleted, - formatFailed, - formatQueued, - type TranscriptResponse, - truncateOutput, + formatCompleted, + formatFailed, + formatQueued, + type TranscriptResponse, + truncateOutput, } from "./format.js"; import { validateUrl } from "./validate.js"; @@ -23,21 +23,21 @@ const OUTPUT_CAP = 50_000; const FULL_OUTPUT_DIR = "/tmp/dispatch/youtube-transcribe"; export interface YoutubeTranscriptToolDeps { - readonly client: TranscriptClient; - readonly outputCap?: number; - /** Injected file writer (defaults to real fs write). */ - readonly writeFile?: (path: string, content: string) => void; + readonly client: TranscriptClient; + readonly outputCap?: number; + /** Injected file writer (defaults to real fs write). */ + readonly writeFile?: (path: string, content: string) => void; } const DESCRIPTION = - "Fetch the transcript/subtitles for a YouTube video from the local transcriber " + - "service. If the transcript has not been downloaded before, the video will be " + - "queued for processing and the tool will return the estimated time when the " + - "transcript will be available. Once available, the tool returns the transcript " + - "text and timestamped segments (truncated if very long). The full transcript " + - "is always saved to /tmp/dispatch/youtube-transcribe/{video_id}.txt — use " + - "read_file to access it. Accepted URL formats: " + - "youtube.com/watch?v=, youtu.be/, youtube.com/embed/, youtube.com/shorts/"; + "Fetch the transcript/subtitles for a YouTube video from the local transcriber " + + "service. If the transcript has not been downloaded before, the video will be " + + "queued for processing and the tool will return the estimated time when the " + + "transcript will be available. Once available, the tool returns the transcript " + + "text and timestamped segments (truncated if very long). The full transcript " + + "is always saved to /tmp/dispatch/youtube-transcribe/{video_id}.txt — use " + + "read_file to access it. Accepted URL formats: " + + "youtube.com/watch?v=, youtu.be/, youtube.com/embed/, youtube.com/shorts/"; /** * Create the `youtube_transcript` tool. `concurrencySafe: true` — transcript @@ -45,73 +45,73 @@ const DESCRIPTION = * capability is declared on the extension manifest (not the tool contract). */ export function createYoutubeTranscriptTool(deps: YoutubeTranscriptToolDeps): ToolContract { - const client = deps.client; - const cap = deps.outputCap ?? OUTPUT_CAP; - const writeFile = - deps.writeFile ?? - ((path, content) => { - mkdirSync(FULL_OUTPUT_DIR, { recursive: true }); - writeFileSync(path, content, "utf-8"); - }); + const client = deps.client; + const cap = deps.outputCap ?? OUTPUT_CAP; + const writeFile = + deps.writeFile ?? + ((path, content) => { + mkdirSync(FULL_OUTPUT_DIR, { recursive: true }); + writeFileSync(path, content, "utf-8"); + }); - return { - name: "youtube_transcript", - description: DESCRIPTION, - parameters: { - type: "object", - properties: { - url: { - type: "string", - description: - "YouTube video URL (e.g. https://www.youtube.com/watch?v=... or https://youtu.be/...)", - }, - }, - required: ["url"], - }, - concurrencySafe: true, - async execute(args: unknown, ctx: ToolExecuteContext): Promise<ToolResult> { - const validated = validateUrl(args); - if (typeof validated !== "string") { - return { content: validated.error, isError: true }; - } - const url = validated; - const span = ctx.log.span("youtube_transcript.execute", { url }); - try { - const data: TranscriptResponse = await client.getTranscript(url, ctx.signal); - let output: string; - let videoId: string | undefined; - // Check the single-literal discriminants ("completed"/"failed") first, - // so the final else narrows to QueuedResponse — whose `status` is itself - // a `"queued" | "processing"` union TS cannot negatively narrow. - if (data.status === "completed") { - output = formatCompleted(url, data); - videoId = data.video_id; - } else if (data.status === "failed") { - output = formatFailed(data); - } else { - output = formatQueued(url, data, Date.now); - } - span.end(); + return { + name: "youtube_transcript", + description: DESCRIPTION, + parameters: { + type: "object", + properties: { + url: { + type: "string", + description: + "YouTube video URL (e.g. https://www.youtube.com/watch?v=... or https://youtu.be/...)", + }, + }, + required: ["url"], + }, + concurrencySafe: true, + async execute(args: unknown, ctx: ToolExecuteContext): Promise<ToolResult> { + const validated = validateUrl(args); + if (typeof validated !== "string") { + return { content: validated.error, isError: true }; + } + const url = validated; + const span = ctx.log.span("youtube_transcript.execute", { url }); + try { + const data: TranscriptResponse = await client.getTranscript(url, ctx.signal); + let output: string; + let videoId: string | undefined; + // Check the single-literal discriminants ("completed"/"failed") first, + // so the final else narrows to QueuedResponse — whose `status` is itself + // a `"queued" | "processing"` union TS cannot negatively narrow. + if (data.status === "completed") { + output = formatCompleted(url, data); + videoId = data.video_id; + } else if (data.status === "failed") { + output = formatFailed(data); + } else { + output = formatQueued(url, data, Date.now); + } + span.end(); - if (videoId !== undefined) { - const filePath = `${FULL_OUTPUT_DIR}/${videoId}.txt`; - try { - writeFile(filePath, output); - } catch { - // File write failed — continue with truncated output only. - } - if (output.length > cap) { - return { content: truncateOutput(output, cap, filePath) }; - } - } - return { content: truncateOutput(output, cap) }; - } catch (err: unknown) { - span.end({ err }); - return { - content: `Error: ${err instanceof Error ? err.message : String(err)}`, - isError: true, - }; - } - }, - }; + if (videoId !== undefined) { + const filePath = `${FULL_OUTPUT_DIR}/${videoId}.txt`; + try { + writeFile(filePath, output); + } catch { + // File write failed — continue with truncated output only. + } + if (output.length > cap) { + return { content: truncateOutput(output, cap, filePath) }; + } + } + return { content: truncateOutput(output, cap) }; + } catch (err: unknown) { + span.end({ err }); + return { + content: `Error: ${err instanceof Error ? err.message : String(err)}`, + isError: true, + }; + } + }, + }; } |
