Skip to content

Commit bc1168d

Browse files
authored
feat(package): expose agentv authoring facade
Expose SDK, provider, and config helpers from the agentv package facade and update examples/docs to import from agentv.
1 parent 0d7b3fe commit bc1168d

127 files changed

Lines changed: 1732 additions & 2111 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ Run bundle layout:
215215
Use `evaluate()` when your application owns the run:
216216

217217
```typescript
218-
import { evaluate } from '@agentv/sdk';
218+
import { evaluate } from 'agentv';
219219

220220
const { results, summary } = await evaluate({
221221
experiment: 'with-skills',
@@ -242,14 +242,25 @@ console.log(`${summary.passed}/${summary.total} passed`);
242242
Use `*.eval.ts` when you want AgentV to run a TypeScript eval config:
243243

244244
```typescript
245-
import type { EvalConfig } from '@agentv/sdk';
245+
import type { EvalConfig } from 'agentv';
246246

247247
const config: EvalConfig = {
248248
description: 'Code generation quality',
249249
tags: { experiment: 'with-skills' },
250-
target: {
251-
extends: 'copilot-sdk',
252-
model: 'claude-sonnet-4.6',
250+
providers: [
251+
{
252+
id: 'copilot-sdk',
253+
label: 'copilot',
254+
config: { model: 'claude-sonnet-4.6' },
255+
},
256+
{
257+
id: 'openai:gpt-5-mini',
258+
label: 'grader',
259+
},
260+
],
261+
defaults: {
262+
provider: 'copilot',
263+
grader: 'grader',
253264
},
254265
repeat: 3,
255266
threshold: 0.8,

apps/cli/package.json

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,42 @@
1414
"bin": {
1515
"agentv": "./dist/cli.js"
1616
},
17+
"main": "./dist/index.js",
18+
"types": "./dist/index.d.ts",
19+
"exports": {
20+
".": {
21+
"types": "./dist/index.d.ts",
22+
"import": "./dist/index.js"
23+
},
24+
"./sdk": {
25+
"types": "./dist/sdk.d.ts",
26+
"import": "./dist/sdk.js"
27+
},
28+
"./provider": {
29+
"types": "./dist/provider.d.ts",
30+
"import": "./dist/provider.js"
31+
},
32+
"./contracts": {
33+
"types": "./dist/contracts.d.ts",
34+
"import": "./dist/contracts.js"
35+
},
36+
"./config": {
37+
"types": "./dist/config.d.ts",
38+
"import": "./dist/config.js"
39+
},
40+
"./package.json": "./package.json"
41+
},
1742
"files": ["dist", "README.md"],
1843
"scripts": {
1944
"dev": "bun src/cli.ts",
20-
"build": "(cd ../../packages/sdk && bun run build) && tsup && bun run copy-readme",
45+
"build": "tsup && bun run copy-readme",
2146
"copy-readme": "bun -e \"import { cpSync } from 'fs'; cpSync('../../README.md', 'README.md')\"",
2247
"prepublishOnly": "node -e \"if(process.env.ALLOW_PUBLISH!=='1'){console.error('ERROR: Use bun run publish:next, then bun run promote:latest');process.exit(1)}\"",
23-
"typecheck": "(cd ../../packages/sdk && bun run build) && tsc --noEmit",
48+
"typecheck": "tsc --noEmit",
2449
"lint": "biome check .",
2550
"format": "biome format --write .",
2651
"fix": "biome check --write .",
27-
"test": "(cd ../../packages/sdk && bun run build) && bun test",
52+
"test": "bun test",
2853
"test:watch": "bun test --watch"
2954
},
3055
"dependencies": {

apps/cli/src/cli-app.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import path from 'node:path';
2+
import {
3+
loadConfig,
4+
loadEnvPathFiles,
5+
runBeforeSessionHook,
6+
runEnvFromEntries,
7+
} from '@agentv/core';
8+
import { binary, run, subcommands } from 'cmd-ts';
9+
import { findRepoRoot } from './commands/eval/shared.js';
10+
11+
import packageJson from '../package.json' with { type: 'json' };
12+
import { compareCommand } from './commands/compare/index.js';
13+
import { convertCommand } from './commands/convert/index.js';
14+
import { createCommand } from './commands/create/index.js';
15+
import { doctorCommand } from './commands/doctor/index.js';
16+
import { evalCommand } from './commands/eval/index.js';
17+
import { gradeCommand } from './commands/grade/index.js';
18+
import { importCommand } from './commands/import/index.js';
19+
import { initCmdTsCommand } from './commands/init/index.js';
20+
import { inspectCommand } from './commands/inspect/index.js';
21+
import { pipelineCommand } from './commands/pipeline/index.js';
22+
import { prepareCommand } from './commands/prepare/index.js';
23+
import { resultsCommand } from './commands/results/index.js';
24+
import { resultsServeCommand } from './commands/results/serve.js';
25+
import { runsCommand } from './commands/runs/index.js';
26+
import { selfCommand } from './commands/self/index.js';
27+
import { skillsCommand } from './commands/skills/index.js';
28+
import { transpileCommand } from './commands/transpile/index.js';
29+
import { trendCommand } from './commands/trend/index.js';
30+
import { trimCommand } from './commands/trim/index.js';
31+
import { validateCommand } from './commands/validate/index.js';
32+
import { getUpdateNotice } from './update-check.js';
33+
34+
export const app = subcommands({
35+
name: 'agentv',
36+
description: 'AgentV CLI',
37+
version: packageJson.version,
38+
cmds: {
39+
dashboard: resultsServeCommand,
40+
eval: evalCommand,
41+
grade: gradeCommand,
42+
import: importCommand,
43+
compare: compareCommand,
44+
convert: convertCommand,
45+
create: createCommand,
46+
doctor: doctorCommand,
47+
init: initCmdTsCommand,
48+
pipeline: pipelineCommand,
49+
prepare: prepareCommand,
50+
results: resultsCommand,
51+
runs: runsCommand,
52+
self: selfCommand,
53+
skills: skillsCommand,
54+
serve: resultsServeCommand,
55+
inspect: inspectCommand,
56+
trend: trendCommand,
57+
transpile: transpileCommand,
58+
trim: trimCommand,
59+
validate: validateCommand,
60+
},
61+
});
62+
63+
/**
64+
* Known eval subcommand names — used to decide whether to inject the
65+
* implicit `run` subcommand for backward-compatible `agentv eval <paths>`.
66+
*/
67+
const EVAL_SUBCOMMANDS = new Set(['run', 'assert', 'aggregate', 'bundle', 'vitest']);
68+
const VITEST_VERIFIER_RE = /(?:^|[/\\])(?:EVAL|[^/\\]+[.-](?:test|spec))\.[cm]?[jt]sx?$/i;
69+
70+
/**
71+
* Top-level CLI command names (excluding `eval` itself).
72+
* Used to ensure `eval` is the top-level subcommand, not nested.
73+
*/
74+
const TOP_LEVEL_COMMANDS = new Set([
75+
'import',
76+
'inspect',
77+
'compare',
78+
'convert',
79+
'create',
80+
'dashboard',
81+
'doctor',
82+
'grade',
83+
'init',
84+
'pipeline',
85+
'prepare',
86+
'results',
87+
'runs',
88+
'self',
89+
'skills',
90+
'serve',
91+
'studio',
92+
'trend',
93+
'transpile',
94+
'trim',
95+
'validate',
96+
]);
97+
98+
export function usesDeprecatedStudioAlias(argv: string[]): boolean {
99+
return argv[2] === 'studio';
100+
}
101+
102+
export function shouldRunBeforeSessionHook(argv: string[]): boolean {
103+
return !(argv[2] === 'eval' && argv[3] === 'vitest');
104+
}
105+
106+
export function inferEvalSubcommand(arg: string | undefined): 'run' | 'vitest' {
107+
return arg && VITEST_VERIFIER_RE.test(arg) ? 'vitest' : 'run';
108+
}
109+
110+
/**
111+
* Preprocess argv for convenience aliases:
112+
* - `--eval-id` → `--test-id`
113+
* - `agentv eval <non-subcommand>` → `agentv eval run <non-subcommand>`
114+
* (backward compat: `eval` used to be a direct command, now it's a group)
115+
*/
116+
export function preprocessArgv(argv: string[]): string[] {
117+
const result = [...argv];
118+
119+
if (result[2] === 'studio') {
120+
result[2] = 'dashboard';
121+
}
122+
123+
// Rewrite --eval-id → --test-id (convenience alias)
124+
for (let i = 0; i < result.length; i++) {
125+
if (result[i] === '--eval-id') {
126+
result[i] = '--test-id';
127+
} else if (result[i].startsWith('--eval-id=')) {
128+
result[i] = `--test-id=${result[i].slice('--eval-id='.length)}`;
129+
}
130+
}
131+
132+
// Implicit eval subcommand: `agentv eval [<arg>]` injects the inferred command
133+
// when the first arg after `eval` is absent or is not a known eval subcommand.
134+
// Backward-compat: `eval` used to be a direct command; now it is a subcommands group.
135+
// Bare `agentv eval` falls through to the run handler so its TTY check can launch
136+
// the interactive wizard. Vitest-looking verifier files use the protocol adapter
137+
// directly so deterministic workspace graders can stay short in eval YAML.
138+
// Only applies when `eval` is the top-level subcommand.
139+
// Exception: `--help` / `-h` should show the eval group help, not run's help.
140+
const evalIdx = result.indexOf('eval');
141+
if (evalIdx !== -1) {
142+
// Ensure no top-level command appears before `eval` in the argv —
143+
// if one does, `eval` is a nested subcommand.
144+
const isTopLevel = !result.slice(0, evalIdx).some((arg) => TOP_LEVEL_COMMANDS.has(arg));
145+
if (isTopLevel) {
146+
const nextArg = result[evalIdx + 1];
147+
const isHelp = nextArg === '--help' || nextArg === '-h';
148+
const isKnownSubcommand = nextArg !== undefined && EVAL_SUBCOMMANDS.has(nextArg);
149+
if (!isHelp && !isKnownSubcommand) {
150+
result.splice(evalIdx + 1, 0, inferEvalSubcommand(nextArg));
151+
}
152+
}
153+
}
154+
155+
return result;
156+
}
157+
158+
export async function runCli(argv: string[] = process.argv): Promise<void> {
159+
// Kick off update check: reads from local cache (fast), spawns a detached
160+
// child to refresh if stale. The notice is printed on process exit so it
161+
// appears after command output, even if the command calls process.exit().
162+
let updateNotice: string | null = null;
163+
process.on('exit', () => {
164+
if (updateNotice) process.stderr.write(`\n${updateNotice}\n`);
165+
});
166+
getUpdateNotice(packageJson.version).then((n) => {
167+
updateNotice = n;
168+
});
169+
170+
const processedArgv = preprocessArgv(argv);
171+
if (usesDeprecatedStudioAlias(argv)) {
172+
process.stderr.write(
173+
'Warning: `agentv studio` is deprecated and will be removed in a future release. Use `agentv dashboard` instead.\n',
174+
);
175+
}
176+
177+
if (shouldRunBeforeSessionHook(processedArgv)) {
178+
// Load env_path/env_from and run the before_session hook once at startup,
179+
// before any command executes. Uses cwd as the search root for
180+
// .agentv/config.yaml so validate/eval commands see the injected vars.
181+
const cwd = process.cwd();
182+
const repoRoot = await findRepoRoot(cwd);
183+
const sessionConfig = await loadConfig(path.join(cwd, '_'), repoRoot);
184+
const configDir = sessionConfig?.configDir ?? repoRoot;
185+
186+
if (sessionConfig?.env_path) {
187+
await loadEnvPathFiles(sessionConfig.env_path, configDir);
188+
}
189+
if (sessionConfig?.env_from) {
190+
await runEnvFromEntries(sessionConfig.env_from, { cwd: configDir });
191+
}
192+
193+
const beforeSessionCommand = sessionConfig?.hooks?.before_session;
194+
if (beforeSessionCommand) {
195+
runBeforeSessionHook(beforeSessionCommand);
196+
}
197+
}
198+
199+
await run(binary(app), processedArgv);
200+
}

apps/cli/src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22
import { killAllTrackedChildren } from '@agentv/core';
33

4-
import { runCli } from './index.js';
4+
import { runCli } from './cli-app.js';
55

66
// Forward SIGINT/SIGTERM to spawned provider subprocesses before exiting.
77
// Without this, Dashboard's `child.kill('SIGTERM')` against the CLI orphans

apps/cli/src/commands/create/commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { command, option, optional, positional, string } from 'cmd-ts';
44

55
const ASSERTION_TEMPLATES: Record<string, string> = {
66
default: `#!/usr/bin/env bun
7-
import { defineAssertion } from '@agentv/sdk';
7+
import { defineAssertion } from 'agentv';
88
99
export default defineAssertion(({ output }) => {
1010
// TODO: Implement your assertion logic
@@ -18,7 +18,7 @@ export default defineAssertion(({ output }) => {
1818
});
1919
`,
2020
score: `#!/usr/bin/env bun
21-
import { defineAssertion } from '@agentv/sdk';
21+
import { defineAssertion } from 'agentv';
2222
2323
export default defineAssertion(({ output }) => {
2424
// TODO: Implement your scoring logic (0.0 to 1.0)

apps/cli/src/commands/eval/commands/vitest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { command, flag, number, option, optional, restPositionals, string } from 'cmd-ts';
22

3-
import { runScriptGrader, runVitestWorkspaceGrader } from '@agentv/sdk';
3+
import { runScriptGrader, runVitestWorkspaceGrader } from '@agentv/core/script-grader';
44

55
function parseCommand(value: string | undefined): readonly string[] | undefined {
66
const trimmed = value?.trim();

apps/cli/src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export {
2+
defineConfig,
3+
type AgentVTsConfig as AgentVConfig,
4+
} from '@agentv/core';

apps/cli/src/contracts.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export * from './provider.js';
2+
export * from './config.js';
3+
export type {
4+
AssertEntry,
5+
ConversationTurnInput,
6+
EvalAssertionInput,
7+
EvalRunArtifacts,
8+
EvalRunResult,
9+
EvalSummary,
10+
EvalTestInput,
11+
EvaluationResultWire,
12+
TraceSummaryWire,
13+
TraceWire,
14+
} from '@agentv/core';

0 commit comments

Comments
 (0)