@datalayer/agent-runtimes 1.3.16 → 1.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/loop/apps/AppRenderer.d.ts +24 -0
- package/lib/loop/apps/AppRenderer.js +101 -0
- package/lib/loop/apps/appspec.d.ts +69 -0
- package/lib/loop/apps/appspec.js +566 -0
- package/lib/loop/apps/checks.d.ts +21 -0
- package/lib/loop/apps/checks.js +556 -0
- package/lib/loop/apps/index.d.ts +10 -0
- package/lib/loop/apps/index.js +14 -0
- package/lib/loop/apps/rules.d.ts +64 -0
- package/lib/loop/apps/rules.js +289 -0
- package/lib/loop/apps/yaml.d.ts +17 -0
- package/lib/loop/apps/yaml.js +185 -0
- package/lib/loop/index.d.ts +1 -0
- package/lib/loop/index.js +3 -0
- package/lib/specs/actions.d.ts +30 -0
- package/lib/specs/actions.js +616 -0
- package/lib/specs/apps.d.ts +25 -0
- package/lib/specs/apps.js +1064 -0
- package/lib/specs/cogs.d.ts +19 -0
- package/lib/specs/cogs.js +736 -0
- package/lib/specs/frames.d.ts +19 -0
- package/lib/specs/frames.js +444 -0
- package/lib/specs/gates.d.ts +22 -0
- package/lib/specs/gates.js +177 -0
- package/lib/specs/guards.d.ts +26 -0
- package/lib/specs/guards.js +930 -0
- package/lib/specs/index.d.ts +8 -0
- package/lib/specs/index.js +8 -0
- package/lib/specs/ops.d.ts +15 -0
- package/lib/specs/ops.js +1272 -0
- package/lib/specs/tracks.d.ts +15 -0
- package/lib/specs/tracks.js +87 -0
- package/lib/types/agentspecs.d.ts +435 -0
- package/package.json +2 -1
- package/scripts/codegen/agentspecs_clone.py +53 -0
- package/scripts/codegen/compose.py +65 -0
- package/scripts/codegen/generate_agents.py +8 -0
- package/scripts/codegen/generate_apps.py +454 -0
- package/scripts/codegen/generate_cogs.py +308 -0
- package/scripts/codegen/generate_frames.py +278 -0
- package/scripts/codegen/generate_ops.py +388 -0
|
@@ -0,0 +1,308 @@
|
|
|
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 YAML Cog specifications.
|
|
7
|
+
|
|
8
|
+
A Cog is an AI worker you can hold to account: it extends an agent spec and is
|
|
9
|
+
equipped with Frames. One YAML per Cog under ``agentspecs/cogs``.
|
|
10
|
+
|
|
11
|
+
A Cog is resolved here, by ``agentspecs`` itself: the agent it extends, the
|
|
12
|
+
Cog's own changes, then its Frames — their skills, tools and MCP servers added
|
|
13
|
+
to the agent's and their context rendered onto its system prompt. What is
|
|
14
|
+
generated is flat: each Cog carries a complete agent spec, written by the
|
|
15
|
+
agent generator, with the Frames it works under and the Guards it answers to.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python generate_cogs.py \\
|
|
19
|
+
--specs-dir agentspecs/agentspecs/cogs \\
|
|
20
|
+
--python-output agent_runtimes/specs/cogs.py \\
|
|
21
|
+
--typescript-output src/specs/cogs.ts
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import re
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
import generate_agents
|
|
32
|
+
from agentspecs_clone import import_from_clone
|
|
33
|
+
from versioning import ensure_spec_version, version_suffix
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def load_cog_specs(specs_dir: Path) -> list[dict[str, Any]]:
|
|
37
|
+
"""Every Cog of a directory, resolved to a flat agent spec with its Frames."""
|
|
38
|
+
cogs = import_from_clone(specs_dir, "cogs")
|
|
39
|
+
raw = cogs.load_raw_cogs(specs_dir)
|
|
40
|
+
specs = []
|
|
41
|
+
for identity in sorted(raw):
|
|
42
|
+
ensure_spec_version(raw[identity])
|
|
43
|
+
flat = cogs.resolve_cog(raw[identity])
|
|
44
|
+
ensure_spec_version(flat)
|
|
45
|
+
specs.append(flat)
|
|
46
|
+
return specs
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _agent_const(spec: dict[str, Any]) -> str:
|
|
50
|
+
"""The constant the agent generator gives a spec."""
|
|
51
|
+
base = spec["id"].upper().replace("-", "_")
|
|
52
|
+
suffix = "_SPEC" if spec["id"].endswith("-agent") else "_AGENTSPEC"
|
|
53
|
+
return base + suffix + version_suffix(spec["version"])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _cog_const(spec: dict[str, Any]) -> str:
|
|
57
|
+
"""The constant of a Cog: `cog-crawler` 0.0.1 → `COG_CRAWLER_0_0_1`."""
|
|
58
|
+
base = spec["id"].upper().replace("-", "_")
|
|
59
|
+
if not base.startswith("COG_"):
|
|
60
|
+
base += "_COG"
|
|
61
|
+
return base + version_suffix(spec["version"])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _cog_fields(spec: dict[str, Any]) -> dict[str, Any]:
|
|
65
|
+
context = spec["frame_context"]
|
|
66
|
+
return {
|
|
67
|
+
"id": spec["id"],
|
|
68
|
+
"version": spec["version"],
|
|
69
|
+
"name": spec["name"],
|
|
70
|
+
"description": " ".join(str(spec.get("description") or "").split()),
|
|
71
|
+
"agent": spec["agent"],
|
|
72
|
+
"frames": list(spec["frames"]),
|
|
73
|
+
"lineage": list(context["lineage"]),
|
|
74
|
+
"kind": spec["kind"],
|
|
75
|
+
"enabled": bool(spec["enabled"]),
|
|
76
|
+
"guards": list(context["guards"]),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
81
|
+
"""Generate Python code from resolved Cog specifications."""
|
|
82
|
+
code = generate_agents.generate_python_code([("", spec) for spec in specs])
|
|
83
|
+
# The agent generator writes an agent library; this is the same specs,
|
|
84
|
+
# under the names of a Cog catalogue.
|
|
85
|
+
head, registry = code.split(
|
|
86
|
+
"# ============================================================================\n# Agent Specs Registry",
|
|
87
|
+
1,
|
|
88
|
+
)
|
|
89
|
+
head = head.replace(
|
|
90
|
+
'"""\nAgent Library.\n\nPredefined agent specifications that can be instantiated as Agent Runtimes.\nTHIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.\nGenerated from YAML specifications in specs/agents/\n"""',
|
|
91
|
+
'"""\nCog Catalog.\n\nAI workers you can hold to account: a Cog extends an agent spec and is\nequipped with Frames. Every Cog is resolved — its `spec` is a complete agent\nspec, with its Frames\' capability and context already in it.\n\nTHIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.\nGenerated from YAML specifications in specs/cogs/\n"""',
|
|
92
|
+
)
|
|
93
|
+
if "Cog Catalog." not in head:
|
|
94
|
+
raise SystemExit(
|
|
95
|
+
"Error: the agent generator's header changed; update generate_cogs.py"
|
|
96
|
+
)
|
|
97
|
+
head = head.replace(
|
|
98
|
+
"from agent_runtimes.types import (\n",
|
|
99
|
+
"from agent_runtimes.types import (\n CogSpec,\n FrameGuardSpec,\n",
|
|
100
|
+
).replace(
|
|
101
|
+
"# Agent Specs\n",
|
|
102
|
+
"# Cog Agent Specs: each Cog, resolved to the agent spec a runtime launches\n",
|
|
103
|
+
)
|
|
104
|
+
del registry
|
|
105
|
+
|
|
106
|
+
lines = [
|
|
107
|
+
head.rstrip("\n"),
|
|
108
|
+
"",
|
|
109
|
+
"",
|
|
110
|
+
"# " + "=" * 76,
|
|
111
|
+
"# Cog Catalog",
|
|
112
|
+
"# " + "=" * 76,
|
|
113
|
+
"",
|
|
114
|
+
]
|
|
115
|
+
for spec in specs:
|
|
116
|
+
fields = _cog_fields(spec)
|
|
117
|
+
const = _cog_const(spec)
|
|
118
|
+
lines.append(f"{const} = CogSpec(")
|
|
119
|
+
for name, value in fields.items():
|
|
120
|
+
if name == "guards":
|
|
121
|
+
rendered = (
|
|
122
|
+
"["
|
|
123
|
+
+ ", ".join(f"FrameGuardSpec(**{guard!r})" for guard in value)
|
|
124
|
+
+ "]"
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
rendered = repr(value)
|
|
128
|
+
lines.append(f" {name}={rendered},")
|
|
129
|
+
lines.extend([f" spec={_agent_const(spec)},", ")", ""])
|
|
130
|
+
lines.extend(["", "COG_CATALOGUE: Dict[str, CogSpec] = {"])
|
|
131
|
+
for spec in specs:
|
|
132
|
+
const = _cog_const(spec)
|
|
133
|
+
lines.append(f" {spec['id']!r}: {const},")
|
|
134
|
+
lines.extend(
|
|
135
|
+
[
|
|
136
|
+
"}",
|
|
137
|
+
"",
|
|
138
|
+
"",
|
|
139
|
+
"def get_cog(cog_id: str) -> CogSpec | None:",
|
|
140
|
+
' """A Cog, by `id` or `id:version`, or None."""',
|
|
141
|
+
" cog = COG_CATALOGUE.get(cog_id)",
|
|
142
|
+
" if cog is not None:",
|
|
143
|
+
" return cog",
|
|
144
|
+
' base, _, version = cog_id.rpartition(":")',
|
|
145
|
+
' return COG_CATALOGUE.get(base) if base and "." in version else None',
|
|
146
|
+
"",
|
|
147
|
+
"",
|
|
148
|
+
"def list_cogs() -> list[CogSpec]:",
|
|
149
|
+
' """Every Cog of the catalogue, resolved."""',
|
|
150
|
+
" return list(COG_CATALOGUE.values())",
|
|
151
|
+
"",
|
|
152
|
+
"",
|
|
153
|
+
"def cogs_using(frame_id: str) -> list[CogSpec]:",
|
|
154
|
+
' """The Cogs that work under a Frame, directly or through one that extends it."""',
|
|
155
|
+
' base, _, version = frame_id.rpartition(":")',
|
|
156
|
+
' wanted = base if base and "." in version else frame_id',
|
|
157
|
+
" return [cog for cog in COG_CATALOGUE.values() if wanted in cog.lineage]",
|
|
158
|
+
"",
|
|
159
|
+
]
|
|
160
|
+
)
|
|
161
|
+
return "\n".join(lines)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def generate_typescript_code(specs: list[dict[str, Any]], specs_dir: Path) -> str:
|
|
165
|
+
"""Generate TypeScript code from resolved Cog specifications."""
|
|
166
|
+
root = specs_dir.resolve().parent
|
|
167
|
+
code = generate_agents.generate_typescript_code(
|
|
168
|
+
[("", spec) for spec in specs],
|
|
169
|
+
str(root / "mcp-servers"),
|
|
170
|
+
str(root / "skills"),
|
|
171
|
+
str(root / "tools"),
|
|
172
|
+
)
|
|
173
|
+
marker = "// ============================================================================\n// Agent Specs Registry"
|
|
174
|
+
if marker not in code:
|
|
175
|
+
raise SystemExit(
|
|
176
|
+
"Error: the agent generator's registry changed; update generate_cogs.py"
|
|
177
|
+
)
|
|
178
|
+
head = code.split(marker, 1)[0]
|
|
179
|
+
if "import type { Agentspec }" in head:
|
|
180
|
+
head = head.replace(
|
|
181
|
+
"import type { Agentspec }", "import type { Agentspec, CogSpec }", 1
|
|
182
|
+
)
|
|
183
|
+
elif "import type {\n Agentspec," in head:
|
|
184
|
+
head = head.replace(
|
|
185
|
+
"import type {\n Agentspec,", "import type {\n Agentspec,\n CogSpec,", 1
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
raise SystemExit(
|
|
189
|
+
"Error: the agent generator's type import changed; update generate_cogs.py"
|
|
190
|
+
)
|
|
191
|
+
# The resolved agent specs are this module's own: a Cog's `spec` exposes them.
|
|
192
|
+
head = head.replace("export const ", "const ").replace(
|
|
193
|
+
"Generated from YAML specifications in specs/agents/",
|
|
194
|
+
"Generated from YAML specifications in specs/cogs/",
|
|
195
|
+
)
|
|
196
|
+
described = (
|
|
197
|
+
" * Cog Catalog.\n *\n * AI workers you can hold to account: a Cog extends an agent spec and is\n"
|
|
198
|
+
" * equipped with Frames. Every Cog is resolved — its `spec` is a complete\n"
|
|
199
|
+
" * agent spec, with its Frames' capability and context already in it.\n"
|
|
200
|
+
)
|
|
201
|
+
library = " * Agent Library.\n *\n * Predefined agent specifications that can be instantiated as Agent Runtimes.\n"
|
|
202
|
+
if library not in head:
|
|
203
|
+
raise SystemExit(
|
|
204
|
+
"Error: the agent generator's header changed; update generate_cogs.py"
|
|
205
|
+
)
|
|
206
|
+
head = head.replace(library, described)
|
|
207
|
+
# The agent generator writes for `src/specs/agents/agents.ts`; this file
|
|
208
|
+
# is one directory up, beside the catalogues it imports.
|
|
209
|
+
head = head.replace("from '../../types'", "from '../types'")
|
|
210
|
+
head = re.sub(
|
|
211
|
+
r"from '\.\./(mcpServers|skills|tools|frontendTools)'", r"from './\1'", head
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
lines = [
|
|
215
|
+
head.rstrip("\n"),
|
|
216
|
+
"",
|
|
217
|
+
"// " + "=" * 76,
|
|
218
|
+
"// Cog Catalog",
|
|
219
|
+
"// " + "=" * 76,
|
|
220
|
+
"",
|
|
221
|
+
]
|
|
222
|
+
for spec in specs:
|
|
223
|
+
fields = _cog_fields(spec)
|
|
224
|
+
const = _cog_const(spec)
|
|
225
|
+
body = json.dumps(fields, indent=2, ensure_ascii=False)
|
|
226
|
+
body = (
|
|
227
|
+
body.rstrip().rstrip("}").rstrip()
|
|
228
|
+
+ f',\n "spec": {_agent_const(spec)}\n}}'
|
|
229
|
+
)
|
|
230
|
+
lines.extend([f"export const {const}: CogSpec = {body};", ""])
|
|
231
|
+
lines.append("export const COG_CATALOGUE: Record<string, CogSpec> = {")
|
|
232
|
+
for spec in specs:
|
|
233
|
+
const = _cog_const(spec)
|
|
234
|
+
lines.append(f" {json.dumps(spec['id'])}: {const},")
|
|
235
|
+
lines.extend(
|
|
236
|
+
[
|
|
237
|
+
"};",
|
|
238
|
+
"",
|
|
239
|
+
"function cogId(ref: string): string {",
|
|
240
|
+
" const at = ref.lastIndexOf(':');",
|
|
241
|
+
" return at > 0 && ref.slice(at + 1).includes('.') ? ref.slice(0, at) : ref;",
|
|
242
|
+
"}",
|
|
243
|
+
"",
|
|
244
|
+
"/** A Cog, by `id` or `id:version`, or undefined. */",
|
|
245
|
+
"export function getCog(ref: string): CogSpec | undefined {",
|
|
246
|
+
" // Own entries only: `constructor` and `toString` are not Cogs.",
|
|
247
|
+
" const id = cogId(ref);",
|
|
248
|
+
" return Object.prototype.hasOwnProperty.call(COG_CATALOGUE, id)",
|
|
249
|
+
" ? COG_CATALOGUE[id]",
|
|
250
|
+
" : undefined;",
|
|
251
|
+
"}",
|
|
252
|
+
"",
|
|
253
|
+
"export function listCogs(): CogSpec[] {",
|
|
254
|
+
" return Object.values(COG_CATALOGUE);",
|
|
255
|
+
"}",
|
|
256
|
+
"",
|
|
257
|
+
"/** The Cogs that work under a Frame, directly or through one that extends it. */",
|
|
258
|
+
"export function cogsUsing(frameId: string): CogSpec[] {",
|
|
259
|
+
" const wanted = cogId(frameId);",
|
|
260
|
+
" return listCogs().filter(cog => cog.lineage.includes(wanted));",
|
|
261
|
+
"}",
|
|
262
|
+
"",
|
|
263
|
+
]
|
|
264
|
+
)
|
|
265
|
+
return "\n".join(lines)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def main():
|
|
269
|
+
parser = argparse.ArgumentParser(
|
|
270
|
+
description="Generate Python and TypeScript code from YAML Cog specifications"
|
|
271
|
+
)
|
|
272
|
+
parser.add_argument(
|
|
273
|
+
"--specs-dir",
|
|
274
|
+
type=Path,
|
|
275
|
+
required=True,
|
|
276
|
+
help="Directory containing Cog YAML files",
|
|
277
|
+
)
|
|
278
|
+
parser.add_argument(
|
|
279
|
+
"--python-output", type=Path, required=True, help="Output path for Python file"
|
|
280
|
+
)
|
|
281
|
+
parser.add_argument(
|
|
282
|
+
"--typescript-output",
|
|
283
|
+
type=Path,
|
|
284
|
+
required=True,
|
|
285
|
+
help="Output path for TypeScript file",
|
|
286
|
+
)
|
|
287
|
+
args = parser.parse_args()
|
|
288
|
+
|
|
289
|
+
if not args.specs_dir.exists():
|
|
290
|
+
print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
|
|
291
|
+
sys.exit(1)
|
|
292
|
+
specs = load_cog_specs(args.specs_dir)
|
|
293
|
+
if not specs:
|
|
294
|
+
print(
|
|
295
|
+
f"Warning: No Cog specifications found in {args.specs_dir}", file=sys.stderr
|
|
296
|
+
)
|
|
297
|
+
return
|
|
298
|
+
args.python_output.parent.mkdir(parents=True, exist_ok=True)
|
|
299
|
+
args.python_output.write_text(generate_python_code(specs))
|
|
300
|
+
print(f"Generated Python code: {args.python_output}")
|
|
301
|
+
args.typescript_output.parent.mkdir(parents=True, exist_ok=True)
|
|
302
|
+
args.typescript_output.write_text(generate_typescript_code(specs, args.specs_dir))
|
|
303
|
+
print(f"Generated TypeScript code: {args.typescript_output}")
|
|
304
|
+
print(f"\n✓ Successfully generated code from {len(specs)} Cog specs")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
main()
|
|
@@ -0,0 +1,278 @@
|
|
|
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 YAML Frame specifications.
|
|
7
|
+
|
|
8
|
+
A Frame is the context work happens in, written down — rules, terminology,
|
|
9
|
+
goals, style, norms, process — owned, scoped, versioned and inherited, with
|
|
10
|
+
the Guards an output has to pass. One YAML per Frame under
|
|
11
|
+
``agentspecs/frames``; a Cog names the Frames it works under.
|
|
12
|
+
|
|
13
|
+
A Frame inherits with ``extends``. It is resolved here, by ``agentspecs``
|
|
14
|
+
itself, so the generated catalogue is flat: every Frame carries what it
|
|
15
|
+
inherits, and ``lineage`` says from which Frames.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
python generate_frames.py \\
|
|
19
|
+
--specs-dir agentspecs/agentspecs/frames \\
|
|
20
|
+
--python-output agent_runtimes/specs/frames.py \\
|
|
21
|
+
--typescript-output src/specs/frames.ts
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from agentspecs_clone import import_from_clone
|
|
31
|
+
from versioning import ensure_spec_version, version_suffix
|
|
32
|
+
|
|
33
|
+
#: The fields of a generated Frame, in the order they are written.
|
|
34
|
+
FIELDS = (
|
|
35
|
+
"id",
|
|
36
|
+
"version",
|
|
37
|
+
"name",
|
|
38
|
+
"description",
|
|
39
|
+
"scope",
|
|
40
|
+
"owner",
|
|
41
|
+
"extends",
|
|
42
|
+
"lineage",
|
|
43
|
+
"tags",
|
|
44
|
+
"enabled",
|
|
45
|
+
"icon",
|
|
46
|
+
"emoji",
|
|
47
|
+
"rules",
|
|
48
|
+
"terminology",
|
|
49
|
+
"goals",
|
|
50
|
+
"style",
|
|
51
|
+
"norms",
|
|
52
|
+
"process",
|
|
53
|
+
"architecture",
|
|
54
|
+
"prompts",
|
|
55
|
+
"skills",
|
|
56
|
+
"tools",
|
|
57
|
+
"mcp_servers",
|
|
58
|
+
"guards",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
#: Python field → TypeScript property, where they differ.
|
|
62
|
+
CAMEL = {"mcp_servers": "mcpServers"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_frame_specs(specs_dir: Path) -> list[dict[str, Any]]:
|
|
66
|
+
"""Every Frame of a directory, resolved and validated, as plain data."""
|
|
67
|
+
frames = import_from_clone(specs_dir, "frames")
|
|
68
|
+
raw = frames.load_raw_frames(specs_dir)
|
|
69
|
+
specs = []
|
|
70
|
+
for identity in sorted(raw):
|
|
71
|
+
ensure_spec_version(raw[identity])
|
|
72
|
+
resolved = frames.resolve_frame(raw[identity], raw)
|
|
73
|
+
lineage = list(resolved["lineage"])
|
|
74
|
+
spec = frames.FrameSpec(
|
|
75
|
+
**{key: value for key, value in resolved.items() if key != "lineage"}
|
|
76
|
+
).model_dump(mode="json")
|
|
77
|
+
spec["description"] = " ".join(str(spec["description"]).split())
|
|
78
|
+
spec["extends"] = raw[identity].get("extends") or None
|
|
79
|
+
# Tags describe the Frame itself; its parent's are the parent's.
|
|
80
|
+
spec["tags"] = list(raw[identity].get("tags") or [])
|
|
81
|
+
spec["lineage"] = lineage
|
|
82
|
+
specs.append({field: spec.get(field) for field in FIELDS})
|
|
83
|
+
return specs
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _const_name(spec: dict[str, Any]) -> str:
|
|
87
|
+
base = spec["id"].replace("-", "_").replace(".", "_").upper()
|
|
88
|
+
return f"{base}_FRAME{version_suffix(spec['version'])}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
92
|
+
"""Generate Python code from Frame specifications."""
|
|
93
|
+
lines = [
|
|
94
|
+
"# Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
95
|
+
"# Distributed under the terms of the Modified BSD License.",
|
|
96
|
+
'"""',
|
|
97
|
+
"Frame Catalog.",
|
|
98
|
+
"",
|
|
99
|
+
"The context work happens in, written down: owned, scoped and inherited.",
|
|
100
|
+
"Every Frame is resolved — what it inherits is already in it.",
|
|
101
|
+
"",
|
|
102
|
+
"This file is AUTO-GENERATED from YAML specifications.",
|
|
103
|
+
"DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
|
|
104
|
+
'"""',
|
|
105
|
+
"",
|
|
106
|
+
"from typing import Dict",
|
|
107
|
+
"",
|
|
108
|
+
"from agent_runtimes.types import FrameGuardSpec, FramePromptSpec, FrameSpec",
|
|
109
|
+
"",
|
|
110
|
+
"",
|
|
111
|
+
"# " + "=" * 76,
|
|
112
|
+
"# Frame Definitions",
|
|
113
|
+
"# " + "=" * 76,
|
|
114
|
+
"",
|
|
115
|
+
]
|
|
116
|
+
for spec in specs:
|
|
117
|
+
lines.append(f"{_const_name(spec)} = FrameSpec(")
|
|
118
|
+
for field in FIELDS:
|
|
119
|
+
value = spec[field]
|
|
120
|
+
if field == "guards":
|
|
121
|
+
rendered = (
|
|
122
|
+
"["
|
|
123
|
+
+ ", ".join(f"FrameGuardSpec(**{guard!r})" for guard in value)
|
|
124
|
+
+ "]"
|
|
125
|
+
)
|
|
126
|
+
elif field == "prompts":
|
|
127
|
+
rendered = (
|
|
128
|
+
"["
|
|
129
|
+
+ ", ".join(f"FramePromptSpec(**{prompt!r})" for prompt in value)
|
|
130
|
+
+ "]"
|
|
131
|
+
)
|
|
132
|
+
else:
|
|
133
|
+
rendered = repr(value)
|
|
134
|
+
lines.append(f" {field}={rendered},")
|
|
135
|
+
lines.extend([")", ""])
|
|
136
|
+
lines.extend(
|
|
137
|
+
[
|
|
138
|
+
"",
|
|
139
|
+
"# " + "=" * 76,
|
|
140
|
+
"# Frame Catalog",
|
|
141
|
+
"# " + "=" * 76,
|
|
142
|
+
"",
|
|
143
|
+
"FRAME_CATALOGUE: Dict[str, FrameSpec] = {",
|
|
144
|
+
]
|
|
145
|
+
)
|
|
146
|
+
for spec in specs:
|
|
147
|
+
lines.append(f" {spec['id']!r}: {_const_name(spec)},")
|
|
148
|
+
lines.extend(
|
|
149
|
+
[
|
|
150
|
+
"}",
|
|
151
|
+
"",
|
|
152
|
+
"",
|
|
153
|
+
"def get_frame(frame_id: str) -> FrameSpec | None:",
|
|
154
|
+
' """The Frame a Cog names, by `id` or `id:version`, or None."""',
|
|
155
|
+
" frame = FRAME_CATALOGUE.get(frame_id)",
|
|
156
|
+
" if frame is not None:",
|
|
157
|
+
" return frame",
|
|
158
|
+
' base, _, version = frame_id.rpartition(":")',
|
|
159
|
+
' return FRAME_CATALOGUE.get(base) if base and "." in version else None',
|
|
160
|
+
"",
|
|
161
|
+
"",
|
|
162
|
+
"def list_frames() -> list[FrameSpec]:",
|
|
163
|
+
' """Every Frame of the catalogue, resolved."""',
|
|
164
|
+
" return list(FRAME_CATALOGUE.values())",
|
|
165
|
+
"",
|
|
166
|
+
]
|
|
167
|
+
)
|
|
168
|
+
return "\n".join(lines)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _ts_object(spec: dict[str, Any]) -> str:
|
|
172
|
+
camel = {
|
|
173
|
+
CAMEL.get(field, field): spec[field]
|
|
174
|
+
for field in FIELDS
|
|
175
|
+
if spec[field] is not None
|
|
176
|
+
}
|
|
177
|
+
return json.dumps(camel, indent=2, ensure_ascii=False)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
|
|
181
|
+
"""Generate TypeScript code from Frame specifications."""
|
|
182
|
+
lines = [
|
|
183
|
+
"/*",
|
|
184
|
+
" * Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
185
|
+
" * Distributed under the terms of the Modified BSD License.",
|
|
186
|
+
" */",
|
|
187
|
+
"",
|
|
188
|
+
"/**",
|
|
189
|
+
" * Frame Catalog.",
|
|
190
|
+
" *",
|
|
191
|
+
" * The context work happens in, written down: owned, scoped and inherited.",
|
|
192
|
+
" * Every Frame is resolved — what it inherits is already in it.",
|
|
193
|
+
" *",
|
|
194
|
+
" * This file is AUTO-GENERATED from YAML specifications.",
|
|
195
|
+
" * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
|
|
196
|
+
" */",
|
|
197
|
+
"",
|
|
198
|
+
"import type { FrameSpec } from '../types/agentspecs';",
|
|
199
|
+
"",
|
|
200
|
+
]
|
|
201
|
+
for spec in specs:
|
|
202
|
+
lines.extend(
|
|
203
|
+
[f"export const {_const_name(spec)}: FrameSpec = {_ts_object(spec)};", ""]
|
|
204
|
+
)
|
|
205
|
+
lines.append("export const FRAME_CATALOGUE: Record<string, FrameSpec> = {")
|
|
206
|
+
for spec in specs:
|
|
207
|
+
lines.append(f" {json.dumps(spec['id'])}: {_const_name(spec)},")
|
|
208
|
+
lines.extend(
|
|
209
|
+
[
|
|
210
|
+
"};",
|
|
211
|
+
"",
|
|
212
|
+
"/** The Frame a Cog names, by `id` or `id:version`, or undefined. */",
|
|
213
|
+
"export function getFrame(frameId: string): FrameSpec | undefined {",
|
|
214
|
+
" // Own entries only: `constructor` and `toString` are not Frames.",
|
|
215
|
+
" const own = (id: string): FrameSpec | undefined =>",
|
|
216
|
+
" Object.prototype.hasOwnProperty.call(FRAME_CATALOGUE, id)",
|
|
217
|
+
" ? FRAME_CATALOGUE[id]",
|
|
218
|
+
" : undefined;",
|
|
219
|
+
" const at = frameId.lastIndexOf(':');",
|
|
220
|
+
" return (",
|
|
221
|
+
" own(frameId) ??",
|
|
222
|
+
" (at > 0 && frameId.slice(at + 1).includes('.')",
|
|
223
|
+
" ? own(frameId.slice(0, at))",
|
|
224
|
+
" : undefined)",
|
|
225
|
+
" );",
|
|
226
|
+
"}",
|
|
227
|
+
"",
|
|
228
|
+
"export function listFrames(): FrameSpec[] {",
|
|
229
|
+
" return Object.values(FRAME_CATALOGUE);",
|
|
230
|
+
"}",
|
|
231
|
+
"",
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
return "\n".join(lines)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def main():
|
|
238
|
+
parser = argparse.ArgumentParser(
|
|
239
|
+
description="Generate Python and TypeScript code from YAML Frame specifications"
|
|
240
|
+
)
|
|
241
|
+
parser.add_argument(
|
|
242
|
+
"--specs-dir",
|
|
243
|
+
type=Path,
|
|
244
|
+
required=True,
|
|
245
|
+
help="Directory containing Frame YAML files",
|
|
246
|
+
)
|
|
247
|
+
parser.add_argument(
|
|
248
|
+
"--python-output", type=Path, required=True, help="Output path for Python file"
|
|
249
|
+
)
|
|
250
|
+
parser.add_argument(
|
|
251
|
+
"--typescript-output",
|
|
252
|
+
type=Path,
|
|
253
|
+
required=True,
|
|
254
|
+
help="Output path for TypeScript file",
|
|
255
|
+
)
|
|
256
|
+
args = parser.parse_args()
|
|
257
|
+
|
|
258
|
+
if not args.specs_dir.exists():
|
|
259
|
+
print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
specs = load_frame_specs(args.specs_dir)
|
|
262
|
+
if not specs:
|
|
263
|
+
print(
|
|
264
|
+
f"Warning: No Frame specifications found in {args.specs_dir}",
|
|
265
|
+
file=sys.stderr,
|
|
266
|
+
)
|
|
267
|
+
return
|
|
268
|
+
args.python_output.parent.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
args.python_output.write_text(generate_python_code(specs))
|
|
270
|
+
print(f"Generated Python code: {args.python_output}")
|
|
271
|
+
args.typescript_output.parent.mkdir(parents=True, exist_ok=True)
|
|
272
|
+
args.typescript_output.write_text(generate_typescript_code(specs))
|
|
273
|
+
print(f"Generated TypeScript code: {args.typescript_output}")
|
|
274
|
+
print(f"\n✓ Successfully generated code from {len(specs)} Frame specs")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == "__main__":
|
|
278
|
+
main()
|