universal-mcp-agents 0.1.21__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,31 +1,33 @@
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,
18
+ AGENT_BUILDER_GENERATING_PROMPT,
19
+ AGENT_BUILDER_META_PROMPT,
20
+ AGENT_BUILDER_PLANNING_PROMPT,
21
+ build_tool_definitions,
19
22
  create_default_prompt,
20
23
  )
21
24
  from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
22
- from universal_mcp.agents.codeact0.state import CodeActState, PlaybookCode, PlaybookPlan
25
+ from universal_mcp.agents.codeact0.state import AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan, CodeActState
23
26
  from universal_mcp.agents.codeact0.tools import (
24
27
  create_meta_tools,
25
- enter_playbook_mode,
26
- get_valid_tools,
28
+ enter_agent_builder_mode,
27
29
  )
28
- 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
29
31
  from universal_mcp.agents.llm import load_chat_model
30
32
  from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on, get_message_text
31
33
 
@@ -37,9 +39,8 @@ class CodeActPlaybookAgent(BaseAgent):
37
39
  instructions: str,
38
40
  model: str,
39
41
  memory: BaseCheckpointSaver | None = None,
40
- tools: ToolConfig | None = None,
41
42
  registry: ToolRegistry | None = None,
42
- playbook_registry: object | None = None,
43
+ agent_builder_registry: object | None = None,
43
44
  sandbox_timeout: int = 20,
44
45
  **kwargs,
45
46
  ):
@@ -51,83 +52,107 @@ class CodeActPlaybookAgent(BaseAgent):
51
52
  **kwargs,
52
53
  )
53
54
  self.model_instance = load_chat_model(model)
54
- self.playbook_model_instance = load_chat_model("azure/gpt-4.1")
55
- self.tools_config = tools or {}
55
+ self.agent_builder_model_instance = load_chat_model("anthropic:claude-sonnet-4-5-20250929", thinking=False)
56
56
  self.registry = registry
57
- self.playbook_registry = playbook_registry
58
- 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 {}
59
61
  self.eval_fn = eval_unsafe
60
62
  self.sandbox_timeout = sandbox_timeout
61
- self.default_tools = {
63
+ self.default_tools_config = {
62
64
  "llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
63
- "markitdown": ["convert_to_markdown"],
64
65
  }
65
- add_tools(self.tools_config, self.default_tools)
66
+ self.final_instructions = ""
67
+ self.tools_context = {}
68
+ self.eval_mode = kwargs.get("eval_mode", False)
66
69
 
67
- async def _build_graph(self):
70
+ async def _build_graph(self): # noqa: PLR0915
71
+ """Build the graph for the CodeAct Playbook Agent."""
68
72
  meta_tools = create_meta_tools(self.registry)
69
- additional_tools = [smart_print, meta_tools["web_search"]]
70
73
  self.additional_tools = [
71
- 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"],
72
79
  ]
80
+
73
81
  if self.tools_config:
74
- # Convert dict format to list format if needed
75
- if isinstance(self.tools_config, dict):
76
- self.tools_config = [
77
- f"{provider}__{tool}" for provider, tools in self.tools_config.items() for tool in tools
78
- ]
79
- if not self.registry:
80
- 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
81
85
 
82
86
  async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
83
- messages = [{"role": "system", "content": self.final_instructions}] + state["messages"]
84
-
85
- # Run the model and potentially loop for reflection
86
- model_with_tools = self.model_instance.bind_tools(
87
- tools=[
88
- execute_ipython_cell,
89
- enter_playbook_mode,
90
- meta_tools["search_functions"],
91
- meta_tools["load_functions"],
92
- ],
93
- tool_choice="auto",
94
- )
95
- 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))
96
115
  if response.tool_calls:
97
116
  return Command(goto="execute_tools", update={"messages": [response]})
98
117
  else:
99
118
  return Command(update={"messages": [response], "model_with_tools": model_with_tools})
100
119
 
101
- async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "playbook"]]:
120
+ async def execute_tools(state: CodeActState) -> Command[Literal["call_model", "agent_builder"]]:
102
121
  """Execute tool calls"""
103
122
  last_message = state["messages"][-1]
104
123
  tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
105
124
 
106
125
  tool_messages = []
107
126
  new_tool_ids = []
127
+ tool_result = ""
108
128
  ask_user = False
109
129
  ai_msg = ""
