Skip to content

Commit 53c3144

Browse files
committed
feat: add Claude Code runner
Adds a runner that generates code using Claude Code.
1 parent ad917ac commit 53c3144

File tree

6 files changed

+224
-2
lines changed

6 files changed

+224
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ You can customize the `web-codegen-scorer eval` script with the following flags:
8383
- Example: `web-codegen-scorer eval --model=gemini-2.5-flash --autorater-model=gemini-2.5-flash --env=<config path>`
8484

8585
- `--runner=<name>`: Specifies the runner to use to execute the eval. Supported runners are
86-
`genkit` (default) or `gemini-cli`.
86+
`genkit` (default), `gemini-cli` or `claude-code`.
8787

8888
- `--local`: Runs the script in local mode for the initial code generation request. Instead of
8989
calling the LLM, it will attempt to read the initial code from a corresponding file in the

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"wcs": "./runner/bin/cli.js"
5252
},
5353
"dependencies": {
54+
"@anthropic-ai/claude-code": "^1.0.128",
5455
"@anthropic-ai/sdk": "^0.63.0",
5556
"@axe-core/puppeteer": "^4.10.2",
5657
"@genkit-ai/compat-oai": "^1.19.1",

pnpm-lock.yaml

Lines changed: 127 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
}

runner/codegen/runner-creation.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {UserFacingError} from '../utils/errors.js';
22
import type {GeminiCliRunner} from './gemini-cli-runner.js';
3+
import type {ClaudeCodeRunner} from './claude-code-runner.js';
34
import type {GenkitRunner} from './genkit/genkit-runner.js';
45

56
interface AvailableRunners {
67
genkit: GenkitRunner;
78
'gemini-cli': GeminiCliRunner;
9+
'claude-code': ClaudeCodeRunner;
810
}
911

1012
/** Names of supported runners. */
@@ -25,6 +27,10 @@ export async function getRunnerByName<T extends RunnerName>(name: T): Promise<Av
2527
return import('./gemini-cli-runner.js').then(
2628
m => new m.GeminiCliRunner() as AvailableRunners[T],
2729
);
30+
case 'claude-code':
31+
return import('./claude-code-runner.js').then(
32+
m => new m.ClaudeCodeRunner() as AvailableRunners[T],
33+
);
2834
default:
2935
throw new UserFacingError(`Unsupported runner ${name}`);
3036
}

runner/eval-cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function builder(argv: Argv): Argv<Options> {
5757
.option('runner', {
5858
type: 'string',
5959
default: 'genkit' as const,
60-
choices: ['genkit', 'gemini-cli'] as RunnerName[],
60+
choices: ['genkit', 'gemini-cli', 'claude-code'] as RunnerName[],
6161
description: 'Runner to use to execute the eval',
6262
})
6363
.option('local', {

0 commit comments

Comments
 (0)