omnilane 0.12.0 → 0.14.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.
@@ -53,7 +53,7 @@ _omnilane() {
53
53
  local command="${words[2]:-}" sub sub_index=3
54
54
  local -a lanes ids
55
55
  if (( CURRENT == 2 )); then
56
- _values 'command' version list route dispatch jobs doctor release-audit ui configure completion help
56
+ _values 'command' version list route dispatch jobs doctor benchmark release-audit ui configure completion help
57
57
  return
58
58
  fi
59
59
  case "$command" in
@@ -78,7 +78,7 @@ _omnilane() {
78
78
  sub="${words[4]:-}"
79
79
  fi
80
80
  if (( CURRENT == sub_index )); then
81
- _values 'job command' list status result tail retry stats wait audit prune help
81
+ _values 'job command' list status result tail retry stats recommend wait audit prune help
82
82
  elif (( CURRENT == sub_index + 1 )) &&
83
83
  [[ "$sub" == status || "$sub" == result || "$sub" == wait ||
84
84
  "$sub" == tail || "$sub" == retry ]]; then
@@ -92,7 +92,13 @@ _omnilane() {
92
92
  _arguments '--background[re-dispatch in the background]'
93
93
  elif [[ "$sub" == wait ]]; then
94
94
  _arguments '--timeout[maximum wait seconds]:seconds:'
95
- elif [[ "$sub" == stats || "$sub" == audit ]]; then
95
+ elif [[ "$sub" == stats ]]; then
96
+ _arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
97
+ '--vendor[filter by vendor]:vendor:' '--json[versioned metadata output]'
98
+ elif [[ "$sub" == recommend ]]; then
99
+ _arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
100
+ '--min-samples[minimum completed samples]:count:' '--json[versioned metadata output]'
101
+ elif [[ "$sub" == audit ]]; then
96
102
  _arguments '--last[maximum jobs]:count:' '--json[versioned metadata output]'
97
103
  elif [[ "$sub" == prune ]]; then
98
104
  _arguments '--keep[completed jobs to retain]:count:' \
@@ -100,7 +106,18 @@ _omnilane() {
100
106
  '--apply[delete eligible jobs]'
101
107
  fi
102
108
  ;;
103
- doctor) _values 'option' --json ;;
109
+ doctor)
110
+ _arguments '--json[versioned JSON output]' '--strict[fail on warnings]' \
111
+ '--probe[run one opt-in live provider probe]:vendor:' \
112
+ '--probe-timeout[probe timeout in seconds]:seconds:'
113
+ ;;
114
+ benchmark)
115
+ _arguments '--json[versioned JSON output]' '--run[invoke providers in advise mode]' \
116
+ '*--vendor[vendor to compare]:vendor:(codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras)' \
117
+ '--timeout[per-workload timeout]:seconds:' \
118
+ '--workloads[TSV workload file]:file:_files' \
119
+ '*--cost-per-call[user-supplied vendor USD estimate]:vendor=USD:'
120
+ ;;
104
121
  release-audit)
105
122
  _arguments '--target[release version]:version:' '--allow-dirty[permit a dirty worktree]' \
106
123
  '--require-tag[require an annotated tag at HEAD]' '--manifest[include tracked inventory]' \
@@ -58,7 +58,7 @@ _omnilane() {
58
58
  prev="${COMP_WORDS[COMP_CWORD-1]:-}"
59
59
  command="${COMP_WORDS[1]:-}"
60
60
  if [[ "$COMP_CWORD" -eq 1 ]]; then
61
- words="version list route dispatch jobs doctor release-audit ui configure completion help"
61
+ words="version list route dispatch jobs doctor benchmark release-audit ui configure completion help"
62
62
  else
63
63
  case "$command" in
64
64
  route|dispatch)
