File size: 3,458 Bytes
b1df986 | 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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | import { createRetryContext, executeCodeRetry } from '../../../../workflow/code-retry/manager'
import type { ConceptInput, OutputMode, PromptOverrides, ProblemFramingPlan } from '../../../../types'
export interface RenderAttemptResult {
success: boolean
stderr: string
stdout: string
peakMemoryMB: number
exitCode?: number
codeSnippet?: string
}
interface RenderFailureLogArgs {
attempt: number
code: string
codeSnippet?: string
stderr: string
stdout: string
peakMemoryMB: number
exitCode?: number
promptRole: 'codeRetry' | 'single-render'
}
interface ExecuteRenderWithRetryArgs extends ConceptInput {
problemPlan?: ProblemFramingPlan
outputMode: OutputMode
sceneDesign?: string
promptOverrides?: PromptOverrides
customApiConfig?: unknown
initialCode: string
usedAI: boolean
timings?: Record<string, number>
renderCode: (code: string) => Promise<RenderAttemptResult>
logRenderFailure: (args: RenderFailureLogArgs) => Promise<void>
ensureJobNotCancelled?: () => Promise<void>
onRetryStart?: () => Promise<void> | void
}
export async function executeRenderWithRetry(args: ExecuteRenderWithRetryArgs): Promise<{
finalCode: string
renderPeakMemoryMB: number
}> {
const {
concept,
outputMode,
sceneDesign,
promptOverrides,
customApiConfig,
initialCode,
usedAI,
timings,
renderCode,
logRenderFailure,
ensureJobNotCancelled,
onRetryStart
} = args
if (usedAI) {
const retryContext = createRetryContext(
concept,
sceneDesign?.trim() || `概念: ${concept}`,
promptOverrides,
outputMode,
args.problemPlan,
args.referenceImages
)
const retryManagerResult = await executeCodeRetry(
retryContext,
renderCode,
customApiConfig,
initialCode,
async (event) => {
await logRenderFailure({ ...event, promptRole: 'codeRetry' })
},
ensureJobNotCancelled,
timings,
onRetryStart
)
if (typeof retryManagerResult.generationTimeMs === 'number' && timings) {
timings.retry = retryManagerResult.generationTimeMs
}
if (!retryManagerResult.success) {
// A cancelled render is not a code failure. Re-raise the job's cancellation with its
// original JobCancelledError instead of the retry manager's wrapped diagnostic.
if (retryManagerResult.failureCategory === 'cancelled') {
await ensureJobNotCancelled?.()
}
const failurePrefix = retryManagerResult.retrySkipped
? `AI retry skipped for ${retryManagerResult.failureCategory || 'operational'} failure`
: `Code retry failed after ${retryManagerResult.attempts} attempts`
throw new Error(
`${failurePrefix}: ${retryManagerResult.lastError}`
)
}
return {
finalCode: retryManagerResult.code,
renderPeakMemoryMB: 0
}
}
const singleAttempt = await renderCode(initialCode)
if (!singleAttempt.success) {
await logRenderFailure({
attempt: 1,
code: initialCode,
codeSnippet: singleAttempt.codeSnippet,
stderr: singleAttempt.stderr,
stdout: singleAttempt.stdout,
peakMemoryMB: singleAttempt.peakMemoryMB,
exitCode: singleAttempt.exitCode,
promptRole: 'single-render'
})
throw new Error(singleAttempt.stderr || 'Manim render failed')
}
return {
finalCode: initialCode,
renderPeakMemoryMB: singleAttempt.peakMemoryMB
}
}
|