@datalayer/agent-runtimes 0.0.10 → 0.0.12

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