@@ -84,7 +84,7 @@ _omnilane() {
84
84
  sub="${COMP_WORDS[3]:-}"
85
85
  fi
86
86
  if [[ "$COMP_CWORD" -eq "$sub_index" ]]; then
87
- words="list status result tail retry stats wait audit prune help"
87
+ words="list status result tail retry stats recommend wait audit prune help"
88
88
  elif [[ "$COMP_CWORD" -eq $((sub_index + 1)) &&
89
89
  ( "$sub" == status || "$sub" == result || "$sub" == wait ||
90
90
  "$sub" == tail || "$sub" == retry ) ]]; then
@@ -99,7 +99,11 @@ _omnilane() {
99
99
  words="--background"
100
100
  elif [[ "$sub" == wait ]]; then
101
101
  words="--timeout"
102
- elif [[ "$sub" == stats || "$sub" == audit ]]; then
102
+ elif [[ "$sub" == stats ]]; then
103
+ words="--last --lane --vendor --json"
104
+ elif [[ "$sub" == recommend ]]; then
105
+ words="--last --lane --min-samples --json"
106
+ elif [[ "$sub" == audit ]]; then
103
107
  words="--last --json"
104
108
  elif [[ "$sub" == prune ]]; then
105
109
  words="--keep --older-than --apply"
@@ -107,7 +111,18 @@ _omnilane() {
107
111
  return
108
112
  fi
109
113
  ;;
110
- doctor) words="--json" ;;
114
+ doctor) words="--json --strict --probe --probe-timeout" ;;
115
+ benchmark)
116
+ case "$prev" in
117
+ --vendor) words="codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras" ;;
118
+ --workloads)
119
+ COMPREPLY=( $(compgen -f -- "$cur") )
120
+ return
121
+ ;;
122
+ --timeout|--cost-per-call) return ;;
123
+ *) words="--json --run --vendor --timeout --workloads --cost-per-call" ;;
124
+ esac
125
+ ;;
111
126
  release-audit) words="--target --allow-dirty --require-tag --manifest --json" ;;
112
127
  ui) words="start status url stop" ;;
113
128
  completion) words="bash zsh" ;;
@@ -10,6 +10,7 @@ complete -c omnilane -f -n __fish_use_subcommand -a dispatch -d 'dispatch o
10
10
  complete -c omnilane -f -n __fish_use_subcommand -a jobs -d 'inspect background jobs'
11
11
  complete -c omnilane -f -n __fish_use_subcommand -a mcp -d 'MCP stdio server'
12
12
  complete -c omnilane -f -n __fish_use_subcommand -a doctor -d 'read-only health report'
13
+ complete -c omnilane -f -n __fish_use_subcommand -a benchmark -d 'fixed quality/cost comparison'
13
14
  complete -c omnilane -f -n __fish_use_subcommand -a release-audit -d 'offline release gate'
14
15
  complete -c omnilane -f -n __fish_use_subcommand -a ui -d 'Live Board server'
15
16
  complete -c omnilane -f -n __fish_use_subcommand -a configure -d 'lane routing (menu or set/get/unset/list/diff)'
@@ -29,19 +30,30 @@ complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l dry-run
29
30
 
30
31
  # jobs subcommands (only before a jobs subcommand is chosen).
31
32
  complete -c omnilane -f \
32
- -n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats wait audit prune cancel rm help' \
33
- -a 'list status result tail retry stats wait audit prune cancel rm help' -d 'jobs subcommand'
33
+ -n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats recommend wait audit prune cancel rm help' \
34
+ -a 'list status result tail retry stats recommend wait audit prune cancel rm help' -d 'jobs subcommand'
34
35
  complete -c omnilane -n '__fish_seen_subcommand_from jobs' -l json -d 'JSON output'
35
36
  # jobs list / stats filters.
36
- complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l lane -x -d 'filter by lane'
37
+ complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats recommend' -l lane -x -d 'filter by lane'
37
38
  complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras exec' -d 'filter by vendor'
38
39
  complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list' -l status -x -a 'running done' -d 'filter by status'
40
+ complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from stats recommend audit' -l last -x -d 'maximum recent jobs'
41
+ complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from recommend' -l min-samples -x -d 'minimum completed samples'
39
42
 
40
43
  # configure subcommands (non-interactive).
41
44
  complete -c omnilane -f -n '__fish_seen_subcommand_from configure; and not __fish_seen_subcommand_from set get unset list diff' -a 'set get unset list diff' -d 'configure action'
42
45
 
43
46
  # doctor / release-audit / ui / completion.
44
47
  complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l json -d 'JSON output'
