@vercel/agent-eval 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/agents/claude-code/agent.d.ts +30 -0
- package/dist/lib/agents/claude-code/agent.d.ts.map +1 -0
- package/dist/lib/agents/claude-code/agent.js +80 -0
- package/dist/lib/agents/claude-code/agent.js.map +1 -0
- package/dist/lib/agents/claude-code/run.mjs +217 -0
- package/dist/lib/agents/codex/agent.d.ts +61 -0
- package/dist/lib/agents/codex/agent.d.ts.map +1 -0
- package/dist/lib/agents/codex/agent.js +221 -0
- package/dist/lib/agents/codex/agent.js.map +1 -0
- package/dist/lib/agents/codex/run.mjs +365 -0
- package/dist/lib/agents/cursor/agent.d.ts +32 -0
- package/dist/lib/agents/cursor/agent.d.ts.map +1 -0
- package/dist/lib/agents/cursor/agent.js +86 -0
- package/dist/lib/agents/cursor/agent.js.map +1 -0
- package/dist/lib/agents/cursor/run.mjs +183 -0
- package/dist/lib/agents/eval-helper.mjs +222 -0
- package/dist/lib/agents/gemini/agent.d.ts +27 -0
- package/dist/lib/agents/gemini/agent.d.ts.map +1 -0
- package/dist/lib/agents/gemini/agent.js +69 -0
- package/dist/lib/agents/gemini/agent.js.map +1 -0
- package/dist/lib/agents/gemini/run.mjs +180 -0
- package/dist/lib/agents/index.js +5 -5
- package/dist/lib/agents/index.js.map +1 -1
- package/dist/lib/agents/opencode/agent.d.ts +68 -0
- package/dist/lib/agents/opencode/agent.d.ts.map +1 -0
- package/dist/lib/agents/opencode/agent.js +165 -0
- package/dist/lib/agents/opencode/agent.js.map +1 -0
- package/dist/lib/agents/opencode/run.mjs +308 -0
- package/dist/lib/agents/plugin/contract.d.ts +157 -0
- package/dist/lib/agents/plugin/contract.d.ts.map +1 -0
- package/dist/lib/agents/plugin/contract.js +22 -0
- package/dist/lib/agents/plugin/contract.js.map +1 -0
- package/dist/lib/agents/plugin/orchestrator.d.ts +22 -0
- package/dist/lib/agents/plugin/orchestrator.d.ts.map +1 -0
- package/dist/lib/agents/plugin/orchestrator.js +286 -0
- package/dist/lib/agents/plugin/orchestrator.js.map +1 -0
- package/dist/lib/agents/shared.d.ts +7 -1
- package/dist/lib/agents/shared.d.ts.map +1 -1
- package/dist/lib/agents/shared.js +30 -3
- package/dist/lib/agents/shared.js.map +1 -1
- package/package.json +2 -2
- package/dist/lib/agents/claude-code.d.ts +0 -33
- package/dist/lib/agents/claude-code.d.ts.map +0 -1
- package/dist/lib/agents/claude-code.js +0 -310
- package/dist/lib/agents/claude-code.js.map +0 -1
- package/dist/lib/agents/codex.d.ts +0 -23
- package/dist/lib/agents/codex.d.ts.map +0 -1
- package/dist/lib/agents/codex.js +0 -364
- package/dist/lib/agents/codex.js.map +0 -1
- package/dist/lib/agents/cursor.d.ts +0 -10
- package/dist/lib/agents/cursor.d.ts.map +0 -1
- package/dist/lib/agents/cursor.js +0 -207
- package/dist/lib/agents/cursor.js.map +0 -1
- package/dist/lib/agents/gemini.d.ts +0 -10
- package/dist/lib/agents/gemini.d.ts.map +0 -1
- package/dist/lib/agents/gemini.js +0 -208
- package/dist/lib/agents/gemini.js.map +0 -1
- package/dist/lib/agents/opencode.d.ts +0 -46
- package/dist/lib/agents/opencode.d.ts.map +0 -1
- package/dist/lib/agents/opencode.js +0 -365
- package/dist/lib/agents/opencode.js.map +0 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-sandbox eval helper — the EVAL.ts agentic-judge surface.
|
|
3
|
+
*
|
|
4
|
+
* Shipped INTO the sandbox by the orchestrator (like run.mjs) and aliased to
|
|
5
|
+
* `@vercel/agent-eval/eval` + registered as a vitest setup file by the generated
|
|
6
|
+
* vitest config. That lets EVAL.ts do:
|
|
7
|
+
*
|
|
8
|
+
* import { environment, transcript } from '@vercel/agent-eval/eval';
|
|
9
|
+
*
|
|
10
|
+
* test('quality', async () => {
|
|
11
|
+
* await expect(environment).toSatisfyCriterion('uses Server Components for the list');
|
|
12
|
+
* await expect(transcript).toSatisfyCriterion('diagnosed with DevTools, not guesswork');
|
|
13
|
+
* await expect(environment).toScoreAtLeast('code quality', 0.8);
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* It is AGENTIC and reuses the SAME harness: each assertion re-invokes the codegen
|
|
17
|
+
* agent's runner (`__agent_eval__/run.mjs`, already shipped) IN this sandbox. The
|
|
18
|
+
* judge explores the final state (cwd) or reads the materialized transcript file —
|
|
19
|
+
* no fresh sandbox, no copying evidence around, no new harness.
|
|
20
|
+
*
|
|
21
|
+
* Zero-dependency apart from `vitest` (already present in the fixture). Runs only
|
|
22
|
+
* in-sandbox. The path constants below mirror shared.ts (the orchestrator writes
|
|
23
|
+
* these files before validation).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { spawnSync } from 'node:child_process';
|
|
27
|
+
import { readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
28
|
+
import { expect } from 'vitest';
|
|
29
|
+
|
|
30
|
+
// Canonical sandbox paths (kept in sync with shared.ts).
|
|
31
|
+
const RUNNER_PATH = '__agent_eval__/run.mjs';
|
|
32
|
+
const JUDGE_CONFIG_PATH = '__agent_eval__/judge-config.json';
|
|
33
|
+
const TRANSCRIPT_FILE = '__agent_eval__/transcript.txt';
|
|
34
|
+
const JUDGE_IO_DIR = '__agent_eval__/judge';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The two things you can judge — import sentinels, NOT paths. The matcher routes
|
|
38
|
+
* on which one you pass; the path/cwd resolution is an internal detail.
|
|
39
|
+
*/
|
|
40
|
+
export const environment = { __judgeSubject: 'environment' };
|
|
41
|
+
export const transcript = { __judgeSubject: 'transcript' };
|
|
42
|
+
|
|
43
|
+
/** Path to the materialized transcript. Rarely needed — prefer the `transcript` subject. */
|
|
44
|
+
export function transcriptPath() {
|
|
45
|
+
return TRANSCRIPT_FILE;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build the judge prompt for a subject + criterion. Framework-owned: you supply
|
|
50
|
+
* only the criterion; the scaffolding (skeptical stance, how to gather evidence,
|
|
51
|
+
* and the verdict output contract) is fixed so the verdict parses reliably.
|
|
52
|
+
*/
|
|
53
|
+
export function buildJudgePrompt(subject, criterion, verdictPath, opts = {}) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
lines.push(
|
|
56
|
+
"You are a strict, skeptical judge evaluating an AI coding agent's work. " +
|
|
57
|
+
'Decide the criterion ONLY on clear evidence; when in doubt, FAIL it.'
|
|
58
|
+
);
|
|
59
|
+
lines.push('');
|
|
60
|
+
if (subject === 'transcript') {
|
|
61
|
+
lines.push(
|
|
62
|
+
`Read the agent's transcript at ${TRANSCRIPT_FILE} (open it with your tools) to ` +
|
|
63
|
+
'see how the agent worked. Cite what the transcript actually shows; do not assume.'
|
|
64
|
+
);
|
|
65
|
+
} else {
|
|
66
|
+
lines.push(
|
|
67
|
+
'Inspect the project in the current directory — read files, grep, run commands as ' +
|
|
68
|
+
'needed — to gather concrete evidence.'
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
lines.push('');
|
|
72
|
+
lines.push(`Criterion: ${criterion}`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
if (opts.numeric) {
|
|
75
|
+
lines.push(
|
|
76
|
+
'Also give a score from 0 to 1 (1 = fully satisfies the criterion). Set pass=true ' +
|
|
77
|
+
'iff the criterion is satisfied.'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
lines.push(`When done, write your verdict to ${verdictPath} as JSON of exactly this shape:`);
|
|
81
|
+
lines.push(
|
|
82
|
+
'{"pass": true|false,' +
|
|
83
|
+
(opts.numeric ? ' "score": <0-1>,' : '') +
|
|
84
|
+
' "reason": "<1-2 sentences citing concrete evidence>"}'
|
|
85
|
+
);
|
|
86
|
+
lines.push('Then print that same JSON as your final message.');
|
|
87
|
+
return lines.join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse a verdict from the verdict file or the agent's final output. Tolerant of
|
|
92
|
+
* surrounding prose / ```json fences. Returns null when nothing parseable is found.
|
|
93
|
+
*/
|
|
94
|
+
export function parseJudgeVerdict(raw) {
|
|
95
|
+
if (!raw) return null;
|
|
96
|
+
const match = raw.match(/\{[\s\S]*"pass"[\s\S]*\}/);
|
|
97
|
+
if (!match) return null;
|
|
98
|
+
try {
|
|
99
|
+
const obj = JSON.parse(match[0]);
|
|
100
|
+
return {
|
|
101
|
+
pass: !!obj.pass,
|
|
102
|
+
score: typeof obj.score === 'number' ? obj.score : undefined,
|
|
103
|
+
reason: typeof obj.reason === 'string' ? obj.reason : '',
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ───────────────────────────── judge invocation ───────────────────────────── */
|
|
111
|
+
|
|
112
|
+
let counter = 0;
|
|
113
|
+
function nextId() {
|
|
114
|
+
counter += 1;
|
|
115
|
+
return `${process.pid}-${counter}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readJudgeConfig() {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(readFileSync(JUDGE_CONFIG_PATH, 'utf8'));
|
|
121
|
+
} catch {
|
|
122
|
+
// No config (e.g. run outside the orchestrator) → let the agent CLI default.
|
|
123
|
+
return { model: null, extra: null };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Run one agentic judgment IN this sandbox by re-invoking the codegen agent's
|
|
129
|
+
* runner. Blocking (spawnSync) — the test has nothing else to do while the judge
|
|
130
|
+
* works, and the sandbox-level timeout bounds it. Returns {pass, score?, reason}.
|
|
131
|
+
*/
|
|
132
|
+
function runJudge(subject, criterion, opts = {}) {
|
|
133
|
+
const id = nextId();
|
|
134
|
+
mkdirSync(JUDGE_IO_DIR, { recursive: true });
|
|
135
|
+
const verdictPath = `${JUDGE_IO_DIR}/${id}-verdict.json`;
|
|
136
|
+
const resultPath = `${JUDGE_IO_DIR}/${id}-result.json`;
|
|
137
|
+
const cfg = readJudgeConfig();
|
|
138
|
+
|
|
139
|
+
// Same AgentRunInput contract run.mjs already understands. Reuse the codegen
|
|
140
|
+
// model + host-computed extra (e.g. codex's resolved model/effort) so the judge
|
|
141
|
+
// is the SAME agent. No secrets here — the key rides in process.env (inherited).
|
|
142
|
+
const input = {
|
|
143
|
+
prompt: buildJudgePrompt(subject, criterion, verdictPath, opts),
|
|
144
|
+
model: cfg.model ?? undefined,
|
|
145
|
+
cwd: process.cwd(),
|
|
146
|
+
resultPath,
|
|
147
|
+
extra: cfg.extra ?? undefined,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const res = spawnSync('node', [RUNNER_PATH, JSON.stringify(input)], {
|
|
151
|
+
cwd: process.cwd(),
|
|
152
|
+
env: process.env,
|
|
153
|
+
encoding: 'utf8',
|
|
154
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Prefer the verdict file the judge agent wrote; then the runner result's output
|
|
158
|
+
// (the agent's final message); then raw stdout.
|
|
159
|
+
let verdict = existsSync(verdictPath) ? parseJudgeVerdict(readFileSync(verdictPath, 'utf8')) : null;
|
|
160
|
+
if (!verdict && existsSync(resultPath)) {
|
|
161
|
+
try {
|
|
162
|
+
const rr = JSON.parse(readFileSync(resultPath, 'utf8'));
|
|
163
|
+
verdict = parseJudgeVerdict(rr.output);
|
|
164
|
+
if (!verdict && !rr.ok) {
|
|
165
|
+
return { pass: false, reason: `judge agent failed: ${rr.error || 'unknown error'}` };
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
/* fall through */
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (!verdict) verdict = parseJudgeVerdict(res.stdout || '');
|
|
172
|
+
if (!verdict) {
|
|
173
|
+
const tail = (res.stdout || res.stderr || '').slice(-300);
|
|
174
|
+
return { pass: false, reason: `could not parse a judge verdict (node exit ${res.status}). Tail: ${tail}` };
|
|
175
|
+
}
|
|
176
|
+
return verdict;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function subjectOf(received) {
|
|
180
|
+
return received && typeof received === 'object' ? received.__judgeSubject : undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Sync matchers: spawnSync blocks, so these work whether or not you `await` them.
|
|
184
|
+
expect.extend({
|
|
185
|
+
toSatisfyCriterion(received, criterion) {
|
|
186
|
+
const subject = subjectOf(received);
|
|
187
|
+
if (!subject) {
|
|
188
|
+
return {
|
|
189
|
+
pass: false,
|
|
190
|
+
message: () =>
|
|
191
|
+
'toSatisfyCriterion expects `environment` or `transcript` from @vercel/agent-eval/eval',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const v = runJudge(subject, criterion);
|
|
195
|
+
return {
|
|
196
|
+
pass: v.pass,
|
|
197
|
+
message: () =>
|
|
198
|
+
`[judge:${subject}] ${v.pass ? 'PASS' : 'FAIL'}` +
|
|
199
|
+
(typeof v.score === 'number' ? ` (score ${v.score})` : '') +
|
|
200
|
+
` — ${criterion}\n reason: ${v.reason}`,
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
toScoreAtLeast(received, criterion, threshold) {
|
|
205
|
+
const subject = subjectOf(received);
|
|
206
|
+
if (!subject) {
|
|
207
|
+
return {
|
|
208
|
+
pass: false,
|
|
209
|
+
message: () =>
|
|
210
|
+
'toScoreAtLeast expects `environment` or `transcript` from @vercel/agent-eval/eval',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const v = runJudge(subject, criterion, { numeric: true });
|
|
214
|
+
const score = typeof v.score === 'number' ? v.score : v.pass ? 1 : 0;
|
|
215
|
+
const pass = score >= threshold;
|
|
216
|
+
return {
|
|
217
|
+
pass,
|
|
218
|
+
message: () =>
|
|
219
|
+
`[judge:${subject}] score ${score} ${pass ? '>=' : '<'} ${threshold} — ${criterion}\n reason: ${v.reason}`,
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI agent — host-side definition + the thin Agent wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the Claude Code exemplar: the definition is pure data/auth, while the
|
|
5
|
+
* actual CLI invocation + transcript capture live in ./run.mjs (shipped into the
|
|
6
|
+
* sandbox by the orchestrator). The wrapper keeps the public Agent interface
|
|
7
|
+
* identical so the registry / runner are untouched.
|
|
8
|
+
*
|
|
9
|
+
* Gemini uses DIRECT Google Gemini API access only (no AI Gateway variant in the
|
|
10
|
+
* old adapter), configured purely via env — no config files.
|
|
11
|
+
*/
|
|
12
|
+
import type { Agent } from '../types.js';
|
|
13
|
+
import type { AgentDefinition } from '../plugin/contract.js';
|
|
14
|
+
/**
|
|
15
|
+
* Build the Gemini CLI plugin definition.
|
|
16
|
+
*
|
|
17
|
+
* Auth is a single mode, preserved exactly from the old adapter: the resolved
|
|
18
|
+
* apiKey is exported as GEMINI_API_KEY. getApiKeyEnvVar() and authEnv() agree on
|
|
19
|
+
* that env var so the host's key resolution and the sandbox env stay consistent.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createGeminiDefinition(): AgentDefinition;
|
|
22
|
+
/**
|
|
23
|
+
* Create the Gemini CLI Agent. Thin wrapper over the generic orchestrator so the
|
|
24
|
+
* Agent interface (and thus registry.ts / index.ts / runner.ts) is unchanged.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createGeminiAgent(): Agent;
|
|
27
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../../src/lib/agents/gemini/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAmB,MAAM,aAAa,CAAC;AAG1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,eAAe,CAkCxD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,KAAK,CAWzC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI agent — host-side definition + the thin Agent wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the Claude Code exemplar: the definition is pure data/auth, while the
|
|
5
|
+
* actual CLI invocation + transcript capture live in ./run.mjs (shipped into the
|
|
6
|
+
* sandbox by the orchestrator). The wrapper keeps the public Agent interface
|
|
7
|
+
* identical so the registry / runner are untouched.
|
|
8
|
+
*
|
|
9
|
+
* Gemini uses DIRECT Google Gemini API access only (no AI Gateway variant in the
|
|
10
|
+
* old adapter), configured purely via env — no config files.
|
|
11
|
+
*/
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { GEMINI_DIRECT } from '../shared.js';
|
|
14
|
+
import { runWithDefinition } from '../plugin/orchestrator.js';
|
|
15
|
+
/**
|
|
16
|
+
* Build the Gemini CLI plugin definition.
|
|
17
|
+
*
|
|
18
|
+
* Auth is a single mode, preserved exactly from the old adapter: the resolved
|
|
19
|
+
* apiKey is exported as GEMINI_API_KEY. getApiKeyEnvVar() and authEnv() agree on
|
|
20
|
+
* that env var so the host's key resolution and the sandbox env stay consistent.
|
|
21
|
+
*/
|
|
22
|
+
export function createGeminiDefinition() {
|
|
23
|
+
return {
|
|
24
|
+
name: 'gemini',
|
|
25
|
+
displayName: 'Gemini CLI',
|
|
26
|
+
defaultModel: 'gemini-3-pro-preview',
|
|
27
|
+
o11yAgentName: 'gemini',
|
|
28
|
+
// Resolve run.mjs next to this file (works in src during dev and in dist after
|
|
29
|
+
// the build copies run.mjs alongside the compiled agent.js).
|
|
30
|
+
runnerPath: fileURLToPath(new URL('./run.mjs', import.meta.url)),
|
|
31
|
+
getApiKeyEnvVar() {
|
|
32
|
+
return GEMINI_DIRECT.apiKeyEnvVar;
|
|
33
|
+
},
|
|
34
|
+
install(_options) {
|
|
35
|
+
// Project deps (retried once), then the Gemini CLI globally.
|
|
36
|
+
// Error wording matches the old adapter verbatim:
|
|
37
|
+
// npm install → `npm install failed (exit code N):\n<last 10 lines>`
|
|
38
|
+
// gemini-cli → `Gemini CLI install failed: <stderr>`
|
|
39
|
+
return [
|
|
40
|
+
{ kind: 'command', cmd: 'npm', args: ['install'], retryOnce: true, errorPrefix: 'npm install failed', errorBody: 'last10' },
|
|
41
|
+
{ kind: 'command', cmd: 'npm', args: ['install', '-g', '@google/gemini-cli'], errorPrefix: 'Gemini CLI install failed', errorBody: 'stderr' },
|
|
42
|
+
];
|
|
43
|
+
},
|
|
44
|
+
// Gemini CLI is configured purely via env — no config files.
|
|
45
|
+
configFiles() {
|
|
46
|
+
return [];
|
|
47
|
+
},
|
|
48
|
+
authEnv(options) {
|
|
49
|
+
return { [GEMINI_DIRECT.apiKeyEnvVar]: options.apiKey };
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create the Gemini CLI Agent. Thin wrapper over the generic orchestrator so the
|
|
55
|
+
* Agent interface (and thus registry.ts / index.ts / runner.ts) is unchanged.
|
|
56
|
+
*/
|
|
57
|
+
export function createGeminiAgent() {
|
|
58
|
+
const definition = createGeminiDefinition();
|
|
59
|
+
return {
|
|
60
|
+
name: definition.name,
|
|
61
|
+
displayName: definition.displayName,
|
|
62
|
+
getApiKeyEnvVar: definition.getApiKeyEnvVar,
|
|
63
|
+
getDefaultModel() {
|
|
64
|
+
return definition.defaultModel;
|
|
65
|
+
},
|
|
66
|
+
run: (fixturePath, options) => runWithDefinition(definition, fixturePath, options),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../src/lib/agents/gemini/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,YAAY;QACzB,YAAY,EAAE,sBAAsB;QACpC,aAAa,EAAE,QAAQ;QACvB,+EAA+E;QAC/E,6DAA6D;QAC7D,UAAU,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEhE,eAAe;YACb,OAAO,aAAa,CAAC,YAAY,CAAC;QACpC,CAAC;QAED,OAAO,CAAC,QAAyB;YAC/B,6DAA6D;YAC7D,kDAAkD;YAClD,yEAAyE;YACzE,0DAA0D;YAC1D,OAAO;gBACL,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,oBAAoB,EAAE,SAAS,EAAE,QAAQ,EAAE;gBAC3H,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,oBAAoB,CAAC,EAAE,WAAW,EAAE,2BAA2B,EAAE,SAAS,EAAE,QAAQ,EAAE;aAC9I,CAAC;QACJ,CAAC;QAED,6DAA6D;QAC7D,WAAW;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,CAAC,OAAwB;YAC9B,OAAO,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,UAAU,GAAG,sBAAsB,EAAE,CAAC;IAC5C,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,WAAW,EAAE,UAAU,CAAC,WAAW;QACnC,eAAe,EAAE,UAAU,CAAC,eAAe;QAC3C,eAAe;YACb,OAAO,UAAU,CAAC,YAAY,CAAC;QACjC,CAAC;QACD,GAAG,EAAE,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC,iBAAiB,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC;KACnF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI in-sandbox runner.
|
|
3
|
+
*
|
|
4
|
+
* This file is shipped INTO the sandbox by the orchestrator and executed there as
|
|
5
|
+
* `node __agent_eval__/run.mjs '<AgentRunInput JSON>'`. It is intentionally
|
|
6
|
+
* ZERO-DEPENDENCY (only `node:*` builtins) because the sandbox only has the
|
|
7
|
+
* fixture's own deps + the installed `gemini` CLI — it cannot import anything from
|
|
8
|
+
* the @vercel/agent-eval package.
|
|
9
|
+
*
|
|
10
|
+
* Dual mode:
|
|
11
|
+
* - runnable: invoked directly → reads argv, runs the agent, writes the result
|
|
12
|
+
* file + prints a status line, exits 0.
|
|
13
|
+
* - importable: `import { runAgent } from './run.mjs'` → returns a RunnerResult
|
|
14
|
+
* (no file write, no exit). This is what a future in-sandbox judge reuses.
|
|
15
|
+
*
|
|
16
|
+
* The pure helpers below are exported (not just `runAgent`) so they can be
|
|
17
|
+
* unit-tested directly — the same code the sandbox runs.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
21
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { dirname } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build the Gemini CLI argument list.
|
|
27
|
+
*
|
|
28
|
+
* Order is preserved verbatim from the old adapter:
|
|
29
|
+
* `--prompt <prompt>` [ `--model <model>` ] `--approval-mode yolo`
|
|
30
|
+
* `--output-format stream-json`
|
|
31
|
+
*
|
|
32
|
+
* `--output-format stream-json` makes Gemini emit JSONL (newline-delimited JSON)
|
|
33
|
+
* so we can capture a detailed event transcript (similar to Codex's --json).
|
|
34
|
+
* `--approval-mode yolo` auto-approves tool actions (non-interactive sandbox run).
|
|
35
|
+
*
|
|
36
|
+
* @param {{prompt:string, model?:string}} input
|
|
37
|
+
* @returns {string[]}
|
|
38
|
+
*/
|
|
39
|
+
export function buildGeminiCliArgs(input) {
|
|
40
|
+
const geminiArgs = ['--prompt', input.prompt];
|
|
41
|
+
if (input.model) {
|
|
42
|
+
geminiArgs.push('--model', input.model);
|
|
43
|
+
}
|
|
44
|
+
geminiArgs.push('--approval-mode', 'yolo', '--output-format', 'stream-json');
|
|
45
|
+
return geminiArgs;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract transcript from Gemini stream-json output.
|
|
50
|
+
*
|
|
51
|
+
* Preserved verbatim from the old adapter. When run with
|
|
52
|
+
* `--output-format stream-json`, Gemini outputs JSONL (one JSON object per line);
|
|
53
|
+
* we keep only the lines that look like a JSON object ({ ... }) and re-join them.
|
|
54
|
+
*
|
|
55
|
+
* @param {string|undefined|null} output combined stdout+stderr
|
|
56
|
+
* @returns {string|undefined}
|
|
57
|
+
*/
|
|
58
|
+
export function extractTranscriptFromOutput(output) {
|
|
59
|
+
if (!output || !output.trim()) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The --output-format stream-json output contains JSON events, one per line.
|
|
64
|
+
// Filter to only include lines that look like JSON objects.
|
|
65
|
+
const lines = output.split('\n').filter((line) => {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
return trimmed.startsWith('{') && trimmed.endsWith('}');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (lines.length === 0) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run Gemini CLI over the workspace at `input.cwd` and return a RunnerResult.
|
|
79
|
+
*
|
|
80
|
+
* Auth (GEMINI_API_KEY) arrives via process.env — the orchestrator sets it on the
|
|
81
|
+
* `node run.mjs` invocation, and we pass process.env straight through to the CLI.
|
|
82
|
+
* The runner never handles secrets itself.
|
|
83
|
+
*
|
|
84
|
+
* @param {import('../plugin/contract.js').AgentRunInput} input
|
|
85
|
+
* @returns {Promise<import('../plugin/contract.js').RunnerResult>}
|
|
86
|
+
*/
|
|
87
|
+
export async function runAgent(input) {
|
|
88
|
+
const args = buildGeminiCliArgs(input);
|
|
89
|
+
|
|
90
|
+
// spawnSync (not a shell string): the prompt is a plain argv element, so there is
|
|
91
|
+
// no shell quoting/escaping to get wrong. Blocking is fine — the runner has
|
|
92
|
+
// nothing else to do while the agent works. The sandbox-level timeout bounds it.
|
|
93
|
+
const res = spawnSync('gemini', args, {
|
|
94
|
+
cwd: input.cwd,
|
|
95
|
+
env: process.env,
|
|
96
|
+
encoding: 'utf8',
|
|
97
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const stdout = res.stdout || '';
|
|
101
|
+
const stderr = res.stderr || '';
|
|
102
|
+
// Preserve the old concatenation order: stdout THEN stderr.
|
|
103
|
+
const output = stdout + stderr;
|
|
104
|
+
// spawnSync sets status=null + error when the binary can't be spawned at all.
|
|
105
|
+
const agentExitCode = res.status == null ? -1 : res.status;
|
|
106
|
+
|
|
107
|
+
// Transcript from the stream-json output. Gemini exposes no observable model,
|
|
108
|
+
// so observedModel is always null (matches the old adapter exactly).
|
|
109
|
+
const transcript = extractTranscriptFromOutput(output) ?? null;
|
|
110
|
+
const observedModel = null;
|
|
111
|
+
|
|
112
|
+
if (res.error || agentExitCode !== 0) {
|
|
113
|
+
// Mirror the old error string: last 5 lines of output, else a coded fallback.
|
|
114
|
+
const errorLines = output.trim().split('\n').slice(-5).join('\n');
|
|
115
|
+
const fallback = res.error
|
|
116
|
+
? `Failed to run gemini: ${res.error.message}`
|
|
117
|
+
: `Gemini CLI exited with code ${agentExitCode}`;
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
output,
|
|
121
|
+
transcript,
|
|
122
|
+
observedModel,
|
|
123
|
+
error: errorLines || fallback,
|
|
124
|
+
agentExitCode,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { ok: true, output, transcript, observedModel, error: null, agentExitCode };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ─────────────────────────── runnable (CLI) entry ─────────────────────────── */
|
|
132
|
+
|
|
133
|
+
// True when this file is executed directly (`node run.mjs ...`), false when imported.
|
|
134
|
+
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
135
|
+
|
|
136
|
+
if (isMain) {
|
|
137
|
+
// argv[2] is the AgentRunInput JSON (never contains secrets).
|
|
138
|
+
const input = JSON.parse(process.argv[2]);
|
|
139
|
+
|
|
140
|
+
// Always produce a RunnerResult, even if the runner itself throws, so the host
|
|
141
|
+
// always has a result file to read (node exit code stays 0 except on a truly
|
|
142
|
+
// unrecoverable crash before we can write).
|
|
143
|
+
let result;
|
|
144
|
+
try {
|
|
145
|
+
result = await runAgent(input);
|
|
146
|
+
} catch (e) {
|
|
147
|
+
result = {
|
|
148
|
+
ok: false,
|
|
149
|
+
output: '',
|
|
150
|
+
transcript: null,
|
|
151
|
+
observedModel: null,
|
|
152
|
+
error: e && e.message ? e.message : String(e),
|
|
153
|
+
agentExitCode: -1,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Source of truth: the result file the host reads back via sandbox.readFile.
|
|
158
|
+
try {
|
|
159
|
+
mkdirSync(dirname(input.resultPath), { recursive: true });
|
|
160
|
+
writeFileSync(input.resultPath, JSON.stringify(result));
|
|
161
|
+
} catch {
|
|
162
|
+
// If the file can't be written, the host falls back to the marker line below.
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Fallback channel: a compact status line (no transcript — it can be huge).
|
|
166
|
+
process.stdout.write(
|
|
167
|
+
'__AGENT_RESULT__ ' +
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
ok: result.ok,
|
|
170
|
+
observedModel: result.observedModel,
|
|
171
|
+
error: result.error,
|
|
172
|
+
agentExitCode: result.agentExitCode,
|
|
173
|
+
}) +
|
|
174
|
+
'\n'
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Exit 0: "the runner ran". Agent success/failure is conveyed via result.ok, not
|
|
178
|
+
// the node exit code (the host distinguishes the two).
|
|
179
|
+
process.exit(0);
|
|
180
|
+
}
|
package/dist/lib/agents/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Agent registry with built-in agents.
|
|
3
3
|
*/
|
|
4
4
|
import { registerAgent, getAgent, listAgents, hasAgent } from './registry.js';
|
|
5
|
-
import { createClaudeCodeAgent } from './claude-code.js';
|
|
6
|
-
import { createCodexAgent } from './codex.js';
|
|
7
|
-
import { createOpenCodeAgent } from './opencode.js';
|
|
8
|
-
import { createGeminiAgent } from './gemini.js';
|
|
9
|
-
import { createCursorAgent } from './cursor.js';
|
|
5
|
+
import { createClaudeCodeAgent } from './claude-code/agent.js';
|
|
6
|
+
import { createCodexAgent } from './codex/agent.js';
|
|
7
|
+
import { createOpenCodeAgent } from './opencode/agent.js';
|
|
8
|
+
import { createGeminiAgent } from './gemini/agent.js';
|
|
9
|
+
import { createCursorAgent } from './cursor/agent.js';
|
|
10
10
|
// Register all agent variants (Vercel AI Gateway + Direct API)
|
|
11
11
|
registerAgent(createClaudeCodeAgent({ useVercelAiGateway: true })); // vercel-ai-gateway/claude-code
|
|
12
12
|
registerAgent(createClaudeCodeAgent({ useVercelAiGateway: false })); // claude-code
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/agents/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/agents/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,+DAA+D;AAC/D,aAAa,CAAC,qBAAqB,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAG,gCAAgC;AACtG,aAAa,CAAC,qBAAqB,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAE,cAAc;AACpF,aAAa,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAQ,0BAA0B;AAChG,aAAa,CAAC,gBAAgB,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAO,QAAQ;AAC9E,aAAa,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAiC,6BAA6B;AACnG,aAAa,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAmC,SAAS;AAC/E,aAAa,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAmC,SAAS;AAE/E,+BAA+B;AAC/B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode agent — host-side definition + the thin Agent wrapper.
|
|
3
|
+
*
|
|
4
|
+
* The definition is pure data/auth/config; the actual CLI invocation + transcript
|
|
5
|
+
* capture live in ./run.mjs (shipped into the sandbox by the orchestrator). The
|
|
6
|
+
* wrapper keeps the public Agent interface identical so the registry / runner are
|
|
7
|
+
* untouched.
|
|
8
|
+
*
|
|
9
|
+
* OpenCode only supports the Vercel AI Gateway (no direct provider APIs), so there
|
|
10
|
+
* is a single variant: `vercel-ai-gateway/opencode`.
|
|
11
|
+
*
|
|
12
|
+
* `generateOpenCodeConfig` (the pure host-side config generator) STAYS in this file
|
|
13
|
+
* and is exported so the existing unit tests keep importing it. The transcript /
|
|
14
|
+
* observed-model parsing helpers (`extractObservedModelFromOpenCodeOutput`,
|
|
15
|
+
* `extractSessionIdFromTranscript`, `extractObservedModelFromSessionExport`) moved
|
|
16
|
+
* INTO ./run.mjs (so the tested logic is exactly what the sandbox runs) and the
|
|
17
|
+
* test now imports them from there — see the test import change in the migration
|
|
18
|
+
* notes.
|
|
19
|
+
*/
|
|
20
|
+
import type { Agent } from '../types.js';
|
|
21
|
+
import type { AgentDefinition } from '../plugin/contract.js';
|
|
22
|
+
/**
|
|
23
|
+
* Additional provider configuration for models not yet available in the
|
|
24
|
+
* default Vercel AI Gateway (e.g., early access / unreleased models).
|
|
25
|
+
*
|
|
26
|
+
* Preserved verbatim from the old adapter — passed through agentOptions.extraProviders.
|
|
27
|
+
*/
|
|
28
|
+
export interface OpenCodeProviderConfig {
|
|
29
|
+
npm?: string;
|
|
30
|
+
options?: Record<string, unknown>;
|
|
31
|
+
models?: Record<string, {
|
|
32
|
+
name?: string;
|
|
33
|
+
tool_call?: boolean;
|
|
34
|
+
reasoning?: boolean;
|
|
35
|
+
attachment?: boolean;
|
|
36
|
+
temperature?: boolean;
|
|
37
|
+
limit?: {
|
|
38
|
+
context: number;
|
|
39
|
+
output: number;
|
|
40
|
+
};
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate OpenCode config file content.
|
|
45
|
+
* Configures the Vercel AI Gateway provider, plus any additional providers.
|
|
46
|
+
*
|
|
47
|
+
* STAYS host-side + exported (the unit test imports it). The content is written
|
|
48
|
+
* verbatim into `opencode.json` in the sandbox by the orchestrator. Behavior is
|
|
49
|
+
* identical to the old adapter, including writing the resolved apiKey into the
|
|
50
|
+
* config (falling back to the `{env:AI_GATEWAY_API_KEY}` placeholder).
|
|
51
|
+
*/
|
|
52
|
+
export declare function generateOpenCodeConfig(extraProviders?: Record<string, OpenCodeProviderConfig>, apiKey?: string, timeoutMs?: number, webResearch?: boolean): string;
|
|
53
|
+
/**
|
|
54
|
+
* Build the OpenCode plugin definition.
|
|
55
|
+
*
|
|
56
|
+
* Auth is single-mode: the Vercel AI Gateway. authEnv() also carries the Exa
|
|
57
|
+
* websearch toggle (OPENCODE_ENABLE_EXA) when webResearch is on — it rides in
|
|
58
|
+
* process.env alongside the gateway key, exactly as the old adapter set it on the
|
|
59
|
+
* CLI's spawn env. The matching tool permissions are written into opencode.json by
|
|
60
|
+
* generateOpenCodeConfig.
|
|
61
|
+
*/
|
|
62
|
+
export declare function createOpenCodeDefinition(): AgentDefinition;
|
|
63
|
+
/**
|
|
64
|
+
* Create the OpenCode Agent. Thin wrapper over the generic orchestrator so the
|
|
65
|
+
* Agent interface (and thus registry.ts / index.ts / runner.ts) is unchanged.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createOpenCodeAgent(): Agent;
|
|
68
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../../src/lib/agents/opencode/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAmB,MAAM,aAAa,CAAC;AAG1D,OAAO,KAAK,EAAE,eAAe,EAA2B,MAAM,uBAAuB,CAAC;AAGtF;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QACtB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC7C,CAAC,CAAC;CACJ;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EACvD,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,OAAO,GACpB,MAAM,CA8BR;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,IAAI,eAAe,CA2F1D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,CAW3C"}
|