@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.
- package/README.md +2 -2
- package/lib/Agent.d.ts +29 -0
- package/lib/Agent.js +131 -0
- package/lib/AgentLexical.d.ts +34 -0
- package/lib/AgentLexical.js +296 -0
- package/lib/AgentNotebook.d.ts +19 -0
- package/lib/AgentNotebook.js +192 -0
- package/lib/agent-lexical-main.d.ts +1 -0
- package/lib/agent-lexical-main.js +11 -0
- package/lib/agent-main.d.ts +1 -0
- package/lib/agent-main.js +11 -0
- package/lib/agent-notebook-main.d.ts +1 -0
- package/lib/agent-notebook-main.js +12 -0
- package/lib/components/AgentConfiguration.d.ts +33 -21
- package/lib/components/AgentConfiguration.js +76 -21
- package/lib/components/chat/components/AgentDetails.d.ts +3 -1
- package/lib/components/chat/components/AgentDetails.js +164 -6
- package/lib/components/chat/components/Chat.d.ts +29 -3
- package/lib/components/chat/components/Chat.js +64 -59
- package/lib/components/chat/components/ChatFloating.d.ts +34 -12
- package/lib/components/chat/components/ChatFloating.js +54 -21
- package/lib/components/chat/components/ChatInline.d.ts +5 -1
- package/lib/components/chat/components/ChatInline.js +8 -1
- package/lib/components/chat/components/ChatSidebar.d.ts +6 -1
- package/lib/components/chat/components/ChatSidebar.js +2 -2
- package/lib/components/chat/components/ChatStandalone.d.ts +6 -1
- package/lib/components/chat/components/ChatStandalone.js +2 -2
- package/lib/components/chat/components/ContextDistribution.js +2 -2
- package/lib/components/chat/components/ContextInspector.js +4 -2
- package/lib/components/chat/components/ContextPanel.js +1 -6
- package/lib/components/chat/components/base/ChatBase.d.ts +49 -8
- package/lib/components/chat/components/base/ChatBase.js +544 -149
- package/lib/components/chat/components/base/InputPrompt.d.ts +42 -0
- package/lib/components/chat/components/base/InputPrompt.js +131 -0
- package/lib/components/chat/components/index.d.ts +3 -3
- package/lib/components/chat/components/index.js +1 -1
- package/lib/components/chat/components/parts/ReasoningPart.js +2 -4
- package/lib/components/chat/components/parts/TextPart.js +2 -70
- package/lib/components/chat/components/styles/streamdownStyles.d.ts +23 -0
- package/lib/components/chat/components/styles/streamdownStyles.js +319 -0
- package/lib/components/chat/index.d.ts +1 -1
- package/lib/components/chat/index.js +1 -1
- package/lib/components/chat/inference/DatalayerInferenceProvider.js +16 -12
- package/lib/components/chat/inference/SelfHostedInferenceProvider.js +16 -12
- package/lib/components/chat/protocols/AGUIAdapter.d.ts +10 -3
- package/lib/components/chat/protocols/AGUIAdapter.js +123 -44
- package/lib/components/chat/types/tool.d.ts +5 -2
- package/lib/components/index.d.ts +2 -19
- package/lib/components/index.js +1 -10
- package/lib/config/index.d.ts +0 -3
- package/lib/config/index.js +0 -3
- package/lib/examples/A2UiRestaurantExample.js +1 -1
- package/lib/examples/AgentRuntimeChatExample.d.ts +15 -0
- package/lib/examples/AgentRuntimeChatExample.js +126 -0
- package/lib/examples/{AgentSpaceFormExample.d.ts → AgentRuntimeFormExample.d.ts} +3 -3
- package/lib/examples/{AgentSpaceFormExample.js → AgentRuntimeFormExample.js} +61 -17
- package/lib/examples/AgentRuntimeLexicalExample.js +6 -3
- package/lib/examples/AgentRuntimeLexicalSidebarExample.js +8 -1
- package/lib/examples/AgentRuntimeNotebookExample.js +6 -5
- package/lib/examples/CopilotKitNotebookExample.js +2 -2
- package/lib/examples/JupyterNotebookExample.js +2 -2
- package/lib/{components → examples/components}/Header.d.ts +2 -1
- package/lib/{components → examples/components}/HeaderControls.js +1 -1
- package/lib/{components → examples/components}/LexicalEditor.d.ts +6 -1
- package/lib/{components → examples/components}/LexicalEditor.js +4 -4
- package/lib/{components → examples/components}/MainContent.d.ts +1 -1
- package/lib/{components → examples/components}/MainContent.js +7 -5
- package/lib/examples/components/index.d.ts +16 -0
- package/lib/examples/components/index.js +13 -0
- package/lib/examples/example-selector.js +2 -1
- package/lib/examples/index.d.ts +1 -1
- package/lib/examples/index.js +1 -1
- package/lib/examples/main.js +2 -2
- package/lib/examples/stores/examplesStore.d.ts +2 -23
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -0
- package/lib/lexical/ChatInlinePlugin.d.ts +13 -2
- package/lib/lexical/ChatInlinePlugin.js +41 -179
- package/lib/lexical/index.d.ts +1 -0
- package/lib/lexical/index.js +1 -0
- package/lib/lexical/useChatInlineToolbarItems.d.ts +28 -0
- package/lib/lexical/useChatInlineToolbarItems.js +163 -0
- package/lib/runtime/useAgentRuntime.d.ts +1 -1
- package/lib/runtime/useAgentRuntime.js +1 -1
- package/lib/specs/agents/codeai/agents.d.ts +28 -0
- package/lib/specs/agents/codeai/agents.js +151 -0
- package/lib/specs/agents/codeai/index.d.ts +1 -0
- package/lib/specs/agents/codeai/index.js +5 -0
- package/lib/{config → specs/agents/codemode-paper}/agents.d.ts +4 -6
- package/lib/specs/agents/codemode-paper/agents.js +308 -0
- package/lib/specs/agents/codemode-paper/index.d.ts +1 -0
- package/lib/specs/agents/codemode-paper/index.js +5 -0
- package/lib/specs/agents/datalayer-ai/agents.d.ts +31 -0
- package/lib/{config → specs/agents/datalayer-ai}/agents.js +42 -184
- package/lib/specs/agents/datalayer-ai/index.d.ts +1 -0
- package/lib/specs/agents/datalayer-ai/index.js +5 -0
- package/lib/specs/agents/index.d.ts +21 -0
- package/lib/specs/agents/index.js +47 -0
- package/lib/specs/envvars.d.ts +29 -0
- package/lib/specs/envvars.js +125 -0
- package/lib/specs/index.d.ts +5 -0
- package/lib/specs/index.js +9 -0
- package/lib/{config → specs}/mcpServers.d.ts +2 -1
- package/lib/{config → specs}/mcpServers.js +47 -1
- package/lib/specs/models.d.ts +68 -0
- package/lib/specs/models.js +239 -0
- package/lib/{config → specs}/skills.d.ts +2 -0
- package/lib/{config → specs}/skills.js +6 -0
- package/lib/state/substates/AIAgentState.d.ts +0 -1
- package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.d.ts +11 -22
- package/lib/tools/adapters/agent-runtimes/AgentRuntimesToolAdapter.js +5 -5
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +6 -6
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +4 -4
- package/lib/tools/adapters/agent-runtimes/notebookHooks.d.ts +6 -6
- package/lib/tools/adapters/agent-runtimes/notebookHooks.js +4 -4
- package/lib/{types.d.ts → types/Types.d.ts} +42 -8
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/package.json +11 -5
- package/scripts/codegen/generate_agents.py +608 -157
- package/scripts/codegen/generate_envvars.py +302 -0
- package/scripts/codegen/generate_mcp_servers.py +33 -21
- package/scripts/codegen/generate_models.py +486 -0
- package/scripts/codegen/generate_skills.py +21 -8
- package/style/primer-primitives.css +22 -0
- package/lib/components/chat/components/elements/ChatInputPrompt.d.ts +0 -37
- package/lib/components/chat/components/elements/ChatInputPrompt.js +0 -150
- /package/lib/{components → examples/components}/FooterMetrics.d.ts +0 -0
- /package/lib/{components → examples/components}/FooterMetrics.js +0 -0
- /package/lib/{components → examples/components}/Header.js +0 -0
- /package/lib/{components → examples/components}/HeaderControls.d.ts +0 -0
- /package/lib/{components → examples/components}/MockFileBrowser.d.ts +0 -0
- /package/lib/{components → examples/components}/MockFileBrowser.js +0 -0
- /package/lib/{components → examples/components}/SessionTabs.d.ts +0 -0
- /package/lib/{components → examples/components}/SessionTabs.js +0 -0
- /package/lib/{components → examples/components}/TimeTravel.d.ts +0 -0
- /package/lib/{components → examples/components}/TimeTravel.js +0 -0
- /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
|
-
"""
|
|
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
|
-
#
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
f'"""
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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 '
|
|
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
|
-
|
|
241
|
-
|
|
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
|
-
|
|
249
|
-
|
|
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
|
-
#
|
|
270
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
if
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
322
|
-
|
|
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
|
-
|
|
325
|
-
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
f.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
specs
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
|