@sverka/benchmark 0.0.0 → 0.1.2
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/bin.d.mts +1 -0
- package/dist/bin.mjs +35 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/index.d.mts +164 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/src-PvGPQO8c.mjs +279 -0
- package/dist/src-PvGPQO8c.mjs.map +1 -0
- package/package.json +44 -12
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { c as BENCHMARK_TASKS, f as runBenchmark, l as DEFAULT_AGENTS, p as writeReport } from "./src-PvGPQO8c.mjs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
//#region src/bin.ts
|
|
6
|
+
async function main() {
|
|
7
|
+
const model = process.env.DEVIN_MODEL ?? "glm-5-2";
|
|
8
|
+
const outputDir = process.argv[2] ?? process.cwd();
|
|
9
|
+
process.stderr.write(`Sverka Benchmark Arena\n`);
|
|
10
|
+
process.stderr.write(`Model: ${model}\n`);
|
|
11
|
+
process.stderr.write(`Tasks: ${BENCHMARK_TASKS.length}\n`);
|
|
12
|
+
process.stderr.write(`Agents: ${DEFAULT_AGENTS.length}\n\n`);
|
|
13
|
+
const result = await runBenchmark({
|
|
14
|
+
tasks: [...BENCHMARK_TASKS],
|
|
15
|
+
agents: [...DEFAULT_AGENTS],
|
|
16
|
+
model,
|
|
17
|
+
outputDir
|
|
18
|
+
});
|
|
19
|
+
const filename = `benchmark-${result.timestamp.replace(/[:.]/g, "-")}.json`;
|
|
20
|
+
const outputPath = join(outputDir, filename);
|
|
21
|
+
await writeReport(result, outputPath);
|
|
22
|
+
process.stderr.write(`\nReport written to: ${outputPath}\n`);
|
|
23
|
+
process.stderr.write(`\nSummary:\n`);
|
|
24
|
+
process.stderr.write(` raw-shell: ${result.summary["raw-shell"].successCount}/${result.summary["raw-shell"].totalTasks} pass, avg ${result.summary["raw-shell"].avgTotalTokens} tokens\n`);
|
|
25
|
+
process.stderr.write(` sverka: ${result.summary.sverka.successCount}/${result.summary.sverka.totalTasks} pass, avg ${result.summary.sverka.avgTotalTokens} tokens\n`);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
main().then(process.exit).catch((err) => {
|
|
29
|
+
process.stderr.write(`Fatal: ${err}\n`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
//#endregion
|
|
33
|
+
export {};
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=bin.mjs.map
|
package/dist/bin.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\nimport process from \"node:process\";\nimport { join } from \"node:path\";\nimport { runBenchmark, writeReport, BENCHMARK_TASKS, DEFAULT_AGENTS } from \"./index.js\";\n\nasync function main(): Promise<number> {\n const model = process.env.DEVIN_MODEL ?? \"glm-5-2\";\n const outputDir = process.argv[2] ?? process.cwd();\n\n process.stderr.write(`Sverka Benchmark Arena\\n`);\n process.stderr.write(`Model: ${model}\\n`);\n process.stderr.write(`Tasks: ${BENCHMARK_TASKS.length}\\n`);\n process.stderr.write(`Agents: ${DEFAULT_AGENTS.length}\\n\\n`);\n\n const result = await runBenchmark({\n tasks: [...BENCHMARK_TASKS],\n agents: [...DEFAULT_AGENTS],\n model,\n outputDir,\n });\n\n const filename = `benchmark-${result.timestamp.replace(/[:.]/g, \"-\")}.json`;\n const outputPath = join(outputDir, filename);\n await writeReport(result, outputPath);\n\n process.stderr.write(`\\nReport written to: ${outputPath}\\n`);\n process.stderr.write(`\\nSummary:\\n`);\n process.stderr.write(` raw-shell: ${result.summary[\"raw-shell\"].successCount}/${result.summary[\"raw-shell\"].totalTasks} pass, avg ${result.summary[\"raw-shell\"].avgTotalTokens} tokens\\n`);\n process.stderr.write(` sverka: ${result.summary.sverka.successCount}/${result.summary.sverka.totalTasks} pass, avg ${result.summary.sverka.avgTotalTokens} tokens\\n`);\n\n return 0;\n}\n\nmain().then(process.exit).catch((err) => {\n process.stderr.write(`Fatal: ${err}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;AAKA,eAAe,OAAwB;CACrC,MAAM,QAAQ,QAAQ,IAAI,eAAe;CACzC,MAAM,YAAY,QAAQ,KAAK,MAAM,QAAQ,IAAI;CAEjD,QAAQ,OAAO,MAAM,0BAA0B;CAC/C,QAAQ,OAAO,MAAM,UAAU,MAAM,GAAG;CACxC,QAAQ,OAAO,MAAM,UAAU,gBAAgB,OAAO,GAAG;CACzD,QAAQ,OAAO,MAAM,WAAW,eAAe,OAAO,KAAK;CAE3D,MAAM,SAAS,MAAM,aAAa;EAChC,OAAO,CAAC,GAAG,eAAe;EAC1B,QAAQ,CAAC,GAAG,cAAc;EAC1B;EACA;CACF,CAAC;CAED,MAAM,WAAW,aAAa,OAAO,UAAU,QAAQ,SAAS,GAAG,EAAE;CACrE,MAAM,aAAa,KAAK,WAAW,QAAQ;CAC3C,MAAM,YAAY,QAAQ,UAAU;CAEpC,QAAQ,OAAO,MAAM,wBAAwB,WAAW,GAAG;CAC3D,QAAQ,OAAO,MAAM,cAAc;CACnC,QAAQ,OAAO,MAAM,gBAAgB,OAAO,QAAQ,YAAY,CAAC,aAAa,GAAG,OAAO,QAAQ,YAAY,CAAC,WAAW,aAAa,OAAO,QAAQ,YAAY,CAAC,eAAe,UAAU;CAC1L,QAAQ,OAAO,MAAM,gBAAgB,OAAO,QAAQ,OAAO,aAAa,GAAG,OAAO,QAAQ,OAAO,WAAW,aAAa,OAAO,QAAQ,OAAO,eAAe,UAAU;CAExK,OAAO;AACT;AAEA,KAAK,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,QAAQ;CACvC,QAAQ,OAAO,MAAM,UAAU,IAAI,GAAG;CACtC,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Usage } from "@agentclientprotocol/sdk";
|
|
2
|
+
//#region src/types.d.ts
|
|
3
|
+
/** A benchmark task scenario. */
|
|
4
|
+
interface Task {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
prompt: string;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Agent configuration — determines workspace setup. */
|
|
11
|
+
interface AgentConfig {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
type: "raw-shell" | "sverka";
|
|
15
|
+
}
|
|
16
|
+
/** Metrics collected from ACP events + timing. */
|
|
17
|
+
interface RunMetrics {
|
|
18
|
+
inputTokens: number;
|
|
19
|
+
outputTokens: number;
|
|
20
|
+
thoughtTokens: number;
|
|
21
|
+
totalTokens: number;
|
|
22
|
+
toolCallCount: number;
|
|
23
|
+
executionTimeMs: number;
|
|
24
|
+
stopReason: string;
|
|
25
|
+
}
|
|
26
|
+
/** Result of a single task+agent run. */
|
|
27
|
+
interface RunResult {
|
|
28
|
+
taskId: string;
|
|
29
|
+
agentId: string;
|
|
30
|
+
agentType: string;
|
|
31
|
+
metrics: RunMetrics;
|
|
32
|
+
success: boolean;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Aggregate metrics for one agent type across all tasks. */
|
|
36
|
+
interface AggregateMetrics {
|
|
37
|
+
totalTasks: number;
|
|
38
|
+
successCount: number;
|
|
39
|
+
avgInputTokens: number;
|
|
40
|
+
avgOutputTokens: number;
|
|
41
|
+
avgTotalTokens: number;
|
|
42
|
+
avgToolCalls: number;
|
|
43
|
+
avgExecutionTimeMs: number;
|
|
44
|
+
}
|
|
45
|
+
/** Full benchmark output. */
|
|
46
|
+
interface BenchmarkResult {
|
|
47
|
+
timestamp: string;
|
|
48
|
+
model: string;
|
|
49
|
+
results: RunResult[];
|
|
50
|
+
summary: {
|
|
51
|
+
"raw-shell": AggregateMetrics;
|
|
52
|
+
sverka: AggregateMetrics;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Arena configuration. */
|
|
56
|
+
interface BenchmarkConfig {
|
|
57
|
+
tasks: Task[];
|
|
58
|
+
agents: AgentConfig[];
|
|
59
|
+
model?: string;
|
|
60
|
+
outputDir?: string;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/arena.d.ts
|
|
64
|
+
/** Extract RunMetrics from a PromptResponse + collected event data. */
|
|
65
|
+
export declare function extractMetrics(promptResult: {
|
|
66
|
+
stopReason: string;
|
|
67
|
+
usage?: Usage | null;
|
|
68
|
+
}, toolCallCount: number, startTime: number): RunMetrics;
|
|
69
|
+
/** Aggregate metrics for one agent type across multiple RunResults. */
|
|
70
|
+
export declare function aggregateMetrics(results: RunResult[], agentType: string): AggregateMetrics;
|
|
71
|
+
/** Run the full benchmark: each task × each agent, sequentially. */
|
|
72
|
+
export declare function runBenchmark(config: BenchmarkConfig): Promise<BenchmarkResult>;
|
|
73
|
+
/** Write benchmark results to a JSON file. */
|
|
74
|
+
export declare function writeReport(result: BenchmarkResult, outputPath: string): Promise<void>;
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/tasks.d.ts
|
|
77
|
+
/** 5 task scenarios, ordered simple → complex. */
|
|
78
|
+
export declare const BENCHMARK_TASKS: readonly Task[];
|
|
79
|
+
/** Default agent configs: raw-shell (no sverka) vs sverka (CLI + skill). */
|
|
80
|
+
export declare const DEFAULT_AGENTS: readonly AgentConfig[];
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/collector.d.ts
|
|
83
|
+
/**
|
|
84
|
+
* Transcript collector — reads Devin CLI transcripts and transforms them
|
|
85
|
+
* to trace data for the benchmark trace viewer.
|
|
86
|
+
*
|
|
87
|
+
* Transcript source: ~/.local/share/devin/cli/transcripts/<session-id>.json
|
|
88
|
+
* Each transcript has steps[] with source, message, timestamp, and extra.telemetry.
|
|
89
|
+
* Agent steps with telemetry.operation === "inference" are LLM calls.
|
|
90
|
+
*/
|
|
91
|
+
interface TranscriptStep {
|
|
92
|
+
step_id: number;
|
|
93
|
+
source: "system" | "agent" | "user";
|
|
94
|
+
message: string;
|
|
95
|
+
timestamp: string;
|
|
96
|
+
extra?: {
|
|
97
|
+
generation_model?: string | null;
|
|
98
|
+
telemetry?: {
|
|
99
|
+
source?: string;
|
|
100
|
+
operation?: string;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
interface TranscriptMetrics {
|
|
105
|
+
total_prompt_tokens: number;
|
|
106
|
+
total_completion_tokens: number;
|
|
107
|
+
total_cached_tokens: number;
|
|
108
|
+
total_steps: number;
|
|
109
|
+
}
|
|
110
|
+
interface Transcript {
|
|
111
|
+
session_id: string;
|
|
112
|
+
schema_version: string;
|
|
113
|
+
agent: {
|
|
114
|
+
name: string;
|
|
115
|
+
version: string;
|
|
116
|
+
model_name: string;
|
|
117
|
+
};
|
|
118
|
+
final_metrics: TranscriptMetrics;
|
|
119
|
+
steps: TranscriptStep[];
|
|
120
|
+
}
|
|
121
|
+
interface TraceStep {
|
|
122
|
+
stepId: number;
|
|
123
|
+
source: "system" | "agent" | "user";
|
|
124
|
+
timestamp: string;
|
|
125
|
+
message: string;
|
|
126
|
+
isLlmCall: boolean;
|
|
127
|
+
}
|
|
128
|
+
interface TraceAgentData {
|
|
129
|
+
sessionId: string;
|
|
130
|
+
model: string;
|
|
131
|
+
finalMetrics: {
|
|
132
|
+
totalPromptTokens: number;
|
|
133
|
+
totalCompletionTokens: number;
|
|
134
|
+
totalCachedTokens: number;
|
|
135
|
+
totalSteps: number;
|
|
136
|
+
};
|
|
137
|
+
llmCallCount: number;
|
|
138
|
+
steps: TraceStep[];
|
|
139
|
+
}
|
|
140
|
+
interface TraceData {
|
|
141
|
+
taskId: string;
|
|
142
|
+
prompt: string;
|
|
143
|
+
agents: {
|
|
144
|
+
"raw-shell"?: TraceAgentData;
|
|
145
|
+
sverka?: TraceAgentData;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** Default transcript directory. */
|
|
149
|
+
export declare function transcriptDir(): string;
|
|
150
|
+
/** Read a transcript JSON file by session ID. */
|
|
151
|
+
export declare function readTranscript(sessionId: string, dir?: string): Transcript;
|
|
152
|
+
/** Transform a raw transcript step to a trace step. */
|
|
153
|
+
export declare function transformStep(step: TranscriptStep): TraceStep;
|
|
154
|
+
/** Count LLM inference calls in a transcript. */
|
|
155
|
+
export declare function countLlmCalls(transcript: Transcript): number;
|
|
156
|
+
/** Transform a full transcript to agent trace data. */
|
|
157
|
+
export declare function transformTranscript(transcript: Transcript, agentType: "raw-shell" | "sverka"): TraceAgentData;
|
|
158
|
+
/** Build a combined trace data object for a task from two transcripts. */
|
|
159
|
+
export declare function buildTraceData(taskId: string, prompt: string, rawShellSessionId: string, sverkaSessionId: string, dir?: string): TraceData;
|
|
160
|
+
/** Write trace data to the benchmark traces directory. */
|
|
161
|
+
export declare function writeTraceData(trace: TraceData, outputDir: string): Promise<string>;
|
|
162
|
+
//#endregion
|
|
163
|
+
export type { AgentConfig, AggregateMetrics, BenchmarkConfig, BenchmarkResult, RunMetrics, RunResult, Task, TraceAgentData, TraceData, TraceStep, Transcript, TranscriptMetrics, TranscriptStep };
|
|
164
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/arena.ts","../src/tasks.ts","../src/collector.ts"],"mappings":";;;UACiB;EACf;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA;EACA,SAAS;EACT;EACA;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA,SAAS;EACT;IACE,aAAa;IACb,QAAQ;;;;UAKK;EACf,OAAO;EACP,QAAQ;EACR;EACA;;;;;wBC1Cc,eACd;EAAgB;EAAoB,QAAQ;GAC5C,uBACA,oBACC;;wBAUa,iBACd,SAAS,aACT,oBACC;;wBA2BmB,aACpB,QAAQ,kBACP,QAAQ;;wBA6HW,YACpB,QAAQ,iBACR,qBACC;;;;qBChMU,0BAA0B;;qBAuC1B,yBAAyB;;;;;;;;;;;UC1BrB;EACf;EACA;EACA;EACA;EACA;IACE;IACA;MACE;MACA;;;;UAKW;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;IACE;IACA;IACA;;EAEF,eAAe;EACf,OAAO;;UAKQ;EACf;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;IACE;IACA;IACA;IACA;;EAEF;EACA,OAAO;;UAGQ;EACf;EACA;EACA;IACE,cAAc;IACd,SAAS;;;;wBAOG;;wBAKA,eACd,mBACA,eACC;;wBASa,cAAc,MAAM,iBAAiB;;wBAcrC,cAAc,YAAY;;wBAS1B,oBACd,YAAY,YACZ,oCACC;;wBAgBa,eACd,gBACA,gBACA,2BACA,yBACA,eACC;;wBAcmB,eACpB,OAAO,WACP,oBACC"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as transformStep, c as BENCHMARK_TASKS, d as extractMetrics, f as runBenchmark, i as transcriptDir, l as DEFAULT_AGENTS, n as countLlmCalls, o as transformTranscript, p as writeReport, r as readTranscript, s as writeTraceData, t as buildTraceData, u as aggregateMetrics } from "./src-PvGPQO8c.mjs";
|
|
2
|
+
export { BENCHMARK_TASKS, DEFAULT_AGENTS, aggregateMetrics, buildTraceData, countLlmCalls, extractMetrics, readTranscript, runBenchmark, transcriptDir, transformStep, transformTranscript, writeReport, writeTraceData };
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { cp, mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { extractUsageTokens, runAcpSession, sanitizeEnv } from "@sverka/arena";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
//#region src/arena.ts
|
|
9
|
+
/** Extract RunMetrics from a PromptResponse + collected event data. */
|
|
10
|
+
function extractMetrics(promptResult, toolCallCount, startTime) {
|
|
11
|
+
return {
|
|
12
|
+
...extractUsageTokens(promptResult.usage),
|
|
13
|
+
toolCallCount,
|
|
14
|
+
executionTimeMs: Date.now() - startTime,
|
|
15
|
+
stopReason: promptResult.stopReason
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Aggregate metrics for one agent type across multiple RunResults. */
|
|
19
|
+
function aggregateMetrics(results, agentType) {
|
|
20
|
+
const filtered = results.filter((r) => r.agentType === agentType);
|
|
21
|
+
const n = filtered.length;
|
|
22
|
+
if (n === 0) return {
|
|
23
|
+
totalTasks: 0,
|
|
24
|
+
successCount: 0,
|
|
25
|
+
avgInputTokens: 0,
|
|
26
|
+
avgOutputTokens: 0,
|
|
27
|
+
avgTotalTokens: 0,
|
|
28
|
+
avgToolCalls: 0,
|
|
29
|
+
avgExecutionTimeMs: 0
|
|
30
|
+
};
|
|
31
|
+
const sum = (f) => filtered.reduce((a, r) => a + f(r), 0);
|
|
32
|
+
return {
|
|
33
|
+
totalTasks: n,
|
|
34
|
+
successCount: filtered.filter((r) => r.success).length,
|
|
35
|
+
avgInputTokens: Math.round(sum((r) => r.metrics.inputTokens) / n),
|
|
36
|
+
avgOutputTokens: Math.round(sum((r) => r.metrics.outputTokens) / n),
|
|
37
|
+
avgTotalTokens: Math.round(sum((r) => r.metrics.totalTokens) / n),
|
|
38
|
+
avgToolCalls: Math.round(sum((r) => r.metrics.toolCallCount) / n * 100) / 100,
|
|
39
|
+
avgExecutionTimeMs: Math.round(sum((r) => r.metrics.executionTimeMs) / n)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Run the full benchmark: each task × each agent, sequentially. */
|
|
43
|
+
async function runBenchmark(config) {
|
|
44
|
+
const model = config.model ?? "glm-5-2";
|
|
45
|
+
const results = [];
|
|
46
|
+
for (const task of config.tasks) for (const agent of config.agents) {
|
|
47
|
+
process.stderr.write(`[${task.id}/${agent.id}] Running...\n`);
|
|
48
|
+
const result = await runTask(task, agent, model);
|
|
49
|
+
results.push(result);
|
|
50
|
+
process.stderr.write(`[${task.id}/${agent.id}] ${result.success ? "PASS" : "FAIL"} — ${result.metrics.totalTokens} tokens, ${result.metrics.toolCallCount} tools, ${result.metrics.executionTimeMs}ms\n`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
54
|
+
model,
|
|
55
|
+
results,
|
|
56
|
+
summary: {
|
|
57
|
+
"raw-shell": aggregateMetrics(results, "raw-shell"),
|
|
58
|
+
sverka: aggregateMetrics(results, "sverka")
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Run a single task with one agent. */
|
|
63
|
+
async function runTask(task, agent, model) {
|
|
64
|
+
const workspace = await setupWorkspace(agent);
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
let proc = null;
|
|
67
|
+
try {
|
|
68
|
+
proc = spawnAcpAgent(workspace, model);
|
|
69
|
+
let toolCallCount = 0;
|
|
70
|
+
const metrics = extractMetrics(await runAcpSession(proc, workspace, task.prompt, task.timeoutMs ?? 12e4, () => {
|
|
71
|
+
toolCallCount++;
|
|
72
|
+
}), toolCallCount, startTime);
|
|
73
|
+
return {
|
|
74
|
+
taskId: task.id,
|
|
75
|
+
agentId: agent.id,
|
|
76
|
+
agentType: agent.type,
|
|
77
|
+
metrics,
|
|
78
|
+
success: metrics.stopReason === "end_turn"
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const metrics = extractMetrics({
|
|
82
|
+
stopReason: "cancelled",
|
|
83
|
+
usage: null
|
|
84
|
+
}, 0, startTime);
|
|
85
|
+
return {
|
|
86
|
+
taskId: task.id,
|
|
87
|
+
agentId: agent.id,
|
|
88
|
+
agentType: agent.type,
|
|
89
|
+
metrics,
|
|
90
|
+
success: false,
|
|
91
|
+
error: error instanceof Error ? error.message : String(error)
|
|
92
|
+
};
|
|
93
|
+
} finally {
|
|
94
|
+
proc?.kill("SIGTERM");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Set up workspace for an agent.
|
|
98
|
+
*
|
|
99
|
+
* Both agents work in the actual Sverka repo so they have the real project
|
|
100
|
+
* to operate on. The only difference is that the "sverka" agent gets the
|
|
101
|
+
* Sverka skill installed in .agents/skills/sverka/.
|
|
102
|
+
*
|
|
103
|
+
* We do NOT create a temp copy — copying node_modules + dist is too slow
|
|
104
|
+
* and the agents need the real build artifacts to run `sverka` commands.
|
|
105
|
+
*/
|
|
106
|
+
async function setupWorkspace(agent) {
|
|
107
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
108
|
+
const workspace = resolve(here, "..", "..", "..");
|
|
109
|
+
if (agent.type === "sverka") {
|
|
110
|
+
const skillDir = join(workspace, ".agents", "skills", "sverka");
|
|
111
|
+
const skillFile = join(skillDir, "SKILL.md");
|
|
112
|
+
try {
|
|
113
|
+
await mkdir(skillDir, { recursive: true });
|
|
114
|
+
const repoSkillPath = resolveRepoFile(".agents/skills/sverka/SKILL.md");
|
|
115
|
+
if (repoSkillPath && resolve(repoSkillPath) !== resolve(skillFile)) await cp(repoSkillPath, skillFile, { recursive: true });
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
return workspace;
|
|
119
|
+
}
|
|
120
|
+
/** Spawn `devin acp` as a subprocess in the given workspace. */
|
|
121
|
+
function spawnAcpAgent(workspace, model) {
|
|
122
|
+
const env = sanitizeEnv({
|
|
123
|
+
model: {
|
|
124
|
+
id: model,
|
|
125
|
+
name: model
|
|
126
|
+
},
|
|
127
|
+
workspace,
|
|
128
|
+
plugins: []
|
|
129
|
+
});
|
|
130
|
+
return spawn("devin", [
|
|
131
|
+
"acp",
|
|
132
|
+
"--model",
|
|
133
|
+
model
|
|
134
|
+
], {
|
|
135
|
+
cwd: workspace,
|
|
136
|
+
stdio: [
|
|
137
|
+
"pipe",
|
|
138
|
+
"pipe",
|
|
139
|
+
"inherit"
|
|
140
|
+
],
|
|
141
|
+
env
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** Write benchmark results to a JSON file. */
|
|
145
|
+
async function writeReport(result, outputPath) {
|
|
146
|
+
const json = JSON.stringify(result, null, 2);
|
|
147
|
+
await writeFile(outputPath, json, "utf-8");
|
|
148
|
+
}
|
|
149
|
+
/** Resolve a file path relative to the repo root. */
|
|
150
|
+
function resolveRepoFile(relPath) {
|
|
151
|
+
try {
|
|
152
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
153
|
+
const repoRoot = resolve(here, "..", "..", "..");
|
|
154
|
+
return join(repoRoot, relPath);
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/tasks.ts
|
|
161
|
+
/** 5 task scenarios, ordered simple → complex. */
|
|
162
|
+
const BENCHMARK_TASKS = [
|
|
163
|
+
{
|
|
164
|
+
id: "run-checks",
|
|
165
|
+
name: "run-checks",
|
|
166
|
+
prompt: "Run all quality checks (lint, typecheck, test, build) for this TypeScript project and report the results. If any check fails, report which one failed and why.",
|
|
167
|
+
timeoutMs: 12e4
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "discover",
|
|
171
|
+
name: "discover",
|
|
172
|
+
prompt: "Discover what checks are available in this project by reading package.json scripts and looking for config files. List each check with its command.",
|
|
173
|
+
timeoutMs: 6e4
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "create-config",
|
|
177
|
+
name: "create-config",
|
|
178
|
+
prompt: "Create a CI workflow configuration file for this project that runs lint, typecheck, test, and build in dependency order. The config should define steps with dependencies: lint and typecheck run first (parallel), test depends on both, build depends on test.",
|
|
179
|
+
timeoutMs: 12e4
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "compile-github",
|
|
183
|
+
name: "compile-github",
|
|
184
|
+
prompt: "Compile a CI workflow to GitHub Actions YAML format for this project. The workflow should run lint, typecheck, test, and build on push events. Output the complete YAML.",
|
|
185
|
+
timeoutMs: 12e4
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "multi-step",
|
|
189
|
+
name: "multi-step",
|
|
190
|
+
prompt: "Create a multi-step pipeline with dependencies: lint and typecheck run in parallel, test depends on both, build depends on test. Define the pipeline as code and validate it works correctly.",
|
|
191
|
+
timeoutMs: 12e4
|
|
192
|
+
}
|
|
193
|
+
];
|
|
194
|
+
/** Default agent configs: raw-shell (no sverka) vs sverka (CLI + skill). */
|
|
195
|
+
const DEFAULT_AGENTS = [{
|
|
196
|
+
id: "raw-shell",
|
|
197
|
+
name: "Raw Shell Agent",
|
|
198
|
+
type: "raw-shell"
|
|
199
|
+
}, {
|
|
200
|
+
id: "sverka",
|
|
201
|
+
name: "Sverka CLI Agent",
|
|
202
|
+
type: "sverka"
|
|
203
|
+
}];
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/collector.ts
|
|
206
|
+
/**
|
|
207
|
+
* Transcript collector — reads Devin CLI transcripts and transforms them
|
|
208
|
+
* to trace data for the benchmark trace viewer.
|
|
209
|
+
*
|
|
210
|
+
* Transcript source: ~/.local/share/devin/cli/transcripts/<session-id>.json
|
|
211
|
+
* Each transcript has steps[] with source, message, timestamp, and extra.telemetry.
|
|
212
|
+
* Agent steps with telemetry.operation === "inference" are LLM calls.
|
|
213
|
+
*/
|
|
214
|
+
/** Default transcript directory. */
|
|
215
|
+
function transcriptDir() {
|
|
216
|
+
return join(homedir(), ".local", "share", "devin", "cli", "transcripts");
|
|
217
|
+
}
|
|
218
|
+
/** Read a transcript JSON file by session ID. */
|
|
219
|
+
function readTranscript(sessionId, dir) {
|
|
220
|
+
const path = join(dir ?? transcriptDir(), `${sessionId}.json`);
|
|
221
|
+
if (!existsSync(path)) throw new Error(`Transcript not found: ${path}`);
|
|
222
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
223
|
+
}
|
|
224
|
+
/** Transform a raw transcript step to a trace step. */
|
|
225
|
+
function transformStep(step) {
|
|
226
|
+
const isLlmCall = step.source === "agent" && step.extra?.telemetry?.operation === "inference";
|
|
227
|
+
return {
|
|
228
|
+
stepId: step.step_id,
|
|
229
|
+
source: step.source,
|
|
230
|
+
timestamp: step.timestamp,
|
|
231
|
+
message: step.message,
|
|
232
|
+
isLlmCall
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** Count LLM inference calls in a transcript. */
|
|
236
|
+
function countLlmCalls(transcript) {
|
|
237
|
+
return transcript.steps.filter((s) => s.source === "agent" && s.extra?.telemetry?.operation === "inference").length;
|
|
238
|
+
}
|
|
239
|
+
/** Transform a full transcript to agent trace data. */
|
|
240
|
+
function transformTranscript(transcript, agentType) {
|
|
241
|
+
return {
|
|
242
|
+
sessionId: transcript.session_id,
|
|
243
|
+
model: transcript.agent.model_name,
|
|
244
|
+
finalMetrics: {
|
|
245
|
+
totalPromptTokens: transcript.final_metrics.total_prompt_tokens,
|
|
246
|
+
totalCompletionTokens: transcript.final_metrics.total_completion_tokens,
|
|
247
|
+
totalCachedTokens: transcript.final_metrics.total_cached_tokens,
|
|
248
|
+
totalSteps: transcript.final_metrics.total_steps
|
|
249
|
+
},
|
|
250
|
+
llmCallCount: countLlmCalls(transcript),
|
|
251
|
+
steps: transcript.steps.map(transformStep)
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/** Build a combined trace data object for a task from two transcripts. */
|
|
255
|
+
function buildTraceData(taskId, prompt, rawShellSessionId, sverkaSessionId, dir) {
|
|
256
|
+
const rawTranscript = readTranscript(rawShellSessionId, dir);
|
|
257
|
+
const sverkaTranscript = readTranscript(sverkaSessionId, dir);
|
|
258
|
+
return {
|
|
259
|
+
taskId,
|
|
260
|
+
prompt,
|
|
261
|
+
agents: {
|
|
262
|
+
"raw-shell": transformTranscript(rawTranscript, "raw-shell"),
|
|
263
|
+
sverka: transformTranscript(sverkaTranscript, "sverka")
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** Write trace data to the benchmark traces directory. */
|
|
268
|
+
async function writeTraceData(trace, outputDir) {
|
|
269
|
+
if (trace.taskId.includes("..") || trace.taskId.includes("/") || trace.taskId.includes("\\")) throw new Error(`Invalid taskId for trace output: ${trace.taskId}`);
|
|
270
|
+
const taskDir = join(outputDir, trace.taskId);
|
|
271
|
+
await mkdir(taskDir, { recursive: true });
|
|
272
|
+
const path = join(taskDir, "trace.json");
|
|
273
|
+
await writeFile(path, JSON.stringify(trace, null, 2), "utf-8");
|
|
274
|
+
return path;
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
export { transformStep as a, BENCHMARK_TASKS as c, extractMetrics as d, runBenchmark as f, transcriptDir as i, DEFAULT_AGENTS as l, countLlmCalls as n, transformTranscript as o, writeReport as p, readTranscript as r, writeTraceData as s, buildTraceData as t, aggregateMetrics as u };
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=src-PvGPQO8c.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src-PvGPQO8c.mjs","names":[],"sources":["../src/arena.ts","../src/tasks.ts","../src/collector.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\";\nimport { cp, mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\n\nimport type { Usage } from \"@agentclientprotocol/sdk\";\n\nimport { sanitizeEnv, runAcpSession, extractUsageTokens } from \"@sverka/arena\";\n\nimport type {\n Task,\n AgentConfig,\n RunMetrics,\n RunResult,\n AggregateMetrics,\n BenchmarkResult,\n BenchmarkConfig,\n} from \"./types.js\";\n\n/** Extract RunMetrics from a PromptResponse + collected event data. */\nexport function extractMetrics(\n promptResult: { stopReason: string; usage?: Usage | null },\n toolCallCount: number,\n startTime: number,\n): RunMetrics {\n return {\n ...extractUsageTokens(promptResult.usage),\n toolCallCount,\n executionTimeMs: Date.now() - startTime,\n stopReason: promptResult.stopReason,\n };\n}\n\n/** Aggregate metrics for one agent type across multiple RunResults. */\nexport function aggregateMetrics(\n results: RunResult[],\n agentType: string,\n): AggregateMetrics {\n const filtered = results.filter((r) => r.agentType === agentType);\n const n = filtered.length;\n if (n === 0) {\n return {\n totalTasks: 0,\n successCount: 0,\n avgInputTokens: 0,\n avgOutputTokens: 0,\n avgTotalTokens: 0,\n avgToolCalls: 0,\n avgExecutionTimeMs: 0,\n };\n }\n const sum = (f: (r: RunResult) => number) => filtered.reduce((a, r) => a + f(r), 0);\n return {\n totalTasks: n,\n successCount: filtered.filter((r) => r.success).length,\n avgInputTokens: Math.round(sum((r) => r.metrics.inputTokens) / n),\n avgOutputTokens: Math.round(sum((r) => r.metrics.outputTokens) / n),\n avgTotalTokens: Math.round(sum((r) => r.metrics.totalTokens) / n),\n avgToolCalls: Math.round((sum((r) => r.metrics.toolCallCount) / n) * 100) / 100,\n avgExecutionTimeMs: Math.round(sum((r) => r.metrics.executionTimeMs) / n),\n };\n}\n\n/** Run the full benchmark: each task × each agent, sequentially. */\nexport async function runBenchmark(\n config: BenchmarkConfig,\n): Promise<BenchmarkResult> {\n const model = config.model ?? \"glm-5-2\";\n const results: RunResult[] = [];\n\n for (const task of config.tasks) {\n for (const agent of config.agents) {\n process.stderr.write(`[${task.id}/${agent.id}] Running...\\n`);\n const result = await runTask(task, agent, model);\n results.push(result);\n process.stderr.write(\n `[${task.id}/${agent.id}] ${result.success ? \"PASS\" : \"FAIL\"} — ` +\n `${result.metrics.totalTokens} tokens, ${result.metrics.toolCallCount} tools, ` +\n `${result.metrics.executionTimeMs}ms\\n`,\n );\n }\n }\n\n return {\n timestamp: new Date().toISOString(),\n model,\n results,\n summary: {\n \"raw-shell\": aggregateMetrics(results, \"raw-shell\"),\n sverka: aggregateMetrics(results, \"sverka\"),\n },\n };\n}\n\n/** Run a single task with one agent. */\nasync function runTask(\n task: Task,\n agent: AgentConfig,\n model: string,\n): Promise<RunResult> {\n const workspace = await setupWorkspace(agent);\n const startTime = Date.now();\n let proc: ChildProcess | null = null;\n\n try {\n proc = spawnAcpAgent(workspace, model);\n let toolCallCount = 0;\n const promptResult = await runAcpSession(\n proc,\n workspace,\n task.prompt,\n task.timeoutMs ?? 120000,\n () => {\n toolCallCount++;\n },\n );\n const metrics = extractMetrics(promptResult, toolCallCount, startTime);\n return {\n taskId: task.id,\n agentId: agent.id,\n agentType: agent.type,\n metrics,\n success: metrics.stopReason === \"end_turn\",\n };\n } catch (error) {\n const metrics = extractMetrics(\n { stopReason: \"cancelled\", usage: null },\n 0,\n startTime,\n );\n return {\n taskId: task.id,\n agentId: agent.id,\n agentType: agent.type,\n metrics,\n success: false,\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n proc?.kill(\"SIGTERM\");\n }\n}\n\n/** Set up workspace for an agent.\n *\n * Both agents work in the actual Sverka repo so they have the real project\n * to operate on. The only difference is that the \"sverka\" agent gets the\n * Sverka skill installed in .agents/skills/sverka/.\n *\n * We do NOT create a temp copy — copying node_modules + dist is too slow\n * and the agents need the real build artifacts to run `sverka` commands.\n */\nasync function setupWorkspace(agent: AgentConfig): Promise<string> {\n const here = dirname(fileURLToPath(import.meta.url));\n const repoRoot = resolve(here, \"..\", \"..\", \"..\");\n const workspace = repoRoot;\n\n if (agent.type === \"sverka\") {\n // Skill is already in the repo at .agents/skills/sverka/SKILL.md\n // No need to copy — just ensure the directory exists\n const skillDir = join(workspace, \".agents\", \"skills\", \"sverka\");\n const skillFile = join(skillDir, \"SKILL.md\");\n try {\n await mkdir(skillDir, { recursive: true });\n const repoSkillPath = resolveRepoFile(\".agents/skills/sverka/SKILL.md\");\n if (repoSkillPath && resolve(repoSkillPath) !== resolve(skillFile)) {\n await cp(repoSkillPath, skillFile, { recursive: true });\n }\n } catch {\n // Skill already exists — that's fine\n }\n }\n\n return workspace;\n}\n\n/** Spawn `devin acp` as a subprocess in the given workspace. */\nfunction spawnAcpAgent(workspace: string, model: string): ChildProcess {\n const env = sanitizeEnv({\n model: { id: model, name: model },\n workspace,\n plugins: [],\n });\n return spawn(\"devin\", [\"acp\", \"--model\", model], { // NOSONAR — PATH needed for devin binary\n cwd: workspace,\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n env,\n });\n}\n\n/** Write benchmark results to a JSON file. */\nexport async function writeReport(\n result: BenchmarkResult,\n outputPath: string,\n): Promise<void> {\n const json = JSON.stringify(result, null, 2);\n await writeFile(outputPath, json, \"utf-8\");\n}\n\n/** Resolve a file path relative to the repo root. */\nfunction resolveRepoFile(relPath: string): string | null {\n try {\n const here = dirname(fileURLToPath(import.meta.url));\n // From packages/benchmark/src/ or packages/benchmark/dist/ go up to repo root\n const repoRoot = resolve(here, \"..\", \"..\", \"..\");\n return join(repoRoot, relPath);\n } catch {\n return null;\n }\n}\n","import type { Task, AgentConfig } from \"./types.js\";\n\n/** 5 task scenarios, ordered simple → complex. */\nexport const BENCHMARK_TASKS: readonly Task[] = [\n {\n id: \"run-checks\",\n name: \"run-checks\",\n prompt:\n \"Run all quality checks (lint, typecheck, test, build) for this TypeScript project and report the results. If any check fails, report which one failed and why.\",\n timeoutMs: 120000,\n },\n {\n id: \"discover\",\n name: \"discover\",\n prompt:\n \"Discover what checks are available in this project by reading package.json scripts and looking for config files. List each check with its command.\",\n timeoutMs: 60000,\n },\n {\n id: \"create-config\",\n name: \"create-config\",\n prompt:\n \"Create a CI workflow configuration file for this project that runs lint, typecheck, test, and build in dependency order. The config should define steps with dependencies: lint and typecheck run first (parallel), test depends on both, build depends on test.\",\n timeoutMs: 120000,\n },\n {\n id: \"compile-github\",\n name: \"compile-github\",\n prompt:\n \"Compile a CI workflow to GitHub Actions YAML format for this project. The workflow should run lint, typecheck, test, and build on push events. Output the complete YAML.\",\n timeoutMs: 120000,\n },\n {\n id: \"multi-step\",\n name: \"multi-step\",\n prompt:\n \"Create a multi-step pipeline with dependencies: lint and typecheck run in parallel, test depends on both, build depends on test. Define the pipeline as code and validate it works correctly.\",\n timeoutMs: 120000,\n },\n] as const;\n\n/** Default agent configs: raw-shell (no sverka) vs sverka (CLI + skill). */\nexport const DEFAULT_AGENTS: readonly AgentConfig[] = [\n {\n id: \"raw-shell\",\n name: \"Raw Shell Agent\",\n type: \"raw-shell\",\n },\n {\n id: \"sverka\",\n name: \"Sverka CLI Agent\",\n type: \"sverka\",\n },\n] as const;\n","/**\n * Transcript collector — reads Devin CLI transcripts and transforms them\n * to trace data for the benchmark trace viewer.\n *\n * Transcript source: ~/.local/share/devin/cli/transcripts/<session-id>.json\n * Each transcript has steps[] with source, message, timestamp, and extra.telemetry.\n * Agent steps with telemetry.operation === \"inference\" are LLM calls.\n */\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\n\n// ─── Transcript source types (raw JSON from Devin CLI) ───────────────\n\nexport interface TranscriptStep {\n step_id: number;\n source: \"system\" | \"agent\" | \"user\";\n message: string;\n timestamp: string;\n extra?: {\n generation_model?: string | null;\n telemetry?: {\n source?: string;\n operation?: string;\n };\n };\n}\n\nexport interface TranscriptMetrics {\n total_prompt_tokens: number;\n total_completion_tokens: number;\n total_cached_tokens: number;\n total_steps: number;\n}\n\nexport interface Transcript {\n session_id: string;\n schema_version: string;\n agent: {\n name: string;\n version: string;\n model_name: string;\n };\n final_metrics: TranscriptMetrics;\n steps: TranscriptStep[];\n}\n\n// ─── Trace data types (transformed for viewer) ───────────────────────\n\nexport interface TraceStep {\n stepId: number;\n source: \"system\" | \"agent\" | \"user\";\n timestamp: string;\n message: string;\n isLlmCall: boolean;\n}\n\nexport interface TraceAgentData {\n sessionId: string;\n model: string;\n finalMetrics: {\n totalPromptTokens: number;\n totalCompletionTokens: number;\n totalCachedTokens: number;\n totalSteps: number;\n };\n llmCallCount: number;\n steps: TraceStep[];\n}\n\nexport interface TraceData {\n taskId: string;\n prompt: string;\n agents: {\n \"raw-shell\"?: TraceAgentData;\n sverka?: TraceAgentData;\n };\n}\n\n// ─── Collector functions ─────────────────────────────────────────────\n\n/** Default transcript directory. */\nexport function transcriptDir(): string {\n return join(homedir(), \".local\", \"share\", \"devin\", \"cli\", \"transcripts\");\n}\n\n/** Read a transcript JSON file by session ID. */\nexport function readTranscript(\n sessionId: string,\n dir?: string,\n): Transcript {\n const path = join(dir ?? transcriptDir(), `${sessionId}.json`);\n if (!existsSync(path)) {\n throw new Error(`Transcript not found: ${path}`);\n }\n return JSON.parse(readFileSync(path, \"utf-8\")) as Transcript;\n}\n\n/** Transform a raw transcript step to a trace step. */\nexport function transformStep(step: TranscriptStep): TraceStep {\n const isLlmCall =\n step.source === \"agent\" &&\n step.extra?.telemetry?.operation === \"inference\";\n return {\n stepId: step.step_id,\n source: step.source,\n timestamp: step.timestamp,\n message: step.message,\n isLlmCall,\n };\n}\n\n/** Count LLM inference calls in a transcript. */\nexport function countLlmCalls(transcript: Transcript): number {\n return transcript.steps.filter(\n (s) =>\n s.source === \"agent\" &&\n s.extra?.telemetry?.operation === \"inference\",\n ).length;\n}\n\n/** Transform a full transcript to agent trace data. */\nexport function transformTranscript(\n transcript: Transcript,\n agentType: \"raw-shell\" | \"sverka\",\n): TraceAgentData {\n return {\n sessionId: transcript.session_id,\n model: transcript.agent.model_name,\n finalMetrics: {\n totalPromptTokens: transcript.final_metrics.total_prompt_tokens,\n totalCompletionTokens: transcript.final_metrics.total_completion_tokens,\n totalCachedTokens: transcript.final_metrics.total_cached_tokens,\n totalSteps: transcript.final_metrics.total_steps,\n },\n llmCallCount: countLlmCalls(transcript),\n steps: transcript.steps.map(transformStep),\n };\n}\n\n/** Build a combined trace data object for a task from two transcripts. */\nexport function buildTraceData(\n taskId: string,\n prompt: string,\n rawShellSessionId: string,\n sverkaSessionId: string,\n dir?: string,\n): TraceData {\n const rawTranscript = readTranscript(rawShellSessionId, dir);\n const sverkaTranscript = readTranscript(sverkaSessionId, dir);\n return {\n taskId,\n prompt,\n agents: {\n \"raw-shell\": transformTranscript(rawTranscript, \"raw-shell\"),\n sverka: transformTranscript(sverkaTranscript, \"sverka\"),\n },\n };\n}\n\n/** Write trace data to the benchmark traces directory. */\nexport async function writeTraceData(\n trace: TraceData,\n outputDir: string,\n): Promise<string> {\n // Reject path traversal in taskId to prevent writing outside outputDir\n if (trace.taskId.includes(\"..\") || trace.taskId.includes(\"/\") || trace.taskId.includes(\"\\\\\")) {\n throw new Error(`Invalid taskId for trace output: ${trace.taskId}`);\n }\n const taskDir = join(outputDir, trace.taskId);\n await mkdir(taskDir, { recursive: true });\n const path = join(taskDir, \"trace.json\");\n await writeFile(path, JSON.stringify(trace, null, 2), \"utf-8\");\n return path;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
{
|
|
2
|
-
"bugs": {
|
|
3
|
-
"url": "https://github.com/sverka-dev/sverka/issues"
|
|
4
|
-
},
|
|
5
|
-
"description": "Placeholder for @sverka/benchmark published by @nx-devkit/prepare-for-release.",
|
|
6
|
-
"homepage": "https://sverka.dev",
|
|
7
|
-
"license": "MIT",
|
|
8
2
|
"name": "@sverka/benchmark",
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
},
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Sverka benchmark arena — two-agent comparison harness",
|
|
5
|
+
"license": "MIT",
|
|
13
6
|
"repository": {
|
|
14
7
|
"type": "git",
|
|
15
8
|
"url": "https://github.com/sverka-dev/sverka",
|
|
16
9
|
"directory": "packages/benchmark"
|
|
17
10
|
},
|
|
11
|
+
"homepage": "https://sverka.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/sverka-dev/sverka/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
18
|
"type": "module",
|
|
19
|
-
"
|
|
20
|
-
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.5.0"
|
|
21
|
+
},
|
|
22
|
+
"main": "./dist/index.mjs",
|
|
23
|
+
"module": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.mts",
|
|
28
|
+
"import": "./dist/index.mjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"sverka-benchmark": "./dist/bin.mjs"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"test": "vitest run --passWithNoTests",
|
|
40
|
+
"lint": "eslint src",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@agentclientprotocol/sdk": "^1.4.0",
|
|
45
|
+
"@sverka/arena": "^0.1.2"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"tsdown": "^0.23.0",
|
|
49
|
+
"typescript": "^5.8.0",
|
|
50
|
+
"vitest": "^5.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|