@datalayer/agent-runtimes 1.5.30 → 1.5.32

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.
@@ -0,0 +1,329 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2025-2026 Datalayer, Inc.
3
+ # Distributed under the terms of the Modified BSD License.
4
+
5
+ """
6
+ Generate Python and TypeScript code from the YAML harness specifications.
7
+
8
+ One YAML per harness under ``agentspecs/harnesses`` (SHIELD.md SH-01, §3.1):
9
+ an agent program driven over the Agent Client Protocol, how it is launched,
10
+ what it needs, what it honours of an agent spec, and the terms it is served
11
+ under. ``register.yaml`` beside them is the register (SH-04, §3.3): a harness
12
+ without an entry is refused here as it is in agentspecs.
13
+
14
+ Usage:
15
+ python generate_harnesses.py \\
16
+ --specs-dir agentspecs/agentspecs/harnesses \\
17
+ --python-output agent_runtimes/specs/harnesses.py \\
18
+ --typescript-output src/specs/harnesses.ts
19
+ """
20
+
21
+ import argparse
22
+ import json
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ import yaml
27
+ from versioning import ensure_spec_version, version_suffix
28
+
29
+ MCP = ("lend", "own", "none")
30
+ THIRD_PARTIES = ("api_key_only", "permitted", "refused", "organizations_own")
31
+ SKILLS_FORMATS = ("skill-md", "none")
32
+ REGISTER = "register.yaml"
33
+
34
+
35
+ def load_register(specs_dir: Path) -> dict[str, dict[str, Any]]:
36
+ """The register, by harness id."""
37
+ register = yaml.safe_load((specs_dir / REGISTER).read_text()) or {}
38
+ return {str(entry["harness"]): entry for entry in register.get("entries") or []}
39
+
40
+
41
+ def load_harness_specs(specs_dir: Path) -> list[dict[str, Any]]:
42
+ """Every harness spec of a directory, each named for its id, registered, its enums known."""
43
+ register = load_register(specs_dir)
44
+ specs = []
45
+ for yaml_file in sorted(specs_dir.glob("*.yaml")):
46
+ if yaml_file.name == REGISTER:
47
+ continue
48
+ spec = yaml.safe_load(yaml_file.read_text()) or {}
49
+ ensure_spec_version(spec)
50
+ if spec["id"] != yaml_file.stem:
51
+ raise ValueError(
52
+ f"{yaml_file.name}: the file is named for {yaml_file.stem!r}, the spec says {spec['id']!r}"
53
+ )
54
+ entry = register.get(spec["id"])
55
+ if entry is None:
56
+ raise ValueError(
57
+ f"{yaml_file.name}: the harness {spec['id']!r} has no entry in the register"
58
+ )
59
+ acp = spec.get("acp")
60
+ if spec["id"] == "custom":
61
+ if acp is not None:
62
+ raise ValueError(f"{yaml_file.name}: custom names no command")
63
+ elif not acp or not acp.get("command"):
64
+ raise ValueError(f"{yaml_file.name}: an acp block with its command")
65
+ if acp is not None and acp.get("mcp") not in MCP:
66
+ raise ValueError(f"{yaml_file.name}: acp.mcp one of {MCP}")
67
+ terms = spec.get("terms") or {}
68
+ if terms.get("served_to_third_parties") not in THIRD_PARTIES:
69
+ raise ValueError(
70
+ f"{yaml_file.name}: terms.served_to_third_parties one of {THIRD_PARTIES}"
71
+ )
72
+ if terms.get("served_to_third_parties") != entry.get("served_to_third_parties"):
73
+ raise ValueError(
74
+ f"{yaml_file.name}: the register and the spec disagree on third parties"
75
+ )
76
+ skills = spec.get("skills") or {}
77
+ if skills.get("format") not in SKILLS_FORMATS:
78
+ raise ValueError(f"{yaml_file.name}: skills.format one of {SKILLS_FORMATS}")
79
+ spec["_register"] = entry
80
+ specs.append(spec)
81
+ return specs
82
+
83
+
84
+ def _const(spec: dict[str, Any]) -> str:
85
+ return (
86
+ spec["id"].replace("-", "_").upper()
87
+ + "_HARNESS"
88
+ + version_suffix(spec["version"])
89
+ )
90
+
91
+
92
+ def _s(value: Any) -> str:
93
+ return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
94
+
95
+
96
+ def _opt(value: Any) -> str:
97
+ return _s(value) if value else "None"
98
+
99
+
100
+ def generate_python_code(specs: list[dict[str, Any]]) -> str:
101
+ lines = [
102
+ "# Copyright (c) 2025-2026 Datalayer, Inc.",
103
+ "# Distributed under the terms of the Modified BSD License.",
104
+ '"""',
105
+ "Harness Catalog.",
106
+ "",
107
+ "An agent program driven over the Agent Client Protocol (SHIELD.md SH-01).",
108
+ "",
109
+ "This file is AUTO-GENERATED from YAML specifications.",
110
+ "DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
111
+ '"""',
112
+ "",
113
+ "from typing import Dict, List, Optional",
114
+ "",
115
+ "from agent_runtimes.types import (",
116
+ " HarnessAcpSpec,",
117
+ " HarnessHonoursSpec,",
118
+ " HarnessRegisterEntry,",
119
+ " HarnessRequiresSpec,",
120
+ " HarnessSkillsSpec,",
121
+ " HarnessSpec,",
122
+ " HarnessTermsSpec,",
123
+ ")",
124
+ ]
125
+ for spec in specs:
126
+ acp = spec.get("acp")
127
+ requires = spec.get("requires") or {}
128
+ terms = spec.get("terms") or {}
129
+ skills = spec.get("skills") or {}
130
+ honours = spec.get("honours") or {}
131
+ entry = spec["_register"]
132
+ lines += [
133
+ "",
134
+ f"{_const(spec)} = HarnessSpec(",
135
+ f" id={_s(spec['id'])},",
136
+ f" version={_s(spec['version'])},",
137
+ f" name={_s(spec['name'])},",
138
+ f" description={_s(spec.get('description'))},",
139
+ f" enabled={bool(spec.get('enabled', True))},",
140
+ f" tags={json.dumps(list(spec.get('tags') or []))},",
141
+ f" docs={_s(spec.get('docs'))},",
142
+ ]
143
+ if acp is None:
144
+ lines.append(" acp=None,")
145
+ else:
146
+ lines += [
147
+ " acp=HarnessAcpSpec(",
148
+ f" command={_s(acp['command'])},",
149
+ f" args={json.dumps(list(acp.get('args') or []))},",
150
+ f" protocol_version={int(acp.get('protocol_version', 1))},",
151
+ f" load_session={bool(acp.get('load_session', False))},",
152
+ f" mcp={_s(acp['mcp'])},",
153
+ f" modes={json.dumps(list(acp.get('modes') or []))},",
154
+ f" env={json.dumps(dict(acp.get('env') or {}))},",
155
+ " ),",
156
+ ]
157
+ lines += [
158
+ " requires=HarnessRequiresSpec(",
159
+ f" secrets={json.dumps(list(requires.get('secrets') or []))},",
160
+ f" binaries={json.dumps(list(requires.get('binaries') or []))},",
161
+ " ),",
162
+ " terms=HarnessTermsSpec(",
163
+ f" served_to_third_parties={_s(terms['served_to_third_parties'])},",
164
+ f" licence={_s(terms.get('licence'))},",
165
+ f" tier_restrictions={_opt(terms.get('tier_restrictions'))},",
166
+ " ),",
167
+ " skills=HarnessSkillsSpec(",
168
+ f" format={_s(skills['format'])},",
169
+ f" dir={_s(skills.get('dir'))},",
170
+ " ),",
171
+ " honours=HarnessHonoursSpec(",
172
+ f" model={bool(honours.get('model', False))},",
173
+ f" subagents={bool(honours.get('subagents', False))},",
174
+ " ),",
175
+ f" instructions_file={_s(spec.get('instructions_file'))},",
176
+ " registered=HarnessRegisterEntry(",
177
+ f" adapter={_s(entry.get('adapter'))},",
178
+ f" version={_s(entry.get('version'))},",
179
+ f" licence_source={_s(entry.get('licence_source'))},",
180
+ f" terms_source={_s(entry.get('terms_source'))},",
181
+ f" credential={_s(entry.get('credential'))},",
182
+ f" binaries={_s(entry.get('binaries'))},",
183
+ f" note={_s(entry.get('note'))},",
184
+ " ),",
185
+ ")",
186
+ ]
187
+ lines += [
188
+ "",
189
+ "",
190
+ "HARNESS_CATALOGUE: Dict[str, HarnessSpec] = {",
191
+ *[f" {_s(spec['id'])}: {_const(spec)}," for spec in specs],
192
+ "}",
193
+ "",
194
+ "",
195
+ "def get_harness(harness_id: str) -> Optional[HarnessSpec]:",
196
+ ' """A harness by id, or None."""',
197
+ " return HARNESS_CATALOGUE.get(harness_id)",
198
+ "",
199
+ "",
200
+ "def list_harnesses() -> List[HarnessSpec]:",
201
+ ' """Every harness of the catalogue."""',
202
+ " return list(HARNESS_CATALOGUE.values())",
203
+ "",
204
+ ]
205
+ return "\n".join(lines)
206
+
207
+
208
+ def _t(value: Any) -> str:
209
+ return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
210
+
211
+
212
+ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
213
+ lines = [
214
+ "/*",
215
+ " * Copyright (c) 2025-2026 Datalayer, Inc.",
216
+ " * Distributed under the terms of the Modified BSD License.",
217
+ " */",
218
+ "",
219
+ "/**",
220
+ " * Harness Catalog.",
221
+ " *",
222
+ " * An agent program driven over the Agent Client Protocol (SHIELD.md SH-01).",
223
+ " *",
224
+ " * This file is AUTO-GENERATED from YAML specifications.",
225
+ " * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
226
+ " */",
227
+ "",
228
+ "import type { HarnessSpec } from '../types/agentspecs';",
229
+ ]
230
+ for spec in specs:
231
+ acp = spec.get("acp")
232
+ requires = spec.get("requires") or {}
233
+ terms = spec.get("terms") or {}
234
+ skills = spec.get("skills") or {}
235
+ honours = spec.get("honours") or {}
236
+ entry = spec["_register"]
237
+ lines += [
238
+ "",
239
+ f"export const {_const(spec)}: HarnessSpec = {{",
240
+ f" id: {_t(spec['id'])},",
241
+ f" version: {_t(spec['version'])},",
242
+ f" name: {_t(spec['name'])},",
243
+ f" description: {_t(spec.get('description'))},",
244
+ f" enabled: {json.dumps(bool(spec.get('enabled', True)))},",
245
+ f" tags: {json.dumps(list(spec.get('tags') or []))},",
246
+ f" docs: {_t(spec.get('docs'))},",
247
+ ]
248
+ if acp is not None:
249
+ lines += [
250
+ " acp: {",
251
+ f" command: {_t(acp['command'])},",
252
+ f" args: {json.dumps(list(acp.get('args') or []))},",
253
+ f" protocolVersion: {int(acp.get('protocol_version', 1))},",
254
+ f" loadSession: {json.dumps(bool(acp.get('load_session', False)))},",
255
+ f" mcp: {_t(acp['mcp'])},",
256
+ f" modes: {json.dumps(list(acp.get('modes') or []))},",
257
+ f" env: {json.dumps(dict(acp.get('env') or {}))},",
258
+ " },",
259
+ ]
260
+ lines += [
261
+ " requires: {",
262
+ f" secrets: {json.dumps(list(requires.get('secrets') or []))},",
263
+ f" binaries: {json.dumps(list(requires.get('binaries') or []))},",
264
+ " },",
265
+ " terms: {",
266
+ f" servedToThirdParties: {_t(terms['served_to_third_parties'])},",
267
+ f" licence: {_t(terms.get('licence'))},",
268
+ *(
269
+ [f" tierRestrictions: {_t(terms.get('tier_restrictions'))},"]
270
+ if terms.get("tier_restrictions")
271
+ else []
272
+ ),
273
+ " },",
274
+ " skills: {",
275
+ f" format: {_t(skills['format'])},",
276
+ f" dir: {_t(skills.get('dir'))},",
277
+ " },",
278
+ " honours: {",
279
+ f" model: {json.dumps(bool(honours.get('model', False)))},",
280
+ f" subagents: {json.dumps(bool(honours.get('subagents', False)))},",
281
+ " },",
282
+ f" instructionsFile: {_t(spec.get('instructions_file'))},",
283
+ " registered: {",
284
+ f" adapter: {_t(entry.get('adapter'))},",
285
+ f" version: {_t(entry.get('version'))},",
286
+ f" licenceSource: {_t(entry.get('licence_source'))},",
287
+ f" termsSource: {_t(entry.get('terms_source'))},",
288
+ f" credential: {_t(entry.get('credential'))},",
289
+ f" binaries: {_t(entry.get('binaries'))},",
290
+ f" note: {_t(entry.get('note'))},",
291
+ " },",
292
+ "};",
293
+ ]
294
+ lines += [
295
+ "",
296
+ "export const HARNESS_CATALOGUE: Record<string, HarnessSpec> = {",
297
+ *[f" {_t(spec['id'])}: {_const(spec)}," for spec in specs],
298
+ "};",
299
+ "",
300
+ "/** A harness by id. */",
301
+ "export function getHarness(harnessId: string): HarnessSpec | undefined {",
302
+ " return HARNESS_CATALOGUE[harnessId];",
303
+ "}",
304
+ "",
305
+ "/** Every harness of the catalogue. */",
306
+ "export function listHarnesses(): HarnessSpec[] {",
307
+ " return Object.values(HARNESS_CATALOGUE);",
308
+ "}",
309
+ "",
310
+ ]
311
+ return "\n".join(lines)
312
+
313
+
314
+ def main() -> None:
315
+ parser = argparse.ArgumentParser(description=__doc__)
316
+ parser.add_argument("--specs-dir", type=Path, required=True)
317
+ parser.add_argument("--python-output", type=Path, required=True)
318
+ parser.add_argument("--typescript-output", type=Path, required=True)
319
+ args = parser.parse_args()
320
+ specs = load_harness_specs(args.specs_dir)
321
+ args.python_output.write_text(generate_python_code(specs))
322
+ args.typescript_output.write_text(generate_typescript_code(specs))
323
+ print(
324
+ f"Generated {len(specs)} harnesses: {args.python_output}, {args.typescript_output}"
325
+ )
326
+
327
+
328
+ if __name__ == "__main__":
329
+ main()