universal-mcp-agents 0.1.23rc7__tar.gz → 0.1.24__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. universal_mcp_agents-0.1.24/.gemini/settings.json +7 -0
  2. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/PKG-INFO +3 -3
  3. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/pyproject.toml +3 -3
  4. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/__init__.py +1 -0
  5. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/base.py +7 -7
  6. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/cli.py +2 -2
  7. universal_mcp_agents-0.1.24/src/universal_mcp/agents/codeact0/agent.py +373 -0
  8. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/prompts.py +121 -54
  9. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/sandbox.py +49 -12
  10. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/state.py +8 -0
  11. universal_mcp_agents-0.1.24/src/universal_mcp/agents/codeact0/tools.py +564 -0
  12. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/utils.py +255 -2
  13. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/llm.py +14 -4
  14. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/sandbox.py +29 -7
  15. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/llm/app.py +81 -27
  16. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/uv.lock +97 -93
  17. universal_mcp_agents-0.1.23rc7/src/universal_mcp/agents/codeact0/agent.py +0 -465
  18. universal_mcp_agents-0.1.23rc7/src/universal_mcp/agents/codeact0/tools.py +0 -322
  19. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/evals.yml +0 -0
  20. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/lint.yml +0 -0
  21. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/release-please.yml +0 -0
  22. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.github/workflows/tests.yml +0 -0
  23. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.gitignore +0 -0
  24. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/.pre-commit-config.yaml +0 -0
  25. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/GEMINI.md +0 -0
  26. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/PROMPTS.md +0 -0
  27. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/README.md +0 -0
  28. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/bump_and_release.sh +0 -0
  29. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/__init__.py +0 -0
  30. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/dataset.py +0 -0
  31. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/datasets/exact.jsonl +0 -0
  32. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/datasets/tasks.jsonl +0 -0
  33. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/datasets/test.jsonl +0 -0
  34. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/evaluators.py +0 -0
  35. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/prompts.py +0 -0
  36. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/run.py +0 -0
  37. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/evals/utils.py +0 -0
  38. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/tests/test_agents.py +0 -0
  39. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/tests/test_sandbox.py +0 -0
  40. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/__init__.py +0 -0
  41. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/__main__.py +0 -0
  42. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/agent.py +0 -0
  43. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/context.py +0 -0
  44. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/graph.py +0 -0
  45. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/prompts.py +0 -0
  46. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/state.py +0 -0
  47. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/bigtool/tools.py +0 -0
  48. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/__main__.py +0 -0
  49. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/builder.py +0 -0
  50. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/helper.py +0 -0
  51. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/prompts.py +0 -0
  52. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/builder/state.py +0 -0
  53. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/__init__.py +0 -0
  54. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/__main__.py +0 -0
  55. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/config.py +0 -0
  56. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/langgraph_agent.py +0 -0
  57. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/codeact0/llm_tool.py +0 -0
  58. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/hil.py +0 -0
  59. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/react.py +0 -0
  60. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/shared/__main__.py +0 -0
  61. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/shared/prompts.py +0 -0
  62. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/shared/tool_node.py +0 -0
  63. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/simple.py +0 -0
  64. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/agents/utils.py +0 -0
  65. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/filesystem/__init__.py +0 -0
  66. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/filesystem/app.py +0 -0
  67. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/llm/__init__.py +0 -0
  68. {universal_mcp_agents-0.1.23rc7 → universal_mcp_agents-0.1.24}/src/universal_mcp/applications/ui/app.py +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "langchain": {
4
+ "httpUrl": "https://docs.langchain.com/mcp"
5
+ }
6
+ }
7
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp-agents
3
- Version: 0.1.23rc7
3
+ Version: 0.1.24
4
4
  Summary: Add your description here
5
5
  Project-URL: Homepage, https://github.com/universal-mcp/applications
6
6
  Project-URL: Repository, https://github.com/universal-mcp/applications
@@ -12,8 +12,8 @@ Requires-Dist: langchain-anthropic>=0.3.19
12
12
  Requires-Dist: langchain-google-genai>=2.1.10
13
13
  Requires-Dist: langchain-openai>=0.3.32
14
14
  Requires-Dist: langgraph>=0.6.6
15
- Requires-Dist: universal-mcp-applications>=0.1.25
16
- Requires-Dist: universal-mcp>=0.1.24rc27
15
+ Requires-Dist: universal-mcp-applications>=0.1.30
16
+ Requires-Dist: universal-mcp>=0.1.24rc29
17
17
  Provides-Extra: dev
