LLM-powered interactive code editor with backup/rollback.
| 175 | |
| 176 | |
| 177 | class InteractiveCodeEditor: |
| 178 | """LLM-powered interactive code editor with backup/rollback.""" |
| 179 | |
| 180 | def __init__( |
| 181 | self, |
| 182 | code_dir: Path, |
| 183 | config: ResearchConfig, |
| 184 | log_fn: Any = None, |
| 185 | ) -> None: |
| 186 | self.code_dir = code_dir.resolve() |
| 187 | self.snapshot_mgr = CodeSnapshotManager(self.code_dir) |
| 188 | self.config = config |
| 189 | self.dispatcher = ModelDispatcher(config) |
| 190 | self._log = log_fn or (lambda msg: None) |
| 191 | self._history: list[dict[str, str]] = [] |
| 192 | |
| 193 | async def preview_instruction(self, instruction: str) -> dict[str, Any]: |
| 194 | """Dry-run: ask LLM for edits but do NOT apply them. |
| 195 | |
| 196 | Returns dict with keys: edits, errors (same format as apply_instruction |
| 197 | but with the raw edit plan so the user can review before applying). |
| 198 | """ |
| 199 | files = read_code_context(self.code_dir) |
| 200 | if not files: |
| 201 | return {"edits": [], "errors": ["No code files found"]} |
| 202 | |
| 203 | code_ctx = _format_code_context(files) |
| 204 | user_prompt = ( |
| 205 | f"## Current Code\n\n{code_ctx}\n\n" |
| 206 | f"## User Instruction\n\n{instruction}" |
| 207 | ) |
| 208 | stage_cfg = self.config.for_stage("code_gen") |
| 209 | try: |
| 210 | raw = await self.dispatcher.generate( |
| 211 | stage_cfg, _SYSTEM_PROMPT, user_prompt, json_mode=True, |
| 212 | ) |
| 213 | except Exception as exc: |
| 214 | return {"edits": [], "errors": [f"LLM call failed: {exc}"]} |
| 215 | |
| 216 | edits, errors = self._parse_edits(raw) |
| 217 | return {"edits": edits, "errors": errors} |
| 218 | |
| 219 | async def apply_instruction(self, instruction: str) -> dict[str, Any]: |
| 220 | """Apply a natural-language instruction to the code. |
| 221 | |
| 222 | 1. Auto-snapshot before changes |
| 223 | 2. Read all code files |
| 224 | 3. Ask LLM for edits |
| 225 | 4. Apply edits |
| 226 | 5. Return summary |
| 227 | |
| 228 | Returns dict with keys: snapshot_id, edits_applied, errors, files_changed. |
| 229 | """ |
| 230 | # Step 1: backup |
| 231 | snap_id = self.snapshot_mgr.create_snapshot(label="before_edit") |
| 232 | self._log(f"Backup created: {snap_id}") |
| 233 | |
| 234 | # Step 2: read code |