universal-mcp-agents 0.1.22__py3-none-any.whl → 0.1.23__py3-none-any.whl

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.

Potentially problematic release.


This version of universal-mcp-agents might be problematic. Click here for more details.

@@ -1,35 +1,35 @@
1
+ import copy
1
2
  import json
2
3
  import re
3
- from typing import Literal, cast
4
4
  import uuid
5
+ from typing import Literal, cast
5
6
 
6
- from langchain_core.messages import AIMessage, ToolMessage
7
- from langchain_core.tools import StructuredTool
7
+ from langchain_anthropic import ChatAnthropic
8
+ from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
8
9
  from langgraph.checkpoint.base import BaseCheckpointSaver
9
10
  from langgraph.graph import START, StateGraph
10
11
  from langgraph.types import Command, RetryPolicy, StreamWriter
11
12
  from universal_mcp.tools.registry import ToolRegistry
12
- from universal_mcp.types import ToolConfig, ToolFormat
13
+ from universal_mcp.types import ToolFormat
13
14
 
14
15
  from universal_mcp.agents.base import BaseAgent
15
16
  from universal_mcp.agents.codeact0.llm_tool import smart_print
16
17
  from universal_mcp.agents.codeact0.prompts import (
17
- PLAYBOOK_GENERATING_PROMPT,
18
- PLAYBOOK_PLANNING_PROMPT,
19
- PLAYBOOK_META_PROMPT,
18
+ AGENT_BUILDER_GENERATING_PROMPT,
19
+ AGENT_BUILDER_META_PROMPT,
20
+ AGENT_BUILDER_PLANNING_PROMPT,
21
+ build_tool_definitions,
20
22
  create_default_prompt,
21
23
  )
22
24
  from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
23
- from universal_mcp.agents.codeact0.state import CodeActState, PlaybookCode, PlaybookPlan, PlaybookMeta
25
+ from universal_mcp.agents.codeact0.state import AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan, CodeActState
24
26
  from universal_mcp.agents.codeact0.tools import (
25
27
  create_meta_tools,
26
- enter_playbook_mode,
27
- get_valid_tools,
28
+ enter_agent_builder_mode,
28
29
  )
29
- from universal_mcp.agents.codeact0.utils import add_tools
30
+ from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, get_connected_apps_string, strip_thinking
30
31
  from universal_mcp.agents.llm import load_chat_model
31
32
  from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
32
- from universal_mcp.agents.codeact0.utils import get_connected_apps_string
33
33
 
34
34
 
35
35
  class CodeActPlaybookAgent(BaseAgent):
@@ -39,9 +39,8 @@ class CodeActPlaybookAgent(BaseAgent):
39
39
  instructions: str,
40
40
  model: str,
41
41
  memory: BaseCheckpointSaver | None = None,
42
- tools: ToolConfig | None = None,
43
42
  registry: ToolRegistry | None = None,
44
- playbook_registry: object | None = None,
43
+ agent_builder_registry: object | None = None,
45
44
  sandbox_timeout: int = 20,
46
45
  **kwargs,
47
46
  ):
@@ -53,83 +52,107 @@ class CodeActPlaybookAgent(BaseAgent):
53
52
  **kwargs,
54
53
  )
55
54
  self.model_instance = load_chat_model(model)
56
- self.playbook_model_instance = load_chat_model("azure/gpt-4.1")
57
- self.tools_config = tools or {}
55
+ self.agent_builder_model_instance = load_chat_model("anthropic:claude-sonnet-4-5-20250929", thinking=False)
58
56
  self.registry = registry
59
- self.playbook_registry = playbook_registry
60
- self.playbook = playbook_registry.get_agent() if playbook_registry else None
57
+ self.agent_builder_registry = agent_builder_registry
58
+ self.agent = agent_builder_registry.get_agent() if agent_builder_registry else None
59
+
60
+ self.tools_config = self.agent.tools if self.agent else {}
61
61
  self.eval_fn = eval_unsafe
