@datalayer/agent-runtimes 1.3.114 → 1.4.0

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.
Files changed (73) hide show
  1. package/dist-embed/chunks/{ChatView-D41Nx5rC.js → ChatView-BpKZPdcp.js} +4 -4
  2. package/dist-embed/datalayer-app-main.js +150 -150
  3. package/lib/apps/apps/appspec.d.ts +13 -0
  4. package/lib/apps/apps/appspec.js +71 -0
  5. package/lib/apps/apps/checks.d.ts +1 -1
  6. package/lib/apps/apps/checks.js +2 -2
  7. package/lib/apps/apps/customComponents.js +1 -1
  8. package/lib/apps/core/canvasBlocks.d.ts +3 -3
  9. package/lib/apps/core/canvasParts.d.ts +6 -4
  10. package/lib/apps/embed/AppEmbed.js +3 -7
  11. package/lib/apps/embed/embedSession.d.ts +8 -4
  12. package/lib/apps/embed/embedSession.js +16 -9
  13. package/lib/apps/plugins/a2ui-surface/AnswerShows.d.ts +21 -0
  14. package/lib/apps/plugins/a2ui-surface/AnswerShows.js +29 -0
  15. package/lib/apps/plugins/a2ui-surface/SurfaceToolResult.js +4 -7
  16. package/lib/apps/plugins/a2ui-surface/index.d.ts +2 -1
  17. package/lib/apps/plugins/a2ui-surface/index.js +10 -1
  18. package/lib/apps/plugins/a2ui-surface/toolResult.d.ts +20 -11
  19. package/lib/apps/plugins/a2ui-surface/toolResult.js +27 -11
  20. package/lib/apps/plugins/app-components/index.d.ts +1 -1
  21. package/lib/apps/plugins/app-components/index.js +2 -2
  22. package/lib/apps/plugins/app-elements/AppElements.d.ts +3 -3
  23. package/lib/apps/plugins/app-elements/AppElements.js +5 -20
  24. package/lib/apps/plugins/app-elements/QuestionView.d.ts +28 -0
  25. package/lib/apps/plugins/app-elements/QuestionView.js +57 -0
  26. package/lib/apps/plugins/app-elements/index.d.ts +7 -4
  27. package/lib/apps/plugins/app-elements/index.js +14 -5
  28. package/lib/apps/plugins/canvas-blocks/index.d.ts +13 -13
  29. package/lib/apps/plugins/canvas-blocks/index.js +24 -24
  30. package/lib/apps/plugins/canvas-parts/index.d.ts +7 -1
  31. package/lib/apps/plugins/canvas-parts/index.js +36 -0
  32. package/lib/apps/plugins/chat/ChatView.js +7 -0
  33. package/lib/chat/base/ChatBase.js +234 -43
  34. package/lib/chat/base/loopElement.d.ts +16 -17
  35. package/lib/chat/base/loopElement.js +11 -27
  36. package/lib/chat/base/loopStep.d.ts +15 -15
  37. package/lib/chat/base/loopStep.js +10 -13
  38. package/lib/chat/base/loopWindow.d.ts +15 -20
  39. package/lib/chat/base/loopWindow.js +26 -27
  40. package/lib/chat/base/uiOps.d.ts +96 -0
  41. package/lib/chat/base/uiOps.js +251 -0
  42. package/lib/chat/messages/ChatMessageList.d.ts +8 -1
  43. package/lib/chat/messages/ChatMessageList.js +4 -4
  44. package/lib/components/a2ui/datalayer/Chart.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/schema.js +1 -1
  46. package/lib/components/teams/AnswerSurfaces.d.ts +3 -2
  47. package/lib/components/teams/AnswerSurfaces.js +7 -7
  48. package/lib/components/teams/useA2ATeam.d.ts +1 -1
  49. package/lib/config/AgentConfiguration.d.ts +1 -1
  50. package/lib/protocols/AGUIAdapter.d.ts +7 -0
  51. package/lib/protocols/AGUIAdapter.js +43 -3
  52. package/lib/specs/agents/agents.js +338 -169
  53. package/lib/specs/apps.js +47 -0
  54. package/lib/specs/appspecSchema.js +16 -0
  55. package/lib/specs/cogs.js +6 -3
  56. package/lib/specs/index.d.ts +2 -1
  57. package/lib/specs/index.js +2 -1
  58. package/lib/specs/protocols.d.ts +24 -0
  59. package/lib/specs/protocols.js +197 -0
  60. package/lib/specs/ui.d.ts +21 -0
  61. package/lib/specs/{uiPlugins.js → ui.js} +14 -14
  62. package/lib/types/agentspecs.d.ts +51 -5
  63. package/lib/types/messages.d.ts +7 -0
  64. package/lib/types/protocol.d.ts +8 -0
  65. package/package.json +2 -2
  66. package/scripts/codegen/generate_agents.py +43 -8
  67. package/scripts/codegen/generate_protocols.py +207 -0
  68. package/scripts/codegen/generate_sandbox_agents.py +1 -1
  69. package/scripts/codegen/{generate_ui_plugins.py → generate_ui.py} +40 -40
  70. package/scripts/gen_workers.py +2 -2
  71. package/lib/chat/base/loopMessage.d.ts +0 -37
  72. package/lib/chat/base/loopMessage.js +0 -37
  73. package/lib/specs/uiPlugins.d.ts +0 -21
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * @module types/messages
6
6
  */
