ManimCat / src /services /code-retry /manager.ts
Bin29's picture
Sync from main: b9996d0 feat: refine studio workspace and plot canvas ui
14ea677
import { createLogger } from '../../utils/logger'
import type {
CodeRetryOptions,
CodeRetryResult,
RenderResult,
RetryManagerResult,
ChatMessage,
CodeRetryContext,
RetryCheckpoint
} from './types'
import type { OutputMode, PromptOverrides } from '../../types'
import { extractErrorMessage, getErrorType } from './utils'
import { retryCodeGeneration } from './code-generation'
import { JobCancelledError } from '../../utils/errors'
const logger = createLogger('CodeRetryManager')
const MAX_RETRIES = parseInt(process.env.CODE_RETRY_MAX_RETRIES || '4', 10)
export function createRetryContext(
concept: string,
sceneDesign: string,
promptOverrides?: PromptOverrides,
outputMode: OutputMode = 'video'
): CodeRetryContext {
return {
concept,
sceneDesign,
outputMode,
promptOverrides
}
}
export { buildRetryFixPrompt } from './prompt-builder'
export async function executeCodeRetry(
context: CodeRetryContext,
renderer: (code: string) => Promise<RenderResult>,
customApiConfig?: any,
initialCode?: string,
onRenderFailure?: (event: {
attempt: number
code: string
codeSnippet?: string
stderr: string
stdout: string
peakMemoryMB: number
exitCode?: number
}) => Promise<void> | void,
onCheckpoint?: RetryCheckpoint
): Promise<RetryManagerResult> {
logger.info('Starting code retry manager', {
concept: context.concept,
maxRetries: MAX_RETRIES
})
let generationTimeMs = 0
let currentCode = initialCode?.trim() || ''
if (!currentCode) {
throw new Error('Code retry requires existing code; full regeneration mode is disabled')
}
if (onCheckpoint) {
await onCheckpoint()
}
let renderResult = await renderer(currentCode)
let currentCodeSnippet = renderResult.codeSnippet || currentCode
if (renderResult.success) {
logger.info('Initial render succeeded')
return { code: currentCode, success: true, attempts: 1, generationTimeMs }
}
if (onRenderFailure) {
try {
await onRenderFailure({
attempt: 1,
code: currentCode,
codeSnippet: renderResult.codeSnippet || currentCode,
stderr: renderResult.stderr,
stdout: renderResult.stdout,
peakMemoryMB: renderResult.peakMemoryMB,
exitCode: renderResult.exitCode
})
} catch (error) {
logger.warn('onRenderFailure callback failed', { attempt: 1, error: String(error) })
}
}
let errorMessage = extractErrorMessage(renderResult.stderr)
let errorType = getErrorType(renderResult.stderr)
logger.warn('Initial render failed', { errorType, error: errorMessage })
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
logger.info('Starting retry patch attempt', {
totalAttempts: attempt + 1,
errorType,
error: errorMessage
})
try {
if (onCheckpoint) {
await onCheckpoint()
}
const generationStart = Date.now()
currentCode = await retryCodeGeneration(
context,
errorMessage,
attempt,
currentCode,
currentCodeSnippet,
customApiConfig
)
generationTimeMs += Date.now() - generationStart
if (onCheckpoint) {
await onCheckpoint()
}
renderResult = await renderer(currentCode)
currentCodeSnippet = renderResult.codeSnippet || currentCode
if (renderResult.success) {
logger.info('Retry render succeeded', { attempt: attempt + 1 })
return { code: currentCode, success: true, attempts: attempt + 1, generationTimeMs }
}
if (onRenderFailure) {
try {
await onRenderFailure({
attempt: attempt + 1,
code: currentCode,
codeSnippet: renderResult.codeSnippet || currentCode,
stderr: renderResult.stderr,
stdout: renderResult.stdout,
peakMemoryMB: renderResult.peakMemoryMB,
exitCode: renderResult.exitCode
})
} catch (error) {
logger.warn('onRenderFailure callback failed', { attempt: attempt + 1, error: String(error) })
}
}
errorMessage = extractErrorMessage(renderResult.stderr)
errorType = getErrorType(renderResult.stderr)
logger.warn('Retry render failed', { attempt: attempt + 1, errorType, error: errorMessage })
} catch (error) {
if (error instanceof JobCancelledError) {
logger.warn('Code retry aborted because job was cancelled', {
attempt: attempt + 1,
reason: error.details
})
throw error
}
logger.error('Retry patch process failed', { attempt: attempt + 1, error: String(error) })
}
}
logger.error('All retry patch attempts failed', {
totalAttempts: MAX_RETRIES + 1,
finalError: extractErrorMessage(renderResult.stderr)
})
return {
code: currentCode,
success: false,
attempts: MAX_RETRIES + 1,
generationTimeMs,
lastError: extractErrorMessage(renderResult.stderr)
}
}
export type {
CodeRetryOptions,
CodeRetryResult,
RenderResult,
RetryManagerResult,
ChatMessage,
CodeRetryContext
} from './types'