@datalayer/agent-runtimes 0.0.9 → 0.0.11

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 (291) hide show
  1. package/README.md +2 -1
  2. package/lib/{examples/components → components}/AgentConfiguration.d.ts +54 -9
  3. package/lib/components/AgentConfiguration.js +585 -0
  4. package/lib/{examples/components → components}/FooterMetrics.d.ts +1 -2
  5. package/lib/{examples/components → components}/Header.d.ts +1 -6
  6. package/lib/{examples/components → components}/Header.js +5 -39
  7. package/lib/{examples/components → components}/HeaderControls.d.ts +1 -2
  8. package/lib/{examples/components → components}/HeaderControls.js +1 -1
  9. package/lib/{examples/components → components}/LexicalEditor.d.ts +2 -3
  10. package/lib/{examples/components → components}/LexicalEditor.js +2 -2
  11. package/lib/components/MainContent.d.ts +34 -0
  12. package/lib/{examples/components → components}/MainContent.js +18 -9
  13. package/lib/components/McpServerManager.d.ts +30 -0
  14. package/lib/components/McpServerManager.js +331 -0
  15. package/lib/{examples/components → components}/MockFileBrowser.d.ts +1 -2
  16. package/lib/{examples/components → components}/SessionTabs.d.ts +2 -3
  17. package/lib/{examples/components → components}/TimeTravel.d.ts +1 -2
  18. package/lib/components/chat/components/AgentDetails.d.ts +3 -1
  19. package/lib/components/chat/components/AgentDetails.js +472 -22
  20. package/lib/components/chat/components/Chat.d.ts +37 -3
  21. package/lib/components/chat/components/Chat.js +29 -10
  22. package/lib/components/chat/components/ChatFloating.d.ts +27 -2
  23. package/lib/components/chat/components/ChatFloating.js +17 -10
  24. package/lib/components/chat/components/ChatPopupStandalone.js +1 -1
  25. package/lib/components/chat/components/ChatSidebar.d.ts +1 -1
  26. package/lib/components/chat/components/ChatStandalone.d.ts +1 -1
  27. package/lib/components/chat/components/ChatStandalone.js +1 -1
  28. package/lib/components/chat/components/ContextDistribution.d.ts +70 -6
  29. package/lib/components/chat/components/ContextDistribution.js +13 -6
  30. package/lib/components/chat/components/ContextInspector.d.ts +81 -0
  31. package/lib/components/chat/components/ContextInspector.js +263 -0
  32. package/lib/components/chat/components/ContextPanel.d.ts +112 -0
  33. package/lib/components/chat/components/ContextPanel.js +368 -0
  34. package/lib/components/chat/components/base/ChatBase.d.ts +74 -19
  35. package/lib/components/chat/components/base/ChatBase.js +296 -37
  36. package/lib/components/chat/components/index.d.ts +3 -1
  37. package/lib/components/chat/components/index.js +2 -0
  38. package/lib/components/chat/extensions/ExtensionRegistry.d.ts +1 -1
  39. package/lib/components/chat/extensions/index.d.ts +1 -0
  40. package/lib/components/chat/index.d.ts +3 -3
  41. package/lib/components/chat/protocols/AGUIAdapter.js +24 -4
  42. package/lib/components/chat/protocols/VercelAIAdapter.js +35 -1
  43. package/lib/components/chat/store/chatStore.d.ts +2 -3
  44. package/lib/components/chat/store/conversationStore.d.ts +83 -0
  45. package/lib/components/chat/store/conversationStore.js +174 -0
  46. package/lib/components/chat/store/index.d.ts +2 -1
  47. package/lib/components/chat/store/index.js +1 -0
  48. package/lib/components/chat/types/inference.d.ts +17 -0
  49. package/lib/components/chat/types/protocol.d.ts +10 -0
  50. package/lib/components/index.d.ts +23 -0
  51. package/lib/components/index.js +11 -0
  52. package/lib/config/agents/code-ai/agents.d.ts +25 -0
  53. package/lib/config/agents/code-ai/agents.js +70 -0
  54. package/lib/config/agents/code-ai/index.d.ts +1 -0
  55. package/lib/config/agents/code-ai/index.js +5 -0
  56. package/lib/config/agents/codemode-paper/agents.d.ts +29 -0
  57. package/lib/config/agents/codemode-paper/agents.js +288 -0
  58. package/lib/config/agents/codemode-paper/index.d.ts +1 -0
  59. package/lib/config/agents/codemode-paper/index.js +5 -0
  60. package/lib/config/agents/datalayer-ai/agents.d.ts +29 -0
  61. package/lib/config/agents/datalayer-ai/agents.js +267 -0
  62. package/lib/config/agents/datalayer-ai/index.d.ts +1 -0
  63. package/lib/config/agents/datalayer-ai/index.js +5 -0
  64. package/lib/config/agents/index.d.ts +19 -0
  65. package/lib/config/agents/index.js +38 -0
  66. package/lib/config/envvars.d.ts +28 -0
  67. package/lib/config/envvars.js +115 -0
  68. package/lib/config/index.d.ts +5 -0
  69. package/lib/config/index.js +9 -0
  70. package/lib/config/mcpServers.d.ts +18 -0
  71. package/lib/config/mcpServers.js +153 -0
  72. package/lib/config/skills.d.ts +27 -0
  73. package/lib/config/skills.js +60 -0
  74. package/lib/{lib → config}/utils.d.ts +1 -1
  75. package/lib/{lib → config}/utils.js +2 -2
  76. package/lib/examples/AgentRuntimeLexical2Example.d.ts +1 -0
  77. package/lib/examples/AgentRuntimeLexical2Example.js +3 -2
  78. package/lib/examples/AgentRuntimeLexicalExample.d.ts +1 -0
  79. package/lib/examples/AgentRuntimeLexicalExample.js +5 -3
  80. package/lib/examples/AgentRuntimeLexicalSidebarExample.d.ts +1 -0
  81. package/lib/examples/AgentRuntimeLexicalSidebarExample.js +3 -3
  82. package/lib/examples/AgentRuntimeNotebookExample.js +1 -1
  83. package/lib/examples/AgentSpaceFormExample.d.ts +2 -2
  84. package/lib/examples/AgentSpaceFormExample.js +215 -35
  85. package/lib/examples/CopilotKitLexicalExample.d.ts +1 -0
  86. package/lib/examples/CopilotKitLexicalExample.js +3 -2
  87. package/lib/examples/index.d.ts +1 -0
  88. package/lib/examples/stores/notebooks/NotebookExample2.ipynb.json +43 -43
  89. package/lib/hooks/useAGUI.d.ts +1 -1
  90. package/lib/hooks/useAGUI.js +1 -1
  91. package/lib/identity/types.d.ts +1 -1
  92. package/lib/index.d.ts +2 -0
  93. package/lib/index.js +1 -0
  94. package/lib/runtime/index.d.ts +3 -0
  95. package/lib/runtime/runtimeStore.d.ts +3 -4
  96. package/lib/runtime/useAgentConnection.d.ts +2 -3
  97. package/lib/runtime/useAgentRuntime.d.ts +2 -3
  98. package/lib/stories/Cell.stories.js +1 -1
  99. package/lib/tools/adapters/agent-runtimes/notebookHooks.js +1 -0
  100. package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -0
  101. package/lib/types.d.ts +158 -0
  102. package/package.json +5 -6
  103. package/scripts/apply-patches.sh +1 -1
  104. package/scripts/codegen/generate_agents.py +863 -0
  105. package/scripts/codegen/generate_envvars.py +302 -0
  106. package/scripts/codegen/generate_mcp_servers.py +436 -0
  107. package/scripts/codegen/generate_skills.py +334 -0
  108. package/scripts/download-ai-elements.py +35 -20
  109. package/scripts/sync-jupyter.sh +6 -0
  110. package/lib/components/ui/accordion.d.ts +0 -7
  111. package/lib/components/ui/accordion.js +0 -22
  112. package/lib/components/ui/alert-dialog.d.ts +0 -14
  113. package/lib/components/ui/alert-dialog.js +0 -43
  114. package/lib/components/ui/alert.d.ts +0 -9
  115. package/lib/components/ui/alert.js +0 -24
  116. package/lib/components/ui/aspect-ratio.d.ts +0 -3
  117. package/lib/components/ui/aspect-ratio.js +0 -11
  118. package/lib/components/ui/avatar.d.ts +0 -6
  119. package/lib/components/ui/avatar.js +0 -18
  120. package/lib/components/ui/badge.d.ts +0 -9
  121. package/lib/components/ui/badge.js +0 -22
  122. package/lib/components/ui/breadcrumb.d.ts +0 -11
  123. package/lib/components/ui/breadcrumb.js +0 -27
  124. package/lib/components/ui/button-group.d.ts +0 -11
  125. package/lib/components/ui/button-group.js +0 -31
  126. package/lib/components/ui/button.d.ts +0 -13
  127. package/lib/components/ui/button.js +0 -39
  128. package/lib/components/ui/calendar.d.ts +0 -8
  129. package/lib/components/ui/calendar.js +0 -80
  130. package/lib/components/ui/card.d.ts +0 -9
  131. package/lib/components/ui/card.js +0 -24
  132. package/lib/components/ui/carousel.d.ts +0 -19
  133. package/lib/components/ui/carousel.js +0 -95
  134. package/lib/components/ui/chart.d.ts +0 -53
  135. package/lib/components/ui/chart.js +0 -136
  136. package/lib/components/ui/checkbox.d.ts +0 -4
  137. package/lib/components/ui/checkbox.js +0 -13
  138. package/lib/components/ui/collapsible.d.ts +0 -5
  139. package/lib/components/ui/collapsible.js +0 -17
  140. package/lib/components/ui/command.d.ts +0 -18
  141. package/lib/components/ui/command.js +0 -38
  142. package/lib/components/ui/context-menu.d.ts +0 -25
  143. package/lib/components/ui/context-menu.js +0 -55
  144. package/lib/components/ui/dialog.d.ts +0 -15
  145. package/lib/components/ui/dialog.js +0 -40
  146. package/lib/components/ui/drawer.d.ts +0 -13
  147. package/lib/components/ui/drawer.js +0 -39
  148. package/lib/components/ui/dropdown-menu.d.ts +0 -25
  149. package/lib/components/ui/dropdown-menu.js +0 -55
  150. package/lib/components/ui/empty.d.ts +0 -11
  151. package/lib/components/ui/empty.js +0 -37
  152. package/lib/components/ui/field.d.ts +0 -24
  153. package/lib/components/ui/field.js +0 -80
  154. package/lib/components/ui/form.d.ts +0 -24
  155. package/lib/components/ui/form.js +0 -63
  156. package/lib/components/ui/hover-card.d.ts +0 -6
  157. package/lib/components/ui/hover-card.js +0 -18
  158. package/lib/components/ui/input-group.d.ts +0 -19
  159. package/lib/components/ui/input-group.js +0 -69
  160. package/lib/components/ui/input-otp.d.ts +0 -11
  161. package/lib/components/ui/input-otp.js +0 -25
  162. package/lib/components/ui/input.d.ts +0 -3
  163. package/lib/components/ui/input.js +0 -6
  164. package/lib/components/ui/item.d.ts +0 -23
  165. package/lib/components/ui/item.js +0 -66
  166. package/lib/components/ui/kbd.d.ts +0 -3
  167. package/lib/components/ui/kbd.js +0 -13
  168. package/lib/components/ui/label.d.ts +0 -4
  169. package/lib/components/ui/label.js +0 -12
  170. package/lib/components/ui/menubar.d.ts +0 -26
  171. package/lib/components/ui/menubar.js +0 -58
  172. package/lib/components/ui/navigation-menu.d.ts +0 -14
  173. package/lib/components/ui/navigation-menu.js +0 -31
  174. package/lib/components/ui/pagination.d.ts +0 -13
  175. package/lib/components/ui/pagination.js +0 -29
  176. package/lib/components/ui/popover.d.ts +0 -7
  177. package/lib/components/ui/popover.js +0 -21
  178. package/lib/components/ui/progress.d.ts +0 -4
  179. package/lib/components/ui/progress.js +0 -12
  180. package/lib/components/ui/radio-group.d.ts +0 -5
  181. package/lib/components/ui/radio-group.js +0 -16
  182. package/lib/components/ui/resizable.d.ts +0 -8
  183. package/lib/components/ui/resizable.js +0 -19
  184. package/lib/components/ui/scroll-area.d.ts +0 -5
  185. package/lib/components/ui/scroll-area.js +0 -17
  186. package/lib/components/ui/select.d.ts +0 -15
  187. package/lib/components/ui/select.js +0 -42
  188. package/lib/components/ui/separator.d.ts +0 -4
  189. package/lib/components/ui/separator.js +0 -12
  190. package/lib/components/ui/sheet.d.ts +0 -13
  191. package/lib/components/ui/sheet.js +0 -44
  192. package/lib/components/ui/sidebar.d.ts +0 -69
  193. package/lib/components/ui/sidebar.js +0 -216
  194. package/lib/components/ui/skeleton.d.ts +0 -2
  195. package/lib/components/ui/skeleton.js +0 -10
  196. package/lib/components/ui/slider.d.ts +0 -4
  197. package/lib/components/ui/slider.js +0 -18
  198. package/lib/components/ui/sonner.d.ts +0 -3
  199. package/lib/components/ui/sonner.js +0 -25
  200. package/lib/components/ui/spinner.d.ts +0 -2
  201. package/lib/components/ui/spinner.js +0 -11
  202. package/lib/components/ui/switch.d.ts +0 -4
  203. package/lib/components/ui/switch.js +0 -12
  204. package/lib/components/ui/table.d.ts +0 -10
  205. package/lib/components/ui/table.js +0 -32
  206. package/lib/components/ui/tabs.d.ts +0 -7
  207. package/lib/components/ui/tabs.js +0 -21
  208. package/lib/components/ui/textarea.d.ts +0 -3
  209. package/lib/components/ui/textarea.js +0 -6
  210. package/lib/components/ui/toast.d.ts +0 -15
  211. package/lib/components/ui/toast.js +0 -38
  212. package/lib/components/ui/toaster.d.ts +0 -1
  213. package/lib/components/ui/toaster.js +0 -14
  214. package/lib/components/ui/toggle-group.d.ts +0 -9
  215. package/lib/components/ui/toggle-group.js +0 -26
  216. package/lib/components/ui/toggle.d.ts +0 -9
  217. package/lib/components/ui/toggle.js +0 -30
  218. package/lib/components/ui/tooltip.d.ts +0 -7
  219. package/lib/components/ui/tooltip.js +0 -21
  220. package/lib/components/vercel-ai-elements/artifact.d.ts +0 -23
  221. package/lib/components/vercel-ai-elements/artifact.js +0 -24
  222. package/lib/components/vercel-ai-elements/code-block.d.ts +0 -17
  223. package/lib/components/vercel-ai-elements/code-block.js +0 -94
  224. package/lib/components/vercel-ai-elements/conversation.d.ts +0 -15
  225. package/lib/components/vercel-ai-elements/conversation.js +0 -21
  226. package/lib/components/vercel-ai-elements/loader.d.ts +0 -5
  227. package/lib/components/vercel-ai-elements/loader.js +0 -8
  228. package/lib/components/vercel-ai-elements/message.d.ts +0 -46
  229. package/lib/components/vercel-ai-elements/message.js +0 -109
  230. package/lib/components/vercel-ai-elements/model-selector.d.ts +0 -35
  231. package/lib/components/vercel-ai-elements/model-selector.js +0 -22
  232. package/lib/components/vercel-ai-elements/prompt-input.d.ts +0 -195
  233. package/lib/components/vercel-ai-elements/prompt-input.js +0 -589
  234. package/lib/components/vercel-ai-elements/reasoning.d.ts +0 -26
  235. package/lib/components/vercel-ai-elements/reasoning.js +0 -80
  236. package/lib/components/vercel-ai-elements/shimmer.d.ts +0 -9
  237. package/lib/components/vercel-ai-elements/shimmer.js +0 -22
  238. package/lib/components/vercel-ai-elements/sources.d.ts +0 -12
  239. package/lib/components/vercel-ai-elements/sources.js +0 -13
  240. package/lib/components/vercel-ai-elements/suggestion.d.ts +0 -10
  241. package/lib/components/vercel-ai-elements/suggestion.js +0 -16
  242. package/lib/components/vercel-ai-elements/tool.d.ts +0 -23
  243. package/lib/components/vercel-ai-elements/tool.js +0 -52
  244. package/lib/examples/components/AgentConfiguration.js +0 -382
  245. package/lib/examples/components/MainContent.d.ts +0 -19
  246. package/lib/examples/components/index.d.ts +0 -10
  247. package/lib/examples/components/index.js +0 -13
  248. package/lib/examples/vercel-ai-elements/VercelAiElementsShowcase.d.ts +0 -12
  249. package/lib/examples/vercel-ai-elements/VercelAiElementsShowcase.js +0 -69
  250. package/lib/examples/vercel-ai-elements/components/ArtifactShowcase.d.ts +0 -1
  251. package/lib/examples/vercel-ai-elements/components/ArtifactShowcase.js +0 -85
  252. package/lib/examples/vercel-ai-elements/components/CodeBlockShowcase.d.ts +0 -1
  253. package/lib/examples/vercel-ai-elements/components/CodeBlockShowcase.js +0 -62
  254. package/lib/examples/vercel-ai-elements/components/ConversationShowcase.d.ts +0 -1
  255. package/lib/examples/vercel-ai-elements/components/ConversationShowcase.js +0 -51
  256. package/lib/examples/vercel-ai-elements/components/LoaderShowcase.d.ts +0 -1
  257. package/lib/examples/vercel-ai-elements/components/LoaderShowcase.js +0 -9
  258. package/lib/examples/vercel-ai-elements/components/MessageShowcase.d.ts +0 -1
  259. package/lib/examples/vercel-ai-elements/components/MessageShowcase.js +0 -56
  260. package/lib/examples/vercel-ai-elements/components/ModelSelectorShowcase.d.ts +0 -1
  261. package/lib/examples/vercel-ai-elements/components/ModelSelectorShowcase.js +0 -50
  262. package/lib/examples/vercel-ai-elements/components/PromptInputShowcase.d.ts +0 -1
  263. package/lib/examples/vercel-ai-elements/components/PromptInputShowcase.js +0 -16
  264. package/lib/examples/vercel-ai-elements/components/ReasoningShowcase.d.ts +0 -1
  265. package/lib/examples/vercel-ai-elements/components/ReasoningShowcase.js +0 -72
  266. package/lib/examples/vercel-ai-elements/components/ShimmerShowcase.d.ts +0 -1
  267. package/lib/examples/vercel-ai-elements/components/ShimmerShowcase.js +0 -9
  268. package/lib/examples/vercel-ai-elements/components/SourcesShowcase.d.ts +0 -1
  269. package/lib/examples/vercel-ai-elements/components/SourcesShowcase.js +0 -43
  270. package/lib/examples/vercel-ai-elements/components/SuggestionShowcase.d.ts +0 -1
  271. package/lib/examples/vercel-ai-elements/components/SuggestionShowcase.js +0 -31
  272. package/lib/examples/vercel-ai-elements/components/ToolShowcase.d.ts +0 -1
  273. package/lib/examples/vercel-ai-elements/components/ToolShowcase.js +0 -54
  274. package/lib/examples/vercel-ai-elements/index.d.ts +0 -13
  275. package/lib/examples/vercel-ai-elements/index.js +0 -17
  276. package/lib/examples/vercel-ai-elements/main.d.ts +0 -1
  277. package/lib/examples/vercel-ai-elements/main.js +0 -9
  278. package/lib/examples/vercel-ai-elements/showcase.css +0 -128
  279. package/lib/hooks/useToast.d.ts +0 -44
  280. package/lib/hooks/useToast.js +0 -128
  281. package/patches/@datalayer+jupyter-lexical+1.0.8.patch +0 -11628
  282. package/patches/@datalayer+jupyter-react+2.0.2.patch +0 -5338
  283. package/style/showcase-vercel-ai.css +0 -137
  284. /package/lib/{examples/components → components}/FooterMetrics.js +0 -0
  285. /package/lib/{examples/components → components}/MockFileBrowser.js +0 -0
  286. /package/lib/{examples/components → components}/SessionTabs.js +0 -0
  287. /package/lib/{examples/components → components}/TimeTravel.js +0 -0
  288. /package/lib/{models → types}/AIAgent.d.ts +0 -0
  289. /package/lib/{models → types}/AIAgent.js +0 -0
  290. /package/lib/{models → types}/index.d.ts +0 -0
  291. /package/lib/{models → types}/index.js +0 -0
