reeval-python 0.14.0__tar.gz → 0.15.1__tar.gz
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.
- {reeval_python-0.14.0 → reeval_python-0.15.1}/PKG-INFO +31 -2
- {reeval_python-0.14.0 → reeval_python-0.15.1}/README.md +30 -1
- {reeval_python-0.14.0 → reeval_python-0.15.1}/pyproject.toml +1 -1
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/_wire.py +125 -20
- reeval_python-0.15.1/src/reeval_python/binaries.json +55 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/cli.py +24 -7
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/declare.py +222 -13
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/errors.py +1 -1
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/options.py +4 -1
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/runner.py +8 -4
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/wire.py +7 -3
- reeval_python-0.15.1/tests/declare/revision.eval.py +20 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/stdio_host.py +13 -3
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_api.py +58 -1
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_cli.py +18 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_conformance_declare.py +8 -2
- reeval_python-0.15.1/tests/test_load.py +96 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_runner_attempts.py +4 -4
- reeval_python-0.15.1/tests/test_runner_json.py +92 -0
- reeval_python-0.15.1/tests/test_source.py +191 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_types.py +3 -3
- {reeval_python-0.14.0 → reeval_python-0.15.1}/uv.lock +1 -1
- reeval_python-0.14.0/src/reeval_python/binaries.json +0 -55
- {reeval_python-0.14.0 → reeval_python-0.15.1}/.gitignore +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/CHANGELOG.md +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/LICENSE +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/package.json +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/__init__.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/__main__.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/aio.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/checks.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/context.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/environment.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/expect.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/py.typed +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/src/reeval_python/types.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/abort.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/abort.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/exec-retries.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/exec-retries.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/exec.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/exec.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/expect-fail.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/expect-fail.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/expect.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/expect.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/files.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/files.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/judge.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/judge.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/record.async.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/record.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/send.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/send.sync.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/sync-async.async.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/context/sync-async.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/compare-questions.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/compare-variants.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/each-families.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/egress-cascade.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/environment-extend.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/environment-plugin-marketplace.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/environment-plugins.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/groups-skip-todo.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/questions-attempts.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/questions-group.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/questions-state.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/declare/tags.eval.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/runner_process.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_checks.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_conformance_context.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_patterns.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_python_wheels.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_runner_checks.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_runner_isolation.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_runner_misuse.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_runner_sdk.py +0 -0
- {reeval_python-0.14.0 → reeval_python-0.15.1}/tests/test_runner_stdio.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: reeval-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.1
|
|
4
4
|
Summary: Python authoring SDK for reeval (*.eval.py eval files)
|
|
5
5
|
Project-URL: Homepage, https://reeval.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/invertase/reeval
|
|
@@ -86,6 +86,35 @@ a retry loop can assert; one the escaping error carries (each failure in a `Task
|
|
|
86
86
|
for quick checks. Keep `expect` for reeval matchers (`to_have_file`, `to_succeed`, soft checks,
|
|
87
87
|
labels) — Playwright-style.
|
|
88
88
|
|
|
89
|
+
#### What a caught failure counts as
|
|
90
|
+
|
|
91
|
+
A failed `expect` is a check the moment it fails: reeval records it before
|
|
92
|
+
`ReevalAssertionError` reaches the body. One the body catches still fails the attempt when the
|
|
93
|
+
body ends, as `expect.soft` does, and its error says the eval caught it. A plain `assert` the body
|
|
94
|
+
catches records nothing, as in Python.
|
|
95
|
+
|
|
96
|
+
| The body catches | Recorded | Fails the attempt |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| a failed `expect(…)` | a failed check | yes, when the body ends |
|
|
99
|
+
| a failed `assert` | nothing | no |
|
|
100
|
+
| neither (it escapes) | a failed check | yes |
|
|
101
|
+
|
|
102
|
+
So retry with `expect.poll`, which retries the matcher and records only its final outcome, or with
|
|
103
|
+
a plain `assert` or `if` in the loop, never `expect` in `try`/`except`:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# Retries the command until it succeeds, for up to 10 seconds: one check.
|
|
107
|
+
expect.poll(lambda: c.exec("curl -sf localhost:3000"), timeout=10).to_succeed()
|
|
108
|
+
|
|
109
|
+
# A plain assert in a retry loop: only the try that held is recorded.
|
|
110
|
+
for _ in range(3):
|
|
111
|
+
try:
|
|
112
|
+
assert c.exec("test -f ready.txt").ok
|
|
113
|
+
break
|
|
114
|
+
except AssertionError:
|
|
115
|
+
time.sleep(1)
|
|
116
|
+
```
|
|
117
|
+
|
|
89
118
|
Matchers that take a pattern (`to_have_file`, `to_have_run`, `to_have_searched`, `to_have_fetched`,
|
|
90
119
|
`to_have_called_tool`, `to_have_edited`, `to_have_changed`) take a `str` or a compiled `re.Pattern`,
|
|
91
120
|
`str` or `bytes`, with any flags. Python matches it with `pattern.search`, so anything `re` accepts
|
|
@@ -101,7 +130,7 @@ Use snake_case on the context (`read_file`, `write_file`, `file`) and on matcher
|
|
|
101
130
|
`to_pass`, `to_equal`, `to_contain`, …). Matcher failures raise `ReevalAssertionError`.
|
|
102
131
|
|
|
103
132
|
Options are typed (`EvalOptions`, and `JudgeOptions` for `c.judge(…)`), so pyright refuses a
|
|
104
|
-
|
|
133
|
+
misspelled key such as `runz`. They take reeval's keys in snake_case at any depth (`min_lift`,
|
|
105
134
|
`delay_ms`); the keys of `env`, `headers` and the other maps of your own keep their spelling. Every
|
|
106
135
|
duration is seconds or a `timedelta`: `{"timeout": 90}`, `docker(start_timeout=timedelta(minutes=2))`,
|
|
107
136
|
`expect.poll(…, timeout=10)`. `docker()`, `fake_harness()`, `fake_judge()` and `plugin.*` return
|
|
@@ -62,6 +62,35 @@ a retry loop can assert; one the escaping error carries (each failure in a `Task
|
|
|
62
62
|
for quick checks. Keep `expect` for reeval matchers (`to_have_file`, `to_succeed`, soft checks,
|
|
63
63
|
labels) — Playwright-style.
|
|
64
64
|
|
|
65
|
+
#### What a caught failure counts as
|
|
66
|
+
|
|
67
|
+
A failed `expect` is a check the moment it fails: reeval records it before
|
|
68
|
+
`ReevalAssertionError` reaches the body. One the body catches still fails the attempt when the
|
|
69
|
+
body ends, as `expect.soft` does, and its error says the eval caught it. A plain `assert` the body
|
|
70
|
+
catches records nothing, as in Python.
|
|
71
|
+
|
|
72
|
+
| The body catches | Recorded | Fails the attempt |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| a failed `expect(…)` | a failed check | yes, when the body ends |
|
|
75
|
+
| a failed `assert` | nothing | no |
|
|
76
|
+
| neither (it escapes) | a failed check | yes |
|
|
77
|
+
|
|
78
|
+
So retry with `expect.poll`, which retries the matcher and records only its final outcome, or with
|
|
79
|
+
a plain `assert` or `if` in the loop, never `expect` in `try`/`except`:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Retries the command until it succeeds, for up to 10 seconds: one check.
|
|
83
|
+
expect.poll(lambda: c.exec("curl -sf localhost:3000"), timeout=10).to_succeed()
|
|
84
|
+
|
|
85
|
+
# A plain assert in a retry loop: only the try that held is recorded.
|
|
86
|
+
for _ in range(3):
|
|
87
|
+
try:
|
|
88
|
+
assert c.exec("test -f ready.txt").ok
|
|
89
|
+
break
|
|
90
|
+
except AssertionError:
|
|
91
|
+
time.sleep(1)
|
|
92
|
+
```
|
|
93
|
+
|
|
65
94
|
Matchers that take a pattern (`to_have_file`, `to_have_run`, `to_have_searched`, `to_have_fetched`,
|
|
66
95
|
`to_have_called_tool`, `to_have_edited`, `to_have_changed`) take a `str` or a compiled `re.Pattern`,
|
|
67
96
|
`str` or `bytes`, with any flags. Python matches it with `pattern.search`, so anything `re` accepts
|
|
@@ -77,7 +106,7 @@ Use snake_case on the context (`read_file`, `write_file`, `file`) and on matcher
|
|
|
77
106
|
`to_pass`, `to_equal`, `to_contain`, …). Matcher failures raise `ReevalAssertionError`.
|
|
78
107
|
|
|
79
108
|
Options are typed (`EvalOptions`, and `JudgeOptions` for `c.judge(…)`), so pyright refuses a
|
|
80
|
-
|
|
109
|
+
misspelled key such as `runz`. They take reeval's keys in snake_case at any depth (`min_lift`,
|
|
81
110
|
`delay_ms`); the keys of `env`, `headers` and the other maps of your own keep their spelling. Every
|
|
82
111
|
duration is seconds or a `timedelta`: `{"timeout": 90}`, `docker(start_timeout=timedelta(minutes=2))`,
|
|
83
112
|
`expect.poll(…, timeout=10)`. `docker()`, `fake_harness()`, `fake_judge()` and `plugin.*` return
|
|
@@ -73,8 +73,8 @@ class AssertionRecord(TypedDict):
|
|
|
73
73
|
class AttemptAbort(TypedDict):
|
|
74
74
|
# reeval's sentence, e.g. `Eval timed out after 600s`.
|
|
75
75
|
message: str
|
|
76
|
-
# `timeout`: the eval's `timeout` ran out. `
|
|
77
|
-
reason: Literal["timeout", "
|
|
76
|
+
# `timeout`: the eval's `timeout` ran out. `canceled`: the run was stopped.
|
|
77
|
+
reason: Literal["timeout", "canceled"]
|
|
78
78
|
type: Literal["abort"]
|
|
79
79
|
|
|
80
80
|
|
|
@@ -389,6 +389,8 @@ class CaptureCallData(TypedDict):
|
|
|
389
389
|
instructions: NotRequired[list[CaptureInstruction]]
|
|
390
390
|
# Overrides cascaded `capture.model` for this call.
|
|
391
391
|
model: NotRequired[str]
|
|
392
|
+
# The `artifacts/<name>.png` file name: 1 to 100 of `a-z`, `0-9`, `_` and `-`, starting with
|
|
393
|
+
# a letter or digit. Unique within the attempt.
|
|
392
394
|
name: NotRequired[str]
|
|
393
395
|
path: NotRequired[str]
|
|
394
396
|
timeout: NotRequired[Duration]
|
|
@@ -500,7 +502,7 @@ class CodexHarnessDescriptor(TypedDict):
|
|
|
500
502
|
class ConfigJudgeOptions(TypedDict):
|
|
501
503
|
# The gateway the judge's calls go through, by name or by its helper. It skips the config's
|
|
502
504
|
# `gateway` fallback: the plan refuses a gateway that isn't listed or can't serve the model.
|
|
503
|
-
gateway: NotRequired[DirectGatewayDescriptor | VercelGatewayDescriptor | Literal["direct", "vercel"]]
|
|
505
|
+
gateway: NotRequired[DirectGatewayDescriptor | OpenRouterGatewayDescriptor | VercelGatewayDescriptor | VertexGatewayDescriptor | Literal["direct", "openrouter", "vercel", "vertex"]]
|
|
504
506
|
# The most judge calls to this model's provider at once, across the run.
|
|
505
507
|
maxConcurrent: NotRequired[float]
|
|
506
508
|
# `'provider/model'`, or a fake judge.
|
|
@@ -557,6 +559,7 @@ class DeclaredEval(TypedDict):
|
|
|
557
559
|
name: str
|
|
558
560
|
# The eval's own options, before the cascade.
|
|
559
561
|
options: EvalOptionsData
|
|
562
|
+
source: DeclaredSource
|
|
560
563
|
type: Literal["eval"]
|
|
561
564
|
|
|
562
565
|
|
|
@@ -589,6 +592,16 @@ DeclaredMode: TypeAlias = "Literal[\"run\", \"skip\", \"only\"]"
|
|
|
589
592
|
DeclaredNode: TypeAlias = "DeclaredEval | DeclaredGroup | DeclaredTodo"
|
|
590
593
|
|
|
591
594
|
|
|
595
|
+
# An eval's own source, hashed by the SDK that declared it: sha256 (hex) of its body's source
|
|
596
|
+
# text, and of an `evaluate.each` case's data as canonical JSON (object keys sorted, no
|
|
597
|
+
# whitespace, non-ASCII characters as themselves).
|
|
598
|
+
class DeclaredSource(TypedDict):
|
|
599
|
+
# sha256 of the body function's source text.
|
|
600
|
+
body: str
|
|
601
|
+
# sha256 of the case's data; set on each case of an `evaluate.each`.
|
|
602
|
+
case: NotRequired[str]
|
|
603
|
+
|
|
604
|
+
|
|
592
605
|
# `evaluate.todo` or `group.todo`.
|
|
593
606
|
class DeclaredTodo(TypedDict):
|
|
594
607
|
# The name passed to `.todo`.
|
|
@@ -601,8 +614,12 @@ class Diff(TypedDict):
|
|
|
601
614
|
patch: str
|
|
602
615
|
|
|
603
616
|
|
|
604
|
-
# Each provider's own key
|
|
617
|
+
# Each provider's own key: made by `direct()` in `reeval/gateway`. Every `gateway` list ends
|
|
618
|
+
# with it unless it's listed elsewhere, where it's tried in that place.
|
|
605
619
|
class DirectGatewayDescriptor(TypedDict):
|
|
620
|
+
# `false` turns off the fallback to each provider's own key, so a cell no other gateway in
|
|
621
|
+
# the list serves is refused at plan time. Default `true`.
|
|
622
|
+
enabled: NotRequired[bool]
|
|
606
623
|
kind: Literal["direct"]
|
|
607
624
|
|
|
608
625
|
|
|
@@ -628,7 +645,7 @@ DockerSandboxDescriptor = TypedDict(
|
|
|
628
645
|
)
|
|
629
646
|
|
|
630
647
|
|
|
631
|
-
# More than zero: `90s`, `15m`, `1h`, or a number of milliseconds.
|
|
648
|
+
# More than zero and at most 596h: `90s`, `15m`, `1h`, or a number of milliseconds.
|
|
632
649
|
Duration: TypeAlias = "float | str"
|
|
633
650
|
|
|
634
651
|
|
|
@@ -755,7 +772,13 @@ class EvalOptionsDataCompare(TypedDict):
|
|
|
755
772
|
# all end; one without is asked of each attempt as it ends. Replaces the inherited list, so
|
|
756
773
|
# `[]` asks none.
|
|
757
774
|
questions: NotRequired[list[Question]]
|
|
758
|
-
#
|
|
775
|
+
# The eval's version, part of its fingerprint: bump it when a change the eval file doesn't
|
|
776
|
+
# show (a helper, a fixture, data it reads) changes what it measures. A group's applies to
|
|
777
|
+
# the evals inside it that set none.
|
|
778
|
+
revision: NotRequired[float | str]
|
|
779
|
+
# Attempts per cell. Unset at every level, a compare with variants runs 4 a side, the fewest
|
|
780
|
+
# whose lift can reach a verdict, and anything else runs once, a pooled `evaluate.each` case
|
|
781
|
+
# included.
|
|
759
782
|
runs: NotRequired[float]
|
|
760
783
|
# The default machine when the resolved environment omits `sandbox`. Cascades like
|
|
761
784
|
# `harness`. Not an environment: it does not invent skills, files or a name.
|
|
@@ -824,7 +847,13 @@ class EvalOptionsDataCompare2(TypedDict):
|
|
|
824
847
|
# all end; one without is asked of each attempt as it ends. Replaces the inherited list, so
|
|
825
848
|
# `[]` asks none.
|
|
826
849
|
questions: NotRequired[list[Question]]
|
|
827
|
-
#
|
|
850
|
+
# The eval's version, part of its fingerprint: bump it when a change the eval file doesn't
|
|
851
|
+
# show (a helper, a fixture, data it reads) changes what it measures. A group's applies to
|
|
852
|
+
# the evals inside it that set none.
|
|
853
|
+
revision: NotRequired[float | str]
|
|
854
|
+
# Attempts per cell. Unset at every level, a compare with variants runs 4 a side, the fewest
|
|
855
|
+
# whose lift can reach a verdict, and anything else runs once, a pooled `evaluate.each` case
|
|
856
|
+
# included.
|
|
828
857
|
runs: NotRequired[float]
|
|
829
858
|
# The default machine when the resolved environment omits `sandbox`. Cascades like
|
|
830
859
|
# `harness`. Not an environment: it does not invent skills, files or a name.
|
|
@@ -877,7 +906,13 @@ class EvalOptionsData3(TypedDict):
|
|
|
877
906
|
# all end; one without is asked of each attempt as it ends. Replaces the inherited list, so
|
|
878
907
|
# `[]` asks none.
|
|
879
908
|
questions: NotRequired[list[Question]]
|
|
880
|
-
#
|
|
909
|
+
# The eval's version, part of its fingerprint: bump it when a change the eval file doesn't
|
|
910
|
+
# show (a helper, a fixture, data it reads) changes what it measures. A group's applies to
|
|
911
|
+
# the evals inside it that set none.
|
|
912
|
+
revision: NotRequired[float | str]
|
|
913
|
+
# Attempts per cell. Unset at every level, a compare with variants runs 4 a side, the fewest
|
|
914
|
+
# whose lift can reach a verdict, and anything else runs once, a pooled `evaluate.each` case
|
|
915
|
+
# included.
|
|
881
916
|
runs: NotRequired[float]
|
|
882
917
|
# The default machine when the resolved environment omits `sandbox`. Cascades like
|
|
883
918
|
# `harness`. Not an environment: it does not invent skills, files or a name.
|
|
@@ -915,7 +950,7 @@ class FakeHarnessDescriptorWithSkill(TypedDict):
|
|
|
915
950
|
|
|
916
951
|
# A canned agent for tests and dry runs, made by `fakeHarness()` in `reeval/harness`.
|
|
917
952
|
class FakeHarnessDescriptor(TypedDict):
|
|
918
|
-
# Simulated turn length,
|
|
953
|
+
# Simulated turn length, honors the abort signal.
|
|
919
954
|
delayMs: NotRequired[float]
|
|
920
955
|
# Requested reasoning effort, recorded as `effort` on the cell. Use `servedModel` for what
|
|
921
956
|
# the turn reports (`fake-1[effort=medium]`). Fast is not this.
|
|
@@ -1026,20 +1061,27 @@ class FileChange(TypedDict):
|
|
|
1026
1061
|
|
|
1027
1062
|
|
|
1028
1063
|
# fx through `@reeval/harness-fx` (`fx acp`), made by `fx()`. It runs on Vercel AI Gateway or
|
|
1029
|
-
# OpenAI's API by the model id, and reports each turn's tokens.
|
|
1064
|
+
# OpenAI's API by the model id, or on OpenRouter or Vertex AI, and reports each turn's tokens.
|
|
1030
1065
|
class FxHarnessDescriptor(TypedDict):
|
|
1066
|
+
# Reasoning effort, sent as `reasoning_effort` on each model call; only on `vertex()`, since
|
|
1067
|
+
# fx sends none itself. Values are what the model takes there, e.g. `low`, `medium` or
|
|
1068
|
+
# `high`.
|
|
1069
|
+
effort: NotRequired[str]
|
|
1031
1070
|
# The gateway this harness's model calls go through, by name. It skips the config's
|
|
1032
1071
|
# `gateway` fallback: the plan refuses a gateway that isn't listed or can't serve this
|
|
1033
1072
|
# harness.
|
|
1034
1073
|
gateway: NotRequired[GatewayName]
|
|
1035
|
-
# Cell id; defaults to `fx:<model
|
|
1074
|
+
# Cell id; defaults to `fx:<model>`, or `fx:<model>[effort=<value>]` when `effort` is set.
|
|
1036
1075
|
id: NotRequired[str]
|
|
1037
1076
|
kind: Literal["fx"]
|
|
1038
1077
|
# The most fx attempts at once, across the run, e.g. for the key's rate limits.
|
|
1039
1078
|
maxConcurrent: NotRequired[float]
|
|
1040
1079
|
# A Vercel AI Gateway model id, `<provider>/<model>`, run on fx's `gateway` provider with
|
|
1041
1080
|
# `AI_GATEWAY_API_KEY`, e.g. `anthropic/claude-sonnet-5`; or a bare OpenAI model id, run on
|
|
1042
|
-
# OpenAI's Chat Completions API with `OPENAI_API_KEY`, e.g. `gpt-5.5`.
|
|
1081
|
+
# OpenAI's Chat Completions API with `OPENAI_API_KEY`, e.g. `gpt-5.5`. On `openrouter()`,
|
|
1082
|
+
# either runs on OpenRouter's Chat Completions API with `OPENROUTER_API_KEY`, a bare id as
|
|
1083
|
+
# `openai/<id>`. On `vertex()`, a Gemini model id, `google/<id>` or a bare `gemini-…` id,
|
|
1084
|
+
# run on Vertex AI's Chat Completions endpoint, e.g. `google/gemini-3.8-flash`.
|
|
1043
1085
|
model: str
|
|
1044
1086
|
# How long the harness's session may take to become ready (its bootstrap and first session)
|
|
1045
1087
|
# before the attempt is an infra error, after one retry; default 90s.
|
|
@@ -1047,8 +1089,9 @@ class FxHarnessDescriptor(TypedDict):
|
|
|
1047
1089
|
|
|
1048
1090
|
|
|
1049
1091
|
# A gateway the config lists by name: `direct` is each provider's own key, as without `gateway`;
|
|
1050
|
-
# `vercel` is Vercel AI Gateway
|
|
1051
|
-
|
|
1092
|
+
# `vercel` is Vercel AI Gateway; `openrouter` is OpenRouter; `vertex` is Google Cloud's Vertex
|
|
1093
|
+
# AI.
|
|
1094
|
+
GatewayName: TypeAlias = "Literal[\"direct\", \"vercel\", \"openrouter\", \"vertex\"]"
|
|
1052
1095
|
|
|
1053
1096
|
|
|
1054
1097
|
# A git repository (or one directory of it) the workspace starts as, from the `git` helper.
|
|
@@ -1149,8 +1192,12 @@ class JudgeArtifactRef(TypedDict):
|
|
|
1149
1192
|
sha256: str
|
|
1150
1193
|
|
|
1151
1194
|
|
|
1152
|
-
# Each provider's own key
|
|
1195
|
+
# Each provider's own key: made by `direct()` in `reeval/gateway`. Every `gateway` list ends
|
|
1196
|
+
# with it unless it's listed elsewhere, where it's tried in that place.
|
|
1153
1197
|
class JudgeCallDataGatewayDirect(TypedDict):
|
|
1198
|
+
# `false` turns off the fallback to each provider's own key, so a cell no other gateway in
|
|
1199
|
+
# the list serves is refused at plan time. Default `true`.
|
|
1200
|
+
enabled: NotRequired[bool]
|
|
1154
1201
|
kind: Literal["direct"]
|
|
1155
1202
|
|
|
1156
1203
|
|
|
@@ -1160,6 +1207,30 @@ class JudgeCallDataGatewayVercel(TypedDict):
|
|
|
1160
1207
|
kind: Literal["vercel"]
|
|
1161
1208
|
|
|
1162
1209
|
|
|
1210
|
+
# OpenRouter, with `OPENROUTER_API_KEY`: made by `openrouter()` in `reeval/gateway`. It serves
|
|
1211
|
+
# Claude Code, Codex, Goose, OpenCode, fx, Grok Build and pi.
|
|
1212
|
+
class JudgeCallDataGatewayOpenrouter(TypedDict):
|
|
1213
|
+
# Let OpenRouter fail over to another upstream provider when the one it picked can't serve a
|
|
1214
|
+
# call. Off by default, so reeval's own calls (the judge, analysis, compare questions and
|
|
1215
|
+
# captures) send `allow_fallbacks: false`. The harnesses' calls take OpenRouter's own
|
|
1216
|
+
# routing either way.
|
|
1217
|
+
fallbacks: NotRequired[bool]
|
|
1218
|
+
kind: Literal["openrouter"]
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
# Google Cloud's Vertex AI, on Application Default Credentials: made by `vertex()` in
|
|
1222
|
+
# `reeval/gateway`.
|
|
1223
|
+
class JudgeCallDataGatewayVertex(TypedDict):
|
|
1224
|
+
kind: Literal["vertex"]
|
|
1225
|
+
# The region the models are called in, e.g. `us-east5`; defaults to `global`.
|
|
1226
|
+
location: NotRequired[str]
|
|
1227
|
+
# The Google Cloud project the calls are billed to; defaults to `GOOGLE_CLOUD_PROJECT`.
|
|
1228
|
+
project: NotRequired[str]
|
|
1229
|
+
# A region per publisher, where it differs from `location`, e.g. `{ anthropic: 'us-east5'
|
|
1230
|
+
# }`.
|
|
1231
|
+
regions: NotRequired[dict[str, str]]
|
|
1232
|
+
|
|
1233
|
+
|
|
1163
1234
|
class JudgeCallDataOnTurn(TypedDict):
|
|
1164
1235
|
# The turn's index, from 0.
|
|
1165
1236
|
turn: int
|
|
@@ -1179,7 +1250,7 @@ class JudgeCallData(TypedDict):
|
|
|
1179
1250
|
artifacts: NotRequired[Literal[True] | list[str | Capture]]
|
|
1180
1251
|
# The gateway the judge's calls go through, by name or by its helper. It skips the config's
|
|
1181
1252
|
# `gateway` fallback: the plan refuses a gateway that isn't listed or can't serve the model.
|
|
1182
|
-
gateway: NotRequired[Literal["direct", "vercel"] | JudgeCallDataGatewayDirect | JudgeCallDataGatewayVercel]
|
|
1253
|
+
gateway: NotRequired[Literal["direct", "vercel", "openrouter", "vertex"] | JudgeCallDataGatewayDirect | JudgeCallDataGatewayVercel | JudgeCallDataGatewayOpenrouter | JudgeCallDataGatewayVertex]
|
|
1183
1254
|
# The most judge calls to this model's provider at once, across the run.
|
|
1184
1255
|
maxConcurrent: NotRequired[int]
|
|
1185
1256
|
# `'provider/model'`, or a fake judge with a numeric score.
|
|
@@ -1203,7 +1274,7 @@ class JudgeFailure(TypedDict):
|
|
|
1203
1274
|
artifacts: NotRequired[list[JudgeArtifactRef]]
|
|
1204
1275
|
error: str
|
|
1205
1276
|
# The gateway from the config's `gateway` list the call went through; unset without one.
|
|
1206
|
-
gateway: NotRequired[Literal["direct", "vercel"]]
|
|
1277
|
+
gateway: NotRequired[Literal["direct", "vercel", "openrouter", "vertex"]]
|
|
1207
1278
|
model: str
|
|
1208
1279
|
question: str
|
|
1209
1280
|
score: None
|
|
@@ -1326,6 +1397,17 @@ class OpenCodeHarnessDescriptor(TypedDict):
|
|
|
1326
1397
|
startTimeout: NotRequired[Duration]
|
|
1327
1398
|
|
|
1328
1399
|
|
|
1400
|
+
# OpenRouter, with `OPENROUTER_API_KEY`: made by `openrouter()` in `reeval/gateway`. It serves
|
|
1401
|
+
# Claude Code, Codex, Goose, OpenCode, fx, Grok Build and pi.
|
|
1402
|
+
class OpenRouterGatewayDescriptor(TypedDict):
|
|
1403
|
+
# Let OpenRouter fail over to another upstream provider when the one it picked can't serve a
|
|
1404
|
+
# call. Off by default, so reeval's own calls (the judge, analysis, compare questions and
|
|
1405
|
+
# captures) send `allow_fallbacks: false`. The harnesses' calls take OpenRouter's own
|
|
1406
|
+
# routing either way.
|
|
1407
|
+
fallbacks: NotRequired[bool]
|
|
1408
|
+
kind: Literal["openrouter"]
|
|
1409
|
+
|
|
1410
|
+
|
|
1329
1411
|
# A matrix factor a pairwise question varies; the others are held.
|
|
1330
1412
|
PairFactor: TypeAlias = "Literal[\"environment\", \"harness\"]"
|
|
1331
1413
|
|
|
@@ -1679,7 +1761,9 @@ class TranscriptEventFileEdit(TypedDict):
|
|
|
1679
1761
|
added: float | None
|
|
1680
1762
|
atMs: float
|
|
1681
1763
|
durationMs: float | None
|
|
1682
|
-
|
|
1764
|
+
# `write` is a whole-file write whose result doesn't say whether the file existed before
|
|
1765
|
+
# (Antigravity's, fx's, Goose's, Grok Build's and pi's write tools, for example).
|
|
1766
|
+
kind: Literal["create", "modify", "write", "delete"]
|
|
1683
1767
|
path: str
|
|
1684
1768
|
removed: float | None
|
|
1685
1769
|
# True when a shell-like tool or edit ran at least two minutes.
|
|
@@ -1744,7 +1828,7 @@ class TranscriptEventRetry(TypedDict):
|
|
|
1744
1828
|
type: Literal["retry"]
|
|
1745
1829
|
|
|
1746
1830
|
|
|
1747
|
-
#
|
|
1831
|
+
# Normalized agent events. Each tool call is one event, merged with its result; `durationMs` is
|
|
1748
1832
|
# wall time until the result arrived (Cursor's own `completed_at_ms` can be earlier). Null when
|
|
1749
1833
|
# the harness never reported the end. Long shell or edit calls set `stalled: true`.
|
|
1750
1834
|
TranscriptEvent: TypeAlias = "TranscriptEvent1 | TranscriptEventFileEdit | TranscriptEventWebSearch | TranscriptEventWebFetch | TranscriptEventTool | TranscriptEventError | TranscriptEventRetry"
|
|
@@ -1786,8 +1870,11 @@ class VercelGatewayDescriptor(TypedDict):
|
|
|
1786
1870
|
class Verdict(TypedDict):
|
|
1787
1871
|
# Captures this call sent the judge as file parts, when any.
|
|
1788
1872
|
artifacts: NotRequired[list[JudgeArtifactRef]]
|
|
1873
|
+
# What the gateway says the call cost, in US dollars (OpenRouter's `usage.cost`); unset when
|
|
1874
|
+
# it names none.
|
|
1875
|
+
costUsd: NotRequired[float]
|
|
1789
1876
|
# The gateway from the config's `gateway` list the call went through; unset without one.
|
|
1790
|
-
gateway: NotRequired[Literal["direct", "vercel"]]
|
|
1877
|
+
gateway: NotRequired[Literal["direct", "vercel", "openrouter", "vertex"]]
|
|
1791
1878
|
model: str
|
|
1792
1879
|
question: str
|
|
1793
1880
|
# The model id the provider says served the call, when it reports one.
|
|
@@ -1795,9 +1882,25 @@ class Verdict(TypedDict):
|
|
|
1795
1882
|
# The judge's probability that the answer is yes, from 0 to 1.
|
|
1796
1883
|
score: float
|
|
1797
1884
|
threshold: float
|
|
1885
|
+
# The upstream provider the gateway says served the call (OpenRouter's); unset when it names
|
|
1886
|
+
# none.
|
|
1887
|
+
upstream: NotRequired[str]
|
|
1798
1888
|
verdict: Literal["pass", "fail"]
|
|
1799
1889
|
|
|
1800
1890
|
|
|
1891
|
+
# Google Cloud's Vertex AI, on Application Default Credentials: made by `vertex()` in
|
|
1892
|
+
# `reeval/gateway`.
|
|
1893
|
+
class VertexGatewayDescriptor(TypedDict):
|
|
1894
|
+
kind: Literal["vertex"]
|
|
1895
|
+
# The region the models are called in, e.g. `us-east5`; defaults to `global`.
|
|
1896
|
+
location: NotRequired[str]
|
|
1897
|
+
# The Google Cloud project the calls are billed to; defaults to `GOOGLE_CLOUD_PROJECT`.
|
|
1898
|
+
project: NotRequired[str]
|
|
1899
|
+
# A region per publisher, where it differs from `location`, e.g. `{ anthropic: 'us-east5'
|
|
1900
|
+
# }`.
|
|
1901
|
+
regions: NotRequired[dict[str, str]]
|
|
1902
|
+
|
|
1903
|
+
|
|
1801
1904
|
class WaitMs(TypedDict):
|
|
1802
1905
|
ms: float
|
|
1803
1906
|
|
|
@@ -1816,6 +1919,7 @@ CAMEL_KEYS: frozenset[str] = frozenset(
|
|
|
1816
1919
|
"atMs",
|
|
1817
1920
|
"cacheRead",
|
|
1818
1921
|
"cacheWrite",
|
|
1922
|
+
"costUsd",
|
|
1819
1923
|
"delayMs",
|
|
1820
1924
|
"durationMs",
|
|
1821
1925
|
"exitCode",
|
|
@@ -1850,6 +1954,7 @@ RECORD_FIELDS: frozenset[str] = frozenset(
|
|
|
1850
1954
|
"mcp",
|
|
1851
1955
|
"params",
|
|
1852
1956
|
"providerOptions",
|
|
1957
|
+
"regions",
|
|
1853
1958
|
"score",
|
|
1854
1959
|
"untested",
|
|
1855
1960
|
"value",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.15.1",
|
|
3
|
+
"binaries": {
|
|
4
|
+
"linux-x64": {
|
|
5
|
+
"name": "reeval-0.15.1-linux-x64",
|
|
6
|
+
"sha256": "1514aa35048abc1bb90174f4bae90cbad829008c62cc7c61b7a061d616f190aa",
|
|
7
|
+
"size": 90777056
|
|
8
|
+
},
|
|
9
|
+
"linux-arm64": {
|
|
10
|
+
"name": "reeval-0.15.1-linux-arm64",
|
|
11
|
+
"sha256": "9765c6f7b5c83cea1f4040143ee15403540404598897b6cef90eaa695b4c7f90",
|
|
12
|
+
"size": 90753320
|
|
13
|
+
},
|
|
14
|
+
"linux-x64-musl": {
|
|
15
|
+
"name": "reeval-0.15.1-linux-x64-musl",
|
|
16
|
+
"sha256": "98524643d0be87ba8f8a4320ec2e20f6c6c1e7a77cd5418005e6602a0cc54f0b",
|
|
17
|
+
"size": 84747824
|
|
18
|
+
},
|
|
19
|
+
"linux-arm64-musl": {
|
|
20
|
+
"name": "reeval-0.15.1-linux-arm64-musl",
|
|
21
|
+
"sha256": "822041705bdaaff553fa34286abf29435c2e9866afd9ee6fa76b06a1c55730f4",
|
|
22
|
+
"size": 83889360
|
|
23
|
+
},
|
|
24
|
+
"darwin-x64": {
|
|
25
|
+
"name": "reeval-0.15.1-darwin-x64",
|
|
26
|
+
"sha256": "cea280d80792e701b9b1d559415d24a02f3cdf6ba1040f9d428e5bf5bb97e533",
|
|
27
|
+
"size": 78786832
|
|
28
|
+
},
|
|
29
|
+
"darwin-arm64": {
|
|
30
|
+
"name": "reeval-0.15.1-darwin-arm64",
|
|
31
|
+
"sha256": "e7f1fdeb31f41e79ec137d22ff036a25201f0e960f996b669602aef246462c16",
|
|
32
|
+
"size": 71754354
|
|
33
|
+
},
|
|
34
|
+
"windows-x64": {
|
|
35
|
+
"name": "reeval-0.15.1-windows-x64.exe",
|
|
36
|
+
"sha256": "efe14b3ee751bbf4132c476eb138e8a32036122d1f4ec6f927bb31f302d0e32d",
|
|
37
|
+
"size": 95552512
|
|
38
|
+
},
|
|
39
|
+
"linux-x64-baseline": {
|
|
40
|
+
"name": "reeval-0.15.1-linux-x64-baseline",
|
|
41
|
+
"sha256": "0d3dca1f8ed50316aff10edf990611394da259d280bbeb52704548080508967d",
|
|
42
|
+
"size": 90777056
|
|
43
|
+
},
|
|
44
|
+
"linux-x64-musl-baseline": {
|
|
45
|
+
"name": "reeval-0.15.1-linux-x64-musl-baseline",
|
|
46
|
+
"sha256": "a5091a4a02eed53d676c5f670a661c18657507b425bf28705b4f7afeb8417d80",
|
|
47
|
+
"size": 84747824
|
|
48
|
+
},
|
|
49
|
+
"windows-x64-baseline": {
|
|
50
|
+
"name": "reeval-0.15.1-windows-x64-baseline.exe",
|
|
51
|
+
"sha256": "8bc8772981f5352f3a08824193a1a59c324e105508b20c41814ec4e41f500c58",
|
|
52
|
+
"size": 95552512
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -40,13 +40,26 @@ class ShimError(Exception):
|
|
|
40
40
|
self.code = code
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
PRERELEASES = {
|
|
43
|
+
PRERELEASES = {
|
|
44
|
+
"a": "alpha",
|
|
45
|
+
"alpha": "alpha",
|
|
46
|
+
"b": "beta",
|
|
47
|
+
"beta": "beta",
|
|
48
|
+
"c": "rc",
|
|
49
|
+
"pre": "rc",
|
|
50
|
+
"preview": "rc",
|
|
51
|
+
"rc": "rc",
|
|
52
|
+
}
|
|
44
53
|
|
|
45
54
|
|
|
46
55
|
def semver(version: str) -> str:
|
|
47
|
-
"""reeval's version for a reeval-python version: 1.0.0b1 is 1.0.0-beta.1.
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
"""reeval's version for a reeval-python version: 1.0.0b1 is 1.0.0-beta.1.
|
|
57
|
+
|
|
58
|
+
Takes any PEP 440 spelling of a prerelease (1.0.0-rc1, 1.0.0RC1 and 1.0.0c1 are 1.0.0rc1).
|
|
59
|
+
"""
|
|
60
|
+
pattern = r"(\d+\.\d+\.\d+)[-_.]?(alpha|a|beta|b|preview|pre|rc|c)[-_.]?(\d+)"
|
|
61
|
+
if m := re.fullmatch(pattern, version, re.IGNORECASE):
|
|
62
|
+
return f"{m[1]}-{PRERELEASES[m[2].lower()]}.{m[3]}"
|
|
50
63
|
return version
|
|
51
64
|
|
|
52
65
|
|
|
@@ -58,9 +71,13 @@ def own_version() -> str | None:
|
|
|
58
71
|
return None
|
|
59
72
|
|
|
60
73
|
|
|
74
|
+
def pinned_version() -> str:
|
|
75
|
+
"""REEVAL_VERSION as reeval spells its version, so 1.0.0rc1 is 1.0.0-rc.1; empty if unset."""
|
|
76
|
+
return semver(os.environ.get("REEVAL_VERSION", "").strip().removeprefix("v"))
|
|
77
|
+
|
|
78
|
+
|
|
61
79
|
def binary_version() -> str:
|
|
62
|
-
pinned
|
|
63
|
-
if pinned:
|
|
80
|
+
if pinned := pinned_version():
|
|
64
81
|
return pinned
|
|
65
82
|
if own := own_version():
|
|
66
83
|
return own
|
|
@@ -415,7 +432,7 @@ def resolve() -> Path:
|
|
|
415
432
|
f"REEVAL_BINARY is {override}, which is this `reeval` command, not the binary.", 2
|
|
416
433
|
)
|
|
417
434
|
return path
|
|
418
|
-
pinned =
|
|
435
|
+
pinned = pinned_version()
|
|
419
436
|
own = bundled()
|
|
420
437
|
if own.is_file() and pinned in ("", own_version()):
|
|
421
438
|
if not WINDOWS and not os.access(own, os.X_OK):
|