import { v4 as uuidv4 } from 'uuid' import type { StudioToolDefinition, StudioToolResult } from '../domain/types' import type { StudioRuntimeBackedToolContext } from '../runtime/tool-runtime-context' import type { CustomApiConfig, OutputMode, VideoQuality } from '../../types' import { videoQueue } from '../../config/bull' import { storeJobStage } from '../../services/job-store' import { createWorkAndTask } from '../works/work-lifecycle' import { resolveJobTimeoutMs } from '../../utils/job-timeout' interface RenderToolInput { concept: string code: string outputMode?: OutputMode quality?: VideoQuality customApiConfig?: CustomApiConfig } export function createStudioRenderTool(): StudioToolDefinition { return { name: 'render', description: 'Create a Manim render task backed by the existing queue.', category: 'render', permission: 'render', allowedAgents: ['builder'], allowedStudioKinds: ['manim'], requiresTask: true, execute: async (input, context) => executeRenderTool(input, context as StudioRuntimeBackedToolContext) } } async function executeRenderTool( input: RenderToolInput, context: StudioRuntimeBackedToolContext ): Promise { if (!input.concept?.trim() || !input.code?.trim()) { throw new Error('Render tool requires non-empty "concept" and "code"') } const jobId = uuidv4() const outputMode = input.outputMode ?? 'video' const quality = input.quality ?? 'medium' await storeJobStage(jobId, 'rendering') await videoQueue.add( { jobId, concept: input.concept, outputMode, quality, preGeneratedCode: input.code, customApiConfig: input.customApiConfig, timestamp: new Date().toISOString(), workspaceDirectory: context.session.directory }, { jobId, timeout: resolveJobTimeoutMs() } ) const lifecycleMetadata = { concept: input.concept, outputMode, quality, jobId } const title = `Render: ${input.concept.slice(0, 80)}` const { work, task } = await createWorkAndTask({ context, work: { sessionId: context.session.id, runId: context.run.id, type: 'video', title, status: 'queued', metadata: lifecycleMetadata }, task: { sessionId: context.session.id, runId: context.run.id, type: 'render', status: 'queued', title, detail: input.concept, metadata: { jobId, outputMode, quality } }, workMetadata: lifecycleMetadata }) return { title: `Render queued ${jobId}`, output: `render_job_id: ${jobId}`, metadata: { jobId, taskId: task?.id, workId: work?.id, outputMode, quality } } }