18
18
  Requires-Dist: pre-commit; extra == 'dev'
19
19
  Requires-Dist: ruff; extra == 'dev'
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "universal-mcp-agents"
9
- version = "0.1.23-rc7"
9
+ version = "0.1.24"
10
10
  description = "Add your description here"
11
11
  readme = "README.md"
12
12
  authors = [
@@ -19,8 +19,8 @@ dependencies = [
19
19
  "langchain-google-genai>=2.1.10",
20
20
  "langchain-openai>=0.3.32",
21
21
  "langgraph>=0.6.6",
22
- "universal-mcp>=0.1.24rc27",
23
- "universal-mcp-applications>=0.1.25",
22
+ "universal-mcp>=0.1.24rc29",
23
+ "universal-mcp-applications>=0.1.30",
24
24
  ]
25
25
 
26
26
  [project.license]
@@ -11,6 +11,7 @@ from universal_mcp.agents.simple import SimpleAgent
11
11
  def get_agent(
12
12
  agent_name: Literal["react", "simple", "builder", "bigtool", "codeact-repl"],
13
13
  ):
14
+ print("agent_name", agent_name)
14
15
  if agent_name == "react":
15
16
  return ReactAgent
16
17
  elif agent_name == "simple":
@@ -1,12 +1,13 @@
1
- from typing import Any, cast
2
- from uuid import uuid4
3
1
  import asyncio
2
+ from typing import cast
3
+ from uuid import uuid4
4
4
 
5
5
  from langchain_core.messages import AIMessageChunk
6
6
  from langgraph.checkpoint.base import BaseCheckpointSaver
7
7
  from langgraph.graph import StateGraph
8
8
  from langgraph.types import Command
9
9
  from universal_mcp.logger import logger
10
+
10
11
  from .utils import RichCLI
11
12
 
12
13
 
@@ -66,11 +67,10 @@ class BaseAgent:
66
67
  ):
67
68
  if event == "messages" and isinstance(meta, (tuple, list)) and len(meta) == 2: # noqa: PLR2004
68
69
  payload, meta_dict = meta
69
- is_agent_builder = (
70
- isinstance(meta_dict, dict) and meta_dict.get("langgraph_node") == "agent_builder"
71
- )
72
- additional_kwargs = getattr(payload, "additional_kwargs", {}) or {}
73
- if is_agent_builder and not additional_kwargs.get("stream"):
70
+ metadata = getattr(payload, "metadata", {}) or {}
71
+ if metadata and "quiet" in metadata.get("tags"):
72
+ continue
73
+ if meta_dict.get("tags") and "quiet" in meta_dict.get("tags"):
74
74
  continue
75
75
  if isinstance(payload, AIMessageChunk):
76
76
  last_ai_chunk = payload
@@ -18,14 +18,14 @@ app = Typer()
18
18
  mcp client run --config client_config.json