110
- tool_result = ""
111
130
  effective_previous_add_context = state.get("add_context", {})
112
131
  effective_existing_context = state.get("context", {})
132
+ # logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
113
133
 
114
134
  for tool_call in tool_calls:
135
+ tool_name = tool_call["name"]
136
+ tool_args = tool_call["args"]
115
137
  try:
116
- if tool_call["name"] == "enter_playbook_mode":
138
+ if tool_name == "enter_agent_builder_mode":
117
139
  tool_message = ToolMessage(
118
- content=json.dumps("Entered Playbook Mode."),
140
+ content=json.dumps("Entered Agent Builder Mode."),
119
141
  name=tool_call["name"],
120
142
  tool_call_id=tool_call["id"],
121
143
  )
122
144
  return Command(
123
- goto="playbook",
124
- 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
125
150
  )
126
- elif tool_call["name"] == "execute_ipython_cell":
151
+ elif tool_name == "execute_ipython_cell":
127
152
  code = tool_call["args"]["snippet"]
128
153
  output, new_context, new_add_context = await handle_execute_ipython_cell(
129
154
  code,
130
- self.tools_context,
155
+ self.tools_context, # Uses the dynamically updated context
131
156
  self.eval_fn,
132
157
  effective_previous_add_context,
133
158
  effective_existing_context,
@@ -135,23 +160,25 @@ class CodeActPlaybookAgent(BaseAgent):
135
160
  effective_existing_context = new_context
136
161
  effective_previous_add_context = new_add_context
137
162
  tool_result = output
138
- elif tool_call["name"] == "load_functions": # Handle load_functions separately
139
- valid_tools, unconnected_links = await get_valid_tools(
140
- tool_ids=tool_call["args"]["tool_ids"], registry=self.registry
141
- )
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`
142
169
  new_tool_ids.extend(valid_tools)
143
- # Create tool message response
144
- tool_result = f"Successfully loaded {len(valid_tools)} tools: {valid_tools}"
145
- links = "\n".join(unconnected_links)
146
- if links:
170
+ if new_tool_ids:
171
+ self.tools_context.update(new_context_for_sandbox)
172
+ if unconnected_links:
147
173
  ask_user = True
148
- ai_msg = f"Please login to the following app(s) using the following links and let me know in order to proceed:\n {links} "
149
- elif tool_call["name"] == "search_functions":
150
- 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)
151
178
  else:
152
179
  raise Exception(
153
180
  f"Unexpected tool call: {tool_call['name']}. "
154
- "tool calls must be one of 'enter_playbook_mode', 'execute_ipython_cell', 'load_functions', or 'search_functions'"
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'."
155
182
  )
156
183
  except Exception as e:
157
184
  tool_result = str(e)
@@ -163,12 +190,6 @@ class CodeActPlaybookAgent(BaseAgent):
163
190
  )
164
191
  tool_messages.append(tool_message)
165
192
 
166
- if new_tool_ids:
167
- self.tools_config.extend(new_tool_ids)
168
- self.exported_tools = await self.registry.export_tools(new_tool_ids, ToolFormat.LANGCHAIN)
169
- self.final_instructions, self.tools_context = create_default_prompt(
170
- self.exported_tools, self.additional_tools, self.instructions, playbook=self.playbook
171
- )
172
193
  if ask_user:
173
194
  tool_messages.append(AIMessage(content=ai_msg))
174
195
  return Command(
@@ -190,27 +211,50 @@ class CodeActPlaybookAgent(BaseAgent):
190
211
  },
191
212
  )
192
213
 
193
- def playbook(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
194
- playbook_mode = state.get("playbook_mode")
195
- 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":
196
217
  plan_id = str(uuid.uuid4())
197
- writer({
198
- "type": "custom",
199
- id: plan_id,
200
- "name": "planning",
201
- "data": {"update": bool(self.playbook)}
202
- })
203
- planning_instructions = self.instructions + PLAYBOOK_PLANNING_PROMPT
204
- messages = [{"role": "system", "content": planning_instructions}] + state["messages"]
205
-
206
- model_with_structured_output = self.playbook_model_instance.with_structured_output(PlaybookPlan)
207
- response = model_with_structured_output.invoke(messages)
208
- plan = cast(PlaybookPlan, response)
209
-
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
+
210
228
  writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan.steps}})
211
- 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
+ )
212
248
 
213
- 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":
214
258
  # Deterministic routing based on three exact button inputs from UI
215
259
  user_text = ""
216
260
  for m in reversed(state["messages"]):
@@ -224,90 +268,228 @@ class CodeActPlaybookAgent(BaseAgent):
224
268
 
225
269
  t = user_text.lower()
226
270
  if t == "yes, this is great":
227
- return Command(goto="playbook", update={"playbook_mode": "generating"})
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
+ )
228
320
  if t == "i would like to modify the plan":
229
- 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"})
230
- 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]})
231
326
  if t == "let's do something else":
232
- return Command(goto="call_model", update={"playbook_mode": "inactive"})
327
+ return Command(goto="call_model", update={"agent_builder_mode": "inactive"})
233
328
 
234
329
  # Fallback safe default
235
- return Command(goto="call_model", update={"playbook_mode": "inactive"})
236
-
237
- elif playbook_mode == "generating":
238
- generate_id = str(uuid.uuid4())
239
- writer({
240
- "type": "custom",
241
- id: generate_id,
242
- "name": "generating",
243
- "data": {"update": bool(self.playbook)}
244
- })
245
- 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
246
334
  messages = [{"role": "system", "content": generating_instructions}] + state["messages"]
247
-
248
- model_with_structured_output = self.playbook_model_instance.with_structured_output(PlaybookCode)
249
- response = model_with_structured_output.invoke(messages)
250
- 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
251
341
 
252
342
  # Extract function name (handle both regular and async functions)
253
343
  match = re.search(r"^\s*(?:async\s+)?def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", func_code, re.MULTILINE)
254
344
  if match:
255
345
  function_name = match.group(1)
256
346
  else:
257
- function_name = "generated_playbook"
347
+ function_name = "generated_agent"
348
+
349
+ # Use generated metadata if available
350
+ final_name = state.get("agent_name") or function_name
351
+ final_description = state.get("agent_description") or f"Generated agent: {function_name}"
258
352
 
259
353
  # Save or update an Agent using the helper registry
260
354
  try:
261
- if not self.playbook_registry:
262
- raise ValueError("Playbook registry is not configured")
355
+ if not self.agent_builder_registry:
356
+ raise ValueError("AgentBuilder registry is not configured")
263
357
 
264
358
  # Build instructions payload embedding the plan and function code
265
359
  instructions_payload = {
266
- "playbookPlan": state["plan"],
267
- "playbookScript": func_code,
360
+ "plan": state["plan"],
361
+ "script": func_code,
268
362
  }
269
363
 
270
364
  # Convert tool ids list to dict
271
365
  tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
272
366
 
273
- res = self.playbook_registry.upsert_agent(
274
- name=function_name,
275
- description=f"Generated playbook: {function_name}",
367
+ res = self.agent_builder_registry.upsert_agent(
368
+ name=final_name,
369
+ description=final_description,
276
370
  instructions=instructions_payload,
277
371
  tools=tool_dict,
278
- visibility="private",
279
372
  )
280
- except Exception as e:
281
- 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
+ )
282
414
 
283
- writer({
284
- "type": "custom",
285
- id: generate_id,
286
- "name": "generating",
287
- "data": {"id": str(res.id), "update": bool(self.playbook)}
288
- })
289
- 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
+ )
290
448
 
291
449
  return Command(
292
- 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
+ }
293
454
  )
294
455
 
295
- async def route_entry(state: CodeActState) -> Literal["call_model", "playbook"]:
296
- """Route to either normal mode or playbook creation"""
297
- self.exported_tools = []
298
- self.tools_config.extend(state.get("selected_tool_ids", []))
299
- 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
300
461
  self.final_instructions, self.tools_context = create_default_prompt(
301
- self.exported_tools, self.additional_tools, self.instructions, playbook=self.playbook
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,
302
468
  )
303
- if state.get("playbook_mode") in ["planning", "confirming", "generating"]:
304
- return "playbook"
305
- 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")
306
488
 
307
489
  agent = StateGraph(state_schema=CodeActState)
308
490
  agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
309
- agent.add_node(playbook)
491
+ agent.add_node(agent_builder)
310
492
  agent.add_node(execute_tools)
311
- agent.add_conditional_edges(START, route_entry)
312
- # agent.add_edge(START, "call_model")
493
+ agent.add_node(route_entry)
494
+ agent.add_edge(START, "route_entry")
313
495
  return agent.compile(checkpointer=self.memory)