62
62
  self.sandbox_timeout = sandbox_timeout
63
- self.default_tools = {
63
+ self.default_tools_config = {
64
64
  "llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
65
65
  }
66
- add_tools(self.tools_config, self.default_tools)
67
-
66
+ self.final_instructions = ""
67
+ self.tools_context = {}
68
+ self.eval_mode = kwargs.get("eval_mode", False)
68
69
 
69
- async def _build_graph(self):
70
+ async def _build_graph(self): # noqa: PLR0915
71
+ """Build the graph for the CodeAct Playbook Agent."""
70
72
  meta_tools = create_meta_tools(self.registry)
71
- additional_tools = [smart_print, meta_tools["web_search"]]
72
73
  self.additional_tools = [
73
- t if isinstance(t, StructuredTool) else StructuredTool.from_function(t) for t in additional_tools
74
+ smart_print,
75
+ meta_tools["web_search"],
76
+ meta_tools["read_file"],
77
+ meta_tools["save_file"],
78
+ meta_tools["upload_file"],
74
79
  ]
80
+
75
81
  if self.tools_config:
76
- # Convert dict format to list format if needed
77
- if isinstance(self.tools_config, dict):
78
- self.tools_config = [
79
- f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
80
- ]
81
- if not self.registry:
82
- raise ValueError("Tools are configured but no registry is provided")
82
+ await self.registry.load_tools(self.tools_config) # Load provided tools
83
+ if self.default_tools_config:
84
+ await self.registry.load_tools(self.default_tools_config) # Load default tools
83
85
 
84
86
  async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
85
- messages = [{"role": "system", "content": self.final_instructions}] + state["messages"]
86
-
87
- # Run the model and potentially loop for reflection
88
- model_with_tools = self.model_instance.bind_tools(
89
- tools=[
90
- execute_ipython_cell,
91
- enter_playbook_mode,
92
- meta_tools["search_functions"],
93
- meta_tools["load_functions"],
94
- ],
95
- tool_choice="auto",
96
- )
97
- response = cast(AIMessage, model_with_tools.invoke(messages))
87
+ """This node now only ever binds the four meta-tools to the LLM."""
88
+ messages = build_anthropic_cache_message(self.final_instructions) + state["messages"]
89
+
90
+ agent_facing_tools = [
91
+ execute_ipython_cell,
92
+ enter_agent_builder_mode,
93
+ meta_tools["search_functions"],
94
+ meta_tools["load_functions"],
95
+ ]
96
+
97
+ if isinstance(self.model_instance, ChatAnthropic):
98
+ model_with_tools = self.model_instance.bind_tools(
99
+ tools=agent_facing_tools,
100
+ tool_choice="auto",
101
+ cache_control={"type": "ephemeral", "ttl": "1h"},
102
+ )
103
+ if isinstance(messages[-1].content, str):
104
+ pass
105
+ else:
106
+ last = copy.deepcopy(messages[-1])
107
+ last.content[-1]["cache_control"] = {"type": "ephemeral", "ttl": "5m"}
108
+ messages[-1] = last
109
+ else:
110
+ model_with_tools = self.model_instance.bind_tools(
111
+ tools=agent_facing_tools,
112
+ tool_choice="auto",
113
+ )
114
+ response = cast(AIMessage, await model_with_tools.ainvoke(messages))
98
115
  if response.tool_calls:
99
116
  return Command(goto="execute_tools", update={"messages": [response]})
100
117
  else:
101
118
  return Command(update={"messages": [response], "model_with_tools": model_with_tools})
102
119
 
103
- async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "playbook"]]:
120
+ async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "agent_builder"]]:
104
121
  """Execute tool calls"""
105
122
  last_message = state["messages"][-1]
106
123
  tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
107
124
 
108
125
  tool_messages = []
109
126
  new_tool_ids = []
127
+ tool_result = ""
110
128
  ask_user = False
111
129
  ai_msg = ""
112
- tool_result = ""
113
130
  effective_previous_add_context = state.get("add_context", {})
