import OpenAI from 'openai' import { createLogger } from '../../utils/logger' import { cleanManimCode } from '../../utils/manim-code-cleaner' import { getClient } from './client' import type { CodeRetryContext } from './types' import { buildRetryPrompt, getCodeRetrySystemPrompt } from './prompt-builder' import { dedupeSharedBlocksInMessages } from '../prompt-dedup' import { createChatCompletionText } from '../openai-stream' import { buildTokenParams } from '../../utils/reasoning-model' import { applyPatchSetToCode, extractTargetLine, parsePatchResponse } from './utils' const logger = createLogger('CodeRetryCodeGen') const AI_TEMPERATURE = parseFloat(process.env.AI_TEMPERATURE || '0.7') const MAX_TOKENS = parseInt(process.env.AI_MAX_TOKENS || '12000', 10) const THINKING_TOKENS = parseInt(process.env.AI_THINKING_TOKENS || '20000', 10) function getModel(customApiConfig?: unknown): string { const model = (customApiConfig as { model?: string } | undefined)?.model const trimmed = model?.trim() || '' if (!trimmed) { throw new Error('No model available') } return trimmed } export async function retryCodeGeneration( context: CodeRetryContext, errorMessage: string, attempt: number, currentCode: string, codeSnippet: string | undefined, customApiConfig?: unknown ): Promise { const client = getClient(customApiConfig as any) if (!client) { throw new Error('No upstream AI is configured for this request') } const retryPrompt = buildRetryPrompt(context, errorMessage, attempt, currentCode, codeSnippet) try { const requestMessages = dedupeSharedBlocksInMessages( [ { role: 'system', content: getCodeRetrySystemPrompt(context.promptOverrides) }, { role: 'user', content: retryPrompt } ], context.promptOverrides ) const { content, mode } = await createChatCompletionText( client, { model: getModel(customApiConfig), messages: requestMessages, temperature: AI_TEMPERATURE, ...buildTokenParams(THINKING_TOKENS, MAX_TOKENS) }, { fallbackToNonStream: true, usageLabel: `retry-${attempt}` } ) if (!content) { throw new Error('AI returned empty content') } logger.info('Code retry model response received', { concept: context.concept, attempt, mode, contentLength: content.length, contentPreview: content.trim().slice(0, 500) }) const patchSet = parsePatchResponse(content) const patchedCode = applyPatchSetToCode(currentCode, patchSet, extractTargetLine(errorMessage)) const cleaned = cleanManimCode(patchedCode) logger.info('Code retry patch applied', { concept: context.concept, attempt, mode, patchCount: patchSet.patches.length, codeLength: cleaned.code.length, patchLengths: patchSet.patches.map((patch) => ({ originalSnippetLength: patch.originalSnippet.length, replacementSnippetLength: patch.replacementSnippet.length })), codePreview: cleaned.code.slice(0, 500) }) return cleaned.code } catch (error) { if (error instanceof OpenAI.APIError) { logger.error('OpenAI API error during code retry', { attempt, status: error.status, message: error.message }) } throw error } }