omnilane 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -1
- package/README.ja.md +18 -0
- package/README.ko.md +18 -0
- package/README.md +54 -2
- package/README.zh-CN.md +18 -0
- package/README.zh-TW.md +39 -2
- package/VERSION +1 -1
- package/benchmarks/workloads.tsv +4 -0
- package/bin/omnilane +10 -2
- package/bin/omnilane-mcp +126 -0
- package/completions/_omnilane +21 -4
- package/completions/omnilane.bash +19 -4
- package/completions/omnilane.fish +15 -3
- package/package.json +2 -1
- package/scripts/benchmark.py +446 -0
- package/scripts/check.sh +1 -1
- package/scripts/dispatch.sh +48 -0
- package/scripts/doctor.sh +53 -10
- package/scripts/jobs.sh +162 -2
- package/scripts/lib/common.sh +19 -0
- package/scripts/provider-probe.sh +180 -0
- package/scripts/runners/run-codex.sh +22 -5
- package/ui/app.js +56 -0
- package/ui/index.html +1 -0
- package/ui/styles.css +22 -0
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "0.15.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",
|
|
@@ -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
|
package/scripts/dispatch.sh
CHANGED
|
@@ -655,10 +655,58 @@ secure_job_files() {
|
|
|
655
655
|
find "$JOB_DIR" -type f -exec chmod 600 {} +
|
|
656
656
|
}
|
|
657
657
|
|
|
658
|
+
completion_tail() {
|
|
659
|
+
local path="$1" size=0 keep value="" sentinel=$'\001'
|
|
660
|
+
local note=$'[truncated: leading output omitted]\n'
|
|
661
|
+
local LC_ALL=C
|
|
662
|
+
|
|
663
|
+
if [[ -f "$path" && ! -L "$path" ]]; then
|
|
664
|
+
size="$(wc -c < "$path" 2>/dev/null | tr -d '[:space:]')" || return 1
|
|
665
|
+
[[ "$size" =~ ^[0-9]+$ ]] || return 1
|
|
666
|
+
if [[ "$size" -gt 2000 ]]; then
|
|
667
|
+
keep=$((2000 - ${#note}))
|
|
668
|
+
value="$(printf '%s' "$note"; tail -c "$keep" "$path"; printf '%s' "$sentinel")" || return 1
|
|
669
|
+
else
|
|
670
|
+
value="$(cat "$path"; printf '%s' "$sentinel")" || return 1
|
|
671
|
+
fi
|
|
672
|
+
value="${value%"$sentinel"}"
|
|
673
|
+
fi
|
|
674
|
+
printf '%s' "$value"
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
write_completion_record() {
|
|
678
|
+
local rc="$1" inbox="$OMNILANE_HOME/inbox"
|
|
679
|
+
local final="$inbox/$JOB_ID.json" tmp="$inbox/.$JOB_ID.tmp.$$-$RANDOM"
|
|
680
|
+
local tail_value finished old_umask write_rc=0 sentinel=$'\001'
|
|
681
|
+
|
|
682
|
+
prepare_inbox_store || return 1
|
|
683
|
+
tail_value="$(completion_tail "$JOB_DIR/out.txt"; printf '%s' "$sentinel")" || return 1
|
|
684
|
+
tail_value="${tail_value%"$sentinel"}"
|
|
685
|
+
finished="$(date -u +%FT%TZ)" || return 1
|
|
686
|
+
old_umask="$(umask)"
|
|
687
|
+
umask 077
|
|
688
|
+
printf '{"job_id":"%s","lane":"%s","vendor":"%s","model":"%s","mode":"%s","workdir":"%s","exit":%s,"finished":"%s","tail":"%s"}\n' \
|
|
689
|
+
"$(json_escape "$JOB_ID")" "$(json_escape "$LANE")" "$(json_escape "$VENDOR")" \
|
|
690
|
+
"$(json_escape "$MODEL")" "$(json_escape "$MODE")" "$(json_escape "$WORKDIR")" \
|
|
691
|
+
"$rc" "$(json_escape "$finished")" "$(json_escape "$tail_value")" > "$tmp" || write_rc=$?
|
|
692
|
+
if [[ "$write_rc" -eq 0 ]]; then
|
|
693
|
+
chmod 600 "$tmp" || write_rc=$?
|
|
694
|
+
fi
|
|
695
|
+
if [[ "$write_rc" -eq 0 ]]; then
|
|
696
|
+
mv "$tmp" "$final" || write_rc=$?
|
|
697
|
+
fi
|
|
698
|
+
umask "$old_umask"
|
|
699
|
+
rm -f "$tmp" 2>/dev/null || true
|
|
700
|
+
return "$write_rc"
|
|
701
|
+
}
|
|
702
|
+
|
|
658
703
|
finish_job() {
|
|
659
704
|
local rc="$1"
|
|
660
705
|
secure_job_files
|
|
661
706
|
(umask 077; printf '%s\n' "$rc" > "$JOB_DIR/exit")
|
|
707
|
+
if [[ "${OMNILANE_INBOX:-1}" != "0" ]]; then
|
|
708
|
+
write_completion_record "$rc" >/dev/null 2>&1 || true
|
|
709
|
+
fi
|
|
662
710
|
}
|
|
663
711
|
|
|
664
712
|
run_job() {
|
package/scripts/doctor.sh
CHANGED
|
@@ -3,14 +3,34 @@ set -u
|
|
|
3
3
|
# Read-only health report for routing, state, watchdog, and optional UI support.
|
|
4
4
|
|
|
5
5
|
JSON_MODE=0
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
STRICT_MODE=0
|
|
7
|
+
PROBE_VENDOR=""
|
|
8
|
+
PROBE_TIMEOUT=30
|
|
9
|
+
PROBE_TIMEOUT_GIVEN=0
|
|
10
|
+
while [[ $# -gt 0 ]]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--json) JSON_MODE=1 ;;
|
|
13
|
+
--strict) STRICT_MODE=1 ;;
|
|
14
|
+
--probe)
|
|
15
|
+
[[ $# -ge 2 ]] || { echo "omnilane: --probe needs a vendor" >&2; exit 2; }
|
|
16
|
+
PROBE_VENDOR="$2"; shift ;;
|
|
17
|
+
--probe-timeout)
|
|
18
|
+
[[ $# -ge 2 ]] || { echo "omnilane: --probe-timeout needs seconds" >&2; exit 2; }
|
|
19
|
+
PROBE_TIMEOUT="$2"; PROBE_TIMEOUT_GIVEN=1; shift ;;
|
|
20
|
+
*)
|
|
21
|
+
echo "usage: omnilane doctor [--json] [--strict] [--probe V [--probe-timeout SEC]]" >&2
|
|
22
|
+
exit 2
|
|
23
|
+
;;
|
|
24
|
+
esac
|
|
25
|
+
shift
|
|
26
|
+
done
|
|
27
|
+
[[ "$PROBE_TIMEOUT_GIVEN" -eq 0 || -n "$PROBE_VENDOR" ]] || {
|
|
28
|
+
echo "omnilane: --probe-timeout requires --probe V" >&2
|
|
10
29
|
exit 2
|
|
11
|
-
|
|
30
|
+
}
|
|
12
31
|
REPO="${OMNILANE_DOCTOR_REPO:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
13
32
|
OMNILANE_HOME="${OMNILANE_HOME:-$HOME/.omnilane}"
|
|
33
|
+
PROBE_SCRIPT="${OMNILANE_PROVIDER_PROBE_SCRIPT:-$REPO/scripts/provider-probe.sh}"
|
|
14
34
|
PASS_COUNT=0
|
|
15
35
|
WARN_COUNT=0
|
|
16
36
|
FAIL_COUNT=0
|
|
@@ -115,7 +135,10 @@ if [[ -L "$OMNILANE_HOME/jobs" ||
|
|
|
115
135
|
( -e "$OMNILANE_HOME/jobs" && ! -d "$OMNILANE_HOME/jobs" ) ]]; then
|
|
116
136
|
report FAIL job-privacy "$OMNILANE_HOME/jobs must be a real directory, not a symlink or file"
|
|
117
137
|
elif [[ -d "$OMNILANE_HOME/jobs" ]]; then
|
|
118
|
-
jobs_mode="$(stat -f '%Lp' "$OMNILANE_HOME/jobs" 2>/dev/null ||
|
|
138
|
+
jobs_mode="$(stat -f '%Lp' "$OMNILANE_HOME/jobs" 2>/dev/null || true)"
|
|
139
|
+
if [[ ! "$jobs_mode" =~ ^[0-7]{3,4}$ ]]; then
|
|
140
|
+
jobs_mode="$(stat -c '%a' "$OMNILANE_HOME/jobs" 2>/dev/null || true)"
|
|
141
|
+
fi
|
|
119
142
|
if [[ "$jobs_mode" =~ ^[0-7]*00$ ]]; then
|
|
120
143
|
report PASS job-privacy "$OMNILANE_HOME/jobs mode is $jobs_mode"
|
|
121
144
|
elif [[ -n "$jobs_mode" ]]; then
|
|
@@ -178,6 +201,24 @@ else
|
|
|
178
201
|
report WARN vendors "no vendor CLI reachable${vendor_absent:+ (missing: $vendor_absent)}; every lane degrades to off"
|
|
179
202
|
fi
|
|
180
203
|
|
|
204
|
+
# A real inference call is explicit and single-vendor only. Default doctor
|
|
205
|
+
# remains local/offline. Never relay provider output or runner stderr.
|
|
206
|
+
if [[ -n "$PROBE_VENDOR" ]]; then
|
|
207
|
+
if [[ ! -x "$PROBE_SCRIPT" ]]; then
|
|
208
|
+
report FAIL provider-probe "probe runner is unavailable"
|
|
209
|
+
else
|
|
210
|
+
probe_output="$(OMNILANE_PROBE_REPO="$REPO" "$PROBE_SCRIPT" \
|
|
211
|
+
--vendor "$PROBE_VENDOR" --timeout "$PROBE_TIMEOUT" 2>/dev/null)"
|
|
212
|
+
probe_rc=$?
|
|
213
|
+
if [[ "$probe_rc" -eq 0 ]]; then
|
|
214
|
+
report PASS provider-probe "$PROBE_VENDOR bounded live inference succeeded"
|
|
215
|
+
else
|
|
216
|
+
report FAIL provider-probe "$PROBE_VENDOR bounded live inference failed (exit $probe_rc)"
|
|
217
|
+
fi
|
|
218
|
+
: "$probe_output"
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
|
|
181
222
|
if command -v python3 >/dev/null 2>&1; then
|
|
182
223
|
if python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 9) else 1)' \
|
|
183
224
|
>/dev/null 2>&1; then
|
|
@@ -192,13 +233,15 @@ fi
|
|
|
192
233
|
|
|
193
234
|
if [[ "$JSON_MODE" -eq 1 ]]; then
|
|
194
235
|
ok=true
|
|
195
|
-
[[ "$FAIL_COUNT" -eq 0 ]] || ok=false
|
|
196
|
-
|
|
197
|
-
|
|
236
|
+
[[ "$FAIL_COUNT" -eq 0 && ( "$STRICT_MODE" -eq 0 || "$WARN_COUNT" -eq 0 ) ]] || ok=false
|
|
237
|
+
strict=false
|
|
238
|
+
[[ "$STRICT_MODE" -eq 0 ]] || strict=true
|
|
239
|
+
printf '{"ok":%s,"checks":[%s],"summary":{"passed":%s,"warnings":%s,"failed":%s},"strict":%s}\n' \
|
|
240
|
+
"$ok" "$JSON_REPORTS" "$PASS_COUNT" "$WARN_COUNT" "$FAIL_COUNT" "$strict"
|
|
198
241
|
else
|
|
199
242
|
warning_suffix=s
|
|
200
243
|
[[ "$WARN_COUNT" -eq 1 ]] && warning_suffix=""
|
|
201
244
|
printf '\nSummary: %s passed, %s warning%s, %s failed\n' \
|
|
202
245
|
"$PASS_COUNT" "$WARN_COUNT" "$warning_suffix" "$FAIL_COUNT"
|
|
203
246
|
fi
|
|
204
|
-
[[ "$FAIL_COUNT" -eq 0 ]]
|
|
247
|
+
[[ "$FAIL_COUNT" -eq 0 && ( "$STRICT_MODE" -eq 0 || "$WARN_COUNT" -eq 0 ) ]]
|