opensaddle 1.0.0__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.
- opensaddle/__init__.py +5 -0
- opensaddle/__main__.py +7 -0
- opensaddle/adapters/__init__.py +1 -0
- opensaddle/adapters/base.py +85 -0
- opensaddle/adapters/cli.py +192 -0
- opensaddle/agent_presets.py +194 -0
- opensaddle/audit/__init__.py +1 -0
- opensaddle/audit/auditor.py +496 -0
- opensaddle/audit/stream.py +43 -0
- opensaddle/audit/types.py +68 -0
- opensaddle/benchmark/__init__.py +27 -0
- opensaddle/benchmark/metrics.py +92 -0
- opensaddle/benchmark/reports.py +56 -0
- opensaddle/benchmark/runner.py +683 -0
- opensaddle/benchmark/suite.py +217 -0
- opensaddle/cli/__init__.py +2 -0
- opensaddle/cli/main.py +1119 -0
- opensaddle/config.py +653 -0
- opensaddle/core/__init__.py +44 -0
- opensaddle/core/agent.py +16 -0
- opensaddle/core/episode.py +116 -0
- opensaddle/core/failure_attribution.py +211 -0
- opensaddle/core/model.py +28 -0
- opensaddle/core/route.py +126 -0
- opensaddle/core/stored_episode.py +197 -0
- opensaddle/core/task.py +49 -0
- opensaddle/dashboard.py +1797 -0
- opensaddle/dashboard_reviews.py +682 -0
- opensaddle/execution/__init__.py +1 -0
- opensaddle/execution/broker.py +242 -0
- opensaddle/execution/configured_routes.py +319 -0
- opensaddle/execution/policy_observer.py +71 -0
- opensaddle/execution/pty.py +140 -0
- opensaddle/execution/runner.py +333 -0
- opensaddle/execution/stored_episodes.py +166 -0
- opensaddle/execution/worktree.py +80 -0
- opensaddle/governance.py +67 -0
- opensaddle/history.py +737 -0
- opensaddle/model_registry.py +168 -0
- opensaddle/openrouter.py +425 -0
- opensaddle/optimize/__init__.py +6 -0
- opensaddle/optimize/estimator.py +128 -0
- opensaddle/optimize/route_materialization.py +328 -0
- opensaddle/optimize/route_scoring.py +286 -0
- opensaddle/optimize/task_profile.py +65 -0
- opensaddle/privacy.py +125 -0
- opensaddle/storage/__init__.py +3 -0
- opensaddle/storage/migrations.py +137 -0
- opensaddle/storage/sqlite.py +923 -0
- opensaddle/templates.py +170 -0
- opensaddle/verification/__init__.py +1 -0
- opensaddle/verification/checks.py +115 -0
- opensaddle/verification/diff_risk.py +50 -0
- opensaddle-1.0.0.dist-info/METADATA +318 -0
- opensaddle-1.0.0.dist-info/RECORD +58 -0
- opensaddle-1.0.0.dist-info/WHEEL +4 -0
- opensaddle-1.0.0.dist-info/entry_points.txt +2 -0
- opensaddle-1.0.0.dist-info/licenses/LICENSE +21 -0
opensaddle/__init__.py
ADDED
opensaddle/__main__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Agent adapter implementations."""
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CliExecutionMode(str, Enum):
|
|
10
|
+
EXEC = "exec"
|
|
11
|
+
PTY = "pty"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TemplateRenderError(ValueError):
|
|
15
|
+
"""Raised when a command template cannot be rendered."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _StrictTemplateValues(dict[str, str]):
|
|
19
|
+
def __missing__(self, key: str) -> str:
|
|
20
|
+
raise TemplateRenderError(f"missing template value: {key}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(slots=True)
|
|
24
|
+
class UsageReport:
|
|
25
|
+
confidence: str
|
|
26
|
+
prompt_tokens: int | None = None
|
|
27
|
+
completion_tokens: int | None = None
|
|
28
|
+
total_tokens: int | None = None
|
|
29
|
+
raw: dict[str, Any] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(slots=True)
|
|
33
|
+
class CommandCapture:
|
|
34
|
+
argv: list[str]
|
|
35
|
+
cwd: str
|
|
36
|
+
exit_code: int | None
|
|
37
|
+
stdout: str
|
|
38
|
+
stderr: str
|
|
39
|
+
timed_out: bool
|
|
40
|
+
duration_seconds: float
|
|
41
|
+
execution_mode: str = CliExecutionMode.EXEC.value
|
|
42
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(slots=True)
|
|
46
|
+
class CliExecutionResult:
|
|
47
|
+
capture: CommandCapture
|
|
48
|
+
usage: UsageReport | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class UsageParser(Protocol):
|
|
52
|
+
def parse(self, *, capture: CommandCapture) -> UsageReport | None:
|
|
53
|
+
"""Extract usage information from a finished command."""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(slots=True)
|
|
57
|
+
class NullUsageParser:
|
|
58
|
+
def parse(self, *, capture: CommandCapture) -> UsageReport | None:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(slots=True)
|
|
63
|
+
class CliCommandTemplate:
|
|
64
|
+
argv: list[str]
|
|
65
|
+
env: dict[str, str] = field(default_factory=dict)
|
|
66
|
+
|
|
67
|
+
def render(self, context: dict[str, Any]) -> tuple[list[str], dict[str, str]]:
|
|
68
|
+
rendered_context = _StrictTemplateValues(
|
|
69
|
+
{key: "" if value is None else str(value) for key, value in context.items()}
|
|
70
|
+
)
|
|
71
|
+
argv = [part.format_map(rendered_context) for part in self.argv]
|
|
72
|
+
env = {
|
|
73
|
+
key: value.format_map(rendered_context)
|
|
74
|
+
for key, value in self.env.items()
|
|
75
|
+
}
|
|
76
|
+
return argv, env
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(slots=True)
|
|
80
|
+
class AgentInvocation:
|
|
81
|
+
instruction: str
|
|
82
|
+
workdir: Path
|
|
83
|
+
model: str | None = None
|
|
84
|
+
timeout_seconds: float | None = None
|
|
85
|
+
extra_context: dict[str, Any] = field(default_factory=dict)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from opensaddle.adapters.base import (
|
|
12
|
+
AgentInvocation,
|
|
13
|
+
CliExecutionMode,
|
|
14
|
+
CliCommandTemplate,
|
|
15
|
+
CliExecutionResult,
|
|
16
|
+
CommandCapture,
|
|
17
|
+
NullUsageParser,
|
|
18
|
+
UsageParser,
|
|
19
|
+
UsageReport,
|
|
20
|
+
)
|
|
21
|
+
from opensaddle.audit.stream import AuditStream
|
|
22
|
+
from opensaddle.execution.pty import PtyCommandRunner
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _normalize_output(value: str | bytes | None) -> str:
|
|
26
|
+
if value is None:
|
|
27
|
+
return ""
|
|
28
|
+
if isinstance(value, bytes):
|
|
29
|
+
return value.decode("utf-8", errors="replace")
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(slots=True)
|
|
34
|
+
class RegexUsageParser:
|
|
35
|
+
pattern: str
|
|
36
|
+
confidence: str = "parsed"
|
|
37
|
+
flags: int = re.MULTILINE
|
|
38
|
+
|
|
39
|
+
def parse(self, *, capture: CommandCapture) -> UsageReport | None:
|
|
40
|
+
match = re.search(self.pattern, capture.stdout, self.flags)
|
|
41
|
+
if not match:
|
|
42
|
+
return None
|
|
43
|
+
raw = match.groupdict()
|
|
44
|
+
return UsageReport(
|
|
45
|
+
confidence=self.confidence,
|
|
46
|
+
prompt_tokens=int(raw["prompt"]) if raw.get("prompt") else None,
|
|
47
|
+
completion_tokens=int(raw["completion"]) if raw.get("completion") else None,
|
|
48
|
+
total_tokens=int(raw["total"]) if raw.get("total") else None,
|
|
49
|
+
raw=raw,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(slots=True)
|
|
54
|
+
class CliAgentAdapter:
|
|
55
|
+
name: str
|
|
56
|
+
command_template: CliCommandTemplate
|
|
57
|
+
usage_parser: UsageParser = field(default_factory=NullUsageParser)
|
|
58
|
+
timeout_seconds: float | None = None
|
|
59
|
+
execution_mode: CliExecutionMode = CliExecutionMode.EXEC
|
|
60
|
+
pty_rows: int = 40
|
|
61
|
+
pty_columns: int = 120
|
|
62
|
+
pty_term: str = "xterm-256color"
|
|
63
|
+
|
|
64
|
+
def run(
|
|
65
|
+
self,
|
|
66
|
+
invocation: AgentInvocation,
|
|
67
|
+
*,
|
|
68
|
+
audit_stream: AuditStream | None = None,
|
|
69
|
+
base_env: dict[str, str] | None = None,
|
|
70
|
+
clean_env: bool = False,
|
|
71
|
+
command_prefix: list[str] | None = None,
|
|
72
|
+
) -> CliExecutionResult:
|
|
73
|
+
context: dict[str, Any] = {
|
|
74
|
+
"instruction": invocation.instruction,
|
|
75
|
+
"model": invocation.model or "",
|
|
76
|
+
"workdir": str(invocation.workdir),
|
|
77
|
+
}
|
|
78
|
+
context.update(invocation.extra_context)
|
|
79
|
+
argv, rendered_env = self.command_template.render(context)
|
|
80
|
+
if command_prefix:
|
|
81
|
+
argv = [*command_prefix, *argv]
|
|
82
|
+
timeout_seconds = invocation.timeout_seconds or self.timeout_seconds
|
|
83
|
+
env = {} if clean_env else dict(os.environ)
|
|
84
|
+
env.update(base_env or {})
|
|
85
|
+
env.update(rendered_env)
|
|
86
|
+
|
|
87
|
+
if audit_stream is not None:
|
|
88
|
+
audit_stream.record(
|
|
89
|
+
"command_started",
|
|
90
|
+
actor="agent",
|
|
91
|
+
payload={
|
|
92
|
+
"argv": argv,
|
|
93
|
+
"cwd": str(invocation.workdir),
|
|
94
|
+
"execution_mode": self.execution_mode.value,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
started_at = time.perf_counter()
|
|
99
|
+
capture = self._run_command(
|
|
100
|
+
argv=argv,
|
|
101
|
+
workdir=invocation.workdir,
|
|
102
|
+
env=env,
|
|
103
|
+
rendered_env=rendered_env,
|
|
104
|
+
timeout_seconds=timeout_seconds,
|
|
105
|
+
started_at=started_at,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if audit_stream is not None:
|
|
109
|
+
audit_stream.record(
|
|
110
|
+
"command_finished",
|
|
111
|
+
actor="agent",
|
|
112
|
+
payload={
|
|
113
|
+
"argv": argv,
|
|
114
|
+
"cwd": str(invocation.workdir),
|
|
115
|
+
"exit_code": capture.exit_code,
|
|
116
|
+
"timed_out": capture.timed_out,
|
|
117
|
+
"duration_seconds": capture.duration_seconds,
|
|
118
|
+
"execution_mode": capture.execution_mode,
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
if capture.stdout:
|
|
122
|
+
audit_stream.record(
|
|
123
|
+
"agent_stdout",
|
|
124
|
+
actor="agent",
|
|
125
|
+
payload={"text": capture.stdout},
|
|
126
|
+
)
|
|
127
|
+
if capture.stderr:
|
|
128
|
+
audit_stream.record(
|
|
129
|
+
"agent_stderr",
|
|
130
|
+
actor="agent",
|
|
131
|
+
payload={"text": capture.stderr},
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
usage = self.usage_parser.parse(capture=capture)
|
|
135
|
+
return CliExecutionResult(capture=capture, usage=usage)
|
|
136
|
+
|
|
137
|
+
def _run_command(
|
|
138
|
+
self,
|
|
139
|
+
*,
|
|
140
|
+
argv: list[str],
|
|
141
|
+
workdir: Path,
|
|
142
|
+
env: dict[str, str],
|
|
143
|
+
rendered_env: dict[str, str],
|
|
144
|
+
timeout_seconds: float | None,
|
|
145
|
+
started_at: float,
|
|
146
|
+
) -> CommandCapture:
|
|
147
|
+
if self.execution_mode is CliExecutionMode.PTY:
|
|
148
|
+
return PtyCommandRunner(
|
|
149
|
+
rows=self.pty_rows,
|
|
150
|
+
columns=self.pty_columns,
|
|
151
|
+
term=self.pty_term,
|
|
152
|
+
).run(
|
|
153
|
+
argv=argv,
|
|
154
|
+
workdir=workdir,
|
|
155
|
+
env=env,
|
|
156
|
+
timeout_seconds=timeout_seconds,
|
|
157
|
+
rendered_env=rendered_env,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
completed = subprocess.run(
|
|
162
|
+
argv,
|
|
163
|
+
cwd=workdir,
|
|
164
|
+
env=env or None,
|
|
165
|
+
capture_output=True,
|
|
166
|
+
text=True,
|
|
167
|
+
timeout=timeout_seconds,
|
|
168
|
+
check=False,
|
|
169
|
+
)
|
|
170
|
+
return CommandCapture(
|
|
171
|
+
argv=argv,
|
|
172
|
+
cwd=str(workdir),
|
|
173
|
+
exit_code=completed.returncode,
|
|
174
|
+
stdout=completed.stdout,
|
|
175
|
+
stderr=completed.stderr,
|
|
176
|
+
timed_out=False,
|
|
177
|
+
duration_seconds=time.perf_counter() - started_at,
|
|
178
|
+
execution_mode=CliExecutionMode.EXEC.value,
|
|
179
|
+
env=rendered_env,
|
|
180
|
+
)
|
|
181
|
+
except subprocess.TimeoutExpired as exc:
|
|
182
|
+
return CommandCapture(
|
|
183
|
+
argv=argv,
|
|
184
|
+
cwd=str(workdir),
|
|
185
|
+
exit_code=None,
|
|
186
|
+
stdout=_normalize_output(exc.stdout),
|
|
187
|
+
stderr=_normalize_output(exc.stderr),
|
|
188
|
+
timed_out=True,
|
|
189
|
+
duration_seconds=time.perf_counter() - started_at,
|
|
190
|
+
execution_mode=CliExecutionMode.EXEC.value,
|
|
191
|
+
env=rendered_env,
|
|
192
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Built-in YAML presets for common coding-agent CLIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class AgentPreset:
|
|
13
|
+
"""A built-in agent config snippet that still uses the generic CLI adapter."""
|
|
14
|
+
|
|
15
|
+
agent_id: str
|
|
16
|
+
command: str
|
|
17
|
+
exec_argv: tuple[str, ...]
|
|
18
|
+
models: tuple[dict[str, str], ...] = ()
|
|
19
|
+
usage_parser_kind: str | None = None
|
|
20
|
+
usage_parser_path_glob: str | None = None
|
|
21
|
+
|
|
22
|
+
def to_config(self) -> dict[str, Any]:
|
|
23
|
+
config: dict[str, Any] = {
|
|
24
|
+
"adapter": "cli",
|
|
25
|
+
"command": self.command,
|
|
26
|
+
"modes": {
|
|
27
|
+
"exec": {
|
|
28
|
+
"argv": list(self.exec_argv),
|
|
29
|
+
},
|
|
30
|
+
"pty": {
|
|
31
|
+
"enabled": False,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
if self.models:
|
|
36
|
+
config["models"] = [dict(binding) for binding in self.models]
|
|
37
|
+
if self.usage_parser_kind is not None:
|
|
38
|
+
usage_parser = {"kind": self.usage_parser_kind}
|
|
39
|
+
if self.usage_parser_path_glob is not None:
|
|
40
|
+
usage_parser["path_glob"] = self.usage_parser_path_glob
|
|
41
|
+
config["usage_parser"] = usage_parser
|
|
42
|
+
return config
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
BUILTIN_AGENT_PRESETS: dict[str, AgentPreset] = {
|
|
46
|
+
"codex": AgentPreset(
|
|
47
|
+
agent_id="codex",
|
|
48
|
+
command="codex",
|
|
49
|
+
exec_argv=("codex", "exec", "--model", "{model}", "{instruction}"),
|
|
50
|
+
models=(
|
|
51
|
+
{
|
|
52
|
+
"id": "gpt-5",
|
|
53
|
+
"cost_profile": "openai_gpt5",
|
|
54
|
+
"success_prior": "strong_coding",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "gpt-5-mini",
|
|
58
|
+
"cost_profile": "openai_gpt5_mini",
|
|
59
|
+
"success_prior": "cheap_coding",
|
|
60
|
+
},
|
|
61
|
+
),
|
|
62
|
+
usage_parser_kind="estimated",
|
|
63
|
+
),
|
|
64
|
+
"claude_code": AgentPreset(
|
|
65
|
+
agent_id="claude_code",
|
|
66
|
+
command="claude",
|
|
67
|
+
exec_argv=("claude", "-p", "--model", "{model}", "{instruction}"),
|
|
68
|
+
usage_parser_kind="transcript_json",
|
|
69
|
+
usage_parser_path_glob=".claude/**/*.json",
|
|
70
|
+
),
|
|
71
|
+
"github_copilot": AgentPreset(
|
|
72
|
+
agent_id="github_copilot",
|
|
73
|
+
command="copilot",
|
|
74
|
+
exec_argv=("copilot", "-p", "--model", "{model}", "{instruction}"),
|
|
75
|
+
usage_parser_kind="estimated",
|
|
76
|
+
),
|
|
77
|
+
"aider": AgentPreset(
|
|
78
|
+
agent_id="aider",
|
|
79
|
+
command="aider",
|
|
80
|
+
exec_argv=(
|
|
81
|
+
"aider",
|
|
82
|
+
"--model",
|
|
83
|
+
"{model}",
|
|
84
|
+
"--yes",
|
|
85
|
+
"--no-auto-commits",
|
|
86
|
+
"--message",
|
|
87
|
+
"{instruction}",
|
|
88
|
+
),
|
|
89
|
+
usage_parser_kind="estimated",
|
|
90
|
+
),
|
|
91
|
+
"cursor": AgentPreset(
|
|
92
|
+
agent_id="cursor",
|
|
93
|
+
command="agent",
|
|
94
|
+
exec_argv=(
|
|
95
|
+
"agent",
|
|
96
|
+
"-p",
|
|
97
|
+
"--model",
|
|
98
|
+
"{model}",
|
|
99
|
+
"--output-format",
|
|
100
|
+
"text",
|
|
101
|
+
"{instruction}",
|
|
102
|
+
),
|
|
103
|
+
usage_parser_kind="estimated",
|
|
104
|
+
),
|
|
105
|
+
"amp": AgentPreset(
|
|
106
|
+
agent_id="amp",
|
|
107
|
+
command="amp",
|
|
108
|
+
exec_argv=(
|
|
109
|
+
"amp",
|
|
110
|
+
"--execute",
|
|
111
|
+
"{instruction}",
|
|
112
|
+
"--stream-json",
|
|
113
|
+
),
|
|
114
|
+
usage_parser_kind="stream_json",
|
|
115
|
+
),
|
|
116
|
+
"antigravity": AgentPreset(
|
|
117
|
+
agent_id="antigravity",
|
|
118
|
+
command="agy",
|
|
119
|
+
exec_argv=(
|
|
120
|
+
"agy",
|
|
121
|
+
"--model",
|
|
122
|
+
"{model}",
|
|
123
|
+
"--prompt",
|
|
124
|
+
"{instruction}",
|
|
125
|
+
),
|
|
126
|
+
usage_parser_kind="estimated",
|
|
127
|
+
),
|
|
128
|
+
"claw": AgentPreset(
|
|
129
|
+
agent_id="claw",
|
|
130
|
+
command="openclaw",
|
|
131
|
+
exec_argv=(
|
|
132
|
+
"openclaw",
|
|
133
|
+
"infer",
|
|
134
|
+
"model",
|
|
135
|
+
"run",
|
|
136
|
+
"--local",
|
|
137
|
+
"--model",
|
|
138
|
+
"{model}",
|
|
139
|
+
"--prompt",
|
|
140
|
+
"{instruction}",
|
|
141
|
+
"--json",
|
|
142
|
+
),
|
|
143
|
+
usage_parser_kind="json",
|
|
144
|
+
),
|
|
145
|
+
"hermes": AgentPreset(
|
|
146
|
+
agent_id="hermes",
|
|
147
|
+
command="hermes",
|
|
148
|
+
exec_argv=(
|
|
149
|
+
"hermes",
|
|
150
|
+
"chat",
|
|
151
|
+
"--model",
|
|
152
|
+
"{model}",
|
|
153
|
+
"-q",
|
|
154
|
+
"{instruction}",
|
|
155
|
+
),
|
|
156
|
+
usage_parser_kind="estimated",
|
|
157
|
+
),
|
|
158
|
+
"pi": AgentPreset(
|
|
159
|
+
agent_id="pi",
|
|
160
|
+
command="pi",
|
|
161
|
+
exec_argv=(
|
|
162
|
+
"pi",
|
|
163
|
+
"--approve",
|
|
164
|
+
"--model",
|
|
165
|
+
"{model}",
|
|
166
|
+
"-p",
|
|
167
|
+
"{instruction}",
|
|
168
|
+
),
|
|
169
|
+
usage_parser_kind="estimated",
|
|
170
|
+
),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def builtin_agent_preset_names() -> tuple[str, ...]:
|
|
175
|
+
"""Return built-in preset ids in stable display order."""
|
|
176
|
+
return tuple(BUILTIN_AGENT_PRESETS)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def builtin_agent_configs() -> dict[str, dict[str, Any]]:
|
|
180
|
+
"""Return the `agents:` mapping used by generated configs."""
|
|
181
|
+
return {
|
|
182
|
+
preset.agent_id: preset.to_config()
|
|
183
|
+
for preset in BUILTIN_AGENT_PRESETS.values()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def render_agent_preset_fragment(preset_name: str) -> str:
|
|
188
|
+
"""Render one built-in preset as a YAML fragment for docs and examples."""
|
|
189
|
+
try:
|
|
190
|
+
preset = BUILTIN_AGENT_PRESETS[preset_name]
|
|
191
|
+
except KeyError as exc:
|
|
192
|
+
available = ", ".join(builtin_agent_preset_names())
|
|
193
|
+
raise ValueError(f"unknown built-in preset '{preset_name}' (expected one of: {available})") from exc
|
|
194
|
+
return yaml.safe_dump({preset.agent_id: preset.to_config()}, sort_keys=False).strip() + "\n"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Audit stream helpers."""
|