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.
Files changed (57) hide show
  1. universal_mcp/agents/__init__.py +15 -16
  2. universal_mcp/agents/base.py +46 -35
  3. universal_mcp/agents/bigtool/state.py +1 -1
  4. universal_mcp/agents/cli.py +2 -5
  5. universal_mcp/agents/codeact0/__init__.py +2 -3
  6. universal_mcp/agents/codeact0/__main__.py +4 -7
  7. universal_mcp/agents/codeact0/agent.py +444 -96
  8. universal_mcp/agents/codeact0/langgraph_agent.py +1 -1
  9. universal_mcp/agents/codeact0/llm_tool.py +2 -254
  10. universal_mcp/agents/codeact0/prompts.py +247 -137
  11. universal_mcp/agents/codeact0/sandbox.py +52 -18
  12. universal_mcp/agents/codeact0/state.py +26 -6
  13. universal_mcp/agents/codeact0/tools.py +400 -74
  14. universal_mcp/agents/codeact0/utils.py +175 -11
  15. universal_mcp/agents/codeact00/__init__.py +3 -0
  16. universal_mcp/agents/{unified → codeact00}/__main__.py +4 -6
  17. universal_mcp/agents/codeact00/agent.py +578 -0
  18. universal_mcp/agents/codeact00/config.py +77 -0
  19. universal_mcp/agents/{unified → codeact00}/langgraph_agent.py +2 -2
  20. universal_mcp/agents/{unified → codeact00}/llm_tool.py +1 -1
  21. universal_mcp/agents/codeact00/prompts.py +364 -0
  22. universal_mcp/agents/{unified → codeact00}/sandbox.py +52 -18
  23. universal_mcp/agents/codeact00/state.py +66 -0
  24. universal_mcp/agents/codeact00/tools.py +525 -0
  25. universal_mcp/agents/codeact00/utils.py +678 -0
  26. universal_mcp/agents/codeact01/__init__.py +3 -0
  27. universal_mcp/agents/{codeact → codeact01}/__main__.py +4 -11
  28. universal_mcp/agents/codeact01/agent.py +413 -0
  29. universal_mcp/agents/codeact01/config.py +77 -0
  30. universal_mcp/agents/codeact01/langgraph_agent.py +14 -0
  31. universal_mcp/agents/codeact01/llm_tool.py +25 -0
  32. universal_mcp/agents/codeact01/prompts.py +246 -0
  33. universal_mcp/agents/codeact01/sandbox.py +162 -0
  34. universal_mcp/agents/{unified → codeact01}/state.py +26 -10
  35. universal_mcp/agents/codeact01/tools.py +648 -0
  36. universal_mcp/agents/{unified → codeact01}/utils.py +175 -11
  37. universal_mcp/agents/llm.py +14 -4
  38. universal_mcp/agents/react.py +3 -3
  39. universal_mcp/agents/sandbox.py +124 -69
  40. universal_mcp/applications/llm/app.py +76 -24
  41. {universal_mcp_agents-0.1.19rc1.dist-info → universal_mcp_agents-0.1.24rc3.dist-info}/METADATA +6 -5
  42. universal_mcp_agents-0.1.24rc3.dist-info/RECORD +66 -0
  43. universal_mcp/agents/codeact/__init__.py +0 -3
  44. universal_mcp/agents/codeact/agent.py +0 -240
  45. universal_mcp/agents/codeact/models.py +0 -11
  46. universal_mcp/agents/codeact/prompts.py +0 -82
  47. universal_mcp/agents/codeact/sandbox.py +0 -85
  48. universal_mcp/agents/codeact/state.py +0 -11
  49. universal_mcp/agents/codeact/utils.py +0 -68
  50. universal_mcp/agents/codeact0/playbook_agent.py +0 -355
  51. universal_mcp/agents/unified/README.md +0 -45
  52. universal_mcp/agents/unified/__init__.py +0 -3
  53. universal_mcp/agents/unified/agent.py +0 -289
  54. universal_mcp/agents/unified/prompts.py +0 -192
  55. universal_mcp/agents/unified/tools.py +0 -188
  56. universal_mcp_agents-0.1.19rc1.dist-info/RECORD +0 -64
  57. {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
- # figure out type
339
- if "type" in meta:
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 = [type_map.get(t["type"], "Any") for t in meta["anyOf"]]
343
- typ = " | ".join(set(types))
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
- default = meta.get("default", None)
348
- default_repr = repr(default)
349
-
350
- params.append(f"{name}: {typ} = {default_repr}")
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
@@ -0,0 +1,3 @@
1
+ from .agent import CodeActPlaybookAgent
2
+
3
+ __all__ = ["CodeActPlaybookAgent"]
@@ -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.unified import UnifiedAgent
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
- default_tools = {"llm": ["generate_text", "classify_data", "extract_data", "call_llm"]}
14
- agent = UnifiedAgent(
13
+ agent = CodeActPlaybookAgent(
15
14
  name="CodeAct Agent",
16
15
  instructions="Be very concise in your answers.",
17
- model="anthropic:claude-4-sonnet-20250514",
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="find the 80th fibonnaci number")
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