|
| 1 | +import {LlmGenerateFilesContext, LlmGenerateFilesRequestOptions, LlmRunner} from './llm-runner.js'; |
| 2 | +import {join} from 'path'; |
| 3 | +import {mkdirSync} from 'fs'; |
| 4 | +import {writeFile} from 'fs/promises'; |
| 5 | +import {BaseCliAgentRunner} from './base-cli-agent-runner.js'; |
| 6 | + |
| 7 | +const MODEL_MAPPING: Record<string, string> = { |
| 8 | + 'claude-4.0-sonnet': 'claude-sonnet-4-20250514', |
| 9 | + 'claude-3.5-haiku': 'claude-3-5-haiku-latest', |
| 10 | +}; |
| 11 | + |
| 12 | +/** Runner that generates code using the Claude Code. */ |
| 13 | +export class ClaudeCodeRunner extends BaseCliAgentRunner implements LlmRunner { |
| 14 | + readonly id = 'claude-code'; |
| 15 | + readonly displayName = 'Claude Code'; |
| 16 | + readonly hasBuiltInRepairLoop = true; |
| 17 | + protected ignoredFilePatterns = ['**/CLAUDE.md', '**/.claude/**']; |
| 18 | + protected binaryName = 'claude'; |
| 19 | + protected override inactivityTimeoutMins = 10; |
| 20 | + protected override totalRequestTimeoutMins = 10; |
| 21 | + |
| 22 | + getSupportedModels(): string[] { |
| 23 | + return Object.keys(MODEL_MAPPING); |
| 24 | + } |
| 25 | + |
| 26 | + protected getCommandLineFlags(options: LlmGenerateFilesRequestOptions): string[] { |
| 27 | + return [ |
| 28 | + '--print', |
| 29 | + '--model', |
| 30 | + MODEL_MAPPING[options.model], |
| 31 | + // Skip all confirmations. |
| 32 | + '--dangerously-skip-permissions', |
| 33 | + '--permission-mode', |
| 34 | + 'bypassPermissions', |
| 35 | + '--verbose', |
| 36 | + options.context.executablePrompt, |
| 37 | + ]; |
| 38 | + } |
| 39 | + |
| 40 | + protected async writeAgentFiles(options: LlmGenerateFilesRequestOptions): Promise<void> { |
| 41 | + const {context} = options; |
| 42 | + const instructionFilePath = join(context.directory, 'CLAUDE.md'); |
| 43 | + const settingsDir = join(context.directory, '.claude'); |
| 44 | + |
| 45 | + mkdirSync(settingsDir); |
| 46 | + |
| 47 | + await Promise.all([ |
| 48 | + writeFile(join(settingsDir, 'settings.json'), this.getSettingsJsonFile(options.context)), |
| 49 | + writeFile(instructionFilePath, super.getCommonInstructions(options)), |
| 50 | + ]); |
| 51 | + } |
| 52 | + |
| 53 | + private getSettingsJsonFile(context: LlmGenerateFilesContext): string { |
| 54 | + const ignoredPatterns = super.getCommonIgnorePatterns(); |
| 55 | + const deniedPermissions: string[] = [ |
| 56 | + // Block some commands like `git` and `npm install` since they aren't relevant for the evals. |
| 57 | + 'Bash(git:*)', |
| 58 | + ...ignoredPatterns.directories.map(dir => `"Read(${join(dir, '**')})"`), |
| 59 | + ...ignoredPatterns.files.map(file => `"Read(${file})"`), |
| 60 | + ...context.possiblePackageManagers |
| 61 | + .filter(manager => manager !== context.packageManager) |
| 62 | + .map(manager => `Bash(${manager}:*)`), |
| 63 | + |
| 64 | + // Note that we don't block all commands, |
| 65 | + // because the build commands also go through it. |
| 66 | + `Bash(${context.packageManager} install:*)`, |
| 67 | + `Bash(${context.packageManager} add:*)`, |
| 68 | + `Bash(${context.packageManager} remove:*)`, |
| 69 | + `Bash(${context.packageManager} update:*)`, |
| 70 | + `Bash(${context.packageManager} list:*)`, |
| 71 | + ]; |
| 72 | + |
| 73 | + return JSON.stringify( |
| 74 | + { |
| 75 | + permissions: { |
| 76 | + deny: deniedPermissions, |
| 77 | + }, |
| 78 | + env: { |
| 79 | + DISABLE_AUTOUPDATER: 1, |
| 80 | + DISABLE_TELEMETRY: 1, |
| 81 | + DISABLE_ERROR_REPORTING: 1, |
| 82 | + }, |
| 83 | + }, |
| 84 | + undefined, |
| 85 | + 2, |
| 86 | + ); |
| 87 | + } |
| 88 | +} |
0 commit comments