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, customApiConfig?: any, initialCode?: string, onRenderFailure?: (event: { attempt: number code: string codeSnippet?: string stderr: string stdout: string peakMemoryMB: number exitCode?: number }) => Promise | void, onCheckpoint?: RetryCheckpoint ): Promise { 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'