onejudge 0.3.2__py3-none-any.whl
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.
- onejudge-0.3.2.dist-info/METADATA +72 -0
- onejudge-0.3.2.dist-info/RECORD +14 -0
- onejudge-0.3.2.dist-info/WHEEL +4 -0
- onejudge-0.3.2.dist-info/licenses/LICENSE +21 -0
- onejudge_sdk/__init__.py +18 -0
- onejudge_sdk/_client.py +156 -0
- onejudge_sdk/_errors.py +26 -0
- onejudge_sdk/_generated/__init__.py +1 -0
- onejudge_sdk/_generated/input-keys.json +22 -0
- onejudge_sdk/_generated/schemas.json +588 -0
- onejudge_sdk/_generated_types.py +20 -0
- onejudge_sdk/_result.py +43 -0
- onejudge_sdk/_version.py +3 -0
- onejudge_sdk/py.typed +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: onejudge
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Typed async Python SDK for onejudge
|
|
5
|
+
Keywords: agent,evaluation,judge,harness
|
|
6
|
+
Author: Nick DeRobertis
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: jsonschema>=4.18,<5
|
|
21
|
+
Requires-Dist: onejudge-cli==0.3.2
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Project-URL: Homepage, https://github.com/nickderobertis/onejudge
|
|
24
|
+
Project-URL: Repository, https://github.com/nickderobertis/onejudge
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# onejudge
|
|
28
|
+
|
|
29
|
+
Typed async Python access to the `onejudge` CLI. The distribution is
|
|
30
|
+
`onejudge`, the import is `onejudge_sdk`, and each release depends on the
|
|
31
|
+
exact same `onejudge-cli` version.
|
|
32
|
+
|
|
33
|
+
```console
|
|
34
|
+
pip install onejudge
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import asyncio
|
|
39
|
+
|
|
40
|
+
from onejudge_sdk import OneJudge
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def main() -> None:
|
|
44
|
+
result = await OneJudge().run(
|
|
45
|
+
{"provider": {"kind": "oneharness"}},
|
|
46
|
+
"Review this repository",
|
|
47
|
+
cwd="/path/to/repository",
|
|
48
|
+
timeout=3600,
|
|
49
|
+
)
|
|
50
|
+
print(result.completed, result.assistant_turns, result.verdicts)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`run` validates the config before starting the CLI, writes a temporary effective
|
|
57
|
+
JSON config (JSON is valid YAML), and always sends the task over stdin with
|
|
58
|
+
`--task -`. It accepts a `provider` override, subprocess `cwd`, additional `env`
|
|
59
|
+
(including `ONEHARNESS_HISTORY_LABELS` and `ONEHARNESS_TIMEOUT`), and a timeout.
|
|
60
|
+
Executable resolution is the constructor's `executable`, then `ONEJUDGE_BIN`,
|
|
61
|
+
then `onejudge` on `PATH`.
|
|
62
|
+
|
|
63
|
+
Exit 0 and 1 return `RunResult`: `exit_code` and `stderr` remain available, and
|
|
64
|
+
`raw`, `completed`, `verdicts`, `usage`, `assistant_turns`, and `agent_turns`
|
|
65
|
+
cover ai-orchestrator's dispatch needs. Exit 2 (bad config or provider/runtime
|
|
66
|
+
failure) and unexpected nonzero exits raise `OneJudgeProcessError` without
|
|
67
|
+
discarding the exit code or stderr. A caller timeout raises
|
|
68
|
+
`OneJudgeTimeoutError`.
|
|
69
|
+
|
|
70
|
+
There is no `run_stream` method. `onejudge run` currently emits one final JSON
|
|
71
|
+
report; the JSONL interface in `docs/protocol.md` is the internal provider
|
|
72
|
+
protocol, not a CLI result stream.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
onejudge_sdk/__init__.py,sha256=misPPdJcb4w_wQFYq1u9bpMsH_0IGT-N_rP_ziHGAE4,449
|
|
2
|
+
onejudge_sdk/_client.py,sha256=jkfmZLybBRuAMZnU4qGOdXn0NQlDZXExFw1fynP4eYg,6194
|
|
3
|
+
onejudge_sdk/_errors.py,sha256=8ZyD2mk8iqnmIdebtHwbnHuJtMvVRd_V4StBbYNLklU,816
|
|
4
|
+
onejudge_sdk/_generated/__init__.py,sha256=kval8EHvlbftbLuIpP9C1qXAAAh0-AlYEb7QyZsvUFk,40
|
|
5
|
+
onejudge_sdk/_generated/input-keys.json,sha256=JlyMaGP_jJVjdP_g2F3AoaenIhQduH__2h6ZaNYB2E8,493
|
|
6
|
+
onejudge_sdk/_generated/schemas.json,sha256=VrtLAQWDRtdcYSD7CwH8yH-R6toOEu9RLedVAItUmNg,20829
|
|
7
|
+
onejudge_sdk/_generated_types.py,sha256=nrzxh4NcXFOTLWbLokxlVI6NGtJg-urGs0BDBoHeZu4,392
|
|
8
|
+
onejudge_sdk/_result.py,sha256=Rl_TexDnH6VV8s70krn-l4Ll3TRex7UItarvH-eOrgo,1261
|
|
9
|
+
onejudge_sdk/_version.py,sha256=2dFqS9d_6JCrfcNn-3y8oLeLOYQJZnizedpcyihEP6A,98
|
|
10
|
+
onejudge_sdk/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
11
|
+
onejudge-0.3.2.dist-info/licenses/LICENSE,sha256=-nAcsuNjoW1EEe6T3WqyGyg4vFrdsiTjA_oo_El8epY,1072
|
|
12
|
+
onejudge-0.3.2.dist-info/WHEEL,sha256=YR4QUxGCbsyoOnWBVTv-p1I4yc3seKGPev46mvmc8Cg,81
|
|
13
|
+
onejudge-0.3.2.dist-info/METADATA,sha256=_9HC1Aj7GyLzbYnWJtyalwgidJvD-HLMlKBwW7g6IPc,2592
|
|
14
|
+
onejudge-0.3.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Nick DeRobertis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
onejudge_sdk/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Async Python SDK for the onejudge CLI."""
|
|
2
|
+
|
|
3
|
+
from ._client import OneJudge
|
|
4
|
+
from ._errors import ContractError, OneJudgeProcessError, OneJudgeTimeoutError
|
|
5
|
+
from ._generated_types import RunConfig, RunReport
|
|
6
|
+
from ._result import RunResult
|
|
7
|
+
from ._version import __version__
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ContractError",
|
|
11
|
+
"OneJudge",
|
|
12
|
+
"OneJudgeProcessError",
|
|
13
|
+
"OneJudgeTimeoutError",
|
|
14
|
+
"RunConfig",
|
|
15
|
+
"RunReport",
|
|
16
|
+
"RunResult",
|
|
17
|
+
"__version__",
|
|
18
|
+
]
|
onejudge_sdk/_client.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Async subprocess client for onejudge's validated JSON interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import tempfile
|
|
10
|
+
from collections.abc import Mapping, Sequence
|
|
11
|
+
from functools import cache
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Optional, cast
|
|
14
|
+
|
|
15
|
+
from jsonschema import Draft202012Validator
|
|
16
|
+
from jsonschema.protocols import Validator
|
|
17
|
+
|
|
18
|
+
from ._errors import ContractError, OneJudgeProcessError, OneJudgeTimeoutError
|
|
19
|
+
from ._generated_types import RunConfig, RunReport
|
|
20
|
+
from ._result import RunResult
|
|
21
|
+
|
|
22
|
+
_STREAM_LIMIT = 16 * 1024 * 1024
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_json(name: str) -> dict[str, Any]:
|
|
26
|
+
path = Path(__file__).with_name("_generated") / name
|
|
27
|
+
return cast("dict[str, Any]", json.loads(path.read_text(encoding="utf-8")))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
_SCHEMAS = _load_json("schemas.json")
|
|
31
|
+
_INPUT_KEYS = cast("dict[str, dict[str, str]]", _load_json("input-keys.json"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@cache
|
|
35
|
+
def _validator(root: str) -> Validator:
|
|
36
|
+
schema = _SCHEMAS[root]
|
|
37
|
+
Draft202012Validator.check_schema(schema)
|
|
38
|
+
return Draft202012Validator(schema)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _validate(root: str, value: Any, label: str) -> Any:
|
|
42
|
+
errors = sorted(_validator(root).iter_errors(value), key=lambda error: list(error.path))
|
|
43
|
+
if not errors:
|
|
44
|
+
return value
|
|
45
|
+
details = []
|
|
46
|
+
for error in errors:
|
|
47
|
+
path = ".".join(str(part) for part in error.absolute_path) or "<root>"
|
|
48
|
+
details.append(f"{path}: {error.message}")
|
|
49
|
+
raise ContractError(f"{label}: {'; '.join(details)}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _input(value: Any) -> dict[str, Any]:
|
|
53
|
+
checked = cast("Mapping[str, Any]", _validate("run_config", value, "invalid onejudge config"))
|
|
54
|
+
keys = _INPUT_KEYS["run_config"]
|
|
55
|
+
return {keys.get(key, key): item for key, item in checked.items()}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def _terminate(process: asyncio.subprocess.Process) -> None:
|
|
59
|
+
if process.returncode is not None:
|
|
60
|
+
return
|
|
61
|
+
try:
|
|
62
|
+
process.terminate()
|
|
63
|
+
except ProcessLookupError: # pragma: no cover - OS race after returncode check
|
|
64
|
+
return
|
|
65
|
+
try:
|
|
66
|
+
await asyncio.wait_for(process.wait(), timeout=2)
|
|
67
|
+
except asyncio.TimeoutError: # pragma: no cover - defensive hard-kill fallback
|
|
68
|
+
process.kill()
|
|
69
|
+
await process.wait()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class OneJudge:
|
|
73
|
+
"""Validated async access to an installed onejudge CLI."""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
*,
|
|
78
|
+
executable: Optional[str] = None,
|
|
79
|
+
executable_args: Sequence[str] = (),
|
|
80
|
+
env: Optional[Mapping[str, str]] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
self._executable = executable
|
|
83
|
+
self._executable_args = tuple(executable_args)
|
|
84
|
+
self._env = dict(env or {})
|
|
85
|
+
|
|
86
|
+
def _command(self, args: Sequence[str], path: Optional[str] = None) -> tuple[str, ...]:
|
|
87
|
+
command = self._executable or os.environ.get("ONEJUDGE_BIN")
|
|
88
|
+
if command is None:
|
|
89
|
+
command = shutil.which("onejudge", path=path) or "onejudge"
|
|
90
|
+
return (command, *self._executable_args, *args)
|
|
91
|
+
|
|
92
|
+
async def run(
|
|
93
|
+
self,
|
|
94
|
+
config: RunConfig,
|
|
95
|
+
task: str,
|
|
96
|
+
*,
|
|
97
|
+
provider: Optional[str] = None,
|
|
98
|
+
cwd: Optional[str] = None,
|
|
99
|
+
env: Optional[Mapping[str, str]] = None,
|
|
100
|
+
timeout: Optional[float] = None,
|
|
101
|
+
) -> RunResult:
|
|
102
|
+
"""Run one task and return exit-faithful process and report data."""
|
|
103
|
+
parsed = _input(config)
|
|
104
|
+
if not isinstance(task, str):
|
|
105
|
+
raise ContractError("invalid onejudge task: expected a string")
|
|
106
|
+
if provider not in (None, "oneharness", "command", "split"):
|
|
107
|
+
raise ContractError("invalid onejudge provider: expected oneharness, command, or split")
|
|
108
|
+
if timeout is not None and (isinstance(timeout, bool) or timeout <= 0):
|
|
109
|
+
raise ContractError("invalid onejudge timeout: expected a positive number")
|
|
110
|
+
process_env = {**os.environ, **self._env, **dict(env or {})}
|
|
111
|
+
for key, item in process_env.items():
|
|
112
|
+
if not isinstance(key, str) or not key or "=" in key or "\0" in key:
|
|
113
|
+
raise ContractError(f"invalid environment variable name: {key!r}")
|
|
114
|
+
if not isinstance(item, str) or "\0" in item:
|
|
115
|
+
raise ContractError(f"invalid environment variable {key!r}: expected a string")
|
|
116
|
+
with tempfile.TemporaryDirectory(prefix="onejudge-python-") as directory:
|
|
117
|
+
config_path = Path(directory) / "effective.onejudge.json"
|
|
118
|
+
config_path.write_text(json.dumps(parsed), encoding="utf-8")
|
|
119
|
+
args = ["run", str(config_path), "--task", "-", "--format", "json"]
|
|
120
|
+
if provider is not None:
|
|
121
|
+
args.extend(("--provider", provider))
|
|
122
|
+
process = await asyncio.create_subprocess_exec(
|
|
123
|
+
*self._command(args, process_env.get("PATH")),
|
|
124
|
+
cwd=cwd,
|
|
125
|
+
env=process_env,
|
|
126
|
+
stdin=asyncio.subprocess.PIPE,
|
|
127
|
+
stdout=asyncio.subprocess.PIPE,
|
|
128
|
+
stderr=asyncio.subprocess.PIPE,
|
|
129
|
+
limit=_STREAM_LIMIT,
|
|
130
|
+
)
|
|
131
|
+
try:
|
|
132
|
+
communication = process.communicate(task.encode())
|
|
133
|
+
if timeout is None:
|
|
134
|
+
stdout_bytes, stderr_bytes = await communication
|
|
135
|
+
else:
|
|
136
|
+
stdout_bytes, stderr_bytes = await asyncio.wait_for(
|
|
137
|
+
communication, timeout=timeout
|
|
138
|
+
)
|
|
139
|
+
except asyncio.TimeoutError as error:
|
|
140
|
+
await _terminate(process)
|
|
141
|
+
raise OneJudgeTimeoutError(timeout or 0) from error
|
|
142
|
+
except BaseException:
|
|
143
|
+
await _terminate(process)
|
|
144
|
+
raise
|
|
145
|
+
stderr = stderr_bytes.decode("utf-8", errors="replace")
|
|
146
|
+
returncode = process.returncode or 0
|
|
147
|
+
if returncode not in (0, 1):
|
|
148
|
+
raise OneJudgeProcessError(returncode, stderr)
|
|
149
|
+
try:
|
|
150
|
+
value = json.loads(stdout_bytes)
|
|
151
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
152
|
+
raise ContractError(f"onejudge returned invalid JSON: {error}") from error
|
|
153
|
+
report = cast(
|
|
154
|
+
"RunReport", _validate("report", value, "invalid onejudge report contract")
|
|
155
|
+
)
|
|
156
|
+
return RunResult(exit_code=returncode, stderr=stderr, raw=report)
|
onejudge_sdk/_errors.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Typed public errors raised by the Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ContractError(ValueError):
|
|
9
|
+
"""A value did not match its Rust-owned SDK contract."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OneJudgeProcessError(RuntimeError):
|
|
13
|
+
"""The onejudge subprocess could not produce a report."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, returncode: int, stderr: str) -> None:
|
|
16
|
+
self.returncode = returncode
|
|
17
|
+
self.stderr = stderr
|
|
18
|
+
super().__init__(f"onejudge exited {returncode}: {stderr.strip()}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OneJudgeTimeoutError(OneJudgeProcessError):
|
|
22
|
+
"""The onejudge subprocess exceeded the caller's timeout."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, timeout: float, stderr: str = "") -> None:
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
super().__init__(-1, stderr or f"timed out after {timeout} seconds")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Rust-generated Python SDK assets."""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"run_config": {
|
|
3
|
+
"assessment": "assessment",
|
|
4
|
+
"bin": "bin",
|
|
5
|
+
"command": "command",
|
|
6
|
+
"criterion": "criterion",
|
|
7
|
+
"done_when": "done_when",
|
|
8
|
+
"evals": "evals",
|
|
9
|
+
"judge": "judge",
|
|
10
|
+
"judge_config": "judge_config",
|
|
11
|
+
"kind": "kind",
|
|
12
|
+
"max_turns": "max_turns",
|
|
13
|
+
"persona": "persona",
|
|
14
|
+
"provider": "provider",
|
|
15
|
+
"scale": "scale",
|
|
16
|
+
"session": "session",
|
|
17
|
+
"skill": "skill",
|
|
18
|
+
"system_prompt": "system_prompt",
|
|
19
|
+
"task": "task",
|
|
20
|
+
"user": "user"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
{
|
|
2
|
+
"report": {
|
|
3
|
+
"$defs": {
|
|
4
|
+
"JudgeKind": {
|
|
5
|
+
"description": "The kind of judgement requested.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"const": "boolean",
|
|
9
|
+
"description": "A yes/no verdict.",
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"const": "numeric",
|
|
14
|
+
"description": "A score on a `[min, max]` scale.",
|
|
15
|
+
"type": "string"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"JudgeValue": {
|
|
20
|
+
"anyOf": [
|
|
21
|
+
{
|
|
22
|
+
"description": "A boolean verdict.",
|
|
23
|
+
"type": "boolean"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"description": "A numeric score.",
|
|
27
|
+
"format": "double",
|
|
28
|
+
"type": "number"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"description": "The raw value a judge returns: a boolean or a number, matching the query kind.\nDeserialized untagged from the provider's `value` field."
|
|
32
|
+
},
|
|
33
|
+
"JudgeVerdict": {
|
|
34
|
+
"description": "A judge verdict: the raw value (bool or number) plus the stated reason. Part\nof onejudge's versioned [`Report`](crate::Report) contract, so it round-trips\nthrough serde.",
|
|
35
|
+
"properties": {
|
|
36
|
+
"reason": {
|
|
37
|
+
"default": "",
|
|
38
|
+
"description": "The judge's one-sentence justification.",
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"usage": {
|
|
42
|
+
"anyOf": [
|
|
43
|
+
{
|
|
44
|
+
"$ref": "#/$defs/Usage"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "null"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"description": "Cost/token usage for the judge call, if reported."
|
|
51
|
+
},
|
|
52
|
+
"value": {
|
|
53
|
+
"$ref": "#/$defs/JudgeValue",
|
|
54
|
+
"description": "The parsed verdict value."
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"required": [
|
|
58
|
+
"value",
|
|
59
|
+
"reason"
|
|
60
|
+
],
|
|
61
|
+
"type": "object"
|
|
62
|
+
},
|
|
63
|
+
"Message": {
|
|
64
|
+
"description": "A single turn in the conversation.",
|
|
65
|
+
"properties": {
|
|
66
|
+
"content": {
|
|
67
|
+
"description": "The turn's text.",
|
|
68
|
+
"type": "string"
|
|
69
|
+
},
|
|
70
|
+
"events": {
|
|
71
|
+
"description": "The normalized tool events the skill took producing this turn (assistant\nturns only, and only when the harness exposed a tool transcript). Empty\notherwise. Surfaced for post-hoc analysis, streamed live, and rendered\ninto the transcript the judge sees.",
|
|
72
|
+
"items": {
|
|
73
|
+
"$ref": "#/$defs/ToolEvent"
|
|
74
|
+
},
|
|
75
|
+
"type": "array"
|
|
76
|
+
},
|
|
77
|
+
"role": {
|
|
78
|
+
"$ref": "#/$defs/Role",
|
|
79
|
+
"description": "Who produced the turn."
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": [
|
|
83
|
+
"role",
|
|
84
|
+
"content"
|
|
85
|
+
],
|
|
86
|
+
"type": "object"
|
|
87
|
+
},
|
|
88
|
+
"NamedVerdict": {
|
|
89
|
+
"description": "A judge verdict paired with the criterion it scored and the kind of\njudgement, so a serialized report is self-describing.",
|
|
90
|
+
"properties": {
|
|
91
|
+
"criterion": {
|
|
92
|
+
"description": "The plain-English criterion that was scored.",
|
|
93
|
+
"type": "string"
|
|
94
|
+
},
|
|
95
|
+
"kind": {
|
|
96
|
+
"$ref": "#/$defs/JudgeKind",
|
|
97
|
+
"description": "Whether it was a boolean or numeric judgement."
|
|
98
|
+
},
|
|
99
|
+
"verdict": {
|
|
100
|
+
"$ref": "#/$defs/JudgeVerdict",
|
|
101
|
+
"description": "The verdict itself (value, reason, and per-call usage)."
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"required": [
|
|
105
|
+
"criterion",
|
|
106
|
+
"kind",
|
|
107
|
+
"verdict"
|
|
108
|
+
],
|
|
109
|
+
"type": "object"
|
|
110
|
+
},
|
|
111
|
+
"Role": {
|
|
112
|
+
"description": "Who produced a message.",
|
|
113
|
+
"oneOf": [
|
|
114
|
+
{
|
|
115
|
+
"const": "user",
|
|
116
|
+
"description": "The (real or simulated) user driving the skill.",
|
|
117
|
+
"type": "string"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"const": "assistant",
|
|
121
|
+
"description": "The skill / assistant under test.",
|
|
122
|
+
"type": "string"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"const": "system",
|
|
126
|
+
"description": "System-level framing, if a provider surfaces it.",
|
|
127
|
+
"type": "string"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"ToolEvent": {
|
|
132
|
+
"description": "One normalized tool-call / action event the skill took during a turn, lifted\nfrom `oneharness`'s `events` array (its `--events` output). Harness-agnostic,\nso a consumer can inspect shell commands, file edits, and tool uses across any\nharness, not just the final text. `input` is the structured, tool-shaped args\nso a consumer can match on the command string or file path without re-parsing.\n\n`input` is free-form JSON, so `Message`/`Transcript` are `PartialEq` but not\n`Eq`.",
|
|
133
|
+
"properties": {
|
|
134
|
+
"index": {
|
|
135
|
+
"default": 0,
|
|
136
|
+
"description": "Position within the run, so ordering (\"did X before Y\") is expressible.",
|
|
137
|
+
"format": "uint",
|
|
138
|
+
"minimum": 0,
|
|
139
|
+
"type": "integer"
|
|
140
|
+
},
|
|
141
|
+
"input": {
|
|
142
|
+
"description": "Structured tool arguments (the command, the file path); `None` when none."
|
|
143
|
+
},
|
|
144
|
+
"kind": {
|
|
145
|
+
"description": "`tool_call` (the skill invoked a tool) or `tool_result` (the observation).",
|
|
146
|
+
"type": "string"
|
|
147
|
+
},
|
|
148
|
+
"name": {
|
|
149
|
+
"description": "Normalized tool name where knowable (e.g. `bash`, `edit_file`); `None` for\na `tool_result` or when the harness did not name it.",
|
|
150
|
+
"type": [
|
|
151
|
+
"string",
|
|
152
|
+
"null"
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
"output": {
|
|
156
|
+
"description": "The result/observation text, when the transcript exposed it.",
|
|
157
|
+
"type": [
|
|
158
|
+
"string",
|
|
159
|
+
"null"
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"required": [
|
|
164
|
+
"kind",
|
|
165
|
+
"index"
|
|
166
|
+
],
|
|
167
|
+
"type": "object"
|
|
168
|
+
},
|
|
169
|
+
"Transcript": {
|
|
170
|
+
"description": "An ordered list of messages.",
|
|
171
|
+
"properties": {
|
|
172
|
+
"messages": {
|
|
173
|
+
"description": "The turns in order, oldest first.",
|
|
174
|
+
"items": {
|
|
175
|
+
"$ref": "#/$defs/Message"
|
|
176
|
+
},
|
|
177
|
+
"type": "array"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"required": [
|
|
181
|
+
"messages"
|
|
182
|
+
],
|
|
183
|
+
"type": "object"
|
|
184
|
+
},
|
|
185
|
+
"Usage": {
|
|
186
|
+
"description": "Token / cost usage for one provider call, or a running total.\n\nEach field is independently optional because not every harness reports every\nsignal (cost is commonly absent on subscription auth, and cache counts only\nwhen the harness surfaces prompt-cache reads/writes). `None` means \"no\nsignal\", never \"zero\" \u2014 so a total stays `None` until something reports a real\nnumber, then accumulates.",
|
|
187
|
+
"properties": {
|
|
188
|
+
"cache_read_tokens": {
|
|
189
|
+
"description": "Prompt tokens served from the provider's prompt cache \u2014 a cheap read of a\npreviously-written prefix \u2014 when the harness reports them. `None` when the\nharness does not surface cache counts, never `0` as a guess.",
|
|
190
|
+
"format": "uint64",
|
|
191
|
+
"minimum": 0,
|
|
192
|
+
"type": [
|
|
193
|
+
"integer",
|
|
194
|
+
"null"
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
"cache_write_tokens": {
|
|
198
|
+
"description": "Prompt tokens written to the provider's prompt cache (a.k.a. cache\ncreation), when the harness reports them. `None` when not surfaced.",
|
|
199
|
+
"format": "uint64",
|
|
200
|
+
"minimum": 0,
|
|
201
|
+
"type": [
|
|
202
|
+
"integer",
|
|
203
|
+
"null"
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
"cost_usd": {
|
|
207
|
+
"description": "Total cost in USD, when reported.",
|
|
208
|
+
"format": "double",
|
|
209
|
+
"type": [
|
|
210
|
+
"number",
|
|
211
|
+
"null"
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
"input_tokens": {
|
|
215
|
+
"description": "Prompt/input tokens billed, when reported.",
|
|
216
|
+
"format": "uint64",
|
|
217
|
+
"minimum": 0,
|
|
218
|
+
"type": [
|
|
219
|
+
"integer",
|
|
220
|
+
"null"
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
"output_tokens": {
|
|
224
|
+
"description": "Completion/output tokens billed, when reported.",
|
|
225
|
+
"format": "uint64",
|
|
226
|
+
"minimum": 0,
|
|
227
|
+
"type": [
|
|
228
|
+
"integer",
|
|
229
|
+
"null"
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"type": "object"
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
237
|
+
"description": "A judged run: the transcript, the verdicts scored against it, aggregated\nusage, and whether a streaming sink short-circuited the run \u2014 stamped with the\n[`SCHEMA_VERSION`] of the contract that produced it.\n\nBuild one from an [`Outcome`](crate::Outcome) with\n[`Outcome::into_report`](crate::Outcome::into_report), or directly with\n[`Report::new`].",
|
|
238
|
+
"properties": {
|
|
239
|
+
"assessment": {
|
|
240
|
+
"description": "A free-text judgement requested by the caller, if any.",
|
|
241
|
+
"type": [
|
|
242
|
+
"string",
|
|
243
|
+
"null"
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
"completion_reason": {
|
|
247
|
+
"description": "Why the per-turn supervisor declared the task complete, if it did.",
|
|
248
|
+
"type": [
|
|
249
|
+
"string",
|
|
250
|
+
"null"
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
"schema_version": {
|
|
254
|
+
"description": "The contract version this report was serialized under.",
|
|
255
|
+
"format": "uint32",
|
|
256
|
+
"minimum": 0,
|
|
257
|
+
"type": "integer"
|
|
258
|
+
},
|
|
259
|
+
"stopped_early": {
|
|
260
|
+
"default": false,
|
|
261
|
+
"description": "Whether a streaming sink asked to short-circuit the run.",
|
|
262
|
+
"type": "boolean"
|
|
263
|
+
},
|
|
264
|
+
"transcript": {
|
|
265
|
+
"$ref": "#/$defs/Transcript",
|
|
266
|
+
"description": "The full conversation transcript, with tool events on assistant turns."
|
|
267
|
+
},
|
|
268
|
+
"usage": {
|
|
269
|
+
"anyOf": [
|
|
270
|
+
{
|
|
271
|
+
"$ref": "#/$defs/Usage"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"type": "null"
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
"description": "Aggregated usage across every provider call (`None` if nothing reported)."
|
|
278
|
+
},
|
|
279
|
+
"verdicts": {
|
|
280
|
+
"description": "The verdicts scored against the transcript, in the order they were added.",
|
|
281
|
+
"items": {
|
|
282
|
+
"$ref": "#/$defs/NamedVerdict"
|
|
283
|
+
},
|
|
284
|
+
"type": "array"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
"required": [
|
|
288
|
+
"schema_version",
|
|
289
|
+
"transcript",
|
|
290
|
+
"stopped_early"
|
|
291
|
+
],
|
|
292
|
+
"title": "Report",
|
|
293
|
+
"type": "object"
|
|
294
|
+
},
|
|
295
|
+
"run_config": {
|
|
296
|
+
"$defs": {
|
|
297
|
+
"EvalConfig": {
|
|
298
|
+
"additionalProperties": false,
|
|
299
|
+
"description": "One eval scored against the finished transcript.",
|
|
300
|
+
"properties": {
|
|
301
|
+
"criterion": {
|
|
302
|
+
"description": "The plain-English criterion.",
|
|
303
|
+
"type": "string"
|
|
304
|
+
},
|
|
305
|
+
"kind": {
|
|
306
|
+
"$ref": "#/$defs/JudgeKind",
|
|
307
|
+
"default": "boolean",
|
|
308
|
+
"description": "Boolean (a pass/fail verdict) or numeric (a score on a scale)."
|
|
309
|
+
},
|
|
310
|
+
"scale": {
|
|
311
|
+
"default": null,
|
|
312
|
+
"description": "The inclusive `[min, max]` scale for a numeric eval; ignored (and rejected)\nfor a boolean one. Defaults to `[0, 10]`.",
|
|
313
|
+
"items": {
|
|
314
|
+
"format": "double",
|
|
315
|
+
"type": "number"
|
|
316
|
+
},
|
|
317
|
+
"maxItems": 2,
|
|
318
|
+
"minItems": 2,
|
|
319
|
+
"type": [
|
|
320
|
+
"array",
|
|
321
|
+
"null"
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
"required": [
|
|
326
|
+
"criterion"
|
|
327
|
+
],
|
|
328
|
+
"type": "object"
|
|
329
|
+
},
|
|
330
|
+
"JudgeKind": {
|
|
331
|
+
"description": "The kind of judgement requested.",
|
|
332
|
+
"oneOf": [
|
|
333
|
+
{
|
|
334
|
+
"const": "boolean",
|
|
335
|
+
"description": "A yes/no verdict.",
|
|
336
|
+
"type": "string"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"const": "numeric",
|
|
340
|
+
"description": "A score on a `[min, max]` scale.",
|
|
341
|
+
"type": "string"
|
|
342
|
+
}
|
|
343
|
+
]
|
|
344
|
+
},
|
|
345
|
+
"ProviderConfig": {
|
|
346
|
+
"additionalProperties": false,
|
|
347
|
+
"description": "Which backend runs the harness. A flat, strict struct (rather than an\ninternally-tagged enum, which serde cannot pair with `deny_unknown_fields`):\n[`ProviderConfig::resolve`] checks that only the chosen `kind`'s fields are set\nand turns it into a validated [`ProviderSpec`], so a misplaced-but-spelled-right\nkey (e.g. `bin` under `kind: command`) is still a loud error.",
|
|
348
|
+
"properties": {
|
|
349
|
+
"bin": {
|
|
350
|
+
"default": null,
|
|
351
|
+
"description": "`oneharness`: the `oneharness` binary (default `oneharness`).",
|
|
352
|
+
"type": [
|
|
353
|
+
"string",
|
|
354
|
+
"null"
|
|
355
|
+
]
|
|
356
|
+
},
|
|
357
|
+
"command": {
|
|
358
|
+
"default": null,
|
|
359
|
+
"description": "`command`: the provider argv (program + args).",
|
|
360
|
+
"items": {
|
|
361
|
+
"type": "string"
|
|
362
|
+
},
|
|
363
|
+
"type": [
|
|
364
|
+
"array",
|
|
365
|
+
"null"
|
|
366
|
+
]
|
|
367
|
+
},
|
|
368
|
+
"judge": {
|
|
369
|
+
"anyOf": [
|
|
370
|
+
{
|
|
371
|
+
"$ref": "#/$defs/ProviderConfig"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"type": "null"
|
|
375
|
+
}
|
|
376
|
+
],
|
|
377
|
+
"description": "`split`: the backend that judges and plays the simulated user."
|
|
378
|
+
},
|
|
379
|
+
"judge_config": {
|
|
380
|
+
"default": null,
|
|
381
|
+
"description": "`oneharness`: the oneharness config file the judge / simulated user run\nunder, passed as `oneharness run --config <path>` (default\n`oneharness.judge.toml`). This is where the judge-side harness/model\nselection lives.",
|
|
382
|
+
"type": [
|
|
383
|
+
"string",
|
|
384
|
+
"null"
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
"kind": {
|
|
388
|
+
"$ref": "#/$defs/ProviderKind",
|
|
389
|
+
"description": "`oneharness` (default) | `command` | `split`."
|
|
390
|
+
},
|
|
391
|
+
"skill": {
|
|
392
|
+
"anyOf": [
|
|
393
|
+
{
|
|
394
|
+
"$ref": "#/$defs/ProviderConfig"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"type": "null"
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
"description": "`split`: the backend that runs the agent's turns."
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
"type": "object"
|
|
404
|
+
},
|
|
405
|
+
"ProviderKind": {
|
|
406
|
+
"description": "The provider backends the CLI can build. One enum is the single source for\nboth the YAML `kind:` (via `Deserialize`) and the `--provider` flag (via\nclap's `ValueEnum`), so the two surfaces cannot drift.",
|
|
407
|
+
"oneOf": [
|
|
408
|
+
{
|
|
409
|
+
"const": "oneharness",
|
|
410
|
+
"description": "Shell out to the `oneharness` CLI (the default).",
|
|
411
|
+
"type": "string"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"const": "command",
|
|
415
|
+
"description": "A custom command speaking the JSON-lines protocol.",
|
|
416
|
+
"type": "string"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"const": "split",
|
|
420
|
+
"description": "Compose a skill-runner with a separate judge / simulated-user backend.",
|
|
421
|
+
"type": "string"
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
"UserConfig": {
|
|
426
|
+
"additionalProperties": false,
|
|
427
|
+
"description": "The simulated user that supervises the agent and drives the loop.",
|
|
428
|
+
"properties": {
|
|
429
|
+
"done_when": {
|
|
430
|
+
"default": null,
|
|
431
|
+
"description": "A plain-English completion condition; when the judge decides it holds, the\nloop ends. Without it the loop ends at `max_turns` or when the agent\ndeclares itself done.",
|
|
432
|
+
"type": [
|
|
433
|
+
"string",
|
|
434
|
+
"null"
|
|
435
|
+
]
|
|
436
|
+
},
|
|
437
|
+
"max_turns": {
|
|
438
|
+
"default": null,
|
|
439
|
+
"description": "The assistant-turn cap for this run.",
|
|
440
|
+
"format": "uint32",
|
|
441
|
+
"minimum": 0,
|
|
442
|
+
"type": [
|
|
443
|
+
"integer",
|
|
444
|
+
"null"
|
|
445
|
+
]
|
|
446
|
+
},
|
|
447
|
+
"persona": {
|
|
448
|
+
"default": "",
|
|
449
|
+
"description": "How the simulated user behaves (their instructions).",
|
|
450
|
+
"type": "string"
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
"type": "object"
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
457
|
+
"additionalProperties": false,
|
|
458
|
+
"description": "The whole YAML config for one run. Field defaults let a minimal file (just a\n`task`) work, while `deny_unknown_fields` makes a typo'd key a hard error\ninstead of a silently-ignored setting.",
|
|
459
|
+
"properties": {
|
|
460
|
+
"assessment": {
|
|
461
|
+
"default": null,
|
|
462
|
+
"description": "Optional prompt for a free-text judgement of the finished transcript.",
|
|
463
|
+
"type": [
|
|
464
|
+
"string",
|
|
465
|
+
"null"
|
|
466
|
+
]
|
|
467
|
+
},
|
|
468
|
+
"evals": {
|
|
469
|
+
"description": "Optional criteria to score the finished transcript with.",
|
|
470
|
+
"items": {
|
|
471
|
+
"$ref": "#/$defs/EvalConfig"
|
|
472
|
+
},
|
|
473
|
+
"type": "array"
|
|
474
|
+
},
|
|
475
|
+
"provider": {
|
|
476
|
+
"$ref": "#/$defs/ProviderConfig",
|
|
477
|
+
"description": "Which backend runs the harness (and judges / plays the user).\n\nHarness/model **selection** is no longer a onejudge concern: the agent side\nuses oneharness's discovered `oneharness.toml`, and the judge side uses the\n`provider.judge_config` file (default `oneharness.judge.toml`). Scaffold both\nwith `onejudge init`."
|
|
478
|
+
},
|
|
479
|
+
"session": {
|
|
480
|
+
"default": null,
|
|
481
|
+
"description": "The caller-owned session name threaded across turns; defaults to\n`onejudge`.",
|
|
482
|
+
"type": [
|
|
483
|
+
"string",
|
|
484
|
+
"null"
|
|
485
|
+
]
|
|
486
|
+
},
|
|
487
|
+
"skill": {
|
|
488
|
+
"default": null,
|
|
489
|
+
"description": "Path to a **skill** directory (containing a `SKILL.md`) whose instruction\nbody seeds the system prompt. Resolved relative to the config file's\ndirectory (or the working dir for a flag-only run). Optional \u2014 combine it\nwith `system_prompt`, use either alone, or neither.",
|
|
490
|
+
"type": [
|
|
491
|
+
"string",
|
|
492
|
+
"null"
|
|
493
|
+
]
|
|
494
|
+
},
|
|
495
|
+
"system_prompt": {
|
|
496
|
+
"default": null,
|
|
497
|
+
"description": "Extra system-prompt text for the harness. When a `skill` is also set, this\ncomes **first** and the skill's body is appended after it.",
|
|
498
|
+
"type": [
|
|
499
|
+
"string",
|
|
500
|
+
"null"
|
|
501
|
+
]
|
|
502
|
+
},
|
|
503
|
+
"task": {
|
|
504
|
+
"default": null,
|
|
505
|
+
"description": "The task to drive to completion. May instead be supplied by `--task`\n(`-` reads stdin); required by the time the plan is built.",
|
|
506
|
+
"type": [
|
|
507
|
+
"string",
|
|
508
|
+
"null"
|
|
509
|
+
]
|
|
510
|
+
},
|
|
511
|
+
"user": {
|
|
512
|
+
"anyOf": [
|
|
513
|
+
{
|
|
514
|
+
"$ref": "#/$defs/UserConfig"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
"type": "null"
|
|
518
|
+
}
|
|
519
|
+
],
|
|
520
|
+
"description": "The simulated user / supervisor that drives the loop. Omit for a\nsingle-turn run (the agent answers once)."
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
"title": "Config",
|
|
524
|
+
"type": "object"
|
|
525
|
+
},
|
|
526
|
+
"stream_event": {
|
|
527
|
+
"$defs": {
|
|
528
|
+
"ToolEvent": {
|
|
529
|
+
"description": "One normalized tool-call / action event the skill took during a turn, lifted\nfrom `oneharness`'s `events` array (its `--events` output). Harness-agnostic,\nso a consumer can inspect shell commands, file edits, and tool uses across any\nharness, not just the final text. `input` is the structured, tool-shaped args\nso a consumer can match on the command string or file path without re-parsing.\n\n`input` is free-form JSON, so `Message`/`Transcript` are `PartialEq` but not\n`Eq`.",
|
|
530
|
+
"properties": {
|
|
531
|
+
"index": {
|
|
532
|
+
"default": 0,
|
|
533
|
+
"description": "Position within the run, so ordering (\"did X before Y\") is expressible.",
|
|
534
|
+
"format": "uint",
|
|
535
|
+
"minimum": 0,
|
|
536
|
+
"type": "integer"
|
|
537
|
+
},
|
|
538
|
+
"input": {
|
|
539
|
+
"description": "Structured tool arguments (the command, the file path); `None` when none."
|
|
540
|
+
},
|
|
541
|
+
"kind": {
|
|
542
|
+
"description": "`tool_call` (the skill invoked a tool) or `tool_result` (the observation).",
|
|
543
|
+
"type": "string"
|
|
544
|
+
},
|
|
545
|
+
"name": {
|
|
546
|
+
"description": "Normalized tool name where knowable (e.g. `bash`, `edit_file`); `None` for\na `tool_result` or when the harness did not name it.",
|
|
547
|
+
"type": [
|
|
548
|
+
"string",
|
|
549
|
+
"null"
|
|
550
|
+
]
|
|
551
|
+
},
|
|
552
|
+
"output": {
|
|
553
|
+
"description": "The result/observation text, when the transcript exposed it.",
|
|
554
|
+
"type": [
|
|
555
|
+
"string",
|
|
556
|
+
"null"
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
"required": [
|
|
561
|
+
"kind",
|
|
562
|
+
"index"
|
|
563
|
+
],
|
|
564
|
+
"type": "object"
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
568
|
+
"description": "One streamed tool event delivered live to an [`Engine::run_streaming`] sink,\ntagged with the turn it belongs to.",
|
|
569
|
+
"properties": {
|
|
570
|
+
"event": {
|
|
571
|
+
"$ref": "#/$defs/ToolEvent",
|
|
572
|
+
"description": "The normalized tool event."
|
|
573
|
+
},
|
|
574
|
+
"turn": {
|
|
575
|
+
"description": "1-based assistant-turn index within this run.",
|
|
576
|
+
"format": "uint",
|
|
577
|
+
"minimum": 0,
|
|
578
|
+
"type": "integer"
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
"required": [
|
|
582
|
+
"turn",
|
|
583
|
+
"event"
|
|
584
|
+
],
|
|
585
|
+
"title": "StreamEvent",
|
|
586
|
+
"type": "object"
|
|
587
|
+
}
|
|
588
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Generated from onejudge. Do not edit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from typing import Any, TypedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RunConfig(TypedDict, total=False):
|
|
10
|
+
assessment: Any
|
|
11
|
+
evals: Sequence[dict[str, Any]]
|
|
12
|
+
provider: dict[str, Any]
|
|
13
|
+
session: Any
|
|
14
|
+
skill: Any
|
|
15
|
+
system_prompt: Any
|
|
16
|
+
task: Any
|
|
17
|
+
user: Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
RunReport = dict[str, Any]
|
onejudge_sdk/_result.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Convenient typed view over a validated onejudge report."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ._generated_types import RunReport
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class RunResult:
|
|
13
|
+
"""A completed or incomplete run, retaining process and raw report data."""
|
|
14
|
+
|
|
15
|
+
exit_code: int
|
|
16
|
+
stderr: str
|
|
17
|
+
raw: RunReport
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def completed(self) -> bool:
|
|
21
|
+
"""Whether onejudge exited with its completed/evals-passed status."""
|
|
22
|
+
return self.exit_code == 0
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def verdicts(self) -> list[dict[str, Any]]:
|
|
26
|
+
"""Return the report's ordered verdicts."""
|
|
27
|
+
return self.raw.get("verdicts", [])
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def usage(self) -> dict[str, Any]:
|
|
31
|
+
"""Return aggregate usage, or an empty mapping when unavailable."""
|
|
32
|
+
return self.raw.get("usage") or {}
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def assistant_turns(self) -> int:
|
|
36
|
+
"""Count assistant turns in the transcript."""
|
|
37
|
+
messages = self.raw["transcript"]["messages"]
|
|
38
|
+
return sum(message["role"] == "assistant" for message in messages)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def agent_turns(self) -> int:
|
|
42
|
+
"""Alias assistant turns using ai-orchestrator terminology."""
|
|
43
|
+
return self.assistant_turns
|
onejudge_sdk/_version.py
ADDED
onejudge_sdk/py.typed
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|