114
131
  effective_existing_context = state.get("context", {})
132
+ # logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
115
133
 
116
134
  for tool_call in tool_calls:
135
+ tool_name = tool_call["name"]
136
+ tool_args = tool_call["args"]
117
137
  try:
118
- if tool_call["name"] == "enter_playbook_mode":
138
+ if tool_name == "enter_agent_builder_mode":
119
139
  tool_message = ToolMessage(
120
- content=json.dumps("Entered Playbook Mode."),
140
+ content=json.dumps("Entered Agent Builder Mode."),
121
141
  name=tool_call["name"],
122
142
  tool_call_id=tool_call["id"],
123
143
  )
124
144
  return Command(
125
- goto="playbook",
126
- update={"playbook_mode": "planning", "messages": [tool_message]}, # Entered Playbook mode
145
+ goto="agent_builder",
146
+ update={
147
+ "agent_builder_mode": "planning",
148
+ "messages": [tool_message],
149
+ }, # Entered Agent Builder mode
127
150
  )
128
- elif tool_call["name"] == "execute_ipython_cell":
151
+ elif tool_name == "execute_ipython_cell":
129
152
  code = tool_call["args"]["snippet"]
130
153
  output, new_context, new_add_context = await handle_execute_ipython_cell(
131
154
  code,
132
- self.tools_context,
155
+ self.tools_context, # Uses the dynamically updated context
133
156
  self.eval_fn,
134
157
  effective_previous_add_context,
135
158
  effective_existing_context,
@@ -137,23 +160,25 @@ class CodeActPlaybookAgent(BaseAgent):
137
160
  effective_existing_context = new_context
138
161
  effective_previous_add_context = new_add_context
139
162
  tool_result = output
140
- elif tool_call["name"] == "load_functions": # Handle load_functions separately
141
- valid_tools, unconnected_links = await get_valid_tools(
142
- tool_ids=tool_call["args"]["tool_ids"], registry=self.registry
143
- )
163
+ elif tool_name == "load_functions":
164
+ # The tool now does all the work of validation and formatting.
165
+ tool_result, new_context_for_sandbox, valid_tools, unconnected_links = await meta_tools[
166
+ "load_functions"
167
+ ].ainvoke(tool_args)
168
+ # We still need to update the sandbox context for `execute_ipython_cell`
144
169
  new_tool_ids.extend(valid_tools)
145
- # Create tool message response
146
- tool_result = f"Successfully loaded {len(valid_tools)} tools: {valid_tools}"
147
- links = "\n".join(unconnected_links)
148
- if links:
170
+ if new_tool_ids:
171
+ self.tools_context.update(new_context_for_sandbox)
172
+ if unconnected_links:
149
173
  ask_user = True
150
- ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {links} "
151
- elif tool_call["name"] == "search_functions":
152
- tool_result = await meta_tools["search_functions"].ainvoke(tool_call["args"])
174
+ ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {unconnected_links} "
175
+
176
+ elif tool_name == "search_functions":
177
+ tool_result = await meta_tools["search_functions"].ainvoke(tool_args)
153
178
  else:
154
179
  raise Exception(
155
180
  f"Unexpected tool call: {tool_call['name']}. "
156
- "tool calls must be one of 'enter_playbook_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'. For using functions, call them in code using 'execute_ipython_cell'."
181
+ "tool calls must be one of 'enter_agent_builder_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'. For using functions, call them in code using 'execute_ipython_cell'."
157
182
  )
158
183
  except Exception as e:
159
184
  tool_result = str(e)
@@ -165,12 +190,6 @@ class CodeActPlaybookAgent(BaseAgent):
165
190
  )
166
191
  tool_messages.append(tool_message)
167
192
 
168
- if new_tool_ids:
169
- self.tools_config.extend(new_tool_ids)
170
- self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
171
- self.final_instructions, self.tools_context = create_default_prompt(
172
- self.exported_tools, self.additional_tools, self.instructions, await get_connected_apps_string(self.registry)
173
- )
174
193
  if ask_user:
