universal-mcp-agents 0.1.19rc1__py3-none-any.whl → 0.1.24rc3__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.
- universal_mcp/agents/__init__.py +15 -16
- universal_mcp/agents/base.py +46 -35
- universal_mcp/agents/bigtool/state.py +1 -1
- universal_mcp/agents/cli.py +2 -5
- universal_mcp/agents/codeact0/__init__.py +2 -3
- universal_mcp/agents/codeact0/__main__.py +4 -7
- universal_mcp/agents/codeact0/agent.py +444 -96
- universal_mcp/agents/codeact0/langgraph_agent.py +1 -1
- universal_mcp/agents/codeact0/llm_tool.py +2 -254
- universal_mcp/agents/codeact0/prompts.py +247 -137
- universal_mcp/agents/codeact0/sandbox.py +52 -18
- universal_mcp/agents/codeact0/state.py +26 -6
- universal_mcp/agents/codeact0/tools.py +400 -74
- universal_mcp/agents/codeact0/utils.py +175 -11
- universal_mcp/agents/codeact00/__init__.py +3 -0
- universal_mcp/agents/{unified → codeact00}/__main__.py +4 -6
- universal_mcp/agents/codeact00/agent.py +578 -0
- universal_mcp/agents/codeact00/config.py +77 -0
- universal_mcp/agents/{unified → codeact00}/langgraph_agent.py +2 -2
- universal_mcp/agents/{unified → codeact00}/llm_tool.py +1 -1
- universal_mcp/agents/codeact00/prompts.py +364 -0
- universal_mcp/agents/{unified → codeact00}/sandbox.py +52 -18
- universal_mcp/agents/codeact00/state.py +66 -0
- universal_mcp/agents/codeact00/tools.py +525 -0
- universal_mcp/agents/codeact00/utils.py +678 -0
- universal_mcp/agents/codeact01/__init__.py +3 -0
- universal_mcp/agents/{codeact → codeact01}/__main__.py +4 -11
- universal_mcp/agents/codeact01/agent.py +413 -0
- universal_mcp/agents/codeact01/config.py +77 -0
- universal_mcp/agents/codeact01/langgraph_agent.py +14 -0
- universal_mcp/agents/codeact01/llm_tool.py +25 -0
- universal_mcp/agents/codeact01/prompts.py +246 -0
- universal_mcp/agents/codeact01/sandbox.py +162 -0
- universal_mcp/agents/{unified → codeact01}/state.py +26 -10
- universal_mcp/agents/codeact01/tools.py +648 -0
- universal_mcp/agents/{unified → codeact01}/utils.py +175 -11
- universal_mcp/agents/llm.py +14 -4
- universal_mcp/agents/react.py +3 -3
- universal_mcp/agents/sandbox.py +124 -69
- universal_mcp/applications/llm/app.py +76 -24
- {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/METADATA +6 -5
- universal_mcp_agents-0.1.24rc3.dist-info/RECORD +66 -0
- universal_mcp/agents/codeact/__init__.py +0 -3
- universal_mcp/agents/codeact/agent.py +0 -240
- universal_mcp/agents/codeact/models.py +0 -11
- universal_mcp/agents/codeact/prompts.py +0 -82
- universal_mcp/agents/codeact/sandbox.py +0 -85
- universal_mcp/agents/codeact/state.py +0 -11
- universal_mcp/agents/codeact/utils.py +0 -68
- universal_mcp/agents/codeact0/playbook_agent.py +0 -355
- universal_mcp/agents/unified/README.md +0 -45
- universal_mcp/agents/unified/__init__.py +0 -3
- universal_mcp/agents/unified/agent.py +0 -289
- universal_mcp/agents/unified/prompts.py +0 -192
- universal_mcp/agents/unified/tools.py +0 -188
- universal_mcp_agents-0.1.19rc1.dist-info/RECORD +0 -64
- {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/WHEEL +0 -0
|
@@ -4,11 +4,90 @@ import re
|
|
|
4
4
|
from collections.abc import Sequence
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from langchain_core.messages import BaseMessage
|
|
7
|
+
from langchain_core.messages import AIMessage, BaseMessage
|
|
8
|
+
from universal_mcp.types import ToolConfig
|
|
8
9
|
|
|
9
10
|
MAX_CHARS = 5000
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
def build_anthropic_cache_message(text: str, role: str = "system", ttl: str = "1h") -> list[dict[str, Any]]:
|
|
14
|
+
"""Build a complete Anthropic cache messages array from text.
|
|
15
|
+
|
|
16
|
+
Returns a list with a single cache message whose content is the
|
|
17
|
+
cached Anthropic content array with ephemeral cache control and TTL.
|
|
18
|
+
"""
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
"role": role,
|
|
22
|
+
"content": [
|
|
23
|
+
{
|
|
24
|
+
"type": "text",
|
|
25
|
+
"text": text,
|
|
26
|
+
"cache_control": {"type": "ephemeral", "ttl": ttl},
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def strip_thinking(messages: list[BaseMessage]):
|
|
34
|
+
"""Remove Anthropic 'thinking' segments from the most recent AIMessage in-place.
|
|
35
|
+
|
|
36
|
+
Scans from the end to find the last AIMessage, then removes thinking blocks
|
|
37
|
+
from its content. Handles both plain-string and block-array content.
|
|
38
|
+
"""
|
|
39
|
+
if not messages:
|
|
40
|
+
return messages
|
|
41
|
+
|
|
42
|
+
# Find the last AIMessage from the end
|
|
43
|
+
last_ai_index = None
|
|
44
|
+
for i in range(len(messages) - 1, -1, -1):
|
|
45
|
+
if isinstance(messages[i], AIMessage):
|
|
46
|
+
last_ai_index = i
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
if last_ai_index is None:
|
|
50
|
+
return messages
|
|
51
|
+
|
|
52
|
+
ai_msg = messages[last_ai_index]
|
|
53
|
+
content = ai_msg.content
|
|
54
|
+
|
|
55
|
+
# If it's already plain text, nothing to strip
|
|
56
|
+
if isinstance(content, str):
|
|
57
|
+
return messages
|
|
58
|
+
|
|
59
|
+
# If Anthropic-style content blocks
|
|
60
|
+
if isinstance(content, list):
|
|
61
|
+
filtered_output: list[object] = []
|
|
62
|
+
removed_any = False
|
|
63
|
+
for b in content:
|
|
64
|
+
is_thinking = False
|
|
65
|
+
if isinstance(b, dict):
|
|
66
|
+
t = b.get("type")
|
|
67
|
+
if t == "thinking":
|
|
68
|
+
is_thinking = True
|
|
69
|
+
elif "thinking" in b and isinstance(b["thinking"], str):
|
|
70
|
+
is_thinking = True
|
|
71
|
+
|
|
72
|
+
if is_thinking:
|
|
73
|
+
removed_any = True
|
|
74
|
+
continue
|
|
75
|
+
filtered_output.append(b)
|
|
76
|
+
|
|
77
|
+
if removed_any:
|
|
78
|
+
ai_msg.content = filtered_output
|
|
79
|
+
messages[last_ai_index] = ai_msg
|
|
80
|
+
|
|
81
|
+
return messages
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def add_tools(tool_config: ToolConfig, tools_to_add: ToolConfig):
|
|
85
|
+
for app_id, new_tools in tools_to_add.items():
|
|
86
|
+
all_tools = tool_config.get(app_id, []) + new_tools
|
|
87
|
+
tool_config[app_id] = list(set(all_tools))
|
|
88
|
+
return tool_config
|
|
89
|
+
|
|
90
|
+
|
|
12
91
|
def light_copy(data):
|
|
13
92
|
"""
|
|
14
93
|
Deep copy a dict[str, any] or Sequence[any] with string truncation.
|
|
@@ -325,31 +404,45 @@ def inject_context(
|
|
|
325
404
|
return namespace
|
|
326
405
|
|
|
327
406
|
|
|
328
|
-
def schema_to_signature(schema: dict, func_name="my_function") -> str:
|
|
407
|
+
def schema_to_signature(schema: dict, func_name: str = "my_function") -> str:
|
|
408
|
+
"""
|
|
409
|
+
Convert a JSON schema into a Python-style function signature string.
|
|
410
|
+
Handles fields with `type`, `anyOf`, defaults, and missing metadata safely.
|
|
411
|
+
"""
|
|
329
412
|
type_map = {
|
|
330
413
|
"integer": "int",
|
|
331
414
|
"string": "str",
|
|
332
415
|
"boolean": "bool",
|
|
333
416
|
"null": "None",
|
|
417
|
+
"number": "float",
|
|
418
|
+
"array": "list",
|
|
419
|
+
"object": "dict",
|
|
334
420
|
}
|
|
335
421
|
|
|
336
422
|
params = []
|
|
337
423
|
for name, meta in schema.items():
|
|
338
|
-
|
|
339
|
-
|
|
424
|
+
if not isinstance(meta, dict):
|
|
425
|
+
typ = "Any"
|
|
426
|
+
elif "type" in meta:
|
|
340
427
|
typ = type_map.get(meta["type"], "Any")
|
|
341
428
|
elif "anyOf" in meta:
|
|
342
|
-
types = [
|
|
343
|
-
|
|
429
|
+
types = []
|
|
430
|
+
for t in meta["anyOf"]:
|
|
431
|
+
if not isinstance(t, dict):
|
|
432
|
+
continue
|
|
433
|
+
t_type = t.get("type")
|
|
434
|
+
types.append(type_map.get(t_type, "Any") if t_type else "Any")
|
|
435
|
+
typ = " | ".join(sorted(set(types))) if types else "Any"
|
|
344
436
|
else:
|
|
345
437
|
typ = "Any"
|
|
346
438
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
439
|
+
# Handle defaults gracefully
|
|
440
|
+
default = meta.get("default")
|
|
441
|
+
if default is None:
|
|
442
|
+
params.append(f"{name}: {typ}")
|
|
443
|
+
else:
|
|
444
|
+
params.append(f"{name}: {typ} = {repr(default)}")
|
|
351
445
|
|
|
352
|
-
# join into signature
|
|
353
446
|
param_str = ",\n ".join(params)
|
|
354
447
|
return f"def {func_name}(\n {param_str},\n):"
|
|
355
448
|
|
|
@@ -386,3 +479,74 @@ def smart_truncate(
|
|
|
386
479
|
truncated = truncated[:summary_threshold] + "\n... [output truncated to fit context] ..."
|
|
387
480
|
|
|
388
481
|
return truncated
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
async def get_connected_apps_string(registry) -> str:
|
|
485
|
+
"""Get a formatted string of connected applications from the registry."""
|
|
486
|
+
if not registry:
|
|
487
|
+
return ""
|
|
488
|
+
|
|
489
|
+
try:
|
|
490
|
+
# Get connected apps from registry
|
|
491
|
+
connections = await registry.list_connected_apps()
|
|
492
|
+
if not connections:
|
|
493
|
+
return "No applications are currently connected."
|
|
494
|
+
|
|
495
|
+
# Extract app names from connections
|
|
496
|
+
connected_app_ids = {connection["app_id"] for connection in connections}
|
|
497
|
+
|
|
498
|
+
# Format the apps list
|
|
499
|
+
apps_list = []
|
|
500
|
+
for app_id in connected_app_ids:
|
|
501
|
+
apps_list.append(f"- {app_id}")
|
|
502
|
+
|
|
503
|
+
return "\n".join(apps_list)
|
|
504
|
+
except Exception:
|
|
505
|
+
return "Unable to retrieve connected applications."
|
|
506
|
+
|
|
507
|
+
def extract_plan_parameters(plan_steps: list[str]) -> list[dict[str, Any]]:
|
|
508
|
+
"""
|
|
509
|
+
Extracts parameters from plan steps and formats them into a list of OpenAPI-like parameter objects.
|
|
510
|
+
|
|
511
|
+
Parses parameters enclosed in backticks, identifying their name, if they are required, and any default values.
|
|
512
|
+
e.g., `variable` -> {"name": "variable", "required": True}
|
|
513
|
+
e.g., `variable(default = 'value')` -> {"name": "variable", "required": False, "default": "value"}
|
|
514
|
+
"""
|
|
515
|
+
parameters_map: dict[str, Any] = {}
|
|
516
|
+
# Regex to find anything inside backticks
|
|
517
|
+
outer_pattern = re.compile(r"`([^`]+)`")
|
|
518
|
+
# Regex to parse parameters with default values
|
|
519
|
+
inner_pattern = re.compile(r"^\s*(\w+)\s*\(\s*default\s*=\s*(.+)\s*\)\s*$")
|
|
520
|
+
|
|
521
|
+
for step in plan_steps:
|
|
522
|
+
matches = outer_pattern.findall(step)
|
|
523
|
+
for match in matches:
|
|
524
|
+
param_str = match.strip()
|
|
525
|
+
inner_match = inner_pattern.match(param_str)
|
|
526
|
+
|
|
527
|
+
if inner_match:
|
|
528
|
+
# Parameter with a default value
|
|
529
|
+
name, default_val_str = inner_match.groups()
|
|
530
|
+
default_value: Any
|
|
531
|
+
try:
|
|
532
|
+
# Safely evaluate the default value (e.g., 'string', 123, True)
|
|
533
|
+
default_value = ast.literal_eval(default_val_str)
|
|
534
|
+
except (ValueError, SyntaxError):
|
|
535
|
+
# If it's not a valid literal, treat it as a string
|
|
536
|
+
default_value = default_val_str
|
|
537
|
+
parameters_map[name] = {"required": False, "default": default_value}
|
|
538
|
+
else:
|
|
539
|
+
# Required parameter (no default value)
|
|
540
|
+
name = param_str
|
|
541
|
+
# Only set as required if it hasn't been defined with a default already
|
|
542
|
+
if name not in parameters_map:
|
|
543
|
+
parameters_map[name] = {"required": True}
|
|
544
|
+
|
|
545
|
+
# Convert the map to the final list format
|
|
546
|
+
final_parameters = []
|
|
547
|
+
for name, details in sorted(parameters_map.items()):
|
|
548
|
+
param_obj = {"name": name}
|
|
549
|
+
param_obj.update(details)
|
|
550
|
+
final_parameters.append(param_obj)
|
|
551
|
+
|
|
552
|
+
return final_parameters
|
|
@@ -4,23 +4,21 @@ from langgraph.checkpoint.memory import MemorySaver
|
|
|
4
4
|
from rich import print
|
|
5
5
|
from universal_mcp.agentr.registry import AgentrRegistry
|
|
6
6
|
|
|
7
|
-
from universal_mcp.agents.
|
|
7
|
+
from universal_mcp.agents.codeact00.agent import CodeActPlaybookAgent
|
|
8
8
|
from universal_mcp.agents.utils import messages_to_list
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
async def main():
|
|
12
12
|
memory = MemorySaver()
|
|
13
|
-
|
|
14
|
-
agent = UnifiedAgent(
|
|
13
|
+
agent = CodeActPlaybookAgent(
|
|
15
14
|
name="CodeAct Agent",
|
|
16
15
|
instructions="Be very concise in your answers.",
|
|
17
|
-
model="
|
|
18
|
-
tools=default_tools,
|
|
16
|
+
model="azure/gpt-4.1",
|
|
19
17
|
registry=AgentrRegistry(),
|
|
20
18
|
memory=memory,
|
|
21
19
|
)
|
|
22
20
|
print("Starting agent...")
|
|
23
|
-
result = await agent.invoke(user_input="
|
|
21
|
+
result = await agent.invoke(user_input="Check my google calendar and show my todays agenda")
|
|
24
22
|
print(messages_to_list(result["messages"]))
|
|
25
23
|
|
|
26
24
|
|