@qvac/harness 1.0.0-rc.2 → 1.0.0-rc.3

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.
Files changed (58) hide show
  1. package/README.md +12 -12
  2. package/bench/README.md +94 -0
  3. package/bench/bfcl/checker.ts +313 -0
  4. package/bench/bfcl/data.ts +93 -0
  5. package/bench/bfcl/observe.ts +93 -0
  6. package/bench/bfcl/run.ts +213 -0
  7. package/bench/bfcl/tools.ts +72 -0
  8. package/bench/bridge/bridge.ts +392 -0
  9. package/bench/bridge/openai.ts +150 -0
  10. package/bench/bridge/python/qvac_bench/__init__.py +3 -0
  11. package/bench/bridge/python/qvac_bench/airline.py +42 -0
  12. package/bench/bridge/python/qvac_bench/approval.py +26 -0
  13. package/bench/bridge/python/qvac_bench/bfcl_multi_turn.py +152 -0
  14. package/bench/bridge/python/qvac_bench/client.py +88 -0
  15. package/bench/bridge/python/qvac_bench/harbor_agent.py +85 -0
  16. package/bench/bridge/python/qvac_bench/session.py +107 -0
  17. package/bench/bridge/python/qvac_bench/speed.py +30 -0
  18. package/bench/bridge/python/qvac_bench/tau2_agent.py +106 -0
  19. package/bench/bridge/python/qvac_bench/tools.py +11 -0
  20. package/bench/bridge/python/run_bfcl_multi_turn.py +95 -0
  21. package/bench/bridge/python/run_tau2.py +150 -0
  22. package/bench/bridge/python/smoke.py +47 -0
  23. package/bench/bridge/python/test_airline.py +28 -0
  24. package/bench/bridge/python/test_approval.py +57 -0
  25. package/bench/bridge/python/test_bfcl_multi_turn.py +185 -0
  26. package/bench/bridge/python/test_harbor_agent.py +107 -0
  27. package/bench/bridge/serve.ts +52 -0
  28. package/bench/bridge/transcript.ts +52 -0
  29. package/bench/bun-tsconfig.json +15 -0
  30. package/bench/cpu-main.mjs +216 -0
  31. package/bench/cpu.ts +457 -0
  32. package/bench/evidence/2026-09-28-review15.md +225 -0
  33. package/bench/evidence/redact-bun1.3.14.json +92 -0
  34. package/bench/evidence/redact-node24.11.json +92 -0
  35. package/bench/graph-metrics.ts +43 -0
  36. package/bench/harness.ts +73 -0
  37. package/bench/issue129-matrix.mjs +722 -0
  38. package/bench/redact-adversarial.ts +256 -0
  39. package/bench/repeat-cpu.mjs +281 -0
  40. package/bench/report.ts +1075 -0
  41. package/bench/skill-eval/harness.ts +30 -0
  42. package/bench/skill-eval/laws.ts +37 -0
  43. package/bench/skill-eval/mermaid/client.ts +132 -0
  44. package/bench/skill-eval/mermaid/validate.mjs +115 -0
  45. package/bench/skill-eval/pack.ts +61 -0
  46. package/bench/skill-eval/packs/diagrams/cases.ts +281 -0
  47. package/bench/skill-eval/packs/diagrams/index.ts +50 -0
  48. package/bench/skill-eval/packs/diagrams/score.ts +74 -0
  49. package/bench/skill-eval/packs/index.ts +5 -0
  50. package/bench/skill-eval/packs/weather/cases.ts +279 -0
  51. package/bench/skill-eval/packs/weather/fixtures/wttr.json +566 -0
  52. package/bench/skill-eval/packs/weather/fixtures.ts +84 -0
  53. package/bench/skill-eval/packs/weather/index.ts +55 -0
  54. package/bench/skill-eval/packs/weather/score.ts +92 -0
  55. package/bench/skill-eval/report.ts +194 -0
  56. package/bench/skill-eval/run.ts +316 -0
  57. package/bench/speed.ts +80 -0
  58. package/package.json +4 -13