7
+ import type { Display } from '@datalayer/reactor/ui';
7
8
  /**
8
9
  * Message role enumeration
9
10
  */
@@ -121,6 +122,12 @@ export interface ChatMessage {
121
122
  note?: 'waiting' | 'failed' | 'stopped';
122
123
  /** Optional metadata */
123
124
  metadata?: Record<string, unknown>;
125
+ /**
126
+ * What the message shows besides its words: the displays sent under it
127
+ * (`reactor.ui`, REACTOR_UI RU-2), in the order they came; each drawn by
128
+ * the renderer a plugin contributed for its type.
129
+ */
130
+ displays?: Display[];
124
131
  /** Tool calls for assistant messages */
125
132
  toolCalls?: ToolCallContentPart[];
126
133
  /** For activity messages (A2A, A2UI, etc.) */
@@ -44,6 +44,14 @@ export interface ProtocolConfig {
44
44
  enableConfigQuery?: boolean;
45
45
  /** Config endpoint URL for non-Jupyter protocols (if not set, uses Jupyter requestAPI) */
46
46
  configEndpoint?: string;
47
+ /**
48
+ * The ui channel's WebSocket (`ws://` or `wss://`, REACTOR_UI RU-W): an
49
+ * application's session says on it what its messages show, its steps, its
50
+ * elements, its authors and its window messages, and takes the presses of
51
+ * what they offered. Set for an application's agent; one connection per
52
+ * conversation, its thread the scope.
53
+ */
54
+ uiSocket?: string;
47
55
  /** Additional protocol options */
48
56
  options?: Record<string, unknown>;
49
57
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.3.114",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -161,7 +161,7 @@
161
161
  "@datalayer/primer-addons": "^1.0.39",
162
162
  "@datalayer/primer-rjsf": "^1.0.3",
163
163
  "@datalayer/personal-agent-protocol": "^0.6.0",
164
- "@datalayer/reactor": "^1.0.4",
164
+ "@datalayer/reactor": "^1.2.0",
165
165
  "@datalayer/reactor-commands": "^1.0.4",
166
166
  "@datalayer/reactor-manager": "^1.0.4",
167
167
  "@datalayer/reactor-shell": "^1.0.4",
@@ -235,7 +235,10 @@ def load_fragments(specs_dir: Path) -> List[Dict[str, Any]]:
235
235
 
236
236
 
237
237
  def refuse_tools_field(spec: Dict[str, Any], path: Path) -> None:
238
- """Refuse an agent spec that still says `tools`: it is `backend_tools` now."""
238
+ """Refuse an agent spec that still says `tools` (it is `backend_tools` now) or
239
+ `ui_plugin` (it is `ui` now, AGENT_PROTOCOLS PS-03)."""
240
+ if "ui_plugin" in spec:
241
+ raise SystemExit(f"Error: {path} says `ui_plugin`: the field is `ui`, one of agentspecs/ui")
239
242
  if "tools" in spec:
240
243
  raise SystemExit(
241
244
  f"Error: {path} says `tools`: the field is `backend_tools` — the tools "
@@ -288,9 +291,37 @@ def load_yaml_specs(specs_dir: Path) -> List[tuple[str, Dict[str, Any]]]:
288
291
  resolved.append((subfolder, spec))
289
292
 
290
293
  check_model_additionals(resolved, load_model_ids(specs_dir))
294
+ check_protocols(resolved, load_protocol_ids(specs_dir))
291
295
  return resolved
292
296
 
293
297
 
298
+ def load_protocol_ids(specs_dir: Path) -> set[str]:
299
+ """Every id of the protocols catalogue (`agentspecs/protocols`, AGENT_PROTOCOLS PS-01)."""
300
+ ids: set[str] = set()
301
+ for yaml_file in sorted((specs_dir.parent / "protocols").glob("*.yaml")):
302
+ protocol = yaml.safe_load(yaml_file.read_text()) or {}
303
+ if protocol.get("id"):
304
+ ids.add(str(protocol["id"]))
305
+ return ids
306
+
307
+
308
+ def check_protocols(specs: List[tuple[str, Dict[str, Any]]], protocol_ids: set[str]) -> None:
309
+ """Refuse an agent whose `protocols` is not a list of the catalogue's ids, each once (PS-02)."""
310
+ problems = []
311
+ for _, spec in specs:
312
+ said = spec.get("protocols")
313
+ if said is None:
314
+ continue
315
+ if not isinstance(said, list) or len(set(map(str, said))) != len(said):
316
+ problems.append(f"{spec.get('id')}: protocols is a list of protocol ids, each once")
317
+ continue
318
+ unknown = [str(p) for p in said if str(p) not in protocol_ids]
319
+ if unknown:
320
+ problems.append(f"{spec.get('id')}: protocols names what the catalogue does not know: {', '.join(unknown)}")
321
+ if problems:
322
+ raise SystemExit("\n".join(problems))
323
+
324
+
294
325
  def load_model_ids(specs_dir: Path) -> set[str]:
295
326
  """Every id the models catalogue answers to: its ids and their aliases."""
296
327
  ids: set[str] = set()
@@ -508,8 +539,8 @@ from agent_runtimes.types import (
508
539
  protocol_val = spec.get("protocol")
509
540
  protocol_str = f'"{protocol_val}"' if protocol_val else "None"
510
541
  delegable_str = _fmt_py_delegable(spec.get("delegable", []))
511
- ui_plugin = spec.get("ui_plugin")
512
- ui_plugin_str = f'"{ui_plugin}"' if ui_plugin else "None"
542
+ ui_id = spec.get("ui")
543
+ ui_str = f'"{ui_id}"' if ui_id else "None"
513
544
  trigger_val = spec.get("trigger")
514
545
  model_cfg = spec.get("model_config")
515
546
  mcp_srv_tools = spec.get("mcp_server_tools")
@@ -620,7 +651,8 @@ from agent_runtimes.types import (
620
651
  goal={goal_str},
621
652
  delegable={delegable_str},
622
653
  protocol={protocol_str},
623
- ui_plugin={ui_plugin_str},
654
+ ui={ui_str},
655
+ protocols={_fmt_list([str(p) for p in spec.get("protocols") or []])},
624
656
  trigger={_fmt_py_literal(trigger_val)},
625
657
  model_configuration={_fmt_py_literal(model_cfg)},
626
658
  mcp_server_tools={_fmt_py_literal(mcp_srv_tools)},
@@ -1135,8 +1167,8 @@ const FRONTEND_TOOL_MAP: Record<string, any> = {
1135
1167
  protocol_val = spec.get("protocol")
1136
1168
  protocol_ts = f"'{protocol_val}'" if protocol_val else "undefined"
1137
1169
  delegable_ts = _fmt_ts_delegable(spec.get("delegable", []))
1138
- ui_plugin = spec.get("ui_plugin")
1139
- ui_plugin_ts = f"'{ui_plugin}'" if ui_plugin else "undefined"
1170
+ ui_id = spec.get("ui")
1171
+ ui_ts = f"'{ui_id}'" if ui_id else "undefined"
1140
1172
  trigger_val = spec.get("trigger")
1141
1173
  model_cfg = spec.get("model_config")
1142
1174
  mcp_srv_tools = spec.get("mcp_server_tools")
@@ -1195,7 +1227,8 @@ const FRONTEND_TOOL_MAP: Record<string, any> = {
1195
1227
  goal: {goal_ts},
1196
1228
  delegable: {delegable_ts},
1197
1229
  protocol: {protocol_ts},
1198
- uiPlugin: {ui_plugin_ts},
1230
+ ui: {ui_ts},
1231
+ protocols: {_fmt_ts_literal([str(p) for p in spec.get("protocols") or []])},
1199
1232
  trigger: {_fmt_ts_literal(trigger_val)},
1200
1233
  modelConfig: {_fmt_ts_literal(model_cfg)},
1201
1234
  mcpServerTools: {_fmt_ts_literal(mcp_srv_tools)},
@@ -1635,11 +1668,13 @@ export * from './memory';
1635
1668
  export * from './models';
1636
1669
  export * from './modelProviders';
1637
1670
  export * from './notifications';
1671
+ export * from './voices';
1638
1672
  export * from './outputs';
1639
1673
  export * from './skills';
1640
1674
  export * from './backendTools';
1641
1675
  export * from './triggers';
1642
- export * from './uiPlugins';
1676
+ export * from './ui';
1677
+ export * from './protocols';
1643
1678
  export * from './frames';
1644
1679
  export * from './cogs';
1645
1680
  export * from './guards';
@@ -0,0 +1,207 @@
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 protocol specifications.
7
+
8
+ One YAML per agent protocol under ``agentspecs/protocols`` (AGENT_PROTOCOLS
9
+ PS-01); an agent spec's or an application's ``protocols`` names the ones it
10
+ speaks (PS-02).
11
+
12
+ Usage:
13
+ python generate_protocols.py \\
14
+ --specs-dir agentspecs/agentspecs/protocols \\
15
+ --python-output agent_runtimes/specs/protocols.py \\
16
+ --typescript-output src/specs/protocols.ts
17
+ """
18
+
19
+ import argparse
20
+ import json
21
+ from pathlib import Path
22
+ from typing import Any
23
+
24
+ import yaml
25
+ from versioning import ensure_spec_version, version_suffix
26
+
27
+ LAYERS = ("transport", "consent", "identity", "commerce", "ui")
28
+ STATUSES = ("shipped", "planned")
29
+ BLOCKS = ("pap", "pact", "a2a")
30
+
31
+
32
+ def load_protocol_specs(specs_dir: Path) -> list[dict[str, Any]]:
33
+ """Every protocol spec of a directory, each named for its id, its layer, status and block known."""
34
+ specs = []
35
+ for yaml_file in sorted(specs_dir.glob("*.yaml")):
36
+ spec = yaml.safe_load(yaml_file.read_text()) or {}
37
+ ensure_spec_version(spec)
38
+ if spec["id"] != yaml_file.stem:
39
+ raise ValueError(
40
+ f"{yaml_file.name}: the file is named for {yaml_file.stem!r}, the spec says {spec['id']!r}"
41
+ )
42
+ if spec["layer"] not in LAYERS or spec["status"] not in STATUSES:
43
+ raise ValueError(
44
+ f"{yaml_file.name}: layer one of {LAYERS}, status one of {STATUSES}"
45
+ )
46
+ if spec.get("appspec_block") not in (None, *BLOCKS):
47
+ raise ValueError(f"{yaml_file.name}: appspec_block one of {BLOCKS}")
48
+ specs.append(spec)
49
+ return specs
50
+
51
+
52
+ def _const(spec: dict[str, Any]) -> str:
53
+ return (
54
+ spec["id"].replace("-", "_").upper()
55
+ + "_PROTOCOL"
56
+ + version_suffix(spec["version"])
57
+ )
58
+
59
+
60
+ def _s(value: Any) -> str:
61
+ return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
62
+
63
+
64
+ def generate_python_code(specs: list[dict[str, Any]]) -> str:
65
+ lines = [
66
+ "# Copyright (c) 2025-2026 Datalayer, Inc.",
67
+ "# Distributed under the terms of the Modified BSD License.",
68
+ '"""',
69
+ "Protocol Catalog.",
70
+ "",
71
+ "The agent protocols Datalayer targets (AGENT_PROTOCOLS PS-01).",
72
+ "",
73
+ "This file is AUTO-GENERATED from YAML specifications.",
74
+ "DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
75
+ '"""',
76
+ "",
77
+ "from typing import Dict, List, Optional",
78
+ "",
79
+ "from agent_runtimes.types import ProtocolPackagesSpec, ProtocolSpec",
80
+ ]
81
+ for spec in specs:
82
+ packages = spec.get("packages") or {}
83
+ block = spec.get("appspec_block")
84
+ lines += [
85
+ "",
86
+ f"{_const(spec)} = ProtocolSpec(",
87
+ f" id={_s(spec['id'])},",
88
+ f" version={_s(spec['version'])},",
89
+ f" name={_s(spec['name'])},",
90
+ f" description={_s(spec.get('description'))},",
91
+ f" publisher={_s(spec.get('publisher'))},",
92
+ f" layer={_s(spec['layer'])},",
93
+ f" spec_url={_s(spec.get('spec_url'))},",
94
+ f" spec_version={_s(spec.get('spec_version'))},",
95
+ f" status={_s(spec['status'])},",
96
+ f" connects={_s(spec.get('connects'))},",
97
+ " packages=ProtocolPackagesSpec(",
98
+ f" python={json.dumps(list(packages.get('python') or []))},",
99
+ f" typescript={json.dumps(list(packages.get('typescript') or []))},",
100
+ " ),",
101
+ f" protocol_tools={json.dumps(list(spec.get('protocol_tools') or []))},",
102
+ f" appspec_block={_s(block) if block else 'None'},",
103
+ ")",
104
+ ]
105
+ lines += [
106
+ "",
107
+ "",
108
+ "PROTOCOL_CATALOGUE: Dict[str, ProtocolSpec] = {",
109
+ *[f" {_s(spec['id'])}: {_const(spec)}," for spec in specs],
110
+ "}",
111
+ "",
112
+ "",
113
+ "def get_protocol(protocol_id: str) -> Optional[ProtocolSpec]:",
114
+ ' """A protocol by id, or None."""',
115
+ " return PROTOCOL_CATALOGUE.get(protocol_id)",
116
+ "",
117
+ "",
118
+ "def list_protocols() -> List[ProtocolSpec]:",
119
+ ' """Every protocol of the catalogue."""',
120
+ " return list(PROTOCOL_CATALOGUE.values())",
121
+ "",
122
+ ]
123
+ return "\n".join(lines)
124
+
125
+
126
+ def _t(value: Any) -> str:
127
+ return json.dumps(" ".join(str(value or "").split()), ensure_ascii=False)
128
+
129
+
130
+ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
131
+ lines = [
132
+ "/*",
133
+ " * Copyright (c) 2025-2026 Datalayer, Inc.",
134
+ " * Distributed under the terms of the Modified BSD License.",
135
+ " */",
136
+ "",
137
+ "/**",
138
+ " * Protocol Catalog.",
139
+ " *",
140
+ " * The agent protocols Datalayer targets (AGENT_PROTOCOLS PS-01).",
141
+ " *",
142
+ " * This file is AUTO-GENERATED from YAML specifications.",
143
+ " * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
144
+ " */",
145
+ "",
146
+ "import type { ProtocolSpec } from '../types/agentspecs';",
147
+ ]
148
+ for spec in specs:
149
+ packages = spec.get("packages") or {}
150
+ block = spec.get("appspec_block")
151
+ lines += [
152
+ "",
153
+ f"export const {_const(spec)}: ProtocolSpec = {{",
154
+ f" id: {_t(spec['id'])},",
155
+ f" version: {_t(spec['version'])},",
156
+ f" name: {_t(spec['name'])},",
157
+ f" description: {_t(spec.get('description'))},",
158
+ f" publisher: {_t(spec.get('publisher'))},",
159
+ f" layer: {_t(spec['layer'])},",
160
+ f" specUrl: {_t(spec.get('spec_url'))},",
161
+ f" specVersion: {_t(spec.get('spec_version'))},",
162
+ f" status: {_t(spec['status'])},",
163
+ f" connects: {_t(spec.get('connects'))},",
164
+ " packages: {",
165
+ f" python: {json.dumps(list(packages.get('python') or []))},",
166
+ f" typescript: {json.dumps(list(packages.get('typescript') or []))},",
167
+ " },",
168
+ f" protocolTools: {json.dumps(list(spec.get('protocol_tools') or []))},",
169
+ *([f" appspecBlock: {_t(block)},"] if block else []),
170
+ "};",
171
+ ]
172
+ lines += [
173
+ "",
174
+ "export const PROTOCOL_CATALOGUE: Record<string, ProtocolSpec> = {",
175
+ *[f" {_t(spec['id'])}: {_const(spec)}," for spec in specs],
176
+ "};",
177
+ "",
178
+ "/** A protocol by id. */",
179
+ "export function getProtocol(protocolId: string): ProtocolSpec | undefined {",
180
+ " return PROTOCOL_CATALOGUE[protocolId];",
181
+ "}",
182
+ "",
183
+ "/** Every protocol of the catalogue. */",
184
+ "export function listProtocolSpecs(): ProtocolSpec[] {",
185
+ " return Object.values(PROTOCOL_CATALOGUE);",
186
+ "}",
187
+ "",
188
+ ]
189
+ return "\n".join(lines)
190
+
191
+
192
+ def main() -> None:
193
+ parser = argparse.ArgumentParser(description=__doc__)
194
+ parser.add_argument("--specs-dir", type=Path, required=True)
195
+ parser.add_argument("--python-output", type=Path, required=True)
196
+ parser.add_argument("--typescript-output", type=Path, required=True)
197
+ args = parser.parse_args()
198
+ specs = load_protocol_specs(args.specs_dir)
199
+ args.python_output.write_text(generate_python_code(specs))
200
+ args.typescript_output.write_text(generate_typescript_code(specs))
201
+ print(
202
+ f"Generated {len(specs)} protocols: {args.python_output}, {args.typescript_output}"
203
+ )
204
+
205
+
206
+ if __name__ == "__main__":
207
+ main()
@@ -84,7 +84,7 @@ mcp_servers: []
84
84
 
85
85
  skills: []
86
86
 
87
- tools: []
87
+ backend_tools: []
88
88
 
89
89
  frontend_tools: []
90
90
 
@@ -3,18 +3,18 @@
3
3
  # Distributed under the terms of the Modified BSD License.
4
4
 
5
5
  """
6
- Generate Python and TypeScript code from YAML UI-plugin specifications.
6
+ Generate Python and TypeScript code from YAML UI specifications.
7
7
 
8
- A UI plugin is how an agent's answer becomes an interface rather than text —
8
+ A UI spec is how an agent's answer becomes an interface rather than text —
9
9
  a protocol the host knows how to render (A2UI, MCP Apps, MCP UI). One YAML per
10
- plugin under ``agentspecs/ui-plugins``; an agent spec's ``ui_plugin`` names
11
- one. A plugin hosts the visual components it renders (LOOP C-13).
10
+ plugin under ``agentspecs/ui``; an agent spec's ``ui`` names
11
+ one. A UI spec hosts the visual components it renders (LOOP C-13).
12
12
 
13
13
  Usage:
14
- python generate_ui_plugins.py \\
15
- --specs-dir agentspecs/agentspecs/ui-plugins \\
16
- --python-output agent_runtimes/specs/ui_plugins.py \\
17
- --typescript-output src/specs/uiPlugins.ts
14
+ python generate_ui.py \\
15
+ --specs-dir agentspecs/agentspecs/ui \\
16
+ --python-output agent_runtimes/specs/ui.py \\
17
+ --typescript-output src/specs/ui.ts
18
18
  """
19
19
 
20
20
  import argparse
@@ -29,7 +29,7 @@ from versioning import ensure_spec_version, version_suffix
29
29
 
30
30
 
31
31
  def load_plugin_specs(specs_dir: Path) -> list[dict[str, Any]]:
32
- """Load all UI-plugin YAML specifications from a directory."""
32
+ """Load all UI YAML specifications from a directory."""
33
33
  specs = []
34
34
  for yaml_file in sorted(specs_dir.glob("*.yaml")):
35
35
  with open(yaml_file) as f:
@@ -44,7 +44,7 @@ def load_plugin_specs(specs_dir: Path) -> list[dict[str, Any]]:
44
44
 
45
45
 
46
46
  def _const_name(plugin_id: str) -> str:
47
- return plugin_id.replace("-", "_").replace(".", "_").upper() + "_UI_PLUGIN"
47
+ return plugin_id.replace("-", "_").replace(".", "_").upper() + "_UI"
48
48
 
49
49
 
50
50
  def _py(value: str) -> str:
@@ -196,12 +196,12 @@ def _py_calls(specs: list[dict[str, Any]]) -> list[str]:
196
196
 
197
197
 
198
198
  def generate_python_code(specs: list[dict[str, Any]]) -> str:
199
- """Generate Python code from UI-plugin specifications."""
199
+ """Generate Python code from UI specifications."""
200
200
  lines = [
201
201
  "# Copyright (c) 2025-2026 Datalayer, Inc.",
202
202
  "# Distributed under the terms of the Modified BSD License.",
203
203
  '"""',
204
- "UI Plugin Catalog.",
204
+ "UI Catalog.",
205
205
  "",
206
206
  "How an agent's answer becomes an interface: the protocols a host renders.",
207
207
  "",
@@ -211,10 +211,10 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
211
211
  "",
212
212
  "from typing import Any, Callable, Dict, List, Literal, Mapping, Optional, Union",
213
213
  "",
214
- "from agent_runtimes.types import ComponentBindingsSpec, ComponentSpec, UIPluginSpec",
214
+ "from agent_runtimes.types import ComponentBindingsSpec, ComponentSpec, UISpec",
215
215
  "",
216
216
  "# " + "=" * 76,
217
- "# UI Plugin Definitions",
217
+ "# UI Definitions",
218
218
  "# " + "=" * 76,
219
219
  "",
220
220
  ]
@@ -222,7 +222,7 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
222
222
  const = _const_name(spec["id"]) + version_suffix(spec["version"])
223
223
  lines.extend(
224
224
  [
225
- f"{const} = UIPluginSpec(",
225
+ f"{const} = UISpec(",
226
226
  f" id={_py(spec['id'])},",
227
227
  f" version={_py(str(spec['version']))},",
228
228
  f" name={_py(spec['name'])},",
@@ -241,10 +241,10 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
241
241
  [
242
242
  "",
243
243
  "# " + "=" * 76,
244
- "# UI Plugin Catalog",
244
+ "# UI Catalog",
245
245
  "# " + "=" * 76,
246
246
  "",
247
- "UI_PLUGIN_CATALOGUE: Dict[str, UIPluginSpec] = {",
247
+ "UI_CATALOGUE: Dict[str, UISpec] = {",
248
248
  ]
249
249
  )
250
250
  for spec in specs:
@@ -256,20 +256,20 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
256
256
  "}",
257
257
  "",
258
258
  "",
259
- "def get_ui_plugin(plugin_id: str) -> UIPluginSpec | None:",
260
- ' """The plugin an agent spec\'s `ui_plugin` names, or None."""',
261
- " return UI_PLUGIN_CATALOGUE.get(plugin_id)",
259
+ "def get_ui(plugin_id: str) -> UISpec | None:",
260
+ ' """The UI an agent spec\'s `ui` names, or None."""',
261
+ " return UI_CATALOGUE.get(plugin_id)",
262
262
  "",
263
263
  "",
264
- "def list_ui_plugins() -> list[UIPluginSpec]:",
265
- " return list(UI_PLUGIN_CATALOGUE.values())",
264
+ "def list_ui() -> list[UISpec]:",
265
+ " return list(UI_CATALOGUE.values())",
266
266
  "",
267
267
  "",
268
- "#: The visual components the enabled UI plugins render, by the name a",
268
+ "#: The visual components the enabled UI specs render, by the name a",
269
269
  "#: surface gives them (LOOP C-13).",
270
270
  "COMPONENT_CATALOGUE: Dict[str, ComponentSpec] = {",
271
271
  " component.id: component",
272
- " for plugin in UI_PLUGIN_CATALOGUE.values()",
272
+ " for plugin in UI_CATALOGUE.values()",
273
273
  " if plugin.enabled",
274
274
  " for component in plugin.components",
275
275
  "}",
@@ -290,7 +290,7 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
290
290
 
291
291
 
292
292
  def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
293
- """Generate TypeScript code from UI-plugin specifications."""
293
+ """Generate TypeScript code from UI specifications."""
294
294
  lines = [
295
295
  "/*",
296
296
  " * Copyright (c) 2025-2026 Datalayer, Inc.",
@@ -298,7 +298,7 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
298
298
  " */",
299
299
  "",
300
300
  "/**",
301
- " * UI Plugin Catalog.",
301
+ " * UI Catalog.",
302
302
  " *",
303
303
  " * How an agent's answer becomes an interface: the protocols a host renders.",
304
304
  " *",
@@ -306,14 +306,14 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
306
306
  " * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
307
307
  " */",
308
308
  "",
309
- "import type { ComponentSpec, UIPluginSpec } from '../types/agentspecs';",
309
+ "import type { ComponentSpec, UISpec } from '../types/agentspecs';",
310
310
  "",
311
311
  ]
312
312
  for spec in specs:
313
313
  const = _const_name(spec["id"]) + version_suffix(spec["version"])
314
314
  lines.extend(
315
315
  [
316
- f"export const {const}: UIPluginSpec = {{",
316
+ f"export const {const}: UISpec = {{",
317
317
  f" id: {_ts(spec['id'])},",
318
318
  f" version: {_ts(str(spec['version']))},",
319
319
  f" name: {_ts(spec['name'])},",
@@ -326,7 +326,7 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
326
326
  "",
327
327
  ]
328
328
  )
329
- lines.append("export const UI_PLUGIN_CATALOGUE: Record<string, UIPluginSpec> = {")
329
+ lines.append("export const UI_CATALOGUE: Record<string, UISpec> = {")
330
330
  for spec in specs:
331
331
  lines.append(
332
332
  f" {_ts(spec['id'])}: {_const_name(spec['id']) + version_suffix(spec['version'])},"
@@ -335,18 +335,18 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
335
335
  [
336
336
  "};",
337
337
  "",
338
- "/** The plugin an agent spec's `uiPlugin` names, or undefined. */",
339
- "export function getUIPlugin(pluginId: string): UIPluginSpec | undefined {",
340
- " return UI_PLUGIN_CATALOGUE[pluginId];",
338
+ "/** The UI an agent spec's `ui` names, or undefined. */",
339
+ "export function getUI(pluginId: string): UISpec | undefined {",
340
+ " return UI_CATALOGUE[pluginId];",
341
341
  "}",
342
342
  "",
343
- "export function listUIPlugins(): UIPluginSpec[] {",
344
- " return Object.values(UI_PLUGIN_CATALOGUE);",
343
+ "export function listUI(): UISpec[] {",
344
+ " return Object.values(UI_CATALOGUE);",
345
345
  "}",
346
346
  "",
347
- "/** The visual components the enabled UI plugins render, by the name a surface gives them (LOOP C-13). */",
347
+ "/** The visual components the enabled UI specs render, by the name a surface gives them (LOOP C-13). */",
348
348
  "export const COMPONENT_CATALOGUE: Record<string, ComponentSpec> = Object.fromEntries(",
349
- " Object.values(UI_PLUGIN_CATALOGUE)",
349
+ " Object.values(UI_CATALOGUE)",
350
350
  " .filter(plugin => plugin.enabled)",
351
351
  " .flatMap(plugin => plugin.components.map(component => [component.id, component])),",
352
352
  ");",
@@ -367,13 +367,13 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
367
367
 
368
368
  def main():
369
369
  parser = argparse.ArgumentParser(
370
- description="Generate Python and TypeScript code from YAML UI-plugin specifications"
370
+ description="Generate Python and TypeScript code from YAML UI specifications"
371
371
  )
372
372
  parser.add_argument(
373
373
  "--specs-dir",
374
374
  type=Path,
375
375
  required=True,
376
- help="Directory containing UI-plugin YAML files",
376
+ help="Directory containing UI YAML files",
377
377
  )
378
378
  parser.add_argument(
379
379
  "--python-output", type=Path, required=True, help="Output path for Python file"
@@ -392,7 +392,7 @@ def main():
392
392
  specs = load_plugin_specs(args.specs_dir)
393
393
  if not specs:
394
394
  print(
395
- f"Warning: No UI-plugin specifications found in {args.specs_dir}",
395
+ f"Warning: No UI specifications found in {args.specs_dir}",
396
396
  file=sys.stderr,
397
397
  )
398
398
  return
@@ -402,7 +402,7 @@ def main():
402
402
  args.typescript_output.parent.mkdir(parents=True, exist_ok=True)
403
403
  args.typescript_output.write_text(generate_typescript_code(specs))
404
404
  print(f"Generated TypeScript code: {args.typescript_output}")
405
- print(f"\n✓ Successfully generated code from {len(specs)} UI-plugin specs")
405
+ print(f"\n✓ Successfully generated code from {len(specs)} UI specs")
406
406
 
407
407
 
408
408
  if __name__ == "__main__":
@@ -441,9 +441,9 @@ color: "{color}"
441
441
  goal: >-
442
442
  {goal}
443
443
 
444
- # Protocols & UI Plugins
444
+ # Protocols & UI
445
445
  protocol: vercel-ai
446
- ui_plugin: a2ui
446
+ ui: a2ui
447
447
 
448
448
  # Model configuration
449
449
  model_config:
@@ -1,37 +0,0 @@
1
- /**
2
- * A message of an application's code that changed after it was sent
3
- * (LOOP P-15).
4
- *
5
- * The session API says a message's author, or that it was taken away, with a
6
- * `CUSTOM` event named `loop.message` — `{id, author}` or `{id, removed: true}`
7
- * — which the AG-UI adapter hands on as an `activity`. A message whose text
8
- * changed needs nothing here: it is written again under its id, and the chat
9
- * draws it in place.
10
- */
11
- import type { MessageSpeaker } from '../../types/messages';
12
- /** The `CUSTOM` event's name. */
13
- export declare const LOOP_MESSAGE = "loop.message";
14
- /** What changed about a message already in the conversation. */
15
- export type LoopMessageChange = {
16
- id: string;
17
- removed: true;
18
- } | {
19
- id: string;
20
- author: string;
21
- };
22
- /** The change an activity says, or `null` when it says none. */
23
- export declare function loopMessageChange(activity: {
24
- type: string;
25
- data: unknown;
26
- } | undefined): LoopMessageChange | null;
27
- /** Who a message is by, as the chat draws a named voice. */
28
- export declare function speakerOf(author: string): MessageSpeaker;
29
- /**
30
- * The conversation with the change made: the message taken away, or given its
31
- * author. Items that are not that message — tool calls among them — are left
32
- * as they are.
33
- */
34
- export declare function withLoopMessage<T extends {
35
- id?: string;
36
- speaker?: MessageSpeaker;
37
- }>(items: T[], change: LoopMessageChange): T[];