@@ -0,0 +1,863 @@
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
+ Agent Specification Code Generator.
7
+
8
+ Generates Python and TypeScript code from YAML agent specifications.
9
+ """
10
+
11
+ import argparse
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List
16
+
17
+ import yaml
18
+
19
+
20
+ def _fmt_list(items: list[str]) -> str:
21
+ """Format a list of strings with double quotes for ruff compliance."""
22
+ if not items:
23
+ return "[]"
24
+ return "[" + ", ".join(f'"{item}"' for item in items) + "]"
25
+
26
+
27
+ def load_yaml_specs(specs_dir: Path) -> List[tuple[str, Dict[str, Any]]]:
28
+ """
29
+ Load all YAML agent specifications from directory and subdirectories.
30
+
31
+ Returns list of tuples: (subfolder_name, spec_dict)
32
+ where subfolder_name is the immediate parent folder name, or "" for root level.
33
+ """
34
+ specs = []
35
+
36
+ # First, load specs from root level
37
+ for yaml_file in sorted(specs_dir.glob("*.yaml")):
38
+ with open(yaml_file, "r") as f:
39
+ spec = yaml.safe_load(f)
40
+ if spec: # Skip empty files
41
+ specs.append(("", spec))
42
+
43
+ # Then, load specs from subdirectories (one level deep)
44
+ for subdir in sorted(specs_dir.iterdir()):
45
+ if subdir.is_dir() and not subdir.name.startswith("."):
46
+ for yaml_file in sorted(subdir.glob("*.yaml")):
47
+ with open(yaml_file, "r") as f:
48
+ spec = yaml.safe_load(f)
49
+ if spec: # Skip empty files
50
+ specs.append((subdir.name, spec))
51
+
52
+ return specs
53
+
54
+
55
+ def generate_python_code(specs: List[tuple[str, Dict[str, Any]]]) -> str:
56
+ """Generate Python code from agent specifications."""
57
+ # Header
58
+ code = '''# Copyright (c) 2025-2026 Datalayer, Inc.
59
+ # Distributed under the terms of the Modified BSD License.
60
+
61
+ """
62
+ Agent Library.
63
+
64
+ Predefined agent specifications that can be instantiated as AgentSpaces.
65
+ THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
66
+ Generated from YAML specifications in specs/agents/
67
+ """
68
+
69
+ from typing import Dict
70
+
71
+ from agent_runtimes.mcp.catalog_mcp_servers import MCP_SERVER_CATALOG
72
+ from agent_runtimes.types import AgentSpec
73
+
74
+ # ============================================================================
75
+ # Agent Specs
76
+ # ============================================================================
77
+
78
+ '''
79
+
80
+ # Organize specs by subfolder
81
+ from collections import defaultdict
82
+
83
+ specs_by_folder: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
84
+ for folder, spec in specs:
85
+ specs_by_folder[folder].append(spec)
86
+
87
+ # Generate agent spec constants organized by folder
88
+ agent_ids = []
89
+
90
+ # Sort folders: empty string (root) first, then alphabetically
91
+ sorted_folders = sorted(
92
+ specs_by_folder.keys(), key=lambda x: "" if x == "" else f"z{x}"
93
+ )
94
+
95
+ for folder in sorted_folders:
96
+ folder_specs = specs_by_folder[folder]
97
+
98
+ # Add folder header if not root
99
+ if folder:
100
+ code += f"\n# {folder.replace('-', ' ').title()} Agents\n"
101
+ code += f"# {'=' * 76}\n\n"
102
+
103
+ for spec in folder_specs:
104
+ agent_id = spec["id"]
105
+ # Prefix agent ID with folder name for uniqueness
106
+ full_agent_id = f"{folder}/{agent_id}" if folder else agent_id
107
+ # Create constant name: e.g., "data-acquisition" -> "DATA_ACQUISITION_AGENT_SPEC"
108
+ # But if id already ends with "-agent", don't duplicate: "github-agent" -> "GITHUB_AGENT_SPEC"
109
+ # NO folder prefix for Python constants
110
+ base_name = agent_id.upper().replace("-", "_")
111
+
112
+ if agent_id.endswith("-agent"):
113
+ const_name = base_name + "_SPEC"
114
+ else:
115
+ const_name = base_name + "_AGENT_SPEC"
116
+ agent_ids.append((full_agent_id, const_name, folder))
117
+
118
+ # Get MCP servers
119
+ mcp_server_ids = spec.get("mcp_servers", [])
120
+ mcp_servers_str = ", ".join(
121
+ f'MCP_SERVER_CATALOG["{sid}"]' for sid in mcp_server_ids
122
+ )
123
+
124
+ # Format optional fields
125
+ icon = f'"{spec.get("icon")}"' if spec.get("icon") else "None"
126
+ emoji = f'"{spec.get("emoji")}"' if spec.get("emoji") else "None"
127
+ color = f'"{spec.get("color")}"' if spec.get("color") else "None"
128
+ suggestions = spec.get("suggestions", [])
129
+ suggestions_str = (
130
+ "[\n "
131
+ + ",\n ".join(f'"{s}"' for s in suggestions)
132
+ + ",\n ]"
133
+ if suggestions
134
+ else "[]"
135
+ )
136
+ # Escape multi-line strings properly
137
+ welcome = (
138
+ spec.get("welcome_message", "").replace('"', '\\"').replace("\n", " ")
139
+ )
140
+ welcome_notebook = spec.get("welcome_notebook")
141
+ welcome_document = spec.get("welcome_document")
142
+ system_prompt = spec.get("system_prompt", "")
143
+ system_prompt_codemode_addons = spec.get(
144
+ "system_prompt_codemode_addons", ""
145
+ )
146
+
147
+ # Escape triple quotes in system prompts for Python triple-quoted strings
148
+ if system_prompt:
149
+ system_prompt = system_prompt.replace('"""', r"\"\"\"")
150
+ if system_prompt_codemode_addons:
151
+ system_prompt_codemode_addons = system_prompt_codemode_addons.replace(
152
+ '"""', r"\"\"\""
153
+ )
154
+
155
+ # Clean description for Python (single line)
156
+ description = (
157
+ spec["description"].replace("\n", " ").replace(" ", " ").strip()
158
+ )
159
+
160
+ # Use triple quotes for multiline system prompts
161
+ system_prompt_str = f'"""{system_prompt}"""' if system_prompt else "None"
162
+ system_prompt_codemode_addons_str = (
163
+ f'"""{system_prompt_codemode_addons}"""'
164
+ if system_prompt_codemode_addons
165
+ else "None"
166
+ )
167
+
168
+ code += f'''{const_name} = AgentSpec(
169
+ id="{full_agent_id}",
170
+ name="{spec["name"]}",
171
+ description="{description}",
172
+ tags={_fmt_list(spec.get("tags", []))},
173
+ enabled={spec.get("enabled", True)},
174
+ mcp_servers=[{mcp_servers_str}],
175
+ skills={_fmt_list(spec.get("skills", []))},
176
+ environment_name="{spec.get("environment_name", "ai-agents-env")}",
177
+ icon={icon},
178
+ emoji={emoji},
179
+ color={color},
180
+ suggestions={suggestions_str},
181
+ welcome_message="{welcome}",
182
+ welcome_notebook={f'"{welcome_notebook}"' if welcome_notebook else "None"},
183
+ welcome_document={f'"{welcome_document}"' if welcome_document else "None"},
184
+ system_prompt={system_prompt_str},
185
+ system_prompt_codemode_addons={system_prompt_codemode_addons_str},
186
+ )
187
+
188
+ '''
189
+
190
+ # Generate registry organized by folder
191
+ code += """
192
+ # ============================================================================
193
+ # Agent Specs Registry
194
+ # ============================================================================
195
+
196
+ AGENT_SPECS: Dict[str, AgentSpec] = {
197
+ """
198
+
199
+ # Sort by folder for organized registry
200
+ for folder in sorted_folders:
201
+ folder_agents = [(aid, cname) for aid, cname, f in agent_ids if f == folder]
202
+ if folder_agents and folder:
203
+ code += f" # {folder.replace('-', ' ').title()}\n"
204
+ for full_agent_id, const_name in folder_agents:
205
+ code += f' "{full_agent_id}": {const_name},\n'
206
+ if folder_agents and folder:
207
+ code += "\n"
208
+
209
+ code += """}
210
+
211
+
212
+ def get_agent_spec(agent_id: str) -> AgentSpec | None:
213
+ \"\"\"
214
+ Get an agent specification by ID.
215
+
216
+ Args:
217
+ agent_id: The unique identifier of the agent.
218
+
219
+ Returns:
220
+ The AgentSpec configuration, or None if not found.
221
+ \"\"\"
222
+ return AGENT_SPECS.get(agent_id)
223
+
224
+
225
+ def list_agent_specs() -> list[AgentSpec]:
226
+ \"\"\"
227
+ List all available agent specifications.
228
+
229
+ Returns:
230
+ List of all AgentSpec configurations.
231
+ \"\"\"
232
+ return list(AGENT_SPECS.values())
233
+ """
234
+
235
+ return code
236
+
237
+
238
+ def generate_typescript_code(
239
+ specs: List[tuple[str, Dict[str, Any]]], mcp_specs_dir: str, skills_specs_dir: str
240
+ ) -> str:
241
+ """Generate TypeScript code from agent specifications."""
242
+ # Load available MCP servers from specs
243
+ import glob
244
+ import os
245
+
246
+ mcp_server_files = glob.glob(os.path.join(mcp_specs_dir, "*.yaml"))
247
+ mcp_server_ids = [
248
+ os.path.basename(f).replace(".yaml", "") for f in mcp_server_files
249
+ ]
250
+ mcp_server_ids.sort()
251
+
252
+ # Load available skills from specs
253
+ skill_files = glob.glob(os.path.join(skills_specs_dir, "*.yaml"))
254
+ skill_ids = [os.path.basename(f).replace(".yaml", "") for f in skill_files]
255
+ skill_ids.sort()
256
+
257
+ # Determine which MCP servers and skills are actually used in these specs
258
+ used_mcp_servers = set()
259
+ used_skills = set()
260
+ for _, spec in specs:
261
+ for server in spec.get("mcp_servers", []):
262
+ used_mcp_servers.add(server)
263
+ for skill in spec.get("skills", []):
264
+ used_skills.add(skill)
265
+
266
+ # Only import what's actually used
267
+ mcp_imports = []
268
+ mcp_map_entries = []
269
+ for server_id in mcp_server_ids:
270
+ if server_id in used_mcp_servers:
271
+ const_name = server_id.upper().replace("-", "_") + "_MCP_SERVER"
272
+ mcp_imports.append(const_name)
273
+ mcp_map_entries.append(f" '{server_id}': {const_name},")
274
+
275
+ # Generate skill import names and map entries
276
+ skill_imports = []
277
+ skill_map_entries = []
278
+ for sid in skill_ids:
279
+ if sid in used_skills:
280
+ const_name = sid.upper().replace("-", "_") + "_SKILL_SPEC"
281
+ skill_imports.append(const_name)
282
+ skill_map_entries.append(f" '{sid}': {const_name},")
283
+
284
+ # Determine if we need any helper code
285
+ has_mcp = len(mcp_imports) > 0
286
+ has_skills = len(skill_imports) > 0
287
+
288
+ # Header
289
+ code = """/*
290
+ * Copyright (c) 2025-2026 Datalayer, Inc.
291
+ * Distributed under the terms of the Modified BSD License.
292
+ */
293
+
294
+ /**
295
+ * Agent Library.
296
+ *
297
+ * Predefined agent specifications that can be instantiated as AgentSpaces.
298
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
299
+ * Generated from YAML specifications in specs/agents/
300
+ */
301
+
302
+ import type { AgentSpec } from '../../../types';
303
+ """
304
+
305
+ # Only add MCP server imports if needed
306
+ if has_mcp:
307
+ code += "import {\n"
308
+ code += " " + ",\n ".join(mcp_imports) + ",\n"
309
+ code += "} from '../../mcpServers';\n"
310
+
311
+ # Only add skill imports if needed
312
+ if has_skills:
313
+ code += "import {\n"
314
+ code += " " + ",\n ".join(skill_imports) + ",\n"
315
+ code += "} from '../../skills';\n"
316
+ code += "import type { SkillSpec } from '../../skills';\n"
317
+
318
+ # Only add MCP server lookup if used
319
+ if has_mcp:
320
+ code += """
321
+ // ============================================================================
322
+ // MCP Server Lookup
323
+ // ============================================================================
324
+
325
+ const MCP_SERVER_MAP: Record<string, any> = {
326
+ """
327
+ code += "\n".join(mcp_map_entries) + "\n"
328
+ code += "};\n"
329
+
330
+ # Only add skill lookup if used
331
+ if has_skills:
332
+ code += """
333
+ /**
334
+ * Map skill IDs to SkillSpec objects, converting to AgentSkillSpec shape.
335
+ */
336
+ const SKILL_MAP: Record<string, any> = {
337
+ """
338
+ code += "\n".join(skill_map_entries) + "\n"
339
+ code += "};\n"
340
+ code += """
341
+ function toAgentSkillSpec(skill: SkillSpec) {
342
+ return {
343
+ id: skill.id,
344
+ name: skill.name,
345
+ description: skill.description,
346
+ version: '1.0.0',
347
+ tags: skill.tags,
348
+ enabled: skill.enabled,
349
+ requiredEnvVars: skill.requiredEnvVars,
350
+ };
351
+ }
352
+ """
353
+
354
+ code += """
355
+ // ============================================================================
356
+ // Agent Specs
357
+ // ============================================================================
358
+
359
+ """
360
+
361
+ # Organize specs by subfolder for TypeScript
362
+ from collections import defaultdict
363
+
364
+ specs_by_folder: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
365
+ for folder, spec in specs:
366
+ specs_by_folder[folder].append(spec)
367
+
368
+ # Sort folders: empty string (root) first, then alphabetically
369
+ sorted_folders = sorted(
370
+ specs_by_folder.keys(), key=lambda x: "" if x == "" else f"z{x}"
371
+ )
372
+
373
+ # Generate agent spec constants organized by folder
374
+ agent_ids = []
375
+
376
+ for folder in sorted_folders:
377
+ folder_specs = specs_by_folder[folder]
378
+
379
+ # Add folder header if not root
380
+ if folder:
381
+ code += f"// {folder.replace('-', ' ').title()} Agents\n"
382
+ code += f"// {'=' * 76}\n\n"
383
+
384
+ for spec in folder_specs:
385
+ agent_id = spec["id"]
386
+ # Prefix agent ID with folder name for uniqueness
387
+ full_agent_id = f"{folder}/{agent_id}" if folder else agent_id
388
+ # Create constant name: e.g., "data-acquisition" -> "DATA_ACQUISITION_AGENT_SPEC"
389
+ # But if id already ends with "-agent", don't duplicate: "github-agent" -> "GITHUB_AGENT_SPEC"
390
+ # NO folder prefix for TypeScript constants
391
+ base_name = agent_id.upper().replace("-", "_")
392
+
393
+ if agent_id.endswith("-agent"):
394
+ const_name = base_name + "_SPEC"
395
+ else:
396
+ const_name = base_name + "_AGENT_SPEC"
397
+ agent_ids.append((full_agent_id, const_name, folder))
398
+
399
+ # Get MCP servers
400
+ mcp_server_ids = spec.get("mcp_servers", [])
401
+ if has_mcp and mcp_server_ids:
402
+ mcp_servers_str = ", ".join(
403
+ f"MCP_SERVER_MAP['{sid}']" for sid in mcp_server_ids
404
+ )
405
+ else:
406
+ mcp_servers_str = ""
407
+
408
+ # Get skills - resolve to AgentSkillSpec via toAgentSkillSpec
409
+ skill_ids_list = spec.get("skills", [])
410
+ if has_skills and skill_ids_list:
411
+ skills_str = ", ".join(
412
+ f"toAgentSkillSpec(SKILL_MAP['{sid}'])" for sid in skill_ids_list
413
+ )
414
+ else:
415
+ skills_str = ""
416
+
417
+ # Format tags and suggestions as arrays
418
+ tags = spec.get("tags", [])
419
+ tags_str = "[" + ", ".join(f"'{t}'" for t in tags) + "]"
420
+
421
+ suggestions = spec.get("suggestions", [])
422
+ # Escape single quotes in suggestions for TypeScript
423
+ escaped_suggestions = [s.replace("'", "\\'") for s in suggestions]
424
+ suggestions_str = (
425
+ "[\n "
426
+ + ",\n ".join(f"'{s}'" for s in escaped_suggestions)
427
+ + ",\n ]"
428
+ if suggestions
429
+ else "[]"
430
+ )
431
+
432
+ # Format optional fields
433
+ icon = f"'{spec.get('icon')}'" if spec.get("icon") else "undefined"
434
+ emoji = f"'{spec.get('emoji')}'" if spec.get("emoji") else "undefined"
435
+ color = f"'{spec.get('color')}'" if spec.get("color") else "undefined"
436
+ system_prompt = spec.get("system_prompt")
437
+ system_prompt_codemode_addons = spec.get("system_prompt_codemode_addons")
438
+
439
+ # Escape backticks for TypeScript template literals
440
+ if system_prompt:
441
+ system_prompt = system_prompt.replace("`", "\\`")
442
+ if system_prompt_codemode_addons:
443
+ system_prompt_codemode_addons = system_prompt_codemode_addons.replace(
444
+ "`", "\\`"
445
+ )
446
+
447
+ # Clean description for TypeScript (multi-line template literal)
448
+ description = (
449
+ spec["description"].replace("\n", " ").replace(" ", " ").strip()
450
+ )
451
+
452
+ code += f"""export const {const_name}: AgentSpec = {{
453
+ id: '{full_agent_id}',
454
+ name: '{spec["name"]}',
455
+ description: `{description}`,
456
+ tags: {tags_str},
457
+ enabled: {str(spec.get("enabled", True)).lower()},
458
+ mcpServers: [{mcp_servers_str}],
459
+ skills: [{skills_str}],
460
+ environmentName: '{spec.get("environment_name", "ai-agents-env")}',
461
+ icon: {icon},
462
+ emoji: {emoji},
463
+ color: {color},
464
+ suggestions: {suggestions_str},
465
+ systemPrompt: {f"`{system_prompt}`" if system_prompt else "undefined"},
466
+ systemPromptCodemodeAddons: {f"`{system_prompt_codemode_addons}`" if system_prompt_codemode_addons else "undefined"},
467
+ }};
468
+
469
+ """
470
+
471
+ # Generate registry organized by folder
472
+ code += """// ============================================================================
473
+ // Agent Specs Registry
474
+ // ============================================================================
475
+
476
+ export const AGENT_SPECS: Record<string, AgentSpec> = {
477
+ """
478
+
479
+ # Sort by folder for organized registry
480
+ for folder in sorted_folders:
481
+ folder_agents = [(aid, cname) for aid, cname, f in agent_ids if f == folder]
482
+ if folder_agents and folder:
483
+ code += f" // {folder.replace('-', ' ').title()}\n"
484
+ for full_agent_id, const_name in folder_agents:
485
+ code += f" '{full_agent_id}': {const_name},\n"
486
+ if folder_agents and folder:
487
+ code += "\n"
488
+
489
+ code += """};
490
+
491
+ /**
492
+ * Get an agent specification by ID.
493
+ */
494
+ export function getAgentSpecs(agentId: string): AgentSpec | undefined {
495
+ return AGENT_SPECS[agentId];
496
+ }
497
+
498
+ /**
499
+ * List all available agent specifications.
500
+ */
501
+ export function listAgentSpecs(): AgentSpec[] {
502
+ return Object.values(AGENT_SPECS);
503
+ }
504
+
505
+ /**
506
+ * Collect all required environment variables for an agent spec.
507
+ *
508
+ * Iterates over the spec's MCP servers and skills and returns the
509
+ * deduplicated union of their `requiredEnvVars` arrays.
510
+ */
511
+ export function getAgentSpecRequiredEnvVars(spec: AgentSpec): string[] {
512
+ const vars = new Set<string>();
513
+ for (const server of spec.mcpServers) {
514
+ for (const v of server.requiredEnvVars ?? []) {
515
+ vars.add(v);
516
+ }
517
+ }
518
+ for (const skill of spec.skills) {
519
+ for (const v of skill.requiredEnvVars ?? []) {
520
+ vars.add(v);
521
+ }
522
+ }
523
+ return Array.from(vars);
524
+ }
525
+ """
526
+
527
+ return code
528
+
529
+
530
+ def update_init_file(
531
+ specs: List[tuple[str, Dict[str, Any]]], init_file_path: Path
532
+ ) -> None:
533
+ """Update __init__.py with the new agent spec constants."""
534
+ # Collect all constant names
535
+ const_names = []
536
+ for folder, spec in specs:
537
+ agent_id = spec["id"]
538
+ if folder:
539
+ base_name = (
540
+ f"{folder}_{agent_id}".upper().replace("-", "_").replace("/", "_")
541
+ )
542
+ else:
543
+ base_name = agent_id.upper().replace("-", "_")
544
+
545
+ if agent_id.endswith("-agent"):
546
+ const_name = base_name + "_SPEC"
547
+ else:
548
+ const_name = base_name + "_AGENT_SPEC"
549
+ const_names.append(const_name)
550
+
551
+ # Sort for consistent ordering
552
+ const_names.sort()
553
+
554
+ # Read the current __init__.py
555
+ with open(init_file_path, "r") as f:
556
+ content = f.read()
557
+
558
+ # Find the agents import block and replace it
559
+ import re
560
+
561
+ # Pattern to match the entire from .agents import block
562
+ pattern = r"(from \.agents import \(\n)(.*?)(\n\))"
563
+
564
+ # Build new imports
565
+ new_imports = " AGENT_SPECS,\n"
566
+ for const_name in const_names:
567
+ new_imports += f" {const_name},\n"
568
+ new_imports += " get_agent_spec,\n"
569
+ new_imports += " list_agent_specs,"
570
+
571
+ # Replace the imports
572
+ new_content = re.sub(pattern, r"\1" + new_imports + r"\3", content, flags=re.DOTALL)
573
+
574
+ # Write back
575
+ with open(init_file_path, "w") as f:
576
+ f.write(new_content)
577
+
578
+
579
+ def generate_subfolder_structure(specs: List[tuple[str, Dict[str, Any]]], args):
580
+ """Generate separate agent files per subfolder."""
581
+ from collections import defaultdict
582
+
583
+ # Organize specs by folder
584
+ specs_by_folder: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
585
+ for folder, spec in specs:
586
+ specs_by_folder[folder].append(spec)
587
+
588
+ # Get MCP and skills specs directories
589
+ mcp_specs_dir = args.specs_dir.parent / "mcp-servers"
590
+ skills_specs_dir = args.specs_dir.parent / "skills"
591
+
592
+ # Determine base directories
593
+ python_base = args.python_output.parent / "agents"
594
+ typescript_base = args.typescript_output.parent / "agents"
595
+
596
+ print(f"Generating subfolder structure in {python_base} and {typescript_base}...")
597
+
598
+ # Generate files for each folder
599
+ all_python_imports = []
600
+ all_typescript_imports = []
601
+
602
+ for folder, folder_specs in sorted(specs_by_folder.items()):
603
+ if not folder: # Skip root level for now
604
+ continue
605
+
606
+ print(f" Generating agents for subfolder: {folder}")
607
+
608
+ # Convert folder name to valid Python module name (replace hyphens with underscores)
609
+ folder_python_name = folder.replace("-", "_")
610
+
611
+ # Create Python subfolder file
612
+ python_folder_dir = python_base / folder_python_name
613
+ python_folder_dir.mkdir(parents=True, exist_ok=True)
614
+ python_file = python_folder_dir / "agents.py"
615
+
616
+ # Generate Python code for this folder
617
+ python_code = generate_python_code([(folder, spec) for spec in folder_specs])
618
+ with open(python_file, "w") as f:
619
+ f.write(python_code)
620
+
621
+ # Create __init__.py for Python subfolder
622
+ python_init = python_folder_dir / "__init__.py"
623
+ with open(python_init, "w") as f:
624
+ f.write(f"""# Copyright (c) 2025-2026 Datalayer, Inc.
625
+ # Distributed under the terms of the Modified BSD License.
626
+
627
+ from .agents import *
628
+
629
+ __all__ = ["AGENT_SPECS", "get_agent_spec", "list_agent_specs"]
630
+ """)
631
+
632
+ # Collect imports for main index
633
+ all_python_imports.append(
634
+ f"from .{folder_python_name} import AGENT_SPECS as {folder_python_name.upper()}_AGENTS"
635
+ )
636
+
637
+ # Create TypeScript subfolder file
638
+ typescript_folder_dir = typescript_base / folder
639
+ typescript_folder_dir.mkdir(parents=True, exist_ok=True)
640
+ typescript_file = typescript_folder_dir / "agents.ts"
641
+
642
+ # Generate TypeScript code for this folder
643
+ typescript_code = generate_typescript_code(
644
+ [(folder, spec) for spec in folder_specs],
645
+ str(mcp_specs_dir),
646
+ str(skills_specs_dir),
647
+ )
648
+ with open(typescript_file, "w") as f:
649
+ f.write(typescript_code)
650
+
651
+ # Create index.ts for TypeScript subfolder
652
+ typescript_index = typescript_folder_dir / "index.ts"
653
+ with open(typescript_index, "w") as f:
654
+ f.write(f"""/*
655
+ * Copyright (c) 2025-2026 Datalayer, Inc.
656
+ * Distributed under the terms of the Modified BSD License.
657
+ */
658
+
659
+ export * from './agents';
660
+ """)
661
+
662
+ # Collect imports for main index
663
+ all_typescript_imports.append(f"export * from './{folder}';")
664
+
665
+ # Create main Python index file
666
+ python_index = python_base / "__init__.py"
667
+ python_index_content = """# Copyright (c) 2025-2026 Datalayer, Inc.
668
+ # Distributed under the terms of the Modified BSD License.
669
+
670
+ \"\"\"
671
+ Agent Library - Subfolder Organization.
672
+
673
+ THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
674
+ \"\"\"
675
+
676
+ from typing import Dict
677
+ from agent_runtimes.types import AgentSpec
678
+
679
+ """
680
+
681
+ # Add imports
682
+ for imp in all_python_imports:
683
+ python_index_content += f"{imp}\n"
684
+
685
+ # Merge all agent specs
686
+ python_index_content += """
687
+ # Merge all agent specs from subfolders
688
+ AGENT_SPECS: Dict[str, AgentSpec] = {}
689
+ """
690
+
691
+ for folder in sorted(specs_by_folder.keys()):
692
+ if folder:
693
+ folder_python_name = folder.replace("-", "_")
694
+ python_index_content += (
695
+ f"AGENT_SPECS.update({folder_python_name.upper()}_AGENTS)\n"
696
+ )
697
+
698
+ python_index_content += """
699
+
700
+ def get_agent_spec(agent_id: str) -> AgentSpec | None:
701
+ \"\"\"Get an agent specification by ID.\"\"\"
702
+ return AGENT_SPECS.get(agent_id)
703
+
704
+
705
+ def list_agent_specs() -> list[AgentSpec]:
706
+ \"\"\"List all available agent specifications.\"\"\"
707
+ return list(AGENT_SPECS.values())
708
+
709
+ __all__ = ["AGENT_SPECS", "get_agent_spec", "list_agent_specs"]
710
+ """
711
+
712
+ with open(python_index, "w") as f:
713
+ f.write(python_index_content)
714
+
715
+ # Create main TypeScript index file
716
+ typescript_index = typescript_base / "index.ts"
717
+ typescript_index_content = """/*
718
+ * Copyright (c) 2025-2026 Datalayer, Inc.
719
+ * Distributed under the terms of the Modified BSD License.
720
+ */
721
+
722
+ /**
723
+ * Agent Library - Subfolder Organization.
724
+ *
725
+ * THIS FILE IS AUTO-GENERATED. DO NOT EDIT MANUALLY.
726
+ */
727
+
728
+ import type { AgentSpec } from '../../types';
729
+
730
+ """
731
+
732
+ # Import AGENT_SPECS from each subfolder
733
+ for folder in sorted(specs_by_folder.keys()):
734
+ if folder:
735
+ folder_const = folder.replace("-", "_").upper()
736
+ typescript_index_content += f"import {{ AGENT_SPECS as {folder_const}_AGENTS }} from './{folder}';\n"
737
+
738
+ typescript_index_content += """
739
+ // Merge all agent specs from subfolders
740
+ export const AGENT_SPECS: Record<string, AgentSpec> = {
741
+ """
742
+
743
+ for folder in sorted(specs_by_folder.keys()):
744
+ if folder:
745
+ folder_const = folder.replace("-", "_").upper()
746
+ typescript_index_content += f" ...{folder_const}_AGENTS,\n"
747
+
748
+ typescript_index_content += """};
749
+
750
+ /**
751
+ * Get an agent specification by ID.
752
+ */
753
+ export function getAgentSpecs(agentId: string): AgentSpec | undefined {
754
+ return AGENT_SPECS[agentId];
755
+ }
756
+
757
+ /**
758
+ * List all available agent specifications.
759
+ */
760
+ export function listAgentSpecs(): AgentSpec[] {
761
+ return Object.values(AGENT_SPECS);
762
+ }
763
+
764
+ /**
765
+ * Collect all required environment variables for an agent spec.
766
+ */
767
+ export function getAgentSpecRequiredEnvVars(spec: AgentSpec): string[] {
768
+ const vars = new Set<string>();
769
+ for (const server of spec.mcpServers) {
770
+ for (const v of server.requiredEnvVars ?? []) {
771
+ vars.add(v);
772
+ }
773
+ }
774
+ for (const skill of spec.skills) {
775
+ for (const v of skill.requiredEnvVars ?? []) {
776
+ vars.add(v);
777
+ }
778
+ }
779
+ return Array.from(vars);
780
+ }
781
+ """
782
+
783
+ with open(typescript_index, "w") as f:
784
+ f.write(typescript_index_content)
785
+
786
+ print(f"✓ Generated {len(specs_by_folder)} subfolder(s)")
787
+
788
+
789
+ def main():
790
+ """Main entry point."""
791
+ parser = argparse.ArgumentParser(
792
+ description="Generate Python and TypeScript code from YAML agent specifications"
793
+ )
794
+ parser.add_argument(
795
+ "--specs-dir",
796
+ type=Path,
797
+ default=Path("specs/agents"),
798
+ help="Directory containing YAML agent specifications",
799
+ )
800
+ parser.add_argument(
801
+ "--python-output",
802
+ type=Path,
803
+ default=Path("agent_runtimes/config/agents.py"),
804
+ help="Output path for generated Python code (if using --subfolder-structure, this will be the parent directory)",
805
+ )
806
+ parser.add_argument(
807
+ "--typescript-output",
808
+ type=Path,
809
+ default=Path("src/config/agents.ts"),
810
+ help="Output path for generated TypeScript code (if using --subfolder-structure, this will be the parent directory)",
811
+ )
812
+ parser.add_argument(
813
+ "--subfolder-structure",
814
+ action="store_true",
815
+ help="Generate separate files per subfolder instead of one combined file",
816
+ )
817
+
818
+ args = parser.parse_args()
819
+
820
+ # Check specs directory exists
821
+ if not args.specs_dir.exists():
822
+ print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
823
+ sys.exit(1)
824
+
825
+ # Load YAML specifications
826
+ print(f"Loading agent specifications from {args.specs_dir}...")
827
+ specs = load_yaml_specs(args.specs_dir)
828
+ print(f"Loaded {len(specs)} agent specification(s)")
829
+
830
+ if args.subfolder_structure:
831
+ # Generate separate files per subfolder
832
+ generate_subfolder_structure(specs, args)
833
+ else:
834
+ # Generate Python code (single file)
835
+ print(f"Generating Python code to {args.python_output}...")
836
+ python_code = generate_python_code(specs)
837
+ args.python_output.parent.mkdir(parents=True, exist_ok=True)
838
+ with open(args.python_output, "w") as f:
839
+ f.write(python_code)
840
+
841
+ # Generate TypeScript code (single file)
842
+ print(f"Generating TypeScript code to {args.typescript_output}...")
843
+ # Get MCP and skills specs directories (siblings to agents directory)
844
+ mcp_specs_dir = args.specs_dir.parent / "mcp-servers"
845
+ skills_specs_dir = args.specs_dir.parent / "skills"
846
+ typescript_code = generate_typescript_code(
847
+ specs, str(mcp_specs_dir), str(skills_specs_dir)
848
+ )
849
+ args.typescript_output.parent.mkdir(parents=True, exist_ok=True)
850
+ with open(args.typescript_output, "w") as f:
851
+ f.write(typescript_code)
852
+
853
+ # Update __init__.py with new agent spec constants
854
+ init_file_path = args.python_output.parent / "__init__.py"
855
+ if init_file_path.exists():
856
+ print(f"Updating {init_file_path}...")
857
+ update_init_file(specs, init_file_path)
858
+
859
+ print("✅ Code generation complete!")
860
+
861
+
862
+ if __name__ == "__main__":
863
+ main()