175
194
  tool_messages.append(AIMessage(content=ai_msg))
176
195
  return Command(
@@ -192,27 +211,50 @@ class CodeActPlaybookAgent(BaseAgent):
192
211
  },
193
212
  )
194
213
 
195
- def playbook(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
196
- playbook_mode = state.get("playbook_mode")
197
- if playbook_mode == "planning":
214
+ async def agent_builder(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
215
+ agent_builder_mode = state.get("agent_builder_mode")
216
+ if agent_builder_mode == "planning":
198
217
  plan_id = str(uuid.uuid4())
199
- writer({
200
- "type": "custom",
201
- id: plan_id,
202
- "name": "planning",
203
- "data": {"update": bool(self.playbook)}
204
- })
205
- planning_instructions = self.instructions + PLAYBOOK_PLANNING_PROMPT
206
- messages = [{"role": "system", "content": planning_instructions}] + state["messages"]
207
-
208
- model_with_structured_output = self.playbook_model_instance.with_structured_output(PlaybookPlan)
209
- response = model_with_structured_output.invoke(messages)
210
- plan = cast(PlaybookPlan, response)
211
-
218
+ writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": bool(self.agent)}})
219
+ planning_instructions = self.instructions + AGENT_BUILDER_PLANNING_PROMPT + self.preloaded_defs
220
+ messages = [{"role": "system", "content": planning_instructions}] + strip_thinking(state["messages"])
221
+
222
+ model_with_structured_output = self.agent_builder_model_instance.with_structured_output(
223
+ AgentBuilderPlan
224
+ )
225
+ response = await model_with_structured_output.ainvoke(messages)
226
+ plan = cast(AgentBuilderPlan, response)
227
+
212
228
  writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan.steps}})
213
- return Command(update={"messages": [AIMessage(content=json.dumps(plan.dict()), additional_kwargs={"type": "planning", "plan": plan.steps, "update": bool(self.playbook)})], "playbook_mode": "confirming", "plan": plan.steps})
229
+ ai_msg = AIMessage(
230
+ content=json.dumps(plan.model_dump()),
231
+ additional_kwargs={
232
+ "type": "planning",
233
+ "plan": plan.steps,
234
+ "update": bool(self.agent),
235
+ },
236
+ )
237
+
238
+ if self.eval_mode:
239
+ mock_user_message = HumanMessage(content="yes, this is great")
240
+ return Command(
241
+ goto="agent_builder",
242
+ update={
243
+ "messages": [ai_msg, mock_user_message],
244
+ "agent_builder_mode": "generating",
245
+ "plan": plan.steps,
246
+ },
247
+ )
214
248
 
215
- elif playbook_mode == "confirming":
249
+ return Command(
250
+ update={
251
+ "messages": [ai_msg],
252
+ "agent_builder_mode": "confirming",
253
+ "plan": plan.steps,
254
+ }
255
+ )
256
+
257
+ elif agent_builder_mode == "confirming":
216
258
  # Deterministic routing based on three exact button inputs from UI
217
259
  user_text = ""
218
260
  for m in reversed(state["messages"]):
@@ -226,110 +268,228 @@ class CodeActPlaybookAgent(BaseAgent):
226
268
 
227
269
  t = user_text.lower()
228
270
  if t == "yes, this is great":