48
+ complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l strict -d 'fail when any warning is reported'
49
+ complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l probe -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras' -d 'run one opt-in live probe'
50
+ complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l probe-timeout -x -d 'probe timeout in seconds'
51
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l json -d 'JSON output'
52
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l run -d 'invoke providers in advise mode'
53
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras' -d 'vendor to compare; repeatable'
54
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l timeout -x -d 'per-workload timeout'
55
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l workloads -r -d 'TSV workload file'
56
+ complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l cost-per-call -x -d 'user-supplied VENDOR=USD estimate'
45
57
  complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l target -x -d 'target version'
46
58
  complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l allow-dirty -d 'permit a dirty tree'
47
59
  complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l require-tag -d 'require an annotated tag'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnilane",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "One routing table, every harness — classify subtasks into lanes and dispatch each lane to the best vendor's agentic CLI (Codex, Claude, Gemini, Grok) using your existing subscription logins.",
5
5
  "bin": {
6
6
  "omnilane": "bin/omnilane"
@@ -11,6 +11,7 @@
11
11
  "!scripts/__pycache__",
12
12
  "!**/*.pyc",
13
13
  "completions/",
14
+ "benchmarks/",
14
15
  "ui/",
15
16
  "routing.yaml",
16
17
  "routing.local.yaml.example",
package/routing.yaml CHANGED
@@ -27,7 +27,7 @@ hard-judgment: claude claude-opus-5 xhigh | codex gpt-5.6-sol max # ordered
27
27
  taste-final: claude claude-opus-5 high | codex gpt-5.6-sol max # user-facing prose, prompt/doc polish, Chinese phrasing, style arbitration
28
28
  consult: codex gpt-5.6-sol max | claude claude-opus-5 high | grok grok-4.5 - | gemini "Gemini 3.1 Pro (High)" - # direct named-model consultation; use --vendor to prevent fallback
29
29
  ui-draft: codex gpt-5.6-sol xhigh | claude claude-opus-5 high # only with a design system / reference images; open-ended visual taste -> taste-final
30
- long-context: gemini "Gemini 3.1 Pro (High)" - | claude claude-opus-5 high | codex gpt-5.6-sol high # all have 1M context; Gemini leads single-needle retrieval at full length and is the cheapest way to sweep volume, Flash for fast loops. For multi-hop synthesis across a large corpus prefer the claude candidate: published multi-needle scores at 1M favour Claude by a wide margin (see docs/model-capabilities-2026-07.md)
30
+ long-context: gemini "Gemini 3.1 Pro (High)" - | codex gpt-5.6-sol high | claude claude-opus-5 high # all have 1M context; ordered on AA-LCR, which scores exactly this lane's work — extracting and synthesising across long documents and where Gemini leads both fallbacks. Corrected 2026-08-03: this comment used to send multi-hop synthesis to the Claude candidate on second-hand prior-generation figures, and current first-party per-effort data reverses that, so the two fallbacks swapped. Caveat in docs: AA-LCR runs at 10k-100k tokens, so nothing here settles behaviour at a full 1M
31
31
  fast-agentic: codex gpt-5.6-luna max | gemini "Gemini 3.6 Flash (High)" - # fast multi-step tool loops. Reordered 2026-08-03: Luna leads Flash on agentic benchmarks AND costs a fraction as much per task, so Flash's remaining edge is raw throughput alone. Keep Flash first only if your loops are latency-bound. Both take image input, so the lane's multimodal checks are unaffected
32
32
  live-search: grok grok-4.5 - | off # native X/web search lane; no real substitute
33
33
  coding-overflow: grok grok-4.5 - | kimi kimi-k3 - | qwen qwen3-coder-plus - | opencode - - | off # codex-quota relief valve: mid-tier coding; Grok 4.5 is a capable mid-tier coder but AA measures a high hallucination rate — verify every factual claim it ships. qwen3-coder-plus = 2025-09-23 snapshot alias (Qwen 3.6 Plus exists; re-evaluate before swapping). kimi/qwen model fields are CLI aliases — adjust to your login. opencode "-" model = its own configured default.
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env python3
2
+ """Fixed, body-free routing quality/cost benchmark."""
3
+
4
+ import argparse
5
+ from decimal import Decimal, InvalidOperation
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import re
10
+ import shlex
11
+ import subprocess
12
+ import sys
13
+ import tempfile
14
+ import time
15
+
16
+
17
+ ROOT = Path(__file__).resolve().parents[1]
18
+ DEFAULT_WORKLOADS = ROOT / "benchmarks" / "workloads.tsv"
19
+ NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")
20
+ ID_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
21
+
22
+
23
+ class BenchmarkError(Exception):
24
+ pass
25
+
26
+
27
+ def parse_args(argv=None):
28
+ parser = argparse.ArgumentParser(
29
+ prog="omnilane benchmark",
30
+ description=(
31
+ "Compare fixed-workload routing quality and user-supplied per-call "
32
+ "costs. Provider calls require --run."
33
+ ),
34
+ )
35
+ parser.add_argument("--json", action="store_true", help="emit JSON")
36
+ parser.add_argument(
37
+ "--run",
38
+ action="store_true",
39
+ help="invoke providers in advise mode; default is routing-only dry-run",
40
+ )
41
+ parser.add_argument(
42
+ "--vendor",
43
+ action="append",
44
+ default=[],
45
+ metavar="VENDOR",
46
+ help="vendor to compare; repeatable (default: configured vendors)",
47
+ )
48
+ parser.add_argument(
49
+ "--timeout",
50
+ type=int,
51
+ default=60,
52
+ metavar="SECONDS",
53
+ help="per-workload timeout, 1..600 (default: 60)",
54
+ )
55
+ parser.add_argument(
56
+ "--workloads",
57
+ type=Path,
58
+ default=DEFAULT_WORKLOADS,
59
+ metavar="FILE",
60
+ help="fixed TSV workload file",
61
+ )
62
+ parser.add_argument(
63
+ "--cost-per-call",
64
+ action="append",
65
+ default=[],
66
+ metavar="VENDOR=USD",
67
+ help="optional user-supplied estimated USD per call; repeatable",
68
+ )
69
+ args = parser.parse_args(argv)
70
+ if not 1 <= args.timeout <= 600:
71
+ parser.error("--timeout must be between 1 and 600 seconds")
72
+ return args
73
+
74
+
75
+ def unique(values):
76
+ return list(dict.fromkeys(values))
77
+
78
+
79
+ def decode_plan_value(value):
80
+ try:
81
+ parsed = shlex.split(value, posix=True)
82
+ except ValueError:
83
+ return value
84
+ return parsed[0] if len(parsed) == 1 else value
85
+
86
+
87
+ def parse_plan(text):
88
+ plan = {}
89
+ for line in text.splitlines():
90
+ if "=" not in line:
91
+ continue
92
+ key, value = line.split("=", 1)
93
+ if re.fullmatch(r"[a-z_]+", key):
94
+ plan[key] = decode_plan_value(value)
95
+ return plan
96
+
97
+
98
+ def load_workloads(path):
99
+ try:
100
+ lines = path.read_text(encoding="utf-8").splitlines()
101
+ except OSError as exc:
102
+ raise BenchmarkError(f"cannot read workloads: {path}: {exc}") from exc
103
+
104
+ workloads = []
105
+ seen = set()
106
+ for line_number, line in enumerate(lines, 1):
107
+ if not line.strip() or line.lstrip().startswith("#"):
108
+ continue
109
+ fields = line.split("\t", 4)
110
+ if len(fields) != 5:
111
+ raise BenchmarkError(
112
+ f"{path}:{line_number}: expected 5 tab-separated fields"
113
+ )
114
+ workload_id, lane, weight_text, pattern, prompt = fields
115
+ if not ID_RE.fullmatch(workload_id) or workload_id in seen:
116
+ raise BenchmarkError(f"{path}:{line_number}: invalid or duplicate id")
117
+ if not NAME_RE.fullmatch(lane):
118
+ raise BenchmarkError(f"{path}:{line_number}: invalid lane")
119
+ try:
120
+ weight = int(weight_text)
121
+ except ValueError as exc:
122
+ raise BenchmarkError(f"{path}:{line_number}: weight must be an integer") from exc
123
+ if not 1 <= weight <= 100:
124
+ raise BenchmarkError(f"{path}:{line_number}: weight must be 1..100")
125
+ try:
126
+ re.compile(pattern)
127
+ except re.error as exc:
128
+ raise BenchmarkError(f"{path}:{line_number}: invalid regex: {exc}") from exc
129
+ if not prompt.strip():
130
+ raise BenchmarkError(f"{path}:{line_number}: prompt must not be empty")
131
+ seen.add(workload_id)
132
+ workloads.append(
133
+ {
134
+ "id": workload_id,
135
+ "lane": lane,
136
+ "weight": weight,
137
+ "pattern": pattern,
138
+ "prompt": prompt,
139
+ }
140
+ )
141
+ if not workloads:
142
+ raise BenchmarkError(f"no workloads found: {path}")
143
+ return workloads
144
+
145
+
146
+ def parse_costs(items):
147
+ costs = {}
148
+ for item in items:
149
+ if "=" not in item:
150
+ raise BenchmarkError("--cost-per-call must use VENDOR=USD")
151
+ vendor, raw = item.split("=", 1)
152
+ if not NAME_RE.fullmatch(vendor):
153
+ raise BenchmarkError(f"invalid cost vendor: {vendor}")
154
+ try:
155
+ value = Decimal(raw)
156
+ except InvalidOperation as exc:
157
+ raise BenchmarkError(f"invalid cost for {vendor}: {raw}") from exc
158
+ if not value.is_finite() or value < 0:
159
+ raise BenchmarkError(f"cost for {vendor} must be non-negative")
160
+ costs[vendor] = value
161
+ return costs
162
+
163
+
164
+ def list_routes(dispatch, env):
165
+ result = subprocess.run(
166
+ [str(dispatch), "--list"],
167
+ text=True,
168
+ stdout=subprocess.PIPE,
169
+ stderr=subprocess.PIPE,
170
+ env=env,
171
+ timeout=15,
172
+ check=False,
173
+ )
174
+ if result.returncode != 0:
175
+ detail = result.stderr.strip().splitlines()[-1:] or ["unknown error"]
176
+ raise BenchmarkError(f"cannot list routing: {detail[0]}")
177
+ lanes = []
178
+ vendors = []
179
+ for line in result.stdout.splitlines():
180
+ if ":" not in line:
181
+ continue
182
+ lane, raw = line.split(":", 1)
183
+ lane = lane.strip()
184
+ if not NAME_RE.fullmatch(lane):
185
+ continue
186
+ lanes.append(lane)
187
+ try:
188
+ fields = shlex.split(raw.strip())
189
+ except ValueError:
190
+ fields = raw.split()
191
+ if fields and NAME_RE.fullmatch(fields[0]) and fields[0] not in {
192
+ "off",
193
+ "exec",
194
+ "vote",
195
+ }:
196
+ vendors.append(fields[0])
197
+ return unique(lanes), unique(vendors)
198
+
199
+
200
+ def resolve_route(dispatch, vendor, preferred_lane, lanes, prompt, env):
201
+ last_error = "vendor is not configured in an available lane"
202
+ for lane in unique([preferred_lane] + list(lanes)):
203
+ result = subprocess.run(
204
+ [
205
+ str(dispatch),
206
+ "--dry-run",
207
+ "--mode",
208
+ "advise",
209
+ "--vendor",
210
+ vendor,
211
+ lane,
212
+ prompt,
213
+ ],
214
+ text=True,
215
+ stdout=subprocess.PIPE,
216
+ stderr=subprocess.PIPE,
217
+ env=env,
218
+ timeout=15,
219
+ check=False,
220
+ )
221
+ if result.returncode == 0:
222
+ plan = parse_plan(result.stdout)
223
+ if plan.get("provider_invoked") != "no":
224
+ return None, "dry-run contract did not confirm provider_invoked=no"
225
+ if plan.get("vendor") != vendor:
226
+ return None, "dry-run resolved a different vendor"
227
+ plan.setdefault("lane", lane)
228
+ return plan, ""
229
+ detail = result.stderr.strip().splitlines()
230
+ if detail:
231
+ last_error = detail[-1]
232
+ return None, last_error
233
+
234
+
235
+ def money(value):
236
+ return format(value.quantize(Decimal("0.01")), "f")
237
+
238
+
239
+ def cost_report(vendor, calls, costs):
240
+ if vendor not in costs:
241
+ return None
242
+ per_call = costs[vendor]
243
+ return {
244
+ "basis": "user-supplied-per-call",
245
+ "currency": "USD",
246
+ "per_call_usd": money(per_call),
247
+ "estimated_total_usd": money(per_call * calls),
248
+ }
249
+
250
+
251
+ def run_one(repo, vendor, plan, workload, timeout, env):
252
+ runner = repo / "scripts" / "runners" / f"run-{vendor}.sh"
253
+ base = {
254
+ "id": workload["id"],
255
+ "requested_lane": workload["lane"],
256
+ "resolved_lane": plan.get("lane", workload["lane"]),
257
+ "model": plan.get("model", "-"),
258
+ "effort": plan.get("effort", "-"),
259
+ "weight": workload["weight"],
260
+ }
261
+ if not runner.is_file() or not os.access(runner, os.X_OK):
262
+ return dict(base, status="runner_error", duration_seconds=0.0, response_bytes=0), True
263
+
264
+ started = time.monotonic()
265
+ with tempfile.TemporaryDirectory(prefix="omnilane-benchmark-") as temporary:
266
+ temporary_path = Path(temporary)
267
+ prompt_file = temporary_path / "prompt.txt"
268
+ output_file = temporary_path / "response.txt"
269
+ prompt_file.write_text(str(workload["prompt"]) + "\n", encoding="utf-8")
270
+ run_env = env.copy()
271
+ run_env["OMNILANE_TIMEOUT"] = str(timeout)
272
+ try:
273
+ result = subprocess.run(
274
+ [
275
+ str(runner),
276
+ "advise",
277
+ str(repo),
278
+ plan.get("model", "-"),
279
+ plan.get("effort", "-"),
280
+ str(prompt_file),
281
+ str(output_file),
282
+ ],
283
+ stdout=subprocess.DEVNULL,
284
+ stderr=subprocess.DEVNULL,
285
+ env=run_env,
286
+ timeout=timeout + 5,
287
+ check=False,
288
+ )
289
+ except subprocess.TimeoutExpired:
290
+ elapsed = round(time.monotonic() - started, 3)
291
+ return dict(base, status="runner_error", duration_seconds=elapsed, response_bytes=0), True
292
+ elapsed = round(time.monotonic() - started, 3)
293
+ try:
294
+ body = output_file.read_text(encoding="utf-8")
295
+ response_bytes = output_file.stat().st_size
296
+ except OSError:
297
+ body = ""
298
+ response_bytes = 0
299
+ if result.returncode != 0:
300
+ status = "runner_error"
301
+ runner_error = True
302
+ else:
303
+ status = "passed" if re.search(str(workload["pattern"]), body) else "failed"
304
+ runner_error = False
305
+ return dict(
306
+ base,
307
+ status=status,
308
+ duration_seconds=elapsed,
309
+ response_bytes=response_bytes,
310
+ ), runner_error
311
+
312
+
313
+ def execute(args):
314
+ repo = Path(os.environ.get("OMNILANE_BENCHMARK_REPO", str(ROOT))).resolve()
315
+ dispatch = repo / "scripts" / "dispatch.sh"
316
+ if not dispatch.is_file() or not os.access(dispatch, os.X_OK):
317
+ raise BenchmarkError(f"dispatch is not executable: {dispatch}")
318
+ env = os.environ.copy()
319
+ workloads = load_workloads(args.workloads)
320
+ costs = parse_costs(args.cost_per_call)
321
+ lanes, configured_vendors = list_routes(dispatch, env)
322
+ vendors = unique(args.vendor or configured_vendors)
323
+ if not vendors:
324
+ raise BenchmarkError("no configured benchmark vendors found")
325
+ for vendor in vendors:
326
+ if not NAME_RE.fullmatch(vendor) or vendor in {"off", "exec", "vote"}:
327
+ raise BenchmarkError(f"unsupported benchmark vendor: {vendor}")
328
+
329
+ report = {
330
+ "schema_version": 1,
331
+ "command": "benchmark",
332
+ "mode": "run" if args.run else "dry-run",
333
+ "provider_invoked": False,
334
+ "workload_count": len(workloads),
335
+ "workloads_file": str(args.workloads.resolve()),
336
+ "vendors": [],
337
+ }
338
+ had_error = False
339
+ provider_invoked = False
340
+ for vendor in vendors:
341
+ vendor_started = time.monotonic()
342
+ items = []
343
+ passed = 0
344
+ score_possible = 0
345
+ score_earned = 0
346
+ for workload in workloads:
347
+ score_possible += int(workload["weight"])
348
+ plan, error = resolve_route(
349
+ dispatch,
350
+ vendor,
351
+ workload["lane"],
352
+ lanes,
353
+ workload["prompt"],
354
+ env,
355
+ )
356
+ if plan is None:
357
+ items.append(
358
+ {
359
+ "id": workload["id"],
360
+ "requested_lane": workload["lane"],
361
+ "weight": workload["weight"],
362
+ "status": "unavailable",
363
+ "duration_seconds": 0.0,
364
+ "response_bytes": 0,
365
+ "error": error,
366
+ }
367
+ )
368
+ had_error = True
369
+ continue
370
+ if not args.run:
371
+ items.append(
372
+ {
373
+ "id": workload["id"],
374
+ "requested_lane": workload["lane"],
375
+ "resolved_lane": plan.get("lane", workload["lane"]),
376
+ "model": plan.get("model", "-"),
377
+ "effort": plan.get("effort", "-"),
378
+ "weight": workload["weight"],
379
+ "status": "planned",
380
+ "duration_seconds": 0.0,
381
+ "response_bytes": 0,
382
+ }
383
+ )
384
+ continue
385
+ provider_invoked = True
386
+ item, runner_error = run_one(
387
+ repo, vendor, plan, workload, args.timeout, env
388
+ )
389
+ items.append(item)
390
+ had_error = had_error or runner_error
391
+ if item["status"] == "passed":
392
+ passed += 1
393
+ score_earned += int(workload["weight"])
394
+
395
+ quality = None
396
+ if args.run and score_possible:
397
+ quality = round((score_earned / score_possible) * 100, 2)
398
+ if quality.is_integer():
399
+ quality = int(quality)
400
+ report["vendors"].append(
401
+ {
402
+ "vendor": vendor,
403
+ "planned_calls": len(workloads),
404
+ "passed": passed,
405
+ "score_earned": score_earned,
406
+ "score_possible": score_possible,
407
+ "quality_percent": quality,
408
+ "duration_seconds": round(time.monotonic() - vendor_started, 3),
409
+ "cost": cost_report(vendor, len(workloads), costs),
410
+ "workloads": items,
411
+ }
412
+ )
413
+ report["provider_invoked"] = provider_invoked
414
+ return report, 1 if had_error else 0
415
+
416
+
417
+ def print_human(report):
418
+ invoked = "yes" if report["provider_invoked"] else "no"
419
+ print(f"mode={report['mode']} provider_invoked={invoked}")
420
+ for vendor in report["vendors"]:
421
+ quality = vendor["quality_percent"]
422
+ quality_text = "planned" if quality is None else f"{quality}%"
423
+ cost = vendor["cost"]
424
+ cost_text = "not supplied" if cost is None else f"USD {cost['estimated_total_usd']}"
425
+ print(
426
+ f"{vendor['vendor']}: calls={vendor['planned_calls']} "
427
+ f"quality={quality_text} estimated_cost={cost_text}"
428
+ )
429
+
430
+
431
+ def main(argv=None):
432
+ args = parse_args(argv)
433
+ try:
434
+ report, return_code = execute(args)
435
+ except BenchmarkError as exc:
436
+ print(f"omnilane benchmark: {exc}", file=sys.stderr)
437
+ return 2
438
+ if args.json:
439
+ print(json.dumps(report, ensure_ascii=False, separators=(",", ":")))
440
+ else:
441
+ print_human(report)
442
+ return return_code
443
+
444
+
445
+ if __name__ == "__main__":
446
+ raise SystemExit(main())
package/scripts/check.sh CHANGED
@@ -75,7 +75,7 @@ fi
75
75
 
76
76
  # 4) python compile of the UI and test modules
77
77
  py_files=()
78
- for rel in scripts/ui.py tests/test_ui.py tests/test_ci_policy.py tests/ui_browser_harness.py; do
78
+ for rel in scripts/ui.py scripts/benchmark.py tests/test_ui.py tests/test_benchmark.py tests/test_ci_policy.py tests/ui_browser_harness.py; do
79
79
  [[ -f "$REPO/$rel" ]] && py_files+=("$REPO/$rel")
80
80
  done
81
81
  if ! command -v python3 >/dev/null 2>&1; then