19
19
  """,
20
20
  )
21
- def run(name: str = "react"):
21
+ def run(name: str = "codeact-repl"):
22
22
  """Run the agent CLI"""
23
23
 
24
24
  setup_logger(log_file=None, level="ERROR")
25
25
  client = AgentrClient()
26
26
  params = {
27
27
  "instructions": "You are a helpful assistant",
28
- "model": "azure/gpt-4.1",
28
+ "model": "anthropic:claude-4-sonnet-20250514",
29
29
  "registry": AgentrRegistry(client=client),
30
30
  "memory": MemorySaver(),
31
31
  }
@@ -0,0 +1,373 @@
1
+ import copy
2
+ import json
3
+ import re
4
+ import uuid
5
+ from typing import Literal, cast
6
+
7
+ from langchain_anthropic import ChatAnthropic
8
+ from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
9
+ from langgraph.checkpoint.base import BaseCheckpointSaver
10
+ from langgraph.graph import START, StateGraph
11
+ from langgraph.types import Command, RetryPolicy, StreamWriter
12
+ from universal_mcp.tools.registry import ToolRegistry
13
+ from universal_mcp.types import ToolFormat
14
+
15
+ from universal_mcp.agents.base import BaseAgent
16
+ from universal_mcp.agents.codeact0.llm_tool import smart_print
17
+ from universal_mcp.agents.codeact0.prompts import (
18
+ AGENT_BUILDER_GENERATING_PROMPT,
19
+ AGENT_BUILDER_META_PROMPT,
20
+ AGENT_BUILDER_PLANNING_PROMPT,
21
+ AGENT_BUILDER_PLAN_PATCH_PROMPT,
22
+ AGENT_BUILDER_CODE_PATCH_PROMPT,
23
+ build_tool_definitions,
24
+ create_default_prompt,
25
+ )
26
+ from universal_mcp.agents.codeact0.sandbox import eval_unsafe, execute_ipython_cell, handle_execute_ipython_cell
27
+ from universal_mcp.agents.codeact0.state import AgentBuilderCode, AgentBuilderMeta, AgentBuilderPlan, AgentBuilderPatch, CodeActState
28
+ from universal_mcp.agents.codeact0.tools import (
29
+ create_agent_builder_tools,
30
+ create_meta_tools,
31
+ )
32
+ from universal_mcp.agents.codeact0.utils import build_anthropic_cache_message, extract_plan_parameters, get_connected_apps_string, strip_thinking
33
+ from universal_mcp.agents.codeact0.utils import apply_patch_or_use_proposed
34
+ from universal_mcp.agents.llm import load_chat_model
35
+ from universal_mcp.agents.utils import convert_tool_ids_to_dict, filter_retry_on
36
+
37
+
38
+ class CodeActPlaybookAgent(BaseAgent):
39
+ def __init__(
40
+ self,
41
+ name: str,
42
+ instructions: str,
43
+ model: str,
44
+ memory: BaseCheckpointSaver | None = None,
45
+ registry: ToolRegistry | None = None,
46
+ agent_builder_registry: object | None = None,
47
+ sandbox_timeout: int = 20,
48
+ **kwargs,
49
+ ):
50
+ super().__init__(
51
+ name=name,
52
+ instructions=instructions,
53
+ model=model,
54
+ memory=memory,
55
+ **kwargs,
56
+ )
57
+ self.model_instance = load_chat_model(model)
58
+ self.agent_builder_model_instance = load_chat_model("anthropic:claude-sonnet-4-5-20250929", thinking=False, disable_streaming = True, tags=("quiet",))
59
+ self.registry = registry
60
+ self.agent_builder_registry = agent_builder_registry
61
+ self.agent = agent_builder_registry.get_agent() if agent_builder_registry else None
62
+
63
+ self.tools_config = self.agent.tools if self.agent else {}
64
+ self.eval_fn = eval_unsafe
65
+ self.sandbox_timeout = sandbox_timeout
66
+ self.default_tools_config = {
67
+ "llm": ["generate_text", "classify_data", "extract_data", "call_llm"],
68
+ }
69
+ self.final_instructions = ""
70
+ self.tools_context = {}
71
+ self.eval_mode = kwargs.get("eval_mode", False)
72
+
73
+ async def _build_graph(self): # noqa: PLR0915
74
+ """Build the graph for the CodeAct Playbook Agent."""
75
+ meta_tools = create_meta_tools(self.registry)
76
+ agent_builder_tools = create_agent_builder_tools()
77
+ self.additional_tools = [
78
+ smart_print,
79
+ meta_tools["web_search"],
80
+ meta_tools["read_file"],
81
+ meta_tools["save_file"],
82
+ meta_tools["upload_file"],
83
+ ]
84
+
85
+ if self.tools_config:
86
+ await self.registry.load_tools(self.tools_config) # Load provided tools
87
+ if self.default_tools_config:
88
+ await self.registry.load_tools(self.default_tools_config) # Load default tools
89
+
90
+ async def call_model(state: CodeActState) -> Command[Literal["execute_tools"]]:
91
+ """This node now only ever binds the four meta-tools to the LLM."""
92
+ messages = build_anthropic_cache_message(self.final_instructions) + state["messages"]
93
+ agent_facing_tools = [
94
+ execute_ipython_cell,
95
+ agent_builder_tools["plan_agent"],
96
+ agent_builder_tools["code_and_save_agent"],
97
+ meta_tools["search_functions"],
98
+ meta_tools["load_functions"],
99
+ ]
100
+
101
+ if isinstance(self.model_instance, ChatAnthropic):
102
+ model_with_tools = self.model_instance.bind_tools(
103
+ tools=agent_facing_tools,
104
+ tool_choice="auto",
105
+ cache_control={"type": "ephemeral", "ttl": "1h"},
106
+ )
107
+ if isinstance(messages[-1].content, str):
108
+ pass
109
+ else:
110
+ last = copy.deepcopy(messages[-1])
111
+ last.content[-1]["cache_control"] = {"type": "ephemeral", "ttl": "5m"}
112
+ messages[-1] = last
113
+ else:
114
+ model_with_tools = self.model_instance.bind_tools(
115
+ tools=agent_facing_tools,
116
+ tool_choice="auto",
117
+ )
118
+ response = cast(AIMessage, await model_with_tools.ainvoke(messages))
119
+ if response.tool_calls:
120
+ return Command(goto="execute_tools", update={"messages": [response]})
121
+ else:
122
+ return Command(update={"messages": [response], "model_with_tools": model_with_tools})
123
+
124
+ async def execute_tools(state: CodeActState, writer: StreamWriter) -> Command[Literal["call_model"]]:
125
+ """Execute tool calls"""
126
+ last_message = state["messages"][-1]
127
+ tool_calls = last_message.tool_calls if isinstance(last_message, AIMessage) else []
128
+
129
+ tool_messages = []
130
+ new_tool_ids = []
131
+ tool_result = ""
132
+ ask_user = False
133
+ ai_msg = None
134
+ effective_previous_add_context = state.get("add_context", {})
135
+ effective_existing_context = state.get("context", {})
136
+ plan = state.get("plan", None)
137
+ agent_name = state.get("agent_name", None)
138
+ agent_description = state.get("agent_description", None)
139
+ # logging.info(f"Initial new_tool_ids_for_context: {new_tool_ids_for_context}")
140
+
141
+ for tool_call in tool_calls:
142
+ tool_name = tool_call["name"]
143
+ tool_args = tool_call["args"]
144
+ try:
145
+ if tool_name == "execute_ipython_cell":
146
+ code = tool_call["args"]["snippet"]
147
+ output, new_context, new_add_context = await handle_execute_ipython_cell(
148
+ code,
149
+ self.tools_context, # Uses the dynamically updated context
150
+ self.eval_fn,
151
+ effective_previous_add_context,
152
+ effective_existing_context,
153
+ )
154
+ effective_existing_context = new_context
155
+ effective_previous_add_context = new_add_context
156
+ tool_result = output
157
+ elif tool_name == "load_functions":
158
+ # The tool now does all the work of validation and formatting.
159
+ tool_result, new_context_for_sandbox, valid_tools, unconnected_links = await meta_tools[
160
+ "load_functions"
161
+ ].ainvoke(tool_args)
162
+ # We still need to update the sandbox context for `execute_ipython_cell`
163
+ new_tool_ids.extend(valid_tools)
164
+ if new_tool_ids:
165
+ self.tools_context.update(new_context_for_sandbox)
166
+ if unconnected_links:
167
+ ask_user = True
168
+ 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} "
169
+
170
+ elif tool_name == "search_functions":
171
+ tool_result = await meta_tools["search_functions"].ainvoke(tool_args)
172
+
173
+ elif tool_name == "plan_agent":
174
+ plan, tool_result = await self._create_or_update_plan(state=state, writer=writer, plan=plan)
175
+ ask_user = True
176
+
177
+ elif tool_name == "code_and_save_agent":
178
+ tool_result, effective_previous_add_context, agent_name, agent_description = await self._build_or_patch_code(
179
+ state=state,
180
+ writer=writer,
181
+ plan=plan,
182
+ agent_name=agent_name,
183
+ agent_description=agent_description,
184
+ effective_previous_add_context=effective_previous_add_context,
185
+ )
186
+ else:
187
+ raise Exception(
188
+ f"Unexpected tool call: {tool_call['name']}. "
189
+ "tool calls must be one of 'execute_ipython_cell', 'load_functions', 'search_functions', 'plan_agent', or 'code_and_save_agent'. For using functions, call them in code using 'execute_ipython_cell'."
190
+ )
191
+ except Exception as e:
192
+ tool_result = str(e)
193
+
194
+ tool_message = ToolMessage(
195
+ content=json.dumps(tool_result),
196
+ name=tool_call["name"],
197
+ tool_call_id=tool_call["id"],
198
+ )
199
+ tool_messages.append(tool_message)
200
+
201
+ if ask_user:
202
+ if ai_msg:
203
+ tool_messages.append(AIMessage(content=ai_msg))
204
+ return Command(
205
+ update={
206
+ "messages": tool_messages,
207
+ "selected_tool_ids": new_tool_ids,
208
+ "context": effective_existing_context,
209
+ "add_context": effective_previous_add_context,
210
+ "agent_name": agent_name,
211
+ "agent_description": agent_description,
212
+ "plan": plan,
213
+ }
214
+ )
215
+
216
+ return Command(
217
+ goto="call_model",
218
+ update={
219
+ "messages": tool_messages,
220
+ "selected_tool_ids": new_tool_ids,
221
+ "context": effective_existing_context,
222
+ "add_context": effective_previous_add_context,
223
+ "agent_name": agent_name,
224
+ "agent_description": agent_description,
225
+ "plan": plan,
226
+ },
227
+ )
228
+
229
+ async def route_entry(state: CodeActState) -> Command[Literal["call_model", "execute_tools"]]:
230
+ """Route to either normal mode or agent builder creation"""
231
+ pre_tools = await self.registry.export_tools(format=ToolFormat.NATIVE)
232
+
233
+ # Create the initial system prompt and tools_context in one go
234
+ self.final_instructions, self.tools_context = create_default_prompt(
235
+ pre_tools,
236
+ self.additional_tools,
237
+ self.instructions,
238
+ await get_connected_apps_string(self.registry),
239
+ self.agent,
240
+ is_initial_prompt=True,
241
+ )
242
+ self.preloaded_defs, _ = build_tool_definitions(pre_tools)
243
+ self.preloaded_defs = "\n".join(self.preloaded_defs)
244
+ await self.registry.load_tools(state["selected_tool_ids"])
245
+ exported_tools = await self.registry.export_tools(
246
+ state["selected_tool_ids"], ToolFormat.NATIVE
247
+ ) # Get definition for only the new tools
248
+ _, loaded_tools_context = build_tool_definitions(exported_tools)
249
+ self.tools_context.update(loaded_tools_context)
250
+
251
+ if (
252
+ len(state["messages"]) == 1 and self.agent
253
+ ): # Inject the agent's script function into add_context for execution
254
+ script = self.agent.instructions.get("script")
255
+ add_context = {"functions": [script]}
256
+ return Command(goto="call_model", update={"add_context": add_context})
257
+ return Command(goto="call_model")
258
+
259
+ agent = StateGraph(state_schema=CodeActState)
260
+ agent.add_node(call_model, retry_policy=RetryPolicy(max_attempts=3, retry_on=filter_retry_on))
261
+ agent.add_node(execute_tools)
262
+ agent.add_node(route_entry)
263
+ agent.add_edge(START, "route_entry")
264
+ return agent.compile(checkpointer=self.memory)
265
+
266
+ async def _create_or_update_plan(self, state: "CodeActState", writer: StreamWriter, plan: list[str] | None):
267
+ """Sub-agent helper: create or patch-update the agent plan and emit UI updates.
268
+ Returns: (plan: list[str], tool_result: str)
269
+ """
270
+ plan_id = str(uuid.uuid4())
271
+ writer({"type": "custom", id: plan_id, "name": "planning", "data": {"update": bool(plan)}})
272
+
273
+ # Determine existing plan (prefer persisted agent's plan) and base messages
274
+ existing_plan_steps = (self.agent.instructions.get("plan") if self.agent and getattr(self.agent, "instructions", None) else None) or plan
275
+ base = strip_thinking(state["messages"])
276
+ def with_sys(text: str):
277
+ return [{"role": "system", "content": text}] + base
278
+
279
+ if existing_plan_steps:
280
+ current = "\n".join(map(str, existing_plan_steps or []))
281
+ sys_prompt = self.instructions + "\n" + AGENT_BUILDER_PLAN_PATCH_PROMPT + self.preloaded_defs
282
+ msgs = with_sys(sys_prompt) + [HumanMessage(content=f"Current plan (one step per line):\n{current}")]
283
+ patch_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderPatch)
284
+ proposed = cast(AgentBuilderPatch, await patch_model.ainvoke(msgs)).patch
285
+ updated = apply_patch_or_use_proposed(current, proposed)
286
+ plan = [line for line in updated.splitlines() if line.strip()]
287
+ else:
288
+ sys_prompt = self.instructions + AGENT_BUILDER_PLANNING_PROMPT + self.preloaded_defs
289
+ plan_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderPlan)
290
+ plan = cast(AgentBuilderPlan, await plan_model.ainvoke(with_sys(sys_prompt))).steps
291
+
292
+ writer({"type": "custom", id: plan_id, "name": "planning", "data": {"plan": plan}})
293
+ tool_result = {"plan": plan, "update": bool(plan), "message": f"Successfully generated the agent plan."}
294
+ return plan, tool_result
295
+
296
+ async def _build_or_patch_code(
297
+ self,
298
+ state: "CodeActState",
299
+ writer: StreamWriter,
300
+ plan: list[str] | None,
301
+ agent_name: str | None,
302
+ agent_description: str | None,
303
+ effective_previous_add_context: dict,
304
+ ):
305
+ """Sub-agent helper: generate new code or patch existing code, save, and emit UI updates.
306
+ Returns: (tool_result: str, effective_previous_add_context: dict, agent_name: str | None, agent_description: str | None)
307
+ """
308
+ generation_id = str(uuid.uuid4())
309
+ writer({"type": "custom", "id": generation_id, "name": "generating", "data": {"update": bool(self.agent)}})
310
+
311
+ base = strip_thinking(state["messages"])
312
+ def with_sys(text: str):
313
+ return [{"role": "system", "content": text}] + base
314
+ plan_text = "\n".join(map(str, plan)) if plan else None
315
+ existing_code = self.agent.instructions.get("script") if self.agent and getattr(self.agent, "instructions", None) else None
316
+
317
+ if self.agent:
318
+ agent_name = getattr(self.agent, "name", None)
319
+ agent_description = getattr(self.agent, "description", None)
320
+
321
+ if not agent_name or not agent_description:
322
+ meta_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderMeta)
323
+ meta = cast(AgentBuilderMeta, await meta_model.ainvoke(with_sys(self.instructions + AGENT_BUILDER_META_PROMPT)))
324
+ agent_name, agent_description = meta.name, meta.description
325
+
326
+ writer({"type": "custom", "id": generation_id, "name": "generating", "data": {"update": bool(self.agent), "name": agent_name, "description": agent_description}})
327
+
328
+ if existing_code:
329
+ generating_instructions = self.instructions + AGENT_BUILDER_CODE_PATCH_PROMPT + self.preloaded_defs
330
+ messages = with_sys(generating_instructions)
331
+ if plan_text:
332
+ messages.append(HumanMessage(content=f"Confirmed plan (one step per line):\n{plan_text}"))
333
+ messages.append(HumanMessage(content=f"Current code to update:\n```python\n{existing_code}\n```"))
334
+ patch_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderPatch)
335
+ proposed = cast(AgentBuilderPatch, await patch_model.ainvoke(messages)).patch
336
+ python_code = apply_patch_or_use_proposed(existing_code, proposed)
337
+ else:
338
+ code_model = self.agent_builder_model_instance.with_structured_output(AgentBuilderCode)
339
+ python_code = cast(AgentBuilderCode, await code_model.ainvoke(with_sys(self.instructions + AGENT_BUILDER_GENERATING_PROMPT + self.preloaded_defs))).code
340
+
341
+ try:
342
+ if not self.agent_builder_registry:
343
+ raise ValueError("AgentBuilder registry is not configured")
344
+
345
+ plan_params = extract_plan_parameters(state["plan"])
346
+ instructions_payload = {
347
+ "plan": state["plan"],
348
+ "script": python_code,
349
+ "params": plan_params,
350
+ }
351
+ tool_dict = convert_tool_ids_to_dict(state["selected_tool_ids"])
352
+ res = self.agent_builder_registry.upsert_agent(
353
+ name=agent_name,
354
+ description=agent_description,
355
+ instructions=instructions_payload,
356
+ tools=tool_dict,
357
+ )
358
+ writer({"type": "custom", "id": generation_id, "name": "generating", "data": {"id": str(res.id), "update": bool(self.agent), "name": agent_name, "description": agent_description}})
359
+ tool_result = {
360
+ "id": str(res.id),
361
+ "update": bool(self.agent),
362
+ "name": agent_name,
363
+ "description": agent_description,
364
+ "message": f"Successfully saved the agent code and plan.",
365
+ }
366
+ except Exception:
367
+ tool_result = f"Displaying the final saved code:\n\n{python_code}\nFinal Name: {agent_name}\nDescription: {agent_description}"
368
+
369
+ if "functions" not in effective_previous_add_context:
370
+ effective_previous_add_context["functions"] = []
371
+ effective_previous_add_context["functions"].append(python_code)
372
+
373
+ return tool_result, effective_previous_add_context, agent_name, agent_description