229
- # Generate playbook metadata (name and description) before moving to generation
230
- meta_id = str(uuid.uuid4())
231
- writer({
232
- "type": "custom",
233
- id: meta_id,
234
- "name": "metadata",
235
- "data": {"update": bool(self.playbook)}
236
- })
237
- meta_instructions = self.instructions + PLAYBOOK_META_PROMPT
238
- messages = [{"role": "system", "content": meta_instructions}] + state["messages"]
239
-
240
- model_with_structured_output = self.playbook_model_instance.with_structured_output(PlaybookMeta)
241
- meta_response = model_with_structured_output.invoke(messages)
242
- meta = cast(PlaybookMeta, meta_response)
243
-
244
- writer({"type": "custom", id: meta_id, "name": "metadata", "data": {"name": meta.name, "description": meta.description}})
245
- return Command(goto="playbook", update={"playbook_mode": "generating", "playbook_name": meta.name, "playbook_description": meta.description})
271
+ self.meta_id = str(uuid.uuid4())
272
+ name, description = None, None
273
+ if self.agent:
274
+ # Update flow: use existing name/description and do not re-generate
275
+ name = getattr(self.agent, "name", None)
276
+ description = getattr(self.agent, "description", None)
277
+ writer(
278
+ {
279
+ "type": "custom",
280
+ id: self.meta_id,
281
+ "name": "generating",
282
+ "data": {
283
+ "update": True,
284
+ "name": name,
285
+ "description": description,
286
+ },
287
+ }
288
+ )
289
+ else:
290
+ writer({"type": "custom", id: self.meta_id, "name": "generating", "data": {"update": False}})
291
+
292
+ meta_instructions = self.instructions + AGENT_BUILDER_META_PROMPT
293
+ messages = [{"role": "system", "content": meta_instructions}] + state["messages"]
294
+
295
+ model_with_structured_output = self.agent_builder_model_instance.with_structured_output(
296
+ AgentBuilderMeta
297
+ )
298
+ meta_response = await model_with_structured_output.ainvoke(messages)
299
+ meta = cast(AgentBuilderMeta, meta_response)
300
+ name, description = meta.name, meta.description
301
+
302
+ # Emit intermediary UI update with created name/description
303
+ writer(
304
+ {
305
+ "type": "custom",
306
+ id: self.meta_id,
307
+ "name": "generating",
308
+ "data": {"update": False, "name": name, "description": description},
309
+ }
310
+ )
311
+
312
+ return Command(
313
+ goto="agent_builder",
314
+ update={
315
+ "agent_builder_mode": "generating",
316
+ "agent_name": name,
317
+ "agent_description": description,
318
+ },
319
+ )
246
320
  if t == "i would like to modify the plan":
247
- prompt_ai = AIMessage(content="What would you like to change about the plan? Let me know and I'll update the plan accordingly.", additional_kwargs={"stream": "true"})
248
- return Command(update={"playbook_mode": "planning", "messages": [prompt_ai]})
321
+ prompt_ai = AIMessage(
322
+ content="What would you like to change about the plan? Let me know and I'll update the plan accordingly.",
323
+ additional_kwargs={"stream": "true"},
324
+ )
325
+ return Command(update={"agent_builder_mode": "planning", "messages": [prompt_ai]})
249
326
  if t == "let's do something else":
250
- return Command(goto="call_model", update={"playbook_mode": "inactive"})
327
+ return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
251
328
 
252
329
  # Fallback safe default
253
- return Command(goto="call_model", update={"playbook_mode": "inactive"})
254
-
255
- elif playbook_mode == "generating":
256
- generate_id = str(uuid.uuid4())
257
- writer({
258
- "type": "custom",
259
- id: generate_id,
260
- "name": "generating",
261
- "data": {"update": bool(self.playbook)}
262
- })
263
- generating_instructions = self.instructions + PLAYBOOK_GENERATING_PROMPT
330
+ return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
331
+
332
+ elif agent_builder_mode == "generating":
333
+ generating_instructions = self.instructions + AGENT_BUILDER_GENERATING_PROMPT + self.preloaded_defs
264
334
  messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
265
-
266
- model_with_structured_output = self.playbook_model_instance.with_structured_output(PlaybookCode)
267
- response = model_with_structured_output.invoke(messages)
268
- func_code = cast(PlaybookCode, response).code
335
+
336
+ model_with_structured_output = self.agent_builder_model_instance.with_structured_output(
337
+ AgentBuilderCode
338
+ )
339
+ response = await model_with_structured_output.ainvoke(messages)
340
+ func_code = cast(AgentBuilderCode, response).code
269
341
 
