File size: 2,893 Bytes
d47b053
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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<RenderToolInput> {
  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<StudioToolResult> {
  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
    }
  }
}