@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
|
@@ -1580,6 +1580,14 @@ export * from './skills';
|
|
|
1580
1580
|
export * from './tools';
|
|
1581
1581
|
export * from './triggers';
|
|
1582
1582
|
export * from './uiPlugins';
|
|
1583
|
+
export * from './frames';
|
|
1584
|
+
export * from './cogs';
|
|
1585
|
+
export * from './guards';
|
|
1586
|
+
export * from './gates';
|
|
1587
|
+
export * from './tracks';
|
|
1588
|
+
export * from './ops';
|
|
1589
|
+
export * from './apps';
|
|
1590
|
+
export * from './actions';
|
|
1583
1591
|
"""
|
|
1584
1592
|
)
|
|
1585
1593
|
|
|
@@ -0,0 +1,454 @@
|
|
|
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 application specifications.
|
|
7
|
+
|
|
8
|
+
Applications (agentspecs >= 0.0.15): an agent with an interface, rules, tests
|
|
9
|
+
and a place to run — a chat, a widget, a decision or a worker, in one spec,
|
|
10
|
+
the Appspec. One YAML per application under ``agentspecs/apps``.
|
|
11
|
+
|
|
12
|
+
A rule is written in a person's words and enforced on what a tool does, so
|
|
13
|
+
the classes of action travel with the applications: this generator also
|
|
14
|
+
writes what every tool of the catalogue and every MCP server's tools do —
|
|
15
|
+
read, write, send, buy, delete, publish — read from ``agentspecs.actions``.
|
|
16
|
+
|
|
17
|
+
Each application is validated here, by ``agentspecs`` itself, and arrives
|
|
18
|
+
with its layout said and with what it names that is not enabled (`setup`).
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
python generate_apps.py \\
|
|
22
|
+
--specs-dir agentspecs/agentspecs/apps \\
|
|
23
|
+
--python-output agent_runtimes/specs/apps.py \\
|
|
24
|
+
--typescript-output src/specs/apps.ts \\
|
|
25
|
+
--actions-python-output agent_runtimes/specs/actions.py \\
|
|
26
|
+
--actions-typescript-output src/specs/actions.ts
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import json
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from agentspecs_clone import import_from_clone
|
|
36
|
+
from versioning import ensure_spec_version, version_suffix
|
|
37
|
+
|
|
38
|
+
#: The keys of an Appspec, as TypeScript spells them. Keys inside a surface's
|
|
39
|
+
#: components are the protocol's own and are left alone.
|
|
40
|
+
CAMEL = {
|
|
41
|
+
"applies_to": "appliesTo",
|
|
42
|
+
"ready_at": "readyAt",
|
|
43
|
+
"keep_for": "keepFor",
|
|
44
|
+
"retention_days": "retentionDays",
|
|
45
|
+
"composed_by": "composedBy",
|
|
46
|
+
"composed_at": "composedAt",
|
|
47
|
+
"min_confidence": "minConfidence",
|
|
48
|
+
"judgment_model": "judgmentModel",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#: The keys whose value is carried as it is written: a component tree, weights by name.
|
|
52
|
+
VERBATIM = ("components", "weights")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _flat(text: Any) -> str:
|
|
56
|
+
"""A text on one line."""
|
|
57
|
+
return " ".join(str(text or "").split())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
61
|
+
"""Every application, validated, as plain data — and the action classes."""
|
|
62
|
+
apps_module = import_from_clone(specs_dir, "apps")
|
|
63
|
+
actions_module = sys.modules["agentspecs.actions"]
|
|
64
|
+
apps = apps_module.load_apps(specs_dir)
|
|
65
|
+
specs: list[dict[str, Any]] = []
|
|
66
|
+
behaviours: dict[str, dict[str, str]] = {}
|
|
67
|
+
escalations: dict[str, dict[str, Any]] = {}
|
|
68
|
+
sources: dict[str, Any] = {}
|
|
69
|
+
for identity in sorted(apps):
|
|
70
|
+
app = apps[identity]
|
|
71
|
+
spec = app.model_dump(mode="json", by_alias=True)
|
|
72
|
+
spec["interface"]["layout"] = app.layout.value
|
|
73
|
+
spec["record"]["retention_days"] = app.record.retention_days
|
|
74
|
+
spec["setup"] = apps_module.app_setup(app)
|
|
75
|
+
behaviours[identity] = {
|
|
76
|
+
tool: behaviour.value
|
|
77
|
+
for tool, behaviour in sorted(apps_module.tool_behaviours(app).items())
|
|
78
|
+
}
|
|
79
|
+
escalations[identity] = dict(sorted(apps_module.tool_escalations(app).items()))
|
|
80
|
+
sources[identity] = apps_module.dump_app(app)
|
|
81
|
+
for rule in spec["rules"]:
|
|
82
|
+
rule["applies_to"] = (
|
|
83
|
+
[rule["applies_to"]]
|
|
84
|
+
if isinstance(rule["applies_to"], str)
|
|
85
|
+
else rule["applies_to"]
|
|
86
|
+
)
|
|
87
|
+
for key in ("description", "instructions", "goal"):
|
|
88
|
+
spec[key] = _flat(spec.get(key))
|
|
89
|
+
spec["interface"]["welcome"] = _flat(spec["interface"].get("welcome"))
|
|
90
|
+
ensure_spec_version(spec)
|
|
91
|
+
specs.append(spec)
|
|
92
|
+
|
|
93
|
+
tools = {
|
|
94
|
+
identity: [item.value for item in actions_module.tool_classes(tool)]
|
|
95
|
+
for identity, tool in sorted(actions_module.tool_specs().items())
|
|
96
|
+
}
|
|
97
|
+
servers: dict[str, Any] = {}
|
|
98
|
+
for identity, server in sorted(actions_module.server_specs().items()):
|
|
99
|
+
declared = server.get("actions") or {}
|
|
100
|
+
names = list(declared.get("tools") or {})
|
|
101
|
+
conditions = {
|
|
102
|
+
name: [
|
|
103
|
+
condition.as_data()
|
|
104
|
+
for condition in actions_module.server_tool_conditions(server, name)
|
|
105
|
+
]
|
|
106
|
+
for name in names
|
|
107
|
+
}
|
|
108
|
+
servers[identity] = {
|
|
109
|
+
"checked": declared.get("checked") or None,
|
|
110
|
+
"default": [
|
|
111
|
+
item.value
|
|
112
|
+
for item in actions_module.classes_from(declared.get("default"))
|
|
113
|
+
],
|
|
114
|
+
# What each tool does of its own, whatever it is asked.
|
|
115
|
+
"tools": {
|
|
116
|
+
name: [
|
|
117
|
+
item.value
|
|
118
|
+
for item in actions_module.server_tool_classes(server, name, {})
|
|
119
|
+
]
|
|
120
|
+
for name in names
|
|
121
|
+
},
|
|
122
|
+
# What an argument makes it do besides.
|
|
123
|
+
"conditions": {name: found for name, found in conditions.items() if found},
|
|
124
|
+
}
|
|
125
|
+
actions = {
|
|
126
|
+
"classes": [item.value for item in actions_module.ActionClass],
|
|
127
|
+
"tools": tools,
|
|
128
|
+
"servers": servers,
|
|
129
|
+
"behaviours": behaviours,
|
|
130
|
+
"escalations": escalations,
|
|
131
|
+
"sources": sources,
|
|
132
|
+
}
|
|
133
|
+
return specs, actions
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _const_name(spec: dict[str, Any]) -> str:
|
|
137
|
+
"""`web-research` 0.0.1 → `WEB_RESEARCH_APP_0_0_1`."""
|
|
138
|
+
base = spec["id"].upper().replace("-", "_").replace(".", "_")
|
|
139
|
+
return f"{base}_APP" + version_suffix(spec["version"])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _typescript_value(value: Any, verbatim: bool = False) -> Any:
|
|
143
|
+
"""An application as the TypeScript interfaces read it; nulls left out."""
|
|
144
|
+
if isinstance(value, dict):
|
|
145
|
+
return {
|
|
146
|
+
(key if verbatim else CAMEL.get(key, key)): _typescript_value(
|
|
147
|
+
item, verbatim or key in VERBATIM
|
|
148
|
+
)
|
|
149
|
+
for key, item in value.items()
|
|
150
|
+
if item is not None
|
|
151
|
+
}
|
|
152
|
+
if isinstance(value, list):
|
|
153
|
+
return [_typescript_value(item, verbatim) for item in value]
|
|
154
|
+
return value
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
HEADER = [
|
|
158
|
+
"This file is AUTO-GENERATED from YAML specifications.",
|
|
159
|
+
"DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
ABOUT_APPS = [
|
|
163
|
+
"Application Catalog.",
|
|
164
|
+
"",
|
|
165
|
+
"What a person uses and relies on: an agent with an interface, rules, tests",
|
|
166
|
+
"and a place to run. A chat, a widget, a decision or a worker, in one spec.",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
ABOUT_ACTIONS = [
|
|
170
|
+
"Action Classes.",
|
|
171
|
+
"",
|
|
172
|
+
"What every tool does to the world: read, write, send, buy, delete, publish.",
|
|
173
|
+
"A tool with no class is unknown, and unknown is the most restricted.",
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
178
|
+
"""Generate the Python application catalogue."""
|
|
179
|
+
lines = [
|
|
180
|
+
"# Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
181
|
+
"# Distributed under the terms of the Modified BSD License.",
|
|
182
|
+
'"""',
|
|
183
|
+
*ABOUT_APPS,
|
|
184
|
+
"",
|
|
185
|
+
*HEADER,
|
|
186
|
+
'"""',
|
|
187
|
+
"",
|
|
188
|
+
"from typing import Dict",
|
|
189
|
+
"",
|
|
190
|
+
"from agent_runtimes.types import AppSpec",
|
|
191
|
+
"",
|
|
192
|
+
"",
|
|
193
|
+
"# " + "=" * 76,
|
|
194
|
+
"# Application Definitions",
|
|
195
|
+
"# " + "=" * 76,
|
|
196
|
+
"",
|
|
197
|
+
]
|
|
198
|
+
for spec in specs:
|
|
199
|
+
lines.extend(
|
|
200
|
+
[f"{_const_name(spec)} = AppSpec.model_validate(", f" {spec!r}", ")", ""]
|
|
201
|
+
)
|
|
202
|
+
lines.extend(
|
|
203
|
+
[
|
|
204
|
+
"",
|
|
205
|
+
"# " + "=" * 76,
|
|
206
|
+
"# Application Catalog",
|
|
207
|
+
"# " + "=" * 76,
|
|
208
|
+
"",
|
|
209
|
+
"APP_CATALOGUE: Dict[str, AppSpec] = {",
|
|
210
|
+
]
|
|
211
|
+
)
|
|
212
|
+
for spec in specs:
|
|
213
|
+
lines.append(f" {spec['id']!r}: {_const_name(spec)},")
|
|
214
|
+
lines.extend(
|
|
215
|
+
[
|
|
216
|
+
"}",
|
|
217
|
+
"",
|
|
218
|
+
"",
|
|
219
|
+
"def get_app(app_id: str) -> AppSpec | None:",
|
|
220
|
+
' """An application, by `id` or `id:version`, or None."""',
|
|
221
|
+
" found = APP_CATALOGUE.get(app_id)",
|
|
222
|
+
" if found is not None:",
|
|
223
|
+
" return found",
|
|
224
|
+
' base, _, version = app_id.rpartition(":")',
|
|
225
|
+
' return APP_CATALOGUE.get(base) if base and "." in version else None',
|
|
226
|
+
"",
|
|
227
|
+
"",
|
|
228
|
+
"def list_apps(kind: str | None = None) -> list[AppSpec]:",
|
|
229
|
+
' """Every application of the catalogue, or those of a kind."""',
|
|
230
|
+
" return [app for app in APP_CATALOGUE.values() if kind is None or app.kind == kind]",
|
|
231
|
+
"",
|
|
232
|
+
]
|
|
233
|
+
)
|
|
234
|
+
return "\n".join(lines)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def generate_typescript_code(
|
|
238
|
+
specs: list[dict[str, Any]], sources: dict[str, Any]
|
|
239
|
+
) -> str:
|
|
240
|
+
"""Generate the TypeScript application catalogue."""
|
|
241
|
+
lines = [
|
|
242
|
+
"/*",
|
|
243
|
+
" * Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
244
|
+
" * Distributed under the terms of the Modified BSD License.",
|
|
245
|
+
" */",
|
|
246
|
+
"",
|
|
247
|
+
"/**",
|
|
248
|
+
*[f" * {line}".rstrip() for line in ABOUT_APPS],
|
|
249
|
+
" *",
|
|
250
|
+
*[f" * {line}" for line in HEADER],
|
|
251
|
+
" */",
|
|
252
|
+
"",
|
|
253
|
+
"import type { AppKind, AppSpec } from '../types/agentspecs';",
|
|
254
|
+
"",
|
|
255
|
+
]
|
|
256
|
+
for spec in specs:
|
|
257
|
+
body = json.dumps(_typescript_value(spec), indent=2, ensure_ascii=False)
|
|
258
|
+
lines.extend([f"export const {_const_name(spec)}: AppSpec = {body};", ""])
|
|
259
|
+
lines.append("export const APP_CATALOGUE: Record<string, AppSpec> = {")
|
|
260
|
+
for spec in specs:
|
|
261
|
+
lines.append(f" {json.dumps(spec['id'])}: {_const_name(spec)},")
|
|
262
|
+
lines.extend(
|
|
263
|
+
[
|
|
264
|
+
"};",
|
|
265
|
+
"",
|
|
266
|
+
"/** An application, by `id` or `id:version`, or undefined. */",
|
|
267
|
+
"export function getApp(ref: string): AppSpec | undefined {",
|
|
268
|
+
" // Own entries only: `constructor` and `toString` are not applications.",
|
|
269
|
+
" const own = (id: string): AppSpec | undefined =>",
|
|
270
|
+
" Object.prototype.hasOwnProperty.call(APP_CATALOGUE, id)",
|
|
271
|
+
" ? APP_CATALOGUE[id]",
|
|
272
|
+
" : undefined;",
|
|
273
|
+
" const at = ref.lastIndexOf(':');",
|
|
274
|
+
" return (",
|
|
275
|
+
" own(ref) ??",
|
|
276
|
+
" (at > 0 && ref.slice(at + 1).includes('.')",
|
|
277
|
+
" ? own(ref.slice(0, at))",
|
|
278
|
+
" : undefined)",
|
|
279
|
+
" );",
|
|
280
|
+
"}",
|
|
281
|
+
"",
|
|
282
|
+
"/**",
|
|
283
|
+
" * Each application as the document its file holds: the spec's own words,",
|
|
284
|
+
" * nothing written that is at its default. What reading and writing an",
|
|
285
|
+
" * Appspec have to give back.",
|
|
286
|
+
" */",
|
|
287
|
+
"export const APP_SOURCES: Record<string, Record<string, unknown>> = "
|
|
288
|
+
+ json.dumps(sources, indent=2, ensure_ascii=False)
|
|
289
|
+
+ ";",
|
|
290
|
+
"",
|
|
291
|
+
"/** Every application of the catalogue, or those of a kind. */",
|
|
292
|
+
"export function listApps(kind?: AppKind): AppSpec[] {",
|
|
293
|
+
" return Object.values(APP_CATALOGUE).filter(",
|
|
294
|
+
" app => kind === undefined || app.kind === kind,",
|
|
295
|
+
" );",
|
|
296
|
+
"}",
|
|
297
|
+
"",
|
|
298
|
+
]
|
|
299
|
+
)
|
|
300
|
+
return "\n".join(lines)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def generate_actions_python_code(actions: dict[str, Any]) -> str:
|
|
304
|
+
"""Generate the Python action classes."""
|
|
305
|
+
lines = [
|
|
306
|
+
"# Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
307
|
+
"# Distributed under the terms of the Modified BSD License.",
|
|
308
|
+
'"""',
|
|
309
|
+
*ABOUT_ACTIONS,
|
|
310
|
+
"",
|
|
311
|
+
*HEADER,
|
|
312
|
+
'"""',
|
|
313
|
+
"",
|
|
314
|
+
"from typing import Any, Dict, List",
|
|
315
|
+
"",
|
|
316
|
+
"from agent_runtimes.types import ServerActionsSpec",
|
|
317
|
+
"",
|
|
318
|
+
"#: The classes of action, from the one that changes nothing.",
|
|
319
|
+
f"ACTION_CLASSES: List[str] = {actions['classes']!r}",
|
|
320
|
+
"",
|
|
321
|
+
"#: What each tool of the tools catalogue does, by id.",
|
|
322
|
+
f"TOOL_ACTIONS: Dict[str, List[str]] = {actions['tools']!r}",
|
|
323
|
+
"",
|
|
324
|
+
"#: What each MCP server's tools do, by server id. `checked` is the day the",
|
|
325
|
+
"#: names were read off the running server, or None when nobody looked.",
|
|
326
|
+
"SERVER_ACTIONS: Dict[str, ServerActionsSpec] = {",
|
|
327
|
+
]
|
|
328
|
+
for identity, server in actions["servers"].items():
|
|
329
|
+
lines.append(f" {identity!r}: ServerActionsSpec.model_validate({server!r}),")
|
|
330
|
+
lines.extend(
|
|
331
|
+
[
|
|
332
|
+
"}",
|
|
333
|
+
"",
|
|
334
|
+
"#: What each application of the catalogue does about each classed tool of the",
|
|
335
|
+
"#: servers it connects to, as agentspecs decides it: `do_it`, `if_asked`,",
|
|
336
|
+
"#: `ask_first` or `leave_to_me`. What the rules engine has to reproduce.",
|
|
337
|
+
f"APP_BEHAVIOURS: Dict[str, Dict[str, str]] = {actions['behaviours']!r}",
|
|
338
|
+
"",
|
|
339
|
+
"#: Where what a tool is asked changes what an application does about it: by",
|
|
340
|
+
"#: application and tool, each condition and the behaviour when it holds.",
|
|
341
|
+
f"APP_ESCALATIONS: Dict[str, Dict[str, List[Dict[str, Any]]]] = {actions['escalations']!r}",
|
|
342
|
+
"",
|
|
343
|
+
]
|
|
344
|
+
)
|
|
345
|
+
return "\n".join(lines)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def generate_actions_typescript_code(actions: dict[str, Any]) -> str:
|
|
349
|
+
"""Generate the TypeScript action classes."""
|
|
350
|
+
lines = [
|
|
351
|
+
"/*",
|
|
352
|
+
" * Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
353
|
+
" * Distributed under the terms of the Modified BSD License.",
|
|
354
|
+
" */",
|
|
355
|
+
"",
|
|
356
|
+
"/**",
|
|
357
|
+
*[f" * {line}".rstrip() for line in ABOUT_ACTIONS],
|
|
358
|
+
" *",
|
|
359
|
+
*[f" * {line}" for line in HEADER],
|
|
360
|
+
" */",
|
|
361
|
+
"",
|
|
362
|
+
"import type {",
|
|
363
|
+
" ActionClass,",
|
|
364
|
+
" AppBehaviour,",
|
|
365
|
+
" AppEscalation,",
|
|
366
|
+
" ServerActionsSpec,",
|
|
367
|
+
"} from '../types/agentspecs';",
|
|
368
|
+
"",
|
|
369
|
+
"/** The classes of action, from the one that changes nothing. */",
|
|
370
|
+
f"export const ACTION_CLASSES: ActionClass[] = {json.dumps(actions['classes'])};",
|
|
371
|
+
"",
|
|
372
|
+
"/** What each tool of the tools catalogue does, by id. */",
|
|
373
|
+
"export const TOOL_ACTIONS: Record<string, ActionClass[]> = "
|
|
374
|
+
+ json.dumps(actions["tools"], indent=2)
|
|
375
|
+
+ ";",
|
|
376
|
+
"",
|
|
377
|
+
"/**",
|
|
378
|
+
" * What each MCP server's tools do, by server id. `checked` is the day the",
|
|
379
|
+
" * names were read off the running server; absent when nobody looked.",
|
|
380
|
+
" */",
|
|
381
|
+
"export const SERVER_ACTIONS: Record<string, ServerActionsSpec> = "
|
|
382
|
+
+ json.dumps(_drop_nulls(actions["servers"]), indent=2)
|
|
383
|
+
+ ";",
|
|
384
|
+
"",
|
|
385
|
+
"/**",
|
|
386
|
+
" * What each application of the catalogue does about each classed tool of the",
|
|
387
|
+
" * servers it connects to, as agentspecs decides it. What the rules engine has",
|
|
388
|
+
" * to reproduce.",
|
|
389
|
+
" */",
|
|
390
|
+
"export const APP_BEHAVIOURS: Record<string, Record<string, AppBehaviour>> = "
|
|
391
|
+
+ json.dumps(actions["behaviours"], indent=2)
|
|
392
|
+
+ ";",
|
|
393
|
+
"",
|
|
394
|
+
"/**",
|
|
395
|
+
" * Where what a tool is asked changes what an application does about it: by",
|
|
396
|
+
" * application and tool, each condition and the behaviour when it holds.",
|
|
397
|
+
" */",
|
|
398
|
+
"export const APP_ESCALATIONS: Record<",
|
|
399
|
+
" string,",
|
|
400
|
+
" Record<string, AppEscalation[]>",
|
|
401
|
+
"> = " + json.dumps(actions["escalations"], indent=2) + ";",
|
|
402
|
+
"",
|
|
403
|
+
]
|
|
404
|
+
return "\n".join(lines)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _drop_nulls(value: Any) -> Any:
|
|
408
|
+
"""A value without the keys that hold nothing."""
|
|
409
|
+
if isinstance(value, dict):
|
|
410
|
+
return {
|
|
411
|
+
key: _drop_nulls(item) for key, item in value.items() if item is not None
|
|
412
|
+
}
|
|
413
|
+
if isinstance(value, list):
|
|
414
|
+
return [_drop_nulls(item) for item in value]
|
|
415
|
+
return value
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def main() -> None:
|
|
419
|
+
"""Generate the application catalogue and the action classes."""
|
|
420
|
+
parser = argparse.ArgumentParser(
|
|
421
|
+
description="Generate Python and TypeScript code from YAML application specifications"
|
|
422
|
+
)
|
|
423
|
+
parser.add_argument("--specs-dir", type=Path, required=True)
|
|
424
|
+
parser.add_argument("--python-output", type=Path, required=True)
|
|
425
|
+
parser.add_argument("--typescript-output", type=Path, required=True)
|
|
426
|
+
parser.add_argument("--actions-python-output", type=Path, required=True)
|
|
427
|
+
parser.add_argument("--actions-typescript-output", type=Path, required=True)
|
|
428
|
+
args = parser.parse_args()
|
|
429
|
+
|
|
430
|
+
if not args.specs_dir.exists():
|
|
431
|
+
print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
|
|
432
|
+
sys.exit(1)
|
|
433
|
+
specs, actions = load_specs(args.specs_dir)
|
|
434
|
+
outputs = [
|
|
435
|
+
(args.python_output, generate_python_code(specs)),
|
|
436
|
+
(
|
|
437
|
+
args.typescript_output,
|
|
438
|
+
generate_typescript_code(specs, actions["sources"]),
|
|
439
|
+
),
|
|
440
|
+
(args.actions_python_output, generate_actions_python_code(actions)),
|
|
441
|
+
(args.actions_typescript_output, generate_actions_typescript_code(actions)),
|
|
442
|
+
]
|
|
443
|
+
for path, text in outputs:
|
|
444
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
445
|
+
path.write_text(text)
|
|
446
|
+
print(f"Generated: {path}")
|
|
447
|
+
print(
|
|
448
|
+
f"\n✓ Successfully generated code from {len(specs)} application specs, "
|
|
449
|
+
f"{len(actions['tools'])} tools and {len(actions['servers'])} MCP servers"
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
if __name__ == "__main__":
|
|
454
|
+
main()
|