270
342
  # Extract function name (handle both regular and async functions)
271
343
  match = re.search(r"^\s*(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", func_code, re.MULTILINE)
272
344
  if match:
273
345
  function_name = match.group(1)
274
346
  else:
275
- function_name = "generated_playbook"
347
+ function_name = "generated_agent"
276
348
 
277
349
  # Use generated metadata if available
278
- final_name = state.get("playbook_name") or function_name
279
- final_description = state.get("playbook_description") or f"Generated playbook: {function_name}"
350
+ final_name = state.get("agent_name") or function_name
351
+ final_description = state.get("agent_description") or f"Generated agent: {function_name}"
280
352
 
281
353
  # Save or update an Agent using the helper registry
282
354
  try:
283
- if not self.playbook_registry:
284
- raise ValueError("Playbook registry is not configured")
355
+ if not self.agent_builder_registry:
356
+ raise ValueError("AgentBuilder registry is not configured")
285
357
 
286
358
  # Build instructions payload embedding the plan and function code
287
359
  instructions_payload = {
288
- "playbookPlan": state["plan"],
289
- "playbookScript": func_code,
360
+ "plan": state["plan"],
361
+ "script": func_code,
290
362
  }
291
363
 
292
364
  # Convert tool ids list to dict
293
365
  tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
294
366
 
