openoperator / lib /morph-fast-apply.ts
Leon4gr45's picture
Deploy to clean space
75fefa7 verified
// Using direct fetch to Morph's OpenAI-compatible API to avoid SDK type issues
export interface MorphEditBlock {
targetFile: string;
instructions: string;
update: string;
}
export interface MorphApplyResult {
success: boolean;
normalizedPath?: string;
mergedCode?: string;
error?: string;
}
// Normalize project-relative paths to sandbox layout
export function normalizeProjectPath(inputPath: string): { normalizedPath: string; fullPath: string } {
let normalizedPath = inputPath.trim();
if (normalizedPath.startsWith('/')) normalizedPath = normalizedPath.slice(1);
const configFiles = new Set([
'tailwind.config.js',
'vite.config.js',
'package.json',
'package-lock.json',
'tsconfig.json',
'postcss.config.js'
]);
const fileName = normalizedPath.split('/').pop() || '';
if (!normalizedPath.startsWith('src/') &&
!normalizedPath.startsWith('public/') &&
normalizedPath !== 'index.html' &&
!configFiles.has(fileName)) {
normalizedPath = 'src/' + normalizedPath;
}
const fullPath = `/home/user/app/${normalizedPath}`;
return { normalizedPath, fullPath };
}
async function morphChatCompletionsCreate(payload: any) {
if (!process.env.MORPH_API_KEY) throw new Error('MORPH_API_KEY is not set');
const res = await fetch('https://api.morphllm.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.MORPH_API_KEY}`
},
body: JSON.stringify(payload)
});
if (!res.ok) {
const text = await res.text();
throw new Error(`Morph API error ${res.status}: ${text}`);
}
return res.json();
}
// Parse <edit> blocks from LLM output
export function parseMorphEdits(text: string): MorphEditBlock[] {
const edits: MorphEditBlock[] = [];
const editRegex = /<edit\s+target_file="([^"]+)">([\s\S]*?)<\/edit>/g;
let match: RegExpExecArray | null;
while ((match = editRegex.exec(text)) !== null) {
const targetFile = match[1].trim();
const inner = match[2];
const instrMatch = inner.match(/<instructions>([\s\S]*?)<\/instructions>/);
const updateMatch = inner.match(/<update>([\s\S]*?)<\/update>/);
const instructions = instrMatch ? instrMatch[1].trim() : '';
const update = updateMatch ? updateMatch[1].trim() : '';
if (targetFile && update) {
edits.push({ targetFile, instructions, update });
}
}
return edits;
}
// Read a file from sandbox: prefers cache, then sandbox.files, then commands.run("cat ...")
async function readFileFromSandbox(sandbox: any, normalizedPath: string, fullPath: string): Promise<string> {
// Try backend cache first
if ((global as any).sandboxState?.fileCache?.files?.[normalizedPath]?.content) {
return (global as any).sandboxState.fileCache.files[normalizedPath].content as string;
}
// Try E2B files API
if (sandbox?.files?.read) {
return await sandbox.files.read(fullPath);
}
// Try provider runCommand (preferred for provider pattern)
if (typeof sandbox?.runCommand === 'function') {
try {
const res = await sandbox.runCommand(`cat ${normalizedPath}`);
if (res && typeof res.stdout === 'string') {
return res.stdout as string;
}
} catch {}
// fallback to absolute path
try {
const resAbs = await sandbox.runCommand(`cat ${fullPath}`);
if (resAbs && typeof resAbs.stdout === 'string') {
return resAbs.stdout as string;
}
} catch {}
}
// Try shell cat via commands.run
if (sandbox?.commands?.run) {
const result = await sandbox.commands.run(`cat ${fullPath}`, { cwd: '/home/user/app', timeout: 30 });
if (result?.exitCode === 0 && typeof result?.stdout === 'string') {
return result.stdout as string;
}
}
throw new Error(`Unable to read file: ${normalizedPath}`);
}
// Write a file to sandbox and update cache
async function writeFileToSandbox(sandbox: any, normalizedPath: string, fullPath: string, content: string): Promise<void> {
// Provider pattern (writeFile)
if (typeof sandbox?.writeFile === 'function') {
await sandbox.writeFile(normalizedPath, content);
return;
}
// Provider pattern (runCommand redirect)
if (typeof sandbox?.runCommand === 'function') {
// Ensure directory exists
const dir = normalizedPath.includes('/') ? normalizedPath.substring(0, normalizedPath.lastIndexOf('/')) : '';
if (dir) {
try { await sandbox.runCommand(`mkdir -p ${dir}`); } catch {}
}
// Write via heredoc with proper escaping
const heredoc = `bash -lc 'cat > ${normalizedPath} <<\"EOF\"\n${content.replace(/\\/g, '\\\\').replace(/\n/g, '\n').replace(/\$/g, '\$')}\nEOF'`;
const result = await sandbox.runCommand(heredoc);
if (result?.stdout || result?.stderr) {
// no-op
}
return;
}
// Prefer E2B files API
if (sandbox?.files?.write) {
await sandbox.files.write(fullPath, content);
} else if (sandbox?.runCode) {
// Use Python to write safely
const escaped = content
.replace(/\\/g, '\\\\')
.replace(/"""/g, '\"\"\"');
await sandbox.runCode(`
import os
os.makedirs(os.path.dirname("${fullPath}"), exist_ok=True)
with open("${fullPath}", 'w') as f:
f.write("""${escaped}""")
print("WROTE:${fullPath}")
`);
} else if (sandbox?.commands?.run) {
// Shell redirection (fallback)
// Note: beware of special chars; this is a last-resort path
const result = await sandbox.commands.run(`bash -lc 'mkdir -p "$(dirname "${fullPath}")" && cat > "${fullPath}" << \EOF\n${content}\nEOF'`, { cwd: '/home/user/app', timeout: 60 });
if (result?.exitCode !== 0) {
throw new Error(`Failed to write file via shell: ${normalizedPath}`);
}
} else {
throw new Error('No available method to write files to sandbox');
}
// Update backend cache if available
if ((global as any).sandboxState?.fileCache) {
(global as any).sandboxState.fileCache.files[normalizedPath] = {
content,
lastModified: Date.now()
};
}
if ((global as any).existingFiles) {
(global as any).existingFiles.add(normalizedPath);
}
}
export async function applyMorphEditToFile(params: {
sandbox: any;
targetPath: string;
instructions: string;
updateSnippet: string;
}): Promise<MorphApplyResult> {
try {
if (!process.env.MORPH_API_KEY) {
return { success: false, error: 'MORPH_API_KEY not set' };
}
const { normalizedPath, fullPath } = normalizeProjectPath(params.targetPath);
// Read original code (existence validation happens here)
const initialCode = await readFileFromSandbox(params.sandbox, normalizedPath, fullPath);
const resp = await morphChatCompletionsCreate({
model: 'morph-v3-large',
messages: [
{
role: 'user',
content: `<instruction>${params.instructions || ''}</instruction>\n<code>${initialCode}</code>\n<update>${params.updateSnippet}</update>`
}
]
});
const mergedCode = (resp as any)?.choices?.[0]?.message?.content || '';
if (!mergedCode) {
return { success: false, error: 'Morph returned empty content', normalizedPath };
}
await writeFileToSandbox(params.sandbox, normalizedPath, fullPath, mergedCode);
return { success: true, normalizedPath, mergedCode };
} catch (error) {
return { success: false, error: (error as Error).message };
}
}