@@ -0,0 +1,185 @@
1
+ """output/venvs/bfcl/bin/python -m unittest test_bfcl_multi_turn, from this folder: no model is loaded.
2
+
3
+ A scripted model plays BFCL's own ground truth through the adapter, so BFCL's checker must pass it;
4
+ the same script with a turn dropped or an argument changed must fail as BFCL fails it.
5
+ """
6
+
7
+ import ast
8
+ import asyncio
9
+ import itertools
10
+ import unittest
11
+
12
+ import json
13
+
14
+ from bfcl_eval.constants.default_prompts import DEFAULT_USER_PROMPT_FOR_ADDITIONAL_FUNCTION_FC, MAXIMUM_STEP_LIMIT
15
+ from bfcl_eval.eval_checker.multi_turn_eval.multi_turn_utils import execute_multi_turn_func_call
16
+
17
+ from qvac_bench.bfcl_multi_turn import CATEGORIES, Episode, executed, load
18
+
19
+ names = (f"test_{index}" for index in itertools.count())
20
+
21
+
22
+ def parse(source: str, tools: list[dict]) -> tuple[str, dict]:
23
+ call = ast.parse(source, mode="eval").body
24
+ name = call.func.id
25
+ params = next(tool for tool in tools if tool["name"] == name)["parameters"]["properties"]
26
+ args = dict(zip(params, (ast.literal_eval(arg) for arg in call.args)))
27
+ args.update({keyword.arg: ast.literal_eval(keyword.value) for keyword in call.keywords})
28
+ return name, args
29
+
30
+
31
+ def scripted(answer: list[list[str]], change=lambda turn, name, args: args, seen=None, refused=None):
32
+ """A run that makes one turn's ground-truth calls, one round each, and returns the bridge's done frame."""
33
+ turns = iter(answer)
34
+
35
+ async def run(messages, tools, execute):
36
+ if seen is not None:
37
+ seen.append((list(messages), [tool["name"] for tool in tools]))
38
+ turn = next(turns)
39
+ history = []
40
+ if refused:
41
+ history.append({"role": "assistant", "content": "", "toolCalls": [refused]})
42
+ for index, source in enumerate(turn):
43
+ name, args = parse(source, tools)
44
+ args = change(turn, name, args)
45
+ call_id = f"c{index}"
46
+ result = await execute(name, args)
47
+ history.append({"role": "assistant", "content": "", "toolCalls": [{"id": call_id, "name": name, "arguments": args}]})
48
+ history.append({"role": "tool", "toolCallId": call_id, "name": name, "content": result})
49
+ history.append({"role": "assistant", "content": "Done."})
50
+ return {"type": "done", "reply": "Done.", "calls": len(turn), "error": None, "metrics": None, "history": history}
51
+
52
+ return run
53
+
54
+
55
+ def play(entry, answer, run):
56
+ return asyncio.run(Episode(entry, answer, next(names)).play(run))
57
+
58
+
59
+ def stepping(steps: int):
60
+ """A run that makes the same call for `steps` rounds every turn."""
61
+
62
+ async def run(messages, tools, execute):
63
+ history = []
64
+ for _ in range(steps):
65
+ await execute("pwd", {})
66
+ history.append({"role": "assistant", "content": "", "toolCalls": [{"id": "c", "name": "pwd", "arguments": {}}]})
67
+ return {"reply": "", "error": None, "metrics": None, "history": history}
68
+
69
+ return run
70
+
71
+
72
+ class GroundTruth(unittest.TestCase):
73
+ def test_bfcl_passes_its_own_answers_in_every_category(self):
74
+ for category in CATEGORIES:
75
+ for entry, answer in load(category, 3):
76
+ with self.subTest(entry=entry["id"]):
77
+ self.assertEqual(play(entry, answer, scripted(answer))["valid"], True)
78
+
79
+ def test_a_missed_function_is_offered_only_from_its_turn(self):
80
+ entry, answer = load("multi_turn_miss_func", 1)[0]
81
+ [(turn, [missed])] = entry["missed_function"].items()
82
+ seen = []
83
+ play(entry, answer, scripted(answer, seen=seen))
84
+
85
+ self.assertNotIn(missed["name"], seen[int(turn) - 1][1])
86
+ self.assertIn(missed["name"], seen[int(turn)][1])
87
+ self.assertEqual(seen[int(turn)][0][-1], {"role": "user", "content": DEFAULT_USER_PROMPT_FOR_ADDITIONAL_FUNCTION_FC})
88
+
89
+ def test_each_turn_sees_the_conversation_so_far(self):
90
+ entry, answer = load("multi_turn_base", 1)[0]
91
+ seen = []
92
+ play(entry, answer, scripted(answer, seen=seen))
93
+
94
+ second = seen[1][0]
95
+ self.assertEqual(second[0], entry["question"][0][0])
96
+ self.assertEqual(second[-1], entry["question"][1][0])
97
+ self.assertTrue(any(message.get("toolCalls") for message in second))
98
+
99
+
100
+ class Failures(unittest.TestCase):
101
+ def setUp(self):
102
+ self.entry, self.answer = load("multi_turn_base", 1)[0]
103
+
104
+ def test_a_turn_without_its_calls_fails_as_an_empty_turn(self):
105
+ dropped = [self.answer[0], []] + self.answer[2:]
106
+ verdict = play(self.entry, self.answer, scripted(dropped))
107
+
108
+ self.assertEqual(verdict["error_type"], "multi_turn:empty_turn_model_response")
109
+
110
+ def test_a_wrong_argument_fails_on_the_backend_state(self):
111
+ def rename(turn, name, args):
112
+ return {**args, "dir_name": "elsewhere"} if name == "mkdir" else args
113
+
114
+ verdict = play(self.entry, self.answer, scripted(self.answer, rename))
115
+
116
+ self.assertEqual(verdict["error_type"], "multi_turn:instance_state_mismatch")
117
+
118
+ def test_a_harness_error_fails_the_entry_as_bfcl_fails_a_raised_inference(self):
119
+ async def broken(messages, tools, execute):
120
+ return {"reply": "", "error": "context overflow", "metrics": None, "history": []}
121
+
122
+ verdict = play(self.entry, self.answer, broken)
123
+
124
+ self.assertEqual((verdict["error_type"], verdict["error"]), ("multi_turn:inference_error", "context overflow"))
125
+
126
+ def test_a_turn_past_the_step_limit_is_force_terminated(self):
127
+ verdict = play(self.entry, self.answer, stepping(MAXIMUM_STEP_LIMIT + 1))
128
+
129
+ self.assertEqual(verdict["error_type"], "multi_turn:force_terminated")
130
+
131
+ def test_a_turn_at_the_step_limit_is_scored(self):
132
+ verdict = play(self.entry, self.answer, stepping(MAXIMUM_STEP_LIMIT))
133
+
134
+ self.assertNotEqual(verdict["error_type"], "multi_turn:force_terminated")
135
+
136
+ def test_a_call_the_harness_refused_is_not_scored(self):
137
+ cut_off = {"id": "x", "name": "mkdir", "arguments": {"dir_name": "te"}}
138
+
139
+ self.assertTrue(play(self.entry, self.answer, scripted(self.answer, refused=cut_off))["valid"])
140
+
141
+
142
+ def test_refused_calls_leave_the_rounds_that_ran_as_steps(self):
143
+ def turn(*calls):
144
+ return {"role": "assistant", "content": "", "toolCalls": [{"id": "x", "name": name, "arguments": args} for name, args in calls]}
145
+
146
+ history = [
147
+ turn(("cd", {"folder": "do"}), ("ls", {"a": "tr"})),
148
+ turn(("cd", {"folder": "document"})),
149
+ turn(("ls", {"a": True})),
150
+ ]
151
+ ran = [{"cd": {"folder": "document"}}, {"ls": {"a": True}}]
152
+
153
+ self.assertEqual(executed(history, ran), [[ran[0]], [ran[1]]])
154
+
155
+
156
+ class Execution(unittest.TestCase):
157
+ def test_a_call_returns_what_bfcl_sends_the_model(self):
158
+ entry, answer = load("multi_turn_base", 1)[0]
159
+ own, _ = execute_multi_turn_func_call(
160
+ ["cd(folder='document')"], entry["initial_config"], entry["involved_classes"], next(names), entry["id"]
161
+ )
162
+ result = asyncio.run(Episode(entry, answer, next(names)).execute("cd", {"folder": "document"}))
163
+
164
+ self.assertEqual(result, json.loads(own[0]), "a JSON result goes out as the object, so it is encoded once")
165
+
166
+ def test_a_text_result_stays_text(self):
167
+ entry, answer = load("multi_turn_base", 1)[0]
168
+ result = asyncio.run(Episode(entry, answer, next(names)).execute("cd", {"path": "document"}))
169
+
170
+ self.assertTrue(result.startswith("Error during execution: "), result)
171
+
172
+ def test_a_long_context_entry_runs_the_long_backend(self):
173
+ def listing(category):
174
+ entry, answer = load(category, 1)[0]
175
+ episode = Episode(entry, answer, next(names))
176
+ asyncio.run(episode.execute("cd", {"folder": "document"}))
177
+ return asyncio.run(episode.execute("ls", {}))
178
+
179
+ base, long = listing("multi_turn_base"), listing("multi_turn_long_context")
180
+
181
+ self.assertGreater(len(long["current_directory_content"]), len(base["current_directory_content"]))
182
+
183
+
184
+ if __name__ == "__main__":
185
+ unittest.main()
@@ -0,0 +1,107 @@
1
+ """python3 -m unittest test_harbor_agent, from this folder: harbor is not needed."""
2
+
3
+ import asyncio
4
+ import json
5
+ import os
6
+ import sys
7
+ import tempfile
8
+ import types
9
+ import unittest
10
+ from unittest import mock
11
+
12
+
13
+ class BaseAgent:
14
+ def __init__(self, logs_dir, model_name=None, **kwargs):
15
+ self.logs_dir = logs_dir
16
+ self.model_name = model_name
17
+
18
+
19
+ for name, attrs in {
20
+ "harbor": {},
21
+ "harbor.agents": {},
22
+ "harbor.agents.base": {"BaseAgent": BaseAgent},
23
+ "harbor.environments": {},
24
+ "harbor.environments.base": {"BaseEnvironment": object},
25
+ "harbor.models": {},
26
+ "harbor.models.agent": {},
27
+ "harbor.models.agent.context": {"AgentContext": object},
28
+ }.items():
29
+ sys.modules.setdefault(name, types.ModuleType(name)).__dict__.update(attrs)
30
+
31
+ from qvac_bench import harbor_agent # noqa: E402
32
+
33
+ DONE = {
34
+ "type": "done",
35
+ "id": "run-0",
36
+ "reply": "Done.",
37
+ "calls": 1,
38
+ "error": None,
39
+ "metrics": {"rounds": [{"promptTokens": 300, "generatedTokens": 20}, {"promptTokens": 50, "generatedTokens": 5}]},
40
+ }
41
+
42
+
43
+ class FakeBridge:
44
+ runs = []
45
+ results = []
46
+
47
+ def __init__(self, repo, model):
48
+ pass
49
+
50
+ async def start(self):
51
+ pass
52
+
53
+ async def run(self, messages, tools, execute, system=None, max_tool_rounds=None):
54
+ FakeBridge.runs.append(max_tool_rounds)
55
+ FakeBridge.results.append(await execute("bash", {"command": "ls"}))
56
+ return DONE
57
+
58
+ async def close(self):
59
+ pass
60
+
61
+
62
+ class Environment:
63
+ async def exec(self, command, timeout_sec=None):
64
+ return types.SimpleNamespace(stdout="a b", stderr="", return_code=0)
65
+
66
+
67
+ class TimeoutEnvironment:
68
+ async def exec(self, command, timeout_sec=None):
69
+ raise RuntimeError(f"Command timed out after {timeout_sec} seconds")
70
+
71
+
72
+ def run_agent(environment, max_tool_rounds="40"):
73
+ with tempfile.TemporaryDirectory() as logs, mock.patch.object(harbor_agent, "QvacBridge", FakeBridge), \
74
+ mock.patch.dict(os.environ, {"QVAC_HARNESS_REPO": "/repo"}):
75
+ agent = harbor_agent.QvacHarnessAgent(logs_dir=logs, model_name="m", max_tool_rounds=max_tool_rounds)
76
+ context = types.SimpleNamespace()
77
+ asyncio.run(agent.run("list the files", environment, context))
78
+ with open(os.path.join(logs, "qvac.jsonl")) as log:
79
+ return [json.loads(line) for line in log], context
80
+
81
+
82
+ class Agent(unittest.TestCase):
83
+ def setUp(self):
84
+ FakeBridge.runs = []
85
+ FakeBridge.results = []
86
+
87
+ def test_the_round_limit_reaches_the_run_and_the_log_records_it(self):
88
+ rows, context = run_agent(Environment())
89
+
90
+ self.assertEqual(FakeBridge.runs, [40])
91
+ self.assertEqual([row["type"] for row in rows], ["start", "call", "done", "end"])
92
+ self.assertEqual(rows[0]["max_tool_rounds"], 40)
93
+ self.assertEqual(len(rows[0]["load"]), 3)
94
+ self.assertEqual((rows[1]["command"], rows[1]["return_code"]), ("ls", 0))
95
+ self.assertEqual(rows[2], DONE)
96
+ self.assertEqual((context.n_input_tokens, context.n_output_tokens), (350, 25))
97
+
98
+ def test_a_command_timeout_returns_a_failed_result_and_the_trial_goes_on(self):
99
+ rows, _ = run_agent(TimeoutEnvironment())
100
+
101
+ self.assertEqual(FakeBridge.results, [{"stdout": "", "stderr": "Command timed out after 180 seconds", "return_code": 124}])
102
+ self.assertEqual([row["type"] for row in rows], ["start", "call", "done", "end"])
103
+ self.assertEqual((rows[1]["command"], rows[1]["return_code"]), ("ls", 124))
104
+
105
+
106
+ if __name__ == "__main__":
107
+ unittest.main()
@@ -0,0 +1,52 @@
1
+ // a benchmark in another process drives the harness over stdio: bare bench/bridge/serve.ts
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import process from 'process'
5
+ import { fileURLToPath } from 'url'
6
+ import { ensureBundledSkillsDir } from '../../lib/tools/skills/bundled-dir.ts'
7
+ import { openBenchHarness } from '../harness.ts'
8
+ import { Bridge, FRAME, type Inbound, type Outbound } from './bridge.ts'
9
+
10
+ const REPO_ROOT = fileURLToPath(new URL('../..', import.meta.url))
11
+ const logFile = process.env.QVAC_BRIDGE_LOG ?? path.join(REPO_ROOT, 'output', 'bridge.log')
12
+
13
+ fs.mkdirSync(path.dirname(logFile), { recursive: true })
14
+ const log = fs.openSync(logFile, 'a')
15
+ // stdout carries the frames; everything the SDK says goes to the log
16
+ console.log =
17
+ console.info =
18
+ console.debug =
19
+ console.warn =
20
+ console.error =
21
+ (...args: unknown[]) => fs.writeSync(log, `${args.map(String).join(' ')}\n`)
22
+
23
+ process.env.QVAC_NO_SKILL_BIN_PROBE = '1'
24
+ const harness = await openBenchHarness({
25
+ root: path.join(REPO_ROOT, 'output', 'bench', 'sdk-root'),
26
+ skillsDir: await ensureBundledSkillsDir()
27
+ })
28
+ const send = (frame: Outbound) => process.stdout.write(`${FRAME}${JSON.stringify(frame)}\n`)
29
+ const bridge = new Bridge((context) => harness.completion(context), send, {
30
+ plan: process.env.QVAC_BRIDGE_PLAN === '1',
31
+ forget: (cacheKey) => harness.deleteKvCache(cacheKey)
32
+ })
33
+
34
+ let buffered = ''
35
+ process.stdin.on('data', (chunk: Uint8Array) => {
36
+ buffered += chunk.toString()
37
+ for (let newline = buffered.indexOf('\n'); newline >= 0; newline = buffered.indexOf('\n')) {
38
+ const line = buffered.slice(0, newline).trim()
39
+ buffered = buffered.slice(newline + 1)
40
+ if (line) {
41
+ receive(JSON.parse(line))
42
+ }
43
+ }
44
+ })
45
+ process.stdin.on('end', () => {
46
+ harness.close().then(() => process.exit(0))
47
+ })
48
+ send({ type: 'ready' })
49
+
50
+ function receive(frame: Inbound): void {
51
+ bridge.receive(frame).catch((err: unknown) => console.error('[bridge]', err))
52
+ }
@@ -0,0 +1,52 @@
1
+ import type { CompletionEvent, Message } from '../../lib/contract/index.ts'
2
+
3
+ // A run's turns as a host replays them: one assistant turn per round, then a tool turn per call.
4
+ export class Transcript {
5
+ private readonly _turns: Message[] = []
6
+ private readonly _rounds = new Map<number, Message>()
7
+
8
+ add(event: CompletionEvent): void {
9
+ const round = event.round ?? 0
10
+ if (event.type === 'content') {
11
+ this._answer(round).content += event.text
12
+ } else if (event.type === 'tool-call') {
13
+ const call = { id: event.callId ?? '', name: event.name, arguments: event.args }
14
+ const turn = this._round(round)
15
+ turn.toolCalls = [...(turn.toolCalls ?? []), call]
16
+ } else if (event.type === 'tool-result') {
17
+ const content = JSON.stringify(event.result) ?? ''
18
+ this._turns.push({ role: 'tool', toolCallId: event.callId ?? '', name: event.name, content })
19
+ } else if (event.type === 'round-reset') {
20
+ this._round(round).content = ''
21
+ }
22
+ }
23
+
24
+ turns(): Message[] {
25
+ return this._turns.filter(
26
+ (turn) => turn.role !== 'assistant' || turn.content || turn.toolCalls?.length
27
+ )
28
+ }
29
+
30
+ // a wrap-up streams under its last tool round, but it answers that round's results
31
+ private _answer(round: number): Message {
32
+ const turn = this._round(round)
33
+ if (turn === this._turns.at(-1)) {
34
+ return turn
35
+ }
36
+
37
+ this._rounds.delete(round)
38
+
39
+ return this._round(round)
40
+ }
41
+
42
+ private _round(round: number): Message {
43
+ let turn = this._rounds.get(round)
44
+ if (!turn) {
45
+ turn = { role: 'assistant', content: '' }
46
+ this._rounds.set(round, turn)
47
+ this._turns.push(turn)
48
+ }
49
+
50
+ return turn
51
+ }
52
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "erasableSyntaxOnly": true,
7
+ "verbatimModuleSyntax": true,
8
+ "allowImportingTsExtensions": true,
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "allowJs": true,
12
+ "checkJs": false,
13
+ "noEmit": true
14
+ }
15
+ }
@@ -0,0 +1,216 @@
1
+ // Pre-state-machine CPU baseline runner.
2
+ import process from 'process'
3
+ import { CompletionEngine } from '../lib/capabilities/completion-engine/index.ts'
4
+
5
+ const TOKENS = 2000
6
+ const FIRST_TOKEN_MS = 110
7
+
8
+ function options() {
9
+ const args = process.argv.slice(2)
10
+ let runs = 3
11
+ let spacing = 'all'
12
+ let workload = 'all'
13
+ let json = false
14
+ let positionalRuns = false
15
+ for (const arg of args) {
16
+ if (arg === '--json') {
17
+ json = true
18
+ } else if (arg.startsWith('--spacing=')) {
19
+ spacing = arg.slice('--spacing='.length)
20
+ } else if (arg.startsWith('--workload=')) {
21
+ workload = arg.slice('--workload='.length)
22
+ } else if (!arg.startsWith('-') && !positionalRuns) {
23
+ runs = Number(arg)
24
+ positionalRuns = true
25
+ } else {
26
+ throw new Error(`Unknown option: ${arg}`)
27
+ }
28
+ }
29
+
30
+ if (!Number.isInteger(runs) || runs < 1) {
31
+ throw new Error('runs must be a positive integer')
32
+ }
33
+
34
+ const spacings = spacing === 'all' ? ['immediate', 'timeout'] : [spacing]
35
+ const workloads = workload === 'all' ? ['tools', 'stream'] : [workload]
36
+ if (spacings.some((value) => !['immediate', 'timeout'].includes(value))) {
37
+ throw new Error(`Unknown spacing: ${spacing}`)
38
+ }
39
+
40
+ if (workloads.some((value) => !['tools', 'stream'].includes(value))) {
41
+ throw new Error(`Unknown workload: ${workload}`)
42
+ }
43
+
44
+ return { runs, spacings, workloads, json }
45
+ }
46
+
47
+ async function collect(run) {
48
+ const events = []
49
+ for await (const event of run) {
50
+ events.push(event)
51
+ }
52
+
53
+ return events
54
+ }
55
+
56
+ function cpuMs(start) {
57
+ const usage = process.cpuUsage(start)
58
+ return (usage.user + usage.system) / 1000
59
+ }
60
+
61
+ function median(values) {
62
+ const sorted = [...values].sort((a, b) => a - b)
63
+ return sorted[Math.floor(sorted.length / 2)]
64
+ }
65
+
66
+ function history() {
67
+ return Array.from({ length: 101 }, (_, i) => ({
68
+ role: i % 2 ? 'assistant' : 'user',
69
+ content: `${i} ${'x'.repeat(2000)}`
70
+ }))
71
+ }
72
+
73
+ function readTool(name) {
74
+ return {
75
+ name,
76
+ description: 'read something',
77
+ toolset: null,
78
+ parameters: { type: 'object', properties: { path: { type: 'string' } } },
79
+ effect: () => 'read',
80
+ autoApprove: true,
81
+ execute: () => Promise.resolve({ content: `${name} ${'r'.repeat(2048)}` })
82
+ }
83
+ }
84
+
85
+ function toolWorkloadSdk(tools) {
86
+ let round = 0
87
+ return {
88
+ completion: () => {
89
+ const at = round++
90
+ if (at < 3) {
91
+ return {
92
+ tokenStream: (async function* () {})(),
93
+ final: Promise.resolve({
94
+ toolCalls: tools.map((tool, i) => ({
95
+ id: `call-${at}-${i}`,
96
+ name: tool.name,
97
+ arguments: { path: `f${at}${i}` }
98
+ })),
99
+ raw: { fullText: '' }
100
+ })
101
+ }
102
+ }
103
+
104
+ return {
105
+ tokenStream: (async function* () {
106
+ yield 'done'
107
+ })(),
108
+ final: Promise.resolve({ toolCalls: [], raw: { fullText: 'done' } })
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ function streamingSdk(spacing) {
115
+ return {
116
+ completion: () => ({
117
+ tokenStream: (async function* () {
118
+ await new Promise((resolve) => setTimeout(resolve, FIRST_TOKEN_MS))
119
+ for (let i = 0; i < TOKENS; i++) {
120
+ await new Promise((resolve) =>
121
+ spacing === 'immediate' ? setImmediate(resolve) : setTimeout(resolve, 0)
122
+ )
123
+ yield 't'
124
+ }
125
+ })(),
126
+ final: Promise.resolve({ toolCalls: [], raw: { fullText: 't'.repeat(TOKENS) } })
127
+ })
128
+ }
129
+ }
130
+
131
+ async function measureTools(runs, tools) {
132
+ // Match the engine benchmark's untimed pre-sample pass for each selected cell.
133
+ await collect(
134
+ CompletionEngine.run(toolWorkloadSdk(tools), {
135
+ modelId: 'm',
136
+ messages: history(),
137
+ tools
138
+ })
139
+ )
140
+
141
+ const samples = []
142
+ for (let i = 0; i < runs; i++) {
143
+ const args = { modelId: 'm', messages: history(), tools }
144
+ const start = process.cpuUsage()
145
+ await collect(CompletionEngine.run(toolWorkloadSdk(tools), args))
146
+ samples.push(cpuMs(start))
147
+ }
148
+
149
+ return {
150
+ workload: 'tools',
151
+ mode: 'main',
152
+ runs,
153
+ cpuMs: median(samples),
154
+ cpuSamplesMs: samples
155
+ }
156
+ }
157
+
158
+ async function measureStream(runs, spacing) {
159
+ // Run one untimed stream before collecting the timed samples.
160
+ await collect(CompletionEngine.run(streamingSdk(spacing), { modelId: 'm', messages: history() }))
161
+
162
+ const samples = []
163
+ let contentEvents = 0
164
+ for (let i = 0; i < runs; i++) {
165
+ const args = { modelId: 'm', messages: history() }
166
+ const start = process.cpuUsage()
167
+ const events = await collect(CompletionEngine.run(streamingSdk(spacing), args))
168
+ samples.push(cpuMs(start))
169
+ contentEvents = events.filter((event) => event.type === 'content').length
170
+ }
171
+
172
+ const cpu = median(samples)
173
+
174
+ return {
175
+ workload: 'stream',
176
+ mode: 'main',
177
+ spacing,
178
+ runs,
179
+ cpuMs: cpu,
180
+ cpuSamplesMs: samples,
181
+ usPerToken: (cpu * 1000) / TOKENS,
182
+ contentEvents
183
+ }
184
+ }
185
+
186
+ async function main() {
187
+ const { runs, spacings, workloads, json } = options()
188
+ const results = []
189
+ const tools = [readTool('read_a'), readTool('read_b')]
190
+ if (workloads.includes('tools')) {
191
+ results.push(await measureTools(runs, tools))
192
+ }
193
+
194
+ if (workloads.includes('stream')) {
195
+ for (const spacing of spacings) {
196
+ results.push(await measureStream(runs, spacing))
197
+ }
198
+ }
199
+
200
+ const report = {
201
+ runtime: globalThis.Bun ? 'bun' : (process.release?.name ?? 'unknown'),
202
+ version: globalThis.Bun?.version ?? process.version,
203
+ runs,
204
+ results
205
+ }
206
+ if (json) {
207
+ console.log(JSON.stringify(report))
208
+ } else {
209
+ console.log(JSON.stringify(report, null, 2))
210
+ }
211
+ }
212
+
213
+ main().catch((error) => {
214
+ console.error(error)
215
+ process.exitCode = 1
216
+ })