295
- res = self.playbook_registry.upsert_agent(
367
+ res = self.agent_builder_registry.upsert_agent(
296
368
  name=final_name,
297
369
  description=final_description,
298
370
  instructions=instructions_payload,
299
371
  tools=tool_dict,
300
- visibility="private",
301
372
  )
302
- except Exception as e:
303
- raise e
373
+ except Exception:
374
+ # In case of error, add the code to the exit message content
375
+
376
+ mock_exit_tool_call = {"name": "exit_agent_builder_mode", "args": {}, "id": "exit_builder_1"}
377
+
378
+ # Create a minimal assistant message to maintain flow
379
+ mock_assistant_message = AIMessage(
380
+ content=json.dumps(response.model_dump()),
381
+ tool_calls=[mock_exit_tool_call],
382
+ additional_kwargs={
383
+ "type": "generating",
384
+ "id": "ignore",
385
+ "update": bool(self.agent),
386
+ "name": final_name.replace(" ", "_"),
387
+ "description": final_description,
388
+ },
389
+ )
390
+ mock_exit_tool_response = ToolMessage(
391
+ content=json.dumps(
392
+ f"An error occurred. Displaying the function code:\n\n{func_code}\nFinal Name: {final_name}\nDescription: {final_description}"
393
+ ),
394
+ name="exit_agent_builder_mode",
395
+ tool_call_id="exit_builder_1",
396
+ )
397
+ if self.eval_mode:
398
+ human_msg = HumanMessage(
399
+ content="Run the generated agent code and check whether it works as expected"
400
+ )
401
+ return Command(
402
+ goto="call_model",
403
+ update={
404
+ "messages": [mock_assistant_message, mock_exit_tool_response, human_msg],
405
+ "agent_builder_mode": "normal",
406
+ },
407
+ )
408
+ return Command(
409
+ update={
410
+ "messages": [mock_assistant_message, mock_exit_tool_response],
411
+ "agent_builder_mode": "normal",
412
+ }
413
+ )
304
414
 
305
- writer({
306
- "type": "custom",
307
- id: generate_id,
308
- "name": "generating",
309
- "data": {"id": str(res.id), "update": bool(self.playbook)}
310
- })
311
- mock_assistant_message = AIMessage(content=json.dumps(response.dict()), additional_kwargs={"type": "generating", "id": str(res.id), "update": bool(self.playbook)})
415
+ writer(
416
+ {
417
+ "type": "custom",
418
+ id: self.meta_id,
419
+ "name": "generating",
420
+ "data": {
421
+ "id": str(res.id),
422
+ "update": bool(self.agent),
423
+ "name": final_name,
424
+ "description": final_description,
425
+ },
426
+ }
427
+ )
428
+ mock_exit_tool_call = {"name": "exit_agent_builder_mode", "args": {}, "id": "exit_builder_1"}
429
+ mock_assistant_message = AIMessage(
430
+ content=json.dumps(response.model_dump()),
431
+ tool_calls=[mock_exit_tool_call],
432
+ additional_kwargs={
433
+ "type": "generating",
434
+ "id": str(res.id),
435
+ "update": bool(self.agent),
436
+ "name": final_name.replace(" ", "_"),
437
+ "description": final_description,
438
+ },
439
+ )
440
+
441
+ mock_exit_tool_response = ToolMessage(
442
+ content=json.dumps(
443
+ "Exited Agent Builder Mode. Enter this mode again if you need to modify the saved agent."
444
+ ),
445
+ name="exit_agent_builder_mode",
446
+ tool_call_id="exit_builder_1",
447
+ )
312
448
 
313
449
  return Command(
314
- update={"messages": [mock_assistant_message], "playbook_mode": "normal"}
450
+ update={
451
+ "messages": [mock_assistant_message, mock_exit_tool_response],
452
+ "agent_builder_mode": "normal",
453
+ }
315
454
  )
316
455
 
317
- async def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
318
- """Route to either normal mode or playbook creation"""
319
- self.exported_tools = []
320
- self.tools_config.extend(state.get("selected_tool_ids", []))
321
- self.exported_tools = await self.registry.export_tools(self.tools_config, ToolFormat.LANGCHAIN)
456
+ async def route_entry(state: CodeActState) -> Command[Literal["call_model", "agent_builder", "execute_tools"]]:
457
+ """Route to either normal mode or agent builder creation"""
458
+ pre_tools = await self.registry.export_tools(format=ToolFormat.NATIVE)
459
+
460
+ # Create the initial system prompt and tools_context in one go
322
461
  self.final_instructions, self.tools_context = create_default_prompt(
323
- self.exported_tools, self.additional_tools, self.instructions, await get_connected_apps_string(self.registry)
462
+ pre_tools,
463
+ self.additional_tools,
464
+ self.instructions,
465
+ await get_connected_apps_string(self.registry),
466
+ self.agent,
467
+ is_initial_prompt=True,
324
468
  )
325
- if state.get("playbook_mode") in ["planning", "confirming", "generating"]:
326
- return "playbook"
327
- return "call_model"
469
+ self.preloaded_defs, _ = build_tool_definitions(pre_tools)
470
+ self.preloaded_defs = "\n".join(self.preloaded_defs)
471
+ await self.registry.load_tools(state["selected_tool_ids"])
472
+ exported_tools = await self.registry.export_tools(
473
+ state["selected_tool_ids"], ToolFormat.NATIVE
474
+ ) # Get definition for only the new tools
475
+ _, loaded_tools_context = build_tool_definitions(exported_tools)
476
+ self.tools_context.update(loaded_tools_context)
477
+
478
+ if (
479
+ len(state["messages"]) == 1 and self.agent
480
+ ): # Inject the agent's script function into add_context for execution
481
+ script = self.agent.instructions.get("script")
482
+ add_context = {"functions": [script]}
483
+ return Command(goto="call_model", update={"add_context": add_context})
484
+
485
+ if state.get("agent_builder_mode") in ["planning", "confirming", "generating"]:
486
+ return Command(goto="agent_builder")
487
+ return Command(goto="call_model")
328
488
 
329
489
  agent = StateGraph(state_schema=CodeActState)
330
490
  agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
331
- agent.add_node(playbook)
491
+ agent.add_node(agent_builder)
332
492
  agent.add_node(execute_tools)
333
- agent.add_conditional_edges(START, route_entry)
334
- # agent.add_edge(START, "call_model")
493
+ agent.add_node(route_entry)
494
+ agent.add_edge(START, "route_entry")
335
495
  return agent.compile(checkpointer=self.memory)